Files
admin-ui/src/views/digitalHuman/modelConfig/modelModule/index.vue
2910410219 72af38ea00 新增管理员权限检查和模型选择逻辑优化
- 在用户 API 中新增 `checkIsSuperAdmin` 函数,用于检查用户是否为超级管理员。
- 更新模型选择器,非管理员用户只能选择内置模型并需配置 API Key,提升安全性和用户体验。
- 优化模型配置页面,动态显示操作按钮,确保管理员与普通用户的操作权限区分明确。
2026-05-12 13:52:24 +08:00

380 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="system-user-container layout-padding">
<el-card shadow="hover" class="layout-padding-auto">
<div class="system-user-search mb15">
<el-input v-model="state.tableData.param.modelName" size="default" placeholder="请输入模型名称" style="max-width: 180px" clearable>
</el-input>
<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-select>
<el-button size="default" type="primary" class="ml10" @click="getTableData">
<el-icon>
<ele-Search />
</el-icon>
查询
</el-button>
<el-button size="default" type="success" class="ml10" @click="onOpenAddModule('add')">
<el-icon>
<ele-FolderAdd />
</el-icon>
新增模型配置
</el-button>
</div>
<el-table :data="state.tableData.data" v-loading="state.tableData.loading" style="width: 100%">
<el-table-column type="index" label="序号" width="60" />
<el-table-column prop="modelName" label="模型名称" show-overflow-tooltip></el-table-column>
<el-table-column label="模型类型" width="120" show-overflow-tooltip>
<template #default="{ row }">
{{ resolveModelTypeLabel(row.modelsType) }}
</template>
</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">
<template #default="scope">
<el-tag :type="scope.row.isPrivate === 1 ? 'primary' : 'info'">{{ scope.row.isPrivate === 1 ? '本地模型' : '服务商模型' }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="httpMethod" label="请求方式" width="100"></el-table-column>
<el-table-column prop="enabled" label="状态" width="100">
<template #default="scope">
<el-tag :type="scope.row.enabled === 1 ? 'success' : 'danger'">{{ scope.row.enabled === 1 ? '启用' : '禁用' }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="maxConcurrency" label="最大并发" width="100"></el-table-column>
<el-table-column prop="queueLimit" label="队列上限" width="100"></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="updatedAt" label="修改时间" show-overflow-tooltip width="160"></el-table-column>
<el-table-column label="操作" :width="isSuperAdmin ? 150 : 300" fixed="right">
<template #default="scope">
<div class="action-buttons">
<el-button size="small" text type="primary" @click="onOpenEditModule('edit', scope.row)">修改</el-button>
<!-- 非管理员才显示会话模型按钮 -->
<template v-if="!isSuperAdmin">
<el-button
v-if="isInferenceModel(scope.row.modelsType) && Number(scope.row.isChatModel) !== 1"
size="small"
text
type="warning"
@click="onSetChatModel(scope.row)"
>
设为会话模型
</el-button>
<el-tag
v-if="isInferenceModel(scope.row.modelsType) && Number(scope.row.isChatModel) === 1"
type="success"
effect="dark"
size="default"
>
当前会话模型
</el-tag>
</template>
<el-button size="small" text type="danger" @click="onRowDel(scope.row)">删除</el-button>
</div>
</template>
</el-table-column>
</el-table>
<el-pagination
@size-change="onHandleSizeChange"
@current-change="onHandleCurrentChange"
class="mt15"
:pager-count="5"
:page-sizes="[10, 20, 30, 50]"
v-model:current-page="state.tableData.param.pageNum"
background
v-model:page-size="state.tableData.param.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="state.tableData.total"
>
</el-pagination>
</el-card>
<EditModule ref="editModuleRef" :model-types="state.modelTypes" :is-super-admin="isSuperAdmin" @refresh="getTableData()" />
<!-- 系统模型 API Key 输入弹窗 -->
<el-dialog v-model="apiKeyDialogVisible" title="配置系统模型" width="500px" :close-on-click-modal="false">
<el-alert type="info" :closable="false" style="margin-bottom: 16px">
<template #title>
<div style="line-height: 1.6">
您选择的是系统模型需要配置您自己的 API Key<br />
系统将为您创建一个模型副本并设置为会话模型
</div>
</template>
</el-alert>
<el-form :model="apiKeyForm" :rules="apiKeyRules" ref="apiKeyFormRef" label-width="100px">
<el-form-item label="模型名称" prop="modelName">
<el-input v-model="apiKeyForm.modelName" placeholder="请输入模型名称" />
</el-form-item>
<el-form-item label="API Key" prop="apiKey">
<el-input v-model="apiKeyForm.apiKey" type="password" show-password placeholder="请输入您的 API Key" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="apiKeyDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleCreatePrivateModelAndSetChat" :loading="creatingModel">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="digitalHumanModelModule">
import { defineAsyncComponent, reactive, onMounted, ref } from 'vue';
import { ElMessageBox, ElMessage, type FormInstance, type FormRules } from 'element-plus';
import {
getModelModuleList,
getModelTypeList,
deleteModelModule,
normalizeModelTypeOptions,
updateChatModel,
addModelModule,
} from '/@/api/digitalHuman/modelConfig/modelModule/index';
import { checkIsSuperAdmin } from '/@/api/system/user/index';
import { getApiErrorMessage } from '/@/utils/request';
const EditModule = defineAsyncComponent(() => import('/@/views/digitalHuman/modelConfig/modelModule/component/editModule.vue'));
const editModuleRef = ref();
const isSuperAdmin = ref(false); // 是否为管理员
const state = reactive({
modelTypes: [] as Array<{ id: number | string; label: string }>,
tableData: {
data: [],
total: 0,
loading: false,
param: {
pageNum: 1,
pageSize: 10,
modelName: '',
modelType: undefined as number | string | undefined,
},
},
});
// 系统模型 API Key 配置
const apiKeyDialogVisible = ref(false);
const apiKeyFormRef = ref<FormInstance>();
const apiKeyForm = reactive({
modelName: '',
apiKey: '',
});
const apiKeyRules: FormRules = {
modelName: [{ required: true, message: '请输入模型名称', trigger: 'blur' }],
apiKey: [{ required: true, message: '请输入 API Key', trigger: 'blur' }],
};
const creatingModel = ref(false);
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) => {
if (modelsType === undefined || modelsType === null || modelsType === '') {
return false;
}
// 查找模型类型标签,判断是否为"推理模型"
const typeInfo = state.modelTypes.find((t) => String(t.id) === String(modelsType));
return typeInfo?.label === '推理模型' || String(modelsType) === '1';
};
// 设置为会话模型
const onSetChatModel = async (row: any) => {
// 判断是否是系统模型tenantId === 1
if (row.tenantId === 1) {
// 系统模型,需要用户配置 API Key
systemModelToClone.value = row;
apiKeyForm.modelName = row.modelName;
apiKeyForm.apiKey = '';
apiKeyDialogVisible.value = true;
} else {
// 非系统模型,直接设置为会话模型
try {
await updateChatModel({
id: row.id!,
isChatModel: 1,
});
ElMessage.success('已设置为会话模型');
await getTableData();
} catch {
// 错误已由全局拦截器处理
}
}
};
// 创建私有模型并设置为会话模型
const handleCreatePrivateModelAndSetChat = async () => {
if (!apiKeyFormRef.value || !systemModelToClone.value) return;
try {
await apiKeyFormRef.value.validate();
creatingModel.value = true;
// 基于系统模型创建新模型(继承原模型的所有配置,只替换 apiKey
const systemModel = systemModelToClone.value;
const createParams = {
modelName: apiKeyForm.modelName,
modelsType: systemModel.modelsType,
baseUrl: systemModel.baseUrl,
httpMethod: systemModel.httpMethod || 'POST',
headMsg: systemModel.headMsg || '',
isPrivate: systemModel.isPrivate ?? 1,
enabled: systemModel.enabled ?? 1,
isChatModel: 1, // 设置为会话模型
apiKey: apiKeyForm.apiKey,
form: systemModel.form || [],
requestMapping: systemModel.requestMapping || {},
responseMapping: systemModel.responseMapping || {},
maxConcurrency: systemModel.maxConcurrency || 10,
queueLimit: systemModel.queueLimit || 100,
timeoutSeconds: systemModel.timeoutSeconds || 30,
expectedSeconds: systemModel.expectedSeconds || 15,
retryTimes: systemModel.retryTimes || 3,
retryQueueMaxSeconds: systemModel.retryQueueMaxSeconds || 60,
autoCleanSeconds: systemModel.autoCleanSeconds || 300,
remark: systemModel.remark || '',
};
await addModelModule(createParams);
ElMessage.success('模型创建成功并已设置为会话模型');
// 关闭对话框
apiKeyDialogVisible.value = false;
// 刷新列表
await getTableData();
} catch (error: any) {
if (error !== 'cancel') {
ElMessage.error(getApiErrorMessage(error, '创建模型失败'));
}
} finally {
creatingModel.value = false;
}
};
const resolveModelTypeLabel = (modelsType: number | string | undefined | null) => {
if (modelsType === undefined || modelsType === null || modelsType === '') {
return '—';
}
const hit = state.modelTypes.find((t) => String(t.id) === String(modelsType));
return hit?.label ?? String(modelsType);
};
const loadModelTypes = async () => {
try {
const res: any = await getModelTypeList();
if (res.code === 0) {
state.modelTypes = normalizeModelTypeOptions(res);
}
} catch {
// 接口错误由 request 全局提示后端 message
}
};
// 初始化表格数据
const getTableData = async () => {
state.tableData.loading = true;
try {
const res: any = await getModelModuleList(state.tableData.param);
if (res.code === 0) {
state.tableData.data = res.data.list || [];
state.tableData.total = res.data.total || 0;
}
} catch {
// 接口错误由 request 全局提示后端 message
} finally {
state.tableData.loading = false;
}
};
// 打开新增模型模块弹窗
const onOpenAddModule = (type: string) => {
editModuleRef.value.openDialog(type);
};
// 打开修改模型模块弹窗
const onOpenEditModule = (type: string, row: any) => {
editModuleRef.value.openDialog(type, row);
};
// 删除模型模块
const onRowDel = (row: any) => {
ElMessageBox.confirm(`确定要删除模型配置:${row.modelName}`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(async () => {
try {
await deleteModelModule(row.id);
ElMessage.success('删除成功');
getTableData();
} catch {
// 接口错误由 request 全局提示后端 message
}
})
.catch(() => {});
};
// 分页改变
const onHandleSizeChange = (val: number) => {
state.tableData.param.pageSize = val;
getTableData();
};
// 分页改变
const onHandleCurrentChange = (val: number) => {
state.tableData.param.pageNum = val;
getTableData();
};
// 页面加载时
onMounted(async () => {
await checkAdminStatus(); // 检查管理员状态
await loadModelTypes();
getTableData();
});
</script>
<style scoped lang="scss">
.text-muted {
color: var(--el-text-color-placeholder);
}
.system-user-container {
:deep(.el-card__body) {
display: flex;
flex-direction: column;
flex: 1;
overflow: auto;
.el-table {
flex: 1;
}
}
}
.action-buttons {
display: flex;
align-items: center;
gap: 8px;
.el-button {
margin: 0;
}
}
</style>