Browse Source

style: redesign shell and login for medtech workspace

wangkangyjy 1 month ago
parent
commit
4bdb312c0b

+ 8 - 0
frontend/scripts/verify-medtech-theme.mjs

@@ -75,6 +75,14 @@ for (const file of requiredFiles) {
   if (!main.includes(importPath)) failures.push(`main.js must import ${importPath}`)
 }
 
+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')
+
 const vueAndCssFiles = walk(src).filter((file) => /\.(vue|css)$/.test(file))
 for (const file of vueAndCssFiles) {
   const text = readFileSync(file, 'utf8')

+ 51 - 41
frontend/src/components/MainLayout.vue

@@ -1,14 +1,16 @@
 <template>
-  <div class="layout">
-    <el-container style="height:100vh">
-      <el-aside :width="isCollapse ? '64px' : '220px'" class="sidebar">
-        <div class="logo-area" v-show="!isCollapse">
-          <img src="/logo.png" class="logo-img" />
-          <span class="logo-text">医梦AI</span>
-          <span class="logo-sub">OKR 绩效管理</span>
+  <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>
-        <div class="logo-area collapsed" v-show="isCollapse">
-          <img src="/logo.png" class="logo-img-mini" />
+        <div class="brand-lockup collapsed" v-show="isCollapse">
+          <img src="/logo.png" class="brand-lockup__mini" alt="医梦AI" />
         </div>
 
         <el-menu
@@ -74,7 +76,7 @@
       </el-aside>
 
       <el-container>
-        <el-header class="topbar">
+        <el-header class="clinical-topbar">
           <div class="topbar-left">
             <el-button text @click="isCollapse = !isCollapse">
               <el-icon :size="20"><Fold v-if="!isCollapse" /><Expand v-else /></el-icon>
@@ -120,7 +122,7 @@
           </div>
         </el-header>
 
-        <el-main class="main-content">
+        <el-main class="clinical-main">
           <router-view />
         </el-main>
       </el-container>
@@ -200,26 +202,30 @@ function handleLogout() {
 </script>
 
 <style scoped>
-.layout { height: 100vh; }
-.sidebar {
-  background: #1a1560 !important;
+.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.3s;
+  transition: width 0.2s ease;
 }
-.logo-area {
-  padding: 20px 16px 16px;
-  border-bottom: 1px solid rgba(255,255,255,0.08);
-  text-align: center;
+.brand-lockup {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  padding: 16px 14px;
+  border-bottom: 1px solid rgba(255,255,255,0.1);
 }
-.logo-area.collapsed { padding: 12px 8px; }
-.logo-img { width: 120px; height: 60px; object-fit: contain; display: block; margin: 0 auto 4px; }
-.logo-img-mini { width: 40px; height: 22px; object-fit: contain; }
-.logo-text { font-size: 16px; font-weight: 700; color: #3ad4d8; letter-spacing: 3px; display: block; text-align: center; }
-.logo-sub { font-size: 11px; color: rgba(255,255,255,0.4); text-align: center; display: block; margin-top: 2px; }
+.brand-lockup.collapsed { justify-content: center; padding: 14px 8px; }
+.brand-lockup__logo { width: 72px; height: 36px; object-fit: contain; flex-shrink: 0; }
+.brand-lockup__mini { width: 38px; height: 22px; object-fit: contain; }
+.brand-lockup__text { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
+.brand-lockup__name { font-size: 16px; font-weight: 700; color: #fff; letter-spacing: 1px; }
+.brand-lockup__sub { font-size: 11px; color: rgba(255,255,255,0.58); }
 .menu-group-title {
   padding: 16px 20px 6px;
   font-size: 11px;
-  color: rgba(255,255,255,0.3);
+  color: rgba(255,255,255,0.36);
   text-transform: uppercase;
   letter-spacing: 1px;
 }
@@ -231,31 +237,35 @@ function handleLogout() {
   border-radius: 6px;
 }
 .el-menu-item.is-active {
-  background: rgba(58,212,216,0.12) !important;
+  background: rgba(58,212,216,0.1) !important;
   border-left: 3px solid #3ad4d8;
 }
-.topbar {
-  height: 48px;
+.clinical-topbar {
+  height: var(--layout-topbar);
   display: flex;
   align-items: center;
   justify-content: space-between;
-  background: #fff;
-  border-bottom: 1px solid #e8eaed;
+  background: var(--color-surface);
+  border-bottom: 1px solid var(--color-border);
   padding: 0 20px;
 }
 .topbar-left { display: flex; align-items: center; gap: 8px; }
 .topbar-right { display: flex; align-items: center; gap: 12px; }
-.user-info { font-size: 13px; color: #666; }
-.main-content { background: #f8f9fb; padding: 20px 24px; min-height: calc(100vh - 48px); }
+.user-info { font-size: 13px; color: var(--color-text-secondary); }
+.clinical-main {
+  min-height: calc(100vh - var(--layout-topbar));
+  padding: 20px 24px;
+  background: var(--color-bg);
+}
 
 .notify-panel { max-height: 400px; overflow-y: auto; }
-.notify-header { display: flex; justify-content: space-between; align-items: center; padding-bottom: 8px; border-bottom: 1px solid #eee; margin-bottom: 4px; font-weight: 600; }
-.notify-item { padding: 10px 4px; border-bottom: 1px solid #f5f5f5; cursor: pointer; border-radius: 4px; }
-.notify-item:hover { background: #f5f7fa; }
-.notify-item.unread { background: #f0f7ff; }
-.notify-title { font-size: 13px; font-weight: 500; }
-.notify-content { font-size: 12px; color: #888; margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-.notify-time { font-size: 11px; color: #bbb; margin-top: 2px; }
-.notify-empty { padding: 20px; text-align: center; color: #999; font-size: 13px; }
-.notify-footer { padding-top: 8px; text-align: center; border-top: 1px solid #eee; margin-top: 4px; }
+.notify-header { display: flex; justify-content: space-between; align-items: center; padding-bottom: 8px; border-bottom: 1px solid var(--color-border-soft); margin-bottom: 4px; font-weight: 600; }
+.notify-item { padding: 10px 8px; border-bottom: 1px solid var(--color-border-soft); cursor: pointer; border-radius: 4px; }
+.notify-item:hover { background: var(--color-surface-subtle); }
+.notify-item.unread { background: var(--color-accent-soft); border-left: 2px solid var(--color-accent); }
+.notify-title { font-size: 13px; font-weight: 600; color: var(--color-text); }
+.notify-content { font-size: 12px; color: var(--color-text-secondary); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.notify-time { font-size: 11px; color: var(--color-text-muted); margin-top: 2px; }
+.notify-empty { padding: 20px; text-align: center; color: var(--color-text-muted); font-size: 13px; }
+.notify-footer { padding-top: 8px; text-align: center; border-top: 1px solid var(--color-border-soft); margin-top: 4px; }
 </style>

+ 98 - 82
frontend/src/views/auth/LoginView.vue

@@ -1,17 +1,19 @@
 <template>
-  <div class="login-page">
-    <div class="login-bg">
-      <div class="bg-grid"></div>
-    </div>
-    <div class="login-card">
-      <div class="card-brand">
-        <img src="/logo.png" class="brand-logo" alt="医梦AI" />
-        <h1 class="brand-name">医梦AI</h1>
-        <p class="brand-desc">OKR 智慧绩效管理平台</p>
+  <div class="internal-login">
+    <section class="internal-login__brand">
+      <img src="/logo.png" class="internal-login__logo" alt="医梦AI" />
+      <div>
+        <h1>OKR 绩效管理系统</h1>
+        <p>医梦AI 内部绩效运营工作台</p>
+      </div>
+    </section>
+
+    <section class="internal-login__panel med-panel">
+      <div class="login-panel__header">
+        <span class="login-panel__eyebrow">Internal Access</span>
+        <h2>{{ isRegister ? '注册新账号' : '账号登录' }}</h2>
       </div>
-      <div class="card-divider"></div>
       <el-form :model="form" :rules="rules" ref="formRef" @submit.prevent="handleSubmit" class="login-form">
-        <div class="form-label">{{ isRegister ? '注册新账号' : '账号登录' }}</div>
         <el-form-item prop="username">
           <el-input
             v-model="form.username"
@@ -43,17 +45,16 @@
           size="large"
           native-type="submit"
           :loading="loading"
-          round
         >
-          {{ isRegister ? '注 册' : '登 录' }}
+          {{ isRegister ? '注册' : '登录' }}
         </el-button>
       </el-form>
-      <div class="card-footer">
+      <div class="login-panel__footer">
         <button type="button" class="switch-link" @click="isRegister = !isRegister">
           {{ isRegister ? '已有账号?立即登录' : '创建新账号' }}
         </button>
       </div>
-    </div>
+    </section>
   </div>
 </template>
 
@@ -93,115 +94,130 @@ async function handleSubmit() {
 </script>
 
 <style scoped>
-.login-page {
+.internal-login {
   min-height: 100vh;
-  display: flex;
+  display: grid;
+  grid-template-columns: minmax(320px, 460px) 420px;
   align-items: center;
   justify-content: center;
-  background: #08061a;
-  position: relative;
-  overflow: hidden;
-}
-.login-bg {
-  position: absolute;
-  inset: 0;
-  overflow: hidden;
-}
-.bg-grid {
-  position: absolute;
-  inset: 0;
-  background: radial-gradient(ellipse at 50% 0%, rgba(58,212,216,0.08) 0%, transparent 60%);
-}
-.login-card {
-  position: relative;
-  width: 420px;
-  background: #110e2b;
-  border: 1px solid rgba(255,255,255,0.06);
-  border-radius: 12px;
-  padding: 0;
-  overflow: hidden;
+  gap: 56px;
+  padding: 40px;
+  background:
+    linear-gradient(135deg, rgba(43,31,153,0.08), transparent 42%),
+    var(--color-bg);
 }
-.card-brand {
-  text-align: center;
-  padding: 40px 32px 20px;
+
+.internal-login__brand {
+  display: flex;
+  align-items: flex-start;
+  gap: 20px;
 }
-.brand-logo {
-  width: 160px;
-  height: 80px;
+
+.internal-login__logo {
+  width: 120px;
+  height: 64px;
   object-fit: contain;
-  margin-bottom: 12px;
+  flex-shrink: 0;
 }
-.brand-name {
-  font-size: 32px;
-  font-weight: 700;
-  color: #ffffff;
-  letter-spacing: 4px;
+
+.internal-login__brand h1 {
+  margin: 8px 0 8px;
+  color: var(--color-text);
+  font-size: 30px;
+  line-height: 1.2;
+  letter-spacing: 0;
+}
+
+.internal-login__brand p {
   margin: 0;
+  color: var(--color-text-secondary);
+  font-size: 15px;
 }
-.brand-desc {
-  font-size: 13px;
-  color: rgba(255,255,255,0.4);
-  margin: 8px 0 0;
+
+.internal-login__panel {
+  padding: 28px;
+}
+
+.login-panel__header {
+  margin-bottom: 22px;
+}
+
+.login-panel__eyebrow {
+  display: block;
+  margin-bottom: 6px;
+  color: var(--color-accent-strong);
+  font-size: 12px;
+  font-weight: 700;
   letter-spacing: 1px;
+  text-transform: uppercase;
 }
-.card-divider {
-  height: 1px;
-  background: rgba(255,255,255,0.06);
-  margin: 0 32px;
+
+.login-panel__header h2 {
+  margin: 0;
+  color: var(--color-text);
+  font-size: 22px;
+  font-weight: 700;
 }
+
 .login-form {
-  padding: 24px 32px 8px;
-}
-.form-label {
-  font-size: 13px;
-  color: rgba(255,255,255,0.5);
-  margin-bottom: 16px;
-  letter-spacing: 1px;
+  padding: 0;
 }
+
 .custom-input :deep(.el-input__wrapper) {
-  background: rgba(255,255,255,0.05);
-  border: 1px solid rgba(255,255,255,0.1);
-  border-radius: 8px;
+  background: var(--color-surface-subtle);
+  border: 1px solid var(--color-border-soft);
   box-shadow: none;
 }
 .custom-input :deep(.el-input__wrapper:hover) {
-  border-color: rgba(58,212,216,0.4);
+  border-color: var(--color-accent);
 }
 .custom-input :deep(.el-input__wrapper.is-focus) {
-  border-color: #3ad4d8;
+  border-color: var(--color-accent);
   box-shadow: 0 0 0 1px rgba(58,212,216,0.2);
 }
 .custom-input :deep(.el-input__inner) {
-  color: #fff;
+  color: var(--color-text);
 }
 .custom-input :deep(.el-input__inner::placeholder) {
-  color: rgba(255,255,255,0.25);
+  color: var(--color-text-muted);
 }
 .login-btn {
   width: 100%;
   height: 44px;
-  background: #3ad4d8;
+  background: var(--color-brand);
   border: none;
-  color: #08061a;
+  color: #fff;
   font-size: 15px;
   font-weight: 600;
-  letter-spacing: 4px;
+  letter-spacing: 0;
   margin-top: 4px;
 }
 .login-btn:hover {
-  background: #2ec4c8;
+  background: var(--color-brand-strong);
 }
-.card-footer {
+.login-panel__footer {
   text-align: center;
-  padding: 12px 32px 32px;
+  padding-top: 14px;
 }
 .switch-link {
   background: none;
   border: none;
-  color: rgba(255,255,255,0.35);
+  color: var(--color-text-muted);
   font-size: 13px;
   cursor: pointer;
-  letter-spacing: 1px;
+  letter-spacing: 0;
+}
+.switch-link:hover { color: var(--color-accent-strong); }
+
+@media (max-width: 900px) {
+  .internal-login {
+    grid-template-columns: 1fr;
+    gap: 24px;
+    padding: 24px;
+  }
+
+  .internal-login__brand {
+    align-items: center;
+  }
 }
-.switch-link:hover { color: #3ad4d8; }
 </style>