import { PAPER_SIZE } from '../constants/colors'; import { downloadPrint } from '../utils/downloadPrint'; import { BaseDrawService } from '../core/draw/baseDraw'; import tracker from '../utils/tracker'; import { defaultShareConfig } from '../config/config'; /** * Canvas 相关的页面实例属性 */ export interface PageCanvasInstance { data: CanvasDataState; canvas: Canvas | null; ctx: RenderingContext | null; boxHeight: number; boxWidth: number; drawService: BaseDrawService | null; setData(data: any, callback?: () => void): void; route: string; getShareOptions(): ShareOptions; } /** * Canvas 数据状态接口 */ export interface CanvasDataState { hasContent: boolean; showShareDialog: boolean; boxWidth: number; boxHeight: number; functionId: string; currentMode?: string; pageTitle: string; subTitle?: string; data: any; setData(data: any, callback?: () => void): void; route: string; } /** * Canvas 初始化选项 */ export interface InitCanvasOptions { /** * 创建绘制服务的工厂函数 */ createDrawService: ( canvas: Canvas, ctx: RenderingContext, options?: Record, ) => BaseDrawService; /** * 绘制服务的选项 */ drawServiceOptions?: Record; /** * Canvas 初始化完成后回调 */ onCanvasReady?: () => void; } /** * 分享配置选项 */ export interface ShareOptions { /** * 页面路径(相对于 mathPages 目录,例如:'missingNumber/missingNumber') */ path: string; title: string; imageUrl: string; } /** * 页面信息接口 */ export interface PageInfo { title: string; desc?: string; } /** * 页面信息查找函数类型 */ export type PageInfoLookup = (functionId: string) => PageInfo | undefined; /** * 页面公共方法配置 */ export interface PageCommonMethodsConfig { /** * 分享配置 */ shareConfig?: { title: string; imageUrl: string; }; /** * 页面信息查找函数(可选) * 如果提供,initPageInfo 方法会使用它来查找页面信息 */ pageInfoLookup?: PageInfoLookup; } /** * 获取页面公共方法 * 这些方法可以在所有Canvas绘制页面中复用 * @param config 配置选项 */ export function getPageCommonMethods(config: PageCommonMethodsConfig = {}) { const { shareConfig = defaultShareConfig, pageInfoLookup } = config; return { /** * 初始化 Canvas */ initCanvas(this: PageCanvasInstance, options: InitCanvasOptions) { const query = wx.createSelectorQuery(); query .select('#canvasWrapper') .boundingClientRect((rect) => { if (!rect) return; const { width, height } = PAPER_SIZE['A4']; const boxWidth = rect.width; const boxHeight = boxWidth / (width / height); this.boxHeight = boxHeight; this.boxWidth = boxWidth; this.setData({ boxWidth, boxHeight }); const canvas = wx .createSelectorQuery() .select('#canvasContent'); canvas.fields({ node: true, size: true }).exec((res) => { if (res[0]) { const canvasNode = res[0].node; const ctx = canvasNode.getContext('2d'); const dpr = wx.getSystemInfoSync().pixelRatio; canvasNode.width = boxWidth * dpr; canvasNode.height = boxHeight * dpr; ctx.scale(dpr, dpr); this.canvas = canvasNode; this.ctx = ctx; // 创建绘制服务 const drawServiceOptions = { title: this.data.pageTitle, subTitle: this.data.subTitle || '', ...options.drawServiceOptions, }; this.drawService = options.createDrawService( canvasNode, ctx, drawServiceOptions, ); // 执行初始化完成回调 if (options.onCanvasReady) { options.onCanvasReady.call(this); } } }); }) .exec(); }, /** * 从 preview-card 组件的 canvas-ready 事件初始化 Canvas * 用于已将 canvas 节点下沉到 preview-card 组件的页面 */ initCanvasFromComponent( this: PageCanvasInstance, detail: { canvas: Canvas; ctx: RenderingContext; width: number; height: number; }, options: InitCanvasOptions, ) { this.canvas = detail.canvas; this.ctx = detail.ctx; this.boxWidth = detail.width; this.boxHeight = detail.height; this.setData({ boxWidth: detail.width, boxHeight: detail.height, }); const drawServiceOptions = { title: this.data.pageTitle, subTitle: this.data.subTitle || '', ...options.drawServiceOptions, }; this.drawService = options.createDrawService( detail.canvas, detail.ctx, drawServiceOptions, ); if (options.onCanvasReady) { options.onCanvasReady.call(this); } }, /** * 导出打印 */ async exportToPrint(this: PageCanvasInstance) { await downloadPrint(this.canvas, { errorToast: '请先生成内容', trackerName: this.data.pageTitle, trackerMode: this.data.currentMode, }); }, getShareOptions(this: PageCanvasInstance): ShareOptions { const route = this.route || ''; const { functionId, currentMode } = this.data; const currentModeQuery = currentMode ? `&mode=${currentMode}` : ''; const path = `${route}?id=${functionId}${currentModeQuery}`; console.log('getShareOptions path', path); return { ...shareConfig, path, }; }, /** * 分享小程序 */ onShareAppMessage(this: PageCanvasInstance) { // 上报分享埋点 tracker.reportShare(this.data.pageTitle); return this.getShareOptions(); }, /** * 分享到朋友圈 */ onShareTimeline(this: PageCanvasInstance) { console.log('onShareTimeline'); // 上报分享埋点 tracker.reportShare(this.data.pageTitle); return this.getShareOptions(); }, /** * 关闭分享引导弹窗 */ onCloseShareDialog(this: PageCanvasInstance) { this.setData({ showShareDialog: false }); }, /** * 分享成功回调 */ async onShareSuccess(this: PageCanvasInstance) { this.setData({ showShareDialog: false }); if (this.canvas) { // 分享成功后下载(不限制次数) await downloadPrint(this.canvas, { errorToast: '请先生成内容', trackerName: this.data.pageTitle, trackerMode: this.data.currentMode, }); } }, /** * 初始化页面信息 * * 支持两种调用方式: * 1. initPageInfo(functionId, defaultTitle) - 如果有 pageInfoLookup,会尝试查找;否则使用 defaultTitle * 2. initPageInfo({ title, desc, functionId }) - 直接提供页面信息 */ initPageInfo( this: PageCanvasInstance, functionIdOrOptions: | string | { title: string; desc?: string; functionId: string }, defaultTitle?: string, ) { let functionId: string; let title: string; let desc: string = ''; // 判断调用方式 if (typeof functionIdOrOptions === 'string') { // 方式1: initPageInfo(functionId, defaultTitle) functionId = functionIdOrOptions; title = defaultTitle || ''; // 如果有 pageInfoLookup,尝试查找 if (pageInfoLookup) { const pageInfo = pageInfoLookup(functionId); if (pageInfo) { title = pageInfo.title; desc = pageInfo.desc || ''; } } } else { // 方式2: initPageInfo({ title, desc, functionId }) functionId = functionIdOrOptions.functionId; title = functionIdOrOptions.title; desc = functionIdOrOptions.desc || ''; } this.setData({ pageTitle: title, subTitle: desc, functionId, }); wx.setNavigationBarTitle({ title }); }, }; } /** * 应用页面公共方法的辅助函数 * 自动混入公共方法,简化页面代码 * @param pageOptions 页面选项 * @param config 公共方法配置 * @returns 合并后的页面选项 */ export function applyPageMixin( pageOptions: any, config: PageCommonMethodsConfig = {}, ) { const commonMethods = getPageCommonMethods(config); // 提取 pageOptions 中的 data(如果有) const pageData = pageOptions.data || {}; // 合并页面选项和公共方法 // 注意:pageOptions 放在后面,这样页面可以覆盖公共方法 const mergedOptions: any = { ...commonMethods, ...pageOptions, // 处理 data 的合并(需要特殊处理,避免覆盖) data: { ...pageData, }, }; return mergedOptions; } /** * 创建页面的便捷函数 * 自动应用公共方法并注册为页面 * @param pageOptions 页面选项 * @param config 公共方法配置 */ export function createPage( pageOptions: any, config: PageCommonMethodsConfig = {}, ) { Page(applyPageMixin(pageOptions, config)); }