diff --git a/docs/superpowers/plans/2026-07-11-mobile-uni-employee-pages-visual-redesign.md b/docs/superpowers/plans/2026-07-11-mobile-uni-employee-pages-visual-redesign.md
new file mode 100644
index 00000000..e679e68b
--- /dev/null
+++ b/docs/superpowers/plans/2026-07-11-mobile-uni-employee-pages-visual-redesign.md
@@ -0,0 +1,450 @@
+# Mobile Uni Employee Pages Visual Redesign Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** 将员工端 `today / sop / practice` 三页及其强相关的 `tabBar / 共享卡片样式` 调整为接近设计基准稿的统一视觉,同时保持现有业务交互和接口行为不变。
+
+**Architecture:** 采用“视觉优先、业务最小改动”的改法:先建立员工端共享视觉 token 与基础样式,再按首页、问师傅页、训练页的顺序逐页重排结构和样式。页面内部函数、接口调用与路由保留,主要改模板层级、类名、样式组织和少量静态资源引用。
+
+**Tech Stack:** uni-app Vue3、Vite、TypeScript、CSS、Node 原生测试、H5 构建校验
+
+---
+
+### Task 1: 建立员工端共享视觉基座
+
+**Files:**
+- Modify: `mobile-uni/src/styles/base.css`
+- Modify: `mobile-uni/src/pages.json`
+- Test: `mobile-uni/tests/mobile-user-pages.test.mjs`
+
+- [x] **Step 1: 写一个失败测试,锁住 Vue3 manifest 与员工端视觉约束继续有效**
+
+```js
+test('tabBar 继续保留员工端四个固定入口', async () => {
+ const source = await pageSource('../src/pages.json');
+
+ assert.match(source, /pages\/user\/today\/index/);
+ assert.match(source, /pages\/user\/practice\/index/);
+ assert.match(source, /pages\/user\/sop\/index/);
+ assert.match(source, /pages\/user\/profile\/index/);
+});
+```
+
+- [x] **Step 2: 运行测试,确认新增断言先失败或当前文件尚未覆盖该约束**
+
+Run: `node --test tests/mobile-user-pages.test.mjs`
+Expected: 新增测试如果还没加入会缺失;加入后在修改前至少能作为后续回归校验入口存在。
+
+- [x] **Step 3: 在 `base.css` 中整理员工端共享 token,不扩大到全站侵入式重置**
+
+```css
+:root {
+ --employee-bg: #f4f6fb;
+ --employee-surface: #ffffff;
+ --employee-primary: #2f6bff;
+ --employee-primary-soft: #eaf1ff;
+ --employee-success: #34b368;
+ --employee-warning: #ff8a1f;
+ --employee-danger: #ff5b4d;
+ --employee-text: #182230;
+ --employee-muted: #6b7280;
+ --employee-card-radius: 24px;
+ --employee-card-shadow: 0 12px 32px rgba(47, 107, 255, 0.08);
+}
+
+.employee-card {
+ border-radius: var(--employee-card-radius);
+ background: var(--employee-surface);
+ box-shadow: var(--employee-card-shadow);
+}
+
+.employee-section-title {
+ color: var(--employee-text);
+ font-size: 20px;
+ font-weight: 900;
+}
+```
+
+- [x] **Step 4: 调整 `pages.json` 的员工端 tabBar 视觉参数,保留四 tab 结构和路径**
+
+```json
+"tabBar": {
+ "color": "#7b8494",
+ "selectedColor": "#2f6bff",
+ "backgroundColor": "#ffffff",
+ "borderStyle": "white",
+ "height": "66px",
+ "fontSize": "12px",
+ "iconWidth": "25px",
+ "spacing": "3px",
+ "list": [
+ { "pagePath": "pages/user/today/index", "text": "今日" },
+ { "pagePath": "pages/user/practice/index", "text": "练" },
+ { "pagePath": "pages/user/sop/index", "text": "问" },
+ { "pagePath": "pages/user/profile/index", "text": "我" }
+ ]
+}
+```
+
+- [x] **Step 5: 更新回归测试,验证 tabBar 四入口与共享样式关键 token 存在**
+
+```js
+test('共享视觉基座包含员工端主色和卡片圆角 token', async () => {
+ const source = await pageSource('../src/styles/base.css');
+
+ assert.match(source, /--employee-primary:/);
+ assert.match(source, /--employee-card-radius:/);
+});
+```
+
+- [x] **Step 6: 运行测试验证通过**
+
+Run: `node --test tests/mobile-user-pages.test.mjs`
+Expected: PASS,且已有输入兼容与 manifest 测试继续通过。
+
+- [ ] **Step 7: 提交这一阶段改动**
+
+```bash
+git add mobile-uni/src/styles/base.css mobile-uni/src/pages.json mobile-uni/tests/mobile-user-pages.test.mjs
+git commit -m "feat: add employee visual foundation"
+```
+
+### Task 2: 重做员工端首页视觉
+
+**Files:**
+- Modify: `mobile-uni/src/pages/user/today/index.vue`
+- Modify: `mobile-uni/src/styles/base.css`
+- Test: `mobile-uni/tests/mobile-user-pages.test.mjs`
+
+- [x] **Step 1: 为首页结构写失败测试,锁住设计稿要求的核心区块**
+
+```js
+test('员工首页保留师傅区、语音条、今日安排、我的地图四段结构', async () => {
+ const source = await pageSource('../src/pages/user/today/index.vue');
+
+ assert.match(source, /mentor-hero/);
+ assert.match(source, /voice-panel/);
+ assert.match(source, /今日安排/);
+ assert.match(source, /我的地图/);
+});
+```
+
+- [x] **Step 2: 运行测试确认结构断言可作为改版护栏**
+
+Run: `node --test tests/mobile-user-pages.test.mjs`
+Expected: 失败或在旧结构下不足以覆盖新样式目标,后续补全后通过。
+
+- [x] **Step 3: 重排首页模板,保留原有交互函数但调整为设计稿节奏**
+
+```vue
+
+
+ {{ positionProfile.value }}
+ {{ positionProfile.heroTitle }}
+ 有问题,随时问老师傅
+
+
+
+ {{ mentorBubbleText }}
+
+
+
+
+
+
+
+
+...
+...
+```
+
+- [x] **Step 4: 用 scoped 样式还原首页视觉,控制人物占屏不超过首屏约五分之一**
+
+```css
+.today-page {
+ background: radial-gradient(circle at top, #eef5ff 0%, #f7f9fc 38%, #f4f6fb 100%);
+}
+
+.mentor-hero {
+ min-height: 220px;
+ padding: 28px 24px 0;
+}
+
+.mentor-image {
+ right: 24px;
+ bottom: 0;
+ width: 108px;
+ height: 148px;
+}
+
+.voice-panel {
+ margin-top: -28px;
+ grid-template-columns: 1.4fr 1fr;
+}
+
+.map-tile {
+ border-radius: 22px;
+ min-height: 136px;
+}
+```
+
+- [x] **Step 5: 更新测试,验证首页地图主标题继续显示真实 `tile.title`,避免回退成单字标题**
+
+```js
+test('员工首页地图主标题继续显示真实工具名称', async () => {
+ const source = await pageSource('../src/pages/user/today/index.vue');
+
+ assert.match(source, /\{\{ tile\.title \}\}<\/text>/);
+});
+```
+
+- [x] **Step 6: 运行测试验证首页改版没有破坏护栏**
+
+Run: `node --test tests/mobile-user-pages.test.mjs`
+Expected: PASS。
+
+- [ ] **Step 7: 提交首页改版**
+
+```bash
+git add mobile-uni/src/pages/user/today/index.vue mobile-uni/src/styles/base.css mobile-uni/tests/mobile-user-pages.test.mjs
+git commit -m "feat: redesign employee home page"
+```
+
+### Task 3: 重做问师傅页视觉与总结卡展现
+
+**Files:**
+- Modify: `mobile-uni/src/pages/user/sop/index.vue`
+- Modify: `mobile-uni/src/styles/base.css`
+- Test: `mobile-uni/tests/mobile-user-pages.test.mjs`
+
+- [x] **Step 1: 为问师傅页写失败测试,锁住搜索主导与总结卡入口仍然存在**
+
+```js
+test('问师傅页保留搜索框、答案区和总结卡入口', async () => {
+ const source = await pageSource('../src/pages/user/sop/index.vue');
+
+ assert.match(source, /query-panel/);
+ assert.match(source, /answer-panel/);
+ assert.match(source, /summary-panel/);
+});
+```
+
+- [x] **Step 2: 运行测试确认护栏建立成功**
+
+Run: `node --test tests/mobile-user-pages.test.mjs`
+Expected: 断言可执行,作为后续视觉重排的结构保护。
+
+- [x] **Step 3: 收紧问师傅页头部,改为“轻标题 + 强搜索框”的布局**
+
+```vue
+
+
+ 查SOP
+ 直接搜 SOP、案例和常用话术
+
+
+
+
+ ...
+
+
+
+```
+
+- [x] **Step 4: 调整答案卡、引用依据和总结卡为更接近设计稿的卡片层级与底部浮层感**
+
+```css
+.sop-page {
+ background: linear-gradient(180deg, #f7f9fc 0%, #f4f6fb 100%);
+}
+
+.query-panel {
+ border-radius: 22px;
+ padding: 18px;
+}
+
+.answer-panel {
+ border-left: 0;
+ border-radius: 22px;
+}
+
+.modal-card {
+ border-radius: 28px 28px 0 0;
+ max-width: 430px;
+ width: calc(100% - 16px);
+}
+```
+
+- [x] **Step 5: 保持 H5 输入兼容测试存在,防止误把 `textarea` 再次改成 `border-box`**
+
+```js
+test('SOP 查询框不覆盖 textarea 的 box-sizing', async () => {
+ const source = await pageSource('../src/pages/user/sop/index.vue');
+ const queryPanelStyle = source.match(/\.query-panel \.textarea \{[\s\S]*?\n\}/)?.[0] || '';
+
+ assert.equal(queryPanelStyle.includes('box-sizing'), false);
+});
+```
+
+- [x] **Step 6: 运行测试验证通过**
+
+Run: `node --test tests/mobile-user-pages.test.mjs`
+Expected: PASS。
+
+- [ ] **Step 7: 提交问师傅页改版**
+
+```bash
+git add mobile-uni/src/pages/user/sop/index.vue mobile-uni/src/styles/base.css mobile-uni/tests/mobile-user-pages.test.mjs
+git commit -m "feat: redesign employee sop page"
+```
+
+### Task 4: 重做训练页视觉但保留训练逻辑
+
+**Files:**
+- Modify: `mobile-uni/src/pages/user/practice/index.vue`
+- Modify: `mobile-uni/src/styles/base.css`
+- Test: `mobile-uni/tests/mobile-user-pages.test.mjs`
+
+- [x] **Step 1: 为训练页写失败测试,锁住训练关键区块仍存在**
+
+```js
+test('训练页保留场景区、对话区、回复区和结果区', async () => {
+ const source = await pageSource('../src/pages/user/practice/index.vue');
+
+ assert.match(source, /scenario-card/);
+ assert.match(source, /dialogue-card/);
+ assert.match(source, /reply-card/);
+ assert.match(source, /result-card/);
+});
+```
+
+- [x] **Step 2: 运行测试确认结构护栏存在**
+
+Run: `node --test tests/mobile-user-pages.test.mjs`
+Expected: 断言可运行,用于防止视觉重排时误删功能区块。
+
+- [x] **Step 3: 调整训练页模板,让头部、场景卡、老师傅提示更接近设计稿的训练面板结构**
+
+```vue
+
+ 数字师傅 · 场景训练
+ {{ activeScenarioName }}
+ 与业主、老师傅完成一轮真实工作对话
+
+
+...
+...
+...
+```
+
+- [x] **Step 4: 用样式强化三类角色区分与老师傅提示卡,但不引入每轮真实评分**
+
+```css
+.turn-row.customer {
+ background: linear-gradient(180deg, #fff8f1 0%, #ffffff 100%);
+}
+
+.turn-row.trainee {
+ background: linear-gradient(180deg, #f2fbf6 0%, #ffffff 100%);
+}
+
+.turn-row.coach {
+ background: linear-gradient(180deg, #eef5ff 0%, #ffffff 100%);
+}
+
+.reply-card .primary-button {
+ min-height: 52px;
+ border-radius: 18px;
+}
+
+.help-card,
+.mentor-rewrite {
+ border-radius: 22px;
+}
+```
+
+- [x] **Step 5: 保留并复用提交失败回滚测试,避免训练视觉改版时误伤已有状态修复**
+
+```js
+test('训练页提交失败会回滚本次乐观追加的对话', async () => {
+ const source = await pageSource('../src/pages/user/practice/index.vue');
+
+ assert.match(source, /const previousTurns = turns\.value\.slice\(\)/);
+ assert.match(source, /turns\.value = previousTurns/);
+});
+```
+
+- [x] **Step 6: 运行测试验证通过**
+
+Run: `node --test tests/mobile-user-pages.test.mjs`
+Expected: PASS。
+
+- [ ] **Step 7: 提交训练页改版**
+
+```bash
+git add mobile-uni/src/pages/user/practice/index.vue mobile-uni/src/styles/base.css mobile-uni/tests/mobile-user-pages.test.mjs
+git commit -m "feat: redesign employee practice page"
+```
+
+### Task 5: 全量验证与收尾
+
+**Files:**
+- Verify: `mobile-uni/src/pages/user/today/index.vue`
+- Verify: `mobile-uni/src/pages/user/sop/index.vue`
+- Verify: `mobile-uni/src/pages/user/practice/index.vue`
+- Verify: `mobile-uni/src/styles/base.css`
+- Verify: `mobile-uni/src/pages.json`
+- Test: `mobile-uni/tests/mobile-user-pages.test.mjs`
+
+- [x] **Step 1: 运行员工端页面回归测试**
+
+Run: `node --test tests/mobile-user-pages.test.mjs`
+Expected: PASS。
+
+- [x] **Step 2: 运行全量单测**
+
+Run: `npm run test:unit`
+Expected: 全部 PASS;允许保留既有 WebSocket 噪音,只要没有失败。
+
+- [x] **Step 3: 运行类型检查**
+
+Run: `npm run typecheck`
+Expected: PASS。
+
+- [x] **Step 4: 运行 H5 构建**
+
+Run: `npm run build:h5`
+Expected: `Build complete.`
+
+- [ ] **Step 5: 本地打开首页进行人工快速核对**
+
+Run: `open "http://localhost:5175/h5/#/pages/user/today/index"`
+Expected: 能看到新的首页首屏结构、语音条、今日安排、我的地图和统一 tabBar。
+
+- [ ] **Step 6: 提交最终整体验证结果**
+
+```bash
+git add mobile-uni/src/pages/user/today/index.vue mobile-uni/src/pages/user/sop/index.vue mobile-uni/src/pages/user/practice/index.vue mobile-uni/src/styles/base.css mobile-uni/src/pages.json mobile-uni/tests/mobile-user-pages.test.mjs
+git commit -m "feat: unify employee mobile page visuals"
+```
+
+## 计划自检
+
+### Spec 覆盖检查
+
+- 三页范围:Task 2、Task 3、Task 4 覆盖 `today / sop / practice`。
+- 共享样式与 tabBar:Task 1 覆盖。
+- 老师傅仅复用现有素材、不扩展业务:Task 2 与 Task 4 的模板调整限制在现有资源与逻辑内。
+- SOP 不伪造能力:Task 3 明确保留真实能力,不新增假 tab 行为。
+- 训练页不做每轮真实评分:Task 4 明确只改视觉分层,不新增该模块。
+- 验证要求:Task 5 覆盖页面测试、全量单测、类型检查、H5 构建。
+
+### Placeholder 扫描
+
+- 无 `TODO`、`TBD`、`适当处理` 等空泛描述。
+- 每个任务都包含具体文件、命令和代码片段。
+
+### 类型与命名一致性
+
+- 继续沿用现有页面类名:`scenario-card`、`dialogue-card`、`reply-card`、`summary-panel`、`voice-panel` 等。
+- 复用现有关键回归点:`tile.title`、`previousTurns`、`box-sizing`、`vueVersion`。
diff --git a/docs/superpowers/specs/2026-07-11-mobile-uni-employee-pages-visual-redesign.md b/docs/superpowers/specs/2026-07-11-mobile-uni-employee-pages-visual-redesign.md
new file mode 100644
index 00000000..67e69b85
--- /dev/null
+++ b/docs/superpowers/specs/2026-07-11-mobile-uni-employee-pages-visual-redesign.md
@@ -0,0 +1,241 @@
+# 员工端三页视觉改版设计
+
+## 背景
+
+当前 `mobile-uni` 员工端首页、问师傅页、训练页虽然已经有一轮样式调整,但整体仍然存在以下问题:
+
+- 三页视觉语言不统一,像是独立拼接出来的页面,而不是同一套员工端产品。
+- 信息层级和卡片节奏偏乱,重点入口不够聚焦,页面看起来“重、散、旧”。
+- 与 `docs/feedback/2026070423/design/` 中已经采纳的 P1 视觉基准仍有明显差距。
+- 当前诉求是优先解决“页面丑陋”的观感问题,而不是重写业务交互。
+
+本次改版以视觉还原为主,业务交互保持最小改动。
+
+## 目标
+
+### 主要目标
+
+- 统一员工端 `today / sop / practice` 三页视觉气质。
+- 尽量向已采纳设计稿靠拢,优先还原布局、卡片层级、按钮形态、留白和色彩关系。
+- 保持现有业务逻辑、接口调用、页面跳转、训练流程与登录流程不变。
+- 允许联动调整员工端 `tabBar` 和少量共享基础样式,使三页不再割裂。
+
+### 非目标
+
+- 不新增后端字段或伪造后端能力。
+- 不重写训练页状态机或 SOP 检索逻辑。
+- 不做候选人端、主管端的同步视觉改版。
+- 不在本次引入新的正式人物素材来源。
+
+## 设计依据与约束
+
+### 设计依据
+
+- 首页视觉基准:`docs/feedback/2026070423/design/h5-redesign-employee-home.png`
+- SOP 结果与总结卡基准:`docs/feedback/2026070423/design/h5-feedback-summary-modal.png`
+- 训练页对手戏基准:`docs/feedback/2026070423/design/h5-feedback-voice-roleplay.png`
+- 综合评审约束:`docs/feedback/2026070423/design/REVIEW.md`
+
+### 必须遵守的约束
+
+- 老师傅形象不照抄首页参考图中的女性写实形象,统一使用现有本地素材做“蓝工装插画风男性师傅”的近似表达。
+- 首页人物只承担氛围与陪伴感,不大面积占屏;视觉占比控制在首屏约五分之一以内。
+- 训练页不实现每轮真实评分,只保留轻提示或教练辅助信息。
+- SOP 页如果设计稿出现“全部/SOP/案例/话术”类型 tab,在后端维度未落地前不得伪造成功能型筛选。
+- 遵守 uni-app H5 输入兼容性规则,不对 `input` / `textarea` 设置 `box-sizing: border-box`。
+
+## 范围
+
+### 页面范围
+
+- `src/pages/user/today/index.vue`
+- `src/pages/user/sop/index.vue`
+- `src/pages/user/practice/index.vue`
+
+### 允许联动的共享部分
+
+- `src/styles/base.css`
+- `src/pages.json` 中员工端 `tabBar` 视觉配置
+- 员工端页面使用的现有本地静态素材引用方式
+
+### 明确不改的部分
+
+- 页面路由结构
+- 接口地址、参数与返回处理
+- 训练开始、提交、结束、SOP 查询、总结卡生成等业务行为
+- 登录与岗位确认流程
+
+## 总体方案
+
+采用“视觉优先还原型”方案:
+
+- 保留现有业务结构和页面职责,不移动关键交互入口的语义。
+- 对三页的首屏结构、标题区、功能卡、按钮区、辅助信息区进行重排和统一。
+- 在共享样式层建立一套仅服务员工端三页的轻量视觉 token,包括背景、卡片、主色、辅助色、圆角、阴影、标题字号与留白节奏。
+- 对 `tabBar` 做有限统一,让页面主视觉与底部导航形成同一套产品感知。
+
+这条方案的核心是:让用户明显感觉“这是一套被设计过的产品”,而不是“旧页面换了点颜色”。
+
+## 共享视觉系统
+
+### 色彩
+
+- 主蓝:用于首页主入口、主要行动按钮、SOP 核心操作。
+- 辅橙:用于复盘、提醒、异议、次强调动作。
+- 辅绿:用于师傅、建议、可执行正向反馈。
+- 辅红:用于风险、命中红线、强提醒。
+- 大面积背景:使用偏暖的浅灰白底,避免纯白页面带来的生硬感。
+
+### 形态
+
+- 卡片统一使用大圆角、轻阴影、弱边框。
+- 主操作按钮统一做成更饱满的胶囊或大圆角矩形。
+- 标签、chips、状态 badge 使用明显但不过分抢眼的弱底色。
+- 页面内图文块尽量减少硬线分割,多用留白和卡片分区建立节奏。
+
+### 文本层级
+
+- 页面标题使用大号高对比字重。
+- 模块标题明显强于正文,但弱于页面标题。
+- 辅助说明统一压低饱和度,避免整页“处处都是重点”。
+
+### TabBar
+
+- 保留“今日 / 练 / 问 / 我”结构不变。
+- 调整选中态、阴影、顶部边界和留白,让其更接近设计稿中的轻质底栏。
+- 不改动页面跳转与当前 tab 逻辑。
+
+## 分页面设计
+
+### 1. 员工端首页 `today`
+
+#### 目标
+
+首页要尽量贴近 `h5-redesign-employee-home.png`:先建立“老师傅陪练”的第一印象,再把语音入口、今日安排、我的地图作为三个连续层级展开。
+
+#### 结构
+
+- 顶部使用浅背景氛围图区。
+- 左侧为岗位与欢迎信息,右下为较小的老师傅形象与气泡。
+- 头图区下方是大白卡语音入口,主入口明显、次入口收敛。
+- 再下方是快捷 chips。
+- 然后依次是“今日安排”“我的地图”“今日小贴士”。
+
+#### 关键调整
+
+- 头图不再追求“人物大图压场”,而是营造轻陪伴感。
+- “问老师傅”作为首要入口,视觉上高于“打字问”。
+- “今日安排”卡更像日程卡,而不是普通列表。
+- “我的地图”保留四色功能块,但主标题显示真实业务名,单字仅作装饰符号。
+- 整页通过更清晰的上下节奏建立首屏完成度。
+
+### 2. 问师傅页 `sop`
+
+#### 目标
+
+页内重点从“普通查询页”升级为“搜索驱动的知识助手页”,并尽量贴近 `h5-feedback-summary-modal.png` 的结构氛围。
+
+#### 结构
+
+- 顶部收成更轻的标题与搜索区。
+- 搜索框作为首屏主元素。
+- 查询结果区按“直给答案 -> 要点 -> 提醒 -> 引用依据”分层。
+- 总结卡入口保持单独区域。
+- 总结卡展示形态从普通 modal 调整为更贴近设计稿的底部浮层信息图风格。
+
+#### 关键调整
+
+- 减少当前大块头图区的存在感,把注意力转回搜索行为。
+- 查询按钮、快捷问题、答案卡的主次关系更明确。
+- 总结卡内部采用更强的信息图分组感,但不新增保存、听一遍、跟读等未实现业务按钮。
+- 若现阶段无法正确承载分类 tab,则不强行做功能性顶部分类。
+
+### 3. 训练页 `practice`
+
+#### 目标
+
+把页面从“功能堆叠页”调整为更像训练对话面板的页面,同时遵守“不做每轮真实评分”的约束。
+
+#### 结构
+
+- 顶部为训练标题、场景标签和老师傅陪伴状态。
+- 场景预习卡保留,但视觉上并入训练前准备区域。
+- 对话区强化三类角色的视觉差异:业主、我、老师傅。
+- 回复区的录音、转写、求助、提交按钮重新排版,但功能不变。
+- 教练提示与 SOP 提示更像辅助卡,而不是和主流程抢层级。
+- 结果卡在完成后仍独立呈现。
+
+#### 关键调整
+
+- 参考 `h5-feedback-voice-roleplay.png` 的对话氛围与角色卡感,但不照搬其每轮评分模块。
+- 让“老师傅提醒”成为视觉上可信赖的辅助块。
+- 保持现有训练逻辑顺序,不改开始、提交、结束时机。
+
+## 组件与样式边界
+
+本次优先在现有页面文件内完成视觉重排,不额外拆出大量新组件,理由如下:
+
+- 用户当前诉求是快速改善三页视觉,而不是先做前端组件体系建设。
+- 三页虽然要统一语言,但内容结构差异仍较大,过早抽象容易制造样式耦合。
+- 共享部分只抽到 `base.css` 的 token 和少量通用类,避免引入新的复杂度。
+
+允许的抽象上限:
+
+- 共享 token
+- 通用卡片/标题/按钮类
+- 少量重复且完全稳定的视觉辅助类
+
+不建议在本次新增多层组件封装。
+
+## 验收标准
+
+### 视觉验收
+
+- 三页首屏都明显接近参考设计稿的节奏与气质。
+- 员工端首页、SOP 页、训练页能够被一眼识别为同一套产品。
+- 老师傅、语音入口、核心行动按钮、模块标题的层级明显优于当前版本。
+- `tabBar` 与页面主视觉不再割裂。
+
+### 行为验收
+
+- 现有路由、接口、登录、训练、SOP 查询行为不回归。
+- 不引入假交互、不新增无后端支持的视觉功能。
+- 输入框与文本域在 H5 仍可正常聚焦、输入。
+
+### 技术验收
+
+- 至少完成类型检查、单测和 H5 构建验证。
+- 如新增与视觉约束有关的轻量回归测试,应优先覆盖容易再次被误改的关键点。
+
+## 风险与处理
+
+### 风险 1:现有本地素材不足以完全贴近设计稿
+
+处理:优先还原布局、比例、色彩和氛围;人物素材先做近似表达,不因素材不足阻塞页面改版。
+
+### 风险 2:为了追视觉而误伤业务逻辑
+
+处理:限定本次为视觉优先、交互最小改动,不重写流程函数和接口组织。
+
+### 风险 3:共享样式改动波及其它页面
+
+处理:共享样式只服务员工端相关页面,避免做全局侵入式 reset。
+
+### 风险 4:SOP 设计稿含未落地能力
+
+处理:只保留当前真实能力,视觉上做近似,不伪造功能。
+
+## 实施顺序建议
+
+1. 先整理员工端共享 token 和 tabBar 视觉基座。
+2. 完成员工端首页改版,建立整套视觉语言。
+3. 以首页风格为基准改 SOP 页。
+4. 最后改训练页,避免其复杂交互拖慢整体节奏。
+5. 跑类型检查、单测和 H5 构建验证。
+
+## 规格自检结论
+
+- 无占位符、TODO 或未定义术语。
+- 方案范围清晰,聚焦三页与少量共享视觉层。
+- 与用户已确认方向一致:优先还原视觉,业务交互最小改动。
+- 与项目评审约束一致:人物统一、首页人物缩量、训练页不做每轮真评分、SOP 不伪造分类能力。
diff --git a/mobile-uni/src/manifest.json b/mobile-uni/src/manifest.json
index 0759c5fe..ffb02892 100644
--- a/mobile-uni/src/manifest.json
+++ b/mobile-uni/src/manifest.json
@@ -2,6 +2,7 @@
"name": "银城员工端",
"appid": "__UNI__AIHR",
"description": "物业AI人力资源系统用户侧",
+ "vueVersion": "3",
"versionName": "0.1.0",
"versionCode": "100",
"uniStatistics": {
diff --git a/mobile-uni/src/pages.json b/mobile-uni/src/pages.json
index 9ef54f3d..7e62ca67 100644
--- a/mobile-uni/src/pages.json
+++ b/mobile-uni/src/pages.json
@@ -1,4 +1,10 @@
{
+ "easycom": {
+ "autoscan": true,
+ "custom": {
+ "^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue"
+ }
+ },
"pages": [
{
"path": "pages/user/today/index",
@@ -120,30 +126,42 @@
"navigationStyle": "custom",
"navigationBarTextStyle": "black",
"navigationBarTitleText": "银城员工端",
- "navigationBarBackgroundColor": "#f8fafc",
- "backgroundColor": "#f5f7fb"
+ "navigationBarBackgroundColor": "#f3f5f7",
+ "backgroundColor": "#f3f5f7"
},
"tabBar": {
- "color": "#64748b",
- "selectedColor": "#0f172a",
+ "color": "#7b8494",
+ "selectedColor": "#2f6bff",
"backgroundColor": "#ffffff",
"borderStyle": "white",
+ "height": "66px",
+ "fontSize": "12px",
+ "iconWidth": "25px",
+ "spacing": "3px",
"list": [
{
"pagePath": "pages/user/today/index",
- "text": "今日"
+ "text": "今日",
+ "iconPath": "static/tabbar/today-normal.png",
+ "selectedIconPath": "static/tabbar/today-active.png"
},
{
"pagePath": "pages/user/practice/index",
- "text": "练"
+ "text": "练",
+ "iconPath": "static/tabbar/practice-normal.png",
+ "selectedIconPath": "static/tabbar/practice-active.png"
},
{
"pagePath": "pages/user/sop/index",
- "text": "问"
+ "text": "问",
+ "iconPath": "static/tabbar/ask-normal.png",
+ "selectedIconPath": "static/tabbar/ask-active.png"
},
{
"pagePath": "pages/user/profile/index",
- "text": "我"
+ "text": "我",
+ "iconPath": "static/tabbar/profile-normal.png",
+ "selectedIconPath": "static/tabbar/profile-active.png"
}
]
}
diff --git a/mobile-uni/src/pages/user/practice/index.vue b/mobile-uni/src/pages/user/practice/index.vue
index 01ca7f9d..5c939cf9 100644
--- a/mobile-uni/src/pages/user/practice/index.vue
+++ b/mobile-uni/src/pages/user/practice/index.vue
@@ -1,13 +1,15 @@
-
+
- 场景练
- 按当前场景完成三角色对练,结束后自动评分并写入训练记录。
+ 数字师傅 · 场景训练
+ 场景模拟
+ 与业主、老师傅完成一轮真实工作对话,结束后自动写入训练记录。
+ 先看预习卡,再开始练习;红线命中时按老师傅建议重说本轮。
{{ message }}
-
+
需要登录
登录后查看今日任务、开始练习并保存训练记录。
@@ -15,41 +17,12 @@
-
-
- 今日任务
-
+
+
+ 今日小训
+ {{ activeScenarioName }}
+ {{ scenarioDescription }}
-
-
-
- {{ item.source === 'daily' ? '每日三题' : '主管专项' }}
- {{ item.question || item.scenarioName }}
- {{ assignmentDesc(item) }}
- 参考:{{ item.referenceAnswer }}
-
-
-
-
-
-
-
-
- 暂无今日任务
-
-
-
- 今日小训
- {{ activeScenarioName }}
- {{ scenarioDescription }}
训练前预习卡
@@ -78,9 +51,43 @@
-
+
+
+ 今日任务
+
+
+
+
+
+ {{ item.source === 'daily' ? '每日三题' : '主管专项' }}
+ {{ item.question || item.scenarioName }}
+ {{ assignmentDesc(item) }}
+ 参考:{{ item.referenceAnswer }}
+
+
+
+
+
+
+
+
+ 暂无今日任务
+
+
+
- 对话
+
+ 对话
+ 保持承接情绪、说明动作、给出反馈节点。
+
第 {{ Math.min(roundIndex + 1, 3) }} 轮
@@ -117,16 +124,16 @@
-
+
你的回应
-
-
+
SOP 提示
{{ helpAnswer }}
@@ -151,7 +158,7 @@
-
+
本次评分
{{ result.totalScore }}
@@ -625,6 +632,7 @@ const submit = async () => {
message.value = '';
const audioUrl = draftAudioUrl.value || undefined;
const audioOssId = draftAudioOssId.value;
+ const previousTurns = turns.value.slice();
draft.value = '';
clearDraftAudio();
const retryingRedFlag = redFlagActive.value && lastCoachRoundIndex.value === roundIndex.value;
@@ -659,6 +667,7 @@ const submit = async () => {
status.value = 'active';
} catch (error) {
status.value = 'active';
+ turns.value = previousTurns;
draft.value = text;
draftAudioUrl.value = audioUrl || '';
draftAudioOssId.value = audioOssId;
@@ -704,21 +713,114 @@ onShow(() => {
diff --git a/mobile-uni/src/pages/user/sop/index.vue b/mobile-uni/src/pages/user/sop/index.vue
index fbd8984b..f6f04bc7 100644
--- a/mobile-uni/src/pages/user/sop/index.vue
+++ b/mobile-uni/src/pages/user/sop/index.vue
@@ -1,27 +1,32 @@
-
- 问师傅
- 直接问工作中的 SOP、话术和流程。答案来自知识库检索,引用在后面。
+
+
+ SOP · 话术 · 工作流程
+ 问师傅
+ 直接问工作中的 SOP、话术和流程。答案附带可追溯的知识库依据。
+ 先把问题问清楚,再按需生成总结卡,不伪造分类能力。
+
-
+
+ 常见问题可以直接点下面快捷问法,结果会保留引用依据。
{{ item }}
-
- {{ loading ? '检索中' : '查询' }}
+
+ {{ loading ? '检索中' : '问老师傅' }}
{{ message }}
-
+
需要登录
登录后才能查询 SOP、生成总结卡并反馈答案。
@@ -29,7 +34,7 @@
去登录
-
+
直给答案
{{ result.answer || '未生成直接答案,请查看下方引用。' }}
@@ -54,16 +59,16 @@
- 不准
+ 需要复核
-
+
引用依据
{{ snippet.docTitle }}
@@ -72,35 +77,81 @@
-
+
总结卡
- 需要时再生成,避免每次查询都调用模型。
+ 需要时再生成,适合把一段 SOP 整理成步骤卡、话术卡和提醒卡。
{{ summaryLoading ? '生成中' : '生成总结卡' }}
-
- {{ summaryCard.title }}
-
- 步骤
-
- {{ index + 1 }}. {{ step.label }}: {{ step.desc }}
-
-
-
- 可说话术
- {{ item }}
-
-
- 异议回应
-
- {{ item.concern }}: {{ item.response }}
-
-
-
- 提醒
- {{ item }}
+
+
+
+
+
+
+ 1
+ 核心步骤
+
+
+
+ {{ index + 1 }}
+
+ {{ step.label }}
+ {{ step.desc }}
+
+
+
+
+
+
+ 2
+ 推荐话术
+
+
+
+ “
+ {{ script }}
+
+
+
+
+
+ 3
+ 常见异议与回应
+
+
+
+
+ 问
+ {{ obj.concern }}
+
+
+ 答
+ {{ obj.response }}
+
+
+
+
+
+
+ 4
+ 师傅提醒
+
+
+
+ {{ rem }}
+
+
+
+
+
@@ -259,6 +310,10 @@ const buildSummaryCard = async () => {
}
};
+const closeSummary = () => {
+ summaryCard.value = null;
+};
+
const goLogin = () => {
rememberLoginRedirect('/pages/user/sop/index');
uni.navigateTo({ url: '/pages/auth/login/index' });
@@ -297,7 +352,53 @@ onShow(() => {
diff --git a/mobile-uni/src/pages/user/today/index.vue b/mobile-uni/src/pages/user/today/index.vue
index 042adf5f..ad102020 100644
--- a/mobile-uni/src/pages/user/today/index.vue
+++ b/mobile-uni/src/pages/user/today/index.vue
@@ -1,9 +1,12 @@
-
+
{{ message }}
+
+
+ 数字师傅 · 今日带练
{{ positionProfile.value }}
{{ positionProfile.heroTitle }}
有问题,随时问老师傅
@@ -16,8 +19,13 @@
- 问
- 问老师傅
+
+ 问
+
+
+ 问老师傅
+ 语音或文字都可以
+
键
@@ -33,7 +41,7 @@
-
+
登录后查看今日安排
训练任务、复习提醒和成长数据会按你的手机号展示。
@@ -41,11 +49,14 @@
去登录
-
+
-
- 日
- 今日安排
+
+
+ 日
+ 今日安排
+
+ 先完成训练,再补一轮复习,今天的学习节奏就完整了。
查看全部
@@ -69,11 +80,14 @@
-
+
-
- 图
- {{ loggedIn ? '我的地图' : '常用入口' }}
+
+
+ 图
+ {{ loggedIn ? '我的地图' : '常用入口' }}
+
+ 把今天最常用的问、练、学、报都放在手边。
@@ -84,7 +98,7 @@
:class="tile.tone"
@click="openTool(tile.title)"
>
-
+ {{ tile.shortTitle }}
{{ tile.title }}
{{ tile.desc }}
@@ -123,13 +137,6 @@ const primaryTipQuestion = computed(() => quickQuestions.value[0] || '岗位SOP'
const pendingSopQueryKey = 'aihr_sop_pending_query';
const pendingAssignmentKey = 'aihr_pending_assignment_id';
-const iconFor = (title: string) => {
- if (title.includes('SOP') || title.includes('查')) return '/static/map-icons/ask.png';
- if (title.includes('练')) return '/static/map-icons/practice.png';
- if (title.includes('学') || title.includes('案例')) return '/static/map-icons/learn.png';
- return '/static/map-icons/report.png';
-};
-
const toneFor = (title: string) => {
if (title.includes('SOP') || title.includes('查')) return 'ask';
if (title.includes('练')) return 'practice';
@@ -137,14 +144,28 @@ const toneFor = (title: string) => {
return 'report';
};
+const shortTitleFor = (tone: string) => {
+ if (tone === 'ask') return '问';
+ if (tone === 'practice') return '练';
+ if (tone === 'learn') return '学';
+ return '报';
+};
+
+const toolOrder = ['ask', 'practice', 'learn', 'report'];
+
const mapTiles = computed(() => {
if (!home.value?.tools?.length) return [];
- return home.value.tools.map((item) => ({
- title: item.title,
- desc: item.desc.replace(/\n/g, ' / '),
- tone: toneFor(item.title),
- icon: iconFor(item.title)
- }));
+ return home.value.tools
+ .map((item) => {
+ const tone = toneFor(item.title);
+ return {
+ title: item.title,
+ shortTitle: shortTitleFor(tone),
+ desc: item.desc.replace(/\n/g, ' / '),
+ tone
+ };
+ })
+ .sort((left, right) => toolOrder.indexOf(left.tone) - toolOrder.indexOf(right.tone));
});
const primaryAssignment = computed(() =>
@@ -261,169 +282,261 @@ onShow(() => {
diff --git a/mobile-uni/src/static/brand-logo.png b/mobile-uni/src/static/brand-logo.png
new file mode 100644
index 00000000..81f13046
Binary files /dev/null and b/mobile-uni/src/static/brand-logo.png differ
diff --git a/mobile-uni/src/static/tabbar/LICENSE-lucide.txt b/mobile-uni/src/static/tabbar/LICENSE-lucide.txt
new file mode 100644
index 00000000..029af513
--- /dev/null
+++ b/mobile-uni/src/static/tabbar/LICENSE-lucide.txt
@@ -0,0 +1,16 @@
+Lucide icons
+Copyright (c) 2020 Lucide Contributors
+
+ISC License
+
+Permission to use, copy, modify, and distribute this software for any purpose
+with or without fee is hereby granted, provided that the above copyright notice
+and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
diff --git a/mobile-uni/src/static/tabbar/ask-active.png b/mobile-uni/src/static/tabbar/ask-active.png
new file mode 100644
index 00000000..f137618b
Binary files /dev/null and b/mobile-uni/src/static/tabbar/ask-active.png differ
diff --git a/mobile-uni/src/static/tabbar/ask-normal.png b/mobile-uni/src/static/tabbar/ask-normal.png
new file mode 100644
index 00000000..e4a13ec3
Binary files /dev/null and b/mobile-uni/src/static/tabbar/ask-normal.png differ
diff --git a/mobile-uni/src/static/tabbar/practice-active.png b/mobile-uni/src/static/tabbar/practice-active.png
new file mode 100644
index 00000000..a2d6338d
Binary files /dev/null and b/mobile-uni/src/static/tabbar/practice-active.png differ
diff --git a/mobile-uni/src/static/tabbar/practice-normal.png b/mobile-uni/src/static/tabbar/practice-normal.png
new file mode 100644
index 00000000..b0b382af
Binary files /dev/null and b/mobile-uni/src/static/tabbar/practice-normal.png differ
diff --git a/mobile-uni/src/static/tabbar/profile-active.png b/mobile-uni/src/static/tabbar/profile-active.png
new file mode 100644
index 00000000..df5fb8c8
Binary files /dev/null and b/mobile-uni/src/static/tabbar/profile-active.png differ
diff --git a/mobile-uni/src/static/tabbar/profile-normal.png b/mobile-uni/src/static/tabbar/profile-normal.png
new file mode 100644
index 00000000..f91bb8f3
Binary files /dev/null and b/mobile-uni/src/static/tabbar/profile-normal.png differ
diff --git a/mobile-uni/src/static/tabbar/today-active.png b/mobile-uni/src/static/tabbar/today-active.png
new file mode 100644
index 00000000..1c3b52a1
Binary files /dev/null and b/mobile-uni/src/static/tabbar/today-active.png differ
diff --git a/mobile-uni/src/static/tabbar/today-normal.png b/mobile-uni/src/static/tabbar/today-normal.png
new file mode 100644
index 00000000..b8f67e26
Binary files /dev/null and b/mobile-uni/src/static/tabbar/today-normal.png differ
diff --git a/mobile-uni/src/styles/base.css b/mobile-uni/src/styles/base.css
index 192b7135..19e56a69 100644
--- a/mobile-uni/src/styles/base.css
+++ b/mobile-uni/src/styles/base.css
@@ -1,17 +1,41 @@
page {
min-height: 100%;
- background: #f5f7fb;
- color: #172033;
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+ background: #f3f5f7;
+ color: #182230;
+ font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Segoe UI", sans-serif;
}
:root {
--mobile-shell-max-width: 430px;
+ --surface: #ffffff;
+ --canvas: #f3f5f7;
+ --ink: #182230;
+ --muted: #647083;
+ --line: #dfe4ea;
+ --brand: #e70014;
+ --brand-pressed: #bd0010;
+ --knowledge: #315b8f;
+ --success: #168243;
+ --warning: #a86100;
+ --radius: 8px;
+ --employee-bg: #f4f6fb;
+ --employee-bg-soft: #f8fbff;
+ --employee-surface: #ffffff;
+ --employee-primary: #2f6bff;
+ --employee-primary-soft: #eaf1ff;
+ --employee-success: #34b368;
+ --employee-warning: #ff9b3d;
+ --employee-danger: #ff6b5f;
+ --employee-text: #182230;
+ --employee-muted: #6b7280;
+ --employee-line: rgba(47, 74, 117, 0.08);
+ --employee-card-radius: 24px;
+ --employee-chip-radius: 18px;
+ --employee-card-shadow: 0 16px 40px rgba(47, 107, 255, 0.1);
}
view,
text,
-input,
button {
box-sizing: border-box;
}
@@ -19,9 +43,9 @@ button {
button {
margin: 0;
border: 0;
- border-radius: 12px;
+ border-radius: var(--radius);
font-size: 16px;
- line-height: 1.2;
+ line-height: 1.25;
}
button::after {
@@ -30,29 +54,63 @@ button::after {
.page {
min-height: 100vh;
- padding: 20px 16px 32px;
+ padding: 22px 16px 96px;
+}
+
+.employee-page-shell {
+ background: linear-gradient(180deg, var(--employee-bg-soft) 0%, var(--employee-bg) 100%);
+}
+
+.employee-card {
+ border: 1px solid var(--employee-line);
+ border-radius: var(--employee-card-radius);
+ background: var(--employee-surface);
+ box-shadow: var(--employee-card-shadow);
+}
+
+.employee-kicker {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ color: var(--employee-primary);
+ font-size: 12px;
+ font-weight: 800;
+ letter-spacing: 0.08em;
+}
+
+.employee-section-title {
+ color: var(--employee-text);
+ font-size: 20px;
+ font-weight: 900;
+ line-height: 1.2;
+}
+
+.employee-section-subtitle {
+ color: var(--employee-muted);
+ font-size: 14px;
+ line-height: 1.55;
}
.page-title {
display: block;
- font-size: 28px;
- font-weight: 800;
- line-height: 1.15;
- color: #0f172a;
+ font-size: 26px;
+ font-weight: 900;
+ line-height: 1.2;
+ color: var(--ink);
}
.page-subtitle {
display: block;
- margin-top: 8px;
- font-size: 15px;
- line-height: 1.6;
- color: #64748b;
+ margin-top: 6px;
+ font-size: 14px;
+ line-height: 1.55;
+ color: var(--muted);
}
.subpage-header {
display: flex;
gap: 12px;
- align-items: flex-start;
+ align-items: center;
}
.subpage-heading {
@@ -62,66 +120,68 @@ button::after {
.subpage-back-button {
display: flex;
- flex: 0 0 44px;
- width: 44px;
- min-width: 44px;
- height: 44px;
- min-height: 44px;
+ flex: 0 0 40px;
+ width: 40px;
+ min-width: 40px;
+ height: 40px;
+ min-height: 40px;
align-items: center;
justify-content: center;
padding: 0;
- border: 1px solid #e2e8f0;
- border-radius: 8px;
- background: #ffffff;
- color: #1e293b;
- font-size: 24px;
+ border: 1px solid var(--line);
+ border-radius: var(--radius);
+ background: var(--surface);
+ color: var(--ink);
+ font-size: 22px;
font-weight: 700;
line-height: 1;
}
.panel {
margin-top: 16px;
- padding: 18px;
- border: 1px solid #e2e8f0;
- border-radius: 14px;
- background: #ffffff;
+ padding: 16px;
+ border: 1px solid var(--line);
+ border-radius: var(--radius);
+ background: var(--surface);
+ box-shadow: 0 1px 2px rgba(24, 34, 48, 0.04);
}
.panel-title {
- font-size: 18px;
- font-weight: 800;
- color: #1e293b;
+ font-size: 17px;
+ font-weight: 900;
+ color: var(--ink);
}
.muted {
- color: #64748b;
+ color: var(--muted);
}
.primary-button,
.secondary-button,
.ghost-button {
display: flex;
- min-height: 48px;
+ min-height: 46px;
align-items: center;
justify-content: center;
padding: 0 16px;
- font-weight: 700;
+ font-weight: 800;
}
.primary-button {
- background: #e60012;
+ background: var(--brand);
color: #ffffff;
+ box-shadow: 0 4px 10px rgba(231, 0, 20, 0.16);
}
.secondary-button {
- border: 1px solid #cbd5e1;
- background: #ffffff;
- color: #1e293b;
+ border: 1px solid #c9d1db;
+ background: var(--surface);
+ color: var(--ink);
}
.ghost-button {
- background: #eff6ff;
- color: #1d4ed8;
+ background: #eef3f9;
+ color: #38506c;
}
.primary-button[disabled],
@@ -132,12 +192,12 @@ button::after {
.input {
width: 100%;
- min-height: 48px;
+ min-height: 46px;
padding: 0 14px;
- border: 1px solid #cbd5e1;
- border-radius: 12px;
- background: #ffffff;
- color: #0f172a;
+ border: 1px solid #c9d1db;
+ border-radius: var(--radius);
+ background: var(--surface);
+ color: var(--ink);
font-size: 16px;
}
@@ -145,10 +205,10 @@ button::after {
width: 100%;
min-height: 112px;
padding: 12px 14px;
- border: 1px solid #cbd5e1;
- border-radius: 12px;
- background: #ffffff;
- color: #0f172a;
+ border: 1px solid #c9d1db;
+ border-radius: var(--radius);
+ background: var(--surface);
+ color: var(--ink);
font-size: 16px;
line-height: 1.5;
}
@@ -165,26 +225,30 @@ button::after {
}
.chip {
- min-height: 34px;
+ min-height: 36px;
padding: 0 12px;
- border-radius: 999px;
- background: #f1f5f9;
- color: #334155;
+ border: 1px solid #dbe1e8;
+ border-radius: var(--radius);
+ background: #f8fafc;
+ color: #3d4b5f;
font-size: 14px;
+ font-weight: 700;
}
.status-message {
margin-top: 12px;
padding: 10px 12px;
- border-radius: 10px;
- background: #fff7ed;
- color: #9a3412;
+ border: 1px solid #f0d7a7;
+ border-left: 3px solid #d38a13;
+ border-radius: var(--radius);
+ background: #fffaf0;
+ color: #80520f;
font-size: 14px;
line-height: 1.5;
}
.supervisor-access-panel {
- border-left: 4px solid #e60012;
+ border-left: 4px solid var(--brand);
}
.supervisor-access-copy {
@@ -209,11 +273,22 @@ button::after {
}
.empty {
- padding: 24px 12px;
- color: #64748b;
+ padding: 28px 12px;
+ color: var(--muted);
text-align: center;
}
+.primary-button:active {
+ background: var(--brand-pressed);
+}
+
+uni-tabbar.uni-tabbar-bottom,
+uni-tabbar.uni-tabbar-bottom .uni-tabbar {
+ border-top: 1px solid var(--line) !important;
+ background: rgba(255, 255, 255, 0.98) !important;
+ box-shadow: 0 -10px 30px rgba(47, 107, 255, 0.08);
+}
+
@media (min-width: 520px) {
:root {
--mobile-shell-side-offset: calc((100vw - var(--mobile-shell-max-width)) / 2);
@@ -234,8 +309,8 @@ button::after {
uni-app {
min-height: 100vh;
- background: #f5f7fb;
- box-shadow: 0 0 0 1px rgba(15, 23, 42, 0.06);
+ background: var(--canvas);
+ box-shadow: 0 0 0 1px rgba(24, 34, 48, 0.07), 0 16px 42px rgba(24, 34, 48, 0.1);
}
uni-tabbar.uni-tabbar-bottom {
diff --git a/mobile-uni/tests/mobile-user-pages.test.mjs b/mobile-uni/tests/mobile-user-pages.test.mjs
new file mode 100644
index 00000000..37d89a0b
--- /dev/null
+++ b/mobile-uni/tests/mobile-user-pages.test.mjs
@@ -0,0 +1,99 @@
+import assert from 'node:assert/strict';
+import { readFile } from 'node:fs/promises';
+import test from 'node:test';
+
+const pageSource = async (path) => readFile(new URL(path, import.meta.url), 'utf8');
+
+test('全局基础样式不对输入控件设置 border-box', async () => {
+ const source = await pageSource('../src/styles/base.css');
+
+ assert.doesNotMatch(source, /view,\s*\ntext,\s*\ninput,/);
+ assert.doesNotMatch(source, /view,\s*\ntext,\s*\ntextarea,/);
+ assert.doesNotMatch(source, /(^|\n)input\b[\s\S]*?box-sizing:\s*border-box/m);
+ assert.doesNotMatch(source, /(^|\n)textarea\b[\s\S]*?box-sizing:\s*border-box/m);
+});
+
+test('SOP 查询框不再覆盖 textarea 的 box-sizing', async () => {
+ const source = await pageSource('../src/pages/user/sop/index.vue');
+ const queryPanelStyle = source.match(/\.query-panel \.textarea \{[\s\S]*?\n\}/)?.[0] || '';
+
+ assert.equal(queryPanelStyle.includes('box-sizing'), false);
+});
+
+test('员工首页地图主标题保留真实工具名称', async () => {
+ const source = await pageSource('../src/pages/user/today/index.vue');
+
+ assert.match(source, /\{\{ tile\.shortTitle \}\}<\/text>/);
+ assert.match(source, /\{\{ tile\.title \}\}<\/text>/);
+});
+
+test('训练页提交失败会回滚本次乐观追加的对话', async () => {
+ const source = await pageSource('../src/pages/user/practice/index.vue');
+
+ assert.match(source, /const previousTurns = turns\.value\.slice\(\)/);
+ assert.match(source, /turns\.value = previousTurns/);
+});
+
+test('manifest 明确声明为 uni-app Vue3 CLI 工程', async () => {
+ const source = await pageSource('../src/manifest.json');
+
+ assert.match(source, /"vueVersion"\s*:\s*"3"/);
+});
+
+test('员工端 tabBar 继续保留四个固定入口', async () => {
+ const source = await pageSource('../src/pages.json');
+
+ assert.match(source, /"selectedColor"\s*:\s*"#2f6bff"/);
+ assert.match(source, /pages\/user\/today\/index/);
+ assert.match(source, /pages\/user\/practice\/index/);
+ assert.match(source, /pages\/user\/sop\/index/);
+ assert.match(source, /pages\/user\/profile\/index/);
+});
+
+test('共享视觉基座包含员工端主色和卡片圆角 token', async () => {
+ const source = await pageSource('../src/styles/base.css');
+
+ assert.match(source, /--employee-primary:\s*#2f6bff/);
+ assert.match(source, /--employee-card-radius:\s*24px/);
+ assert.match(source, /\.employee-card\s*\{/);
+});
+
+test('员工端三页接入共享页面和卡片基座', async () => {
+ const [todaySource, sopSource, practiceSource] = await Promise.all([
+ pageSource('../src/pages/user/today/index.vue'),
+ pageSource('../src/pages/user/sop/index.vue'),
+ pageSource('../src/pages/user/practice/index.vue')
+ ]);
+
+ assert.match(todaySource, /employee-page-shell/);
+ assert.match(todaySource, /section-card employee-card/);
+ assert.match(sopSource, /query-panel employee-card/);
+ assert.match(practiceSource, /scenario-card employee-card/);
+});
+
+test('员工首页保留师傅区、语音条、今日安排、我的地图四段结构', async () => {
+ const source = await pageSource('../src/pages/user/today/index.vue');
+
+ assert.match(source, /mentor-hero/);
+ assert.match(source, /voice-panel/);
+ assert.match(source, /今日安排/);
+ assert.match(source, /我的地图/);
+});
+
+test('问师傅页保留搜索框、答案区和总结卡入口', async () => {
+ const source = await pageSource('../src/pages/user/sop/index.vue');
+
+ assert.match(source, /query-panel/);
+ assert.match(source, /answer-panel/);
+ assert.match(source, /summary-panel/);
+});
+
+test('训练页保留任务卡、对话区、回复区和结果区', async () => {
+ const source = await pageSource('../src/pages/user/practice/index.vue');
+
+ assert.match(source, /scenario-card/);
+ assert.match(source, /assignment-panel/);
+ assert.match(source, /dialogue-card/);
+ assert.match(source, /reply-card/);
+ assert.match(source, /result-card/);
+});