Files
doodle-mini/miniprogram/base/pageMixin.ts
T
2025-12-19 17:26:54 +08:00

335 lines
9.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { PAPER_SIZE } from '../constants/colors';
import { checkAndSaveImage } from '../utils/saveImage';
import { shouldShowShareGuide } from '../utils/shareGuide';
import { BaseDrawService } from '../service/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<string, any>,
) => BaseDrawService;
/**
* 绘制服务的选项
*/
drawServiceOptions?: Record<string, any>;
/**
* 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();
},
/**
* 导出打印
*/
exportToPrint(this: PageCanvasInstance) {
if (!this.canvas || !this.data.hasContent) {
return;
}
if (shouldShowShareGuide()) {
this.setData({ showShareDialog: true });
return;
}
// 上报下载埋点
tracker.reportDownload(this.data.pageTitle, this.data.currentMode);
checkAndSaveImage(this.canvas);
},
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 });
},
/**
* 分享成功回调
*/
onShareSuccess(this: PageCanvasInstance) {
this.setData({ showShareDialog: false });
if (this.canvas) {
// 上报下载埋点(分享成功后下载)
tracker.reportDownload(this.data.pageTitle);
checkAndSaveImage(this.canvas);
}
},
/**
* 初始化页面信息
*
* 支持两种调用方式:
* 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));
}