fix bugs
This commit is contained in:
@@ -133,7 +133,7 @@ export default {
|
||||
await userAPI.updatePassword({
|
||||
oldPassword: this.formData.oldPassword,
|
||||
newPassword: this.formData.newPassword,
|
||||
confirmPassword: this.formData.confirmPassword
|
||||
newPassword1: this.formData.confirmPassword
|
||||
})
|
||||
|
||||
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>
|
||||
import userAPI from '@/api/user.js'
|
||||
import { getUserInfo as getStoredUserInfo, isLogin, clearAuth } from '@/utils/auth.js'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
@@ -62,13 +63,40 @@ export default {
|
||||
};
|
||||
},
|
||||
onLoad() {
|
||||
this.loadUserInfo()
|
||||
this.checkLoginAndLoadInfo()
|
||||
},
|
||||
onShow() {
|
||||
// 每次显示时刷新用户信息
|
||||
this.loadUserInfo()
|
||||
this.checkLoginAndLoadInfo()
|
||||
},
|
||||
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) {
|
||||
console.error('加载用户信息失败:', err)
|
||||
// 失败时不显示错误提示,使用默认值
|
||||
// 如果是401错误,说明token过期,跳转到登录页
|
||||
if (err.statusCode === 401) {
|
||||
clearAuth()
|
||||
uni.reLaunch({
|
||||
url: '/pages/login/login'
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -109,30 +143,39 @@ export default {
|
||||
icon: 'none'
|
||||
});
|
||||
},
|
||||
handleLogout() {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要退出登录吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 清除本地存储的token
|
||||
uni.removeStorageSync('token')
|
||||
uni.removeStorageSync('userInfo')
|
||||
async handleLogout() {
|
||||
const confirmRes = await new Promise((resolve) => {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要退出登录吗?',
|
||||
success: (res) => resolve(res)
|
||||
})
|
||||
})
|
||||
|
||||
uni.showToast({
|
||||
title: '退出成功',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
// 延迟跳转到登录页
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({
|
||||
url: '/pages/login/login'
|
||||
})
|
||||
}, 1500)
|
||||
}
|
||||
if (confirmRes.confirm) {
|
||||
try {
|
||||
// 调用退出登录接口
|
||||
await userAPI.logout()
|
||||
} catch (err) {
|
||||
console.error('退出登录接口调用失败:', err)
|
||||
// 即使接口失败也继续清除本地数据
|
||||
}
|
||||
});
|
||||
|
||||
// 清除本地认证信息
|
||||
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>
|
||||
Reference in New Issue
Block a user