feat[preview]: 适配签名组件

- 适配签名组件 PreviewSign
This commit is contained in:
Huangzhe
2025-03-21 18:04:15 +08:00
parent 03f79ee66a
commit 9b237cb443
3 changed files with 92 additions and 25 deletions

View File

@@ -1,12 +1,16 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, ref, useTemplateRef, toRefs } from 'vue'; import { computed, onMounted, ref, useTemplateRef } from 'vue';
const props = defineProps<{ import CommonApi from '@/api/common.js';
element: any; // question 属性
active: boolean; const element = defineModel<question>('element', { default: {} });
index: number; // 属性框是否激活
}>(); const active = defineModel<boolean>('active', { default: false });
// 题目索引
const { element, active } = toRefs(props); const index = defineModel<number>('index', { default: 0 });
// 答案
const answer = defineModel<string>('answer', { default: '' });
// 错误信息
const errorMessage = defineModel<string>('errorMessage', { default: '' });
const signatureCanvas = useTemplateRef('signatureCanvas'); const signatureCanvas = useTemplateRef('signatureCanvas');
@@ -54,10 +58,7 @@ const togglePen = () => {
isEraser.value = !isEraser.value; isEraser.value = !isEraser.value;
setPenStyle(); setPenStyle();
}; };
const errorMessage = defineModel('errorMessage', {
type: String,
default: ''
});
onMounted(() => { onMounted(() => {
if (!signatureCanvas.value) return; if (!signatureCanvas.value) return;
// 将 canvas 宽度和窗口的宽度保持一致 // 将 canvas 宽度和窗口的宽度保持一致
@@ -146,10 +147,24 @@ const clearCanvas = () => {
/** /**
* 保存画布 * 保存画布
* @param type {'dataUrl' | 'blob'} 保存类型
* @return {string | Blob | undefined | null}
*/ */
const saveCanvas = () => { const saveCanvas = (type: 'dataUrl' | 'blob'): string | Blob | undefined | null => {
let img: string | Blob | undefined | null = undefined;
if (!ctx || !signatureCanvas.value) return; if (!ctx || !signatureCanvas.value) return;
return signatureCanvas.value.toDataURL('image/png'); if (type === 'blob') {
signatureCanvas.value.toBlob(
(blob) => {
img = blob;
},
'image/png',
{ quality: 0.9 }
);
return img;
}
if (type === 'dataUrl') return signatureCanvas.value.toDataURL('image/png');
}; };
/** /**
@@ -168,10 +183,19 @@ const emit = defineEmits(['update:element']);
const emitValue = () => { const emitValue = () => {
emit('update:element', element.value); emit('update:element', element.value);
}; };
/**
* 上传文件
*/
async function handleUploadImg() {
const file = new File([saveCanvas('blob')!], 'sign.png', { type: 'image/png' });
const { url } = await CommonApi.cosUpload(file);
// 传递答案
answer.value = url;
}
</script> </script>
<template> <template>
<!-- <van-cell>-->
<van-field <van-field
:label="element.stem" :label="element.stem"
:required="element.config.is_required === 1" :required="element.config.is_required === 1"
@@ -211,13 +235,12 @@ const emitValue = () => {
:class="isEraser ? 'mobilefont-huabi' : 'mobilefont-rubber'" :class="isEraser ? 'mobilefont-huabi' : 'mobilefont-rubber'"
@click="togglePen" @click="togglePen"
></span> ></span>
<span class="icon mobilefont mobilefont-shangchuan" @click="saveCanvas"></span> <span class="icon mobilefont mobilefont-shangchuan" @click="handleUploadImg"></span>
</div> </div>
<div v-else class="sign-tips">请在空白区域书写您的签名</div> <div v-else class="sign-tips">请在空白区域书写您的签名</div>
</div> </div>
</template> </template>
</van-field> </van-field>
<!-- </van-cell>-->
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@@ -323,14 +323,15 @@
<!-- v-model:answer="question.answer"--> <!-- v-model:answer="question.answer"-->
<!-- isMobile--> <!-- isMobile-->
<!-- />--> <!-- />-->
<!-- &lt;!&ndash; 签名题 &ndash;&gt;--> <!-- 签名题 -->
<!-- <q-sign--> <preview-sign
<!-- v-else-if="question.question_type === 22"--> v-else-if="question.question_type === 22"
<!-- :config="question.config"--> :config="question.config"
<!-- :question="question"--> :question="question"
<!-- v-model:answer="question.answer"--> v-model:answer="question.answer"
<!-- isMobile--> :answerIndex="getQuestionIndex(questionsData.questions, question)"
<!-- />--> @change-answer="onRelation($event, question)"
/>
<!-- &lt;!&ndash; 知情同意书 &ndash;&gt;--> <!-- &lt;!&ndash; 知情同意书 &ndash;&gt;-->
<!-- <q-consent--> <!-- <q-consent-->
<!-- v-else-if="question.question_type === 23"--> <!-- v-else-if="question.question_type === 23"-->
@@ -540,6 +541,7 @@ import { getQuestionIndex } from '@/utils/utils.js';
import icon from '@/assets/img/create-right-back.png'; import icon from '@/assets/img/create-right-back.png';
import PreviewCheckbox from '@/views/Survey/views/Preview/components/questions/PreviewCheckbox.vue'; import PreviewCheckbox from '@/views/Survey/views/Preview/components/questions/PreviewCheckbox.vue';
import PreviewRate from '@/views/Survey/views/Preview/components/questions/PreviewRate.vue'; import PreviewRate from '@/views/Survey/views/Preview/components/questions/PreviewRate.vue';
import PreviewSign from '@/views/Survey/views/Preview/components/questions/PreviewSign.vue';
const isPreview = defineModel('isPreview', { const isPreview = defineModel('isPreview', {
type: Boolean, type: Boolean,
default: true default: true

View File

@@ -0,0 +1,42 @@
<script setup lang="ts">
import SignQuestion from '@/views/Design/components/Questions/SignQuestion.vue';
import { ref, watch } from 'vue';
const question = defineModel<question>('question', { default: {} });
const answer = defineModel<{ value: string }>('answer', { default: undefined });
const answerValue = ref<string>('');
const answerIndex = defineModel<number>('answerIndex', { default: 0 });
// emit
const emit = defineEmits(['changeAnswer']);
// 如果 answer 不为空,需要解析答案
answer.value?.value && parseAnswer();
/**
* 监听答案,如果答案变动,重新生成答案,然后提交
*/
watch(answerValue, (newValue) => {
emit('changeAnswer', { value: newValue });
});
/**
* 解析答案
* answer 格式
* {
* value: 'url'
* }
*/
function parseAnswer() {
answerValue.value = answer.value.value;
}
</script>
<template>
<sign-question
:element="question"
:active="false"
:index="answerIndex"
v-model:answer="answerValue"
:error-message="question.error"
/>
</template>
<style scoped lang="scss"></style>