完善报价单填表与币种回填,并补齐账号末次登录、角色按钮权限与陆运全线匹配。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
linmianxun
2026-09-19 22:03:45 +08:00
co-authored by Cursor
parent 1a2e1d6cb2
commit e4743541f7
25 changed files with 2262 additions and 163 deletions
@@ -0,0 +1,178 @@
/**
* 运营后台数据权限 / 员工负责线路 单一目录。
*
* 来源:《询价智能体运营后台数据权限下拉选项.xlsx》(2026-09)。
* key = 写入 sys_role.description / inq_staff_role.routes 的稳定码;
* title = 界面展示文案。
*/
export type DataPermNode = {
key: string;
title: string;
children?: DataPermNode[];
};
/** 空运具体线路(不含全线) */
export const AIR_ROUTE_ITEMS: { key: string; title: string }[] = [
{ key: "CGO-KUL", title: "郑州-吉隆坡(CGO-KUL)" },
{ key: "SZX-KUL", title: "深圳-吉隆坡(SZX-KUL)" },
{ key: "HKG-KUL", title: "香港-吉隆坡(HKG-KUL)" },
{ key: "NKG-KUL", title: "南京-吉隆坡(NKG-KUL)" },
{ key: "HKG-DEL", title: "香港-新德里(HKG-DEL)" },
{ key: "CGO-DEL", title: "郑州-新德里(CGO-DEL)" },
{ key: "CGO-BLR", title: "郑州-班加罗尔(CGO-BLR)" },
{ key: "CGO-MAA", title: "郑州-金奈(CGO-MAA)" },
{ key: "CTU-MAA", title: "成都-金奈(CTU-MAA)" },
{ key: "CKG-BOM", title: "重庆-孟买(CKG-BOM)" },
{ key: "ZUH-CRK", title: "珠海-克拉克(ZUH-CRK)" },
{ key: "SZX-CRK", title: "深圳-克拉克(SZX-CRK)" },
{ key: "EHU-ORD", title: "鄂州-芝加哥(EHU-ORD)" },
{ key: "WNZ-CGK", title: "温州-雅加达(WNZ-CGK)" },
{ key: "国内-曼谷(BKK 外采)", title: "国内-曼谷(BKK 外采)" },
{ key: "国外-国外(外采-空运)", title: "国外-国外(外采-空运)" },
{ key: "其他线路和快递双清包税(外采-空运)", title: "其他线路和快递双清包税(外采-空运)" },
];
export const LAND_ROUTE_ITEMS: { key: string; title: string }[] = [
{ key: "中港/中亚/中欧", title: "中港/中亚/中欧" },
{ key: "国内长途/零担", title: "国内长途/零担" },
{ key: "国内城配/拖车/打包", title: "国内城配/拖车/打包" },
{ key: "东南亚", title: "东南亚" },
];
/** 角色弹窗数据权限树(父节点仅作分组,不写入 description;文案对齐旧后台「空运数据」) */
export const DATA_PERM_TREE: DataPermNode[] = [
{
key: "group:air",
title: "空运数据",
children: [{ key: "空运全线", title: "空运全线" }, ...AIR_ROUTE_ITEMS],
},
{
key: "group:sea",
title: "海运数据",
children: [{ key: "海运全线", title: "海运全线" }],
},
{
key: "group:land",
title: "陆运数据",
children: [{ key: "陆运全线", title: "陆运全线" }, ...LAND_ROUTE_ITEMS],
},
];
/** 旧后台独立勾选项:系统异常消息通知(企微) */
export const SYSTEM_EXCEPTION_NOTIFY_KEY = "系统异常·消息通知";
export const SYSTEM_EXCEPTION_NOTIFY_TITLE = "系统异常消息通知(企微)";
/** 员工岗位 → 可选负责线路 key(与角色数据权限同源) */
export const ROUTES_BY_ROLE: Record<"air" | "sea" | "land", string[]> = {
air: ["空运全线", ...AIR_ROUTE_ITEMS.map((x) => x.key)],
sea: ["海运全线"],
land: ["陆运全线", ...LAND_ROUTE_ITEMS.map((x) => x.key)],
};
const TITLE_BY_KEY: Record<string, string> = (() => {
const m: Record<string, string> = {
空运全线: "空运全线",
海运全线: "海运全线",
陆运全线: "陆运全线",
[SYSTEM_EXCEPTION_NOTIFY_KEY]: SYSTEM_EXCEPTION_NOTIFY_TITLE,
};
for (const x of AIR_ROUTE_ITEMS) m[x.key] = x.title;
for (const x of LAND_ROUTE_ITEMS) m[x.key] = x.title;
return m;
})();
/** 已知稳定码集合(不含分组父节点) */
export const KNOWN_DATA_PERM_KEYS = new Set(Object.keys(TITLE_BY_KEY));
/**
* 旧写法 → 新稳定码。
* 无法识别时返回 trim 后原文(可能仍展示,但不进勾选树)。
*/
const ALIAS_TO_KEY: Record<string, string> = {
空运全线: "空运全线",
空运全部线路: "空运全线",
"空运·全部线路": "空运全线",
"空运·全线": "空运全线",
全线: "空运全线",
海运全线: "海运全线",
"海运·全部线路": "海运全线",
"海运·海运全线": "海运全线",
陆运全线: "陆运全线",
"陆运·全部线路": "陆运全线",
"陆运·中港/中亚/中欧": "中港/中亚/中欧",
"陆运·国内长途/零担": "国内长途/零担",
"陆运·国内城配/拖车/打包": "国内城配/拖车/打包",
"陆运·东南亚": "东南亚",
BKK: "国内-曼谷(BKK 外采)",
"外采-空运": "国外-国外(外采-空运)",
[SYSTEM_EXCEPTION_NOTIFY_KEY]: SYSTEM_EXCEPTION_NOTIFY_KEY,
系统异常消息通知: SYSTEM_EXCEPTION_NOTIFY_KEY,
"系统异常消息通知(企微)": SYSTEM_EXCEPTION_NOTIFY_KEY,
};
for (const item of AIR_ROUTE_ITEMS) {
ALIAS_TO_KEY[item.key] = item.key;
ALIAS_TO_KEY[item.title] = item.key;
ALIAS_TO_KEY[`空运·${item.key}`] = item.key;
}
for (const item of LAND_ROUTE_ITEMS) {
ALIAS_TO_KEY[item.key] = item.key;
ALIAS_TO_KEY[`陆运·${item.key}`] = item.key;
}
export function normalizeDataPermKey(raw: string): string {
const s = String(raw || "").trim();
if (!s) return "";
if (ALIAS_TO_KEY[s]) return ALIAS_TO_KEY[s];
// 空运·XXX / 陆运·XXX 兜底剥前缀
const m = s.match(/^(?:空运|海运|陆运)[·•](.+)$/);
if (m) {
const inner = m[1].trim();
if (ALIAS_TO_KEY[inner]) return ALIAS_TO_KEY[inner];
if (inner === "全部线路" || inner === "全线") {
if (s.startsWith("空运")) return "空运全线";
if (s.startsWith("海运")) return "海运全线";
if (s.startsWith("陆运")) return "陆运全线";
}
if (KNOWN_DATA_PERM_KEYS.has(inner)) return inner;
}
return s;
}
/** 批量归一;去重保序;只保留已知码(角色勾选回显用) */
export function normalizeDataPermKeys(rawList: string[]): string[] {
const out: string[] = [];
const seen = new Set<string>();
for (const raw of rawList || []) {
const k = normalizeDataPermKey(raw);
if (!k || !KNOWN_DATA_PERM_KEYS.has(k) || seen.has(k)) continue;
seen.add(k);
out.push(k);
}
return out;
}
/** 员工 routes:归一后保留已知码;未知旧值也保留以免丢数据 */
export function normalizeStaffRouteKeys(rawList: string[]): string[] {
const out: string[] = [];
const seen = new Set<string>();
for (const raw of rawList || []) {
const k = normalizeDataPermKey(raw);
if (!k || seen.has(k)) continue;
seen.add(k);
out.push(k);
}
return out;
}
export function dataPermTitle(key: string): string {
const k = normalizeDataPermKey(key);
return TITLE_BY_KEY[k] || key;
}
/** Ant Design Tree 用:过滤掉分组父 key,只交叶子 */
export function leafDataPermKeys(checked: string[]): string[] {
return (checked || []).filter((k) => KNOWN_DATA_PERM_KEYS.has(k));
}