# 前端开发指南
## 技术栈
| 技术 | 版本 | 用途 |
|------|------|------|
| 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
搜索
重置
新增
{{ statusMap[row.status] }}
编辑
删除
```
## 开发命令
```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自动刷新机制。