Merge remote-tracking branch 'origin/master' into feature/bfi-api-rate
# Conflicts: # src/views/payment/receipt-flow.vue
This commit is contained in:
+123
-19
@@ -1,29 +1,133 @@
|
||||
/**
|
||||
* 路线变更记录内容格式化
|
||||
* 路线变更记录内容解析
|
||||
*
|
||||
* 存储格式(单行):
|
||||
* YD-20260904-0022:发货地 A → B;到货地 C → D;YD-20260904-0025:发货地 E → F;到货地 G → H
|
||||
* 路线顺序:A → B → C 调整为 B → A → C
|
||||
*
|
||||
* 展示格式(变更前 / 变更后分行):
|
||||
* YD-20260904-0022:发货地 A →
|
||||
* B
|
||||
* 到货地 C →
|
||||
* D
|
||||
* 解析结果(分组,供变更记录列表按「分组标题 - 字段 - 变更前/后」渲染):
|
||||
* [
|
||||
* {
|
||||
* title: 'YD-20260904-0022',
|
||||
* text: '',
|
||||
* fields: [{ label: '发货地', before: 'A', after: 'B', single: 'B', changed: true, unchanged: false }],
|
||||
* },
|
||||
* { title: '', text: '无法识别的原文', fields: [] },
|
||||
* ]
|
||||
*
|
||||
* 说明:仅用于展示层,不改变已存储的 content 文本(历史数据同样适用)。
|
||||
*/
|
||||
export const formatRouteChangeContent = (content, emptyText = '-') => {
|
||||
if (content === undefined || content === null) return emptyText;
|
||||
const text = String(content).trim();
|
||||
if (!text) return emptyText;
|
||||
// 长标签必须排在短标签前(「发货地址」优先于「发货地」),否则会被前缀吃掉
|
||||
const FIELD_LABELS = ['发货地址', '到货地址', '发货地', '到货地', '装货地', '卸货地', '收货地'];
|
||||
const FIELD_LABEL_PATTERN = new RegExp(`^(${FIELD_LABELS.join('|')})\\s*([\\s\\S]*)$`);
|
||||
// 变更前 / 变更后分隔符:→、➔、➜、->、-->、–>、—>
|
||||
const ARROW_PATTERN = /\s*(?:→|➔|➜|[\-–—]{1,2}\s*>)\s*/;
|
||||
// 运单地址之外的变更描述,例如「A → B → C 调整为 B → A → C」
|
||||
const ADJUST_PATTERN = /^([\s\S]*?)\s调整为\s*([\s\S]*)$/;
|
||||
const EMPTY_TEXT = ['', '-', '--', '—', 'null', 'undefined'];
|
||||
// 冒号前内容超过该长度时不视为分组标题(运单号通常长于该值)
|
||||
const TITLE_MAX_LENGTH = 12;
|
||||
|
||||
return text
|
||||
.replace(/\r\n?/g, '\n')
|
||||
// 多条记录之间换行
|
||||
.replace(/[ \t]*[;;][ \t]*/g, '\n')
|
||||
// 变更前地址 / 变更后地址分行(箭头保留在上一行行尾)
|
||||
.replace(/[ \t]*[-—]+>[ \t]*|[ \t]*→[ \t]*/g, ' →\n')
|
||||
.replace(/\n{2,}/g, '\n')
|
||||
.replace(/[ \t]+\n/g, '\n')
|
||||
.trim();
|
||||
const normalizeValue = value => {
|
||||
const text = String(value === undefined || value === null ? '' : value).trim();
|
||||
return EMPTY_TEXT.includes(text.toLowerCase()) ? '-' : text;
|
||||
};
|
||||
|
||||
const buildField = (label, rawValue) => {
|
||||
const value = String(rawValue === undefined || rawValue === null ? '' : rawValue).trim();
|
||||
const adjustMatched = value.match(ADJUST_PATTERN);
|
||||
let before = value;
|
||||
let after = '';
|
||||
let hasPair = false;
|
||||
|
||||
if (adjustMatched) {
|
||||
before = adjustMatched[1];
|
||||
after = adjustMatched[2];
|
||||
hasPair = true;
|
||||
} else {
|
||||
const parts = value.split(ARROW_PATTERN);
|
||||
if (parts.length > 1) {
|
||||
before = parts[0];
|
||||
after = parts[1];
|
||||
hasPair = true;
|
||||
}
|
||||
}
|
||||
|
||||
const beforeText = normalizeValue(before);
|
||||
const afterText = normalizeValue(after);
|
||||
|
||||
return {
|
||||
label,
|
||||
before: beforeText,
|
||||
after: afterText,
|
||||
// 单值展示(未拆分出前后值时使用)
|
||||
single: hasPair ? afterText : beforeText,
|
||||
changed: hasPair && beforeText !== afterText,
|
||||
unchanged: hasPair && beforeText === afterText,
|
||||
};
|
||||
};
|
||||
|
||||
const parseSegment = segment => {
|
||||
// 形如「发货地 A → B」(前面只有「;」分隔,无分组标题)→ 归入上一条运单
|
||||
const leadingMatched = segment.match(FIELD_LABEL_PATTERN);
|
||||
if (leadingMatched && leadingMatched[2].trim()) {
|
||||
return {
|
||||
type: 'field',
|
||||
title: '',
|
||||
text: '',
|
||||
fields: [buildField(leadingMatched[1], leadingMatched[2])],
|
||||
};
|
||||
}
|
||||
|
||||
const colonIndex = segment.search(/[::]/);
|
||||
if (colonIndex === -1) {
|
||||
return { type: 'text', title: '', text: segment, fields: [] };
|
||||
}
|
||||
|
||||
const head = segment.slice(0, colonIndex).trim();
|
||||
const tail = segment.slice(colonIndex + 1).trim();
|
||||
const fieldMatched = tail.match(FIELD_LABEL_PATTERN);
|
||||
|
||||
// 形如「YD-20260904-0022:发货地 A → B」
|
||||
if (fieldMatched && fieldMatched[2].trim()) {
|
||||
return {
|
||||
type: 'waybill',
|
||||
title: head,
|
||||
text: '',
|
||||
fields: [buildField(fieldMatched[1], fieldMatched[2])],
|
||||
};
|
||||
}
|
||||
|
||||
// 形如「路线顺序:A → B → C 调整为 B → A → C」
|
||||
if (head && head.length <= TITLE_MAX_LENGTH) {
|
||||
return { type: 'group', title: head, text: '', fields: [buildField('', tail)] };
|
||||
}
|
||||
|
||||
return { type: 'text', title: '', text: segment, fields: [] };
|
||||
};
|
||||
|
||||
export const parseRouteChangeContent = content => {
|
||||
if (content === undefined || content === null) return [];
|
||||
const text = String(content).replace(/\r\n?/g, '\n').trim();
|
||||
if (!text) return [];
|
||||
|
||||
const segments = text
|
||||
.split(/[;;\n]+/)
|
||||
.map(item => item.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
const groups = [];
|
||||
|
||||
segments.forEach(segment => {
|
||||
const group = parseSegment(segment);
|
||||
const last = groups[groups.length - 1];
|
||||
// 同一运单下的「到货地…」等无标题字段,合并进上一个运单分组
|
||||
if (group.type === 'field' && last && (last.type === 'waybill' || last.type === 'field')) {
|
||||
last.fields.push(...group.fields);
|
||||
return;
|
||||
}
|
||||
groups.push(group);
|
||||
});
|
||||
|
||||
return groups;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user