diff --git a/doc/study-task-detail-prd.md b/doc/study-task-detail-prd.md new file mode 100644 index 0000000..b116074 --- /dev/null +++ b/doc/study-task-detail-prd.md @@ -0,0 +1,109 @@ +# 学习任务详情页 (Study Task Detail) 产品需求文档 + +## 1. 文档概述 +本文档描述了“学习任务详情页”的功能需求、界面布局及交互逻辑。该页面旨在为用户提供一个高效、沉浸式的任务查看与评估环境,支持视频回放、标准示例对照、实时评估打分等核心功能。 + +## 2. 页面布局与视觉风格 + +### 2.1 整体布局 +- **结构**:采用左右分栏设计的桌面端布局(Web Desktop Layout)。 + - **左侧栏 (Media Column)**:占据约 58% 宽度,专注于多媒体内容展示。 + - **右侧栏 (Info Column)**:占据约 42% 宽度,承载任务信息、标准对照及评估操作。 +- **底部栏 (Bottom Bar)**:固定悬浮于页面底部,展示核心评分数据。 + +### 2.2 视觉风格 (High-end Design) +- **色调**:背景采用柔和浅灰 (`#f2f3f5`),营造沉浸感;卡片采用纯白背景。 +- **质感**: + - 卡片无边框,使用细腻的悬浮阴影 (`box-shadow`) 增强层次感。 + - 底部栏采用**毛玻璃效果** (`backdrop-filter: blur`),提升现代感。 +- **圆角**:统一使用 `12px` 大圆角,视觉更加柔和。 +- **排版**: + - 标题使用加粗字体,配合蓝色竖条装饰 (`#409eff`) 进行视觉引导。 + - 数字展示采用 DIN 等宽字体,强调数据专业性。 + +## 3. 功能模块详解 + +### 3.1 顶部导航 (Page Header) +- **返回按钮**:点击左侧“Back”按钮返回上一级列表页。 +- **操作区**: + - **保存**:主操作按钮,保存当前评估结果。 + - **更多/查看**:辅助功能入口(图标按钮)。 + +### 3.2 媒体播放区 (左侧栏) +- **视频播放器**: + - 16:9 宽屏展示。 + - 支持视频播放/暂停、进度控制。 + - 无信号时显示占位符及提示文案。 +- **控制条 (Controls Bar)**: + - **回放/直播切换**:下拉选择器,支持切换“回放”与“直播”模式。 + - **监控点位选择**:下拉选择器,支持切换不同监控视角(如“监控点位一”、“监控点位二”)。 + +### 3.3 任务信息与评估区 (右侧栏) +#### A. 任务基础信息 +- **标题**:大字号展示任务名称,辅以圆形 Tag 标签显示任务类型/状态。 +- **描述**: + - 展示任务详细说明。 + - **交互**:支持长文本折叠/展开功能(当前设计为默认展开,预留收起按钮)。 + +#### B. 标准示例 (Standard Examples) +- **展示形式**:网格布局展示标准作业图片。 +- **交互**: + - 鼠标悬停图片时有放大动效 (`scale 1.05`)。 + - 点击图片支持大图预览(Lightbox 模式)。 +- **数据展示**:标题旁显示图片总数量。 + +#### C. 评估操作 (Evaluation) +- **评估项内容**:显示具体的评估标准文本。 +- **评估状态选择**: + - 三态选择器:**不合格 (Failed)**、**不适用 (N/A)**、**合格 (Passed)**。 + - **样式**:宽大卡片式按钮,选中后高亮显示对应颜色(红/灰/绿),提供明确的视觉反馈。 + +#### D. 建议反馈 (Suggestion) +- **输入框**:多行文本域,支持输入评估建议。 +- **限制**:最大支持 1000 字,右下角显示字数统计。 + +#### E. 上次扣分情况 (Last Deduction) - *条件渲染* +- **触发条件**:仅当存在历史扣分记录时显示。 +- **样式**:淡红色背景 (`#fff0f0`) 警示卡片。 +- **内容**: + - 历史扣分分值(如 `-2分`)。 + - 违规现场照片缩略图。 + - 历史整改建议。 + +### 3.4 底部状态栏 (Bottom Bar) +- **定位**:Fixed 定位底部,z-index 层级最高。 +- **核心指标**: + 1. **分值**:该任务的总分值(蓝色高亮)。 + 2. **扣分规则**:简要显示的文字规则。 + 3. **当前扣分**:实时计算的扣分数值(红色高亮)。 + +## 4. 数据接口需求 (Mock Schema) + +### 4.1 详情接口 +- **Endpoint**: `/api/study-tasks/detail` +- **Method**: `GET` +- **Params**: `id` (Task ID) +- **Response Structure**: +```typescript +interface TaskDetail { + id: number; + title: string; // 任务标题 + tag: string; // 标签(如"日常任务") + longDescription: string;// 详细描述 + videoUrl: string; // 视频地址 + standardImages: string[]; // 标准示例图片URL数组 + evaluationContent: string;// 评估标准文本 + score: number; // 总分 + rule: string; // 扣分规则文本 + deduction: number; // 当前扣分 + lastDeduction?: { // 上次扣分记录(可选) + score: number; + photos: string[]; + suggestion: string; + }; +} +``` + +## 5. 后续规划 +- 视频播放器对接真实的流媒体服务(HLS/FLV)。 +- 评估状态与扣分逻辑的自动联动(如选择“不合格”自动计算扣分)。 diff --git a/mock/modules.ts b/mock/modules.ts index d695fd8..ade69ed 100644 --- a/mock/modules.ts +++ b/mock/modules.ts @@ -29,6 +29,13 @@ export default [ description: 'View key performance indicators and analytics.', icon: 'DataLine', path: '/data-dashboard' + }, + { + id: 4, + title: 'Study Task', + description: 'Manage study tasks and track completion status.', + icon: 'List', + path: '/study-task' } ] } diff --git a/mock/store-inspection.ts b/mock/store-inspection.ts new file mode 100644 index 0000000..39826ac --- /dev/null +++ b/mock/store-inspection.ts @@ -0,0 +1,86 @@ +import { MockMethod } from 'vite-plugin-mock' + +const inspections = [ + { + id: 1, + storeName: '百年神厨 (格林店)', + templateName: '2023 Q4 标准巡检模板', + inspectors: ['陈经理', '张欣欣', '李雷', '韩梅梅'], + date: '2023-10-20 13:00 至 2023-10-20 15:00', + totalScore: 400.0, + scoreRate: 50.5, + rating: '非常的优秀', + score: 200.0, + totalDeduction: 199.0, + naScore: 1, + totalItems: 999, + status: 'completed', + modules: [ + { name: '01 产品', rating: '非常的优秀', score: 20, deduction: 10, rate: 33.33 }, + { name: '02 服务', rating: '优秀', score: 10, deduction: 100, rate: 50.00 }, + { name: '03 安全', rating: 'B', score: 5, deduction: 10, rate: 33.33 }, + { name: '04 生产生产', rating: 'C', score: 1, deduction: 5, rate: 83.41 }, + { name: '05 内容显示', rating: 'D', score: 0, deduction: 10, rate: 100.00 }, + ], + redLine: { + deduction: 12, + total: 20 + }, + recheck: { + lastIssues: 3, + recurringIssues: 4, + rate: 30 + }, + na: { + count: 1, + totalCount: 20, + score: 5, + totalScore: 100 + }, + additional: { + suddenIssues: 18, + highlights: 24 + } + }, + { + id: 2, + storeName: '百年神厨 (万达店)', + templateName: '2023 Q4 标准巡检模板', + inspectors: ['王督导'], + date: '2023-10-21 09:00 至 2023-10-21 11:00', + totalScore: 95.0, + scoreRate: 95.0, + rating: '优秀', + status: 'pending' + } +] + +export default [ + { + url: '/api/inspections', + method: 'get', + response: () => { + return { + code: 0, + message: 'ok', + data: inspections + } + } + }, + { + url: '/api/inspections/detail', + method: 'get', + response: ({ query }: { query: any }) => { + const id = Number(query.id) + const item = inspections.find(i => i.id === id) + if (!item) { + return { code: 1, message: 'Inspection not found' } + } + return { + code: 0, + message: 'ok', + data: item + } + } + } +] as MockMethod[] diff --git a/mock/study-task.ts b/mock/study-task.ts new file mode 100644 index 0000000..d6073d7 --- /dev/null +++ b/mock/study-task.ts @@ -0,0 +1,135 @@ +import { MockMethod } from 'vite-plugin-mock' + +let tasks = [ + { + id: 1, + title: 'Learn Vue 3', + description: 'Study Composition API and script setup', + dueDate: '2023-12-31', + status: 'pending' + }, + { + id: 2, + title: 'Master TypeScript', + description: 'Understand generics and utility types', + dueDate: '2023-11-30', + status: 'completed' + }, + { + id: 3, + title: 'Element Plus Basics', + description: 'Learn grid system and basic components', + dueDate: '2024-01-15', + status: 'pending' + } +] + +export default [ + { + url: '/api/study-tasks', + method: 'get', + response: () => { + return { + code: 0, + message: 'ok', + data: tasks + } + } + }, + { + url: '/api/study-tasks/detail', + method: 'get', + response: ({ query }: { query: any }) => { + const id = Number(query.id) + const task = tasks.find(t => t.id === id) + + if (!task) { + return { + code: 1, + message: 'Task not found' + } + } + + // Enrich with detail data + return { + code: 0, + message: 'ok', + data: { + ...task, + // Extra fields for detail view + videoUrl: 'https://media.w3.org/2010/05/sintel/trailer.mp4', + tag: '服务岗位', + longDescription: '争吵打架争吵打架争吵打架争吵打架争吵打架争吵打架争吵打架争吵打架争吵打架\n\n员工和员工之间发生吵架、打架;与顾客发生争吵、打架。员工和员工之间发生吵架、打架;与顾客发生争吵。', + evaluationContent: '争吵打架争吵打架争吵打架争吵打架争吵打架', + standardImages: [ + 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg', + 'https://fuss10.elemecdn.com/8/27/f01c15bb73e1ef3793e64e6b7bbccjpeg.jpeg', + 'https://fuss10.elemecdn.com/1/8e/aeffeb4de74e2fde4bd74fc7b4486jpeg.jpeg', + 'https://fuss10.elemecdn.com/3/28/bbf893f792f03a54408b3b7a7ebf0jpeg.jpeg' + ], + livePhotos: [ + 'https://fuss10.elemecdn.com/2/11/6535bcfb26e4c79b48ddde44f4b6fjpeg.jpeg', + 'https://fuss10.elemecdn.com/d/e6/c4d93a3805b3ce3f323f7974e6f78jpeg.jpeg' + ], + lastDeduction: { + photos: [ + 'https://fuss10.elemecdn.com/3/28/bbf893f792f03a54408b3b7a7ebf0jpeg.jpeg' + ], + suggestion: '工作期间应该注重仪容仪表工作期间应该注重仪容仪表', + score: -3 + }, + score: 999, + rule: '一次性扣除', + deduction: -999 + } + } + } + }, + { + url: '/api/study-tasks', + method: 'post', + response: ({ body }: { body: any }) => { + const newTask = { + id: tasks.length > 0 ? Math.max(...tasks.map(t => t.id)) + 1 : 1, + ...body + } + tasks.push(newTask) + return { + code: 0, + message: 'ok', + data: newTask + } + } + }, + { + url: '/api/study-tasks', + method: 'put', + response: ({ body }: { body: any }) => { + const index = tasks.findIndex(t => t.id === body.id) + if (index !== -1) { + tasks[index] = { ...tasks[index], ...body } + return { + code: 0, + message: 'ok', + data: tasks[index] + } + } + return { + code: 1, + message: 'Task not found' + } + } + }, + { + url: '/api/study-tasks', + method: 'delete', + response: ({ query }: { query: any }) => { + const id = Number(query.id) + tasks = tasks.filter(t => t.id !== id) + return { + code: 0, + message: 'ok' + } + } + } +] as MockMethod[] diff --git a/package-lock.json b/package-lock.json index e913e49..b2aa3e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@element-plus/icons-vue": "^2.3.1", "axios": "^1.6.0", + "echarts": "^6.0.0", "element-plus": "^2.5.0", "pinia": "^2.1.0", "vue": "^3.4.0", @@ -1828,6 +1829,16 @@ "node": ">= 0.4" } }, + "node_modules/echarts": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/echarts/-/echarts-6.0.0.tgz", + "integrity": "sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "2.3.0", + "zrender": "6.0.0" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", @@ -2776,6 +2787,12 @@ "node": ">=8.0" } }, + "node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", @@ -3472,6 +3489,15 @@ "peerDependencies": { "typescript": "*" } + }, + "node_modules/zrender": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/zrender/-/zrender-6.0.0.tgz", + "integrity": "sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==", + "license": "BSD-3-Clause", + "dependencies": { + "tslib": "2.3.0" + } } } } diff --git a/package.json b/package.json index c2fdba9..464348e 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "dependencies": { "@element-plus/icons-vue": "^2.3.1", "axios": "^1.6.0", + "echarts": "^6.0.0", "element-plus": "^2.5.0", "pinia": "^2.1.0", "vue": "^3.4.0", diff --git a/src/layout/index.vue b/src/layout/index.vue index 5002601..a8a7fc9 100644 --- a/src/layout/index.vue +++ b/src/layout/index.vue @@ -10,6 +10,14 @@ 工作台 + + + Study Task + + + + 巡店管理 + diff --git a/src/router/index.ts b/src/router/index.ts index 3d9706c..d814cfc 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -42,6 +42,30 @@ const routes: Array = [ name: 'DataDashboard', component: () => import('../views/data-dashboard/index.vue'), meta: { title: '数据工作台' } + }, + { + path: 'study-task', + name: 'StudyTask', + component: () => import('../views/study-task/index.vue'), + meta: { title: 'Study Task' } + }, + { + path: 'study-task/detail/:id', + name: 'StudyTaskDetail', + component: () => import('../views/study-task/detail.vue'), + meta: { title: 'Task Detail' } + }, + { + path: 'store-inspection', + name: 'StoreInspection', + component: () => import('../views/store-inspection/index.vue'), + meta: { title: '巡店管理' } + }, + { + path: 'store-inspection/detail/:id', + name: 'StoreInspectionDetail', + component: () => import('../views/store-inspection/detail.vue'), + meta: { title: '巡店报告' } } ] } diff --git a/src/views/store-inspection/detail.vue b/src/views/store-inspection/detail.vue new file mode 100644 index 0000000..278f4d2 --- /dev/null +++ b/src/views/store-inspection/detail.vue @@ -0,0 +1,556 @@ + + + + + + + + + {{ data.storeName || '门店详情' }} + + + + + + + + + + + + + + + + + + + + + {{ data.templateName }} + 巡店人:{{ data.inspectors?.join('、') }} 等{{ data.inspectors?.length }}人 + 时间:{{ data.date }} + + + + + {{ data.totalScore }} + 总分 + + + {{ data.scoreRate }}% + 得分率 + + + {{ data.rating }} + + + + + {{ data.score }} / {{ data.totalDeduction }} + 得分/扣分 + + + {{ data.naScore }}% + 不适用分 + + + 本次扣分评估项总数 {{ data.totalItems }} + + + + + + + + + + + 评分细则 + + + + + + + + + + + + {{ row.rating }} + + + + + + + {{ row.rate }}% + + + + + 查看全部 + + + + + + + + + + + + 红线项 + + + + + 红线项扣分数 {{ data.redLine?.deduction }} / {{ data.redLine?.total }} + + + + + + + + + + + + 问题复查 + + + + + 上次问题数 {{ data.recheck?.lastIssues }} + 问题复现数 {{ data.recheck?.recurringIssues }} + + + + + + + + + + + + 不适用 + + + + + {{ data.na?.count }}/{{ data.na?.totalCount }} + 不适用数/总数 + + + {{ data.na?.score }}/{{ data.na?.totalScore }} + 不适用分/总分 + + + + + + + + + + + 附加项 + + + + + 突发问题数 + 亮点数 + + + {{ data.additional?.suddenIssues }} + {{ data.additional?.highlights }} + + + + + + + + + + + + 现场拍摄 {{ data.totalItems }} + + 报告确认 + + + + + + + diff --git a/src/views/store-inspection/index.vue b/src/views/store-inspection/index.vue new file mode 100644 index 0000000..f9b64d2 --- /dev/null +++ b/src/views/store-inspection/index.vue @@ -0,0 +1,76 @@ + + + + 巡店管理 + 新建巡店 + + + + + + + + + {{ row.inspectors?.join(', ') }} + + + + + + {{ row.scoreRate }}% + + + + + 查看报告 + + + + + + + + + + diff --git a/src/views/study-task/detail.vue b/src/views/study-task/detail.vue new file mode 100644 index 0000000..87a7595 --- /dev/null +++ b/src/views/study-task/detail.vue @@ -0,0 +1,494 @@ + + + + + + + + Back + + 详情 + + + 保存 + + + + + + + + + + + + + + + + + + No Video Available + + + + + + + + + + + + + + + + + + + + + + + + {{ taskData.title }} + {{ taskData.tag }} + + + {{ taskData.longDescription }} + 收起 + + + + + + 标准示例 + {{ taskData.standardImages?.length || 0 }}项 + + + + + + + + 评估项内容 + {{ taskData.evaluationContent }} + + + + + 不合格 + + + 不适用 + + + 合格 + + + + + + + 建议 + + + + + 上次扣分情况 + + + 现场拍摄 + -{{ taskData.lastDeduction.score }}分 + + + + + + 建议:{{ taskData.lastDeduction.suggestion }} + + + + + + + + + + 分值 + {{ taskData.score }} 分 + + + 扣分规则 + {{ taskData.rule }} + + + 当前扣分 + {{ taskData.deduction }} 分 + + + + + + + + diff --git a/src/views/study-task/index.vue b/src/views/study-task/index.vue new file mode 100644 index 0000000..f6ba546 --- /dev/null +++ b/src/views/study-task/index.vue @@ -0,0 +1,186 @@ + + + + Study Task Management + Add Task + + + + + + {{ row.title }} + + + + + + + + {{ row.status }} + + + + + + Detail + Edit + Delete + + + + + + + + + + + + + + + + + + + + + + + + + Cancel + Confirm + + + + + + + + +
巡店人:{{ data.inspectors?.join('、') }} 等{{ data.inspectors?.length }}人
时间:{{ data.date }}
{{ taskData.evaluationContent }}