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 text_field: 文本字段
:param value_field: 值字段
:param provider: 指定供应商
:param method: 执行供应商函数 method
:param provider: 指定模型
:param method: 执行模型函数 method
:param required: 是否必填
:param default_value: 默认值
: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)
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="模型认证信息")

View File

@ -3272,7 +3272,7 @@ msgstr "基础模型"
#: apps/shared/api/shared_model.py:39
#: apps/shared/serializers/shared_model.py:114
msgid "provider"
msgstr "供应商"
msgstr "模型"
#: apps/models_provider/api/model.py:65
#: 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:23
msgid "Get a list of model suppliers"
msgstr "获取模型供应商列表"
msgstr "获取模型模型列表"
#: apps/models_provider/views/provide.py:43
#: 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='设置类型')),
('model_type', 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='模型认证信息')),
('meta', models.JSONField(default=dict, 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)
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="模型认证信息")

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):
"""
获取模型实例
@param provider: 供应商
@param provider: 模型
@param model_type: 模型类型
@param model_name: 模型名称
@param credential: 认证信息
@param model_id: 模型id
@param use_local: 是否调用本地模型 只适用于本地供应商
@param use_local: 是否调用本地模型 只适用于本地模型
@return: 模型实例
"""
model = get_provider(provider).get_model(model_type, model_name,
@ -52,9 +52,9 @@ def get_model(model, **kwargs):
def get_provider(provider):
"""
获取供应商实例
@param provider: 供应商字符串
@return: 供应商实例
获取模型实例
@param provider: 模型字符串
@return: 模型实例
"""
return ModelProvideConstants[provider].value
@ -62,7 +62,7 @@ def get_provider(provider):
def get_model_list(provider, model_type):
"""
获取模型列表
@param provider: 供应商字符串
@param provider: 模型字符串
@param model_type: 模型类型
@return: 模型列表
"""
@ -72,7 +72,7 @@ def get_model_list(provider, model_type):
def get_model_credential(provider, model_type, model_name):
"""
获取模型认证实例
@param provider: 供应商字符串
@param provider: 模型字符串
@param model_type: 模型类型
@param model_name: 模型名称
@return: 认证实例对象
@ -83,7 +83,7 @@ def get_model_credential(provider, model_type, model_name):
def get_model_type_list(provider):
"""
获取模型类型列表
@param provider: 供应商字符串
@param provider: 模型字符串
@return: 模型类型列表
"""
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):
"""
校验模型认证参数
@param provider: 供应商字符串
@param provider: 模型字符串
@param model_type: 模型类型
@param model_name: 模型名称
@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 getProvider: (loading?: Ref<boolean>) => Promise<Result<Array<Provider>>> = (loading) => {
return get(`${prefix_provider}`, {}, loading)
}
/**
*
*
*/
const getProviderByModelType: (
model_type: string,
@ -42,7 +42,7 @@ const getModelCreateForm: (
/**
*
* @param provider
* @param provider
* @param loading
* @returns
*/

View File

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

View File

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

View File

@ -5,9 +5,10 @@
<h1 style="font-size: 18px; font-weight: 600; margin: 0;">AI-RAG</h1>
</div>
<!-- 一级业务菜单 -->
<!-- 业务菜单或系统管理菜单 -->
<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>
@ -21,6 +22,48 @@
模型
</router-link>
</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>
<!-- 底部用户信息区 -->
@ -31,12 +74,19 @@
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { computed, ref } from 'vue'
import { useRoute } from 'vue-router'
import UserAvatar from '@/layout/layout-header/avatar/index.vue'
const route = useRoute()
//
const isSystemManagement = computed(() => {
const path = route.path
return path.startsWith('/system')
})
//
const activeMainMenu = computed(() => {
const path = route.path
if (path.startsWith('/application')) return '/application'
@ -46,6 +96,34 @@ const activeMainMenu = computed(() => {
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 path = route.path
return path

View File

@ -1,36 +1,16 @@
<template>
<div class="app-top-bar-container border-b flex-center">
<div class="logo mt-4">
<LogoFull />
</div>
<div class="flex-between w-full align-center">
<h4><el-divider class="ml-16 mr-16" direction="vertical" />{{ $t('views.system.title') }}</h4>
<div class="flex align-center mr-8">
<TopAbout></TopAbout>
<el-divider class="ml-8 mr-8" direction="vertical" />
<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 class="flex-between w-full align-center" style="padding: 0 16px;">
<div style="display: flex; align-items: center;">
<h1 style="font-size: 18px; font-weight: 600; margin: 0;">AI-RAG | 系统管理</h1>
</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>
<Avatar></Avatar>
</div>
</template>
<script setup lang="ts">

View File

@ -3,6 +3,7 @@ import { computed } from 'vue'
import Sidebar from '@/layout/components/sidebar/index.vue'
import AppMain from '@/layout/app-main/index.vue'
import LayoutContainer from '@/components/layout-container/index.vue'
import UserAvatar from '@/layout/layout-header/avatar/index.vue'
import useStore from '@/stores'
import { useRoute } from 'vue-router'
const route = useRoute()
@ -19,6 +20,30 @@ const isShared = computed(() => {
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>
<template>
@ -28,7 +53,22 @@ const isShared = computed(() => {
<template #left>
<Sidebar />
</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>
</div>
</div>

View File

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