优化全局注入dict_data
This commit is contained in:
24
backend/ai/migrations/0003_aimodel_model_type.py
Normal file
24
backend/ai/migrations/0003_aimodel_model_type.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# Generated by Django 5.2.1 on 2025-07-16 03:18
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("ai", "0002_alter_chatrole_knowledge_alter_chatrole_tools"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="aimodel",
|
||||
name="model_type",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
db_comment="模型类型",
|
||||
max_length=32,
|
||||
null=True,
|
||||
verbose_name="模型类型",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -46,6 +46,7 @@ class AIModel(CoreModel):
|
||||
on_delete=models.CASCADE,
|
||||
db_comment='API 秘钥编号', verbose_name="API 秘钥编号"
|
||||
)
|
||||
model_type = models.CharField(max_length=32, db_comment="模型类型", verbose_name="模型类型", blank=True, null=True)
|
||||
platform = models.CharField(max_length=32, db_comment="模型平台", verbose_name="模型平台")
|
||||
model = models.CharField(max_length=64, db_comment="模型标识", verbose_name="模型标识")
|
||||
|
||||
|
||||
@@ -219,38 +219,124 @@ class Command(BaseCommand):
|
||||
field_name = field.name
|
||||
field_label = getattr(field, 'verbose_name', field_name)
|
||||
if field_name == 'status':
|
||||
return "{ component: 'RadioGroup', componentProps: { buttonStyle: 'solid', options: [{ label: $t('common.enabled'), value: 1 }, { label: $t('common.disabled'), value: 0 }], optionType: 'button' }, defaultValue: 1, fieldName: 'status', label: $t('system.status') },"
|
||||
return f''' {{
|
||||
component: 'RadioGroup',
|
||||
componentProps: {{
|
||||
buttonStyle: 'solid',
|
||||
options: [
|
||||
{{ label: $t('common.enabled'), value: 1 }},
|
||||
{{ label: $t('common.disabled'), value: 0 }},
|
||||
],
|
||||
optionType: 'button',
|
||||
}},
|
||||
defaultValue: 1,
|
||||
fieldName: 'status',
|
||||
label: $t('system.status'),
|
||||
}},'''
|
||||
if isinstance(field, models.CharField):
|
||||
return f"{{ component: 'Input', fieldName: '{field_name}', label: '{field_label}', rules: z.string().min(1, $t('ui.formRules.required', ['{field_label}'])).max(100, $t('ui.formRules.maxLength', ['{field_label}', 100])) }},"
|
||||
return f''' {{
|
||||
component: 'Input',
|
||||
fieldName: '{field_name}',
|
||||
label: '{field_label}',
|
||||
rules: z.string().min(1, $t('ui.formRules.required', ['{field_label}'])).max(100, $t('ui.formRules.maxLength', ['{field_label}', 100])),
|
||||
}},'''
|
||||
elif isinstance(field, models.TextField):
|
||||
return f"{{ component: 'Input', componentProps: {{ rows: 3, showCount: true }}, fieldName: '{field_name}', label: '{field_label}', rules: z.string().max(500, $t('ui.formRules.maxLength', ['{field_label}', 500])).optional() }},"
|
||||
return f''' {{
|
||||
component: 'Input',
|
||||
componentProps: {{ rows: 3, showCount: true }},
|
||||
fieldName: '{field_name}',
|
||||
label: '{field_label}',
|
||||
rules: z.string().max(500, $t('ui.formRules.maxLength', ['{field_label}', 500])).optional(),
|
||||
}},'''
|
||||
elif isinstance(field, models.IntegerField):
|
||||
return f"{{ component: 'InputNumber', fieldName: '{field_name}', label: '{field_label}' }},"
|
||||
return f''' {{
|
||||
component: 'InputNumber',
|
||||
fieldName: '{field_name}',
|
||||
label: '{field_label}',
|
||||
}},'''
|
||||
elif isinstance(field, models.BooleanField):
|
||||
return f"{{ component: 'RadioGroup', componentProps: {{ buttonStyle: 'solid', options: [{{ label: '开启', value: 1 }}, {{ label: '关闭', value: 0 }}], optionType: 'button' }}, defaultValue: 1, fieldName: '{field_name}', label: '{field_label}' }},"
|
||||
return f''' {{
|
||||
component: 'RadioGroup',
|
||||
componentProps: {{
|
||||
buttonStyle: 'solid',
|
||||
options: [
|
||||
{{ label: '开启', value: 1 }},
|
||||
{{ label: '关闭', value: 0 }},
|
||||
],
|
||||
optionType: 'button',
|
||||
}},
|
||||
defaultValue: 1,
|
||||
fieldName: '{field_name}',
|
||||
label: '{field_label}',
|
||||
}},'''
|
||||
else:
|
||||
return f"{{ component: 'Input', fieldName: '{field_name}', label: '{field_label}' }},"
|
||||
return f''' {{
|
||||
component: 'Input',
|
||||
fieldName: '{field_name}',
|
||||
label: '{field_label}',
|
||||
}},'''
|
||||
|
||||
def generate_grid_form_field(self, field):
|
||||
field_name = field.name
|
||||
field_label = getattr(field, 'verbose_name', field_name)
|
||||
if field_name == 'status':
|
||||
return "{ component: 'Select', fieldName: 'status', label: '状态', componentProps: { allowClear: true, options: [{ label: '启用', value: 1 }, { label: '禁用', value: 0 }] } },"
|
||||
return f''' {{
|
||||
component: 'Select',
|
||||
fieldName: 'status',
|
||||
label: '状态',
|
||||
componentProps: {{
|
||||
allowClear: true,
|
||||
options: [
|
||||
{{ label: '启用', value: 1 }},
|
||||
{{ label: '禁用', value: 0 }},
|
||||
],
|
||||
}},
|
||||
}},'''
|
||||
if isinstance(field, models.CharField):
|
||||
return f"{{ component: 'Input', fieldName: '{field_name}', label: '{field_label}' }},"
|
||||
return f''' {{
|
||||
component: 'Input',
|
||||
fieldName: '{field_name}',
|
||||
label: '{field_label}',
|
||||
}},'''
|
||||
elif isinstance(field, models.IntegerField):
|
||||
return f"{{ component: 'InputNumber', fieldName: '{field_name}', label: '{field_label}' }},"
|
||||
return f''' {{
|
||||
component: 'InputNumber',
|
||||
fieldName: '{field_name}',
|
||||
label: '{field_label}',
|
||||
}},'''
|
||||
else:
|
||||
return f"{{ component: 'Input', fieldName: '{field_name}', label: '{field_label}' }},"
|
||||
return f''' {{
|
||||
component: 'Input',
|
||||
fieldName: '{field_name}',
|
||||
label: '{field_label}',
|
||||
}},'''
|
||||
|
||||
def get_columns_code(self, fields):
|
||||
columns = []
|
||||
for field in fields:
|
||||
if field.name == 'status':
|
||||
columns.append("{ field: 'status', title: '状态', cellRender: { name: 'CellTag' } },")
|
||||
columns.append(
|
||||
""" {
|
||||
field: 'status',
|
||||
title: '状态',
|
||||
cellRender: { name: 'CellTag' },
|
||||
},"""
|
||||
)
|
||||
continue
|
||||
if isinstance(field, (models.DateField, models.DateTimeField)):
|
||||
columns.append(f"{{ field: '{field.name}', title: '{getattr(field, 'verbose_name', field.name)}', width: 150, formatter: ({{ cellValue }}) => format_datetime(cellValue) }},")
|
||||
columns.append(
|
||||
f""" {{
|
||||
field: '{field.name}',
|
||||
title: '{getattr(field, 'verbose_name', field.name)}',
|
||||
width: 150,
|
||||
formatter: ({{ cellValue }}) => format_datetime(cellValue),
|
||||
}},"""
|
||||
)
|
||||
continue
|
||||
columns.append(f"{{ field: '{field.name}', title: '{getattr(field, 'verbose_name', field.name)}' }},")
|
||||
columns.append(
|
||||
f""" {{
|
||||
field: '{field.name}',
|
||||
title: '{getattr(field, 'verbose_name', field.name)}',
|
||||
}},"""
|
||||
)
|
||||
return columns
|
||||
@@ -1,6 +1,8 @@
|
||||
from $app_name.models import $model_name
|
||||
from utils.serializers import CustomModelSerializer
|
||||
from utils.custom_model_viewSet import CustomModelViewSet
|
||||
from django_filters import rest_framework as filters
|
||||
|
||||
|
||||
class ${model_name}Serializer(CustomModelSerializer):
|
||||
"""
|
||||
@@ -12,13 +14,20 @@ class ${model_name}Serializer(CustomModelSerializer):
|
||||
read_only_fields = ['id', 'create_time', 'update_time']
|
||||
|
||||
|
||||
class $model_nameFilter(filters.FilterSet):
|
||||
|
||||
class Meta:
|
||||
model = $model_name
|
||||
fields = [$filterset_fields]
|
||||
|
||||
|
||||
class ${model_name}ViewSet(CustomModelViewSet):
|
||||
"""
|
||||
$verbose_name 视图集
|
||||
"""
|
||||
queryset = $model_name.objects.filter(is_deleted=False).order_by('-id')
|
||||
serializer_class = ${model_name}Serializer
|
||||
filterset_fields = [$filterset_fields]
|
||||
filterset_class = [$filterset_fields]
|
||||
search_fields = ['name'] # 根据实际字段调整
|
||||
ordering_fields = ['create_time', 'id']
|
||||
ordering = ['-create_time']
|
||||
|
||||
@@ -22,7 +22,7 @@ class DictDataFilter(filters.FilterSet):
|
||||
|
||||
|
||||
class DictDataLabelValueSerializer(serializers.ModelSerializer):
|
||||
dict_type_value = serializers.CharField(source='dict_type.value')
|
||||
dict_type = serializers.CharField(source='dict_type.value')
|
||||
|
||||
class Meta:
|
||||
model = DictData
|
||||
@@ -39,4 +39,4 @@ class DictDataViewSet(CustomModelViewSet):
|
||||
# 复用filterset_class过滤DictData
|
||||
queryset = self.get_queryset().filter(status=CommonStatus.ENABLED)
|
||||
serializer = DictDataLabelValueSerializer(queryset, many=True)
|
||||
return Response(serializer.data)
|
||||
return self._build_response(data=serializer.data)
|
||||
@@ -7,12 +7,17 @@ import { preferences, usePreferences } from '@vben/preferences';
|
||||
import { App, ConfigProvider, theme } from 'ant-design-vue';
|
||||
|
||||
import { antdLocale } from '#/locales';
|
||||
import { useDictStore } from '#/store/dict';
|
||||
|
||||
defineOptions({ name: 'App' });
|
||||
|
||||
const { isDark } = usePreferences();
|
||||
const { tokens } = useAntdDesignTokens();
|
||||
|
||||
const dictStore = useDictStore();
|
||||
|
||||
dictStore.fetchDictData();
|
||||
|
||||
const tokenTheme = computed(() => {
|
||||
const algorithm = isDark.value
|
||||
? [theme.darkAlgorithm]
|
||||
|
||||
23
web/apps/web-antd/src/hooks/useDictOptions.ts
Normal file
23
web/apps/web-antd/src/hooks/useDictOptions.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { useDictStore } from '#/store/dict';
|
||||
|
||||
export function useDictOptions(dictType: string) {
|
||||
const dictStore = useDictStore();
|
||||
return dictStore.getOptionsByType(dictType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用字典 value 转 label
|
||||
* @param dictType 字典类型
|
||||
* @param value 字典值
|
||||
* @returns label
|
||||
*/
|
||||
export function useDictLabel(dictType: string, value: any): string {
|
||||
const options = useDictOptions(dictType);
|
||||
const item = options.find((opt) => opt.value === value);
|
||||
return item ? item.label : value;
|
||||
}
|
||||
|
||||
export function dictFormatter(dictType: string) {
|
||||
return ({ cellValue }: { cellValue: any }) =>
|
||||
useDictLabel(dictType, cellValue);
|
||||
}
|
||||
@@ -11,6 +11,7 @@ export namespace AiAIApiKeyApi {
|
||||
is_deleted: boolean;
|
||||
name: string;
|
||||
platform: string;
|
||||
model_type: string;
|
||||
api_key: string;
|
||||
url: string;
|
||||
status: number;
|
||||
|
||||
18
web/apps/web-antd/src/store/dict.ts
Normal file
18
web/apps/web-antd/src/store/dict.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
// src/store/dict.ts
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
import { getDictDataSimple } from '#/api/system/dict_data'; // 根据实际路径调整
|
||||
|
||||
export const useDictStore = defineStore('dict', {
|
||||
state: () => ({
|
||||
dictData: [] as any[],
|
||||
}),
|
||||
actions: {
|
||||
async fetchDictData() {
|
||||
this.dictData = await getDictDataSimple(); // 根据接口返回结构调整
|
||||
},
|
||||
getOptionsByType(type: string) {
|
||||
return this.dictData.filter((item) => item.dict_type === type);
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -28,8 +28,8 @@ export function useSchema(): VbenFormSchema[] {
|
||||
label: '平台',
|
||||
componentProps: {
|
||||
options: PLATFORM_OPTIONS,
|
||||
style: { minWidth: '180px' },
|
||||
dropdownStyle: { minWidth: '180px' },
|
||||
class: 'w-full',
|
||||
placeholder: '请选择',
|
||||
},
|
||||
rules: z.string(),
|
||||
},
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { OnActionClickFn } from '#/adapter/vxe-table';
|
||||
import type { AiAIModelApi } from '#/models/ai/ai_model';
|
||||
|
||||
import { z } from '#/adapter/form';
|
||||
import { dictFormatter, useDictOptions } from '#/hooks/useDictOptions';
|
||||
import { $t } from '#/locales';
|
||||
import { AiAIApiKeyModel } from '#/models/ai/ai_api_key';
|
||||
import { op } from '#/utils/permission';
|
||||
@@ -27,15 +28,27 @@ export function useSchema(): VbenFormSchema[] {
|
||||
},
|
||||
fieldName: 'key',
|
||||
label: 'API 秘钥',
|
||||
rules: z.number(),
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
component: 'ApiSelect',
|
||||
fieldName: 'platform',
|
||||
componentProps: {
|
||||
options: useDictOptions('ai_platform'),
|
||||
class: 'w-full',
|
||||
},
|
||||
label: '模型平台',
|
||||
rules: z
|
||||
.string()
|
||||
.min(1, $t('ui.formRules.required', ['模型平台']))
|
||||
.max(100, $t('ui.formRules.maxLength', ['模型平台', 100])),
|
||||
rules: z.string(),
|
||||
},
|
||||
{
|
||||
component: 'ApiSelect',
|
||||
fieldName: 'model_type',
|
||||
componentProps: {
|
||||
options: useDictOptions('ai_model_type'),
|
||||
class: 'w-full',
|
||||
},
|
||||
label: '模型类型',
|
||||
rules: z.string(),
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
@@ -59,6 +72,9 @@ export function useSchema(): VbenFormSchema[] {
|
||||
component: 'InputNumber',
|
||||
fieldName: 'sort',
|
||||
label: '排序',
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'RadioGroup',
|
||||
@@ -82,12 +98,18 @@ export function useSchema(): VbenFormSchema[] {
|
||||
{
|
||||
component: 'InputNumber',
|
||||
fieldName: 'max_tokens',
|
||||
label: '回复数 Token 数',
|
||||
label: '回复Token数',
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'InputNumber',
|
||||
fieldName: 'max_contexts',
|
||||
label: '上下文数量',
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
@@ -162,6 +184,12 @@ export function useColumns(
|
||||
{
|
||||
field: 'platform',
|
||||
title: '模型平台',
|
||||
formatter: dictFormatter('ai_platform'),
|
||||
},
|
||||
{
|
||||
field: 'model_type',
|
||||
title: '模型类型',
|
||||
formatter: dictFormatter('ai_model_type'),
|
||||
},
|
||||
{
|
||||
field: 'model',
|
||||
|
||||
Reference in New Issue
Block a user