index.vue 5.66 KB
<!--
 * @Date: 2022-09-06 16:29:31
 * @LastEditors: hookehuyr hookehuyr@gmail.com
 * @LastEditTime: 2022-09-07 16:50:16
 * @FilePath: /data-table/src/components/SignField/index.vue
 * @Description: 电子签名控件
-->
<template>
  <div class="sign-page">
    <div class="label">
      {{ item.component_props.label }}{{ valid
      }}<span v-if="item.component_props.required">&nbsp;*</span>
    </div>
    <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 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
      v-if="show_empty"
      class="van-field__error-message"
      style="padding: 0 1rem 1rem 1rem"
    >
      电子签名不能为空
    </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,
});

const emit = defineEmits(["active"]);

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 image_url = ref("");
const show_empty = ref(false);

const handleReset = () => {
  // 清空画板
  esign.value.reset();
  show_control.value = true;
  // 删除可能存在的签名
  image_url.value = "";
  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 };
          image_url.value = data.src;
          show_control.value = false;
          show_empty.value = false;
          emit("active", props.item.value);
        }
      }
    })
    .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;
  show_empty.value = false;
};
const cancelSign = () => {
  show_sign.value = true;
  show_empty.value = false;
  handleReset();
};

const validSign = () => {
  // 必填项 未生成签名
  if (props.item.component_props.required && !image_url.value) {
    show_empty.value = true;
  } else {
    show_empty.value = false;
  }
  return !show_empty.value;
};

defineExpose({ validSign });
</script>

<!-- <script>
export default {
  methods: {
    validSign () {
      console.warn(0);
    }
  }
}
</script> -->

<style lang="less" scoped>
.sign-page {
  // padding-bottom: 1rem;
  .label {
    padding: 1rem 1rem 0 1rem;
    font-size: 0.9rem;
    font-weight: bold;

    span {
      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%);
    }
  }

  .control-sign {
    padding-bottom: 1rem;
  }
}
</style>