feat(mini-user): v3.5.10 同城配送提示可配置并支持换行

承运商 HTML 按收货市下发;textarea 回车在 C 端转成换行。含门店去掉分享按钮与小程序企微客服。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-25 14:29:48 +08:00
parent fed8ff3d3a
commit 797b20979d
29 changed files with 758 additions and 28 deletions
+57
View File
@@ -0,0 +1,57 @@
import type { LocalDeliveryDto } from '@dukang/shared-types';
import { DEFAULT_LOCAL_DELIVERY_HINT } from '@dukang/shared-types';
import { request } from './api';
export { DEFAULT_LOCAL_DELIVERY_HINT };
const CACHE_TTL_MS = 60_000;
let cached: LocalDeliveryDto[] | null = null;
let cachedAt = 0;
let inflight: Promise<LocalDeliveryDto[]> | null = null;
function cityAliases(name: string): string[] {
const raw = name.trim();
if (!raw) return [];
const noSuffix = raw.replace(/市$/, '');
const withSuffix = raw.endsWith('市') ? raw : `${raw}`;
return [raw, noSuffix, withSuffix];
}
export async function loadLocalDeliveries(force = false): Promise<LocalDeliveryDto[]> {
if (!force && cached && Date.now() - cachedAt < CACHE_TTL_MS) return cached;
if (!force && inflight) return inflight;
inflight = request<LocalDeliveryDto[]>('/catalog/local-deliveries')
.then((list) => {
cached = Array.isArray(list) ? list : [];
cachedAt = Date.now();
return cached;
})
.catch(() => {
cached = cached ?? [];
return cached;
})
.finally(() => {
inflight = null;
});
return inflight;
}
export function matchLocalDelivery(
list: LocalDeliveryDto[],
opts: { cityCode?: string | null; cityName?: string | null },
): LocalDeliveryDto | null {
const code = String(opts.cityCode || '').trim();
if (code) {
const byCode = list.find((row) => row.city.code === code);
if (byCode) return byCode;
}
const names = new Set(cityAliases(String(opts.cityName || '')));
if (!names.size) return null;
return list.find((row) => cityAliases(row.city.name).some((n) => names.has(n))) ?? null;
}
export function resolveLocalDeliveryHintHtml(row: LocalDeliveryDto | null): string {
const html = row?.hintHtml?.trim();
if (html) return html;
return DEFAULT_LOCAL_DELIVERY_HINT;
}
@@ -1,3 +1,8 @@
import { DEFAULT_LOCAL_DELIVERY_HINT } from '@dukang/shared-types';
/** @deprecated 使用 DEFAULT_LOCAL_DELIVERY_HINT;空配置回退 */
export const LOCAL_DELIVERY_ETA_HINT = DEFAULT_LOCAL_DELIVERY_HINT;
/** 商品履约能力(与 HQ / 交易硬闸一致) */
export type FulfillmentFlags = {
+85
View File
@@ -0,0 +1,85 @@
import Taro from '@tarojs/taro';
export type WecomCsSessionContext = {
orderId?: string;
orderNo?: string;
from?: string;
};
type OpenCsChatOption = {
extInfo: { url: string };
corpId: string;
showMessageCard?: boolean;
sendMessageTitle?: string;
sendMessagePath?: string;
};
type OpenCsChatFn = (option: OpenCsChatOption) => Promise<unknown>;
function getTaroOpenCsChat(): OpenCsChatFn | null {
const api = (Taro as unknown as { openCustomerServiceChat?: OpenCsChatFn }).openCustomerServiceChat;
return typeof api === 'function' ? api : null;
}
function getWxOpenCsChat(): ((option: OpenCsChatOption & {
success?: () => void;
fail?: (err: { errMsg?: string }) => void;
}) => void) | null {
const wxApi = (
globalThis as {
wx?: {
openCustomerServiceChat?: (option: OpenCsChatOption & {
success?: () => void;
fail?: (err: { errMsg?: string }) => void;
}) => void;
};
}
).wx?.openCustomerServiceChat;
return typeof wxApi === 'function' ? wxApi : null;
}
/** 小程序调起企业微信「微信客服」会话 */
export async function openWecomCustomerServiceChat(params: {
url: string;
corpId: string;
session?: WecomCsSessionContext;
}): Promise<void> {
const url = params.url.trim();
const corpId = params.corpId.trim();
if (!url || !corpId) {
throw new Error('企微客服未配置');
}
const option: OpenCsChatOption = {
extInfo: { url },
corpId,
};
if (params.session?.orderNo || params.session?.orderId) {
option.showMessageCard = true;
option.sendMessageTitle = params.session.orderNo
? `订单 ${params.session.orderNo}`
: '订单咨询';
if (params.session.orderId) {
option.sendMessagePath = `pages/order-detail/index?id=${params.session.orderId}`;
}
}
const taroApi = getTaroOpenCsChat();
if (taroApi) {
await taroApi(option);
return;
}
const wxApi = getWxOpenCsChat();
if (!wxApi) {
throw new Error('当前微信版本不支持企业微信客服');
}
await new Promise<void>((resolve, reject) => {
wxApi({
...option,
success: () => resolve(),
fail: (err) => reject(new Error(err?.errMsg || '无法打开企业微信客服')),
});
});
}