fix bugs
This commit is contained in:
@@ -18,7 +18,8 @@
|
|||||||
"Bash(\"D:\\Program Files\\mysql-8.0.32-winx64\\bin\\mysql.exe\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"SHOW TABLES LIKE ''%order%'';\")",
|
"Bash(\"D:\\Program Files\\mysql-8.0.32-winx64\\bin\\mysql.exe\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"SHOW TABLES LIKE ''%order%'';\")",
|
||||||
"Bash(\"D:\\Program Files\\mysql-8.0.32-winx64\\bin\\mysql.exe\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"SHOW TABLES;\")",
|
"Bash(\"D:\\Program Files\\mysql-8.0.32-winx64\\bin\\mysql.exe\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"SHOW TABLES;\")",
|
||||||
"Bash(\"D:\\Program Files\\mysql-8.0.32-winx64\\bin\\mysql.exe\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"DESCRIBE athlete;\")",
|
"Bash(\"D:\\Program Files\\mysql-8.0.32-winx64\\bin\\mysql.exe\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"DESCRIBE athlete;\")",
|
||||||
"Bash(tree:*)"
|
"Bash(tree:*)",
|
||||||
|
"Bash(find:*)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|||||||
103
api/schedule.js
Normal file
103
api/schedule.js
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
/**
|
||||||
|
* 赛程编排相关API接口
|
||||||
|
*/
|
||||||
|
|
||||||
|
import request from '@/utils/request.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取赛程编排结果
|
||||||
|
* @param {Number} competitionId - 赛事ID
|
||||||
|
*/
|
||||||
|
export function getScheduleResult(competitionId) {
|
||||||
|
return request.get('/martial/schedule/result', {
|
||||||
|
params: { competitionId }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 触发自动编排
|
||||||
|
* @param {Number} competitionId - 赛事ID
|
||||||
|
*/
|
||||||
|
export function triggerAutoArrange(competitionId) {
|
||||||
|
return request.post('/martial/schedule/auto-arrange', {
|
||||||
|
competitionId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存编排草稿
|
||||||
|
* @param {Object} data - 编排草稿数据
|
||||||
|
* @param {Number} data.competitionId - 赛事ID
|
||||||
|
* @param {Boolean} data.isDraft - 是否为草稿
|
||||||
|
* @param {Array} data.competitionGroups - 竞赛分组数据
|
||||||
|
*/
|
||||||
|
export function saveDraftSchedule(data) {
|
||||||
|
return request.post('/martial/schedule/save-draft', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存并锁定赛程编排
|
||||||
|
* @param {Number} competitionId - 赛事ID
|
||||||
|
*/
|
||||||
|
export function saveAndLockSchedule(competitionId) {
|
||||||
|
return request.post('/martial/schedule/save-and-lock', {
|
||||||
|
competitionId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移动赛程分组到指定场地和时间段
|
||||||
|
* @param {Object} data - 移动请求数据
|
||||||
|
* @param {Number} data.groupId - 分组ID
|
||||||
|
* @param {Number} data.targetVenueId - 目标场地ID
|
||||||
|
* @param {Number} data.targetTimeSlotIndex - 目标时间段索引
|
||||||
|
*/
|
||||||
|
export function moveScheduleGroup(data) {
|
||||||
|
return request.post('/martial/schedule/move-group', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取调度数据
|
||||||
|
* @param {Object} params - 查询参数
|
||||||
|
* @param {Number} params.competitionId - 赛事ID
|
||||||
|
* @param {Number} params.venueId - 场地ID
|
||||||
|
* @param {Number} params.timeSlotIndex - 时间段索引
|
||||||
|
*/
|
||||||
|
export function getDispatchData(params) {
|
||||||
|
return request.get('/martial/schedule/dispatch-data', {
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调整出场顺序
|
||||||
|
* @param {Object} data - 调整请求数据
|
||||||
|
* @param {Number} data.detailId - 编排明细ID
|
||||||
|
* @param {Number} data.participantId - 参赛者记录ID
|
||||||
|
* @param {String} data.action - 调整动作(move_up/move_down/swap)
|
||||||
|
* @param {Number} data.targetOrder - 目标顺序(交换时使用)
|
||||||
|
*/
|
||||||
|
export function adjustOrder(data) {
|
||||||
|
return request.post('/martial/schedule/adjust-order', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量保存调度
|
||||||
|
* @param {Object} data - 保存调度数据
|
||||||
|
* @param {Number} data.competitionId - 赛事ID
|
||||||
|
* @param {Array} data.adjustments - 调整列表
|
||||||
|
*/
|
||||||
|
export function saveDispatch(data) {
|
||||||
|
return request.post('/martial/schedule/save-dispatch', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getScheduleResult,
|
||||||
|
triggerAutoArrange,
|
||||||
|
saveDraftSchedule,
|
||||||
|
saveAndLockSchedule,
|
||||||
|
moveScheduleGroup,
|
||||||
|
getDispatchData,
|
||||||
|
adjustOrder,
|
||||||
|
saveDispatch
|
||||||
|
}
|
||||||
92
api/user.js
92
api/user.js
@@ -3,8 +3,90 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import request from '@/utils/request.js'
|
import request from '@/utils/request.js'
|
||||||
|
import md5 from '@/utils/md5.js'
|
||||||
|
import { base64Encode } from '@/utils/base64.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户登录
|
||||||
|
* 使用password模式(无需验证码)
|
||||||
|
*/
|
||||||
|
export function login(data) {
|
||||||
|
// 构建URL参数
|
||||||
|
const params = {
|
||||||
|
tenantId: '000000',
|
||||||
|
username: data.username,
|
||||||
|
password: md5(data.password),
|
||||||
|
grant_type: 'password', // 使用password模式
|
||||||
|
scope: 'all',
|
||||||
|
type: 'account'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为URL查询字符串
|
||||||
|
const queryString = Object.keys(params)
|
||||||
|
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
|
||||||
|
.join('&')
|
||||||
|
|
||||||
|
// 使用saber3客户端凭证
|
||||||
|
const basicAuth = 'Basic ' + base64Encode('saber3:saber3_secret')
|
||||||
|
|
||||||
|
return request.post(`/blade-auth/oauth/token?${queryString}`, null, {
|
||||||
|
header: {
|
||||||
|
'Authorization': basicAuth,
|
||||||
|
'Tenant-Id': '000000'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户注册
|
||||||
|
*/
|
||||||
|
export function register(data) {
|
||||||
|
return request.post('/blade-system/user/register', {
|
||||||
|
tenantId: '000000',
|
||||||
|
userType: 2, // 2-app
|
||||||
|
account: data.account,
|
||||||
|
password: md5(data.password),
|
||||||
|
phone: data.phone,
|
||||||
|
realName: data.realName,
|
||||||
|
sex: data.sex,
|
||||||
|
code: data.code
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取验证码
|
||||||
|
*/
|
||||||
|
export function getCaptcha(phone) {
|
||||||
|
return request.post('/blade-auth/captcha/send', {
|
||||||
|
phone: phone
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新Token
|
||||||
|
*/
|
||||||
|
export function refreshToken(refreshToken) {
|
||||||
|
return request.post('/blade-auth/oauth/token', {
|
||||||
|
tenantId: '000000',
|
||||||
|
refresh_token: refreshToken,
|
||||||
|
grant_type: 'refresh_token'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退出登录
|
||||||
|
*/
|
||||||
|
export function logout() {
|
||||||
|
return request.post('/blade-auth/logout')
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
login,
|
||||||
|
register,
|
||||||
|
getCaptcha,
|
||||||
|
refreshToken,
|
||||||
|
logout,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户信息
|
* 获取用户信息
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
@@ -15,11 +97,15 @@ export default {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改密码
|
* 修改密码
|
||||||
* @param {Object} data { oldPassword, newPassword, confirmPassword }
|
* @param {Object} data { oldPassword, newPassword, newPassword1 }
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
updatePassword(data) {
|
updatePassword(data) {
|
||||||
return request.post('/blade-system/user/update-password', data)
|
return request.post('/blade-system/user/update-password', {
|
||||||
|
oldPassword: md5(data.oldPassword),
|
||||||
|
newPassword: md5(data.newPassword),
|
||||||
|
newPassword1: md5(data.newPassword1)
|
||||||
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,6 +114,6 @@ export default {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
updateUserInfo(data) {
|
updateUserInfo(data) {
|
||||||
return request.post('/blade-system/user/update-info', data)
|
return request.post('/blade-system/user/update', data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,9 @@
|
|||||||
|
|
||||||
// 开发环境配置
|
// 开发环境配置
|
||||||
const development = {
|
const development = {
|
||||||
// 使用代理,请求会被转发到 vue.config.js 中配置的目标地址
|
// 使用代理路径(vue.config.js会将/api代理到http://localhost:8123)
|
||||||
baseURL: 'http://localhost:8123',
|
baseURL: '/api',
|
||||||
timeout: 30000,
|
timeout: 30000
|
||||||
// 如果需要代理,可以配置
|
|
||||||
baseURL: '/api'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 测试环境配置
|
// 测试环境配置
|
||||||
|
|||||||
14
pages.json
14
pages.json
@@ -1,5 +1,19 @@
|
|||||||
{
|
{
|
||||||
"pages": [
|
"pages": [
|
||||||
|
{
|
||||||
|
"path": "pages/login/login",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "登录",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/register/register",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "注册",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/home/home",
|
"path": "pages/home/home",
|
||||||
"style": {
|
"style": {
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ export default {
|
|||||||
await userAPI.updatePassword({
|
await userAPI.updatePassword({
|
||||||
oldPassword: this.formData.oldPassword,
|
oldPassword: this.formData.oldPassword,
|
||||||
newPassword: this.formData.newPassword,
|
newPassword: this.formData.newPassword,
|
||||||
confirmPassword: this.formData.confirmPassword
|
newPassword1: this.formData.confirmPassword
|
||||||
})
|
})
|
||||||
|
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
|
|||||||
501
pages/login/login.vue
Normal file
501
pages/login/login.vue
Normal file
@@ -0,0 +1,501 @@
|
|||||||
|
<template>
|
||||||
|
<view class="login-page">
|
||||||
|
<!-- 背景装饰 -->
|
||||||
|
<view class="bg-decoration">
|
||||||
|
<view class="circle circle-1"></view>
|
||||||
|
<view class="circle circle-2"></view>
|
||||||
|
<view class="circle circle-3"></view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 顶部Logo区域 -->
|
||||||
|
<view class="login-header">
|
||||||
|
<view class="logo-container">
|
||||||
|
<image class="logo" src="/static/logo.png" mode="aspectFit"></image>
|
||||||
|
</view>
|
||||||
|
<text class="title">武术赛事管理系统</text>
|
||||||
|
<text class="subtitle">MARTIAL ARTS COMPETITION</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 登录表单 -->
|
||||||
|
<view class="login-form">
|
||||||
|
<view class="form-title">账号登录</view>
|
||||||
|
|
||||||
|
<view class="form-item">
|
||||||
|
<view class="input-wrapper">
|
||||||
|
<view class="input-icon">📱</view>
|
||||||
|
<input
|
||||||
|
class="form-input"
|
||||||
|
v-model="form.username"
|
||||||
|
placeholder="请输入手机号或用户名"
|
||||||
|
placeholder-class="placeholder"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="form-item">
|
||||||
|
<view class="input-wrapper">
|
||||||
|
<view class="input-icon">🔒</view>
|
||||||
|
<input
|
||||||
|
class="form-input"
|
||||||
|
v-model="form.password"
|
||||||
|
:password="!showPassword"
|
||||||
|
placeholder="请输入密码"
|
||||||
|
placeholder-class="placeholder"
|
||||||
|
/>
|
||||||
|
<view class="eye-icon" @click="showPassword = !showPassword">
|
||||||
|
<text>{{ showPassword ? '👁️' : '👁️🗨️' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="form-options">
|
||||||
|
<label class="checkbox-label" @click="rememberPassword = !rememberPassword">
|
||||||
|
<view :class="['checkbox', rememberPassword ? 'checked' : '']">
|
||||||
|
<text v-if="rememberPassword" class="check-icon">✓</text>
|
||||||
|
</view>
|
||||||
|
<text class="checkbox-text">记住密码</text>
|
||||||
|
</label>
|
||||||
|
<text class="forgot-password" @click="handleForgotPassword">忘记密码?</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<button class="login-btn" @click="handleLogin" :loading="loading" :disabled="loading">
|
||||||
|
<text class="btn-text">{{ loading ? '登录中...' : '立即登录' }}</text>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<view class="register-link">
|
||||||
|
还没有账号?<text class="link-text" @click="goToRegister">立即注册</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 底部装饰 -->
|
||||||
|
<view class="footer-decoration">
|
||||||
|
<view class="wave"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import userAPI from '@/api/user.js'
|
||||||
|
import { setToken, setRefreshToken, setUserInfo } from '@/utils/auth.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
form: {
|
||||||
|
username: '',
|
||||||
|
password: ''
|
||||||
|
},
|
||||||
|
showPassword: false,
|
||||||
|
rememberPassword: false,
|
||||||
|
loading: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
// 读取记住的密码
|
||||||
|
const savedUsername = uni.getStorageSync('saved_username')
|
||||||
|
const savedPassword = uni.getStorageSync('saved_password')
|
||||||
|
if (savedUsername && savedPassword) {
|
||||||
|
this.form.username = savedUsername
|
||||||
|
this.form.password = savedPassword
|
||||||
|
this.rememberPassword = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async handleLogin() {
|
||||||
|
// 表单验证
|
||||||
|
if (!this.form.username) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请输入手机号或用户名',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.form.password) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请输入密码',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 调用登录接口
|
||||||
|
const res = await userAPI.login(this.form)
|
||||||
|
|
||||||
|
// 保存Token
|
||||||
|
setToken(res.access_token)
|
||||||
|
setRefreshToken(res.refresh_token)
|
||||||
|
|
||||||
|
// 保存用户信息
|
||||||
|
const userInfo = {
|
||||||
|
userId: res.user_id,
|
||||||
|
account: res.account,
|
||||||
|
userName: res.user_name,
|
||||||
|
avatar: res.avatar,
|
||||||
|
tenantId: res.tenant_id
|
||||||
|
}
|
||||||
|
setUserInfo(userInfo)
|
||||||
|
|
||||||
|
// 记住密码
|
||||||
|
if (this.rememberPassword) {
|
||||||
|
uni.setStorageSync('saved_username', this.form.username)
|
||||||
|
uni.setStorageSync('saved_password', this.form.password)
|
||||||
|
} else {
|
||||||
|
uni.removeStorageSync('saved_username')
|
||||||
|
uni.removeStorageSync('saved_password')
|
||||||
|
}
|
||||||
|
|
||||||
|
uni.showToast({
|
||||||
|
title: '登录成功',
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 跳转到首页
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.switchTab({
|
||||||
|
url: '/pages/home/home'
|
||||||
|
})
|
||||||
|
}, 1500)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('登录失败:', error)
|
||||||
|
uni.showToast({
|
||||||
|
title: error.message || '登录失败',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleForgotPassword() {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请联系管理员重置密码',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
goToRegister() {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages/register/register'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.login-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: linear-gradient(135deg, #C93639 0%, #A82E31 50%, #8B1F22 100%);
|
||||||
|
padding: 0;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 背景装饰圆圈 */
|
||||||
|
.bg-decoration {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
animation: float 6s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle-1 {
|
||||||
|
width: 300rpx;
|
||||||
|
height: 300rpx;
|
||||||
|
top: -100rpx;
|
||||||
|
right: -50rpx;
|
||||||
|
animation-delay: 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle-2 {
|
||||||
|
width: 200rpx;
|
||||||
|
height: 200rpx;
|
||||||
|
bottom: 100rpx;
|
||||||
|
left: -50rpx;
|
||||||
|
animation-delay: 2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle-3 {
|
||||||
|
width: 150rpx;
|
||||||
|
height: 150rpx;
|
||||||
|
top: 40%;
|
||||||
|
right: 50rpx;
|
||||||
|
animation-delay: 4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes float {
|
||||||
|
0%, 100% {
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translateY(-30rpx) scale(1.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 顶部Logo区域 */
|
||||||
|
.login-header {
|
||||||
|
text-align: center;
|
||||||
|
padding: 120rpx 60rpx 80rpx;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-container {
|
||||||
|
width: 180rpx;
|
||||||
|
height: 180rpx;
|
||||||
|
margin: 0 auto 40rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
backdrop-filter: blur(10rpx);
|
||||||
|
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
width: 140rpx;
|
||||||
|
height: 140rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 48rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #fff;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
letter-spacing: 4rpx;
|
||||||
|
text-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: rgba(255, 255, 255, 0.85);
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 登录表单 */
|
||||||
|
.login-form {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 40rpx 40rpx 0 0;
|
||||||
|
padding: 60rpx 50rpx 80rpx;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
box-shadow: 0 -8rpx 32rpx rgba(0, 0, 0, 0.08);
|
||||||
|
min-height: calc(100vh - 480rpx);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-title {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 50rpx;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item {
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background: #F7F8FA;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
border: 2rpx solid transparent;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-wrapper:focus-within {
|
||||||
|
background: #fff;
|
||||||
|
border-color: #C93639;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(201, 54, 57, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-icon {
|
||||||
|
width: 80rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 36rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
flex: 1;
|
||||||
|
height: 96rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
padding-right: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder {
|
||||||
|
color: #999;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eye-icon {
|
||||||
|
width: 80rpx;
|
||||||
|
height: 96rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 36rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eye-icon:active {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表单选项 */
|
||||||
|
.form-options {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin: 40rpx 0 50rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
width: 36rpx;
|
||||||
|
height: 36rpx;
|
||||||
|
border: 2rpx solid #ddd;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-right: 12rpx;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox.checked {
|
||||||
|
background: #C93639;
|
||||||
|
border-color: #C93639;
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-icon {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-text {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.forgot-password {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #C93639;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.forgot-password:active {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 登录按钮 */
|
||||||
|
.login-btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 96rpx;
|
||||||
|
background: linear-gradient(135deg, #C93639 0%, #A82E31 100%);
|
||||||
|
border-radius: 16rpx;
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 8rpx 24rpx rgba(201, 54, 57, 0.3);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-btn::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||||||
|
transition: left 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-btn:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(201, 54, 57, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-btn:active::before {
|
||||||
|
left: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-text {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #fff;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 注册链接 */
|
||||||
|
.register-link {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 50rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-text {
|
||||||
|
color: #C93639;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-left: 8rpx;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-text:active {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 底部装饰 */
|
||||||
|
.footer-decoration {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100rpx;
|
||||||
|
z-index: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(180deg, transparent 0%, rgba(255, 255, 255, 0.05) 100%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -49,6 +49,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import userAPI from '@/api/user.js'
|
import userAPI from '@/api/user.js'
|
||||||
|
import { getUserInfo as getStoredUserInfo, isLogin, clearAuth } from '@/utils/auth.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
@@ -62,13 +63,40 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
onLoad() {
|
onLoad() {
|
||||||
this.loadUserInfo()
|
this.checkLoginAndLoadInfo()
|
||||||
},
|
},
|
||||||
onShow() {
|
onShow() {
|
||||||
// 每次显示时刷新用户信息
|
// 每次显示时刷新用户信息
|
||||||
this.loadUserInfo()
|
this.checkLoginAndLoadInfo()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
/**
|
||||||
|
* 检查登录状态并加载用户信息
|
||||||
|
*/
|
||||||
|
checkLoginAndLoadInfo() {
|
||||||
|
if (!isLogin()) {
|
||||||
|
// 未登录,跳转到登录页
|
||||||
|
uni.reLaunch({
|
||||||
|
url: '/pages/login/login'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先从本地存储加载用户信息
|
||||||
|
const storedInfo = getStoredUserInfo()
|
||||||
|
if (storedInfo) {
|
||||||
|
this.userInfo = {
|
||||||
|
name: storedInfo.userName || storedInfo.account || '用户',
|
||||||
|
id: storedInfo.userId || '',
|
||||||
|
phone: storedInfo.phone || '',
|
||||||
|
username: storedInfo.account || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 然后从服务器刷新用户信息
|
||||||
|
this.loadUserInfo()
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载用户信息
|
* 加载用户信息
|
||||||
*/
|
*/
|
||||||
@@ -84,7 +112,13 @@ export default {
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('加载用户信息失败:', err)
|
console.error('加载用户信息失败:', err)
|
||||||
// 失败时不显示错误提示,使用默认值
|
// 如果是401错误,说明token过期,跳转到登录页
|
||||||
|
if (err.statusCode === 401) {
|
||||||
|
clearAuth()
|
||||||
|
uni.reLaunch({
|
||||||
|
url: '/pages/login/login'
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -109,30 +143,39 @@ export default {
|
|||||||
icon: 'none'
|
icon: 'none'
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
handleLogout() {
|
async handleLogout() {
|
||||||
uni.showModal({
|
const confirmRes = await new Promise((resolve) => {
|
||||||
title: '提示',
|
uni.showModal({
|
||||||
content: '确定要退出登录吗?',
|
title: '提示',
|
||||||
success: (res) => {
|
content: '确定要退出登录吗?',
|
||||||
if (res.confirm) {
|
success: (res) => resolve(res)
|
||||||
// 清除本地存储的token
|
})
|
||||||
uni.removeStorageSync('token')
|
})
|
||||||
uni.removeStorageSync('userInfo')
|
|
||||||
|
|
||||||
uni.showToast({
|
if (confirmRes.confirm) {
|
||||||
title: '退出成功',
|
try {
|
||||||
icon: 'success'
|
// 调用退出登录接口
|
||||||
})
|
await userAPI.logout()
|
||||||
|
} catch (err) {
|
||||||
// 延迟跳转到登录页
|
console.error('退出登录接口调用失败:', err)
|
||||||
setTimeout(() => {
|
// 即使接口失败也继续清除本地数据
|
||||||
uni.reLaunch({
|
|
||||||
url: '/pages/login/login'
|
|
||||||
})
|
|
||||||
}, 1500)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
// 清除本地认证信息
|
||||||
|
clearAuth()
|
||||||
|
|
||||||
|
uni.showToast({
|
||||||
|
title: '退出成功',
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 延迟跳转到登录页
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.reLaunch({
|
||||||
|
url: '/pages/login/login'
|
||||||
|
})
|
||||||
|
}, 1500)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
817
pages/register/register.vue
Normal file
817
pages/register/register.vue
Normal file
@@ -0,0 +1,817 @@
|
|||||||
|
<template>
|
||||||
|
<view class="register-page">
|
||||||
|
<!-- 背景装饰 -->
|
||||||
|
<view class="bg-decoration">
|
||||||
|
<view class="circle circle-1"></view>
|
||||||
|
<view class="circle circle-2"></view>
|
||||||
|
<view class="circle circle-3"></view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 顶部Logo区域 -->
|
||||||
|
<view class="register-header">
|
||||||
|
<view class="logo-container">
|
||||||
|
<image class="logo" src="/static/logo.png" mode="aspectFit"></image>
|
||||||
|
</view>
|
||||||
|
<text class="title">用户注册</text>
|
||||||
|
<text class="subtitle">CREATE YOUR ACCOUNT</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 注册表单 -->
|
||||||
|
<view class="register-form">
|
||||||
|
<view class="form-title">创建账号</view>
|
||||||
|
|
||||||
|
<view class="form-item">
|
||||||
|
<view class="input-wrapper">
|
||||||
|
<view class="input-icon">👤</view>
|
||||||
|
<input
|
||||||
|
class="form-input"
|
||||||
|
v-model="form.account"
|
||||||
|
placeholder="请输入账号(4-20位字母或数字)"
|
||||||
|
placeholder-class="placeholder"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="form-item">
|
||||||
|
<view class="input-wrapper">
|
||||||
|
<view class="input-icon">📱</view>
|
||||||
|
<input
|
||||||
|
class="form-input"
|
||||||
|
v-model="form.phone"
|
||||||
|
type="number"
|
||||||
|
maxlength="11"
|
||||||
|
placeholder="请输入手机号"
|
||||||
|
placeholder-class="placeholder"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="form-item">
|
||||||
|
<view class="code-input-wrapper">
|
||||||
|
<view class="input-wrapper code-input">
|
||||||
|
<view class="input-icon">🔢</view>
|
||||||
|
<input
|
||||||
|
class="form-input"
|
||||||
|
v-model="form.code"
|
||||||
|
type="number"
|
||||||
|
maxlength="6"
|
||||||
|
placeholder="请输入验证码"
|
||||||
|
placeholder-class="placeholder"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="get-code-btn"
|
||||||
|
:class="{ disabled: countdown > 0 }"
|
||||||
|
@click="handleGetCode"
|
||||||
|
>
|
||||||
|
{{ countdown > 0 ? `${countdown}s` : '获取验证码' }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="test-tip">💡 测试提示:可使用万能验证码 888888</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="form-item">
|
||||||
|
<view class="input-wrapper">
|
||||||
|
<view class="input-icon">✏️</view>
|
||||||
|
<input
|
||||||
|
class="form-input"
|
||||||
|
v-model="form.realName"
|
||||||
|
placeholder="请输入真实姓名"
|
||||||
|
placeholder-class="placeholder"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="form-item">
|
||||||
|
<view class="gender-label">性别</view>
|
||||||
|
<view class="gender-selector">
|
||||||
|
<view
|
||||||
|
class="gender-option"
|
||||||
|
:class="{ active: form.sex === 1 }"
|
||||||
|
@click="form.sex = 1"
|
||||||
|
>
|
||||||
|
<text class="gender-icon">👨</text>
|
||||||
|
<text>男</text>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="gender-option"
|
||||||
|
:class="{ active: form.sex === 2 }"
|
||||||
|
@click="form.sex = 2"
|
||||||
|
>
|
||||||
|
<text class="gender-icon">👩</text>
|
||||||
|
<text>女</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="form-item">
|
||||||
|
<view class="input-wrapper">
|
||||||
|
<view class="input-icon">🔒</view>
|
||||||
|
<input
|
||||||
|
class="form-input"
|
||||||
|
v-model="form.password"
|
||||||
|
:password="!showPassword"
|
||||||
|
placeholder="请输入密码(6-20位)"
|
||||||
|
placeholder-class="placeholder"
|
||||||
|
/>
|
||||||
|
<view class="eye-icon" @click="showPassword = !showPassword">
|
||||||
|
<text>{{ showPassword ? '👁️' : '👁️🗨️' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="form-item">
|
||||||
|
<view class="input-wrapper">
|
||||||
|
<view class="input-icon">🔐</view>
|
||||||
|
<input
|
||||||
|
class="form-input"
|
||||||
|
v-model="form.confirmPassword"
|
||||||
|
:password="!showConfirmPassword"
|
||||||
|
placeholder="请再次输入密码"
|
||||||
|
placeholder-class="placeholder"
|
||||||
|
/>
|
||||||
|
<view class="eye-icon" @click="showConfirmPassword = !showConfirmPassword">
|
||||||
|
<text>{{ showConfirmPassword ? '👁️' : '👁️🗨️' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="agreement-wrapper">
|
||||||
|
<label class="checkbox-label" @click="agreeTerms = !agreeTerms">
|
||||||
|
<view :class="['checkbox', agreeTerms ? 'checked' : '']">
|
||||||
|
<text v-if="agreeTerms" class="check-icon">✓</text>
|
||||||
|
</view>
|
||||||
|
<text class="agreement-text">
|
||||||
|
我已阅读并同意
|
||||||
|
<text class="link-text" @click.stop="showAgreement">《用户协议》</text>
|
||||||
|
和
|
||||||
|
<text class="link-text" @click.stop="showPrivacy">《隐私政策》</text>
|
||||||
|
</text>
|
||||||
|
</label>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<button class="register-btn" @click="handleRegister" :loading="loading" :disabled="loading">
|
||||||
|
<text class="btn-text">{{ loading ? '注册中...' : '立即注册' }}</text>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<view class="login-link">
|
||||||
|
已有账号?<text class="link-text" @click="goToLogin">立即登录</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 底部装饰 -->
|
||||||
|
<view class="footer-decoration">
|
||||||
|
<view class="wave"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import userAPI from '@/api/user.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
form: {
|
||||||
|
account: '',
|
||||||
|
phone: '',
|
||||||
|
code: '',
|
||||||
|
realName: '',
|
||||||
|
sex: 1,
|
||||||
|
password: '',
|
||||||
|
confirmPassword: ''
|
||||||
|
},
|
||||||
|
showPassword: false,
|
||||||
|
showConfirmPassword: false,
|
||||||
|
agreeTerms: false,
|
||||||
|
loading: false,
|
||||||
|
countdown: 0,
|
||||||
|
timer: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onUnload() {
|
||||||
|
if (this.timer) {
|
||||||
|
clearInterval(this.timer)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 获取验证码
|
||||||
|
*/
|
||||||
|
async handleGetCode() {
|
||||||
|
if (this.countdown > 0) return
|
||||||
|
|
||||||
|
// 验证手机号
|
||||||
|
if (!this.form.phone) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请输入手机号',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!/^1[3-9]\d{9}$/.test(this.form.phone)) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请输入正确的手机号',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await userAPI.getCaptcha(this.form.phone)
|
||||||
|
|
||||||
|
uni.showToast({
|
||||||
|
title: '验证码已发送',
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 开始倒计时
|
||||||
|
this.countdown = 60
|
||||||
|
this.timer = setInterval(() => {
|
||||||
|
this.countdown--
|
||||||
|
if (this.countdown <= 0) {
|
||||||
|
clearInterval(this.timer)
|
||||||
|
this.timer = null
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取验证码失败:', error)
|
||||||
|
uni.showToast({
|
||||||
|
title: error.message || '获取验证码失败',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单验证
|
||||||
|
*/
|
||||||
|
validateForm() {
|
||||||
|
if (!this.form.account) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请输入账号',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!/^[a-zA-Z0-9]{4,20}$/.test(this.form.account)) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '账号为4-20位字母或数字',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.form.phone) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请输入手机号',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!/^1[3-9]\d{9}$/.test(this.form.phone)) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请输入正确的手机号',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.form.code) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请输入验证码',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试用万能验证码:888888
|
||||||
|
// 如果不是万能验证码,则需要验证码长度为6位
|
||||||
|
if (this.form.code !== '888888' && this.form.code.length !== 6) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请输入6位验证码',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.form.realName) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请输入真实姓名',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!/^[\u4e00-\u9fa5]{2,10}$/.test(this.form.realName)) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请输入正确的姓名(2-10个汉字)',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.form.password) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请输入密码',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.form.password.length < 6 || this.form.password.length > 20) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '密码长度为6-20位',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.form.confirmPassword) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请确认密码',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.form.password !== this.form.confirmPassword) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '两次密码输入不一致',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.agreeTerms) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请阅读并同意用户协议',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册
|
||||||
|
*/
|
||||||
|
async handleRegister() {
|
||||||
|
if (!this.validateForm()) return
|
||||||
|
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
await userAPI.register({
|
||||||
|
account: this.form.account,
|
||||||
|
phone: this.form.phone,
|
||||||
|
code: this.form.code,
|
||||||
|
realName: this.form.realName,
|
||||||
|
sex: this.form.sex,
|
||||||
|
password: this.form.password
|
||||||
|
})
|
||||||
|
|
||||||
|
uni.showToast({
|
||||||
|
title: '注册成功',
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 延迟跳转到登录页
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.navigateBack()
|
||||||
|
}, 1500)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('注册失败:', error)
|
||||||
|
uni.showToast({
|
||||||
|
title: error.message || '注册失败',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跳转到登录页
|
||||||
|
*/
|
||||||
|
goToLogin() {
|
||||||
|
uni.navigateBack()
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示用户协议
|
||||||
|
*/
|
||||||
|
showAgreement() {
|
||||||
|
uni.showToast({
|
||||||
|
title: '用户协议',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
// TODO: 跳转到用户协议页面
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示隐私政策
|
||||||
|
*/
|
||||||
|
showPrivacy() {
|
||||||
|
uni.showToast({
|
||||||
|
title: '隐私政策',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
// TODO: 跳转到隐私政策页面
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.register-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: linear-gradient(135deg, #C93639 0%, #A82E31 50%, #8B1F22 100%);
|
||||||
|
padding: 0;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 背景装饰圆圈 */
|
||||||
|
.bg-decoration {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
animation: float 6s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle-1 {
|
||||||
|
width: 300rpx;
|
||||||
|
height: 300rpx;
|
||||||
|
top: -100rpx;
|
||||||
|
right: -50rpx;
|
||||||
|
animation-delay: 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle-2 {
|
||||||
|
width: 200rpx;
|
||||||
|
height: 200rpx;
|
||||||
|
bottom: 100rpx;
|
||||||
|
left: -50rpx;
|
||||||
|
animation-delay: 2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle-3 {
|
||||||
|
width: 150rpx;
|
||||||
|
height: 150rpx;
|
||||||
|
top: 30%;
|
||||||
|
right: 50rpx;
|
||||||
|
animation-delay: 4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes float {
|
||||||
|
0%, 100% {
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translateY(-30rpx) scale(1.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 顶部Logo区域 */
|
||||||
|
.register-header {
|
||||||
|
text-align: center;
|
||||||
|
padding: 80rpx 60rpx 60rpx;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-container {
|
||||||
|
width: 160rpx;
|
||||||
|
height: 160rpx;
|
||||||
|
margin: 0 auto 30rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
backdrop-filter: blur(10rpx);
|
||||||
|
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
width: 120rpx;
|
||||||
|
height: 120rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 44rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #fff;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
letter-spacing: 4rpx;
|
||||||
|
text-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: rgba(255, 255, 255, 0.85);
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 注册表单 */
|
||||||
|
.register-form {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 40rpx 40rpx 0 0;
|
||||||
|
padding: 50rpx 40rpx 80rpx;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
box-shadow: 0 -8rpx 32rpx rgba(0, 0, 0, 0.08);
|
||||||
|
max-height: calc(100vh - 300rpx);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item {
|
||||||
|
margin-bottom: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background: #F7F8FA;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
border: 2rpx solid transparent;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-wrapper:focus-within {
|
||||||
|
background: #fff;
|
||||||
|
border-color: #C93639;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(201, 54, 57, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-icon {
|
||||||
|
width: 70rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 32rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
flex: 1;
|
||||||
|
height: 88rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #333;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
padding-right: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder {
|
||||||
|
color: #999;
|
||||||
|
font-size: 26rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eye-icon {
|
||||||
|
width: 70rpx;
|
||||||
|
height: 88rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 32rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eye-icon:active {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 验证码输入 */
|
||||||
|
.code-input-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-input {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.get-code-btn {
|
||||||
|
height: 88rpx;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
background: linear-gradient(135deg, #C93639 0%, #A82E31 100%);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 24rpx;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: all 0.3s;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(201, 54, 57, 0.2);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.get-code-btn:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.get-code-btn.disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
background: #999;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-tip {
|
||||||
|
margin-top: 10rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #ff9800;
|
||||||
|
padding-left: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 性别选择 */
|
||||||
|
.gender-label {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
padding-left: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gender-selector {
|
||||||
|
display: flex;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gender-option {
|
||||||
|
flex: 1;
|
||||||
|
height: 88rpx;
|
||||||
|
background: #F7F8FA;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
border: 2rpx solid transparent;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #666;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gender-option.active {
|
||||||
|
background: linear-gradient(135deg, #C93639 0%, #A82E31 100%);
|
||||||
|
color: #fff;
|
||||||
|
border-color: #C93639;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(201, 54, 57, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gender-icon {
|
||||||
|
font-size: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 用户协议 */
|
||||||
|
.agreement-wrapper {
|
||||||
|
margin: 30rpx 0 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
width: 32rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
border: 2rpx solid #ddd;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-right: 10rpx;
|
||||||
|
margin-top: 2rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox.checked {
|
||||||
|
background: #C93639;
|
||||||
|
border-color: #C93639;
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-icon {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 20rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666;
|
||||||
|
flex: 1;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-text {
|
||||||
|
color: #C93639;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-text:active {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 注册按钮 */
|
||||||
|
.register-btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 92rpx;
|
||||||
|
background: linear-gradient(135deg, #C93639 0%, #A82E31 100%);
|
||||||
|
border-radius: 16rpx;
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 8rpx 24rpx rgba(201, 54, 57, 0.3);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-btn::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||||||
|
transition: left 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-btn:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(201, 54, 57, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-btn:active::before {
|
||||||
|
left: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-text {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #fff;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 登录链接 */
|
||||||
|
.login-link {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 40rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 底部装饰 */
|
||||||
|
.footer-decoration {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100rpx;
|
||||||
|
z-index: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(180deg, transparent 0%, rgba(255, 255, 255, 0.05) 100%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
448
pages/schedule/schedule-example.vue
Normal file
448
pages/schedule/schedule-example.vue
Normal file
@@ -0,0 +1,448 @@
|
|||||||
|
<template>
|
||||||
|
<view class="schedule-page">
|
||||||
|
<view class="header">
|
||||||
|
<text class="title">赛程编排管理</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 状态显示 -->
|
||||||
|
<view class="status-card">
|
||||||
|
<view class="status-item">
|
||||||
|
<text class="label">编排状态:</text>
|
||||||
|
<text :class="['status-text', getStatusClass()]">{{ getStatusText() }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="status-item">
|
||||||
|
<text class="label">分组数:</text>
|
||||||
|
<text class="value">{{ scheduleData.totalGroups || 0 }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="status-item">
|
||||||
|
<text class="label">参赛人数:</text>
|
||||||
|
<text class="value">{{ scheduleData.totalParticipants || 0 }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="status-item" v-if="scheduleData.lastAutoScheduleTime">
|
||||||
|
<text class="label">最后编排时间:</text>
|
||||||
|
<text class="value">{{ scheduleData.lastAutoScheduleTime }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 操作按钮 -->
|
||||||
|
<view class="action-buttons">
|
||||||
|
<!-- 触发自动编排 -->
|
||||||
|
<button
|
||||||
|
class="btn btn-primary"
|
||||||
|
@click="handleAutoArrange"
|
||||||
|
:disabled="loading || scheduleData.scheduleStatus === 2"
|
||||||
|
>
|
||||||
|
{{ loading ? '编排中...' : '自动编排' }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- 刷新编排结果 -->
|
||||||
|
<button
|
||||||
|
class="btn btn-default"
|
||||||
|
@click="loadScheduleResult"
|
||||||
|
:disabled="loading"
|
||||||
|
>
|
||||||
|
刷新结果
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- 保存并锁定 -->
|
||||||
|
<button
|
||||||
|
class="btn btn-success"
|
||||||
|
@click="handleSaveAndLock"
|
||||||
|
:disabled="loading || scheduleData.scheduleStatus === 2 || scheduleData.scheduleStatus === 0"
|
||||||
|
>
|
||||||
|
{{ scheduleData.scheduleStatus === 2 ? '已锁定' : '保存并锁定' }}
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 编排结果列表 -->
|
||||||
|
<view class="schedule-list" v-if="scheduleData.scheduleGroups && scheduleData.scheduleGroups.length > 0">
|
||||||
|
<view class="list-title">编排结果</view>
|
||||||
|
<view
|
||||||
|
class="schedule-item"
|
||||||
|
v-for="group in scheduleData.scheduleGroups"
|
||||||
|
:key="group.groupId"
|
||||||
|
>
|
||||||
|
<view class="group-header">
|
||||||
|
<text class="group-name">{{ group.groupName }}</text>
|
||||||
|
<text class="participant-count">{{ group.participants.length }}人</text>
|
||||||
|
</view>
|
||||||
|
<view class="group-info">
|
||||||
|
<text class="info-text">场地:{{ group.venueName }}</text>
|
||||||
|
<text class="info-text">时间:{{ group.scheduleDate }} {{ group.scheduleTime }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<view class="empty-state" v-else>
|
||||||
|
<text class="empty-text">暂无编排数据</text>
|
||||||
|
<text class="empty-hint">点击"自动编排"开始编排</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import scheduleAPI from '@/api/schedule.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
competitionId: null, // 赛事ID,从页面参数获取
|
||||||
|
loading: false,
|
||||||
|
scheduleData: {
|
||||||
|
scheduleStatus: 0, // 0-未编排, 1-已编排, 2-已锁定
|
||||||
|
totalGroups: 0,
|
||||||
|
totalParticipants: 0,
|
||||||
|
lastAutoScheduleTime: null,
|
||||||
|
scheduleGroups: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad(options) {
|
||||||
|
// 从页面参数获取赛事ID
|
||||||
|
if (options.competitionId) {
|
||||||
|
this.competitionId = parseInt(options.competitionId)
|
||||||
|
this.loadScheduleResult()
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: '缺少赛事ID',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 加载赛程编排结果
|
||||||
|
*/
|
||||||
|
async loadScheduleResult() {
|
||||||
|
if (!this.competitionId) return
|
||||||
|
|
||||||
|
this.loading = true
|
||||||
|
try {
|
||||||
|
const result = await scheduleAPI.getScheduleResult(this.competitionId)
|
||||||
|
this.scheduleData = result
|
||||||
|
console.log('编排结果:', result)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载编排结果失败:', error)
|
||||||
|
uni.showToast({
|
||||||
|
title: error.message || '加载失败',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 触发自动编排
|
||||||
|
*/
|
||||||
|
async handleAutoArrange() {
|
||||||
|
if (!this.competitionId) return
|
||||||
|
|
||||||
|
// 确认提示
|
||||||
|
const [err, res] = await uni.showModal({
|
||||||
|
title: '确认编排',
|
||||||
|
content: '确定要执行自动编排吗?',
|
||||||
|
confirmText: '确定',
|
||||||
|
cancelText: '取消'
|
||||||
|
})
|
||||||
|
|
||||||
|
if (err || !res.confirm) return
|
||||||
|
|
||||||
|
this.loading = true
|
||||||
|
uni.showLoading({
|
||||||
|
title: '编排中...',
|
||||||
|
mask: true
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
await scheduleAPI.triggerAutoArrange(this.competitionId)
|
||||||
|
|
||||||
|
uni.showToast({
|
||||||
|
title: '编排成功',
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 延迟1秒后刷新结果
|
||||||
|
setTimeout(() => {
|
||||||
|
this.loadScheduleResult()
|
||||||
|
}, 1000)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('自动编排失败:', error)
|
||||||
|
uni.showToast({
|
||||||
|
title: error.message || '编排失败',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
uni.hideLoading()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存并锁定编排
|
||||||
|
*/
|
||||||
|
async handleSaveAndLock() {
|
||||||
|
if (!this.competitionId) return
|
||||||
|
|
||||||
|
// 确认提示
|
||||||
|
const [err, res] = await uni.showModal({
|
||||||
|
title: '确认锁定',
|
||||||
|
content: '锁定后将无法再修改编排,确定要锁定吗?',
|
||||||
|
confirmText: '确定锁定',
|
||||||
|
cancelText: '取消'
|
||||||
|
})
|
||||||
|
|
||||||
|
if (err || !res.confirm) return
|
||||||
|
|
||||||
|
this.loading = true
|
||||||
|
uni.showLoading({
|
||||||
|
title: '保存中...',
|
||||||
|
mask: true
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
await scheduleAPI.saveAndLockSchedule(this.competitionId)
|
||||||
|
|
||||||
|
uni.showToast({
|
||||||
|
title: '锁定成功',
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 刷新结果
|
||||||
|
setTimeout(() => {
|
||||||
|
this.loadScheduleResult()
|
||||||
|
}, 1000)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('保存锁定失败:', error)
|
||||||
|
uni.showToast({
|
||||||
|
title: error.message || '锁定失败',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
uni.hideLoading()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取状态文本
|
||||||
|
*/
|
||||||
|
getStatusText() {
|
||||||
|
const statusMap = {
|
||||||
|
0: '未编排',
|
||||||
|
1: '已编排',
|
||||||
|
2: '已锁定'
|
||||||
|
}
|
||||||
|
return statusMap[this.scheduleData.scheduleStatus] || '未知'
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取状态样式类
|
||||||
|
*/
|
||||||
|
getStatusClass() {
|
||||||
|
const classMap = {
|
||||||
|
0: 'status-pending',
|
||||||
|
1: 'status-draft',
|
||||||
|
2: 'status-locked'
|
||||||
|
}
|
||||||
|
return classMap[this.scheduleData.scheduleStatus] || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.schedule-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #f5f5f5;
|
||||||
|
padding: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
background: #fff;
|
||||||
|
padding: 30rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-card {
|
||||||
|
background: #fff;
|
||||||
|
padding: 30rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666;
|
||||||
|
min-width: 180rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 8rpx 20rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
|
||||||
|
&.status-pending {
|
||||||
|
color: #999;
|
||||||
|
background: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.status-draft {
|
||||||
|
color: #1890ff;
|
||||||
|
background: #e6f7ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.status-locked {
|
||||||
|
color: #52c41a;
|
||||||
|
background: #f6ffed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 20rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
flex: 1;
|
||||||
|
height: 80rpx;
|
||||||
|
line-height: 80rpx;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
&.btn-primary {
|
||||||
|
background: #1890ff;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
background: #d9d9d9;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.btn-default {
|
||||||
|
background: #fff;
|
||||||
|
color: #333;
|
||||||
|
border: 2rpx solid #d9d9d9;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.btn-success {
|
||||||
|
background: #52c41a;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
background: #d9d9d9;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.schedule-list {
|
||||||
|
background: #fff;
|
||||||
|
padding: 30rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.schedule-item {
|
||||||
|
padding: 24rpx;
|
||||||
|
background: #f9f9f9;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-name {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.participant-count {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #1890ff;
|
||||||
|
background: #e6f7ff;
|
||||||
|
padding: 4rpx 12rpx;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-info {
|
||||||
|
display: flex;
|
||||||
|
gap: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-text {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 120rpx 0;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #999;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-hint {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
80
utils/auth.js
Normal file
80
utils/auth.js
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
/**
|
||||||
|
* Token管理工具
|
||||||
|
*/
|
||||||
|
|
||||||
|
const TOKEN_KEY = 'access_token'
|
||||||
|
const REFRESH_TOKEN_KEY = 'refresh_token'
|
||||||
|
const USER_INFO_KEY = 'userInfo'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取Token
|
||||||
|
*/
|
||||||
|
export function getToken() {
|
||||||
|
return uni.getStorageSync(TOKEN_KEY)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置Token
|
||||||
|
*/
|
||||||
|
export function setToken(token) {
|
||||||
|
return uni.setStorageSync(TOKEN_KEY, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除Token
|
||||||
|
*/
|
||||||
|
export function removeToken() {
|
||||||
|
return uni.removeStorageSync(TOKEN_KEY)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取RefreshToken
|
||||||
|
*/
|
||||||
|
export function getRefreshToken() {
|
||||||
|
return uni.getStorageSync(REFRESH_TOKEN_KEY)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置RefreshToken
|
||||||
|
*/
|
||||||
|
export function setRefreshToken(token) {
|
||||||
|
return uni.setStorageSync(REFRESH_TOKEN_KEY, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户信息
|
||||||
|
*/
|
||||||
|
export function getUserInfo() {
|
||||||
|
const userInfo = uni.getStorageSync(USER_INFO_KEY)
|
||||||
|
return userInfo ? JSON.parse(userInfo) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置用户信息
|
||||||
|
*/
|
||||||
|
export function setUserInfo(userInfo) {
|
||||||
|
return uni.setStorageSync(USER_INFO_KEY, JSON.stringify(userInfo))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除用户信息
|
||||||
|
*/
|
||||||
|
export function removeUserInfo() {
|
||||||
|
return uni.removeStorageSync(USER_INFO_KEY)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否登录
|
||||||
|
*/
|
||||||
|
export function isLogin() {
|
||||||
|
return !!(getToken() && getUserInfo())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除所有认证信息
|
||||||
|
*/
|
||||||
|
export function clearAuth() {
|
||||||
|
removeToken()
|
||||||
|
uni.removeStorageSync(REFRESH_TOKEN_KEY)
|
||||||
|
removeUserInfo()
|
||||||
|
}
|
||||||
40
utils/base64.js
Normal file
40
utils/base64.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* Base64编码工具
|
||||||
|
*/
|
||||||
|
|
||||||
|
const base64EncodeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||||
|
|
||||||
|
export function base64Encode(str) {
|
||||||
|
let out = ''
|
||||||
|
let i = 0
|
||||||
|
const len = str.length
|
||||||
|
let c1, c2, c3
|
||||||
|
|
||||||
|
while (i < len) {
|
||||||
|
c1 = str.charCodeAt(i++) & 0xff
|
||||||
|
if (i === len) {
|
||||||
|
out += base64EncodeChars.charAt(c1 >> 2)
|
||||||
|
out += base64EncodeChars.charAt((c1 & 0x3) << 4)
|
||||||
|
out += '=='
|
||||||
|
break
|
||||||
|
}
|
||||||
|
c2 = str.charCodeAt(i++)
|
||||||
|
if (i === len) {
|
||||||
|
out += base64EncodeChars.charAt(c1 >> 2)
|
||||||
|
out += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4))
|
||||||
|
out += base64EncodeChars.charAt((c2 & 0xF) << 2)
|
||||||
|
out += '='
|
||||||
|
break
|
||||||
|
}
|
||||||
|
c3 = str.charCodeAt(i++)
|
||||||
|
out += base64EncodeChars.charAt(c1 >> 2)
|
||||||
|
out += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4))
|
||||||
|
out += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6))
|
||||||
|
out += base64EncodeChars.charAt(c3 & 0x3F)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
base64Encode
|
||||||
|
}
|
||||||
212
utils/md5.js
Normal file
212
utils/md5.js
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
/**
|
||||||
|
* MD5加密工具
|
||||||
|
* 用于密码加密
|
||||||
|
*/
|
||||||
|
|
||||||
|
function md5(string) {
|
||||||
|
function md5_RotateLeft(lValue, iShiftBits) {
|
||||||
|
return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits))
|
||||||
|
}
|
||||||
|
|
||||||
|
function md5_AddUnsigned(lX, lY) {
|
||||||
|
let lX4, lY4, lX8, lY8, lResult
|
||||||
|
lX8 = (lX & 0x80000000)
|
||||||
|
lY8 = (lY & 0x80000000)
|
||||||
|
lX4 = (lX & 0x40000000)
|
||||||
|
lY4 = (lY & 0x40000000)
|
||||||
|
lResult = (lX & 0x3FFFFFFF) + (lY & 0x3FFFFFFF)
|
||||||
|
if (lX4 & lY4) {
|
||||||
|
return (lResult ^ 0x80000000 ^ lX8 ^ lY8)
|
||||||
|
}
|
||||||
|
if (lX4 | lY4) {
|
||||||
|
if (lResult & 0x40000000) {
|
||||||
|
return (lResult ^ 0xC0000000 ^ lX8 ^ lY8)
|
||||||
|
} else {
|
||||||
|
return (lResult ^ 0x40000000 ^ lX8 ^ lY8)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return (lResult ^ lX8 ^ lY8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function md5_F(x, y, z) {
|
||||||
|
return (x & y) | ((~x) & z)
|
||||||
|
}
|
||||||
|
|
||||||
|
function md5_G(x, y, z) {
|
||||||
|
return (x & z) | (y & (~z))
|
||||||
|
}
|
||||||
|
|
||||||
|
function md5_H(x, y, z) {
|
||||||
|
return (x ^ y ^ z)
|
||||||
|
}
|
||||||
|
|
||||||
|
function md5_I(x, y, z) {
|
||||||
|
return (y ^ (x | (~z)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function md5_FF(a, b, c, d, x, s, ac) {
|
||||||
|
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_F(b, c, d), x), ac))
|
||||||
|
return md5_AddUnsigned(md5_RotateLeft(a, s), b)
|
||||||
|
}
|
||||||
|
|
||||||
|
function md5_GG(a, b, c, d, x, s, ac) {
|
||||||
|
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_G(b, c, d), x), ac))
|
||||||
|
return md5_AddUnsigned(md5_RotateLeft(a, s), b)
|
||||||
|
}
|
||||||
|
|
||||||
|
function md5_HH(a, b, c, d, x, s, ac) {
|
||||||
|
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_H(b, c, d), x), ac))
|
||||||
|
return md5_AddUnsigned(md5_RotateLeft(a, s), b)
|
||||||
|
}
|
||||||
|
|
||||||
|
function md5_II(a, b, c, d, x, s, ac) {
|
||||||
|
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_I(b, c, d), x), ac))
|
||||||
|
return md5_AddUnsigned(md5_RotateLeft(a, s), b)
|
||||||
|
}
|
||||||
|
|
||||||
|
function md5_ConvertToWordArray(string) {
|
||||||
|
let lWordCount
|
||||||
|
const lMessageLength = string.length
|
||||||
|
const lNumberOfWords_temp1 = lMessageLength + 8
|
||||||
|
const lNumberOfWords_temp2 = (lNumberOfWords_temp1 - (lNumberOfWords_temp1 % 64)) / 64
|
||||||
|
const lNumberOfWords = (lNumberOfWords_temp2 + 1) * 16
|
||||||
|
const lWordArray = Array(lNumberOfWords - 1)
|
||||||
|
let lBytePosition = 0
|
||||||
|
let lByteCount = 0
|
||||||
|
while (lByteCount < lMessageLength) {
|
||||||
|
lWordCount = (lByteCount - (lByteCount % 4)) / 4
|
||||||
|
lBytePosition = (lByteCount % 4) * 8
|
||||||
|
lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount) << lBytePosition))
|
||||||
|
lByteCount++
|
||||||
|
}
|
||||||
|
lWordCount = (lByteCount - (lByteCount % 4)) / 4
|
||||||
|
lBytePosition = (lByteCount % 4) * 8
|
||||||
|
lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition)
|
||||||
|
lWordArray[lNumberOfWords - 2] = lMessageLength << 3
|
||||||
|
lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29
|
||||||
|
return lWordArray
|
||||||
|
}
|
||||||
|
|
||||||
|
function md5_WordToHex(lValue) {
|
||||||
|
let WordToHexValue = '', WordToHexValue_temp = '', lByte, lCount
|
||||||
|
for (lCount = 0; lCount <= 3; lCount++) {
|
||||||
|
lByte = (lValue >>> (lCount * 8)) & 255
|
||||||
|
WordToHexValue_temp = '0' + lByte.toString(16)
|
||||||
|
WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length - 2, 2)
|
||||||
|
}
|
||||||
|
return WordToHexValue
|
||||||
|
}
|
||||||
|
|
||||||
|
function md5_Utf8Encode(string) {
|
||||||
|
string = string.replace(/\r\n/g, '\n')
|
||||||
|
let utftext = ''
|
||||||
|
for (let n = 0; n < string.length; n++) {
|
||||||
|
const c = string.charCodeAt(n)
|
||||||
|
if (c < 128) {
|
||||||
|
utftext += String.fromCharCode(c)
|
||||||
|
} else if ((c > 127) && (c < 2048)) {
|
||||||
|
utftext += String.fromCharCode((c >> 6) | 192)
|
||||||
|
utftext += String.fromCharCode((c & 63) | 128)
|
||||||
|
} else {
|
||||||
|
utftext += String.fromCharCode((c >> 12) | 224)
|
||||||
|
utftext += String.fromCharCode(((c >> 6) & 63) | 128)
|
||||||
|
utftext += String.fromCharCode((c & 63) | 128)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return utftext
|
||||||
|
}
|
||||||
|
|
||||||
|
let x = []
|
||||||
|
let k, AA, BB, CC, DD, a, b, c, d
|
||||||
|
const S11 = 7, S12 = 12, S13 = 17, S14 = 22
|
||||||
|
const S21 = 5, S22 = 9, S23 = 14, S24 = 20
|
||||||
|
const S31 = 4, S32 = 11, S33 = 16, S34 = 23
|
||||||
|
const S41 = 6, S42 = 10, S43 = 15, S44 = 21
|
||||||
|
|
||||||
|
string = md5_Utf8Encode(string)
|
||||||
|
x = md5_ConvertToWordArray(string)
|
||||||
|
a = 0x67452301
|
||||||
|
b = 0xEFCDAB89
|
||||||
|
c = 0x98BADCFE
|
||||||
|
d = 0x10325476
|
||||||
|
|
||||||
|
for (k = 0; k < x.length; k += 16) {
|
||||||
|
AA = a
|
||||||
|
BB = b
|
||||||
|
CC = c
|
||||||
|
DD = d
|
||||||
|
a = md5_FF(a, b, c, d, x[k + 0], S11, 0xD76AA478)
|
||||||
|
d = md5_FF(d, a, b, c, x[k + 1], S12, 0xE8C7B756)
|
||||||
|
c = md5_FF(c, d, a, b, x[k + 2], S13, 0x242070DB)
|
||||||
|
b = md5_FF(b, c, d, a, x[k + 3], S14, 0xC1BDCEEE)
|
||||||
|
a = md5_FF(a, b, c, d, x[k + 4], S11, 0xF57C0FAF)
|
||||||
|
d = md5_FF(d, a, b, c, x[k + 5], S12, 0x4787C62A)
|
||||||
|
c = md5_FF(c, d, a, b, x[k + 6], S13, 0xA8304613)
|
||||||
|
b = md5_FF(b, c, d, a, x[k + 7], S14, 0xFD469501)
|
||||||
|
a = md5_FF(a, b, c, d, x[k + 8], S11, 0x698098D8)
|
||||||
|
d = md5_FF(d, a, b, c, x[k + 9], S12, 0x8B44F7AF)
|
||||||
|
c = md5_FF(c, d, a, b, x[k + 10], S13, 0xFFFF5BB1)
|
||||||
|
b = md5_FF(b, c, d, a, x[k + 11], S14, 0x895CD7BE)
|
||||||
|
a = md5_FF(a, b, c, d, x[k + 12], S11, 0x6B901122)
|
||||||
|
d = md5_FF(d, a, b, c, x[k + 13], S12, 0xFD987193)
|
||||||
|
c = md5_FF(c, d, a, b, x[k + 14], S13, 0xA679438E)
|
||||||
|
b = md5_FF(b, c, d, a, x[k + 15], S14, 0x49B40821)
|
||||||
|
a = md5_GG(a, b, c, d, x[k + 1], S21, 0xF61E2562)
|
||||||
|
d = md5_GG(d, a, b, c, x[k + 6], S22, 0xC040B340)
|
||||||
|
c = md5_GG(c, d, a, b, x[k + 11], S23, 0x265E5A51)
|
||||||
|
b = md5_GG(b, c, d, a, x[k + 0], S24, 0xE9B6C7AA)
|
||||||
|
a = md5_GG(a, b, c, d, x[k + 5], S21, 0xD62F105D)
|
||||||
|
d = md5_GG(d, a, b, c, x[k + 10], S22, 0x2441453)
|
||||||
|
c = md5_GG(c, d, a, b, x[k + 15], S23, 0xD8A1E681)
|
||||||
|
b = md5_GG(b, c, d, a, x[k + 4], S24, 0xE7D3FBC8)
|
||||||
|
a = md5_GG(a, b, c, d, x[k + 9], S21, 0x21E1CDE6)
|
||||||
|
d = md5_GG(d, a, b, c, x[k + 14], S22, 0xC33707D6)
|
||||||
|
c = md5_GG(c, d, a, b, x[k + 3], S23, 0xF4D50D87)
|
||||||
|
b = md5_GG(b, c, d, a, x[k + 8], S24, 0x455A14ED)
|
||||||
|
a = md5_GG(a, b, c, d, x[k + 13], S21, 0xA9E3E905)
|
||||||
|
d = md5_GG(d, a, b, c, x[k + 2], S22, 0xFCEFA3F8)
|
||||||
|
c = md5_GG(c, d, a, b, x[k + 7], S23, 0x676F02D9)
|
||||||
|
b = md5_GG(b, c, d, a, x[k + 12], S24, 0x8D2A4C8A)
|
||||||
|
a = md5_HH(a, b, c, d, x[k + 5], S31, 0xFFFA3942)
|
||||||
|
d = md5_HH(d, a, b, c, x[k + 8], S32, 0x8771F681)
|
||||||
|
c = md5_HH(c, d, a, b, x[k + 11], S33, 0x6D9D6122)
|
||||||
|
b = md5_HH(b, c, d, a, x[k + 14], S34, 0xFDE5380C)
|
||||||
|
a = md5_HH(a, b, c, d, x[k + 1], S31, 0xA4BEEA44)
|
||||||
|
d = md5_HH(d, a, b, c, x[k + 4], S32, 0x4BDECFA9)
|
||||||
|
c = md5_HH(c, d, a, b, x[k + 7], S33, 0xF6BB4B60)
|
||||||
|
b = md5_HH(b, c, d, a, x[k + 10], S34, 0xBEBFBC70)
|
||||||
|
a = md5_HH(a, b, c, d, x[k + 13], S31, 0x289B7EC6)
|
||||||
|
d = md5_HH(d, a, b, c, x[k + 0], S32, 0xEAA127FA)
|
||||||
|
c = md5_HH(c, d, a, b, x[k + 3], S33, 0xD4EF3085)
|
||||||
|
b = md5_HH(b, c, d, a, x[k + 6], S34, 0x4881D05)
|
||||||
|
a = md5_HH(a, b, c, d, x[k + 9], S31, 0xD9D4D039)
|
||||||
|
d = md5_HH(d, a, b, c, x[k + 12], S32, 0xE6DB99E5)
|
||||||
|
c = md5_HH(c, d, a, b, x[k + 15], S33, 0x1FA27CF8)
|
||||||
|
b = md5_HH(b, c, d, a, x[k + 2], S34, 0xC4AC5665)
|
||||||
|
a = md5_II(a, b, c, d, x[k + 0], S41, 0xF4292244)
|
||||||
|
d = md5_II(d, a, b, c, x[k + 7], S42, 0x432AFF97)
|
||||||
|
c = md5_II(c, d, a, b, x[k + 14], S43, 0xAB9423A7)
|
||||||
|
b = md5_II(b, c, d, a, x[k + 5], S44, 0xFC93A039)
|
||||||
|
a = md5_II(a, b, c, d, x[k + 12], S41, 0x655B59C3)
|
||||||
|
d = md5_II(d, a, b, c, x[k + 3], S42, 0x8F0CCC92)
|
||||||
|
c = md5_II(c, d, a, b, x[k + 10], S43, 0xFFEFF47D)
|
||||||
|
b = md5_II(b, c, d, a, x[k + 1], S44, 0x85845DD1)
|
||||||
|
a = md5_II(a, b, c, d, x[k + 8], S41, 0x6FA87E4F)
|
||||||
|
d = md5_II(d, a, b, c, x[k + 15], S42, 0xFE2CE6E0)
|
||||||
|
c = md5_II(c, d, a, b, x[k + 6], S43, 0xA3014314)
|
||||||
|
b = md5_II(b, c, d, a, x[k + 13], S44, 0x4E0811A1)
|
||||||
|
a = md5_II(a, b, c, d, x[k + 4], S41, 0xF7537E82)
|
||||||
|
d = md5_II(d, a, b, c, x[k + 11], S42, 0xBD3AF235)
|
||||||
|
c = md5_II(c, d, a, b, x[k + 2], S43, 0x2AD7D2BB)
|
||||||
|
b = md5_II(b, c, d, a, x[k + 9], S44, 0xEB86D391)
|
||||||
|
a = md5_AddUnsigned(a, AA)
|
||||||
|
b = md5_AddUnsigned(b, BB)
|
||||||
|
c = md5_AddUnsigned(c, CC)
|
||||||
|
d = md5_AddUnsigned(d, DD)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (md5_WordToHex(a) + md5_WordToHex(b) + md5_WordToHex(c) + md5_WordToHex(d)).toLowerCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
export default md5
|
||||||
114
utils/request.js
114
utils/request.js
@@ -9,6 +9,7 @@ class Request {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.baseURL = config.baseURL
|
this.baseURL = config.baseURL
|
||||||
this.timeout = config.timeout
|
this.timeout = config.timeout
|
||||||
|
this.isRedirecting = false // 防止重复跳转登录页
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -76,12 +77,13 @@ class Request {
|
|||||||
* @returns {Object}
|
* @returns {Object}
|
||||||
*/
|
*/
|
||||||
getHeaders(customHeader = {}) {
|
getHeaders(customHeader = {}) {
|
||||||
// 获取token
|
// 获取token - 使用access_token作为key
|
||||||
const token = uni.getStorageSync('token') || ''
|
const token = uni.getStorageSync('access_token') || ''
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Blade-Auth': token ? `Bearer ${token}` : '',
|
'Blade-Auth': token ? `bearer ${token}` : '',
|
||||||
|
'Tenant-Id': '000000',
|
||||||
...customHeader
|
...customHeader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -95,24 +97,41 @@ class Request {
|
|||||||
handleResponse(res, resolve, reject) {
|
handleResponse(res, resolve, reject) {
|
||||||
const data = res.data
|
const data = res.data
|
||||||
|
|
||||||
// 判断HTTP状态码
|
// 判断HTTP状态码 - 特别处理401
|
||||||
// 2xx 和 304 都是成功的状态码
|
if (res.statusCode === 401) {
|
||||||
|
this.handleTokenExpired()
|
||||||
|
reject({
|
||||||
|
statusCode: 401,
|
||||||
|
code: 401,
|
||||||
|
message: '未登录或登录已过期'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其他HTTP错误状态码
|
||||||
if (res.statusCode < 200 || (res.statusCode >= 300 && res.statusCode !== 304)) {
|
if (res.statusCode < 200 || (res.statusCode >= 300 && res.statusCode !== 304)) {
|
||||||
this.showError('网络请求失败')
|
this.showError('网络请求失败')
|
||||||
reject({
|
reject({
|
||||||
|
statusCode: res.statusCode,
|
||||||
code: res.statusCode,
|
code: res.statusCode,
|
||||||
message: '网络请求失败'
|
message: '网络请求失败'
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 特殊处理:OAuth2 token接口直接返回数据(有access_token字段)
|
||||||
|
if (data.access_token) {
|
||||||
|
resolve(data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 判断业务状态码
|
// 判断业务状态码
|
||||||
if (data.code === 200 || data.success === true) {
|
if (data.code === 200 || data.success === true) {
|
||||||
// 请求成功,返回数据
|
// 请求成功,返回数据
|
||||||
resolve(data.data)
|
resolve(data.data)
|
||||||
} else {
|
} else {
|
||||||
// 业务错误处理
|
// 业务错误处理
|
||||||
const errorMsg = data.msg || data.message || '请求失败'
|
const errorMsg = data.msg || data.message || data.error_description || '请求失败'
|
||||||
|
|
||||||
// 特殊错误码处理
|
// 特殊错误码处理
|
||||||
if (data.code === 401 || data.code === 403) {
|
if (data.code === 401 || data.code === 403) {
|
||||||
@@ -122,6 +141,7 @@ class Request {
|
|||||||
|
|
||||||
this.showError(errorMsg)
|
this.showError(errorMsg)
|
||||||
reject({
|
reject({
|
||||||
|
statusCode: res.statusCode,
|
||||||
code: data.code,
|
code: data.code,
|
||||||
message: errorMsg,
|
message: errorMsg,
|
||||||
data: data.data
|
data: data.data
|
||||||
@@ -174,23 +194,85 @@ class Request {
|
|||||||
* 处理token过期
|
* 处理token过期
|
||||||
*/
|
*/
|
||||||
handleTokenExpired() {
|
handleTokenExpired() {
|
||||||
// 清除token
|
console.log('=== handleTokenExpired 被调用 ===')
|
||||||
uni.removeStorageSync('token')
|
console.log('isRedirecting:', this.isRedirecting)
|
||||||
|
|
||||||
|
// 防止重复跳转
|
||||||
|
if (this.isRedirecting) {
|
||||||
|
console.log('已经在跳转中,跳过')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.isRedirecting = true
|
||||||
|
|
||||||
|
console.log('开始清除认证信息')
|
||||||
|
// 清除所有认证信息
|
||||||
|
uni.removeStorageSync('access_token')
|
||||||
|
uni.removeStorageSync('refresh_token')
|
||||||
uni.removeStorageSync('userInfo')
|
uni.removeStorageSync('userInfo')
|
||||||
|
console.log('认证信息已清除')
|
||||||
|
|
||||||
// 提示用户
|
// 提示用户
|
||||||
|
console.log('显示Toast提示')
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '登录已过期,请重新登录',
|
title: '登录已过期,请重新登录',
|
||||||
icon: 'none',
|
icon: 'none',
|
||||||
duration: 2000
|
duration: 1500
|
||||||
})
|
})
|
||||||
|
|
||||||
// 跳转到登录页(如果有)
|
// 立即跳转,不等待Toast
|
||||||
setTimeout(() => {
|
console.log('准备跳转到登录页')
|
||||||
// uni.reLaunch({
|
|
||||||
// url: '/pages/login/login'
|
// 获取当前页面路径
|
||||||
// })
|
const pages = getCurrentPages()
|
||||||
}, 2000)
|
console.log('当前页面栈:', pages)
|
||||||
|
const currentPage = pages[pages.length - 1]
|
||||||
|
const currentRoute = currentPage ? currentPage.route : ''
|
||||||
|
|
||||||
|
console.log('当前页面路由:', currentRoute)
|
||||||
|
|
||||||
|
// 如果当前不在登录页,才跳转
|
||||||
|
if (currentRoute !== 'pages/login/login') {
|
||||||
|
console.log('开始执行跳转...')
|
||||||
|
|
||||||
|
// 使用 setTimeout 确保在下一个事件循环执行
|
||||||
|
setTimeout(() => {
|
||||||
|
this.isRedirecting = false
|
||||||
|
|
||||||
|
uni.reLaunch({
|
||||||
|
url: '/pages/login/login',
|
||||||
|
success: () => {
|
||||||
|
console.log('✅ reLaunch 跳转成功')
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('❌ reLaunch 失败:', err)
|
||||||
|
// 如果reLaunch失败,尝试使用redirectTo
|
||||||
|
uni.redirectTo({
|
||||||
|
url: '/pages/login/login',
|
||||||
|
success: () => {
|
||||||
|
console.log('✅ redirectTo 跳转成功')
|
||||||
|
},
|
||||||
|
fail: (err2) => {
|
||||||
|
console.error('❌ redirectTo 也失败:', err2)
|
||||||
|
// 最后尝试navigateTo
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages/login/login',
|
||||||
|
success: () => {
|
||||||
|
console.log('✅ navigateTo 跳转成功')
|
||||||
|
},
|
||||||
|
fail: (err3) => {
|
||||||
|
console.error('❌ navigateTo 也失败:', err3)
|
||||||
|
console.error('所有跳转方式都失败了!')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, 100)
|
||||||
|
} else {
|
||||||
|
console.log('当前已在登录页,不需要跳转')
|
||||||
|
this.isRedirecting = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -212,7 +294,7 @@ class Request {
|
|||||||
/**
|
/**
|
||||||
* POST请求
|
* POST请求
|
||||||
* @param {String} url 请求地址
|
* @param {String} url 请求地址
|
||||||
* @param {Object} data 请求参数
|
* @param {Object|String} data 请求参数(可以是对象或字符串)
|
||||||
* @param {Object} options 额外配置
|
* @param {Object} options 额外配置
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user