|
|
@@ -0,0 +1,990 @@
|
|
|
+# 前端医疗科技化重构实现计划
|
|
|
+
|
|
|
+> **面向 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
|
|
|
+<template>
|
|
|
+ <router-view />
|
|
|
+</template>
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **步骤 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
|
|
|
+<div class="clinical-shell">
|
|
|
+ <el-container class="clinical-container">
|
|
|
+ <el-aside :width="isCollapse ? '64px' : '220px'" class="clinical-sidebar">
|
|
|
+ <div class="brand-lockup" v-show="!isCollapse">
|
|
|
+ <img src="/logo.png" class="brand-lockup__logo" alt="医梦AI" />
|
|
|
+ <div class="brand-lockup__text">
|
|
|
+ <span class="brand-lockup__name">医梦AI</span>
|
|
|
+ <span class="brand-lockup__sub">OKR 绩效管理</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-aside>
|
|
|
+ <el-container>
|
|
|
+ <el-header class="clinical-topbar">...</el-header>
|
|
|
+ <el-main class="clinical-main">
|
|
|
+ <router-view />
|
|
|
+ </el-main>
|
|
|
+ </el-container>
|
|
|
+ </el-container>
|
|
|
+</div>
|
|
|
+```
|
|
|
+
|
|
|
+保留现有菜单、通知、轮询和退出逻辑。
|
|
|
+
|
|
|
+- [ ] **步骤 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
|
|
|
+<div class="internal-login">
|
|
|
+ <section class="internal-login__brand">
|
|
|
+ <img src="/logo.png" class="internal-login__logo" alt="医梦AI" />
|
|
|
+ <h1>OKR 绩效管理系统</h1>
|
|
|
+ <p>医梦AI 内部绩效运营工作台</p>
|
|
|
+ </section>
|
|
|
+ <section class="internal-login__panel med-panel">
|
|
|
+ ...
|
|
|
+ </section>
|
|
|
+</div>
|
|
|
+```
|
|
|
+
|
|
|
+禁止保留暗色 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
|
|
|
+<div class="period-workbench">
|
|
|
+ <div class="period-metrics">
|
|
|
+ <div class="metric-card">
|
|
|
+ <span class="metric-card__label">进行中</span>
|
|
|
+ <strong>{{ active.length }}</strong>
|
|
|
+ </div>
|
|
|
+ <div class="metric-card">
|
|
|
+ <span class="metric-card__label">OKR 对齐</span>
|
|
|
+ <strong>{{ okrAlign.length }}</strong>
|
|
|
+ </div>
|
|
|
+ <div class="metric-card">
|
|
|
+ <span class="metric-card__label">考评中</span>
|
|
|
+ <strong>{{ periods.filter(p => p.status === 'ASSESSING').length }}</strong>
|
|
|
+ </div>
|
|
|
+ <div class="metric-card">
|
|
|
+ <span class="metric-card__label">已归档</span>
|
|
|
+ <strong>{{ archived.length }}</strong>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</div>
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **步骤 3:周期卡片降低装饰**
|
|
|
+
|
|
|
+将 `.period-card` 改为 `med-panel` 风格:细边框、状态点、无大面积彩色 badge。保留所有按钮和 `transition()` 调用。
|
|
|
+
|
|
|
+- [ ] **步骤 4:改造 `PeriodBanner.vue`**
|
|
|
+
|
|
|
+根元素改为:
|
|
|
+
|
|
|
+```vue
|
|
|
+<div class="period-status-strip" :class="'is-' + active.status">
|
|
|
+```
|
|
|
+
|
|
|
+状态图标不使用空 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
|
|
|
+<div class="rules-title">OKR 编写标准 · {{ auth.user?.position || '通用岗位' }}</div>
|
|
|
+<div class="pr-title">岗位指标参考({{ positionTemplate.name }})</div>
|
|
|
+<div class="self-block-title">专项加减分</div>
|
|
|
+```
|
|
|
+
|
|
|
+确保没有 `📋`、`📌`、`⚡`、`⏳`、`↗` 这类视觉装饰字符。承接上级改为文本“承接上级”或 Element Plus 图标。
|
|
|
+
|
|
|
+- [ ] **步骤 3:统一 OKR/KR 结构**
|
|
|
+
|
|
|
+关键类:
|
|
|
+
|
|
|
+```vue
|
|
|
+<div class="okr-workbench">
|
|
|
+ <section class="okr-standard-panel med-panel">...</section>
|
|
|
+ <section class="okr-section med-panel">...</section>
|
|
|
+</div>
|
|
|
+```
|
|
|
+
|
|
|
+保留 `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
|
|
|
+<div class="same-screen" v-if="data.targetUser">
|
|
|
+ <div class="left-panel">
|
|
|
+ ...
|
|
|
+ <div class="score-panel">
|
|
|
+```
|
|
|
+
|
|
|
+改为:
|
|
|
+
|
|
|
+```vue
|
|
|
+<div class="review-workbench" v-if="data.targetUser">
|
|
|
+ <section class="evidence-panel med-panel">
|
|
|
+ ...
|
|
|
+ <aside class="decision-panel">
|
|
|
+```
|
|
|
+
|
|
|
+保留内部计算属性、提交函数和 API 调用不变。
|
|
|
+
|
|
|
+- [ ] **步骤 3:决策区视觉收敛**
|
|
|
+
|
|
|
+将 `.score-card`、`.result-preview` 调整为:
|
|
|
+
|
|
|
+```css
|
|
|
+.decision-card {
|
|
|
+ background: var(--color-surface);
|
|
|
+ border: 1px solid var(--color-border-soft);
|
|
|
+ border-radius: var(--radius-md);
|
|
|
+ padding: 16px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.result-preview {
|
|
|
+ background: var(--color-surface-subtle);
|
|
|
+ border: 1px solid var(--color-border);
|
|
|
+ border-radius: var(--radius-md);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+总分使用 `var(--color-brand)`,不使用大面积紫色背景。
|
|
|
+
|
|
|
+- [ ] **步骤 4:证据区视觉收敛**
|
|
|
+
|
|
|
+OKR 记录、自评、日志都使用边框和分隔线表达层级,不再使用 `#f5f3ff` 作为大面积背景。
|
|
|
+
|
|
|
+- [ ] **步骤 5:列表页统一**
|
|
|
+
|
|
|
+`ScoringListView.vue` 根内容使用:
|
|
|
+
|
|
|
+```vue
|
|
|
+<div class="app-page scoring-worklist">
|
|
|
+```
|
|
|
+
|
|
|
+表格外层使用 `.med-panel`,空状态使用 `.empty-state`。
|
|
|
+
|
|
|
+- [ ] **步骤 6:验证**
|
|
|
+
|
|
|
+运行:
|
|
|
+
|
|
|
+```bash
|
|
|
+cd frontend
|
|
|
+npm run verify:theme
|
|
|
+npm run build
|
|
|
+```
|
|
|
+
|
|
|
+预期:两个命令 exit 0。
|
|
|
+
|
|
|
+- [ ] **步骤 7:Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add frontend/scripts/verify-medtech-theme.mjs frontend/src/views/scoring/ScoringEditView.vue frontend/src/views/scoring/ScoringListView.vue
|
|
|
+git commit -m "style: redesign performance review workspace"
|
|
|
+```
|
|
|
+
|
|
|
+## 任务 7:重构结果页、历史页和管理页统一外观
|
|
|
+
|
|
|
+**文件:**
|
|
|
+
|
|
|
+- 修改:`frontend/src/views/scoring/MyResultView.vue`
|
|
|
+- 修改:`frontend/src/views/scoring/HistoryView.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`
|
|
|
+
|
|
|
+- [ ] **步骤 1:扩展验证脚本红灯**
|
|
|
+
|
|
|
+增加:
|
|
|
+
|
|
|
+```js
|
|
|
+const requiredClasses = new Map([
|
|
|
+ ['src/views/scoring/MyResultView.vue', 'result-workbench'],
|
|
|
+ ['src/views/scoring/HistoryView.vue', 'history-workbench'],
|
|
|
+ ['src/views/feedback/FeedbackView.vue', 'feedback-workbench'],
|
|
|
+ ['src/views/org/OrgView.vue', 'org-workbench'],
|
|
|
+ ['src/views/system/LogView.vue', 'log-workbench'],
|
|
|
+ ['src/views/system/DimensionConfigView.vue', 'dimension-workbench'],
|
|
|
+ ['src/views/period/KpiTemplateView.vue', 'template-workbench']
|
|
|
+])
|
|
|
+
|
|
|
+for (const [file, className] of requiredClasses) {
|
|
|
+ const text = readFileSync(join(root, file), 'utf8')
|
|
|
+ if (!text.includes(className)) failures.push(`${file} must use ${className}`)
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+运行 `npm run verify:theme`,预期失败。
|
|
|
+
|
|
|
+- [ ] **步骤 2:结果页结构化**
|
|
|
+
|
|
|
+`MyResultView.vue` 使用:
|
|
|
+
|
|
|
+```vue
|
|
|
+<div class="app-page result-workbench">
|
|
|
+```
|
|
|
+
|
|
|
+结果总览使用 `.med-panel`,自评/上级评分对比使用结构化对照区,确认和沟通状态使用 `.audit-note`。
|
|
|
+
|
|
|
+- [ ] **步骤 3:历史页归档语义**
|
|
|
+
|
|
|
+`HistoryView.vue` 使用:
|
|
|
+
|
|
|
+```vue
|
|
|
+<div class="app-page history-workbench">
|
|
|
+```
|
|
|
+
|
|
|
+左侧周期列表、右侧结果详情都使用 `.med-panel`;归档文案使用 `.audit-note`。
|
|
|
+
|
|
|
+- [ ] **步骤 4:管理页统一外观**
|
|
|
+
|
|
|
+把反馈、组织、日志、维度、模板页面根元素改为对应 `*-workbench` 类;表格或表单容器用 `.med-panel`;空状态用 `.empty-state`。
|
|
|
+
|
|
|
+- [ ] **步骤 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/scoring/MyResultView.vue frontend/src/views/scoring/HistoryView.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
|
|
|
+git commit -m "style: align result and admin pages with medtech system"
|
|
|
+```
|
|
|
+
|
|
|
+## 任务 8:最终构建和视觉验证
|
|
|
+
|
|
|
+**文件:**
|
|
|
+
|
|
|
+- 修改:按视觉检查发现的问题限定在前面已改文件中。
|
|
|
+
|
|
|
+- [ ] **步骤 1:运行自动化验证**
|
|
|
+
|
|
|
+运行:
|
|
|
+
|
|
|
+```bash
|
|
|
+cd frontend
|
|
|
+npm run verify:theme
|
|
|
+npm run build
|
|
|
+```
|
|
|
+
|
|
|
+预期:
|
|
|
+
|
|
|
+- `verify:theme` 输出 `medtech theme checks passed`
|
|
|
+- `build` exit 0,生成 `dist/`
|
|
|
+
|
|
|
+- [ ] **步骤 2:启动前端开发服务器**
|
|
|
+
|
|
|
+运行:
|
|
|
+
|
|
|
+```bash
|
|
|
+cd frontend
|
|
|
+npm run dev
|
|
|
+```
|
|
|
+
|
|
|
+预期:Vite 启动在 `http://localhost:5173`。
|
|
|
+
|
|
|
+- [ ] **步骤 3:浏览器视觉检查**
|
|
|
+
|
|
|
+用浏览器打开:
|
|
|
+
|
|
|
+- `http://localhost:5173/#/login`
|
|
|
+- 登录后检查 `#/periods`
|
|
|
+- `#/okr/my`
|
|
|
+- `#/scoring`
|
|
|
+- `#/scores/my`
|
|
|
+- `#/feedback`
|
|
|
+- `#/org`
|
|
|
+
|
|
|
+检查点:
|
|
|
+
|
|
|
+- 1366px 宽度下没有重叠。
|
|
|
+- 评分页左右工作台结构清晰。
|
|
|
+- 页面没有 emoji。
|
|
|
+- 没有大面积紫青渐变或光晕。
|
|
|
+- 按钮、tag、表格风格统一。
|
|
|
+
|
|
|
+- [ ] **步骤 4:修复视觉检查发现的问题**
|
|
|
+
|
|
|
+只修复与规格相关的问题:
|
|
|
+
|
|
|
+- 文本溢出。
|
|
|
+- 按钮挤压。
|
|
|
+- 状态颜色不一致。
|
|
|
+- 面板间距不一致。
|
|
|
+- 窄屏布局重叠。
|
|
|
+
|
|
|
+- [ ] **步骤 5:重新验证**
|
|
|
+
|
|
|
+运行:
|
|
|
+
|
|
|
+```bash
|
|
|
+cd frontend
|
|
|
+npm run verify:theme
|
|
|
+npm run build
|
|
|
+```
|
|
|
+
|
|
|
+预期:两个命令 exit 0。
|
|
|
+
|
|
|
+- [ ] **步骤 6:Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add frontend
|
|
|
+git commit -m "style: complete medtech frontend visual QA"
|
|
|
+```
|
|
|
+
|
|
|
+## 自检
|
|
|
+
|
|
|
+规格覆盖:
|
|
|
+
|
|
|
+- 主题 token:任务 2。
|
|
|
+- Element Plus 覆盖:任务 2。
|
|
|
+- 主布局:任务 3。
|
|
|
+- 登录页:任务 3。
|
|
|
+- 周期工作台:任务 4。
|
|
|
+- 我的 OKR:任务 5。
|
|
|
+- 绩效考评工作台:任务 6。
|
|
|
+- 我的绩效和历史绩效:任务 7。
|
|
|
+- 管理页统一:任务 7。
|
|
|
+- 构建和视觉验证:任务 8。
|
|
|
+
|
|
|
+范围控制:
|
|
|
+
|
|
|
+- 不修改 API、路由、store、后端、数据库、评分公式。
|
|
|
+- 不引入新 UI 框架。
|
|
|
+- 不做营销页、复杂动画、3D 或插画系统。
|
|
|
+
|
|
|
+执行策略:
|
|
|
+
|
|
|
+- 每个任务先扩展或运行验证脚本,让当前状态红灯。
|
|
|
+- 再做最小页面/样式改造。
|
|
|
+- 每个任务完成后运行 `npm run verify:theme` 和 `npm run build`。
|
|
|
+- 每个任务独立 commit,避免大批量不可回滚改动。
|