From 2580f447a4ce5d816e0420255e6ca333653512a7 Mon Sep 17 00:00:00 2001 From: YangQiang Date: Tue, 21 Apr 2026 15:35:50 +0800 Subject: [PATCH] =?UTF-8?q?style(OnlineLearningMonitorPage):=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96UI=E6=A0=B7=E5=BC=8F=E5=92=8C=E4=BA=A4=E4=BA=92?= =?UTF-8?q?=E6=95=88=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新全局字体和背景渐变 - 改进导航栏、按钮和卡片的设计 - 增强悬停和点击动画效果 - 优化移动端响应式布局 - 调整颜色方案和阴影效果 --- .../scripts/__pycache__/core.cpython-312.pyc | Bin 0 -> 11761 bytes .../__pycache__/design_system.cpython-312.pyc | Bin 0 -> 57852 bytes src/components/EnglishWordReport.vue | 1748 +++++++++++++---- src/components/OnlineLearningMonitorPage.vue | 606 ++++-- 4 files changed, 1760 insertions(+), 594 deletions(-) create mode 100644 .trae/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-312.pyc create mode 100644 .trae/skills/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-312.pyc diff --git a/.trae/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-312.pyc b/.trae/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8cc30bdb1f35ab3f5b052de5049795c6321d8d0a GIT binary patch literal 11761 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 HRrdb_DmB?g literal 0 HcmV?d00001 diff --git a/.trae/skills/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-312.pyc b/.trae/skills/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8341c0a5aecdd46c5c13e62ec6d60c54f0d54683 GIT binary patch literal 57852 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 literal 0 HcmV?d00001 diff --git a/src/components/EnglishWordReport.vue b/src/components/EnglishWordReport.vue index 09c3b0f..2651c0c 100644 --- a/src/components/EnglishWordReport.vue +++ b/src/components/EnglishWordReport.vue @@ -64,10 +64,10 @@
- + - + @@ -85,9 +86,22 @@ style="width:120px; min-width: 100px;" size="large" :disabled="!selectedGrade" + clearable > + + + 重置 +
@@ -273,12 +287,25 @@
-

请确认或调整导出数据的筛选条件:

+
+ + + + + +

请确认或调整导出数据的筛选条件:

