添加会话模型和API Key配置功能

- 在模型模块中新增会话开关状态字段,支持会话模型的管理。
- 更新模型选择器,增加系统模型的API Key配置弹窗,提升用户体验。
- 优化错误处理逻辑,确保接口错误由全局拦截器处理,减少冗余提示。
- 更新相关样式以增强界面可读性和美观性。
This commit is contained in:
2026-05-11 20:01:03 +08:00
parent 0a42e700e2
commit 29838b030f
19 changed files with 1296 additions and 274 deletions

View File

@@ -138,7 +138,7 @@ const openDialog = async (row?: DialogFormData) => {
}
} catch (error) {
console.error('获取账号详情失败:', error);
ElMessage.error('获取账号详情失败');
// 错误已由全局拦截器处理
} finally {
state.loading = false;
}

View File

@@ -98,7 +98,7 @@ const loadDatasets = async () => {
}));
}
} catch (error) {
ElMessage.error('加载数据集列表失败');
// 错误已由全局拦截器处理
}
};

View File

@@ -790,10 +790,11 @@ const buildTreeNodes = (tree: ExecutionTreeItem[]): TreeNode[] =>
const getList = async () => {
treeLoading.value = true;
try {
const res = await getExecutionList({ errorMode: 'page' });
const res = await getExecutionList();
imgAddressPrefix.value = res.data?.imgAddressPrefix || '';
treeNodes.value = buildTreeNodes(res.data?.tree || []);
} catch {
// 错误已由全局拦截器处理
treeNodes.value = [];
imgAddressPrefix.value = '';
} finally {
@@ -802,9 +803,10 @@ const getList = async () => {
};
const getNodeLibrary = async () => {
try {
const res = await getNodeLibraryList({ errorMode: 'page' });
const res = await getNodeLibraryList();
nodeLibraryGroups.value = res.data?.groups || [];
} catch {
// 错误已由全局拦截器处理
nodeLibraryGroups.value = [];
}
};
@@ -812,7 +814,7 @@ const getNodeLibrary = async () => {
const fetchWorkflowList = async () => {
workflowListLoading.value = true;
try {
const res = await getWorkflowList({ errorMode: 'page' });
const res = await getWorkflowList();
// 分别处理用户工作流和模板工作流
const userWorkflows = res.data?.listFlowUserRes?.list || [];
@@ -833,6 +835,7 @@ const fetchWorkflowList = async () => {
const templateEnd = templateStart + templateWorkflowPagination.pageSize;
templateWorkflowList.value = templateWorkflows.slice(templateStart, templateEnd);
} catch {
// 错误已由全局拦截器处理
userWorkflowList.value = [];
templateWorkflowList.value = [];
userWorkflowPagination.total = 0;
@@ -892,7 +895,7 @@ const handleRemoveModel = () => {
const useWorkflow = async (workflow: WorkflowItem) => {
try {
// 调用详情接口获取最新的工作流数据
const res = await getWorkflowDetail(workflow.id, { errorMode: 'page' });
const res = await getWorkflowDetail(workflow.id);
if (res.data) {
// 切换到创作模式
isCreationMode.value = true;
@@ -946,7 +949,7 @@ const useWorkflow = async (workflow: WorkflowItem) => {
const editWorkflow = async (workflow: WorkflowItem) => {
try {
// 调用详情接口获取最新的工作流数据
const res = await getWorkflowDetail(workflow.id, { errorMode: 'page' });
const res = await getWorkflowDetail(workflow.id);
if (res.data?.flowContent) {
// 切换回画布编辑模式
isCreationMode.value = false;
@@ -994,7 +997,7 @@ const deleteWorkflowAction = async (workflow: WorkflowItem) => {
type: 'warning',
});
await deleteWorkflow(workflow.id, { errorMode: 'page' });
await deleteWorkflow(workflow.id);
ElMessage.success('工作流删除成功');
// 如果删除的是当前正在编辑的工作流,清空编辑状态
@@ -1038,15 +1041,10 @@ const sendMessage = async () => {
const fileUrls: string[] = [];
if (selectedFiles.value.length > 0) {
for (const file of selectedFiles.value) {
try {
const uploadRes = await uploadFile(file, { errorMode: 'page' });
// 拼接完整的文件地址
const fullUrl = uploadRes.data.fileAddressPrefix ? `${uploadRes.data.fileAddressPrefix}${uploadRes.data.fileURL}` : uploadRes.data.fileURL;
fileUrls.push(fullUrl);
} catch (error) {
ElMessage.error(`文件 ${file.name} 上传失败`);
throw error;
}
const uploadRes = await uploadFile(file);
// 拼接完整的文件地址
const fullUrl = uploadRes.data.fileAddressPrefix ? `${uploadRes.data.fileAddressPrefix}${uploadRes.data.fileURL}` : uploadRes.data.fileURL;
fileUrls.push(fullUrl);
}
}
@@ -1109,7 +1107,7 @@ const sendMessage = async () => {
};
// 5. 调用执行接口(不再使用 FormData直接传 JSON
await executeFlow(params, { errorMode: 'page' });
await executeFlow(params);
ElMessage.success('创作完成!');
@@ -1117,8 +1115,8 @@ const sendMessage = async () => {
userInput.value = '';
selectedFiles.value = [];
selectedCreationSkill.value = null;
} catch (error) {
ElMessage.error('创作失败,请重试');
} catch {
// 接口错误由 request 全局提示后端 message
} finally {
isCreating.value = false;
}
@@ -1149,7 +1147,7 @@ const downloadNode = async (d: TreeNode) => {
if (!d.fileUrl) return ElMessage.warning('当前节点没有可下载地址');
try {
// 下载失败时希望展示更贴近页面语义的提示,因此改为 page 模式。
const r = await downloadToFile({ fileURL: d.fileUrl }, { errorMode: 'page' });
const r = await downloadToFile({ fileURL: d.fileUrl });
const blob = r instanceof Blob ? r : r?.data;
if (!(blob instanceof Blob)) throw new Error('invalid blob');
const name = decodeURIComponent(d.fileUrl.split('/').pop() || `${d.label}.${d.nodeType === 'html' ? 'html' : 'png'}`);
@@ -1163,7 +1161,7 @@ const downloadNode = async (d: TreeNode) => {
URL.revokeObjectURL(u);
ElMessage.success('下载成功');
} catch {
// 下载接口使用 errorMode: 'page',后端错误会自动显示
// 下载失败由 request 全局提示后端 message
}
};
const syncDsl = () => {
@@ -1847,26 +1845,20 @@ const confirmSaveWorkflow = async () => {
// 判断是新建还是更新
if (currentEditingWorkflowId.value) {
// 更新现有工作流
await updateWorkflow(
{
id: currentEditingWorkflowId.value,
flowName: saveForm.flowName,
description: saveForm.description,
flowContent: workflowDsl.value,
},
{ errorMode: 'page' }
);
await updateWorkflow({
id: currentEditingWorkflowId.value,
flowName: saveForm.flowName,
description: saveForm.description,
flowContent: workflowDsl.value,
});
ElMessage.success('工作流更新成功');
} else {
// 创建新工作流
await saveWorkflow(
{
flowName: saveForm.flowName,
description: saveForm.description,
flowContent: workflowDsl.value,
},
{ errorMode: 'page' }
);
await saveWorkflow({
flowName: saveForm.flowName,
description: saveForm.description,
flowContent: workflowDsl.value,
});
ElMessage.success('工作流保存成功');
}
saveDialogVisible.value = false;

View File

@@ -1,4 +1,4 @@
<template>
<template>
<div class="system-edit-module-container">
<el-dialog :title="state.dialog.title" v-model="state.dialog.isShowDialog" width="900px">
<el-form
@@ -19,12 +19,7 @@
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="模型类型" prop="modelsType">
<el-select v-model="state.ruleForm.modelsType" placeholder="请选择模型类型" clearable style="width: 100%">
<el-option
v-for="t in modelTypeOptions"
:key="String(t.id)"
:label="t.label"
:value="typeOptionValue(t.id)"
></el-option>
<el-option v-for="t in modelTypeOptions" :key="String(t.id)" :label="t.label" :value="typeOptionValue(t.id)"></el-option>
</el-select>
</el-form-item>
</el-col>
@@ -53,13 +48,7 @@
</el-col>
<el-col v-if="state.ruleForm.isPrivate === 1" :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<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-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
@@ -139,29 +128,21 @@
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="自动清理间隔(秒)" prop="autoCleanSeconds">
<el-input-number v-model="state.ruleForm.autoCleanSeconds" :min="0" :max="86400" style="width: 100%"></el-input-number>
<el-input-number v-model="state.ruleForm.autoCleanSeconds" :min="0" :max="86400" style="width: 100%"> </el-input-number>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="请求映射" prop="requestMappingJson">
<el-input
v-model="state.ruleForm.requestMappingJson"
type="textarea"
:rows="4"
placeholder='JSON 对象,例如 {}'
clearable
></el-input>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="请求映射" prop="requestMapping">
<el-button @click="showRequestMappingDialog = true" style="width: 100%">
配置请求映射 ({{ state.requestMappingFields.length }})
</el-button>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="响应映射" prop="responseMappingJson">
<el-input
v-model="state.ruleForm.responseMappingJson"
type="textarea"
:rows="4"
placeholder='JSON 对象,例如 {}'
clearable
></el-input>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="响应映射" prop="responseMapping">
<el-button @click="showResponseMappingDialog = true" style="width: 100%">
配置响应映射 ({{ state.responseMappingFields.length }})
</el-button>
</el-form-item>
</el-col>
</el-row>
@@ -170,13 +151,7 @@
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancel" size="default"> </el-button>
<el-button
type="primary"
@click="onSubmit"
size="default"
:loading="state.dialog.loading"
:disabled="state.dialog.detailLoading"
>
<el-button type="primary" @click="onSubmit" size="default" :loading="state.dialog.loading" :disabled="state.dialog.detailLoading">
{{ state.dialog.submitTxt }}
</el-button>
</span>
@@ -219,6 +194,51 @@
</span>
</template>
</el-dialog>
<!-- 请求映射配置弹窗 -->
<el-dialog v-model="showRequestMappingDialog" title="配置请求映射" width="600px" :close-on-click-modal="false">
<div class="mapping-config-container">
<div v-for="(field, index) in state.requestMappingFields" :key="index" class="mapping-field-item">
<el-input v-model="field.key" placeholder="请输入字段名 (Key)" style="width: 40%" clearable></el-input>
<span class="separator">=</span>
<el-input v-model="field.value" placeholder="请输入字段值 (Value)" style="width: 40%" clearable></el-input>
<el-button type="danger" link @click="removeRequestMappingField(index)">删除</el-button>
</div>
<el-button type="primary" link @click="addRequestMappingField" style="margin-top: 10px">+ 添加字段</el-button>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="showRequestMappingDialog = false" size="default"> </el-button>
<el-button type="primary" @click="confirmRequestMappingFields" size="default"> </el-button>
</span>
</template>
</el-dialog>
<!-- 响应映射配置弹窗 -->
<el-dialog v-model="showResponseMappingDialog" title="配置响应映射" width="700px" :close-on-click-modal="false">
<div class="mapping-config-container">
<div v-for="(field, index) in state.responseMappingFields" :key="index" class="mapping-field-item">
<el-input v-model="field.key" placeholder="请输入字段名 (Key)" style="width: 30%" clearable></el-input>
<span class="separator">=</span>
<el-input v-model="field.value" placeholder="请输入字段值 (Value)" style="width: 30%" clearable></el-input>
<el-button
:type="field.isMainBody ? 'success' : 'primary'"
:plain="!field.isMainBody"
@click="setMainBody(index)"
size="small"
>
{{ field.isMainBody ? '✓ 返回主体' : '设置返回主体' }}
</el-button>
<el-button type="danger" link @click="removeResponseMappingField(index)">删除</el-button>
</div>
<el-button type="primary" link @click="addResponseMappingField" style="margin-top: 10px">+ 添加字段</el-button>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="showResponseMappingDialog = false" size="default"> </el-button>
<el-button type="primary" @click="confirmResponseMappingFields" size="default"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
@@ -227,12 +247,7 @@ import { reactive, ref, computed } from 'vue';
import { ElMessage } from 'element-plus';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { ArrowUp, ArrowDown } from '@element-plus/icons-vue';
import {
addModelModule,
updateModelModule,
getModelModuleDetail,
type ModelFormEntry,
} from '/@/api/digitalHuman/modelConfig/modelModule/index';
import { addModelModule, updateModelModule, getModelModuleDetail, type ModelFormEntry } from '/@/api/digitalHuman/modelConfig/modelModule/index';
export type ModelTypeOption = { id: number | string; label: string };
@@ -254,6 +269,8 @@ const editModuleFormRef = ref();
const emit = defineEmits(['refresh']);
const showHeaderDialog = ref(false);
const showFormDialog = ref(false);
const showRequestMappingDialog = ref(false);
const showResponseMappingDialog = ref(false);
const state = reactive({
ruleForm: {
id: '',
@@ -274,8 +291,6 @@ const state = reactive({
retryQueueMaxSeconds: 60,
autoCleanSeconds: 300,
remark: '',
requestMappingJson: '{}',
responseMappingJson: '{}',
},
rules: {
modelName: [{ required: true, message: '请输入模型名称', trigger: 'blur' }],
@@ -293,48 +308,6 @@ const state = reactive({
],
baseUrl: [{ required: true, message: '请输入模型服务地址', trigger: 'blur' }],
httpMethod: [{ required: true, message: '请选择请求方式', trigger: 'change' }],
requestMappingJson: [
{
validator: (_rule: unknown, value: string, callback: (e?: Error) => void) => {
if (!value || !String(value).trim()) {
callback(new Error('请输入请求映射 JSON'));
return;
}
try {
const o = JSON.parse(value);
if (o === null || typeof o !== 'object' || Array.isArray(o)) {
callback(new Error('请求映射须为 JSON 对象'));
return;
}
callback();
} catch {
callback(new Error('请求映射 JSON 格式无效'));
}
},
trigger: 'blur',
},
],
responseMappingJson: [
{
validator: (_rule: unknown, value: string, callback: (e?: Error) => void) => {
if (!value || !String(value).trim()) {
callback(new Error('请输入响应映射 JSON'));
return;
}
try {
const o = JSON.parse(value);
if (o === null || typeof o !== 'object' || Array.isArray(o)) {
callback(new Error('响应映射须为 JSON 对象'));
return;
}
callback();
} catch {
callback(new Error('响应映射 JSON 格式无效'));
}
},
trigger: 'blur',
},
],
maxConcurrency: [{ required: true, message: '请输入最大并发数', trigger: 'blur' }],
queueLimit: [{ required: true, message: '请输入排队队列上限', trigger: 'blur' }],
timeoutSeconds: [{ required: true, message: '请输入请求超时时间', trigger: 'blur' }],
@@ -351,8 +324,22 @@ const state = reactive({
showAdvanced: false,
headers: [] as Array<{ key: string; value: string }>,
formFields: [] as Array<{ key: string; value: string }>,
requestMappingFields: [] as Array<{ key: string; value: string }>,
responseMappingFields: [] as Array<{ key: string; value: string; isMainBody?: boolean }>,
mainBodyIndex: -1, // 记录哪一行被设置为返回主体
});
// 将数组转换为对象
const fieldsToObject = (fields: Array<{ key: string; value: string }>) => {
const obj: Record<string, string> = {};
fields.forEach((f) => {
if (f.key && f.key.trim()) {
obj[f.key.trim()] = f.value || '';
}
});
return obj;
};
const parseHeaders = (headMsg: string) => parseKeyValueString(headMsg);
// 解析 form支持数组 [{ key, value }] 或历史对象 { k: { value } }
const parseFormFields = (form: unknown) => {
@@ -368,15 +355,25 @@ const parseFormFields = (form: unknown) => {
if (typeof form === 'object') {
const fields: Array<{ key: string; value: string }> = [];
Object.keys(form as Record<string, unknown>).forEach((key) => {
const v = (form as Record<string, { value?: string }>)[key];
if (v && typeof v === 'object' && v.value !== undefined) {
fields.push({ key, value: String(v.value) });
}
const v = (form as Record<string, unknown>)[key];
const value = String(v || '');
fields.push({ key, value });
});
return fields;
}
return [];
};
// 解析 requestMapping 对象为数组
const parseRequestMappingFields = (mapping: unknown) => {
if (!mapping || typeof mapping !== 'object' || Array.isArray(mapping)) return [];
return Object.entries(mapping).map(([key, value]) => ({ key, value: String(value) }));
};
// 解析 responseMapping 对象为数组
const parseResponseMappingFields = (mapping: unknown) => {
if (!mapping || typeof mapping !== 'object' || Array.isArray(mapping)) return [];
return Object.entries(mapping).map(([key, value]) => ({ key, value: String(value) }));
};
const buildFormArray = (): ModelFormEntry[] => {
return state.formFields
@@ -454,9 +451,48 @@ const removeFormField = (index: number) => {
const confirmFormFields = () => {
showFormDialog.value = false;
};
// 请求映射字段操作
const addRequestMappingField = () => {
state.requestMappingFields.push({ key: '', value: '' });
};
const removeRequestMappingField = (index: number) => {
state.requestMappingFields.splice(index, 1);
};
const confirmRequestMappingFields = () => {
showRequestMappingDialog.value = false;
};
// 响应映射字段操作
const addResponseMappingField = () => {
state.responseMappingFields.push({ key: '', value: '', isMainBody: false });
};
const removeResponseMappingField = (index: number) => {
state.responseMappingFields.splice(index, 1);
};
// 设置返回主体(单选)
const setMainBody = (index: number) => {
// 清除所有字段的返回主体标记
state.responseMappingFields.forEach((field, i) => {
field.isMainBody = i === index;
});
state.mainBodyIndex = index;
};
const confirmResponseMappingFields = () => {
showResponseMappingDialog.value = false;
};
const ensureKeyValueRows = (rows: Array<{ key: string; value: string }>) => (rows.length ? rows : [{ key: '', value: '' }]);
const ensureResponseMappingRows = (rows: Array<{ key: string; value: string; isMainBody?: boolean }>) => {
if (!rows.length) return [{ key: '', value: '', isMainBody: false }];
return rows.map(row => ({ ...row, isMainBody: row.isMainBody || false }));
};
/** 从 getModel 返回的 data 中取出单条模型对象 */
const unwrapModelDetailPayload = (data: unknown): Record<string, unknown> | null => {
if (data == null) return null;
@@ -487,10 +523,7 @@ const fillFormFromDetailRow = (row: Record<string, unknown>) => {
state.ruleForm = {
id: row.id as string,
modelName: String(row.modelName ?? ''),
modelsType:
row.modelsType !== undefined && row.modelsType !== null
? typeOptionValue(row.modelsType as number | string)
: null,
modelsType: row.modelsType !== undefined && row.modelsType !== null ? typeOptionValue(row.modelsType as number | string) : null,
baseUrl: String(row.baseUrl ?? ''),
httpMethod: String(row.httpMethod || 'POST'),
headMsg: String(row.headMsg || ''),
@@ -506,21 +539,25 @@ const fillFormFromDetailRow = (row: Record<string, unknown>) => {
retryQueueMaxSeconds: Number(row.retryQueueMaxSeconds ?? 60),
autoCleanSeconds: Number(row.autoCleanSeconds ?? 300),
remark: String(row.remark || ''),
requestMappingJson: JSON.stringify(
row.requestMapping && typeof row.requestMapping === 'object' ? row.requestMapping : {},
null,
2
),
responseMappingJson: JSON.stringify(
row.responseMapping && typeof row.responseMapping === 'object' ? row.responseMapping : {},
null,
2
),
};
state.headers = ensureKeyValueRows(parseHeaders(String(row.headMsg || '')));
state.formFields = ensureKeyValueRows(parseFormFields(row.form));
// 解析请求映射和响应映射
state.requestMappingFields = ensureKeyValueRows(parseRequestMappingFields(row.requestMapping));
state.responseMappingFields = ensureResponseMappingRows(parseResponseMappingFields(row.responseMapping));
// 根据 responseBody 字段设置返回主体标记 (responseBody 是对象 {key: value})
if (row.responseBody && typeof row.responseBody === 'object') {
const responseBodyKey = Object.keys(row.responseBody)[0];
if (responseBodyKey) {
const mainBodyIndex = state.responseMappingFields.findIndex(f => f.key === responseBodyKey);
if (mainBodyIndex !== -1) {
state.responseMappingFields[mainBodyIndex].isMainBody = true;
state.mainBodyIndex = mainBodyIndex;
}
}
}
};
// 打开弹窗(编辑时会请求 /model/getModel 详情)
const openDialog = async (type: string, row?: Record<string, unknown>) => {
state.dialog.type = type;
@@ -553,7 +590,7 @@ const openDialog = async (type: string, row?: Record<string, unknown>) => {
}
fillFormFromDetailRow(detail as Record<string, unknown>);
} catch {
ElMessage.error('获取模型详情失败');
// 接口错误由 request 全局提示后端 message
state.dialog.isShowDialog = false;
} finally {
state.dialog.detailLoading = false;
@@ -578,11 +615,11 @@ const openDialog = async (type: string, row?: Record<string, unknown>) => {
retryQueueMaxSeconds: 60,
autoCleanSeconds: 300,
remark: '',
requestMappingJson: '{}',
responseMappingJson: '{}',
};
state.headers = [{ key: '', value: '' }];
state.formFields = [{ key: '', value: '' }];
state.requestMappingFields = [{ key: '', value: '' }];
state.responseMappingFields = [{ key: '', value: '', isMainBody: false }];
state.dialog.title = '新增模型配置';
state.dialog.submitTxt = '新 增';
}
@@ -608,8 +645,11 @@ const onSubmit = () => {
state.dialog.loading = true;
try {
state.ruleForm.headMsg = stringifyHeaders();
const requestMapping = parseJsonObjectField(state.ruleForm.requestMappingJson, {});
const responseMapping = parseJsonObjectField(state.ruleForm.responseMappingJson, {});
const requestMapping = fieldsToObject(state.requestMappingFields);
const responseMapping = fieldsToObject(state.responseMappingFields);
// 获取被设置为返回主体的字段 {key: value}
const responseBodyField = state.responseMappingFields.find(f => f.isMainBody);
const responseBody = responseBodyField ? { [responseBodyField.key.trim()]: responseBodyField.value } : {};
const submitData = {
modelName: state.ruleForm.modelName,
modelsType: state.ruleForm.modelsType as number | string,
@@ -620,9 +660,10 @@ const onSubmit = () => {
enabled: state.ruleForm.enabled,
isChatModel: state.ruleForm.isChatModel,
apiKey: state.ruleForm.isPrivate === 1 ? String(state.ruleForm.apiKey ?? '').trim() : '',
form: buildFormArray(),
form: fieldsToObject(state.formFields),
requestMapping,
responseMapping,
responseBody,
maxConcurrency: state.ruleForm.maxConcurrency,
queueLimit: state.ruleForm.queueLimit,
timeoutSeconds: state.ruleForm.timeoutSeconds,
@@ -642,8 +683,8 @@ const onSubmit = () => {
}
closeDialog();
emit('refresh');
} catch (error) {
ElMessage.error('保存失败');
} catch {
// 接口错误由 request 全局提示后端 message
} finally {
state.dialog.loading = false;
}
@@ -657,6 +698,19 @@ defineExpose({
</script>
<style scoped lang="scss">
.mapping-config-container {
.mapping-field-item {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 10px;
.separator {
font-weight: bold;
color: #606266;
}
}
}
.form-config-container {
max-height: 400px;
overflow-y: auto;
@@ -689,3 +743,50 @@ defineExpose({
color: #606266;
}
</style>

View File

@@ -32,6 +32,25 @@
</template>
</el-table-column>
<el-table-column prop="httpMethod" label="请求方式" width="100"></el-table-column>
<el-table-column label="会话模型" width="100" align="center">
<template #default="{ row }">
<el-tag :type="Number(row.isChatModel) === 1 ? 'success' : 'info'" size="small">
{{ Number(row.isChatModel) === 1 ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="会话开关" width="110" align="center">
<template #default="{ row }">
<template v-if="Number(row.isChatModel) === 1">
<el-switch
size="small"
:model-value="chatSessionSwitchOn(row)"
:before-change="() => onChatSessionSwitchRequest(row)"
/>
</template>
<span v-else class="text-muted">—</span>
</template>
</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>
@@ -94,6 +113,16 @@ const state = reactive({
},
});
/** 列表行与会话开关接口约定字段 chatSessionEnabled0/1接口未就绪前占位 */
const chatSessionSwitchOn = (row: { chatSessionEnabled?: number }) => Number(row.chatSessionEnabled) === 1;
const onChatSessionSwitchRequest = (_row: { id?: number | string }) => {
return new Promise<boolean>((resolve) => {
ElMessage.info('会话开关接口接入后即可生效');
resolve(false);
});
};
const resolveModelTypeLabel = (modelsType: number | string | undefined | null) => {
if (modelsType === undefined || modelsType === null || modelsType === '') {
return '—';
@@ -108,8 +137,8 @@ const loadModelTypes = async () => {
if (res.code === 0) {
state.modelTypes = normalizeModelTypeOptions(res);
}
} catch (e) {
ElMessage.error('获取模型类型失败:');
} catch {
// 接口错误由 request 全局提示后端 message
}
};
@@ -122,8 +151,8 @@ const getTableData = async () => {
state.tableData.data = res.data.list || [];
state.tableData.total = res.data.total || 0;
}
} catch (error) {
ElMessage.error('获取模型列表失败');
} catch {
// 接口错误由 request 全局提示后端 message
} finally {
state.tableData.loading = false;
}
@@ -151,8 +180,8 @@ const onRowDel = (row: any) => {
await deleteModelModule(row.id);
ElMessage.success('删除成功');
getTableData();
} catch (error) {
ElMessage.error('删除失败');
} catch {
// 接口错误由 request 全局提示后端 message
}
})
.catch(() => {});
@@ -178,6 +207,10 @@ onMounted(async () => {
</script>
<style scoped lang="scss">
.text-muted {
color: var(--el-text-color-placeholder);
}
.system-user-container {
:deep(.el-card__body) {
display: flex;

View File

@@ -162,7 +162,7 @@ const handleFileChange: UploadProps['onChange'] = async (uploadFile) => {
}
try {
ElMessage.info('正在上传文件到 OSS...');
const uploadRes = await uploadFileToOss(uploadFile.raw, { errorMode: 'page' });
const uploadRes = await uploadFileToOss(uploadFile.raw);
formData.fileName = uploadRes.data.fileName;
formData.fileUrl = uploadRes.data.fileURL;
fileList.value = [uploadFile];
@@ -180,7 +180,7 @@ const fetchSkillList = async () => {
loading.value = true;
try {
const params = { pageNum: pagination.pageNum, pageSize: pagination.pageSize, keyword: searchParams.keyword || undefined };
const res = await getUserSkillList(params, { errorMode: 'page' });
const res = await getUserSkillList(params);
skillList.value = res.data?.list || [];
pagination.total = res.data?.total || 0;
} catch (error) {
@@ -222,21 +222,18 @@ const handleSubmit = async () => {
}
submitting.value = true;
try {
await createUserSkill(
{
name: formData.name,
description: formData.description,
category: formData.category,
fileName: formData.fileName,
fileUrl: formData.fileUrl,
},
{ errorMode: 'page' }
);
await createUserSkill({
name: formData.name,
description: formData.description,
category: formData.category,
fileName: formData.fileName,
fileUrl: formData.fileUrl,
});
ElMessage.success('创建成功');
dialogVisible.value = false;
fetchSkillList();
} catch (error) {
// 错误errorMode: 'page' 处理
} catch {
// 接口错误由 request 全局提示后端 message
} finally {
submitting.value = false;
}
@@ -251,12 +248,12 @@ const handleCommand = async (command: string, skill: SkillItem) => {
cancelButtonText: '取消',
type: 'warning',
});
await deleteUserSkill(skill.id, { errorMode: 'page' });
await deleteUserSkill(skill.id);
ElMessage.success('删除成功');
fetchSkillList();
} catch (error) {
if (error !== 'cancel') {
// 错误errorMode: 'page' 处理
// 接口错误由 request 全局提示后端 message
}
}
}

View File

@@ -558,7 +558,7 @@ const getknowledgeList = async () => {
});
knowledgeList.value = response.data.list || [];
} catch (_error) {
ElMessage.error('获取知识库列表失败');
// 错误已由全局拦截器处理
} finally {
knowledgeLoading.value = false;
}
@@ -648,7 +648,7 @@ const onSaveknowledge = async () => {
showknowledgeDialog.value = false;
getknowledgeList();
} catch (_error) {
ElMessage.error('保存失败,请重试');
// 错误已由全局拦截器处理
} finally {
knowledgeSaving.value = false;
}
@@ -673,7 +673,7 @@ const getFileList = async () => {
statusEnabled: item.status === 1,
}));
} catch (_error) {
ElMessage.error('获取文件列表失败');
// 错误已由全局拦截器处理
} finally {
fileLoading.value = false;
}
@@ -745,7 +745,7 @@ const onConfirmUpload = async () => {
showUploadDialog.value = false;
getFileList();
} catch (_error) {
ElMessage.error('创建文档失败,请重试');
// 错误已由全局拦截器处理
} finally {
uploading.value = false;
}
@@ -764,7 +764,7 @@ const onFileStatusChange = async (row: any) => {
} catch (error) {
// 失败时恢复原状态
row.statusEnabled = !row.statusEnabled;
ElMessage.error('状态更新失败');
// 错误已由全局拦截器处理
}
};
@@ -779,7 +779,7 @@ const onGenerateVector = async (row: any) => {
getFileList();
}, 1000);
} catch (error) {
ElMessage.error('生成向量失败');
// 错误已由全局拦截器处理
}
};
@@ -791,7 +791,7 @@ const onViewDocumentDetail = async (row: any) => {
currentDocument.value = response.data;
showDocumentDetailDialog.value = true;
} catch (error) {
ElMessage.error('获取文件详情失败');
// 错误已由全局拦截器处理
}
};
@@ -917,7 +917,7 @@ const onEditModelConfig = async (row: any) => {
// 打开弹窗
showCreateModelDialog.value = true;
} catch (error) {
ElMessage.error('获取模型配置详情失败');
// 错误已由全局拦截器处理
}
};
@@ -928,7 +928,7 @@ const getModelEnums = async () => {
const response = await getAllModelEnums();
modelEnums.value = response.data?.options || [];
} catch (error) {
ElMessage.error('获取模型类型枚举失败');
// 错误已由全局拦截器处理
modelEnums.value = [];
} finally {
modelEnumsLoading.value = false;
@@ -975,7 +975,7 @@ const getModelFormFields = async () => {
}
});
} catch (error) {
ElMessage.error('获取模型表单字段失败');
// 错误已由全局拦截器处理
modelFormFields.value = [];
} finally {
modelFormLoading.value = false;
@@ -1016,7 +1016,7 @@ const onSaveModelConfig = async () => {
showCreateModelDialog.value = false;
getModelConfigList();
} catch (error) {
ElMessage.error(isEditMode.value ? '更新模型配置失败' : '创建模型配置失败');
// 错误已由全局拦截器处理
}
};
@@ -1040,7 +1040,7 @@ const getModelConfigList = async () => {
});
modelConfigList.value = response.data?.list || [];
} catch (error) {
ElMessage.error('获取模型配置列表失败');
// 错误已由全局拦截器处理
modelConfigList.value = [];
} finally {
modelConfigLoading.value = false;
@@ -1128,7 +1128,7 @@ const getTaskList = async () => {
const response = await listTasks();
taskList.value = response.data?.list || [];
} catch (error) {
ElMessage.error('获取任务列表失败');
// 错误已由全局拦截器处理
taskList.value = [];
} finally {
taskListLoading.value = false;
@@ -1149,7 +1149,7 @@ const onReexecuteTask = async (task: any) => {
// 重新获取任务列表
await getTaskList();
} catch (error) {
ElMessage.error('重新执行任务失败,请重试');
// 错误已由全局拦截器处理
}
})
.catch(() => {});

View File

@@ -114,7 +114,7 @@ const openDialog = async (row?: DialogFormData) => {
};
}
} catch (error) {
ElMessage.error('获取主播详情失败');
// 错误已由全局拦截器处理
} finally {
state.loading = false;
}
@@ -160,7 +160,7 @@ const onSubmit = async () => {
closeDialog();
emit('refresh');
} catch (error) {
ElMessage.error('操作失败');
// 错误已由全局拦截器处理
} finally {
state.loading = false;
}

View File

@@ -166,7 +166,7 @@ const getList = async () => {
tableData.total = res.data.total || 0;
}
} catch (error) {
ElMessage.error('获取主播列表失败');
// 错误已由全局拦截器处理
} finally {
tableData.loading = false;
}
@@ -192,7 +192,7 @@ const handleDelete = async (row: TableDataItem) => {
getList();
} catch (error) {
if (error !== 'cancel') {
ElMessage.error('删除失败');
// 错误已由全局拦截器处理
}
}
};

View File

@@ -101,14 +101,13 @@ const openDialog = async (row?: { id?: string }) => {
try {
loading.value = true;
// 详情加载失败时由当前弹窗给出更易懂的业务提示。
const res = await getLiveAccountDetail({ id: String(row.id) }, { errorMode: 'page' });
const res = await getLiveAccountDetail({ id: String(row.id) });
if (res?.data) {
fillForm(res.data);
}
dialogVisible.value = true;
} catch (error) {
ElMessage.error('获取直播账号详情失败');
} catch {
// 接口错误由 request 全局提示后端 message
} finally {
loading.value = false;
}
@@ -130,19 +129,18 @@ const handleSubmit = async () => {
remark: formData.remark,
};
// 提交失败提示交给当前弹窗自己处理,避免和 request.ts 的统一报错重复。
if (isEdit.value) {
await updateLiveAccount(payload, { errorMode: 'page' });
await updateLiveAccount(payload);
ElMessage.success('修改成功');
} else {
await createLiveAccount(payload, { errorMode: 'page' });
await createLiveAccount(payload);
ElMessage.success('新增成功');
}
dialogVisible.value = false;
emit('refresh');
} catch (error) {
ElMessage.error(isEdit.value ? '修改失败' : '新增失败');
} catch {
// 接口错误由 request 全局提示后端 message
} finally {
loading.value = false;
}

View File

@@ -131,17 +131,13 @@ const tableData = reactive({
const getList = async () => {
try {
tableData.loading = true;
// 列表失败文案由当前页面决定,避免和全局请求报错同时出现。
const res = await getLiveAccountList(
{
...tableData.param,
platform: searchForm.platform || undefined,
accountName: searchForm.accountName || undefined,
accountId: searchForm.accountId || undefined,
status: searchForm.status,
},
{ errorMode: 'page' }
);
const res = await getLiveAccountList({
...tableData.param,
platform: searchForm.platform || undefined,
accountName: searchForm.accountName || undefined,
accountId: searchForm.accountId || undefined,
status: searchForm.status,
});
if (res && res.data) {
tableData.data = (res.data.list || []).map((item: any) => ({
...item,
@@ -149,8 +145,8 @@ const getList = async () => {
}));
tableData.total = res.data.total || 0;
}
} catch (error) {
ElMessage.error('获取直播账号列表失败');
} catch {
// 接口错误由 request 全局提示后端 message
} finally {
tableData.loading = false;
}
@@ -195,12 +191,12 @@ const handleDelete = async (row: LiveAccountItem) => {
cancelButtonText: '取消',
type: 'warning',
});
await deleteLiveAccount({ id: row.id }, { errorMode: 'page' });
await deleteLiveAccount({ id: row.id });
ElMessage.success('删除成功');
getList();
} catch (error) {
if (error !== 'cancel') {
ElMessage.error('删除失败');
// 接口错误由 request 全局提示后端 message
}
}
};

View File

@@ -153,8 +153,7 @@ const openDialog = async (row?: { id?: string }) => {
await loadOptions();
if (row?.id) {
// 详情请求失败时,这个弹窗希望给出更明确的页面语义提示。
const res = await getScheduleDetail({ id: String(row.id) }, { errorMode: 'page' });
const res = await getScheduleDetail({ id: String(row.id) });
const detail = res?.data;
if (detail) {
formData.id = String(detail.id);
@@ -170,8 +169,8 @@ const openDialog = async (row?: { id?: string }) => {
}
dialogVisible.value = true;
} catch (error) {
ElMessage.error(isEdit.value ? '获取排班详情失败' : '加载排班基础数据失败');
} catch {
// 接口错误由 request 全局提示后端 message表单校验错误由表单项展示
} finally {
loading.value = false;
}
@@ -196,19 +195,18 @@ const handleSubmit = async () => {
remark: formData.remark,
};
// 提交失败文案由弹窗自己控制,避免接口层和弹窗层重复报错。
if (isEdit.value) {
await updateSchedule(payload, { errorMode: 'page' });
await updateSchedule(payload);
ElMessage.success('修改排班成功');
} else {
await createSchedule(payload, { errorMode: 'page' });
await createSchedule(payload);
ElMessage.success('新增排班成功');
}
dialogVisible.value = false;
emit('refresh');
} catch (error) {
ElMessage.error(isEdit.value ? '修改排班失败' : '新增排班失败');
} catch {
// 接口错误由 request 全局提示后端 message
} finally {
loading.value = false;
}

View File

@@ -144,16 +144,12 @@ const getStatusTagType = (status: number): 'success' | 'info' | 'warning' => {
const getList = async () => {
try {
tableData.loading = true;
// 列表失败文案由当前页面决定,避免和 request.ts 的全局错误提示重复。
const res = await getScheduleList(
{
...tableData.param,
anchorName: searchForm.anchorName || undefined,
accountName: searchForm.accountName || undefined,
status: searchForm.status,
} as any,
{ errorMode: 'page' }
);
const res = await getScheduleList({
...tableData.param,
anchorName: searchForm.anchorName || undefined,
accountName: searchForm.accountName || undefined,
status: searchForm.status,
} as any);
const scheduleData = res?.data;
if (scheduleData) {
tableData.data = (scheduleData.list || []).map((item: any) => ({
@@ -166,8 +162,8 @@ const getList = async () => {
}));
tableData.total = scheduleData.total || 0;
}
} catch (error) {
ElMessage.error('获取排班列表失败');
} catch {
// 接口错误由 request 全局提示后端 message
} finally {
tableData.loading = false;
}
@@ -211,12 +207,12 @@ const handleDelete = async (row: ScheduleItem) => {
cancelButtonText: '取消',
type: 'warning',
});
await deleteSchedule({ id: row.id }, { errorMode: 'page' });
await deleteSchedule({ id: row.id });
ElMessage.success('删除成功');
getList();
} catch (error) {
if (error !== 'cancel') {
ElMessage.error('删除失败');
// 接口错误由 request 全局提示后端 message
}
}
};