feat(school): 家长绑定孩子支持多选并修复表格勾选回显
Made-with: Cursor
This commit is contained in:
parent
c8f97c45d4
commit
94ea219f2f
@ -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 {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user