276 lines
12 KiB
JavaScript
276 lines
12 KiB
JavaScript
/* 学生管理:增删改查 + 按学号/姓名/班级/顾问筛选
|
||
注意:学号是后端按班级开课时间生成的,表单里不给填,新增成功后提示出来 */
|
||
|
||
window.PAGES.push({
|
||
key: 'students',
|
||
title: '学生管理',
|
||
sub: '学生基本信息,学号由班级开课时间自动生成',
|
||
group: '数据管理',
|
||
icon: 'student',
|
||
|
||
component: {
|
||
name: 'StudentsPage',
|
||
template: `
|
||
<div>
|
||
<div class="card">
|
||
<div class="filters">
|
||
<el-input v-model="filter.stu_no" placeholder="学号(模糊)" clearable style="width:150px" @keyup.enter="load" />
|
||
<el-input v-model="filter.stu_name" placeholder="姓名(模糊)" clearable style="width:150px" @keyup.enter="load" />
|
||
<el-select v-model="filter.class_id" placeholder="全部班级" clearable style="width:210px">
|
||
<el-option v-for="c in classes" :key="c.class_id" :label="classLabel(c.class_id)" :value="c.class_id" />
|
||
</el-select>
|
||
<el-select v-model="filter.advisor_id" placeholder="全部顾问" clearable style="width:160px">
|
||
<el-option v-for="a in advisors" :key="a.adv_id" :label="a.adv_name" :value="a.adv_id" />
|
||
</el-select>
|
||
<el-button type="primary" @click="load">查询</el-button>
|
||
<el-button @click="reset">重置</el-button>
|
||
</div>
|
||
|
||
<div class="card-head">
|
||
<div>
|
||
<div class="card-title">学生列表</div>
|
||
<div class="card-hint">共 {{ rows.length }} 人</div>
|
||
</div>
|
||
<div class="card-tools">
|
||
<el-button type="primary" @click="openCreate">+ 新增学生</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<el-table :data="pagedRows" v-loading="loading" border stripe row-key="stu_no"
|
||
:header-cell-style="{ background: '#F8FAFC' }">
|
||
<el-table-column prop="stu_no" label="学号" width="120" fixed />
|
||
<el-table-column prop="stu_name" label="姓名" width="100" />
|
||
<el-table-column label="班级" width="180" show-overflow-tooltip>
|
||
<template #default="{ row }">{{ className(row.class_id) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="顾问" width="100">
|
||
<template #default="{ row }">{{ advisorName(row.advisor_id) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="性别" width="70">
|
||
<template #default="{ row }">
|
||
<el-tag v-if="row.stu_gender" size="small"
|
||
:type="row.stu_gender === '男' ? 'primary' : 'danger'" effect="light">
|
||
{{ row.stu_gender }}
|
||
</el-tag>
|
||
<span v-else class="muted">—</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="stu_age" label="年龄" width="70">
|
||
<template #default="{ row }">{{ dash(row.stu_age) }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="native_place" label="籍贯" width="100">
|
||
<template #default="{ row }">{{ dash(row.native_place) }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="graduate_school" label="毕业学校" min-width="140" show-overflow-tooltip>
|
||
<template #default="{ row }">{{ dash(row.graduate_school) }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="major" label="专业" width="120" show-overflow-tooltip>
|
||
<template #default="{ row }">{{ dash(row.major) }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="edu" label="学历" width="80">
|
||
<template #default="{ row }">{{ dash(row.edu) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="入学时间" width="110">
|
||
<template #default="{ row }">{{ dash(fmtDate(row.in_time)) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="毕业时间" width="110">
|
||
<template #default="{ row }">{{ dash(fmtDate(row.out_time)) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="140" fixed="right">
|
||
<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>
|
||
|
||
<div class="pager">
|
||
<el-pagination background layout="total, sizes, prev, pager, next"
|
||
:total="rows.length" v-model:current-page="page"
|
||
v-model:page-size="pageSize" :page-sizes="[10, 20, 50, 100]" />
|
||
</div>
|
||
</div>
|
||
|
||
<el-dialog v-model="dialog" :title="editing ? '编辑学生' : '新增学生'" width="720px" destroy-on-close>
|
||
<el-form ref="formRef" :model="form" :rules="rules" label-width="88px">
|
||
<div class="form-grid">
|
||
<el-form-item label="班级" prop="class_id" class="span2">
|
||
<el-select v-model="form.class_id" placeholder="请选择班级(学号按开班时间生成)" style="width:100%">
|
||
<el-option v-for="c in classes" :key="c.class_id" :label="classLabel(c.class_id)" :value="c.class_id" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="姓名" prop="stu_name">
|
||
<el-input v-model="form.stu_name" placeholder="学生姓名" maxlength="30" />
|
||
</el-form-item>
|
||
<el-form-item label="顾问">
|
||
<el-select v-model="form.advisor_id" placeholder="可不选" clearable style="width:100%">
|
||
<el-option v-for="a in advisors" :key="a.adv_id" :label="a.adv_name" :value="a.adv_id" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="性别">
|
||
<el-select v-model="form.stu_gender" placeholder="可不选" clearable style="width:100%">
|
||
<el-option v-for="g in GENDER_OPTIONS" :key="g" :label="g" :value="g" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="年龄">
|
||
<el-input-number v-model="form.stu_age" :min="0" :max="100" controls-position="right" style="width:100%" />
|
||
</el-form-item>
|
||
<el-form-item label="籍贯">
|
||
<el-input v-model="form.native_place" placeholder="如 广东 深圳" maxlength="50" />
|
||
</el-form-item>
|
||
<el-form-item label="学历">
|
||
<el-select v-model="form.edu" placeholder="可不选" clearable filterable allow-create style="width:100%">
|
||
<el-option v-for="e in EDU_OPTIONS" :key="e" :label="e" :value="e" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="毕业学校" class="span2">
|
||
<el-input v-model="form.graduate_school" placeholder="毕业学校" maxlength="128" />
|
||
</el-form-item>
|
||
<el-form-item label="专业" class="span2">
|
||
<el-input v-model="form.major" placeholder="所学专业" maxlength="64" />
|
||
</el-form-item>
|
||
<el-form-item label="入学时间">
|
||
<el-date-picker v-model="form.in_time" type="date" value-format="YYYY-MM-DD"
|
||
placeholder="选择日期" style="width:100%" />
|
||
</el-form-item>
|
||
<el-form-item label="毕业时间">
|
||
<el-date-picker v-model="form.out_time" type="date" value-format="YYYY-MM-DD"
|
||
placeholder="选择日期" style="width:100%" />
|
||
</el-form-item>
|
||
</div>
|
||
<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 classes = ref([]);
|
||
const advisors = ref([]);
|
||
const dialog = ref(false);
|
||
const editing = ref(null);
|
||
const formRef = ref(null);
|
||
|
||
const filter = ref({ stu_no: '', stu_name: '', class_id: null, advisor_id: null });
|
||
const page = ref(1);
|
||
const pageSize = ref(10);
|
||
|
||
const emptyForm = () => ({
|
||
class_id: null, advisor_id: null, stu_name: '', native_place: '',
|
||
graduate_school: '', major: '', in_time: null, out_time: null,
|
||
edu: null, stu_age: null, stu_gender: null,
|
||
});
|
||
const form = ref(emptyForm());
|
||
|
||
// 学号是后端生成的,所以班级必选;其余字段都可空
|
||
const rules = {
|
||
class_id: [{ required: true, message: '请选择班级', trigger: 'change' }],
|
||
stu_name: [{ required: true, message: '请填写姓名', trigger: 'blur' }],
|
||
};
|
||
|
||
// 表格是客户端分页:接口本身没有分页参数,一次把命中的都取回来
|
||
const pagedRows = computed(() => {
|
||
const start = (page.value - 1) * pageSize.value;
|
||
return rows.value.slice(start, start + pageSize.value);
|
||
});
|
||
|
||
const load = async () => {
|
||
loading.value = true;
|
||
try {
|
||
rows.value = await StudentApi.list({
|
||
stu_no: filter.value.stu_no,
|
||
stu_name: filter.value.stu_name,
|
||
class_id: filter.value.class_id,
|
||
advisor_id: filter.value.advisor_id,
|
||
});
|
||
page.value = 1;
|
||
} catch (e) { toastErr(e); }
|
||
finally { loading.value = false; }
|
||
};
|
||
|
||
const reset = () => {
|
||
filter.value = { stu_no: '', stu_name: '', class_id: null, advisor_id: null };
|
||
load();
|
||
};
|
||
|
||
const openCreate = () => {
|
||
editing.value = null;
|
||
form.value = emptyForm();
|
||
dialog.value = true;
|
||
};
|
||
|
||
const openEdit = (row) => {
|
||
editing.value = row.stu_no;
|
||
form.value = {
|
||
class_id: row.class_id, advisor_id: row.advisor_id, stu_name: row.stu_name,
|
||
native_place: row.native_place, graduate_school: row.graduate_school,
|
||
major: row.major, in_time: fmtDate(row.in_time) || null,
|
||
out_time: fmtDate(row.out_time) || null, edu: row.edu,
|
||
stu_age: row.stu_age, stu_gender: row.stu_gender,
|
||
};
|
||
dialog.value = true;
|
||
};
|
||
|
||
const submit = async () => {
|
||
if (!formRef.value) return;
|
||
try { await formRef.value.validate(); } catch (e) { return; }
|
||
saving.value = true;
|
||
try {
|
||
// pruneEmpty:没填的字段不下发,避免把可空字段写成空字符串
|
||
const payload = pruneEmpty(form.value);
|
||
if (editing.value) {
|
||
await StudentApi.update(editing.value, payload);
|
||
toastOk('修改成功');
|
||
} else {
|
||
const created = await StudentApi.create(payload);
|
||
toastOk('新增成功,学号:' + created.stu_no);
|
||
}
|
||
Cache.invalidate('students');
|
||
dialog.value = false;
|
||
await load();
|
||
} catch (e) { toastErr(e); }
|
||
finally { saving.value = false; }
|
||
};
|
||
|
||
const remove = async (row) => {
|
||
try {
|
||
await ElementPlus.ElMessageBox.confirm(
|
||
`确定删除学生「${row.stu_name || row.stu_no}」吗?`,
|
||
'删除确认', { type: 'warning', confirmButtonText: '确定删除', cancelButtonText: '取消' });
|
||
} catch (e) { return; }
|
||
try {
|
||
await StudentApi.remove(row.stu_no);
|
||
toastOk('删除成功');
|
||
Cache.invalidate('students');
|
||
await load();
|
||
} catch (e) { toastErr(e); }
|
||
};
|
||
|
||
// 班级/顾问下拉的数据先准备好,再拉学生列表
|
||
onMounted(async () => {
|
||
try {
|
||
[classes.value, advisors.value] = await Promise.all([
|
||
Cache.loadClasses(), Cache.loadAdvisors(),
|
||
]);
|
||
} catch (e) { toastErr(e); }
|
||
load();
|
||
});
|
||
|
||
return {
|
||
loading, saving, rows, classes, advisors, dialog, editing, formRef,
|
||
filter, form, rules, page, pageSize, pagedRows,
|
||
load, reset, openCreate, openEdit, submit, remove,
|
||
GENDER_OPTIONS, EDU_OPTIONS, dash, fmtDate, className, advisorName, classLabel,
|
||
};
|
||
},
|
||
},
|
||
});
|