diff --git a/backend/Dockerfile b/backend/Dockerfile index d8064f4..5232f28 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -15,6 +15,8 @@ COPY . . RUN pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple # 入口命令由 docker-compose 控制 +# 数据库迁移 +RUN python manage.py makemigrations && python manage.py migrate # 收集静态文件 RUN python manage.py collectstatic --noinput diff --git a/backend/system/management/commands/generate_crud.py b/backend/system/management/commands/generate_crud.py index 85bb460..65948f8 100644 --- a/backend/system/management/commands/generate_crud.py +++ b/backend/system/management/commands/generate_crud.py @@ -164,12 +164,23 @@ class Command(BaseCommand): field_config = self.generate_form_field(field) if field_config: form_fields.append(field_config) + + # useGridFormSchema + grid_form_fields = [] + for field in business_fields: + if field.name in ['id']: + continue + field_config = self.generate_grid_form_field(field) + if field_config: + grid_form_fields.append(field_config) + # 生成 useColumns columns = [] for field in business_fields + core_fields: columns.append(f" {{\n field: '{field.name}',\n title: '{getattr(field, 'verbose_name', field.name)}',\n }},") context = get_context(app_name, model_name, model, model_name_snake) context['form_fields'] = '\n'.join(form_fields) + context['grid_form_fields'] = '\n'.join(grid_form_fields) context['columns'] = '\n'.join(columns) data_path = f'../web/apps/web-antd/src/views/{app_name.lower()}/{model_name_snake}/data.ts' data_code = render_tpl('frontend_data.ts.tpl', context) @@ -219,4 +230,16 @@ class Command(BaseCommand): elif isinstance(field, models.BooleanField): return f''' {{\n component: 'RadioGroup',\n componentProps: {{\n buttonStyle: 'solid',\n options: [\n {{ label: '开启', value: 1 }},\n {{ label: '关闭', value: 0 }},\n ],\n optionType: 'button',\n }},\n defaultValue: 1,\n fieldName: '{field_name}',\n label: '{field_label}',\n }},''' else: - return f''' {{\n component: 'Input',\n fieldName: '{field_name}',\n label: '{field_label}',\n }},''' \ No newline at end of file + return f''' {{\n component: 'Input',\n fieldName: '{field_name}',\n label: '{field_label}',\n }},''' + + def generate_grid_form_field(self, field): + field_name = field.name + field_label = getattr(field, 'verbose_name', field_name) + # 查询表单一般只需要 component/fieldName/label,不需要 rules + if isinstance(field, models.CharField): + return f''' {{\n component: 'Input',\n fieldName: '{field_name}',\n label: '{field_label}',\n }},''' + elif isinstance(field, models.IntegerField): + return f''' {{\n component: 'InputNumber',\n fieldName: '{field_name}',\n label: '{field_label}',\n }},''' + # 其他类型同理 + else: + return f''' {{\n component: 'Input',\n fieldName: '{field_name}',\n label: '{field_label}',\n }},''' \ No newline at end of file diff --git a/backend/system/management/commands/tpl/frontend_data.ts.tpl b/backend/system/management/commands/tpl/frontend_data.ts.tpl index dd69d3f..e0ce5c5 100644 --- a/backend/system/management/commands/tpl/frontend_data.ts.tpl +++ b/backend/system/management/commands/tpl/frontend_data.ts.tpl @@ -23,7 +23,7 @@ ${form_fields} */ export function useGridFormSchema(): VbenFormSchema[] { return [ -${form_fields} +${grid_form_fields} ]; } diff --git a/backend/system/management/commands/tpl/frontend_form.vue.tpl b/backend/system/management/commands/tpl/frontend_form.vue.tpl index 81d2d63..10c6c8f 100644 --- a/backend/system/management/commands/tpl/frontend_form.vue.tpl +++ b/backend/system/management/commands/tpl/frontend_form.vue.tpl @@ -20,8 +20,8 @@ const formModel = new ${app_name_camel}${model_name}Model(); const formData = ref<${app_name_camel}${model_name}Api.${app_name_camel}${model_name}>(); const getTitle = computed(() => { return formData.value?.id - ? $$t('ui.actionTitle.edit', [$$t('${app_name}.${model_name_lower}.name')]) - : $$t('ui.actionTitle.create', [$$t('${app_name}.${model_name_lower}.name')]); + ? $$t('ui.actionTitle.edit', [$$t('${app_name}.${model_name_snake}.name')]) + : $$t('ui.actionTitle.create', [$$t('${app_name}.${model_name_snake}.name')]); }); const [Form, formApi] = useVbenForm({ diff --git a/web/apps/web-antd/src/views/ai/ai_api_key/data.ts b/web/apps/web-antd/src/views/ai/ai_api_key/data.ts index a078c64..312627c 100644 --- a/web/apps/web-antd/src/views/ai/ai_api_key/data.ts +++ b/web/apps/web-antd/src/views/ai/ai_api_key/data.ts @@ -16,52 +16,55 @@ export function useSchema(): VbenFormSchema[] { { component: 'Input', fieldName: 'name', - label: 'name', + label: '名称', rules: z .string() - .min(1, $t('ui.formRules.required', ['name'])) - .max(100, $t('ui.formRules.maxLength', ['name', 100])), + .min(1, $t('ui.formRules.required', ['名称'])) + .max(100, $t('ui.formRules.maxLength', ['名称', 100])), }, { - component: 'Input', + component: 'Select', fieldName: 'platform', - label: 'platform', - rules: z - .string() - .min(1, $t('ui.formRules.required', ['platform'])) - .max(100, $t('ui.formRules.maxLength', ['platform', 100])), + label: '平台', + componentProps: { + options: PLATFORM_OPTIONS, + style: { minWidth: '180px' }, + dropdownStyle: { minWidth: '180px' }, + }, + rules: z.string(), }, { component: 'Input', fieldName: 'api_key', - label: 'api key', + label: '密钥', rules: z .string() - .min(1, $t('ui.formRules.required', ['api key'])) - .max(100, $t('ui.formRules.maxLength', ['api key', 100])), + .min(1, $t('ui.formRules.required', ['密钥'])) + .max(100, $t('ui.formRules.maxLength', ['密钥', 100])), }, { component: 'Input', fieldName: 'url', - label: 'url', - rules: z - .string() - .min(1, $t('ui.formRules.required', ['url'])) - .max(100, $t('ui.formRules.maxLength', ['url', 100])), + label: '自定义 API 地址', }, { - component: 'InputNumber', + 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: '状态', + label: $t('system.status'), }, { component: 'Input', fieldName: 'remark', - label: 'remark', - rules: z - .string() - .min(1, $t('ui.formRules.required', ['remark'])) - .max(100, $t('ui.formRules.maxLength', ['remark', 100])), + label: $t('system.remark'), }, ]; } @@ -74,32 +77,28 @@ export function useGridFormSchema(): VbenFormSchema[] { { component: 'Input', fieldName: 'name', - label: 'name', + label: '名称', }, { - component: 'Input', + component: 'Select', fieldName: 'platform', - label: 'platform', + label: '平台', + componentProps: { + allowClear: true, + options: PLATFORM_OPTIONS, + }, }, { - component: 'Input', - fieldName: 'api_key', - label: 'api key', - }, - { - component: 'Input', - fieldName: 'url', - label: 'url', - }, - { - component: 'InputNumber', + component: 'Select', fieldName: 'status', label: '状态', - }, - { - component: 'Input', - fieldName: 'remark', - label: 'remark', + componentProps: { + allowClear: true, + options: [ + { label: '启用', value: 1 }, + { label: '禁用', value: 0 }, + ], + }, }, ]; } @@ -112,6 +111,7 @@ export function useGridFormSchema(): VbenFormSchema[] { export function useColumns( onActionClick?: OnActionClickFn, ): VxeTableGridOptions['columns'] { + // 平台映射表 return [ { field: 'id', @@ -119,47 +119,32 @@ export function useColumns( }, { field: 'name', - title: 'name', + title: '名称', }, { + cellRender: { + name: 'CellTag', + }, field: 'platform', - title: 'platform', - }, - { - field: 'api_key', - title: 'api key', + title: '平台', + formatter: ({ cellValue }: { cellValue: string }) => { + return PLATFORM_MAP[String(cellValue)] || String(cellValue); + }, }, { field: 'url', - title: 'url', + title: '自定义 API 地址', }, { + cellRender: { + name: 'CellTag', + }, field: 'status', title: '状态', }, { field: 'remark', - title: 'remark', - }, - { - field: 'creator', - title: 'creator', - }, - { - field: 'modifier', - title: 'modifier', - }, - { - field: 'update_time', - title: 'update time', - }, - { - field: 'create_time', - title: 'create time', - }, - { - field: 'is_deleted', - title: 'is deleted', + title: '备注', }, { align: 'center', @@ -182,3 +167,27 @@ export function useColumns( }, ]; } + +// 平台选项和映射常量 +export const PLATFORM_OPTIONS = [ + { label: 'OpenAI 微软', value: 'AzureOpenAI' }, + { label: 'OpenAI', value: 'OpenAI' }, + { label: 'Ollama', value: 'Ollama' }, + { label: '文心一言', value: 'YiYan' }, + { label: '讯飞星火', value: 'XingHuo' }, + { label: '通义千问', value: 'TongYi' }, + { label: 'StableDiffusion', value: 'StableDiffusion' }, + { label: 'Midjourney', value: 'Midjourney' }, + { label: 'Suno', value: 'Suno' }, + { label: 'DeepSeek', value: 'DeepSeek' }, + { label: '字节豆包', value: 'DouBao' }, + { label: '腾讯混元', value: 'HunYuan' }, + { label: '硅基流动', value: 'SiliconFlow' }, + { label: '智谱', value: 'ZhiPu' }, + { label: 'MiniMax', value: 'MiniMax' }, + { label: '月之暗灭', value: 'Moonshot' }, + { label: '百川智能', value: 'BaiChuan' }, +]; +export const PLATFORM_MAP: Record = Object.fromEntries( + PLATFORM_OPTIONS.map((opt) => [opt.value, opt.label]), +); diff --git a/web/apps/web-antd/src/views/ai/ai_api_key/modules/form.vue b/web/apps/web-antd/src/views/ai/ai_api_key/modules/form.vue index dd0f99c..a32ec36 100644 --- a/web/apps/web-antd/src/views/ai/ai_api_key/modules/form.vue +++ b/web/apps/web-antd/src/views/ai/ai_api_key/modules/form.vue @@ -20,8 +20,8 @@ const formModel = new AiAIApiKeyModel(); const formData = ref(); const getTitle = computed(() => { return formData.value?.id - ? $t('ui.actionTitle.edit', [$t('ai.aiapikey.name')]) - : $t('ui.actionTitle.create', [$t('ai.aiapikey.name')]); + ? $t('ui.actionTitle.edit', [$t('ai.ai_api_key.name')]) + : $t('ui.actionTitle.create', [$t('ai.ai_api_key.name')]); }); const [Form, formApi] = useVbenForm({ diff --git a/web/apps/web-antd/src/views/system/post/data.ts b/web/apps/web-antd/src/views/system/post/data.ts index 6a1e2ba..8748e24 100644 --- a/web/apps/web-antd/src/views/system/post/data.ts +++ b/web/apps/web-antd/src/views/system/post/data.ts @@ -7,7 +7,7 @@ import type { SystemPostApi } from '#/models/system/post'; import { z } from '#/adapter/form'; import { $t } from '#/locales'; import { format_datetime } from '#/utils/date'; -import {op} from "#/utils/permission"; +import { op } from '#/utils/permission'; /** * 获取编辑表单的字段配置