如果vue使用redis会报出Uncaught ReferenceError: Buffer is not defined错误,但是使用在node.js中就没问题

This commit is contained in:
Kunagisa 2025-06-22 17:25:33 +08:00
parent ce92712a30
commit d5ee474b09
13 changed files with 444 additions and 92 deletions

9
package-lock.json generated
View File

@ -9,6 +9,7 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"axios": "^1.9.0", "axios": "^1.9.0",
"process": "^0.11.10",
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-router": "^4.5.1", "vue-router": "^4.5.1",
"vue-tournament-bracket": "^3.0.0", "vue-tournament-bracket": "^3.0.0",
@ -2472,6 +2473,14 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/process": {
"version": "0.11.10",
"resolved": "https://registry.npmmirror.com/process/-/process-0.11.10.tgz",
"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/proxy-from-env": { "node_modules/proxy-from-env": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz",

View File

@ -10,6 +10,7 @@
}, },
"dependencies": { "dependencies": {
"axios": "^1.9.0", "axios": "^1.9.0",
"process": "^0.11.10",
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-router": "^4.5.1", "vue-router": "^4.5.1",
"vue-tournament-bracket": "^3.0.0", "vue-tournament-bracket": "^3.0.0",

View File

@ -12,3 +12,83 @@ export const getMapEditors = async () => {
throw error; throw error;
} }
}; };
/**
* 用户提交地图评分
* @param {string} mapName - 地图名称
* @param {number} reward - 评分(1~5)
* @returns {Promise<any>} 返回提交评分的响应数据
*/
export const submitMapReward = async (mapName, reward) => {
try {
const payload = {
mapname: mapName,
reward: reward
};
const response = await axiosInstance.post('/user/map/reward', payload);
return response.data;
} catch (error) {
console.error('提交地图评分失败:', error);
throw error;
}
};
/**
* 获取用户地图信息
* @param {string} mapName - 地图名称
* @returns {Promise<any>} 返回用户地图信息的Promise对象
*/
export const getUserMapInfo = async (mapName) => {
try {
const response = await axiosInstance.get(`/user/map/${mapName}`);
return response.data;
} catch (error) {
console.error('获取用户地图信息失败:', error);
throw error;
}
};
/**
* 更新用户下载地图信息
* @param {string} mapName - 地图名称
* @returns {Promise<any>} 返回更新下载信息的响应数据
*/
export const updateUserDownloadMapInfo = async (mapName) => {
try {
const response = await axiosInstance.post(`/map/download/${mapName}`);
return response.data;
} catch (error) {
console.error('更新用户下载地图信息失败:', error);
throw error;
}
};
/**
* 获取指定地图评分
* @param {string} mapName - 地图名称
* @returns {Promise<any>} 返回一个包含指定地图评分信息的Promise对象
*/
export const getAppointMapRating = async (mapName) => {
try {
const response = await axiosInstance.get(`/map/reward/${mapName}`);
return response.data;
} catch (error) {
console.error('获取指定地图评分失败:', error);
throw error;
}
};
/**
* 获取所有地图的评分
* @returns {Promise<any>} 返回一个包含所有地图评分信息的Promise对象
*/
export const getAllMapRating = async () => {
try {
const response = await axiosInstance.get(`/map/reward`);
return response.data;
} catch (error) {
console.error('获取所有地图评分失败:', error);
throw error;
}
};

View File

@ -81,7 +81,7 @@ export const userLogin = async (username, password, token, captcha) => {
}); });
if (response.data.access_token) { if (response.data.access_token) {
loginSuccess(response.data.access_token); loginSuccess(response.data.access_token, username);
} }
return response.data; return response.data;

View File

