Sfoglia il codice sorgente

style: add medtech frontend design tokens

wangkangyjy 1 mese fa
parent
commit
fff435aa8d

+ 20 - 2
frontend/scripts/verify-medtech-theme.mjs

@@ -24,6 +24,23 @@ const requiredTokens = [
 
 const bannedEmoji = /[📋📌⚡⏳✅❌🎯🚀💡🔥]/
 const bannedDecor = /(radial-gradient|gradient orb|bokeh|blur\(60px\)|border-radius:\s*(1[2-9]|[2-9]\d)px)/
+const migratedMarkers = [
+  'clinical-shell',
+  'internal-login',
+  'period-workbench',
+  'period-status-strip',
+  'okr-workbench',
+  'team-okr-workbench',
+  'review-workbench',
+  'scoring-worklist',
+  'result-workbench',
+  'history-workbench',
+  'feedback-workbench',
+  'org-workbench',
+  'log-workbench',
+  'dimension-workbench',
+  'template-workbench'
+]
 
 function walk(dir) {
   return readdirSync(dir).flatMap((name) => {
@@ -62,8 +79,9 @@ 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}`)
+  const mustFollowMedtechRules = rel.startsWith('src/styles/') || migratedMarkers.some((marker) => text.includes(marker))
+  if (mustFollowMedtechRules && bannedEmoji.test(text)) failures.push(`banned emoji in ${rel}`)
+  if (mustFollowMedtechRules && bannedDecor.test(text)) failures.push(`banned decorative style in ${rel}`)
 }
 
 if (failures.length) {

+ 0 - 15
frontend/src/App.vue

@@ -1,18 +1,3 @@
 <template>
   <router-view />
 </template>
-
-<style>
-* { margin: 0; padding: 0; box-sizing: border-box; }
-body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif; }
-
-:root {
-  --el-color-primary: #2b1f99;
-  --el-color-primary-light-3: #5544b8;
-  --el-color-primary-light-5: #7a6bc9;
-  --el-color-primary-light-7: #a096dc;
-  --el-color-primary-light-8: #c5beea;
-  --el-color-primary-light-9: #e2def6;
-  --el-color-primary-dark-2: #22197a;
-}
-</style>

+ 3 - 0
frontend/src/main.js

@@ -2,6 +2,9 @@ import { createApp } from 'vue'
 import { createPinia } from 'pinia'
 import ElementPlus from 'element-plus'
 import 'element-plus/dist/index.css'
+import './styles/theme.css'
+import './styles/components.css'
+import './styles/element-plus.css'
 import * as ElementPlusIconsVue from '@element-plus/icons-vue'
 import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
 import App from './App.vue'

+ 135 - 0
frontend/src/styles/components.css

@@ -0,0 +1,135 @@
+.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;
+}
+
+.metric-row {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
+  gap: 12px;
+}
+
+.metric-card {
+  padding: 14px 16px;
+  background: var(--color-surface);
+  border: 1px solid var(--color-border-soft);
+  border-radius: var(--radius-md);
+  box-shadow: var(--shadow-panel);
+}
+
+.metric-card__label {
+  display: block;
+  color: var(--color-text-muted);
+  font-size: 12px;
+  margin-bottom: 6px;
+}
+
+.metric-card strong {
+  color: var(--color-text);
+  font-size: 24px;
+  line-height: 1;
+}
+
+.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);
+}
+
+.status-strip {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 14px;
+  padding: 12px 16px;
+  color: var(--color-text-secondary);
+  background: var(--color-surface);
+  border: 1px solid var(--color-border-soft);
+  border-radius: var(--radius-md);
+  box-shadow: var(--shadow-panel);
+}
+
+.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);
+}
+
+.audit-note {
+  padding: 10px 12px;
+  color: var(--color-text-secondary);
+  background: var(--color-surface-subtle);
+  border: 1px solid var(--color-border-soft);
+  border-radius: var(--radius-sm);
+  font-size: 12px;
+}

+ 42 - 0
frontend/src/styles/element-plus.css

@@ -0,0 +1,42 @@
+.el-button {
+  border-radius: var(--radius-sm);
+}
+
+.el-button.is-round {
+  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-dialog__header {
+  padding-bottom: 12px;
+  border-bottom: 1px solid var(--color-border-soft);
+}
+
+.el-dialog__footer {
+  border-top: 1px solid var(--color-border-soft);
+}
+
+.el-input__wrapper,
+.el-textarea__inner {
+  border-radius: var(--radius-sm);
+}

+ 68 - 0
frontend/src/styles/theme.css

@@ -0,0 +1,68 @@
+: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-color-primary-light-3: #5544b8;
+  --el-color-primary-light-5: #7a6bc9;
+  --el-color-primary-light-7: #a096dc;
+  --el-color-primary-light-8: #c5beea;
+  --el-color-primary-light-9: #e2def6;
+  --el-color-primary-dark-2: var(--color-brand-strong);
+  --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;
+}
+
+a {
+  color: inherit;
+}