# 前端开发指南 ## 技术栈 | 技术 | 版本 | 用途 | |------|------|------| | Vue | 3.4+ | 前端框架 | | Vue Router | 4.2+ | 路由管理 | | Pinia | 2.1+ | 状态管理 | | Element Plus | 2.5+ | UI组件库 | | Axios | 1.6+ | HTTP请求 | | ECharts | 5.4+ | 图表库 | | Day.js | 1.11+ | 日期处理 | | Vite | 5.0+ | 构建工具 | | Sass | 1.70+ | CSS预处理 | ## 项目结构 ``` frontend/src/ ├── api/ # API请求模块 │ ├── request.js # Axios实例配置 │ ├── auth.js # 认证API │ ├── staff.js # 员工API │ ├── department.js # 科室API │ ├── indicator.js # 指标API │ ├── assessment.js # 考核API │ ├── salary.js # 工资API │ └── stats.js # 统计API ├── stores/ # Pinia状态管理 │ ├── index.js # Store入口 │ ├── user.js # 用户状态 │ └── app.js # 应用状态 ├── router/ # 路由配置 │ └── index.js ├── views/ # 页面组件 │ ├── Login.vue # 登录页 │ ├── Layout.vue # 布局框架 │ ├── Dashboard.vue # 工作台 │ ├── basic/ # 基础数据管理 │ ├── assessment/ # 考核管理 │ ├── salary/ # 工资核算 │ └── reports/ # 统计报表 ├── components/ # 通用组件 ├── assets/ # 静态资源 ├── App.vue # 根组件 └── main.js # 入口文件 ``` ## 开发规范 ### 组件规范 #### 使用 ` ``` #### 命名规范 - **组件文件**: `PascalCase.vue` (如 `StaffList.vue`) - **变量**: `camelCase` (如 `tableData`, `searchForm`) - **常量**: `UPPER_SNAKE_CASE` (如 `API_BASE_URL`) - **模板ref**: `xxxRef` (如 `formRef`, `tableRef`) ### API层规范 #### request.js 配置 ```javascript import axios from 'axios' import { ElMessage } from 'element-plus' import router from '@/router' const request = axios.create({ baseURL: '/api/v1', timeout: 10000 }) // 请求拦截器 request.interceptors.request.use( config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }, error => Promise.reject(error) ) // 响应拦截器 request.interceptors.response.use( response => response.data, error => { if (error.response?.status === 401) { localStorage.removeItem('token') router.push('/login') } ElMessage.error(error.response?.data?.detail || '请求失败') return Promise.reject(error) } ) export default request ``` #### API函数定义 ```javascript // api/staff.js import request from './request' export function getStaffList(params) { return request.get('/staff', { params }) } export function getStaffById(id) { return request.get(`/staff/${id}`) } export function createStaff(data) { return request.post('/staff', data) } export function updateStaff(id, data) { return request.put(`/staff/${id}`, data) } export function deleteStaff(id) { return request.delete(`/staff/${id}`) } ``` ### 状态管理规范 ```javascript // stores/user.js import { defineStore } from 'pinia' import { ref, computed } from 'vue' import { login, getCurrentUser } from '@/api/auth' export const useUserStore = defineStore('user', () => { // 状态 const token = ref(localStorage.getItem('token') || '') const userInfo = ref(null) // 计算属性 const isLoggedIn = computed(() => !!token.value) // 方法 async function loginAction(username, password) { const res = await login({ username, password }) token.value = res.access_token localStorage.setItem('token', res.access_token) await fetchUserInfo() } async function fetchUserInfo() { const res = await getCurrentUser() userInfo.value = res } function logout() { token.value = '' userInfo.value = null localStorage.removeItem('token') } return { token, userInfo, isLoggedIn, loginAction, fetchUserInfo, logout } }) ``` ### 路由规范 ```javascript // router/index.js import { createRouter, createWebHistory } from 'vue-router' const routes = [ { path: '/login', name: 'Login', component: () => import('@/views/Login.vue'), meta: { title: '登录' } }, { path: '/', component: () => import('@/views/Layout.vue'), redirect: '/dashboard', children: [ { path: 'dashboard', name: 'Dashboard', component: () => import('@/views/Dashboard.vue'), meta: { title: '工作台', icon: 'HomeFilled' } } ] } ] const router = createRouter({ history: createWebHistory(), routes }) // 路由守卫 router.beforeEach((to, from, next) => { document.title = `${to.meta.title || '首页'} - 医院绩效考核系统` const token = localStorage.getItem('token') if (to.path !== '/login' && !token) { next('/login') } else { next() } }) export default router ``` ### 样式规范 ```vue ``` ### 错误处理规范 ```javascript // 使用try-catch-finally处理异步操作 async function handleSubmit() { submitting.value = true try { await createStaff(form) ElMessage.success('创建成功') dialogVisible.value = false loadData() // 刷新列表 } catch (error) { // 错误已在request.js中统一处理 console.error('创建失败:', error) } finally { submitting.value = false } } // 删除确认 async function handleDelete(row) { try { await ElMessageBox.confirm('确定要删除该记录吗?', '提示', { type: 'warning' }) await deleteStaff(row.id) ElMessage.success('删除成功') loadData() } catch (error) { if (error !== 'cancel') { console.error('删除失败:', error) } } } ``` ## 页面模板 ### 列表页面模板 ```vue ``` ## 开发命令 ```bash # 安装依赖 npm install # 启动开发服务器 npm run dev # 构建生产版本 npm run build # 预览生产构建 npm run preview ``` ## 常见问题 ### 跨域问题 开发环境通过Vite代理解决: ```javascript // vite.config.js export default { server: { proxy: { '/api': { target: 'http://localhost:8000', changeOrigin: true } } } } ``` ### Token刷新 当前实现为Token过期后跳转登录页,后续可考虑实现Token自动刷新机制。