新增管理员权限检查和模型选择逻辑优化

- 在用户 API 中新增 `checkIsSuperAdmin` 函数,用于检查用户是否为超级管理员。
- 更新模型选择器,非管理员用户只能选择内置模型并需配置 API Key,提升安全性和用户体验。
- 优化模型配置页面,动态显示操作按钮,确保管理员与普通用户的操作权限区分明确。
This commit is contained in:
2026-05-12 13:52:24 +08:00
parent 87b25dee42
commit 72af38ea00
5 changed files with 103 additions and 40 deletions

View File

@@ -69,3 +69,10 @@ export function deleteUser(ids: number[]) {
data: { ids }, data: { ids },
}); });
} }
export function checkIsSuperAdmin() {
return request({
url: '/admin-go/api/v1/system/user/checkIsSuperAdmin',
method: 'get',
});
}

View File

@@ -19,13 +19,13 @@
v-for="model in modelList" v-for="model in modelList"
:key="model.id" :key="model.id"
class="model-card" class="model-card"
:class="{ selected: selectedModel?.id === model.id, 'system-model': model.tenantId === 1 }" :class="{ selected: selectedModel?.id === model.id, 'builtin-model': !model.apiKey }"
@click="handleSelectModel(model)" @click="handleSelectModel(model)"
> >
<div class="model-card-header"> <div class="model-card-header">
<div class="model-type">{{ getModelTypeName(model.modelsType) }}</div> <div class="model-type">{{ getModelTypeName(model.modelsType) }}</div>
<div class="model-badges"> <div class="model-badges">
<el-tag v-if="model.tenantId === 1" type="warning" size="small">系统模型</el-tag> <el-tag v-if="!model.apiKey" type="warning" size="small">内置模型</el-tag>
<el-icon v-if="selectedModel?.id === model.id" class="check-icon" color="#67c23a"><CircleCheck /></el-icon> <el-icon v-if="selectedModel?.id === model.id" class="check-icon" color="#67c23a"><CircleCheck /></el-icon>
</div> </div>
</div> </div>
@@ -62,12 +62,12 @@
<!-- 新建模型弹窗 --> <!-- 新建模型弹窗 -->
<EditModule ref="editModuleRef" @refresh="handleRefresh" /> <EditModule ref="editModuleRef" @refresh="handleRefresh" />
<!-- 系统模型 API Key 输入弹窗 --> <!-- 内置模型 API Key 输入弹窗 -->
<el-dialog v-model="apiKeyDialogVisible" title="配置系统模型" width="500px" :close-on-click-modal="false" append-to-body> <el-dialog v-model="apiKeyDialogVisible" title="配置内置模型" width="500px" :close-on-click-modal="false" append-to-body>
<el-alert type="info" :closable="false" style="margin-bottom: 16px"> <el-alert type="info" :closable="false" style="margin-bottom: 16px">
<template #title> <template #title>
<div style="line-height: 1.6"> <div style="line-height: 1.6">
您选择的是系统模型需要配置您自己的 API Key<br /> 您选择的是内置模型需要配置您自己的 API Key<br />
系统将为您创建一个模型副本 系统将为您创建一个模型副本
</div> </div>
</template> </template>
@@ -89,10 +89,11 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, watch } from 'vue'; import { ref, reactive, watch, onMounted } from 'vue';
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'; import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
import { Search, CircleCheck } from '@element-plus/icons-vue'; import { Search, CircleCheck } from '@element-plus/icons-vue';
import { getModelModuleList, addModelModule } from '/@/api/digitalHuman/modelConfig/modelModule'; import { getModelModuleList, addModelModule } from '/@/api/digitalHuman/modelConfig/modelModule';
import { checkIsSuperAdmin } from '/@/api/system/user/index';
import { getApiErrorMessage } from '/@/utils/request'; import { getApiErrorMessage } from '/@/utils/request';
import EditModule from '/@/views/digitalHuman/modelConfig/modelModule/component/editModule.vue'; import EditModule from '/@/views/digitalHuman/modelConfig/modelModule/component/editModule.vue';
@@ -147,8 +148,9 @@ const modelList = ref<ModelItem[]>([]);
const loading = ref(false); const loading = ref(false);
const selectedModel = ref<ModelItem | null>(null); const selectedModel = ref<ModelItem | null>(null);
const editModuleRef = ref(); const editModuleRef = ref();
const isSuperAdmin = ref(false); // 是否为管理员
// 系统模型 API Key 配置 // 内置模型 API Key 配置
const apiKeyDialogVisible = ref(false); const apiKeyDialogVisible = ref(false);
const apiKeyFormRef = ref<FormInstance>(); const apiKeyFormRef = ref<FormInstance>();
const apiKeyForm = reactive({ const apiKeyForm = reactive({
@@ -162,6 +164,16 @@ const apiKeyRules: FormRules = {
const creatingModel = ref(false); const creatingModel = ref(false);
const systemModelToClone = ref<ModelItem | null>(null); const systemModelToClone = ref<ModelItem | null>(null);
// 检查是否为管理员
const checkAdminStatus = async () => {
try {
const res: any = await checkIsSuperAdmin();
isSuperAdmin.value = res.data?.isSuperAdmin || false;
} catch {
isSuperAdmin.value = false;
}
};
watch( watch(
() => props.modelValue, () => props.modelValue,
(val) => { (val) => {
@@ -195,6 +207,7 @@ const fetchModelList = async () => {
pageNum: pagination.pageNum, pageNum: pagination.pageNum,
pageSize: pagination.pageSize, pageSize: pagination.pageSize,
modelName: searchParams.modelName || undefined, modelName: searchParams.modelName || undefined,
modelType: 1, // 只获取推理模型
}; };
const res: any = await getModelModuleList(params); const res: any = await getModelModuleList(params);
modelList.value = res.data?.list || []; modelList.value = res.data?.list || [];
@@ -218,15 +231,21 @@ const handlePageChange = () => {
}; };
const handleSelectModel = (model: ModelItem) => { const handleSelectModel = (model: ModelItem) => {
// 判断是否是系统模型tenantId === 1 // 如果是管理员,直接选中任何模型,不需要配置 API Key
if (model.tenantId === 1) { if (isSuperAdmin.value) {
// 系统模型,需要用户配置 API Key selectedModel.value = model;
return;
}
// 非管理员判断是否是内置模型apiKey 为空)
if (!model.apiKey) {
// 内置模型,需要用户配置 API Key
systemModelToClone.value = model; systemModelToClone.value = model;
apiKeyForm.modelName = model.modelName; apiKeyForm.modelName = model.modelName;
apiKeyForm.apiKey = ''; apiKeyForm.apiKey = '';
apiKeyDialogVisible.value = true; apiKeyDialogVisible.value = true;
} else { } else {
// 非系统模型,直接选中 // 用户模型,直接选中
selectedModel.value = model; selectedModel.value = model;
} }
}; };
@@ -239,7 +258,7 @@ const handleCreatePrivateModel = async () => {
creatingModel.value = true; creatingModel.value = true;
// 基于系统模型创建新模型(继承原模型的所有配置,只替换 apiKey // 基于内置模型创建新模型(继承原模型的所有配置,只替换 apiKey
const systemModel = systemModelToClone.value; const systemModel = systemModelToClone.value;
const createParams = { const createParams = {
modelName: apiKeyForm.modelName, modelName: apiKeyForm.modelName,
@@ -312,6 +331,10 @@ const handleClose = () => {
apiKeyDialogVisible.value = false; apiKeyDialogVisible.value = false;
systemModelToClone.value = null; systemModelToClone.value = null;
}; };
onMounted(() => {
checkAdminStatus();
});
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@@ -360,12 +383,12 @@ const handleClose = () => {
background: #f0f9ff; background: #f0f9ff;
} }
.model-card.system-model { .model-card.builtin-model {
border-color: #fbbf24; border-color: #fbbf24;
background: #fffbeb; background: #fffbeb;
} }
.model-card.system-model:hover { .model-card.builtin-model:hover {
border-color: #f59e0b; border-color: #f59e0b;
} }

View File

@@ -1058,6 +1058,9 @@ const useWorkflow = async (workflow: WorkflowItem) => {
// 切换到创作模式 // 切换到创作模式
isCreationMode.value = true; isCreationMode.value = true;
currentWorkflowForCreation.value = res.data; currentWorkflowForCreation.value = res.data;
// 从工作流进入,不禁用表单
isFromWorkspace.value = false;
currentSessionId.value = null; // 清空会话 ID
// 初始化创作表单的值 // 初始化创作表单的值
Object.keys(creationFormValues).forEach((key) => delete creationFormValues[key]); Object.keys(creationFormValues).forEach((key) => delete creationFormValues[key]);
@@ -1296,6 +1299,9 @@ const sendMessage = async () => {
userInput.value = ''; userInput.value = '';
selectedFiles.value = []; selectedFiles.value = [];
selectedCreationSkill.value = null; selectedCreationSkill.value = null;
// 7. 重新获取工作空间数据
await getList();
} catch { } catch {
// 接口错误由 request 全局提示后端 message // 接口错误由 request 全局提示后端 message
} finally { } finally {

View File

@@ -41,12 +41,12 @@
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20"> <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="访问类型" prop="isPrivate"> <el-form-item label="访问类型" prop="isPrivate">
<el-radio-group v-model="state.ruleForm.isPrivate" @change="onIsPrivateChange"> <el-radio-group v-model="state.ruleForm.isPrivate" @change="onIsPrivateChange">
<el-radio :label="0">私有</el-radio> <el-radio :label="0">本地模型</el-radio>
<el-radio :label="1">公共</el-radio> <el-radio :label="1">服务商模型</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col v-if="state.ruleForm.isPrivate === 1" :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20"> <el-col v-if="!props.isSuperAdmin && state.ruleForm.isPrivate === 1" :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="API 密钥" prop="apiKey"> <el-form-item label="API 密钥" prop="apiKey">
<el-input v-model="state.ruleForm.apiKey" type="password" show-password placeholder="请输入 API 密钥字符串" clearable></el-input> <el-input v-model="state.ruleForm.apiKey" type="password" show-password placeholder="请输入 API 密钥字符串" clearable></el-input>
</el-form-item> </el-form-item>
@@ -254,8 +254,12 @@ export type ModelTypeOption = { id: number | string; label: string };
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
modelTypes?: ModelTypeOption[]; modelTypes?: ModelTypeOption[];
isSuperAdmin?: boolean;
}>(), }>(),
{ modelTypes: () => [] as ModelTypeOption[] } {
modelTypes: () => [] as ModelTypeOption[],
isSuperAdmin: false,
}
); );
const modelTypeOptions = computed(() => props.modelTypes); const modelTypeOptions = computed(() => props.modelTypes);

View File

@@ -4,7 +4,14 @@
<div class="system-user-search mb15"> <div class="system-user-search mb15">
<el-input v-model="state.tableData.param.modelName" size="default" placeholder="请输入模型名称" style="max-width: 180px" clearable> <el-input v-model="state.tableData.param.modelName" size="default" placeholder="请输入模型名称" style="max-width: 180px" clearable>
</el-input> </el-input>
<el-select v-model="state.tableData.param.modelType" size="default" placeholder="请选择模型类型" style="max-width: 180px" clearable class="ml10"> <el-select
v-model="state.tableData.param.modelType"
size="default"
placeholder="请选择模型类型"
style="max-width: 180px"
clearable
class="ml10"
>
<el-option v-for="type in state.modelTypes" :key="type.id" :label="type.label" :value="type.id" /> <el-option v-for="type in state.modelTypes" :key="type.id" :label="type.label" :value="type.id" />
</el-select> </el-select>
<el-button size="default" type="primary" class="ml10" @click="getTableData"> <el-button size="default" type="primary" class="ml10" @click="getTableData">
@@ -31,7 +38,7 @@
<!-- <el-table-column prop="baseUrl" label="模型服务地址" show-overflow-tooltip width="200"></el-table-column> --> <!-- <el-table-column prop="baseUrl" label="模型服务地址" show-overflow-tooltip width="200"></el-table-column> -->
<el-table-column prop="isPrivate" label="访问类型" width="100"> <el-table-column prop="isPrivate" label="访问类型" width="100">
<template #default="scope"> <template #default="scope">
<el-tag :type="scope.row.isPrivate === 1 ? 'primary' : 'info'">{{ scope.row.isPrivate === 1 ? '公共' : '私有' }}</el-tag> <el-tag :type="scope.row.isPrivate === 1 ? 'primary' : 'info'">{{ scope.row.isPrivate === 1 ? '本地模型' : '服务商模型' }}</el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="httpMethod" label="请求方式" width="100"></el-table-column> <el-table-column prop="httpMethod" label="请求方式" width="100"></el-table-column>
@@ -45,27 +52,30 @@
<el-table-column prop="remark" label="备注" show-overflow-tooltip></el-table-column> <el-table-column prop="remark" label="备注" show-overflow-tooltip></el-table-column>
<el-table-column prop="createdAt" label="创建时间" show-overflow-tooltip width="160"></el-table-column> <el-table-column prop="createdAt" label="创建时间" show-overflow-tooltip width="160"></el-table-column>
<el-table-column prop="updatedAt" label="修改时间" show-overflow-tooltip width="160"></el-table-column> <el-table-column prop="updatedAt" label="修改时间" show-overflow-tooltip width="160"></el-table-column>
<el-table-column label="操作" width="300" fixed="right"> <el-table-column label="操作" :width="isSuperAdmin ? 150 : 300" fixed="right">
<template #default="scope"> <template #default="scope">
<div class="action-buttons"> <div class="action-buttons">
<el-button size="small" text type="primary" @click="onOpenEditModule('edit', scope.row)">修改</el-button> <el-button size="small" text type="primary" @click="onOpenEditModule('edit', scope.row)">修改</el-button>
<el-button <!-- 非管理员才显示会话模型按钮 -->
v-if="isInferenceModel(scope.row.modelsType) && Number(scope.row.isChatModel) !== 1" <template v-if="!isSuperAdmin">
size="small" <el-button
text v-if="isInferenceModel(scope.row.modelsType) && Number(scope.row.isChatModel) !== 1"
type="warning" size="small"
@click="onSetChatModel(scope.row)" text
> type="warning"
设为会话模型 @click="onSetChatModel(scope.row)"
</el-button> >
<el-tag 设为会话模型
v-if="isInferenceModel(scope.row.modelsType) && Number(scope.row.isChatModel) === 1" </el-button>
type="success" <el-tag
effect="dark" v-if="isInferenceModel(scope.row.modelsType) && Number(scope.row.isChatModel) === 1"
size="default" type="success"
> effect="dark"
当前会话模型 size="default"
</el-tag> >
当前会话模型
</el-tag>
</template>
<el-button size="small" text type="danger" @click="onRowDel(scope.row)">删除</el-button> <el-button size="small" text type="danger" @click="onRowDel(scope.row)">删除</el-button>
</div> </div>
</template> </template>
@@ -85,7 +95,7 @@
> >
</el-pagination> </el-pagination>
</el-card> </el-card>
<EditModule ref="editModuleRef" :model-types="state.modelTypes" @refresh="getTableData()" /> <EditModule ref="editModuleRef" :model-types="state.modelTypes" :is-super-admin="isSuperAdmin" @refresh="getTableData()" />
<!-- 系统模型 API Key 输入弹窗 --> <!-- 系统模型 API Key 输入弹窗 -->
<el-dialog v-model="apiKeyDialogVisible" title="配置系统模型" width="500px" :close-on-click-modal="false"> <el-dialog v-model="apiKeyDialogVisible" title="配置系统模型" width="500px" :close-on-click-modal="false">
@@ -124,11 +134,13 @@ import {
updateChatModel, updateChatModel,
addModelModule, addModelModule,
} from '/@/api/digitalHuman/modelConfig/modelModule/index'; } from '/@/api/digitalHuman/modelConfig/modelModule/index';
import { checkIsSuperAdmin } from '/@/api/system/user/index';
import { getApiErrorMessage } from '/@/utils/request'; import { getApiErrorMessage } from '/@/utils/request';
const EditModule = defineAsyncComponent(() => import('/@/views/digitalHuman/modelConfig/modelModule/component/editModule.vue')); const EditModule = defineAsyncComponent(() => import('/@/views/digitalHuman/modelConfig/modelModule/component/editModule.vue'));
const editModuleRef = ref(); const editModuleRef = ref();
const isSuperAdmin = ref(false); // 是否为管理员
const state = reactive({ const state = reactive({
modelTypes: [] as Array<{ id: number | string; label: string }>, modelTypes: [] as Array<{ id: number | string; label: string }>,
tableData: { tableData: {
@@ -158,6 +170,16 @@ const apiKeyRules: FormRules = {
const creatingModel = ref(false); const creatingModel = ref(false);
const systemModelToClone = ref<any>(null); const systemModelToClone = ref<any>(null);
// 检查是否为管理员
const checkAdminStatus = async () => {
try {
const res: any = await checkIsSuperAdmin();
isSuperAdmin.value = res.data?.isSuperAdmin || false;
} catch {
isSuperAdmin.value = false;
}
};
// 判断是否为推理模型(只有推理模型才能设置为会话模型) // 判断是否为推理模型(只有推理模型才能设置为会话模型)
const isInferenceModel = (modelsType: number | string | undefined | null) => { const isInferenceModel = (modelsType: number | string | undefined | null) => {
if (modelsType === undefined || modelsType === null || modelsType === '') { if (modelsType === undefined || modelsType === null || modelsType === '') {
@@ -322,6 +344,7 @@ const onHandleCurrentChange = (val: number) => {
// 页面加载时 // 页面加载时
onMounted(async () => { onMounted(async () => {
await checkAdminStatus(); // 检查管理员状态
await loadModelTypes(); await loadModelTypes();
getTableData(); getTableData();
}); });
@@ -348,7 +371,7 @@ onMounted(async () => {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
.el-button { .el-button {
margin: 0; margin: 0;
} }