| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460 | <template>	<view class="lime-painter" ref="limepainter">		<view v-if="canvasId && size" :style="styles">			<!-- #ifndef APP-NVUE -->			<canvas class="lime-painter__canvas" v-if="use2dCanvas" :id="canvasId" type="2d" :style="size"></canvas>			<canvas class="lime-painter__canvas" v-else :id="canvasId" :canvas-id="canvasId" :style="size"				:width="boardWidth * dpr" :height="boardHeight * dpr" :hidpi="hidpi"></canvas>			<!-- #endif -->			<!-- #ifdef APP-NVUE -->			<web-view :style="size" ref="webview" src="/uni_modules/lime-painter/hybrid/html/index.html"				class="lime-painter__canvas" @pagefinish="onPageFinish" @error="onError" @onPostMessage="onMessage">			</web-view>			<!-- #endif -->		</view>		<slot />	</view></template><script>import { parent } from '../common/relation'import props from './props'import { toPx, base64ToPath, pathToBase64, isBase64, sleep, getImageInfo } from './utils';//  #ifndef APP-NVUEimport { canIUseCanvas2d, isPC } from './utils';import Painter from './painter';// import Painter from '@painter'const nvue = {}//  #endif//  #ifdef APP-NVUEimport nvue from './nvue'//  #endifexport default {	name: 'lime-painter',	mixins: [props, parent('painter'), nvue],	data() {		return {			use2dCanvas: false,			canvasHeight: 150,			canvasWidth: null,			parentWidth: 0,			inited: false,			progress: 0,			firstRender: 0,			done: false,			tasks: []		};	},	computed: {		styles() {			return `${this.size}${this.customStyle || ''};` + (this.hidden && 'position: fixed; left: 1500rpx;')		},		canvasId() {			return `l-painter${this._ && this._.uid || this._uid}`		},		size() {			if (this.boardWidth && this.boardHeight) {				return `width:${this.boardWidth}px; height: ${this.boardHeight}px;`;			}		},		dpr() {			return this.pixelRatio || uni.getSystemInfoSync().pixelRatio;		},		boardWidth() {			const { width = 0 } = (this.elements && this.elements.css) || this.elements || this			const w = toPx(width || this.width)			return w || Math.max(w, toPx(this.canvasWidth));		},		boardHeight() {			const { height = 0 } = (this.elements && this.elements.css) || this.elements || this			const h = toPx(height || this.height)			return h || Math.max(h, toPx(this.canvasHeight));		},		hasBoard() {			return this.board && Object.keys(this.board).length		},		elements() {			return this.hasBoard ? this.board : JSON.parse(JSON.stringify(this.el))		}	},	created() {		this.use2dCanvas = this.type === '2d' && canIUseCanvas2d() && !isPC	},	async mounted() {		await sleep(30)		await this.getParentWeith()		this.$nextTick(() => {			setTimeout(() => {				this.$watch('elements', this.watchRender, {					deep: true,					immediate: true				});			}, 30)		})	},	// #ifdef VUE3	unmounted() {		this.done = false		this.inited = false		this.firstRender = 0		this.progress = 0		this.painter = null		clearTimeout(this.rendertimer)	},	// #endif	// #ifdef VUE2	destroyed() {		this.done = false		this.inited = false		this.firstRender = 0		this.progress = 0		this.painter = null		clearTimeout(this.rendertimer)	},	// #endif	methods: {		async watchRender(val, old) {			if (!val || !val.views || (!this.firstRender ? !val.views.length : !this.firstRender) || !Object.keys(val).length || JSON.stringify(val) == JSON.stringify(old)) return;			this.firstRender = 1			this.progress = 0			this.done = false			clearTimeout(this.rendertimer)			this.rendertimer = setTimeout(() => {				this.render(val);			}, this.beforeDelay)		},		async setFilePath(path, param) {			let filePath = path			const { pathType = this.pathType } = param || this			if (pathType == 'base64' && !isBase64(path)) {				filePath = await pathToBase64(path)			} else if (pathType == 'url' && isBase64(path)) {				filePath = await base64ToPath(path)			}			if (param && param.isEmit) {				this.$emit('success', filePath);			}			return filePath		},		async getSize(args) {			const { width } = args.css || args			const { height } = args.css || args			if (!this.size) {				if (width || height) {					this.canvasWidth = width || this.canvasWidth					this.canvasHeight = height || this.canvasHeight					await sleep(30);				} else {					await this.getParentWeith()				}			}		},		canvasToTempFilePathSync(args) {			// this.stopWatch && this.stopWatch()			// this.stopWatch = this.$watch('done', (v) => {			// 	if (v) {			// 		this.canvasToTempFilePath(args)			// 		this.stopWatch && this.stopWatch()			// 	}			// }, {			// 	immediate: true			// })			this.tasks.push(args)			if (this.done) {				this.runTask()			}		},		runTask() {			while (this.tasks.length) {				const task = this.tasks.shift()				this.canvasToTempFilePath(task)			}		},		// #ifndef APP-NVUE		getParentWeith() {			return new Promise(resolve => {				uni.createSelectorQuery()					.in(this)					.select(`.lime-painter`)					.boundingClientRect()					.exec(res => {						const { width, height } = res[0] || {}						this.parentWidth = Math.ceil(width || 0)						this.canvasWidth = this.parentWidth || 300						this.canvasHeight = height || this.canvasHeight || 150						resolve(res[0])					})			})		},		async render(args = {}) {			if (!Object.keys(args).length) {				return console.error('空对象')			}			this.progress = 0			this.done = false			// #ifdef APP-NVUE			this.tempFilePath.length = 0			// #endif			await this.getSize(args)			const ctx = await this.getContext();			let {				use2dCanvas,				boardWidth,				boardHeight,				canvas,				afterDelay			} = this;			if (use2dCanvas && !canvas) {				return Promise.reject(new Error('canvas 没创建'));			}			this.boundary = {				top: 0,				left: 0,				width: boardWidth,				height: boardHeight			};			this.painter = null			if (!this.painter) {				const { width } = args.css || args				const { height } = args.css || args				if (!width && this.parentWidth) {					Object.assign(args, { width: this.parentWidth })				}				const param = {					context: ctx,					canvas,					width: boardWidth,					height: boardHeight,					pixelRatio: this.dpr,					useCORS: this.useCORS,					createImage: getImageInfo.bind(this),					performance: this.performance,					listen: {						onProgress: (v) => {							this.progress = v							this.$emit('progress', v)						},						onEffectFail: (err) => {							this.$emit('faill', err)						}					}				}				this.painter = new Painter(param)			}			try {				// vue3 赋值给data会引起图片无法绘制				const { width, height } = await this.painter.source(JSON.parse(JSON.stringify(args)))				this.boundary.height = this.canvasHeight = height				this.boundary.width = this.canvasWidth = width				await sleep(this.sleep);				await this.painter.render()				await new Promise(resolve => this.$nextTick(resolve));				if (!use2dCanvas) {					await this.canvasDraw();				}				if (afterDelay && use2dCanvas) {					await sleep(afterDelay);				}				this.$emit('done');				this.done = true				if (this.isCanvasToTempFilePath) {					this.canvasToTempFilePath()						.then(res => {							this.$emit('success', res.tempFilePath)						})						.catch(err => {							this.$emit('fail', new Error(JSON.stringify(err)));						});				}				this.runTask()				return Promise.resolve({					ctx,					draw: this.painter,					node: this.node				});			} catch (e) {				//TODO handle the exception			}		},		canvasDraw(flag = false) {			return new Promise((resolve, reject) => this.ctx.draw(flag, () => setTimeout(() => resolve(), this				.afterDelay)));		},		async getContext() {			if (!this.canvasWidth) {				this.$emit('fail', 'painter no size')				console.error('[lime-painter]: 给画板或父级设置尺寸')				return Promise.reject();			}			if (this.ctx && this.inited) {				return Promise.resolve(this.ctx);			}			const { type, use2dCanvas, dpr, boardWidth, boardHeight } = this;			const _getContext = () => {				return new Promise(resolve => {					uni.createSelectorQuery()						.in(this)						.select(`#${this.canvasId}`)						.boundingClientRect()						.exec(res => {							if (res) {								const ctx = uni.createCanvasContext(this.canvasId, this);								if (!this.inited) {									this.inited = true;									this.use2dCanvas = false;									this.canvas = res;								}								// 钉钉小程序框架不支持 measureText 方法,用此方法 mock								if (!ctx.measureText) {									function strLen(str) {										let len = 0;										for (let i = 0; i < str.length; i++) {											if (str.charCodeAt(i) > 0 && str.charCodeAt(i) < 128) {												len++;											} else {												len += 2;											}										}										return len;									}									ctx.measureText = text => {										let fontSize = ctx.state && ctx.state.fontSize || 12;										const font = ctx.__font										if (font && fontSize == 12) {											fontSize = parseInt(font.split(' ')[3], 10);										}										fontSize /= 2;										return {											width: strLen(text) * fontSize										};									}								}								// #ifdef MP-ALIPAY								ctx.scale(dpr, dpr);								// #endif								this.ctx = ctx								resolve(this.ctx);							} else {								console.error('[lime-painter] no node')							}						});				});			};			if (!use2dCanvas) {				return _getContext();			}			return new Promise(resolve => {				uni.createSelectorQuery()					.in(this)					.select(`#${this.canvasId}`)					.node()					.exec(res => {						let { node: canvas } = res && res[0] || {};						if (canvas) {							const ctx = canvas.getContext(type);							if (!this.inited) {								this.inited = true;								this.use2dCanvas = true;								this.canvas = canvas;							}							this.ctx = ctx							resolve(this.ctx);						} else {							console.error('[lime-painter]: no size')						}					});			});		},		canvasToTempFilePath(args = {}) {			return new Promise(async (resolve, reject) => {				const { use2dCanvas, canvasId, dpr, fileType, quality } = this;				const success = async (res) => {					try {						const tempFilePath = await this.setFilePath(res.tempFilePath || res, args)						const result = Object.assign(res, { tempFilePath })						args.success && args.success(result)						resolve(result)					} catch (e) {						this.$emit('fail', e)					}				}				let { top: y = 0, left: x = 0, width, height } = this.boundary || this;				// let destWidth = width * dpr;				// let destHeight = height * dpr;				// #ifdef MP-ALIPAY				// width = destWidth;				// height = destHeight;				// #endif				const copyArgs = Object.assign({					// x,					// y,					// width,					// height,					// destWidth,					// destHeight,					canvasId,					id: canvasId,					fileType,					quality,				}, args, { success });				// if(this.isPC || use2dCanvas) {				// 	copyArgs.canvas = this.canvas				// }				if (use2dCanvas) {					copyArgs.canvas = this.canvas					try {						// #ifndef MP-ALIPAY						const oFilePath = this.canvas.toDataURL(`image/${args.fileType || fileType}`.replace(/pg/, 'peg'), args.quality || quality)						if (/data:,/.test(oFilePath)) {							uni.canvasToTempFilePath(copyArgs, this);						} else {							const tempFilePath = await this.setFilePath(oFilePath, args)							args.success && args.success({ tempFilePath })							resolve({ tempFilePath })						}						// #endif						// #ifdef MP-ALIPAY						this.canvas.toTempFilePath(copyArgs)						// #endif					} catch (e) {						args.fail && args.fail(e)						reject(e)					}				} else {					// #ifdef MP-ALIPAY					if (this.ctx.toTempFilePath) {						// 钉钉						const ctx = uni.createCanvasContext(canvasId);						ctx.toTempFilePath(copyArgs);					} else {						my.canvasToTempFilePath(copyArgs);					}					// #endif					// #ifndef MP-ALIPAY					uni.canvasToTempFilePath(copyArgs, this);					// #endif				}			})		}		// #endif	}};</script><style lang="scss">.lime-painter,.lime-painter__canvas {	// #ifndef APP-NVUE	width: 100%;	// #endif	// #ifdef APP-NVUE	flex: 1;	// #endif}</style>
 |