优化更新

This commit is contained in:
2026-09-14 10:33:29 +08:00
parent e8b5d55603
commit 11c46addfa
61 changed files with 1025 additions and 896 deletions
@@ -39,7 +39,7 @@
### 请求
```
GET /api/captcha
GET /api/v1/captcha
```
### 响应
@@ -89,7 +89,7 @@ async def captcha(request: Request):
### 请求
```
POST /api/login
POST /api/v1/login
Content-Type: application/json
{
@@ -140,7 +140,7 @@ PUT /api/v1/config/item/update
| 用途 | 枚举值(下拉选项) | 系统参数(配置值) |
| 值类型 | 简单值(字符串) | 可存储较长的配置内容 |
| 典型场景 | 状态、性别、类型 | SMTP配置、站点信息 |
| 前端用法 | el-select 选项 | 配置表单 |
| 前端用法 | n-select 选项 | 配置表单 |
## 总结
@@ -88,20 +88,41 @@ class TrainingService(BaseService[Training]):
效果:列表/详情返回数据中自动添加 `trainingMethod` 和 `trainingMethodName` 字段。
### 方式 2:手动获取字典值
### 方式 2:通过 dict_util 获取(推荐后端使用)
```python
from modules.dictionary.dict_item.repository import dict_item_repo
from utils.dict_util import get_dict_map, get_dict_label, get_dict_items
# 获取字典项列表
items = dict_item_repo.get_all(dict_code='training_method', status=1)
# 获取 {value: label} 映射
options = get_dict_map('training_method')
# 结果:{'1': '线上', '2': '线下', '3': '混合'}
# 转为 {value: label} 字典
options = {item.value: item.label for item in items}
# 单值翻译
label = get_dict_label('training_method', '1')
# 结果:'线上'
# 获取字典项列表(含排序信息)
items = get_dict_items('training_method')
# 结果:[{'label': '线上', 'value': '1', 'sort': 1}, ...]
```
### 方式 3:通过 Repository 直接查询
```python
from modules.dictionary.dict.repository import dict_repo
from modules.dictionary.dict_item.repository import dictitem_repo
# 先查字典类型获取 dict_id
dict_obj = dict_repo.get_one(code='training_method')
# 再查字典项
items = dictitem_repo.filter_by(dict_id=dict_obj.id).all()
# 转为 {value: name} 字典
options = {item.value: item.name for item in items}
# 结果:{'1': '线上', '2': '线下', '3': '混合'}
```
### 方式 3:通过参数模块获取(适合系统配置)
### 方式 4:通过参数模块获取(适合系统配置)
```python
from modules.param.service import param_service
@@ -116,14 +137,7 @@ timeout = param_service.get_value_by_code('REQUEST_TIMEOUT', '30')
```vue
<template>
<el-select v-model="form.trainingMethod" placeholder="请选择培训方式">
<el-option
v-for="item in dictOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<n-select v-model:value="form.trainingMethod" :options="dictOptions" placeholder="请选择培训方式" />
</template>
<script setup>
@@ -145,17 +159,17 @@ onMounted(async () => {
```typescript
import { h } from 'vue';
import { ElTag } from 'element-plus';
import { NTag } from 'naive-ui';
export const columns = [
{
label: '培训方式',
prop: 'trainingMethod',
title: '培训方式',
key: 'trainingMethod',
render(record) {
const map = { 1: '线上', 2: '线下', 3: '混合' };
const typeMap = { 1: 'success', 2: 'warning', 3: 'info' };
return h(ElTag, { type: typeMap[record.row.trainingMethod] }, {
default: () => map[record.row.trainingMethod] || '-'
return h(NTag, { type: typeMap[record.trainingMethod], bordered: false }, {
default: () => map[record.trainingMethod] || '-'
});
},
},
@@ -94,18 +94,25 @@ class DictItem(base_model, base_db):
## 使用示例
### 后端获取字典
### 后端获取字典(推荐使用 dict_util)
```python
# 通过字典编码获取字典项列表
from modules.dictionary.dict_item import service as dict_item_service
from utils.dict_util import get_dict_items, get_dict_map, get_dict_label
# 获取状态字典
status_list = await dict_item_service.get_by_dict_code("sys_status")
# 返回: [{"label": "正常", "value": "1"}, {"label": "停用", "value": "2"}]
# 获取字典项列表(自动缓存,TTL 60秒兜底)
items = get_dict_items("sys_status")
# 返回: [{"label": "正常", "value": "1", "sort": 0}, {"label": "停用", "value": "2", "sort": 1}]
# 获取 {value: label} 映射
status_map = get_dict_map("sys_status")
# 返回: {"1": "正常", "2": "停用"}
# 单值翻译(value 自动转字符串匹配)
label = get_dict_label("sys_status", 1, default="未知")
# 返回: "正常"
```
### dict_util 缓存工具
### dict_util 缓存工具详解
项目提供 `dict_util` 工具(`src/utils/dict_util.py`),内置进程内 TTL 缓存。由于字典数据几乎不变,且读写均落在同步 service 路径(无法使用请求级异步 Redis),因此采用进程内缓存而非 Redis 缓存:
@@ -168,15 +175,8 @@ const dictStore = useDictStore()
const statusOptions = dictStore.getDict('sys_status')
// [{label: "正常", value: "1"}, {label: "停用", value: "2"}]
// 在 el-select 中使用
<el-select v-model="form.status">
<el-option
v-for="item in statusOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
// 在 n-select 中使用
<n-select v-model:value="form.status" :options="statusOptions" />
```
## 常用字典示例
@@ -195,7 +195,7 @@ const statusOptions = dictStore.getDict('sys_status')
```
1. 两级结构:字典类型(分组)+ 字典项(具体值),清晰管理
2. 进程内缓存:dict_util 内置 TTL 缓存(60秒兜底),变更时主动失效
3. 前端集成:useDictStore 统一管理,el-select 直接绑定
3. 前端集成:useDictStore 统一管理,n-select 直接绑定
4. 值为字符串:字典项 value 统一为字符串,get_dict_label 自动转字符串匹配
5. 避免硬编码:枚举值统一走字典,修改无需改代码
6. 三种取值:get_dict_items(列表)、get_dict_map(映射)、get_dict_label(单值翻译)
@@ -14,7 +14,7 @@ import { useLockFn } from '@/utils/lock'
// 包装提交函数,防止重复点击
const handleSubmit = useLockFn(async () => {
await axios.post('/api/v1/level/add', formData)
ElMessage.success('添加成功')
message.success('添加成功')
})
```
@@ -37,25 +37,27 @@ function useLockFn(fn) {
### 按钮 loading
ElementPlus 的按钮组件支持 `loading` 属性,请求期间禁用按钮:
NaiveUI 的按钮组件支持 `loading` 属性,请求期间禁用按钮:
```vue
<el-button
<n-button
type="primary"
:loading="submitting"
@click="handleSubmit"
>
提交
</el-button>
</n-button>
<script setup>
import { useMessage } from 'naive-ui';
const message = useMessage();
const submitting = ref(false)
async function handleSubmit() {
submitting.value = true
try {
await axios.post('/api/v1/level/add', formData)
ElMessage.success('添加成功')
message.success('添加成功')
} finally {
submitting.value = false
}
@@ -185,10 +185,10 @@ def permission_required(permission: str):
```html
<!-- 只有拥有 sys:user:add 权限的用户才能看到此按钮 -->
<el-button v-perm="'sys:user:add'" type="primary">新增</el-button>
<n-button v-perm="'sys:user:add'" type="primary">新增</n-button>
<!-- 多个权限,满足其一即可 -->
<el-button v-perm="['sys:user:update', 'sys:user:delete']">操作</el-button>
<n-button v-perm="['sys:user:update', 'sys:user:delete']">操作</n-button>
```
### 路由守卫
@@ -136,7 +136,7 @@ class BaseForm(BaseModel):
```
:::warning 前端空字符串
前端某些组件(如 el-select)在未选择时可能传空字符串 `""`,BaseForm 的 `empty_str_to_none` 验证器会自动将其转为 `None`,避免整数字段解析报错。
前端某些组件(如 n-select)在未选择时可能传空字符串 `""`,BaseForm 的 `empty_str_to_none` 验证器会自动将其转为 `None`,避免整数字段解析报错。
:::
## 唯一性校验