All checks were successful
Build and Deploy Front / deploy (push) Successful in 5m47s
优化运单详情抽屉操作栏与申报表编辑观感,限制我的订单入库后不可编辑;完善接口参数全量替换/脏检查与全局 Message;修复价格表批量填价选中被清与弹窗被遮挡。 Co-authored-by: Cursor <cursoragent@cursor.com>
785 lines
27 KiB
TypeScript
785 lines
27 KiB
TypeScript
import { ContentType, FieldPosition } from '@/constants/itfxEnums';
|
||
|
||
/** 参数树节点种类:0=普通,1=Mapping Body(仅 FormUrlEncoded 映射体) */
|
||
export const ParaKind = {
|
||
Normal: 0,
|
||
MappingBody: 1,
|
||
} as const;
|
||
|
||
export type InterfaceParameterMode = 'upstream' | 'downstream';
|
||
|
||
/** 字典 / 表单可能返回 string,统一为数字再比较 */
|
||
export function normalizeContentType(contentType: unknown): number | undefined {
|
||
if (contentType === '' || contentType === undefined || contentType === null) {
|
||
return undefined;
|
||
}
|
||
const n = Number(contentType);
|
||
return Number.isNaN(n) ? undefined : n;
|
||
}
|
||
|
||
export function isNoneContentType(contentType?: unknown): boolean {
|
||
return normalizeContentType(contentType) === ContentType.None;
|
||
}
|
||
|
||
export function isFormUrlEncodedContentType(contentType?: unknown): boolean {
|
||
return normalizeContentType(contentType) === ContentType.FormUrlEncoded;
|
||
}
|
||
|
||
export function isJsonOrXmlContentType(contentType?: unknown): boolean {
|
||
const ct = normalizeContentType(contentType);
|
||
return ct === ContentType.Json || ct === ContentType.Xml;
|
||
}
|
||
|
||
/** FormUrlEncoded 与 none:需 Mapping Body Tab(Query 等可 $FromMappingBody) */
|
||
export function isMappingBodyContentType(contentType?: unknown): boolean {
|
||
return isFormUrlEncodedContentType(contentType) || isNoneContentType(contentType);
|
||
}
|
||
|
||
/** 从 GetInterfaceById 响应拆成 InterfaceDetail 各 Tab 数据 */
|
||
export function hydrateDetailParamsFromInterface(apiData: Record<string, any>) {
|
||
const contentType = apiData.contentType as number | undefined;
|
||
const isForm = isFormUrlEncodedContentType(contentType);
|
||
const isNone = isNoneContentType(contentType);
|
||
|
||
return {
|
||
queryParams: apiData.queryFormParameterList ?? apiData.formParameterList ?? [],
|
||
headerParams: apiData.headerFormParameterList ?? [],
|
||
responseParams: apiData.outParameterList ?? [],
|
||
requestParams: isForm || isNone
|
||
? []
|
||
: (apiData.requestBodyParameterList ?? apiData.inParameterList ?? []),
|
||
bodyFormParams: isForm ? (apiData.bodyFormParameterList ?? []) : [],
|
||
mappingBodyParams: isForm || isNone ? (apiData.mappingBodyParameterList ?? []) : [],
|
||
};
|
||
}
|
||
|
||
/** vxe 树表 fullData 常为带 children 的层级;转为扁平 id/pId 列表 */
|
||
export function flattenTreeTableRows(arr: any[]): any[] {
|
||
if (!Array.isArray(arr) || !arr.length) return [];
|
||
return arr.reduce((acc: any[], val: any) => {
|
||
const { children, ...rest } = val;
|
||
acc.push(rest);
|
||
if (Array.isArray(children) && children.length) {
|
||
acc.push(...flattenTreeTableRows(children));
|
||
}
|
||
return acc;
|
||
}, []);
|
||
}
|
||
|
||
function flattenTree(arr: any[]): any[] {
|
||
return flattenTreeTableRows(arr);
|
||
}
|
||
|
||
/**
|
||
* 读取树表完整扁平数据。
|
||
* 已传入表格 fullData(含清空后的 [])时必须以表格为准;
|
||
* 旧逻辑在 grid 为空时回退 props,会把已删除行「复活」,导致清空后保存仍报重名。
|
||
*/
|
||
export function getTreeTableFlatRows(fromGrid?: any[], propsData?: any[]): any[] {
|
||
if (Array.isArray(fromGrid)) {
|
||
return flattenTreeTableRows(fromGrid);
|
||
}
|
||
return flattenTreeTableRows(Array.isArray(propsData) ? propsData : []);
|
||
}
|
||
|
||
function getRowSeq(row: any, tableRef: any): string {
|
||
return `${tableRef?.xGrid?.getRowSeq(row)}` || '0';
|
||
}
|
||
|
||
function normalizeRowId(row: any): number {
|
||
const id = Number(row?.id ?? row?.Id);
|
||
return Number.isNaN(id) ? 0 : id;
|
||
}
|
||
|
||
function buildParamPathKey(pId: number, name: string): string {
|
||
return `${pId}:${String(name || '').trim().toLowerCase()}`;
|
||
}
|
||
|
||
type NormalizedParamRow = {
|
||
id: number;
|
||
pId: number;
|
||
name: string;
|
||
alias: string;
|
||
type: number;
|
||
isRequired: boolean;
|
||
cnName: string | null;
|
||
description: string | null;
|
||
sort: string;
|
||
};
|
||
|
||
function normalizeTreeParamRow(row: any): NormalizedParamRow {
|
||
return {
|
||
id: normalizeRowId(row),
|
||
pId: Number(row?.pId ?? row?.PId ?? 0),
|
||
name: String(row?.name ?? row?.Name ?? '').trim(),
|
||
alias: String(row?.alias ?? row?.Alias ?? '').trim(),
|
||
type: Number(row?.type ?? row?.Type ?? 1),
|
||
isRequired: !!(row?.isRequired ?? row?.IsRequired),
|
||
cnName: row?.cnName === '' ? null : (row?.cnName ?? row?.CnName ?? null),
|
||
description: row?.description ?? row?.Description ?? null,
|
||
sort: String(row?.sort ?? row?.Sort ?? ''),
|
||
};
|
||
}
|
||
|
||
function isSameTreeParamRow(row: any, baseline: NormalizedParamRow): boolean {
|
||
const current = normalizeTreeParamRow(row);
|
||
return (
|
||
current.name === baseline.name
|
||
&& current.pId === baseline.pId
|
||
&& current.type === baseline.type
|
||
&& current.isRequired === baseline.isRequired
|
||
&& (current.cnName ?? null) === (baseline.cnName ?? null)
|
||
&& (current.description ?? null) === (baseline.description ?? null)
|
||
);
|
||
}
|
||
|
||
export interface OriginalParamSnapshot {
|
||
ids: Set<number>;
|
||
pathToId: Map<string, number>;
|
||
rowById: Map<number, NormalizedParamRow>;
|
||
}
|
||
|
||
export interface OriginalParamIdSets {
|
||
query: OriginalParamSnapshot;
|
||
header: OriginalParamSnapshot;
|
||
bodyForm: OriginalParamSnapshot;
|
||
request: OriginalParamSnapshot;
|
||
mappingBody: OriginalParamSnapshot;
|
||
response: OriginalParamSnapshot;
|
||
}
|
||
|
||
export function createEmptyOriginalParamSnapshot(): OriginalParamSnapshot {
|
||
return {
|
||
ids: new Set<number>(),
|
||
pathToId: new Map<string, number>(),
|
||
rowById: new Map<number, NormalizedParamRow>(),
|
||
};
|
||
}
|
||
|
||
export function createEmptyOriginalParamIds(): OriginalParamIdSets {
|
||
const empty = createEmptyOriginalParamSnapshot();
|
||
return {
|
||
query: empty,
|
||
header: createEmptyOriginalParamSnapshot(),
|
||
bodyForm: createEmptyOriginalParamSnapshot(),
|
||
request: createEmptyOriginalParamSnapshot(),
|
||
mappingBody: createEmptyOriginalParamSnapshot(),
|
||
response: createEmptyOriginalParamSnapshot(),
|
||
};
|
||
}
|
||
|
||
/** 从加载时的参数行构建快照(id / 路径 / 原始字段) */
|
||
export function buildOriginalParamSnapshot(rows: any[]): OriginalParamSnapshot {
|
||
const snapshot = createEmptyOriginalParamSnapshot();
|
||
|
||
flattenTreeTableRows(rows || []).forEach((raw) => {
|
||
const row = normalizeTreeParamRow(raw);
|
||
if (row.id <= 0 || !row.name) return;
|
||
snapshot.ids.add(row.id);
|
||
snapshot.rowById.set(row.id, row);
|
||
snapshot.pathToId.set(buildParamPathKey(row.pId, row.name), row.id);
|
||
});
|
||
|
||
return snapshot;
|
||
}
|
||
|
||
function buildOriginalFormSnapshot(rows: any[]): OriginalParamSnapshot {
|
||
const snapshot = createEmptyOriginalParamSnapshot();
|
||
|
||
(rows || []).forEach((raw) => {
|
||
const rest = stripFormKvMeta(raw);
|
||
const id = normalizeRowId(rest);
|
||
const name = String(rest.name || rest.Name || '').trim();
|
||
if (id <= 0 || !name) return;
|
||
const row: NormalizedParamRow = {
|
||
id,
|
||
pId: 0,
|
||
name,
|
||
alias: String(rest.value || rest.Value || '').trim(),
|
||
type: 0,
|
||
isRequired: false,
|
||
cnName: null,
|
||
description: rest.description || rest.Description || null,
|
||
sort: '',
|
||
};
|
||
snapshot.ids.add(id);
|
||
snapshot.rowById.set(id, row);
|
||
snapshot.pathToId.set(buildParamPathKey(0, name), id);
|
||
});
|
||
|
||
return snapshot;
|
||
}
|
||
|
||
/** @deprecated 使用 buildOriginalParamSnapshot */
|
||
export function collectParamIds(rows: any[]): Set<number> {
|
||
return buildOriginalParamSnapshot(rows).ids;
|
||
}
|
||
|
||
export function buildOriginalParamIdsFromHydrated(source: {
|
||
queryParams?: any[];
|
||
headerParams?: any[];
|
||
bodyFormParams?: any[];
|
||
requestParams?: any[];
|
||
mappingBodyParams?: any[];
|
||
responseParams?: any[];
|
||
}): OriginalParamIdSets {
|
||
return {
|
||
query: buildOriginalFormSnapshot(source.queryParams || []),
|
||
header: buildOriginalFormSnapshot(source.headerParams || []),
|
||
bodyForm: buildOriginalFormSnapshot(source.bodyFormParams || []),
|
||
request: buildOriginalParamSnapshot(source.requestParams || []),
|
||
mappingBody: buildOriginalParamSnapshot(source.mappingBodyParams || []),
|
||
response: buildOriginalParamSnapshot(source.responseParams || []),
|
||
};
|
||
}
|
||
|
||
function mapTreeRow(item: any, optionType: number, tableRef: any, paraKind: number, serverId?: number) {
|
||
const id = serverId ?? normalizeRowId(item);
|
||
const pId = Number(item?.pId ?? item?.PId ?? 0);
|
||
const name = String(item?.name ?? item?.Name ?? '').trim();
|
||
return {
|
||
optionType,
|
||
id,
|
||
pId,
|
||
alias: optionType === 1 ? name : (item?.alias || item?.Alias || name || ''),
|
||
name,
|
||
cnName: item?.cnName === '' ? null : (item?.cnName ?? item?.CnName ?? null),
|
||
type: item?.type ?? item?.Type ?? 1,
|
||
isRequired: !!(item?.isRequired ?? item?.IsRequired),
|
||
sort: getRowSeq(item, tableRef),
|
||
description: item?.description ?? item?.Description ?? null,
|
||
paraKind,
|
||
};
|
||
}
|
||
|
||
function mapTreeChangeRows(
|
||
actionRows: { insertRecords?: any[]; removeRecords?: any[]; updateRecords?: any[] },
|
||
tableRef: any,
|
||
paraKind: number,
|
||
) {
|
||
const install = (actionRows.insertRecords || []).map((i: any) => {
|
||
if (!i.name) return null;
|
||
return mapTreeRow(i, 1, tableRef, paraKind);
|
||
}).filter(Boolean);
|
||
|
||
const remove: any[] = [];
|
||
(actionRows.removeRecords || []).forEach((i: any) => {
|
||
remove.push(mapTreeRow(i, 2, tableRef, paraKind));
|
||
if (i.children?.length) {
|
||
flattenTree(i.children).forEach((child: any) => {
|
||
remove.push(mapTreeRow(child, 2, tableRef, paraKind));
|
||
});
|
||
}
|
||
});
|
||
|
||
const update = (actionRows.updateRecords || []).map((i: any) => {
|
||
if (!i.name) return null;
|
||
return mapTreeRow(i, 3, tableRef, paraKind);
|
||
}).filter(Boolean);
|
||
|
||
return [...install, ...remove, ...update];
|
||
}
|
||
|
||
function mapTreeDeleteFromBaseline(
|
||
serverId: number,
|
||
baseline: NormalizedParamRow,
|
||
paraKind: number,
|
||
) {
|
||
return {
|
||
optionType: 2,
|
||
id: serverId,
|
||
pId: baseline.pId,
|
||
alias: baseline.alias || baseline.name,
|
||
name: baseline.name,
|
||
cnName: baseline.cnName,
|
||
type: baseline.type,
|
||
isRequired: baseline.isRequired,
|
||
sort: baseline.sort || '0',
|
||
description: baseline.description,
|
||
paraKind,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 编辑保存时对齐后端 MaintainParametersAsync:
|
||
* - 仅提交增/删/改增量
|
||
* - 未变更的已有参数不传(后端会自动补全 DB 节点)
|
||
* - 假 id + 已存在路径 → 按服务端 id 走修改,避免与 DB 补全节点重复
|
||
* - 解析全量导入会 reloadData(清空 removeRecords)且行 id 全是新假 id:走「删光快照 + 整树新增」
|
||
* - VXE removeRecords 丢失时,按「快照有、当前表无」补删(与 FormKV 一致)
|
||
*/
|
||
function buildTreeParameterList(
|
||
actionRows: { insertRecords?: any[]; removeRecords?: any[]; updateRecords?: any[] },
|
||
treeData: any[],
|
||
tableRef: any,
|
||
paraKind: number,
|
||
isEditMode: boolean,
|
||
original: OriginalParamSnapshot,
|
||
) {
|
||
const changes = mapTreeChangeRows(actionRows, tableRef, paraKind);
|
||
const changeById = new Map(changes.map((c) => [Number(c.id), c]));
|
||
const removedIds = new Set(
|
||
changes.filter((c) => c.optionType === 2).map((c) => Number(c.id)),
|
||
);
|
||
|
||
const flatCurrent = flattenTree(treeData).filter(
|
||
(item) => item?.name && String(item.name).trim() !== '',
|
||
);
|
||
|
||
if (!isEditMode) {
|
||
if (changes.length > 0) return changes;
|
||
return flatCurrent.map((item) => mapTreeRow(item, 1, tableRef, paraKind));
|
||
}
|
||
|
||
// 全量替换:当前行没有任何加载时的服务端 id(解析导入假 id / 或已清空整表)
|
||
const preservesAnyServerId = flatCurrent.some((item) =>
|
||
original.ids.has(normalizeRowId(item)),
|
||
);
|
||
if (original.ids.size > 0 && !preservesAnyServerId) {
|
||
const result: any[] = [];
|
||
original.ids.forEach((serverId) => {
|
||
const baseline = original.rowById.get(serverId);
|
||
if (!baseline?.name) return;
|
||
result.push(mapTreeDeleteFromBaseline(serverId, baseline, paraKind));
|
||
});
|
||
flatCurrent.forEach((item) => {
|
||
result.push(mapTreeRow(item, 1, tableRef, paraKind));
|
||
});
|
||
return result;
|
||
}
|
||
|
||
const result: any[] = [];
|
||
changes.filter((c) => c.optionType === 2).forEach((c) => result.push(c));
|
||
const presentServerIds = new Set<number>();
|
||
|
||
flatCurrent.forEach((item) => {
|
||
const id = normalizeRowId(item);
|
||
const pId = Number(item?.pId ?? item?.PId ?? 0);
|
||
const name = String(item?.name ?? item?.Name ?? '').trim();
|
||
const pathKey = buildParamPathKey(pId, name);
|
||
|
||
if (removedIds.has(id)) return;
|
||
|
||
const tracked = changeById.get(id);
|
||
if (tracked && tracked.optionType !== 2) {
|
||
result.push(tracked);
|
||
const trackedServerId = Number(tracked.id);
|
||
if (trackedServerId > 0 && original.ids.has(trackedServerId)) {
|
||
presentServerIds.add(trackedServerId);
|
||
}
|
||
return;
|
||
}
|
||
|
||
const serverIdByPath = original.pathToId.get(pathKey);
|
||
const serverId = original.ids.has(id) ? id : serverIdByPath;
|
||
const baseline = serverId ? original.rowById.get(serverId) : undefined;
|
||
|
||
if (serverId && baseline) {
|
||
presentServerIds.add(serverId);
|
||
if (!isSameTreeParamRow(item, baseline)) {
|
||
result.push(mapTreeRow(item, 3, tableRef, paraKind, serverId));
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (!serverId) {
|
||
result.push(mapTreeRow(item, 1, tableRef, paraKind));
|
||
}
|
||
});
|
||
|
||
// VXE removeRecords 丢失时,仍按「原始快照有、当前表无」补删,避免库里残留
|
||
original.ids.forEach((serverId) => {
|
||
if (removedIds.has(serverId) || presentServerIds.has(serverId)) return;
|
||
const baseline = original.rowById.get(serverId);
|
||
if (!baseline?.name) return;
|
||
result.push(mapTreeDeleteFromBaseline(serverId, baseline, paraKind));
|
||
});
|
||
|
||
return result;
|
||
}
|
||
|
||
/** 客户端校验:同一 pId 下参数名不可重复(对齐后端 ValidateParameter,跳过删除项) */
|
||
export function validateTreeParameterPaths(rows: any[]): string | null {
|
||
const seen = new Set<string>();
|
||
for (const item of flattenTree(rows)) {
|
||
// 后端 ValidateParameter 会先排除 OptionType.删除;全量替换时「删旧 + 增同名」不能当重复
|
||
const optionType = Number(item?.optionType ?? item?.OptionType ?? 0);
|
||
if (optionType === 2) continue;
|
||
|
||
const name = String(item?.name ?? item?.Name ?? '').trim();
|
||
if (!name) continue;
|
||
const pId = Number(item?.pId ?? item?.PId ?? 0);
|
||
const key = buildParamPathKey(pId, name);
|
||
if (seen.has(key)) {
|
||
return `同一层级下不能存在重复参数名「${name}」(父节点 id=${pId || 0})`;
|
||
}
|
||
seen.add(key);
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/** 编辑模式下无增量变更时不传该列表,避免空数组覆盖后端已有参数 */
|
||
function assignParameterList(
|
||
payload: Record<string, any>,
|
||
key: string,
|
||
list: any[],
|
||
isEditMode: boolean,
|
||
hadOriginalItems: boolean,
|
||
) {
|
||
if (isEditMode && list.length === 0 && hadOriginalItems) {
|
||
delete payload[key];
|
||
return;
|
||
}
|
||
payload[key] = list;
|
||
}
|
||
|
||
function mapFormKvRow(row: any, optionType: number, fieldPosition: number, serverId?: number) {
|
||
return {
|
||
id: serverId ?? normalizeRowId(row),
|
||
name: row.name || row.Name || '',
|
||
value: row.value || row.Value || '',
|
||
description: row.description || row.Description || '',
|
||
optionType,
|
||
fieldPosition,
|
||
};
|
||
}
|
||
|
||
function stripFormKvMeta(item: any) {
|
||
const { _X_ROW_CHILD, _X_ROW_KEY, children, Id, ...rest } = item || {};
|
||
return rest;
|
||
}
|
||
|
||
function isSameFormKvRow(row: any, baseline: NormalizedParamRow): boolean {
|
||
const rest = stripFormKvMeta(row);
|
||
return (
|
||
String(rest.name || rest.Name || '').trim() === baseline.name
|
||
&& String(rest.value || rest.Value || '').trim() === String(baseline.alias || '')
|
||
&& String(rest.description || rest.Description || '').trim() === String(baseline.description || '')
|
||
);
|
||
}
|
||
|
||
function isPersistableFormKvRow(p: any) {
|
||
// 删除只依赖 id;参数值允许留空(签名类由前置脚本写入)
|
||
if (Number(p?.optionType) === 2) {
|
||
return Number(p?.id) > 0;
|
||
}
|
||
return !!(p.name && String(p.name).trim() !== '');
|
||
}
|
||
|
||
function buildFormKvParameterList(
|
||
changes: any[],
|
||
tableData: any[],
|
||
fieldPosition: number,
|
||
isEditMode: boolean,
|
||
original: OriginalParamSnapshot,
|
||
) {
|
||
const changeById = new Map(changes.map((c) => [Number(c.id), c]));
|
||
const removedIds = new Set(
|
||
changes.filter((c) => c.optionType === 2).map((c) => Number(c.id)),
|
||
);
|
||
|
||
if (!isEditMode) {
|
||
if (changes.length) return changes.filter(isPersistableFormKvRow);
|
||
return (tableData || [])
|
||
.map((item) => mapFormKvRow(stripFormKvMeta(item), 1, fieldPosition))
|
||
.filter(isPersistableFormKvRow);
|
||
}
|
||
|
||
const result: any[] = [];
|
||
changes.filter((c) => c.optionType === 2).forEach((c) => result.push(c));
|
||
|
||
const presentServerIds = new Set<number>();
|
||
|
||
(tableData || []).forEach((item) => {
|
||
const rest = stripFormKvMeta(item);
|
||
const id = normalizeRowId(rest);
|
||
const name = String(rest.name || rest.Name || '').trim();
|
||
const pathKey = buildParamPathKey(0, name);
|
||
|
||
if (removedIds.has(id)) return;
|
||
|
||
const tracked = changeById.get(id);
|
||
if (tracked && tracked.optionType !== 2) {
|
||
result.push(tracked);
|
||
const trackedServerId = Number(tracked.id);
|
||
if (trackedServerId > 0) presentServerIds.add(trackedServerId);
|
||
return;
|
||
}
|
||
|
||
const serverIdByPath = original.pathToId.get(pathKey);
|
||
const serverId = original.ids.has(id) ? id : serverIdByPath;
|
||
const baseline = serverId ? original.rowById.get(serverId) : undefined;
|
||
|
||
if (serverId && baseline) {
|
||
presentServerIds.add(serverId);
|
||
if (!isSameFormKvRow(rest, baseline)) {
|
||
result.push(mapFormKvRow(rest, 3, fieldPosition, serverId));
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (!serverId) {
|
||
result.push(mapFormKvRow(rest, 1, fieldPosition));
|
||
}
|
||
});
|
||
|
||
// VXE removeRecords 丢失时,仍按「原始快照有、当前表无」补删,避免库里残留
|
||
original.ids.forEach((serverId) => {
|
||
if (removedIds.has(serverId) || presentServerIds.has(serverId)) return;
|
||
const baseline = original.rowById.get(serverId);
|
||
if (!baseline?.name) return;
|
||
result.push({
|
||
id: serverId,
|
||
name: baseline.name,
|
||
value: baseline.alias || '',
|
||
description: baseline.description || '',
|
||
optionType: 2,
|
||
fieldPosition,
|
||
});
|
||
});
|
||
|
||
return result.filter(isPersistableFormKvRow);
|
||
}
|
||
|
||
function mapFormKvChanges(
|
||
actionRows: { insertRecords?: any[]; removeRecords?: any[]; updateRecords?: any[] },
|
||
fieldPosition: number,
|
||
) {
|
||
const mapRow = (row: any, optionType: number) => ({
|
||
id: normalizeRowId(row),
|
||
name: row.name || '',
|
||
value: row.value || '',
|
||
description: row.description || '',
|
||
optionType,
|
||
fieldPosition,
|
||
});
|
||
|
||
const list = [
|
||
...(actionRows.insertRecords || []).map((r) => mapRow(r, 1)),
|
||
...(actionRows.removeRecords || []).map((r) => mapRow(r, 2)),
|
||
...(actionRows.updateRecords || []).map((r) => mapRow(r, 3)),
|
||
];
|
||
|
||
return list.filter(isPersistableFormKvRow);
|
||
}
|
||
|
||
export interface InterfaceDetailSnapshot {
|
||
queryParams: any[];
|
||
headerParams: any[];
|
||
bodyFormParams: any[];
|
||
requestParams: any[];
|
||
mappingBodyParams: any[];
|
||
responseParams: any[];
|
||
queryActionRows: { insertRecords: any[]; removeRecords: any[]; updateRecords: any[] };
|
||
headerActionRows: { insertRecords: any[]; removeRecords: any[]; updateRecords: any[] };
|
||
bodyFormActionRows: { insertRecords: any[]; removeRecords: any[]; updateRecords: any[] };
|
||
requestActionRows: { insertRecords: any[]; removeRecords: any[]; updateRecords: any[] };
|
||
mappingBodyActionRows: { insertRecords: any[]; removeRecords: any[]; updateRecords: any[] };
|
||
responseActionRows: { insertRecords: any[]; removeRecords: any[]; updateRecords: any[] };
|
||
queryTableRef: any;
|
||
headerTableRef: any;
|
||
bodyFormTableRef: any;
|
||
requestTableRef: any;
|
||
mappingBodyTableRef: any;
|
||
responseTableRef: any;
|
||
requestExample: string;
|
||
originalParamIds?: OriginalParamIdSets;
|
||
}
|
||
|
||
export interface BuildSavePayloadOptions {
|
||
formData: Record<string, any>;
|
||
mode: InterfaceParameterMode;
|
||
isEditMode: boolean;
|
||
snapshot: InterfaceDetailSnapshot;
|
||
extraFields?: Record<string, any>;
|
||
}
|
||
|
||
function snapshotSize(snapshot: OriginalParamSnapshot): boolean {
|
||
return snapshot.ids.size > 0;
|
||
}
|
||
|
||
export function buildInterfaceSavePayload(options: BuildSavePayloadOptions) {
|
||
const { formData, mode, isEditMode, snapshot, extraFields = {} } = options;
|
||
const contentType = formData.contentType as number | undefined;
|
||
const original = snapshot.originalParamIds ?? createEmptyOriginalParamIds();
|
||
|
||
const queryChanges = mapFormKvChanges(snapshot.queryActionRows, FieldPosition.Query);
|
||
const headerChanges = mapFormKvChanges(snapshot.headerActionRows, FieldPosition.Header);
|
||
const bodyFormChanges = mapFormKvChanges(snapshot.bodyFormActionRows, FieldPosition.Body);
|
||
|
||
const queryFormParameterList = buildFormKvParameterList(
|
||
queryChanges,
|
||
snapshot.queryParams,
|
||
FieldPosition.Query,
|
||
isEditMode,
|
||
original.query,
|
||
);
|
||
const headerFormParameterList = buildFormKvParameterList(
|
||
headerChanges,
|
||
snapshot.headerParams,
|
||
FieldPosition.Header,
|
||
isEditMode,
|
||
original.header,
|
||
);
|
||
const bodyFormParameterList = buildFormKvParameterList(
|
||
bodyFormChanges,
|
||
snapshot.bodyFormParams,
|
||
FieldPosition.Body,
|
||
isEditMode,
|
||
original.bodyForm,
|
||
);
|
||
|
||
const outParameterList = buildTreeParameterList(
|
||
snapshot.responseActionRows,
|
||
snapshot.responseParams,
|
||
snapshot.responseTableRef,
|
||
ParaKind.Normal,
|
||
isEditMode,
|
||
original.response,
|
||
).filter((p) => p.name && String(p.name).trim() !== '');
|
||
|
||
const emptyLists = {
|
||
inParameterList: [] as any[],
|
||
requestBodyParameterList: [] as any[],
|
||
mappingBodyParameterList: [] as any[],
|
||
bodyFormParameterList: [] as any[],
|
||
};
|
||
|
||
let payload: Record<string, any> = {
|
||
...formData,
|
||
...extraFields,
|
||
inParams: snapshot.requestExample,
|
||
...emptyLists,
|
||
};
|
||
|
||
assignParameterList(payload, 'queryFormParameterList', queryFormParameterList, isEditMode, snapshotSize(original.query));
|
||
assignParameterList(payload, 'headerFormParameterList', headerFormParameterList, isEditMode, snapshotSize(original.header));
|
||
assignParameterList(payload, 'outParameterList', outParameterList, isEditMode, snapshotSize(original.response));
|
||
|
||
if (mode === 'upstream') {
|
||
const inParameterList = buildTreeParameterList(
|
||
snapshot.requestActionRows,
|
||
snapshot.requestParams,
|
||
snapshot.requestTableRef,
|
||
ParaKind.Normal,
|
||
isEditMode,
|
||
original.request,
|
||
).filter((p) => p.name && String(p.name).trim() !== '');
|
||
assignParameterList(payload, 'inParameterList', inParameterList, isEditMode, snapshotSize(original.request));
|
||
} else if (isFormUrlEncodedContentType(contentType)) {
|
||
assignParameterList(payload, 'bodyFormParameterList', bodyFormParameterList, isEditMode, snapshotSize(original.bodyForm));
|
||
const mappingBodyParameterList = buildTreeParameterList(
|
||
snapshot.mappingBodyActionRows,
|
||
snapshot.mappingBodyParams,
|
||
snapshot.mappingBodyTableRef,
|
||
ParaKind.MappingBody,
|
||
isEditMode,
|
||
original.mappingBody,
|
||
).filter((p) => p.name && String(p.name).trim() !== '');
|
||
assignParameterList(payload, 'mappingBodyParameterList', mappingBodyParameterList, isEditMode, snapshotSize(original.mappingBody));
|
||
} else if (isNoneContentType(contentType)) {
|
||
const mappingBodyParameterList = buildTreeParameterList(
|
||
snapshot.mappingBodyActionRows,
|
||
snapshot.mappingBodyParams,
|
||
snapshot.mappingBodyTableRef,
|
||
ParaKind.MappingBody,
|
||
isEditMode,
|
||
original.mappingBody,
|
||
).filter((p) => p.name && String(p.name).trim() !== '');
|
||
assignParameterList(payload, 'mappingBodyParameterList', mappingBodyParameterList, isEditMode, snapshotSize(original.mappingBody));
|
||
} else if (isJsonOrXmlContentType(contentType)) {
|
||
const requestBodyParameterList = buildTreeParameterList(
|
||
snapshot.requestActionRows,
|
||
snapshot.requestParams,
|
||
snapshot.requestTableRef,
|
||
ParaKind.Normal,
|
||
isEditMode,
|
||
original.request,
|
||
).filter((p) => p.name && String(p.name).trim() !== '');
|
||
assignParameterList(payload, 'requestBodyParameterList', requestBodyParameterList, isEditMode, snapshotSize(original.request));
|
||
}
|
||
|
||
if (isEditMode) {
|
||
payload.optionType = 3;
|
||
} else {
|
||
delete payload.id;
|
||
payload.optionType = 1;
|
||
}
|
||
|
||
return payload;
|
||
}
|
||
|
||
export function validateInterfaceSavePayload(
|
||
payload: Record<string, any>,
|
||
mode: InterfaceParameterMode,
|
||
): string | null {
|
||
const hasItems = (list?: any[]) => Array.isArray(list) && list.length > 0;
|
||
const mustEmpty = (list?: any[]) => !list || list.length === 0;
|
||
const isEdit = payload.optionType === 3;
|
||
|
||
const treeLists: any[][] = [];
|
||
if (mode === 'upstream') treeLists.push(payload.inParameterList || []);
|
||
else if (isFormUrlEncodedContentType(payload.contentType)) {
|
||
treeLists.push(payload.mappingBodyParameterList || []);
|
||
} else if (isNoneContentType(payload.contentType)) {
|
||
treeLists.push(payload.mappingBodyParameterList || []);
|
||
} else if (isJsonOrXmlContentType(payload.contentType)) {
|
||
treeLists.push(payload.requestBodyParameterList || []);
|
||
}
|
||
treeLists.push(payload.outParameterList || []);
|
||
|
||
for (const list of treeLists) {
|
||
const pathError = validateTreeParameterPaths(list);
|
||
if (pathError) return pathError;
|
||
}
|
||
|
||
if (mode === 'upstream') {
|
||
if (!isEdit && !hasItems(payload.inParameterList)) return '上游入参(In Parameters)不能为空';
|
||
if (!mustEmpty(payload.requestBodyParameterList)) return '上游不应包含 requestBodyParameterList';
|
||
if (!mustEmpty(payload.mappingBodyParameterList)) return '上游不应包含 mappingBodyParameterList';
|
||
if (!mustEmpty(payload.bodyFormParameterList)) return '上游不应包含 bodyFormParameterList';
|
||
return null;
|
||
}
|
||
|
||
const ct = payload.contentType;
|
||
if (isNoneContentType(ct)) {
|
||
if (!mustEmpty(payload.requestBodyParameterList)) return 'none 不应包含 requestBodyParameterList';
|
||
if (!mustEmpty(payload.bodyFormParameterList)) return 'none 不应包含 bodyFormParameterList';
|
||
if (!mustEmpty(payload.inParameterList)) return 'none 不应包含 inParameterList';
|
||
return null;
|
||
}
|
||
|
||
if (isFormUrlEncodedContentType(ct)) {
|
||
if (!isEdit && !hasItems(payload.bodyFormParameterList)) return 'FormUrlEncoded 须配置 Body 表单字段';
|
||
if (!isEdit && !hasItems(payload.mappingBodyParameterList)) return 'FormUrlEncoded 须配置 Mapping Body';
|
||
if (!mustEmpty(payload.inParameterList)) return '下游 FormUrlEncoded 不应包含 inParameterList';
|
||
if (!mustEmpty(payload.requestBodyParameterList)) return '下游 FormUrlEncoded 不应包含 requestBodyParameterList';
|
||
return null;
|
||
}
|
||
|
||
if (isJsonOrXmlContentType(ct)) {
|
||
if (!isEdit && !hasItems(payload.requestBodyParameterList)) return 'Json/Xml 下游须配置 Body 参数树';
|
||
if (!mustEmpty(payload.inParameterList)) return '下游 Json/Xml 不应包含 inParameterList';
|
||
if (!mustEmpty(payload.mappingBodyParameterList)) return '下游 Json/Xml 不应包含 mappingBodyParameterList';
|
||
if (!mustEmpty(payload.bodyFormParameterList)) return '下游 Json/Xml 不应包含 bodyFormParameterList';
|
||
return null;
|
||
}
|
||
|
||
return '不支持的 ContentType';
|
||
}
|
||
|
||
/** 映射页:下游目标参数树来源 */
|
||
export function resolveDownstreamMappingTargetList(interfaceData: Record<string, any>): any[] {
|
||
const ct = interfaceData.contentType;
|
||
if (isNoneContentType(ct)) {
|
||
return interfaceData.mappingBodyParameterList ?? [];
|
||
}
|
||
if (isFormUrlEncodedContentType(ct)) {
|
||
return interfaceData.mappingBodyParameterList ?? [];
|
||
}
|
||
if (isJsonOrXmlContentType(ct)) {
|
||
return interfaceData.requestBodyParameterList ?? interfaceData.inParameterList ?? [];
|
||
}
|
||
return interfaceData.requestBodyParameterList ?? interfaceData.inParameterList ?? [];
|
||
}
|