242 lines
6.7 KiB
TypeScript
242 lines
6.7 KiB
TypeScript
import Taro from '@tarojs/taro';
|
||
import type { WechatShareData } from '@dukang/weixin-sdk';
|
||
import { getWechatShareLink, isWechatBrowser, isMiniProgram } from '@dukang/weixin-sdk';
|
||
import {
|
||
applyShareTitleTemplate,
|
||
DEFAULT_SHARE_DESC,
|
||
DEFAULT_SHARE_HINT,
|
||
DEFAULT_SHARE_TITLE,
|
||
resolveMiniShareRuntime,
|
||
type ClientRuntimeConfig,
|
||
type MiniShareRuntime,
|
||
type MiniShareSceneConfig,
|
||
} from '@dukang/shared-types';
|
||
import { toast } from './api';
|
||
import { getBrandAssetsSync, loadBrandAssets } from './brand-assets';
|
||
import { fetchClientConfig } from './pay-wechat';
|
||
import { isWechatEnv, weixinSdk } from './weixin';
|
||
|
||
export type ShareScene =
|
||
| 'home'
|
||
| 'stores'
|
||
| 'storeDetail'
|
||
| 'benefit'
|
||
| 'mine'
|
||
| 'productDetail'
|
||
| 'orderDetail';
|
||
|
||
export { DEFAULT_SHARE_TITLE, DEFAULT_SHARE_DESC };
|
||
|
||
export const WECHAT_SHARE_HINT = DEFAULT_SHARE_HINT;
|
||
|
||
const FALLBACK_SHARE: MiniShareRuntime = resolveMiniShareRuntime({});
|
||
|
||
let shareCached: MiniShareRuntime | null = null;
|
||
|
||
export function getShareRuntimeSync(): MiniShareRuntime {
|
||
return shareCached ?? FALLBACK_SHARE;
|
||
}
|
||
|
||
export function applyShareFromClientConfig(config?: ClientRuntimeConfig | null) {
|
||
if (config?.share) {
|
||
shareCached = config.share;
|
||
return shareCached;
|
||
}
|
||
return getShareRuntimeSync();
|
||
}
|
||
|
||
export async function loadShareConfig(force = false): Promise<MiniShareRuntime> {
|
||
if (!force && shareCached) return shareCached;
|
||
try {
|
||
const cfg = await fetchClientConfig();
|
||
if (cfg?.share) {
|
||
shareCached = cfg.share;
|
||
return shareCached;
|
||
}
|
||
} catch {
|
||
/* ignore */
|
||
}
|
||
shareCached = FALLBACK_SHARE;
|
||
return shareCached;
|
||
}
|
||
|
||
export function getDefaultShareImageUrl(): string {
|
||
const share = getShareRuntimeSync();
|
||
return share.default.imageUrl || getBrandAssetsSync().brandLogoUrl;
|
||
}
|
||
|
||
export function getShareHint(): string {
|
||
return getShareRuntimeSync().hint || DEFAULT_SHARE_HINT;
|
||
}
|
||
|
||
/** 预热分享配置与品牌图 */
|
||
export function prefetchShareBrandAssets() {
|
||
void loadBrandAssets();
|
||
void loadShareConfig();
|
||
}
|
||
|
||
function sceneConfig(scene?: ShareScene): MiniShareSceneConfig {
|
||
const runtime = getShareRuntimeSync();
|
||
if (!scene) return runtime.default;
|
||
return runtime[scene] ?? runtime.default;
|
||
}
|
||
|
||
/**
|
||
* 组装页面分享。
|
||
* - productDetail / storeDetail:title 与 imageUrl 固定用业务字段(不被 HQ 场景配置覆盖)
|
||
* - 其它场景:场景配置优先;空字段用 dynamic → 默认分享
|
||
* - orderDetail 标题支持 {productName}
|
||
*/
|
||
export function buildSceneSharePayload(
|
||
scene: ShareScene,
|
||
options?: {
|
||
path?: string;
|
||
/** 业务动态标题(场景配置为空时使用) */
|
||
dynamicTitle?: string | null;
|
||
dynamicDesc?: string | null;
|
||
dynamicImageUrl?: string | null;
|
||
titleVars?: Record<string, string | undefined | null>;
|
||
},
|
||
): PageSharePayload {
|
||
const def = getShareRuntimeSync().default;
|
||
const sc = sceneConfig(scene);
|
||
const preferEntity = scene === 'productDetail' || scene === 'storeDetail';
|
||
|
||
let title: string;
|
||
if (preferEntity) {
|
||
title = (options?.dynamicTitle || '').trim() || def.title;
|
||
} else {
|
||
title = (sc.title || '').trim();
|
||
if (title && options?.titleVars) {
|
||
const vars = options.titleVars;
|
||
const missingRequired = Object.entries(vars).some(
|
||
([key, value]) => title.includes(`{${key}}`) && !(value != null && String(value).trim()),
|
||
);
|
||
title = missingRequired ? '' : applyShareTitleTemplate(title, vars);
|
||
}
|
||
if (!title) {
|
||
title = (options?.dynamicTitle || '').trim() || def.title;
|
||
}
|
||
}
|
||
|
||
const desc = (sc.desc || '').trim() || (options?.dynamicDesc || '').trim() || def.desc;
|
||
const imgUrl = preferEntity
|
||
? (options?.dynamicImageUrl || '').trim() || def.imageUrl || getDefaultShareImageUrl()
|
||
: (sc.imageUrl || '').trim() ||
|
||
(options?.dynamicImageUrl || '').trim() ||
|
||
def.imageUrl ||
|
||
getDefaultShareImageUrl();
|
||
|
||
return {
|
||
title,
|
||
desc,
|
||
path: options?.path,
|
||
imgUrl,
|
||
};
|
||
}
|
||
|
||
export function buildDefaultShareData(
|
||
overrides?: Partial<WechatShareData>,
|
||
): WechatShareData {
|
||
let link = overrides?.link;
|
||
if (!link) {
|
||
if (process.env.TARO_ENV === 'h5' && typeof window !== 'undefined') {
|
||
link = getWechatShareLink();
|
||
} else {
|
||
link = '';
|
||
}
|
||
}
|
||
const def = getShareRuntimeSync().default;
|
||
return {
|
||
title: overrides?.title ?? def.title,
|
||
desc: overrides?.desc ?? def.desc,
|
||
link,
|
||
imgUrl: overrides?.imgUrl ?? getDefaultShareImageUrl(),
|
||
};
|
||
}
|
||
|
||
/** 配置 H5 微信内右上角分享卡片 */
|
||
export async function applyWechatShare(
|
||
overrides?: Partial<WechatShareData>,
|
||
): Promise<void> {
|
||
if (process.env.TARO_ENV !== 'h5') return;
|
||
if (!isWechatBrowser()) return;
|
||
await weixinSdk.setShare(buildDefaultShareData(overrides));
|
||
}
|
||
|
||
export type PageSharePayload = {
|
||
title?: string;
|
||
desc?: string;
|
||
/** 小程序分享 path,如 /pages/product-detail/index?id=1 */
|
||
path?: string;
|
||
imgUrl?: string;
|
||
/** H5 自定义分享 link,默认当前页 */
|
||
link?: string;
|
||
};
|
||
|
||
/**
|
||
* 点击「分享」按钮。
|
||
* H5:走微信 JSSDK 配置分享卡片,并尝试 invoke 调起面板;否则返回需展示引导蒙层。
|
||
*/
|
||
export async function handleShareButtonClick(
|
||
payload?: PageSharePayload,
|
||
): Promise<{ showGuide: boolean }> {
|
||
if (process.env.TARO_ENV === 'weapp' || isMiniProgram()) {
|
||
try {
|
||
await Taro.showShareMenu({ withShareTicket: true, showShareItems: ['shareAppMessage', 'shareTimeline'] });
|
||
} catch {
|
||
try {
|
||
await Taro.showShareMenu({ withShareTicket: true });
|
||
} catch {
|
||
/* ignore */
|
||
}
|
||
}
|
||
toast(getShareHint());
|
||
return { showGuide: false };
|
||
}
|
||
|
||
if (!isWechatEnv()) {
|
||
toast('请在微信内打开后分享');
|
||
return { showGuide: false };
|
||
}
|
||
|
||
const data = buildDefaultShareData({
|
||
title: payload?.title,
|
||
desc: payload?.desc,
|
||
imgUrl: payload?.imgUrl,
|
||
link: payload?.link,
|
||
});
|
||
|
||
try {
|
||
const result = await weixinSdk.share(data);
|
||
if (result.invoked) {
|
||
return { showGuide: false };
|
||
}
|
||
toast(getShareHint());
|
||
return { showGuide: true };
|
||
} catch (e) {
|
||
toast(e instanceof Error ? e.message : '分享配置失败,请刷新后重试');
|
||
return { showGuide: true };
|
||
}
|
||
}
|
||
|
||
/** 供 useShareAppMessage 使用的标题/路径/图 */
|
||
export function toWeappShareMessage(payload?: PageSharePayload) {
|
||
const def = getShareRuntimeSync().default;
|
||
return {
|
||
title: payload?.title || def.title,
|
||
path: payload?.path || '/pages/home/index',
|
||
imageUrl: payload?.imgUrl || getDefaultShareImageUrl(),
|
||
};
|
||
}
|
||
|
||
/** 供 useShareTimeline */
|
||
export function toWeappShareTimeline(payload?: PageSharePayload, query = '') {
|
||
const def = getShareRuntimeSync().default;
|
||
return {
|
||
title: payload?.title || def.title,
|
||
query,
|
||
imageUrl: payload?.imgUrl || getDefaultShareImageUrl(),
|
||
};
|
||
}
|