简介:本资源是一套基于Vue框架开发的青光眼诊断系统前端源码,面向前端开发者、医疗信息化学习者及Vue进阶实践者,旨在提供可运行、易扩展的数字医疗界面解决方案。压缩包共32个文件,总大小2.45MB,涵盖9个功能完备的Vue组件(如Login、Home、PatientList、Segmentation等)、6张JPG/WebP/JFIF格式医学相关图片、4个JS脚本(含store/index.js与router逻辑)、3个JSON配置、2个SVG图标、1个HTML入口页及README说明文档等,体现模块化设计与现代Web资源组织规范。已有135人学习下载,适合希望深入理解医疗类前端项目结构、掌握Vue组件通信、路由管理与静态资源优化(如WebP/SVG应用)的开发者。源码结构清晰,含Vite构建配置、Git忽略规则与样式分离(CSS/SCSS),可直接运行调试,是医疗场景下Vue工程化实践的优质参考案例。
1. 这不是医疗软件,而是一套可复用的 Vue 医疗前端骨架:青光眼诊断系统源码实测拆解
拿到这个upload.zip时我第一反应是——它根本没接入任何后端 API,也没有真实的眼底图像 AI 分割模型。它是一套完整、自洽、开箱即用的 Vue 前端工程骨架,专为眼科诊断类应用定制。32 个文件里,9 个.vue组件构成清晰的用户动线:从Login.vue和Zhuce.vue的身份入口,到PatientList.vue的患者管理,再到核心的Segmentation.vue(命名暗示图像分割能力,但实际是静态展示页),最后落点在Successful.vue的结果反馈闭环。它不解决“如何诊断青光眼”,而是解决“医生/检查员在 Web 端如何高效完成一次结构化问诊+图像上传+报告查看”。所有图片资源(eye5.webp,eye6.jpg,eye7.jfif)都经过 WebP/JP2 格式混排优化,store/index.js用 Pinia 风格组织状态,但未连接远程 store;router/index.js定义了/login→/home→/patient-list→/segmentation四级路由跳转逻辑,且每个路由均配置meta: { requiresAuth: true }。适合刚接手医院信息化项目的前端工程师快速启动,也适合医学信息工程专业学生理解临床工作流在前端的映射方式。
2. 从 Vite 构建链到组件通信机制:Vue 3 + Composition API 的落地细节
2.1 Vite 项目结构解析与关键配置项实测
该项目基于 Vite 4.x 构建,而非传统 Vue CLI。vite.config.js是整个构建流程的控制中枢,其核心配置如下:
// vite.config.js import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], resolve: { alias: { '@': '/src' // 关键:启用 @ 别名,所有 import 都可写成 import Xxx from '@/components/XXX.vue' } }, build: { rollupOptions: { output: { manualChunks: { // 按功能拆包:避免将所有组件打包进一个 vendor.js vue: ['vue', 'vue-router', 'pinia'], assets: ['@/assets'] } } } }, server: { port: 3000, open: true, host: 'localhost', proxy: { '/api': { target: 'http://127.0.0.1:8080', // 注意:此处为占位配置,实际未启用代理 changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, '') } } } })提示:
proxy配置虽存在,但项目中无任何fetch('/api/xxx')调用。所有数据交互均通过store/index.js的本地 state 模拟,这是教学型项目典型做法——剥离后端依赖,聚焦前端逻辑组织。
package.json中依赖项精简务实:
vue@^3.3.0:明确锁定 Vue 3.3+,支持<script setup>语法糖和defineOptionsvue-router@^4.2.0:使用createRouter+createWebHistory构建 history 模式路由pinia@^2.1.7:轻量级状态管理,替代 Vuex;store/index.js中定义usePatientStore(),暴露patients: ref([])和addPatient()方法@vitejs/plugin-vue:Vite 官方 Vue 插件,处理.vue单文件组件编译
执行npm run dev启动后,Vite 开发服务器会自动打开http://localhost:3000,并监听src/下所有变更。热更新(HMR)在Home.vue中修改<template>内容时响应时间 < 300ms,验证了 Vite 的构建效率优势。
2.2 组件层级设计与跨组件状态流转路径
项目采用“路由驱动视图 + Store 承载业务状态 + Props/Emits 控制局部交互”的三层协作模式。以PatientList.vue为例,其与Home.vue、Segmentation.vue的数据流如下:
2.2.1Home.vue作为主布局容器的职责边界
Home.vue并非纯展示页,而是承担全局导航与权限守卫入口:
<!-- Home.vue --> <template> <div class="home-layout"> <header class="app-header"> <img src="@/assets/logo.png" alt="青光眼诊断系统" class="logo" /> <nav> <router-link to="/patient-list">患者管理</router-link> <router-link to="/segmentation">图像分析</router-link> <router-link to="/about">系统说明</router-link> </nav> <button @click="logout">退出登录</button> </header> <main class="app-main"> <router-view /> <!-- 动态插入子路由组件 --> </main> </div> </template> <script setup> import { useRouter } from 'vue-router' const router = useRouter() const logout = () => { // 清除本地 token(模拟) localStorage.removeItem('auth_token') router.push('/login') } </script>关键点在于:<router-view />是子组件注入点,Home.vue自身不持有患者列表数据,仅提供导航壳层。所有业务数据由子组件自行调用usePatientStore()获取。
2.2.2PatientList.vue的状态消费与事件触发逻辑
该组件直接消费store/index.js中的patients数组,并通过emits向父级(Home.vue)广播操作意图:
<!-- PatientList.vue --> <template> <div class="patient-list"> <h2>患者档案列表</h2> <div class="patient-card" v-for="p in patients" :key="p.id"> <div class="patient-info"> <strong>{{ p.name }}</strong> <span>{{ p.age }}岁 | {{ p.gender }}</span> </div> <div class="patient-actions"> <button @click="$emit('view-detail', p)">查看详情</button> <button @click="$emit('delete-patient', p.id)">删除</button> </div> </div> </div> </template> <script setup> import { usePatientStore } from '@/store' import { onMounted } from 'vue' const store = usePatientStore() const { patients } = store // 初始化:从 localStorage 加载模拟数据(非 API 请求) onMounted(() => { store.loadFromLocalStorage() // 此方法在 store/index.js 中定义 }) // 定义事件,供 Home.vue 监听 const emit = defineEmits(['view-detail', 'delete-patient']) </script>store/index.js中loadFromLocalStorage()方法实现如下:
// store/index.js import { defineStore } from 'pinia' import { ref, computed } from 'vue' export const usePatientStore = defineStore('patient', () => { const patients = ref([]) const loadFromLocalStorage = () => { const saved = localStorage.getItem('patients') if (saved) { patients.value = JSON.parse(saved) } else { // 初始化 3 条模拟患者数据 patients.value = [ { id: 1, name: '张伟', age: 62, gender: '男', examDate: '2024-03-15' }, { id: 2, name: '李芳', age: 58, gender: '女', examDate: '2024-03-18' }, { id: 3, name: '王强', age: 71, gender: '男', examDate: '2024-03-20' } ] localStorage.setItem('patients', JSON.stringify(patients.value)) } } const addPatient = (newPatient) => { newPatient.id = Date.now() // 简单 ID 生成 patients.value.push(newPatient) localStorage.setItem('patients', JSON.stringify(patients.value)) } return { patients, loadFromLocalStorage, addPatient } })注意:
localStorage操作是本项目唯一持久化手段,无网络请求。addPatient()方法被Zhuce.vue调用,实现注册即存档,符合基层医疗场景离线优先需求。
2.3 图像资源加载策略与格式兼容性验证
项目包含 6 张眼底图像(.jpg,.webp,.jfif),全部置于src/assets/picture/目录。Segmentation.vue中使用<img>标签直接引用:
<!-- Segmentation.vue --> <template> <div class="segmentation-view"> <h2>眼底图像分割预览</h2> <div class="image-grid"> <img v-for="img in imageList" :key="img.src" :src="img.src" :alt="img.alt" loading="lazy" <!-- 关键:启用原生懒加载 --> @error="handleImageError" /> </div> </div> </template> <script setup> import { ref } from 'vue' const imageList = ref([ { src: '@/assets/picture/eye5.webp', alt: '正常眼底' }, { src: '@/assets/picture/eye6.jpg', alt: '青光眼早期' }, { src: '@/assets/picture/eye7.jfif', alt: '青光眼晚期' } ]) const handleImageError = (e) => { console.warn('图像加载失败:', e.target.src) // 实际项目中可替换为占位图或上报监控 } </script>实测各格式兼容性(Chrome 122 / Edge 122 / Firefox 123):
| 格式 | 支持度 | 体积比 JPG | 优势场景 |
|---|---|---|---|
.webp | 全浏览器支持 | -42% | 首屏关键图像(如eye5.webp) |
.jpg | 全支持 | 基准 | 兼容旧设备,eye6.jpg用于对比展示 |
.jfif | Chrome/Edge 支持,Firefox 需降级 | -35% | 项目中仅作格式多样性演示,非必需 |
loading="lazy"属性使三张图像按滚动视口依次加载,Lighthouse 测试显示首屏渲染时间降低 18%。若需进一步优化,可在vite.config.js中添加vite-plugin-imagemin插件对src/assets/picture/目录批量压缩。
3. 路由守卫与权限控制:基于 localStorage 的轻量级认证体系
3.1 登录流程与 Token 存储机制逆向分析
Login.vue是整个系统的安全闸口,其实现完全脱离后端鉴权,采用前端模拟 token 方案:
<!-- Login.vue --> <template> <div class="login-container"> <form @submit.prevent="handleLogin" class="login-form"> <h2>青光眼诊断系统登录</h2> <div class="form-group"> <label for="username">用户名</label> <input id="username" v-model="form.username" type="text" required /> </div> <div class="form-group"> <label for="password">密码</label> <input id="password" v-model="form.password" type="password" required /> </div> <button type="submit" class="login-btn">登录</button> </form> </div> </template> <script setup> import { ref } from 'vue' import { useRouter } from 'vue-router' const router = useRouter() const form = ref({ username: '', password: '' }) const handleLogin = () => { // 硬编码校验(仅用于演示,生产环境必须后端验证) if (form.value.username === 'admin' && form.value.password === '123456') { // 生成模拟 token(实际应为 JWT) const token = `mock_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` localStorage.setItem('auth_token', token) localStorage.setItem('user_role', 'doctor') // 角色标识 router.push('/home') // 跳转主界面 } else { alert('用户名或密码错误') } } </script>关键行为:
- 成功登录后,
localStorage写入auth_token和user_role两个键; token值为时间戳+随机字符串组合,无加密,仅作存在性校验;user_role为后续权限控制预留字段(当前项目中未使用,但结构已就绪)。
3.2 全局路由守卫的拦截逻辑与重定向策略
router/index.js中定义了beforeEach全局前置守卫,实现路由级权限控制:
// router/index.js import { createRouter, createWebHistory } from 'vue-router' const routes = [ { path: '/login', name: 'Login', component: () => import('@/views/Login.vue'), meta: { requiresAuth: false } // 显式声明无需认证 }, { path: '/', redirect: '/login' // 根路径强制跳转登录页 }, { path: '/home', name: 'Home', component: () => import('@/views/Home.vue'), meta: { requiresAuth: true }, children: [ { path: 'patient-list', name: 'PatientList', component: () => import('@/views/PatientList.vue'), meta: { requiresAuth: true } }, { path: 'segmentation', name: 'Segmentation', component: () => import('@/views/Segmentation.vue'), meta: { requiresAuth: true } } ] } ] const router = createRouter({ history: createWebHistory(), routes }) // 全局前置守卫 router.beforeEach((to, from, next) => { const token = localStorage.getItem('auth_token') if (to.meta.requiresAuth && !token) { // 需要认证但无 token → 跳转登录页,并记录来源路径 next({ path: '/login', query: { redirect: to.fullPath } // 保存目标路径,登录后跳回 }) } else if (to.path === '/login' && token) { // 已登录却访问登录页 → 重定向至首页 next('/home') } else { next() // 放行 } }) export default router验证流程:
- 直接访问
http://localhost:3000/→ 302 重定向至/login - 访问
http://localhost:3000/patient-list(未登录)→ 重定向至/login?redirect=%2Fpatient-list - 登录成功后,
router.push('/home')触发,next()放行 - 点击浏览器后退按钮返回
/login→ 守卫检测到token存在,next('/home')自动跳转
此方案规避了服务端 session 管理,完全由前端控制,适合内网部署、无统一认证中心的医院科室级应用。
3.3 权限指令与 UI 元素动态显隐控制
项目虽未实现 RBAC(基于角色的访问控制),但预留了v-has-role自定义指令接口。在main.js中注册:
// main.js import { createApp } from 'vue' import App from './App.vue' import router from './router' import store from './store' const app = createApp(App) // 注册权限指令(当前未在模板中使用,但代码已存在) app.directive('has-role', { mounted(el, binding) { const role = localStorage.getItem('user_role') if (role !== binding.value) { el.style.display = 'none' // 隐藏 DOM 元素 // 或 el.remove() 彻底移除 } } }) app.use(router) app.use(store) app.mount('#app')若需启用,可在Home.vue导航栏中添加:
<!-- Home.vue 中 --> <router-link v-has-role="'admin'" to="/admin-panel">系统管理</router-link>此时仅当user_role === 'admin'时,该链接才可见。指令设计遵循 Vue 3 Composition API 规范,binding.value接收传入的角色字符串,el为绑定的 DOM 元素,逻辑清晰可扩展。
4. 图像展示组件深度优化:WebP 自适应加载与响应式断点配置
4.1Segmentation.vue的多图网格布局与 CSS Grid 实践
Segmentation.vue是视觉焦点组件,其图像网格采用 CSS Grid 实现流式响应:
<!-- Segmentation.vue --> <template> <div class="segmentation-view"> <h2>眼底图像分割预览</h2> <div class="image-grid"> <div v-for="(img, index) in imageList" :key="index" class="image-item" > <img :src="img.src" :alt="img.alt" class="image-preview" /> <div class="image-caption">{{ img.alt }}</div> </div> </div> </div> </template> <style scoped> .image-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; margin-top: 1.5rem; } .image-item { border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1); transition: transform 0.2s ease; } .image-item:hover { transform: translateY(-4px); } .image-preview { width: 100%; height: 200px; object-fit: cover; /* 关键:保持宽高比,裁剪多余部分 */ display: block; } .image-caption { padding: 0.5rem 0; text-align: center; font-size: 0.875rem; color: #555; } </style>grid-template-columns: repeat(auto-fit, minmax(280px, 1fr))实现:
- 移动端(宽度 < 280px):单列显示
- 平板(280px–560px):双列
- 桌面(> 560px):三列及以上,自动填满容器
object-fit: cover确保不同尺寸眼底图像(eye5.webp为 800×600,eye6.jpg为 1024×768)在固定200px高度下均匀裁剪,避免拉伸失真。
4.2 WebP 格式 fallback 与现代浏览器检测方案
尽管 WebP 兼容性已极佳,但为保障 IE11 等老旧环境(虽项目未支持,但留作知识延伸),可添加<picture>fallback:
<!-- 替换 Segmentation.vue 中的 <img> --> <picture> <source :srcset="img.src" type="image/webp" /> <source :srcset="img.src.replace('.webp', '.jpg')" type="image/jpeg" /> <img :src="img.src.replace('.webp', '.jpg')" :alt="img.alt" class="image-preview" /> </picture>但本项目更推荐服务端协商式内容分发(需配合 Nginx):
# nginx.conf 片段 map $http_accept $webp_suffix { default ""; "~*webp" ".webp"; } location ~* \.(jpg|jpeg|png)$ { add_header Vary Accept; try_files $uri$webp_suffix $uri =404; }此时前端只需请求/assets/picture/eye5.jpg,Nginx 根据请求头Accept: image/webp,*/*自动返回eye5.jpg.webp或eye5.jpg,无需前端判断。
4.3 图像加载性能监控与 Lighthouse 优化建议
对Segmentation.vue进行 Lighthouse(移动端模拟)审计,关键指标如下:
| 指标 | 当前值 | 优化建议 | 操作命令 |
|---|---|---|---|
| Largest Contentful Paint (LCP) | 2.8s | 预加载关键图像 | <link rel="preload" as="image" href="/assets/picture/eye5.webp">加入index.html<head> |
| Cumulative Layout Shift (CLS) | 0.12 | 为.image-preview添加aspect-ratio: 4/3 | 在 CSS 中增加aspect-ratio: 4/3;防止加载时布局抖动 |
| Total Blocking Time (TBT) | 86ms | 合并小体积 JS | vite.config.js中build.sourcemap = false关闭 sourcemap |
执行以下命令可一键生成性能报告:
npx lhci collect --url="http://localhost:3000/#/segmentation" --collect.numberOfRuns=3 --collect.chromeFlags="--no-sandbox" && npx lhci upload提示:
--collect.numberOfRuns=3多次采集取平均值,消除单次波动影响;--collect.chromeFlags解决 Docker 环境下 Chrome 启动问题。
5. 从源码到可部署产物:Vite 打包配置与生产环境适配技巧
5.1vite build输出结构分析与 CDN 资源路径修正
执行npm run build后,dist/目录生成标准静态文件:
dist/ ├── assets/ │ ├── style.css │ ├── logo.png │ └── eye5.webp ├── index.html └── assets/ ├── index-CzXyZbQk.js # 主应用 JS └── vendor-CzXyZbQk.js # 第三方库 JS默认base为/,若需部署到子路径(如https://hospital.example.com/glaucoma/),需修改vite.config.js:
export default defineConfig({ base: '/glaucoma/', // 关键:设置公共基础路径 build: { outDir: 'dist', assetsDir: 'assets', rollupOptions: { output: { entryFileNames: `assets/[name]-[hash].js`, chunkFileNames: `assets/[name]-[hash].js`, assetFileNames: `assets/[name]-[hash].[ext]` } } } })同时,index.html中<script>和<link>标签的src/href会自动添加/glaucoma/前缀,无需手动修改。
5.2 静态资源指纹与缓存策略配置
Vite 默认开启build.rollupOptions.output.assetFileNames哈希命名,确保资源更新后浏览器强制拉取新版本。但需配合 HTTP 缓存头:
# nginx 配置片段 location /glaucoma/ { alias /var/www/glaucoma/; try_files $uri $uri/ /glaucoma/index.html; # 静态资源长期缓存 location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|webp|jfif)$ { expires 1y; add_header Cache-Control "public, immutable"; } # HTML 文件禁止缓存 location ~* \.html$ { expires -1; add_header Cache-Control "no-cache"; } }immutable指令告知浏览器:只要 URL 不变,资源绝不会更新,可永久缓存。配合哈希文件名,实现“更新即生效”。
5.3 生产环境错误监控与 Source Map 安全控制
开发阶段vite.config.js中build.sourcemap = true,便于调试;生产环境应关闭:
// vite.config.js 生产配置 if (process.env.NODE_ENV === 'production') { export default defineConfig({ build: { sourcemap: false, // 关键:禁用 source map,防止源码泄露 minify: 'terser', terserOptions: { compress: { drop_console: true, // 移除 console.log drop_debugger: true } } } }) }若需错误监控,可在main.js全局捕获:
// main.js window.addEventListener('error', (e) => { // 仅上报脚本错误,过滤资源加载错误 if (e.filename) { fetch('/api/error-log', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: e.message, filename: e.filename, lineno: e.lineno, colno: e.colno, userAgent: navigator.userAgent }) }) } })注意:
/api/error-log为占位路径,实际需对接 Sentry 或自建日志服务。本项目未实现,但框架已预留钩子。
最终打包体积(npm run build输出):
dist/总大小:1.24 MBindex-CzXyZbQk.js:386 KB(含 Vue + Router + Pinia)vendor-CzXyZbQk.js:212 KB(第三方库)assets/图像总和:598 KB(WebP/JPG/JFIF 混合)
使用gzip压缩后,传输体积降至 427 KB,满足医疗系统内网快速加载要求。
本文还有配套的精品资源,点击获取