feat(移动端): 优化学校端页面排版

- 统一学校端各管理页的头部排版、背景和外边距,在移动端左对齐标题并增加合理留白

- 优化筛选条、搜索框和操作按钮在小屏下的栅格布局,确保控件整行展示且不被压缩

- 调整统计卡片、列表和空状态在手机上的排列方式,提升阅读性和交互体验

Made-with: Cursor
This commit is contained in:
zhonghua 2026-03-04 15:15:45 +08:00
parent 73b7f10d56
commit 8bedf18f5d
13 changed files with 365 additions and 278 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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 />

View File

@ -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>

View File

@ -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>

View File

@ -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" />

View File

@ -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" />

View File

@ -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"

View File

@ -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>

View File

@ -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>

View File

@ -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 />

View File

@ -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>

View File

@ -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" />