SanHQin 7 months ago
parent
commit
f0efda8b42

+ 557 - 0
src/components/pages/test/check/docxTemplateDialog.vue

@@ -0,0 +1,557 @@
+<template>
+  <el-dialog
+    :visible.sync="show"
+    :append-to-body="true"
+    title="word导出"
+    width="1000px"
+    top="10vh"
+    :before-close="handleClose"
+    class="dialog_diy"
+  >
+    <div class="box" v-loading="loading">
+      <div class="b_left"></div>
+      <div class="b_right">
+        <div class="d_box">
+          <div class="d_b_step">
+            <h2>第一步:下载模板文档</h2>
+            <p>点击下载模板文档来下载指定文档</p>
+            <el-button
+              class="d_b_s_button"
+              type="primary"
+              :disabled="!downFileData"
+              @click="downloadTemplateDocx()"
+              >下载模板文档</el-button
+            >
+          </div>
+
+          <div class="d_b_step">
+            <h2>第二步:填入模板变量</h2>
+            <p>按需制作排版Word模板文档,目前支持docx格式</p>
+            <p>
+              在文件中指定位置输入下方的"字段变量",用于显示真实数据的准确位置
+            </p>
+            <img
+              src="https://ccrb.s3.cn-northwest-1.amazonaws.com.cn/default%2F%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_202410090922471728436994846.png"
+            />
+            <div
+              v-for="(item, index) in fieldList"
+              :key="index"
+              class="d_b_s_fieldListItem"
+            >
+              <span
+                >{{ item.name }}:{{
+                  "{" + (item.type == "image" ? "%" : "") + item.field + "}"
+                }}</span
+              >
+              <span @click="copyContent(`{${item.type == 'image' ? '%' : ''}${item.field}}`)">
+                <svg
+                  width="14"
+                  height="14"
+                  viewBox="0 0 14 14"
+                  xmlns="http://www.w3.org/2000/svg"
+                >
+                  <path
+                    fill-rule="evenodd"
+                    clip-rule="evenodd"
+                    d="M1.85645 2.28599C1.85645 2.0493 2.04832 1.85742 2.28502 1.85742H8.71359C8.95028 1.85742 9.14216 2.0493 9.14216 2.28599C9.14216 2.52269 8.95028 2.71456 8.71359 2.71456H2.71359V8.71456C2.71359 8.95126 2.52171 9.14314 2.28502 9.14314C2.04832 9.14314 1.85645 8.95126 1.85645 8.71456V2.28599Z"
+                  />
+                  <path
+                    fill-rule="evenodd"
+                    clip-rule="evenodd"
+                    d="M4.42871 4.85631C4.42871 4.61961 4.62059 4.42773 4.85728 4.42773H11.7144C11.9511 4.42773 12.143 4.61961 12.143 4.85631V11.7134C12.143 11.9501 11.9511 12.142 11.7144 12.142H4.85728C4.62059 12.142 4.42871 11.9501 4.42871 11.7134V4.85631ZM5.28585 5.28488V11.2849H11.2859V5.28488H5.28585Z"
+                  />
+                </svg>
+              </span>
+            </div>
+          </div>
+
+          <div class="d_b_step">
+            <h2>第三步:上传填入后的模板文档</h2>
+            <div v-if="uploadTemplateDocxData" class="d_b_s_fileCard">
+              <svg
+                t="1728376433028"
+                class="icon"
+                viewBox="0 0 1024 1024"
+                version="1.1"
+                xmlns="http://www.w3.org/2000/svg"
+                p-id="5172"
+                width="200"
+                height="200"
+              >
+                <path
+                  d="M815.5 160v704c0 17.7-14.3 32-32 32h-543c-17.7 0-32-14.3-32-32V160c0-17.7 14.3-32 32-32h543c17.7 0 32 14.3 32 32z m0-96h-607c-35.3 0-64 28.7-64 64v768c0 35.3 28.7 64 64 64h607c35.3 0 64-28.7 64-64V128c0-35.3-28.7-64-64-64z"
+                  p-id="5173"
+                ></path>
+                <path
+                  d="M703.5 320h-384c-17.7 0-32-14.3-32-32s14.3-32 32-32h384c17.7 0 32 14.3 32 32s-14.3 32-32 32zM703.5 512h-384c-17.7 0-32-14.3-32-32s14.3-32 32-32h384c17.7 0 32 14.3 32 32s-14.3 32-32 32zM511.5 704h-192c-17.7 0-32-14.3-32-32s14.3-32 32-32h192c17.7 0 32 14.3 32 32s-14.3 32-32 32z"
+                  p-id="5174"
+                ></path>
+              </svg>
+
+              <span>{{ uploadTemplateDocxData.name }}</span>
+              <span class="d_b_s_f_c_del" @click="clearUploadTemplateDocxData()"
+                >删除</span
+              >
+            </div>
+            <el-button
+              class="d_b_s_button"
+              type="primary"
+              :disabled="uploadTemplateDocxData != null"
+              @click="uploadTemplateDocx()"
+              >上传填入后的模板文档</el-button
+            >
+          </div>
+
+          <div class="d_b_step">
+            <h2>第四步:点击导出</h2>
+            <p></p>
+            <el-button
+              class="d_b_s_button"
+              type="primary"
+              :disabled="!uploadTemplateDocxData"
+              @click="exportDocx()"
+              >导出Word文档</el-button
+            >
+          </div>
+        </div>
+      </div>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import PizZip from "pizzip";
+import Docxtemplater from "docxtemplater";
+import ImageModule from "docxtemplater-image-module-free";
+import { saveAs } from 'file-saver';
+
+const getFile = url => {
+  return new Promise((resolve, reject) => {
+    var credentials = {
+      accessKeyId: "AKIATLPEDU37QV5CHLMH",
+      secretAccessKey: "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"
+    }; //秘钥形式的登录上传
+    window.AWS.config.update(credentials);
+    window.AWS.config.region = "cn-northwest-1"; //设置区域
+    let url2 = url;
+    let _url2 = "";
+    if (
+      url2.indexOf("https://view.officeapps.live.com/op/view.aspx?src=") != -1
+    ) {
+      _url2 = url2.split(
+        "https://view.officeapps.live.com/op/view.aspx?src="
+      )[1];
+    } else {
+      _url2 = url2;
+    }
+    var s3 = new window.AWS.S3({ params: { Bucket: "ccrb" } });
+    let name = decodeURIComponent(
+      _url2.split("https://ccrb.s3.cn-northwest-1.amazonaws.com.cn/")[1]
+    );
+    var params = {
+      Bucket: "ccrb",
+      Key: name
+    };
+    s3.getObject(params, function(err, data) {
+      if (err) {
+        console.log(err, err.stack);
+        resolve({ data: 1 });
+      } else {
+        resolve({ data: data.Body });
+        console.log(data);
+      }
+    });
+  });
+};
+
+export default {
+  props: {},
+  data() {
+    return {
+      show: false,
+      loading: false,
+      fieldList: [
+        { name: "第一题", field: "ti_01", type: "text", value: "第一题ti_01" },
+        { name: "第二题", field: "ti_02", type: "text", value: "第二题ti_02" },
+        { name: "第三题", field: "ti_03", type: "text", value: "第三题ti_03" },
+        { name: "第四题", field: "ti_04", type: "text", value: "第四题ti_04" },
+        { name: "第五题", field: "ti_05", type: "text", value: "第五题ti_05" },
+        {
+          name: "图片1",
+          field: "image_01",
+          type: "image",
+          value:
+            "https://ccrb.s3.cn-northwest-1.amazonaws.com.cn/default%2F%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_202410090922471728436994846.png"
+        }
+      ],
+      uploadTemplateDocxData: null, //上传的模板文档
+      downFileData: {
+        fileName: "大学生重补免缓考申请表.docx",
+        url:
+          "https://ccrb.s3.cn-northwest-1.amazonaws.com.cn/default%2F%E5%A4%A7%E5%AD%A6%E7%94%9F%E9%87%8D%E8%A1%A5%E5%85%8D%E7%BC%93%E8%80%83%E7%94%B3%E8%AF%B7%E8%A1%A81728378389891.docx"
+      } // 下载模板文档的url
+    };
+  },
+  methods: {
+    handleClose(done) {
+      this.close();
+      done();
+    },
+    open() {
+      this.show = true;
+    },
+    close() {
+      this.show = false;
+    },
+    init() {
+      // 初始化
+    },
+    downloadTemplateDocx() {
+      getFile(this.downFileData.url).then(data => {
+        if (data.data != 1) {
+          // 下载文件, 并存成ArrayBuffer对象
+          const file_name = this.downFileData.fileName; // 获取文件名
+          const file_data = data.data; // 获取文件数据
+          let url = window.URL.createObjectURL(new Blob([file_data]));
+          let a = document.createElement("a");
+          a.name = file_name;
+          a.href = url;
+          a.download = file_name;
+          a.click();
+          console.log(data);
+          this.$message.success("下载成功");
+        } else {
+          this.$message.error("下载失败");
+        }
+      });
+    },
+    uploadTemplateDocx() {
+      let input = document.createElement("input");
+      input.type = "file";
+      // input.accept = ".wav";
+      // input.accept = "audio/*, .txt, .pdf, .xlsx";
+      input.accept = ".docx";
+      input.click();
+      input.onchange = () => {
+        this.loading = true;
+        let file = input.files[0];
+        if (!/\.(docx)$/i.test(file.name)) {
+          this.loading = false;
+          return this.$message.error("请上传.docx格式的文件");
+        }
+        console.log(file);
+        this.uploadTemplateDocxData = file;
+        this.loading = false;
+        // this.uploadWavFileAndGetText(file);
+      };
+    },
+    async exportDocx() {
+      if (!this.uploadTemplateDocxData)
+        return this.$message.error("请先上传模板文档");
+      let reader = new FileReader();
+      reader.readAsArrayBuffer(this.uploadTemplateDocxData);
+      reader.onload = async e => {
+        try {
+          this.loading = true;
+          const binary = new Uint8Array(reader.result);
+          //创建一个PizZip实例
+          const zip = new PizZip(binary);
+          // 将模板内容加载到 Docxtemplater 中
+          const doc = new Docxtemplater().loadZip(zip);
+
+          let _data = {};
+          let _image = {};
+          // 设置模板值
+          // this.fieldList.forEach(i => {
+          //   _data[i.field] = i.value;
+          // });
+          for (let i = 0; i < this.fieldList.length; i++) {
+						// console.log(this.fieldList[i])
+            if (this.fieldList[i].type == "text") {
+              //文本处理
+              _data[this.fieldList[i].field] = this.fieldList[i].value;
+            } else if (this.fieldList[i].type == "image") {
+              //图片处理
+              let _imageObj = await this.convertImageUrlToBase64(
+                this.fieldList[i].value
+              );
+              _data[this.fieldList[i].field] = _imageObj.url;
+              _image[this.fieldList[i].field] = {
+                width: _imageObj.width,
+                height: _imageObj.height
+              };
+            }
+          }
+					// return this.loading = false;
+
+          // 图片处理
+          const opts = {
+            centered: false,
+            fileType: "docx",
+            getImage: (value, value2, value3) => {
+              return this.base64DataURLToArrayBuffer(value);
+            },
+            getSize: (arrayValue, value, tagName) => {
+							// console.log(_image)
+							// console.log(tagName)
+              let newWidth = _image[tagName].width;
+              let newHeight = _image[tagName].height;
+
+							// let newWidth = 550;
+              // let newHeight = 100;
+              return [newWidth, newHeight];
+            }
+          };
+					doc.attachModule(new ImageModule(opts));
+          //渲染模板
+          doc.setData(_data);
+          doc.render();
+          //获取渲染后的文本
+          const output = doc.getZip().generate({
+            type: "blob",
+            mimeType:
+              "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+            compression: "DEFLATE"
+          });
+
+					saveAs(output,`${this.uploadTemplateDocxData.name}`)
+          // let link = document.createElement("a");
+          // link.download = this.uploadTemplateDocxData.name;
+          // link.style.display = "none";
+          // let blob = new Blob([output]);
+          // link.href = URL.createObjectURL(blob);
+          // document.body.appendChild(link);
+          // link.click();
+          // document.body.removeChild(link);
+          this.loading = false;
+          this.$message.success("导出成功");
+        } catch (error) {
+          console.log(error);
+          this.loading = false;
+          return this.$message.error("导出失败");
+        }
+      };
+    },
+    clearUploadTemplateDocxData() {
+      this.uploadTemplateDocxData = null;
+    },
+    copyContent(content) {
+      const input = document.createElement("input");
+      // 设置 display为none会导致无法复制
+      // input.style.display = "none";
+      // 所以只能用其他方法隐藏
+      input.style.opacity = 0;
+      // 为了不影响布局
+      input.style.position = "fixed";
+      input.style.left = "-100%";
+      input.style.top = "-100%";
+      input.value = content;
+      document.body.appendChild(input);
+      input.select();
+      const success = document.execCommand("copy");
+      document.body.removeChild(input);
+      if (!success) {
+        return this.$message.error("复制失败");
+      } else {
+        return this.$message.success("复制成功");
+      }
+    },
+    convertImageUrlToBase64(imageUrl) {
+      return new Promise((resolve, reject) => {
+        const img = new Image();
+        img.crossOrigin = "Anonymous"; // 允许跨域请求
+        img.src = imageUrl;
+
+        img.onload = () => {
+          const canvas = document.createElement("canvas");
+          canvas.width = img.width;
+          canvas.height = img.height;
+          const ctx = canvas.getContext("2d");
+          ctx.drawImage(img, 0, 0);
+          const base64 = canvas.toDataURL("image/png");
+          resolve({ url: base64, width: img.width, height: img.height });
+        };
+
+        img.onerror = error => {
+					console.log("图片转base64失败")
+					console.log(error)
+          resolve({url:"",width:0,height:0});
+        };
+      });
+    },
+    base64DataURLToArrayBuffer(dataURL) {
+      const base64Regex = /^data:image\/(png|jpg|svg|svg\+xml);base64,/;
+      if (!base64Regex.test(dataURL)) {
+        return false;
+      }
+      const stringBase64 = dataURL.replace(base64Regex, "");
+      let binaryString;
+      if (typeof window !== "undefined") {
+        binaryString = window.atob(stringBase64);
+      } else {
+        binaryString = new Buffer(stringBase64, "base64").toString("binary");
+      }
+      const len = binaryString.length;
+      const bytes = new Uint8Array(len);
+      for (let i = 0; i < len; i++) {
+        const ascii = binaryString.charCodeAt(i);
+        bytes[i] = ascii;
+      }
+      return bytes.buffer;
+    }
+  }
+};
+</script>
+
+<style scoped>
+.dialog_diy >>> .el-dialog {
+  /* height: 100%; */
+  /* margin: 0 auto !important; */
+}
+
+.dialog_diy >>> .el-dialog__header {
+  background: #fff !important;
+  padding: 15px 20px;
+}
+
+.dialog_diy >>> .el-dialog__body {
+  height: calc(100% - 124px);
+  box-sizing: border-box;
+  padding: 0px;
+}
+
+.dialog_diy >>> .el-dialog__title {
+  color: #000;
+}
+
+.dialog_diy >>> .el-dialog__headerbtn {
+  top: 19px;
+}
+
+.dialog_diy >>> .el-dialog__headerbtn .el-dialog__close {
+  color: #000;
+}
+
+.dialog_diy >>> .el-dialog__headerbtn .el-dialog__close:hover {
+  color: #000;
+}
+
+.dialog_diy >>> .el-dialog__body,
+.dialog_diy >>> .el-dialog__footer {
+  background: #fff;
+}
+
+.box {
+  width: 100%;
+  height: 70vh;
+  padding: 0 20px 15px;
+  display: flex;
+  box-sizing: border-box;
+}
+
+.b_left {
+  width: 400px;
+  height: 100%;
+}
+
+.b_right {
+  flex: 1;
+  height: 100%;
+}
+
+.d_box {
+  width: 100%;
+  height: 100%;
+  overflow: auto;
+}
+
+.d_b_step {
+  width: 100%;
+  max-width: 100%;
+  height: auto;
+  padding: 20px;
+  box-sizing: border-box;
+  /* background-color: red; */
+}
+
+.d_b_step > h2 {
+  margin-bottom: 10px;
+}
+
+.d_b_step > p {
+  font-size: 16px;
+}
+
+.d_b_step > img {
+  width: 100%;
+  margin-top: 10px;
+}
+
+.d_b_s_button {
+  margin-top: 10px;
+}
+
+.d_b_s_fieldListItem {
+  margin-top: 15px;
+  font-size: 16px;
+  display: flex;
+  align-items: center;
+}
+
+.d_b_s_fieldListItem > span {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.d_b_s_fieldListItem > span > svg {
+  width: 15px;
+  height: 15px;
+  cursor: pointer;
+  fill: #000;
+  margin-left: 10px;
+  transition: 0.3s;
+  fill-opacity: 0.6;
+}
+
+.d_b_s_fieldListItem > span > svg:hover {
+  fill: #409eff;
+  fill-opacity: 1;
+}
+
+.d_b_s_fileCard {
+  width: 100%;
+  height: 45px;
+  border-radius: 2px;
+  margin-bottom: 10px;
+  display: flex;
+  align-items: center;
+  box-sizing: border-box;
+  padding: 0 10px;
+  border: solid 1px #dfdfdf;
+  position: relative;
+  cursor: default;
+}
+
+.d_b_s_fileCard > svg {
+  width: 30px;
+  height: 30px;
+  fill: #a3a8ac;
+  margin-right: 10px;
+}
+
+.d_b_s_fileCard > span {
+  max-width: calc(100% - 100px);
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  font-size: 16px;
+}
+
+.d_b_s_f_c_del {
+  position: absolute;
+  right: 10px;
+  cursor: pointer;
+  color: #e60012;
+}
+</style>

+ 12 - 1
src/components/pages/test/check/index.vue

@@ -633,6 +633,7 @@
                 <template slot-scope="scope">
                   <el-button @click="getTest(scope.row)" type="primary" size="small">查看</el-button>
                   <el-button @click="setWordHtml(scope.row)" type="primary" size="small">导出答题信息</el-button>
+									<!-- <el-button @click="setWordTemplate(scope.row)" type="primary" size="small">word导出</el-button> -->
                   <el-button @click="deleteTest(scope.row.id)" type="primary" size="small">删除</el-button>
                 </template>
               </el-table-column>
@@ -893,6 +894,8 @@
     <wpdf :dialogVisiblePdf.sync="dialogVisiblePdf" :url="wurl"></wpdf>
     <wVideo :dialogVisibleVideo.sync="dialogVisibleVideo" :url="wurl"></wVideo>
     <wOffice :dialogVisibleOffice.sync="dialogVisibleOffice" :url="wurl"></wOffice>
+
+		<docxTemplateDialog ref="docxTemplateDialogRef"/>
   </div>
 </template>
 
@@ -916,6 +919,9 @@ import FileSaver from "file-saver";
 import XLSX from "xlsx-js-style";
 import aiBoxRight from './aiBoxRight.vue'
 
+// word模板
+import docxTemplateDialog from './docxTemplateDialog' 
+
 const getFile = (url) => {
     return new Promise((resolve, reject) => {
         var credentials = {
@@ -963,7 +969,8 @@ export default {
     wOffice,
     checkPie,
     wordcloud,
-    aiBoxRight
+    aiBoxRight,
+		docxTemplateDialog
   },
   data() {
     return {
@@ -2326,6 +2333,10 @@ export default {
           console.error(err);
         });
     },
+		setWordTemplate(item){
+			this.$refs.docxTemplateDialogRef.open()//这里可以传数据
+			// console.log(item)
+		}
   },
   beforeDestroy() {
     document.getElementsByTagName('html')[0].style.overflow = '';