优化更新
This commit is contained in:
@@ -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 选项 | 配置表单 |
|
||||
| 前端用法 | a-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,15 @@ timeout = param_service.get_value_by_code('REQUEST_TIMEOUT', '30')
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<el-select v-model="form.trainingMethod" placeholder="请选择培训方式">
|
||||
<el-option
|
||||
<a-select v-model:value="form.trainingMethod" placeholder="请选择培训方式">
|
||||
<a-select-option
|
||||
v-for="item in dictOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
>
|
||||
{{ item.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -141,22 +163,22 @@ onMounted(async () => {
|
||||
|
||||
### 方式 2:表格列显示字典文本
|
||||
|
||||
在 `columns.ts` 中使用 `render` 函数:
|
||||
在 `columns.ts` 中使用 `customRender` 函数:
|
||||
|
||||
```typescript
|
||||
import { h } from 'vue';
|
||||
import { ElTag } from 'element-plus';
|
||||
import { Tag } from 'ant-design-vue';
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
label: '培训方式',
|
||||
prop: 'trainingMethod',
|
||||
render(record) {
|
||||
title: '培训方式',
|
||||
dataIndex: 'trainingMethod',
|
||||
customRender: ({ 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] || '-'
|
||||
});
|
||||
const colorMap = { 1: 'success', 2: 'warning', 3: 'default' };
|
||||
return h(Tag, { color: colorMap[record.trainingMethod] }, () =>
|
||||
map[record.trainingMethod] || '-'
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -174,7 +196,7 @@ export const schemas: FormSchema[] = [
|
||||
label: '培训方式',
|
||||
componentProps: {
|
||||
placeholder: '请选择培训方式',
|
||||
clearable: true,
|
||||
allowClear: true,
|
||||
options: [
|
||||
{ label: '线上', value: '1' },
|
||||
{ label: '线下', value: '2' },
|
||||
|
||||
@@ -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,16 @@ 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
|
||||
// 在 a-select 中使用
|
||||
<a-select v-model:value="form.status">
|
||||
<a-select-option
|
||||
v-for="item in statusOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
>
|
||||
{{ item.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
```
|
||||
|
||||
## 常用字典示例
|
||||
@@ -195,7 +203,7 @@ const statusOptions = dictStore.getDict('sys_status')
|
||||
```
|
||||
1. 两级结构:字典类型(分组)+ 字典项(具体值),清晰管理
|
||||
2. 进程内缓存:dict_util 内置 TTL 缓存(60秒兜底),变更时主动失效
|
||||
3. 前端集成:useDictStore 统一管理,el-select 直接绑定
|
||||
3. 前端集成:useDictStore 统一管理,a-select 直接绑定
|
||||
4. 值为字符串:字典项 value 统一为字符串,get_dict_label 自动转字符串匹配
|
||||
5. 避免硬编码:枚举值统一走字典,修改无需改代码
|
||||
6. 三种取值:get_dict_items(列表)、get_dict_map(映射)、get_dict_label(单值翻译)
|
||||
|
||||
@@ -10,11 +10,12 @@
|
||||
|
||||
```javascript
|
||||
import { useLockFn } from '@/utils/lock'
|
||||
import { message } from 'ant-design-vue'
|
||||
|
||||
// 包装提交函数,防止重复点击
|
||||
const handleSubmit = useLockFn(async () => {
|
||||
await axios.post('/api/v1/level/add', formData)
|
||||
ElMessage.success('添加成功')
|
||||
message.success('添加成功')
|
||||
})
|
||||
```
|
||||
|
||||
@@ -37,25 +38,27 @@ function useLockFn(fn) {
|
||||
|
||||
### 按钮 loading
|
||||
|
||||
ElementPlus 的按钮组件支持 `loading` 属性,请求期间禁用按钮:
|
||||
AntDesign Vue 的按钮组件支持 `loading` 属性,请求期间禁用按钮:
|
||||
|
||||
```vue
|
||||
<el-button
|
||||
<a-button
|
||||
type="primary"
|
||||
:loading="submitting"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
提交
|
||||
</el-button>
|
||||
</a-button>
|
||||
|
||||
<script setup>
|
||||
import { message } from 'ant-design-vue'
|
||||
|
||||
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>
|
||||
<a-button v-perm="'sys:user:add'" type="primary">新增</a-button>
|
||||
|
||||
<!-- 多个权限,满足其一即可 -->
|
||||
<el-button v-perm="['sys:user:update', 'sys:user:delete']">操作</el-button>
|
||||
<a-button v-perm="['sys:user:update', 'sys:user:delete']">操作</a-button>
|
||||
```
|
||||
|
||||
### 路由守卫
|
||||
|
||||
@@ -136,7 +136,7 @@ class BaseForm(BaseModel):
|
||||
```
|
||||
|
||||
:::warning 前端空字符串
|
||||
前端某些组件(如 el-select)在未选择时可能传空字符串 `""`,BaseForm 的 `empty_str_to_none` 验证器会自动将其转为 `None`,避免整数字段解析报错。
|
||||
前端某些组件(如 a-select)在未选择时可能传空字符串 `""`,BaseForm 的 `empty_str_to_none` 验证器会自动将其转为 `None`,避免整数字段解析报错。
|
||||
:::
|
||||
|
||||
## 唯一性校验
|
||||
|
||||
Reference in New Issue
Block a user