feat: 迁移自定义功能至 1.5.9.9 版本

包含音乐源限制、CI 防 OOM 优化、数据库连接池优化、管理端权限及 UI 细节定制等功能
This commit is contained in:
2026-09-12 19:26:42 +08:00
parent 34f0679117
commit 1106aeea6c
161 changed files with 18963 additions and 27900 deletions
+159 -232
View File
@@ -3,14 +3,14 @@
<!-- 顶部标题栏 -->
<div class="flex flex-col md:flex-row md:items-end justify-between gap-6">
<div>
<h2 class="text-2xl font-black text-text-primary tracking-tight">{{ locale.title }}</h2>
<p class="text-xs text-text-tertiary mt-1">
{{ locale.desc }}
<h2 class="text-2xl font-black text-zinc-100 tracking-tight">学期管理</h2>
<p class="text-xs text-zinc-500 mt-1">
定义系统的教学周期,点歌、排期及统计数据将基于当前活跃学期进行归档
</p>
</div>
<button
class="flex items-center gap-2 px-6 py-2.5 bg-primary-hover hover:bg-primary text-text-primary text-xs font-black rounded-xl shadow-lg shadow-[var(--primary-glow)] transition-all active:scale-95"
@click="openModal"
class="flex items-center gap-2 px-6 py-2.5 bg-blue-600 hover:bg-blue-500 text-white text-xs font-black rounded-xl shadow-lg shadow-blue-900/20 transition-all active:scale-95"
@click="openAddModal"
>
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -26,7 +26,7 @@
<line x1="12" y1="5" x2="12" y2="19" />
<line x1="5" y1="12" x2="19" y2="12" />
</svg>
{{ locale.add }}
添加新学期
</button>
</div>
@@ -44,24 +44,24 @@
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="text-warning fill-warning"
class="text-amber-500 fill-amber-500"
>
<polygon
points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"
/>
</svg>
<h3 class="text-[10px] font-black text-text-disabled uppercase tracking-[0.2em]">
{{ locale.currentActive }}
<h3 class="text-[10px] font-black text-zinc-600 uppercase tracking-[0.2em]">
当前活跃学期
</h3>
</div>
<div
v-if="currentSemester"
class="relative bg-gradient-to-br from-primary to-primary-hover rounded-3xl p-10 shadow-2xl shadow-[var(--primary-glow)] overflow-hidden group"
class="relative bg-gradient-to-br from-blue-600 to-blue-700 rounded-3xl p-10 shadow-2xl shadow-blue-900/20 overflow-hidden group"
>
<div class="relative z-10 space-y-6">
<div
class="w-16 h-16 rounded-3xl bg-bg-secondary-10 backdrop-blur-md flex items-center justify-center text-text-primary border border-primary-20"
class="w-16 h-16 rounded-3xl bg-white/10 backdrop-blur-md flex items-center justify-center text-white border border-white/20"
>
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -79,10 +79,10 @@
</svg>
</div>
<div class="space-y-2">
<h4 class="text-3xl font-black text-text-primary tracking-tight leading-tight">
<h4 class="text-3xl font-black text-white tracking-tight leading-tight">
{{ currentSemester.name }}
</h4>
<div class="flex items-center gap-2 text-text-primary-80 font-bold text-xs">
<div class="flex items-center gap-2 text-blue-100/60 font-bold text-xs">
<svg
xmlns="http://www.w3.org/2000/svg"
width="12"
@@ -99,20 +99,20 @@
<line x1="8" y1="2" x2="8" y2="6" />
<line x1="3" y1="10" x2="21" y2="10" />
</svg>
{{ getLocaleMessage('createdAt', formatDate(currentSemester.createdAt)) }}
创建于 {{ formatDate(currentSemester.createdAt) }}
</div>
</div>
<div class="pt-4">
<span
class="px-4 py-2 bg-bg-secondary-20 text-text-primary text-[10px] font-black uppercase tracking-widest rounded-xl shadow-lg"
class="px-4 py-2 bg-white text-blue-600 text-[10px] font-black uppercase tracking-widest rounded-xl shadow-lg"
>
{{ locale.inProgress }}
正在进行中
</span>
</div>
</div>
<!-- 装饰性背景元素 -->
<div class="absolute -right-10 -bottom-10 w-64 h-64 bg-bg-secondary-5 blur-3xl rounded-full" />
<div class="absolute -right-10 -bottom-10 w-64 h-64 bg-white/5 blur-3xl rounded-full" />
<div class="absolute top-10 right-10 opacity-10">
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -124,7 +124,7 @@
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="text-text-primary"
class="text-white"
>
<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" />
<path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" />
@@ -134,7 +134,7 @@
<div
v-else
class="bg-bg-secondary-30 border border-border-secondary border-dashed rounded-3xl p-20 flex flex-col items-center justify-center text-center gap-4"
class="bg-zinc-900/30 border border-zinc-800 border-dashed rounded-3xl p-20 flex flex-col items-center justify-center text-center gap-4"
>
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -146,19 +146,19 @@
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="text-text-secondary"
class="text-zinc-700"
>
<circle cx="12" cy="12" r="10" />
<line x1="12" y1="8" x2="12" y2="12" />
<line x1="12" y1="16" x2="12.01" y2="16" />
</svg>
<p class="text-sm font-bold text-text-disabled">{{ locale.noCurrent }}</p>
<button class="text-xs text-primary font-black hover:underline" @click="openModal">
{{ locale.createNow }}
<p class="text-sm font-bold text-zinc-600">未设置当前活跃学期</p>
<button class="text-xs text-blue-500 font-black hover:underline" @click="openAddModal">
立即创建
</button>
</div>
<div class="p-6 bg-bg-secondary-20 border border-border-secondary rounded-3xl flex items-start gap-4">
<div class="p-6 bg-zinc-900/20 border border-zinc-800 rounded-3xl flex items-start gap-4">
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
@@ -169,16 +169,16 @@
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="text-text-tertiary shrink-0 mt-0.5"
class="text-zinc-500 shrink-0 mt-0.5"
>
<circle cx="12" cy="12" r="10" />
<line x1="12" y1="16" x2="12" y2="12" />
<line x1="12" y1="8" x2="12.01" y2="8" />
</svg>
<div class="space-y-1">
<p class="text-xs font-bold text-text-tertiary">{{ locale.switchTitle }}</p>
<p class="text-[11px] text-text-disabled leading-relaxed">
{{ locale.switchDesc }}
<p class="text-xs font-bold text-zinc-400">学期切换说明</p>
<p class="text-[11px] text-zinc-600 leading-relaxed">
切换学期后,前端点歌界面将清空旧排期。所有历史数据会自动归档至对应学期下,您仍可在数据中心进行查看。
</p>
</div>
</div>
@@ -198,25 +198,25 @@
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="text-text-disabled"
class="text-zinc-600"
>
<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
<path d="M3 3v5h5" />
<path d="M12 7v5l4 2" />
</svg>
<h3 class="text-[10px] font-black text-text-disabled uppercase tracking-[0.2em]">
{{ locale.historyTitle }}
<h3 class="text-[10px] font-black text-zinc-600 uppercase tracking-[0.2em]">
学期历史记录
</h3>
</div>
<span class="text-[10px] font-black text-text-secondary">{{ getLocaleMessage('historyCount', semesters.length) }}</span>
<span class="text-[10px] font-black text-zinc-700">共 {{ semesters.length }} 条</span>
</div>
<div class="space-y-4">
<div
v-if="loading && semesters.length === 0"
class="text-center py-20 bg-bg-secondary-10 border border-border-secondary border-dashed rounded-[2rem]"
class="text-center py-20 bg-zinc-900/10 border border-zinc-800 border-dashed rounded-[2rem]"
>
<p class="text-xs font-bold text-text-disabled">{{ locale.loading }}</p>
<p class="text-xs font-bold text-zinc-600">加载中...</p>
</div>
<div
@@ -225,8 +225,8 @@
class="group flex items-center justify-between p-6 rounded-[2rem] border transition-all"
:class="
sem.isActive
? 'bg-primary-hover-5 border-primary-30'
: 'bg-bg-secondary-40 border-border-secondary hover:border-border-tertiary'
? 'bg-blue-600/5 border-blue-500/30'
: 'bg-zinc-900/40 border-zinc-800 hover:border-zinc-700'
"
>
<div class="flex items-center gap-5">
@@ -234,8 +234,8 @@
class="w-12 h-12 rounded-2xl flex items-center justify-center transition-all"
:class="
sem.isActive
? 'bg-primary-hover text-text-primary shadow-lg shadow-[var(--primary-glow)]'
: 'bg-bg-tertiary text-text-disabled border border-border-tertiary'
? 'bg-blue-600 text-white shadow-lg shadow-blue-900/20'
: 'bg-zinc-800 text-zinc-600 border border-zinc-700'
"
>
<svg
@@ -258,13 +258,13 @@
<h5
class="font-bold transition-colors"
:class="
sem.isActive ? 'text-primary' : 'text-text-primary group-hover:text-primary'
sem.isActive ? 'text-blue-400' : 'text-zinc-200 group-hover:text-blue-400'
"
>
{{ sem.name }}
</h5>
<p class="text-[10px] text-text-disabled font-medium uppercase tracking-widest mt-0.5">
{{ getLocaleMessage('createdAt', formatDate(sem.createdAt)) }}
<p class="text-[10px] text-zinc-600 font-medium uppercase tracking-widest mt-0.5">
创建于 {{ formatDate(sem.createdAt) }}
</p>
</div>
</div>
@@ -272,7 +272,7 @@
<div class="flex items-center gap-2">
<div
v-if="sem.isActive"
class="flex items-center gap-2 px-4 py-2 bg-success-10 text-success rounded-xl border border-success-20"
class="flex items-center gap-2 px-4 py-2 bg-emerald-500/10 text-emerald-500 rounded-xl border border-emerald-500/20"
>
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -288,108 +288,83 @@
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
<polyline points="22 4 12 14.01 9 11.01" />
</svg>
<span class="text-[10px] font-black uppercase tracking-widest">{{ locale.currentSemester }}</span>
<span class="text-[10px] font-black uppercase tracking-widest">当前学期</span>
</div>
<button
v-else
:disabled="loading"
class="flex items-center gap-2 px-4 py-2 bg-bg-tertiary hover:bg-bg-quaternary text-text-tertiary hover:text-text-primary text-[10px] font-black rounded-xl border border-border-tertiary transition-all uppercase tracking-widest disabled:opacity-50 disabled:cursor-not-allowed"
@click="setActive(sem.id)"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
<template v-else>
<button
:disabled="loading"
class="flex items-center gap-2 px-4 py-2 bg-zinc-800 hover:bg-zinc-700 text-zinc-400 hover:text-white text-[10px] font-black rounded-xl border border-zinc-700 transition-all uppercase tracking-widest disabled:opacity-50 disabled:cursor-not-allowed"
@click="setActive(sem.id)"
>
<line x1="5" y1="12" x2="19" y2="12" />
<polyline points="12 5 19 12 12 19" />
</svg>
{{ locale.setCurrent }}
</button>
<button
:disabled="loading"
class="p-2.5 text-text-secondary hover:text-primary hover:bg-primary-10 rounded-xl transition-all disabled:opacity-50 disabled:cursor-not-allowed"
:title="locale.editSemester"
@click="openEditModal(sem)"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
<svg
xmlns="http://www.w3.org/2000/svg"
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<line x1="5" y1="12" x2="19" y2="12" />
<polyline points="12 5 19 12 12 19" />
</svg>
设为当前
</button>
<button
:disabled="loading"
class="p-2.5 text-zinc-700 hover:text-red-500 hover:bg-red-500/10 rounded-xl transition-all disabled:opacity-50 disabled:cursor-not-allowed"
@click="deleteSemester(sem.id)"
>
<path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z" />
</svg>
</button>
<button
v-if="!sem.isActive"
:disabled="loading"
class="p-2.5 text-text-secondary hover:text-error hover:bg-error-10 rounded-xl transition-all disabled:opacity-50 disabled:cursor-not-allowed"
:title="locale.deleteSemester"
@click="deleteSemester(sem.id)"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="3 6 5 6 21 6" />
<path
d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"
/>
<line x1="10" y1="11" x2="10" y2="17" />
<line x1="14" y1="11" x2="14" y2="17" />
</svg>
</button>
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="3 6 5 6 21 6" />
<path
d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"
/>
<line x1="10" y1="11" x2="10" y2="17" />
<line x1="14" y1="11" x2="14" y2="17" />
</svg>
</button>
</template>
</div>
</div>
<div
v-if="semesters.length === 0 && !loading"
class="text-center py-20 bg-bg-secondary-10 border border-border-secondary border-dashed rounded-[2rem]"
class="text-center py-20 bg-zinc-900/10 border border-zinc-800 border-dashed rounded-[2rem]"
>
<p class="text-xs font-bold text-text-disabled">{{ locale.empty }}</p>
<p class="text-xs font-bold text-zinc-600">暂无学期数据</p>
</div>
</div>
</div>
</div>
<!-- 添加/修改学期弹窗 -->
<!-- 添加学期弹窗 -->
<div
v-if="showModal"
class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-bg-primary-60 backdrop-blur-sm"
v-if="showAddModal"
class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm"
>
<div
class="w-full max-w-md bg-bg-secondary rounded-3xl border border-border-secondary shadow-2xl overflow-hidden"
class="w-full max-w-md bg-zinc-900 rounded-3xl border border-zinc-800 shadow-2xl overflow-hidden"
@click.stop
>
<div class="p-8 space-y-6">
<div class="flex items-center justify-between">
<h3 class="text-xl font-black text-text-primary tracking-tight">
{{ isEditing ? locale.editSemester : locale.add }}
</h3>
<h3 class="text-xl font-black text-white tracking-tight">添加新学期</h3>
<button
class="p-2 text-text-tertiary hover:text-text-primary transition-colors"
@click="closeModal"
class="p-2 text-zinc-500 hover:text-white transition-colors"
@click="closeAddModal"
>
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -410,35 +385,35 @@
<div class="space-y-6">
<div class="space-y-2">
<label class="text-[10px] font-black text-text-disabled uppercase tracking-widest px-1"
>{{ locale.semesterName }}</label
<label class="text-[10px] font-black text-zinc-600 uppercase tracking-widest px-1"
>学期名称</label
>
<input
v-model="semesterForm.name"
v-model="newSemester.name"
type="text"
:placeholder="locale.namePlaceholder"
class="w-full bg-bg-primary border border-border-secondary rounded-2xl px-5 py-4 text-sm text-text-primary focus:outline-none focus:border-primary-30 font-bold transition-all"
placeholder="例如: 2025学年第一学期"
class="w-full bg-zinc-950 border border-zinc-800 rounded-2xl px-5 py-4 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30 font-bold transition-all"
>
</div>
<label v-if="!isEditing" class="flex items-center gap-3 cursor-pointer group px-1">
<label class="flex items-center gap-3 cursor-pointer group px-1">
<input
v-model="semesterForm.isActive"
v-model="newSemester.isActive"
type="checkbox"
class="w-4.5 h-4.5 rounded-lg border-border-secondary bg-bg-primary transition-all"
class="w-4.5 h-4.5 rounded-lg border-zinc-800 bg-zinc-950 accent-blue-600 transition-all"
>
<div>
<span
class="text-xs font-bold text-text-secondary group-hover:text-primary transition-colors"
>{{ locale.setAsActive }}</span
class="text-xs font-bold text-zinc-300 group-hover:text-blue-400 transition-colors"
>设为当前活跃学期</span
>
<p class="text-[10px] text-text-disabled font-medium">
{{ locale.setAsActiveHint }}
<p class="text-[10px] text-zinc-600 font-medium">
创建后将立即激活此学期,原学期将变为历史状态
</p>
</div>
</label>
<div class="p-4 bg-bg-primary border border-border-secondary rounded-2xl flex items-start gap-3">
<div class="p-4 bg-zinc-950 border border-zinc-800 rounded-2xl flex items-start gap-3">
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
@@ -449,32 +424,32 @@
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="text-warning shrink-0 mt-0.5"
class="text-amber-500 shrink-0 mt-0.5"
>
<circle cx="12" cy="12" r="10" />
<line x1="12" y1="8" x2="12" y2="12" />
<line x1="12" y1="16" x2="12.01" y2="16" />
</svg>
<p class="text-[10px] text-text-tertiary leading-normal">
{{ locale.namingTip }}
<p class="text-[10px] text-zinc-500 leading-normal">
提示:请按照统一的命名规范设置学期名称,这有助于数据归档后的检索与分析。
</p>
</div>
</div>
</div>
<div class="p-6 bg-bg-tertiary-50 border-t border-border-secondary flex gap-3 justify-end">
<div class="p-6 bg-zinc-800/50 border-t border-zinc-800 flex gap-3 justify-end">
<button
class="px-6 py-2.5 text-xs font-bold text-text-tertiary hover:text-text-secondary transition-colors"
@click="closeModal"
class="px-6 py-2.5 text-xs font-bold text-zinc-500 hover:text-zinc-300 transition-colors"
@click="closeAddModal"
>
{{ locale.cancel }}
取消
</button>
<button
:disabled="submitting || !semesterForm.name.trim()"
class="px-8 py-2.5 bg-primary-hover hover:bg-primary disabled:bg-primary-hover disabled:opacity-50 text-text-primary text-xs font-black rounded-xl shadow-lg transition-all active:scale-95"
@click="handleSubmit"
:disabled="submitting || !newSemester.name.trim()"
class="px-8 py-2.5 bg-blue-600 hover:bg-blue-500 disabled:bg-blue-800 disabled:opacity-50 text-white text-xs font-black rounded-xl shadow-lg transition-all active:scale-95"
@click="handleAddSemester"
>
{{ submitting ? (isEditing ? locale.saving : locale.creating) : (isEditing ? locale.saveChanges : locale.createSemester) }}
{{ submitting ? '创建中...' : '创建学期' }}
</button>
</div>
</div>
@@ -484,10 +459,10 @@
<ConfirmDialog
:loading="loading"
:show="showDeleteDialog"
:cancel-text="locale.cancel"
:confirm-text="locale.confirmDelete"
cancel-text="取消"
confirm-text="确认删除"
:message="deleteMessage"
:title="locale.deleteDialogTitle"
title="确认删除学期"
type="danger"
@close="showDeleteDialog = false"
@confirm="confirmDelete"
@@ -499,12 +474,8 @@
import { onMounted, ref, computed } from 'vue'
import ConfirmDialog from '~/components/UI/ConfirmDialog.vue'
import { useToast } from '~/composables/useToast'
import { useLocale } from '~/utils/locale'
const { showToast: showNotification } = useToast()
const { admin } = useLocale()
const locale = computed(() => admin.value?.semesterManager || {})
const { msg: getLocaleMessage, nested: getNestedMessage } = useLocaleText(locale)
const {
semesters,
@@ -515,20 +486,15 @@ const {
fetchCurrentSemester,
createSemester,
setActiveSemester,
updateSemester,
deleteSemester: deleteSemesterAPI
} = useSemesters()
const showModal = ref(false)
const isEditing = ref(false)
const editSemesterId = ref(null)
const originalSemesterName = ref('')
const showAddModal = ref(false)
const showDeleteDialog = ref(false)
const deleteTargetId = ref(null)
const deleteTargetName = ref('')
const submitting = ref(false)
const semesterForm = ref({
const newSemester = ref({
name: '',
isActive: false
})
@@ -542,7 +508,7 @@ const sortedSemesters = computed(() => {
// 删除确认信息
const deleteMessage = computed(() => {
return getLocaleMessage('deleteMessage', deleteTargetName.value)
return `确定要彻底删除 "${deleteTargetName.value}" 吗?此操作将同步清除该学期下的所有点歌记录、排期数据和统计信息。数据一旦删除将无法找回。`
})
// 格式化日期
@@ -563,13 +529,13 @@ const setActive = async (semesterId) => {
// 注意:这里需要重新获取error,因为useSemesters内部可能没有正确更新error的响应式引用
// 或者直接依据success返回值判断
if (success) {
showNotification(getNestedMessage('messages', 'activeSet'), 'success')
showNotification('活跃学期设置成功!', 'success')
} else {
// 如果setActiveSemester返回false,尝试直接显示一个通用错误,因为error可能为空
showNotification(error.value || getNestedMessage('errors', 'setActiveFailedRetry'), 'error')
showNotification(error.value || '设置活跃学期失败,请稍后重试', 'error')
}
} catch (err) {
showNotification(err.message || getNestedMessage('errors', 'setFailed'), 'error')
showNotification(err.message || '设置失败', 'error')
}
}
@@ -590,12 +556,12 @@ const confirmDelete = async () => {
try {
const success = await deleteSemesterAPI(deleteTargetId.value)
if (success) {
showNotification(getNestedMessage('messages', 'deleted'), 'success')
showNotification('学期删除成功!', 'success')
} else {
showNotification(error.value || getNestedMessage('errors', 'deleteFailed'), 'error')
showNotification(error.value || '删除学期失败', 'error')
}
} catch (err) {
showNotification(err.message || getNestedMessage('errors', 'deleteFailedShort'), 'error')
showNotification(err.message || '删除失败', 'error')
}
showDeleteDialog.value = false
@@ -603,103 +569,64 @@ const confirmDelete = async () => {
deleteTargetName.value = ''
}
// 处理表单提交
const handleSubmit = async () => {
const normalizedName = semesterForm.value.name.trim()
if (!normalizedName) return
if (isEditing.value && normalizedName === originalSemesterName.value) {
showNotification(getNestedMessage('messages', 'nameUnchanged'), 'info')
closeModal()
return
}
// 处理添加学期
const handleAddSemester = async () => {
if (!newSemester.value.name.trim()) return
submitting.value = true
try {
if (isEditing.value && editSemesterId.value) {
// 修改学期
const success = await updateSemester(editSemesterId.value, {
name: normalizedName
})
if (success) {
showNotification(getNestedMessage('messages', 'updated'), 'success')
closeModal()
} else {
showNotification(error.value || getNestedMessage('errors', 'updateFailed'), 'error')
}
const result = await createSemester({
name: newSemester.value.name,
isActive: newSemester.value.isActive
})
if (result) {
showNotification('学期创建成功!', 'success')
closeAddModal()
} else {
// 创建学期
const result = await createSemester({
name: normalizedName,
isActive: semesterForm.value.isActive
})
if (result) {
showNotification(getNestedMessage('messages', 'created'), 'success')
closeModal()
} else {
showNotification(error.value || getNestedMessage('errors', 'createFailed'), 'error')
}
showNotification(error.value || '创建学期失败', 'error')
}
} catch (err) {
console.error(isEditing.value ? '修改学期失败:' : '创建学期失败:', err)
showNotification(
err.message || (isEditing.value ? getNestedMessage('errors', 'updateFailed') : getNestedMessage('errors', 'createFailed')),
'error'
)
console.error('创建学期失败:', err)
showNotification(err.message || '创建学期失败', 'error')
} finally {
submitting.value = false
}
}
// 关闭弹窗
const closeModal = () => {
showModal.value = false
isEditing.value = false
editSemesterId.value = null
originalSemesterName.value = ''
semesterForm.value = {
// 关闭添加弹窗
const closeAddModal = () => {
showAddModal.value = false
newSemester.value = {
name: '',
isActive: false
}
}
const getRecommendedName = () => {
const now = getSyncedDate()
const now = new Date()
const year = now.getFullYear()
const month = now.getMonth() + 1
let academicYear = year
let term = locale.value?.terms?.first || '第一学期'
let term = '第一学期'
if (month >= 3 && month <= 8) {
academicYear = year - 1
term = locale.value?.terms?.second || '第二学期'
term = '第二学期'
} else if (month <= 2) {
academicYear = year - 1
term = locale.value?.terms?.first || '第一学期'
term = '第一学期'
} else {
academicYear = year
term = locale.value?.terms?.first || '第一学期'
term = '第一学期'
}
return getLocaleMessage('recommendedName', academicYear, term)
return `${academicYear}学年${term}`
}
const openModal = () => {
isEditing.value = false
editSemesterId.value = null
semesterForm.value.name = getRecommendedName()
semesterForm.value.isActive = false
showModal.value = true
}
const openEditModal = (sem) => {
isEditing.value = true
editSemesterId.value = sem.id
originalSemesterName.value = sem.name
semesterForm.value.name = sem.name
semesterForm.value.isActive = sem.isActive
showModal.value = true
const openAddModal = () => {
newSemester.value.name = getRecommendedName()
showAddModal.value = true
}
// 组件挂载时获取数据