370 lines
11 KiB
TypeScript
370 lines
11 KiB
TypeScript
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<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();
|
||
},
|
||
|
||
/**
|
||
* 从 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));
|
||
}
|