|
|
@@ -1,42 +1,53 @@
|
|
|
<template>
|
|
|
<view
|
|
|
+ :id="containerId"
|
|
|
class="signature-container"
|
|
|
:style="containerStyle"
|
|
|
>
|
|
|
+ <text v-if="placeholder" :style="placeholderStyle">{{ placeholder }}</text>
|
|
|
<canvas
|
|
|
class="signature-canvas"
|
|
|
disable-scroll
|
|
|
:id="id"
|
|
|
:canvas-id="id"
|
|
|
:style="canvasStyle"
|
|
|
- @touchstart="handleTouchStart"
|
|
|
- @touchmove="handleTouchMove"
|
|
|
- @touchend="handleTouchEnd"
|
|
|
- @touchcancel="handleTouchEnd"
|
|
|
- @mousedown="handleMouseDown"
|
|
|
- @mousemove="handleMouseMove"
|
|
|
- @mouseup="handleMouseUp"
|
|
|
+ :width="canvasWidth"
|
|
|
+ :height="canvasHeight"
|
|
|
+ @touchstart.stop="handleTouchStart"
|
|
|
+ @touchmove.stop="handleTouchMove"
|
|
|
+ @touchend.stop="handleTouchEnd"
|
|
|
+ @touchcancel.stop="handleTouchEnd"
|
|
|
+ @mousedown.stop="handleMouseDown"
|
|
|
+ @mousemove.stop="handleMouseMove"
|
|
|
+ @mouseup.stop ="handleMouseUp"
|
|
|
></canvas>
|
|
|
</view>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import { ref, onMounted, type Ref, computed, getCurrentInstance, onBeforeUnmount } from 'vue';
|
|
|
+import { ref, onMounted, type Ref, computed, getCurrentInstance, onBeforeUnmount, nextTick } from 'vue';
|
|
|
import { propGetThemeVar, useTheme } from '../theme/ThemeDefine';
|
|
|
-import { RandomUtils } from '@imengyu/imengyu-utils';
|
|
|
+import { RandomUtils, waitTimeOut } from '@imengyu/imengyu-utils';
|
|
|
|
|
|
const id = `signatureCanvas${RandomUtils.genNonDuplicateID(10)}`;
|
|
|
+const containerId = `signatureContainer${RandomUtils.genNonDuplicateID(10)}`;
|
|
|
|
|
|
// 定义绘图相关类型
|
|
|
interface Point { x: number; y: number; }
|
|
|
interface Line { points: Point[]; color: string; width: number; }
|
|
|
|
|
|
export interface SignatureProps {
|
|
|
+ innerStyle?: object;
|
|
|
+ placeholderStyle?: object;
|
|
|
backgroundColor?: string;
|
|
|
lineColor?: string;
|
|
|
lineWidth?: number;
|
|
|
round?: boolean;
|
|
|
border?: boolean;
|
|
|
+ borderStyle?: string;
|
|
|
+ borderWidth?: number;
|
|
|
+ borderColor?: string;
|
|
|
+ placeholder?: string;
|
|
|
}
|
|
|
export interface SignatureInstance {
|
|
|
/**
|
|
|
@@ -44,6 +55,10 @@ export interface SignatureInstance {
|
|
|
*/
|
|
|
clear: () => void;
|
|
|
/**
|
|
|
+ * 设置签名图片
|
|
|
+ */
|
|
|
+ setImage: (image: string) => void;
|
|
|
+ /**
|
|
|
* 导出签名为图片
|
|
|
*/
|
|
|
export: () => Promise<string>;
|
|
|
@@ -55,8 +70,10 @@ const props = withDefaults(defineProps<SignatureProps>(), {
|
|
|
lineWidth: () => propGetThemeVar('SignatureLineWidth', 3),
|
|
|
round: () => propGetThemeVar('SignatureRound', true),
|
|
|
border: () => propGetThemeVar('SignatureBorder', true),
|
|
|
- borderWidth: () => propGetThemeVar('SignatureBorderWidth', 2),
|
|
|
- borderColor: () => propGetThemeVar('SignatureBorderColor', 'border.cell'),
|
|
|
+ borderWidth: () => propGetThemeVar('SignatureBorderWidth', 3),
|
|
|
+ borderStyle: () => propGetThemeVar('SignatureBorderStyle', 'dashed'),
|
|
|
+ borderColor: () => propGetThemeVar('SignatureBorderColor', 'border.signature'),
|
|
|
+ placeholder: () => propGetThemeVar('SignaturePlaceholder', '请在虚线框内签名'),
|
|
|
});
|
|
|
|
|
|
const theme = useTheme();
|
|
|
@@ -66,8 +83,14 @@ const containerStyle = computed(() => ({
|
|
|
backgroundColor: theme.resolveThemeColor(props.backgroundColor),
|
|
|
borderRadius: props.round ? theme.resolveThemeSize('SignatureBorderRadius', 12) : '0',
|
|
|
border: props.border ?
|
|
|
- `${theme.resolveThemeSize(props.borderWidth)}px solid ${theme.resolveThemeColor(props.borderColor)}`
|
|
|
- : 'none'
|
|
|
+ `${theme.resolveThemeSize(props.borderWidth)} ${props.borderStyle} ${theme.resolveThemeColor(props.borderColor)}`
|
|
|
+ : 'none',
|
|
|
+ ...props.innerStyle
|
|
|
+}));
|
|
|
+const placeholderStyle = computed(() => ({
|
|
|
+ fontSize: theme.resolveThemeSize('SignaturePlaceholderFontSize', 26),
|
|
|
+ color: theme.resolveThemeColor('SignaturePlaceholderColor', 'text.second'),
|
|
|
+ ...props.placeholderStyle
|
|
|
}));
|
|
|
const canvasStyle = computed(() => ({
|
|
|
width: `${canvasWidth.value}px`,
|
|
|
@@ -85,27 +108,38 @@ let timer = 0;
|
|
|
let isDrawing = false;
|
|
|
let absPos = [0,0];
|
|
|
let isDirty = true;
|
|
|
+let isCreating = false;
|
|
|
|
|
|
async function initCanvas() {
|
|
|
+ if (isCreating)
|
|
|
+ return;
|
|
|
+ isCreating = true;
|
|
|
+ await waitTimeOut(100);
|
|
|
// 获取容器尺寸信息
|
|
|
containerRect.value = await new Promise<UniApp.NodeInfo>((resolve) => {
|
|
|
uni.createSelectorQuery()
|
|
|
.in(instance)
|
|
|
- .select('.signature-container')
|
|
|
+ .select(`#${containerId}`)
|
|
|
.boundingClientRect(resolve as any)
|
|
|
.exec();
|
|
|
});
|
|
|
|
|
|
if (containerRect.value) {
|
|
|
- canvasWidth.value = containerRect.value.width!;
|
|
|
- canvasHeight.value = containerRect.value.height!;
|
|
|
+ canvasWidth.value = containerRect.value.width! - 2;
|
|
|
+ canvasHeight.value = containerRect.value.height! - 2;
|
|
|
}
|
|
|
-
|
|
|
- // 创建Canvas上下文
|
|
|
- canvasContext.value = uni.createCanvasContext(id, instance);
|
|
|
- timer = setInterval(render, 50) as any;
|
|
|
+ if (canvasWidth.value > 0 && canvasHeight.value > 0) {
|
|
|
+ if (!canvasContext.value)
|
|
|
+ // 创建Canvas上下文
|
|
|
+ canvasContext.value = uni.createCanvasContext(id, instance);
|
|
|
+ }
|
|
|
+ isCreating = false;
|
|
|
}
|
|
|
function render() {
|
|
|
+ if (canvasWidth.value <= 0 || canvasHeight.value <= 0) {
|
|
|
+ initCanvas();
|
|
|
+ return;
|
|
|
+ }
|
|
|
if (!canvasContext.value)
|
|
|
return;
|
|
|
if (!isDirty)
|
|
|
@@ -142,7 +176,7 @@ function endDrag() {
|
|
|
}
|
|
|
|
|
|
function handleTouchStart(e: any) {
|
|
|
- if (!canvasContext.value) return;
|
|
|
+ if (!canvasContext.value) return;
|
|
|
const { x, y } = e.touches[0];
|
|
|
startDrag(x, y);
|
|
|
}
|
|
|
@@ -233,15 +267,26 @@ async function exportImage(): Promise<string> {
|
|
|
}, instance);
|
|
|
});
|
|
|
}
|
|
|
+function setImage(image: string) {
|
|
|
+ if (!canvasContext.value) return;
|
|
|
+ canvasContext.value.drawImage(image, 0, 0, canvasWidth.value, canvasHeight.value);
|
|
|
+ isDirty = true;
|
|
|
+}
|
|
|
|
|
|
-onMounted(initCanvas);
|
|
|
+onMounted(async () => {
|
|
|
+ await nextTick();
|
|
|
+ await initCanvas();
|
|
|
+ timer = setInterval(render, 100) as any;
|
|
|
+});
|
|
|
onBeforeUnmount(() => {
|
|
|
clearInterval(timer);
|
|
|
+ timer = 0;
|
|
|
});
|
|
|
|
|
|
defineExpose<SignatureInstance>({
|
|
|
clear,
|
|
|
- export: exportImage
|
|
|
+ export: exportImage,
|
|
|
+ setImage
|
|
|
});
|
|
|
</script>
|
|
|
|
|
|
@@ -250,6 +295,9 @@ defineExpose<SignatureInstance>({
|
|
|
width: 100%;
|
|
|
height: 200px;
|
|
|
position: relative;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
}
|
|
|
.signature-canvas {
|
|
|
width: 100%;
|