feat(移动端): 优化学校端页面排版
- 统一学校端各管理页的头部排版、背景和外边距,在移动端左对齐标题并增加合理留白 - 优化筛选条、搜索框和操作按钮在小屏下的栅格布局,确保控件整行展示且不被压缩 - 调整统计卡片、列表和空状态在手机上的排列方式,提升阅读性和交互体验 Made-with: Cursor
This commit is contained in:
parent
73b7f10d56
commit
8bedf18f5d
@ -1,65 +1,73 @@
|
||||
<template>
|
||||
<div class="p-0 min-h-screen bg-gradient-to-b from-[#FFF8F0] to-white">
|
||||
<div class="min-h-screen bg-gradient-to-b from-[#FFF8F0] to-white px-4 py-4 md:px-6 md:py-6 lg:px-8">
|
||||
<!-- 欢迎横幅 -->
|
||||
<div class="bg-gradient-to-br from-[#FF8C42] via-[#FFB347] to-[#FFD93D] rounded-[20px] py-8 px-10 mb-6 relative overflow-hidden sm:py-6 sm:px-6">
|
||||
<div class="flex justify-between items-center relative z-[1]">
|
||||
<div>
|
||||
<h1 class="text-white text-[28px] font-bold m-0 mb-2 text-shadow-[0_2px_4px_rgba(0,0,0,0.1)] sm:text-[22px]"><HomeOutlined /> 校园阅读管理中心</h1>
|
||||
<div
|
||||
class="bg-gradient-to-br from-[#FF8C42] via-[#FFB347] to-[#FFD93D] rounded-[20px] px-5 py-5 mb-6 relative overflow-hidden sm:px-6 sm:py-6 lg:px-8 lg:py-8">
|
||||
<div class="flex justify-between items-center relative z-[1] pos-relative">
|
||||
<div class="z-10">
|
||||
<h1 class="text-white text-[28px] font-bold m-0 mb-2 text-shadow-[0_2px_4px_rgba(0,0,0,0.1)] sm:text-[22px]">
|
||||
<HomeOutlined /> 校园阅读管理中心
|
||||
</h1>
|
||||
<p class="text-white/90 text-base m-0">让每一个孩子都能享受阅读的快乐,智慧成长每一天!</p>
|
||||
</div>
|
||||
<div class="flex gap-4 sm:hidden">
|
||||
<span class="text-4xl animate-float"><BookOutlined /></span>
|
||||
<span class="text-4xl animate-float"><StarOutlined /></span>
|
||||
<span class="text-4xl animate-float"><BgColorsOutlined /></span>
|
||||
<span class="text-4xl animate-float"><SmileOutlined /></span>
|
||||
<div
|
||||
class="flex gap-4 pos-absolute top-0 right-0 w-full h-full pointer-events-none flex items-center justify-end">
|
||||
<span class="text-4xl animate-float">
|
||||
<BookOutlined />
|
||||
</span>
|
||||
<span class="text-4xl animate-float">
|
||||
<StarOutlined />
|
||||
</span>
|
||||
<span class="text-4xl animate-float">
|
||||
<BgColorsOutlined />
|
||||
</span>
|
||||
<span class="text-4xl animate-float">
|
||||
<SmileOutlined />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<div class="grid grid-cols-4 gap-5 mb-6 lg:grid-cols-2 md:grid-cols-1">
|
||||
<div
|
||||
v-for="(stat, index) in statCards"
|
||||
:key="index"
|
||||
class="bg-white rounded-2xl p-6 flex items-center gap-4 shadow-md transition-all duration-300 hover:-translate-y-1 hover:shadow-xl"
|
||||
>
|
||||
<div class="grid grid-cols-1 gap-5 mb-6 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<div v-for="(stat, index) in statCards" :key="index"
|
||||
class="bg-white rounded-2xl p-4 md:p-6 flex items-center gap-4 shadow-md transition-all duration-300 hover:-translate-y-1 hover:shadow-xl">
|
||||
<div
|
||||
class="w-[60px] h-[60px] rounded-2xl flex items-center justify-center text-[28px] text-white shrink-0"
|
||||
:style="{ background: stat.gradient }"
|
||||
>
|
||||
class="w-12 h-12 md:w-[60px] md:h-[60px] rounded-2xl flex items-center justify-center text-2xl md:text-[28px] text-white shrink-0"
|
||||
:style="{ background: stat.gradient }">
|
||||
<component :is="stat.icon" />
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-3xl font-bold text-[#2D3436] leading-tight">{{ stat.value }}</div>
|
||||
<div class="text-2xl md:text-3xl font-bold text-[#2D3436] leading-tight">{{ stat.value }}</div>
|
||||
<div class="text-sm text-[#636E72] mt-1">{{ stat.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 趋势图和分布图 -->
|
||||
<div class="grid grid-cols-[3fr_2fr] gap-6 mb-6 lg:grid-cols-1">
|
||||
<div class="grid grid-cols-1 gap-6 mb-6 lg:grid-cols-[3fr_2fr]">
|
||||
<div class="bg-white rounded-[20px] overflow-hidden shadow-md">
|
||||
<div class="flex items-center gap-3 py-5 px-6 border-b border-[#F5F5F5]">
|
||||
<span class="text-2xl text-[#FF8C42] flex items-center"><LineChartOutlined /></span>
|
||||
<span class="text-2xl text-[#FF8C42] flex items-center">
|
||||
<LineChartOutlined />
|
||||
</span>
|
||||
<h3 class="m-0 text-lg font-semibold text-[#2D3436] flex-1">授课趋势</h3>
|
||||
</div>
|
||||
<div
|
||||
class="py-5 px-6 min-h-[200px] flex items-center justify-center"
|
||||
:class="{ 'flex items-center justify-center': trendLoading }"
|
||||
>
|
||||
<div class="py-5 px-6 min-h-[200px] flex items-center justify-center"
|
||||
:class="{ 'flex items-center justify-center': trendLoading }">
|
||||
<a-spin v-if="trendLoading" />
|
||||
<div v-else ref="trendChartRef" class="w-full h-[300px]"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-[20px] overflow-hidden shadow-md">
|
||||
<div class="flex items-center gap-3 py-5 px-6 border-b border-[#F5F5F5]">
|
||||
<span class="text-2xl text-[#FF8C42] flex items-center"><BarChartOutlined /></span>
|
||||
<span class="text-2xl text-[#FF8C42] flex items-center">
|
||||
<BarChartOutlined />
|
||||
</span>
|
||||
<h3 class="m-0 text-lg font-semibold text-[#2D3436] flex-1">课程分布</h3>
|
||||
</div>
|
||||
<div
|
||||
class="py-5 px-6 min-h-[200px] flex items-center justify-center"
|
||||
:class="{ 'flex items-center justify-center': distributionLoading }"
|
||||
>
|
||||
<div class="py-5 px-6 min-h-[200px] flex items-center justify-center"
|
||||
:class="{ 'flex items-center justify-center': distributionLoading }">
|
||||
<a-spin v-if="distributionLoading" />
|
||||
<div v-else ref="distributionChartRef" class="w-full h-[300px]"></div>
|
||||
</div>
|
||||
@ -67,29 +75,29 @@
|
||||
</div>
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<div class="grid grid-cols-2 gap-6 mb-6 lg:grid-cols-1">
|
||||
<div class="grid grid-cols-1 gap-6 mb-6 md:grid-cols-2">
|
||||
<!-- 近期活动 -->
|
||||
<div class="bg-white rounded-[20px] overflow-hidden shadow-md">
|
||||
<div class="flex items-center gap-3 py-5 px-6 border-b border-[#F5F5F5]">
|
||||
<span class="text-2xl text-[#FF8C42] flex items-center"><CalendarOutlined /></span>
|
||||
<span class="text-2xl text-[#FF8C42] flex items-center">
|
||||
<CalendarOutlined />
|
||||
</span>
|
||||
<h3 class="m-0 text-lg font-semibold text-[#2D3436] flex-1">近期课程活动</h3>
|
||||
</div>
|
||||
<div
|
||||
class="py-5 px-6 min-h-[200px]"
|
||||
:class="loading ? 'flex items-center justify-center' : ''"
|
||||
>
|
||||
<div class="py-5 px-6 min-h-[200px]" :class="loading ? 'flex items-center justify-center' : ''">
|
||||
<a-spin v-if="loading" />
|
||||
<div v-else-if="recentActivities.length === 0" class="flex flex-col items-center justify-center py-10 text-[#B2BEC3]">
|
||||
<span class="text-5xl mb-3 flex items-center justify-center text-[#B2BEC3]"><InboxOutlined /></span>
|
||||
<div v-else-if="recentActivities.length === 0"
|
||||
class="flex flex-col items-center justify-center py-10 text-[#B2BEC3]">
|
||||
<span class="text-5xl mb-3 flex items-center justify-center text-[#B2BEC3]">
|
||||
<InboxOutlined />
|
||||
</span>
|
||||
<p class="m-0 text-sm">暂无近期活动</p>
|
||||
</div>
|
||||
<div v-else class="flex flex-col gap-4">
|
||||
<div
|
||||
v-for="item in recentActivities"
|
||||
:key="item.id"
|
||||
class="flex items-center gap-3 py-3 px-4 bg-[#FAFAFA] rounded-xl transition-all duration-200 hover:bg-[#FFF8F0]"
|
||||
>
|
||||
<div class="w-10 h-10 rounded-[10px] bg-gradient-to-br from-[#FF8C42] to-[#FFB347] flex items-center justify-center text-white shrink-0">
|
||||
<div v-for="item in recentActivities" :key="item.id"
|
||||
class="flex items-center gap-3 py-3 px-4 bg-[#FAFAFA] rounded-xl transition-all duration-200 hover:bg-[#FFF8F0]">
|
||||
<div
|
||||
class="w-10 h-10 rounded-[10px] bg-gradient-to-br from-[#FF8C42] to-[#FFB347] flex items-center justify-center text-white shrink-0">
|
||||
<BookOutlined />
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
@ -104,39 +112,37 @@
|
||||
<!-- 教师活跃度排行 -->
|
||||
<div class="bg-white rounded-[20px] overflow-hidden shadow-md">
|
||||
<div class="flex items-center gap-3 py-5 px-6 border-b border-[#F5F5F5]">
|
||||
<span class="text-2xl text-[#FF8C42] flex items-center"><TrophyOutlined /></span>
|
||||
<span class="text-2xl text-[#FF8C42] flex items-center">
|
||||
<TrophyOutlined />
|
||||
</span>
|
||||
<h3 class="m-0 text-lg font-semibold text-[#2D3436] flex-1">教师活跃度排行</h3>
|
||||
</div>
|
||||
<div
|
||||
class="py-5 px-6 min-h-[200px]"
|
||||
:class="loading ? 'flex items-center justify-center' : ''"
|
||||
>
|
||||
<div class="py-5 px-6 min-h-[200px]" :class="loading ? 'flex items-center justify-center' : ''">
|
||||
<a-spin v-if="loading" />
|
||||
<div v-else-if="activeTeachers.length === 0" class="flex flex-col items-center justify-center py-10 text-[#B2BEC3]">
|
||||
<span class="text-5xl mb-3 flex items-center justify-center text-[#B2BEC3]"><TeamOutlined /></span>
|
||||
<div v-else-if="activeTeachers.length === 0"
|
||||
class="flex flex-col items-center justify-center py-10 text-[#B2BEC3]">
|
||||
<span class="text-5xl mb-3 flex items-center justify-center text-[#B2BEC3]">
|
||||
<TeamOutlined />
|
||||
</span>
|
||||
<p class="m-0 text-sm">暂无数据</p>
|
||||
</div>
|
||||
<div v-else class="flex flex-col gap-3">
|
||||
<div
|
||||
v-for="(item, index) in activeTeachers"
|
||||
:key="item.id"
|
||||
class="flex items-center gap-3 py-3 px-4 bg-[#FAFAFA] rounded-xl transition-all duration-200 hover:bg-[#FFF8F0]"
|
||||
>
|
||||
<div
|
||||
class="w-7 h-7 rounded-lg flex items-center justify-center text-sm font-semibold text-white shrink-0"
|
||||
:class="
|
||||
index === 0 ? 'bg-gradient-to-br from-[#FFD700] to-[#FFA500]' :
|
||||
<div v-for="(item, index) in activeTeachers" :key="item.id"
|
||||
class="flex items-center gap-3 py-3 px-4 bg-[#FAFAFA] rounded-xl transition-all duration-200 hover:bg-[#FFF8F0]">
|
||||
<div class="w-7 h-7 rounded-lg flex items-center justify-center text-sm font-semibold text-white shrink-0"
|
||||
:class="index === 0 ? 'bg-gradient-to-br from-[#FFD700] to-[#FFA500]' :
|
||||
index === 1 ? 'bg-gradient-to-br from-[#C0C0C0] to-[#A8A8A8]' :
|
||||
index === 2 ? 'bg-gradient-to-br from-[#CD7F32] to-[#B8860B]' :
|
||||
'bg-[#B2BEC3]'
|
||||
"
|
||||
>
|
||||
index === 2 ? 'bg-gradient-to-br from-[#CD7F32] to-[#B8860B]' :
|
||||
'bg-[#B2BEC3]'
|
||||
">
|
||||
{{ index + 1 }}
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-sm font-medium text-[#2D3436]">{{ item.name }}</div>
|
||||
<div class="text-xs text-[#636E72] mt-1 flex items-center gap-1">
|
||||
<span class="text-sm text-[#636E72] flex items-center"><ReadOutlined /></span>
|
||||
<span class="text-sm text-[#636E72] flex items-center">
|
||||
<ReadOutlined />
|
||||
</span>
|
||||
授课 {{ item.lessonCount }} 次
|
||||
</div>
|
||||
</div>
|
||||
@ -151,49 +157,44 @@
|
||||
|
||||
<!-- 课程使用统计 -->
|
||||
<div class="mb-6 bg-white rounded-[20px] overflow-hidden shadow-md">
|
||||
<div class="flex items-center gap-3 py-5 px-6 border-b border-[#F5F5F5]">
|
||||
<span class="text-2xl text-[#FF8C42] flex items-center"><BarChartOutlined /></span>
|
||||
<h3 class="m-0 text-lg font-semibold text-[#2D3436] flex-1">课程使用统计</h3>
|
||||
<div class="ml-auto">
|
||||
<a-range-picker
|
||||
v-model:value="dateRange"
|
||||
@change="loadCourseStats"
|
||||
:placeholder="['开始日期', '结束日期']"
|
||||
class="w-60"
|
||||
/>
|
||||
<div class="flex flex-col gap-3 py-5 px-6 border-b border-[#F5F5F5] sm:flex-row sm:items-center">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-2xl text-[#FF8C42] flex items-center">
|
||||
<BarChartOutlined />
|
||||
</span>
|
||||
<h3 class="m-0 text-lg font-semibold text-[#2D3436]">课程使用统计</h3>
|
||||
</div>
|
||||
<div class="w-full sm:w-auto sm:ml-auto">
|
||||
<a-range-picker v-model:value="dateRange" @change="loadCourseStats" :placeholder="['开始日期', '结束日期']"
|
||||
class="w-full sm:w-60" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="py-5 px-6 min-h-[200px]"
|
||||
:class="courseStatsLoading ? 'flex items-center justify-center' : ''"
|
||||
>
|
||||
<div class="py-5 px-6 min-h-[200px]" :class="courseStatsLoading ? 'flex items-center justify-center' : ''">
|
||||
<a-spin v-if="courseStatsLoading" />
|
||||
<div v-else-if="courseStats.length === 0" class="flex flex-col items-center justify-center py-10 text-[#B2BEC3]">
|
||||
<span class="text-5xl mb-3 flex items-center justify-center text-[#B2BEC3]"><LineChartOutlined /></span>
|
||||
<div v-else-if="courseStats.length === 0"
|
||||
class="flex flex-col items-center justify-center py-10 text-[#B2BEC3]">
|
||||
<span class="text-5xl mb-3 flex items-center justify-center text-[#B2BEC3]">
|
||||
<LineChartOutlined />
|
||||
</span>
|
||||
<p class="m-0 text-sm">暂无课程使用数据</p>
|
||||
</div>
|
||||
<div v-else class="flex flex-col gap-3">
|
||||
<div
|
||||
v-for="(item, index) in courseStats"
|
||||
:key="item.courseId"
|
||||
class="flex items-center gap-4 py-3 px-4 bg-[#FAFAFA] rounded-xl transition-all duration-200 hover:bg-[#FFF8F0]"
|
||||
>
|
||||
<div v-for="(item, index) in courseStats" :key="item.courseId"
|
||||
class="flex items-center gap-4 py-3 px-4 bg-[#FAFAFA] rounded-xl transition-all duration-200 hover:bg-[#FFF8F0]">
|
||||
<div class="w-8 text-center text-sm font-semibold text-[#636E72] shrink-0">
|
||||
<TrophyFilled v-if="index < 3" class="text-2xl" :style="getTrophyColor(index)" />
|
||||
<span v-else>{{ index + 1 }}</span>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0 text-sm font-medium text-[#2D3436]">{{ item.courseName }}</div>
|
||||
<div class="flex items-center gap-3 w-[200px] md:w-[120px]">
|
||||
<div class="flex items-center gap-3 w-full md:w-[200px]">
|
||||
<div class="flex-1 h-2 bg-[#F0F0F0] rounded overflow-hidden">
|
||||
<div
|
||||
class="h-full rounded transition-[width] duration-300"
|
||||
:style="{
|
||||
width: getUsagePercent(item.usageCount) + '%',
|
||||
background: getProgressGradient(index)
|
||||
}"
|
||||
></div>
|
||||
<div class="h-full rounded transition-[width] duration-300" :style="{
|
||||
width: getUsagePercent(item.usageCount) + '%',
|
||||
background: getProgressGradient(index)
|
||||
}"></div>
|
||||
</div>
|
||||
<span class="text-xs text-[#636E72] whitespace-nowrap min-w-[40px] text-right">{{ item.usageCount }}次</span>
|
||||
<span class="text-xs text-[#636E72] whitespace-nowrap min-w-[40px] text-right">{{ item.usageCount
|
||||
}}次</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -204,16 +205,18 @@
|
||||
<div class="mb-6">
|
||||
<div class="bg-white rounded-[20px] overflow-hidden shadow-md">
|
||||
<div class="flex items-center gap-3 py-5 px-6 border-b border-[#F5F5F5]">
|
||||
<span class="text-2xl text-[#FF8C42] flex items-center"><DownloadOutlined /></span>
|
||||
<span class="text-2xl text-[#FF8C42] flex items-center">
|
||||
<DownloadOutlined />
|
||||
</span>
|
||||
<h3 class="m-0 text-lg font-semibold text-[#2D3436] flex-1">数据导出</h3>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<div class="grid grid-cols-3 gap-4 lg:grid-cols-1">
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-3">
|
||||
<div
|
||||
class="flex items-center gap-4 p-5 bg-[#FAFAFA] rounded-xl cursor-pointer transition-all duration-300 border border-transparent hover:bg-[#FFF8F0] hover:border-[#FFD4B8] hover:-translate-y-0.5"
|
||||
@click="handleExportLessons"
|
||||
>
|
||||
<div class="w-12 h-12 rounded-xl flex items-center justify-center text-white text-xl shrink-0 bg-gradient-to-br from-[#FF8C42] to-[#FFB347]">
|
||||
class="flex items-center gap-4 p-4 md:p-5 bg-[#FAFAFA] rounded-xl cursor-pointer transition-all duration-300 border border-transparent hover:bg-[#FFF8F0] hover:border-[#FFD4B8] hover:-translate-y-0.5"
|
||||
@click="handleExportLessons">
|
||||
<div
|
||||
class="w-12 h-12 rounded-xl flex items-center justify-center text-white text-xl shrink-0 bg-gradient-to-br from-[#FF8C42] to-[#FFB347]">
|
||||
<ReadOutlined />
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
@ -222,10 +225,10 @@
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center gap-4 p-5 bg-[#FAFAFA] rounded-xl cursor-pointer transition-all duration-300 border border-transparent hover:bg-[#FFF8F0] hover:border-[#FFD4B8] hover:-translate-y-0.5"
|
||||
@click="handleExportTeacherStats"
|
||||
>
|
||||
<div class="w-12 h-12 rounded-xl flex items-center justify-center text-white text-xl shrink-0 bg-gradient-to-br from-[#667eea] to-[#764ba2]">
|
||||
class="flex items-center gap-4 p-4 md:p-5 bg-[#FAFAFA] rounded-xl cursor-pointer transition-all duration-300 border border-transparent hover:bg-[#FFF8F0] hover:border-[#FFD4B8] hover:-translate-y-0.5"
|
||||
@click="handleExportTeacherStats">
|
||||
<div
|
||||
class="w-12 h-12 rounded-xl flex items-center justify-center text-white text-xl shrink-0 bg-gradient-to-br from-[#667eea] to-[#764ba2]">
|
||||
<SolutionOutlined />
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
@ -234,10 +237,10 @@
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center gap-4 p-5 bg-[#FAFAFA] rounded-xl cursor-pointer transition-all duration-300 border border-transparent hover:bg-[#FFF8F0] hover:border-[#FFD4B8] hover:-translate-y-0.5"
|
||||
@click="handleExportStudentStats"
|
||||
>
|
||||
<div class="w-12 h-12 rounded-xl flex items-center justify-center text-white text-xl shrink-0 bg-gradient-to-br from-[#4facfe] to-[#00f2fe]">
|
||||
class="flex items-center gap-4 p-4 md:p-5 bg-[#FAFAFA] rounded-xl cursor-pointer transition-all duration-300 border border-transparent hover:bg-[#FFF8F0] hover:border-[#FFD4B8] hover:-translate-y-0.5"
|
||||
@click="handleExportStudentStats">
|
||||
<div
|
||||
class="w-12 h-12 rounded-xl flex items-center justify-center text-white text-xl shrink-0 bg-gradient-to-br from-[#4facfe] to-[#00f2fe]">
|
||||
<UserOutlined />
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
@ -710,13 +713,30 @@ onUnmounted(() => {
|
||||
<style scoped>
|
||||
/* 横幅装饰浮动动画,UnoCSS 无法表达 keyframes */
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-10px); }
|
||||
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-float {
|
||||
animation: float 3s ease-in-out infinite;
|
||||
}
|
||||
.animate-float:nth-child(2) { animation-delay: 0.5s; }
|
||||
.animate-float:nth-child(3) { animation-delay: 1s; }
|
||||
.animate-float:nth-child(4) { animation-delay: 1.5s; }
|
||||
|
||||
.animate-float:nth-child(2) {
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
|
||||
.animate-float:nth-child(3) {
|
||||
animation-delay: 1s;
|
||||
}
|
||||
|
||||
.animate-float:nth-child(4) {
|
||||
animation-delay: 1.5s;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
<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">
|
||||
<div class="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-16 h-16 rounded-2xl flex items-center justify-center bg-white/20">
|
||||
<BarChartOutlined class="text-[32px] text-white" />
|
||||
@ -12,13 +12,15 @@
|
||||
<p class="text-white/80 text-sm mt-1 m-0">查看学校教学数据和统计分析</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<div class="flex gap-3 flex-wrap w-full md:w-auto">
|
||||
<a-range-picker
|
||||
v-model:value="dateRange"
|
||||
:placeholder="['开始日期', '结束日期']"
|
||||
class="w-[240px]"
|
||||
class="w-full md:w-[240px]"
|
||||
/>
|
||||
<a-button class="!bg-white !border-0 rounded-xl text-[#FF8C42] font-600 export-btn hover:!bg-[#FFF8F0] hover:!text-[#FF7A2A]">
|
||||
<a-button
|
||||
class="w-full md:w-auto !bg-white !border-0 rounded-xl text-[#FF8C42] font-600 export-btn hover:!bg-[#FFF8F0] hover:!text-[#FF7A2A]"
|
||||
>
|
||||
<DownloadOutlined class="mr-2 text-base" />
|
||||
导出报告
|
||||
</a-button>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<div class="min-h-100vh bg-[linear-gradient(180deg,#FFF8F0_0%,#FFFFFF_100%)] p-0">
|
||||
<!-- 页面头部 -->
|
||||
<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">
|
||||
<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%)]">
|
||||
<HomeOutlined />
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<div class="min-h-100vh p-0 bg-[linear-gradient(180deg,#FFF8F0_0%,#FFFFFF_100%)]">
|
||||
<!-- 页面头部 -->
|
||||
<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">
|
||||
<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">
|
||||
<span class="flex items-center justify-center w-14 h-14 rounded-[14px] bg-white/25 backdrop-blur">
|
||||
<BookOutlined class="text-[32px] text-white" />
|
||||
@ -27,35 +27,25 @@
|
||||
|
||||
<!-- 年级切换Tab + 操作栏 -->
|
||||
<div class="flex flex-col gap-4 mb-6 py-5 px-6 bg-white rounded-2xl shadow-[0_2px_8px_rgba(0,0,0,0.04)]">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex items-center gap-4 flex-wrap">
|
||||
<span class="text-sm font-600 text-[#333] whitespace-nowrap">年级筛选</span>
|
||||
<div class="flex gap-2">
|
||||
<div
|
||||
v-for="grade in gradeOptions"
|
||||
:key="grade.value"
|
||||
<div class="flex gap-2 flex-wrap">
|
||||
<div v-for="grade in gradeOptions" :key="grade.value"
|
||||
class="py-2 px-5 rounded-[10px] text-sm font-500 cursor-pointer transition-all duration-300 border-none grade-tab"
|
||||
:class="selectedGrade === grade.value ? 'bg-[linear-gradient(135deg,#43e97b_0%,#38f9d7_100%)] text-white shadow-[0_4px_12px_rgba(67,233,123,0.3)]' : 'bg-[#F5F5F5] text-[#666] hover:bg-[#E8F5E9] hover:text-[#43e97b]'"
|
||||
@click="selectedGrade = grade.value"
|
||||
>
|
||||
@click="selectedGrade = grade.value">
|
||||
{{ grade.label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex justify-between items-center gap-3 max-md:flex-col max-md:items-stretch">
|
||||
<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>
|
||||
<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,#43e97b_0%,#38f9d7_100%)] hover:!bg-[linear-gradient(135deg,#32d86c_0%,#2ed9c0_100%)] !border-0 rounded-xl h-10 px-6 font-600" @click="showAuthModal">
|
||||
<a-button type="primary"
|
||||
class="w-full md:w-auto !bg-[linear-gradient(135deg,#43e97b_0%,#38f9d7_100%)] hover:!bg-[linear-gradient(135deg,#32d86c_0%,#2ed9c0_100%)] !border-0 rounded-xl h-10 px-6 font-600"
|
||||
@click="showAuthModal">
|
||||
<StarFilled class="mr-2 text-sm" />
|
||||
授权新课程
|
||||
</a-button>
|
||||
@ -63,22 +53,18 @@
|
||||
</div>
|
||||
|
||||
<!-- 课程卡片网格 -->
|
||||
<div class="grid gap-5 mb-6 course-grid" style="grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));" v-if="!loading && filteredCourses.length > 0">
|
||||
<div
|
||||
v-for="course in filteredCourses"
|
||||
:key="course.id"
|
||||
<div class="grid gap-5 mb-6 course-grid" style="grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));"
|
||||
v-if="!loading && filteredCourses.length > 0">
|
||||
<div v-for="course in filteredCourses" :key="course.id"
|
||||
class="bg-white rounded-2xl overflow-hidden shadow-[0_4px_12px_rgba(0,0,0,0.05)] transition-all duration-300 hover:translate-y-[-4px] hover:shadow-[0_8px_24px_rgba(0,0,0,0.1)]"
|
||||
:class="!course.authorized ? 'opacity-80' : ''"
|
||||
>
|
||||
:class="!course.authorized ? 'opacity-80' : ''">
|
||||
<div class="relative h-[140px] bg-[linear-gradient(135deg,#43e97b_0%,#38f9d7_100%)] card-cover">
|
||||
<div class="w-full h-full flex items-center justify-center" v-if="!course.pictureUrl">
|
||||
<ReadOutlined class="text-[48px] text-white/90" />
|
||||
</div>
|
||||
<img v-else :src="course.pictureUrl" alt="cover" class="w-full h-full object-cover" />
|
||||
<div
|
||||
class="absolute top-3 right-3 py-1 px-3 rounded-xl text-[11px] font-600 flex items-center gap-1"
|
||||
:class="course.authorized ? 'bg-white/90 text-[#43A047]' : 'bg-black/50 text-white'"
|
||||
>
|
||||
<div class="absolute top-3 right-3 py-1 px-3 rounded-xl text-[11px] font-600 flex items-center gap-1"
|
||||
:class="course.authorized ? 'bg-white/90 text-[#43A047]' : 'bg-black/50 text-white'">
|
||||
<CheckCircleOutlined v-if="course.authorized" />
|
||||
<ClockCircleOutlined v-else />
|
||||
{{ course.authorized ? '已授权' : '未授权' }}
|
||||
@ -90,20 +76,12 @@
|
||||
<p class="text-xs text-[#636E72] m-0 mb-3">《{{ course.pictureBookName }}》</p>
|
||||
|
||||
<div class="flex flex-wrap gap-1.5 mb-3">
|
||||
<span
|
||||
v-for="tag in course.gradeTags.slice(0, 2)"
|
||||
:key="tag"
|
||||
class="py-0.5 px-2 rounded-lg text-[10px] tag"
|
||||
:style="getGradeTagStyle(translateGradeTag(tag))"
|
||||
>
|
||||
<span v-for="tag in course.gradeTags.slice(0, 2)" :key="tag" class="py-0.5 px-2 rounded-lg text-[10px] tag"
|
||||
:style="getGradeTagStyle(translateGradeTag(tag))">
|
||||
{{ translateGradeTag(tag) }}
|
||||
</span>
|
||||
<span
|
||||
v-for="tag in course.domainTags.slice(0, 2)"
|
||||
:key="tag"
|
||||
class="py-0.5 px-2 rounded-lg text-[10px] tag"
|
||||
:style="getDomainTagStyle(translateDomainTag(tag))"
|
||||
>
|
||||
<span v-for="tag in course.domainTags.slice(0, 2)" :key="tag" class="py-0.5 px-2 rounded-lg text-[10px] tag"
|
||||
:style="getDomainTagStyle(translateDomainTag(tag))">
|
||||
{{ translateDomainTag(tag) }}
|
||||
</span>
|
||||
</div>
|
||||
@ -121,25 +99,17 @@
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-2 py-3 px-4 border-t border-[#F0F0F0] bg-[#FAFAFA] card-actions">
|
||||
<a-button type="link" size="small" @click="handleView(course)" class="!py-1 !px-2 !h-auto flex items-center gap-1">
|
||||
<a-button type="link" size="small" @click="handleView(course)"
|
||||
class="!py-1 !px-2 !h-auto flex items-center gap-1">
|
||||
<FileTextOutlined />
|
||||
详情
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="!course.authorized"
|
||||
type="link"
|
||||
size="small"
|
||||
class="!text-[#43e97b]"
|
||||
@click="handleAuthorize(course)"
|
||||
>
|
||||
<a-button v-if="!course.authorized" type="link" size="small" class="!text-[#43e97b]"
|
||||
@click="handleAuthorize(course)">
|
||||
<StarFilled />
|
||||
授权
|
||||
</a-button>
|
||||
<a-popconfirm
|
||||
v-else
|
||||
title="确定要取消授权吗?"
|
||||
@confirm="handleRevoke(course)"
|
||||
>
|
||||
<a-popconfirm v-else title="确定要取消授权吗?" @confirm="handleRevoke(course)">
|
||||
<a-button type="link" size="small" danger>
|
||||
<StopOutlined />
|
||||
取消
|
||||
@ -150,12 +120,15 @@
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div class="flex flex-col items-center justify-center py-20 bg-white rounded-2xl empty-state" v-if="!loading && filteredCourses.length === 0">
|
||||
<div class="flex items-center justify-center w-20 h-20 rounded-[20px] mb-4 bg-[linear-gradient(135deg,#43e97b_0%,#38f9d7_100%)]">
|
||||
<div class="flex flex-col items-center justify-center py-20 bg-white rounded-2xl empty-state"
|
||||
v-if="!loading && filteredCourses.length === 0">
|
||||
<div
|
||||
class="flex items-center justify-center w-20 h-20 rounded-[20px] mb-4 bg-[linear-gradient(135deg,#43e97b_0%,#38f9d7_100%)]">
|
||||
<BookOutlined class="text-[40px] text-white" />
|
||||
</div>
|
||||
<p class="text-[#636E72] text-base mb-6">{{ searchKeyword ? '未找到匹配的课程' : '暂无课程数据' }}</p>
|
||||
<a-button type="primary" class="!bg-[linear-gradient(135deg,#43e97b_0%,#38f9d7_100%)] !border-0 rounded-xl" @click="showAuthModal">
|
||||
<a-button type="primary" class="!bg-[linear-gradient(135deg,#43e97b_0%,#38f9d7_100%)] !border-0 rounded-xl"
|
||||
@click="showAuthModal">
|
||||
授权第一门课程
|
||||
</a-button>
|
||||
</div>
|
||||
@ -167,12 +140,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 授权课程模态框 -->
|
||||
<a-modal
|
||||
v-model:open="authModalVisible"
|
||||
width="800px"
|
||||
@ok="handleAuthModalOk"
|
||||
@cancel="authModalVisible = false"
|
||||
>
|
||||
<a-modal v-model:open="authModalVisible" width="800px" @ok="handleAuthModalOk" @cancel="authModalVisible = false">
|
||||
<template #title>
|
||||
<span class="flex items-center gap-2">
|
||||
<StarFilled class="text-[#43e97b] text-lg" />
|
||||
@ -181,33 +149,25 @@
|
||||
</template>
|
||||
<div class="flex flex-col gap-5">
|
||||
<div class="auth-search">
|
||||
<a-input-search
|
||||
v-model:value="searchKeyword"
|
||||
placeholder="输入课程名称搜索..."
|
||||
@search="searchCourses"
|
||||
size="large"
|
||||
>
|
||||
<a-input-search v-model:value="searchKeyword" placeholder="输入课程名称搜索..." @search="searchCourses" size="large">
|
||||
<template #prefix>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
</a-input-search>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-3 max-h-[400px] overflow-y-auto available-courses" v-if="!authLoading && availableCourses.length > 0">
|
||||
<div
|
||||
v-for="course in availableCourses"
|
||||
:key="course.id"
|
||||
<div class="grid grid-cols-2 gap-3 max-h-[400px] overflow-y-auto available-courses"
|
||||
v-if="!authLoading && availableCourses.length > 0">
|
||||
<div v-for="course in availableCourses" :key="course.id"
|
||||
class="flex gap-3 p-3 bg-[#F8F9FA] rounded-xl cursor-pointer transition-all duration-200 border-2 border-transparent available-course-item hover:bg-[#FFF8F0]"
|
||||
:class="selectedCourseIds.includes(course.id) ? 'border-[#43e97b] bg-[#E8F5E9]' : ''"
|
||||
@click="toggleCourseSelection(course.id)"
|
||||
>
|
||||
<div
|
||||
class="w-6 h-6 border-2 rounded-md flex items-center justify-center text-white text-sm course-checkbox"
|
||||
:class="selectedCourseIds.includes(course.id) ? 'bg-[#43e97b] border-[#43e97b]' : 'border-[#E0E0E0]'"
|
||||
>
|
||||
@click="toggleCourseSelection(course.id)">
|
||||
<div class="w-6 h-6 border-2 rounded-md flex items-center justify-center text-white text-sm course-checkbox"
|
||||
:class="selectedCourseIds.includes(course.id) ? 'bg-[#43e97b] border-[#43e97b]' : 'border-[#E0E0E0]'">
|
||||
<CheckCircleOutlined v-if="selectedCourseIds.includes(course.id)" />
|
||||
</div>
|
||||
<div class="w-[50px] h-[50px] rounded-lg flex items-center justify-center text-2xl overflow-hidden bg-[linear-gradient(135deg,#43e97b_0%,#38f9d7_100%)]">
|
||||
<div
|
||||
class="w-[50px] h-[50px] rounded-lg flex items-center justify-center text-2xl overflow-hidden bg-[linear-gradient(135deg,#43e97b_0%,#38f9d7_100%)]">
|
||||
<ReadOutlined v-if="!course.pictureUrl" class="text-white" />
|
||||
<img v-else :src="course.pictureUrl" alt="cover" class="w-full h-full object-cover" />
|
||||
</div>
|
||||
@ -215,7 +175,8 @@
|
||||
<div class="text-sm font-500 text-[#2D3436]">{{ course.name }}</div>
|
||||
<div class="text-[11px] text-[#636E72] mt-0.5">《{{ course.pictureBookName }}》</div>
|
||||
<div class="flex gap-1 mt-1.5">
|
||||
<span v-for="tag in course.gradeTags.slice(0, 2)" :key="tag" class="py-0.5 px-1.5 bg-[#E3F2FD] text-[#1976D2] rounded text-[10px]">
|
||||
<span v-for="tag in course.gradeTags.slice(0, 2)" :key="tag"
|
||||
class="py-0.5 px-1.5 bg-[#E3F2FD] text-[#1976D2] rounded text-[10px]">
|
||||
{{ tag }}
|
||||
</span>
|
||||
</div>
|
||||
@ -443,10 +404,8 @@ 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) {}
|
||||
|
||||
.search-box :deep(.ant-input-affix-wrapper:hover) {
|
||||
border-color: #43e97b;
|
||||
}
|
||||
@ -459,8 +418,13 @@ onMounted(() => {
|
||||
.course-grid {
|
||||
grid-template-columns: 1fr !important;
|
||||
}
|
||||
|
||||
.available-courses {
|
||||
grid-template-columns: 1fr !important;
|
||||
}
|
||||
|
||||
.search-box :deep(.ant-input-search) {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<div class="min-h-100vh p-0 bg-[linear-gradient(180deg,#FFF8F0_0%,#FFFFFF_100%)]">
|
||||
<!-- 页面头部 -->
|
||||
<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">
|
||||
<div class="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-16 h-16 rounded-2xl flex items-center justify-center text-[32px] text-white bg-white/20">
|
||||
<MessageOutlined />
|
||||
@ -34,13 +34,15 @@
|
||||
</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)]">
|
||||
<div class="flex gap-3">
|
||||
<div
|
||||
class="flex justify-between items-center gap-3 flex-wrap mb-6 py-4 px-5 bg-white rounded-2xl shadow-[0_2px_8px_rgba(0,0,0,0.04)] filter-bar max-md:flex-col max-md:items-stretch"
|
||||
>
|
||||
<div class="flex gap-3 flex-wrap w-full md:w-auto">
|
||||
<a-select
|
||||
v-model:value="filters.teacherId"
|
||||
placeholder="选择教师"
|
||||
allow-clear
|
||||
class="w-[150px]"
|
||||
class="w-full md:w-[150px]"
|
||||
@change="handleFilter"
|
||||
>
|
||||
<a-select-option v-for="teacher in teachers" :key="teacher.id" :value="teacher.id">
|
||||
@ -50,7 +52,7 @@
|
||||
<a-input-search
|
||||
v-model:value="filters.keyword"
|
||||
placeholder="搜索课程名称"
|
||||
class="w-[200px]"
|
||||
class="w-full md:w-[200px]"
|
||||
@search="handleFilter"
|
||||
allow-clear
|
||||
/>
|
||||
@ -376,8 +378,19 @@ onMounted(() => {
|
||||
.feedback-grid {
|
||||
grid-template-columns: 1fr !important;
|
||||
}
|
||||
|
||||
.detail-ratings {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.filter-bar {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.filter-bar :deep(.ant-select),
|
||||
.filter-bar :deep(.ant-input-search) {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<div class="min-h-100vh p-0 bg-[linear-gradient(180deg,#FFF8F0_0%,#FFFFFF_100%)]">
|
||||
<!-- 页面头部 -->
|
||||
<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:text-center">
|
||||
<div class="flex justify-between items-center max-md:flex-col max-md:gap-4 max-md:items-start">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-14 h-14 rounded-2xl flex items-center justify-center bg-white/20">
|
||||
<CameraOutlined class="text-[28px] text-white" />
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<div class="min-h-100vh p-0 bg-[linear-gradient(180deg,#FFF8F0_0%,#FFFFFF_100%)]">
|
||||
<!-- 页面头部 -->
|
||||
<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:gap-4 max-md:text-center">
|
||||
<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-12 h-12 rounded-xl flex items-center justify-center bg-white/20">
|
||||
<IdcardOutlined class="text-[28px] text-white" />
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="flex flex-wrap gap-3 md:flex-nowrap md:justify-between md:items-center items-start mb-5"
|
||||
>
|
||||
<h2 class="m-0 flex-shrink-0">课程排期</h2>
|
||||
<a-space :wrap="true">
|
||||
<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="bg-white rounded-2xl p-4 md:p-6 shadow-[0_4px_16px_rgba(0,0,0,0.04)] mb-4">
|
||||
<div
|
||||
class="flex flex-wrap gap-3 md:flex-nowrap md:justify-between md:items-center items-start mb-5 max-md:flex-col max-md:items-start"
|
||||
>
|
||||
<h2 class="m-0 flex-shrink-0">课程排期</h2>
|
||||
<a-space :wrap="true">
|
||||
<a-dropdown>
|
||||
<a-button>
|
||||
<template #icon>
|
||||
@ -56,41 +57,61 @@
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 筛选区 -->
|
||||
<div class="mb-5 p-4 bg-[#fafafa] rounded-lg">
|
||||
<a-space wrap>
|
||||
<a-select v-model:value="filters.classId" placeholder="选择班级" allowClear class="w-[150px]"
|
||||
@change="loadSchedules">
|
||||
<a-select-option v-for="cls in classes" :key="cls.id" :value="cls.id">
|
||||
{{ cls.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-select v-model:value="filters.teacherId" placeholder="选择教师" allowClear class="w-[150px]"
|
||||
@change="loadSchedules">
|
||||
<a-select-option v-for="teacher in teachers" :key="teacher.id" :value="teacher.id">
|
||||
{{ teacher.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-range-picker :value="dateRange" @change="handleDateChange" />
|
||||
<a-select v-model:value="filters.status" placeholder="状态" allowClear class="w-[120px]" @change="loadSchedules">
|
||||
<a-select-option value="ACTIVE">有效</a-select-option>
|
||||
<a-select-option value="CANCELLED">已取消</a-select-option>
|
||||
</a-select>
|
||||
</a-space>
|
||||
</div>
|
||||
<!-- 筛选区 -->
|
||||
<div class="mb-5 p-4 bg-[#fafafa] rounded-xl">
|
||||
<a-space wrap>
|
||||
<a-select
|
||||
v-model:value="filters.classId"
|
||||
placeholder="选择班级"
|
||||
allowClear
|
||||
class="w-full md:w-[150px]"
|
||||
@change="loadSchedules"
|
||||
>
|
||||
<a-select-option v-for="cls in classes" :key="cls.id" :value="cls.id">
|
||||
{{ cls.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-select
|
||||
v-model:value="filters.teacherId"
|
||||
placeholder="选择教师"
|
||||
allowClear
|
||||
class="w-full md:w-[150px]"
|
||||
@change="loadSchedules"
|
||||
>
|
||||
<a-select-option v-for="teacher in teachers" :key="teacher.id" :value="teacher.id">
|
||||
{{ teacher.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-range-picker
|
||||
:value="dateRange"
|
||||
@change="handleDateChange"
|
||||
class="w-full md:w-auto"
|
||||
/>
|
||||
<a-select
|
||||
v-model:value="filters.status"
|
||||
placeholder="状态"
|
||||
allowClear
|
||||
class="w-full md:w-[120px]"
|
||||
@change="loadSchedules"
|
||||
>
|
||||
<a-select-option value="ACTIVE">有效</a-select-option>
|
||||
<a-select-option value="CANCELLED">已取消</a-select-option>
|
||||
</a-select>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<!-- 排课列表 -->
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="schedules"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
rowKey="id"
|
||||
:scroll="{ x: true }"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<!-- 排课列表 -->
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="schedules"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
rowKey="id"
|
||||
:scroll="{ x: true }"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'scheduledDate'">
|
||||
{{ formatDate(record.scheduledDate) }}
|
||||
@ -118,7 +139,8 @@
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
<!-- 新建/编辑排课弹窗 -->
|
||||
<a-modal v-model:open="modalVisible" :title="editingSchedule ? '编辑排课' : '新建排课'" :confirm-loading="modalLoading"
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="min-h-100vh bg-[linear-gradient(180deg,#FFF8F0_0%,#FFFFFF_100%)] p-6">
|
||||
<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="mb-6 flex justify-between items-center">
|
||||
<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)]">
|
||||
<AppstoreOutlined class="text-[28px] text-white" />
|
||||
@ -11,7 +11,11 @@
|
||||
<p class="text-[#666] text-sm mt-1 mb-0">管理本校教师创建的校本课程包</p>
|
||||
</div>
|
||||
</div>
|
||||
<a-button type="primary" @click="handleCreate">
|
||||
<a-button
|
||||
type="primary"
|
||||
class="w-full md:w-auto"
|
||||
@click="handleCreate"
|
||||
>
|
||||
<PlusOutlined /> 创建校本课程包
|
||||
</a-button>
|
||||
</div>
|
||||
@ -53,12 +57,15 @@
|
||||
<span>校本课程包列表</span>
|
||||
</template>
|
||||
<template #extra>
|
||||
<a-input-search
|
||||
v-model:value="searchKeyword"
|
||||
placeholder="搜索课程包名称"
|
||||
class="w-[200px]"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
<div class="search-box">
|
||||
<a-input-search
|
||||
v-model:value="searchKeyword"
|
||||
placeholder="搜索课程包名称"
|
||||
class="w-[220px]"
|
||||
@search="handleSearch"
|
||||
allow-clear
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<a-table
|
||||
@ -543,4 +550,24 @@ onMounted(() => {
|
||||
:deep(.ant-table-thead > tr > th) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.search-box :deep(.ant-input-affix-wrapper) {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.list-card :deep(.ant-card-head-wrapper) {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.list-card :deep(.ant-card-extra) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-box :deep(.ant-input-search) {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="p-0 min-h-screen bg-gradient-to-b from-[#FFF8F0] to-white">
|
||||
<div class="min-h-screen bg-gradient-to-b from-[#FFF8F0] to-white px-4 py-4 md:px-6 md:py-6">
|
||||
<div class="mb-6">
|
||||
<h1 class="text-2xl font-semibold text-[#2D3436] m-0 mb-2 flex items-center gap-3"><SettingOutlined /> 系统设置</h1>
|
||||
<p class="text-sm text-[#636E72] m-0">配置学校基本信息和通知偏好</p>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<div class="min-h-100vh p-0 bg-[linear-gradient(180deg,#FFF8F0_0%,#FFFFFF_100%)]">
|
||||
<!-- 页面头部 -->
|
||||
<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:text-center">
|
||||
<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-16 h-16 rounded-2xl flex items-center justify-center bg-white/20 text-[32px] text-white">
|
||||
<TeamOutlined />
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
<template>
|
||||
<div class="min-h-100vh p-4 bg-[#fafafa]">
|
||||
<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="flex justify-between items-start mb-6 page-header">
|
||||
<div class="flex justify-between items-start gap-4 mb-6 page-header max-md:flex-col max-md:items-start">
|
||||
<div class="header-left">
|
||||
<h2 class="m-0 text-2xl font-600 text-[#333]">阅读任务</h2>
|
||||
<p class="mt-1 mb-0 text-[#999] text-sm">管理全校阅读任务,跟踪学生完成情况</p>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<a-button type="primary" @click="showCreateModal">
|
||||
<a-button type="primary" class="w-full md:w-auto" @click="showCreateModal">
|
||||
<PlusOutlined /> 发布任务
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<div class="flex gap-5 mb-6 stats-cards">
|
||||
<div class="flex flex-col md:flex-row gap-5 mb-6 stats-cards">
|
||||
<div class="flex-1 flex items-center gap-4 p-5 bg-white rounded-xl shadow-[0_2px_8px_rgba(0,0,0,0.04)] stat-card">
|
||||
<div class="w-12 h-12 rounded-xl flex items-center justify-center text-2xl bg-[#e6f7ff] text-[#1890ff]">
|
||||
<FileTextOutlined />
|
||||
@ -45,19 +45,24 @@
|
||||
</div>
|
||||
|
||||
<!-- 筛选区域 -->
|
||||
<div class="flex gap-3 mb-6 filter-bar">
|
||||
<a-select v-model:value="filters.status" placeholder="任务状态" class="w-[120px]" allowClear @change="loadTasks">
|
||||
<div class="flex flex-wrap gap-3 mb-6 filter-bar">
|
||||
<a-select v-model:value="filters.status" placeholder="任务状态" class="w-full md:w-[120px]" allowClear @change="loadTasks">
|
||||
<a-select-option value="PUBLISHED">进行中</a-select-option>
|
||||
<a-select-option value="DRAFT">草稿</a-select-option>
|
||||
<a-select-option value="ARCHIVED">已归档</a-select-option>
|
||||
</a-select>
|
||||
<a-select v-model:value="filters.taskType" placeholder="任务类型" class="w-[120px]" allowClear @change="loadTasks">
|
||||
<a-select v-model:value="filters.taskType" placeholder="任务类型" class="w-full md:w-[120px]" allowClear @change="loadTasks">
|
||||
<a-select-option value="READING">阅读</a-select-option>
|
||||
<a-select-option value="ACTIVITY">活动</a-select-option>
|
||||
<a-select-option value="HOMEWORK">作业</a-select-option>
|
||||
</a-select>
|
||||
<a-input-search v-model:value="filters.keyword" placeholder="搜索任务标题" class="w-[200px]" @search="loadTasks"
|
||||
allow-clear />
|
||||
<a-input-search
|
||||
v-model:value="filters.keyword"
|
||||
placeholder="搜索任务标题"
|
||||
class="w-full md:w-[200px]"
|
||||
@search="loadTasks"
|
||||
allow-clear
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 任务列表 -->
|
||||
@ -177,8 +182,14 @@
|
||||
<a-tag color="orange">{{ completionStats.inProgress }} 进行中</a-tag>
|
||||
<a-tag color="green">{{ completionStats.completed }} 已完成</a-tag>
|
||||
</div>
|
||||
<a-table :dataSource="completions" :columns="completionColumns" :pagination="false" :loading="loadingCompletions"
|
||||
rowKey="id" size="small">
|
||||
<a-table
|
||||
:dataSource="completions"
|
||||
:columns="completionColumns"
|
||||
:pagination="false"
|
||||
:loading="loadingCompletions"
|
||||
rowKey="id"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getCompletionStatusColor(record.status)">
|
||||
@ -484,3 +495,31 @@ onMounted(() => {
|
||||
loadTasks();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@media (max-width: 768px) {
|
||||
.stats-cards {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.filter-bar :deep(.ant-select),
|
||||
.filter-bar :deep(.ant-input-search) {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.task-meta {
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.progress-info {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.progress-info :deep(.ant-progress) {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<div class="min-h-100vh p-0 bg-[linear-gradient(180deg,#FFF8F0_0%,#FFFFFF_100%)]">
|
||||
<!-- 页面头部 -->
|
||||
<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:gap-4 max-md:text-center">
|
||||
<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-12 h-12 rounded-xl flex items-center justify-center bg-white/20">
|
||||
<SolutionOutlined class="text-[28px] text-white" />
|
||||
|
||||
Loading…
Reference in New Issue
Block a user