fix: 更新开发环境 API 地址并增强 editModule.vue 表单验证

- 在 editModule.vue 中添加响应字段和请求映射的验证逻辑,确保用户输入有效性,提升表单提交的可靠性。
This commit is contained in:
2026-05-26 10:11:21 +08:00
parent 4cade1ab94
commit 5152121c33

View File

@@ -563,6 +563,18 @@ const state = reactive({
callback(new Error('主动拉取时,请填写拉取地址'));
return;
}
// 验证响应字段至少有一个有效字段
const validResponseFields = pullConfigForm.responseFields.filter((f) => String(f.value || '').trim() !== '');
if (validResponseFields.length === 0) {
callback(new Error('主动拉取时,至少需要配置一个响应字段'));
return;
}
// 验证是否设置了返回主体字段
const hasMainBody = pullConfigForm.responseFields.some((f) => f.isMainBody && String(f.value || '').trim() !== '');
if (!hasMainBody) {
callback(new Error('主动拉取时,必须设置一个返回主体字段'));
return;
}
callback();
},
trigger: 'change',
@@ -595,10 +607,47 @@ const state = reactive({
queueLimit: [{ required: true, message: '请输入排队队列上限', trigger: 'blur' }],
timeoutSeconds: [{ required: true, message: '请输入请求超时时间', trigger: 'blur' }],
expectedSeconds: [{ required: true, message: '请输入预计执行时间', trigger: 'blur' }],
requestMapping: [
{
validator: (_rule: unknown, _value: unknown, callback: (e?: Error) => void) => {
const emptyKeys = state.requestMappingFields.filter((x) => String(x.key || '').trim() === '' && String(x.value || '').trim() !== '');
if (emptyKeys.length > 0) {
callback(new Error('请求映射字段名不能为空'));
return;
}
callback();
},
trigger: 'change',
},
],
responseMapping: [
{
validator: (_rule: unknown, _value: unknown, callback: (e?: Error) => void) => {
// 检查是否有空键名但有值的字段
const emptyKeys = state.responseMappingFields.filter((x) => String(x.key || '').trim() === '' && String(x.value || '').trim() !== '');
if (emptyKeys.length > 0) {
callback(new Error('响应映射字段名不能为空'));
return;
}
// 检查是否设置了返回主体字段(必填)
const validFields = state.responseMappingFields.filter((x) => String(x.key || '').trim() !== '');
if (validFields.length > 0) {
const hasMainBody = state.responseMappingFields.some((f) => f.isMainBody && String(f.key || '').trim() !== '');
if (!hasMainBody) {
callback(new Error('响应映射必须设置一个返回主体字段'));
return;
}
}
callback();
},
trigger: 'change',
},
],
tokenConfig: [
{
validator: (_rule: unknown, _value: unknown, callback: (e?: Error) => void) => {
if (!state.tokenConfigFields.length || state.tokenConfigFields.some((x) => !String(x.key || '').trim())) {
const emptyKeys = state.tokenConfigFields.filter((x) => String(x.key || '').trim() === '' && String(x.value || '').trim() !== '');
if (emptyKeys.length > 0) {
callback(new Error('Token计算配置字段名不能为空'));
return;
}
@@ -610,7 +659,8 @@ const state = reactive({
extendMapping: [
{
validator: (_rule: unknown, _value: unknown, callback: (e?: Error) => void) => {
if (state.extendMappingFields.some((x) => !String(x.key || '').trim())) {
const emptyKeys = state.extendMappingFields.filter((x) => String(x.key || '').trim() === '' && String(x.value || '').trim() !== '');
if (emptyKeys.length > 0) {
callback(new Error('附加映射字段名不能为空'));
return;
}
@@ -650,26 +700,41 @@ const fieldsToObject = (fields: Array<{ key: string; value: string }>) => {
};
const parseHeaders = (headMsg: string) => parseKeyValueString(headMsg);
// 解析 form支持数组 [{ key, value }] 或历史对象 { k: { value } }
const parseFormFields = (form: unknown) => {
if (!form) return [];
if (Array.isArray(form)) {
return (form as ModelFormEntry[])
// 统一的字段解析函数支持数组、对象、JSON字符串
const parseFieldsUnified = (raw: unknown): Array<{ key: string; value: string }> => {
if (!raw) return [];
// 如果是字符串尝试解析为JSON
if (typeof raw === 'string') {
try {
const parsed = JSON.parse(raw);
return parseFieldsUnified(parsed);
} catch {
return [];
}
}
// 如果是数组格式 [{ key, value }]
if (Array.isArray(raw)) {
return (raw as ModelFormEntry[])
.filter((item) => item && (item.key !== undefined || item.value !== undefined))
.map((item) => ({
key: String(item.key ?? '').trim(),
value: String(item.value ?? '').trim(),
}));
}
if (typeof form === 'object') {
// 如果是对象格式 { key: value }
if (typeof raw === 'object') {
const fields: Array<{ key: string; value: string }> = [];
Object.keys(form as Record<string, unknown>).forEach((key) => {
const v = (form as Record<string, unknown>)[key];
const value = String(v || '');
Object.keys(raw as Record<string, unknown>).forEach((key) => {
const v = (raw as Record<string, unknown>)[key];
const value = String(v ?? '');
fields.push({ key, value });
});
return fields;
}
return [];
};
// 解析 requestMapping 对象为数组
@@ -949,12 +1014,11 @@ const fillFormFromDetailRow = (row: Record<string, unknown>) => {
tokenConfig: '{}',
};
state.headers = ensureKeyValueRows(parseHeaders(String(row.headMsg || '')));
state.formFields = ensureKeyValueRows(parseFormFields(row.form));
// 解析请求映射和响应映射
state.formFields = ensureKeyValueRows(parseFieldsUnified(row.form));
state.requestMappingFields = ensureKeyValueRows(parseRequestMappingFields(row.requestMapping));
state.responseMappingFields = ensureResponseMappingRows(parseResponseMappingFields(row.responseMapping));
state.extendMappingFields = ensureKeyValueRows(objectToFields((row.extendMapping as Record<string, unknown>) || {}));
state.tokenConfigFields = ensureKeyValueRows(objectToFields((row.tokenConfig as Record<string, unknown>) || {}));
state.extendMappingFields = ensureKeyValueRows(parseFieldsUnified(row.extendMapping));
state.tokenConfigFields = ensureKeyValueRows(parseFieldsUnified(row.tokenConfig));
// 根据 responseTokenField 字段设置计费字段标记(单选)
const tokenFieldKey = String(row.responseTokenField || '').trim();
@@ -1102,17 +1166,37 @@ const onSubmit = () => {
state.dialog.loading = true;
try {
// 触发所有自定义字段的验证
if (state.ruleForm.queryResponseType === 'pull') {
await editModuleFormRef.value?.validateField?.('queryPullConfig');
}
// 验证响应映射(如果有配置)
const validResponseFields = state.responseMappingFields.filter((x) => String(x.key || '').trim() !== '');
if (validResponseFields.length > 0) {
await editModuleFormRef.value?.validateField?.('responseMapping');
}
// 验证请求映射(如果有配置)
const validRequestFields = state.requestMappingFields.filter((x) => String(x.key || '').trim() !== '');
if (validRequestFields.length > 0) {
await editModuleFormRef.value?.validateField?.('requestMapping');
}
state.ruleForm.headMsg = stringifyHeaders();
const requestMapping = fieldsToObject(state.requestMappingFields);
const responseMapping = fieldsToObject(state.responseMappingFields);
// 过滤掉空键名的字段
const requestMapping = fieldsToObject(state.requestMappingFields.filter((f) => String(f.key || '').trim() !== ''));
const responseMapping = fieldsToObject(state.responseMappingFields.filter((f) => String(f.key || '').trim() !== ''));
// 获取被设置为返回主体的字段 {key: value}
const responseBodyField = state.responseMappingFields.find((f) => f.isMainBody);
const responseBodyField = state.responseMappingFields.find((f) => f.isMainBody && String(f.key || '').trim() !== '');
const responseBody = responseBodyField ? { [responseBodyField.key.trim()]: responseBodyField.value } : {};
// 获取计费字段(可选)
const responseTokenField =
state.responseMappingFields.find((f) => f.isTokenField)?.key?.trim() || String(state.ruleForm.responseTokenField || '').trim();
const submitData: CreateModelParams = {
modelName: state.ruleForm.modelName,
modelType: state.ruleForm.modelType as number | string,
@@ -1123,6 +1207,7 @@ const onSubmit = () => {
isPrivate: state.ruleForm.isPrivate,
enabled: state.ruleForm.enabled,
isChatModel: state.ruleForm.isChatModel,
// 确保 API 密钥只在 isPrivate=1 时提交
apiKey: state.ruleForm.isPrivate === 1 ? String(state.ruleForm.apiKey ?? '').trim() : '',
form: state.formFields
.filter((f) => String(f.key || '').trim() !== '')
@@ -1138,9 +1223,9 @@ const onSubmit = () => {
retryQueueMaxSeconds: state.ruleForm.retryQueueMaxSeconds,
autoCleanSeconds: state.ruleForm.autoCleanSeconds,
remark: state.ruleForm.remark || '',
extendMapping: fieldsToUnknownObject(state.extendMappingFields),
extendMapping: fieldsToUnknownObject(state.extendMappingFields.filter((f) => String(f.key || '').trim() !== '')),
responseTokenField,
tokenConfig: fieldsToUnknownObject(state.tokenConfigFields),
tokenConfig: fieldsToUnknownObject(state.tokenConfigFields.filter((f) => String(f.key || '').trim() !== '')),
queryConfig: buildQueryConfig(),
};