refactor: ♻️ 重构signature组件 (#853)

This commit is contained in:
不如摸鱼去 2025-01-18 00:01:35 +08:00 committed by GitHub
parent c0d48b25c1
commit 21fbfbb49b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 428 additions and 224 deletions

View File

@ -1,8 +1,15 @@
# Signature 组件 # Signature 签名 <el-tag text style="vertical-align: middle;margin-left:8px;" effect="plain">$LOWEST_VERSION$</el-tag>
`Signature`组件是一个用于生成手写签名的 Vue 组件。它提供了多种自定义选项,包括签名笔的颜色、宽度以及自定义操作按钮
用于签名场景,基于 Canvas 实现的签名组件。它提供了多种自定义选项,包括签名笔的颜色、宽度以及自定义操作按钮。
:::tip 提醒
如果遇到导出图片不清晰,可以将`exportScale`设置为`2`以上。
:::
## 基础用法 ## 基础用法
```html ```html
<wd-signature @confirm="confirm" /> <wd-signature @submit="confirm" @clear="clear" />
<wd-img :height="img.height" :width="img.width" :src="img.src" v-if="img.src" /> <wd-img :height="img.height" :width="img.width" :src="img.src" v-if="img.src" />
``` ```
@ -12,36 +19,60 @@ const img = ref({
height: 0, height: 0,
src: '' src: ''
}) })
function confirm(result: FileType) {
function confirm(result: SignatureResult) {
img.value.src = result.tempFilePath img.value.src = result.tempFilePath
img.value.height = result.height img.value.height = result.height
img.value.width = result.width img.value.width = result.width
} }
``` ```
## 自定义颜色
`pen-color`设置签名笔的颜色,默认为`黑色` ## 自定义画笔颜色
`pen-color`设置签名笔的颜色,默认为`黑色`
```html ```html
<wd-signature pen-color="red" /> <wd-signature pen-color="red" />
``` ```
## 自定义画笔宽度
## 自定义宽度
`line-width`设置签名笔的宽度,默认为`2` `line-width`设置签名笔的宽度,默认为`2`
```html ```html
<wd-signature :line-width="6" /> <wd-signature :line-width="6" />
``` ```
## 自定义背景色
`background-color`设置画板的背景色,无默认值。
```html
<wd-signature background-color="lightgray" />
```
## 禁用滚动
`disable-scroll`设置是否禁用画布滚动,默认为`true`
```html
<wd-signature :disable-scroll="false" />
```
## 自定义按钮 ## 自定义按钮
通过`footer`插槽可以自定义按钮
通过`footer`插槽可以自定义按钮。
```html ```html
<wd-signature :disabled="disabled"> <wd-signature :disabled="disabled">
<template #footer="{ clear, confirm }"> <template #footer="{ clear, confirm }">
<wd-button block @click="changeDisabled" v-if="disabled">开始签名</wd-button> <wd-button block @click="changeDisabled" v-if="disabled">开始签名</wd-button>
<wd-button v-if="!disabled" size="small" plain @click="clear">清除</wd-button> <wd-button v-if="!disabled" size="small" plain @click="clear">清除</wd-button>
<wd-button v-if="!disabled" size="small" style="margin-left: 4px" @click="confirm">确认</wd-button> <wd-button v-if="!disabled" size="small" custom-style="margin-left: 4px" @click="confirm">确认</wd-button>
</template> </template>
</wd-signature> </wd-signature>
``` ```
```typescript ```typescript
const disabled = ref(true) const disabled = ref(true)
@ -49,39 +80,45 @@ function changeDisabled() {
disabled.value = false disabled.value = false
} }
``` ```
## Attributes ## Attributes
| 参数 | 说明 | 类型 | 可选值 | 默认值| 最低版本 |
|-----|------|-----|-------|-------|--------| | 参数 | 说明 | 类型 | 可选值 | 默认值 | 最低版本 |
| penColor | 签名笔颜色 | String | -- | #000000 | -- | |-----------------|----------------------------------------------------------------------|---------|--------|----------|----------|
| lineWidth | 签名笔宽度 | Number | -- | 2 | -- | | penColor | 签名笔颜色 | String | -- | #000000 | -- |
| height | 画布的高度 | Number | -- | 200 | -- | | lineWidth | 签名笔宽度 | Number | -- | 2 | -- |
| width | 画布的宽度 | Number | -- | 300 | -- | | height | 画布的高度 | Number | -- | 200 | -- |
| clearText | 清空按钮的文本 | String |-- | 清空 | -- | | width | 画布的宽度 | Number | -- | 300 | -- |
| confirmText | 确认按钮的文本 | String | -- | 确认 | -- | | clearText | 清空按钮的文本 | String | -- | 清空 | -- |
| fileType | 目标文件的类型,[wx.canvasToTempFilePath属性介绍](https://developers.weixin.qq.com/miniprogram/dev/api/canvas/wx.canvasToTempFilePath.html#%E5%8F%82%E6%95%B0) | String | -- | png | -- | | confirmText | 确认按钮的文本 | String | -- | 确认 | -- |
| quality | 目标文件的类型,[wx.canvasToTempFilePath属性介绍](https://developers.weixin.qq.com/miniprogram/dev/api/canvas/wx.canvasToTempFilePath.html#%E5%8F%82%E6%95%B0) | Number | -- | 1 |-- | | fileType | 目标文件的类型,[uni.canvasToTempFilePath属性介绍](https://uniapp.dcloud.net.cn/api/canvas/canvasToTempFilePath.html) | String | -- | png | -- |
| exportScale | 导出图片的缩放比例 | Number | -- | 1 |-- | | quality | 图片的质量,取值范围为 `(0, 1]`不在范围内时当作1.0处理,[uni.canvasToTempFilePath属性介绍](https://uniapp.dcloud.net.cn/api/canvas/canvasToTempFilePath.html) | Number | -- | 1 | -- |
| disabled | 是否禁用签名板 | Boolean | -- | false | -- | | exportScale | 导出图片的缩放比例 | Number | -- | 1 | -- |
| disabled | 是否禁用签名板 | Boolean | -- | false | -- |
| backgroundColor | 画板的背景色 | String | -- | -- | -- |
| disableScroll | 是否禁用画布滚动 | Boolean | -- | true | -- |
## Slot ## Slot
| name | 说明 |参数 | 最低版本 | | name | 说明 | 参数 | 最低版本 |
| ------- | ------------------------ |--- | -------- | |--------|----------------|-----------------------|----------|
| footer | 自定义footer | `{ clear, confirm }` |- | | footer | 自定义footer | `{ clear, confirm }` | - |
## Events ## Events
| 事件名称 | 说明 | 参数 | 最低版本 | | 事件名称 | 说明 | 参数 | 最低版本 |
|---------|-----|-----|---------| |-----------|--------------------|-------------------------------------------|----------|
| confirm | 点击确认按钮时触发 | `{tempFilePath, width, height}` 分别为生成文件的临时路径 (本地路径)、生成图片宽、生成图片高| - | | start | 开始签名时触发 | - | - |
| clear | 点击清空按钮时触发 | - | - | | end | 结束签名时触发 | - | - |
| touchstart | 按下时触发 | `event`| - | | signing | 签名过程中触发 | `event` | - |
| touchend | 按下结束时触发 | `event` | - | | confirm | 点击确定按钮时触发 | `{tempFilePath, width, height, success}` 分别为生成文件的临时路径 (本地路径)、生成图片宽、生成图片高、是否成功 | - |
| clear | 点击清空按钮时触发 | - | - |
## Methods ## Methods
对外暴露函数 对外暴露函数
| 事件名称 | 说明 | 参数 | 最低版本 | | 事件名称 | 说明 | 参数 | 最低版本 |
|--------|------|-----|---------| |-----------|--------------------|-------------------------------------------|----------|
| confirm | 点击确认按钮时触发 | `{tempFilePath, width, height}` 分别为生成文件的临时路径 (本地路径)、生成图片宽、生成图片高| - | | confirm | 点击确认按钮时触发 | `{tempFilePath, width, height, success}` 分别为生成文件的临时路径 (本地路径)、生成图片宽、生成图片高、是否成功 | - |
| clear | 点击清空按钮时触发 | - | - | | clear | 点击清空按钮时触发 | - | - |

View File

@ -1,24 +1,19 @@
<!--
* @Author: 810505339
* @Date: 2025-01-10 15:49:26
* @LastEditors: 810505339
* @LastEditTime: 2025-01-11 22:10:22
* @FilePath: \wot-design-uni\src\pages\signature\Index.vue
* 记得注释
-->
<template> <template>
<page-wraper> <page-wraper>
<demo-block title="基础用法"> <demo-block title="基础用法">
<wd-signature @confirm="confirm" /> <wd-signature @confirm="confirm" @clear="clear" :export-scale="2" />
<wd-img :height="img.height" :width="img.width" :src="img.src" v-if="img.src" /> <wd-img v-if="img.tempFilePath" mode="widthFix" width="100%" :src="img.tempFilePath" />
</demo-block> </demo-block>
<demo-block title="自定义颜色"> <demo-block title="自定义画笔颜色">
<wd-signature pen-color="red" /> <wd-signature pen-color="red" />
</demo-block> </demo-block>
<demo-block title="自定义宽度"> <demo-block title="自定义画笔宽度">
<wd-signature :line-width="6" /> <wd-signature :line-width="6" />
</demo-block> </demo-block>
<demo-block title="自定义按钮"> <demo-block title="自定义背景颜色">
<wd-signature background-color="lightgray" />
</demo-block>
<demo-block title="自定义插槽">
<wd-signature :disabled="disabled"> <wd-signature :disabled="disabled">
<template #footer="{ clear, confirm }"> <template #footer="{ clear, confirm }">
<wd-button block @click="changeDisabled" v-if="disabled">开始签名</wd-button> <wd-button block @click="changeDisabled" v-if="disabled">开始签名</wd-button>
@ -29,20 +24,23 @@
</demo-block> </demo-block>
</page-wraper> </page-wraper>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import type { FileType } from '@/uni_modules/wot-design-uni/components/wd-signature/types' import type { SignatureResult } from '@/uni_modules/wot-design-uni/components/wd-signature/types'
import { ref } from 'vue' import { ref } from 'vue'
const img = ref({
width: 0, const img = ref<Partial<SignatureResult>>({})
height: 0,
src: ''
})
const disabled = ref(true) const disabled = ref(true)
function confirm(result: FileType) {
img.value.src = result.tempFilePath function confirm(result: SignatureResult) {
img.value.height = result.height img.value = result
img.value.width = result.width
} }
function clear() {
img.value = {}
}
function changeDisabled() { function changeDisabled() {
disabled.value = false disabled.value = false
} }

View File

@ -958,3 +958,8 @@ $-floating-panel-content-bg: var(--wot-floating-panel-content-bg, $-color-white)
$-signature-bg: var(--wot-signature-bg, $-color-white) !default; // 背景色 $-signature-bg: var(--wot-signature-bg, $-color-white) !default; // 背景色
$-signature-radius: var(--wot-signature-radius, 4px) !default; // 圆角 $-signature-radius: var(--wot-signature-radius, 4px) !default; // 圆角
$-signature-border: var(--wot-signature-border, 1px solid $-color-gray-5) !default; // 边框圆角 $-signature-border: var(--wot-signature-border, 1px solid $-color-gray-5) !default; // 边框圆角
$-signature-footer-margin-top: var(--wot-signature-footer-margin-top, 8px) !default; // 底部按钮上边距
$-signature-button-margin-left: var(--wot-signature-button-margin-left, 8px) !default; // 底部按钮左边距

View File

@ -237,9 +237,10 @@ export type RectResultType<T extends boolean> = T extends true ? UniApp.NodeInfo
* @param selector #id,.class * @param selector #id,.class
* @param all selector * @param all selector
* @param scope * @param scope
* @param useFields 使 fields
* @returns * @returns
*/ */
export function getRect<T extends boolean>(selector: string, all: T, scope?: any): Promise<RectResultType<T>> { export function getRect<T extends boolean>(selector: string, all: T, scope?: any, useFields?: boolean): Promise<RectResultType<T>> {
return new Promise<RectResultType<T>>((resolve, reject) => { return new Promise<RectResultType<T>>((resolve, reject) => {
let query: UniNamespace.SelectorQuery | null = null let query: UniNamespace.SelectorQuery | null = null
if (scope) { if (scope) {
@ -247,17 +248,24 @@ export function getRect<T extends boolean>(selector: string, all: T, scope?: any
} else { } else {
query = uni.createSelectorQuery() query = uni.createSelectorQuery()
} }
query[all ? 'selectAll' : 'select'](selector)
.boundingClientRect((rect) => { const method = all ? 'selectAll' : 'select'
if (all && isArray(rect) && rect.length > 0) {
resolve(rect as RectResultType<T>) const callback = (rect: UniApp.NodeInfo | UniApp.NodeInfo[]) => {
} else if (!all && rect) { if (all && isArray(rect) && rect.length > 0) {
resolve(rect as RectResultType<T>) resolve(rect as RectResultType<T>)
} else { } else if (!all && rect) {
reject(new Error('No nodes found')) resolve(rect as RectResultType<T>)
} } else {
}) reject(new Error('No nodes found'))
.exec() }
}
if (useFields) {
query[method](selector).fields({ size: true, node: true }, callback).exec()
} else {
query[method](selector).boundingClientRect(callback).exec()
}
}) })
} }
@ -459,7 +467,7 @@ export const pause = (ms: number = 1000 / 30) => {
* @returns * @returns
*/ */
export function deepClone<T>(obj: T, cache: Map<any, any> = new Map()): T { export function deepClone<T>(obj: T, cache: Map<any, any> = new Map()): T {
// 如果对象为 null 或者不是对象类型,则直接返回该对象 // 如果对象为 null 或者不是对象类型,则直接返回该对象
if (obj === null || typeof obj !== 'object') { if (obj === null || typeof obj !== 'object') {
return obj return obj
} }

View File

@ -1,10 +1,10 @@
/* /*
* @Author: weisheng * @Author: weisheng
* @Date: 2024-01-25 23:06:48 * @Date: 2024-01-25 23:06:48
* @LastEditTime: 2024-01-26 14:00:48 * @LastEditTime: 2025-01-16 21:30:14
* @LastEditors: weisheng * @LastEditors: weisheng
* @Description: * @Description:
* @FilePath: \wot-design-uni\src\uni_modules\wot-design-uni\components\composables\useTranslate.ts * @FilePath: /wot-design-uni/src/uni_modules/wot-design-uni/components/composables/useTranslate.ts
* *
*/ */
import { camelCase, getPropByPath, isFunction } from '../common/util' import { camelCase, getPropByPath, isFunction } from '../common/util'

View File

@ -540,32 +540,28 @@ function handleConfirm() {
*/ */
function canvasToImage() { function canvasToImage() {
const { fileType, quality, exportScale } = props const { fileType, quality, exportScale } = props
try { uni.canvasToTempFilePath(
uni.canvasToTempFilePath( {
{ width: cutWidth.value * exportScale,
width: cutWidth.value * exportScale, height: Math.round(cutHeight.value * exportScale),
height: Math.round(cutHeight.value * exportScale), destWidth: cutWidth.value * exportScale,
destWidth: cutWidth.value * exportScale, destHeight: Math.round(cutHeight.value * exportScale),
destHeight: Math.round(cutHeight.value * exportScale), fileType,
fileType, quality,
quality, canvasId: 'wd-img-cropper-canvas',
canvasId: 'wd-img-cropper-canvas', success: (res: any) => {
success: (res: any) => { const result = { tempFilePath: res.tempFilePath, width: cutWidth.value * exportScale, height: cutHeight.value * exportScale }
const result = { tempFilePath: res.tempFilePath, width: cutWidth.value * exportScale, height: cutHeight.value * exportScale } // #ifdef MP-DINGTALK
// #ifdef MP-DINGTALK result.tempFilePath = res.filePath
result.tempFilePath = res.filePath // #endif
// #endif emit('confirm', result)
emit('confirm', result)
},
complete: () => {
emit('update:modelValue', false)
}
}, },
proxy complete: () => {
) emit('update:modelValue', false)
} catch (error) { }
console.log(error) },
} proxy
)
} }
/** /**

View File

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

View File

@ -1,65 +1,115 @@
/* /*
* @Author: 810505339 * @Author: 810505339
* @Date: 2025-01-10 20:03:57 * @Date: 2025-01-10 20:03:57
* @LastEditors: 810505339 * @LastEditors: weisheng
* @LastEditTime: 2025-01-11 23:03:44 * @LastEditTime: 2025-01-17 16:43:19
* @FilePath: \wot-design-uni\src\uni_modules\wot-design-uni\components\wd-signature\types.ts * @FilePath: /wot-design-uni/src/uni_modules/wot-design-uni/components/wd-signature/types.ts
* *
*/ */
import type { ExtractPropTypes, Prop, PropType } from 'vue' import { baseProps, numericProp } from '../common/props'
import { baseProps, makeBooleanProp, makeNumberProp, makeRequiredProp } from '../common/props'
export const signatureProps = { export const signatureProps = {
...baseProps, ...baseProps,
/**
*
* string
* #000
*/
penColor: { penColor: {
type: String, type: String,
default: '#000' default: '#000'
}, },
/**
*
* number
* 3
*/
lineWidth: { lineWidth: {
type: Number, type: Number,
default: 3 default: 3
}, },
height: { /**
type: Number, *
default: 200 * string
}, */
width: { clearText: String,
type: Number, /**
default: 300 *
}, * string
clearText: { */
type: String, confirmText: String,
default: '清空' /**
}, *
confirmText: { * string
type: String, * png
default: '确认' */
},
fileType: { fileType: {
type: String, type: String,
default: 'png' default: 'png'
}, },
/**
*
* number
* 1
*/
quality: { quality: {
type: Number, type: Number,
default: 1 default: 1
}, },
/**
*
* number
* 1
*/
exportScale: { exportScale: {
type: Number, type: Number,
default: 1 default: 1
}, },
/**
*
* boolean
* false
*/
disabled: { disabled: {
type: Boolean, type: Boolean,
default: false default: false
},
/**
*
* number
*/
height: numericProp,
/**
*
* number
*/
width: numericProp,
/**
*
* string
*/
backgroundColor: String,
/**
*
* boolean
* true
*/
disableScroll: {
type: Boolean,
default: true
} }
} }
export type FileType = {
export type SignatureResult = {
tempFilePath: string tempFilePath: string
success: boolean
width: number width: number
height: number height: number
} }
export type SignatureExpose = { export type SignatureExpose = {
/** 点击清除按钮清除签名 */ /** 点击清除按钮清除签名 */
clear: () => void clear: () => void
/** 点击确定按钮 */ /** 点击确定按钮 */
confirm: (result: FileType) => void confirm: (result: SignatureResult) => void
} }

View File

@ -1,19 +1,14 @@
<!--
* @Author: 810505339
* @Date: 2025-01-10 15:41:12
* @LastEditors: 810505339
* @LastEditTime: 2025-01-11 22:53:54
* @FilePath: \wot-design-uni\src\uni_modules\wot-design-uni\components\wd-signature\wd-signature.vue
* 记得注释
-->
<template> <template>
<view class="wd-signature"> <view class="wd-signature">
<view class="wd-signature__context"> <view class="wd-signature__content" :style="canvasStyle">
<!-- #ifdef MP-WEIXIN --> <!-- #ifdef MP-WEIXIN -->
<canvas <canvas
:style="canvasStyle" class="wd-signature__content-canvas"
:width="canvasState.canvasWidth"
:height="canvasState.canvasHeight"
:canvas-id="canvasId" :canvas-id="canvasId"
:id="canvasId" :id="canvasId"
:disable-scroll="disableScroll"
@touchstart="startDrawing" @touchstart="startDrawing"
@touchend="stopDrawing" @touchend="stopDrawing"
@touchmove="draw" @touchmove="draw"
@ -22,11 +17,12 @@
<!-- #endif --> <!-- #endif -->
<!-- #ifndef MP-WEIXIN --> <!-- #ifndef MP-WEIXIN -->
<canvas <canvas
:height="height" class="wd-signature__content-canvas"
:width="width"
:style="canvasStyle"
:canvas-id="canvasId" :canvas-id="canvasId"
:width="canvasState.canvasWidth"
:height="canvasState.canvasHeight"
:id="canvasId" :id="canvasId"
:disable-scroll="disableScroll"
@touchstart="startDrawing" @touchstart="startDrawing"
@touchend="stopDrawing" @touchend="stopDrawing"
@touchmove="draw" @touchmove="draw"
@ -35,8 +31,8 @@
</view> </view>
<view class="wd-signature__footer"> <view class="wd-signature__footer">
<slot name="footer" :clear="clear" :confirm="confirmSignature"> <slot name="footer" :clear="clear" :confirm="confirmSignature">
<wd-button size="small" plain @click="clear">{{ clearText }}</wd-button> <wd-button size="small" plain @click="clear">{{ clearText || translate('clearText') }}</wd-button>
<wd-button size="small" @click="confirmSignature">{{ confirmText }}</wd-button> <wd-button size="small" @click="confirmSignature">{{ confirmText || translate('confirmText') }}</wd-button>
</slot> </slot>
</view> </view>
</view> </view>
@ -53,31 +49,32 @@ export default {
} }
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, getCurrentInstance, onBeforeMount, onMounted, ref, watch, defineExpose } from 'vue' import { computed, getCurrentInstance, onBeforeMount, onMounted, reactive, ref, watch, type CSSProperties } from 'vue'
import { addUnit, objToStyle, uuid } from '../common/util' import { addUnit, getRect, isDef, objToStyle, uuid } from '../common/util'
import { signatureProps, type SignatureExpose } from './types' import { signatureProps, type SignatureExpose, type SignatureResult } from './types'
import { useTranslate } from '../composables/useTranslate'
// #ifdef MP-WEIXIN // #ifdef MP-WEIXIN
import { canvas2dAdapter } from '../common/canvasHelper' import { canvas2dAdapter } from '../common/canvasHelper'
// #endif // #endif
const props = defineProps(signatureProps) const props = defineProps(signatureProps)
const emit = defineEmits(['clear', 'confirm', 'touchstart', 'touchend']) const emit = defineEmits(['start', 'end', 'signing', 'confirm', 'clear'])
const { translate } = useTranslate('signature')
const { proxy } = getCurrentInstance() as any const { proxy } = getCurrentInstance() as any
const canvasId = ref<string>(`signature${uuid()}`) // canvas const canvasId = ref<string>(`signature${uuid()}`) // canvas
let canvas: null = null //canvas let canvas: null = null //canvas
let ctx: UniApp.CanvasContext | null = null // canvas
const drawing = ref<boolean>(false) // const drawing = ref<boolean>(false) //
const pixelRatio = ref<number>(1) // const pixelRatio = ref<number>(1) //
const canvasDom = ref({ const canvasState = reactive({
w: 0, canvasWidth: 0,
h: 0 canvasHeight: 0,
ctx: null as UniApp.CanvasContext | null // canvas
}) })
watch( watch(
() => props.penColor, () => props.penColor,
() => { () => {
setLine() setLine()
},
{
immediate: true
} }
) )
@ -85,43 +82,63 @@ watch(
() => props.lineWidth, () => props.lineWidth,
() => { () => {
setLine() setLine()
},
{
immediate: true
} }
) )
const canvasStyle = computed(() => { const canvasStyle = computed(() => {
const style = { const style: CSSProperties = {}
height: addUnit(props.height), if (isDef(props.width)) {
width: addUnit(props.width) style.width = addUnit(props.width)
} }
if (isDef(props.height)) {
style.height = addUnit(props.height)
}
return `${objToStyle(style)};` return `${objToStyle(style)};`
}) })
const disableScroll = computed(() => props.disableScroll)
/* 开始画线 */ /* 开始画线 */
const startDrawing = (e: TouchEvent) => { const startDrawing = (e: TouchEvent) => {
e.preventDefault()
drawing.value = true drawing.value = true
setLine() setLine()
emit('touchstart', e) emit('start', e)
draw(e) draw(e)
} }
/* 结束画线 */ /* 结束画线 */
const stopDrawing = (e: TouchEvent) => { const stopDrawing = (e: TouchEvent) => {
e.preventDefault()
drawing.value = false drawing.value = false
const { ctx } = canvasState
if (ctx) ctx.beginPath() if (ctx) ctx.beginPath()
emit('touchend', e) emit('end', e)
} }
// canvas // canvas
const initCanvas = () => { const initCanvas = () => {
getContext() 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 // canvas
const clear = () => { const clear = () => {
const { w, h } = canvasDom.value const { canvasWidth, canvasHeight, ctx } = canvasState
if (ctx) { if (ctx) {
ctx.clearRect(0, 0, w, h) ctx.clearRect(0, 0, canvasWidth, canvasHeight)
if (isDef(props.backgroundColor)) {
ctx.setFillStyle(props.backgroundColor)
ctx.fillRect(0, 0, canvasWidth, canvasHeight)
}
ctx.draw() ctx.draw()
} }
emit('clear') emit('clear')
@ -134,59 +151,77 @@ const confirmSignature = () => {
//canvas线 //canvas线
const draw = (e: any) => { const draw = (e: any) => {
if (!drawing.value) return e.preventDefault()
if (props.disabled) return const { ctx } = canvasState
if (!ctx) return
if (!drawing.value || props.disabled || !ctx) return
const { x, y } = e.touches[0] const { x, y } = e.touches[0]
ctx.lineTo(x, y) ctx.lineTo(x, y)
ctx.stroke() ctx.stroke()
ctx.draw(true) //线 ctx.draw(true) //线
ctx.moveTo(x, y) ctx.moveTo(x, y)
emit('signing', e)
} }
onMounted(() => { onMounted(() => {
initCanvas() initCanvas()
}) })
onBeforeMount(() => { onBeforeMount(() => {
// #ifdef MP
pixelRatio.value = uni.getSystemInfoSync().pixelRatio pixelRatio.value = uni.getSystemInfoSync().pixelRatio
// #endif
}) })
/** /**
* 获取canvas上下文 * 获取canvas上下文
*/ */
function getContext() { function getContext() {
return new Promise<UniApp.CanvasContext>((resolve) => { return new Promise<UniApp.CanvasContext>((resolve) => {
const { ctx } = canvasState
if (ctx) { if (ctx) {
return resolve(ctx) return resolve(ctx)
} }
uni // #ifndef MP-WEIXIN
.createSelectorQuery() getRect(`#${canvasId.value}`, false, proxy).then((canvasRect) => {
.in(proxy) setcanvasState(canvasRect.width!, canvasRect.height!)
.select(`#${canvasId.value}`) canvasState.ctx = uni.createCanvasContext(canvasId.value, proxy)
.node((res) => { if (canvasState.ctx) {
if (res && res.node) { canvasState.ctx.scale(pixelRatio.value, pixelRatio.value)
const canvasInstance = res.node }
// #ifndef MP-WEIXIN resolve(canvasState.ctx)
ctx = uni.createCanvasContext(canvasId.value, proxy) })
// #endif // #endif
// #ifdef MP-WEIXIN // #ifdef MP-WEIXIN
ctx = canvas2dAdapter(canvasInstance.getContext('2d') as CanvasRenderingContext2D)
canvasInstance.width = props.width * pixelRatio.value getRect(`#${canvasId.value}`, false, proxy, true).then((canvasRect: any) => {
canvasInstance.height = props.height * pixelRatio.value if (canvasRect && canvasRect.node) {
ctx.scale(pixelRatio.value, pixelRatio.value) const canvasInstance = canvasRect.node
// #endif canvasState.ctx = canvas2dAdapter(canvasInstance.getContext('2d') as CanvasRenderingContext2D)
canvas = canvasInstance canvasInstance.width = canvasRect.width * pixelRatio.value
canvasDom.value = { canvasInstance.height = canvasRect.height * pixelRatio.value
w: canvasInstance.width, canvasState.ctx.scale(pixelRatio.value, pixelRatio.value)
h: canvasInstance.height canvas = canvasInstance
} setcanvasState(canvasRect.width, canvasRect.height)
resolve(ctx) resolve(canvasState.ctx)
} }
}) })
.exec() // #endif
}) })
} }
/**
* 设置 canvasState
*/
function setcanvasState(width: number, height: number) {
canvasState.canvasHeight = height * pixelRatio.value
canvasState.canvasWidth = width * pixelRatio.value
}
/* 设置线段 */ /* 设置线段 */
function setLine() { function setLine() {
const { ctx } = canvasState
if (ctx) { if (ctx) {
ctx.setLineWidth(props.lineWidth) ctx.setLineWidth(props.lineWidth)
ctx.setStrokeStyle(props.penColor) ctx.setStrokeStyle(props.penColor)
@ -194,39 +229,47 @@ function setLine() {
ctx.setLineCap('round') ctx.setLineCap('round')
} }
} }
/** /**
* canvas 绘制图片输出成文件类型 * canvas 绘制图片输出成文件类型
*/ */
function canvasToImage() { function canvasToImage() {
const { fileType, quality, exportScale } = props const { fileType, quality, exportScale } = props
const { w, h } = canvasDom.value const { canvasWidth, canvasHeight } = canvasState
try { uni.canvasToTempFilePath(
uni.canvasToTempFilePath( {
{ width: canvasWidth * exportScale,
width: w * exportScale, height: canvasHeight * exportScale,
height: h * exportScale, destWidth: canvasWidth * exportScale,
destWidth: w * exportScale, destHeight: canvasHeight * exportScale,
destHeight: h * exportScale, fileType,
fileType, quality,
quality, canvasId: canvasId.value,
canvasId: canvasId.value, canvas: canvas,
canvas: canvas, success: (res) => {
success: (res: any) => { const result: SignatureResult = {
const result = { tempFilePath: res.tempFilePath, width: (w * exportScale) / pixelRatio.value, height: (h * exportScale) / pixelRatio.value } tempFilePath: res.tempFilePath,
// #ifdef MP-DINGTALK width: (canvasWidth * exportScale) / pixelRatio.value,
result.tempFilePath = res.filePath height: (canvasHeight * exportScale) / pixelRatio.value,
// #endif success: true
emit('confirm', result)
},
fail: (error) => {
console.error(error)
} }
// #ifdef MP-DINGTALK
result.tempFilePath = (res as any).filePath
// #endif
emit('confirm', result)
}, },
proxy fail: () => {
) const result: SignatureResult = {
} catch (error) { tempFilePath: '',
console.error(error) width: (canvasWidth * exportScale) / pixelRatio.value,
} height: (canvasHeight * exportScale) / pixelRatio.value,
success: false
}
emit('confirm', result)
}
},
proxy
)
} }
defineExpose<SignatureExpose>({ defineExpose<SignatureExpose>({

View File

@ -1,7 +1,7 @@
<!-- <!--
* @Author: weisheng * @Author: weisheng
* @Date: 2023-04-05 21:32:56 * @Date: 2023-04-05 21:32:56
* @LastEditTime: 2024-04-01 20:40:34 * @LastEditTime: 2025-01-16 21:43:47
* @LastEditors: weisheng * @LastEditors: weisheng
* @Description: 水印组件 * @Description: 水印组件
* @FilePath: /wot-design-uni/src/uni_modules/wot-design-uni/components/wd-watermark/wd-watermark.vue * @FilePath: /wot-design-uni/src/uni_modules/wot-design-uni/components/wd-watermark/wd-watermark.vue
@ -31,7 +31,7 @@ export default {
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, onMounted, ref, watch, nextTick } from 'vue' import { computed, onMounted, ref, watch, nextTick, type CSSProperties } from 'vue'
import { addUnit, buildUrlWithParams, isBase64Image, objToStyle, uuid } from '../common/util' import { addUnit, buildUrlWithParams, isBase64Image, objToStyle, uuid } from '../common/util'
import { watermarkProps } from './types' import { watermarkProps } from './types'
@ -68,7 +68,7 @@ const rootClass = computed(() => {
* 水印样式 * 水印样式
*/ */
const rootStyle = computed(() => { const rootStyle = computed(() => {
const style: Record<string, string | number> = { const style: CSSProperties = {
opacity: props.opacity, opacity: props.opacity,
backgroundSize: addUnit(props.width + props.gutterX) backgroundSize: addUnit(props.width + props.gutterX)
} }

View File

@ -123,5 +123,9 @@ export default {
}, },
tableCol: { tableCol: {
indexLabel: 'فهرس' indexLabel: 'فهرس'
},
signature: {
confirmText: 'تأكيد',
clearText: 'مسح'
} }
} }

View File

@ -123,5 +123,9 @@ export default {
}, },
tableCol: { tableCol: {
indexLabel: 'Index' indexLabel: 'Index'
},
signature: {
confirmText: 'Bestätigen',
clearText: 'Löschen'
} }
} }

View File

@ -123,5 +123,9 @@ export default {
}, },
tableCol: { tableCol: {
indexLabel: 'index' indexLabel: 'index'
},
signature: {
confirmText: 'OK',
clearText: 'Clear'
} }
} }

View File

@ -123,5 +123,9 @@ export default {
}, },
tableCol: { tableCol: {
indexLabel: 'Índice' indexLabel: 'Índice'
},
signature: {
confirmText: 'Confirmar',
clearText: 'Limpiar'
} }
} }

View File

@ -123,5 +123,9 @@ export default {
}, },
tableCol: { tableCol: {
indexLabel: 'Indice' indexLabel: 'Indice'
},
signature: {
confirmText: 'Signer',
clearText: 'Effacer'
} }
} }

View File

@ -123,5 +123,9 @@ export default {
}, },
tableCol: { tableCol: {
indexLabel: '索引' indexLabel: '索引'
},
signature: {
confirmText: '確認',
clearText: 'クリア'
} }
} }

View File

@ -123,5 +123,9 @@ export default {
}, },
tableCol: { tableCol: {
indexLabel: '인덱스' indexLabel: '인덱스'
},
signature: {
confirmText: '확인',
clearText: '지우기'
} }
} }

View File

@ -123,5 +123,9 @@ export default {
}, },
tableCol: { tableCol: {
indexLabel: 'Número' indexLabel: 'Número'
},
signature: {
confirmText: 'Concluir',
clearText: 'Limpar'
} }
} }

