feat(路由): 添加在线学习监控和学习总览页面路由
feat(首页): 新增学习情况总览表入口和在线学习监控卡片 refactor(班级分配): 重构班级分配页面,增加学习官查询功能 chore: 删除无用的Git清理脚本和文档文件 style: 调整样式和图标,优化用户体验
This commit is contained in:
parent
37d15fdda9
commit
b67fc03c01
|
|
@ -2,9 +2,9 @@
|
|||
"projectName": "aixuediebian-kanban",
|
||||
"projectId": "pages-eip23arpzoar",
|
||||
"deployUrl": "https://aixuediebian-kanban-3fzkam9s.edgeone.cool",
|
||||
"previewUrl": "https://aixuediebian-kanban-3fzkam9s.edgeone.cool?eo_token=e87d722adbc6936c1429a1f77bba0d71&eo_time=1776139462",
|
||||
"previewUrl": "https://aixuediebian-kanban-3fzkam9s.edgeone.cool?eo_token=500f76b21d03fd0693ba2f52f2757888&eo_time=1776404056",
|
||||
"consoleUrl": "https://console.cloud.tencent.com/edgeone/pages/project/pages-eip23arpzoar/index",
|
||||
"deploymentUrl": "https://console.cloud.tencent.com/edgeone/pages/project/pages-eip23arpzoar/deployment/gnpthgy3s1",
|
||||
"deployId": "gnpthgy3s1",
|
||||
"lastDeployTime": 1776139462
|
||||
}
|
||||
"deploymentUrl": "https://console.cloud.tencent.com/edgeone/pages/project/pages-eip23arpzoar/deployment/4dxuaw1yre",
|
||||
"deployId": "4dxuaw1yre",
|
||||
"lastDeployTime": 1776404056
|
||||
}
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
# Git清理命令指南
|
||||
|
||||
## 📋 查看和验证命令
|
||||
|
||||
### 1. 查看当前状态
|
||||
```bash
|
||||
git status --short
|
||||
```
|
||||
|
||||
### 2. 预览将被.gitignore忽略的文件(仅删除临时文件)
|
||||
```bash
|
||||
git clean -d -X -n
|
||||
```
|
||||
|
||||
### 3. 预览所有未追踪的文件(包含重要文件)
|
||||
```bash
|
||||
git clean -d -n
|
||||
```
|
||||
|
||||
### 4. 查看未追踪的文件(排除.gitignore)
|
||||
```bash
|
||||
git ls-files --others --exclude-standard
|
||||
```
|
||||
|
||||
### 5. 查看已被.gitignore排除的文件
|
||||
```bash
|
||||
git ls-files --others --ignored --exclude-standard
|
||||
```
|
||||
|
||||
## 🧹 清理命令
|
||||
|
||||
### ✅ 推荐:仅删除临时文件(安全)
|
||||
```bash
|
||||
# 预览
|
||||
git clean -d -X -n
|
||||
|
||||
# 确认无误后执行
|
||||
git clean -d -X -f
|
||||
```
|
||||
|
||||
### ⚠️ 仅删除未被忽略的未追踪文件(危险!)
|
||||
```bash
|
||||
# 这会删除你的源代码!仅用于确认
|
||||
git clean -d -n
|
||||
|
||||
# 不要执行这个,除非你确定
|
||||
# git clean -d -f
|
||||
```
|
||||
|
||||
### ❌ 危险:删除所有未追踪文件
|
||||
```bash
|
||||
# 这会删除所有文件(包括源代码和临时文件)
|
||||
git clean -d -x -f
|
||||
```
|
||||
|
||||
## 🎯 推荐工作流程
|
||||
|
||||
### 步骤1:添加重要文件到Git
|
||||
```bash
|
||||
# 添加所有重要文件(排除.gitignore中的文件)
|
||||
git add .
|
||||
|
||||
# 查看已暂存的文件
|
||||
git status
|
||||
```
|
||||
|
||||
### 步骤2:首次提交
|
||||
```bash
|
||||
git commit -m "Initial commit"
|
||||
```
|
||||
|
||||
### 步骤3:清理临时文件
|
||||
```bash
|
||||
# 删除临时文件(node_modules, dist等)
|
||||
git clean -d -X -f
|
||||
```
|
||||
|
||||
### 步骤4:验证状态
|
||||
```bash
|
||||
git status
|
||||
```
|
||||
|
||||
## 📝 参数说明
|
||||
|
||||
- `-d`: 删除未追踪的目录
|
||||
- `-f`: 强制删除
|
||||
- `-n`: 预览模式,不实际删除(dry run)
|
||||
- `-X`: 仅删除被.gitignore忽略的文件
|
||||
- `-x`: 删除所有未追踪文件(忽略.gitignore)
|
||||
|
||||
## ⚠️ 重要提示
|
||||
|
||||
1. **始终先使用 `-n` 参数预览**
|
||||
2. **不要运行 `git clean -d -f`**,除非你确定要删除源代码
|
||||
3. **推荐使用 `git clean -d -X -f`** 清理临时文件
|
||||
4. **建议先提交重要文件**,再清理临时文件
|
||||
|
||||
## 🔄 恢复误删
|
||||
|
||||
如果误删了文件,可以使用以下方法恢复:
|
||||
```bash
|
||||
# 如果文件已提交过
|
||||
git checkout HEAD -- <文件路径>
|
||||
|
||||
# 如果文件未提交,但还在工作区
|
||||
# 可以使用系统回收站或备份恢复
|
||||
```
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
@echo off
|
||||
chcp 65001 >nul
|
||||
echo ==========================================
|
||||
echo Git临时文件清理脚本
|
||||
echo ==========================================
|
||||
echo.
|
||||
|
||||
echo [1/4] 检查Git状态...
|
||||
git status --short
|
||||
echo.
|
||||
|
||||
echo [2/4] 预览将被删除的临时文件...
|
||||
echo ==========================================
|
||||
git clean -d -X -n
|
||||
echo ==========================================
|
||||
echo.
|
||||
|
||||
set /p confirm="确认删除这些临时文件吗?(Y/N): "
|
||||
if /i "%confirm%" neq "Y" (
|
||||
echo 已取消操作
|
||||
pause
|
||||
exit /b 0
|
||||
)
|
||||
|
||||
echo.
|
||||
echo [3/4] 正在删除临时文件...
|
||||
git clean -d -X -f
|
||||
echo.
|
||||
|
||||
echo [4/4] 验证清理结果...
|
||||
echo ==========================================
|
||||
echo 当前未追踪的文件(应为核心源代码):
|
||||
echo ==========================================
|
||||
git ls-files --others --exclude-standard
|
||||
echo.
|
||||
|
||||
echo ==========================================
|
||||
echo 清理完成!
|
||||
echo ==========================================
|
||||
echo.
|
||||
echo 提示:使用 'git add .' 添加源代码到Git
|
||||
pause
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
# Git清理脚本 - PowerShell版本
|
||||
# 用于排除未提交且不重要的文件
|
||||
|
||||
Write-Host "==========================================" -ForegroundColor Cyan
|
||||
Write-Host " Git清理脚本 - 安全模式" -ForegroundColor Cyan
|
||||
Write-Host "==========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# 1. 验证当前状态
|
||||
Write-Host "📋 当前Git状态:" -ForegroundColor Green
|
||||
Write-Host "----------------------------------------" -ForegroundColor DarkGray
|
||||
git status --short
|
||||
Write-Host ""
|
||||
|
||||
# 2. 预览将被删除的未追踪文件(不遵循.gitignore)
|
||||
Write-Host "🔍 预览将删除的未追踪文件(不遵循.gitignore):" -ForegroundColor Yellow
|
||||
Write-Host "----------------------------------------" -ForegroundColor DarkGray
|
||||
git clean -d -n
|
||||
Write-Host ""
|
||||
|
||||
# 3. 预览将被删除的未追踪文件(遵循.gitignore)
|
||||
Write-Host "🔍 预览将被删除的未追踪文件(遵循.gitignore):" -ForegroundColor Yellow
|
||||
Write-Host "----------------------------------------" -ForegroundColor DarkGray
|
||||
git clean -d -X -n
|
||||
Write-Host ""
|
||||
|
||||
# 4. 预览将被删除的所有未追踪文件
|
||||
Write-Host "🔍 预览将被删除的所有未追踪文件:" -ForegroundColor Yellow
|
||||
Write-Host "----------------------------------------" -ForegroundColor DarkGray
|
||||
git clean -d -x -n
|
||||
Write-Host ""
|
||||
|
||||
Write-Host "==========================================" -ForegroundColor Cyan
|
||||
Write-Host " 清理选项:" -ForegroundColor Cyan
|
||||
Write-Host "==========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "选项1: 删除未被.gitignore忽略的文件(安全)" -ForegroundColor White
|
||||
Write-Host " git clean -d -f" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host "选项2: 仅删除被.gitignore忽略的文件(清理临时文件)" -ForegroundColor White
|
||||
Write-Host " git clean -d -X -f" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host "选项3: 删除所有未追踪文件(危险)" -ForegroundColor White
|
||||
Write-Host " git clean -d -x -f" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host "建议:先运行预览命令(-n参数),确认无误后再执行实际删除" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
|
||||
# 5. 验证哪些文件已被排除
|
||||
Write-Host "==========================================" -ForegroundColor Cyan
|
||||
Write-Host " 验证排除状态:" -ForegroundColor Cyan
|
||||
Write-Host "==========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "查看所有未追踪的文件:" -ForegroundColor Green
|
||||
git ls-files --others --exclude-standard
|
||||
Write-Host ""
|
||||
|
||||
Write-Host "查看已被.gitignore排除的文件:" -ForegroundColor Green
|
||||
git ls-files --others --ignored --exclude-standard
|
||||
Write-Host ""
|
||||
47
git-clean.sh
47
git-clean.sh
|
|
@ -1,47 +0,0 @@
|
|||
#!/bin/bash
|
||||
# Git清理脚本 - 用于排除未提交且不重要的文件
|
||||
|
||||
echo "=========================================="
|
||||
echo " Git清理脚本 - 安全模式"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# 1. 验证当前状态
|
||||
echo "📋 当前Git状态:"
|
||||
echo "----------------------------------------"
|
||||
git status --short
|
||||
echo ""
|
||||
|
||||
# 2. 预览将被删除的未追踪文件(不遵循.gitignore)
|
||||
echo "🔍 预览将删除的未追踪文件(不遵循.gitignore):"
|
||||
echo "----------------------------------------"
|
||||
git clean -d -n
|
||||
echo ""
|
||||
|
||||
# 3. 预览将被删除的未追踪文件(遵循.gitignore)
|
||||
echo "🔍 预览将被删除的未追踪文件(遵循.gitignore):"
|
||||
echo "----------------------------------------"
|
||||
git clean -d -X -n
|
||||
echo ""
|
||||
|
||||
# 4. 预览将被删除的所有未追踪文件
|
||||
echo "🔍 预览将被删除的所有未追踪文件:"
|
||||
echo "----------------------------------------"
|
||||
git clean -d -x -n
|
||||
echo ""
|
||||
|
||||
echo "=========================================="
|
||||
echo " 清理选项:"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "选项1: 删除未被.gitignore忽略的文件(安全)"
|
||||
echo " git clean -d -f"
|
||||
echo ""
|
||||
echo "选项2: 仅删除被.gitignore忽略的文件(清理临时文件)"
|
||||
echo " git clean -d -X -f"
|
||||
echo ""
|
||||
echo "选项3: 删除所有未追踪文件(危险)"
|
||||
echo " git clean -d -x -f"
|
||||
echo ""
|
||||
echo "建议:先运行预览命令(-n参数),确认无误后再执行实际删除"
|
||||
echo ""
|
||||
|
|
@ -2,91 +2,242 @@
|
|||
<div class="class-allocation-page">
|
||||
<!-- 左侧区域 -->
|
||||
<div class="left-panel">
|
||||
<!-- 学校选择下拉框 -->
|
||||
<div class="school-select-wrapper">
|
||||
<a-select
|
||||
v-model:value="selectedSchool"
|
||||
placeholder="请选择学校"
|
||||
show-search
|
||||
:filter-option="filterSchoolOption"
|
||||
@change="handleSchoolChange"
|
||||
class="school-select"
|
||||
>
|
||||
<a-select-option v-for="school in schoolList" :key="school.id" :value="school.id">
|
||||
{{ school.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
|
||||
<!-- 树形结构控件 -->
|
||||
<div class="tree-wrapper">
|
||||
<a-tree
|
||||
:tree-data="treeData"
|
||||
:expanded-keys="expandedKeys"
|
||||
:selected-keys="selectedKeys"
|
||||
@expand="handleExpand"
|
||||
@select="handleSelect"
|
||||
class="class-tree"
|
||||
>
|
||||
<template #title="{ data }">
|
||||
<div
|
||||
class="tree-node"
|
||||
:class="{
|
||||
'has-officer': data.hasOfficer,
|
||||
'no-officer': data.isClass && !data.hasOfficer,
|
||||
'selected': selectedKeys.includes(data.key)
|
||||
}"
|
||||
>
|
||||
<!-- 学校节点图标 -->
|
||||
<template v-if="data.type === 'school'">
|
||||
<span class="node-icon school-icon">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M12 3L1 9L12 15L21 10.09V17H23V9L12 3Z" fill="#1890ff"/>
|
||||
<path d="M5 13.18V17.18L12 21L19 17.18V13.18L12 17L5 13.18Z" fill="#1890ff"/>
|
||||
</svg>
|
||||
</span>
|
||||
</template>
|
||||
<!-- 年级节点图标 -->
|
||||
<template v-else-if="data.type === 'grade'">
|
||||
<span class="node-icon grade-icon">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M12 3L1 9L12 15L23 9L12 3Z" stroke="#52c41a" stroke-width="2" stroke-linejoin="round"/>
|
||||
<path d="M5 13.18V17.18L12 21L19 17.18V13.18" stroke="#52c41a" stroke-width="2" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</span>
|
||||
</template>
|
||||
<!-- 班级节点图标 -->
|
||||
<template v-else-if="data.type === 'class'">
|
||||
<span class="node-icon class-icon" :class="{ assigned: data.hasOfficer, unassigned: !data.hasOfficer }">
|
||||
<svg v-if="data.hasOfficer" width="16" height="16" viewBox="0 0 24 24" fill="none">
|
||||
<rect x="3" y="4" width="18" height="16" rx="2" stroke="#52c41a" stroke-width="2"/>
|
||||
<path d="M8 10H16M8 14H12" stroke="#52c41a" stroke-width="2" stroke-linecap="round"/>
|
||||
<circle cx="17" cy="14" r="2" fill="#52c41a"/>
|
||||
</svg>
|
||||
<svg v-else width="16" height="16" viewBox="0 0 24 24" fill="none">
|
||||
<rect x="3" y="4" width="18" height="16" rx="2" stroke="#ff4d4f" stroke-width="2"/>
|
||||
<path d="M8 10H16M8 14H12" stroke="#ff4d4f" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<span class="node-title">{{ data.title }}</span>
|
||||
|
||||
<!-- 年级节点显示待分配班级数量 -->
|
||||
<span v-if="data.type === 'grade' && data.unassignedCount > 0" class="unassigned-count-badge">
|
||||
{{ data.unassignedCount }}个班待分配
|
||||
</span>
|
||||
|
||||
<!-- 班级节点显示学习官信息 -->
|
||||
<span v-if="data.type === 'class' && data.hasOfficer" class="officer-tag">
|
||||
{{ data.officerName }}
|
||||
</span>
|
||||
<span v-else-if="data.type === 'class' && !data.hasOfficer" class="officer-tag unassigned-tag">
|
||||
待分配
|
||||
</span>
|
||||
<!-- 页签切换 -->
|
||||
<div class="tabs-wrapper">
|
||||
<a-tabs v-model:activeKey="activeTabKey" type="card" class="custom-tabs">
|
||||
<a-tab-pane key="school" tab="学校班级">
|
||||
<!-- 学校选择下拉框 -->
|
||||
<div class="school-select-wrapper">
|
||||
<a-select
|
||||
v-model:value="selectedSchool"
|
||||
placeholder="请选择学校"
|
||||
show-search
|
||||
:filter-option="filterSchoolOption"
|
||||
@change="handleSchoolChange"
|
||||
class="school-select"
|
||||
>
|
||||
<a-select-option v-for="school in schoolList" :key="school.id" :value="school.id">
|
||||
{{ school.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
</template>
|
||||
</a-tree>
|
||||
|
||||
<!-- 树形结构控件 -->
|
||||
<div class="tree-wrapper">
|
||||
<!-- 未选择学校时的提示 -->
|
||||
<div v-if="!selectedSchool" class="tree-placeholder">
|
||||
<div class="placeholder-content">
|
||||
<div class="placeholder-icon">
|
||||
<svg width="64" height="64" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M12 3L1 9L12 15L21 10.09V17H23V9L12 3Z" stroke="#d9d9d9" stroke-width="1.5" stroke-linejoin="round"/>
|
||||
<path d="M5 13.18V17.18L12 21L19 17.18V13.18" stroke="#d9d9d9" stroke-width="1.5" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<p class="placeholder-title">请先选择学校</p>
|
||||
<p class="placeholder-desc">选择学校后将加载相关班级数据</p>
|
||||
<div class="placeholder-arrow">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M12 5v14M5 12l7 7 7-7" stroke="#1890ff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-tree
|
||||
v-else
|
||||
:tree-data="treeData"
|
||||
:expanded-keys="expandedKeys"
|
||||
:selected-keys="selectedKeys"
|
||||
@expand="handleExpand"
|
||||
@select="handleSelect"
|
||||
class="class-tree"
|
||||
>
|
||||
<template #title="{ data }">
|
||||
<div
|
||||
class="tree-node"
|
||||
:class="{
|
||||
'has-officer': data.hasOfficer,
|
||||
'no-officer': data.isClass && !data.hasOfficer,
|
||||
'selected': selectedKeys.includes(data.key)
|
||||
}"
|
||||
>
|
||||
<!-- 学校节点图标 -->
|
||||
<template v-if="data.type === 'school'">
|
||||
<span class="node-icon school-icon">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M12 3L1 9L12 15L21 10.09V17H23V9L12 3Z" fill="#1890ff"/>
|
||||
<path d="M5 13.18V17.18L12 21L19 17.18V13.18L12 17L5 13.18Z" fill="#1890ff"/>
|
||||
</svg>
|
||||
</span>
|
||||
</template>
|
||||
<!-- 年级节点图标 -->
|
||||
<template v-else-if="data.type === 'grade'">
|
||||
<span class="node-icon grade-icon">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M12 3L1 9L12 15L23 9L12 3Z" stroke="#52c41a" stroke-width="2" stroke-linejoin="round"/>
|
||||
<path d="M5 13.18V17.18L12 21L19 17.18V13.18" stroke="#52c41a" stroke-width="2" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</span>
|
||||
</template>
|
||||
<!-- 班级节点图标 -->
|
||||
<template v-else-if="data.type === 'class'">
|
||||
<span class="node-icon class-icon" :class="{ assigned: data.hasOfficer, unassigned: !data.hasOfficer }">
|
||||
<svg v-if="data.hasOfficer" width="16" height="16" viewBox="0 0 24 24" fill="none">
|
||||
<rect x="3" y="4" width="18" height="16" rx="2" stroke="#52c41a" stroke-width="2"/>
|
||||
<path d="M8 10H16M8 14H12" stroke="#52c41a" stroke-width="2" stroke-linecap="round"/>
|
||||
<circle cx="17" cy="14" r="2" fill="#52c41a"/>
|
||||
</svg>
|
||||
<svg v-else width="16" height="16" viewBox="0 0 24 24" fill="none">
|
||||
<rect x="3" y="4" width="18" height="16" rx="2" stroke="#ff4d4f" stroke-width="2"/>
|
||||
<path d="M8 10H16M8 14H12" stroke="#ff4d4f" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<span class="node-title">{{ data.title }}</span>
|
||||
|
||||
<!-- 年级节点显示待分配班级数量 -->
|
||||
<span v-if="data.type === 'grade' && data.unassignedCount > 0" class="unassigned-count-badge">
|
||||
{{ data.unassignedCount }}个班待分配
|
||||
</span>
|
||||
|
||||
<!-- 班级节点显示学习官信息 -->
|
||||
<span v-if="data.type === 'class' && data.hasOfficer" class="officer-tag">
|
||||
{{ data.officerName }}
|
||||
</span>
|
||||
<span v-else-if="data.type === 'class' && !data.hasOfficer" class="officer-tag unassigned-tag">
|
||||
待分配
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</a-tree>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="officer" tab="学习官查询">
|
||||
<!-- 学习官查询区域 -->
|
||||
<div class="officer-query-section">
|
||||
<!-- 搜索输入框 -->
|
||||
<div class="query-input-wrapper">
|
||||
<a-input-search
|
||||
v-model:value="officerQuery.searchText"
|
||||
placeholder="请输入学习官姓名或手机号"
|
||||
allow-clear
|
||||
:loading="officerQuery.loading"
|
||||
@change="handleOfficerQueryChange"
|
||||
@search="handleOfficerQuerySearch"
|
||||
class="officer-query-input"
|
||||
>
|
||||
<template #prefix>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
|
||||
<circle cx="11" cy="11" r="8" stroke="#999" stroke-width="2"/>
|
||||
<path d="M21 21l-4.35-4.35" stroke="#999" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</template>
|
||||
</a-input-search>
|
||||
</div>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="officerQuery.loading" class="query-loading">
|
||||
<a-spin size="large" tip="查询中..." />
|
||||
</div>
|
||||
|
||||
<!-- 无结果提示 -->
|
||||
<div v-else-if="officerQuery.searched && !officerQuery.hasResults && officerQuery.searchText" class="query-empty">
|
||||
<a-empty description="未找到匹配的学习官">
|
||||
<template #image>
|
||||
<svg width="64" height="64" viewBox="0 0 24 24" fill="none">
|
||||
<circle cx="11" cy="11" r="8" stroke="#d9d9d9" stroke-width="2"/>
|
||||
<path d="M21 21l-4.35-4.35" stroke="#d9d9d9" stroke-width="2" stroke-linecap="round"/>
|
||||
<path d="M8 11h6" stroke="#d9d9d9" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</template>
|
||||
</a-empty>
|
||||
<p class="empty-hint">请检查输入的姓名或手机号是否正确</p>
|
||||
</div>
|
||||
|
||||
<!-- 查询结果树 -->
|
||||
<div v-else-if="officerQuery.hasResults" class="query-result-wrapper">
|
||||
<div class="result-header">
|
||||
<span class="result-count">找到 <strong>{{ officerQuery.resultCount }}</strong> 位学习官</span>
|
||||
</div>
|
||||
<a-tree
|
||||
:tree-data="officerQuery.treeData"
|
||||
:expanded-keys="officerQuery.expandedKeys"
|
||||
:selected-keys="officerQuery.selectedKeys"
|
||||
@expand="handleOfficerQueryExpand"
|
||||
@select="handleOfficerQuerySelect"
|
||||
class="officer-query-tree"
|
||||
>
|
||||
<template #title="{ data }">
|
||||
<div
|
||||
class="query-tree-node"
|
||||
:class="{
|
||||
'officer-node': data.type === 'officer',
|
||||
'school-node': data.type === 'school',
|
||||
'grade-node': data.type === 'grade',
|
||||
'class-node': data.type === 'class'
|
||||
}"
|
||||
>
|
||||
<!-- 学习官节点 -->
|
||||
<template v-if="data.type === 'officer'">
|
||||
<span class="node-icon officer-icon">
|
||||
<div class="officer-avatar-small" :style="{ background: getAvatarColor(data.name) }">
|
||||
{{ data.name.charAt(0) }}
|
||||
</div>
|
||||
</span>
|
||||
<span class="node-title officer-name">{{ data.title }}</span>
|
||||
<span class="officer-phone-tag">{{ data.phone }}</span>
|
||||
</template>
|
||||
|
||||
<!-- 学校节点 -->
|
||||
<template v-else-if="data.type === 'school'">
|
||||
<span class="node-icon school-icon">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M12 3L1 9L12 15L21 10.09V17H23V9L12 3Z" fill="#1890ff"/>
|
||||
<path d="M5 13.18V17.18L12 21L19 17.18V13.18L12 17L5 13.18Z" fill="#1890ff"/>
|
||||
</svg>
|
||||
</span>
|
||||
<span class="node-title">{{ data.title }}</span>
|
||||
</template>
|
||||
|
||||
<!-- 年级节点 -->
|
||||
<template v-else-if="data.type === 'grade'">
|
||||
<span class="node-icon grade-icon">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M12 3L1 9L12 15L23 9L12 3Z" stroke="#52c41a" stroke-width="2" stroke-linejoin="round"/>
|
||||
<path d="M5 13.18V17.18L12 21L19 17.18V13.18" stroke="#52c41a" stroke-width="2" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</span>
|
||||
<span class="node-title">{{ data.title }}</span>
|
||||
</template>
|
||||
|
||||
<!-- 班级节点 -->
|
||||
<template v-else-if="data.type === 'class'">
|
||||
<span class="node-icon class-icon">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
|
||||
<rect x="3" y="4" width="18" height="16" rx="2" stroke="#52c41a" stroke-width="2"/>
|
||||
<path d="M8 10H16M8 14H12" stroke="#52c41a" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</span>
|
||||
<span class="node-title">{{ data.title }}</span>
|
||||
<span class="class-student-count">{{ data.studentCount }}人</span>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</a-tree>
|
||||
</div>
|
||||
|
||||
<!-- 初始提示 -->
|
||||
<div v-else class="query-hint">
|
||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none">
|
||||
<circle cx="11" cy="11" r="8" stroke="#d9d9d9" stroke-width="2"/>
|
||||
<path d="M21 21l-4.35-4.35" stroke="#d9d9d9" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
<p>请输入学习官姓名或手机号进行查询</p>
|
||||
</div>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -283,58 +434,7 @@
|
|||
class="filter-input"
|
||||
/>
|
||||
</div>
|
||||
<div class="filter-item">
|
||||
<span class="filter-label">状态</span>
|
||||
<a-select
|
||||
v-model:value="officerFilter.status"
|
||||
placeholder="全部状态"
|
||||
allowClear
|
||||
class="filter-select-modal"
|
||||
>
|
||||
<a-select-option value="available">空闲</a-select-option>
|
||||
<a-select-option value="busy">忙碌</a-select-option>
|
||||
<a-select-option value="leave">请假</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
<div class="filter-item">
|
||||
<span class="filter-label">区域</span>
|
||||
<a-select
|
||||
v-model:value="officerFilter.area"
|
||||
placeholder="全部区域"
|
||||
allowClear
|
||||
class="filter-select-modal"
|
||||
>
|
||||
<a-select-option value="east">东区</a-select-option>
|
||||
<a-select-option value="west">西区</a-select-option>
|
||||
<a-select-option value="south">南区</a-select-option>
|
||||
<a-select-option value="north">北区</a-select-option>
|
||||
<a-select-option value="central">中心区</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter-row">
|
||||
<div class="filter-item">
|
||||
<span class="filter-label">管理班级数</span>
|
||||
<a-select
|
||||
v-model:value="officerFilter.classCount"
|
||||
placeholder="不限"
|
||||
allowClear
|
||||
class="filter-select-modal"
|
||||
>
|
||||
<a-select-option value="0">无管理班级</a-select-option>
|
||||
<a-select-option value="1-3">1~3个</a-select-option>
|
||||
<a-select-option value="4-6">4~6个</a-select-option>
|
||||
<a-select-option value="7+">7个以上</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
<div class="filter-item">
|
||||
<span class="filter-label">入职时间</span>
|
||||
<a-range-picker
|
||||
v-model:value="officerFilter.dateRange"
|
||||
class="filter-date"
|
||||
format="YYYY-MM-DD"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="filter-actions">
|
||||
<a-button type="primary" @click="handleFilterOfficers">
|
||||
<template #icon>
|
||||
|
|
@ -420,7 +520,172 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
|
||||
// 当前激活的页签
|
||||
const activeTabKey = ref('school')
|
||||
|
||||
// ========== 学习官查询相关 ==========
|
||||
const officerQuery = ref({
|
||||
searchText: '',
|
||||
loading: false,
|
||||
searched: false,
|
||||
hasResults: false,
|
||||
resultCount: 0,
|
||||
treeData: [],
|
||||
expandedKeys: [],
|
||||
selectedKeys: []
|
||||
})
|
||||
|
||||
let queryTimeout = null
|
||||
|
||||
// 处理学习官查询输入变化(500ms防抖)
|
||||
const handleOfficerQueryChange = () => {
|
||||
// 清除之前的定时器
|
||||
if (queryTimeout) {
|
||||
clearTimeout(queryTimeout)
|
||||
}
|
||||
|
||||
const searchText = officerQuery.value.searchText?.trim()
|
||||
|
||||
if (!searchText) {
|
||||
officerQuery.value.searched = false
|
||||
officerQuery.value.hasResults = false
|
||||
officerQuery.value.treeData = []
|
||||
return
|
||||
}
|
||||
|
||||
// 设置新的定时器,500ms防抖
|
||||
queryTimeout = setTimeout(() => {
|
||||
performOfficerQuery(searchText)
|
||||
}, 500)
|
||||
}
|
||||
|
||||
// 执行查询
|
||||
const performOfficerQuery = (searchText) => {
|
||||
officerQuery.value.loading = true
|
||||
officerQuery.value.searched = true
|
||||
|
||||
// 模拟异步查询
|
||||
setTimeout(() => {
|
||||
// 生成模拟查询结果
|
||||
const results = generateOfficerQueryResults(searchText)
|
||||
|
||||
officerQuery.value.treeData = results
|
||||
officerQuery.value.hasResults = results.length > 0
|
||||
officerQuery.value.resultCount = results.length
|
||||
officerQuery.value.expandedKeys = results.map(r => r.key)
|
||||
officerQuery.value.loading = false
|
||||
}, 300)
|
||||
}
|
||||
|
||||
// 生成查询结果(模拟数据)
|
||||
const generateOfficerQueryResults = (searchText) => {
|
||||
const results = []
|
||||
const surnames = ['张', '李', '王', '刘', '陈', '杨', '赵', '黄', '周', '吴']
|
||||
const names = ['明', '华', '强', '伟', '磊', '静', '敏', '丽', '婷', '娜']
|
||||
|
||||
// 根据搜索文本生成匹配的学习官
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
const surname = surnames[Math.floor(Math.random() * surnames.length)]
|
||||
const name = names[Math.floor(Math.random() * names.length)]
|
||||
const fullName = surname + name
|
||||
const phone = `138${String(Math.floor(Math.random() * 100000000)).padStart(8, '0')}`
|
||||
|
||||
// 检查是否匹配
|
||||
const nameMatch = fullName.includes(searchText)
|
||||
const phoneMatch = phone.includes(searchText)
|
||||
|
||||
if (nameMatch || phoneMatch || searchText.length < 2) {
|
||||
// 生成该学习官的学校班级层级数据
|
||||
const officerNode = {
|
||||
key: `officer-${i}`,
|
||||
title: fullName,
|
||||
name: fullName,
|
||||
phone: phone,
|
||||
type: 'officer',
|
||||
children: generateOfficerSchoolTree(i)
|
||||
}
|
||||
results.push(officerNode)
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// 生成学习官的学校班级树
|
||||
const generateOfficerSchoolTree = (officerId) => {
|
||||
const schools = []
|
||||
const schoolCount = Math.floor(Math.random() * 2) + 1
|
||||
|
||||
for (let s = 0; s < schoolCount; s++) {
|
||||
const school = schoolList.value[Math.floor(Math.random() * schoolList.value.length)]
|
||||
const schoolData = classDataMap[school.id]
|
||||
|
||||
if (schoolData) {
|
||||
const schoolNode = {
|
||||
key: `officer-${officerId}-school-${s}`,
|
||||
title: school.name,
|
||||
type: 'school',
|
||||
children: []
|
||||
}
|
||||
|
||||
// 添加年级和班级
|
||||
schoolData.forEach((grade, gradeIdx) => {
|
||||
const gradeNode = {
|
||||
key: `officer-${officerId}-school-${s}-grade-${gradeIdx}`,
|
||||
title: grade.gradeName,
|
||||
type: 'grade',
|
||||
children: []
|
||||
}
|
||||
|
||||
// 随机选择1-3个班级
|
||||
const classCount = Math.min(Math.floor(Math.random() * 3) + 1, grade.classes.length)
|
||||
const shuffled = [...grade.classes].sort(() => Math.random() - 0.5)
|
||||
|
||||
shuffled.slice(0, classCount).forEach((cls, classIdx) => {
|
||||
gradeNode.children.push({
|
||||
key: `officer-${officerId}-school-${s}-grade-${gradeIdx}-class-${classIdx}`,
|
||||
title: cls.className,
|
||||
type: 'class',
|
||||
studentCount: cls.studentCount,
|
||||
isLeaf: true
|
||||
})
|
||||
})
|
||||
|
||||
if (gradeNode.children.length > 0) {
|
||||
schoolNode.children.push(gradeNode)
|
||||
}
|
||||
})
|
||||
|
||||
if (schoolNode.children.length > 0) {
|
||||
schools.push(schoolNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return schools
|
||||
}
|
||||
|
||||
// 处理查询搜索按钮
|
||||
const handleOfficerQuerySearch = () => {
|
||||
handleOfficerQueryChange()
|
||||
}
|
||||
|
||||
// 处理查询结果树展开/折叠
|
||||
const handleOfficerQueryExpand = (keys) => {
|
||||
officerQuery.value.expandedKeys = keys
|
||||
}
|
||||
|
||||
// 处理查询结果树选择
|
||||
const handleOfficerQuerySelect = (selectedKeysValue, { node }) => {
|
||||
officerQuery.value.selectedKeys = selectedKeysValue
|
||||
|
||||
// 如果选中的是班级节点,可以在这里处理选中逻辑
|
||||
if (node.type === 'class') {
|
||||
// 可以添加选中班级的逻辑
|
||||
}
|
||||
}
|
||||
|
||||
// 学校列表数据
|
||||
const schoolList = ref([
|
||||
|
|
@ -491,11 +756,9 @@ const officerCandidates = ref([
|
|||
const officerColumns = [
|
||||
{ title: '姓名', dataIndex: 'name', key: 'name', width: 140 },
|
||||
{ title: '手机号', dataIndex: 'phone', key: 'phone', width: 130 },
|
||||
{ title: '区域', dataIndex: 'areaName', key: 'areaName', width: 90 },
|
||||
{ title: '状态', dataIndex: 'status', key: 'status', width: 90, align: 'center' },
|
||||
|
||||
{ title: '管理班级', dataIndex: 'classCount', key: 'classCount', width: 100, align: 'center', sorter: (a, b) => a.classCount - b.classCount },
|
||||
{ title: '入职时间', dataIndex: 'joinDate', key: 'joinDate', width: 110, sorter: (a, b) => a.joinDate.localeCompare(b.joinDate) },
|
||||
{ title: '经验', dataIndex: 'experience', key: 'experience', width: 80 },
|
||||
|
||||
{ title: '操作', key: 'action', width: 90, align: 'center', fixed: 'right' }
|
||||
]
|
||||
|
||||
|
|
@ -935,6 +1198,167 @@ const handleSelect = (selectedKeysValue, { node }) => {
|
|||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
/* 页签样式 */
|
||||
.tabs-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.custom-tabs {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.custom-tabs :deep(.ant-tabs-nav) {
|
||||
margin-bottom: 0;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.custom-tabs :deep(.ant-tabs-content) {
|
||||
height: calc(100% - 46px);
|
||||
}
|
||||
|
||||
.custom-tabs :deep(.ant-tabs-tabpane) {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 学习官查询区域样式 */
|
||||
.officer-query-section {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.query-input-wrapper {
|
||||
padding: 16px;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.officer-query-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.query-loading {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.query-empty {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.empty-hint {
|
||||
margin-top: 8px;
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.query-hint {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.query-hint p {
|
||||
margin-top: 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.query-result-wrapper {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.result-header {
|
||||
padding: 8px 12px;
|
||||
margin-bottom: 12px;
|
||||
background: linear-gradient(135deg, #e6f7ff 0%, #f0f5ff 100%);
|
||||
border-radius: 6px;
|
||||
border: 1px solid #d6e4ff;
|
||||
}
|
||||
|
||||
.result-count {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.result-count strong {
|
||||
color: #1890ff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.officer-query-tree {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.query-tree-node {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.query-tree-node.officer-node {
|
||||
background: linear-gradient(135deg, #f6ffed 0%, #fcfff7 100%);
|
||||
border-radius: 6px;
|
||||
padding: 8px 12px;
|
||||
margin: 4px 0;
|
||||
border: 1px solid #d9f7be;
|
||||
}
|
||||
|
||||
.officer-avatar-small {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.officer-name {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.officer-phone-tag {
|
||||
font-size: 11px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
background-color: #f0f0f0;
|
||||
color: #666;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.class-student-count {
|
||||
font-size: 11px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
background-color: #f5f5f5;
|
||||
color: #999;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.school-select-wrapper {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
|
|
@ -949,6 +1373,69 @@ const handleSelect = (selectedKeysValue, { node }) => {
|
|||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 未选择学校时的提示样式 */
|
||||
.tree-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.placeholder-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.placeholder-icon {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #f5f5f5 0%, #fafafa 100%);
|
||||
border-radius: 50%;
|
||||
margin-bottom: 20px;
|
||||
border: 2px dashed #e8e8e8;
|
||||
}
|
||||
|
||||
.placeholder-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.placeholder-desc {
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
margin: 0 0 20px 0;
|
||||
}
|
||||
|
||||
.placeholder-arrow {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #e6f7ff 0%, #f0f5ff 100%);
|
||||
border-radius: 50%;
|
||||
animation: bounce 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 树节点样式 */
|
||||
|
|
|
|||
|
|
@ -136,6 +136,13 @@ const reports = [
|
|||
icon: 'layers',
|
||||
iconBg: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', // 紫色渐变
|
||||
description: '查看英语单词学情统计、正确率排名、进度分布等报告'
|
||||
},
|
||||
{
|
||||
id: 'learning_overview',
|
||||
name: '学习情况总览表',
|
||||
icon: 'table',
|
||||
iconBg: 'linear-gradient(135deg, #722ed1 0%, #531dab 100%)', // 深紫色渐变
|
||||
description: '全面查看学生学习数据统计与分析'
|
||||
}
|
||||
]
|
||||
|
||||
|
|
@ -151,7 +158,8 @@ const handleReportClick = (report) => {
|
|||
'cloud_school': '/cloud-school-report',
|
||||
'school': '/learning-behavior-report',
|
||||
'leaderboard': '/leaderboard',
|
||||
'english_word': '/english-word-report'
|
||||
'english_word': '/english-word-report',
|
||||
'learning_overview': '/learning-overview'
|
||||
}
|
||||
|
||||
const routePath = routeMap[report.id]
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -108,6 +108,20 @@
|
|||
<span class="card-label">笔记评优</span>
|
||||
<span class="card-arrow">→</span>
|
||||
</div>
|
||||
|
||||
<div class="feature-card" @click="handleCardClick('online-monitor')">
|
||||
<div class="card-icon-wrap card-icon-cyan">
|
||||
<svg width="28" height="28" viewBox="0 0 24 24" fill="none">
|
||||
<rect x="2" y="3" width="20" height="14" rx="2" stroke="#13c2c2" stroke-width="1.8"/>
|
||||
<path d="M8 21h8M12 17v4" stroke="#13c2c2" stroke-width="1.8" stroke-linecap="round"/>
|
||||
<circle cx="12" cy="10" r="2" stroke="#13c2c2" stroke-width="1.6"/>
|
||||
<path d="M9 7.5a4.5 4.5 0 0 1 6 0" stroke="#13c2c2" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M7 5.5a7 7 0 0 1 10 0" stroke="#13c2c2" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="card-label">在线学习监控</span>
|
||||
<span class="card-arrow">→</span>
|
||||
</div>
|
||||
<div class="feature-card" @click="handleCardClick('report')">
|
||||
<div class="card-icon-wrap card-icon-green">
|
||||
<svg width="28" height="28" viewBox="0 0 24 24" fill="none">
|
||||
|
|
@ -180,6 +194,14 @@ const handleCardClick = (cardId) => {
|
|||
router.push('/report-home')
|
||||
return
|
||||
}
|
||||
if (cardId === 'online-monitor') {
|
||||
router.push('/online-learning-monitor')
|
||||
return
|
||||
}
|
||||
if (cardId === 'learning-overview') {
|
||||
router.push('/learning-overview')
|
||||
return
|
||||
}
|
||||
emit('card-click', cardId)
|
||||
}
|
||||
|
||||
|
|
@ -571,6 +593,16 @@ const handleLogout = () => {
|
|||
box-shadow: 0 2px 8px rgba(82, 196, 26, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.card-icon-cyan {
|
||||
background: linear-gradient(135deg, #e6fffb 0%, #b5f5ec 100%);
|
||||
box-shadow: 0 2px 8px rgba(19, 194, 194, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.card-icon-purple {
|
||||
background: linear-gradient(135deg, #f9f0ff 0%, #efdbff 100%);
|
||||
box-shadow: 0 2px 8px rgba(114, 46, 209, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.card-label {
|
||||
font-size: 15px;
|
||||
color: #1a1a2e;
|
||||
|
|
|
|||
|
|
@ -60,6 +60,16 @@ const routes = [
|
|||
path: '/english-word-report',
|
||||
name: 'EnglishWordReport',
|
||||
component: () => import('../components/EnglishWordReport.vue')
|
||||
},
|
||||
{
|
||||
path: '/online-learning-monitor',
|
||||
name: 'OnlineLearningMonitor',
|
||||
component: () => import('../components/OnlineLearningMonitorPage.vue')
|
||||
},
|
||||
{
|
||||
path: '/learning-overview',
|
||||
name: 'LearningOverview',
|
||||
component: () => import('../components/LearningOverviewPage.vue')
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue