| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- <template>
- <view
- class="signature-container"
- :style="containerStyle"
- ref="containerRef"
- >
- <canvas
- class="signature-canvas"
- disable-scroll
- :canvas-id="id"
- :style="canvasStyle"
- @touchstart="handleTouchStart"
- @touchmove="handleTouchMove"
- @touchend="handleTouchEnd"
- @touchcancel="handleTouchEnd"
- ></canvas>
- </view>
- </template>
- <script setup lang="ts">
- import { ref, onMounted, type Ref, computed, getCurrentInstance, onBeforeUnmount } from 'vue';
- import { propGetThemeVar, useTheme } from '../theme/ThemeDefine';
- import { RandomUtils } from '@imengyu/imengyu-utils';
- const id = `signatureCanvas${RandomUtils.genNonDuplicateID(10)}`;
- // 定义绘图相关类型
- interface Point { x: number; y: number; }
- interface Line { points: Point[]; color: string; width: number; }
- export interface SignatureProps {
- backgroundColor?: string;
- lineColor?: string;
- lineWidth?: number;
- round?: boolean;
- border?: boolean;
- }
- export interface SignatureInstance {
- /**
- * 清空签名
- */
- clear: () => void;
- /**
- * 导出签名为图片
- */
- export: () => Promise<string>;
- }
- const props = withDefaults(defineProps<SignatureProps>(), {
- backgroundColor: () => propGetThemeVar('SignatureBackgroundColor', 'white'),
- lineColor: () => propGetThemeVar('SignatureLineColor', 'black'),
- lineWidth: () => propGetThemeVar('SignatureLineWidth', 3),
- round: () => propGetThemeVar('SignatureRound', true),
- border: () => propGetThemeVar('SignatureBorder', true),
- borderWidth: () => propGetThemeVar('SignatureBorderWidth', 2),
- borderColor: () => propGetThemeVar('SignatureBorderColor', 'border.cell'),
- });
- const theme = useTheme();
- const instance = getCurrentInstance();
- 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'
- }));
- const canvasStyle = computed(() => ({
- width: `${canvasWidth.value}px`,
- height: `${canvasHeight.value}px`
- }));
- // 组件状态
- const canvasContext: Ref<UniApp.CanvasContext | null> = ref(null);
- const canvasWidth = ref(0);
- const canvasHeight = ref(0);
- const lines = ref<Line[]>([]);
- const currentLine = ref<Line | null>(null);
- const containerRect = ref<UniApp.NodeInfo>();
- let timer = 0;
- let isDrawing = false;
- let isDirty = true;
- async function initCanvas() {
- // 获取容器尺寸信息
- containerRect.value = await new Promise<UniApp.NodeInfo>((resolve) => {
- uni.createSelectorQuery()
- .in(instance)
- .select('.signature-container')
- .boundingClientRect(resolve as any)
- .exec();
- });
- if (containerRect.value) {
- canvasWidth.value = containerRect.value.width!;
- canvasHeight.value = containerRect.value.height!;
- }
- // 创建Canvas上下文
- canvasContext.value = uni.createCanvasContext(id, instance);
- timer = setInterval(render, 50) as any;
- }
- function render() {
- if (!canvasContext.value)
- return;
- if (!isDirty)
- return;
- isDirty = false;
- drawBackground();
- lines.value.forEach(drawLine);
- if (currentLine.value)
- drawLine(currentLine.value);
- canvasContext.value.draw();
- }
- function handleTouchStart(e: any) {
- if (!canvasContext.value) return;
- isDrawing = true;
- const { x, y } = e.touches[0];
- // 创建新线条
- currentLine.value = {
- points: [{ x, y }],
- color: props.lineColor || 'black',
- width: props.lineWidth || 3
- };
- }
- function handleTouchMove(e: any) {
- if (!isDrawing || !currentLine.value || !canvasContext.value)
- return;
- const { x, y } = e.touches[0];
- currentLine.value.points.push({ x, y });
- isDirty = true;
- }
- function handleTouchEnd() {
- if (!isDrawing || !currentLine.value)
- return;
- isDrawing = false;
- lines.value.push(currentLine.value);
- currentLine.value = null;
- }
- function drawBackground() {
- if (!canvasContext.value || !canvasWidth.value || !canvasHeight.value) return;
- const ctx = canvasContext.value;
- ctx.save();
- ctx.fillStyle = props.backgroundColor || 'white';
- ctx.fillRect(0, 0, canvasWidth.value, canvasHeight.value);
- ctx.restore();
- }
- function drawLine(line: Line) {
- if (!canvasContext.value || line.points.length < 2)
- return;
- const ctx = canvasContext.value;
- ctx.strokeStyle = line.color;
- ctx.lineWidth = line.width;
- ctx.lineCap = 'round';
- ctx.lineJoin = 'round';
- ctx.beginPath();
- const firstPoint = line.points[0];
- ctx.moveTo(firstPoint.x, firstPoint.y);
- for (let i = 1; i < line.points.length; i++) {
- ctx.lineTo(line.points[i].x, line.points[i].y);
- }
- ctx.stroke();
- }
- function clear() {
- lines.value = [];
- currentLine.value = null;
- isDirty = true;
- }
- async function exportImage(): Promise<string> {
- if (!canvasWidth.value || !canvasHeight.value) {
- throw new Error('Canvas is not initialized');
- }
- return new Promise((resolve, reject) => {
- uni.canvasToTempFilePath({
- canvasId: id,
- width: canvasWidth.value,
- height: canvasHeight.value,
- destWidth: canvasWidth.value * 2,
- destHeight: canvasHeight.value * 2,
- quality: 1,
- success: (res) => resolve(res.tempFilePath),
- fail: (err) => reject(err)
- }, instance);
- });
- }
- onMounted(initCanvas);
- onBeforeUnmount(() => {
- clearInterval(timer);
- });
- defineExpose<SignatureInstance>({
- clear,
- export: exportImage
- });
- </script>
- <style lang="scss">
- .signature-container {
- width: 100%;
- height: 200px;
- position: relative;
- }
- .signature-canvas {
- width: 100%;
- height: 100%;
- }
- </style>
|