补齐超时响应唯一 timeoutCode 与智能体按码查询,并增强报价模板预览与解析落库。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
linmianxun
2026-09-18 22:25:04 +08:00
co-authored by Cursor
parent 19f94252f0
commit 6cd6803147
15 changed files with 706 additions and 35 deletions
@@ -1,6 +1,6 @@
/**
* 报价模板预览:Excel 样式网格(上传原文),辅以映射摘要。
* 主数据来自 /previewSheet;mappingJson 仅用于未映射必填提示。
* 主数据来自 /previewSheet;mappingJson 用于已映射/未映射清单。
*/
/** 系统字段 code → 中文标签 */
@@ -27,8 +27,131 @@ export const FIELD_LABELS: Record<string, string> = {
weight_kg: "重量",
volume_cbm: "体积",
total_amount: "报价合计",
chargeable_weight_kg: "计费重",
delivery_address: "送货地址",
pickup_address: "提货地址",
transit_time: "运输时效",
cargo_value: "货值",
hs_code: "HS编码",
oil_elec_mag: "油电磁",
has_battery: "是否带电",
on_board_date: "开船日",
special_remarks: "特殊备注",
eta_request: "要求到港",
sailing_schedule: "船期",
shipping_line: "船公司",
flight_no: "航班号",
transfer_airport: "中转机场",
effective_date: "生效日期",
fuel_surcharge_rate: "燃油附加费率",
tax_rate: "税率",
has_oil: "是否含油",
has_magnet: "是否含磁",
vehicle_qty: "车型/数量",
};
/** 解析结果摘要:供预览弹窗展示「已解析 / 未解析」 */
export type MappingFieldItem = {
code: string;
label: string;
cell: string;
};
export type MappingRegionItem = {
kind: "fee" | "route";
id: string;
title: string;
};
export type MappingParseSummary = {
fields: MappingFieldItem[];
regions: MappingRegionItem[];
gaps: string[];
unmappedRequired: string[];
unmappedLabels: string[];
pointCount: number;
};
function asMappingObject(mappingJson: unknown): Record<string, any> | null {
if (!mappingJson) return null;
if (typeof mappingJson === "object") return mappingJson as Record<string, any>;
if (typeof mappingJson === "string") {
try {
const obj = JSON.parse(mappingJson);
return obj && typeof obj === "object" ? obj : null;
} catch {
return null;
}
}
return null;
}
/**
* 从 mappingJson 拆出运营可读的已映射 / 未映射清单。
* 不打接口;mappingGaps 为空时(旧版解析)仍可看 fields 与 unmapped*。
*/
export function summarizeMappingParse(mappingJson: unknown): MappingParseSummary {
const obj = asMappingObject(mappingJson);
const empty: MappingParseSummary = {
fields: [],
regions: [],
gaps: [],
unmappedRequired: [],
unmappedLabels: [],
pointCount: 0,
};
if (!obj) return empty;
const fieldsObj = obj.fields && typeof obj.fields === "object" ? obj.fields : {};
const fields: MappingFieldItem[] = Object.keys(fieldsObj)
.filter((k) => fieldsObj[k])
.map((code) => ({
code,
label: FIELD_LABELS[code] || code,
cell: String(fieldsObj[code] || ""),
}))
.sort((a, b) => a.label.localeCompare(b.label, "zh"));
const regions: MappingRegionItem[] = [];
const feeRegions = Array.isArray(obj.feeRegions) ? obj.feeRegions : [];
for (const r of feeRegions) {
if (!r || typeof r !== "object") continue;
regions.push({
kind: "fee",
id: String(r.id || ""),
title: String(r.title || r.id || "费用区"),
});
}
const routeRegions = Array.isArray(obj.routeRegions) ? obj.routeRegions : [];
for (const r of routeRegions) {
if (!r || typeof r !== "object") continue;
regions.push({
kind: "route",
id: String(r.id || ""),
title: String(r.title || r.id || "线路区"),
});
}
const gaps = Array.isArray(obj.mappingGaps)
? obj.mappingGaps.map((x: any) => String(x)).filter(Boolean)
: [];
const unmappedRequired = Array.isArray(obj.unmappedRequired)
? obj.unmappedRequired.map((x: any) => String(x)).filter(Boolean)
: [];
const unmappedLabels = Array.isArray(obj.unmappedLabels)
? obj.unmappedLabels.map((x: any) => String(x)).filter(Boolean)
: [];
return {
fields,
regions,
gaps,
unmappedRequired,
unmappedLabels,
pointCount: fields.length + regions.length,
};
}
export type SheetPreviewCell = {
addr: string;
text: string;