1
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
CI / CI OK (push) Has been cancelled
Deploy Website on push / Rerun on failure (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled

This commit is contained in:
杨志
2025-12-05 13:39:40 +08:00
parent 21107f02fd
commit 51a72f1f0c
1239 changed files with 107262 additions and 1 deletions

View File

@@ -0,0 +1,250 @@
<script lang="ts" setup>
import { ref, onMounted, computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { Page } from '@vben/common-ui';
import { Card, Form, Input, Button, message, Select, Radio } from 'ant-design-vue';
import {
getClassDetailApi,
saveClassApi,
getTeachersApi,
getClassroomsApi,
type ClassApi
} from '#/api';
defineOptions({ name: 'ClassDetail' });
const route = useRoute();
const router = useRouter();
const formRef = ref();
const loading = ref(false);
const teacherList = ref<any[]>([]);
const classroomList = ref<any[]>([]);
const teacherListLoading = ref(false);
const classroomListLoading = ref(false);
const formData = ref<Partial<ClassApi.SaveParams>>({
name: '',
grade: '',
teacher_id: undefined,
class_room_id: undefined,
description: '',
status: 1,
});
const isEdit = computed(() => !!route.params.id);
const handleSubmit = async () => {
try {
await formRef.value.validate();
loading.value = true;
const data = { ...formData.value };
if (isEdit.value) {
data.id = Number(route.params.id);
}
await saveClassApi(data);
message.success(isEdit.value ? '更新成功' : '创建成功');
router.back();
} catch (error: any) {
console.error('保存失败:', error);
message.error(error?.response?.data?.message || error?.response?.data?.msg || '保存失败');
} finally {
loading.value = false;
}
};
const fetchDetail = async () => {
if (!isEdit.value) return;
loading.value = true;
try {
const res = await getClassDetailApi({ id: Number(route.params.id) });
// 支持 code 为 0 或 200 的成功响应
if (res && (res.code === 0 || res.code === 200)) {
// 根据实际返回格式,数据在 res.data 中
const data = res.data;
if (data) {
formData.value = {
name: data.name || '',
grade: data.grade || '',
teacher_id: data.teacher_id || undefined,
class_room_id: data.class_room_id || undefined,
description: data.description || '',
status: data.status !== undefined ? data.status : 1,
};
} else {
message.error('获取班级详情失败:数据为空');
}
} else {
message.error(res?.message || res?.msg || '获取班级详情失败');
}
} catch (error: any) {
console.error('获取详情失败:', error);
message.error(error?.response?.data?.message || error?.response?.data?.msg || '获取班级详情失败');
} finally {
loading.value = false;
}
};
// 获取班主任列表
const fetchTeacherList = async () => {
teacherListLoading.value = true;
try {
const res: any = await getTeachersApi();
if (Array.isArray(res)) {
teacherList.value = res;
} else if (res && (res.code === 0 || res.code === 200)) {
teacherList.value = Array.isArray(res.data) ? res.data : [];
} else {
teacherList.value = [];
}
} catch (error) {
console.error('获取班主任列表失败:', error);
teacherList.value = [];
} finally {
teacherListLoading.value = false;
}
};
// 获取教室列表
const fetchClassroomList = async () => {
classroomListLoading.value = true;
try {
const res = await getClassroomsApi();
if (res && (res.code === 0 || res.code === 200)) {
// 根据实际返回格式,教室列表在 res.data.all_classrooms 中
if (res.data && res.data.all_classrooms) {
classroomList.value = Array.isArray(res.data.all_classrooms) ? res.data.all_classrooms : [];
} else if (Array.isArray(res.data)) {
// 兼容直接返回数组的情况
classroomList.value = res.data;
} else {
classroomList.value = [];
}
} else if (Array.isArray(res)) {
// 兼容直接返回数组的情况
classroomList.value = res;
} else {
classroomList.value = [];
}
} catch (error) {
console.error('获取教室列表失败:', error);
classroomList.value = [];
} finally {
classroomListLoading.value = false;
}
};
onMounted(async () => {
// 先加载下拉列表数据,确保在填充表单时选项已存在
await Promise.all([fetchTeacherList(), fetchClassroomList()]);
// 然后再加载详情数据
if (isEdit.value) {
fetchDetail();
}
});
</script>
<template>
<Page :title="isEdit ? '编辑班级' : '新增班级'">
<Card>
<Form
ref="formRef"
:model="formData"
:label-col="{ span: 4 }"
:wrapper-col="{ span: 20 }"
>
<Form.Item
label="班级名称"
name="name"
:rules="[{ required: true, message: '请输入班级名称' }]"
>
<Input v-model:value="formData.name" placeholder="请输入班级名称" />
</Form.Item>
<Form.Item
label="年级"
name="grade"
:rules="[{ required: true, message: '请输入年级' }]"
>
<Input v-model:value="formData.grade" placeholder="请输入年级" />
</Form.Item>
<Form.Item
label="班主任"
name="teacher_id"
:rules="[{ required: true, message: '请选择班主任' }]"
>
<Select
v-model:value="formData.teacher_id"
placeholder="请选择班主任"
:loading="teacherListLoading"
allow-clear
show-search
:filter-option="(input, option) => {
const label = option?.label || '';
return label.toLowerCase().includes(input.toLowerCase());
}"
>
<template v-if="teacherList.length === 0 && !teacherListLoading">
<Select.Option disabled value="">暂无班主任数据</Select.Option>
</template>
<Select.Option
v-for="item in teacherList"
:key="item.id"
:value="item.id"
:label="item.name || ''"
>
{{ item.name || `班主任 ${item.id}` }}
</Select.Option>
</Select>
<div v-if="teacherList.length > 0" style="margin-top: 4px; font-size: 12px; color: #999;">
共 {{ teacherList.length }} 个班主任可选
</div>
</Form.Item>
<Form.Item
label="教室"
name="class_room_id"
:rules="[{ required: true, message: '请选择教室' }]"
>
<Select
v-model:value="formData.class_room_id"
placeholder="请选择教室"
:loading="classroomListLoading"
allow-clear
show-search
:filter-option="(input, option) => {
const label = option?.label || '';
return label.toLowerCase().includes(input.toLowerCase());
}"
>
<template v-if="classroomList.length === 0 && !classroomListLoading">
<Select.Option disabled value="">暂无教室数据</Select.Option>
</template>
<Select.Option
v-for="item in classroomList"
:key="item.id"
:value="item.id"
:label="item.name || ''"
>
{{ item.name || `教室 ${item.id}` }}
</Select.Option>
</Select>
<div v-if="classroomList.length > 0" style="margin-top: 4px; font-size: 12px; color: #999;">
共 {{ classroomList.length }} 个教室可选
</div>
</Form.Item>
<Form.Item label="描述" name="description">
<Input.TextArea v-model:value="formData.description" placeholder="请输入描述" :rows="3" />
</Form.Item>
<Form.Item label="状态" name="status">
<Radio.Group v-model:value="formData.status">
<Radio :value="1">启用</Radio>
<Radio :value="0">禁用</Radio>
</Radio.Group>
</Form.Item>
<Form.Item :wrapper-col="{ offset: 4, span: 20 }">
<Button type="primary" :loading="loading" @click="handleSubmit">保存</Button>
<Button class="ml-2" @click="router.back()">取消</Button>
</Form.Item>
</Form>
</Card>
</Page>
</template>

