# 前端医疗科技化重构实现计划
> **面向 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
...
```
保留现有菜单、通知、轮询和退出逻辑。
- [ ] **步骤 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
...