631 lines
18 KiB
Vue
631 lines
18 KiB
Vue
<template>
|
||
<view class="wd-signature">
|
||
<view class="wd-signature__content">
|
||
<!-- #ifdef MP-WEIXIN -->
|
||
<canvas
|
||
class="wd-signature__content-canvas"
|
||
:style="canvasStyle"
|
||
:width="canvasState.canvasWidth"
|
||
:height="canvasState.canvasHeight"
|
||
:canvas-id="canvasId"
|
||
:id="canvasId"
|
||
:disable-scroll="disableScroll"
|
||
@touchstart="startDrawing"
|
||
@touchend="stopDrawing"
|
||
@touchmove="draw"
|
||
type="2d"
|
||
/>
|
||
<!-- #endif -->
|
||
<!-- #ifndef MP-WEIXIN -->
|
||
<canvas
|
||
class="wd-signature__content-canvas"
|
||
:canvas-id="canvasId"
|
||
:style="canvasStyle"
|
||
:width="canvasState.canvasWidth"
|
||
:height="canvasState.canvasHeight"
|
||
:id="canvasId"
|
||
:disable-scroll="disableScroll"
|
||
@touchstart="startDrawing"
|
||
@touchend="stopDrawing"
|
||
@touchmove="draw"
|
||
/>
|
||
<!-- #endif -->
|
||
</view>
|
||
<view class="wd-signature__footer">
|
||
<slot
|
||
name="footer"
|
||
:clear="clear"
|
||
:confirm="confirmSignature"
|
||
:current-step="currentStep"
|
||
:revoke="revoke"
|
||
:restore="restore"
|
||
:can-undo="lines.length > 0"
|
||
:can-redo="redoLines.length > 0"
|
||
:history-list="lines"
|
||
>
|
||
<block v-if="enableHistory">
|
||
<wd-button size="small" plain @click="revoke" :disabled="lines.length <= 0">
|
||
{{ revokeText || translate('revokeText') }}
|
||
</wd-button>
|
||
<wd-button size="small" plain @click="restore" :disabled="redoLines.length <= 0">
|
||
{{ restoreText || translate('restoreText') }}
|
||
</wd-button>
|
||
</block>
|
||
<wd-button size="small" plain @click="clear">{{ clearText || translate('clearText') }}</wd-button>
|
||
<wd-button size="small" @click="confirmSignature">{{ confirmText || translate('confirmText') }}</wd-button>
|
||
</slot>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
<script lang="ts">
|
||
export default {
|
||
name: 'wd-signature',
|
||
options: {
|
||
addGlobalClass: true,
|
||
virtualHost: true,
|
||
styleIsolation: 'shared'
|
||
}
|
||
}
|
||
</script>
|
||
<script lang="ts" setup>
|
||
import { computed, getCurrentInstance, onBeforeMount, onMounted, reactive, ref, watch, type CSSProperties } from 'vue'
|
||
import { addUnit, getRect, isDef, objToStyle, uuid } from '../common/util'
|
||
import { signatureProps, type SignatureExpose, type SignatureResult, type Point, type Line } from './types'
|
||
import { useTranslate } from '../composables/useTranslate'
|
||
// #ifdef MP-WEIXIN
|
||
import { canvas2dAdapter } from '../common/canvasHelper'
|
||
// #endif
|
||
|
||
const props = defineProps(signatureProps)
|
||
const emit = defineEmits(['start', 'end', 'signing', 'confirm', 'clear'])
|
||
const { translate } = useTranslate('signature')
|
||
const { proxy } = getCurrentInstance() as any
|
||
const canvasId = ref<string>(`signature${uuid()}`) // canvas 组件的唯一标识符
|
||
let canvas: null = null //canvas对象 微信小程序生成图片必须传入
|
||
const drawing = ref<boolean>(false) // 是否正在绘制
|
||
const pixelRatio = ref<number>(1) // 像素比
|
||
|
||
const canvasState = reactive({
|
||
canvasWidth: 0,
|
||
canvasHeight: 0,
|
||
ctx: null as UniApp.CanvasContext | null // canvas上下文
|
||
})
|
||
|
||
/**
|
||
* 判断颜色是否为透明色
|
||
* @param color 颜色值(支持 rgba/hsla/hex/transparent)
|
||
*/
|
||
function isTransparentColor(color: string | undefined): boolean {
|
||
if (!color) return true
|
||
const transparentKeywords = ['transparent', '#0000', '#00000000']
|
||
|
||
// 标准透明关键字
|
||
if (transparentKeywords.includes(color.toLowerCase())) {
|
||
return true
|
||
}
|
||
|
||
// 匹配 rgba(r, g, b, a)
|
||
const rgbaMatch = color.match(/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*(\d*\.?\d+))?\)$/i)
|
||
if (rgbaMatch) {
|
||
const alpha = rgbaMatch[4] ? parseFloat(rgbaMatch[4]) : 1
|
||
return alpha === 0
|
||
}
|
||
|
||
// 匹配 hsla(h, s, l, a)
|
||
const hslaMatch = color.match(/^hsla?\(\s*(\d+)(?:deg)?\s*,\s*(\d+)%\s*,\s*(\d+)%(?:\s*,\s*(\d*\.?\d+))?\)$/i)
|
||
if (hslaMatch) {
|
||
const alpha = hslaMatch[4] ? parseFloat(hslaMatch[4]) : 1
|
||
return alpha === 0
|
||
}
|
||
|
||
// 匹配 #RRGGBBAA 或 #RGBA
|
||
const hexMatch = color.match(/^#([0-9a-f]{8}|[0-9a-f]{4})$/i)
|
||
if (hexMatch) {
|
||
const hex = hexMatch[1]
|
||
const alphaHex = hex.length === 8 ? hex.slice(6, 8) : hex.slice(3, 4).repeat(2)
|
||
const alpha = parseInt(alphaHex, 16)
|
||
return alpha === 0
|
||
}
|
||
|
||
return false
|
||
}
|
||
|
||
watch(
|
||
() => props.penColor,
|
||
() => {
|
||
setLine()
|
||
}
|
||
)
|
||
|
||
watch(
|
||
() => props.lineWidth,
|
||
() => {
|
||
setLine()
|
||
}
|
||
)
|
||
|
||
const canvasStyle = computed(() => {
|
||
const style: CSSProperties = {}
|
||
if (isDef(props.width)) {
|
||
style.width = addUnit(props.width)
|
||
}
|
||
|
||
if (isDef(props.height)) {
|
||
style.height = addUnit(props.height)
|
||
}
|
||
|
||
return `${objToStyle(style)}`
|
||
})
|
||
|
||
const disableScroll = computed(() => props.disableScroll)
|
||
const enableHistory = computed(() => props.enableHistory)
|
||
|
||
const lines = ref<Line[]>([]) // 保存所有线条
|
||
const redoLines = ref<Line[]>([]) // 保存撤销的线条
|
||
const currentLine = ref<Line>() // 当前正在绘制的线
|
||
const currentStep = ref(0) // 当前步骤
|
||
|
||
// 添加计算笔画宽度的方法
|
||
function calculateLineWidth(speed: number): number {
|
||
if (!props.pressure) return props.lineWidth
|
||
|
||
const minSpeed = props.minSpeed || 1.5
|
||
const limitedSpeed = Math.min(minSpeed * 10, Math.max(minSpeed, speed))
|
||
const addWidth = ((props.maxWidth - props.minWidth) * (limitedSpeed - minSpeed)) / minSpeed
|
||
const lineWidth = Math.max(props.maxWidth - addWidth, props.minWidth)
|
||
return Math.min(lineWidth, props.maxWidth)
|
||
}
|
||
|
||
/* 获取默认笔画宽度 */
|
||
const getDefaultLineWidth = () => {
|
||
if (props.pressure) {
|
||
// 在压感模式下,使用最大和最小宽度的平均值作为默认值
|
||
return (props.maxWidth + props.minWidth) / 2
|
||
}
|
||
return props.lineWidth
|
||
}
|
||
|
||
/* 开始画线 */
|
||
const startDrawing = (e: any) => {
|
||
e.preventDefault()
|
||
drawing.value = true
|
||
setLine()
|
||
emit('start', e)
|
||
|
||
// 创建新线条,同时保存当前的所有绘制参数
|
||
const { x, y } = e.touches[0]
|
||
currentLine.value = {
|
||
points: [
|
||
{
|
||
x,
|
||
y,
|
||
t: Date.now() // 使用 t 替换 width
|
||
}
|
||
],
|
||
color: props.penColor,
|
||
width: getDefaultLineWidth(),
|
||
backgroundColor: props.backgroundColor,
|
||
isPressure: props.pressure // 添加笔锋模式标记
|
||
}
|
||
|
||
// 清空重做记录
|
||
redoLines.value = []
|
||
draw(e)
|
||
}
|
||
|
||
/* 结束画线 */
|
||
const stopDrawing = (e: TouchEvent) => {
|
||
e.preventDefault()
|
||
drawing.value = false
|
||
if (currentLine.value) {
|
||
// 保存完整的线条信息,包括所有点的参数
|
||
lines.value.push({
|
||
...currentLine.value,
|
||
points: currentLine.value.points.map((point) => ({
|
||
...point,
|
||
t: point.t,
|
||
speed: point.speed,
|
||
distance: point.distance,
|
||
lineWidth: point.lineWidth,
|
||
lastX1: point.lastX1,
|
||
lastY1: point.lastY1,
|
||
lastX2: point.lastX2,
|
||
lastY2: point.lastY2,
|
||
isFirstPoint: point.isFirstPoint
|
||
}))
|
||
})
|
||
currentStep.value = lines.value.length
|
||
}
|
||
currentLine.value = undefined
|
||
const { ctx } = canvasState
|
||
if (ctx) ctx.beginPath()
|
||
emit('end', e)
|
||
}
|
||
|
||
/**
|
||
* 初始化 canvas
|
||
* @param forceUpdate 是否强制更新
|
||
*/
|
||
const initCanvas = (forceUpdate: boolean = false) => {
|
||
// 如果不是强制更新,且已经初始化过 canvas,则不再重复初始化
|
||
if (!forceUpdate && canvasState.canvasHeight && canvasState.canvasWidth) {
|
||
return
|
||
}
|
||
getContext().then(() => {
|
||
const { ctx } = canvasState
|
||
if (ctx && isDef(props.backgroundColor)) {
|
||
ctx.setFillStyle(props.backgroundColor)
|
||
ctx.fillRect(0, 0, canvasState.canvasWidth, canvasState.canvasHeight)
|
||
ctx.draw()
|
||
}
|
||
})
|
||
}
|
||
|
||
// 清空 canvas
|
||
const clear = () => {
|
||
lines.value = []
|
||
redoLines.value = []
|
||
currentStep.value = 0
|
||
clearCanvas()
|
||
emit('clear')
|
||
}
|
||
|
||
// 确认签名
|
||
const confirmSignature = () => {
|
||
canvasToImage()
|
||
}
|
||
|
||
//canvas划线
|
||
const draw = (e: any) => {
|
||
e.preventDefault()
|
||
const { ctx } = canvasState
|
||
|
||
if (!drawing.value || props.disabled || !ctx) return
|
||
const { x, y } = e.touches[0]
|
||
|
||
const point: Point = {
|
||
x,
|
||
y,
|
||
t: Date.now()
|
||
}
|
||
|
||
if (currentLine.value) {
|
||
const points = currentLine.value.points
|
||
const prePoint = points[points.length - 1]
|
||
|
||
if (prePoint.t === point.t || (prePoint.x === x && prePoint.y === y)) {
|
||
return
|
||
}
|
||
|
||
// 计算点的速度和距离
|
||
point.distance = Math.sqrt(Math.pow(point.x - prePoint.x, 2) + Math.pow(point.y - prePoint.y, 2))
|
||
point.speed = point.distance / (point.t - prePoint.t || 0.1)
|
||
|
||
if (props.pressure) {
|
||
point.lineWidth = calculateLineWidth(point.speed)
|
||
// 处理线宽变化率限制
|
||
if (points.length >= 2) {
|
||
const prePoint2 = points[points.length - 2]
|
||
if (prePoint2.lineWidth && prePoint.lineWidth) {
|
||
const rate = (point.lineWidth - prePoint.lineWidth) / prePoint.lineWidth
|
||
const maxRate = 0.2 // 最大变化率20%
|
||
if (Math.abs(rate) > maxRate) {
|
||
const per = rate > 0 ? maxRate : -maxRate
|
||
point.lineWidth = prePoint.lineWidth * (1 + per)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
points.push(point)
|
||
|
||
// 非笔锋模式直接使用线段连接
|
||
if (!props.pressure) {
|
||
ctx.beginPath()
|
||
ctx.moveTo(prePoint.x, prePoint.y)
|
||
ctx.lineTo(point.x, point.y)
|
||
ctx.stroke()
|
||
ctx.draw(true)
|
||
} else if (points.length >= 2) {
|
||
// 笔锋模式使用贝塞尔曲线
|
||
drawSmoothLine(prePoint, point)
|
||
}
|
||
}
|
||
|
||
emit('signing', e)
|
||
}
|
||
|
||
/* 重绘整个画布 */
|
||
const redrawCanvas = () => {
|
||
const { ctx } = canvasState
|
||
if (!ctx) return
|
||
|
||
// 清除画布并设置背景
|
||
if (!isTransparentColor(props.backgroundColor)) {
|
||
ctx.setFillStyle(props.backgroundColor as string)
|
||
ctx.fillRect(0, 0, canvasState.canvasWidth, canvasState.canvasHeight)
|
||
} else {
|
||
ctx.clearRect(0, 0, canvasState.canvasWidth, canvasState.canvasHeight)
|
||
}
|
||
|
||
// 如果没有线条,只需要清空画布
|
||
if (lines.value.length === 0) {
|
||
ctx.draw()
|
||
return
|
||
}
|
||
|
||
// 收集所有绘制操作,最后一次性 draw
|
||
lines.value.forEach((line) => {
|
||
if (!line.points.length) return
|
||
|
||
ctx.setStrokeStyle(line.color)
|
||
ctx.setLineJoin('round')
|
||
ctx.setLineCap('round')
|
||
|
||
if (line.isPressure && props.pressure) {
|
||
// 笔锋模式的重绘
|
||
line.points.forEach((point, index) => {
|
||
if (index === 0) return
|
||
const prePoint = line.points[index - 1]
|
||
const dis_x = point.x - prePoint.x
|
||
const dis_y = point.y - prePoint.y
|
||
const distance = Math.sqrt(dis_x * dis_x + dis_y * dis_y)
|
||
|
||
if (distance <= 2) {
|
||
point.lastX1 = point.lastX2 = prePoint.x + dis_x * 0.5
|
||
point.lastY1 = point.lastY2 = prePoint.y + dis_y * 0.5
|
||
} else {
|
||
const speed = point.speed || 0
|
||
const minSpeed = props.minSpeed || 1.5
|
||
const speedFactor = Math.max(0.1, Math.min(0.9, speed / (minSpeed * 10)))
|
||
|
||
point.lastX1 = prePoint.x + dis_x * (0.2 + speedFactor * 0.3)
|
||
point.lastY1 = prePoint.y + dis_y * (0.2 + speedFactor * 0.3)
|
||
point.lastX2 = prePoint.x + dis_x * (0.8 - speedFactor * 0.3)
|
||
point.lastY2 = prePoint.y + dis_y * (0.8 - speedFactor * 0.3)
|
||
}
|
||
|
||
const lineWidth = point.lineWidth || line.width
|
||
if (typeof prePoint.lastX1 === 'number') {
|
||
ctx.setLineWidth(lineWidth)
|
||
ctx.beginPath()
|
||
ctx.moveTo(prePoint.lastX2!, prePoint.lastY2!)
|
||
ctx.quadraticCurveTo(prePoint.x, prePoint.y, point.lastX1, point.lastY1)
|
||
ctx.stroke()
|
||
|
||
if (!prePoint.isFirstPoint) {
|
||
ctx.beginPath()
|
||
ctx.moveTo(prePoint.lastX1!, prePoint.lastY1!)
|
||
ctx.quadraticCurveTo(prePoint.x, prePoint.y, prePoint.lastX2!, prePoint.lastY2!)
|
||
ctx.stroke()
|
||
}
|
||
} else {
|
||
point.isFirstPoint = true
|
||
}
|
||
})
|
||
} else {
|
||
// 非笔锋模式的重绘
|
||
ctx.setLineWidth(line.width)
|
||
line.points.forEach((point, index) => {
|
||
if (index === 0) return
|
||
const prePoint = line.points[index - 1]
|
||
ctx.beginPath()
|
||
ctx.moveTo(prePoint.x, prePoint.y)
|
||
ctx.lineTo(point.x, point.y)
|
||
ctx.stroke()
|
||
})
|
||
}
|
||
})
|
||
|
||
// 所有线条绘制完成后,一次性更新画布
|
||
ctx.draw()
|
||
}
|
||
|
||
// 修改撤销功能
|
||
const revoke = () => {
|
||
if (!lines.value.length) return
|
||
const step = Math.min(props.step, lines.value.length)
|
||
const removedLines = lines.value.splice(lines.value.length - step)
|
||
redoLines.value.push(...removedLines)
|
||
currentStep.value = Math.max(0, currentStep.value - step)
|
||
redrawCanvas()
|
||
}
|
||
|
||
// 修改恢复功能
|
||
const restore = () => {
|
||
if (!redoLines.value.length) return
|
||
const step = Math.min(props.step, redoLines.value.length)
|
||
const restoredLines = redoLines.value.splice(redoLines.value.length - step)
|
||
lines.value.push(...restoredLines)
|
||
currentStep.value = Math.min(lines.value.length, currentStep.value + step)
|
||
redrawCanvas()
|
||
}
|
||
|
||
// 添加平滑线条绘制方法
|
||
function drawSmoothLine(prePoint: Point, point: Point) {
|
||
const { ctx } = canvasState
|
||
if (!ctx) return
|
||
|
||
// 计算两点间距离
|
||
const dis_x = point.x - prePoint.x
|
||
const dis_y = point.y - prePoint.y
|
||
const distance = Math.sqrt(dis_x * dis_x + dis_y * dis_y)
|
||
|
||
if (distance <= 2) {
|
||
// 对于非常近的点,直接使用中点
|
||
point.lastX1 = point.lastX2 = prePoint.x + dis_x * 0.5
|
||
point.lastY1 = point.lastY2 = prePoint.y + dis_y * 0.5
|
||
} else {
|
||
// 根据点的速度计算控制点的偏移程度
|
||
const speed = point.speed || 0
|
||
const minSpeed = props.minSpeed || 1.5
|
||
const speedFactor = Math.max(0.1, Math.min(0.9, speed / (minSpeed * 10)))
|
||
|
||
// 计算控制点
|
||
point.lastX1 = prePoint.x + dis_x * (0.2 + speedFactor * 0.3)
|
||
point.lastY1 = prePoint.y + dis_y * (0.2 + speedFactor * 0.3)
|
||
point.lastX2 = prePoint.x + dis_x * (0.8 - speedFactor * 0.3)
|
||
point.lastY2 = prePoint.y + dis_y * (0.8 - speedFactor * 0.3)
|
||
}
|
||
|
||
// 计算线宽
|
||
const lineWidth = point.lineWidth || props.lineWidth
|
||
|
||
// 绘制贝塞尔曲线
|
||
if (typeof prePoint.lastX1 === 'number') {
|
||
// 设置线宽
|
||
ctx.setLineWidth(lineWidth)
|
||
// 绘制第一段曲线
|
||
ctx.beginPath()
|
||
ctx.moveTo(prePoint.lastX2!, prePoint.lastY2!)
|
||
ctx.quadraticCurveTo(prePoint.x, prePoint.y, point.lastX1, point.lastY1)
|
||
ctx.stroke()
|
||
|
||
if (!prePoint.isFirstPoint) {
|
||
// 绘制连接段曲线
|
||
ctx.beginPath()
|
||
ctx.moveTo(prePoint.lastX1!, prePoint.lastY1!)
|
||
ctx.quadraticCurveTo(prePoint.x, prePoint.y, prePoint.lastX2!, prePoint.lastY2!)
|
||
ctx.stroke()
|
||
}
|
||
|
||
// 批量更新绘制内容
|
||
ctx.draw(true)
|
||
} else {
|
||
point.isFirstPoint = true
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
initCanvas()
|
||
})
|
||
|
||
onBeforeMount(() => {
|
||
// #ifdef MP
|
||
pixelRatio.value = uni.getSystemInfoSync().pixelRatio
|
||
// #endif
|
||
})
|
||
|
||
/**
|
||
* 获取canvas上下文
|
||
*/
|
||
function getContext() {
|
||
return new Promise<UniApp.CanvasContext>((resolve) => {
|
||
const { ctx } = canvasState
|
||
|
||
if (ctx) {
|
||
return resolve(ctx)
|
||
}
|
||
// #ifndef MP-WEIXIN
|
||
getRect(`#${canvasId.value}`, false, proxy).then((canvasRect) => {
|
||
setcanvasState(canvasRect.width!, canvasRect.height!)
|
||
canvasState.ctx = uni.createCanvasContext(canvasId.value, proxy)
|
||
if (canvasState.ctx) {
|
||
canvasState.ctx.scale(pixelRatio.value, pixelRatio.value)
|
||
}
|
||
resolve(canvasState.ctx)
|
||
})
|
||
// #endif
|
||
// #ifdef MP-WEIXIN
|
||
|
||
getRect(`#${canvasId.value}`, false, proxy, true).then((canvasRect: any) => {
|
||
if (canvasRect && canvasRect.node && canvasRect.width && canvasRect.height) {
|
||
const canvasInstance = canvasRect.node
|
||
canvasState.ctx = canvas2dAdapter(canvasInstance.getContext('2d') as CanvasRenderingContext2D)
|
||
canvasInstance.width = canvasRect.width * pixelRatio.value
|
||
canvasInstance.height = canvasRect.height * pixelRatio.value
|
||
canvasState.ctx.scale(pixelRatio.value, pixelRatio.value)
|
||
canvas = canvasInstance
|
||
setcanvasState(canvasRect.width, canvasRect.height)
|
||
resolve(canvasState.ctx)
|
||
}
|
||
})
|
||
// #endif
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 设置 canvasState
|
||
*/
|
||
function setcanvasState(width: number, height: number) {
|
||
canvasState.canvasHeight = height * pixelRatio.value
|
||
canvasState.canvasWidth = width * pixelRatio.value
|
||
}
|
||
|
||
/* 设置线段 */
|
||
function setLine() {
|
||
const { ctx } = canvasState
|
||
if (ctx) {
|
||
ctx.setLineWidth(getDefaultLineWidth()) // 使用新的默认宽度
|
||
ctx.setStrokeStyle(props.penColor)
|
||
ctx.setLineJoin('round')
|
||
ctx.setLineCap('round')
|
||
}
|
||
}
|
||
|
||
/**
|
||
* canvas 绘制图片输出成文件类型
|
||
*/
|
||
function canvasToImage() {
|
||
const { fileType, quality, exportScale } = props
|
||
const { canvasWidth, canvasHeight } = canvasState
|
||
uni.canvasToTempFilePath(
|
||
{
|
||
width: canvasWidth,
|
||
height: canvasHeight,
|
||
destWidth: canvasWidth * exportScale,
|
||
destHeight: canvasHeight * exportScale,
|
||
fileType,
|
||
quality,
|
||
canvasId: canvasId.value,
|
||
canvas: canvas,
|
||
success: (res) => {
|
||
const result: SignatureResult = {
|
||
tempFilePath: res.tempFilePath,
|
||
width: (canvasWidth * exportScale) / pixelRatio.value,
|
||
height: (canvasHeight * exportScale) / pixelRatio.value,
|
||
success: true
|
||
}
|
||
// #ifdef MP-DINGTALK
|
||
result.tempFilePath = (res as any).filePath
|
||
// #endif
|
||
emit('confirm', result)
|
||
},
|
||
fail: () => {
|
||
const result: SignatureResult = {
|
||
tempFilePath: '',
|
||
width: (canvasWidth * exportScale) / pixelRatio.value,
|
||
height: (canvasHeight * exportScale) / pixelRatio.value,
|
||
success: false
|
||
}
|
||
emit('confirm', result)
|
||
}
|
||
},
|
||
proxy
|
||
)
|
||
}
|
||
|
||
function clearCanvas() {
|
||
const { canvasWidth, canvasHeight, ctx } = canvasState
|
||
if (ctx) {
|
||
ctx.clearRect(0, 0, canvasWidth, canvasHeight)
|
||
if (!isTransparentColor(props.backgroundColor)) {
|
||
ctx.setFillStyle(props.backgroundColor as string)
|
||
ctx.fillRect(0, 0, canvasWidth, canvasHeight)
|
||
}
|
||
ctx.draw()
|
||
}
|
||
}
|
||
|
||
defineExpose<SignatureExpose>({
|
||
init: initCanvas,
|
||
clear,
|
||
confirm: confirmSignature,
|
||
restore,
|
||
revoke
|
||
})
|
||
</script>
|
||
<style scoped lang="scss">
|
||
@import './index.scss';
|
||
</style>
|