فهرست منبع

feature/签章设置

dengrui 6 ماه پیش
والد
کامیت
4103d01dc8

+ 2 - 0
package.json

@@ -52,6 +52,7 @@
     "@wangeditor/editor-for-vue": "5.1.10",
     "animate.css": "^4.1.1",
     "axios": "^1.6.7",
+    "cropperjs": "^1.6.2",
     "echarts": "^5.5.0",
     "element-plus": "^2.6.0",
     "exceljs": "^4.4.0",
@@ -79,6 +80,7 @@
     "vue-pdf-embed": "2.0.2",
     "vue-qrcode": "^2.2.2",
     "vue-router": "^4.3.0",
+    "vue-signature-pad": "^3.0.2",
     "vue3-pdfjs": "^0.1.6",
     "vue3-print-nb": "^0.1.4",
     "xlsx": "^0.18.5"

+ 1 - 0
src/api/file/index.ts

@@ -7,6 +7,7 @@ export function uploadFileApi(
   generatePdf: boolean = false
 ): AxiosPromise<FileInfo> {
   const formData = new FormData();
+  console.log(file);
   formData.append("file", file);
   formData.append("fileName", file.name);
   formData.append("generatePdf", generatePdf);

+ 32 - 0
src/api/signature/index.ts

@@ -0,0 +1,32 @@
+import request from "@/utils/request";
+
+export function addSignature(data: string) {
+  return request({
+    url: "/api/v1/base/signature/add",
+    method: "post",
+    data,
+  });
+}
+//签章预览
+export function getGenerateSeal(data: any) {
+  return request({
+    url: "/generate/seal",
+    method: "post",
+    data,
+  });
+}
+export function getClipSeal(data: any) {
+  return request({
+    url: "/clip/seal",
+    method: "post",
+    data,
+  });
+}
+export function updateSignature(data: any) {
+  return request({
+    url: "/api/v1/base/signature/update",
+    method: "post",
+    data,
+  });
+}
+

BIN
src/assets/images/signature-bg.png


+ 2 - 0
src/store/modules/dictionary.ts

@@ -35,6 +35,8 @@ export const useDictionaryStore = defineStore("dictionaryStore", () => {
     "form_params",
     "bill_type",
     "warehouse_type",
+    "signature_type",
+    "signature_attribution",
   ];
   const dicts = ref<{ [key: string]: any[] }>({});
 

+ 967 - 0
src/views/base/signature/components/opSignature.vue

@@ -0,0 +1,967 @@
+<template>
+  <el-dialog v-model="Visible" title="新增签章" width="1200" align-center>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="Visible = false">取消</el-button>
+        <el-button
+          v-if="activeName != null && sealFrom.createType != 2"
+          type="primary"
+          @click="submit()"
+          v-loading="opSignatureLoading"
+        >
+          确认{{ addStatus ? "创建" : "修改" }}
+        </el-button>
+      </div>
+    </template>
+    <el-form
+      v-if="sealFrom.createType != 2"
+      :model="form"
+      ref="signatureFormRef"
+    >
+      <el-form-item
+        label="印章名称:"
+        prop="signatureName"
+        :rules="[{ required: true, message: '请输入印章名称' }]"
+        style="width: 400px"
+      >
+        <el-input v-model="form.signatureName" />
+      </el-form-item>
+
+      <el-form-item
+        label="签章归属:"
+        prop="signatureAttribution"
+        :rules="[{ required: false, message: '请输入印章名称' }]"
+        style="width: 400px"
+      >
+        <el-select v-model="form.signatureAttribution">
+          <el-option
+            v-for="item in dicts.signature_attribution"
+            :key="item"
+            :label="item.dictLabel"
+            :value="item.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item
+        v-if="activeName == '2' && myselfUpload == true"
+        label="工序名称:"
+        :rules="[{ required: true, message: '请输入' }]"
+        style="width: 400px"
+      >
+        <el-input v-model="form.opName" />
+      </el-form-item>
+      <el-form-item
+        v-if="activeName == '3' && myselfUpload == true"
+        label="检验编号:"
+        :rules="[{ required: true, message: '请输入' }]"
+        style="width: 400px"
+      >
+        <el-input v-model="form.jyName" />
+      </el-form-item>
+      <el-form-item
+        v-if="
+          sealFrom.createType == 1 && activeName == '4' && myselfUpload == true
+        "
+        label="环绕字段:"
+        prop="topText"
+        :rules="[{ required: true, message: '请输入' }]"
+        style="width: 400px"
+      >
+        <el-input v-model="form.topText" />
+      </el-form-item>
+      <el-form-item
+        v-if="
+          sealFrom.createType == 1 && activeName == '4' && myselfUpload == true
+        "
+        label="横排字段:"
+        :rules="[{ required: false, message: '请输入' }]"
+        style="width: 400px"
+      >
+        <el-input v-model="form.middleText" />
+      </el-form-item>
+      <el-form-item label="启用状态:">
+        <el-switch
+          v-model="form.signatureState"
+          active-text="启用"
+          active-value="1"
+          inactive-text="禁用"
+          inactive-value="0"
+          style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949"
+        />
+      </el-form-item>
+    </el-form>
+
+    <div style="display: flex">
+      <div v-if="editStatus == true" style="margin-right: 20px">
+        <div>原签章内容</div>
+        <img
+          style="width: 150px; height: 150px"
+          :src="imageUrl"
+          v-if="oldUrl"
+        />
+        <span v-else>无内容(请上传后查看效果)</span>
+      </div>
+      <div
+        v-if="
+          myselfUpload != true && (addStatus ? true : editFirst ? false : true)
+        "
+      >
+        <div>现签章内容</div>
+        <img
+          style="width: 150px; height: 150px"
+          :src="newImageUrl"
+          v-if="form.signatureFiles"
+        />
+        <span v-else>无内容(请上传后查看效果)</span>
+      </div>
+    </div>
+
+    <!-- 签章区域 -->
+    <div v-if="activeName != null">
+      <el-tabs v-model="activeName" class="demo-tabs" type="card">
+        <el-tab-pane
+          :label="item.dictLabel"
+          :name="item.dictValue"
+          v-for="(item, index) in dicts.signature_type"
+          :key="item"
+        />
+      </el-tabs>
+      <el-switch
+        v-model="myselfUpload"
+        active-text="借用模版"
+        inactive-text="自定义上传"
+        @click="changeMyselfUpload"
+      />
+      <div v-if="myselfUpload">
+        <!-- 人员签章 -->
+        <div
+          v-show="activeName == '1'"
+          style="display: flex; flex-direction: column; align-items: center"
+        >
+          <div class="body">
+            <div>
+              <div>
+                <div
+                  class="signature-box"
+                  :class="!startSignature ? 'signature-bg' : ''"
+                >
+                  <VueSignaturePad
+                    ref="signaturePad"
+                    :key="startSignature"
+                    :images="[
+                      { src: 'A.png', x: 0, y: 0 },
+                      { src: 'B.png', x: 0, y: 10 },
+                      { src: 'C.png', x: 0, y: 20 },
+                    ]"
+                    :options="options"
+                  />
+                </div>
+              </div>
+              <div class="operaBox">
+                <el-button type="primary" @click="undoSignature"
+                  >撤销</el-button
+                >
+                <el-button @click="clearSignature">重写</el-button>
+                <el-button type="success" @click="showSignature"
+                  >预览</el-button
+                >
+              </div>
+            </div>
+            <div>
+              <div class="showBox">
+                <img
+                  ref="imgRef"
+                  :src="'data:image/png;base64,' + handwritingEl"
+                  v-if="handwritingEl"
+                />
+                <span v-else
+                  >请先设置手写签名
+                  <br />
+                  建议签名铺满书写框
+                </span>
+              </div>
+              <div class="operaBox" v-if="handwritingEl">
+                <el-button type="primary" @click="updownPng">下载</el-button>
+              </div>
+            </div>
+          </div>
+        </div>
+        <!-- 工序签章 -->
+        <div
+          v-if="activeName == '2'"
+          style="display: flex; flex-direction: column; align-items: center"
+        >
+          <div class="showGJBox">
+            <div v-if="form.opName" ref="GJRef" class="GJBox">
+              {{ form.opName }}
+            </div>
+            <span v-else>请先设置工序名称</span>
+          </div>
+          <div class="operaBox" v-if="form.name">
+            <el-button type="primary" @click="updownGJPng">下载</el-button>
+          </div>
+        </div>
+        <!-- 检验签章 -->
+        <div
+          v-if="activeName == '3'"
+          style="display: flex; flex-direction: column; align-items: center"
+        >
+          <div class="showGJBox">
+            <div v-if="form.jyName" ref="JYRef" class="JYBox">
+              检<span style="margin-left: 20px">{{ form.jyName }}</span>
+            </div>
+            <span v-else>请先设置检验编号</span>
+          </div>
+          <div class="operaBox" v-if="form.name">
+            <el-button type="primary" @click="updownJYPng">下载</el-button>
+          </div>
+        </div>
+        <!-- 公章签章 -->
+        <div
+          v-if="activeName == '4'"
+          style="display: flex; flex-direction: column; align-items: center"
+        >
+          <el-radio-group
+            v-model="sealFrom.createType"
+            size="large"
+            @change="createTypeChange"
+            style="margin-bottom: 20px"
+          >
+            <el-radio-button :value="1">模板创建制作</el-radio-button>
+            <el-radio-button :value="2">印模生成工具</el-radio-button>
+          </el-radio-group>
+
+          <div
+            style="display: flex; flex-direction: column; align-items: center"
+          >
+            <div class="seal-make-layout">
+              <div class="template-make" v-if="sealFrom.createType == 1">
+                <el-button
+                  type="success"
+                  v-loading="uploading"
+                  @click="showGZSignature"
+                  >预览</el-button
+                >
+              </div>
+              <div class="seal-upload" v-if="sealFrom.createType == 2">
+                <div v-if="fileList.length == 0" class="upload-before">
+                  <el-upload
+                    v-model:file-list="fileList"
+                    :show-file-list="false"
+                    :limit="1"
+                    @change="handleChange"
+                    :auto-upload="false"
+                  >
+                    <div
+                      class="local-upload pointer"
+                      v-if="fileList.length < 1"
+                    >
+                      <div style="width: 100%">点击上传</div>
+                    </div>
+                  </el-upload>
+                  <div class="prompt">
+                    <p style="font-weight: 600">注意:</p>
+                    <p>
+                      此模块仅作为工具实现目标效果(旋转、虚化后可导出),如需创建请在模版创建制作。
+                    </p>
+                  </div>
+                </div>
+                <div class="upload-img" v-else>
+                  <img
+                    :src="uploadSeal"
+                    ref="sealRef"
+                    @load="imgeLoad"
+                    style="height: 100%; display: none"
+                  />
+                </div>
+                <div class="SealActions" v-if="fileList.length > 0">
+                  <el-row style="margin-top: 10px">
+                    <el-col :span="4" class="center">
+                      <el-icon size="20"
+                        ><RefreshLeft @click="sealActions(-1)"
+                      /></el-icon>
+                    </el-col>
+                    <el-col :span="4" class="center">
+                      <el-icon size="20"
+                        ><RefreshRight @click="sealActions(1)"
+                      /></el-icon>
+                    </el-col>
+                    <el-col :span="4" class="center">
+                      <el-icon size="20"
+                        ><Minus @click="sealActions(-2)"
+                      /></el-icon>
+                    </el-col>
+                    <el-col :span="4" class="center">
+                      <el-icon size="20"
+                        ><Plus @click="sealActions(2)"
+                      /></el-icon>
+                    </el-col>
+                    <el-col :span="24">
+                      <el-upload
+                        v-model:file-list="fileList"
+                        :show-file-list="false"
+                        @change="handleChange2"
+                      >
+                        <el-button type="link">重新上传</el-button>
+                      </el-upload>
+                    </el-col>
+                  </el-row>
+                  <el-row style="display: flex; align-items: center">
+                    <el-col :span="8">
+                      <h4 style="line-height: 32px">旋转角度:</h4>
+                    </el-col>
+                    <el-col :span="16">
+                      <el-slider
+                        @input="sealRotateChange"
+                        v-model="sealOptions.sealRotate"
+                        :min="-180"
+                        :max="180"
+                      />
+                    </el-col>
+                  </el-row>
+                  <el-row style="display: flex; align-items: center">
+                    <el-col :span="8">
+                      <h4 style="line-height: 32px">背景透明度:</h4>
+                    </el-col>
+                    <el-col :span="16">
+                      <el-slider
+                        @input="buildCropperImage"
+                        v-model="sealOptions.sealBackground"
+                        :min="0"
+                        :max="100"
+                      />
+                    </el-col>
+                  </el-row>
+                </div>
+              </div>
+
+              <div class="seal-preview">
+                <div class="title">签章预览</div>
+                <div class="seal-preview-img">
+                  <img
+                    ref="GZRef"
+                    :src="'data:image/png;base64,' + sealFrom.sealPreview"
+                    style="width: 100%; height: 100%"
+                    alt="暂未生成签章"
+                    v-if="sealFrom.sealPreview"
+                  />
+                  <span style="color: #aaa" v-else>暂未生成签章</span>
+                </div>
+                <div class="operaBox" v-if="sealFrom.sealPreview">
+                  <el-button type="primary" @click="updownGZPng"
+                    >下载</el-button
+                  >
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div v-else>
+        <!-- //自定义上传 -->
+        <FilesUpload
+          v-model:src="fileUrl"
+          v-model:pdf-list="pdfUrlList"
+          v-model:file-name-list="fileNameList"
+          :generate-pdf="true"
+          @finished="setFileUrl"
+        />
+      </div>
+    </div>
+    <div v-else style="color: red">功能未开放,请联系管理员!</div>
+  </el-dialog>
+</template>
+<script setup>
+import { useDictionaryStore } from "@/store";
+import { VueSignaturePad } from "vue-signature-pad";
+import { useDebounceFn } from "@vueuse/core";
+import html2canvas from "html2canvas";
+import { uploadFileApi } from "@/api/file";
+import Cropper from "cropperjs";
+import "cropperjs/dist/cropper.css";
+import {
+  addSignature,
+  getGenerateSeal,
+  getClipSeal,
+  updateSignature,
+} from "@/api/signature";
+
+const { dicts } = useDictionaryStore();
+const Visible = ref(true);
+const addStatus = ref(false);
+const props = defineProps({
+  editStatus: Boolean,
+  formData: Object,
+  rowObj: Object,
+});
+const form = ref({
+  signatureName: "",
+  signatureFiles: "",
+  signatureAttribution: "",
+});
+const changeMyselfUpload = () => {
+  form.value.signatureFiles = null;
+};
+function base64ToBlob(base64, type = "image/png") {
+  const base64Data = base64.split(",")[1];
+  const byteCharacters = atob(base64Data);
+  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: type });
+}
+
+const opSignatureLoading = ref(false);
+const uploading = ref(false);
+const submit = async () => {
+  opSignatureLoading.value = true;
+  try {
+    await signatureFormRef.value.validate(async (valid, fields) => {
+      if (valid) {
+        //入口逻辑判断是否为自定义上传
+        if (myselfUpload.value == false) {
+          //此处为自定义上传
+          if (form.value.signatureFiles) {
+            if (addStatus.value == true) {
+              add(form.value.signatureFiles);
+            } else {
+              edit(form.value.signatureFiles);
+            }
+          } else {
+            ElMessage.warning("请先上传印章!");
+          }
+        } else {
+          let blob = null;
+          //此处为借用模版创建先生成文件,进行自动进行上传操作,后在提交文件地址完成创建
+          //根据类型选择图片数据进行处理上传
+          switch (activeName.value) {
+            case "1":
+              const { isEmpty, data } = signaturePad.value.saveSignature();
+              if (isEmpty) {
+                message.warning("签名为空,请书写后再进行添加");
+                return;
+              }
+              const resultImg = data.split(",");
+              blob = base64ToBlob("data:image/png;base64," + resultImg[1]);
+              break;
+            case "2":
+              const chartContainer1 = GJRef.value;
+              const canvas1 = await html2canvas(chartContainer1);
+              const img1 = canvas1.toDataURL("image/png");
+              blob = base64ToBlob(img1);
+              break;
+            case "3":
+              const chartContainer2 = JYRef.value;
+              const canvas2 = await html2canvas(chartContainer2);
+              const img2 = canvas2.toDataURL("image/png");
+              blob = base64ToBlob(img2);
+              break;
+            case "4":
+              const response = await getGenerateSeal({
+                middleText: form.value.middleText,
+                topText: form.value.topText,
+              });
+              blob = base64ToBlob(
+                "data:image/png;base64," + response.data.result.entSeal
+              );
+              break;
+          }
+
+          const file = new File([blob], "签章文件.png", {
+            type: "image/png",
+          });
+
+          //上传到文件服务器
+          uploadFileApi(file, true)
+            .then((res) => {
+              if (addStatus.value == true) {
+                add(res.data.fileUrl);
+              } else {
+                edit(res.data.fileUrl);
+              }
+            })
+            .catch((err) => {
+              ElMessage.error(err.message);
+            });
+        }
+      } else {
+        ElMessage.error("请检查表单选项");
+      }
+    });
+  } catch (e) {
+    ElMessage.error(e);
+  } finally {
+    opSignatureLoading.value = false;
+  }
+};
+const add = async (url) => {
+  form.value.signatureFiles = url;
+  const { code } = await addSignature({
+    ...form.value,
+    signatureType: activeName.value,
+  });
+  if (code == "200") {
+    ElMessage.success("添加成功");
+    emit("close");
+  }
+};
+const edit = async (url) => {
+  form.value.signatureFiles = url;
+  const { code } = await updateSignature({
+    ...form.value,
+    signatureType: activeName.value,
+  });
+  if (code == "200") {
+    ElMessage.success("添加成功");
+    emit("close");
+  }
+};
+const update = async () => {};
+const oldUrl = ref("");
+const imageUrl = computed(() => {
+  let url = import.meta.env.VITE_APP_UPLOAD_URL + oldUrl.value;
+  return url;
+});
+const newImageUrl = computed(() => {
+  let url = import.meta.env.VITE_APP_UPLOAD_URL + form.value.signatureFiles;
+  return url;
+});
+const activeName = ref(null);
+const emit = defineEmits(["close"]);
+const editFirst = ref(true);
+const setActiveName = () => {
+  if (dicts.signature_type) {
+    activeName.value = dicts.signature_type[0].dictValue;
+  } else {
+    activeName.value = null;
+  }
+};
+const myselfUploadUseHook = () => {
+  //是否自己上传
+  const myselfUpload = ref(false);
+  const fileUrl = ref();
+  const pdfUrlList = ref([]);
+  const fileNameList = ref([]);
+  const setFileUrl = () => {
+    form.value.signatureFiles = fileUrl.value;
+    if (addStatus.value == false) {
+      editFirst.value = false;
+    }
+  };
+  return { myselfUpload, fileUrl, pdfUrlList, fileNameList, setFileUrl };
+};
+const { myselfUpload, fileUrl, pdfUrlList, fileNameList, setFileUrl } =
+  myselfUploadUseHook();
+
+const handwritingUseHook = () => {
+  const signaturePad = ref();
+  const signatureFormRef = ref();
+  const startSignature = ref(false);
+  //图片二进制编码
+  const handwritingEl = ref(null);
+  const imgRef = ref(null);
+  const options = ref({
+    penColor: "#000",
+    dotSize: (1 + 4) / 2,
+    minWidth: 1,
+    maxWidth: 4,
+    throttle: 16,
+    minDistance: 5,
+    backgroundColor: "rgba(0,0,0,0)",
+    velocityFilterWeight: 0.5,
+    onBegin: () => {
+      startSignature.value = true;
+    },
+    onEnd: () => {},
+  });
+  function undoSignature() {
+    signaturePad.value.undoSignature();
+  }
+  function clearSignature() {
+    signaturePad.value.clearSignature();
+    startSignature.value = false;
+  }
+  //下载png
+  const updownPng = async () => {
+    const chartContainer = imgRef.value;
+    const canvas = await html2canvas(chartContainer);
+    const img = canvas.toDataURL("image/png");
+    const a = document.createElement("a");
+    a.href = img;
+    a.download = "手写签名.png";
+    a.click();
+  };
+  //展示图片
+  function showSignature() {
+    //获取签名图片
+    const { isEmpty, data } = signaturePad.value.saveSignature();
+    if (isEmpty) {
+      ElMessage.warning("签名为空,请书写后再进行添加");
+      return;
+    }
+    const resultImg = data.split(",");
+    //将签名图片返给main页面
+    handwritingEl.value = resultImg[1];
+  }
+  return {
+    signaturePad,
+    signatureFormRef,
+    startSignature,
+    handwritingEl,
+    imgRef,
+    options,
+    undoSignature,
+    clearSignature,
+    updownPng,
+    showSignature,
+  };
+};
+const {
+  signaturePad,
+  signatureFormRef,
+  startSignature,
+  handwritingEl,
+  imgRef,
+  options,
+  undoSignature,
+  clearSignature,
+  updownPng,
+  showSignature,
+} = handwritingUseHook();
+
+//工序签章
+const GJUseHook = () => {
+  const GJRef = ref(null);
+  const updownGJPng = async () => {
+    const chartContainer = GJRef.value;
+    const canvas = await html2canvas(chartContainer);
+    const img = canvas.toDataURL("image/png");
+    const a = document.createElement("a");
+    a.href = img;
+    a.download = "手写签名.png";
+    a.click();
+  };
+  return { GJRef, updownGJPng };
+};
+const { GJRef, updownGJPng } = GJUseHook();
+//检验签章
+const JYUseHook = () => {
+  const JYRef = ref(null);
+  const updownJYPng = async () => {
+    const chartContainer = JYRef.value;
+    const canvas = await html2canvas(chartContainer);
+    const img = canvas.toDataURL("image/png");
+    const a = document.createElement("a");
+    a.href = img;
+    a.download = "手写签名.png";
+    a.click();
+  };
+  return { JYRef, updownJYPng };
+};
+const { JYRef, updownJYPng } = JYUseHook();
+//公章签章
+const GZUseHook = () => {
+  const GZRef = ref(null);
+  const fileList = ref([]);
+  const sealCropper = ref();
+  const uploadSeal = ref(null);
+  const sealRef = ref(null);
+  const firstUpload = ref(true);
+  const sealOptions = ref({
+    sealRotate: 0,
+    sealBackground: 50,
+    imageWidth: 400,
+    imageHeight: 400,
+  });
+  function getBase64(file) {
+    return new Promise((resolve, reject) => {
+      const reader = new FileReader();
+      reader.readAsDataURL(file);
+      reader.onload = () => resolve(reader.result);
+      reader.onerror = (error) => reject(error);
+    });
+  }
+  const sealFrom = ref({
+    createType: 1,
+    sealStyle: 1,
+    entpName: "",
+    middleText: "测试印章",
+    sealPreview: false,
+  });
+  function createTypeChange(value) {
+    sealFrom.value.sealPreview = false;
+  }
+  function handleChange(info) {
+    getBase64(info.raw).then((res) => {
+      uploadSeal.value = res;
+    });
+  }
+  const showGZSignature = async () => {
+    uploading.value = true;
+    try {
+      const response = await getGenerateSeal({
+        middleText: form.value.middleText,
+        topText: form.value.topText,
+      });
+      sealFrom.value.sealPreview = response.data.result.entSeal;
+    } finally {
+      uploading.value = false;
+    }
+  };
+  const updownGZPng = async () => {
+    const chartContainer = GZRef.value;
+    const canvas = await html2canvas(chartContainer);
+    const img = canvas.toDataURL("image/png");
+    const a = document.createElement("a");
+    a.href = img;
+    a.download = "签章.png";
+    a.click();
+  };
+  //重新上传印模图片
+  async function handleChange2(info) {
+    firstUpload.value = false;
+    await handleChange(info);
+  }
+
+  //印章图片旋转
+  const sealActions = useDebounceFn((type) => {
+    switch (type) {
+      case -1:
+        sealCropper.value.rotate(-90);
+        break;
+      case 1:
+        sealCropper.value.rotate(90);
+        break;
+      case -2:
+        sealCropper.value.zoom(-0.1);
+        break;
+      case 2:
+        sealCropper.value.zoom(0.1);
+        break;
+    }
+    setTimeout(function () {
+      buildCropperImage();
+    }, 100);
+  }, 500);
+
+  function sealRotateChange(to) {
+    sealCropper.value.rotateTo(to);
+    setTimeout(function () {
+      buildCropperImage();
+    }, 100);
+  }
+  //将上传的图片加载到 图片处理工具中
+  function sealPreviewCropper() {
+    sealCropper.value = new Cropper(sealRef.value, {
+      viewMode: 1,
+      dragMode: "move",
+      preview: ".before",
+      initialAspectRatio: 1,
+      aspectRatio: 1,
+      background: true,
+      autoCrop: true,
+      autoCropArea: 0.7,
+      zoomOnWheel: true,
+      zoomOnTouch: true,
+      cropBoxResizable: false,
+      cropBoxMovable: false,
+      wheelZoomRatio: 0.05,
+      cropend: sealCropend,
+    });
+  }
+
+  //图片处理完成后调用后端服务进行处理
+  function sealCropend(end) {
+    buildCropperImage();
+  }
+
+  //印模图片加载完成事件
+  function imgeLoad(img) {
+    if (!firstUpload.value) {
+      sealCropper.value.destroy();
+    }
+    sealPreviewCropper();
+  }
+
+  const buildCropperImage = useDebounceFn(async () => {
+    if (!sealCropper.value) {
+      return;
+    } else {
+    }
+
+    // sealCropper.value.rotateTo(sealOptions.value.sealRotate);
+    const seal = sealCropper.value
+      .getCroppedCanvas({
+        width: sealOptions.value.imageWidth,
+        height: sealOptions.value.imageHeight,
+        imageSmoothingQuality: "high",
+      })
+      .toDataURL("image/jpeg");
+
+    const data = {
+      image: seal.split(",")[1],
+      colorRange: sealOptions.value.sealBackground + 120,
+    };
+    const response = await getClipSeal(data);
+    sealFrom.value.sealPreview = response.data.result.entSeal;
+  });
+  return {
+    GZRef,
+    fileList,
+    sealFrom,
+    sealOptions,
+    sealRef,
+    uploadSeal,
+    handleChange,
+    createTypeChange,
+    updownGZPng,
+    showGZSignature,
+    imgeLoad,
+    handleChange2,
+    sealActions,
+    sealRotateChange,
+    buildCropperImage,
+  };
+};
+const {
+  GZRef,
+  fileList,
+  sealFrom,
+  sealOptions,
+  sealRef,
+  uploadSeal,
+  handleChange,
+  createTypeChange,
+  updownGZPng,
+  showGZSignature,
+  imgeLoad,
+  handleChange2,
+  sealActions,
+  sealRotateChange,
+  buildCropperImage,
+} = GZUseHook();
+onMounted(() => {
+  if (props.editStatus == true) {
+    addStatus.value = false;
+    form.value.signatureName = props.rowObj.signatureName;
+    form.value.signatureState = props.rowObj.signatureState;
+    form.value.signatureType = props.rowObj.signatureType;
+    form.value.signatureAttribution = props.rowObj.signatureAttribution;
+    form.value.signatureFiles = props.rowObj.signatureFiles;
+    form.value.id = props.rowObj.id;
+    oldUrl.value = props.rowObj.signatureFiles;
+  } else {
+    addStatus.value = true;
+  }
+  setActiveName();
+});
+watch(
+  () => Visible.value,
+  (val) => {
+    if (val == false) {
+      emit("close");
+    }
+  }
+);
+</script>
+<style lang="scss" scoped>
+.local-upload {
+  width: 160px;
+  height: 160px;
+  padding: 10px;
+  border: 1px dashed rgba(0, 0, 0, 0.3);
+  display: flex;
+  align-items: center;
+  justify-items: center;
+  text-align: center;
+}
+.upload-img {
+  width: 200px;
+  height: 200px;
+}
+.upload-before {
+  display: flex;
+}
+.upload-before .prompt {
+  flex: 1;
+  height: 100%;
+  font-size: 12px;
+  padding: 0 10px 0 20px;
+}
+.signature-box {
+  width: 360px;
+  height: 240px;
+  border: 1px solid #dedede;
+  margin: 0 auto;
+}
+.signature-bg {
+  background: url("@/assets/images/signature-bg.png") no-repeat;
+  background-size: 90% 90%;
+  background-position: 50% 50%;
+}
+.operaBox {
+  text-align: center;
+  margin-top: 20px;
+}
+.showGJBox {
+  border: 1px solid #dedede;
+  width: 240px;
+  height: 60px;
+  margin: 0 auto;
+  justify-content: center;
+  align-items: center;
+  display: flex;
+  .GJBox {
+    font-size: 20px;
+    color: red;
+    display: inline;
+    border: 2px solid red;
+  }
+  .JYBox {
+    font-size: 20px;
+    color: red;
+    display: inline;
+    border: 2px solid red;
+  }
+}
+.body {
+  display: flex;
+  justify-content: space-evenly;
+  .showBox {
+    width: 360px;
+    height: 240px;
+    border: 1px solid #dedede;
+    margin: 0 auto;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+  }
+}
+.seal-make-layout {
+  display: flex;
+
+  .template-make {
+  }
+  .seal-preview {
+    padding-left: 40px;
+    .title {
+      font-size: 14px;
+      font-weight: 600;
+      height: 30px;
+    }
+  }
+
+  .seal-upload {
+    padding: 20px 0;
+    width: 442px;
+  }
+  .seal-preview-img {
+    width: 200px;
+    height: 200px;
+    padding: 10px;
+    border: 1px solid #ededed;
+  }
+}
+</style>