View File

@@ -0,0 +1,203 @@
<script lang="ts" setup>
import { ref, reactive, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { Page } from '@vben/common-ui';
import { Button, Card, Table, Space, message, Modal, Input } from 'ant-design-vue';
import {
getClassListApi,
deleteClassApi,
type ClassApi
} from '#/api';
defineOptions({ name: 'ClassList' });
const router = useRouter();
const loading = ref(false);
const tableData = ref<ClassApi.ClassInfo[]>([]);
const total = ref(0);
const pagination = reactive({
current: 1,
pageSize: 10,
});
const searchForm = reactive({
name: '',
teacher_name: '',
school_id: undefined as number | undefined,
});
const columns = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
width: 80,
},
{
title: '班级名称',
dataIndex: 'name',
key: 'name',
},
{
title: '年级',
dataIndex: 'grade',
key: 'grade',
width: 100,
},
{
title: '班主任',
key: 'teacher_name',
width: 120,
},
{
title: '教室',
key: 'classroom_name',
width: 150,
},
{
title: '学生人数',
dataIndex: 'student_count',
key: 'student_count',
width: 100,
},
{
title: '操作',
key: 'action',
width: 200,
},
];
const fetchData = async () => {
loading.value = true;
try {
const params: ClassApi.ListParams = {
page: pagination.current,
limit: pagination.pageSize,
...searchForm,
};
const res = await getClassListApi(params);
// 根据实际返回格式code 为 0 表示成功,返回格式为 {code, msg, count, data}
if (res && (res.code === 0 || res.code === 200)) {
// 处理数据,将嵌套的 teacher 和 classroom 信息映射到表格需要的字段
const data = Array.isArray(res.data) ? res.data : [];
tableData.value = data.map((item: ClassApi.ClassInfo) => ({
...item,
// 映射 teacher_name 字段
teacher_name: item.teacher?.name || '',
// 映射 classroom_name 字段
classroom_name: item.classroom?.name || '',
// 确保 classroom_id 字段存在(使用 class_room_id
classroom_id: item.class_room_id || item.classroom?.id,
}));
total.value = res.count || 0;
} else {
tableData.value = [];
total.value = 0;
}
} catch (error) {
console.error('获取班级列表失败:', error);
tableData.value = [];
total.value = 0;
} finally {
loading.value = false;
}
};
const handleSearch = () => {
pagination.current = 1;
fetchData();
};
const handleAdd = () => {
router.push({ name: 'ClassDetail' });
};
const handleEdit = (record: ClassApi.ClassInfo) => {
router.push({
name: 'ClassDetail',
params: { id: record.id }
});
};
const handleDelete = (record: ClassApi.ClassInfo) => {
Modal.confirm({
title: '确认删除',
content: `确定要删除班级"${record.name}"吗?`,
onOk: async () => {
try {
await deleteClassApi({ id: record.id! });
message.success('删除成功');
fetchData();
} catch (error) {
console.error('删除失败:', error);
}
},
});
};
const handleTableChange = (pag: any) => {
pagination.current = pag.current;
pagination.pageSize = pag.pageSize;
fetchData();
};
onMounted(() => {
fetchData();
});
</script>
<template>
<Page title="班级列表">
<Card>
<div class="mb-4">
<Space>
<Input
v-model:value="searchForm.name"
placeholder="班级名称"
style="width: 200px"
@press-enter="handleSearch"
/>
<Input
v-model:value="searchForm.teacher_name"
placeholder="班主任姓名"
style="width: 200px"
@press-enter="handleSearch"
/>
<Button type="primary" @click="handleSearch">搜索</Button>
<Button type="primary" @click="handleAdd">添加班级</Button>
</Space>
</div>
<Table
:columns="columns"
:data-source="tableData"
:loading="loading"
:pagination="{
current: pagination.current,
pageSize: pagination.pageSize,
total: total,
showSizeChanger: true,
showTotal: (total) => `共 ${total} 条`,
}"
row-key="id"
@change="handleTableChange"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'teacher_name'">
{{ record.teacher?.name || '-' }}
</template>
<template v-else-if="column.key === 'classroom_name'">
{{ record.classroom?.name || '-' }}
</template>
<template v-else-if="column.key === 'action'">
<Space>
<Button type="link" size="small" @click="handleEdit(record)">编辑</Button>
<Button type="link" danger size="small" @click="handleDelete(record)">删除</Button>
</Space>
</template>
</template>
</Table>
</Card>
</Page>
</template>