hookehuyr

✨ feat: 电子签名功能完善

<!--
* @Date: 2022-09-06 16:29:31
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2022-09-07 00:46:48
* @LastEditTime: 2022-09-07 15:58:45
* @FilePath: /data-table/src/components/SignField/index.vue
* @Description: 文件描述
* @Description: 电子签名控件
-->
<template>
<div class="sign-page">
<div class="label">{{ item.label }}<span v-if="item.required">&nbsp;*</span></div>
<div style="padding: 1rem;">
<vue-esign ref="esign" style="border: 1px dashed #c2c1c1;" :height="700" :isCrop="isCrop" :lineWidth="lineWidth"
<div style="padding: 1rem; position: relative;">
<!-- <div style="padding: 1rem; position: relative; height: 150px; background-color: #FCFCFC;border: 1px solid #EAEAEA; border-radius: 5px;"> -->
<vue-esign ref="esign" class="sign-wrapper" style="" :isCrop="isCrop" :lineWidth="lineWidth"
:lineColor="lineColor" :bgColor.sync="bgColor" />
<div v-if="show_sign" class="whiteboard">
<div class="text" @click="startSign">
<van-icon name="edit" />&nbsp;点击开始签署电子签名
</div>
</div>
</div>
<div class="control-sign">
<button @click="handleReset">清空画板</button>
<button @click="handleGenerate">生成图片</button>
<div v-if="!show_sign">
<div v-if="show_control" class="control-sign">
<van-row gutter="20" style="padding: 0 1rem;">
<van-col :span="12">
<van-button type="default" block @click="handleGenerate">确认签名</van-button>
</van-col>
<van-col :span="12">
<van-button type="default" block @click="cancelSign">取消签名</van-button>
</van-col>
</van-row>
</div>
<div v-else style="padding: 0 1rem;">
<van-button type="danger" block @click="handleReset">删除签名</van-button>
</div>
</div>
</div>
</template>
<script setup>
import { v4 as uuidv4 } from 'uuid';
import { qiniuTokenAPI, qiniuUploadAPI, saveFileAPI } from '@/api/common'
import { showSuccessToast, showFailToast } from 'vant';
const props = defineProps({
item: Object
});
</script>
<script>
import { v4 as uuidv4 } from 'uuid';
import { qiniuTokenAPI, qiniuUploadAPI, saveFileAPI } from '@/api/common'
const emit = defineEmits(['active']);
export default {
data() {
return {
lineWidth: 6,
lineColor: "#000000",
bgColor: "",
resultImg: "",
isCrop: false,
}
},
methods: {
//清空画板..
handleReset() {
this.$refs.esign.reset();
this.resultImg = "";
},
//生成签名图片..
handleGenerate () {
this.$refs.esign.generate()
.then(async res => {
// let fileName = "img1.png";
// let file = this.dataURLtoFile(res, fileName);
// console.log("file", file);
let affix = uuidv4();
let base64url = res.slice(res.indexOf(',') + 1); // 截取前缀的base64 data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAnoAAAJeCAYAA.......
// 获取七牛token
const { token, key, code } = await qiniuTokenAPI({ filename: `${affix}_sign`, file: base64url });
if (code) {
const config = {
headers: {
'Content-Type': 'application/octet-stream',
'Authorization': 'UpToken ' + token, // UpToken后必须有一个 ' '(空格)
}
}
// 上传七牛服务器
const { filekey, hash, image_info } = await qiniuUploadAPI('http://upload.qiniup.com/putb64/-1/key/' + key, base64url, config)
if (filekey) {
// 保存图片
const { data } = await saveFileAPI({ filekey, hash, format: image_info.format, height: image_info.height, width: image_info.width });
console.warn(data.src);
}
}
});
},
//将图片base64转换为文件
dataURLtoFile(dataurl, filename) {
var arr = dataurl.split(","),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
const esign = ref(null);
const lineWidth = ref(6)
const lineColor = ref('#000000')
const bgColor = ref('#FCFCFC')
const isCrop = ref(false)
const show_control = ref(true)
const handleReset = () => {
// 清空画板
esign.value.reset();
show_control.value = true;
// 删除可能存在的签名
props.item.value = { key: 'sign', value: '' };
emit('active', props.item.value)
}
const handleGenerate = () => {
esign.value.generate()
.then(async res => {
// let fileName = "img1.png";
// let file = this.dataURLtoFile(res, fileName);
// console.log("file", file);
let affix = uuidv4();
let base64url = res.slice(res.indexOf(',') + 1); // 截取前缀的base64 data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAnoAAAJeCAYAA.......
// 获取七牛token
const { token, key, code } = await qiniuTokenAPI({ filename: `${affix}_sign`, file: base64url });
if (code) {
const config = {
headers: {
'Content-Type': 'application/octet-stream',
'Authorization': 'UpToken ' + token, // UpToken后必须有一个 ' '(空格)
}
}
// 上传七牛服务器
const { filekey, hash, image_info } = await qiniuUploadAPI('http://upload.qiniup.com/putb64/-1/key/' + key, base64url, config)
if (filekey) {
// 保存图片
const { data } = await saveFileAPI({ filekey, hash, format: image_info.format, height: image_info.height, width: image_info.width });
props.item.value = { key: 'sign', value: data.src};
show_control.value = false;
emit('active', props.item.value)
}
return new File([u8arr], filename, { type: mime });
}
})
.catch(err => {
// 签名生成失败
console.warn(err);
if (err) {
showFailToast('签名生成失败');
}
})
}
//将图片base64转换为文件
const dataURLtoFile = (dataurl, filename) => {
var arr = dataurl.split(","),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, { type: mime });
}
const show_sign = ref(true);
const startSign = () => {
show_sign.value = false;
}
const cancelSign = () => {
show_sign.value = true;
handleReset()
}
</script>
<style lang="less" scoped>
.sign-page {
padding-bottom: 1rem;
.label {
padding: 1rem 1rem 0 1rem;
font-size: 0.9rem;
......@@ -101,5 +136,25 @@ export default {
color: red;
}
}
.sign-wrapper {
border: 1px solid #EAEAEA;
border-radius: 5px;
}
.whiteboard {
position: absolute;
height: 100%;
width: 100%;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
text-align: center;
.text {
position: absolute;
width: 100%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
}
</style>
......
<!--
* @Date: 2022-07-18 10:22:22
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2022-09-07 14:12:37
* @LastEditTime: 2022-09-07 16:02:34
* @FilePath: /data-table/src/views/index.vue
* @Description: 首页
-->
......@@ -11,7 +11,7 @@
<div class="table-box">
<van-form @submit="onSubmit">
<van-cell-group>
<component v-for="(item, index) in mockData" :key="index" :is="item.component" :item="item" />
<component v-for="(item, index) in mockData" :key="index" :is="item.component" :item="item" @active="onActive" />
</van-cell-group>
<div style="margin: 16px;">
<van-button round block type="primary" native-type="submit">
......@@ -24,37 +24,39 @@
<script setup>
import { createComponentType } from '@/hooks/useComponentType'
import _ from 'lodash'
const table_cover = ref('');
const table_title = ref('');
const mockData = ref([]);
const postData = ref({})
onMounted(() => {
table_cover.value = 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg'
table_title.value = '这是一个表单的描述'
mockData.value = [
// {
// key: 'phone',
// value: '',
// label: '手机号',
// placeholder: '请输入手机号',
// component: '',
// component_props: {
// name: 'phone'
// },
// required: true,
// },
// {
// key: 'username',
// value: 'test',
// label: '用户名',
// placeholder: '请输入用户名',
// component: '',
// component_props: {
// name: 'text'
// },
// required: true,
// },
{
key: 'phone',
value: '',
label: '手机号',
placeholder: '请输入手机号',
component: '',
component_props: {
name: 'phone'
},
required: true,
},
{
key: 'username',
value: 'test',
label: '用户名',
placeholder: '请输入用户名',
component: '',
component_props: {
name: 'text'
},
required: true,
},
// {
// key: 'email',
// value: '',
......@@ -143,7 +145,7 @@ onMounted(() => {
placeholder: '',
component: '',
component_props: {
name: 'sign'
name: 'sign',
},
},
// {
......@@ -185,9 +187,17 @@ onMounted(() => {
})
const onSubmit = (values) => {
console.log('submit', values);
// 合并自定义字段到提交表单字段
postData.value = _.assign(postData.value, values);
console.warn(postData.value);
// console.warn(mockData.value);
};
const onActive = (item) => {
// 返回自定义字段
if (item.key === 'sign') {
postData.value['sign'] = item.value
}
}
</script>
<style lang="less" scoped>
......