import { getFirmProfile, getLawyerDetail, recordCardShare } from '../../api/open'; import type { FirmInfo, Lawyer } from '../../types/card'; import { appendCardViewRecord } from '../../utils/history'; import { generateSharePoster, SHARE_POSTER_CANVAS_ID } from '../../utils/share-poster'; interface DetailPageData { firm: FirmInfo; lawyer: Lawyer | null; notFound: boolean; specialtiesText: string; detailBgStyle: string; loading: boolean; sharePosterPath: string; } interface DetailPageCustom { handleDockAction(event: WechatMiniprogram.CustomEvent<{ action: string }>): void; saveContact(): void; callPhone(): void; navigateOffice(): void; previewWechatQr(): void; goFirmProfile(): void; goLawyerList(): void; getSharePosterCanvas(): Promise; loadFirmForShare(): Promise; prepareSharePoster(): void; ensureSharePoster(): Promise; } type DetailPageInstance = WechatMiniprogram.Page.Instance; const sharePosterPromiseMap = new WeakMap>(); const shareFirmPromiseMap = new WeakMap>(); const shareCanvasPromiseMap = new WeakMap>(); const pageReadyMap = new WeakMap(); function createEmptyFirm(): FirmInfo { return { id: '', name: '', logo: '', intro: '', hotlinePhone: '', hqAddress: '', hqLatitude: 0, hqLongitude: 0, officeCount: 0, lawyerCount: 0, heroImage: '', officeList: [], practiceAreas: [], }; } function buildDetailBgStyle(coverImage?: string): string { const cover = typeof coverImage === 'string' ? coverImage.trim() : ''; if (!cover) { return ''; } return `background-image: url("${cover}"); background-size: cover; background-position: center;`; } Page({ data: { firm: createEmptyFirm(), lawyer: null, notFound: false, specialtiesText: '', detailBgStyle: '', loading: false, sharePosterPath: '', }, async onLoad(options) { const lawyerId = typeof options.id === 'string' ? options.id : ''; const sourceType = typeof options.sourceType === 'string' ? options.sourceType : 'DIRECT'; const shareFromCardId = typeof options.shareFromCardId === 'string' ? options.shareFromCardId : ''; if (!lawyerId) { this.setData({ notFound: true, specialtiesText: '', detailBgStyle: '' }); return; } this.setData({ loading: true }); try { const payload = await getLawyerDetail(lawyerId, sourceType, shareFromCardId); this.setData({ firm: payload.firm, lawyer: payload.lawyer, notFound: false, specialtiesText: payload.lawyer.specialties.join(';'), detailBgStyle: buildDetailBgStyle(payload.lawyer.coverImage), }); appendCardViewRecord(payload.lawyer); wx.showShareMenu({ menus: ['shareAppMessage'] }); this.prepareSharePoster(); } catch (error) { const message = error instanceof Error ? error.message : '律师信息不存在'; this.setData({ notFound: true, specialtiesText: '', detailBgStyle: '' }); wx.showToast({ title: message, icon: 'none', }); } finally { this.setData({ loading: false }); } }, onReady() { pageReadyMap.set(this, true); this.prepareSharePoster(); }, onUnload() { pageReadyMap.delete(this); sharePosterPromiseMap.delete(this); shareFirmPromiseMap.delete(this); shareCanvasPromiseMap.delete(this); }, handleDockAction(event) { const action = event.detail.action; switch (action) { case 'home': this.goFirmProfile(); break; case 'save': this.saveContact(); break; case 'location': this.navigateOffice(); break; default: break; } }, saveContact() { const lawyer = this.data.lawyer; if (!lawyer) { return; } wx.addPhoneContact({ firstName: lawyer.name, mobilePhoneNumber: lawyer.phone, email: lawyer.email, organization: this.data.firm.name, title: lawyer.title, workAddressStreet: lawyer.address, remark: lawyer.specialties.join('、'), success: () => { wx.showToast({ title: '已添加到通讯录', icon: 'success' }); }, fail: () => { wx.showToast({ title: '添加失败', icon: 'none' }); }, }); }, callPhone() { const phone = this.data.lawyer ? this.data.lawyer.phone : ''; if (!phone) { wx.showToast({ title: '暂无联系电话', icon: 'none' }); return; } wx.makePhoneCall({ phoneNumber: phone, fail: () => { wx.showToast({ title: '拨号失败', icon: 'none' }); }, }); }, navigateOffice() { if (!this.data.firm.hqLatitude || !this.data.firm.hqLongitude) { wx.showToast({ title: '暂未配置地图位置', icon: 'none' }); return; } wx.openLocation({ latitude: this.data.firm.hqLatitude, longitude: this.data.firm.hqLongitude, name: this.data.firm.name, address: this.data.firm.hqAddress, scale: 18, fail: () => { wx.showToast({ title: '打开地图失败', icon: 'none' }); }, }); }, previewWechatQr() { const url = this.data.lawyer ? this.data.lawyer.wechatQrImage : ''; if (!url) { wx.showToast({ title: '二维码未配置', icon: 'none' }); return; } wx.previewImage({ current: url, urls: [url], }); }, goFirmProfile() { wx.navigateTo({ url: '/pages/firm/index' }); }, goLawyerList() { wx.navigateTo({ url: '/pages/lawyer-list/index' }); }, async getSharePosterCanvas() { const cachedPromise = shareCanvasPromiseMap.get(this); if (cachedPromise) { return cachedPromise; } const canvasPromise = new Promise((resolve, reject) => { const query = wx.createSelectorQuery(); query .in(this) .select(`#${SHARE_POSTER_CANVAS_ID}`) .node((result) => { const canvas = result && 'node' in result ? (result.node as WechatMiniprogram.Canvas | undefined) : undefined; if (canvas) { resolve(canvas); return; } reject(new Error('分享海报画布未就绪')); }) .exec(); }).then( (canvas) => { shareCanvasPromiseMap.delete(this); return canvas; }, (error: unknown) => { shareCanvasPromiseMap.delete(this); throw error; } ); shareCanvasPromiseMap.set(this, canvasPromise); return canvasPromise; }, async loadFirmForShare() { const cachedPromise = shareFirmPromiseMap.get(this); if (cachedPromise) { return cachedPromise; } const currentFirm = this.data.firm; if (currentFirm.logo) { return currentFirm; } const firmPromise = getFirmProfile() .then((firmProfile) => ({ ...currentFirm, ...firmProfile, name: firmProfile.name || currentFirm.name, hqAddress: currentFirm.hqAddress || firmProfile.hqAddress, })) .catch(() => currentFirm); const firmPromiseWithCleanup = firmPromise.then( (result) => { shareFirmPromiseMap.delete(this); return result; }, (error: unknown) => { shareFirmPromiseMap.delete(this); throw error; } ); shareFirmPromiseMap.set(this, firmPromiseWithCleanup); return firmPromiseWithCleanup; }, prepareSharePoster() { if (!pageReadyMap.get(this) || !this.data.lawyer || this.data.notFound) { return; } this.ensureSharePoster().catch(() => { // 海报生成失败时使用默认头像兜底 }); }, async ensureSharePoster() { if (this.data.sharePosterPath) { return this.data.sharePosterPath; } const currentPromise = sharePosterPromiseMap.get(this); if (currentPromise) { return currentPromise; } const lawyer = this.data.lawyer; if (!lawyer) { throw new Error('名片数据未就绪'); } const posterPromise = this.loadFirmForShare() .then((firm) => this.getSharePosterCanvas().then((canvas) => generateSharePoster({ canvas, firm: { name: firm.name, logo: firm.logo, hqAddress: firm.hqAddress, }, lawyer: { name: lawyer.name, title: lawyer.title, office: lawyer.office, phone: lawyer.phone, email: lawyer.email, address: lawyer.address, avatar: lawyer.avatar, }, }) ) ) .then((posterPath) => { if (posterPath) { this.setData({ sharePosterPath: posterPath }); } return posterPath; }); const posterPromiseWithCleanup = posterPromise.then( (result) => { sharePosterPromiseMap.delete(this); return result; }, (error: unknown) => { sharePosterPromiseMap.delete(this); throw error; } ); sharePosterPromiseMap.set(this, posterPromiseWithCleanup); return posterPromiseWithCleanup; }, onShareAppMessage() { const lawyer = this.data.lawyer; const firmName = this.data.firm.name || '电子名片'; if (!lawyer) { return { title: firmName, path: '/pages/firm/index', }; } const sharePath = `/pages/lawyer-detail/index?id=${lawyer.id}&sourceType=SHARE&shareFromCardId=${lawyer.id}`; recordCardShare(lawyer.id, sharePath).catch(() => { // 分享埋点失败不阻断分享动作 }); const title = firmName; const fallbackImage = lawyer.avatar; if (this.data.sharePosterPath) { return { title, path: sharePath, imageUrl: this.data.sharePosterPath, }; } const promise = this.ensureSharePoster() .then((posterPath) => ({ title, path: sharePath, imageUrl: posterPath || fallbackImage, })) .catch(() => ({ title, path: sharePath, imageUrl: fallbackImage, })); return { title, path: sharePath, imageUrl: fallbackImage, promise, }; }, });