feat: 签名组件增加状态
- 签名组件在提交之后,可以保存状态 - 在刷新之后取消状态
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, useTemplateRef, watch } from 'vue';
|
||||
import { computed, onMounted, ref, useTemplateRef, onUnmounted, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { showConfirmDialog } from 'vant';
|
||||
|
||||
// question 属性
|
||||
@@ -17,7 +18,7 @@ const isPreview = defineModel('isPreview', { default: false });
|
||||
const signatureCanvas = useTemplateRef('signatureCanvas');
|
||||
|
||||
const canvasWidth = ref(100);
|
||||
const canvasHeight = computed(() => canvasWidth.value / 1.5);
|
||||
const canvasHeight = computed(() => canvasWidth.value / 1);
|
||||
const isEraser = ref(false);
|
||||
|
||||
let ctx: CanvasRenderingContext2D;
|
||||
@@ -61,7 +62,12 @@ const togglePen = () => {
|
||||
setPenStyle();
|
||||
};
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
onMounted(() => {
|
||||
// 设置页面刷新标记
|
||||
sessionStorage.setItem('is_page_refresh', '1');
|
||||
|
||||
if (!signatureCanvas.value) return;
|
||||
// 将 canvas 宽度和窗口的宽度保持一致
|
||||
canvasWidth.value = window.innerWidth - 60;
|
||||
@@ -190,7 +196,7 @@ let aIndex = 1;
|
||||
/**
|
||||
* 上传文件
|
||||
*/
|
||||
async function handleUploadImg() {
|
||||
async function handleUploadImg () {
|
||||
// const file = new File([saveCanvas('blob')!], 'sign.png', { type: 'image/png' });
|
||||
// const res = await CommonApi.cosUpload(file);
|
||||
// console.log(`sign upload url`, res);
|
||||
@@ -204,48 +210,199 @@ async function handleUploadImg() {
|
||||
aIndex++;
|
||||
answer.value = 'this is test' + aIndex;
|
||||
});
|
||||
|
||||
saveCanvasState();
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存canvas 状态
|
||||
* 当组件注销时需要保存状态,存到本地indexDB ,然后等待下次组件加载时恢复
|
||||
* 但在页面刷新时不保存
|
||||
*/
|
||||
function saveCanvasState () {
|
||||
if (!ctx || !signatureCanvas.value) return;
|
||||
const imageData = ctx.getImageData(0, 0, canvasWidth.value, canvasHeight.value);
|
||||
undoStack.value.push(imageData);
|
||||
currentStep.value = undoStack.value.length - 1;
|
||||
|
||||
// 检查是否是页面刷新或关闭
|
||||
const isPageRefresh = !sessionStorage.getItem('is_page_refresh');
|
||||
|
||||
// 如果是页面刷新,则不保存状态
|
||||
if (isPageRefresh) {
|
||||
console.log('页面刷新,不保存画布状态');
|
||||
// 清除之前保存的状态
|
||||
if (window.indexedDB) {
|
||||
try {
|
||||
const request = window.indexedDB.open('canvasState', 1);
|
||||
request.onsuccess = (e: Event) => {
|
||||
const db = (e.target as IDBOpenDBRequest).result;
|
||||
const tx = db.transaction('canvasState', 'readwrite');
|
||||
const store = tx.objectStore('canvasState');
|
||||
store.delete(element.value.id || 'default_id');
|
||||
tx.oncomplete = () => {
|
||||
db.close();
|
||||
};
|
||||
};
|
||||
} catch (err) {
|
||||
console.error('Error clearing canvas state:', err);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 没有 indexDB, 不保存
|
||||
if (!window.indexedDB) return;
|
||||
|
||||
// 将 canvas 转换为 base64 字符串
|
||||
const canvasDataUrl = signatureCanvas.value.toDataURL('image/png');
|
||||
|
||||
// 保存到 indexDB
|
||||
const request = window.indexedDB.open('canvasState', 1);
|
||||
|
||||
// 当数据库首次创建或版本升级时创建对象存储
|
||||
request.onupgradeneeded = (e: IDBVersionChangeEvent) => {
|
||||
const db = (e.target as IDBOpenDBRequest).result;
|
||||
if (!db.objectStoreNames.contains('canvasState')) {
|
||||
db.createObjectStore('canvasState');
|
||||
}
|
||||
};
|
||||
|
||||
request.onerror = (e: Event) => {
|
||||
console.error('Failed to open indexDB', e);
|
||||
};
|
||||
|
||||
request.onsuccess = (e: Event) => {
|
||||
const db = (e.target as IDBOpenDBRequest).result;
|
||||
try {
|
||||
const tx = db.transaction('canvasState', 'readwrite');
|
||||
const store = tx.objectStore('canvasState');
|
||||
// 只存储 base64 字符串,而不是 ImageData 对象
|
||||
store.put({ canvasDataUrl, currentStep: currentStep.value }, element.value.id || 'default_id');
|
||||
tx.oncomplete = () => {
|
||||
db.close();
|
||||
};
|
||||
} catch (err) {
|
||||
console.error('Error saving canvas state:', err);
|
||||
}
|
||||
};
|
||||
}
|
||||
onUnmounted(() => {
|
||||
// 当组件注销时,保存状态
|
||||
saveCanvasState();
|
||||
})
|
||||
|
||||
/**
|
||||
* 恢复canvas 状态
|
||||
* 当组件加载时需要从localStorage恢复状态
|
||||
*/
|
||||
function restoreCanvasState () {
|
||||
// 没有 indexDB, 不恢复
|
||||
if (!window.indexedDB) return;
|
||||
if (!ctx || !signatureCanvas.value) return;
|
||||
|
||||
const request = window.indexedDB.open('canvasState', 1);
|
||||
|
||||
// 当数据库首次创建或版本升级时创建对象存储
|
||||
request.onupgradeneeded = (e: IDBVersionChangeEvent) => {
|
||||
const db = (e.target as IDBOpenDBRequest).result;
|
||||
if (!db.objectStoreNames.contains('canvasState')) {
|
||||
db.createObjectStore('canvasState');
|
||||
}
|
||||
};
|
||||
|
||||
request.onerror = (e: Event) => {
|
||||
console.error('Failed to open indexDB', e);
|
||||
};
|
||||
|
||||
request.onsuccess = (e: Event) => {
|
||||
const db = (e.target as IDBOpenDBRequest).result;
|
||||
try {
|
||||
const tx = db.transaction('canvasState', 'readonly');
|
||||
const store = tx.objectStore('canvasState');
|
||||
const getRequest = store.get(element.value.id || 'default_id');
|
||||
|
||||
getRequest.onsuccess = (e: Event) => {
|
||||
const savedState = (e.target as IDBRequest).result;
|
||||
if (savedState && savedState.canvasDataUrl) {
|
||||
// 从 base64 字符串恢复 canvas
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
ctx.clearRect(0, 0, canvasWidth.value, canvasHeight.value);
|
||||
ctx.drawImage(img, 0, 0);
|
||||
// 恢复后获取当前 ImageData 并存入 undoStack
|
||||
const imageData = ctx.getImageData(0, 0, canvasWidth.value, canvasHeight.value);
|
||||
undoStack.value = [imageData];
|
||||
currentStep.value = 0;
|
||||
};
|
||||
img.src = savedState.canvasDataUrl;
|
||||
}
|
||||
db.close();
|
||||
};
|
||||
|
||||
getRequest.onerror = (e: Event) => {
|
||||
console.error('Error retrieving canvas state:', e);
|
||||
db.close();
|
||||
};
|
||||
} catch (err) {
|
||||
console.error('Error restoring canvas state:', err);
|
||||
}
|
||||
};
|
||||
}
|
||||
onMounted(() => {
|
||||
// 当组件加载时,恢复状态
|
||||
restoreCanvasState();
|
||||
})
|
||||
|
||||
/**
|
||||
* 清除画布状态
|
||||
*/
|
||||
function clearCanvasState () {
|
||||
if (window.indexedDB) {
|
||||
try {
|
||||
const request = window.indexedDB.open('canvasState', 1);
|
||||
request.onsuccess = (e: Event) => {
|
||||
const db = (e.target as IDBOpenDBRequest).result;
|
||||
const tx = db.transaction('canvasState', 'readwrite');
|
||||
const store = tx.objectStore('canvasState');
|
||||
store.delete(element.value.id || 'default_id');
|
||||
tx.oncomplete = () => {
|
||||
db.close();
|
||||
};
|
||||
};
|
||||
} catch (err) {
|
||||
console.error('Error clearing canvas state:', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 监听页面卸载事件,用于检测页面刷新
|
||||
window.addEventListener('beforeunload', () => {
|
||||
// 清除页面刷新标记
|
||||
sessionStorage.removeItem('is_page_refresh');
|
||||
// 页面刷新时清除画布状态
|
||||
clearCanvasState();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<van-field
|
||||
:label="element.stem"
|
||||
:required="element.config.is_required === 1"
|
||||
label-align="top"
|
||||
:border="false"
|
||||
readonly
|
||||
>
|
||||
<van-field :label="element.stem" :required="element.config.is_required === 1" label-align="top" :border="false"
|
||||
readonly>
|
||||
<template #left-icon> {{ isPreview ? element.title : index + 1 }}. </template>
|
||||
<template #label>
|
||||
<contenteditable
|
||||
v-model="element.stem"
|
||||
className="contenteditable-label"
|
||||
:active="active"
|
||||
@blur="emitValue"
|
||||
:errorMessage="errorMessage"
|
||||
></contenteditable>
|
||||
<contenteditable v-model="element.stem" className="contenteditable-label" :active="active" @blur="emitValue"
|
||||
:errorMessage="errorMessage"></contenteditable>
|
||||
</template>
|
||||
<template #input>
|
||||
<div class="sign-question">
|
||||
<canvas
|
||||
ref="signatureCanvas"
|
||||
:width="canvasWidth"
|
||||
:height="canvasHeight"
|
||||
style="border: 1px dashed #ccc; border-radius: 4px"
|
||||
>
|
||||
<canvas ref="signatureCanvas" :width="canvasWidth" :height="canvasHeight"
|
||||
style="border: 1px dashed #ccc; border-radius: 4px">
|
||||
</canvas>
|
||||
<div class="sign-text" :class="{ show: true }">
|
||||
<span
|
||||
class="icon mobilefont mobilefont-qingkong"
|
||||
title="清空"
|
||||
@click="clearCanvas"
|
||||
></span>
|
||||
<span class="icon mobilefont mobilefont-qingkong" title="清空" @click="clearCanvas"></span>
|
||||
<span class="icon mobilefont mobilefont-chexiao" @click="undo"></span>
|
||||
<span
|
||||
class="icon mobilefont"
|
||||
:class="isEraser ? 'mobilefont-huabi' : 'mobilefont-rubber'"
|
||||
@click="togglePen"
|
||||
></span>
|
||||
<span class="icon mobilefont" :class="isEraser ? 'mobilefont-huabi' : 'mobilefont-rubber'"
|
||||
@click="togglePen"></span>
|
||||
<span class="icon mobilefont mobilefont-shangchuan" @click="handleUploadImg"></span>
|
||||
</div>
|
||||
<div class="sign-tips">请在空白区域书写您的签名</div>
|
||||
|
||||
Reference in New Issue
Block a user