+ 226 - 0
src/views/base/signature/index.vue

@@ -0,0 +1,226 @@
+<template>
+  <div class="mainContentBox">
+    <avue-crud
+      ref="crudRef"
+      v-model:search="search"
+      v-model="form"
+      :data="data"
+      :option="option"
+      v-model:page="page"
+      @row-save="addRow"
+      @row-update="updateRow"
+      @row-del="deleteRow"
+      @search-change="searchChange"
+      @search-reset="resetChange"
+      @size-change="dataList"
+      @current-change="dataList"
+      @selection-change="selectionChange"
+    >
+      <template #drawingPath-form="scope">
+        <!--        <single-upload v-model="form.drawingPath" :generatePdf="true"/>-->
+        <FilesUpload
+          v-model:src-list="srcList"
+          v-model:pdf-list="pdfUrlList"
+          v-model:file-name-list="fileNameList"
+          :limit="10"
+          :generate-pdf="true"
+          @finished="testFiles"
+        />
+      </template>
+      <template #signatureState="scope">
+        <el-switch
+          active-value="1"
+          inactive-value="0"
+          inline-prompt
+          active-text="启用"
+          inactive-text="禁用"
+          v-model="scope.row.signatureState"
+          @click="changeItem(scope.row)"
+          class="ml-2"
+          style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949"
+        />
+      </template>
+      <template #menu="{ row, index, type }">
+        <el-button @click="toEdiet(row)" text type="primary">修改</el-button>
+        <el-button @click="deleteRecord(row, index, done)" text type="primary"
+          >删除</el-button
+        >
+        <el-button @click="toLook(row.signatureFiles)" text type="primary"
+          >预览</el-button
+        >
+      </template>
+      <template #menu-left="{ size }">
+        <el-button
+          type="primary"
+          @click="
+            editStatus = false;
+            addShow = true;
+          "
+          >新增</el-button
+        >
+      </template>
+    </avue-crud>
+    <div id="imgeview">
+      <el-image
+        style="width: 1px; height: 1px"
+        :src="url"
+        :zoom-rate="1.2"
+        :max-scale="7"
+        :min-scale="0.2"
+        :initial-index="0"
+        :preview-src-list="imgSrcList"
+        :hide-on-click-modal="true"
+        fit="cover"
+      />
+    </div>
+  </div>
+
+  <OpSignature v-if="addShow" @close="closeShow" :editStatus :rowObj />
+</template>
+<script setup>
+import { ref, getCurrentInstance } from "vue";
+import { useCrud } from "@/hooks/userCrud";
+import { useCommonStoreHook, useDictionaryStore } from "@/store";
+import { updateSignature } from "@/api/signature";
+import dictDataUtil from "@/common/configs/dictDataUtil";
+import PDFView from "@/components/PDFView/index.vue";
+import OpSignature from "./components/opSignature.vue";
+const { isShowTable, tableType } = toRefs(useCommonStoreHook());
+// 数据字典相关
+const { dicts } = useDictionaryStore();
+const url =
+  "https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg";
+const toLook = (url) => {
+  imgSrcList.value = [];
+  imgSrcList.value.push(import.meta.env.VITE_APP_UPLOAD_URL + url);
+  setTimeout(() => {
+    document
+      .getElementById("imgeview")
+      .firstElementChild.firstElementChild.click();
+  }, 0);
+};
+const rowObj = ref(null);
+const toEdiet = (row) => {
+  editStatus.value = true;
+  rowObj.value = row;
+  addShow.value = true;
+};
+const imgSrcList = ref([]);
+const editStatus = ref(false);
+const addShow = ref(false);
+const closeShow = () => {
+  addShow.value = false;
+  dataList();
+};
+
+const pdfUrlList = ref([]);
+const srcList = ref([]);
+const fileNameList = ref([]);
+const testFiles = () => {
+  form.value.pdfPathList = pdfUrlList.value;
+  form.value.drawingPathList = srcList.value;
+  form.value.drawingPath = srcList.value[0];
+  form.value.fileNameList = fileNameList.value;
+};
+
+const changeItem = (row) => {
+  updateSignature(row).then(() => {
+    ElMessage.success("操作成功");
+    dataList();
+  });
+};
+
+const addRow = (form2, done) => {
+  createRow(form, done, done);
+  pdfUrlList.value = [];
+  srcList.value = [];
+  fileNameList.value = [];
+};
+// 传入一个url,后面不带/
+const { form, data, option, search, page, toDeleteIds, Methords, Utils } =
+  useCrud({
+    src: "/api/v1/base/signature",
+  });
+const {
+  dataEditList,
+  createRow,
+  updateRow,
+  deleteRow,
+  searchChange,
+  dataList,
+  resetChange,
+} = Methords; //增删改查
+const { selectionChange, multipleDelete } = Methords; //选中和批量删除事件
+const { checkBtnPerm, downloadTemplate, exportData } = Utils; //按钮权限等工具
+
+const crudRef = ref(null); //crudRef.value 获取avue-crud对象
+
+onMounted?.(() => {
+  dataEditList();
+});
+
+// 设置表格列或者其他自定义的option
+option.value = Object.assign(option.value, {
+  selection: false,
+  viewBtn: false,
+  editBtn: false,
+  delBtn: false,
+  menu: true,
+  addBtn: false,
+  column: [
+    {
+      label: "公章名称",
+      prop: "signatureName",
+      search: true,
+      overHidden: true,
+    },
+    {
+      label: "公章类型",
+      prop: "signatureType",
+      overHidden: true,
+      search: true,
+      type: "select",
+      dicData: dicts.signature_type,
+      props: { label: "dictLabel", value: "dictValue" },
+    },
+    {
+      label: "公章归属",
+      prop: "signatureAttribution",
+      type: "select",
+      search: true,
+      overHidden: true,
+      editDisplay: false,
+      addDisplay: false,
+      dicData: dicts.signature_attribution,
+      props: { label: "dictLabel", value: "dictValue" },
+    },
+
+    {
+      label: "创建时间",
+      prop: "created",
+      overHidden: true,
+      display: false,
+    },
+    {
+      label: "启用状态",
+      slot: true,
+      headerAlign: "center",
+      prop: "signatureState",
+      width: 100,
+      addDisplay: false,
+    },
+  ],
+});
+
+const deleteRecord = (row, index, done) => {
+  deleteRow(row, index, done);
+  dataEditList();
+};
+</script>
+<style lang="scss" scoped>
+#imgeview {
+  position: fixed;
+  top: -2px;
+  z-index: 10;
+}
+</style>