feat(ui): 添加系统管理菜单功能并优化模型相关文案

- 新增系统管理菜单,包含用户管理、资源授权和系统设置功能
- 实现资源授权下拉菜单,支持应用、知识库、工具和模型的授权管理
- 实现系统设置下拉菜单,支持邮件设置等功能
- 将模型相关文案中的"供应商"统一修改为"模型",提升表述准确性
- 更新模型表单和API中的字段名称和提示信息
- 修复前端国际化文件中模型相关的翻译内容
v3.2
tanlianwang 2026-03-04 10:57:37 +08:00
parent b70a4c5825
commit 0546b773db
14 changed files with 160 additions and 62 deletions

View File

@ -134,8 +134,8 @@ class BaseExecField(BaseField):
:param label: 提示 :param label: 提示
:param text_field: 文本字段 :param text_field: 文本字段
:param value_field: 值字段 :param value_field: 值字段
:param provider: 指定供应商 :param provider: 指定模型
:param method: 执行供应商函数 method :param method: 执行模型函数 method
:param required: 是否必填 :param required: 是否必填
:param default_value: 默认值 :param default_value: 默认值
:param relation_show_field_dict: {field:field_value_list} 表示在 field有值 ,并且值在field_value_list中才显示 :param relation_show_field_dict: {field:field_value_list} 表示在 field有值 ,并且值在field_value_list中才显示

View File

@ -35,7 +35,7 @@ class Model(AppModelMixin):
user = models.ForeignKey(User, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True) user = models.ForeignKey(User, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True)
provider = models.CharField(max_length=128, verbose_name='供应商', db_index=True) provider = models.CharField(max_length=128, verbose_name='模型', db_index=True)
credential = models.CharField(max_length=102400, verbose_name="模型认证信息") credential = models.CharField(max_length=102400, verbose_name="模型认证信息")

View File

@ -3272,7 +3272,7 @@ msgstr "基础模型"
#: apps/shared/api/shared_model.py:39 #: apps/shared/api/shared_model.py:39
#: apps/shared/serializers/shared_model.py:114 #: apps/shared/serializers/shared_model.py:114
msgid "provider" msgid "provider"
msgstr "供应商" msgstr "模型"
#: apps/models_provider/api/model.py:65 #: apps/models_provider/api/model.py:65
#: apps/models_provider/serializers/model_serializer.py:322 #: apps/models_provider/serializers/model_serializer.py:322
@ -5115,7 +5115,7 @@ msgstr "重新排序文档"
#: apps/models_provider/views/provide.py:22 #: apps/models_provider/views/provide.py:22
#: apps/models_provider/views/provide.py:23 #: apps/models_provider/views/provide.py:23
msgid "Get a list of model suppliers" msgid "Get a list of model suppliers"
msgstr "获取模型供应商列表" msgstr "获取模型模型列表"
#: apps/models_provider/views/provide.py:43 #: apps/models_provider/views/provide.py:43
#: apps/models_provider/views/provide.py:44 #: apps/models_provider/views/provide.py:44

View File

@ -46,7 +46,7 @@ class Migration(migrations.Migration):
('status', models.CharField(choices=[('SUCCESS', '成功'), ('ERROR', '失败'), ('DOWNLOAD', '下载中'), ('PAUSE_DOWNLOAD', '暂停下载')], db_index=True, default='SUCCESS', max_length=20, verbose_name='设置类型')), ('status', models.CharField(choices=[('SUCCESS', '成功'), ('ERROR', '失败'), ('DOWNLOAD', '下载中'), ('PAUSE_DOWNLOAD', '暂停下载')], db_index=True, default='SUCCESS', max_length=20, verbose_name='设置类型')),
('model_type', models.CharField(db_index=True, max_length=128, verbose_name='模型类型')), ('model_type', models.CharField(db_index=True, max_length=128, verbose_name='模型类型')),
('model_name', models.CharField(db_index=True, max_length=128, verbose_name='模型名称')), ('model_name', models.CharField(db_index=True, max_length=128, verbose_name='模型名称')),
('provider', models.CharField(db_index=True, max_length=128, verbose_name='供应商')), ('provider', models.CharField(db_index=True, max_length=128, verbose_name='模型')),
('credential', models.CharField(max_length=102400, verbose_name='模型认证信息')), ('credential', models.CharField(max_length=102400, verbose_name='模型认证信息')),
('meta', models.JSONField(default=dict, verbose_name='模型元数据,用于存储下载,或者错误信息')), ('meta', models.JSONField(default=dict, verbose_name='模型元数据,用于存储下载,或者错误信息')),
('model_params_form', models.JSONField(default=list, verbose_name='模型参数配置')), ('model_params_form', models.JSONField(default=list, verbose_name='模型参数配置')),

