优化全局注入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,
|
on_delete=models.CASCADE,
|
||||||
db_comment='API 秘钥编号', verbose_name="API 秘钥编号"
|
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="模型平台")
|
platform = models.CharField(max_length=32, db_comment="模型平台", verbose_name="模型平台")
|
||||||
model = models.CharField(max_length=64, 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_name = field.name
|
||||||
field_label = getattr(field, 'verbose_name', field_name)
|
field_label = getattr(field, 'verbose_name', field_name)
|
||||||
if field_name == 'status':
|
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):
|
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):
|
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):
|
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):
|
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:
|
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):
|
def generate_grid_form_field(self, field):
|
||||||
field_name = field.name
|
field_name = field.name
|
||||||
field_label = getattr(field, 'verbose_name', field_name)
|
field_label = getattr(field, 'verbose_name', field_name)
|
||||||
if field_name == 'status':
|
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):
|
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):
|
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:
|
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):
|
def get_columns_code(self, fields):
|
||||||
columns = []
|
columns = []
|
||||||
for field in fields:
|
for field in fields:
|
||||||
if field.name == 'status':
|
if field.name == 'status':
|
||||||
columns.append("{ field: 'status', title: '状态', cellRender: { name: 'CellTag' } },")
|
columns.append(
|
||||||
|
""" {
|
||||||
|
field: 'status',
|
||||||
|
title: '状态',
|
||||||
|
cellRender: { name: 'CellTag' },
|
||||||
|
},"""
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
if isinstance(field, (models.DateField, models.DateTimeField)):
|
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
|
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
|
return columns
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
from $app_name.models import $model_name
|
from $app_name.models import $model_name
|
||||||
from utils.serializers import CustomModelSerializer
|
from utils.serializers import CustomModelSerializer
|
||||||
from utils.custom_model_viewSet import CustomModelViewSet
|
from utils.custom_model_viewSet import CustomModelViewSet
|
||||||
|
from django_filters import rest_framework as filters
|
||||||
|
|
||||||
|
|
||||||
class ${model_name}Serializer(CustomModelSerializer):
|
class ${model_name}Serializer(CustomModelSerializer):
|
||||||
"""
|
"""
|
||||||
@@ -12,13 +14,20 @@ class ${model_name}Serializer(CustomModelSerializer):
|
|||||||
read_only_fields = ['id', 'create_time', 'update_time']
|
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):
|
class ${model_name}ViewSet(CustomModelViewSet):
|
||||||
"""
|
"""
|
||||||
$verbose_name 视图集
|
$verbose_name 视图集
|
||||||
"""
|
"""
|
||||||
queryset = $model_name.objects.filter(is_deleted=False).order_by('-id')
|
queryset = $model_name.objects.filter(is_deleted=False).order_by('-id')
|
||||||
serializer_class = ${model_name}Serializer
|
serializer_class = ${model_name}Serializer
|
||||||
filterset_fields = [$filterset_fields]
|
filterset_class = [$filterset_fields]
|
||||||
search_fields = ['name'] # 根据实际字段调整
|
search_fields = ['name'] # 根据实际字段调整
|
||||||
ordering_fields = ['create_time', 'id']
|
ordering_fields = ['create_time', 'id']
|
||||||
ordering = ['-create_time']
|
ordering = ['-create_time']
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class DictDataFilter(filters.FilterSet):
|
|||||||
|
|
||||||
|
|
||||||
class DictDataLabelValueSerializer(serializers.ModelSerializer):
|
class DictDataLabelValueSerializer(serializers.ModelSerializer):
|
||||||
dict_type_value = serializers.CharField(source='dict_type.value')
|
dict_type = serializers.CharField(source='dict_type.value')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = DictData
|
model = DictData
|
||||||
@@ -39,4 +39,4 @@ class DictDataViewSet(CustomModelViewSet):
|
|||||||
# 复用filterset_class过滤DictData
|
# 复用filterset_class过滤DictData
|
||||||
queryset = self.get_queryset().filter(status=CommonStatus.ENABLED)
|
queryset = self.get_queryset().filter(status=CommonStatus.ENABLED)
|
||||||
serializer = DictDataLabelValueSerializer(queryset, many=True)
|
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 { App, ConfigProvider, theme } from 'ant-design-vue';
|
||||||
|
|
||||||
import { antdLocale } from '#/locales';
|
import { antdLocale } from '#/locales';
|
||||||
|
import { useDictStore } from '#/store/dict';
|
||||||
|
|
||||||
defineOptions({ name: 'App' });
|
defineOptions({ name: 'App' });
|
||||||
|
|
||||||
const { isDark } = usePreferences();
|
const { isDark } = usePreferences();
|
||||||
const { tokens } = useAntdDesignTokens();
|
const { tokens } = useAntdDesignTokens();
|
||||||
|
|
||||||
|
const dictStore = useDictStore();
|
||||||
|
|
||||||
|
dictStore.fetchDictData();
|
||||||
|
|
||||||
const tokenTheme = computed(() => {
|
const tokenTheme = computed(() => {
|
||||||
const algorithm = isDark.value
|
const algorithm = isDark.value
|
||||||
? [theme.darkAlgorithm]
|
? [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;
|
is_deleted: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
platform: string;
|
platform: string;
|
||||||
|
model_type: string;
|
||||||
api_key: string;
|
api_key: string;
|
||||||
url: string;
|
url: string;
|
||||||
status: number;
|
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: '平台',
|
label: '平台',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
options: PLATFORM_OPTIONS,
|
options: PLATFORM_OPTIONS,
|
||||||
style: { minWidth: '180px' },
|
class: 'w-full',
|
||||||
dropdownStyle: { minWidth: '180px' },
|
placeholder: '请选择',
|
||||||
},
|
},
|
||||||
rules: z.string(),
|
rules: z.string(),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type { OnActionClickFn } from '#/adapter/vxe-table';
|
|||||||
import type { AiAIModelApi } from '#/models/ai/ai_model';
|
import type { AiAIModelApi } from '#/models/ai/ai_model';
|
||||||
|
|
||||||
import { z } from '#/adapter/form';
|
import { z } from '#/adapter/form';
|
||||||
|
import { dictFormatter, useDictOptions } from '#/hooks/useDictOptions';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
import { AiAIApiKeyModel } from '#/models/ai/ai_api_key';
|
import { AiAIApiKeyModel } from '#/models/ai/ai_api_key';
|
||||||
import { op } from '#/utils/permission';
|
import { op } from '#/utils/permission';
|
||||||
@@ -27,15 +28,27 @@ export function useSchema(): VbenFormSchema[] {
|
|||||||
},
|
},
|
||||||
fieldName: 'key',
|
fieldName: 'key',
|
||||||
label: 'API 秘钥',
|
label: 'API 秘钥',
|
||||||
|
rules: z.number(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: 'Input',
|
component: 'ApiSelect',
|
||||||
fieldName: 'platform',
|
fieldName: 'platform',
|
||||||
|
componentProps: {
|
||||||
|
options: useDictOptions('ai_platform'),
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
label: '模型平台',
|
label: '模型平台',
|
||||||
rules: z
|
rules: z.string(),
|
||||||
.string()
|
},
|
||||||
.min(1, $t('ui.formRules.required', ['模型平台']))
|
{
|
||||||
.max(100, $t('ui.formRules.maxLength', ['模型平台', 100])),
|
component: 'ApiSelect',
|
||||||
|
fieldName: 'model_type',
|
||||||
|
componentProps: {
|
||||||
|
options: useDictOptions('ai_model_type'),
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
|
label: '模型类型',
|
||||||
|
rules: z.string(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
@@ -59,6 +72,9 @@ export function useSchema(): VbenFormSchema[] {
|
|||||||
component: 'InputNumber',
|
component: 'InputNumber',
|
||||||
fieldName: 'sort',
|
fieldName: 'sort',
|
||||||
label: '排序',
|
label: '排序',
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: 'RadioGroup',
|
component: 'RadioGroup',
|
||||||
@@ -82,12 +98,18 @@ export function useSchema(): VbenFormSchema[] {
|
|||||||
{
|
{
|
||||||
component: 'InputNumber',
|
component: 'InputNumber',
|
||||||
fieldName: 'max_tokens',
|
fieldName: 'max_tokens',
|
||||||
label: '回复数 Token 数',
|
label: '回复Token数',
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: 'InputNumber',
|
component: 'InputNumber',
|
||||||
fieldName: 'max_contexts',
|
fieldName: 'max_contexts',
|
||||||
label: '上下文数量',
|
label: '上下文数量',
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
@@ -162,6 +184,12 @@ export function useColumns(
|
|||||||
{
|
{
|
||||||
field: 'platform',
|
field: 'platform',
|
||||||
title: '模型平台',
|
title: '模型平台',
|
||||||
|
formatter: dictFormatter('ai_platform'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'model_type',
|
||||||
|
title: '模型类型',
|
||||||
|
formatter: dictFormatter('ai_model_type'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'model',
|
field: 'model',
|
||||||
|
|||||||
Reference in New Issue
Block a user