|
|
<template>
|
|
|
<view class="lime-signature" :style="[canvasStyle, styles]" ref="limeSignature">
|
|
|
<!-- #ifndef APP-VUE || APP-NVUE -->
|
|
|
<canvas v-if="useCanvas2d" class="lime-signature__canvas" :id="canvasId" type="2d"
|
|
|
:disableScroll="disableScroll" @touchstart="touchStart" @touchmove="touchMove"
|
|
|
@touchend="touchEnd"></canvas>
|
|
|
<canvas v-else :disableScroll="disableScroll" class="lime-signature__canvas" :canvas-id="canvasId"
|
|
|
:id="canvasId" @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd" @mousedown="touchStart"
|
|
|
@mousemove="touchMove" @mouseup="touchEnd"></canvas>
|
|
|
<!-- #endif -->
|
|
|
<!-- #ifdef APP-VUE -->
|
|
|
<view :id="canvasId" :disableScroll="disableScroll" :rparam="param" :change:rparam="sign.update"
|
|
|
:rclear="rclear" :change:rclear="sign.clear" :rundo="rundo" :change:rundo="sign.undo" :rsave="rsave"
|
|
|
:change:rsave="sign.save" :rempty="rempty" :change:rempty="sign.isEmpty"></view>
|
|
|
<!-- #endif -->
|
|
|
<!-- #ifdef APP-NVUE -->
|
|
|
<web-view src="/uni_modules/lime-signature/static/index.html" class="lime-signature__canvas" ref="webview"
|
|
|
@pagefinish="onPageFinish" @error="onError" @onPostMessage="onMessage"></web-view>
|
|
|
<!-- #endif -->
|
|
|
</view>
|
|
|
</template>
|
|
|
<!-- #ifdef APP-VUE -->
|
|
|
<script module="sign" lang="renderjs">
|
|
|
// #ifdef APP-VUE
|
|
|
// import { Signature } from '@signature'
|
|
|
import {
|
|
|
Signature
|
|
|
} from './signature'
|
|
|
// import {base64ToPath} from './utils'
|
|
|
|
|
|
export default {
|
|
|
data() {
|
|
|
return {
|
|
|
canvasid: null,
|
|
|
signature: null,
|
|
|
observer: null,
|
|
|
options: {},
|
|
|
saveCount: 0,
|
|
|
}
|
|
|
},
|
|
|
mounted() {
|
|
|
this.$nextTick(this.init)
|
|
|
},
|
|
|
methods: {
|
|
|
init() {
|
|
|
const el = this.$refs.limeSignature;
|
|
|
const canvas = document.createElement('canvas')
|
|
|
canvas.style = 'width:100%; height: 100%;'
|
|
|
el.appendChild(canvas)
|
|
|
this.signature = new Signature({
|
|
|
el: canvas
|
|
|
})
|
|
|
this.signature.pen.setOption(this.options)
|
|
|
const width = this.signature.canvas.get('width')
|
|
|
const height = this.signature.canvas.get('height')
|
|
|
|
|
|
this.emit({
|
|
|
changeSize: {
|
|
|
width,
|
|
|
height
|
|
|
}
|
|
|
})
|
|
|
},
|
|
|
undo(v) {
|
|
|
if (v && this.signature) {
|
|
|
this.signature.undo()
|
|
|
}
|
|
|
},
|
|
|
clear(v) {
|
|
|
if (v && this.signature) {
|
|
|
this.signature.clear()
|
|
|
}
|
|
|
},
|
|
|
save(v) {
|
|
|
if (v !== this.saveCount) {
|
|
|
this.saveCount = v;
|
|
|
const image = this.signature.canvas.get('el').toDataURL()
|
|
|
const {
|
|
|
backgroundColor
|
|
|
} = this.options
|
|
|
if (backgroundColor) {
|
|
|
const canvas = document.createElement('canvas')
|
|
|
const width = this.signature.canvas.get('width')
|
|
|
const height = this.signature.canvas.get('height')
|
|
|
const pixelRatio = this.signature.canvas.get('pixelRatio')
|
|
|
canvas.width = width * pixelRatio
|
|
|
canvas.height = height * pixelRatio
|
|
|
const context = canvas.getContext('2d')
|
|
|
context.scale(pixelRatio, pixelRatio)
|
|
|
context.fillStyle = backgroundColor
|
|
|
context.fillRect(0, 0, width, height)
|
|
|
context.drawImage(this.signature.canvas.get('el'), 0, 0, width, height)
|
|
|
this.emit({
|
|
|
save: canvas.toDataURL()
|
|
|
})
|
|
|
canvas.remove()
|
|
|
} else {
|
|
|
this.emit({
|
|
|
save: image
|
|
|
})
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// base64ToPath(image).then((res) => {
|
|
|
// this.emit({save: res})
|
|
|
// })
|
|
|
}
|
|
|
},
|
|
|
isEmpty(v) {
|
|
|
if (v && this.signature) {
|
|
|
const isEmpty = this.signature.isEmpty()
|
|
|
this.emit({
|
|
|
isEmpty
|
|
|
})
|
|
|
}
|
|
|
},
|
|
|
emit(event) {
|
|
|
this.$ownerInstance.callMethod('onMessage', {
|
|
|
detail: {
|
|
|
data: [{
|
|
|
event
|
|
|
}]
|
|
|
}
|
|
|
})
|
|
|
},
|
|
|
update(v) {
|
|
|
if (v) {
|
|
|
if (this.signature) {
|
|
|
this.options = v
|
|
|
this.signature.pen.setOption(v)
|
|
|
} else {
|
|
|
this.options = v
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
// #endif
|
|
|
</script>
|
|
|
<!-- #endif -->
|
|
|
|
|
|
|
|
|
<script>
|
|
|
// #ifndef APP-NVUE
|
|
|
import {
|
|
|
getCanvas2d,
|
|
|
wrapEvent,
|
|
|
requestAnimationFrame,
|
|
|
sleep
|
|
|
} from './utils'
|
|
|
import {
|
|
|
Signature
|
|
|
} from './signature'
|
|
|
// import {Signature} from '@signature';
|
|
|
import {
|
|
|
uniContext,
|
|
|
createImage,
|
|
|
toDataURL
|
|
|
} from './context'
|
|
|
// #endif
|
|
|
import {
|
|
|
base64ToPath
|
|
|
} from './utils'
|
|
|
export default {
|
|
|
props: {
|
|
|
styles: String,
|
|
|
disableScroll: Boolean,
|
|
|
type: {
|
|
|
type: String,
|
|
|
default: '2d'
|
|
|
},
|
|
|
// 画笔颜色
|
|
|
penColor: {
|
|
|
type: String,
|
|
|
default: 'black'
|
|
|
},
|
|
|
penSize: {
|
|
|
type: Number,
|
|
|
default: 2
|
|
|
},
|
|
|
// 画板背景颜色
|
|
|
backgroundColor: String,
|
|
|
// 笔锋
|
|
|
openSmooth: Boolean,
|
|
|
// 画笔最小值
|
|
|
minLineWidth: {
|
|
|
type: Number,
|
|
|
default: 2
|
|
|
},
|
|
|
// 画笔最大值
|
|
|
maxLineWidth: {
|
|
|
type: Number,
|
|
|
default: 6
|
|
|
},
|
|
|
// 画笔达到最小宽度所需最小速度(px/ms),取值范围1.0-10.0,值越小,画笔越容易变细,笔锋效果会比较明显,可以自行调整查看效果,选出自己满意的值。
|
|
|
minSpeed: {
|
|
|
type: Number,
|
|
|
default: 1.5
|
|
|
},
|
|
|
// 相邻两线宽度增(减)量最大百分比,取值范围1-100,为了达到笔锋效果,画笔宽度会随画笔速度而改变,如果相邻两线宽度差太大,过渡效果就会很突兀,使用maxWidthDiffRate限制宽度差,让过渡效果更自然。可以自行调整查看效果,选出自己满意的值。
|
|
|
maxWidthDiffRate: {
|
|
|
type: Number,
|
|
|
default: 20
|
|
|
},
|
|
|
// 限制历史记录数,即最大可撤销数,传入0则关闭历史记录功能
|
|
|
maxHistoryLength: {
|
|
|
type: Number,
|
|
|
default: 20
|
|
|
},
|
|
|
beforeDelay: {
|
|
|
type: Number,
|
|
|
default: 0
|
|
|
}
|
|
|
},
|
|
|
data() {
|
|
|
return {
|
|
|
canvasWidth: null,
|
|
|
canvasHeight: null,
|
|
|
useCanvas2d: true,
|
|
|
// #ifdef APP-PLUS
|
|
|
rclear: 0,
|
|
|
rundo: 0,
|
|
|
rsave: 0,
|
|
|
rempty: 0,
|
|
|
risEmpty: true,
|
|
|
toDataURL: null,
|
|
|
tempFilePath: [],
|
|
|
// #endif
|
|
|
}
|
|
|
},
|
|
|
computed: {
|
|
|
canvasId() {
|
|
|
return `lime-signature${this._uid||this._.uid}`
|
|
|
},
|
|
|
canvasStyle() {
|
|
|
const {
|
|
|
canvasWidth,
|
|
|
canvasHeight,
|
|
|
backgroundColor
|
|
|
} = this
|
|
|
return {
|
|
|
width: canvasWidth && (canvasWidth + 'px'),
|
|
|
height: canvasHeight && (canvasHeight + 'px'),
|
|
|
background: backgroundColor
|
|
|
}
|
|
|
},
|
|
|
param() {
|
|
|
const {
|
|
|
penColor,
|
|
|
penSize,
|
|
|
backgroundColor,
|
|
|
openSmooth,
|
|
|
minLineWidth,
|
|
|
maxLineWidth,
|
|
|
minSpeed,
|
|
|
maxWidthDiffRate,
|
|
|
maxHistoryLength,
|
|
|
disableScroll
|
|
|
} = this
|
|
|
return JSON.parse(JSON.stringify({
|
|
|
penColor,
|
|
|
penSize,
|
|
|
backgroundColor,
|
|
|
openSmooth,
|
|
|
minLineWidth,
|
|
|
maxLineWidth,
|
|
|
minSpeed,
|
|
|
maxWidthDiffRate,
|
|
|
maxHistoryLength,
|
|
|
disableScroll
|
|
|
}))
|
|
|
}
|
|
|
},
|
|
|
// #ifdef APP-NVUE
|
|
|
watch: {
|
|
|
param(v) {
|
|
|
this.$refs.webview.evalJS(`update(${JSON.stringify(v)})`)
|
|
|
}
|
|
|
},
|
|
|
// #endif
|
|
|
// #ifndef APP-PLUS
|
|
|
created() {
|
|
|
this.useCanvas2d = this.type === '2d' && getCanvas2d()
|
|
|
},
|
|
|
// #endif
|
|
|
// #ifndef APP-PLUS
|
|
|
async mounted() {
|
|
|
if (this.beforeDelay) {
|
|
|
await sleep(this.beforeDelay)
|
|
|
}
|
|
|
const config = await this.getContext()
|
|
|
this.signature = new Signature(config)
|
|
|
this.canvasEl = this.signature.canvas.get('el')
|
|
|
this.canvasWidth = this.signature.canvas.get('width')
|
|
|
this.canvasHeight = this.signature.canvas.get('height')
|
|
|
|
|
|
this.stopWatch = this.$watch('param', (v) => {
|
|
|
this.signature.pen.setOption(v)
|
|
|
}, {
|
|
|
immediate: true
|
|
|
})
|
|
|
},
|
|
|
// #endif
|
|
|
// #ifndef APP-PLUS
|
|
|
// #ifdef VUE3
|
|
|
beforeUnmount() {
|
|
|
this.stopWatch()
|
|
|
this.signature.destroy()
|
|
|
},
|
|
|
// #endif
|
|
|
// #ifdef VUE2
|
|
|
beforeDestroy() {
|
|
|
this.stopWatch()
|
|
|
this.signature.destroy()
|
|
|
},
|
|
|
// #endif
|
|
|
// #endif
|
|
|
methods: {
|
|
|
// #ifdef APP-PLUS
|
|
|
onPageFinish() {
|
|
|
this.$refs.webview.evalJS(`update(${JSON.stringify(this.param)})`)
|
|
|
},
|
|
|
onMessage(e = {}) {
|
|
|
const {
|
|
|
detail: {
|
|
|
data: [res]
|
|
|
}
|
|
|
} = e
|
|
|
if (res.event?.save) {
|
|
|
this.toDataURL = res.event.save
|
|
|
}
|
|
|
if (res.event?.changeSize) {
|
|
|
const {
|
|
|
width,
|
|
|
height
|
|
|
} = res.event.changeSize
|
|
|
}
|
|
|
if (res.event.hasOwnProperty('isEmpty')) {
|
|
|
this.risEmpty = res.event.isEmpty
|
|
|
}
|
|
|
if (res.event?.file) {
|
|
|
this.tempFilePath.push(res.event.file)
|
|
|
if (this.tempFilePath.length > 7) {
|
|
|
this.tempFilePath.shift()
|
|
|
}
|
|
|
return
|
|
|
}
|
|
|
if (res.event?.success) {
|
|
|
if (res.event.success) {
|
|
|
this.tempFilePath.push(res.event.success)
|
|
|
if (this.tempFilePath.length > 8) {
|
|
|
this.tempFilePath.shift()
|
|
|
}
|
|
|
this.toDataURL = this.tempFilePath.join('')
|
|
|
this.tempFilePath = []
|
|
|
// base64ToPath(this.tempFilePath.join('')).then(res => {
|
|
|
|
|
|
// })
|
|
|
} else {
|
|
|
this.$emit('fail', 'canvas no data')
|
|
|
}
|
|
|
return
|
|
|
}
|
|
|
},
|
|
|
// #endif
|
|
|
undo() {
|
|
|
// #ifdef APP-VUE || APP-NVUE
|
|
|
this.rundo += 1
|
|
|
// #endif
|
|
|
// #ifdef APP-NVUE
|
|
|
this.$refs.webview.evalJS(`undo()`)
|
|
|
// #endif
|
|
|
// #ifndef APP-VUE
|
|
|
if (this.signature)
|
|
|
this.signature.undo()
|
|
|
// #endif
|
|
|
},
|
|
|
clear() {
|
|
|
// #ifdef APP-VUE || APP-NVUE
|
|
|
this.rclear += 1
|
|
|
// #endif
|
|
|
// #ifdef APP-NVUE
|
|
|
this.$refs.webview.evalJS(`clear()`)
|
|
|
// #endif
|
|
|
// #ifndef APP-VUE
|
|
|
if (this.signature)
|
|
|
this.signature.clear()
|
|
|
// #endif
|
|
|
},
|
|
|
isEmpty() {
|
|
|
// #ifdef APP-NVUE
|
|
|
this.$refs.webview.evalJS(`isEmpty()`)
|
|
|
// #endif
|
|
|
// #ifdef APP-VUE || APP-NVUE
|
|
|
this.rempty += 1
|
|
|
// #endif
|
|
|
// #ifndef APP-VUE || APP-NVUE
|
|
|
return this.signature.isEmpty()
|
|
|
// #endif
|
|
|
},
|
|
|
canvasToTempFilePath(param) {
|
|
|
const isEmpty = this.isEmpty()
|
|
|
// #ifdef APP-NVUE
|
|
|
this.$refs.webview.evalJS(`save()`)
|
|
|
// #endif
|
|
|
// #ifdef APP-VUE || APP-NVUE
|
|
|
const stopURLWatch = this.$watch('toDataURL', (v, n) => {
|
|
|
if (v && v !== n) {
|
|
|
if (param.pathType == 'url') {
|
|
|
base64ToPath(v).then(res => {
|
|
|
param.success({
|
|
|
tempFilePath: res,
|
|
|
isEmpty: this.risEmpty
|
|
|
})
|
|
|
})
|
|
|
} else {
|
|
|
param.success({
|
|
|
tempFilePath: v,
|
|
|
isEmpty: this.risEmpty
|
|
|
})
|
|
|
}
|
|
|
this.toDataURL = ''
|
|
|
}
|
|
|
stopURLWatch && stopURLWatch()
|
|
|
})
|
|
|
this.rsave += 1
|
|
|
// #endif
|
|
|
// #ifndef APP-VUE || APP-NVUE
|
|
|
const success = (success) => param.success && param.success(success)
|
|
|
const fail = (fail) => param.fail && param.fail(err)
|
|
|
const {
|
|
|
canvas
|
|
|
} = this.signature.canvas.get('el')
|
|
|
const context = this.signature.canvas.get('context')
|
|
|
const {
|
|
|
backgroundColor
|
|
|
} = this
|
|
|
const width = this.signature.canvas.get('width')
|
|
|
const height = this.signature.canvas.get('height')
|
|
|
|
|
|
if (this.useCanvas2d) {
|
|
|
try {
|
|
|
// #ifndef MP-ALIPAY
|
|
|
const tempFilePath = canvas.toDataURL()
|
|
|
if (backgroundColor) {
|
|
|
const image = canvas.createImage()
|
|
|
image.src = tempFilePath
|
|
|
image.onload = () => {
|
|
|
context.fillStyle = backgroundColor
|
|
|
context.fillRect(0, 0, width, height)
|
|
|
context.drawImage(image, 0, 0, width, height);
|
|
|
const tempFilePath = canvas.toDataURL()
|
|
|
success({
|
|
|
tempFilePath,
|
|
|
isEmpty
|
|
|
})
|
|
|
context.clearRect(0, 0, width, height)
|
|
|
context.drawImage(image, 0, 0, width, height);
|
|
|
}
|
|
|
} else {
|
|
|
success({
|
|
|
tempFilePath,
|
|
|
isEmpty
|
|
|
})
|
|
|
}
|
|
|
// #endif
|
|
|
// #ifdef MP-ALIPAY
|
|
|
canvas.toTempFilePath({
|
|
|
canvasid: this.canvasid,
|
|
|
success(res) {
|
|
|
if (backgroundColor) {
|
|
|
const image = canvas.createImage()
|
|
|
image.src = tempFilePath
|
|
|
image.onload = () => {
|
|
|
canvas.toTempFilePath({
|
|
|
canvasid: this.canvasid,
|
|
|
success(res) {
|
|
|
context.fillStyle = backgroundColor
|
|
|
context.fillRect(0, 0, width, height)
|
|
|
context.drawImage(image, 0, 0, width, height);
|
|
|
success({
|
|
|
tempFilePath,
|
|
|
isEmpty
|
|
|
})
|
|
|
context.clearRect(0, 0, width, height)
|
|
|
context.drawImage(image, 0, 0, width, height);
|
|
|
}
|
|
|
})
|
|
|
|
|
|
}
|
|
|
} else {
|
|
|
success({
|
|
|
tempFilePath: res,
|
|
|
isEmpty
|
|
|
})
|
|
|
}
|
|
|
},
|
|
|
fail
|
|
|
})
|
|
|
// #endif
|
|
|
} catch (err) {
|
|
|
console.warn(err)
|
|
|
fail(err)
|
|
|
}
|
|
|
} else {
|
|
|
toDataURL(this.canvasId, this).then(res => {
|
|
|
if (backgroundColor) {
|
|
|
const image = createImage()
|
|
|
image.src = res
|
|
|
image.onload = () => {
|
|
|
context.fillStyle = backgroundColor
|
|
|
context.fillRect(0, 0, width, height)
|
|
|
context.drawImage(image, 0, 0, width, height);
|
|
|
context.draw && context.draw(true, () => {
|
|
|
toDataURL(this.canvasId, this).then(res => {
|
|
|
success({
|
|
|
tempFilePath: res,
|
|
|
isEmpty
|
|
|
})
|
|
|
context.clearRect(0, 0, width, height)
|
|
|
context.drawImage(image, 0, 0, width, height);
|
|
|
context.draw && context.draw(true)
|
|
|
})
|
|
|
});
|
|
|
|
|
|
}
|
|
|
} else {
|
|
|
success({
|
|
|
tempFilePath: res,
|
|
|
isEmpty
|
|
|
})
|
|
|
}
|
|
|
}).catch(err => {
|
|
|
console.warn(err)
|
|
|
fail(err)
|
|
|
})
|
|
|
}
|
|
|
// #endif
|
|
|
},
|
|
|
// #ifndef APP-PLUS
|
|
|
getContext() {
|
|
|
const {
|
|
|
pixelRatio
|
|
|
} = uni.getSystemInfoSync()
|
|
|
return new Promise(resolve => {
|
|
|
if (this.useCanvas2d) {
|
|
|
uni.createSelectorQuery().in(this)
|
|
|
.select(`#${this.canvasId}`)
|
|
|
.fields({
|
|
|
node: true,
|
|
|
size: true,
|
|
|
rect: true,
|
|
|
})
|
|
|
.exec(res => {
|
|
|
if (res) {
|
|
|
const {
|
|
|
width,
|
|
|
height,
|
|
|
node,
|
|
|
left,
|
|
|
top,
|
|
|
right
|
|
|
} = res[0]
|
|
|
const context = node.getContext('2d')
|
|
|
node.width = width * pixelRatio;
|
|
|
node.height = height * pixelRatio;
|
|
|
resolve({
|
|
|
left,
|
|
|
top,
|
|
|
right,
|
|
|
width,
|
|
|
height,
|
|
|
context,
|
|
|
canvas: node,
|
|
|
pixelRatio
|
|
|
})
|
|
|
}
|
|
|
})
|
|
|
} else {
|
|
|
uni.createSelectorQuery().in(this)
|
|
|
.select(`#${this.canvasId}`)
|
|
|
.boundingClientRect()
|
|
|
.exec(res => {
|
|
|
if (res) {
|
|
|
const {
|
|
|
width,
|
|
|
height,
|
|
|
left,
|
|
|
top,
|
|
|
right
|
|
|
} = res[0]
|
|
|
const context = uniContext(uni.createCanvasContext(this.canvasId, this))
|
|
|
const canvas = {
|
|
|
createImage,
|
|
|
toDataURL: () => toDataURL(this.canvasId, this),
|
|
|
requestAnimationFrame
|
|
|
}
|
|
|
resolve({
|
|
|
left,
|
|
|
top,
|
|
|
right,
|
|
|
width,
|
|
|
height,
|
|
|
context,
|
|
|
pixelRatio: 1,
|
|
|
canvas
|
|
|
})
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
})
|
|
|
},
|
|
|
touchStart(e) {
|
|
|
if (!this.canvasEl) return
|
|
|
this.isStart = true
|
|
|
this.canvasEl.dispatchEvent('touchstart', wrapEvent(e))
|
|
|
},
|
|
|
touchMove(e) {
|
|
|
if (!this.canvasEl || !this.isStart && this.canvasEl) return
|
|
|
this.canvasEl.dispatchEvent('touchmove', wrapEvent(e))
|
|
|
},
|
|
|
touchEnd(e) {
|
|
|
if (!this.canvasEl) return
|
|
|
this.isStart = false
|
|
|
this.canvasEl.dispatchEvent('touchend', wrapEvent(e))
|
|
|
},
|
|
|
// #endif
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
<style lang="stylus">
|
|
|
.lime-signature,
|
|
|
.lime-signature__canvas {
|
|
|
// #ifndef APP-NVUE
|
|
|
width: 100%;
|
|
|
height: 100% // #endif
|
|
|
// #ifdef APP-NVUE
|
|
|
flex: 1;
|
|
|
// #endif}
|
|
|
</style>
|