From 779f15d29ff8e83d57657fc7eb30aee95ce56d31 Mon Sep 17 00:00:00 2001 From: yelan Date: Thu, 27 Aug 2026 13:22:45 +0800 Subject: [PATCH] =?UTF-8?q?feat(auth):=20=E6=B7=BB=E5=8A=A0=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E9=A1=B5=E4=B8=8E=E6=BC=94=E7=A4=BA=E8=AE=A4=E8=AF=81?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- clinical-web/README.md | 5 +- clinical-web/src/components/layout/Menu.vue | 10 +- clinical-web/src/main.ts | 5 +- clinical-web/src/router/index.ts | 34 +++++ clinical-web/src/stores/app.ts | 10 ++ clinical-web/src/styles/index.css | 157 ++++++++++++++++++++ clinical-web/src/styles/theme.css | 3 + clinical-web/src/views/auth/LoginView.vue | 113 ++++++++++++++ 8 files changed, 333 insertions(+), 4 deletions(-) create mode 100644 clinical-web/src/views/auth/LoginView.vue diff --git a/clinical-web/README.md b/clinical-web/README.md index 4d02ee2..8ea799e 100644 --- a/clinical-web/README.md +++ b/clinical-web/README.md @@ -84,11 +84,14 @@ TanStack Vue Query、PDF 预览、报表图表、自动化测试和签字板适 ## 当前状态 -已完成 Vite 基础初始化、路由、Pinia、原型主题 CSS 和医护端整体布局。当前所有业务 View 暂时保留文字占位,尚未接入真实后端。 +已完成 Vite 基础初始化、路由、Pinia、原型主题 CSS、登录页和医护端整体布局。当前所有业务 View 暂时保留文字占位,登录使用演示认证逻辑,尚未接入真实后端。 ## 当前路由结构 ```text +公开页 +└─ /login 登录页 + ClinicalLayout ├─ 工作台 │ ├─ /workbench/home 首页 diff --git a/clinical-web/src/components/layout/Menu.vue b/clinical-web/src/components/layout/Menu.vue index 9124a78..c4cc8bd 100644 --- a/clinical-web/src/components/layout/Menu.vue +++ b/clinical-web/src/components/layout/Menu.vue @@ -1,5 +1,5 @@ diff --git a/clinical-web/src/main.ts b/clinical-web/src/main.ts index c1df6cc..47c5ee6 100644 --- a/clinical-web/src/main.ts +++ b/clinical-web/src/main.ts @@ -7,4 +7,7 @@ import App from './App.vue' import router from './router' import './styles/index.css' -createApp(App).use(router).use(createPinia()).use(ElementPlus).mount('#app') +const app = createApp(App) +const pinia = createPinia() + +app.use(pinia).use(router).use(ElementPlus).mount('#app') diff --git a/clinical-web/src/router/index.ts b/clinical-web/src/router/index.ts index 6612d90..b8402f8 100644 --- a/clinical-web/src/router/index.ts +++ b/clinical-web/src/router/index.ts @@ -1,10 +1,12 @@ import { createRouter, createWebHistory } from 'vue-router' +import { useAppStore } from '@/stores/app' import ClinicalLayout from '@/layouts/ClinicalLayout.vue' import ConsentTasksView from '@/views/consent-tasks/ConsentTasksView.vue' import DashboardView from '@/views/dashboard/DashboardView.vue' import DocumentPermissionsView from '@/views/document-permissions/DocumentPermissionsView.vue' import DocumentsView from '@/views/documents/DocumentsView.vue' +import LoginView from '@/views/auth/LoginView.vue' import ReportsView from '@/views/reports/ReportsView.vue' import SettingsView from '@/views/settings/SettingsView.vue' import UserPermissionsView from '@/views/users/UserPermissionsView.vue' @@ -12,10 +14,18 @@ import UserPermissionsView from '@/views/users/UserPermissionsView.vue' const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ + { + path: '/login', + name: 'login', + component: LoginView, + meta: { title: '登录' }, + }, { path: '/', + name: 'clinical', component: ClinicalLayout, redirect: '/workbench/home', + meta: { requiresAuth: true }, children: [ { path: 'workbench', @@ -23,11 +33,13 @@ const router = createRouter({ children: [ { path: 'home', + name: 'workbench-home', component: DashboardView, meta: { title: '首页', group: '工作台' }, }, { path: 'signing', + name: 'workbench-signing', component: ConsentTasksView, meta: { title: '签署工作台', group: '工作台' }, }, @@ -39,26 +51,31 @@ const router = createRouter({ children: [ { path: 'documents', + name: 'management-documents', component: DocumentsView, meta: { title: '文档库管理', group: '管理' }, }, { path: 'reports', + name: 'management-reports', component: ReportsView, meta: { title: '报表分析', group: '管理' }, }, { path: 'users', + name: 'management-users', component: UserPermissionsView, meta: { title: '用户与权限', group: '管理' }, }, { path: 'document-permissions', + name: 'management-document-permissions', component: DocumentPermissionsView, meta: { title: '文档权限', group: '管理' }, }, { path: 'settings', + name: 'management-settings', component: SettingsView, meta: { title: '系统设置', group: '管理' }, }, @@ -75,4 +92,21 @@ const router = createRouter({ ], }) +router.beforeEach((to) => { + const appStore = useAppStore() + + if (to.name === 'login' && appStore.isLoggedIn) { + return { name: 'workbench-home' } + } + + if (to.meta.requiresAuth && !appStore.isLoggedIn) { + return { + name: 'login', + query: { redirect: to.fullPath }, + } + } + + return true +}) + export default router diff --git a/clinical-web/src/stores/app.ts b/clinical-web/src/stores/app.ts index 525440f..c4e5c86 100644 --- a/clinical-web/src/stores/app.ts +++ b/clinical-web/src/stores/app.ts @@ -2,8 +2,18 @@ import { defineStore } from 'pinia' export const useAppStore = defineStore('app', { state: () => ({ + isLoggedIn: false, userName: '张文静', department: '医务管理 · 主任医师', environment: '原型演示环境', }), + actions: { + login(userName: string) { + this.userName = userName || '张文静' + this.isLoggedIn = true + }, + logout() { + this.isLoggedIn = false + }, + }, }) diff --git a/clinical-web/src/styles/index.css b/clinical-web/src/styles/index.css index 1873243..27ebd9c 100644 --- a/clinical-web/src/styles/index.css +++ b/clinical-web/src/styles/index.css @@ -365,3 +365,160 @@ a { padding: 16px; } } + +/* 原型登录页 */ +.login-page { + position: fixed; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + background: var(--login-gradient); +} + +.login-card { + width: 380px; + padding: 38px 36px; + background: #fff; + border-radius: 14px; + box-shadow: var(--sh-login); +} + +.login-brand { + display: flex; + gap: 12px; + align-items: center; + justify-content: center; + margin-bottom: 8px; +} + +.login-brand-icon { + display: grid; + width: 46px; + height: 46px; + place-items: center; + border-radius: 12px; + background: linear-gradient(135deg, var(--brand), #18a0c0); +} + +.login-brand h1 { + margin: 0; + color: var(--ink); + font-size: 22px; + letter-spacing: 1px; +} + +.login-brand p { + margin: 2px 0 0; + color: #8aa7b3; + font-size: 11px; + letter-spacing: 2px; +} + +.login-subtitle { + margin: 0 0 26px; + color: var(--mut); + font-size: 12px; + text-align: center; +} + +.login-field { + margin-bottom: 14px; +} + +.login-field label { + display: block; + margin-bottom: 6px; + color: var(--mut); + font-size: 12px; +} + +.login-field input { + width: 100%; + padding: 7px 10px; + color: var(--ink); + font-size: 13px; + background: #fff; + border: 1px solid var(--line); + border-radius: 6px; + outline: none; +} + +.login-field input:focus { + border-color: var(--brand); +} + +.login-captcha-row { + display: flex; + gap: 8px; + align-items: center; +} + +.login-captcha-row input { + flex: 1; +} + +.login-captcha-image { + flex-shrink: 0; + padding: 8px 14px; + color: #5c7d8a; + font-size: 13px; + font-style: italic; + font-weight: 700; + letter-spacing: 6px; + user-select: none; + background: linear-gradient(120deg, #dfeef3, #c8dfe8); + border-radius: 6px; +} + +.login-error { + margin: -4px 0 10px; + color: var(--err); + font-size: 12px; +} + +.login-submit { + width: 100%; + margin-top: 10px; + padding: 10px; + color: #fff; + font-size: 13px; + font-weight: 700; + background: var(--brand); + border-radius: 7px; +} + +.login-submit:hover { + background: var(--brand-d); +} + +.login-tip { + margin: 16px 0 0; + padding: 8px; + color: var(--mut); + font-size: 12px; + text-align: center; + background: var(--brand-l); + border-radius: 8px; +} + +.login-footer { + position: absolute; + bottom: 18px; + width: 100%; + margin: 0; + color: rgb(255 255 255 / 55%); + font-size: 12px; + text-align: center; +} + +@media (max-width: 520px) { + body { + min-width: 0; + } + + .login-card { + width: calc(100% - 32px); + padding: 32px 24px; + } +} diff --git a/clinical-web/src/styles/theme.css b/clinical-web/src/styles/theme.css index 520a2e7..2dd1283 100644 --- a/clinical-web/src/styles/theme.css +++ b/clinical-web/src/styles/theme.css @@ -2,6 +2,7 @@ --brand: #0e6e8c; --brand-d: #0a4f66; --brand-l: #e3f0f4; + --brand-m: #1487a5; --side: #0a3d51; --bg: #f1f5f7; --card: #ffffff; @@ -20,6 +21,8 @@ --sh: 0 2px 10px rgb(13 62 81 / 7%); --sh-hover: 0 6px 20px rgb(14 110 140 / 18%); --sh-modal: 0 20px 60px rgb(0 0 0 / 30%); + --sh-login: 0 20px 60px rgb(0 0 0 / 30%); + --login-gradient: linear-gradient(135deg, #0a3d51 0%, var(--brand) 55%, var(--brand-m) 100%); --font-sans: 'Microsoft YaHei', 'PingFang SC', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; diff --git a/clinical-web/src/views/auth/LoginView.vue b/clinical-web/src/views/auth/LoginView.vue new file mode 100644 index 0000000..dc75414 --- /dev/null +++ b/clinical-web/src/views/auth/LoginView.vue @@ -0,0 +1,113 @@ + + +