@ -0,0 +1,128 @@
<template>
<div v-if="visible" class="success-dialog-overlay" @click.self="handleClose">
<div class="success-dialog">
<div class="success-dialog-content">
<div class="success-icon"></div>
<div class="success-message">{{ message }}</div>
</div>
<div class="success-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
},
message: {
type: String,
required: true
}
})
const emit = defineEmits(['close'])
const handleClose = () => {
emit('close')
}
</script>
<style scoped>
.success-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;
}
.success-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;
}
.success-dialog-content {
padding: 20px;
text-align: center;
}
.success-icon {
font-size: 48px;
color: #67c23a;
margin-bottom: 16px;
font-weight: bold;
}
.success-message {
color: #606266;
font-size: 16px;
line-height: 1.5;
}
.success-dialog-footer {
padding: 10px 20px 20px;
text-align: center;
}
.confirm-button {
background-color: #67c23a;
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: #85ce61;
}
@keyframes dialog-fade-in {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 移动端适配 */
@media screen and (max-width: 480px) {
.success-dialog {
width: 85%;
}
.success-icon {
font-size: 40px;
}
.success-message {
font-size: 14px;
padding: 16px;
}
.confirm-button {
padding: 6px 20px;
font-size: 13px;
}
}
</style>

View File

@ -45,7 +45,7 @@
<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('register')">注册账号</a> <a @click.prevent="$emit('register')">注册账号</a>
@ -83,16 +83,6 @@ const showError = ref(false)
const errorTitle = ref('错误提示') const errorTitle = ref('错误提示')
const errorMessage = ref('') const errorMessage = ref('')
const isFormValid = computed(() => {
return !usernameError.value &&
!passwordError.value &&
!captchaError.value &&
username.value &&
password.value &&
captcha.value
})
const showErrorMessage = (message, title = '错误提示') => { const showErrorMessage = (message, title = '错误提示') => {
errorMessage.value = message errorMessage.value = message
errorTitle.value = title errorTitle.value = title
@ -108,7 +98,13 @@ const refreshCaptcha = async () => {
captcha.value = '' // captcha.value = '' //
} catch (error) { } catch (error) {
console.error('获取验证码失败:', error) console.error('获取验证码失败:', error)
showErrorMessage('获取验证码失败,请刷新页面重试') // 使APIdetail
const apiError = error.response?.data?.detail || error.response?.data?.message
if (apiError) {
showErrorMessage(apiError)
} else {
showErrorMessage('获取验证码失败,请刷新页面重试')
}
} }
} }
@ -128,23 +124,18 @@ const handleLogin = async () => {
captcha.value captcha.value
) )
// // APIloginSuccess
if (response.access_token) { // localStorage
localStorage.setItem('access_token', response.access_token) console.log('登录成功')
localStorage.setItem('user_id', username.value)
router.push('/')
} else {
throw new Error('登录失败:未获取到访问令牌')
}
} catch (error) { } catch (error) {
console.error('登录失败:', error) console.error('登录失败:', error)
if (error.response) { if (error.response) {
switch (error.response.status) { // 使APIdetail
case 401: const apiError = error.response?.data?.detail || error.response?.data?.message
showErrorMessage('错误的用户名或密码') if (apiError) {
break showErrorMessage(apiError)
default: } else {
showErrorMessage(error.response?.data?.message || error.message || '登录失败,请稍后重试') showErrorMessage('登录失败,请稍后重试')
} }
} else { } else {
showErrorMessage(error.message || '登录失败,请稍后重试') showErrorMessage(error.message || '登录失败,请稍后重试')
@ -165,7 +156,8 @@ const validateForm = () => {
usernameError.value = '请输入QQ号码' usernameError.value = '请输入QQ号码'
return false return false
} }
if (!/^[/\d/g]+$/.test(username.value)) { //
if (!/^\d+$/.test(username.value)) {
usernameError.value = 'QQ号码只能包含数字' usernameError.value = 'QQ号码只能包含数字'
return false return false
} }
@ -201,6 +193,22 @@ const validateForm = () => {
return true return true
} }
//
const resetForm = () => {
username.value = ''
password.value = ''
captcha.value = ''
usernameError.value = ''
passwordError.value = ''
captchaError.value = ''
refreshCaptcha()
}
// resetForm
defineExpose({
resetForm
})
onMounted(() => { onMounted(() => {
refreshCaptcha() refreshCaptcha()
}) })

View File

@ -53,6 +53,11 @@
:message="errorMessage" :message="errorMessage"
@close="showError = false" @close="showError = false"
/> />
<SuccessDialog
:visible="showSuccess"
:message="successMessage"
@close="showSuccess = false"
/>
</div> </div>
</template> </template>
@ -61,6 +66,7 @@ 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' import ErrorDialog from './ErrorDialog.vue'
import SuccessDialog from './SuccessDialog.vue'
const router = useRouter() const router = useRouter()
const username = ref('') const username = ref('')
@ -84,12 +90,21 @@ const showError = ref(false)
const errorTitle = ref('错误提示') const errorTitle = ref('错误提示')
const errorMessage = ref('') const errorMessage = ref('')
//
const showSuccess = ref(false)
const successMessage = ref('')
const showErrorMessage = (message, title = '错误提示') => { const showErrorMessage = (message, title = '错误提示') => {
errorMessage.value = message errorMessage.value = message
errorTitle.value = title errorTitle.value = title
showError.value = true showError.value = true
} }
const showSuccessMessage = (message) => {
successMessage.value = message
showSuccess.value = true
}
const refreshCaptcha = async () => { const refreshCaptcha = async () => {
try { try {
const response = await getCaptcha() const response = await getCaptcha()
@ -98,7 +113,13 @@ const refreshCaptcha = async () => {
captcha.value = '' // captcha.value = '' //
} catch (error) { } catch (error) {
console.error('获取验证码失败:', error) console.error('获取验证码失败:', error)
showErrorMessage('获取验证码失败,请刷新页面重试') // 使APIdetail
const apiError = error.response?.data?.detail || error.response?.data?.message
if (apiError) {
showErrorMessage(apiError)
} else {
showErrorMessage('获取验证码失败,请刷新页面重试')
}
} }
} }
@ -121,21 +142,31 @@ const handleRegister = async () => {
captcha.value captcha.value
) )
showErrorMessage('注册成功', '提示') //
// showSuccessMessage('注册成功!正在切换到登录页面...')
emit('login')
//
username.value = ''
password.value = ''
confirmPassword.value = ''
captcha.value = ''
//
refreshCaptcha()
// 1.5
setTimeout(() => {
emit('login')
}, 1500)
} catch (error) { } catch (error) {
console.error('注册失败:', error) console.error('注册失败:', error)
if (error.response) { if (error.response) {
switch (error.response.status) { // 使APIdetail
case 400: const apiError = error.response?.data?.detail || error.response?.data?.message
showErrorMessage('该QQ号已被注册') if (apiError) {
break showErrorMessage(apiError)
case 409: } else {
showErrorMessage('用户名已存在') showErrorMessage('注册失败,请稍后重试')
break
default:
showErrorMessage(error.response?.data?.message || error.message || '注册失败,请稍后重试')
} }
} else { } else {
showErrorMessage(error.message || '注册失败,请稍后重试') showErrorMessage(error.message || '注册失败,请稍后重试')
@ -281,7 +312,7 @@ const validateForm = () => {
transition: background 0.2s; transition: background 0.2s;
} }
.login-button button:hover:not(:disabled) { .login-button button:hover {
background: linear-gradient(90deg, #66b1ff 0%, #6dd5fa 100%); background: linear-gradient(90deg, #66b1ff 0%, #6dd5fa 100%);
} }

View File

@ -72,16 +72,24 @@ export const logoutUser = () => { // 不再是 async因为它不执行异步
* @param {string} userId - 用户ID * @param {string} userId - 用户ID
*/ */
export const loginSuccess = (accessToken, userId) => { export const loginSuccess = (accessToken, userId) => {
// console.log('jwt.js: loginSuccess. Storing token and user ID.'); // 清除可能存在的旧数据
localStorage.removeItem('access_token');
localStorage.removeItem('user_id');
sessionStorage.removeItem('currentUser');
// 存储新的认证信息
localStorage.setItem('access_token', accessToken); localStorage.setItem('access_token', accessToken);
if (userId) { if (userId) {
localStorage.setItem('user_id', userId); localStorage.setItem('user_id', userId);
} }
justLoggedIn.value = true; // Set the flag before navigation // 设置登录标志
justLoggedIn.value = true;
// 登录成功后重定向 // 获取重定向路径优先使用查询参数中的redirect否则跳转到首页
// 尝试获取之前尝试访问的路径,否则重定向到默认路径(例如仪表盘) const currentRoute = router.currentRoute.value;
const redirectPath = router.currentRoute.value.query.redirect || '/backend/dashboard'; const redirectPath = currentRoute.query.redirect || '/';
// 使用replace而不是push避免在历史记录中留下登录页面
router.replace(redirectPath); router.replace(redirectPath);
}; };

View File

@ -11,16 +11,17 @@
返回主界面 返回主界面
</button> </button>
<div class="form-content"> <div class="form-content">
<LoginModule v-if="!showRegister" @register="showRegister = true" /> <LoginModule ref="loginModuleRef" v-if="!showRegister" @register="showRegister = true" />
<RegisterModule v-else @login="showRegister = false" /> <RegisterModule v-else @login="handleSwitchToLogin" />
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue' import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { hasValidToken } from '@/utils/jwt'
import loginBg from '@/assets/login_1.jpg' import loginBg from '@/assets/login_1.jpg'
import loginBg1 from '@/assets/login_2.jpg' import loginBg1 from '@/assets/login_2.jpg'
import loginBg3 from '@/assets/login_3.jpg' import loginBg3 from '@/assets/login_3.jpg'
@ -33,10 +34,29 @@ const bgImg = ref(images[randomIndex])
const router = useRouter() const router = useRouter()
const showRegister = ref(false) const showRegister = ref(false)
const loginModuleRef = ref(null)
const handleBack = () => { const handleBack = () => {
router.push('/') router.push('/')
} }
//
const handleSwitchToLogin = () => {
showRegister.value = false
// tick
setTimeout(() => {
if (loginModuleRef.value) {
loginModuleRef.value.resetForm()
}
}, 0)
}
//
onMounted(() => {
if (hasValidToken()) {
router.replace('/')
}
})
</script> </script>
<style scoped> <style scoped>

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref, onMounted } from 'vue' import { computed, ref, onMounted, onUnmounted } from 'vue'
import { getUserInfo } from '@/utils/jwt' import { getUserInfo } from '@/utils/jwt'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
@ -18,6 +18,7 @@ const isAdmin = computed(() => {
const showMobileMenu = ref(false) const showMobileMenu = ref(false)
const currentUserData = ref(null) const currentUserData = ref(null)
const showDropdown = ref(false) const showDropdown = ref(false)
const userInfoNavRef = ref(null)
const router = useRouter() const router = useRouter()
const userAvatarUrl = computed(() => { const userAvatarUrl = computed(() => {
@ -38,6 +39,16 @@ const handleLogout = () => {
router.push('/') router.push('/')
} }
const handleClickOutside = (event: Event) => {
if (userInfoNavRef.value && !userInfoNavRef.value.contains(event.target as Node)) {
showDropdown.value = false
}
}
const toggleDropdown = () => {
showDropdown.value = !showDropdown.value
}
onMounted(() => { onMounted(() => {
if (localStorage.getItem('access_token')) { if (localStorage.getItem('access_token')) {
getUserInfo().then(userInfo => { getUserInfo().then(userInfo => {
@ -48,6 +59,14 @@ onMounted(() => {
} }
}) })
} }
//
document.addEventListener('click', handleClickOutside)
})
onUnmounted(() => {
//
document.removeEventListener('click', handleClickOutside)
}) })
</script> </script>
@ -72,16 +91,16 @@ onMounted(() => {
<i class="fas fa-user"></i> <i class="fas fa-user"></i>
登录 登录
</router-link> </router-link>
<div v-if="isLoggedIn && currentUserData" class="user-info-nav" @click="showDropdown = !showDropdown"> <div v-if="isLoggedIn && currentUserData" class="user-info-nav" ref="userInfoNavRef" @click="toggleDropdown">
<img v-if="userAvatarUrl" :src="userAvatarUrl" alt="User Avatar" class="nav-avatar" /> <img v-if="userAvatarUrl" :src="userAvatarUrl" alt="User Avatar" class="nav-avatar" />
<span class="nav-username">{{ currentUserData.username }}</span> <span class="nav-username">{{ currentUserData.username }}</span>
<i class="fas fa-chevron-down dropdown-icon"></i> <i class="fas fa-chevron-down dropdown-icon"></i>
<div v-show="showDropdown" class="dropdown-menu"> <div v-show="showDropdown" class="dropdown-menu">
<div v-if="isAdmin" class="dropdown-item" @click="router.push('/backend/dashboard'); showDropdown = false"> <div v-if="isAdmin" class="dropdown-item" @click.stop="router.push('/backend/dashboard'); showDropdown = false">
<i class="fas fa-cog"></i> <i class="fas fa-cog"></i>
管理后台 管理后台
</div> </div>
<div class="dropdown-item" @click="handleLogout"> <div class="dropdown-item" @click.stop="handleLogout">
<i class="fas fa-sign-out-alt"></i> <i class="fas fa-sign-out-alt"></i>
退出登录 退出登录
</div> </div>

View File

@ -77,6 +77,10 @@
v-model="addForm.author_contact" v-model="addForm.author_contact"
class="input" class="input"
placeholder="请输入您的QQ号" placeholder="请输入您的QQ号"
type="text"
pattern="[0-9]*"
inputmode="numeric"
readonly
required required
/> />
</div> </div>
@ -388,10 +392,12 @@ const closeReplyModal = () => {
// //
const resetReplyForm = () => { const resetReplyForm = () => {
// qq_code
const user = getStoredUser()
addForm.value = { addForm.value = {
sendcontent: '', sendcontent: '',
author: '', author: '',
author_contact: '', author_contact: user && user.qq_code ? user.qq_code : '',
replyTo: '' replyTo: ''
}; };
addError.value = ''; addError.value = '';
@ -496,11 +502,6 @@ const submitReply = async () => {
addError.value = 'QQ号不能为空'; addError.value = 'QQ号不能为空';
return; return;
} }
// QQ
if (!/^\d+$/.test(addForm.value.author_contact)) {
addError.value = 'QQ号必须为纯数字';
return;
}
addLoading.value = true; addLoading.value = true;
addError.value = ''; addError.value = '';

View File

@ -182,6 +182,8 @@ const fetchAuthorMaps = async () => {
return return
} }
authorName.value = author authorName.value = author
// 使API
const cacheKey = 'allMaps' const cacheKey = 'allMaps'
let allMaps = [] let allMaps = []
const cached = sessionStorage.getItem(cacheKey) const cached = sessionStorage.getItem(cacheKey)

View File

@ -14,7 +14,6 @@
<div class="map-image"> <div class="map-image">
<img :src="map.img_file" :alt="map.chinese_name"> <img :src="map.img_file" :alt="map.chinese_name">
</div> </div>
<div class="map-info"> <div class="map-info">
<div class="info-grid"> <div class="info-grid">
<div class="info-item"> <div class="info-item">
@ -33,6 +32,10 @@
<span class="info-label">创建时间</span> <span class="info-label">创建时间</span>
<span class="info-value">{{ formatDate(map.create_time) }}</span> <span class="info-value">{{ formatDate(map.create_time) }}</span>
</div> </div>
<div>
<span class="info-label">地图评分</span>
<span class="info-value">{{ mapRating ? mapRating.toFixed(1) : '暂无评分' }}</span>
</div>
</div> </div>
<div class="tags"> <div class="tags">
@ -44,7 +47,13 @@
<div class="actions"> <div class="actions">
<a :href="map.zip_file" class="download-btn" download @click="handleDownload">下载地图</a> <a :href="map.zip_file" class="download-btn" download @click="handleDownload">下载地图</a>
<a class="score-btn" @click="handleScoreClick">地图评分</a> <a
class="score-btn"
:class="{ 'disabled': userMapInfo && userMapInfo.is_rewarded }"
@click="handleScoreClick"
>
{{ userMapInfo && userMapInfo.is_rewarded ? '已评分' : '地图评分' }}
</a>
</div> </div>
</div> </div>
</div> </div>
@ -70,19 +79,6 @@
></span> ></span>
</div> </div>
</div> </div>
<div class="score-section">
<h3>作者评分</h3>
<div class="rating">
<span
v-for="star in 5"
:key="'author-'+star"
class="star"
:class="{ active: star <= authorScore }"
@click="authorScore = star"
></span>
</div>
</div>
</div> </div>
<div class="dialog-footer"> <div class="dialog-footer">
<button class="cancel-btn" @click="showScoreDialog = false">取消</button> <button class="cancel-btn" @click="showScoreDialog = false">取消</button>
@ -104,12 +100,14 @@
import { ref, onMounted } from 'vue' 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 { submitMapReward, updateUserDownloadMapInfo, getUserMapInfo, getAppointMapRating } from '../../api/centre_maps.js'
import { hasValidToken } from '../../utils/jwt' import { hasValidToken } from '../../utils/jwt'
import ErrorDialog from '@/components/ErrorDialog.vue' 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 mapRating = ref(null)
// //
const showError = ref(false) const showError = ref(false)
@ -135,18 +133,52 @@ const handleErrorClose = () => {
// //
const showScoreDialog = ref(false) const showScoreDialog = ref(false)
const mapScore = ref(0) const mapScore = ref(0)
const authorScore = ref(0)
const mapComment = ref('')
const authorComment = ref('')
const hasDownloaded = ref(false) const hasDownloaded = ref(false)
const userMapInfo = ref(null)
//
const fetchMapRating = async () => {
try {
const ratingData = await getAppointMapRating(map.value.name)
mapRating.value = ratingData.rewards
} catch (error) {
console.error('获取地图评分失败:', error)
}
}
// //
const handleDownload = () => { const handleDownload = async () => {
hasDownloaded.value = true try {
await updateUserDownloadMapInfo(map.value.name)
hasDownloaded.value = true
//
await fetchUserMapInfo()
} catch (error) {
console.error('更新下载信息失败:', error)
// 使
hasDownloaded.value = true
}
}
//
const fetchUserMapInfo = async () => {
if (!hasValidToken()) return
try {
userMapInfo.value = await getUserMapInfo(map.value.name)
hasDownloaded.value = userMapInfo.value.is_download
} catch (error) {
console.error('获取用户地图信息失败:', error)
}
} }
// //
const handleScoreClick = () => { const handleScoreClick = () => {
//
if (userMapInfo.value && userMapInfo.value.is_rewarded) {
return
}
if (!hasValidToken()) { if (!hasValidToken()) {
showErrorMessage('请先登录后再进行评分', '', () => { showErrorMessage('请先登录后再进行评分', '', () => {
router.push({ router.push({
@ -168,6 +200,11 @@ const handleScoreClick = () => {
const fetchMapDetail = async () => { const fetchMapDetail = async () => {
try { try {
map.value = await getMapDetail(route.params.id) map.value = await getMapDetail(route.params.id)
//
await Promise.all([
fetchUserMapInfo(),
fetchMapRating()
])
} catch (error) { } catch (error) {
console.error('获取地图详情失败:', error) console.error('获取地图详情失败:', error)
showErrorMessage('获取地图详情失败,请稍后重试') showErrorMessage('获取地图详情失败,请稍后重试')
@ -184,26 +221,24 @@ const formatDate = (dateString) => {
// //
const submitScores = async () => { const submitScores = async () => {
if (mapScore.value === 0 || authorScore.value === 0) { if (mapScore.value === 0) {
showErrorMessage('请为地图和作者都进行评分') showErrorMessage('请为地图进行评分')
return return
} }
try { try {
// TODO: API // API
console.log('提交评分:', { await submitMapReward(map.value.name, mapScore.value)
mapScore: mapScore.value,
authorScore: authorScore.value,
mapComment: mapComment.value,
authorComment: authorComment.value
})
// //
showScoreDialog.value = false showScoreDialog.value = false
mapScore.value = 0 mapScore.value = 0
authorScore.value = 0
mapComment.value = '' //
authorComment.value = '' await Promise.all([
fetchUserMapInfo(),
fetchMapRating()
])
showErrorMessage('评分成功!') showErrorMessage('评分成功!')
} catch (error) { } catch (error) {
@ -380,6 +415,16 @@ onMounted(() => {
background: #6c8bb9; background: #6c8bb9;
} }
.score-btn.disabled {
background: #ccc;
cursor: not-allowed;
opacity: 0.6;
}
.score-btn.disabled:hover {
background: #ccc;
}
.actions { .actions {
display: flex; display: flex;
flex-direction: column; flex-direction: column;