+
云校: @@ -464,6 +491,21 @@ const classList = [ { value: 'class4', label: '高二2班' }, ] +const hasActiveFilters = computed(() => { + return selectedCloud.value !== '' || + selectedSchool.value !== '' || + selectedGrade.value !== '' || + selectedClass.value !== '' +}) + +const resetFilters = () => { + selectedCloud.value = '' + selectedSchool.value = '' + selectedGrade.value = '' + selectedClass.value = '' + MessagePlugin.success('筛选条件已重置') +} + // ── 总体学情统计 ────────────────────────────────────────── const disabledFutureDate = (date) => { return dayjs(date).isAfter(dayjs().endOf('day')) @@ -939,11 +981,11 @@ const progressDistData = ref([ const progressColor = (range) => { const value = parseInt(range) - if (value >= 90) return '#00a870' - if (value >= 80) return '#0052d9' - if (value >= 60) return '#ed7b2f' - if (value >= 40) return '#f5a623' - return '#e34d59' + if (value >= 90) return '#10B981' + if (value >= 80) return '#3B82F6' + if (value >= 60) return '#F59E0B' + if (value >= 40) return '#F97316' + return '#EF4444' } const progressDistColumns = [ @@ -975,25 +1017,39 @@ const initProgressChart = () => { progressChart = echarts.init(progressChartRef.value) - // 翻转数据以使其在图表上从0到100显示,或者保持从高到低视需求而定 - // 这里我们让X轴从0%到100%排列 const chartData = [...progressDistData.value].reverse() const option = { tooltip: { trigger: 'axis', - axisPointer: { type: 'shadow' }, - formatter: '{b}
学生人数: {c}人', - backgroundColor: 'rgba(255, 255, 255, 0.95)', - borderColor: '#e2e8f0', + axisPointer: { + type: 'shadow', + shadowStyle: { + color: 'rgba(30, 64, 175, 0.08)' + } + }, + formatter: (params) => { + const data = params[0] + return `
+
${data.name}
+
+ + 学生人数: + ${data.value}人 +
+
` + }, + backgroundColor: 'rgba(255, 255, 255, 0.98)', + borderColor: 'rgba(30, 64, 175, 0.15)', + borderWidth: 1, textStyle: { color: '#1e293b' }, - extraCssText: 'box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); border-radius: 8px;' + extraCssText: 'box-shadow: 0 10px 25px rgba(0, 0, 0, 0.12), 0 4px 10px rgba(30, 64, 175, 0.08); border-radius: 12px; backdrop-filter: blur(10px);' }, grid: { - top: '10%', - left: '3%', - right: '4%', - bottom: '10%', + top: '8%', + left: '4%', + right: '3%', + bottom: '12%', containLabel: true }, xAxis: { @@ -1001,29 +1057,59 @@ const initProgressChart = () => { data: chartData.map(item => item.progressRange), axisLabel: { interval: 0, - rotate: 45, + rotate: 30, color: '#64748b', - fontSize: 12 + fontSize: 11, + fontWeight: 500 }, - axisLine: { lineStyle: { color: '#e2e8f0' } } + axisLine: { + lineStyle: { + color: '#cbd5e1', + width: 2 + } + }, + axisTick: { + show: false + } }, yAxis: { type: 'value', - name: '人数', - nameTextStyle: { color: '#64748b', padding: [0, 0, 0, 20] }, - splitLine: { lineStyle: { type: 'dashed', color: '#f1f5f9' } }, - axisLabel: { color: '#64748b' } + name: '学生人数', + nameTextStyle: { + color: '#475569', + padding: [0, 0, 10, 0], + fontSize: 13, + fontWeight: 600 + }, + splitLine: { + lineStyle: { + type: 'dashed', + color: '#e2e8f0', + width: 1 + } + }, + axisLabel: { + color: '#64748b', + fontSize: 12, + fontWeight: 500, + padding: [0, 8, 0, 0] + }, + axisLine: { show: false }, + axisTick: { show: false } }, series: [ { name: '学生人数', type: 'bar', - barWidth: '60%', - data: chartData.map(item => ({ + barWidth: '55%', + data: chartData.map((item, index) => ({ value: item.studentCount, itemStyle: { - color: progressColor(item.progressRange), - borderRadius: [4, 4, 0, 0] + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { offset: 0, color: progressColor(item.progressRange) }, + { offset: 1, color: progressColor(item.progressRange) + '99' } + ]), + borderRadius: [8, 8, 0, 0] } })), label: { @@ -1031,24 +1117,39 @@ const initProgressChart = () => { position: 'top', color: '#475569', fontSize: 12, - formatter: (params) => params.value > 0 ? params.value : '' - } + fontWeight: 600, + formatter: (params) => params.value > 0 ? `${params.value}人` : '', + offset: [0, -4] + }, + animationDelay: (idx) => idx * 80, + animationDuration: 1000, + animationEasing: 'cubicOut' } - ] + ], + animationDuration: 1200, + animationEasing: 'cubicOut' } progressChart.setOption(option) } // 监听 Tab 切换,重新渲染图表 -// watch(activeTab, (newVal) => { -// currentPage.value = 1 -// if (newVal === 'progress_dist') { -// nextTick(() => { -// initProgressChart() -// }) -// } -// }) +watch(activeTab, (newVal) => { + currentPage.value = 1 + if (newVal === 'progress_dist') { + nextTick(() => { + initProgressChart() + }) + } else if (newVal === 'accuracy_dist') { + nextTick(() => { + initAccuracyChart() + }) + } else if (newVal === 'online_stats') { + nextTick(() => { + initOnlineTrendChart() + }) + } +}) // 窗口大小变化时重绘图表 const handleResize = () => { @@ -1109,11 +1210,11 @@ const accuracyDistData = ref([ const levelColor = (range) => { const value = parseInt(range) - if (value >= 90) return '#00a870' - if (value >= 80) return '#0052d9' - if (value >= 60) return '#ed7b2f' - if (value >= 40) return '#f5a623' - return '#e34d59' + if (value >= 90) return '#10B981' + if (value >= 80) return '#3B82F6' + if (value >= 60) return '#F59E0B' + if (value >= 40) return '#F97316' + return '#EF4444' } const accuracyDistColumns = [ @@ -1150,18 +1251,34 @@ const initAccuracyChart = () => { const option = { tooltip: { trigger: 'axis', - axisPointer: { type: 'shadow' }, - formatter: '{b}
学生人数: {c}人', - backgroundColor: 'rgba(255, 255, 255, 0.95)', - borderColor: '#e2e8f0', + axisPointer: { + type: 'shadow', + shadowStyle: { + color: 'rgba(30, 64, 175, 0.08)' + } + }, + formatter: (params) => { + const data = params[0] + return `
+
${data.name}
+
+ + 学生人数: + ${data.value}人 +
+
` + }, + backgroundColor: 'rgba(255, 255, 255, 0.98)', + borderColor: 'rgba(30, 64, 175, 0.15)', + borderWidth: 1, textStyle: { color: '#1e293b' }, - extraCssText: 'box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); border-radius: 8px;' + extraCssText: 'box-shadow: 0 10px 25px rgba(0, 0, 0, 0.12), 0 4px 10px rgba(30, 64, 175, 0.08); border-radius: 12px; backdrop-filter: blur(10px);' }, grid: { - top: '10%', - left: '3%', - right: '4%', - bottom: '10%', + top: '8%', + left: '4%', + right: '3%', + bottom: '12%', containLabel: true }, xAxis: { @@ -1169,29 +1286,59 @@ const initAccuracyChart = () => { data: chartData.map(item => item.accuracyRange), axisLabel: { interval: 0, - rotate: 45, + rotate: 30, color: '#64748b', - fontSize: 12 + fontSize: 11, + fontWeight: 500 }, - axisLine: { lineStyle: { color: '#e2e8f0' } } + axisLine: { + lineStyle: { + color: '#cbd5e1', + width: 2 + } + }, + axisTick: { + show: false + } }, yAxis: { type: 'value', - name: '人数', - nameTextStyle: { color: '#64748b', padding: [0, 0, 0, 20] }, - splitLine: { lineStyle: { type: 'dashed', color: '#f1f5f9' } }, - axisLabel: { color: '#64748b' } + name: '学生人数', + nameTextStyle: { + color: '#475569', + padding: [0, 0, 10, 0], + fontSize: 13, + fontWeight: 600 + }, + splitLine: { + lineStyle: { + type: 'dashed', + color: '#e2e8f0', + width: 1 + } + }, + axisLabel: { + color: '#64748b', + fontSize: 12, + fontWeight: 500, + padding: [0, 8, 0, 0] + }, + axisLine: { show: false }, + axisTick: { show: false } }, series: [ { name: '学生人数', type: 'bar', - barWidth: '40%', + barWidth: '45%', data: chartData.map(item => ({ value: item.studentCount, itemStyle: { - color: levelColor(item.accuracyRange), - borderRadius: [4, 4, 0, 0] + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { offset: 0, color: levelColor(item.accuracyRange) }, + { offset: 1, color: levelColor(item.accuracyRange) + '99' } + ]), + borderRadius: [8, 8, 0, 0] } })), label: { @@ -1199,10 +1346,17 @@ const initAccuracyChart = () => { position: 'top', color: '#475569', fontSize: 12, - formatter: (params) => params.value > 0 ? params.value : '' - } + fontWeight: 600, + formatter: (params) => params.value > 0 ? `${params.value}人` : '', + offset: [0, -4] + }, + animationDelay: (idx) => idx * 80, + animationDuration: 1000, + animationEasing: 'cubicOut' } - ] + ], + animationDuration: 1200, + animationEasing: 'cubicOut' } accuracyChart.setOption(option) @@ -1373,22 +1527,58 @@ const initOnlineTrendChart = () => { const option = { tooltip: { trigger: 'axis', - axisPointer: { type: 'cross' }, - backgroundColor: 'rgba(255, 255, 255, 0.95)', - borderColor: '#e2e8f0', + axisPointer: { + type: 'cross', + crossStyle: { + color: '#94A3B8', + width: 1, + type: 'dashed' + }, + lineStyle: { + color: '#94A3B8', + width: 2, + type: 'dashed' + } + }, + formatter: (params) => { + return `
+
${params[0].axisValue}
+
+ + 在线用户数: + ${params[0].value}人 +
+
+ + 答题数量: + ${params[1].value}次 +
+
` + }, + backgroundColor: 'rgba(255, 255, 255, 0.98)', + borderColor: 'rgba(30, 64, 175, 0.15)', + borderWidth: 1, textStyle: { color: '#1e293b' }, - extraCssText: 'box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); border-radius: 8px;' + extraCssText: 'box-shadow: 0 10px 25px rgba(0, 0, 0, 0.12), 0 4px 10px rgba(30, 64, 175, 0.08); border-radius: 12px; backdrop-filter: blur(10px);' }, legend: { data: ['在线用户数', '答题数量'], - top: 0, - textStyle: { color: '#475569' } + top: 8, + itemWidth: 18, + itemHeight: 10, + itemGap: 24, + textStyle: { + color: '#475569', + fontSize: 13, + fontWeight: 500 + }, + icon: 'roundRect' }, grid: { - top: '15%', - left: '3%', + top: '16%', + left: '4%', right: '4%', - bottom: '10%', + bottom: '12%', containLabel: true }, xAxis: { @@ -1397,60 +1587,132 @@ const initOnlineTrendChart = () => { data: trendData.time, axisLabel: { color: '#64748b', - fontSize: 12, - interval: onlineTimeDimension.value === 'minute' ? 5 : 0, // 10分钟维度下每隔5个点(即1小时)显示一个标签 - rotate: onlineTimeDimension.value === 'day' ? 45 : 0 // 按天显示时倾斜标签以防重叠 + fontSize: 11, + fontWeight: 500, + interval: onlineTimeDimension.value === 'minute' ? 5 : 0, + rotate: onlineTimeDimension.value === 'day' ? 30 : 0, + padding: [8, 0, 0, 0] }, - axisLine: { lineStyle: { color: '#e2e8f0' } } + axisLine: { + lineStyle: { + color: '#cbd5e1', + width: 2 + } + }, + axisTick: { show: false } }, yAxis: [ { type: 'value', name: '在线用户数', position: 'left', - splitLine: { lineStyle: { type: 'dashed', color: '#f1f5f9' } }, - axisLabel: { color: '#64748b' }, - nameTextStyle: { color: '#64748b', padding: [0, 0, 0, 20] } + splitLine: { + lineStyle: { + type: 'dashed', + color: '#e2e8f0', + width: 1 + } + }, + axisLabel: { + color: '#64748b', + fontSize: 12, + fontWeight: 500, + padding: [0, 8, 0, 0] + }, + nameTextStyle: { + color: '#475569', + padding: [0, 0, 10, 0], + fontSize: 13, + fontWeight: 600 + }, + axisLine: { show: false }, + axisTick: { show: false } }, { type: 'value', name: '答题数量', position: 'right', splitLine: { show: false }, - axisLabel: { color: '#64748b' }, - nameTextStyle: { color: '#64748b', padding: [0, 20, 0, 0] } + axisLabel: { + color: '#64748b', + fontSize: 12, + fontWeight: 500, + padding: [0, 0, 0, 8] + }, + nameTextStyle: { + color: '#475569', + padding: [0, 20, 10, 0], + fontSize: 13, + fontWeight: 600 + }, + axisLine: { show: false }, + axisTick: { show: false } } ], series: [ { name: '在线用户数', type: 'line', - smooth: true, + smooth: 0.4, yAxisIndex: 0, - itemStyle: { color: '#0052d9' }, + symbol: 'circle', + symbolSize: 6, + itemStyle: { + color: '#1E40AF', + borderWidth: 2, + borderColor: '#fff' + }, + lineStyle: { + width: 3, + shadowColor: 'rgba(30, 64, 175, 0.3)', + shadowBlur: 10, + shadowOffsetY: 4 + }, areaStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ - { offset: 0, color: 'rgba(0, 82, 217, 0.3)' }, - { offset: 1, color: 'rgba(0, 82, 217, 0.05)' } + { offset: 0, color: 'rgba(30, 64, 175, 0.35)' }, + { offset: 0.5, color: 'rgba(30, 64, 175, 0.15)' }, + { offset: 1, color: 'rgba(30, 64, 175, 0.02)' } ]) }, - data: trendData.onlineCount + data: trendData.onlineCount, + animationDelay: 0, + animationDuration: 1200, + animationEasing: 'cubicOut' }, { name: '答题数量', type: 'line', - smooth: true, + smooth: 0.4, yAxisIndex: 1, - itemStyle: { color: '#00a870' }, + symbol: 'circle', + symbolSize: 6, + itemStyle: { + color: '#10B981', + borderWidth: 2, + borderColor: '#fff' + }, + lineStyle: { + width: 3, + shadowColor: 'rgba(16, 185, 129, 0.3)', + shadowBlur: 10, + shadowOffsetY: 4 + }, areaStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ - { offset: 0, color: 'rgba(0, 168, 112, 0.3)' }, - { offset: 1, color: 'rgba(0, 168, 112, 0.05)' } + { offset: 0, color: 'rgba(16, 185, 129, 0.35)' }, + { offset: 0.5, color: 'rgba(16, 185, 129, 0.15)' }, + { offset: 1, color: 'rgba(16, 185, 129, 0.02)' } ]) }, - data: trendData.answerCount + data: trendData.answerCount, + animationDelay: 200, + animationDuration: 1200, + animationEasing: 'cubicOut' } - ] + ], + animationDuration: 1400, + animationEasing: 'cubicOut' } onlineTrendChart.setOption(option) @@ -1465,6 +1727,24 @@ import * as XLSX from 'xlsx' const exportDialogVisible = ref(false) const exportType = ref('') +const exporting = ref(false) + +const exportDialogTitle = computed(() => { + switch (exportType.value) { + case 'overview': + return '总体学情统计' + case 'accuracy_rank': + return '答题正确率排名' + case 'progress_dist': + return '答题进度分布' + case 'accuracy_dist': + return '正确率分布' + case 'online_stats': + return '学生在线统计' + default: + return '数据' + } +}) const exportFilters = ref({ cloud: '', @@ -1506,97 +1786,94 @@ const handleExport = (type) => { exportDialogVisible.value = true } -const confirmExport = () => { - exportDialogVisible.value = false +const confirmExport = async () => { + exporting.value = true - let dataToExport = [] - let columns = [] - let fileName = '' + try { + let dataToExport = [] + let columns = [] + let fileName = '' - // 在实际应用中,这里应该根据 exportFilters 的值重新请求数据 - // 由于当前是静态数据展示,我们直接使用现有的数据作为导出数据 + switch (exportType.value) { + case 'overview': + dataToExport = overviewData.value + columns = overviewColumns + fileName = '总体学情统计分析表' + break + case 'accuracy_rank': + dataToExport = accuracyRankData.value + columns = accuracyRankColumns + fileName = '学生答题正确率排名表' + break + case 'progress_dist': + dataToExport = progressDistData.value + columns = progressDistColumns + fileName = '学生答题进度分布表' + break + case 'accuracy_dist': + dataToExport = accuracyDistData.value + columns = accuracyDistColumns + fileName = '学生答题正确率分布表' + break + case 'online_stats': + const originalOnlineSingleDate = onlineSingleDate.value + const originalOnlineDateRange = onlineDateRange.value + + if (onlineTimeDimension.value === 'minute' || onlineTimeDimension.value === 'hour') { + onlineSingleDate.value = exportFilters.value.singleDate + } else { + onlineDateRange.value = exportFilters.value.dateRange + } + + const trendData = generateTrendData(onlineTimeDimension.value) + + onlineSingleDate.value = originalOnlineSingleDate + onlineDateRange.value = originalOnlineDateRange + + dataToExport = trendData.time.map((t, index) => ({ + time: t, + onlineCount: trendData.onlineCount[index], + answerCount: trendData.answerCount[index] + })) + columns = [ + { colKey: 'time', title: '时间' }, + { colKey: 'onlineCount', title: '在线用户数' }, + { colKey: 'answerCount', title: '答题数量' } + ] + fileName = '学生在线与答题趋势' + break + } - switch (exportType.value) { - case 'overview': - dataToExport = overviewData.value - columns = overviewColumns - fileName = '总体学情统计分析表' - break - case 'accuracy_rank': - dataToExport = accuracyRankData.value - columns = accuracyRankColumns - fileName = '学生答题正确率排名表' - break - case 'progress_dist': - dataToExport = progressDistData.value - columns = progressDistColumns - fileName = '学生答题进度分布表' - break - case 'accuracy_dist': - dataToExport = accuracyDistData.value - columns = accuracyDistColumns - fileName = '学生答题正确率分布表' - break - case 'online_stats': - // 模拟根据导出对话框中的筛选条件生成数据 - // 实际应用中应该传递 exportFilters 重新请求数据 - // 这里为了保持演示,临时修改一下组件内的状态来生成对应数据,生成后再恢复 - const originalOnlineSingleDate = onlineSingleDate.value - const originalOnlineDateRange = onlineDateRange.value - - if (onlineTimeDimension.value === 'minute' || onlineTimeDimension.value === 'hour') { - onlineSingleDate.value = exportFilters.value.singleDate - } else { - onlineDateRange.value = exportFilters.value.dateRange - } - - const trendData = generateTrendData(onlineTimeDimension.value) - - // 恢复原始状态 - onlineSingleDate.value = originalOnlineSingleDate - onlineDateRange.value = originalOnlineDateRange - - dataToExport = trendData.time.map((t, index) => ({ - time: t, - onlineCount: trendData.onlineCount[index], - answerCount: trendData.answerCount[index] - })) - columns = [ - { colKey: 'time', title: '时间' }, - { colKey: 'onlineCount', title: '在线用户数' }, - { colKey: 'answerCount', title: '答题数量' } - ] - fileName = '学生在线与答题趋势' - break - } - - // 提取表头 - const headers = columns.map(col => col.title) - // 提取数据 - const rows = dataToExport.map(row => { - return columns.map(col => { - return row[col.colKey] + const headers = columns.map(col => col.title) + const rows = dataToExport.map(row => { + return columns.map(col => { + return row[col.colKey] + }) }) - }) - // 组装工作表数据 - const worksheetData = [headers, ...rows] - const worksheet = XLSX.utils.aoa_to_sheet(worksheetData) - const workbook = XLSX.utils.book_new() - XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1') + const worksheetData = [headers, ...rows] + const worksheet = XLSX.utils.aoa_to_sheet(worksheetData) + const workbook = XLSX.utils.book_new() + XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1') - // 获取筛选条件的标签用于生成文件名 - const expCloudLabel = cloudList.find(item => item.value === exportFilters.value.cloud)?.label || '' - const expSchoolLabel = schoolList.find(item => item.value === exportFilters.value.school)?.label || '' - const expGradeLabel = gradeList.find(item => item.value === exportFilters.value.grade)?.label || '' - const expClassLabel = classList.find(item => item.value === exportFilters.value.class)?.label || '' + const expCloudLabel = cloudList.find(item => item.value === exportFilters.value.cloud)?.label || '' + const expSchoolLabel = schoolList.find(item => item.value === exportFilters.value.school)?.label || '' + const expGradeLabel = gradeList.find(item => item.value === exportFilters.value.grade)?.label || '' + const expClassLabel = classList.find(item => item.value === exportFilters.value.class)?.label || '' - // 构造文件名,包含筛选条件 - const filterStr = [expCloudLabel, expSchoolLabel, expGradeLabel, expClassLabel].filter(Boolean).join('_') - const finalFileName = `${fileName}${filterStr ? '_' + filterStr : ''}.xlsx` + const filterStr = [expCloudLabel, expSchoolLabel, expGradeLabel, expClassLabel].filter(Boolean).join('_') + const finalFileName = `${fileName}${filterStr ? '_' + filterStr : ''}.xlsx` - XLSX.writeFile(workbook, finalFileName) - MessagePlugin.success('导出成功') + await new Promise(resolve => setTimeout(resolve, 500)) + + XLSX.writeFile(workbook, finalFileName) + exportDialogVisible.value = false + MessagePlugin.success('导出成功') + } catch (error) { + MessagePlugin.error('导出失败,请重试') + } finally { + exporting.value = false + } } // 级联重置:切换学校时清空年级和班级 @@ -1860,111 +2137,147 @@ watch(activeTab, (newVal) => {