View File

@ -35,7 +35,7 @@ class Model(AppModelMixin):
user = models.ForeignKey(User, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True) user = models.ForeignKey(User, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True)
provider = models.CharField(max_length=128, verbose_name='供应商', db_index=True) provider = models.CharField(max_length=128, verbose_name='模型', db_index=True)
credential = models.CharField(max_length=102400, verbose_name="模型认证信息") credential = models.CharField(max_length=102400, verbose_name="模型认证信息")

View File

@ -24,12 +24,12 @@ from models_provider.constants.model_provider_constants import ModelProvideConst
def get_model_(provider, model_type, model_name, credential, model_id, use_local=False, **kwargs): def get_model_(provider, model_type, model_name, credential, model_id, use_local=False, **kwargs):
""" """
获取模型实例 获取模型实例
@param provider: 供应商 @param provider: 模型
@param model_type: 模型类型 @param model_type: 模型类型
@param model_name: 模型名称 @param model_name: 模型名称
@param credential: 认证信息 @param credential: 认证信息
@param model_id: 模型id @param model_id: 模型id
@param use_local: 是否调用本地模型 只适用于本地供应商 @param use_local: 是否调用本地模型 只适用于本地模型
@return: 模型实例 @return: 模型实例
""" """
model = get_provider(provider).get_model(model_type, model_name, model = get_provider(provider).get_model(model_type, model_name,
@ -52,9 +52,9 @@ def get_model(model, **kwargs):
def get_provider(provider): def get_provider(provider):
""" """
获取供应商实例 获取模型实例
@param provider: 供应商字符串 @param provider: 模型字符串
@return: 供应商实例 @return: 模型实例
""" """
return ModelProvideConstants[provider].value return ModelProvideConstants[provider].value
@ -62,7 +62,7 @@ def get_provider(provider):
def get_model_list(provider, model_type): def get_model_list(provider, model_type):
""" """
获取模型列表 获取模型列表
@param provider: 供应商字符串 @param provider: 模型字符串
@param model_type: 模型类型 @param model_type: 模型类型
@return: 模型列表 @return: 模型列表
""" """
@ -72,7 +72,7 @@ def get_model_list(provider, model_type):
def get_model_credential(provider, model_type, model_name): def get_model_credential(provider, model_type, model_name):
""" """
获取模型认证实例 获取模型认证实例
@param provider: 供应商字符串 @param provider: 模型字符串
@param model_type: 模型类型 @param model_type: 模型类型
@param model_name: 模型名称 @param model_name: 模型名称
@return: 认证实例对象 @return: 认证实例对象
@ -83,7 +83,7 @@ def get_model_credential(provider, model_type, model_name):
def get_model_type_list(provider): def get_model_type_list(provider):
""" """
获取模型类型列表 获取模型类型列表
@param provider: 供应商字符串 @param provider: 模型字符串
@return: 模型类型列表 @return: 模型类型列表
""" """
return get_provider(provider).get_model_type_list() return get_provider(provider).get_model_type_list()
@ -93,7 +93,7 @@ def is_valid_credential(provider, model_type, model_name, model_credential: Dict
raise_exception=False): raise_exception=False):
""" """
校验模型认证参数 校验模型认证参数
@param provider: 供应商字符串 @param provider: 模型字符串
@param model_type: 模型类型 @param model_type: 模型类型
@param model_name: 模型名称 @param model_name: 模型名称
@param model_credential: 模型认证数据 @param model_credential: 模型认证数据

File diff suppressed because one or more lines are too long

View File

@ -7,14 +7,14 @@ import type {KeyValue} from '../type/common'
const prefix_provider = '/provider' const prefix_provider = '/provider'
/** /**
* *
*/ */
const getProvider: (loading?: Ref<boolean>) => Promise<Result<Array<Provider>>> = (loading) => { const getProvider: (loading?: Ref<boolean>) => Promise<Result<Array<Provider>>> = (loading) => {
return get(`${prefix_provider}`, {}, loading) return get(`${prefix_provider}`, {}, loading)
} }
/** /**
* *
*/ */
const getProviderByModelType: ( const getProviderByModelType: (
model_type: string, model_type: string,
@ -42,7 +42,7 @@ const getModelCreateForm: (
/** /**
* *
* @param provider * @param provider
* @param loading * @param loading
* @returns * @returns
*/ */

View File

@ -7,15 +7,15 @@ interface modelRequest {
interface Provider { interface Provider {
/** /**
* *
*/ */
provider: string provider: string
/** /**
* *
*/ */
name: string name: string
/** /**
* icon * icon
*/ */
icon: string icon: string
} }
@ -34,7 +34,7 @@ interface ListModelRequest {
*/ */
model_name?: string model_name?: string
/** /**
* *
*/ */
provider?: string provider?: string
@ -66,7 +66,7 @@ interface Model {
*/ */
credential: any credential: any
/** /**
* *
*/ */
provider: string provider: string
/** /**
@ -100,7 +100,7 @@ interface CreateModelRequest {
*/ */
credential: any credential: any
/** /**
* *
*/ */
provider: string provider: string
} }

View File

@ -163,7 +163,7 @@ interface FormField {
*/ */
option_list?: Array<any> option_list?: Array<any>
/** /**
* *
*/ */
provider?: string provider?: string
/** /**

View File

@ -5,9 +5,10 @@
<h1 style="font-size: 18px; font-weight: 600; margin: 0;">AI-RAG</h1> <h1 style="font-size: 18px; font-weight: 600; margin: 0;">AI-RAG</h1>
</div> </div>
<!-- 一级业务菜单 --> <!-- 业务菜单或系统管理菜单 -->
<div style="padding: 0 16px;"> <div style="padding: 0 16px;">
<div style="display: flex; flex-direction: column; gap: 8px;"> <!-- 业务菜单 -->
<div v-if="!isSystemManagement" style="display: flex; flex-direction: column; gap: 8px;">
<router-link to="/application" style="display: block; padding: 10px 16px; border-radius: 8px; text-decoration: none; color: #333; font-size: 14px;" :style="activeMainMenu === '/application' ? { backgroundColor: '#e6f0ff', color: '#1890ff' } : {}"> <router-link to="/application" style="display: block; padding: 10px 16px; border-radius: 8px; text-decoration: none; color: #333; font-size: 14px;" :style="activeMainMenu === '/application' ? { backgroundColor: '#e6f0ff', color: '#1890ff' } : {}">
应用 应用
</router-link> </router-link>
@ -21,6 +22,48 @@
模型 模型
</router-link> </router-link>
</div> </div>
<!-- 系统管理菜单 -->
<div v-else style="display: flex; flex-direction: column; gap: 8px;">
<router-link to="/system/user" style="display: block; padding: 10px 16px; border-radius: 8px; text-decoration: none; color: #333; font-size: 14px;" :style="activeSystemMenu === '/system/user' ? { backgroundColor: '#e6f0ff', color: '#1890ff' } : {}">
用户管理
</router-link>
<!-- 资源授权下拉展开 -->
<div>
<div style="display: flex; justify-content: space-between; align-items: center; padding: 10px 16px; border-radius: 8px; cursor: pointer;" :style="activeSystemMenu.startsWith('/system/authorization') ? { backgroundColor: '#e6f0ff', color: '#1890ff' } : {}" @click="toggleResourceAuth">
<span>资源授权</span>
<span>{{ resourceAuthExpanded ? '▼' : '▶' }}</span>
</div>
<div v-if="resourceAuthExpanded" style="padding-left: 32px; margin-top: 4px; display: flex; flex-direction: column; gap: 4px;">
<router-link to="/system/authorization/application" style="display: block; padding: 8px 16px; border-radius: 8px; text-decoration: none; color: #666; font-size: 13px;" :style="activeSystemMenu === '/system/authorization/application' ? { backgroundColor: '#e6f0ff', color: '#1890ff' } : {}">
应用
</router-link>
<router-link to="/system/authorization/knowledge" style="display: block; padding: 8px 16px; border-radius: 8px; text-decoration: none; color: #666; font-size: 13px;" :style="activeSystemMenu === '/system/authorization/knowledge' ? { backgroundColor: '#e6f0ff', color: '#1890ff' } : {}">
知识库
</router-link>
<router-link to="/system/authorization/tool" style="display: block; padding: 8px 16px; border-radius: 8px; text-decoration: none; color: #666; font-size: 13px;" :style="activeSystemMenu === '/system/authorization/tool' ? { backgroundColor: '#e6f0ff', color: '#1890ff' } : {}">
工具
</router-link>
<router-link to="/system/authorization/model" style="display: block; padding: 8px 16px; border-radius: 8px; text-decoration: none; color: #666; font-size: 13px;" :style="activeSystemMenu === '/system/authorization/model' ? { backgroundColor: '#e6f0ff', color: '#1890ff' } : {}">
模型
</router-link>
</div>
</div>
<!-- 系统设置下拉展开 -->
<div>
<div style="display: flex; justify-content: space-between; align-items: center; padding: 10px 16px; border-radius: 8px; cursor: pointer;" :style="activeSystemMenu.startsWith('/system/email') ? { backgroundColor: '#e6f0ff', color: '#1890ff' } : {}" @click="toggleSystemSettings">
<span>系统设置</span>
<span>{{ systemSettingsExpanded ? '▼' : '▶' }}</span>
</div>
<div v-if="systemSettingsExpanded" style="padding-left: 32px; margin-top: 4px; display: flex; flex-direction: column; gap: 4px;">
<router-link to="/system/email" style="display: block; padding: 8px 16px; border-radius: 8px; text-decoration: none; color: #666; font-size: 13px;" :style="activeSystemMenu === '/system/email' ? { backgroundColor: '#e6f0ff', color: '#1890ff' } : {}">
邮件设置
</router-link>
</div>
</div>
</div>
</div> </div>
<!-- 底部用户信息区 --> <!-- 底部用户信息区 -->
@ -31,12 +74,19 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { computed, ref } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import UserAvatar from '@/layout/layout-header/avatar/index.vue' import UserAvatar from '@/layout/layout-header/avatar/index.vue'
const route = useRoute() const route = useRoute()
//
const isSystemManagement = computed(() => {
const path = route.path
return path.startsWith('/system')
})
//
const activeMainMenu = computed(() => { const activeMainMenu = computed(() => {
const path = route.path const path = route.path
if (path.startsWith('/application')) return '/application' if (path.startsWith('/application')) return '/application'
@ -46,6 +96,34 @@ const activeMainMenu = computed(() => {
return '/application' return '/application'
}) })
//
const activeSystemMenu = computed(() => {
const path = route.path
if (path.startsWith('/system/user')) return '/system/user'
if (path.startsWith('/system/authorization')) return path
if (path.startsWith('/system/email')) return path
return '/system/user'
})
//
const resourceAuthExpanded = ref(false)
//
const systemSettingsExpanded = ref(false)
//
const toggleResourceAuth = () => {
resourceAuthExpanded.value = !resourceAuthExpanded.value
//
systemSettingsExpanded.value = false
}
//
const toggleSystemSettings = () => {
systemSettingsExpanded.value = !systemSettingsExpanded.value
//
resourceAuthExpanded.value = false
}
const activeSubMenu = computed(() => { const activeSubMenu = computed(() => {
const path = route.path const path = route.path
return path return path

View File

@ -1,36 +1,16 @@
<template> <template>
<div class="app-top-bar-container border-b flex-center"> <div class="app-top-bar-container border-b flex-center">
<div class="logo mt-4"> <div class="flex-between w-full align-center" style="padding: 0 16px;">
<LogoFull /> <div style="display: flex; align-items: center;">
</div> <h1 style="font-size: 18px; font-weight: 600; margin: 0;">AI-RAG | 系统管理</h1>
</div>
<div class="flex-between w-full align-center"> <div style="display: flex; align-items: center;">
<h4><el-divider class="ml-16 mr-16" direction="vertical" />{{ $t('views.system.title') }}</h4> <router-link to="/application" style="display: flex; align-items: center; padding: 6px 12px; border-radius: 6px; text-decoration: none; color: #1890ff; border: 1px solid #1890ff;">
<div class="flex align-center mr-8"> <span style="margin-right: 4px;"></span>
<TopAbout></TopAbout> 返回工作空间
<el-divider class="ml-8 mr-8" direction="vertical" /> </router-link>
<el-button
link
@click="router.push({ path: '/' })"
style="color: var(--el-text-color-primary)"
v-if="
hasPermission(
[
RoleConst.USER.getWorkspaceRole,
RoleConst.EXTENDS_USER.getWorkspaceRole,
RoleConst.EXTENDS_WORKSPACE_MANAGE.getWorkspaceRole,
RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,
],
'OR',
)
"
>
<AppIcon class="mr-8" iconName="app-workspace" style="font-size: 16px"></AppIcon>
{{ $t('views.workspace.toWorkspace') }}</el-button
>
</div> </div>
</div> </div>
<Avatar></Avatar>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@ -3,6 +3,7 @@ import { computed } from 'vue'
import Sidebar from '@/layout/components/sidebar/index.vue' import Sidebar from '@/layout/components/sidebar/index.vue'
import AppMain from '@/layout/app-main/index.vue' import AppMain from '@/layout/app-main/index.vue'
import LayoutContainer from '@/components/layout-container/index.vue' import LayoutContainer from '@/components/layout-container/index.vue'
import UserAvatar from '@/layout/layout-header/avatar/index.vue'
import useStore from '@/stores' import useStore from '@/stores'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
const route = useRoute() const route = useRoute()
@ -19,6 +20,30 @@ const isShared = computed(() => {
route.path.includes('resource-management') route.path.includes('resource-management')
) )
}) })
//
const isSystemManagement = computed(() => {
const path = route.path
return path.startsWith('/system')
})
//
const pageTitle = computed(() => {
return 'AI-RAG'
})
//
const currentSystemPage = computed(() => {
const path = route.path
if (path.startsWith('/system/user')) {
return '用户管理'
} else if (path.startsWith('/system/resource')) {
return '资源授权'
} else if (path.startsWith('/system/settings')) {
return '系统设置'
}
return '用户管理'
})
</script> </script>
<template> <template>
@ -28,7 +53,22 @@ const isShared = computed(() => {
<template #left> <template #left>
<Sidebar /> <Sidebar />
</template> </template>
<AppMain /> <div>
<!-- 顶部功能区仅系统管理模式显示 -->
<div v-if="isSystemManagement" style="display: flex; justify-content: space-between; align-items: center; padding: 16px; border-bottom: 1px solid #e5e7eb;">
<div style="display: flex; align-items: center;">
<h2 style="font-size: 18px; font-weight: 600; margin: 0;">AI-RAG | 系统管理</h2>
</div>
<div style="display: flex; align-items: center;">
<router-link to="/application" style="display: flex; align-items: center; padding: 6px 12px; border-radius: 6px; text-decoration: none; color: #1890ff; border: 1px solid #1890ff;">
<span style="margin-right: 4px;"></span>
返回工作空间
</router-link>
</div>
</div>
<AppMain />
</div>
</LayoutContainer> </LayoutContainer>
</div> </div>
</div> </div>

View File

@ -1,7 +1,7 @@
export default { export default {
title: '模型', title: '模型',
provider: '供应商', provider: '模型',
providerPlaceholder: '选择供应商', providerPlaceholder: '选择模型',
addModel: '添加模型', addModel: '添加模型',
delete: { delete: {
confirmTitle: '是否删除:', confirmTitle: '是否删除:',