feat: 增加签名问卷的支持
- 增加 SignQuestion 组件 - 增加响应的内容数据 - 在 Index 页面正常挂载组件显示
This commit is contained in:
225
src/views/Design/components/Questions/SignQuestion.vue
Normal file
225
src/views/Design/components/Questions/SignQuestion.vue
Normal file
@@ -0,0 +1,225 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, useTemplateRef } from 'vue';
|
||||
const signatureCanvas = useTemplateRef('signatureCanvas');
|
||||
|
||||
const canvasWidth = ref(100);
|
||||
const canvasHeight = computed(() => canvasWidth.value / 2.5);
|
||||
const showSignText = ref(true);
|
||||
const isEraser = ref(false);
|
||||
|
||||
let ctx: CanvasRenderingContext2D;
|
||||
let isDrawing = false;
|
||||
const undoStack = ref<ImageData[]>([]);
|
||||
const currentStep = ref(-1);
|
||||
|
||||
// 保存当前状态
|
||||
const saveState = () => {
|
||||
if (!ctx || !signatureCanvas.value) return;
|
||||
const imageData = ctx.getImageData(0, 0, signatureCanvas.value.width, signatureCanvas.value.height);
|
||||
currentStep.value += 1;
|
||||
// 移除当前步骤之后的所有状态(处理在撤销后又进行了新的绘制的情况)
|
||||
undoStack.value.splice(currentStep.value);
|
||||
undoStack.value.push(imageData);
|
||||
};
|
||||
|
||||
// 设置画笔样式
|
||||
const setPenStyle = () => {
|
||||
if (!ctx) return;
|
||||
if (isEraser.value) {
|
||||
ctx.globalCompositeOperation = 'destination-out';
|
||||
ctx.lineWidth = 20;
|
||||
} else {
|
||||
ctx.globalCompositeOperation = 'source-over';
|
||||
ctx.lineWidth = 2;
|
||||
}
|
||||
ctx.strokeStyle = '#000';
|
||||
ctx.lineCap = 'round';
|
||||
ctx.lineJoin = 'round';
|
||||
};
|
||||
|
||||
// 切换画笔/橡皮擦
|
||||
const togglePen = () => {
|
||||
isEraser.value = !isEraser.value;
|
||||
setPenStyle();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (!signatureCanvas.value) return;
|
||||
// 将 canvas 宽度和窗口的宽度保持一致
|
||||
canvasWidth.value = window.innerWidth - 50;
|
||||
|
||||
// 获取 canvas 上下文
|
||||
ctx = signatureCanvas.value.getContext('2d')!;
|
||||
if (!ctx) return;
|
||||
setPenStyle();
|
||||
|
||||
// 保存初始空白状态
|
||||
saveState();
|
||||
|
||||
// 触摸开始,开始绘制适用于移动设备
|
||||
signatureCanvas.value?.addEventListener('touchstart', (e) => {
|
||||
e.preventDefault(); // 防止页面滚动
|
||||
isDrawing = true;
|
||||
const rect = signatureCanvas.value!.getBoundingClientRect();
|
||||
const touch = e.touches[0];
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(
|
||||
touch.clientX - rect.left,
|
||||
touch.clientY - rect.top
|
||||
);
|
||||
});
|
||||
|
||||
signatureCanvas.value?.addEventListener('touchmove', (e) => {
|
||||
e.preventDefault();
|
||||
if (!isDrawing) return;
|
||||
const rect = signatureCanvas.value!.getBoundingClientRect();
|
||||
const touch = e.touches[0];
|
||||
ctx.lineTo(
|
||||
touch.clientX - rect.left,
|
||||
touch.clientY - rect.top
|
||||
);
|
||||
ctx.stroke();
|
||||
});
|
||||
|
||||
signatureCanvas.value?.addEventListener('touchend', () => {
|
||||
if (isDrawing) {
|
||||
saveState();
|
||||
}
|
||||
isDrawing = false;
|
||||
});
|
||||
|
||||
signatureCanvas.value?.addEventListener('touchcancel', () => {
|
||||
isDrawing = false;
|
||||
});
|
||||
|
||||
// 鼠标事件处理
|
||||
signatureCanvas.value?.addEventListener('mousedown', (e) => {
|
||||
isDrawing = true;
|
||||
const rect = signatureCanvas.value!.getBoundingClientRect();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(
|
||||
e.clientX - rect.left,
|
||||
e.clientY - rect.top
|
||||
);
|
||||
});
|
||||
|
||||
signatureCanvas.value?.addEventListener('mousemove', (e) => {
|
||||
if (!isDrawing) return;
|
||||
const rect = signatureCanvas.value!.getBoundingClientRect();
|
||||
ctx.lineTo(
|
||||
e.clientX - rect.left,
|
||||
e.clientY - rect.top
|
||||
);
|
||||
ctx.stroke();
|
||||
});
|
||||
|
||||
signatureCanvas.value?.addEventListener('mouseup', () => {
|
||||
if (isDrawing) {
|
||||
saveState();
|
||||
}
|
||||
isDrawing = false;
|
||||
});
|
||||
|
||||
signatureCanvas.value?.addEventListener('mouseleave', () => {
|
||||
if (isDrawing) {
|
||||
saveState();
|
||||
}
|
||||
isDrawing = false;
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 清除画布
|
||||
*/
|
||||
const clearCanvas = () => {
|
||||
if (!ctx || !signatureCanvas.value) return;
|
||||
ctx.clearRect(0, 0, signatureCanvas.value.width, signatureCanvas.value.height);
|
||||
saveState();
|
||||
};
|
||||
|
||||
/**
|
||||
* 保存画布
|
||||
*/
|
||||
const saveCanvas = () => {
|
||||
if (!ctx || !signatureCanvas.value) return;
|
||||
return signatureCanvas.value.toDataURL('image/png');
|
||||
};
|
||||
|
||||
/**
|
||||
* 撤销
|
||||
*/
|
||||
const undo = () => {
|
||||
if (!ctx || !signatureCanvas.value || currentStep.value <= 0) return;
|
||||
currentStep.value -= 1;
|
||||
const imageData = undoStack.value[currentStep.value];
|
||||
if (imageData) {
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<van-cell>
|
||||
<div class="sign-question">
|
||||
<canvas
|
||||
ref="signatureCanvas" :width="canvasWidth" :height="canvasHeight"
|
||||
style="border: 1px solid #ccc; border-radius: 4px;"
|
||||
>
|
||||
</canvas>
|
||||
<div class="sign-text" :class="{ 'show': showSignText }">
|
||||
<span @click="clearCanvas">清空</span>
|
||||
<span @click="undo">撤销</span>
|
||||
<span @click="togglePen">{{ isEraser ? '画笔' : '橡皮擦' }}</span>
|
||||
<span @click="saveCanvas">完成并上传</span>
|
||||
</div>
|
||||
</div>
|
||||
</van-cell>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.sign-question {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 0;
|
||||
|
||||
// canvas {
|
||||
// width: 100%;
|
||||
// touch-action: none;
|
||||
// }
|
||||
|
||||
.sign-text {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 100%;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
pointer-events: none;
|
||||
|
||||
&.show {
|
||||
opacity: 0.8;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
span {
|
||||
cursor: pointer;
|
||||
padding: 5px 10px;
|
||||
|
||||
&:hover {
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user