Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a986709776 | ||
|
|
bab12cbed3 | ||
|
|
c8ad14449b |
27
reading-platform-frontend/src/components.d.ts
vendored
27
reading-platform-frontend/src/components.d.ts
vendored
@ -11,35 +11,22 @@ declare module 'vue' {
|
||||
AAvatar: typeof import('ant-design-vue/es')['Avatar']
|
||||
ABadge: typeof import('ant-design-vue/es')['Badge']
|
||||
AButton: typeof import('ant-design-vue/es')['Button']
|
||||
AButtonGroup: typeof import('ant-design-vue/es')['ButtonGroup']
|
||||
ACard: typeof import('ant-design-vue/es')['Card']
|
||||
ACheckbox: typeof import('ant-design-vue/es')['Checkbox']
|
||||
ACheckboxGroup: typeof import('ant-design-vue/es')['CheckboxGroup']
|
||||
ACol: typeof import('ant-design-vue/es')['Col']
|
||||
ACollapse: typeof import('ant-design-vue/es')['Collapse']
|
||||
ACollapsePanel: typeof import('ant-design-vue/es')['CollapsePanel']
|
||||
ADatePicker: typeof import('ant-design-vue/es')['DatePicker']
|
||||
ADescriptions: typeof import('ant-design-vue/es')['Descriptions']
|
||||
ADescriptionsItem: typeof import('ant-design-vue/es')['DescriptionsItem']
|
||||
ADivider: typeof import('ant-design-vue/es')['Divider']
|
||||
ADrawer: typeof import('ant-design-vue/es')['Drawer']
|
||||
ADropdown: typeof import('ant-design-vue/es')['Dropdown']
|
||||
AEmpty: typeof import('ant-design-vue/es')['Empty']
|
||||
AForm: typeof import('ant-design-vue/es')['Form']
|
||||
AFormItem: typeof import('ant-design-vue/es')['FormItem']
|
||||
AImage: typeof import('ant-design-vue/es')['Image']
|
||||
AImagePreviewGroup: typeof import('ant-design-vue/es')['ImagePreviewGroup']
|
||||
AInput: typeof import('ant-design-vue/es')['Input']
|
||||
AInputNumber: typeof import('ant-design-vue/es')['InputNumber']
|
||||
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
|
||||
AInputSearch: typeof import('ant-design-vue/es')['InputSearch']
|
||||
ALayout: typeof import('ant-design-vue/es')['Layout']
|
||||
ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent']
|
||||
ALayoutHeader: typeof import('ant-design-vue/es')['LayoutHeader']
|
||||
ALayoutSider: typeof import('ant-design-vue/es')['LayoutSider']
|
||||
AList: typeof import('ant-design-vue/es')['List']
|
||||
AListItem: typeof import('ant-design-vue/es')['ListItem']
|
||||
AListItemMeta: typeof import('ant-design-vue/es')['ListItemMeta']
|
||||
AMenu: typeof import('ant-design-vue/es')['Menu']
|
||||
AMenuDivider: typeof import('ant-design-vue/es')['MenuDivider']
|
||||
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
|
||||
@ -48,34 +35,20 @@ declare module 'vue' {
|
||||
APagination: typeof import('ant-design-vue/es')['Pagination']
|
||||
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
|
||||
AProgress: typeof import('ant-design-vue/es')['Progress']
|
||||
ARadio: typeof import('ant-design-vue/es')['Radio']
|
||||
ARadioButton: typeof import('ant-design-vue/es')['RadioButton']
|
||||
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
|
||||
ARangePicker: typeof import('ant-design-vue/es')['RangePicker']
|
||||
ARate: typeof import('ant-design-vue/es')['Rate']
|
||||
AResult: typeof import('ant-design-vue/es')['Result']
|
||||
ARow: typeof import('ant-design-vue/es')['Row']
|
||||
ASelect: typeof import('ant-design-vue/es')['Select']
|
||||
ASelectOptGroup: typeof import('ant-design-vue/es')['SelectOptGroup']
|
||||
ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
|
||||
ASkeleton: typeof import('ant-design-vue/es')['Skeleton']
|
||||
ASpace: typeof import('ant-design-vue/es')['Space']
|
||||
ASpin: typeof import('ant-design-vue/es')['Spin']
|
||||
AStatistic: typeof import('ant-design-vue/es')['Statistic']
|
||||
AStep: typeof import('ant-design-vue/es')['Step']
|
||||
ASteps: typeof import('ant-design-vue/es')['Steps']
|
||||
ASubMenu: typeof import('ant-design-vue/es')['SubMenu']
|
||||
ASwitch: typeof import('ant-design-vue/es')['Switch']
|
||||
ATable: typeof import('ant-design-vue/es')['Table']
|
||||
ATabPane: typeof import('ant-design-vue/es')['TabPane']
|
||||
ATabs: typeof import('ant-design-vue/es')['Tabs']
|
||||
ATag: typeof import('ant-design-vue/es')['Tag']
|
||||
ATextarea: typeof import('ant-design-vue/es')['Textarea']
|
||||
ATimeRangePicker: typeof import('ant-design-vue/es')['TimeRangePicker']
|
||||
ATooltip: typeof import('ant-design-vue/es')['Tooltip']
|
||||
ATypographyText: typeof import('ant-design-vue/es')['TypographyText']
|
||||
AUpload: typeof import('ant-design-vue/es')['Upload']
|
||||
AUploadDragger: typeof import('ant-design-vue/es')['UploadDragger']
|
||||
FilePreviewModal: typeof import('./components/FilePreviewModal.vue')['default']
|
||||
FileUploader: typeof import('./components/course/FileUploader.vue')['default']
|
||||
LessonConfigPanel: typeof import('./components/course/LessonConfigPanel.vue')['default']
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="course-detail-view">
|
||||
<!-- 顶部导航 -->
|
||||
<div class="detail-header">
|
||||
<div class="header-left">
|
||||
<div class="detail-header flex flex-wrap items-center">
|
||||
<div class="header-left flex-shrink-0">
|
||||
<a-button type="text" @click="router.back()">
|
||||
<ArrowLeftOutlined />
|
||||
</a-button>
|
||||
@ -788,7 +788,9 @@ const fetchCourseDetail = async () => {
|
||||
background: white;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
padding-top: 1.5rem;
|
||||
top: -1.5rem;
|
||||
margin-top: -1.5rem;
|
||||
z-index: 100;
|
||||
|
||||
.header-left {
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
<template>
|
||||
<div class="min-h-100vh bg-[linear-gradient(180deg,#FFF8F0_0%,#FFFFFF_100%)] p-0">
|
||||
<div class="min-h-100vh bg-[linear-gradient(180deg,#FFF8F0_0%,#FFFFFF_100%)] px-4 py-4 md:px-6 md:py-6">
|
||||
<!-- 页面头部 -->
|
||||
<div class="rounded-[20px] py-6 px-8 mb-6 bg-[linear-gradient(135deg,#4facfe_0%,#00f2fe_100%)]">
|
||||
<div class="flex justify-between items-center max-md:flex-col max-md:items-start max-md:gap-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-14 h-14 rounded-2xl flex items-center justify-center text-[28px] text-white bg-[linear-gradient(135deg,rgba(255,255,255,0.3)_0%,rgba(255,255,255,0.1)_100%)]">
|
||||
<div
|
||||
class="w-14 h-14 rounded-2xl flex items-center justify-center text-[28px] text-white bg-[linear-gradient(135deg,rgba(255,255,255,0.3)_0%,rgba(255,255,255,0.1)_100%)]">
|
||||
<HomeOutlined />
|
||||
</div>
|
||||
<div>
|
||||
@ -26,48 +27,36 @@
|
||||
</div>
|
||||
|
||||
<!-- 操作栏 -->
|
||||
<div class="flex justify-between items-center mb-6 py-4 px-5 bg-white rounded-2xl shadow-[0_2px_8px_rgba(0,0,0,0.04)] search-box-wrapper">
|
||||
<div class="search-box">
|
||||
<a-input-search
|
||||
v-model:value="searchKeyword"
|
||||
placeholder="搜索班级名称"
|
||||
class="w-[250px]"
|
||||
@search="handleSearch"
|
||||
allow-clear
|
||||
>
|
||||
<template #prefix>
|
||||
<SearchOutlined class="text-[#B2BEC3]" />
|
||||
</template>
|
||||
</a-input-search>
|
||||
<div
|
||||
class="flex justify-between items-center mb-6 py-4 px-5 bg-white rounded-2xl shadow-[0_2px_8px_rgba(0,0,0,0.04)] search-box-wrapper">
|
||||
<div class="">
|
||||
<a-input-search v-model:value="searchKeyword" placeholder="搜索班级名称" class="w-[250px]" @search="handleSearch"
|
||||
allow-clear :enter-button="false" />
|
||||
</div>
|
||||
<a-button type="primary" class="!bg-[linear-gradient(135deg,#4facfe_0%,#00f2fe_100%)] hover:!bg-[linear-gradient(135deg,#3d9be8_0%,#00d8e4_100%)] !border-0 rounded-xl h-10 px-6 font-600 add-btn" @click="showAddModal">
|
||||
<a-button type="primary"
|
||||
class="!bg-[linear-gradient(135deg,#4facfe_0%,#00f2fe_100%)] hover:!bg-[linear-gradient(135deg,#3d9be8_0%,#00d8e4_100%)] !border-0 rounded-xl h-10 px-6 font-600 add-btn"
|
||||
@click="showAddModal">
|
||||
<PlusOutlined class="mr-2" />
|
||||
添加班级
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<!-- 班级卡片网格 -->
|
||||
<div class="grid gap-5 mb-6 class-grid" style="grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));" v-if="!loading && classes.length > 0">
|
||||
<div
|
||||
v-for="cls in classes"
|
||||
:key="cls.id"
|
||||
<div class="grid gap-5 mb-6 class-grid" style="grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));"
|
||||
v-if="!loading && classes.length > 0">
|
||||
<div v-for="cls in classes" :key="cls.id"
|
||||
class="bg-white rounded-2xl overflow-hidden shadow-[0_4px_12px_rgba(0,0,0,0.05)] transition-all duration-300 border-2 border-transparent hover:translate-y-[-4px] hover:shadow-[0_8px_24px_rgba(0,0,0,0.1)] class-card"
|
||||
:class="getGradeKey(cls.grade) === 'small' ? 'border-t-4 border-t-[#43e97b]' : getGradeKey(cls.grade) === 'middle' ? 'border-t-4 border-t-[#4facfe]' : 'border-t-4 border-t-[#FF8C42]'"
|
||||
>
|
||||
:class="getGradeKey(cls.grade) === 'small' ? 'border-t-4 border-t-[#43e97b]' : getGradeKey(cls.grade) === 'middle' ? 'border-t-4 border-t-[#4facfe]' : 'border-t-4 border-t-[#FF8C42]'">
|
||||
<div class="flex items-center gap-3 py-4 px-5 bg-[linear-gradient(135deg,#F8F9FA_0%,#FFFFFF_100%)] card-header">
|
||||
<div
|
||||
class="w-12 h-12 rounded-xl flex items-center justify-center icon-wrapper"
|
||||
:class="getGradeKey(cls.grade) === 'small' ? 'bg-[linear-gradient(135deg,#43e97b_0%,#38f9d7_100%)]' : getGradeKey(cls.grade) === 'middle' ? 'bg-[linear-gradient(135deg,#4facfe_0%,#00f2fe_100%)]' : 'bg-[linear-gradient(135deg,#FF8C42_0%,#FFB347_100%)]'"
|
||||
>
|
||||
<div class="w-12 h-12 rounded-xl flex items-center justify-center icon-wrapper"
|
||||
:class="getGradeKey(cls.grade) === 'small' ? 'bg-[linear-gradient(135deg,#43e97b_0%,#38f9d7_100%)]' : getGradeKey(cls.grade) === 'middle' ? 'bg-[linear-gradient(135deg,#4facfe_0%,#00f2fe_100%)]' : 'bg-[linear-gradient(135deg,#FF8C42_0%,#FFB347_100%)]'">
|
||||
<component :is="getGradeIcon(cls.grade)" class="text-2xl text-white" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="text-base font-600 text-[#2D3436]">{{ cls.name }}</div>
|
||||
<div class="mt-1">
|
||||
<span
|
||||
class="py-0.5 px-2.5 rounded-[10px] text-[11px] font-500 grade-badge"
|
||||
:class="getGradeKey(cls.grade) === 'small' ? 'bg-[#E8F5E9] text-[#43A047]' : getGradeKey(cls.grade) === 'middle' ? 'bg-[#E3F2FD] text-[#1976D2]' : 'bg-[#FFF8F0] text-[#FF8C42]'"
|
||||
>
|
||||
<span class="py-0.5 px-2.5 rounded-[10px] text-[11px] font-500 grade-badge"
|
||||
:class="getGradeKey(cls.grade) === 'small' ? 'bg-[#E8F5E9] text-[#43A047]' : getGradeKey(cls.grade) === 'middle' ? 'bg-[#E3F2FD] text-[#1976D2]' : 'bg-[#FFF8F0] text-[#FF8C42]'">
|
||||
{{ cls.grade }}
|
||||
</span>
|
||||
</div>
|
||||
@ -76,13 +65,15 @@
|
||||
|
||||
<div class="py-4 px-5 card-body">
|
||||
<div class="flex items-center gap-2 mb-2 text-[13px] info-row">
|
||||
<div class="w-6 h-6 rounded-md flex items-center justify-center bg-[linear-gradient(135deg,#F0F4F8_0%,#E8EDF2_100%)]">
|
||||
<div
|
||||
class="w-6 h-6 rounded-md flex items-center justify-center bg-[linear-gradient(135deg,#F0F4F8_0%,#E8EDF2_100%)]">
|
||||
<UserOutlined class="text-xs text-[#4facfe]" />
|
||||
</div>
|
||||
<div class="text-[#636E72] flex flex-wrap items-center gap-0.5 teachers-value">
|
||||
<template v-if="cls.teachers && cls.teachers.length > 0">
|
||||
<span v-for="(teacher, index) in cls.teachers.slice(0, 3)" :key="teacher.teacherId">
|
||||
{{ teacher.teacherName }}<span v-if="teacher.isPrimary" class="text-[10px] py-0.5 px-1 rounded bg-[linear-gradient(135deg,#4facfe_0%,#00f2fe_100%)] text-white ml-1">班主任</span>
|
||||
{{ teacher.teacherName }}<span v-if="teacher.isPrimary"
|
||||
class="text-[10px] py-0.5 px-1 rounded bg-[linear-gradient(135deg,#4facfe_0%,#00f2fe_100%)] text-white ml-1">班主任</span>
|
||||
<span v-if="index < Math.min(cls.teachers.length, 3) - 1">、</span>
|
||||
</span>
|
||||
<span v-if="cls.teachers.length > 3" class="text-[#888] text-xs ml-1">等{{ cls.teachers.length }}人</span>
|
||||
@ -91,7 +82,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 mb-2 text-[13px] info-row">
|
||||
<div class="w-6 h-6 rounded-md flex items-center justify-center bg-[linear-gradient(135deg,#F0F4F8_0%,#E8EDF2_100%)]">
|
||||
<div
|
||||
class="w-6 h-6 rounded-md flex items-center justify-center bg-[linear-gradient(135deg,#F0F4F8_0%,#E8EDF2_100%)]">
|
||||
<TeamOutlined class="text-xs text-[#4facfe]" />
|
||||
</div>
|
||||
<span class="text-[#636E72]">
|
||||
@ -99,7 +91,8 @@
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-[13px] info-row">
|
||||
<div class="w-6 h-6 rounded-md flex items-center justify-center bg-[linear-gradient(135deg,#F0F4F8_0%,#E8EDF2_100%)]">
|
||||
<div
|
||||
class="w-6 h-6 rounded-md flex items-center justify-center bg-[linear-gradient(135deg,#F0F4F8_0%,#E8EDF2_100%)]">
|
||||
<BookOutlined class="text-xs text-[#4facfe]" />
|
||||
</div>
|
||||
<span class="text-[#636E72]">
|
||||
@ -110,29 +103,30 @@
|
||||
|
||||
<div class="py-3 px-5 bg-[#FAFAFA] card-footer">
|
||||
<div class="h-1.5 bg-[#E0E0E0] rounded overflow-hidden">
|
||||
<div class="h-full bg-[linear-gradient(90deg,#4facfe_0%,#00f2fe_100%)] rounded transition-[width] duration-300" :style="{ width: getProgressWidth(cls.studentCount) }"></div>
|
||||
<div
|
||||
class="h-full bg-[linear-gradient(90deg,#4facfe_0%,#00f2fe_100%)] rounded transition-[width] duration-300"
|
||||
:style="{ width: getProgressWidth(cls.studentCount) }"></div>
|
||||
</div>
|
||||
<div class="text-[11px] text-[#B2BEC3] mt-1">班级活跃度</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-2 py-3 px-5 border-t border-[#F0F0F0] card-actions">
|
||||
<a-button type="link" size="small" @click="handleViewStudents(cls)" class="!py-1 !px-2 !h-auto inline-flex items-center gap-1">
|
||||
<a-button type="link" size="small" @click="handleViewStudents(cls)"
|
||||
class="!py-1 !px-2 !h-auto inline-flex items-center gap-1">
|
||||
<TeamOutlined />
|
||||
学生
|
||||
</a-button>
|
||||
<a-button type="link" size="small" @click="handleManageTeachers(cls)" class="!py-1 !px-2 !h-auto inline-flex items-center gap-1">
|
||||
<a-button type="link" size="small" @click="handleManageTeachers(cls)"
|
||||
class="!py-1 !px-2 !h-auto inline-flex items-center gap-1">
|
||||
<UsergroupAddOutlined />
|
||||
教师
|
||||
</a-button>
|
||||
<a-button type="link" size="small" @click="handleEdit(cls)" class="!py-1 !px-2 !h-auto inline-flex items-center gap-1">
|
||||
<a-button type="link" size="small" @click="handleEdit(cls)"
|
||||
class="!py-1 !px-2 !h-auto inline-flex items-center gap-1">
|
||||
<EditOutlined />
|
||||
编辑
|
||||
</a-button>
|
||||
<a-popconfirm
|
||||
title="确定要删除这个班级吗?"
|
||||
:disabled="cls.studentCount > 0"
|
||||
@confirm="handleDelete(cls.id)"
|
||||
>
|
||||
<a-popconfirm title="确定要删除这个班级吗?" :disabled="cls.studentCount > 0" @confirm="handleDelete(cls.id)">
|
||||
<a-tooltip v-if="cls.studentCount > 0" title="班级内有学生,无法删除">
|
||||
<a-button type="link" size="small" disabled class="!py-1 !px-2 !h-auto opacity-50">
|
||||
<DeleteOutlined />
|
||||
@ -149,12 +143,15 @@
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div class="flex flex-col items-center justify-center py-20 bg-white rounded-2xl empty-state" v-if="!loading && classes.length === 0">
|
||||
<div class="w-20 h-20 rounded-[20px] flex items-center justify-center mb-4 bg-[linear-gradient(135deg,#4facfe_0%,#00f2fe_100%)] empty-icon">
|
||||
<div class="flex flex-col items-center justify-center py-20 bg-white rounded-2xl empty-state"
|
||||
v-if="!loading && classes.length === 0">
|
||||
<div
|
||||
class="w-20 h-20 rounded-[20px] flex items-center justify-center mb-4 bg-[linear-gradient(135deg,#4facfe_0%,#00f2fe_100%)] empty-icon">
|
||||
<BankOutlined />
|
||||
</div>
|
||||
<p class="text-[#636E72] text-base mb-6">暂无班级数据</p>
|
||||
<a-button type="primary" class="!bg-[linear-gradient(135deg,#4facfe_0%,#00f2fe_100%)] !border-0" @click="showAddModal">
|
||||
<a-button type="primary" class="!bg-[linear-gradient(135deg,#4facfe_0%,#00f2fe_100%)] !border-0"
|
||||
@click="showAddModal">
|
||||
创建第一个班级
|
||||
</a-button>
|
||||
</div>
|
||||
@ -166,21 +163,9 @@
|
||||
</div>
|
||||
|
||||
<!-- 添加/编辑班级模态框 -->
|
||||
<a-modal
|
||||
v-model:open="modalVisible"
|
||||
:title="isEdit ? modalEditTitle : modalAddTitle"
|
||||
@ok="handleModalOk"
|
||||
@cancel="handleModalCancel"
|
||||
:confirm-loading="submitting"
|
||||
:width="480"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formState"
|
||||
:rules="rules"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
>
|
||||
<a-modal v-model:open="modalVisible" :title="isEdit ? modalEditTitle : modalAddTitle" @ok="handleModalOk"
|
||||
@cancel="handleModalCancel" :confirm-loading="submitting" :width="480">
|
||||
<a-form ref="formRef" :model="formState" :rules="rules" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
|
||||
<a-form-item label="班级名称" name="name">
|
||||
<a-input v-model:value="formState.name" placeholder="请输入班级名称">
|
||||
<template #prefix>
|
||||
@ -211,12 +196,7 @@
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="班主任" name="teacherId">
|
||||
<a-select
|
||||
v-model:value="formState.teacherId"
|
||||
placeholder="请选择班主任"
|
||||
:loading="teachersLoading"
|
||||
allow-clear
|
||||
>
|
||||
<a-select v-model:value="formState.teacherId" placeholder="请选择班主任" :loading="teachersLoading" allow-clear>
|
||||
<a-select-option v-for="teacher in teachers" :key="teacher.id" :value="teacher.id">
|
||||
{{ teacher.name }}
|
||||
</a-select-option>
|
||||
@ -226,15 +206,12 @@
|
||||
</a-modal>
|
||||
|
||||
<!-- 班级学生列表模态框 -->
|
||||
<a-modal
|
||||
v-model:open="studentsModalVisible"
|
||||
:title="studentsModalTitle"
|
||||
:footer="null"
|
||||
width="700px"
|
||||
>
|
||||
<a-modal v-model:open="studentsModalVisible" :title="studentsModalTitle" :footer="null" width="700px">
|
||||
<div class="p-0">
|
||||
<div class="flex items-center gap-4 py-5 px-5 rounded-2xl mb-5 bg-[linear-gradient(135deg,#4facfe_0%,#00f2fe_100%)] class-info-header">
|
||||
<div class="w-16 h-16 rounded-2xl flex items-center justify-center bg-[linear-gradient(135deg,rgba(255,255,255,0.3)_0%,rgba(255,255,255,0.1)_100%)]">
|
||||
<div
|
||||
class="flex items-center gap-4 py-5 px-5 rounded-2xl mb-5 bg-[linear-gradient(135deg,#4facfe_0%,#00f2fe_100%)] class-info-header">
|
||||
<div
|
||||
class="w-16 h-16 rounded-2xl flex items-center justify-center bg-[linear-gradient(135deg,rgba(255,255,255,0.3)_0%,rgba(255,255,255,0.1)_100%)]">
|
||||
<component :is="getGradeIcon(currentClass?.grade || '')" class="text-[32px] text-white" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
@ -250,15 +227,10 @@
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-3 students-list" v-if="!studentsLoading && classStudents.length > 0">
|
||||
<div
|
||||
v-for="student in classStudents"
|
||||
:key="student.id"
|
||||
class="flex items-center gap-3 p-3 bg-[#F8F9FA] rounded-xl student-item"
|
||||
>
|
||||
<div
|
||||
class="w-9 h-9 rounded-full flex items-center justify-center student-avatar"
|
||||
:class="student.gender === '男' ? 'bg-[linear-gradient(135deg,#4FC3F7_0%,#29B6F6_100%)]' : 'bg-[linear-gradient(135deg,#f093fb_0%,#f5576c_100%)]'"
|
||||
>
|
||||
<div v-for="student in classStudents" :key="student.id"
|
||||
class="flex items-center gap-3 p-3 bg-[#F8F9FA] rounded-xl student-item">
|
||||
<div class="w-9 h-9 rounded-full flex items-center justify-center student-avatar"
|
||||
:class="student.gender === '男' ? 'bg-[linear-gradient(135deg,#4FC3F7_0%,#29B6F6_100%)]' : 'bg-[linear-gradient(135deg,#f093fb_0%,#f5576c_100%)]'">
|
||||
<BoyOutlined v-if="student.gender === '男'" class="text-base text-white" />
|
||||
<GirlOutlined v-else class="text-base text-white" />
|
||||
</div>
|
||||
@ -272,8 +244,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center py-10 empty-students" v-if="!studentsLoading && classStudents.length === 0">
|
||||
<div class="w-16 h-16 rounded-2xl flex items-center justify-center mb-3 bg-[linear-gradient(135deg,#E8EDF2_0%,#D4DAE0_100%)]">
|
||||
<div class="flex flex-col items-center py-10 empty-students"
|
||||
v-if="!studentsLoading && classStudents.length === 0">
|
||||
<div
|
||||
class="w-16 h-16 rounded-2xl flex items-center justify-center mb-3 bg-[linear-gradient(135deg,#E8EDF2_0%,#D4DAE0_100%)]">
|
||||
<InboxOutlined class="text-[32px] text-[#B2BEC3]" />
|
||||
</div>
|
||||
<p class="text-[#636E72] mt-2">该班级暂无学生</p>
|
||||
@ -284,36 +258,24 @@
|
||||
<p class="text-[#636E72] mt-2">加载学生列表...</p>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center mt-5 pt-4 border-t border-[#F0F0F0] pagination-wrapper" v-if="studentsPagination.total > studentsPagination.pageSize">
|
||||
<a-pagination
|
||||
v-model:current="studentsPagination.current"
|
||||
v-model:pageSize="studentsPagination.pageSize"
|
||||
:total="studentsPagination.total"
|
||||
:show-total="(total: number) => `共 ${total} 条`"
|
||||
size="small"
|
||||
@change="handleStudentsPageChange"
|
||||
/>
|
||||
<div class="flex justify-center mt-5 pt-4 border-t border-[#F0F0F0] pagination-wrapper"
|
||||
v-if="studentsPagination.total > studentsPagination.pageSize">
|
||||
<a-pagination v-model:current="studentsPagination.current" v-model:pageSize="studentsPagination.pageSize"
|
||||
:total="studentsPagination.total" :show-total="(total: number) => `共 ${total} 条`" size="small"
|
||||
@change="handleStudentsPageChange" />
|
||||
</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
|
||||
<!-- 班级教师管理模态框 -->
|
||||
<a-modal
|
||||
v-model:open="teachersModalVisible"
|
||||
:title="`管理 ${currentClass?.name || ''} 教师团队`"
|
||||
:footer="null"
|
||||
width="600px"
|
||||
>
|
||||
<a-modal v-model:open="teachersModalVisible" :title="`管理 ${currentClass?.name || ''} 教师团队`" :footer="null"
|
||||
width="600px">
|
||||
<div class="p-0">
|
||||
<!-- 添加教师表单 -->
|
||||
<div class="p-4 bg-[#F8F9FA] rounded-xl mb-4 add-teacher-form">
|
||||
<div class="flex items-center gap-3 form-row">
|
||||
<a-select
|
||||
v-model:value="teacherFormState.teacherId"
|
||||
placeholder="选择教师"
|
||||
class="w-[150px]"
|
||||
:loading="teachersLoading"
|
||||
>
|
||||
<a-select v-model:value="teacherFormState.teacherId" placeholder="选择教师" class="w-[150px]"
|
||||
:loading="teachersLoading">
|
||||
<a-select-option v-for="teacher in teachers" :key="teacher.id" :value="teacher.id">
|
||||
{{ teacher.name }}
|
||||
</a-select-option>
|
||||
@ -332,23 +294,18 @@
|
||||
|
||||
<!-- 教师列表 -->
|
||||
<div class="flex flex-col gap-2 teachers-list" v-if="classTeachers.length > 0">
|
||||
<div v-for="teacher in classTeachers" :key="teacher.teacherId" class="flex justify-between items-center py-3 px-4 bg-[#F8F9FA] rounded-[10px] teacher-item">
|
||||
<div v-for="teacher in classTeachers" :key="teacher.teacherId"
|
||||
class="flex justify-between items-center py-3 px-4 bg-[#F8F9FA] rounded-[10px] teacher-item">
|
||||
<div class="flex items-center teacher-info">
|
||||
<span class="font-500 text-[#2D3436]">{{ teacher.teacherName }}</span>
|
||||
<a-select
|
||||
v-model:value="teacher.role"
|
||||
size="small"
|
||||
class="w-20 ml-2"
|
||||
@change="handleUpdateTeacherRole(teacher)"
|
||||
>
|
||||
<a-select v-model:value="teacher.role" size="small" class="w-20 ml-2"
|
||||
@change="handleUpdateTeacherRole(teacher)">
|
||||
<a-select-option value="MAIN">主班</a-select-option>
|
||||
<a-select-option value="ASSIST">配班</a-select-option>
|
||||
<a-select-option value="CARE">保育员</a-select-option>
|
||||
</a-select>
|
||||
<a-checkbox
|
||||
v-model:checked="teacher.isPrimary"
|
||||
@change="handleUpdateTeacherRole(teacher)"
|
||||
>班主任</a-checkbox>
|
||||
<a-checkbox v-model:checked="teacher.isPrimary"
|
||||
@change="handleUpdateTeacherRole(teacher)">班主任</a-checkbox>
|
||||
</div>
|
||||
<a-button type="link" danger size="small" @click="handleRemoveTeacher(teacher.teacherId)">
|
||||
移除
|
||||
@ -370,7 +327,6 @@ import {
|
||||
HomeOutlined,
|
||||
BankOutlined,
|
||||
PlusOutlined,
|
||||
SearchOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
TeamOutlined,
|
||||
@ -758,6 +714,7 @@ onMounted(() => {
|
||||
border-radius: 12px;
|
||||
border: 2px solid #F0F0F0;
|
||||
}
|
||||
|
||||
.search-box :deep(.ant-input-affix-wrapper:hover) {
|
||||
border-color: #4facfe;
|
||||
}
|
||||
@ -784,13 +741,16 @@ onMounted(() => {
|
||||
.class-grid {
|
||||
grid-template-columns: 1fr !important;
|
||||
}
|
||||
|
||||
.students-list {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.search-box-wrapper {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.search-box :deep(.ant-input-search) {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
</div>
|
||||
|
||||
<a-spin :spinning="loading">
|
||||
<div class="py-6 px-6 max-w-[1400px] mx-auto">
|
||||
<div class="px-4 py-4 md:px-6 md:py-6">
|
||||
<!-- 封面和基本信息 -->
|
||||
<div class="mb-6 text-center" v-if="course.coverImagePath">
|
||||
<img :src="getFileUrl(course.coverImagePath)" alt="课程封面"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="min-h-100vh p-0 bg-[linear-gradient(180deg,#FFF8F0_0%,#FFFFFF_100%)]">
|
||||
<div class="min-h-100vh bg-[linear-gradient(180deg,#FFF8F0_0%,#FFFFFF_100%)] px-4 py-4 md:px-6 md:py-6">
|
||||
<!-- 页面头部 -->
|
||||
<div class="rounded-[20px] py-6 px-8 mb-6 bg-[linear-gradient(135deg,#43e97b_0%,#38f9d7_100%)]">
|
||||
<div class="flex justify-between items-center max-md:flex-col max-md:items-start max-md:gap-4">
|
||||
@ -149,11 +149,14 @@
|
||||
</template>
|
||||
<div class="flex flex-col gap-5">
|
||||
<div class="auth-search">
|
||||
<a-input-search v-model:value="searchKeyword" placeholder="输入课程名称搜索..." @search="searchCourses" size="large">
|
||||
<template #prefix>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
</a-input-search>
|
||||
<a-input-search
|
||||
v-model:value="searchKeyword"
|
||||
placeholder="输入课程名称搜索..."
|
||||
@search="searchCourses"
|
||||
size="large"
|
||||
allow-clear
|
||||
:enter-button="false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-3 max-h-[400px] overflow-y-auto available-courses"
|
||||
@ -206,7 +209,6 @@
|
||||
import { ref, reactive, computed, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import {
|
||||
SearchOutlined,
|
||||
BookOutlined,
|
||||
ReadOutlined,
|
||||
StarFilled,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="min-h-100vh p-0 bg-[linear-gradient(180deg,#FFF8F0_0%,#FFFFFF_100%)]">
|
||||
<div class="min-h-100vh bg-[linear-gradient(180deg,#FFF8F0_0%,#FFFFFF_100%)] px-4 py-4 md:px-6 md:py-6">
|
||||
<!-- 页面头部 -->
|
||||
<div class="rounded-[20px] py-6 px-8 mb-6 bg-[linear-gradient(135deg,#11998e_0%,#38ef7d_100%)]">
|
||||
<div class="flex justify-between items-center gap-4 max-md:flex-col max-md:items-start">
|
||||
@ -55,6 +55,7 @@
|
||||
class="w-full md:w-[200px]"
|
||||
@search="handleFilter"
|
||||
allow-clear
|
||||
:enter-button="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="min-h-100vh p-0 bg-[linear-gradient(180deg,#FFF8F0_0%,#FFFFFF_100%)]">
|
||||
<div class="min-h-100vh bg-[linear-gradient(180deg,#FFF8F0_0%,#FFFFFF_100%)] px-4 py-4 md:px-6 md:py-6">
|
||||
<!-- 页面头部 -->
|
||||
<div class="rounded-[20px] py-6 px-8 mb-6 bg-[linear-gradient(135deg,#f093fb_0%,#f5576c_100%)]">
|
||||
<div class="flex justify-between items-center max-md:flex-col max-md:gap-4 max-md:items-start">
|
||||
@ -41,6 +41,7 @@
|
||||
class="w-[200px]"
|
||||
@search="handleFilter"
|
||||
allow-clear
|
||||
:enter-button="false"
|
||||
/>
|
||||
</div>
|
||||
<a-button type="primary" class="!bg-[linear-gradient(135deg,#f093fb_0%,#f5576c_100%)] hover:!bg-[linear-gradient(135deg,#e080e8_0%,#e04a5d_100%)] !border-0 rounded-xl h-10 px-6 font-600" @click="showAddModal">
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="min-h-100vh p-0 bg-[linear-gradient(180deg,#FFF8F0_0%,#FFFFFF_100%)]">
|
||||
<div class="min-h-100vh bg-[linear-gradient(180deg,#FFF8F0_0%,#FFFFFF_100%)] px-4 py-4 md:px-6 md:py-6">
|
||||
<!-- 页面头部 -->
|
||||
<div class="rounded-[20px] py-6 px-8 mb-6 bg-[linear-gradient(135deg,#FF8C42_0%,#FFB347_100%)]">
|
||||
<div class="flex justify-between items-center max-md:flex-col max-md:items-start max-md:gap-4">
|
||||
@ -30,11 +30,7 @@
|
||||
class="flex justify-between items-center mb-6 py-4 px-5 bg-white rounded-2xl shadow-[0_2px_8px_rgba(0,0,0,0.04)] max-md:flex-col max-md:gap-3">
|
||||
<div class="search-box">
|
||||
<a-input-search v-model:value="searchKeyword" placeholder="搜索家长姓名/手机号/账号" class="w-[280px]"
|
||||
@search="handleSearch" allow-clear>
|
||||
<template #prefix>
|
||||
<SearchOutlined class="text-[#B2BEC3]" />
|
||||
</template>
|
||||
</a-input-search>
|
||||
@search="handleSearch" allow-clear :enter-button="false" />
|
||||
</div>
|
||||
<a-button type="primary"
|
||||
class="!bg-[linear-gradient(135deg,#FF8C42_0%,#FFB347_100%)] hover:!bg-[linear-gradient(135deg,#FF7A2A_0%,#FFA030_100%)] !border-0 rounded-xl h-10 px-6 font-600"
|
||||
@ -79,7 +75,7 @@
|
||||
<TeamOutlined class="text-sm text-[#FF8C42]" />
|
||||
<span class="text-[#636E72]">
|
||||
<span v-if="parent.childrenCount > 0">关联 <strong class="text-[#FF8C42]">{{ parent.childrenCount
|
||||
}}</strong>
|
||||
}}</strong>
|
||||
个孩子</span>
|
||||
<span v-else class="text-[#B2BEC3] italic">未关联孩子</span>
|
||||
</span>
|
||||
@ -246,7 +242,7 @@
|
||||
|
||||
<div class="flex gap-3 mb-4 select-search-bar">
|
||||
<a-input-search v-model:value="studentSearchKeyword" placeholder="搜索学生姓名" class="w-[240px]"
|
||||
@search="handleStudentSearch" allow-clear />
|
||||
@search="handleStudentSearch" allow-clear :enter-button="false" />
|
||||
<a-select v-model:value="studentClassFilter" placeholder="按班级筛选" class="w-[160px]" allow-clear
|
||||
@change="handleStudentSearch">
|
||||
<a-select-option v-for="cls in classOptions" :key="cls.id" :value="cls.id">
|
||||
@ -316,7 +312,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue';
|
||||
import {
|
||||
SearchOutlined,
|
||||
IdcardOutlined,
|
||||
PlusOutlined,
|
||||
PhoneOutlined,
|
||||
@ -692,11 +687,6 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.search-box :deep(.ant-input-affix-wrapper) {
|
||||
border-radius: 12px;
|
||||
border: 2px solid #F0F0F0;
|
||||
}
|
||||
|
||||
.search-box :deep(.ant-input-affix-wrapper:hover) {
|
||||
border-color: #FF8C42;
|
||||
}
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
</div>
|
||||
|
||||
<a-spin :spinning="loading">
|
||||
<div class="p-6 max-w-[1200px] mx-auto">
|
||||
<div class="px-4 py-4 md:px-6 md:py-6">
|
||||
<!-- 基本信息 -->
|
||||
<div class="bg-white rounded-xl overflow-hidden shadow-[0_2px_8px_rgba(0,0,0,0.06)] mb-6">
|
||||
<div class="py-4 px-6 border-b border-[#f0f0f0] flex justify-between items-center">
|
||||
|
||||
@ -3,7 +3,8 @@
|
||||
<!-- 页面头部 -->
|
||||
<div class="mb-6 flex justify-between items-center gap-4 max-md:flex-col max-md:items-start">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-14 h-14 flex items-center justify-center bg-[linear-gradient(135deg,#667eea_0%,#764ba2_100%)] rounded-[14px] shadow-[0_4px_12px_rgba(102,126,234,0.3)]">
|
||||
<div
|
||||
class="w-14 h-14 flex items-center justify-center bg-[linear-gradient(135deg,#667eea_0%,#764ba2_100%)] rounded-[14px] shadow-[0_4px_12px_rgba(102,126,234,0.3)]">
|
||||
<AppstoreOutlined class="text-[28px] text-white" />
|
||||
</div>
|
||||
<div>
|
||||
@ -11,11 +12,7 @@
|
||||
<p class="text-[#666] text-sm mt-1 mb-0">管理本校教师创建的校本课程包</p>
|
||||
</div>
|
||||
</div>
|
||||
<a-button
|
||||
type="primary"
|
||||
class="w-full md:w-auto"
|
||||
@click="handleCreate"
|
||||
>
|
||||
<a-button type="primary" class="w-full md:w-auto" @click="handleCreate">
|
||||
<PlusOutlined /> 创建校本课程包
|
||||
</a-button>
|
||||
</div>
|
||||
@ -23,7 +20,8 @@
|
||||
<!-- 统计概览 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
||||
<div class="bg-white rounded-xl p-5 flex items-center gap-4 shadow-[0_2px_8px_rgba(0,0,0,0.06)]">
|
||||
<div class="w-12 h-12 rounded-xl flex items-center justify-center text-2xl bg-[linear-gradient(135deg,#667eea_0%,#764ba2_100%)] text-white">
|
||||
<div
|
||||
class="w-12 h-12 rounded-xl flex items-center justify-center text-2xl bg-[linear-gradient(135deg,#667eea_0%,#764ba2_100%)] text-white">
|
||||
<AppstoreOutlined />
|
||||
</div>
|
||||
<div>
|
||||
@ -32,7 +30,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl p-5 flex items-center gap-4 shadow-[0_2px_8px_rgba(0,0,0,0.06)]">
|
||||
<div class="w-12 h-12 rounded-xl flex items-center justify-center text-2xl bg-[linear-gradient(135deg,#43e97b_0%,#38f9d7_100%)] text-white">
|
||||
<div
|
||||
class="w-12 h-12 rounded-xl flex items-center justify-center text-2xl bg-[linear-gradient(135deg,#43e97b_0%,#38f9d7_100%)] text-white">
|
||||
<BarChartOutlined />
|
||||
</div>
|
||||
<div>
|
||||
@ -41,7 +40,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl p-5 flex items-center gap-4 shadow-[0_2px_8px_rgba(0,0,0,0.06)]">
|
||||
<div class="w-12 h-12 rounded-xl flex items-center justify-center text-2xl bg-[linear-gradient(135deg,#4facfe_0%,#00f2fe_100%)] text-white">
|
||||
<div
|
||||
class="w-12 h-12 rounded-xl flex items-center justify-center text-2xl bg-[linear-gradient(135deg,#4facfe_0%,#00f2fe_100%)] text-white">
|
||||
<CalendarOutlined />
|
||||
</div>
|
||||
<div>
|
||||
@ -57,25 +57,14 @@
|
||||
<span>校本课程包列表</span>
|
||||
</template>
|
||||
<template #extra>
|
||||
<div class="search-box">
|
||||
<a-input-search
|
||||
v-model:value="searchKeyword"
|
||||
placeholder="搜索课程包名称"
|
||||
class="w-[220px]"
|
||||
@search="handleSearch"
|
||||
allow-clear
|
||||
/>
|
||||
<div class="">
|
||||
<a-input-search v-model:value="searchKeyword" placeholder="搜索课程包名称" class="w-[220px]" @search="handleSearch"
|
||||
allow-clear :enter-button="false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="filteredData"
|
||||
:loading="loading"
|
||||
row-key="id"
|
||||
:pagination="{ pageSize: 10 }"
|
||||
:scroll="{ x: true }"
|
||||
>
|
||||
<a-table :columns="columns" :data-source="filteredData" :loading="loading" row-key="id"
|
||||
:pagination="{ pageSize: 10 }" :scroll="{ x: true }">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'name'">
|
||||
<div class="flex flex-col">
|
||||
@ -87,11 +76,8 @@
|
||||
</template>
|
||||
<template v-else-if="column.key === 'sourceCourse'">
|
||||
<div class="flex items-center gap-2">
|
||||
<img
|
||||
v-if="record.sourceCourse?.coverImagePath"
|
||||
:src="getFileUrl(record.sourceCourse.coverImagePath)"
|
||||
class="w-10 h-10 object-cover rounded"
|
||||
/>
|
||||
<img v-if="record.sourceCourse?.coverImagePath" :src="getFileUrl(record.sourceCourse.coverImagePath)"
|
||||
class="w-10 h-10 object-cover rounded" />
|
||||
<div v-else class="w-10 h-10 bg-[#f0f0f0] rounded flex items-center justify-center text-[#999]">
|
||||
<BookOutlined />
|
||||
</div>
|
||||
@ -149,13 +135,8 @@
|
||||
</a-card>
|
||||
|
||||
<!-- 预约弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="reserveModalVisible"
|
||||
title="预约校本课程包"
|
||||
width="500px"
|
||||
@ok="handleReserve"
|
||||
:confirmLoading="reserveLoading"
|
||||
>
|
||||
<a-modal v-model:open="reserveModalVisible" title="预约校本课程包" width="500px" @ok="handleReserve"
|
||||
:confirmLoading="reserveLoading">
|
||||
<div v-if="selectedCourse">
|
||||
<div class="bg-[#f9f9f9] rounded-lg py-3 px-4 flex gap-2">
|
||||
<span class="text-[#666]">课程包名称</span>
|
||||
@ -180,35 +161,21 @@
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="预约时间" required>
|
||||
<a-date-picker
|
||||
v-model:value="reserveForm.scheduledDate"
|
||||
show-time
|
||||
format="YYYY-MM-DD HH:mm"
|
||||
placeholder="选择预约时间"
|
||||
class="w-full"
|
||||
/>
|
||||
<a-date-picker v-model:value="reserveForm.scheduledDate" show-time format="YYYY-MM-DD HH:mm"
|
||||
placeholder="选择预约时间" class="w-full" />
|
||||
</a-form-item>
|
||||
<a-form-item label="备注">
|
||||
<a-textarea v-model:value="reserveForm.note" :rows="2" placeholder="备注信息(可选)" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<a-alert
|
||||
v-if="conflictInfo"
|
||||
:type="conflictInfo.hasConflict ? 'error' : 'success'"
|
||||
:message="conflictInfo.message"
|
||||
show-icon
|
||||
/>
|
||||
<a-alert v-if="conflictInfo" :type="conflictInfo.hasConflict ? 'error' : 'success'"
|
||||
:message="conflictInfo.message" show-icon />
|
||||
</div>
|
||||
</a-modal>
|
||||
|
||||
<!-- 排课弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="scheduleModalVisible"
|
||||
title="排课管理"
|
||||
width="800px"
|
||||
:footer="null"
|
||||
>
|
||||
<a-modal v-model:open="scheduleModalVisible" title="排课管理" width="800px" :footer="null">
|
||||
<div v-if="selectedCourse">
|
||||
<div class="flex justify-between items-center mb-4 py-3 px-4 bg-[#f9f9f9] rounded-lg">
|
||||
<span>课程包:{{ selectedCourse.name }}</span>
|
||||
@ -219,14 +186,8 @@
|
||||
|
||||
<a-tabs v-model:activeKey="scheduleTab">
|
||||
<a-tab-pane key="upcoming" tab="即将上课">
|
||||
<a-table
|
||||
:columns="reservationColumns"
|
||||
:data-source="upcomingReservations"
|
||||
:loading="reservationLoading"
|
||||
row-key="id"
|
||||
size="small"
|
||||
:pagination="false"
|
||||
>
|
||||
<a-table :columns="reservationColumns" :data-source="upcomingReservations" :loading="reservationLoading"
|
||||
row-key="id" size="small" :pagination="false">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">{{ getStatusText(record.status) }}</a-tag>
|
||||
@ -243,14 +204,8 @@
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="history" tab="历史记录">
|
||||
<a-table
|
||||
:columns="reservationColumns"
|
||||
:data-source="historyReservations"
|
||||
:loading="reservationLoading"
|
||||
row-key="id"
|
||||
size="small"
|
||||
:pagination="{ pageSize: 5 }"
|
||||
>
|
||||
<a-table :columns="reservationColumns" :data-source="historyReservations" :loading="reservationLoading"
|
||||
row-key="id" size="small" :pagination="{ pageSize: 5 }">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">{{ getStatusText(record.status) }}</a-tag>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="min-h-100vh p-0 bg-[linear-gradient(180deg,#FFF8F0_0%,#FFFFFF_100%)]">
|
||||
<div class="min-h-100vh bg-[linear-gradient(180deg,#FFF8F0_0%,#FFFFFF_100%)] px-4 py-4 md:px-6 md:py-6">
|
||||
<!-- 页面头部 -->
|
||||
<div class="rounded-[20px] py-6 px-8 mb-6 bg-[linear-gradient(135deg,#f093fb_0%,#f5576c_100%)]">
|
||||
<div class="flex justify-between items-center max-md:flex-col max-md:items-start max-md:gap-4">
|
||||
@ -19,48 +19,43 @@
|
||||
</div>
|
||||
<div class="text-center stat-item flex flex-col items-center gap-0.5">
|
||||
<span class="block text-[28px] font-700 text-[#4FC3F7]">{{ boysCount }}</span>
|
||||
<span class="text-xs text-white/80 flex items-center gap-1"><UserOutlined class="text-sm text-[#4FC3F7]" /> 男生</span>
|
||||
<span class="text-xs text-white/80 flex items-center gap-1">
|
||||
<UserOutlined class="text-sm text-[#4FC3F7]" /> 男生
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-center stat-item flex flex-col items-center gap-0.5">
|
||||
<span class="block text-[28px] font-700 text-[#FFD93D]">{{ girlsCount }}</span>
|
||||
<span class="text-xs text-white/80 flex items-center gap-1"><UserOutlined class="text-sm text-[#FFD93D]" /> 女生</span>
|
||||
<span class="text-xs text-white/80 flex items-center gap-1">
|
||||
<UserOutlined class="text-sm text-[#FFD93D]" /> 女生
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作栏 -->
|
||||
<div class="flex justify-between items-center mb-6 py-4 px-5 bg-white rounded-2xl shadow-[0_2px_8px_rgba(0,0,0,0.04)] max-md:flex-col max-md:gap-3">
|
||||
<div
|
||||
class="flex justify-between items-center mb-6 py-4 px-5 bg-white rounded-2xl shadow-[0_2px_8px_rgba(0,0,0,0.04)] max-md:flex-col max-md:gap-3">
|
||||
<div class="flex gap-3 max-md:w-full flex-wrap filters">
|
||||
<a-select
|
||||
v-model:value="selectedClassId"
|
||||
placeholder="选择班级"
|
||||
class="w-[150px]"
|
||||
@change="handleClassChange"
|
||||
allow-clear
|
||||
>
|
||||
<a-select v-model:value="selectedClassId" placeholder="选择班级" class="w-[150px]" @change="handleClassChange"
|
||||
allow-clear>
|
||||
<a-select-option v-for="cls in classes" :key="cls.id" :value="cls.id">
|
||||
{{ cls.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-input-search
|
||||
v-model:value="searchKeyword"
|
||||
placeholder="搜索学生姓名/家长"
|
||||
class="w-[220px] search-input-wrap"
|
||||
@search="handleSearch"
|
||||
allow-clear
|
||||
>
|
||||
<template #prefix>
|
||||
<SearchOutlined class="text-[#B2BEC3]" />
|
||||
</template>
|
||||
</a-input-search>
|
||||
<a-input-search v-model:value="searchKeyword" placeholder="搜索学生姓名/家长" class="w-[220px] search-input-wrap"
|
||||
@search="handleSearch" allow-clear :enter-button="false" />
|
||||
</div>
|
||||
<div class="flex gap-3 max-md:w-full flex-wrap actions">
|
||||
<a-button class="rounded-xl h-10 border-2 border-[#f093fb] text-[#f5576c] hover:bg-[#FFF0F5] hover:border-[#f5576c] hover:text-[#f5576c]" @click="showImportModal">
|
||||
<a-button
|
||||
class="rounded-xl h-10 border-2 border-[#f093fb] text-[#f5576c] hover:bg-[#FFF0F5] hover:border-[#f5576c] hover:text-[#f5576c]"
|
||||
@click="showImportModal">
|
||||
<DownloadOutlined class="mr-2" />
|
||||
批量导入
|
||||
</a-button>
|
||||
<a-button type="primary" class="!bg-[linear-gradient(135deg,#f093fb_0%,#f5576c_100%)] hover:!bg-[linear-gradient(135deg,#e080e8_0%,#e04a5d_100%)] !border-0 rounded-xl h-10 px-6 font-600" @click="showAddModal">
|
||||
<a-button type="primary"
|
||||
class="!bg-[linear-gradient(135deg,#f093fb_0%,#f5576c_100%)] hover:!bg-[linear-gradient(135deg,#e080e8_0%,#e04a5d_100%)] !border-0 rounded-xl h-10 px-6 font-600"
|
||||
@click="showAddModal">
|
||||
<PlusOutlined class="mr-2" />
|
||||
添加学生
|
||||
</a-button>
|
||||
@ -68,17 +63,14 @@
|
||||
</div>
|
||||
|
||||
<!-- 学生卡片网格 -->
|
||||
<div class="grid gap-5 mb-6 grid-cols-1 md:grid-cols-[repeat(auto-fill,minmax(280px,1fr))]" v-if="!loading && students.length > 0">
|
||||
<div
|
||||
v-for="student in students"
|
||||
:key="student.id"
|
||||
class="bg-white rounded-2xl overflow-hidden shadow-[0_4px_12px_rgba(0,0,0,0.05)] transition-all duration-300 border-2 border-transparent hover:translate-y-[-4px] hover:shadow-[0_8px_24px_rgba(0,0,0,0.1)] hover:border-[#f093fb]"
|
||||
>
|
||||
<div class="flex items-center gap-3 py-4 px-5 bg-[linear-gradient(135deg,#F8F9FA_0%,#FFFFFF_100%)] border-b border-[#F0F0F0]">
|
||||
<div
|
||||
class="w-12 h-12 rounded-full flex items-center justify-center text-2xl text-white"
|
||||
:class="normalizeGender(student.gender) === '男' ? 'bg-[linear-gradient(135deg,#4FC3F7_0%,#29B6F6_100%)]' : 'bg-[linear-gradient(135deg,#f093fb_0%,#f5576c_100%)]'"
|
||||
>
|
||||
<div class="grid gap-5 mb-6 grid-cols-1 md:grid-cols-[repeat(auto-fill,minmax(280px,1fr))]"
|
||||
v-if="!loading && students.length > 0">
|
||||
<div v-for="student in students" :key="student.id"
|
||||
class="bg-white rounded-2xl overflow-hidden shadow-[0_4px_12px_rgba(0,0,0,0.05)] transition-all duration-300 border-2 border-transparent hover:translate-y-[-4px] hover:shadow-[0_8px_24px_rgba(0,0,0,0.1)] hover:border-[#f093fb]">
|
||||
<div
|
||||
class="flex items-center gap-3 py-4 px-5 bg-[linear-gradient(135deg,#F8F9FA_0%,#FFFFFF_100%)] border-b border-[#F0F0F0]">
|
||||
<div class="w-12 h-12 rounded-full flex items-center justify-center text-2xl text-white"
|
||||
:class="normalizeGender(student.gender) === '男' ? 'bg-[linear-gradient(135deg,#4FC3F7_0%,#29B6F6_100%)]' : 'bg-[linear-gradient(135deg,#f093fb_0%,#f5576c_100%)]'">
|
||||
<UserOutlined />
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
@ -90,11 +82,10 @@
|
||||
<div class="py-4 px-5 card-body">
|
||||
<div class="flex items-center gap-2 mb-2 text-[13px]">
|
||||
<CalendarOutlined class="text-sm text-[#f5576c] w-5" />
|
||||
<span class="text-[#636E72] flex-1">{{ calculateAge(student.birthDate) || '--' }}{{ student.birthDate ? '岁' : '' }}</span>
|
||||
<span
|
||||
class="py-0.5 px-2 rounded-[10px] text-[11px]"
|
||||
:class="normalizeGender(student.gender) === '男' ? 'bg-[#E3F2FD] text-[#1976D2]' : 'bg-[#FCE4EC] text-[#E91E63]'"
|
||||
>
|
||||
<span class="text-[#636E72] flex-1">{{ calculateAge(student.birthDate) || '--' }}{{ student.birthDate ? '岁'
|
||||
: '' }}</span>
|
||||
<span class="py-0.5 px-2 rounded-[10px] text-[11px]"
|
||||
:class="normalizeGender(student.gender) === '男' ? 'bg-[#E3F2FD] text-[#1976D2]' : 'bg-[#FCE4EC] text-[#E91E63]'">
|
||||
{{ normalizeGender(student.gender) }}
|
||||
</span>
|
||||
</div>
|
||||
@ -108,21 +99,21 @@
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-[13px]">
|
||||
<BookOutlined class="text-sm text-[#f5576c] w-5" />
|
||||
<span class="text-[#636E72]">参与课程 <strong class="text-[#f5576c]">{{ student.lessonCount || 0 }}</strong> 次</span>
|
||||
<span class="text-[#636E72]">参与课程 <strong class="text-[#f5576c]">{{ student.lessonCount || 0 }}</strong>
|
||||
次</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-2 py-3 px-5 border-t border-[#F0F0F0] bg-[#FAFAFA] card-actions">
|
||||
<a-button type="link" size="small" @click="handleEdit(student)" class="!py-1 !px-2 !h-auto inline-flex items-center gap-1">
|
||||
<a-button type="link" size="small" @click="handleEdit(student)"
|
||||
class="!py-1 !px-2 !h-auto inline-flex items-center gap-1">
|
||||
<EditOutlined /> 编辑
|
||||
</a-button>
|
||||
<a-button type="link" size="small" @click="handleTransfer(student)" class="!py-1 !px-2 !h-auto inline-flex items-center gap-1">
|
||||
<a-button type="link" size="small" @click="handleTransfer(student)"
|
||||
class="!py-1 !px-2 !h-auto inline-flex items-center gap-1">
|
||||
<SwapOutlined /> 调班
|
||||
</a-button>
|
||||
<a-popconfirm
|
||||
title="确定要删除这位学生吗?"
|
||||
@confirm="handleDelete(student.id)"
|
||||
>
|
||||
<a-popconfirm title="确定要删除这位学生吗?" @confirm="handleDelete(student.id)">
|
||||
<a-button type="link" size="small" danger class="!py-1 !px-2 !h-auto">
|
||||
<DeleteOutlined /> 删除
|
||||
</a-button>
|
||||
@ -132,12 +123,15 @@
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div class="flex flex-col items-center justify-center py-20 bg-white rounded-2xl empty-state" v-if="!loading && students.length === 0">
|
||||
<div class="w-20 h-20 rounded-full flex items-center justify-center mb-4 text-[40px] text-[#f5576c] bg-[linear-gradient(135deg,#FFF0F5_0%,#FCE4EC_100%)]">
|
||||
<div class="flex flex-col items-center justify-center py-20 bg-white rounded-2xl empty-state"
|
||||
v-if="!loading && students.length === 0">
|
||||
<div
|
||||
class="w-20 h-20 rounded-full flex items-center justify-center mb-4 text-[40px] text-[#f5576c] bg-[linear-gradient(135deg,#FFF0F5_0%,#FCE4EC_100%)]">
|
||||
<InboxOutlined />
|
||||
</div>
|
||||
<p class="text-[#636E72] text-base mb-6">暂无学生数据</p>
|
||||
<a-button type="primary" class="!bg-[linear-gradient(135deg,#f093fb_0%,#f5576c_100%)] !border-0 rounded-xl" @click="showAddModal">
|
||||
<a-button type="primary" class="!bg-[linear-gradient(135deg,#f093fb_0%,#f5576c_100%)] !border-0 rounded-xl"
|
||||
@click="showAddModal">
|
||||
添加第一位学生
|
||||
</a-button>
|
||||
</div>
|
||||
@ -150,55 +144,41 @@
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="flex justify-center py-6 bg-white rounded-2xl pagination-wrapper" v-if="students.length > 0">
|
||||
<a-pagination
|
||||
v-model:current="pagination.current"
|
||||
v-model:pageSize="pagination.pageSize"
|
||||
:total="pagination.total"
|
||||
:show-size-changer="true"
|
||||
:show-total="(total: number) => `共 ${total} 条`"
|
||||
@change="handlePageChange"
|
||||
/>
|
||||
<a-pagination v-model:current="pagination.current" v-model:pageSize="pagination.pageSize"
|
||||
:total="pagination.total" :show-size-changer="true" :show-total="(total: number) => `共 ${total} 条`"
|
||||
@change="handlePageChange" />
|
||||
</div>
|
||||
|
||||
<!-- 添加/编辑学生模态框 -->
|
||||
<a-modal
|
||||
v-model:open="modalVisible"
|
||||
@ok="handleModalOk"
|
||||
@cancel="handleModalCancel"
|
||||
:confirm-loading="submitting"
|
||||
:width="520"
|
||||
>
|
||||
<a-modal v-model:open="modalVisible" @ok="handleModalOk" @cancel="handleModalCancel" :confirm-loading="submitting"
|
||||
:width="520">
|
||||
<template #title>
|
||||
<span class="flex items-center gap-2">
|
||||
<component :is="isEdit ? EditOutlined : PlusOutlined" class="text-[#f5576c]" />
|
||||
{{ isEdit ? '编辑学生' : '添加学生' }}
|
||||
</span>
|
||||
</template>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formState"
|
||||
:rules="rules"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
>
|
||||
<a-form ref="formRef" :model="formState" :rules="rules" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
|
||||
<a-form-item label="姓名" name="name">
|
||||
<a-input v-model:value="formState.name" placeholder="请输入学生姓名">
|
||||
<template #prefix><UserOutlined class="text-[#B2BEC3]" /></template>
|
||||
<template #prefix>
|
||||
<UserOutlined class="text-[#B2BEC3]" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="性别" name="gender">
|
||||
<a-radio-group v-model:value="formState.gender">
|
||||
<a-radio value="男"><UserOutlined class="mr-1 text-[#4FC3F7]" /> 男孩</a-radio>
|
||||
<a-radio value="女"><UserOutlined class="mr-1 text-[#f5576c]" /> 女孩</a-radio>
|
||||
<a-radio value="男">
|
||||
<UserOutlined class="mr-1 text-[#4FC3F7]" /> 男孩
|
||||
</a-radio>
|
||||
<a-radio value="女">
|
||||
<UserOutlined class="mr-1 text-[#f5576c]" /> 女孩
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="出生日期" name="birthDate">
|
||||
<a-date-picker
|
||||
v-model:value="formState.birthDate"
|
||||
class="w-full"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="选择出生日期"
|
||||
/>
|
||||
<a-date-picker v-model:value="formState.birthDate" class="w-full" value-format="YYYY-MM-DD"
|
||||
placeholder="选择出生日期" />
|
||||
</a-form-item>
|
||||
<a-form-item label="所在班级" name="classId">
|
||||
<a-select v-model:value="formState.classId" placeholder="请选择班级" :loading="classesLoading">
|
||||
@ -209,26 +189,24 @@
|
||||
</a-form-item>
|
||||
<a-form-item label="家长姓名" name="parentName">
|
||||
<a-input v-model:value="formState.parentName" placeholder="请输入家长姓名">
|
||||
<template #prefix><TeamOutlined class="text-[#B2BEC3]" /></template>
|
||||
<template #prefix>
|
||||
<TeamOutlined class="text-[#B2BEC3]" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="家长电话" name="parentPhone">
|
||||
<a-input v-model:value="formState.parentPhone" placeholder="请输入家长电话">
|
||||
<template #prefix><PhoneOutlined class="text-[#B2BEC3]" /></template>
|
||||
<template #prefix>
|
||||
<PhoneOutlined class="text-[#B2BEC3]" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
<!-- 学生调班模态框 -->
|
||||
<a-modal
|
||||
v-model:open="transferModalVisible"
|
||||
title="学生调班"
|
||||
:confirm-loading="transferSubmitting"
|
||||
@ok="handleTransferSubmit"
|
||||
@cancel="transferModalVisible = false"
|
||||
width="480px"
|
||||
>
|
||||
<a-modal v-model:open="transferModalVisible" title="学生调班" :confirm-loading="transferSubmitting"
|
||||
@ok="handleTransferSubmit" @cancel="transferModalVisible = false" width="480px">
|
||||
<div class="py-2 transfer-modal-content">
|
||||
<div class="py-3 px-4 bg-[#F8F9FA] rounded-lg mb-4 current-info">
|
||||
<span>当前学生:</span>
|
||||
@ -237,22 +215,14 @@
|
||||
</div>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="目标班级" required>
|
||||
<a-select
|
||||
v-model:value="transferTargetClassId"
|
||||
placeholder="请选择目标班级"
|
||||
:loading="classesLoading"
|
||||
>
|
||||
<a-select v-model:value="transferTargetClassId" placeholder="请选择目标班级" :loading="classesLoading">
|
||||
<a-select-option v-for="cls in classes" :key="cls.id" :value="cls.id">
|
||||
{{ cls.name }} ({{ cls.grade }})
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="调班原因">
|
||||
<a-textarea
|
||||
v-model:value="transferReason"
|
||||
placeholder="请输入调班原因(选填)"
|
||||
:rows="3"
|
||||
/>
|
||||
<a-textarea v-model:value="transferReason" placeholder="请输入调班原因(选填)" :rows="3" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
@ -261,7 +231,8 @@
|
||||
<HistoryOutlined /> 调班历史
|
||||
</div>
|
||||
<div class="max-h-[150px] overflow-y-auto history-list">
|
||||
<div v-for="h in transferHistory" :key="h.id" class="flex items-center gap-2 py-2 px-3 bg-[#F8F9FA] rounded-md mb-2 text-[13px] history-item">
|
||||
<div v-for="h in transferHistory" :key="h.id"
|
||||
class="flex items-center gap-2 py-2 px-3 bg-[#F8F9FA] rounded-md mb-2 text-[13px] history-item">
|
||||
<span class="text-[#888]">{{ h.fromClass?.name || '无' }}</span>
|
||||
<span class="text-[#B2BEC3]">→</span>
|
||||
<span class="text-[#2D3436] font-500">{{ h.toClass.name }}</span>
|
||||
@ -273,11 +244,7 @@
|
||||
</a-modal>
|
||||
|
||||
<!-- 批量导入模态框 -->
|
||||
<a-modal
|
||||
v-model:open="importModalVisible"
|
||||
:footer="null"
|
||||
width="560px"
|
||||
>
|
||||
<a-modal v-model:open="importModalVisible" :footer="null" width="560px">
|
||||
<template #title>
|
||||
<span class="flex items-center gap-2">
|
||||
<DownloadOutlined class="text-[#f5576c]" />
|
||||
@ -295,17 +262,15 @@
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<a-button class="rounded-xl border-2 border-dashed border-[#f093fb] text-[#f5576c] hover:border-[#f5576c] hover:text-[#f5576c] hover:bg-[#FFF0F5]" @click="downloadTemplate">
|
||||
<a-button
|
||||
class="rounded-xl border-2 border-dashed border-[#f093fb] text-[#f5576c] hover:border-[#f5576c] hover:text-[#f5576c] hover:bg-[#FFF0F5]"
|
||||
@click="downloadTemplate">
|
||||
<DownloadOutlined class="mr-2" />
|
||||
下载导入模板
|
||||
</a-button>
|
||||
|
||||
<a-upload-dragger
|
||||
:before-upload="beforeUpload"
|
||||
:show-upload-list="false"
|
||||
accept=".xlsx,.xls,.csv"
|
||||
class="upload-area rounded-xl"
|
||||
>
|
||||
<a-upload-dragger :before-upload="beforeUpload" :show-upload-list="false" accept=".xlsx,.xls,.csv"
|
||||
class="upload-area rounded-xl">
|
||||
<div class="py-6 text-center upload-content">
|
||||
<FileAddOutlined class="block text-[48px] text-[#f5576c] mb-3" />
|
||||
<p class="text-sm text-[#636E72] mb-1">点击或拖拽文件到此区域上传</p>
|
||||
@ -321,26 +286,15 @@
|
||||
|
||||
<div v-if="importFile" class="mt-2 default-class-select">
|
||||
<label class="block mb-2 text-[13px] text-[#636E72]">默认班级(用于未指定班级的学生)</label>
|
||||
<a-select
|
||||
v-model:value="importDefaultClassId"
|
||||
placeholder="选择默认班级"
|
||||
class="w-full"
|
||||
allow-clear
|
||||
>
|
||||
<a-select v-model:value="importDefaultClassId" placeholder="选择默认班级" class="w-full" allow-clear>
|
||||
<a-select-option v-for="cls in classes" :key="cls.id" :value="cls.id">
|
||||
{{ cls.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
|
||||
<a-button
|
||||
type="primary"
|
||||
:loading="importing"
|
||||
:disabled="!importFile"
|
||||
@click="handleImport"
|
||||
block
|
||||
class="h-11 rounded-xl text-[15px] font-600 !bg-[linear-gradient(135deg,#f093fb_0%,#f5576c_100%)] hover:!bg-[linear-gradient(135deg,#e080e8_0%,#e04a5d_100%)] !border-0"
|
||||
>
|
||||
<a-button type="primary" :loading="importing" :disabled="!importFile" @click="handleImport" block
|
||||
class="h-11 rounded-xl text-[15px] font-600 !bg-[linear-gradient(135deg,#f093fb_0%,#f5576c_100%)] hover:!bg-[linear-gradient(135deg,#e080e8_0%,#e04a5d_100%)] !border-0">
|
||||
<template v-if="!importing">
|
||||
<RocketOutlined class="mr-2" />
|
||||
开始导入
|
||||
@ -348,16 +302,21 @@
|
||||
<span v-else>导入中...</span>
|
||||
</a-button>
|
||||
|
||||
<div v-if="importResult" class="p-4 rounded-xl import-result" :class="importResult.failed === 0 ? 'bg-[#E8F5E9]' : 'bg-[#FFF8E1]'">
|
||||
<div v-if="importResult" class="p-4 rounded-xl import-result"
|
||||
:class="importResult.failed === 0 ? 'bg-[#E8F5E9]' : 'bg-[#FFF8E1]'">
|
||||
<div class="flex items-center gap-2 font-600 mb-3 result-header">
|
||||
<component :is="importResult.failed === 0 ? CheckCircleOutlined : WarningOutlined" class="text-xl" :class="importResult.failed === 0 ? 'text-[#43A047]' : 'text-[#FF8C42]'" />
|
||||
<component :is="importResult.failed === 0 ? CheckCircleOutlined : WarningOutlined" class="text-xl"
|
||||
:class="importResult.failed === 0 ? 'text-[#43A047]' : 'text-[#FF8C42]'" />
|
||||
<span>导入完成</span>
|
||||
</div>
|
||||
<div class="flex gap-4 mb-3 result-stats">
|
||||
<span class="text-[13px]" :class="importResult.failed === 0 ? 'text-[#43A047]' : ''">成功 {{ importResult.success }} 条</span>
|
||||
<span class="text-[13px]" :class="importResult.failed === 0 ? 'text-[#43A047]' : ''">成功 {{
|
||||
importResult.success
|
||||
}} 条</span>
|
||||
<span class="text-[13px] text-[#E53935]">失败 {{ importResult.failed }} 条</span>
|
||||
</div>
|
||||
<div v-if="importResult.errors.length > 0" class="max-h-[150px] overflow-y-auto py-3 px-3 bg-white rounded-lg text-xs text-[#636E72] result-errors">
|
||||
<div v-if="importResult.errors.length > 0"
|
||||
class="max-h-[150px] overflow-y-auto py-3 px-3 bg-white rounded-lg text-xs text-[#636E72] result-errors">
|
||||
<p v-for="(error, index) in importResult.errors" :key="index" class="my-1">
|
||||
第 {{ error.row }} 行:{{ error.message }}
|
||||
</p>
|
||||
@ -371,7 +330,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue';
|
||||
import {
|
||||
SearchOutlined,
|
||||
TeamOutlined,
|
||||
UserOutlined,
|
||||
EditOutlined,
|
||||
@ -792,11 +750,11 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.filters :deep(.ant-select-selector),
|
||||
.filters :deep(.ant-input-affix-wrapper) {
|
||||
.filters :deep(.ant-select-selector) {
|
||||
border-radius: 12px;
|
||||
border: 2px solid #F0F0F0;
|
||||
}
|
||||
|
||||
.filters :deep(.ant-select-selector:hover),
|
||||
.filters :deep(.ant-input-affix-wrapper:hover) {
|
||||
border-color: #f093fb;
|
||||
@ -812,8 +770,8 @@ onMounted(() => {
|
||||
border: 2px dashed #E0E0E0;
|
||||
background: #FAFAFA;
|
||||
}
|
||||
|
||||
.upload-area :deep(.ant-upload-drag:hover) {
|
||||
border-color: #f093fb;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@ -62,6 +62,7 @@
|
||||
class="w-full md:w-[200px]"
|
||||
@search="loadTasks"
|
||||
allow-clear
|
||||
:enter-button="false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@ -32,6 +32,8 @@
|
||||
placeholder="搜索模板名称"
|
||||
class="w-[250px]"
|
||||
@search="loadTemplates"
|
||||
allow-clear
|
||||
:enter-button="false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="min-h-100vh p-0 bg-[linear-gradient(180deg,#FFF8F0_0%,#FFFFFF_100%)]">
|
||||
<div class="min-h-100vh bg-[linear-gradient(180deg,#FFF8F0_0%,#FFFFFF_100%)] px-4 py-4 md:px-6 md:py-6">
|
||||
<!-- 页面头部 -->
|
||||
<div class="rounded-[20px] py-6 px-8 mb-6 bg-[linear-gradient(135deg,#667eea_0%,#764ba2_100%)]">
|
||||
<div class="flex justify-between items-center max-md:flex-col max-md:items-start max-md:gap-4">
|
||||
@ -26,46 +26,38 @@
|
||||
</div>
|
||||
|
||||
<!-- 操作栏 -->
|
||||
<div class="flex justify-between items-center mb-6 py-4 px-5 bg-white rounded-2xl shadow-[0_2px_8px_rgba(0,0,0,0.04)] max-md:flex-col max-md:gap-3">
|
||||
<div class="search-box">
|
||||
<a-input-search
|
||||
v-model:value="searchKeyword"
|
||||
placeholder="搜索教师姓名/手机号/账号"
|
||||
class="w-[280px]"
|
||||
@search="handleSearch"
|
||||
allow-clear
|
||||
>
|
||||
<template #prefix>
|
||||
<SearchOutlined class="text-[#B2BEC3]" />
|
||||
</template>
|
||||
</a-input-search>
|
||||
<div
|
||||
class="flex justify-between items-center mb-6 py-4 px-5 bg-white rounded-2xl shadow-[0_2px_8px_rgba(0,0,0,0.04)] max-md:flex-col max-md:gap-3">
|
||||
<div class="">
|
||||
<a-input-search v-model:value="searchKeyword" placeholder="搜索教师姓名/手机号/账号" class="w-[280px]"
|
||||
@search="handleSearch" allow-clear :enter-button="false" />
|
||||
</div>
|
||||
<a-button type="primary" class="!bg-[linear-gradient(135deg,#FF8C42_0%,#FFB347_100%)] hover:!bg-[linear-gradient(135deg,#FF7A2A_0%,#FFA030_100%)] !border-0 rounded-xl h-10 px-6 font-600" @click="showAddModal">
|
||||
<a-button type="primary"
|
||||
class="!bg-[linear-gradient(135deg,#FF8C42_0%,#FFB347_100%)] hover:!bg-[linear-gradient(135deg,#FF7A2A_0%,#FFA030_100%)] !border-0 rounded-xl h-10 px-6 font-600"
|
||||
@click="showAddModal">
|
||||
<PlusOutlined class="mr-2 text-sm" />
|
||||
添加教师
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<!-- 教师卡片列表 -->
|
||||
<div class="grid gap-5 mb-6 grid-cols-1 md:grid-cols-[repeat(auto-fill,minmax(320px,1fr))]" v-if="!loading && teachers.length > 0">
|
||||
<div
|
||||
v-for="teacher in teachers"
|
||||
:key="teacher.id"
|
||||
<div class="grid gap-5 mb-6 grid-cols-1 md:grid-cols-[repeat(auto-fill,minmax(320px,1fr))]"
|
||||
v-if="!loading && teachers.length > 0">
|
||||
<div v-for="teacher in teachers" :key="teacher.id"
|
||||
class="bg-white rounded-2xl overflow-hidden shadow-[0_4px_12px_rgba(0,0,0,0.05)] transition-all duration-300 border-2 border-transparent hover:translate-y-[-4px] hover:shadow-[0_8px_24px_rgba(0,0,0,0.1)] hover:border-[#FF8C42]"
|
||||
:class="teacher.status !== 'ACTIVE' ? 'opacity-70' : ''"
|
||||
>
|
||||
<div class="flex items-center gap-3 py-4 px-5 bg-[linear-gradient(135deg,#F8F9FA_0%,#FFFFFF_100%)] border-b border-[#F0F0F0]">
|
||||
<div class="w-12 h-12 rounded-xl flex items-center justify-center bg-[linear-gradient(135deg,#667eea_0%,#764ba2_100%)]">
|
||||
:class="teacher.status !== 'ACTIVE' ? 'opacity-70' : ''">
|
||||
<div
|
||||
class="flex items-center gap-3 py-4 px-5 bg-[linear-gradient(135deg,#F8F9FA_0%,#FFFFFF_100%)] border-b border-[#F0F0F0]">
|
||||
<div
|
||||
class="w-12 h-12 rounded-xl flex items-center justify-center bg-[linear-gradient(135deg,#667eea_0%,#764ba2_100%)]">
|
||||
<SolutionOutlined class="text-2xl text-white" />
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-base font-600 text-[#2D3436]">{{ teacher.name }}</div>
|
||||
<div class="text-xs text-[#636E72] mt-0.5">@{{ teacher.loginAccount }}</div>
|
||||
</div>
|
||||
<span
|
||||
class="py-1 px-3 rounded-[20px] text-xs font-500"
|
||||
:class="teacher.status === 'ACTIVE' ? 'bg-[#E8F5E9] text-[#43A047]' : 'bg-[#FFEBEE] text-[#E53935]'"
|
||||
>
|
||||
<span class="py-1 px-3 rounded-[20px] text-xs font-500"
|
||||
:class="teacher.status === 'ACTIVE' ? 'bg-[#E8F5E9] text-[#43A047]' : 'bg-[#FFEBEE] text-[#E53935]'">
|
||||
{{ teacher.status === 'ACTIVE' ? '在职' : '离职' }}
|
||||
</span>
|
||||
</div>
|
||||
@ -82,30 +74,32 @@
|
||||
<div class="flex items-center gap-2 mb-2 text-[13px]">
|
||||
<BankOutlined class="text-sm text-[#667eea]" />
|
||||
<span class="text-[#636E72] classes-tag">
|
||||
<span v-if="teacher.classNames && (Array.isArray(teacher.classNames) ? teacher.classNames.length > 0 : teacher.classNames)">
|
||||
<span
|
||||
v-if="teacher.classNames && (Array.isArray(teacher.classNames) ? teacher.classNames.length > 0 : teacher.classNames)">
|
||||
{{ Array.isArray(teacher.classNames) ? teacher.classNames.slice(0, 2).join('、') : teacher.classNames }}
|
||||
<span v-if="Array.isArray(teacher.classNames) && teacher.classNames.length > 2">等{{ teacher.classNames.length }}个班级</span>
|
||||
<span v-if="Array.isArray(teacher.classNames) && teacher.classNames.length > 2">等{{
|
||||
teacher.classNames.length }}个班级</span>
|
||||
</span>
|
||||
<span v-else class="text-[#B2BEC3] italic">未分配班级</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-[13px]">
|
||||
<BookOutlined class="text-sm text-[#667eea]" />
|
||||
<span class="text-[#636E72]">授课 <strong class="text-[#FF8C42]">{{ teacher.lessonCount || 0 }}</strong> 次</span>
|
||||
<span class="text-[#636E72]">授课 <strong class="text-[#FF8C42]">{{ teacher.lessonCount || 0 }}</strong>
|
||||
次</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-2 py-3 px-5 border-t border-[#F0F0F0] bg-[#FAFAFA] card-actions">
|
||||
<a-button type="link" size="small" @click="handleEdit(teacher)" class="!py-1 !px-2 !h-auto inline-flex items-center gap-1">
|
||||
<a-button type="link" size="small" @click="handleEdit(teacher)"
|
||||
class="!py-1 !px-2 !h-auto inline-flex items-center gap-1">
|
||||
<EditOutlined /> 编辑
|
||||
</a-button>
|
||||
<a-button type="link" size="small" @click="handleResetPassword(teacher)" class="!py-1 !px-2 !h-auto inline-flex items-center gap-1">
|
||||
<a-button type="link" size="small" @click="handleResetPassword(teacher)"
|
||||
class="!py-1 !px-2 !h-auto inline-flex items-center gap-1">
|
||||
<KeyOutlined /> 重置密码
|
||||
</a-button>
|
||||
<a-popconfirm
|
||||
title="确定要删除这位教师吗?"
|
||||
@confirm="handleDelete(teacher.id)"
|
||||
>
|
||||
<a-popconfirm title="确定要删除这位教师吗?" @confirm="handleDelete(teacher.id)">
|
||||
<a-button type="link" size="small" danger class="!py-1 !px-2 !h-auto">
|
||||
<DeleteOutlined /> 删除
|
||||
</a-button>
|
||||
@ -115,10 +109,12 @@
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div class="flex flex-col items-center justify-center py-20 bg-white rounded-2xl empty-state" v-if="!loading && teachers.length === 0">
|
||||
<div class="flex flex-col items-center justify-center py-20 bg-white rounded-2xl empty-state"
|
||||
v-if="!loading && teachers.length === 0">
|
||||
<InboxOutlined class="text-[64px] text-[#B2BEC3] mb-4" />
|
||||
<p class="text-[#636E72] text-base mb-6">暂无教师数据</p>
|
||||
<a-button type="primary" class="!bg-[linear-gradient(135deg,#FF8C42_0%,#FFB347_100%)] !border-0 rounded-xl" @click="showAddModal">
|
||||
<a-button type="primary" class="!bg-[linear-gradient(135deg,#FF8C42_0%,#FFB347_100%)] !border-0 rounded-xl"
|
||||
@click="showAddModal">
|
||||
添加第一位教师
|
||||
</a-button>
|
||||
</div>
|
||||
@ -131,25 +127,14 @@
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="flex justify-center py-6 bg-white rounded-2xl pagination-wrapper" v-if="teachers.length > 0">
|
||||
<a-pagination
|
||||
v-model:current="pagination.current"
|
||||
v-model:pageSize="pagination.pageSize"
|
||||
:total="pagination.total"
|
||||
:show-size-changer="true"
|
||||
:show-total="(total: number) => `共 ${total} 条`"
|
||||
@change="handlePageChange"
|
||||
/>
|
||||
<a-pagination v-model:current="pagination.current" v-model:pageSize="pagination.pageSize"
|
||||
:total="pagination.total" :show-size-changer="true" :show-total="(total: number) => `共 ${total} 条`"
|
||||
@change="handlePageChange" />
|
||||
</div>
|
||||
|
||||
<!-- 添加/编辑教师模态框 -->
|
||||
<a-modal
|
||||
v-model:open="modalVisible"
|
||||
:title="isEdit ? '编辑教师' : '添加教师'"
|
||||
@ok="handleModalOk"
|
||||
@cancel="handleModalCancel"
|
||||
:confirm-loading="submitting"
|
||||
:width="520"
|
||||
>
|
||||
<a-modal v-model:open="modalVisible" :title="isEdit ? '编辑教师' : '添加教师'" @ok="handleModalOk"
|
||||
@cancel="handleModalCancel" :confirm-loading="submitting" :width="520">
|
||||
<template #title>
|
||||
<span class="flex items-center gap-2 modal-title">
|
||||
<EditOutlined v-if="isEdit" class="text-[#667eea]" />
|
||||
@ -157,49 +142,44 @@
|
||||
{{ isEdit ? '编辑教师' : '添加教师' }}
|
||||
</span>
|
||||
</template>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formState"
|
||||
:rules="rules"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
>
|
||||
<a-form ref="formRef" :model="formState" :rules="rules" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
|
||||
<a-form-item label="姓名" name="name">
|
||||
<a-input v-model:value="formState.name" placeholder="请输入教师姓名">
|
||||
<template #prefix><UserOutlined class="text-[#B2BEC3]" /></template>
|
||||
<template #prefix>
|
||||
<UserOutlined class="text-[#B2BEC3]" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="手机号" name="phone">
|
||||
<a-input v-model:value="formState.phone" placeholder="请输入手机号">
|
||||
<template #prefix><PhoneOutlined class="text-[#B2BEC3]" /></template>
|
||||
<template #prefix>
|
||||
<PhoneOutlined class="text-[#B2BEC3]" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="邮箱" name="email">
|
||||
<a-input v-model:value="formState.email" placeholder="请输入邮箱(可选)">
|
||||
<template #prefix><MailOutlined class="text-[#B2BEC3]" /></template>
|
||||
<template #prefix>
|
||||
<MailOutlined class="text-[#B2BEC3]" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="登录账号" name="loginAccount">
|
||||
<a-input
|
||||
v-model:value="formState.loginAccount"
|
||||
placeholder="请输入登录账号"
|
||||
:disabled="isEdit"
|
||||
>
|
||||
<template #prefix><KeyOutlined class="text-[#B2BEC3]" /></template>
|
||||
<a-input v-model:value="formState.loginAccount" placeholder="请输入登录账号" :disabled="isEdit">
|
||||
<template #prefix>
|
||||
<KeyOutlined class="text-[#B2BEC3]" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="!isEdit" label="密码" name="password">
|
||||
<a-input-password v-model:value="formState.password" placeholder="请输入密码(默认123456)">
|
||||
<template #prefix><LockOutlined class="text-[#B2BEC3]" /></template>
|
||||
<template #prefix>
|
||||
<LockOutlined class="text-[#B2BEC3]" />
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
<a-form-item label="负责班级" name="classIds">
|
||||
<a-select
|
||||
v-model:value="formState.classIds"
|
||||
mode="multiple"
|
||||
placeholder="请选择负责的班级"
|
||||
:loading="classesLoading"
|
||||
>
|
||||
<a-select v-model:value="formState.classIds" mode="multiple" placeholder="请选择负责的班级" :loading="classesLoading">
|
||||
<a-select-option v-for="cls in classes" :key="cls.id" :value="cls.id">
|
||||
{{ cls.name }}
|
||||
</a-select-option>
|
||||
@ -209,12 +189,7 @@
|
||||
</a-modal>
|
||||
|
||||
<!-- 重置密码确认模态框 -->
|
||||
<a-modal
|
||||
v-model:open="resetPasswordVisible"
|
||||
@ok="confirmResetPassword"
|
||||
:confirm-loading="resetting"
|
||||
:width="400"
|
||||
>
|
||||
<a-modal v-model:open="resetPasswordVisible" @ok="confirmResetPassword" :confirm-loading="resetting" :width="400">
|
||||
<template #title>
|
||||
<span class="flex items-center gap-2 modal-title">
|
||||
<KeyOutlined class="text-[#667eea]" />
|
||||
@ -228,7 +203,8 @@
|
||||
</div>
|
||||
<div v-if="newPassword" class="new-password-box">
|
||||
<p class="mb-2 text-[#636E72]">新密码:</p>
|
||||
<div class="p-4 rounded-xl font-bold text-2xl text-white bg-[linear-gradient(135deg,#FF8C42_0%,#FFB347_100%)] password-display">
|
||||
<div
|
||||
class="p-4 rounded-xl font-bold text-2xl text-white bg-[linear-gradient(135deg,#FF8C42_0%,#FFB347_100%)] password-display">
|
||||
<a-typography-text copyable class="!text-white">{{ newPassword }}</a-typography-text>
|
||||
</div>
|
||||
</div>
|
||||
@ -240,7 +216,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue';
|
||||
import {
|
||||
SearchOutlined,
|
||||
SolutionOutlined,
|
||||
PlusOutlined,
|
||||
PhoneOutlined,
|
||||
@ -472,6 +447,7 @@ onMounted(() => {
|
||||
border-radius: 12px;
|
||||
border: 2px solid #F0F0F0;
|
||||
}
|
||||
|
||||
.search-box :deep(.ant-input-affix-wrapper:hover) {
|
||||
border-color: #FF8C42;
|
||||
}
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import UnoCSS from 'unocss/vite';
|
||||
import { resolve } from 'path';
|
||||
import AutoImport from 'unplugin-auto-import/vite';
|
||||
import Components from 'unplugin-vue-components/vite';
|
||||
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers';
|
||||
import viteCompression from 'vite-plugin-compression';
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import UnoCSS from "unocss/vite";
|
||||
import { resolve } from "path";
|
||||
import AutoImport from "unplugin-auto-import/vite";
|
||||
import Components from "unplugin-vue-components/vite";
|
||||
import { AntDesignVueResolver } from "unplugin-vue-components/resolvers";
|
||||
import viteCompression from "vite-plugin-compression";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
@ -13,14 +13,14 @@ export default defineConfig({
|
||||
UnoCSS(),
|
||||
AutoImport({
|
||||
imports: [
|
||||
'vue',
|
||||
'vue-router',
|
||||
'pinia',
|
||||
"vue",
|
||||
"vue-router",
|
||||
"pinia",
|
||||
{
|
||||
'ant-design-vue': ['message', 'notification', 'Modal'],
|
||||
"ant-design-vue": ["message", "notification", "Modal"],
|
||||
},
|
||||
],
|
||||
dts: 'src/auto-imports.d.ts',
|
||||
dts: "src/auto-imports.d.ts",
|
||||
}),
|
||||
Components({
|
||||
resolvers: [
|
||||
@ -28,32 +28,32 @@ export default defineConfig({
|
||||
importStyle: false,
|
||||
}),
|
||||
],
|
||||
dts: 'src/components.d.ts',
|
||||
dts: "src/components.d.ts",
|
||||
}),
|
||||
viteCompression({
|
||||
verbose: true,
|
||||
disable: false,
|
||||
threshold: 10240,
|
||||
algorithm: 'gzip',
|
||||
ext: '.gz',
|
||||
algorithm: "gzip",
|
||||
ext: ".gz",
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, 'src'),
|
||||
"@": resolve(__dirname, "src"),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
port: 5173,
|
||||
host: true,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:3000',
|
||||
"/api": {
|
||||
target: "http://8.148.151.56:8080",
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api/, '/api'),
|
||||
rewrite: (path) => path.replace(/^\/api/, "/api"),
|
||||
},
|
||||
'/uploads': {
|
||||
target: 'http://localhost:3000',
|
||||
"/uploads": {
|
||||
target: "http://8.148.151.56:8080",
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
@ -62,10 +62,16 @@ export default defineConfig({
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
'ant-design-vue': ['ant-design-vue', '@ant-design/icons-vue'],
|
||||
'echarts': ['echarts'],
|
||||
'fullcalendar': ['@fullcalendar/vue3', '@fullcalendar/core', '@fullcalendar/daygrid', '@fullcalendar/timegrid', '@fullcalendar/interaction'],
|
||||
'dayjs': ['dayjs'],
|
||||
"ant-design-vue": ["ant-design-vue", "@ant-design/icons-vue"],
|
||||
echarts: ["echarts"],
|
||||
fullcalendar: [
|
||||
"@fullcalendar/vue3",
|
||||
"@fullcalendar/core",
|
||||
"@fullcalendar/daygrid",
|
||||
"@fullcalendar/timegrid",
|
||||
"@fullcalendar/interaction",
|
||||
],
|
||||
dayjs: ["dayjs"],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Loading…
Reference in New Issue
Block a user