328 lines
8.0 KiB
Markdown
328 lines
8.0 KiB
Markdown
# AGENTS.md - 医院绩效考核管理系统
|
|
|
|
This document provides essential context for AI coding agents working in this repository.
|
|
|
|
## Project Overview
|
|
|
|
A hospital performance management system (绩效考核管理系统) for a county-level TCM hospital. Supports department management, staff management, assessment indicators, performance evaluation workflows, data analysis reports, and salary calculation.
|
|
|
|
**Tech Stack:**
|
|
- **Backend**: FastAPI + SQLAlchemy 2.0 (async) + PostgreSQL/Alembic + Pydantic v2
|
|
- **Frontend**: Vue 3 (Composition API) + Element Plus + Pinia + Vite + ECharts
|
|
|
|
---
|
|
|
|
## Build/Lint/Test Commands
|
|
|
|
### Backend (from `backend/` directory)
|
|
|
|
```bash
|
|
# Install dependencies
|
|
pip install -r requirements.txt
|
|
|
|
# Configure environment
|
|
cp .env.example .env
|
|
# Edit .env with your database credentials
|
|
|
|
# Run database migrations
|
|
alembic upgrade head
|
|
|
|
# Start development server
|
|
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
|
|
|
|
# Run tests (when available)
|
|
pytest
|
|
pytest tests/test_specific.py -v # Run specific test file
|
|
pytest -k "test_name" -v # Run tests matching name
|
|
```
|
|
|
|
### Frontend (from `frontend/` directory)
|
|
|
|
```bash
|
|
# Install dependencies
|
|
npm install
|
|
|
|
# Start development server (http://localhost:5173)
|
|
npm run dev
|
|
|
|
# Build for production
|
|
npm run build
|
|
|
|
# Preview production build
|
|
npm run preview
|
|
```
|
|
|
|
**Note**: No ESLint/Prettier configuration exists. Follow existing code patterns.
|
|
|
|
---
|
|
|
|
## Code Style Guidelines
|
|
|
|
### Backend (Python/FastAPI)
|
|
|
|
#### Imports
|
|
```python
|
|
# Standard library first
|
|
from datetime import datetime
|
|
from typing import Optional, List
|
|
|
|
# Third-party next
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from pydantic import BaseModel, Field, ConfigDict
|
|
|
|
# Local imports last (absolute paths)
|
|
from app.core.database import get_db
|
|
from app.schemas.schemas import StaffCreate, StaffResponse
|
|
from app.services.staff_service import StaffService
|
|
```
|
|
|
|
#### Naming Conventions
|
|
- **Files**: `snake_case.py` (e.g., `staff_service.py`)
|
|
- **Classes**: `PascalCase` (e.g., `StaffService`, `StaffCreate`)
|
|
- **Functions/Methods**: `snake_case` (e.g., `get_staff_list`)
|
|
- **Variables**: `snake_case` (e.g., `staff_list`, `department_id`)
|
|
- **Constants**: `UPPER_SNAKE_CASE` (e.g., `DATABASE_URL`)
|
|
- **Enums**: `PascalCase` for class, `UPPER_CASE` for values
|
|
|
|
#### Pydantic Schemas (v2)
|
|
```python
|
|
class StaffResponse(StaffBase):
|
|
"""员工响应"""
|
|
model_config = ConfigDict(from_attributes=True) # Required for ORM mode
|
|
|
|
id: int
|
|
status: StaffStatus
|
|
created_at: datetime
|
|
```
|
|
|
|
#### Async Patterns
|
|
```python
|
|
# Always use async for database operations
|
|
async def get_staff_list(db: AsyncSession) -> tuple[List[Staff], int]:
|
|
result = await db.execute(query)
|
|
return result.scalars().all()
|
|
|
|
# Dependency injection for database sessions
|
|
@router.get("/staff")
|
|
async def get_staff(db: AsyncSession = Depends(get_db)):
|
|
...
|
|
```
|
|
|
|
#### Error Handling
|
|
```python
|
|
# Use HTTPException for API errors
|
|
if not staff:
|
|
raise HTTPException(status_code=404, detail="员工不存在")
|
|
|
|
# Check business constraints
|
|
if existing:
|
|
raise HTTPException(status_code=400, detail="工号已存在")
|
|
```
|
|
|
|
#### API Response Format
|
|
```python
|
|
# Standard response structure
|
|
return {
|
|
"code": 200,
|
|
"message": "success",
|
|
"data": result,
|
|
"total": total, # For paginated responses
|
|
"page": page,
|
|
"page_size": page_size
|
|
}
|
|
```
|
|
|
|
### Frontend (Vue 3/JavaScript)
|
|
|
|
#### Imports
|
|
```javascript
|
|
// Vue imports first
|
|
import { ref, reactive, onMounted } from 'vue'
|
|
|
|
// Third-party next
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
|
// Local imports last (use @ alias)
|
|
import { getStaffList, createStaff } from '@/api/staff'
|
|
import { useUserStore } from '@/stores/user'
|
|
```
|
|
|
|
#### Vue Composition API
|
|
```vue
|
|
<script setup>
|
|
// Use <script setup> syntax (always)
|
|
import { ref, reactive, onMounted } from 'vue'
|
|
|
|
// Reactive state
|
|
const loading = ref(false)
|
|
const tableData = ref([])
|
|
const form = reactive({
|
|
name: '',
|
|
status: 'active'
|
|
})
|
|
|
|
// Lifecycle
|
|
onMounted(() => {
|
|
loadData()
|
|
})
|
|
|
|
// Functions (regular, not async in template)
|
|
async function loadData() {
|
|
loading.value = true
|
|
try {
|
|
const res = await getStaffList({ ...searchForm })
|
|
tableData.value = res.data || []
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
</script>
|
|
```
|
|
|
|
#### Naming Conventions
|
|
- **Components**: `PascalCase.vue` (e.g., `Staff.vue`, `AssessmentDetail.vue`)
|
|
- **Composables**: `useCamelCase.js` (e.g., `useUserStore`)
|
|
- **API functions**: `camelCase` (e.g., `getStaffList`, `createStaff`)
|
|
- **Template refs**: `camelCaseRef` (e.g., `formRef`, `tableRef`)
|
|
|
|
#### Pinia Stores
|
|
```javascript
|
|
// Use composition API style with defineStore
|
|
export const useUserStore = defineStore('user', () => {
|
|
const token = ref('')
|
|
const userInfo = ref(null)
|
|
|
|
async function login(username, password) { ... }
|
|
function logout() { ... }
|
|
|
|
return { token, userInfo, login, logout }
|
|
})
|
|
```
|
|
|
|
#### API Layer
|
|
```javascript
|
|
// Simple wrapper functions around axios instance
|
|
import request from './request'
|
|
|
|
export function getStaffList(params) {
|
|
return request.get('/staff', { params })
|
|
}
|
|
|
|
export function createStaff(data) {
|
|
return request.post('/staff', data)
|
|
}
|
|
```
|
|
|
|
#### Error Handling
|
|
```javascript
|
|
// Use Element Plus messages
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
|
// User confirmation
|
|
await ElMessageBox.confirm('确定要删除吗?', '提示', { type: 'warning' })
|
|
|
|
// Success/error feedback
|
|
ElMessage.success('操作成功')
|
|
ElMessage.error('操作失败')
|
|
|
|
// Try-catch with finally for loading states
|
|
try {
|
|
await createStaff(form)
|
|
ElMessage.success('创建成功')
|
|
dialogVisible.value = false
|
|
} catch (error) {
|
|
console.error('创建失败', error)
|
|
} finally {
|
|
submitting.value = false
|
|
}
|
|
```
|
|
|
|
#### Styling
|
|
```vue
|
|
<style scoped lang="scss">
|
|
// Use scoped styles with SCSS
|
|
.search-bar {
|
|
display: flex;
|
|
gap: 12px;
|
|
margin-bottom: 20px;
|
|
|
|
.el-input {
|
|
width: 160px;
|
|
}
|
|
}
|
|
</style>
|
|
```
|
|
|
|
---
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
backend/
|
|
├── app/
|
|
│ ├── api/v1/ # API routes (auth, staff, departments, etc.)
|
|
│ ├── core/ # Config, database, security
|
|
│ ├── models/ # SQLAlchemy ORM models
|
|
│ ├── schemas/ # Pydantic validation schemas
|
|
│ ├── services/ # Business logic layer
|
|
│ └── main.py # FastAPI app factory
|
|
├── alembic/ # Database migrations
|
|
└── requirements.txt
|
|
|
|
frontend/
|
|
├── src/
|
|
│ ├── api/ # Axios API functions
|
|
│ ├── assets/ # Static assets (SCSS, images)
|
|
│ ├── components/ # Reusable components
|
|
│ ├── router/ # Vue Router config
|
|
│ ├── stores/ # Pinia stores
|
|
│ └── views/ # Page components
|
|
│ ├── basic/ # Staff, Departments, Indicators
|
|
│ ├── assessment/ # Assessments
|
|
│ ├── salary/ # Salary management
|
|
│ └── reports/ # Statistics & reports
|
|
└── package.json
|
|
```
|
|
|
|
---
|
|
|
|
## Key Patterns
|
|
|
|
### Backend Service Layer
|
|
Services encapsulate database operations. Controllers call services, not ORM directly.
|
|
|
|
```python
|
|
# API route calls service
|
|
@router.get("/staff")
|
|
async def get_staff_list(db: AsyncSession = Depends(get_db)):
|
|
staff_list, total = await StaffService.get_list(db, ...)
|
|
return { "data": staff_list, "total": total }
|
|
```
|
|
|
|
### Frontend API Layer
|
|
Centralized axios instance with interceptors handles auth tokens and error display.
|
|
|
|
```javascript
|
|
// request.js handles:
|
|
// - Adding Bearer token from localStorage
|
|
// - Error responses (401 → redirect to login)
|
|
// - Showing ElMessage for errors
|
|
```
|
|
|
|
### Database Sessions
|
|
Uses async sessions with dependency injection. Commits happen automatically in `get_db()`.
|
|
|
|
---
|
|
|
|
## Default Credentials
|
|
|
|
- **Username**: admin
|
|
- **Password**: admin123
|
|
|
|
## API Documentation
|
|
|
|
When backend is running:
|
|
- Swagger UI: http://localhost:8000/api/v1/docs
|
|
- ReDoc: http://localhost:8000/api/v1/redoc
|