From cd216222d3c295633505b7521df5d76d16685107 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E7=90=A6?= Date: Tue, 20 Oct 2026 09:39:19 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=B0=B1=E8=AF=8A?= =?UTF-8?q?=E4=BA=BA=E5=9C=B0=E5=9D=80=E8=90=BD=E5=BA=93=EF=BC=8C=E5=B9=B6?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=AE=A2=E5=8D=95=E6=9C=AA=E5=90=8C=E6=AD=A5?= =?UTF-8?q?ERP=E6=97=B6=E6=94=B9=E6=94=B6=E8=B4=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 选择并接诊时可预览确认导入本次地址;粘贴识别改为长到短取名,避免「罗朝亮」被截成「朝亮」。 Co-authored-by: Cursor --- apps/web-antd/src/util/address-recognizer.js | 15 +- .../business/order/product-order/api/index.ts | 24 ++ .../components/EditShipAddressModal.vue | 295 ++++++++++++++++++ .../order/product-order/components/detail.vue | 50 ++- .../business/order/product-order/index.vue | 41 +++ .../components/CreateUserPatientModal.vue | 223 +++++++++++-- .../EditCreatedUserPatientModal.vue | 56 +++- 7 files changed, 663 insertions(+), 41 deletions(-) create mode 100644 apps/web-antd/src/views/business/order/product-order/components/EditShipAddressModal.vue diff --git a/apps/web-antd/src/util/address-recognizer.js b/apps/web-antd/src/util/address-recognizer.js index 73b793c1..f2c1fd78 100644 --- a/apps/web-antd/src/util/address-recognizer.js +++ b/apps/web-antd/src/util/address-recognizer.js @@ -387,7 +387,8 @@ export class AddressRecognizer { const m = after.match(/^\s*[,,、]?\s*([\u4E00-\u9FA5]{2,6})/); if (!m) return null; const chunk = m[1]; - for (let len = 2; len <= chunk.length; len++) { + // 长到短:与手机号前取名同口径,避免短姓误截 + for (let len = chunk.length; len >= 2; len--) { const cand = chunk.slice(0, len); if (this.isValidName(cand)) { return { value: cand, confidence: len <= 3 ? 0.8 : 0.7 }; @@ -397,7 +398,8 @@ export class AddressRecognizer { } /** - * 从手机号前取名:短到长尝试,避免「张三杭州」整段误判;支持复姓最长 6 字 + * 从手机号前取名:长到短优先,避免姓氏表含生僻单姓(如「朝」)时把「罗朝亮」截成「朝亮」 + * 支持复姓最长 6 字 */ _nameBeforePhone(text, phoneInfo) { if (!phoneInfo || !phoneInfo.value) return null; @@ -407,7 +409,7 @@ export class AddressRecognizer { const m = before.match(/([\u4E00-\u9FA5]{2,6})\s*$/); if (!m) return null; const chunk = m[1]; - for (let len = 2; len <= chunk.length; len++) { + for (let len = chunk.length; len >= 2; len--) { const cand = chunk.slice(chunk.length - len); if (this.isValidName(cand)) { return { value: cand, confidence: len <= 3 ? 0.85 : 0.75 }; @@ -770,8 +772,9 @@ export class AddressRecognizer { normalizeEcommerceText(text) { const src = String(text || ''); if (src.indexOf('\n') < 0 && src.indexOf('\r') < 0) return src; + // 保留标签名:展平后仍能走「收货人:xxx」关键词取名,避免只剩裸姓名被短姓截断 const labelRe = - /^(?:收货人|收件人|联系人|姓名|手机号码|手机号|手机|联系电话|联系方式|电话|所在地区|详细地址|地址)\s*[::]\s*(.+)$/; + /^(收货人|收件人|联系人|姓名|手机号码|手机号|手机|联系电话|联系方式|电话|所在地区|详细地址|地址)\s*[::]\s*(.+)$/; const lines = src.split(/\r?\n/); const parts = []; let hitLabel = false; @@ -779,9 +782,9 @@ export class AddressRecognizer { const line = String(lines[i] || '').trim(); if (!line) continue; const m = line.match(labelRe); - if (m && m[1]) { + if (m && m[2]) { hitLabel = true; - parts.push(m[1].trim()); + parts.push(`${m[1]}:${String(m[2]).trim()}`); } else { parts.push(line); } diff --git a/apps/web-antd/src/views/business/order/product-order/api/index.ts b/apps/web-antd/src/views/business/order/product-order/api/index.ts index 64ee33c8..06049dbb 100644 --- a/apps/web-antd/src/views/business/order/product-order/api/index.ts +++ b/apps/web-antd/src/views/business/order/product-order/api/index.ts @@ -120,6 +120,30 @@ export async function getStoreShipAddress(orderId: number) { }); } +/** + * 单独修改收货信息(不发货;后端校验未同步 ERP) + */ +export async function updateShipAddressApi(data: { + order_id: number; + express_name?: string; + express_mobile?: string; + express_region?: string; + express_address?: string; + province?: string; + city?: string; + area?: string; +}) { + return requestClient.post<{ + order_id: number; + order_no: string; + delivery_method: number; + express_name: string; + express_mobile: string; + express_region: string; + express_address: string; + }>(`${prefix}update-ship-address`, data); +} + /** * 订单物流追踪 * @param data diff --git a/apps/web-antd/src/views/business/order/product-order/components/EditShipAddressModal.vue b/apps/web-antd/src/views/business/order/product-order/components/EditShipAddressModal.vue new file mode 100644 index 00000000..abf69f8e --- /dev/null +++ b/apps/web-antd/src/views/business/order/product-order/components/EditShipAddressModal.vue @@ -0,0 +1,295 @@ + + + + + diff --git a/apps/web-antd/src/views/business/order/product-order/components/detail.vue b/apps/web-antd/src/views/business/order/product-order/components/detail.vue index 2e698c22..afbdcd50 100644 --- a/apps/web-antd/src/views/business/order/product-order/components/detail.vue +++ b/apps/web-antd/src/views/business/order/product-order/components/detail.vue @@ -296,7 +296,12 @@ const [Modal, modalApi] = useVbenModal({ gridApi.value = isOpen ? modalApi.getData()?.gridApi : null; if (isOpen) { const modalData = modalApi.getData>() || {}; - const { values, id, onShipPackage: shipFn } = modalData; + const { + values, + id, + onShipPackage: shipFn, + onEditShipAddress: editShipFn, + } = modalData; fromTrace.value = !!modalData.fromTrace; onOpenTraceFn.value = typeof modalData.onOpenTrace === 'function' @@ -308,6 +313,8 @@ const [Modal, modalApi] = useVbenModal({ : null; // 列表传入:按 warehouse_id 打开发货弹窗(平台包=0) onShipPackage.value = typeof shipFn === 'function' ? shipFn : null; + onEditShipAddress.value = + typeof editShipFn === 'function' ? editShipFn : null; if (id) { getOrderInfo(id).then((res) => { data.value = res; @@ -322,6 +329,7 @@ const [Modal, modalApi] = useVbenModal({ } } else { onShipPackage.value = null; + onEditShipAddress.value = null; fromTrace.value = false; onOpenTraceFn.value = null; onOpenPrescriptionFn.value = null; @@ -352,6 +360,18 @@ const packageTabs = computed(() => { const activePkg = ref('0'); /** 详情内「发本包」回调(由列表传入时仅平台包裹可发) */ const onShipPackage = ref<((warehouseId: number) => void) | null>(null); +/** 详情内打开「修改收货信息」(未同步 ERP) */ +const onEditShipAddress = ref<((row: Record) => void) | null>( + null, +); +/** 快递且未同步 ERP 时可改收货 */ +const canEditShipAddress = computed( + () => + deliveryMethod.value === 0 && + data.value && + Number(data.value.is_sync_erp) !== 1 && + typeof onEditShipAddress.value === 'function', +); /** * Tab 文案:有仓名才拼括号;空串不兜底「平台/仓」 @@ -614,15 +634,25 @@ function prescriptionStatusColor() {

{{ deliveryMethod === 0 ? '收货人' : '就诊人' }}信息

- + + + + { const [FormModal, formModalApi] = useVbenModal({ connectedComponent: FormModalDemo, }); +/** 发货前单独改收货信息(未同步 ERP) */ +const [EditShipAddressModalComp, editShipAddressModalApi] = useVbenModal({ + connectedComponent: EditShipAddressModal, +}); const [Modal, modalApi] = useVbenModal({ connectedComponent: DetailModal, }); @@ -400,6 +405,11 @@ const infoModal = (data: Record = {}) => { modalApi.close(); wareSend(data, warehouseId); }, + // 详情内改收货:关详情后打开改址弹窗(避免叠层) + onEditShipAddress: (row: Record) => { + modalApi.close(); + openEditShipAddress(row || data); + }, // 二级弹窗必须页面级挂载,详情里再嵌溯源会被盖住 onOpenTrace: (payload: Record) => { openOrderPopup(traceDrawerApi, payload, ORDER_POPUP_Z_INDEX.NESTED); @@ -421,6 +431,27 @@ const openLogisticsModal = (data: Record = {}) => { logisticsModalApi.open(); }; +/** + * 打开「修改收货信息」弹窗(不发货;仅未同步 ERP) + */ +const openEditShipAddress = (data: Record = {}) => { + const addr = + data?.address && typeof data.address === 'object' ? data.address : {}; + editShipAddressModalApi.setData({ + values: { + order_id: data?.id, + order_no: data?.order_no, + express_name: data?.express_name || addr.name || '', + express_mobile: data?.express_mobile || addr.mobile || '', + express_region: data?.express_region || addr.region || '', + express_address: data?.express_address || addr.detail_address || '', + address: addr, + }, + gridApi: gridApiProxy, + }); + editShipAddressModalApi.open(); +}; + /** * 打开发货弹窗。 * 列表不传 warehouse_id(后端平台默认 0);详情按包发时仅传合法非负整数。 @@ -1328,6 +1359,15 @@ function buildRowActions(row: Record): { (Number(row.status) === 2 || Number(row.status) === 7), onClick: handleRetryConfirmReceive.bind(null, row), }, + { + // 发货前独立改址通道;已同步 ERP 后端也会拒绝 + label: '改收货', + type: 'link', + icon: 'mdi:map-marker-path', + auth: ['Super Admin', 'Admin'], + ifShow: row.is_sync_erp !== 1, + onClick: () => openEditShipAddress(row), + }, { label: '发货', type: 'link', @@ -1362,6 +1402,7 @@ function buildRowActions(row: Record): { diff --git a/apps/web-antd/src/views/doctor/doctor-reception/components/EditCreatedUserPatientModal.vue b/apps/web-antd/src/views/doctor/doctor-reception/components/EditCreatedUserPatientModal.vue index 04a2e81e..7206e930 100644 --- a/apps/web-antd/src/views/doctor/doctor-reception/components/EditCreatedUserPatientModal.vue +++ b/apps/web-antd/src/views/doctor/doctor-reception/components/EditCreatedUserPatientModal.vue @@ -57,6 +57,8 @@ const saving = ref(false); const editMode = ref<'full' | 'limited'>('full'); const userPatientId = ref(0); const clinicAddress = ref(null); +/** 中文省市区兜底:详情回填/粘贴写入,ID 解析失败时仍能提交 */ +const shipLabels = ref({ province: '', city: '', area: '' }); const form = ref({ name: '', mobile: '', @@ -115,6 +117,11 @@ async function applyClinicAddress() { } form.value.ship_region = [a.province_id, a.city_id]; form.value.ship_detail_address = a.position; + shipLabels.value = { + province: a.province || '', + city: a.city || '', + area: '', + }; await ensureResolvedAddress(form.value.ship_region); if (a.contact) { form.value.ship_name = a.contact.slice(0, 10); @@ -152,6 +159,11 @@ async function loadDetail(id: number) { form.value.ship_name = ship.name || ''; form.value.ship_mobile = ship.mobile || ''; form.value.ship_detail_address = ship.detail_address || ''; + shipLabels.value = { + province: String(ship.province || '').trim(), + city: String(ship.city || '').trim(), + area: String(ship.area || '').trim(), + }; if (ship.province || ship.city || ship.area) { const pair = await resolveAddressPairFromNames( ship.province, @@ -173,6 +185,11 @@ async function onAddressRecognized(result: AddressRecognizeResult) { if (result.phone) form.value.ship_mobile = result.phone; if (result.detail) form.value.ship_detail_address = result.detail; if (result.province || result.city || result.area) { + shipLabels.value = { + province: String(result.province || '').trim(), + city: String(result.city || '').trim(), + area: String(result.area || '').trim(), + }; const pair = await resolveAddressPairFromNames( result.province, result.city, @@ -185,21 +202,50 @@ async function onAddressRecognized(result: AddressRecognizeResult) { } } +/** + * 组装更新体:省市区优先 ID 解析,失败回落详情/粘贴缓存的中文名 + * 有详细地址却无任何省市区文案时阻断,避免只写 detail、后端报错或半截更新 + */ async function buildPayload() { + const detail = String(form.value.ship_detail_address || '').trim(); const payload: Record = { user_patient_id: userPatientId.value, mobile: form.value.mobile, ship_name: form.value.ship_name, ship_mobile: form.value.ship_mobile, - ship_detail_address: form.value.ship_detail_address, + ship_detail_address: detail, }; if (form.value.ship_region?.length) { const resolved = await ensureResolvedAddress(form.value.ship_region); - payload.ship_province = resolved.province?.label || ''; - payload.ship_city = resolved.city?.label || ''; - payload.ship_area = resolved.district?.label || ''; + const province = + resolved.province?.label || shipLabels.value.province || ''; + const city = resolved.city?.label || shipLabels.value.city || ''; + const area = resolved.district?.label || shipLabels.value.area || ''; + if (detail && !province && !city && !area) { + throw new Error('省市区解析失败,请重新选择后再保存'); + } + payload.ship_province = province; + payload.ship_city = city; + payload.ship_area = area; payload.ship_region = - resolved.pathLabel || formatAddressDisplay(form.value.ship_region); + resolved.pathLabel || + formatAddressDisplay(form.value.ship_region) || + [province, city, area].filter(Boolean).join(' '); + } else if ( + detail && + (shipLabels.value.province || shipLabels.value.city || shipLabels.value.area) + ) { + // 有详情中文省市区但 ID 未解析出:仍提交中文,避免地址丢 + payload.ship_province = shipLabels.value.province; + payload.ship_city = shipLabels.value.city; + payload.ship_area = shipLabels.value.area; + payload.ship_region = [ + shipLabels.value.province, + shipLabels.value.city, + shipLabels.value.area, + ] + .filter(Boolean) + .join(' '); } if (editMode.value === 'full') { payload.name = form.value.name;