| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461 | <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-NVUE	import { canIUseCanvas2d, isPC} from './utils';	import Painter from './painter';	// import Painter from '@painter'	const nvue = {}	//  #endif	//  #ifdef APP-NVUE	import nvue from './nvue'	//  #endif	export 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>	.lime-painter,	.lime-painter__canvas {		// #ifndef APP-NVUE		width: 100%;		// #endif		// #ifdef APP-NVUE		flex: 1;		// #endif	}</style>
 |