This commit is contained in:
2026-07-26 22:16:58 +08:00 Unverified
parent dd1b752fa3
commit bc6ffa2ada
7 changed files with 582 additions and 146 deletions
+62 -23
View File
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, onMounted, reactive, ref } from 'vue'
import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
import { Check, CircleCheck, Plus, Refresh, Stamp } from '@element-plus/icons-vue'
import http, { apiErrorMessage } from '../api/http'
import { useAuthStore } from '../stores/auth'
@@ -16,6 +16,12 @@ const createDialog = ref(false)
const recordDialog = ref(false)
const recordTarget = ref<any | null>(null)
const recordForm = reactive({ status: 'Completed', notes: '' })
const keyword = ref('')
const progressFilter = ref('')
const resultPage = ref(1)
const resultPageSize = 20
const resultRequestId = ref(0)
let filterTimer: ReturnType<typeof setTimeout> | undefined
const batchForm = reactive({
name: '2030届毕业生离校手续',
graduationYear: 2030,
@@ -34,23 +40,8 @@ const statusLabels: Record<string, string> = {
const roleLabels: Record<string, string> = {
AcademicAdmin: '校级办理', CollegeAdmin: '学院办理', Counselor: '辅导员办理',
}
const studentLedgers = computed(() => {
const map = new Map<string, any>()
for (const record of selected.value?.records ?? []) {
if (!map.has(record.studentId)) {
map.set(record.studentId, {
studentId: record.studentId,
studentNumber: record.studentNumber,
name: record.name,
className: record.className,
majorName: record.majorName,
records: [],
})
}
map.get(record.studentId).records.push(record)
}
return [...map.values()]
})
const studentItems = computed(() => selected.value?.students?.items ?? [])
const studentTotal = computed(() => selected.value?.students?.total ?? 0)
function dateText(value?: string) {
if (!value) return '—'
return new Intl.DateTimeFormat('zh-CN', {
@@ -88,7 +79,28 @@ async function load() {
}
}
async function selectBatch(id: string) {
selected.value = (await http.get(`/graduation-clearance/batches/${id}`)).data
resultPage.value = 1
await loadBatch(id)
}
async function loadBatch(id = selected.value?.id) {
if (!id) return
const requestId = ++resultRequestId.value
loading.value = true
try {
const response = await http.get(`/graduation-clearance/batches/${id}`, {
params: {
page: resultPage.value,
pageSize: resultPageSize,
keyword: keyword.value.trim() || undefined,
progress: progressFilter.value || undefined,
},
})
if (requestId === resultRequestId.value) selected.value = response.data
} catch (error) {
if (requestId === resultRequestId.value) ElMessage.error(apiErrorMessage(error))
} finally {
if (requestId === resultRequestId.value) loading.value = false
}
}
function addItem() {
batchForm.items.push({
@@ -135,7 +147,7 @@ async function saveRecord() {
await http.put(`/graduation-clearance/records/${recordTarget.value.id}`, recordForm)
recordDialog.value = false
ElMessage.success('离校事项办理状态已更新。')
await selectBatch(selected.value.id)
await loadBatch()
} catch (error) {
ElMessage.error(apiErrorMessage(error))
}
@@ -153,6 +165,17 @@ async function closeBatch() {
}
}
watch([keyword, progressFilter], () => {
resultPage.value = 1
if (filterTimer) clearTimeout(filterTimer)
filterTimer = setTimeout(() => loadBatch(), 300)
})
onUnmounted(() => {
if (filterTimer) clearTimeout(filterTimer)
resultRequestId.value++
})
onMounted(load)
</script>
@@ -195,15 +218,23 @@ onMounted(load)
<section v-if="selected" class="clearance-workbench" v-loading="loading">
<header>
<div><span>CLEARANCE LEDGER</span><h3>{{ selected.name }}</h3><p>{{ selected.items.length }} 项离校手续 · {{ studentLedgers.length }} 名毕业生</p></div>
<div><span>CLEARANCE LEDGER</span><h3>{{ selected.name }}</h3><p>{{ selected.items.length }} 项离校手续 · {{ selected.studentCount }} 名毕业生</p></div>
<div v-if="selected.status === 'Open' && isManager"><el-button :icon="Refresh" @click="generate">补齐名单</el-button><el-button type="primary" :icon="Stamp" @click="closeBatch">关闭批次</el-button></div>
<el-tag v-else type="success" effect="plain">批次已锁定</el-tag>
</header>
<div class="clearance-item-legend">
<div v-for="item in selected.items" :key="item.id"><span>{{ item.code }}</span><b>{{ item.name }}</b><small>{{ item.responsibleUnit }} · {{ roleLabels[item.responsibleRole] }}</small></div>
</div>
<div class="graduation-filter">
<el-input v-model="keyword" clearable placeholder="搜索学号、姓名、专业或班级" />
<el-select v-model="progressFilter" clearable placeholder="全部进度">
<el-option label="有必办待办" value="Pending" />
<el-option label="必办已完成" value="Completed" />
</el-select>
<span> {{ studentTotal }} 名学生</span>
</div>
<div class="clearance-ledgers">
<article v-for="student in studentLedgers" :key="student.studentId">
<article v-for="student in studentItems" :key="student.studentId">
<header><div><span>{{ student.studentNumber }} · {{ student.className }}</span><h4>{{ student.name }}</h4><p>{{ student.majorName }}</p></div><b>{{ completedCount(student.records) }}/{{ student.records.length }}</b></header>
<div class="clearance-record-grid">
<div v-for="record in student.records" :key="record.id" :class="{ done: record.status !== 'Pending' }">
@@ -218,7 +249,15 @@ onMounted(load)
</div>
</div>
</article>
<el-empty v-if="!studentLedgers.length" description="尚未生成毕业生离校办理记录" />
<el-empty v-if="!studentItems.length" description="没有符合筛选条件的离校办理记录" />
<el-pagination
v-if="studentTotal > resultPageSize"
v-model:current-page="resultPage"
:page-size="resultPageSize"
:total="studentTotal"
layout="total, prev, pager, next"
@current-change="loadBatch()"
/>
</div>
</section>
<el-empty v-else v-loading="loading" description="尚未建立毕业离校批次" />