first commit
This commit is contained in:
327
AGENTS.md
Normal file
327
AGENTS.md
Normal file
@@ -0,0 +1,327 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user