473 lines
12 KiB
Plaintext
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.

<template>
<view class="l-signature" ref="signatureRef" :style="[drawableStyle]">
<!-- #ifdef APP -->
<view class="l-signature-landscape" ref="signatureLandscapeRef" v-if="landscape && url !=''"
:style="[landscapeStyle]">
<image class="l-signature-image" :style="[landscapeImageStyle]" :src="url"></image>
</view>
<!-- #endif -->
<!-- #ifndef APP -->
<canvas id="l-signature" class="l-signature__canvas" @touchstart="touchstart" @touchmove.stop="touchmove"
@touchend="touchend">
</canvas>
<!-- #endif -->
</view>
</template>
<script lang="uts" setup>
// @ts-nocheck
// #ifdef APP
import { Signature } from './signature.uts'
// #endif
// #ifndef APP
import { Signature } from './signature.js'
import { wrapEvent } from './utils'
// #endif
import { LSignatureToTempFilePathOptions, LSignatureToFileSuccess, LSignatureOptions } from '../../index.uts'
// type SignatureToFileSuccessCallback = (res : UTSJSONObject) => void
// type SignatureToFileFailCallback = (res : TakeSnapshotFail) => void
// type SignatureToFileCompleteCallback = (res : any) => void
/**
* LimeSignature 手写板签名
* @description 手写板签名插件,uvue专用版。
* @tutorial https://ext.dcloud.net.cn/plugin?id=4354
* @property {Number} penSize 画笔大小
* @property {String} penColor 画笔颜色
* @property {String} backgroundColor 背景颜色,不填则为透明
* @property {Boolean} disableScroll 当在写字时禁止屏幕滚动以及下拉刷新nvue无效
*/
const emit = defineEmits(['change'])
const props = defineProps({
styles: {
type: String,
default: ''
},
penColor: {
type: String,
default: 'black'
},
penSize: {
type: Number,
default: 2
},
backgroundColor: {
type: String,
default: ''
},
openSmooth: {
type: Boolean,
default: false
},
minLineWidth: {
type: Number,
default: 2
},
maxLineWidth: {
type: Number,
default: 6
},
minSpeed: {
type: Number,
default: 1.5
},
maxWidthDiffRate: {
type: Number,
default: 20
},
maxHistoryLength: {
type: Number,
default: 20
},
disableScroll: {
type: Boolean,
default: true
},
disabled: {
type: Boolean,
default: false
},
landscape: {
type: Boolean,
default: false
},
})
const drawableStyle = computed<string>(() : string => {
let style : string = ''
if (props.backgroundColor != '') {
style += `background-color: ${props.backgroundColor};`
}
if (props.styles != '') {
style += props.styles
}
return style
})
const signatureRef = ref<UniElement | null>(null)
let signatureLandscapeRef = ref<UniElement | null>(null)
let landscapeStyle = ref<Map<string, string>>(new Map())
let landscapeImageStyle = ref<Map<string, string>>(new Map())
let isCanvasEmpty = true
let signature : Signature | null = null
let url = ref('')
// 检查并触发事件的方法
const checkAndEmitEmptyStatus = () => {
// #ifndef APP
// @ts-ignore
const isEmpty = signature?.isEmpty() ?? true
// #endif
// #ifdef APP
// @ts-ignore
const isEmpty = signature?.isEmpty ?? true
// #endif
if (isEmpty != isCanvasEmpty) {
isCanvasEmpty = isEmpty
emit('change', isCanvasEmpty)
}
}
// #ifndef APP
let canvas : HTMLCanvasElement | null = null;
const getTouch = (event : UniTouchEvent) => {
const touch = event.touches.length ? event.touches[0] : event.changedTouches[0];
return {
points: [
{
x: touch.clientX, //- rect.left,
y: touch.clientY //- rect.top
}
]
}
}
let touchstart = (e : UniTouchEvent) => {
// #ifndef WEB
signature!.canvas.emit('touchstart', getTouch(e))
// #endif
}
let touchmove = (e : UniTouchEvent) => {
// #ifndef WEB
signature!.canvas.emit('touchmove', getTouch(e))
// #endif
}
let touchend = (e : UniTouchEvent) => {
// #ifndef WEB
signature!.canvas.emit('touchend', getTouch(e))
// #endif
setTimeout(() => {
checkAndEmitEmptyStatus()
}, 0); // 绘制结束时检查
}
// #endif
const clear = () => {
signature?.clear()
checkAndEmitEmptyStatus() // 状态检查
}
const redo = () => {
signature?.redo()
checkAndEmitEmptyStatus() // 状态检查
}
const undo = () => {
signature?.undo()
checkAndEmitEmptyStatus() // 状态检查
}
const canvasToTempFilePath = (options : LSignatureToTempFilePathOptions) => {
const success = options.success // as SignatureToFileSuccessCallback | null
const fail = options.fail // as SignatureToFileFailCallback | null
const complete = options.complete// as SignatureToFileCompleteCallback | null
const format = options.format ?? 'png'
// #ifdef APP
signatureRef.value?.takeSnapshot({
format,
success: (res) => {
if (props.landscape) {
url.value = res.tempFilePath;
setTimeout(() => {
signatureLandscapeRef.value?.takeSnapshot({
format,
success: (res2) => {
success?.({
tempFilePath: res2.tempFilePath,
isEmpty: signature?.isEmpty ?? false
} as LSignatureToFileSuccess)
}
})
}, 300)
} else {
success?.({
tempFilePath: res.tempFilePath,
isEmpty: signature?.isEmpty ?? false
} as LSignatureToFileSuccess)
}
},
fail: (res) => {
fail?.(res)
},
complete: (res) => {
complete?.(res)
}
} as TakeSnapshotOptions)
// #endif
// #ifndef APP
// @ts-ignore
const { backgroundColor, backgroundImage, landscape, boundingBox } = props
const { quality = 1 } = options
const flag = landscape || backgroundColor || boundingBox
const type = `image/${format}`.replace(/jpg/, 'jpeg');
const image = canvas?.toDataURL(!flag && type, !flag && quality)
if (flag) {
// @ts-ignore
// #ifdef WEB
const offCanvas = document.createElement('canvas')
// #endif
// #ifndef WEB
const offCanvas = uni.createOffscreenCanvas({ type: '2d' });
// #endif
// @ts-ignore
const pixelRatio = signature?.canvas.get('pixelRatio')
// @ts-ignore
let width = signature?.canvas.get('width')
// @ts-ignore
let height = signature?.canvas.get('height')
let x = 0
let y = 0
// @ts-ignore
const next = () => {
const size = [width, height]
if (landscape) {
size.reverse()
}
offCanvas.width = size[0] * pixelRatio
offCanvas.height = size[1] * pixelRatio
const param = [x, y, width, height, 0, 0, width, height].map(item => item * pixelRatio)
const context = offCanvas.getContext('2d')
if (landscape) {
context.translate(0, width * pixelRatio)
context.rotate(-Math.PI / 2)
}
if (backgroundColor) {
context.fillStyle = backgroundColor
context.fillRect(0, 0, width * pixelRatio, height * pixelRatio)
}
const drawImage = () => {
// @ts-ignore
context.drawImage(image, ...param)//signature?.canvas!.get('el')
success?.({
tempFilePath: offCanvas.toDataURL(type, quality),
// @ts-ignore
isEmpty: signature?.isEmpty() ?? false
} as LSignatureToFileSuccess)
offCanvas.remove()
}
if (backgroundImage) {
const img = new Image();
img.onload = () => {
context.drawImage(img, ...param)
drawImage()
}
img.src = backgroundImage
}
if (!backgroundImage) {
drawImage()
}
}
if (boundingBox) {
// @ts-ignore
const res = signature?.getContentBoundingBox()
width = res.width
height = res.height
x = res.startX
y = res.startY
next()
} else {
next()
}
} else {
success?.({
tempFilePath: image,
// @ts-ignore
isEmpty: signature?.isEmpty() ?? false
} as LSignatureToFileSuccess)
}
// #endif
}
defineExpose({
clear,
redo,
undo,
canvasToTempFilePath,
})
// #ifndef APP
// #ifdef WEB
let _touchstart, _touchmove, _touchend
// #endif
const instance = getCurrentInstance()!.proxy!;
uni.createCanvasContextAsync({
id: 'l-signature',
component: instance,
success: (context : CanvasContext) => {
const canvasContext = context.getContext('2d')!;
canvas = canvasContext.canvas;
// 处理高清屏逻辑
const dpr = uni.getDeviceInfo().devicePixelRatio ?? 1;
canvas.width = canvas.offsetWidth * dpr;
canvas.height = canvas.offsetHeight * dpr;
canvasContext.scale(dpr, dpr); // 仅需调用一次,当调用 reset 方法后需要再次 scale
signature = new Signature({
// @ts-ignore
context: canvasContext,
width: canvas.offsetWidth,
height: canvas.offsetHeight,
canvas: context,
pixelRatio: 1,
})
// signature = new Signature({el: canvas})
watchEffect(() => {
const options : LSignatureOptions = {
penColor: props.penColor,
openSmooth: props.openSmooth,
disableScroll: props.disableScroll,
disabled: props.disabled,
penSize: props.penSize,
minLineWidth: props.minLineWidth,
maxLineWidth: props.maxLineWidth,
minSpeed: props.minSpeed,
maxWidthDiffRate: props.maxWidthDiffRate,
maxHistoryLength: props.maxHistoryLength
}
// @ts-ignore
signature?.pen.setOption(options)
})
// #ifdef WEB
let isTouch = false
_touchstart = (event : UniMouseEvent) => {
isTouch = true
const rect = canvas?.getBoundingClientRect()
// @ts-ignore
signature!.canvas.emit('touchstart', {
points: [
{
x: event.clientX - rect.left,
y: event.clientY - rect.top
}
]
})
}
_touchmove = (event : UniMouseEvent) => {
if (!isTouch) return
const rect = canvas?.getBoundingClientRect()
// @ts-ignore
signature!.canvas.emit('touchmove', {
points: [
{
x: event.clientX - rect.left,
y: event.clientY - rect.top
}
]
})
}
_touchend = (event : UniMouseEvent) => {
isTouch = false
const rect = canvas?.getBoundingClientRect();
// @ts-ignore
signature!.canvas.emit('touchend', {
points: [
{
x: event.clientX - rect.left,
y: event.clientY - rect.top
}
]
})
setTimeout(function() {
checkAndEmitEmptyStatus()
}, 0); // 绘制结束时检查
}
canvas?.addEventListener('mousedown', _touchstart)
canvas?.addEventListener('mousemove', _touchmove)
canvas?.addEventListener('mouseup', _touchend)
// canvas?.addEventListener('mouseleave', _touchend)
// #endif
}
})
// #endif
onMounted(() => {
nextTick(() => {
const width = signatureRef.value?.offsetWidth
const height = signatureRef.value?.offsetHeight
// #ifdef APP
landscapeStyle.value.set('width', `${height}px`)
landscapeStyle.value.set('height', `${width}px`)
landscapeImageStyle.value.set('width', `${width}px`)
landscapeImageStyle.value.set('height', `${height}px`)
landscapeImageStyle.value.set('transform', `rotate(-90deg) translateY(${width}px)`)
signature = new Signature(signatureRef.value!)
watchEffect(() => {
const options : LSignatureOptions = {
penColor: props.penColor,
openSmooth: props.openSmooth,
disableScroll: props.disableScroll,
disabled: props.disabled,
penSize: props.penSize,
minLineWidth: props.minLineWidth,
maxLineWidth: props.maxLineWidth,
minSpeed: props.minSpeed,
maxWidthDiffRate: props.maxWidthDiffRate,
maxHistoryLength: props.maxHistoryLength
}
// #ifdef APP
signature?.setOption(options)
signature?.onChange((_isEmpty: boolean)=>{
checkAndEmitEmptyStatus()
})
// #endif
})
// #endif
})
})
onUnmounted(() => {
// #ifdef WEB
canvas?.removeEventListener('mousedown', _touchstart)
canvas?.removeEventListener('mousemove', _touchmove)
canvas?.removeEventListener('mouseup', _touchend)
// canvas?.removeEventListener('mouseleave', touchend)
canvas?.remove()
// #endif
})
</script>
<style lang="scss">
.l-signature {
flex: 1;
&__canvas {
width: 100%;
height: 100%;
}
&-landscape {
position: absolute;
pointer-events: none;
left: 1000rpx;
}
&-image {
transform-origin: 0% 0%;
}
}
</style>