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"
|
<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;">
|
@change="handleStudentTableChange" style="margin-top: 16px;">
|
||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<template v-if="column.dataIndex === 'gender'">
|
<template v-if="column.dataIndex === 'gender'">
|
||||||
@ -255,11 +256,17 @@
|
|||||||
</a-table>
|
</a-table>
|
||||||
|
|
||||||
<!-- 选择关系并确认 -->
|
<!-- 选择关系并确认 -->
|
||||||
<div class="select-footer" v-if="selectedStudent">
|
<div class="select-footer" v-if="selectedStudentIds.length > 0">
|
||||||
<div class="selected-info">
|
<div class="selected-info">
|
||||||
<span>已选择:</span>
|
<span class="selected-count">已选择 {{ selectedStudentIds.length }} 人:</span>
|
||||||
<a-tag color="orange">{{ selectedStudent.name }}</a-tag>
|
<a-space wrap class="selected-tags">
|
||||||
<span class="selected-class">{{ selectedStudent.className }}</span>
|
<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>
|
||||||
<div class="select-actions">
|
<div class="select-actions">
|
||||||
<a-select v-model:value="addChildForm.relationship" style="width: 100px; margin-right: 12px;">
|
<a-select v-model:value="addChildForm.relationship" style="width: 100px; margin-right: 12px;">
|
||||||
@ -302,7 +309,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, computed, onMounted } from 'vue';
|
import { ref, reactive, computed, watch, onMounted } from 'vue';
|
||||||
import {
|
import {
|
||||||
IdcardOutlined,
|
IdcardOutlined,
|
||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
@ -378,7 +385,6 @@ const addChildLoading = ref(false);
|
|||||||
const studentsLoading = ref(false);
|
const studentsLoading = ref(false);
|
||||||
|
|
||||||
const addChildForm = reactive({
|
const addChildForm = reactive({
|
||||||
studentId: undefined as number | undefined,
|
|
||||||
relationship: 'FATHER',
|
relationship: 'FATHER',
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -386,7 +392,10 @@ const addChildForm = reactive({
|
|||||||
const selectStudentModalVisible = ref(false);
|
const selectStudentModalVisible = ref(false);
|
||||||
const studentSearchKeyword = ref('');
|
const studentSearchKeyword = ref('');
|
||||||
const studentClassFilter = ref<number | undefined>();
|
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 studentTableData = ref<Student[]>([]);
|
||||||
const studentPagination = reactive({
|
const studentPagination = reactive({
|
||||||
current: 1,
|
current: 1,
|
||||||
@ -613,24 +622,53 @@ const studentTableColumns = [
|
|||||||
{ title: '班级', dataIndex: 'className', key: 'className', width: 120 },
|
{ title: '班级', dataIndex: 'className', key: 'className', width: 120 },
|
||||||
];
|
];
|
||||||
|
|
||||||
const studentRowSelection = computed(() => ({
|
const studentIdNum = (s: Student) => Number(s.id);
|
||||||
type: 'radio' as 'radio',
|
|
||||||
selectedRowKeys: selectedStudent.value ? [selectedStudent.value.id] : [],
|
/** 合并当前页勾选与其它页已选,实现表格多选跨页保留(id 统一为 number,避免与 rowKey 类型不一致导致无法回显) */
|
||||||
onChange: (selectedRowKeys: (string | number)[]) => {
|
const mergeStudentTableSelection = (keysFromThisPage: (string | number)[]) => {
|
||||||
if (selectedRowKeys.length > 0) {
|
const currentPageIds = studentTableData.value.map(studentIdNum);
|
||||||
const student = studentTableData.value.find(s => s.id === selectedRowKeys[0]);
|
const keysNum = keysFromThisPage.map(Number);
|
||||||
selectedStudent.value = student || null;
|
const fromOtherPages = selectedStudentIds.value.filter((id) => !currentPageIds.includes(id));
|
||||||
} else {
|
selectedStudentIds.value = [...new Set([...fromOtherPages, ...keysNum])];
|
||||||
selectedStudent.value = null;
|
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 () => {
|
const openSelectStudentModal = async () => {
|
||||||
selectStudentModalVisible.value = true;
|
selectStudentModalVisible.value = true;
|
||||||
studentSearchKeyword.value = '';
|
studentSearchKeyword.value = '';
|
||||||
studentClassFilter.value = undefined;
|
studentClassFilter.value = undefined;
|
||||||
selectedStudent.value = null;
|
selectedStudentIds.value = [];
|
||||||
|
selectedStudentMeta.value = {};
|
||||||
studentPagination.current = 1;
|
studentPagination.current = 1;
|
||||||
await loadStudentsForSelect();
|
await loadStudentsForSelect();
|
||||||
await loadClassOptions();
|
await loadClassOptions();
|
||||||
@ -652,6 +690,7 @@ const loadStudentsForSelect = async () => {
|
|||||||
studentTableData.value = [];
|
studentTableData.value = [];
|
||||||
} finally {
|
} finally {
|
||||||
studentsLoading.value = false;
|
studentsLoading.value = false;
|
||||||
|
syncStudentTableRowSelectionKeys();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -677,26 +716,42 @@ const handleStudentTableChange = (pagination: any) => {
|
|||||||
|
|
||||||
const cancelSelectStudent = () => {
|
const cancelSelectStudent = () => {
|
||||||
selectStudentModalVisible.value = false;
|
selectStudentModalVisible.value = false;
|
||||||
selectedStudent.value = null;
|
selectedStudentIds.value = [];
|
||||||
|
selectedStudentMeta.value = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
const confirmAddChild = async () => {
|
const confirmAddChild = async () => {
|
||||||
if (!selectedStudent.value || !currentParent.value) return;
|
if (selectedStudentIds.value.length === 0 || !currentParent.value) return;
|
||||||
|
|
||||||
addChildLoading.value = true;
|
addChildLoading.value = true;
|
||||||
|
let success = 0;
|
||||||
|
let lastError = '';
|
||||||
try {
|
try {
|
||||||
await addChildToParent(currentParent.value.id, {
|
for (const studentId of selectedStudentIds.value) {
|
||||||
studentId: selectedStudent.value.id,
|
try {
|
||||||
relationship: addChildForm.relationship,
|
await addChildToParent(currentParent.value.id, {
|
||||||
});
|
studentId,
|
||||||
message.success('添加成功');
|
relationship: addChildForm.relationship,
|
||||||
selectStudentModalVisible.value = false;
|
});
|
||||||
selectedStudent.value = null;
|
success++;
|
||||||
addChildForm.relationship = 'FATHER';
|
} catch (e: any) {
|
||||||
await loadParentChildren(currentParent.value.id);
|
lastError = e?.response?.data?.message || e?.message || '添加失败';
|
||||||
loadParents(); // 刷新家长列表更新孩子数量
|
}
|
||||||
} catch (error: any) {
|
}
|
||||||
message.error(error?.response?.data?.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 {
|
} finally {
|
||||||
addChildLoading.value = false;
|
addChildLoading.value = false;
|
||||||
}
|
}
|
||||||
@ -1154,8 +1209,10 @@ onMounted(() => {
|
|||||||
|
|
||||||
.select-footer {
|
.select-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
background: #F8F9FA;
|
background: #F8F9FA;
|
||||||
@ -1164,8 +1221,20 @@ onMounted(() => {
|
|||||||
|
|
||||||
.selected-info {
|
.selected-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-count {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-tags {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected-class {
|
.selected-class {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user