fix: 更新开发环境 API 地址并增强 editModule.vue 表单验证
- 在 editModule.vue 中添加响应字段和请求映射的验证逻辑,确保用户输入有效性,提升表单提交的可靠性。
This commit is contained in:
@@ -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(),
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user