View File

@ -123,5 +123,9 @@ export default {
}, },
tableCol: { tableCol: {
indexLabel: '№' indexLabel: '№'
},
signature: {
confirmText: 'Подтвердить',
clearText: 'Очистить'
} }
} }

View File

@ -123,5 +123,9 @@ export default {
}, },
tableCol: { tableCol: {
indexLabel: 'หมายเลขซีเรียล' indexLabel: 'หมายเลขซีเรียล'
},
signature: {
confirmText: 'ยืนยัน',
clearText: 'ล้าง'
} }
} }

View File

@ -123,5 +123,9 @@ export default {
}, },
tableCol: { tableCol: {
indexLabel: 'Sıra No' indexLabel: 'Sıra No'
},
signature: {
confirmText: 'İmzala',
clearText: 'Temizle'
} }
} }

View File

@ -64,5 +64,9 @@ export default {
textarea: { placeholder: 'Vui lòng nhập...' }, textarea: { placeholder: 'Vui lòng nhập...' },
tableCol: { tableCol: {
indexLabel: 'Số sê-ri' indexLabel: 'Số sê-ri'
},
signature: {
confirmText: 'Xác nhận',
clearText: 'Xóa'
} }
} }

View File

@ -123,5 +123,9 @@ export default {
}, },
tableCol: { tableCol: {
indexLabel: '序号' indexLabel: '序号'
},
signature: {
confirmText: '确认',
clearText: '清空'
} }
} }

View File

@ -57,5 +57,9 @@ export default {
textarea: { placeholder: '請輸入...' }, textarea: { placeholder: '請輸入...' },
tableCol: { tableCol: {
indexLabel: '序號' indexLabel: '序號'
},
signature: {
confirmText: '確認',
clearText: '清空'
} }
} }

View File

@ -57,5 +57,9 @@ export default {
textarea: { placeholder: '請輸入...' }, textarea: { placeholder: '請輸入...' },
tableCol: { tableCol: {
indexLabel: '序號' indexLabel: '序號'
},
signature: {
confirmText: '確認',
clearText: '清空'
} }
} }