更新:登录功能

This commit is contained in:
2025-11-04 12:37:04 +08:00
commit a21fb92916
897 changed files with 51500 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
@import '../common/abstracts/variable';
@import '../common/abstracts/mixin';
@include b(signature) {
@include e(content) {
justify-content: center;
align-items: center;
display: flex;
overflow: hidden;
background: $-signature-bg;
border-radius: $-signature-radius;
border: $-signature-border;
}
@include e(content-canvas) {
width: 100%;
}
@include e(footer) {
margin-top: $-signature-footer-margin-top;
justify-content: flex-end;
display: flex;
:deep(){
.wd-button{
margin-left: $-signature-button-margin-left;
}
}
}
}

View File

@@ -0,0 +1,263 @@
/*
* @Author: 810505339
* @Date: 2025-01-10 20:03:57
* @LastEditors: weisheng
* @LastEditTime: 2025-03-23 16:35:14
* @FilePath: /wot-design-uni/src/uni_modules/wot-design-uni/components/wd-signature/types.ts
* 记得注释
*/
import type { ComponentPublicInstance, ExtractPropTypes } from 'vue'
import { baseProps, numericProp } from '../common/props'
export const signatureProps = {
...baseProps,
/**
* 签名笔颜色
* 类型string
* 默认值:#000
*/
penColor: {
type: String,
default: '#000'
},
/**
* 签名笔宽度
* 类型number
* 默认值3
*/
lineWidth: {
type: Number,
default: 3
},
/**
* 清空按钮的文本
* 类型string
*/
clearText: String,
/**
* 撤回按钮的文本
* 类型string
*/
revokeText: String,
/**
* 恢复按钮的文本
* 类型string
*/
restoreText: String,
/**
* 确认按钮的文本
* 类型string
*/
confirmText: String,
/**
* 目标文件的类型
* 类型string
* 默认值png
*/
fileType: {
type: String,
default: 'png'
},
/**
* 目标文件的质量
* 类型number
* 默认值1
*/
quality: {
type: Number,
default: 1
},
/**
* 导出图片的缩放比例
* 类型number
* 默认值1
*/
exportScale: {
type: Number,
default: 1
},
/**
* 是否禁用签名板
* 类型boolean
* 默认值false
*/
disabled: {
type: Boolean,
default: false
},
/**
* 画布的高度
* 类型number
*/
height: numericProp,
/**
* 画布的宽度
* 类型number
*/
width: numericProp,
/**
* 画板的背景色
* 类型string
*/
backgroundColor: String,
/**
* 是否禁用画布滚动
* 类型boolean
* 默认值true
*/
disableScroll: {
type: Boolean,
default: true
},
/**
* 是否开启历史记录
* 类型boolean
* 默认值false
*/
enableHistory: {
type: Boolean,
default: false
},
/**
* 撤回和恢复的步长
* 类型number
* 默认值1
*/
step: {
type: Number,
default: 1
},
/**
* 撤回按钮的文本
* 类型string
* 默认值:撤销
*/
undoText: String,
/**
* 恢复按钮的文本
* 类型string
* 默认值:恢复
*/
redoText: String,
/**
* 是否启用压感模式(笔锋)
* 类型boolean
* 默认值false
*/
pressure: {
type: Boolean,
default: false
},
/**
* 压感模式下笔画最小宽度
* 类型number
* 默认值2
*/
minWidth: {
type: Number,
default: 2
},
/**
* 压感模式下笔画最大宽度
* 类型number
* 默认值6
*/
maxWidth: {
type: Number,
default: 6
},
/**
* 最小速度阈值,影响压感模式下的笔画宽度变化
* 类型number
* 默认值1.5
*/
minSpeed: {
type: Number,
default: 1.5
}
}
/**
* 签名结果类型
* @property tempFilePath - 生成图片的临时路径
* @property success - 是否成功生成图片
* @property width - 生成图片的宽度
* @property height - 生成图片的高度
*/
export type SignatureResult = {
tempFilePath: string
success: boolean
width: number
height: number
}
/**
* 签名线条类型
* @property points - 线条所包含的所有点的数组
* @property color - 线条颜色
* @property width - 线条宽度
* @property backgroundColor - 线条背景色 (可选)
* @property isPressure - 是否为笔锋模式的线条 (可选)
*/
export interface Line {
points: Point[]
color: string
width: number
backgroundColor?: string
isPressure?: boolean
}
/**
* 签名点位类型
* @property x - 点的横坐标
* @property y - 点的纵坐标
* @property t - 点的时间戳
* @property speed - 当前点的绘制速度 (可选)
* @property distance - 与上一个点的距离 (可选)
* @property lineWidth - 当前点的线宽 (可选,用于笔锋模式)
* @property lastX1 - 贝塞尔曲线第一个控制点的x坐标 (可选)
* @property lastY1 - 贝塞尔曲线第一个控制点的y坐标 (可选)
* @property lastX2 - 贝塞尔曲线第二个控制点的x坐标 (可选)
* @property lastY2 - 贝塞尔曲线第二个控制点的y坐标 (可选)
* @property isFirstPoint - 是否为线条的第一个点 (可选)
*/
export interface Point {
x: number
y: number
t: number
speed?: number
distance?: number
lineWidth?: number
lastX1?: number
lastY1?: number
lastX2?: number
lastY2?: number
isFirstPoint?: boolean
}
/**
* 签名组件暴露的方法类型
* @property init - 初始化签名板
* @property clear - 清除签名
* @property confirm - 确认签名并生成图片
* @property restore - 恢复上一步操作
* @property revoke - 撤销上一步操作
*/
export type SignatureExpose = {
/** 初始化签名板
* @param forceUpdate - 是否强制更新
*/
init: (forceUpdate?: boolean) => void
/** 点击清除按钮清除签名 */
clear: () => void
/** 点击确定按钮 */
confirm: () => void
/* 点击恢复 */
restore: () => void
/* 点击撤回 */
revoke: () => void
}
export type SignatureProps = ExtractPropTypes<typeof signatureProps>
export type SignatureInstance = ComponentPublicInstance<SignatureExpose, SignatureProps>

View File

@@ -0,0 +1,630 @@
<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>