From 65bdda6377cde3c0226c7f8af7a2e7100c5b4ec3 Mon Sep 17 00:00:00 2001 From: xie7654 <765462425@qq.com> Date: Thu, 10 Jul 2025 15:32:00 +0800 Subject: [PATCH] init ai api key --- backend/ai/__init__.py | 0 backend/ai/admin.py | 3 + backend/ai/apps.py | 6 + backend/ai/choices.py | 21 + backend/ai/migrations/0001_initial.py | 214 ++++++ ...key_alter_aiapikey_create_time_and_more.py | 262 ++++++++ backend/ai/migrations/__init__.py | 0 backend/ai/models.py | 61 ++ backend/ai/tests.py | 3 + backend/ai/urls.py | 11 + backend/ai/views.py | 3 + backend/ai/views/__init__.py | 5 + backend/ai/views/ai_api_key.py | 27 + backend/backend/settings.py | 1 + backend/backend/urls.py | 1 + .../commands/tpl/frontend_data.ts.tpl | 1 + ..._creator_alter_dept_is_deleted_and_more.py | 617 ++++++++++++++++++ backend/utils/models.py | 16 +- docs/docs/essential/module_creation_guide.md | 137 ++++ docs/docs/essential/new_module.png | Bin 0 -> 60034 bytes .../web-antd/src/locales/langs/en-US/ai.json | 7 + .../web-antd/src/locales/langs/zh-CN/ai.json | 7 + web/apps/web-antd/src/models/ai/ai_api_key.ts | 24 + .../web-antd/src/views/ai/ai_api_key/data.ts | 184 ++++++ .../web-antd/src/views/ai/ai_api_key/list.vue | 141 ++++ .../src/views/ai/ai_api_key/modules/form.vue | 79 +++ 26 files changed, 1825 insertions(+), 6 deletions(-) create mode 100644 backend/ai/__init__.py create mode 100644 backend/ai/admin.py create mode 100644 backend/ai/apps.py create mode 100644 backend/ai/choices.py create mode 100644 backend/ai/migrations/0001_initial.py create mode 100644 backend/ai/migrations/0002_alter_aiapikey_api_key_alter_aiapikey_create_time_and_more.py create mode 100644 backend/ai/migrations/__init__.py create mode 100644 backend/ai/models.py create mode 100644 backend/ai/tests.py create mode 100644 backend/ai/urls.py create mode 100644 backend/ai/views.py create mode 100644 backend/ai/views/__init__.py create mode 100644 backend/ai/views/ai_api_key.py create mode 100644 backend/system/migrations/0002_alter_dept_creator_alter_dept_is_deleted_and_more.py create mode 100644 docs/docs/essential/module_creation_guide.md create mode 100644 docs/docs/essential/new_module.png create mode 100644 web/apps/web-antd/src/locales/langs/en-US/ai.json create mode 100644 web/apps/web-antd/src/locales/langs/zh-CN/ai.json create mode 100644 web/apps/web-antd/src/models/ai/ai_api_key.ts create mode 100644 web/apps/web-antd/src/views/ai/ai_api_key/data.ts create mode 100644 web/apps/web-antd/src/views/ai/ai_api_key/list.vue create mode 100644 web/apps/web-antd/src/views/ai/ai_api_key/modules/form.vue diff --git a/backend/ai/__init__.py b/backend/ai/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/ai/admin.py b/backend/ai/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/backend/ai/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/backend/ai/apps.py b/backend/ai/apps.py new file mode 100644 index 0000000..ad0ae5f --- /dev/null +++ b/backend/ai/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AiConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'ai' diff --git a/backend/ai/choices.py b/backend/ai/choices.py new file mode 100644 index 0000000..d0d8dd3 --- /dev/null +++ b/backend/ai/choices.py @@ -0,0 +1,21 @@ +from django.db import models + + +class PlatformChoices(models.TextChoices): + AZURE_OPENAI = 'AzureOpenAI', 'OpenAI 微软' + OPENAI = 'OpenAI', 'OpenAI' + OLLAMA = 'Ollama', 'Ollama' + YIYAN = 'YiYan', '文心一言' + XINGHUO = 'XingHuo', '讯飞星火' + TONGYI = 'TongYi', '通义千问' + STABLE_DIFFUSION = 'StableDiffusion', 'StableDiffusion' + MIDJOURNEY = 'Midjourney', 'Midjourney' + SUNO = 'Suno', 'Suno' + DEEPSEEK = 'DeepSeek', 'DeepSeek' + DOUBAO = 'DouBao', '字节豆包' + HUNYUAN = 'HunYuan', '腾讯混元' + SILICON_FLOW = 'SiliconFlow', '硅基流动' + ZHIPU = 'ZhiPu', '智谱' + MINIMAX = 'MiniMax', 'MiniMax' + MOONSHOT = 'Moonshot', '月之暗灭' + BAICHUAN = 'BaiChuan', '百川智能' \ No newline at end of file diff --git a/backend/ai/migrations/0001_initial.py b/backend/ai/migrations/0001_initial.py new file mode 100644 index 0000000..f1bcd6c --- /dev/null +++ b/backend/ai/migrations/0001_initial.py @@ -0,0 +1,214 @@ +# Generated by Django 5.2.1 on 2025-07-10 03:03 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="AIApiKey", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "remark", + models.CharField( + blank=True, + db_comment="备注", + help_text="备注", + max_length=256, + null=True, + ), + ), + ( + "creator", + models.CharField( + blank=True, + db_comment="创建人", + help_text="创建人", + max_length=64, + null=True, + ), + ), + ( + "modifier", + models.CharField( + blank=True, + db_comment="修改人", + help_text="修改人", + max_length=64, + null=True, + ), + ), + ( + "update_time", + models.DateTimeField( + auto_now=True, + db_comment="修改时间", + help_text="修改时间", + null=True, + ), + ), + ( + "create_time", + models.DateTimeField( + auto_now_add=True, + db_comment="创建时间", + help_text="创建时间", + null=True, + ), + ), + ( + "is_deleted", + models.BooleanField(db_comment="是否软删除", default=False), + ), + ("name", models.CharField(db_comment="名称", max_length=255)), + ("platform", models.CharField(db_comment="平台", max_length=255)), + ("api_key", models.CharField(db_comment="密钥", max_length=255)), + ( + "url", + models.CharField( + blank=True, + db_comment="自定义 API 地址", + max_length=255, + null=True, + ), + ), + ( + "status", + models.SmallIntegerField( + choices=[(0, "禁用"), (1, "启用")], + db_comment="状态", + default=0, + verbose_name="状态", + ), + ), + ], + options={ + "verbose_name": "AI API 密钥", + "verbose_name_plural": "AI API 密钥", + "db_table": "ai_api_key", + }, + ), + migrations.CreateModel( + name="AIModel", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "remark", + models.CharField( + blank=True, + db_comment="备注", + help_text="备注", + max_length=256, + null=True, + ), + ), + ( + "creator", + models.CharField( + blank=True, + db_comment="创建人", + help_text="创建人", + max_length=64, + null=True, + ), + ), + ( + "modifier", + models.CharField( + blank=True, + db_comment="修改人", + help_text="修改人", + max_length=64, + null=True, + ), + ), + ( + "update_time", + models.DateTimeField( + auto_now=True, + db_comment="修改时间", + help_text="修改时间", + null=True, + ), + ), + ( + "create_time", + models.DateTimeField( + auto_now_add=True, + db_comment="创建时间", + help_text="创建时间", + null=True, + ), + ), + ( + "is_deleted", + models.BooleanField(db_comment="是否软删除", default=False), + ), + ("name", models.CharField(db_comment="模型名字", max_length=64)), + ("sort", models.IntegerField(db_comment="排序", default=0)), + ( + "status", + models.SmallIntegerField( + choices=[(0, "禁用"), (1, "启用")], + db_comment="状态", + default=0, + verbose_name="状态", + ), + ), + ("platform", models.CharField(db_comment="模型平台", max_length=32)), + ("model", models.CharField(db_comment="模型标识", max_length=64)), + ( + "temperature", + models.FloatField(blank=True, db_comment="温度参数", null=True), + ), + ( + "max_tokens", + models.IntegerField( + blank=True, db_comment="单条回复的最大 Token 数量", null=True + ), + ), + ( + "max_contexts", + models.IntegerField( + blank=True, db_comment="上下文的最大 Message 数量", null=True + ), + ), + ( + "key", + models.ForeignKey( + db_comment="API 秘钥编号", + on_delete=django.db.models.deletion.CASCADE, + to="ai.aiapikey", + ), + ), + ], + options={ + "verbose_name": "AI 模型", + "verbose_name_plural": "AI 模型", + "db_table": "ai_model", + }, + ), + ] diff --git a/backend/ai/migrations/0002_alter_aiapikey_api_key_alter_aiapikey_create_time_and_more.py b/backend/ai/migrations/0002_alter_aiapikey_api_key_alter_aiapikey_create_time_and_more.py new file mode 100644 index 0000000..7333bd2 --- /dev/null +++ b/backend/ai/migrations/0002_alter_aiapikey_api_key_alter_aiapikey_create_time_and_more.py @@ -0,0 +1,262 @@ +# Generated by Django 5.2.1 on 2025-07-10 04:07 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("ai", "0001_initial"), + ] + + operations = [ + migrations.AlterField( + model_name="aiapikey", + name="api_key", + field=models.CharField( + db_comment="密钥", max_length=255, verbose_name="密钥" + ), + ), + migrations.AlterField( + model_name="aiapikey", + name="create_time", + field=models.DateTimeField( + auto_now_add=True, + db_comment="创建时间", + help_text="创建时间", + null=True, + verbose_name="创建时间", + ), + ), + migrations.AlterField( + model_name="aiapikey", + name="creator", + field=models.CharField( + blank=True, + db_comment="创建人", + help_text="创建人", + max_length=64, + null=True, + verbose_name="创建人", + ), + ), + migrations.AlterField( + model_name="aiapikey", + name="is_deleted", + field=models.BooleanField( + db_comment="是否软删除", default=False, verbose_name="是否软删除" + ), + ), + migrations.AlterField( + model_name="aiapikey", + name="modifier", + field=models.CharField( + blank=True, + db_comment="修改人", + help_text="修改人", + max_length=64, + null=True, + verbose_name="修改人", + ), + ), + migrations.AlterField( + model_name="aiapikey", + name="name", + field=models.CharField( + db_comment="名称", max_length=255, verbose_name="名称" + ), + ), + migrations.AlterField( + model_name="aiapikey", + name="platform", + field=models.CharField( + choices=[ + ("AzureOpenAI", "OpenAI 微软"), + ("OpenAI", "OpenAI"), + ("Ollama", "Ollama"), + ("YiYan", "文心一言"), + ("XingHuo", "讯飞星火"), + ("TongYi", "通义千问"), + ("StableDiffusion", "StableDiffusion"), + ("Midjourney", "Midjourney"), + ("Suno", "Suno"), + ("DeepSeek", "DeepSeek"), + ("DouBao", "字节豆包"), + ("HunYuan", "腾讯混元"), + ("SiliconFlow", "硅基流动"), + ("ZhiPu", "智谱"), + ("MiniMax", "MiniMax"), + ("Moonshot", "月之暗灭"), + ("BaiChuan", "百川智能"), + ], + db_comment="平台", + max_length=100, + verbose_name="平台", + ), + ), + migrations.AlterField( + model_name="aiapikey", + name="remark", + field=models.CharField( + blank=True, + db_comment="备注", + help_text="备注", + max_length=256, + null=True, + verbose_name="备注", + ), + ), + migrations.AlterField( + model_name="aiapikey", + name="update_time", + field=models.DateTimeField( + auto_now=True, + db_comment="修改时间", + help_text="修改时间", + null=True, + verbose_name="修改时间", + ), + ), + migrations.AlterField( + model_name="aiapikey", + name="url", + field=models.CharField( + blank=True, + db_comment="自定义 API 地址", + max_length=255, + null=True, + verbose_name="自定义 API 地址", + ), + ), + migrations.AlterField( + model_name="aimodel", + name="create_time", + field=models.DateTimeField( + auto_now_add=True, + db_comment="创建时间", + help_text="创建时间", + null=True, + verbose_name="创建时间", + ), + ), + migrations.AlterField( + model_name="aimodel", + name="creator", + field=models.CharField( + blank=True, + db_comment="创建人", + help_text="创建人", + max_length=64, + null=True, + verbose_name="创建人", + ), + ), + migrations.AlterField( + model_name="aimodel", + name="is_deleted", + field=models.BooleanField( + db_comment="是否软删除", default=False, verbose_name="是否软删除" + ), + ), + migrations.AlterField( + model_name="aimodel", + name="key", + field=models.ForeignKey( + db_comment="API 秘钥编号", + on_delete=django.db.models.deletion.CASCADE, + to="ai.aiapikey", + verbose_name="API 秘钥编号", + ), + ), + migrations.AlterField( + model_name="aimodel", + name="max_contexts", + field=models.IntegerField( + blank=True, + db_comment="上下文的最大 Message 数量", + null=True, + verbose_name="上下文的最大 Message 数量", + ), + ), + migrations.AlterField( + model_name="aimodel", + name="max_tokens", + field=models.IntegerField( + blank=True, + db_comment="单条回复的最大 Token 数量", + null=True, + verbose_name="单条回复的最大 Token 数量", + ), + ), + migrations.AlterField( + model_name="aimodel", + name="model", + field=models.CharField( + db_comment="模型标识", max_length=64, verbose_name="模型标识" + ), + ), + migrations.AlterField( + model_name="aimodel", + name="modifier", + field=models.CharField( + blank=True, + db_comment="修改人", + help_text="修改人", + max_length=64, + null=True, + verbose_name="修改人", + ), + ), + migrations.AlterField( + model_name="aimodel", + name="name", + field=models.CharField( + db_comment="模型名字", max_length=64, verbose_name="模型名字" + ), + ), + migrations.AlterField( + model_name="aimodel", + name="platform", + field=models.CharField( + db_comment="模型平台", max_length=32, verbose_name="模型平台" + ), + ), + migrations.AlterField( + model_name="aimodel", + name="remark", + field=models.CharField( + blank=True, + db_comment="备注", + help_text="备注", + max_length=256, + null=True, + verbose_name="备注", + ), + ), + migrations.AlterField( + model_name="aimodel", + name="sort", + field=models.IntegerField( + db_comment="排序", default=0, verbose_name="排序" + ), + ), + migrations.AlterField( + model_name="aimodel", + name="temperature", + field=models.FloatField( + blank=True, db_comment="温度参数", null=True, verbose_name="温度参数" + ), + ), + migrations.AlterField( + model_name="aimodel", + name="update_time", + field=models.DateTimeField( + auto_now=True, + db_comment="修改时间", + help_text="修改时间", + null=True, + verbose_name="修改时间", + ), + ), + ] diff --git a/backend/ai/migrations/__init__.py b/backend/ai/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/ai/models.py b/backend/ai/models.py new file mode 100644 index 0000000..7415569 --- /dev/null +++ b/backend/ai/models.py @@ -0,0 +1,61 @@ +from django.db import models + +from ai.choices import PlatformChoices +from utils.models import CommonStatus, CoreModel + +class AIApiKey(CoreModel): + """ AI API 密钥表 """ + name = models.CharField(max_length=255, db_comment="名称", verbose_name="名称") + platform = models.CharField( + max_length=100, + choices=PlatformChoices.choices, + verbose_name="平台", + db_comment="平台" + ) + api_key = models.CharField(max_length=255, db_comment="密钥", verbose_name="密钥") + url = models.CharField(max_length=255, null=True, blank=True, db_comment="自定义 API 地址", verbose_name="自定义 API 地址") + status = models.SmallIntegerField( + choices=CommonStatus.choices, + default=CommonStatus.DISABLED, + verbose_name="状态", + db_comment="状态", + ) + + class Meta: + db_table = "ai_api_key" + verbose_name = "AI API 密钥" + verbose_name_plural = verbose_name + + def __str__(self): + return self.name + + +class AIModel(CoreModel): + """ AI 模型 """ + name = models.CharField(max_length=64, db_comment="模型名字", verbose_name="模型名字") + sort = models.IntegerField(db_comment="排序", default=0, verbose_name="排序") + status = models.SmallIntegerField( + choices=CommonStatus.choices, + default=CommonStatus.DISABLED, + verbose_name="状态", + db_comment="状态", + ) + key = models.ForeignKey( + 'AIApiKey', + on_delete=models.CASCADE, + db_comment='API 秘钥编号', verbose_name="API 秘钥编号" + ) + platform = models.CharField(max_length=32, db_comment="模型平台", verbose_name="模型平台") + model = models.CharField(max_length=64, db_comment="模型标识", verbose_name="模型标识") + + temperature = models.FloatField(null=True, blank=True, db_comment="温度参数", verbose_name="温度参数") + max_tokens = models.IntegerField(null=True, blank=True, db_comment="单条回复的最大 Token 数量", verbose_name="单条回复的最大 Token 数量") + max_contexts = models.IntegerField(null=True, blank=True, db_comment="上下文的最大 Message 数量", verbose_name="上下文的最大 Message 数量") + + class Meta: + db_table = "ai_model" + verbose_name = "AI 模型" + verbose_name_plural = verbose_name + + def __str__(self): + return self.name diff --git a/backend/ai/tests.py b/backend/ai/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/backend/ai/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/backend/ai/urls.py b/backend/ai/urls.py new file mode 100644 index 0000000..08299ad --- /dev/null +++ b/backend/ai/urls.py @@ -0,0 +1,11 @@ +from django.urls import include, path +from rest_framework import routers + +from . import views + +router = routers.DefaultRouter() +router.register(r'ai_api_key', views.AIApiKeyViewSet) +urlpatterns = [ + path('', include(router.urls)), + +] \ No newline at end of file diff --git a/backend/ai/views.py b/backend/ai/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/backend/ai/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/backend/ai/views/__init__.py b/backend/ai/views/__init__.py new file mode 100644 index 0000000..348726c --- /dev/null +++ b/backend/ai/views/__init__.py @@ -0,0 +1,5 @@ +__all__ = [ + 'AIApiKeyViewSet' +] + +from ai.views.ai_api_key import AIApiKeyViewSet \ No newline at end of file diff --git a/backend/ai/views/ai_api_key.py b/backend/ai/views/ai_api_key.py new file mode 100644 index 0000000..abec12c --- /dev/null +++ b/backend/ai/views/ai_api_key.py @@ -0,0 +1,27 @@ +from ai.models import AIApiKey +from utils.serializers import CustomModelSerializer +from utils.custom_model_viewSet import CustomModelViewSet + +class AIApiKeySerializer(CustomModelSerializer): + """ + AI API 密钥 序列化器 + """ + class Meta: + model = AIApiKey + fields = '__all__' + read_only_fields = ['id', 'create_time', 'update_time'] + + +class AIApiKeyViewSet(CustomModelViewSet): + """ + AI API 密钥 视图集 + """ + queryset = AIApiKey.objects.filter(is_deleted=False).order_by('-id') + serializer_class = AIApiKeySerializer + filterset_fields = ['id', 'remark', 'creator', 'modifier', 'is_deleted', 'name', 'platform', 'api_key', 'url', 'status'] + search_fields = ['name'] # 根据实际字段调整 + ordering_fields = ['create_time', 'id'] + ordering = ['-create_time'] + +# 移入urls中 +# diff --git a/backend/backend/settings.py b/backend/backend/settings.py index 7e8abfa..2a9681c 100644 --- a/backend/backend/settings.py +++ b/backend/backend/settings.py @@ -54,6 +54,7 @@ INSTALLED_APPS = [ 'corsheaders', 'rest_framework.authtoken', "system", + "ai", ] MIDDLEWARE = [ diff --git a/backend/backend/urls.py b/backend/backend/urls.py index 04d3763..f6e6303 100644 --- a/backend/backend/urls.py +++ b/backend/backend/urls.py @@ -20,6 +20,7 @@ from django.conf import settings urlpatterns = [ path('api/system/', include('system.urls')), + path('api/ai/', include('ai.urls')), ] # 演示环境下禁用 admin 路由 diff --git a/backend/system/management/commands/tpl/frontend_data.ts.tpl b/backend/system/management/commands/tpl/frontend_data.ts.tpl index 4524623..dd69d3f 100644 --- a/backend/system/management/commands/tpl/frontend_data.ts.tpl +++ b/backend/system/management/commands/tpl/frontend_data.ts.tpl @@ -7,6 +7,7 @@ import type { ${app_name_camel}${model_name}Api } from '#/models/${app_name}/${m import { z } from '#/adapter/form'; import { $$t } from '#/locales'; import { format_datetime } from '#/utils/date'; +import { op } from '#/utils/permission'; /** * 获取编辑表单的字段配置 diff --git a/backend/system/migrations/0002_alter_dept_creator_alter_dept_is_deleted_and_more.py b/backend/system/migrations/0002_alter_dept_creator_alter_dept_is_deleted_and_more.py new file mode 100644 index 0000000..4af477c --- /dev/null +++ b/backend/system/migrations/0002_alter_dept_creator_alter_dept_is_deleted_and_more.py @@ -0,0 +1,617 @@ +# Generated by Django 5.2.1 on 2025-07-10 04:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("system", "0001_initial"), + ] + + operations = [ + migrations.AlterField( + model_name="dept", + name="creator", + field=models.CharField( + blank=True, + db_comment="创建人", + help_text="创建人", + max_length=64, + null=True, + verbose_name="创建人", + ), + ), + migrations.AlterField( + model_name="dept", + name="is_deleted", + field=models.BooleanField( + db_comment="是否软删除", default=False, verbose_name="是否软删除" + ), + ), + migrations.AlterField( + model_name="dept", + name="modifier", + field=models.CharField( + blank=True, + db_comment="修改人", + help_text="修改人", + max_length=64, + null=True, + verbose_name="修改人", + ), + ), + migrations.AlterField( + model_name="dept", + name="update_time", + field=models.DateTimeField( + auto_now=True, + db_comment="修改时间", + help_text="修改时间", + null=True, + verbose_name="修改时间", + ), + ), + migrations.AlterField( + model_name="dictdata", + name="create_time", + field=models.DateTimeField( + auto_now_add=True, + db_comment="创建时间", + help_text="创建时间", + null=True, + verbose_name="创建时间", + ), + ), + migrations.AlterField( + model_name="dictdata", + name="creator", + field=models.CharField( + blank=True, + db_comment="创建人", + help_text="创建人", + max_length=64, + null=True, + verbose_name="创建人", + ), + ), + migrations.AlterField( + model_name="dictdata", + name="is_deleted", + field=models.BooleanField( + db_comment="是否软删除", default=False, verbose_name="是否软删除" + ), + ), + migrations.AlterField( + model_name="dictdata", + name="modifier", + field=models.CharField( + blank=True, + db_comment="修改人", + help_text="修改人", + max_length=64, + null=True, + verbose_name="修改人", + ), + ), + migrations.AlterField( + model_name="dictdata", + name="remark", + field=models.CharField( + blank=True, + db_comment="备注", + help_text="备注", + max_length=256, + null=True, + verbose_name="备注", + ), + ), + migrations.AlterField( + model_name="dictdata", + name="update_time", + field=models.DateTimeField( + auto_now=True, + db_comment="修改时间", + help_text="修改时间", + null=True, + verbose_name="修改时间", + ), + ), + migrations.AlterField( + model_name="dicttype", + name="create_time", + field=models.DateTimeField( + auto_now_add=True, + db_comment="创建时间", + help_text="创建时间", + null=True, + verbose_name="创建时间", + ), + ), + migrations.AlterField( + model_name="dicttype", + name="creator", + field=models.CharField( + blank=True, + db_comment="创建人", + help_text="创建人", + max_length=64, + null=True, + verbose_name="创建人", + ), + ), + migrations.AlterField( + model_name="dicttype", + name="is_deleted", + field=models.BooleanField( + db_comment="是否软删除", default=False, verbose_name="是否软删除" + ), + ), + migrations.AlterField( + model_name="dicttype", + name="modifier", + field=models.CharField( + blank=True, + db_comment="修改人", + help_text="修改人", + max_length=64, + null=True, + verbose_name="修改人", + ), + ), + migrations.AlterField( + model_name="dicttype", + name="remark", + field=models.CharField( + blank=True, + db_comment="备注", + help_text="备注", + max_length=256, + null=True, + verbose_name="备注", + ), + ), + migrations.AlterField( + model_name="dicttype", + name="update_time", + field=models.DateTimeField( + auto_now=True, + db_comment="修改时间", + help_text="修改时间", + null=True, + verbose_name="修改时间", + ), + ), + migrations.AlterField( + model_name="loginlog", + name="create_time", + field=models.DateTimeField( + auto_now_add=True, + db_comment="创建时间", + help_text="创建时间", + null=True, + verbose_name="创建时间", + ), + ), + migrations.AlterField( + model_name="loginlog", + name="creator", + field=models.CharField( + blank=True, + db_comment="创建人", + help_text="创建人", + max_length=64, + null=True, + verbose_name="创建人", + ), + ), + migrations.AlterField( + model_name="loginlog", + name="is_deleted", + field=models.BooleanField( + db_comment="是否软删除", default=False, verbose_name="是否软删除" + ), + ), + migrations.AlterField( + model_name="loginlog", + name="modifier", + field=models.CharField( + blank=True, + db_comment="修改人", + help_text="修改人", + max_length=64, + null=True, + verbose_name="修改人", + ), + ), + migrations.AlterField( + model_name="loginlog", + name="remark", + field=models.CharField( + blank=True, + db_comment="备注", + help_text="备注", + max_length=256, + null=True, + verbose_name="备注", + ), + ), + migrations.AlterField( + model_name="loginlog", + name="update_time", + field=models.DateTimeField( + auto_now=True, + db_comment="修改时间", + help_text="修改时间", + null=True, + verbose_name="修改时间", + ), + ), + migrations.AlterField( + model_name="menu", + name="create_time", + field=models.DateTimeField( + auto_now_add=True, + db_comment="创建时间", + help_text="创建时间", + null=True, + verbose_name="创建时间", + ), + ), + migrations.AlterField( + model_name="menu", + name="creator", + field=models.CharField( + blank=True, + db_comment="创建人", + help_text="创建人", + max_length=64, + null=True, + verbose_name="创建人", + ), + ), + migrations.AlterField( + model_name="menu", + name="is_deleted", + field=models.BooleanField( + db_comment="是否软删除", default=False, verbose_name="是否软删除" + ), + ), + migrations.AlterField( + model_name="menu", + name="modifier", + field=models.CharField( + blank=True, + db_comment="修改人", + help_text="修改人", + max_length=64, + null=True, + verbose_name="修改人", + ), + ), + migrations.AlterField( + model_name="menu", + name="remark", + field=models.CharField( + blank=True, + db_comment="备注", + help_text="备注", + max_length=256, + null=True, + verbose_name="备注", + ), + ), + migrations.AlterField( + model_name="menu", + name="update_time", + field=models.DateTimeField( + auto_now=True, + db_comment="修改时间", + help_text="修改时间", + null=True, + verbose_name="修改时间", + ), + ), + migrations.AlterField( + model_name="menumeta", + name="create_time", + field=models.DateTimeField( + auto_now_add=True, + db_comment="创建时间", + help_text="创建时间", + null=True, + verbose_name="创建时间", + ), + ), + migrations.AlterField( + model_name="menumeta", + name="creator", + field=models.CharField( + blank=True, + db_comment="创建人", + help_text="创建人", + max_length=64, + null=True, + verbose_name="创建人", + ), + ), + migrations.AlterField( + model_name="menumeta", + name="is_deleted", + field=models.BooleanField( + db_comment="是否软删除", default=False, verbose_name="是否软删除" + ), + ), + migrations.AlterField( + model_name="menumeta", + name="modifier", + field=models.CharField( + blank=True, + db_comment="修改人", + help_text="修改人", + max_length=64, + null=True, + verbose_name="修改人", + ), + ), + migrations.AlterField( + model_name="menumeta", + name="remark", + field=models.CharField( + blank=True, + db_comment="备注", + help_text="备注", + max_length=256, + null=True, + verbose_name="备注", + ), + ), + migrations.AlterField( + model_name="menumeta", + name="update_time", + field=models.DateTimeField( + auto_now=True, + db_comment="修改时间", + help_text="修改时间", + null=True, + verbose_name="修改时间", + ), + ), + migrations.AlterField( + model_name="post", + name="create_time", + field=models.DateTimeField( + auto_now_add=True, + db_comment="创建时间", + help_text="创建时间", + null=True, + verbose_name="创建时间", + ), + ), + migrations.AlterField( + model_name="post", + name="creator", + field=models.CharField( + blank=True, + db_comment="创建人", + help_text="创建人", + max_length=64, + null=True, + verbose_name="创建人", + ), + ), + migrations.AlterField( + model_name="post", + name="is_deleted", + field=models.BooleanField( + db_comment="是否软删除", default=False, verbose_name="是否软删除" + ), + ), + migrations.AlterField( + model_name="post", + name="modifier", + field=models.CharField( + blank=True, + db_comment="修改人", + help_text="修改人", + max_length=64, + null=True, + verbose_name="修改人", + ), + ), + migrations.AlterField( + model_name="post", + name="remark", + field=models.CharField( + blank=True, + db_comment="备注", + help_text="备注", + max_length=256, + null=True, + verbose_name="备注", + ), + ), + migrations.AlterField( + model_name="post", + name="update_time", + field=models.DateTimeField( + auto_now=True, + db_comment="修改时间", + help_text="修改时间", + null=True, + verbose_name="修改时间", + ), + ), + migrations.AlterField( + model_name="role", + name="create_time", + field=models.DateTimeField( + auto_now_add=True, + db_comment="创建时间", + help_text="创建时间", + null=True, + verbose_name="创建时间", + ), + ), + migrations.AlterField( + model_name="role", + name="creator", + field=models.CharField( + blank=True, + db_comment="创建人", + help_text="创建人", + max_length=64, + null=True, + verbose_name="创建人", + ), + ), + migrations.AlterField( + model_name="role", + name="is_deleted", + field=models.BooleanField( + db_comment="是否软删除", default=False, verbose_name="是否软删除" + ), + ), + migrations.AlterField( + model_name="role", + name="modifier", + field=models.CharField( + blank=True, + db_comment="修改人", + help_text="修改人", + max_length=64, + null=True, + verbose_name="修改人", + ), + ), + migrations.AlterField( + model_name="role", + name="update_time", + field=models.DateTimeField( + auto_now=True, + db_comment="修改时间", + help_text="修改时间", + null=True, + verbose_name="修改时间", + ), + ), + migrations.AlterField( + model_name="rolepermission", + name="creator", + field=models.CharField( + blank=True, + db_comment="创建人", + help_text="创建人", + max_length=64, + null=True, + verbose_name="创建人", + ), + ), + migrations.AlterField( + model_name="rolepermission", + name="is_deleted", + field=models.BooleanField( + db_comment="是否软删除", default=False, verbose_name="是否软删除" + ), + ), + migrations.AlterField( + model_name="rolepermission", + name="modifier", + field=models.CharField( + blank=True, + db_comment="修改人", + help_text="修改人", + max_length=64, + null=True, + verbose_name="修改人", + ), + ), + migrations.AlterField( + model_name="rolepermission", + name="remark", + field=models.CharField( + blank=True, + db_comment="备注", + help_text="备注", + max_length=256, + null=True, + verbose_name="备注", + ), + ), + migrations.AlterField( + model_name="rolepermission", + name="update_time", + field=models.DateTimeField( + auto_now=True, + db_comment="修改时间", + help_text="修改时间", + null=True, + verbose_name="修改时间", + ), + ), + migrations.AlterField( + model_name="user", + name="create_time", + field=models.DateTimeField( + auto_now_add=True, + db_comment="创建时间", + help_text="创建时间", + null=True, + verbose_name="创建时间", + ), + ), + migrations.AlterField( + model_name="user", + name="creator", + field=models.CharField( + blank=True, + db_comment="创建人", + help_text="创建人", + max_length=64, + null=True, + verbose_name="创建人", + ), + ), + migrations.AlterField( + model_name="user", + name="is_deleted", + field=models.BooleanField( + db_comment="是否软删除", default=False, verbose_name="是否软删除" + ), + ), + migrations.AlterField( + model_name="user", + name="modifier", + field=models.CharField( + blank=True, + db_comment="修改人", + help_text="修改人", + max_length=64, + null=True, + verbose_name="修改人", + ), + ), + migrations.AlterField( + model_name="user", + name="remark", + field=models.CharField( + blank=True, + db_comment="备注", + help_text="备注", + max_length=256, + null=True, + verbose_name="备注", + ), + ), + migrations.AlterField( + model_name="user", + name="update_time", + field=models.DateTimeField( + auto_now=True, + db_comment="修改时间", + help_text="修改时间", + null=True, + verbose_name="修改时间", + ), + ), + ] diff --git a/backend/utils/models.py b/backend/utils/models.py index e9619d7..60cb7ac 100644 --- a/backend/utils/models.py +++ b/backend/utils/models.py @@ -10,13 +10,17 @@ class CommonStatus(models.IntegerChoices): ENABLED = 1, '启用' class CoreModel(models.Model): - remark = models.CharField(max_length=256, db_comment="备注", null=True, blank=True, help_text="备注") - creator = models.CharField(max_length=64, null=True, blank=True, help_text="创建人", db_comment="创建人") - modifier = models.CharField(max_length=64, null=True, blank=True, help_text="修改人", db_comment="修改人") - update_time = models.DateTimeField(auto_now=True, null=True, blank=True, help_text="修改时间", db_comment="修改时间") + remark = models.CharField(max_length=256, db_comment="备注", null=True, blank=True, help_text="备注", + verbose_name="备注") + creator = models.CharField(max_length=64, null=True, blank=True, help_text="创建人", db_comment="创建人", + verbose_name="创建人") + modifier = models.CharField(max_length=64, null=True, blank=True, help_text="修改人", db_comment="修改人", + verbose_name="修改人") + update_time = models.DateTimeField(auto_now=True, null=True, blank=True, help_text="修改时间", + db_comment="修改时间", verbose_name="修改时间") create_time = models.DateTimeField(auto_now_add=True, null=True, blank=True, help_text="创建时间", - db_comment="创建时间") - is_deleted = models.BooleanField(default=False, db_comment='是否软删除') + db_comment="创建时间", verbose_name="创建时间") + is_deleted = models.BooleanField(default=False, db_comment='是否软删除', verbose_name="是否软删除") class Meta: abstract = True diff --git a/docs/docs/essential/module_creation_guide.md b/docs/docs/essential/module_creation_guide.md new file mode 100644 index 0000000..0a02b3d --- /dev/null +++ b/docs/docs/essential/module_creation_guide.md @@ -0,0 +1,137 @@ +--- +sidebar_position: 10 +--- + +# 新建模块 + +本文档适用于本项目(Django + Vue3),指导如何规范地新建一个完整的功能模块(含后端与前端)。 + +--- + +## 一、后端模块新建流程(Django) + +1. **新建Django App** + ```bash + cd backend + python manage.py startapp <模块名> + # 如: python manage.py startapp ai + ``` + > 建议模块名使用小写英文,避免与已有模块重名。 + +2. **注册App** + - 在 `backend/backend/settings.py` 的 `INSTALLED_APPS` 中添加新模块。 + ```python + INSTALLED_APPS = [ + "simpleui", + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + "rest_framework", + 'django_filters', + 'corsheaders', + 'rest_framework.authtoken', + "system", + "ai", # 新加 + ] + ``` + +3. **定义模型(models.py)** + - 继承 `CoreModel`,包含必要的审计字段(如 creator、modifier)。 + - 示例: + ```python + from system.models import CoreModel + class AIApiKey(CoreModel): + """ AI API 密钥表 """ + name = models.CharField(max_length=255, db_comment="名称") + platform = models.CharField(max_length=255, db_comment="平台") + ``` + +4. **生成迁移并迁移数据库** + ```bash + python manage.py makemigrations <模块名> + python manage.py migrate + ``` + +5. **注册路由(urls.py)** + - 按照RESTful规范注册路由。 在ai文件夹下新建urls文件,并添加以下内容 + ```python + from django.urls import include, path + from rest_framework import routers + + from . import views + + router = routers.DefaultRouter() + + urlpatterns = [ + path('', include(router.urls)), + + ] + ``` +6. **修改路由(urls.py)** + - 按照RESTful规范注册路由。 在ai文件夹下新建urls文件,并添加以下内容 + ```python + urlpatterns = [ + path('api/system/', include('system.urls')), + path('api/ai/', include('ai.urls')), # 新加 + ] + ``` + +7. **权限与菜单自动生成(推荐)** + - 使用代码生成器: + ```bash + python manage.py generate_crud <模块名> <模型名> --frontend + ``` + +8. ** +```python +# 在ai/views下新建 __init__.py文件 +__all__ = [ + 'AIApiKeyViewSet' +] + +from ai.views.ai_api_key import AIApiKeyViewSet +``` + + +--- + +## 二、前端模块新建流程(Vue3)(代码生成器已经生成) +1. **配置多语言** + 在web/apps/web-antd/src/locales/langs/zh-CN/ai.json 配置多语言 + ```json + { + "title": "AI 大模型" + } + + ``` + +2. **新建菜单配置** + + ![new_module](./new_module.png) + + +3. **生成权限菜单** +```shell + cd backend + python manage.py gen_menu_json --app ai --model AIApiKey --parent AI +``` + + + +--- + +## 三、注意事项 + +- 模块命名规范统一,避免重复。 +- 建议优先使用代码生成器自动生成基础代码,减少重复劳动。 +- 新模块需补充单元测试和接口文档。 +- 前后端接口字段需保持一致,类型安全。 +- 权限点、菜单、路由需同步配置。 +- 代码提交前请自查并走团队代码评审流程。 + +--- + +如有疑问请联系项目负责人或查阅项目主文档。 \ No newline at end of file diff --git a/docs/docs/essential/new_module.png b/docs/docs/essential/new_module.png new file mode 100644 index 0000000000000000000000000000000000000000..eff17f1716ce3a4b0e18c8a13bc40a30f9209bc0 GIT binary patch literal 60034 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%0A_(u= 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 literal 0 HcmV?d00001 diff --git a/web/apps/web-antd/src/locales/langs/en-US/ai.json b/web/apps/web-antd/src/locales/langs/en-US/ai.json new file mode 100644 index 0000000..915be92 --- /dev/null +++ b/web/apps/web-antd/src/locales/langs/en-US/ai.json @@ -0,0 +1,7 @@ +{ + "title": "AI Management", + "ai_api_key": { + "title": "API KEY", + "name": "API KEY" + } +} diff --git a/web/apps/web-antd/src/locales/langs/zh-CN/ai.json b/web/apps/web-antd/src/locales/langs/zh-CN/ai.json new file mode 100644 index 0000000..337b444 --- /dev/null +++ b/web/apps/web-antd/src/locales/langs/zh-CN/ai.json @@ -0,0 +1,7 @@ +{ + "title": "AI大模型", + "ai_api_key": { + "title": "API 密钥", + "name": "API 密钥" + } +} diff --git a/web/apps/web-antd/src/models/ai/ai_api_key.ts b/web/apps/web-antd/src/models/ai/ai_api_key.ts new file mode 100644 index 0000000..c29eb6c --- /dev/null +++ b/web/apps/web-antd/src/models/ai/ai_api_key.ts @@ -0,0 +1,24 @@ +import { BaseModel } from '#/models/base'; + +export namespace AiAIApiKeyApi { + export interface AiAIApiKey { + id: number; + remark: string; + creator: string; + modifier: string; + update_time: string; + create_time: string; + is_deleted: boolean; + name: string; + platform: string; + api_key: string; + url: string; + status: number; + } +} + +export class AiAIApiKeyModel extends BaseModel { + constructor() { + super('/ai/ai_api_key/'); + } +} 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 new file mode 100644 index 0000000..a078c64 --- /dev/null +++ b/web/apps/web-antd/src/views/ai/ai_api_key/data.ts @@ -0,0 +1,184 @@ +import type { VxeTableGridOptions } from '@vben/plugins/vxe-table'; + +import type { VbenFormSchema } from '#/adapter/form'; +import type { OnActionClickFn } from '#/adapter/vxe-table'; +import type { AiAIApiKeyApi } from '#/models/ai/ai_api_key'; + +import { z } from '#/adapter/form'; +import { $t } from '#/locales'; +import { op } from '#/utils/permission'; + +/** + * 获取编辑表单的字段配置 + */ +export function useSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'name', + label: 'name', + rules: z + .string() + .min(1, $t('ui.formRules.required', ['name'])) + .max(100, $t('ui.formRules.maxLength', ['name', 100])), + }, + { + component: 'Input', + fieldName: 'platform', + label: 'platform', + rules: z + .string() + .min(1, $t('ui.formRules.required', ['platform'])) + .max(100, $t('ui.formRules.maxLength', ['platform', 100])), + }, + { + component: 'Input', + fieldName: 'api_key', + label: 'api key', + rules: z + .string() + .min(1, $t('ui.formRules.required', ['api key'])) + .max(100, $t('ui.formRules.maxLength', ['api key', 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])), + }, + { + component: 'InputNumber', + fieldName: 'status', + label: '状态', + }, + { + component: 'Input', + fieldName: 'remark', + label: 'remark', + rules: z + .string() + .min(1, $t('ui.formRules.required', ['remark'])) + .max(100, $t('ui.formRules.maxLength', ['remark', 100])), + }, + ]; +} + +/** + * 获取编辑表单的字段配置 + */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'name', + label: 'name', + }, + { + component: 'Input', + fieldName: 'platform', + label: 'platform', + }, + { + component: 'Input', + fieldName: 'api_key', + label: 'api key', + }, + { + component: 'Input', + fieldName: 'url', + label: 'url', + }, + { + component: 'InputNumber', + fieldName: 'status', + label: '状态', + }, + { + component: 'Input', + fieldName: 'remark', + label: 'remark', + }, + ]; +} + +/** + * 获取表格列配置 + * @description 使用函数的形式返回列数据而不是直接export一个Array常量,是为了响应语言切换时重新翻译表头 + * @param onActionClick 表格操作按钮点击事件 + */ +export function useColumns( + onActionClick?: OnActionClickFn, +): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: 'ID', + }, + { + field: 'name', + title: 'name', + }, + { + field: 'platform', + title: 'platform', + }, + { + field: 'api_key', + title: 'api key', + }, + { + field: 'url', + title: 'url', + }, + { + 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', + }, + { + align: 'center', + cellRender: { + attrs: { + nameField: 'name', + nameTitle: $t('ai.ai_api_key.name'), + onClick: onActionClick, + }, + name: 'CellOperation', + options: [ + op('ai:ai_api_key:edit', 'edit'), + op('ai:ai_api_key:delete', 'delete'), + ], + }, + field: 'action', + fixed: 'right', + title: '操作', + width: 120, + }, + ]; +} diff --git a/web/apps/web-antd/src/views/ai/ai_api_key/list.vue b/web/apps/web-antd/src/views/ai/ai_api_key/list.vue new file mode 100644 index 0000000..2f1bc73 --- /dev/null +++ b/web/apps/web-antd/src/views/ai/ai_api_key/list.vue @@ -0,0 +1,141 @@ + + + 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 new file mode 100644 index 0000000..dd0f99c --- /dev/null +++ b/web/apps/web-antd/src/views/ai/ai_api_key/modules/form.vue @@ -0,0 +1,79 @@ + + + +