修改布局,修复BUG
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
CI / CI OK (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
Deploy Website on push / Rerun on failure (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
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
CI / CI OK (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
Deploy Website on push / Rerun on failure (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
This commit is contained in:
@@ -55,8 +55,7 @@ const formSchema = computed((): VbenFormSchema[] => {
|
||||
* @param values 登录表单数据
|
||||
*/
|
||||
async function handleLogin(values: Recordable<any>) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(values);
|
||||
// 处理验证码登录
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -29,8 +29,7 @@ const formSchema = computed((): VbenFormSchema[] => {
|
||||
});
|
||||
|
||||
function handleSubmit(value: Recordable<any>) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('reset email:', value);
|
||||
// 处理重置密码提交
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -82,8 +82,7 @@ const formSchema = computed((): VbenFormSchema[] => {
|
||||
});
|
||||
|
||||
function handleSubmit(value: Recordable<any>) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('register submit:', value);
|
||||
// 处理注册提交
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
type SchoolApi,
|
||||
type TeacherApi
|
||||
} from '#/api';
|
||||
import SeatLayoutEditor from '#/components/classroom/SeatLayoutEditor.vue';
|
||||
|
||||
defineOptions({ name: 'ClassroomDetail' });
|
||||
|
||||
@@ -37,7 +36,6 @@ const formData = ref<ClassroomApi.SaveParams>({
|
||||
description: '',
|
||||
status: 1,
|
||||
});
|
||||
const layout = ref<ClassroomApi.ClassroomLayout | null>(null);
|
||||
|
||||
const isEdit = computed(() => !!route.params.id);
|
||||
const classroomId = computed(() => (isEdit.value ? Number(route.params.id) : 0));
|
||||
@@ -96,10 +94,6 @@ const fetchDetail = async () => {
|
||||
description: data.description || '',
|
||||
status: data.status !== undefined ? data.status : 1,
|
||||
};
|
||||
// 加载布局数据
|
||||
if (data.layout) {
|
||||
layout.value = data.layout;
|
||||
}
|
||||
} else {
|
||||
message.error(res?.message || res?.msg || '获取教室详情失败');
|
||||
}
|
||||
@@ -147,15 +141,6 @@ const fetchTeacherList = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleLayoutSaved = (newLayout: ClassroomApi.ClassroomLayout) => {
|
||||
layout.value = newLayout;
|
||||
// 更新容量(座位数量)
|
||||
const seatCount = newLayout.cells.filter((cell) => cell.type === 'seat').length;
|
||||
if (seatCount > 0) {
|
||||
formData.value.capacity = seatCount;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchSchoolList();
|
||||
fetchTeacherList();
|
||||
@@ -275,13 +260,6 @@ onMounted(() => {
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane v-if="isEdit" key="layout" tab="座位布局">
|
||||
<SeatLayoutEditor
|
||||
:classroom-id="classroomId"
|
||||
:layout="layout"
|
||||
@saved="handleLayoutSaved"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Card>
|
||||
</Page>
|
||||
|
||||
@@ -2,14 +2,17 @@
|
||||
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, Select } from 'ant-design-vue';
|
||||
import { Button, Card, Table, Space, message, Modal, Input, Select, Tag } from 'ant-design-vue';
|
||||
import {
|
||||
getClassroomListApi,
|
||||
deleteClassroomApi,
|
||||
batchDeleteClassroomApi,
|
||||
getClassroomSeatListApi,
|
||||
getClassroomDetailApi,
|
||||
type ClassroomApi
|
||||
} from '#/api';
|
||||
import { $t } from '#/locales';
|
||||
import SeatLayoutEditor from '#/components/classroom/SeatLayoutEditor.vue';
|
||||
|
||||
|
||||
defineOptions({ name: 'ClassroomList' });
|
||||
|
||||
@@ -30,6 +33,18 @@ const searchForm = reactive({
|
||||
});
|
||||
|
||||
const selectedRowKeys = ref<number[]>([]);
|
||||
const seatListVisible = ref(false);
|
||||
const seatListLoading = ref(false);
|
||||
const seatListData = ref<ClassroomApi.SeatInfo[]>([]);
|
||||
const currentClassroomName = ref('');
|
||||
const seatListPagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
});
|
||||
const layoutVisible = ref(false);
|
||||
const layoutLoading = ref(false);
|
||||
const currentClassroomId = ref<number | null>(null);
|
||||
const currentLayout = ref<ClassroomApi.ClassroomLayout | null>(null);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
@@ -88,7 +103,7 @@ const columns = [
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 200,
|
||||
width: 300,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -178,9 +193,156 @@ const handleBatchDelete = () => {
|
||||
};
|
||||
|
||||
const handleTableChange = (pag: any) => {
|
||||
pagination.current = pag.current;
|
||||
pagination.pageSize = pag.pageSize;
|
||||
fetchData();
|
||||
if (pag) {
|
||||
pagination.current = pag.current || pagination.current;
|
||||
pagination.pageSize = pag.pageSize || pagination.pageSize;
|
||||
fetchData();
|
||||
}
|
||||
};
|
||||
|
||||
// 处理行选择变化
|
||||
const handleRowSelectionChange = (keys: (string | number)[]) => {
|
||||
selectedRowKeys.value = keys.map(key => typeof key === 'string' ? Number(key) : key);
|
||||
};
|
||||
|
||||
// 查看使用状态
|
||||
const handleViewStatus = async (record: ClassroomApi.ClassroomInfo) => {
|
||||
if (!record.id) {
|
||||
message.error('教室ID不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
seatListVisible.value = true;
|
||||
currentClassroomName.value = record.name || '';
|
||||
seatListLoading.value = true;
|
||||
seatListData.value = [];
|
||||
|
||||
try {
|
||||
const res = await getClassroomSeatListApi({ id: record.id });
|
||||
if (res && (res.code === 0 || res.code === 200)) {
|
||||
seatListData.value = Array.isArray(res.data) ? res.data : [];
|
||||
} else {
|
||||
message.error(res?.message || res?.msg || '获取座位列表失败');
|
||||
seatListData.value = [];
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('获取座位列表失败:', error);
|
||||
message.error(error?.response?.data?.message || error?.message || '获取座位列表失败');
|
||||
seatListData.value = [];
|
||||
} finally {
|
||||
seatListLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 关闭使用状态弹窗
|
||||
const handleCloseSeatList = () => {
|
||||
seatListVisible.value = false;
|
||||
seatListData.value = [];
|
||||
currentClassroomName.value = '';
|
||||
seatListPagination.current = 1;
|
||||
seatListPagination.pageSize = 10;
|
||||
};
|
||||
|
||||
// 查看座位布局
|
||||
const handleViewLayout = async (record: ClassroomApi.ClassroomInfo) => {
|
||||
if (!record.id) {
|
||||
message.error('教室ID不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
layoutVisible.value = true;
|
||||
currentClassroomId.value = record.id;
|
||||
currentClassroomName.value = record.name || '';
|
||||
layoutLoading.value = true;
|
||||
currentLayout.value = null;
|
||||
|
||||
try {
|
||||
const res = await getClassroomDetailApi({ id: record.id });
|
||||
if (res && (res.code === 0 || res.code === 200)) {
|
||||
const data = res.data || res;
|
||||
currentLayout.value = data.layout || null;
|
||||
} else {
|
||||
message.error(res?.message || res?.msg || '获取教室详情失败');
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('获取教室详情失败:', error);
|
||||
message.error(error?.response?.data?.message || error?.message || '获取教室详情失败');
|
||||
} finally {
|
||||
layoutLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 关闭座位布局弹窗
|
||||
const handleCloseLayout = () => {
|
||||
layoutVisible.value = false;
|
||||
currentClassroomId.value = null;
|
||||
currentLayout.value = null;
|
||||
currentClassroomName.value = '';
|
||||
};
|
||||
|
||||
// 座位布局保存成功回调
|
||||
const handleLayoutSaved = (layout: ClassroomApi.ClassroomLayout) => {
|
||||
currentLayout.value = layout;
|
||||
message.success('座位布局保存成功');
|
||||
// 可以选择关闭弹窗或保持打开
|
||||
// handleCloseLayout();
|
||||
};
|
||||
|
||||
// 处理座位列表分页变化
|
||||
const handleSeatListTableChange = (pag: any) => {
|
||||
if (pag) {
|
||||
seatListPagination.current = pag.current || seatListPagination.current;
|
||||
seatListPagination.pageSize = pag.pageSize || seatListPagination.pageSize;
|
||||
}
|
||||
};
|
||||
|
||||
// 导出座位列表
|
||||
const handleExportSeatList = () => {
|
||||
if (seatListData.value.length === 0) {
|
||||
message.warning('没有数据可导出');
|
||||
return;
|
||||
}
|
||||
|
||||
// CSV表头
|
||||
const headers = ['座位号', '状态', '学员姓名', '学员手机号', '选座时间'];
|
||||
|
||||
// 转换数据
|
||||
const csvData = seatListData.value.map((item) => {
|
||||
const status = item.is_selected === 1 ? '已选' : '未选';
|
||||
const studentName = item.student_name || '';
|
||||
const studentMobile = item.student_mobile || '';
|
||||
const selectTime = item.select_time || '';
|
||||
return [
|
||||
item.seat_number || '',
|
||||
status,
|
||||
studentName,
|
||||
studentMobile,
|
||||
selectTime,
|
||||
];
|
||||
});
|
||||
|
||||
// 组合CSV内容
|
||||
const csvContent = [
|
||||
headers.join(','),
|
||||
...csvData.map((row) => row.map((cell) => `"${String(cell).replace(/"/g, '""')}"`).join(',')),
|
||||
].join('\n');
|
||||
|
||||
// 添加BOM以支持中文
|
||||
const BOM = '\uFEFF';
|
||||
const blob = new Blob([BOM + csvContent], { type: 'text/csv;charset=utf-8;' });
|
||||
|
||||
// 创建下载链接
|
||||
const link = document.createElement('a');
|
||||
const url = URL.createObjectURL(blob);
|
||||
link.setAttribute('href', url);
|
||||
link.setAttribute('download', `${currentClassroomName.value || '教室'}_座位使用状态_${new Date().toISOString().slice(0, 10)}.csv`);
|
||||
link.style.visibility = 'hidden';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
message.success('导出成功');
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
@@ -233,12 +395,11 @@ onMounted(() => {
|
||||
total: total,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total) => `共 ${total} 条`,
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
}"
|
||||
:row-selection="{
|
||||
selectedRowKeys: selectedRowKeys,
|
||||
onChange: (keys: number[]) => {
|
||||
selectedRowKeys = keys;
|
||||
},
|
||||
onChange: handleRowSelectionChange,
|
||||
}"
|
||||
row-key="id"
|
||||
@change="handleTableChange"
|
||||
@@ -247,12 +408,88 @@ onMounted(() => {
|
||||
<template v-if="column.key === 'action'">
|
||||
<Space>
|
||||
<Button type="link" size="small" @click="handleEdit(record)">编辑</Button>
|
||||
<Button type="link" size="small" @click="handleViewLayout(record)">座位布局</Button>
|
||||
<Button type="link" size="small" @click="handleViewStatus(record)">使用状态</Button>
|
||||
<Button type="link" danger size="small" @click="handleDelete(record)">删除</Button>
|
||||
</Space>
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
</Card>
|
||||
|
||||
<!-- 使用状态弹窗 -->
|
||||
<Modal
|
||||
v-model:open="seatListVisible"
|
||||
:title="`${currentClassroomName} - 使用状态`"
|
||||
width="800px"
|
||||
:footer="null"
|
||||
@cancel="handleCloseSeatList"
|
||||
>
|
||||
<div style="margin-bottom: 16px; text-align: right;">
|
||||
<Button type="primary" @click="handleExportSeatList">
|
||||
导出
|
||||
</Button>
|
||||
</div>
|
||||
<Table
|
||||
:columns="[
|
||||
{ title: '座位号', dataIndex: 'seat_number', key: 'seat_number', width: 100 },
|
||||
{ title: '状态', key: 'status', width: 100 },
|
||||
{ title: '学员姓名', dataIndex: 'student_name', key: 'student_name' },
|
||||
{ title: '学员手机号', dataIndex: 'student_mobile', key: 'student_mobile' },
|
||||
{ title: '选座时间', dataIndex: 'select_time', key: 'select_time' },
|
||||
]"
|
||||
:data-source="seatListData"
|
||||
:loading="seatListLoading"
|
||||
:pagination="{
|
||||
current: seatListPagination.current,
|
||||
pageSize: seatListPagination.pageSize,
|
||||
total: seatListData.length,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total) => `共 ${total} 条`,
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
}"
|
||||
row-key="seat_number"
|
||||
size="small"
|
||||
@change="handleSeatListTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<Tag :color="record.is_selected === 1 ? 'red' : 'green'">
|
||||
{{ record.is_selected === 1 ? '已选' : '未选' }}
|
||||
</Tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'student_name'">
|
||||
{{ record.student_name || '-' }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'student_mobile'">
|
||||
{{ record.student_mobile || '-' }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'select_time'">
|
||||
{{ record.select_time || '-' }}
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
</Modal>
|
||||
|
||||
<!-- 座位布局弹窗 -->
|
||||
<Modal
|
||||
v-model:open="layoutVisible"
|
||||
:title="`${currentClassroomName} - 座位布局`"
|
||||
width="90%"
|
||||
:footer="null"
|
||||
:mask-closable="false"
|
||||
@cancel="handleCloseLayout"
|
||||
>
|
||||
<div v-if="layoutLoading" style="text-align: center; padding: 40px;">
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
<SeatLayoutEditor
|
||||
v-else-if="currentClassroomId"
|
||||
:classroom-id="currentClassroomId"
|
||||
:layout="currentLayout"
|
||||
@saved="handleLayoutSaved"
|
||||
/>
|
||||
</Modal>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -17,11 +17,9 @@ const fetchSystemInfo = async () => {
|
||||
if (res && (res.code === 0 || res.code === 200)) {
|
||||
// 根据实际返回格式,数据在 res.data 中
|
||||
systemInfo.value = res.data || {};
|
||||
} else {
|
||||
console.error('获取系统信息失败:', res?.message || res?.msg);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取系统信息失败:', error);
|
||||
// 静默处理错误
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
@@ -34,45 +32,75 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<Page title="系统概览">
|
||||
<Card title="系统信息">
|
||||
<Spin :spinning="loading">
|
||||
<Descriptions :column="2" bordered>
|
||||
<Descriptions.Item label="操作系统">
|
||||
{{ systemInfo.os || '-' }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="PHP版本">
|
||||
{{ systemInfo.php || '-' }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="Web服务器">
|
||||
{{ systemInfo.server || '-' }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="MySQL版本">
|
||||
{{ systemInfo.mysql || '-' }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="框架版本">
|
||||
{{ systemInfo.framework_version || '-' }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="上传限制">
|
||||
{{ systemInfo.upload_max || '-' }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="最大执行时间">
|
||||
{{ systemInfo.max_execution_time || '-' }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="运行目录">
|
||||
{{ systemInfo.runtime_path || '-' }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="磁盘总空间" :span="2">
|
||||
{{ systemInfo.disk_total_space || '-' }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="磁盘可用空间">
|
||||
{{ systemInfo.disk_free_space || '-' }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="磁盘使用率">
|
||||
{{ systemInfo.disk_usage || '-' }}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</Spin>
|
||||
</Card>
|
||||
<div style="display: flex; flex-direction: column; gap: 16px;">
|
||||
<Card title="系统信息">
|
||||
<Spin :spinning="loading">
|
||||
<Descriptions :column="2" bordered>
|
||||
<Descriptions.Item label="操作系统">
|
||||
{{ systemInfo.os || '-' }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="PHP版本">
|
||||
{{ systemInfo.php || '-' }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="Web服务器">
|
||||
{{ systemInfo.server || '-' }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="MySQL版本">
|
||||
{{ systemInfo.mysql || '-' }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="框架版本">
|
||||
{{ systemInfo.framework_version || '-' }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="上传限制">
|
||||
{{ systemInfo.upload_max || '-' }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="最大执行时间">
|
||||
{{ systemInfo.max_execution_time || '-' }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="运行目录">
|
||||
{{ systemInfo.runtime_path || '-' }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="磁盘总空间" :span="2">
|
||||
{{ systemInfo.disk_total_space || '-' }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="磁盘可用空间">
|
||||
{{ systemInfo.disk_free_space || '-' }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="磁盘使用率">
|
||||
{{ systemInfo.disk_usage || '-' }}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</Spin>
|
||||
</Card>
|
||||
|
||||
<Card title="数据统计">
|
||||
<Spin :spinning="loading">
|
||||
<Descriptions :column="2" bordered>
|
||||
<Descriptions.Item label="学校数量">
|
||||
{{ systemInfo.statistics?.school_count ?? '-' }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="教室数量">
|
||||
{{ systemInfo.statistics?.classroom_count ?? '-' }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="班级数量">
|
||||
{{ systemInfo.statistics?.class_count ?? '-' }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="学生数量">
|
||||
{{ systemInfo.statistics?.student_count ?? '-' }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="教师数量">
|
||||
{{ systemInfo.statistics?.teacher_count ?? '-' }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="预订数量">
|
||||
{{ systemInfo.statistics?.booking_count ?? '-' }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="已选座位数量" :span="2">
|
||||
{{ systemInfo.statistics?.selected_seat_count ?? '-' }}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</Spin>
|
||||
</Card>
|
||||
</div>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { Page } from '@vben/common-ui';
|
||||
import { Card, Form, Input, Button, message, Tabs, InputNumber } from 'ant-design-vue';
|
||||
import { Card, Form, Input, Button, message, Tabs, InputNumber, Upload } from 'ant-design-vue';
|
||||
import { useAppConfig } from '@vben/hooks';
|
||||
import {
|
||||
getSettingApi,
|
||||
saveSettingApi,
|
||||
uploadSettingImageApi,
|
||||
type SettingApi
|
||||
} from '#/api';
|
||||
|
||||
@@ -12,7 +14,11 @@ defineOptions({ name: 'SettingIndex' });
|
||||
|
||||
const formRef = ref();
|
||||
const loading = ref(false);
|
||||
const uploading = ref(false);
|
||||
const activeTab = ref('basic');
|
||||
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||||
const fileList = ref<any[]>([]);
|
||||
|
||||
const formData = ref<Partial<SettingApi.SettingInfo>>({
|
||||
site_name: '',
|
||||
site_desc: '',
|
||||
@@ -33,6 +39,23 @@ const formData = ref<Partial<SettingApi.SettingInfo>>({
|
||||
wxapp_original: '',
|
||||
});
|
||||
|
||||
// 获取完整的头像URL
|
||||
const getAvatarUrl = (avatarPath?: string | null): string => {
|
||||
if (!avatarPath) return '';
|
||||
// 如果已经是完整URL,直接返回
|
||||
if (avatarPath.startsWith('http://') || avatarPath.startsWith('https://')) {
|
||||
return avatarPath;
|
||||
}
|
||||
// 拼接完整的URL
|
||||
if (!apiURL) {
|
||||
console.warn('apiURL is not configured');
|
||||
return avatarPath;
|
||||
}
|
||||
const baseUrl = apiURL.replace(/\/$/, '');
|
||||
const path = avatarPath.startsWith('/') ? avatarPath : `/${avatarPath}`;
|
||||
return `${baseUrl}${path}`;
|
||||
};
|
||||
|
||||
const fetchData = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
@@ -61,6 +84,20 @@ const fetchData = async () => {
|
||||
wxapp_name: data.wxapp_name || '',
|
||||
wxapp_original: data.wxapp_original || '',
|
||||
};
|
||||
// 初始化头像文件列表
|
||||
if (formData.value.default_avatar) {
|
||||
const avatarUrl = getAvatarUrl(formData.value.default_avatar);
|
||||
fileList.value = [
|
||||
{
|
||||
uid: '-1',
|
||||
name: 'avatar',
|
||||
status: 'done',
|
||||
url: avatarUrl,
|
||||
},
|
||||
];
|
||||
} else {
|
||||
fileList.value = [];
|
||||
}
|
||||
} else {
|
||||
message.error(res?.message || res?.msg || '获取系统设置失败');
|
||||
}
|
||||
@@ -72,6 +109,94 @@ const fetchData = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 处理头像上传前
|
||||
const beforeUpload = (file: File) => {
|
||||
const isImage = file.type.startsWith('image/');
|
||||
if (!isImage) {
|
||||
message.error('只能上传图片文件!');
|
||||
return false;
|
||||
}
|
||||
const isLt10M = file.size / 1024 / 1024 < 10;
|
||||
if (!isLt10M) {
|
||||
message.error('图片大小不能超过 10MB!');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 立即上传
|
||||
handleUpload(file);
|
||||
|
||||
return false; // 阻止自动上传
|
||||
};
|
||||
|
||||
// 上传图片
|
||||
const handleUpload = async (file: File) => {
|
||||
try {
|
||||
uploading.value = true;
|
||||
const res = await uploadSettingImageApi(file);
|
||||
|
||||
// 处理响应,获取图片路径
|
||||
// 支持多种响应格式:
|
||||
// 1. { code: 200, data: "path" } 或 { code: 200, data: { src: "path" } }
|
||||
// 2. { code: 200, data: { url: "path" } } 或 { code: 200, data: { path: "path" } }
|
||||
// 3. 直接返回字符串路径
|
||||
// 4. { url: "path" } 或 { path: "path" }
|
||||
let imagePath = '';
|
||||
if (res && (res.code === 0 || res.code === 200)) {
|
||||
// 标准响应格式
|
||||
if (typeof res.data === 'string') {
|
||||
imagePath = res.data;
|
||||
} else if (res.data?.src) {
|
||||
// 优先使用 src 字段
|
||||
imagePath = res.data.src;
|
||||
} else if (res.data?.url || res.data?.path) {
|
||||
imagePath = res.data.url || res.data.path;
|
||||
}
|
||||
} else if (typeof res === 'string') {
|
||||
// 直接返回字符串
|
||||
imagePath = res;
|
||||
} else if (res?.data) {
|
||||
// 嵌套的 data 字段
|
||||
if (typeof res.data === 'string') {
|
||||
imagePath = res.data;
|
||||
} else {
|
||||
imagePath = res.data.src || res.data.url || res.data.path || '';
|
||||
}
|
||||
} else if (res?.src || res?.url || res?.path) {
|
||||
// 直接包含 src、url 或 path
|
||||
imagePath = res.src || res.url || res.path;
|
||||
}
|
||||
|
||||
if (imagePath) {
|
||||
formData.value.default_avatar = imagePath;
|
||||
// 更新文件列表
|
||||
const avatarUrl = getAvatarUrl(imagePath);
|
||||
fileList.value = [
|
||||
{
|
||||
uid: Date.now().toString(),
|
||||
name: file.name,
|
||||
status: 'done',
|
||||
url: avatarUrl,
|
||||
},
|
||||
];
|
||||
message.success('头像上传成功');
|
||||
} else {
|
||||
message.error('上传失败:未获取到图片路径');
|
||||
}
|
||||
} catch (error: any) {
|
||||
message.error(error?.response?.data?.message || error?.message || '上传头像失败');
|
||||
} finally {
|
||||
uploading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 处理头像上传变化
|
||||
const handleAvatarChange = (info: any) => {
|
||||
if (info.file.status === 'removed') {
|
||||
fileList.value = [];
|
||||
formData.value.default_avatar = '';
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
await formRef.value.validate();
|
||||
@@ -111,7 +236,21 @@ onMounted(() => {
|
||||
<Input v-model:value="formData.site_icp" placeholder="请输入ICP备案号" />
|
||||
</Form.Item>
|
||||
<Form.Item label="默认头像" name="default_avatar">
|
||||
<Input v-model:value="formData.default_avatar" placeholder="默认头像路径" />
|
||||
<Upload
|
||||
v-model:file-list="fileList"
|
||||
list-type="picture-card"
|
||||
:max-count="1"
|
||||
:before-upload="beforeUpload"
|
||||
:disabled="uploading"
|
||||
@change="handleAvatarChange"
|
||||
accept="image/*"
|
||||
>
|
||||
<div v-if="fileList.length < 1">
|
||||
<div style="margin-top: 8px">
|
||||
<span>上传</span>
|
||||
</div>
|
||||
</div>
|
||||
</Upload>
|
||||
</Form.Item>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="upload" tab="上传设置">
|
||||
|
||||
@@ -105,28 +105,21 @@ const fetchData = async () => {
|
||||
...searchForm,
|
||||
};
|
||||
const res = await getStudentListApi(params);
|
||||
console.log('学生列表API返回:', res); // 调试用
|
||||
// 根据实际返回格式,code 为 0 表示成功
|
||||
if (res && (res.code === 0 || res.code === 200)) {
|
||||
const data = res.data;
|
||||
console.log('学生列表数据:', data); // 调试用
|
||||
console.log('学生列表总数:', res.count); // 调试用
|
||||
if (Array.isArray(data)) {
|
||||
tableData.value = data;
|
||||
total.value = res.count || data.length;
|
||||
console.log('设置学生列表,数量:', tableData.value.length, '总数:', total.value); // 调试用
|
||||
} else {
|
||||
console.warn('学生列表数据不是数组:', data);
|
||||
tableData.value = [];
|
||||
total.value = 0;
|
||||
}
|
||||
} else {
|
||||
console.warn('获取学生列表失败,响应码:', res?.code, res);
|
||||
tableData.value = [];
|
||||
total.value = 0;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取学生列表失败:', error);
|
||||
tableData.value = [];
|
||||
total.value = 0;
|
||||
} finally {
|
||||
@@ -191,24 +184,18 @@ const fileList = ref<any[]>([]);
|
||||
const fetchClassList = async () => {
|
||||
try {
|
||||
const res = await getClassListApi({ page: 1, limit: 1000 });
|
||||
console.log('导入对话框-班级列表API返回:', res); // 调试用
|
||||
if (res && (res.code === 0 || res.code === 200)) {
|
||||
// 根据实际返回格式,data 是数组
|
||||
const data = res.data;
|
||||
console.log('导入对话框-班级列表数据:', data); // 调试用
|
||||
if (Array.isArray(data)) {
|
||||
classList.value = data;
|
||||
console.log('导入对话框-设置班级列表,数量:', classList.value.length); // 调试用
|
||||
} else {
|
||||
console.warn('导入对话框-班级列表数据不是数组:', data);
|
||||
classList.value = [];
|
||||
}
|
||||
} else {
|
||||
console.warn('导入对话框-获取班级列表失败:', res);
|
||||
classList.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('导入对话框-获取班级列表失败:', error);
|
||||
classList.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user