| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
- <template>
- <div class="sign">
- <vue-esign v-show="showSign" ref="esign" :disabled="disabled" />
- <div v-show="!showSign" class="history">
- <img :src="modelValue" alt="历史签名" />
- </div>
- <div v-if="!disabled" class="footer">
- <div v-if="state === 'default'" class="tip">
- <InfoCircleOutlined />
- <span>请在虚线框内签名</span>
- </div>
- <div v-else-if="state === 'error'" class="tip error">
- <ExclamationCircleOutlined />
- <span>签名上传失败,请尝试重新上传</span>
- </div>
- <div v-else-if="state === 'success'" class="tip success">
- <CheckOutlined />
- <span>已签名,若有问题可以点击“重签”重新签名</span>
- </div>
- <div>
- <a-button size="small" shape="round" @click="clear">重签</a-button>
- <a-button type="primary" shape="round" size="small" @click="confirm">确认</a-button>
- </div>
- </div>
- </div>
- </template>
- <script setup lang="ts">
- import { onMounted, ref, type PropType } from 'vue';
- import VueEsign from 'vue-esign';
- import { InfoCircleOutlined, ExclamationCircleOutlined, CheckOutlined } from '@ant-design/icons-vue';
- import { Form, message, Modal } from 'ant-design-vue';
- import type { UploadCoInterface } from './UploadImageFormItem';
- const props = defineProps({
- disabled: {
- type: Boolean,
- default: false
- },
- modelValue: {
- type: String,
- default: ''
- },
- /**
- * 上传工厂类
- */
- uploadCo: {
- type: Object as PropType<UploadCoInterface>,
- default: null,
- },
- });
- const emit = defineEmits(['update:modelValue']);
- const esign = ref();
- const state = ref<'default'|'error'|'success'>('default');
- const showSign = ref(true);
- const formItemContext = Form.useInjectFormItemContext();
- onMounted(() => {
- if (props.modelValue) {
- state.value = 'success';
- showSign.value = false;
- }
- })
- const clear = () => {
- if (props.modelValue) {
- Modal.confirm({
- title: '确认清除签名?',
- okText: '清除',
- onOk: () => {
- emit('update:modelValue', '');
- formItemContext.onFieldChange();
- esign.value.reset();
- state.value = 'default';
- showSign.value = true;
- }
- })
- } else {
- state.value = 'default';
- esign.value.reset();
- showSign.value = true;
- }
- }
- const confirm = () => {
- esign.value.generate().then((res: string) => {
- if (props.uploadCo) {
- //上传
- return new Promise((resolve, reject) => {
- const blob = base64ToBlob(res, 'image/png');
- const file = new File([blob], 'image.png', { type: 'image/png' });
- props.uploadCo.uploadRequest({
- file: file,
- filename: 'sign.png',
- action: '',
- headers: {},
- withCredentials: true,
- method: 'post',
- data: {},
- onProgress: () => {},
- onSuccess: (res) => {
- resolve(res.url);
- },
- onError: (err) => {
- reject(err);
- },
- })
- }).then((res) => {
- message.success('签名上传成功');
- state.value = 'success';
- formItemContext.onFieldChange();
- showSign.value = false;
- emit('update:modelValue', res);
- }).catch((err) => {
- state.value = 'error';
- Modal.error({
- title: '上传失败',
- content: '签名上传失败,请尝试重新上传:' + err.message,
- })
- });
- } else {
- // 不上传,直接返回base64字符串
- formItemContext.onFieldChange();
- showSign.value = false;
- emit('update:modelValue', res);
- }
- });
- }
- function base64ToBlob(base64: string, mimeType = 'image/png') {
- const byteCharacters = atob(base64.split(',')[1]); // 去掉 data:image/png;base64, 前缀
- const byteNumbers = new Array(byteCharacters.length);
- for (let i = 0; i < byteCharacters.length; i++) {
- byteNumbers[i] = byteCharacters.charCodeAt(i);
- }
- const byteArray = new Uint8Array(byteNumbers);
- return new Blob([byteArray], { type: mimeType });
- }
- </script>
- <style lang="scss" scoped>
- .sign {
- width: 100%;
- height: 100%;
- border: 1px dashed #ddd;
- border-radius: 10px;
- overflow: hidden;
- .history {
- position: relative;
- margin-bottom: 10px;
- img {
- max-width: 100%;
- height: 100%;
- object-fit: contain;
- }
- }
- .tip {
- font-size: 12px;
- color: #999;
- &.error {
- color: #bd2028;
- }
- &.success {
- color: #198754;
- }
- }
- .footer {
- padding: 10px;
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: space-between;
- > div {
- display: flex;
- gap: 5px;
- }
- }
- }
- </style>
|