feat(school): 家长绑定孩子支持多选并修复表格勾选回显

Made-with: Cursor
This commit is contained in:
zhonghua 2026-03-24 17:07:09 +08:00
parent c8f97c45d4
commit 94ea219f2f

View File

@ -242,7 +242,8 @@
<!-- 学生表格 -->
<a-table :columns="studentTableColumns" :data-source="studentTableData" :loading="studentsLoading"
:pagination="studentPagination" :row-selection="studentRowSelection" row-key="id" size="small"
:pagination="studentPagination" :row-selection="studentRowSelection"
:row-key="(r: Student) => studentIdNum(r)" size="small"
@change="handleStudentTableChange" style="margin-top: 16px;">
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'gender'">
@ -255,11 +256,17 @@
</a-table>
<!-- 选择关系并确认 -->
<div class="select-footer" v-if="selectedStudent">
<div class="select-footer" v-if="selectedStudentIds.length > 0">
<div class="selected-info">
<span>已选择</span>
<a-tag color="orange">{{ selectedStudent.name }}</a-tag>
<span class="selected-class">{{ selectedStudent.className }}</span>
<span class="selected-count">已选择 {{ selectedStudentIds.length }} </span>
<a-space wrap class="selected-tags">
<a-tag v-for="sid in selectedStudentIds" :key="sid" color="orange">
{{ selectedStudentMeta[sid]?.name ?? sid }}
<span v-if="selectedStudentMeta[sid]?.className" class="selected-class">
{{ selectedStudentMeta[sid]?.className }}
</span>
</a-tag>
</a-space>
</div>
<div class="select-actions">
<a-select v-model:value="addChildForm.relationship" style="width: 100px; margin-right: 12px;">
@ -302,7 +309,7 @@
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue';
import { ref, reactive, computed, watch, onMounted } from 'vue';
import {
IdcardOutlined,
PlusOutlined,
@ -378,7 +385,6 @@ const addChildLoading = ref(false);
const studentsLoading = ref(false);
const addChildForm = reactive({
studentId: undefined as number | undefined,
relationship: 'FATHER',
});
@ -386,7 +392,10 @@ const addChildForm = reactive({
const selectStudentModalVisible = ref(false);
const studentSearchKeyword = ref('');
const studentClassFilter = ref<number | undefined>();
const selectedStudent = ref<Student | null>(null);
/** 跨页保留:已勾选的学生 id */
const selectedStudentIds = ref<number[]>([]);
/** 用于底部展示姓名、班级(在勾选时写入) */
const selectedStudentMeta = ref<Record<number, { name: string; className?: string | null }>>({});
const studentTableData = ref<Student[]>([]);
const studentPagination = reactive({
current: 1,
@ -613,24 +622,53 @@ const studentTableColumns = [
{ title: '班级', dataIndex: 'className', key: 'className', width: 120 },
];
const studentRowSelection = computed(() => ({
type: 'radio' as 'radio',
selectedRowKeys: selectedStudent.value ? [selectedStudent.value.id] : [],
onChange: (selectedRowKeys: (string | number)[]) => {
if (selectedRowKeys.length > 0) {
const student = studentTableData.value.find(s => s.id === selectedRowKeys[0]);
selectedStudent.value = student || null;
} else {
selectedStudent.value = null;
const studentIdNum = (s: Student) => Number(s.id);
/** 合并当前页勾选与其它页已选实现表格多选跨页保留id 统一为 number避免与 rowKey 类型不一致导致无法回显) */
const mergeStudentTableSelection = (keysFromThisPage: (string | number)[]) => {
const currentPageIds = studentTableData.value.map(studentIdNum);
const keysNum = keysFromThisPage.map(Number);
const fromOtherPages = selectedStudentIds.value.filter((id) => !currentPageIds.includes(id));
selectedStudentIds.value = [...new Set([...fromOtherPages, ...keysNum])];
studentTableData.value.forEach((s) => {
const sid = studentIdNum(s);
if (keysNum.includes(sid)) {
selectedStudentMeta.value[sid] = { name: s.name, className: s.className };
} else if (currentPageIds.includes(sid)) {
delete selectedStudentMeta.value[sid];
}
});
syncStudentTableRowSelectionKeys();
};
/** 当前页应勾选的 keys须与表格 row-key 字段类型一致,否则复选框不回显 */
const syncStudentTableRowSelectionKeys = () => {
const ids = new Set(selectedStudentIds.value);
/** 与 :row-key 一致使用 number否则受控勾选无法与行匹配 */
studentRowSelection.selectedRowKeys = studentTableData.value
.filter((s) => ids.has(studentIdNum(s)))
.map((s) => studentIdNum(s));
};
const studentRowSelection = reactive({
type: 'checkbox' as const,
selectedRowKeys: [] as (string | number)[],
onChange: (selectedRowKeys: (string | number)[]) => {
mergeStudentTableSelection(selectedRowKeys);
},
}));
});
/** 翻页、筛选后数据源变化时同步复选框回显 */
watch(studentTableData, () => {
syncStudentTableRowSelectionKeys();
});
const openSelectStudentModal = async () => {
selectStudentModalVisible.value = true;
studentSearchKeyword.value = '';
studentClassFilter.value = undefined;
selectedStudent.value = null;
selectedStudentIds.value = [];
selectedStudentMeta.value = {};
studentPagination.current = 1;
await loadStudentsForSelect();
await loadClassOptions();
@ -652,6 +690,7 @@ const loadStudentsForSelect = async () => {
studentTableData.value = [];
} finally {
studentsLoading.value = false;
syncStudentTableRowSelectionKeys();
}
};
@ -677,26 +716,42 @@ const handleStudentTableChange = (pagination: any) => {
const cancelSelectStudent = () => {
selectStudentModalVisible.value = false;
selectedStudent.value = null;
selectedStudentIds.value = [];
selectedStudentMeta.value = {};
};
const confirmAddChild = async () => {
if (!selectedStudent.value || !currentParent.value) return;
if (selectedStudentIds.value.length === 0 || !currentParent.value) return;
addChildLoading.value = true;
let success = 0;
let lastError = '';
try {
await addChildToParent(currentParent.value.id, {
studentId: selectedStudent.value.id,
relationship: addChildForm.relationship,
});
message.success('添加成功');
selectStudentModalVisible.value = false;
selectedStudent.value = null;
addChildForm.relationship = 'FATHER';
await loadParentChildren(currentParent.value.id);
loadParents(); //
} catch (error: any) {
message.error(error?.response?.data?.message || '添加失败');
for (const studentId of selectedStudentIds.value) {
try {
await addChildToParent(currentParent.value.id, {
studentId,
relationship: addChildForm.relationship,
});
success++;
} catch (e: any) {
lastError = e?.response?.data?.message || e?.message || '添加失败';
}
}
if (success > 0) {
const fail = selectedStudentIds.value.length - success;
message.success(
fail > 0 ? `成功关联 ${success} 个孩子,${fail} 个未添加(可能已关联)` : `成功关联 ${success} 个孩子`
);
selectStudentModalVisible.value = false;
selectedStudentIds.value = [];
selectedStudentMeta.value = {};
addChildForm.relationship = 'FATHER';
await loadParentChildren(currentParent.value.id);
loadParents();
} else {
message.error(lastError || '添加失败');
}
} finally {
addChildLoading.value = false;
}
@ -1154,8 +1209,10 @@ onMounted(() => {
.select-footer {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
align-items: flex-start;
gap: 12px;
margin-top: 16px;
padding: 16px;
background: #F8F9FA;
@ -1164,8 +1221,20 @@ onMounted(() => {
.selected-info {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
flex: 1;
min-width: 0;
}
.selected-count {
flex-shrink: 0;
}
.selected-tags {
flex: 1;
min-width: 0;
}
.selected-class {