优化部分代码
This commit is contained in:
parent
3bd286f50e
commit
e222de1850
150
src/components/ErrorDialog.vue
Normal file
150
src/components/ErrorDialog.vue
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="visible" class="error-dialog-overlay" @click.self="handleClose">
|
||||||
|
<div class="error-dialog">
|
||||||
|
<div class="error-dialog-content">
|
||||||
|
{{ message }}
|
||||||
|
</div>
|
||||||
|
<div class="error-dialog-footer">
|
||||||
|
<button class="confirm-button" @click="handleClose">确定</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { defineProps, defineEmits } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: '错误提示'
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['close'])
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('close')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.error-dialog-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-dialog {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 400px;
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
animation: dialog-fade-in 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-dialog-header {
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-dialog-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
color: #f56c6c;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 24px;
|
||||||
|
color: #909399;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-button:hover {
|
||||||
|
color: #f56c6c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-dialog-content {
|
||||||
|
padding: 20px;
|
||||||
|
color: #606266;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.5;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-dialog-footer {
|
||||||
|
padding: 10px 20px 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-button {
|
||||||
|
background-color: #f56c6c;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 24px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-button:hover {
|
||||||
|
background-color: #f78989;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes dialog-fade-in {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动端适配 */
|
||||||
|
@media screen and (max-width: 480px) {
|
||||||
|
.error-dialog {
|
||||||
|
width: 85%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-dialog-header h3 {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-dialog-content {
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-button {
|
||||||
|
padding: 6px 20px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -9,7 +9,6 @@
|
|||||||
id="username"
|
id="username"
|
||||||
v-model="username"
|
v-model="username"
|
||||||
placeholder="请输入QQ号"
|
placeholder="请输入QQ号"
|
||||||
@blur="validateUsername"
|
|
||||||
:class="{ 'error': usernameError }"
|
:class="{ 'error': usernameError }"
|
||||||
/>
|
/>
|
||||||
<span class="error-message" v-if="usernameError">{{ usernameError }}</span>
|
<span class="error-message" v-if="usernameError">{{ usernameError }}</span>
|
||||||
@ -21,7 +20,6 @@
|
|||||||
id="password"
|
id="password"
|
||||||
v-model="password"
|
v-model="password"
|
||||||
placeholder="请输入密码"
|
placeholder="请输入密码"
|
||||||
@blur="validatePassword"
|
|
||||||
:class="{ 'error': passwordError }"
|
:class="{ 'error': passwordError }"
|
||||||
/>
|
/>
|
||||||
<span class="error-message" v-if="passwordError">{{ passwordError }}</span>
|
<span class="error-message" v-if="passwordError">{{ passwordError }}</span>
|
||||||
@ -34,7 +32,6 @@
|
|||||||
id="captcha"
|
id="captcha"
|
||||||
v-model="captcha"
|
v-model="captcha"
|
||||||
placeholder="请输入验证码"
|
placeholder="请输入验证码"
|
||||||
@blur="validateCaptcha"
|
|
||||||
:class="{ 'error': captchaError }"
|
:class="{ 'error': captchaError }"
|
||||||
/>
|
/>
|
||||||
<img
|
<img
|
||||||
@ -54,6 +51,12 @@
|
|||||||
<a @click.prevent="$emit('register')">注册账号</a>
|
<a @click.prevent="$emit('register')">注册账号</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<ErrorDialog
|
||||||
|
:visible="showError"
|
||||||
|
:title="errorTitle"
|
||||||
|
:message="errorMessage"
|
||||||
|
@close="showError = false"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -61,6 +64,7 @@
|
|||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { userLogin, getCaptcha } from '../api/login'
|
import { userLogin, getCaptcha } from '../api/login'
|
||||||
|
import ErrorDialog from './ErrorDialog.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const username = ref('')
|
const username = ref('')
|
||||||
@ -74,6 +78,11 @@ const usernameError = ref('')
|
|||||||
const passwordError = ref('')
|
const passwordError = ref('')
|
||||||
const captchaError = ref('')
|
const captchaError = ref('')
|
||||||
|
|
||||||
|
// 错误弹窗相关
|
||||||
|
const showError = ref(false)
|
||||||
|
const errorTitle = ref('错误提示')
|
||||||
|
const errorMessage = ref('')
|
||||||
|
|
||||||
const validateUsername = () => {
|
const validateUsername = () => {
|
||||||
if (!username.value) {
|
if (!username.value) {
|
||||||
usernameError.value = '请输入QQ号码'
|
usernameError.value = '请输入QQ号码'
|
||||||
@ -130,6 +139,12 @@ const isFormValid = computed(() => {
|
|||||||
captcha.value
|
captcha.value
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const showErrorMessage = (message, title = '错误提示') => {
|
||||||
|
errorMessage.value = message
|
||||||
|
errorTitle.value = title
|
||||||
|
showError.value = true
|
||||||
|
}
|
||||||
|
|
||||||
const refreshCaptcha = async () => {
|
const refreshCaptcha = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await getCaptcha()
|
const response = await getCaptcha()
|
||||||
@ -139,7 +154,7 @@ const refreshCaptcha = async () => {
|
|||||||
captcha.value = '' // 清空验证码输入
|
captcha.value = '' // 清空验证码输入
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取验证码失败:', error)
|
console.error('获取验证码失败:', error)
|
||||||
alert('获取验证码失败,请刷新页面重试')
|
showErrorMessage('获取验证码失败,请刷新页面重试')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,10 +187,16 @@ const handleLogin = async () => {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('登录失败:', error)
|
console.error('登录失败:', error)
|
||||||
if (error.response && error.response.status === 401) {
|
if (error.response) {
|
||||||
alert('错误的用户名或密码')
|
switch (error.response.status) {
|
||||||
|
case 401:
|
||||||
|
showErrorMessage('错误的用户名或密码')
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
showErrorMessage(error.response?.data?.message || error.message || '登录失败,请稍后重试')
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
alert(error.response?.data?.message || error.message || '登录失败,请稍后重试')
|
showErrorMessage(error.message || '登录失败,请稍后重试')
|
||||||
}
|
}
|
||||||
// 登录失败时刷新验证码
|
// 登录失败时刷新验证码
|
||||||
refreshCaptcha()
|
refreshCaptcha()
|
||||||
@ -183,7 +204,50 @@ const handleLogin = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const validateForm = () => {
|
const validateForm = () => {
|
||||||
return validateUsername() && validatePassword() && validateCaptcha()
|
// 重置所有错误信息
|
||||||
|
usernameError.value = ''
|
||||||
|
passwordError.value = ''
|
||||||
|
captchaError.value = ''
|
||||||
|
|
||||||
|
// 验证用户名
|
||||||
|
if (!username.value) {
|
||||||
|
usernameError.value = '请输入QQ号码'
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (!/^[/\d/g]+$/.test(username.value)) {
|
||||||
|
usernameError.value = 'QQ号码只能包含数字'
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (username.value.length < 4) {
|
||||||
|
usernameError.value = 'QQ号码长度不能小于4个字符'
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证密码
|
||||||
|
if (!password.value) {
|
||||||
|
passwordError.value = '请输入密码'
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (password.value.length < 4) {
|
||||||
|
passwordError.value = '密码长度不能小于4个字符'
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (password.value.length > 20) {
|
||||||
|
passwordError.value = '密码长度不能超过20个字符'
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证验证码
|
||||||
|
if (!captcha.value) {
|
||||||
|
captchaError.value = '请输入验证码'
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (captcha.value.length !== 4) {
|
||||||
|
captchaError.value = '验证码长度不正确'
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -4,20 +4,20 @@
|
|||||||
<form class="login-form-container" @submit.prevent="handleRegister">
|
<form class="login-form-container" @submit.prevent="handleRegister">
|
||||||
<div class="input-container">
|
<div class="input-container">
|
||||||
<label for="username">QQ号</label>
|
<label for="username">QQ号</label>
|
||||||
<input type="text" id="username" v-model="username" placeholder="请输入QQ号" @blur="validateUsername"
|
<input type="text" id="username" v-model="username" placeholder="请输入QQ号"
|
||||||
:class="{ 'error': usernameError }" />
|
:class="{ 'error': usernameError }" />
|
||||||
<span class="error-message" v-if="usernameError">{{ usernameError }}</span>
|
<span class="error-message" v-if="usernameError">{{ usernameError }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-container">
|
<div class="input-container">
|
||||||
<label for="password">密码</label>
|
<label for="password">密码</label>
|
||||||
<input type="password" id="password" v-model="password" placeholder="请输入密码" @blur="validatePassword"
|
<input type="password" id="password" v-model="password" placeholder="请输入密码"
|
||||||
:class="{ 'error': passwordError }" />
|
:class="{ 'error': passwordError }" />
|
||||||
<span class="error-message" v-if="passwordError">{{ passwordError }}</span>
|
<span class="error-message" v-if="passwordError">{{ passwordError }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-container">
|
<div class="input-container">
|
||||||
<label for="confirmPassword">再次输入密码</label>
|
<label for="confirmPassword">再次输入密码</label>
|
||||||
<input type="password" id="confirmPassword" v-model="confirmPassword" placeholder="请输入密码"
|
<input type="password" id="confirmPassword" v-model="confirmPassword" placeholder="请输入密码"
|
||||||
@blur="validateConfirmPassword" :class="{ 'error': confirmPasswordError }" />
|
:class="{ 'error': confirmPasswordError }" />
|
||||||
<span class="error-message" v-if="confirmPasswordError">{{ confirmPasswordError }}</span>
|
<span class="error-message" v-if="confirmPasswordError">{{ confirmPasswordError }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-container captcha-container">
|
<div class="input-container captcha-container">
|
||||||
@ -28,7 +28,6 @@
|
|||||||
id="captcha"
|
id="captcha"
|
||||||
v-model="captcha"
|
v-model="captcha"
|
||||||
placeholder="请输入验证码"
|
placeholder="请输入验证码"
|
||||||
@blur="validateCaptcha"
|
|
||||||
:class="{ 'error': captchaError }"
|
:class="{ 'error': captchaError }"
|
||||||
/>
|
/>
|
||||||
<img
|
<img
|
||||||
@ -42,12 +41,18 @@
|
|||||||
<span class="error-message" v-if="captchaError">{{ captchaError }}</span>
|
<span class="error-message" v-if="captchaError">{{ captchaError }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="login-button">
|
<div class="login-button">
|
||||||
<button type="submit" :disabled="!isFormValid">注册</button>
|
<button type="submit">注册</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="register-link">
|
<div class="register-link">
|
||||||
<a @click.prevent="$emit('login')">返回登陆</a>
|
<a @click.prevent="$emit('login')">返回登陆</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<ErrorDialog
|
||||||
|
:visible="showError"
|
||||||
|
:title="errorTitle"
|
||||||
|
:message="errorMessage"
|
||||||
|
@close="showError = false"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -55,6 +60,7 @@
|
|||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { userRegister, getCaptcha } from "@/api/login.js";
|
import { userRegister, getCaptcha } from "@/api/login.js";
|
||||||
|
import ErrorDialog from './ErrorDialog.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const username = ref('')
|
const username = ref('')
|
||||||
@ -73,6 +79,17 @@ const passwordError = ref('')
|
|||||||
const confirmPasswordError = ref('')
|
const confirmPasswordError = ref('')
|
||||||
const captchaError = ref('')
|
const captchaError = ref('')
|
||||||
|
|
||||||
|
// 错误弹窗相关
|
||||||
|
const showError = ref(false)
|
||||||
|
const errorTitle = ref('错误提示')
|
||||||
|
const errorMessage = ref('')
|
||||||
|
|
||||||
|
const showErrorMessage = (message, title = '错误提示') => {
|
||||||
|
errorMessage.value = message
|
||||||
|
errorTitle.value = title
|
||||||
|
showError.value = true
|
||||||
|
}
|
||||||
|
|
||||||
// 表单验证规则
|
// 表单验证规则
|
||||||
const validateUsername = () => {
|
const validateUsername = () => {
|
||||||
if (!username.value) {
|
if (!username.value) {
|
||||||
@ -127,18 +144,6 @@ const validateCaptcha = () => {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算表单是否有效
|
|
||||||
const isFormValid = computed(() => {
|
|
||||||
return !usernameError.value &&
|
|
||||||
!passwordError.value &&
|
|
||||||
!confirmPasswordError.value &&
|
|
||||||
!captchaError.value &&
|
|
||||||
username.value &&
|
|
||||||
password.value &&
|
|
||||||
confirmPassword.value &&
|
|
||||||
captcha.value
|
|
||||||
})
|
|
||||||
|
|
||||||
const refreshCaptcha = async () => {
|
const refreshCaptcha = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await getCaptcha()
|
const response = await getCaptcha()
|
||||||
@ -147,7 +152,7 @@ const refreshCaptcha = async () => {
|
|||||||
captcha.value = '' // 清空验证码输入
|
captcha.value = '' // 清空验证码输入
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取验证码失败:', error)
|
console.error('获取验证码失败:', error)
|
||||||
alert('获取验证码失败,请刷新页面重试')
|
showErrorMessage('获取验证码失败,请刷新页面重试')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,7 +163,7 @@ onMounted(() => {
|
|||||||
const handleRegister = async () => {
|
const handleRegister = async () => {
|
||||||
try {
|
try {
|
||||||
// 验证表单
|
// 验证表单
|
||||||
if (!validateUsername() || !validatePassword() || !validateConfirmPassword() || !validateCaptcha()) {
|
if (!validateForm()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,20 +175,80 @@ const handleRegister = async () => {
|
|||||||
captcha.value
|
captcha.value
|
||||||
)
|
)
|
||||||
|
|
||||||
alert('注册成功')
|
showErrorMessage('注册成功', '提示')
|
||||||
// 切换到登录模块
|
// 切换到登录模块
|
||||||
emit('login')
|
emit('login')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('注册失败:', error)
|
console.error('注册失败:', error)
|
||||||
if (error.response && error.response.status === 400) {
|
if (error.response) {
|
||||||
alert('该QQ号已被注册')
|
switch (error.response.status) {
|
||||||
|
case 400:
|
||||||
|
showErrorMessage('该QQ号已被注册')
|
||||||
|
break
|
||||||
|
case 409:
|
||||||
|
showErrorMessage('用户名已存在')
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
showErrorMessage(error.response?.data?.message || error.message || '注册失败,请稍后重试')
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
alert(error.response?.data?.message || error.message || '注册失败,请稍后重试')
|
showErrorMessage(error.message || '注册失败,请稍后重试')
|
||||||
}
|
}
|
||||||
// 注册失败时刷新验证码
|
// 注册失败时刷新验证码
|
||||||
refreshCaptcha()
|
refreshCaptcha()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const validateForm = () => {
|
||||||
|
// 重置所有错误信息
|
||||||
|
usernameError.value = ''
|
||||||
|
passwordError.value = ''
|
||||||
|
confirmPasswordError.value = ''
|
||||||
|
captchaError.value = ''
|
||||||
|
|
||||||
|
// 验证用户名
|
||||||
|
if (!username.value) {
|
||||||
|
usernameError.value = '请输入QQ号码'
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// 只允许纯数字
|
||||||
|
if (!/^\d+$/.test(username.value)) {
|
||||||
|
usernameError.value = 'QQ号只能包含数字'
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证密码
|
||||||
|
if (!password.value) {
|
||||||
|
passwordError.value = '请输入密码'
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (password.value.length < 6) {
|
||||||
|
passwordError.value = '密码长度不能小于6个字符'
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证确认密码
|
||||||
|
if (!confirmPassword.value) {
|
||||||
|
confirmPasswordError.value = '请再次输入密码'
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (confirmPassword.value !== password.value) {
|
||||||
|
confirmPasswordError.value = '两次输入的密码不一致'
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证验证码
|
||||||
|
if (!captcha.value) {
|
||||||
|
captchaError.value = '请输入验证码'
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (captcha.value.length !== 4) {
|
||||||
|
captchaError.value = '验证码长度不正确'
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@ -6,7 +6,13 @@
|
|||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h1>{{ authorName }}</h1>
|
<h1>{{ authorName }}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-container">
|
<div v-if="isLoading" class="loading-container">
|
||||||
|
<div class="loading-bar">
|
||||||
|
<div class="loading-progress"></div>
|
||||||
|
</div>
|
||||||
|
<div class="loading-text">加载中...</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="table-container">
|
||||||
<table class="maps-table">
|
<table class="maps-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@ -39,7 +45,7 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<!-- 分页控件 -->
|
<!-- 分页控件 -->
|
||||||
<div class="pagination">
|
<div v-if="!isLoading" class="pagination">
|
||||||
<button
|
<button
|
||||||
class="page-btn"
|
class="page-btn"
|
||||||
:disabled="currentPage === 1"
|
:disabled="currentPage === 1"
|
||||||
@ -73,6 +79,13 @@
|
|||||||
<button class="jump-btn" @click="handleJumpPage">跳转</button>
|
<button class="jump-btn" @click="handleJumpPage">跳转</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 添加错误弹窗 -->
|
||||||
|
<ErrorDialog
|
||||||
|
:visible="showError"
|
||||||
|
:title="errorTitle"
|
||||||
|
:message="errorMessage"
|
||||||
|
@close="showError = false"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -80,6 +93,7 @@
|
|||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { getMaps } from '@/api/maps.js'
|
import { getMaps } from '@/api/maps.js'
|
||||||
|
import ErrorDialog from '@/components/ErrorDialog.vue'
|
||||||
import '../../assets/styles/common.css'
|
import '../../assets/styles/common.css'
|
||||||
import '../../assets/styles/Maps.css'
|
import '../../assets/styles/Maps.css'
|
||||||
|
|
||||||
@ -87,6 +101,12 @@ const route = useRoute()
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const authorMaps = ref([])
|
const authorMaps = ref([])
|
||||||
const authorName = ref('')
|
const authorName = ref('')
|
||||||
|
const isLoading = ref(true)
|
||||||
|
|
||||||
|
// 错误弹窗相关
|
||||||
|
const showError = ref(false)
|
||||||
|
const errorTitle = ref('')
|
||||||
|
const errorMessage = ref('')
|
||||||
|
|
||||||
const currentPage = ref(1)
|
const currentPage = ref(1)
|
||||||
const pageSize = 20
|
const pageSize = 20
|
||||||
@ -146,9 +166,16 @@ const formatDate = (dateString) => {
|
|||||||
return date.toLocaleDateString()
|
return date.toLocaleDateString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const showErrorMessage = (message, title = '') => {
|
||||||
|
errorMessage.value = message
|
||||||
|
errorTitle.value = title
|
||||||
|
showError.value = true
|
||||||
|
}
|
||||||
|
|
||||||
// 获取作者的地图列表
|
// 获取作者的地图列表
|
||||||
const fetchAuthorMaps = async () => {
|
const fetchAuthorMaps = async () => {
|
||||||
try {
|
try {
|
||||||
|
isLoading.value = true
|
||||||
const author = route.query.author
|
const author = route.query.author
|
||||||
if (!author) {
|
if (!author) {
|
||||||
router.push('/author')
|
router.push('/author')
|
||||||
@ -180,6 +207,9 @@ const fetchAuthorMaps = async () => {
|
|||||||
console.log('作者地图列表:', authorMaps.value)
|
console.log('作者地图列表:', authorMaps.value)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取作者地图列表失败:', error)
|
console.error('获取作者地图列表失败:', error)
|
||||||
|
showErrorMessage('获取作者地图列表失败,请稍后重试')
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,4 +382,57 @@ onMounted(() => {
|
|||||||
.jump-btn:hover {
|
.jump-btn:hover {
|
||||||
background: #1a237e;
|
background: #1a237e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loading-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 40px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-bar {
|
||||||
|
width: 300px;
|
||||||
|
height: 4px;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-radius: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-progress {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 30%;
|
||||||
|
background: linear-gradient(90deg, #2563eb, #1a237e);
|
||||||
|
border-radius: 2px;
|
||||||
|
animation: loading 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-text {
|
||||||
|
margin-top: 12px;
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loading {
|
||||||
|
0% {
|
||||||
|
left: -30%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
left: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 480px) {
|
||||||
|
.loading-bar {
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-text {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
@ -50,7 +50,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 评分弹窗 -->
|
<!-- 评分弹窗 -->
|
||||||
<div class="score-dialog" v-if="showScoreDialog">
|
<div v-if="showScoreDialog" class="score-dialog">
|
||||||
<div class="dialog-overlay" @click="showScoreDialog = false"></div>
|
<div class="dialog-overlay" @click="showScoreDialog = false"></div>
|
||||||
<div class="dialog-content">
|
<div class="dialog-content">
|
||||||
<div class="dialog-header">
|
<div class="dialog-header">
|
||||||
@ -69,11 +69,6 @@
|
|||||||
@click="mapScore = star"
|
@click="mapScore = star"
|
||||||
>★</span>
|
>★</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- <textarea
|
|
||||||
v-model="mapComment"
|
|
||||||
placeholder="请输入对地图的评价(选填)"
|
|
||||||
class="comment-input"
|
|
||||||
></textarea> -->
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="score-section">
|
<div class="score-section">
|
||||||
@ -87,11 +82,6 @@
|
|||||||
@click="authorScore = star"
|
@click="authorScore = star"
|
||||||
>★</span>
|
>★</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- <textarea
|
|
||||||
v-model="authorComment"
|
|
||||||
placeholder="请输入对作者的评价(选填)"
|
|
||||||
class="comment-input"
|
|
||||||
></textarea> -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
@ -100,6 +90,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 添加错误弹窗 -->
|
||||||
|
<ErrorDialog
|
||||||
|
:visible="showError"
|
||||||
|
:title="errorTitle"
|
||||||
|
:message="errorMessage"
|
||||||
|
@close="handleErrorClose"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -108,11 +105,33 @@ import { ref, onMounted } from 'vue'
|
|||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { getMapDetail } from '../../api/maps.js'
|
import { getMapDetail } from '../../api/maps.js'
|
||||||
import { hasValidToken } from '../../utils/jwt'
|
import { hasValidToken } from '../../utils/jwt'
|
||||||
|
import ErrorDialog from '@/components/ErrorDialog.vue'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const map = ref(null)
|
const map = ref(null)
|
||||||
|
|
||||||
|
// 错误弹窗相关
|
||||||
|
const showError = ref(false)
|
||||||
|
const errorTitle = ref('')
|
||||||
|
const errorMessage = ref('')
|
||||||
|
const pendingAction = ref(null)
|
||||||
|
|
||||||
|
const showErrorMessage = (message, title = '', action = null) => {
|
||||||
|
errorMessage.value = message
|
||||||
|
errorTitle.value = title
|
||||||
|
showError.value = true
|
||||||
|
pendingAction.value = action
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleErrorClose = () => {
|
||||||
|
showError.value = false
|
||||||
|
if (pendingAction.value) {
|
||||||
|
pendingAction.value()
|
||||||
|
pendingAction.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 评分相关的响应式变量
|
// 评分相关的响应式变量
|
||||||
const showScoreDialog = ref(false)
|
const showScoreDialog = ref(false)
|
||||||
const mapScore = ref(0)
|
const mapScore = ref(0)
|
||||||
@ -129,16 +148,17 @@ const handleDownload = () => {
|
|||||||
// 处理评分点击
|
// 处理评分点击
|
||||||
const handleScoreClick = () => {
|
const handleScoreClick = () => {
|
||||||
if (!hasValidToken()) {
|
if (!hasValidToken()) {
|
||||||
alert('请先登录后再进行评分')
|
showErrorMessage('请先登录后再进行评分', '', () => {
|
||||||
router.push({
|
router.push({
|
||||||
path: '/backend/login',
|
path: '/backend/login',
|
||||||
query: { redirect: route.fullPath }
|
query: { redirect: route.fullPath }
|
||||||
|
})
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasDownloaded.value) {
|
if (!hasDownloaded.value) {
|
||||||
alert('请先下载地图后再进行评分')
|
showErrorMessage('请先下载地图后再进行评分')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,6 +170,7 @@ const fetchMapDetail = async () => {
|
|||||||
map.value = await getMapDetail(route.params.id)
|
map.value = await getMapDetail(route.params.id)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取地图详情失败:', error)
|
console.error('获取地图详情失败:', error)
|
||||||
|
showErrorMessage('获取地图详情失败,请稍后重试')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,7 +185,7 @@ const formatDate = (dateString) => {
|
|||||||
// 提交评分
|
// 提交评分
|
||||||
const submitScores = async () => {
|
const submitScores = async () => {
|
||||||
if (mapScore.value === 0 || authorScore.value === 0) {
|
if (mapScore.value === 0 || authorScore.value === 0) {
|
||||||
alert('请为地图和作者都进行评分')
|
showErrorMessage('请为地图和作者都进行评分')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,10 +205,10 @@ const submitScores = async () => {
|
|||||||
mapComment.value = ''
|
mapComment.value = ''
|
||||||
authorComment.value = ''
|
authorComment.value = ''
|
||||||
|
|
||||||
alert('评分成功!')
|
showErrorMessage('评分成功!')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('评分失败:', error)
|
console.error('评分失败:', error)
|
||||||
alert('评分失败,请稍后重试')
|
showErrorMessage('评分失败,请稍后重试')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -380,6 +401,15 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 评分弹窗样式 */
|
/* 评分弹窗样式 */
|
||||||
|
.score-dialog {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
.dialog-overlay {
|
.dialog-overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
@ -387,7 +417,7 @@ onMounted(() => {
|
|||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background: rgba(0, 0, 0, 0.5);
|
background: rgba(0, 0, 0, 0.5);
|
||||||
z-index: 100;
|
z-index: 1001;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog-content {
|
.dialog-content {
|
||||||
@ -400,7 +430,7 @@ onMounted(() => {
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
z-index: 101;
|
z-index: 1002;
|
||||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -456,17 +486,6 @@ onMounted(() => {
|
|||||||
color: #ffd700;
|
color: #ffd700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-input {
|
|
||||||
width: 100%;
|
|
||||||
height: 80px;
|
|
||||||
padding: 8px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 4px;
|
|
||||||
resize: vertical;
|
|
||||||
font-size: 14px;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-footer {
|
.dialog-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
@ -515,9 +534,5 @@ onMounted(() => {
|
|||||||
.star {
|
.star {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-input {
|
|
||||||
height: 60px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
Loading…
x
Reference in New Issue
Block a user