This commit is contained in:
zhouwentao 2025-12-20 10:11:08 +08:00
parent 57f7937c2d
commit 256c34d5af
7 changed files with 174 additions and 18 deletions

View File

@ -1,5 +1,10 @@
<script setup lang="ts">
import { ref } from 'vue'
import { ref, onMounted, watch } from 'vue'
import { useScoreStore } from '~/stores/score'
import { type SaveScoreRequest } from '~/service/api/score'
import message from '~/utils/message'
const scoreStore = useScoreStore()
//
export interface ScoreFormData {
@ -25,6 +30,7 @@ const examType = ref('历史组')
const selectedElectives = ref<string[]>([])
const majorCategory = ref('')
const selectedSubMajors = ref<string[]>([])
const isSubmitting = ref(false)
// Score inputs
const scores = ref({
@ -54,6 +60,8 @@ const electiveOptions = [
{ label: '政治', value: '政治' },
{ label: '化学', value: '化学' },
{ label: '生物', value: '生物' },
{ label: '历史', value: '历史' },
{ label: '物理', value: '物理' },
]
const majorCategoryOptions = [
@ -194,7 +202,7 @@ function validateForm() {
}
// --- Submit ---
function handleSubmit() {
async function handleSubmit() {
if (validateForm()) {
//
const formData: ScoreFormData = {
@ -205,11 +213,60 @@ function handleSubmit() {
scores: { ...scores.value },
}
//
emit('confirm', formData)
// Transform to API request
const requestData: SaveScoreRequest = {
cognitioPolyclinic: examType.value === '历史组' ? '文科' : '理科',
subjectList: selectedElectives.value,
professionalCategory: majorCategory.value,
professionalCategoryChildren: selectedSubMajors.value,
professionalCategoryChildrenScore: {},
professionalScore: Number(scores.value.unified) || 0,
culturalScore: Number(scores.value.culture) || 0,
englishScore: Number(scores.value.english) || 0,
chineseScore: Number(scores.value.chinese) || 0,
province: '河南', // Default
}
try {
isSubmitting.value = true
await scoreStore.saveScore(requestData)
//
scoreStore.fetchScore()
message.success('保存成功')
//
emit('confirm', formData)
} catch (e: any) {
message.error(e.message || '保存失败')
} finally {
isSubmitting.value = false
}
}
}
// --- Init ---
function initForm() {
console.warn('initForm', scoreStore.scoreInfo)
if (scoreStore.scoreInfo) {
const info = scoreStore.scoreInfo
examType.value = info.cognitioPolyclinic === '文科' ? '历史组' : '物理组'
selectedElectives.value = info.subjectList || []
majorCategory.value = info.professionalCategory || ''
selectedSubMajors.value = info.professionalCategoryChildren || []
scores.value.unified = info.professionalScore?.toString() || ''
scores.value.culture = info.culturalScore?.toString() || ''
scores.value.chinese = info.chineseScore?.toString() || ''
scores.value.english = info.englishScore?.toString() || ''
}
}
onMounted(() => {
initForm()
})
watch(() => scoreStore.scoreInfo, () => {
initForm()
})
//
defineExpose({
resetForm: () => {
@ -333,8 +390,8 @@ defineExpose({
v-model="scores.culture"
type="number"
min="0"
max="300"
placeholder="0-300"
max="750"
placeholder="0-750"
class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<div v-if="errors.scores.culture" class="mt-2 text-sm text-red-600">
@ -377,7 +434,7 @@ defineExpose({
</div>
<!-- Submit Button -->
<button
<button :disabled="isSubmitting"
class="w-full rounded-full bg-blue-600 px-6 py-3 text-lg text-white font-semibold shadow-lg transition-colors hover:bg-blue-700 hover:shadow-xl"
@click="handleSubmit"
>

View File

@ -2,11 +2,14 @@
import type { ScoreFormData } from './ScoreForm.vue'
import { useWindowScroll } from '@vueuse/core' // 使 VueUse
import { ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import { useRoute, useRouter } from 'vue-router'
import { useUserStore } from '../stores/user'
import { useScoreStore } from '../stores/score'
import message from '~/utils/message'
import { logout as apiLogout } from '~/service/api/auth'
const userStore = useUserStore()
const scoreStore = useScoreStore()
const isLoginModalOpen = ref(false)
//
const isMobileMenuOpen = ref(false)
@ -259,14 +262,20 @@ function handleMobileLinkClick() {
</template>
<template v-else>
<div class="mb-3 flex items-center px-3">
<span class="border-r-2 border-gray-3 pr-2 text-sm text-gray-700 dark:text-gray-200">音乐类</span>
<div v-if="scoreStore.scoreInfo" class="mb-3 flex items-center px-3">
<span class="border-r-2 border-gray-3 pr-2 text-sm text-gray-700 dark:text-gray-200">{{ scoreStore.scoreInfo.professionalCategory || '未设置' }}</span>
<span class="border-r-2 border-gray-3 pl-2 pr-2 text-sm text-gray-700 dark:text-gray-200">文化成绩</span>
<span class="pl-2 pr-2 text-sm text-gray-700 dark:text-gray-200">334</span>
<span class="pl-2 pr-2 text-sm text-gray-700 dark:text-gray-200">{{ scoreStore.scoreInfo.culturalScore }}</span>
<a target="_blank" class="cursor-pointer text-gray-400 transition-colors hover:text-gray-500 dark:hover:text-gray-300" @click="openScoreFormModal">
<div i-carbon:edit class="text-xs" />
</a>
</div>
<div v-else class="mb-3 flex items-center px-3">
<span class="text-sm text-gray-700 dark:text-gray-200">未设置分数</span>
<a target="_blank" class="cursor-pointer text-gray-400 transition-colors hover:text-gray-500 dark:hover:text-gray-300" @click="openScoreFormModal">
<div i-carbon:edit class="text-xs" />
</a>
</div>
<div class="mb-3 flex items-center px-3">
<span>欢迎, {{ userStore.userInfo.username }}</span>
<button

View File

@ -12,13 +12,13 @@ export interface LoginResult {
}
export function login(params: LoginParams) {
return request.post<LoginResult>('/auth/login', params)
return request.post<LoginResult>('/user/auth/login', params)
}
export function logout() {
return request.post<null>('/auth/logout')
return request.post<null>('/user/auth/logout')
}
export function getUserInfo() {
return request.get<UserInfo>('/auth/info')
return request.get<UserInfo>('/user/auth/info')
}

39
src/service/api/score.ts Normal file
View File

@ -0,0 +1,39 @@
import request from '../request'
export interface SaveScoreRequest {
cognitioPolyclinic: string
subjectList: string[]
professionalCategory: string
professionalCategoryChildren: string[]
professionalCategoryChildrenScore: Record<string, number>
professionalScore: number
culturalScore: number
englishScore: number
chineseScore: number
province: string
}
export interface ScoreInfo {
id: string
type: string
educationalLevel: string
cognitioPolyclinic: string
subjectList: string[]
professionalCategory: string
professionalCategoryChildren: string[]
professionalCategoryChildrenScore: Record<string, number>
professionalScore: number
culturalScore: number
englishScore: number
chineseScore: number
province: string
state: string
}
export function saveScore(data: SaveScoreRequest) {
return request.post<ScoreInfo>('/user/score/save-score', data)
}
export function getScore() {
return request.get<ScoreInfo>('/user/score')
}

View File

@ -4,6 +4,7 @@ import CryptoJS from 'crypto-js'
import { useUserStore } from '~/stores/user'
import message from '~/utils/message'
import { InternalAxiosRequestConfig } from 'axios'
interface CustomRequestConfig extends InternalAxiosRequestConfig {
showLoading?: boolean,
showError?: boolean
@ -81,7 +82,10 @@ class Request {
case 401:
msg = backendMsg || 'Unauthorized'
const userStore = useUserStore()
userStore.logout()
userStore.clearToken()
userStore.clearUserInfo()
window.location.href= '/'
// userStore.logout()
// router push login?
break
case 403:
@ -98,9 +102,9 @@ class Request {
}
}
if (config?.showError !== false) {
message.error(msg)
}
// if (config?.showError !== false) {
// message.error(msg)
// }
// 返回包含具体错误信息的 Error 对象
return Promise.reject(new Error(msg))
}

45
src/stores/score.ts Normal file
View File

@ -0,0 +1,45 @@
import { acceptHMRUpdate, defineStore } from 'pinia'
import { ref } from 'vue'
import { getScore, saveScore as apiSaveScore, type SaveScoreRequest, type ScoreInfo } from '~/service/api/score'
export const useScoreStore = defineStore('score', () => {
const scoreInfo = ref<ScoreInfo | null>(null)
async function fetchScore() {
try {
const data = await getScore()
scoreInfo.value = data
return data
} catch (error) {
// If error occurs (e.g. 404 not found if user hasn't set score), we might want to handle it.
// For now, just throw or log.
console.error('Failed to fetch score', error)
throw error
}
}
async function saveScore(data: SaveScoreRequest) {
try {
const response = await apiSaveScore(data)
scoreInfo.value = response
return response
} catch (error) {
console.error('Failed to save score', error)
throw error
}
}
function clearScore() {
scoreInfo.value = null
}
return {
scoreInfo,
fetchScore,
saveScore,
clearScore,
}
})
if (import.meta.hot)
import.meta.hot.accept(acceptHMRUpdate(useScoreStore as any, import.meta.hot))

View File

@ -81,6 +81,8 @@ export const useUserStore = defineStore('user', () => {
userInfo,
setToken,
setUserInfo,
clearToken,
clearUserInfo,
login,
logout,
}