Files
1/static/js/pages/scores.js
T
2026-09-14 10:23:36 +08:00

184 lines
7.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 成绩管理:按学号查成绩,再做增删改
成绩模块的五个接口都用业务学号 stu_no,参数字段是 query 不是 JSON body,
这里通过 ScoreApi 调用,不再关心细节 */
window.PAGES.push({
key: 'scores',
title: '成绩管理',
sub: '按学生维护每次考核的成绩',
group: '数据管理',
icon: 'score',
component: {
name: 'ScoresPage',
template: `
<div>
<div class="card">
<div class="filters">
<el-select v-model="stuNo" placeholder="请选择学生(可按学号/姓名搜索)" filterable clearable
style="width:320px" @change="onPickStudent">
<el-option v-for="s in students" :key="s.stu_no" :label="studentLabel(s)" :value="s.stu_no" />
</el-select>
<el-button @click="load" :disabled="!stuNo">刷新</el-button>
<el-button type="primary" :disabled="!stuNo" @click="openCreate">+ 新增成绩</el-button>
</div>
<template v-if="stuNo">
<div class="card-head">
<div>
<div class="card-title">{{ currentName }} 的成绩</div>
<div class="card-hint">
学号 {{ stuNo }} · 共 {{ rows.length }} 次考核<span v-if="rows.length">
· 平均 {{ avgScore }} 分</span>
</div>
</div>
</div>
<el-table :data="rows" v-loading="loading" border stripe>
<el-table-column prop="exam_order" label="考核序次" width="120" />
<el-table-column label="成绩" width="140">
<template #default="{ row }">
<!-- 60 分是后端统计不及格时用的固定阈值,这里跟着一起标红 -->
<span :style="{ color: row.score < 60 ? '#E34948' : '', fontWeight: row.score < 60 ? 600 : 400 }">
{{ row.score }}
</span>
<el-tag v-if="row.score < 60" size="small" type="danger" effect="plain" style="margin-left:8px">
不及格
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="140">
<template #default="{ row }">
<el-button link type="primary" @click="openEdit(row)">编辑</el-button>
<el-button link type="danger" @click="remove(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-empty v-if="!loading && !rows.length" description="该学生暂无成绩记录" />
</template>
<el-empty v-else description="请先在上方选择一个学生" />
</div>
<el-dialog v-model="dialog" :title="editing ? '编辑成绩' : '新增成绩'" width="480px" destroy-on-close>
<el-form ref="formRef" :model="form" :rules="rules" label-width="88px">
<el-form-item label="学生">
<el-input :model-value="currentName + '(' + form.stu_no + ')'" disabled />
</el-form-item>
<el-form-item label="考核序次" prop="exam_order">
<el-input-number v-model="form.exam_order" :min="1" :max="999"
controls-position="right" :disabled="!!editing" style="width:100%" />
</el-form-item>
<el-form-item label="成绩" prop="score">
<el-input-number v-model="form.score" :min="0" :max="100" :precision="1" :step="0.5"
controls-position="right" style="width:100%" />
</el-form-item>
<el-alert v-if="editing" type="info" :closable="false" show-icon
title="学号和考核序次是联合主键,只能在新增时确定,编辑只改分数" />
</el-form>
<template #footer>
<el-button @click="dialog = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="submit">保存</el-button>
</template>
</el-dialog>
</div>
`,
setup() {
const loading = ref(false);
const saving = ref(false);
const rows = ref([]);
const students = ref([]);
const stuNo = ref(null);
const dialog = ref(false);
const editing = ref(null);
const formRef = ref(null);
const form = ref({ stu_no: null, exam_order: 1, score: 80 });
const rules = {
exam_order: [{ required: true, message: '请填写考核序次', trigger: 'blur' }],
score: [{ required: true, message: '请填写成绩', trigger: 'blur' }],
};
const currentName = computed(() => {
const s = students.value.find(x => x.stu_no === stuNo.value);
return s ? (s.stu_name || '未命名') : '';
});
const avgScore = computed(() => {
if (!rows.value.length) return 0;
const sum = rows.value.reduce((a, r) => a + Number(r.score || 0), 0);
return Math.round((sum / rows.value.length) * 10) / 10;
});
const load = async () => {
if (!stuNo.value) { rows.value = []; return; }
loading.value = true;
try {
rows.value = await ScoreApi.listByStuNo(stuNo.value);
rows.value.sort((a, b) => a.exam_order - b.exam_order);
} catch (e) { toastErr(e); }
finally { loading.value = false; }
};
const onPickStudent = () => { load(); };
const openCreate = () => {
editing.value = null;
// 序次默认接着最后一条往后排,省得每次手填
const next = rows.value.length
? Math.max.apply(null, rows.value.map(r => r.exam_order)) + 1 : 1;
form.value = { stu_no: stuNo.value, exam_order: next, score: 80 };
dialog.value = true;
};
const openEdit = (row) => {
editing.value = { stu_no: row.stu_no, exam_order: row.exam_order };
form.value = { stu_no: row.stu_no, exam_order: row.exam_order, score: row.score };
dialog.value = true;
};
const submit = async () => {
if (!formRef.value) return;
try { await formRef.value.validate(); } catch (e) { return; }
saving.value = true;
try {
if (editing.value) {
await ScoreApi.update(form.value.stu_no, form.value.exam_order, form.value.score);
toastOk('修改成功');
} else {
await ScoreApi.add(form.value.stu_no, form.value.exam_order, form.value.score);
toastOk('新增成功');
}
dialog.value = false;
await load();
} catch (e) { toastErr(e); }
finally { saving.value = false; }
};
const remove = async (row) => {
try {
await ElementPlus.ElMessageBox.confirm(
`确定删除第 ${row.exam_order} 次考核的成绩(${row.score} 分)吗?`,
'删除确认', { type: 'warning', confirmButtonText: '确定删除', cancelButtonText: '取消' });
} catch (e) { return; }
try {
await ScoreApi.del(row.stu_no || stuNo.value, row.exam_order);
toastOk('删除成功');
await load();
} catch (e) { toastErr(e); }
};
onMounted(async () => {
try { students.value = await Cache.loadStudents(); }
catch (e) { toastErr(e); }
});
return { loading, saving, rows, students, stuNo, dialog, editing, formRef, form, rules,
currentName, avgScore, load, onPickStudent, openCreate, openEdit, submit, remove,
studentLabel };
},
},
});