# 前端医疗科技化重构实现计划 > **面向 AI 代理的工作者:** 必需子技能:使用 superpowers:subagent-driven-development(推荐)或 superpowers:executing-plans 逐任务实现此计划。步骤使用复选框(`- [ ]`)语法来跟踪进度。 **目标:** 将现有 OKR 绩效系统前端重构为现代医疗科技型内部工作台,保留业务流程和接口契约不变。 **架构:** 先建立全局设计 token、Element Plus 覆盖和可复用业务样式,再逐页迁移核心页面。自动化验证使用一个 Node 脚本约束 token、emoji、圆角、阴影和构建入口,视觉验证用 Vite dev server + 浏览器截图检查。 **技术栈:** Vue 3、Vite、Element Plus、Pinia、Vue Router、Node.js 验证脚本、CSS custom properties。 --- ## 文件结构 创建: - `frontend/src/styles/theme.css`:设计 token、全局基础、Element Plus 变量覆盖。 - `frontend/src/styles/components.css`:页面标题、面板、状态条、指标、空状态、审计提示等业务样式模式。 - `frontend/src/styles/element-plus.css`:按钮、表格、标签、弹窗、表单等 Element Plus 覆盖。 - `frontend/scripts/verify-medtech-theme.mjs`:自动化扫描脚本,检查核心约束。 修改: - `frontend/src/main.js`:引入新增样式入口。 - `frontend/src/App.vue`:移除分散 Element Plus 变量,保留基础根组件。 - `frontend/src/components/MainLayout.vue`:主布局医疗科技化。 - `frontend/src/components/PeriodBanner.vue`:周期状态条统一。 - `frontend/src/views/auth/LoginView.vue`:登录页改为内部系统入口。 - `frontend/src/views/period/PeriodListView.vue`:周期生命周期工作台。 - `frontend/src/views/okr/MyOkrView.vue`:OKR 页面去 emoji、强化对齐和审核结构。 - `frontend/src/views/scoring/ScoringEditView.vue`:评审工作台。 - `frontend/src/views/scoring/MyResultView.vue`:结果页结构化。 - `frontend/src/views/scoring/HistoryView.vue`:历史结果归档语义。 - `frontend/src/views/scoring/ScoringListView.vue`:列表页统一标题、表格、空状态。 - `frontend/src/views/feedback/FeedbackView.vue`:统一标题、表格、空状态。 - `frontend/src/views/org/OrgView.vue`:统一管理页面板。 - `frontend/src/views/system/LogView.vue`:统一日志表格。 - `frontend/src/views/system/DimensionConfigView.vue`:统一配置页。 - `frontend/src/views/period/KpiTemplateView.vue`:统一模板页。 - `frontend/src/views/okr/TeamOkrView.vue`:统一团队 OKR 列表样式。 不修改: - `frontend/src/api/index.js` - `frontend/src/router/index.js` - `frontend/src/stores/auth.js` - 后端 Java、数据库、schema、评分公式。 ## 任务 1:建立自动化视觉约束测试 **文件:** - 创建:`frontend/scripts/verify-medtech-theme.mjs` - 修改:`frontend/package.json` - [ ] **步骤 1:编写失败的验证脚本** 创建 `frontend/scripts/verify-medtech-theme.mjs`: ```js import { readFileSync, readdirSync, statSync } from 'node:fs' import { join, relative } from 'node:path' const root = new URL('..', import.meta.url).pathname const src = join(root, 'src') const requiredFiles = [ 'src/styles/theme.css', 'src/styles/components.css', 'src/styles/element-plus.css' ] const requiredTokens = [ '--color-brand: #2b1f99', '--color-accent: #3ad4d8', '--color-bg:', '--color-surface:', '--color-border:', '--color-text:', '--radius-xs: 4px', '--radius-sm: 6px', '--radius-md: 8px' ] const bannedEmoji = /[📋📌⚡⏳✅❌🎯🚀💡🔥]/ const bannedDecor = /(radial-gradient|gradient orb|bokeh|blur\(60px\)|border-radius:\s*(1[2-9]|[2-9]\d)px)/ function walk(dir) { return readdirSync(dir).flatMap((name) => { const path = join(dir, name) return statSync(path).isDirectory() ? walk(path) : [path] }) } const failures = [] for (const file of requiredFiles) { try { readFileSync(join(root, file), 'utf8') } catch { failures.push(`missing required style file: ${file}`) } } const themePath = join(root, 'src/styles/theme.css') let theme = '' try { theme = readFileSync(themePath, 'utf8') } catch {} for (const token of requiredTokens) { if (!theme.includes(token)) failures.push(`missing theme token: ${token}`) } const main = readFileSync(join(root, 'src/main.js'), 'utf8') for (const file of requiredFiles) { const importPath = './' + file.replace('src/', '') if (!main.includes(importPath)) failures.push(`main.js must import ${importPath}`) } const vueAndCssFiles = walk(src).filter((file) => /\.(vue|css)$/.test(file)) for (const file of vueAndCssFiles) { const text = readFileSync(file, 'utf8') const rel = relative(root, file) if (bannedEmoji.test(text)) failures.push(`banned emoji in ${rel}`) if (bannedDecor.test(text)) failures.push(`banned decorative style in ${rel}`) } if (failures.length) { console.error(failures.join('\n')) process.exit(1) } console.log('medtech theme checks passed') ``` - [ ] **步骤 2:运行验证并确认失败** 运行: ```bash cd frontend node scripts/verify-medtech-theme.mjs ``` 预期:FAIL,至少报出缺失 `src/styles/theme.css`、`components.css`、`element-plus.css`。 - [ ] **步骤 3:添加 package 脚本** 修改 `frontend/package.json` 的 `scripts`: ```json { "dev": "vite", "build": "vite build", "preview": "vite preview", "verify:theme": "node scripts/verify-medtech-theme.mjs" } ``` - [ ] **步骤 4:Commit** ```bash git add frontend/scripts/verify-medtech-theme.mjs frontend/package.json git commit -m "test: add medtech frontend theme checks" ``` ## 任务 2:新增全局主题和样式入口 **文件:** - 创建:`frontend/src/styles/theme.css` - 创建:`frontend/src/styles/components.css` - 创建:`frontend/src/styles/element-plus.css` - 修改:`frontend/src/main.js` - 修改:`frontend/src/App.vue` - [ ] **步骤 1:运行主题验证确认红灯** 运行: ```bash cd frontend npm run verify:theme ``` 预期:FAIL,报缺少样式文件和 token。 - [ ] **步骤 2:创建 `theme.css`** 核心内容: ```css :root { --color-brand: #2b1f99; --color-brand-strong: #1d166b; --color-brand-muted: #33269f; --color-brand-soft: #eceaf8; --color-accent: #3ad4d8; --color-accent-strong: #17a9ad; --color-accent-soft: #e5fbfc; --color-bg: #f5f8fa; --color-surface: #ffffff; --color-surface-subtle: #f8fafc; --color-border: #dfe7ee; --color-border-soft: #edf2f6; --color-text: #18212f; --color-text-secondary: #5f6b7a; --color-text-muted: #8a97a8; --color-success: #2e9d65; --color-warning: #c98219; --color-danger: #c94a4a; --color-info: #4c6f91; --radius-xs: 4px; --radius-sm: 6px; --radius-md: 8px; --shadow-panel: 0 1px 2px rgba(15, 23, 42, 0.04); --shadow-popover: 0 12px 28px rgba(15, 23, 42, 0.12); --layout-sidebar: 220px; --layout-topbar: 52px; --el-color-primary: var(--color-brand); --el-border-radius-base: var(--radius-sm); } * { box-sizing: border-box; } html, body, #app { min-height: 100%; } body { margin: 0; color: var(--color-text); background: var(--color-bg); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; font-size: 14px; } button, input, textarea, select { font: inherit; } ``` - [ ] **步骤 3:创建 `components.css`** 包含: ```css .app-page { width: 100%; } .app-page-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 18px; } .app-page-title { margin: 0; color: var(--color-text); font-size: 24px; font-weight: 700; letter-spacing: 0; } .app-page-subtitle { margin: 4px 0 0; color: var(--color-text-secondary); font-size: 13px; } .med-panel { background: var(--color-surface); border: 1px solid var(--color-border-soft); border-radius: var(--radius-md); box-shadow: var(--shadow-panel); } .med-panel__header { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 16px; border-bottom: 1px solid var(--color-border-soft); } .med-panel__title { color: var(--color-text); font-size: 15px; font-weight: 700; } .med-panel__body { padding: 16px; } .status-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--color-info); } .status-dot.is-active { background: var(--color-accent); } .status-dot.is-success { background: var(--color-success); } .status-dot.is-warning { background: var(--color-warning); } .status-dot.is-danger { background: var(--color-danger); } .empty-state { display: flex; align-items: center; justify-content: center; min-height: 260px; color: var(--color-text-muted); background: var(--color-surface); border: 1px dashed var(--color-border); border-radius: var(--radius-md); } ``` - [ ] **步骤 4:创建 `element-plus.css`** 包含: ```css .el-button { border-radius: var(--radius-sm); } .el-tag { border-radius: var(--radius-xs); font-weight: 600; } .el-table { --el-table-header-bg-color: var(--color-surface-subtle); --el-table-border-color: var(--color-border-soft); color: var(--color-text); border-radius: var(--radius-md); } .el-table th.el-table__cell { color: var(--color-text-secondary); font-weight: 700; } .el-dialog { border-radius: var(--radius-md); } .el-input__wrapper, .el-textarea__inner { border-radius: var(--radius-sm); } ``` - [ ] **步骤 5:引入样式入口** 修改 `frontend/src/main.js`: ```js import 'element-plus/dist/index.css' import './styles/theme.css' import './styles/components.css' import './styles/element-plus.css' ``` - [ ] **步骤 6:简化 `App.vue`** 保留: ```vue ``` - [ ] **步骤 7:运行验证确认通过** 运行: ```bash cd frontend npm run verify:theme npm run build ``` 预期:两个命令 exit 0。 - [ ] **步骤 8:Commit** ```bash git add frontend/src/styles frontend/src/main.js frontend/src/App.vue git commit -m "style: add medtech frontend design tokens" ``` ## 任务 3:重构主布局和登录页 **文件:** - 修改:`frontend/src/components/MainLayout.vue` - 修改:`frontend/src/views/auth/LoginView.vue` - [ ] **步骤 1:扩展验证脚本红灯** 在 `frontend/scripts/verify-medtech-theme.mjs` 增加检查: ```js const layout = readFileSync(join(root, 'src/components/MainLayout.vue'), 'utf8') const login = readFileSync(join(root, 'src/views/auth/LoginView.vue'), 'utf8') if (!layout.includes('clinical-shell')) failures.push('MainLayout must use clinical-shell') if (!layout.includes('clinical-sidebar')) failures.push('MainLayout must use clinical-sidebar') if (!login.includes('internal-login')) failures.push('LoginView must use internal-login') if (login.includes('radial-gradient')) failures.push('LoginView must not use radial-gradient') ``` 运行: ```bash cd frontend npm run verify:theme ``` 预期:FAIL,报缺少 `clinical-shell`、`clinical-sidebar`、`internal-login`。 - [ ] **步骤 2:改造 `MainLayout.vue` 模板类名** 关键结构: ```vue
医梦AI OKR 绩效管理
...
``` 保留现有菜单、通知、轮询和退出逻辑。 - [ ] **步骤 3:改造 `MainLayout.vue` 样式** 样式目标: ```css .clinical-shell { height: 100vh; color: var(--color-text); } .clinical-container { height: 100vh; } .clinical-sidebar { background: linear-gradient(180deg, var(--color-brand-strong), #120d4a); overflow-y: auto; transition: width 0.2s ease; } .clinical-topbar { height: var(--layout-topbar); display: flex; align-items: center; justify-content: space-between; background: var(--color-surface); border-bottom: 1px solid var(--color-border); padding: 0 20px; } .clinical-main { min-height: calc(100vh - var(--layout-topbar)); padding: 20px 24px; background: var(--color-bg); } ``` - [ ] **步骤 4:改造 `LoginView.vue`** 关键结构: ```vue

OKR 绩效管理系统

医梦AI 内部绩效运营工作台

...
``` 禁止保留暗色 radial 背景和大尺寸赛博风卡片。 - [ ] **步骤 5:验证** 运行: ```bash cd frontend npm run verify:theme npm run build ``` 预期:两个命令 exit 0。 - [ ] **步骤 6:Commit** ```bash git add frontend/scripts/verify-medtech-theme.mjs frontend/src/components/MainLayout.vue frontend/src/views/auth/LoginView.vue git commit -m "style: redesign shell and login for medtech workspace" ``` ## 任务 4:重构周期工作台和周期状态条 **文件:** - 修改:`frontend/src/views/period/PeriodListView.vue` - 修改:`frontend/src/components/PeriodBanner.vue` - [ ] **步骤 1:扩展验证脚本红灯** 增加: ```js const periodList = readFileSync(join(root, 'src/views/period/PeriodListView.vue'), 'utf8') const periodBanner = readFileSync(join(root, 'src/components/PeriodBanner.vue'), 'utf8') if (!periodList.includes('period-workbench')) failures.push('PeriodListView must use period-workbench') if (!periodList.includes('period-metrics')) failures.push('PeriodListView must show period-metrics') if (!periodBanner.includes('period-status-strip')) failures.push('PeriodBanner must use period-status-strip') ``` 运行 `npm run verify:theme`,预期失败。 - [ ] **步骤 2:周期页顶部改为指标区** 在 `PeriodListView.vue` 的 `page-hero` 后新增: ```vue
进行中 {{ active.length }}
OKR 对齐 {{ okrAlign.length }}
考评中 {{ periods.filter(p => p.status === 'ASSESSING').length }}
已归档 {{ archived.length }}
``` - [ ] **步骤 3:周期卡片降低装饰** 将 `.period-card` 改为 `med-panel` 风格:细边框、状态点、无大面积彩色 badge。保留所有按钮和 `transition()` 调用。 - [ ] **步骤 4:改造 `PeriodBanner.vue`** 根元素改为: ```vue
``` 状态图标不使用空 emoji;用 `.status-dot` 表达状态。 - [ ] **步骤 5:验证** 运行: ```bash cd frontend npm run verify:theme npm run build ``` 预期:两个命令 exit 0。 - [ ] **步骤 6:Commit** ```bash git add frontend/scripts/verify-medtech-theme.mjs frontend/src/views/period/PeriodListView.vue frontend/src/components/PeriodBanner.vue git commit -m "style: redesign period lifecycle workspace" ``` ## 任务 5:重构我的 OKR 和团队 OKR **文件:** - 修改:`frontend/src/views/okr/MyOkrView.vue` - 修改:`frontend/src/views/okr/TeamOkrView.vue` - [ ] **步骤 1:扩展验证脚本红灯** 增加: ```js const myOkr = readFileSync(join(root, 'src/views/okr/MyOkrView.vue'), 'utf8') const teamOkr = readFileSync(join(root, 'src/views/okr/TeamOkrView.vue'), 'utf8') if (!myOkr.includes('okr-workbench')) failures.push('MyOkrView must use okr-workbench') if (!myOkr.includes('okr-standard-panel')) failures.push('MyOkrView must use okr-standard-panel') if (!teamOkr.includes('team-okr-workbench')) failures.push('TeamOkrView must use team-okr-workbench') ``` 运行 `npm run verify:theme`,预期失败。 - [ ] **步骤 2:替换 `MyOkrView.vue` 中 emoji 文案** 示例替换: ```vue
OKR 编写标准 · {{ auth.user?.position || '通用岗位' }}
岗位指标参考({{ positionTemplate.name }})
专项加减分
``` 确保没有 `📋`、`📌`、`⚡`、`⏳`、`↗` 这类视觉装饰字符。承接上级改为文本“承接上级”或 Element Plus 图标。 - [ ] **步骤 3:统一 OKR/KR 结构** 关键类: ```vue
...
...
``` 保留 `openCreateForm()`、`startEdit()`、`doReview()`、`updateProgress()`、`handleSubmit()` 等业务逻辑。 - [ ] **步骤 4:团队 OKR 使用同一结构** 将 `TeamOkrView.vue` 的根内容改为 `team-okr-workbench`,卡片使用 `.okr-section` 和 `.med-panel` 风格。 - [ ] **步骤 5:验证** 运行: ```bash cd frontend npm run verify:theme npm run build ``` 预期:两个命令 exit 0。 - [ ] **步骤 6:Commit** ```bash git add frontend/scripts/verify-medtech-theme.mjs frontend/src/views/okr/MyOkrView.vue frontend/src/views/okr/TeamOkrView.vue git commit -m "style: redesign okr alignment workspace" ``` ## 任务 6:重构绩效考评工作台 **文件:** - 修改:`frontend/src/views/scoring/ScoringEditView.vue` - 修改:`frontend/src/views/scoring/ScoringListView.vue` - [ ] **步骤 1:扩展验证脚本红灯** 增加: ```js const scoringEdit = readFileSync(join(root, 'src/views/scoring/ScoringEditView.vue'), 'utf8') const scoringList = readFileSync(join(root, 'src/views/scoring/ScoringListView.vue'), 'utf8') if (!scoringEdit.includes('review-workbench')) failures.push('ScoringEditView must use review-workbench') if (!scoringEdit.includes('evidence-panel')) failures.push('ScoringEditView must use evidence-panel') if (!scoringEdit.includes('decision-panel')) failures.push('ScoringEditView must use decision-panel') if (!scoringList.includes('scoring-worklist')) failures.push('ScoringListView must use scoring-worklist') ``` 运行 `npm run verify:theme`,预期失败。 - [ ] **步骤 2:重命名评分布局结构** 在 `ScoringEditView.vue` 中将: ```vue
...
``` 改为: ```vue
...