view.vue 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768
  1. <template>
  2. <div class="com-viewer">
  3. <!-- getComponentType() -->
  4. <div class="content" ref="content">
  5. <template v-for="(item, index) in coms">
  6. <component
  7. :key="index"
  8. :is="item.type"
  9. :com="item"
  10. :coms="comList"
  11. :currentIndex="index"
  12. :isActive="false"
  13. @onUpdateData="onUpdateData"
  14. />
  15. </template>
  16. </div>
  17. <div class="export">
  18. <el-button
  19. @click="onExports"
  20. type="primary"
  21. :loading="exporting"
  22. :disabled="exporting"
  23. >导出</el-button
  24. >
  25. <el-progress v-if="exporting" :percentage="exportProgress" />
  26. </div>
  27. </div>
  28. </template>
  29. <script>
  30. import { exportDocument } from "@/api/document";
  31. import TextArea from "./components/TextView";
  32. import htmlDocx from "html-docx-js/dist/html-docx";
  33. import jsPDF from "jspdf";
  34. import html2canvas from "html2canvas";
  35. import { findData } from "@/api/sourceData";
  36. import HTMLtoDOCX from "html-to-docx";
  37. import { saveAs } from "file-saver";
  38. export default {
  39. name: "Viewer",
  40. components: {
  41. TextArea,
  42. },
  43. props: {
  44. id: {
  45. type: Number,
  46. default: 0,
  47. },
  48. coms: {
  49. type: Array,
  50. default: null,
  51. },
  52. docAttr: {
  53. type: Object,
  54. default: () => ({
  55. title: "",
  56. }),
  57. },
  58. },
  59. watch: {
  60. coms: {
  61. handler(val) {
  62. if (val == null) return;
  63. this.comList = JSON.parse(JSON.stringify(val));
  64. this.replaceData(val);
  65. },
  66. immediate: true,
  67. deep: true,
  68. },
  69. },
  70. data() {
  71. return {
  72. comList: [],
  73. content: "",
  74. exporting: false, // 新增:用于跟踪导出状态
  75. exportProgress: 0,
  76. };
  77. },
  78. methods: {
  79. async replaceData(data) {
  80. for (let item of this.comList) {
  81. for (let el of item.attrs) {
  82. let attrId = el.id;
  83. if (el.type == "formual") {
  84. try {
  85. let formual = await this.analysisFormual(el);
  86. formual = await this.getRemote(formual);
  87. el.content = eval(formual);
  88. } catch (error) {}
  89. } else if (el.type == "variableNull") {
  90. console.log(el);
  91. }
  92. }
  93. }
  94. //this.$emit('update:coms', [...this.comList])
  95. return data;
  96. },
  97. //分析公式
  98. async analysisFormual(attrs) {
  99. let _this = this;
  100. // console.log("开始分析公式:", attrs.formula);
  101. var pattern = /(\[.*?\]){3}/;
  102. var formual = attrs.formula;
  103. var reg = new RegExp(pattern);
  104. while (true) {
  105. var items = formual.match(reg);
  106. if (items == null) break;
  107. let itemName = items[0];
  108. // console.log("处理项:", itemName);
  109. try {
  110. let data = await _this.getFormualData(itemName);
  111. // console.log(`获取到的数据: ${itemName} = ${data}`);
  112. if (data === null || data === undefined || isNaN(data)) {
  113. console.warn(`获取到的数据无效: ${itemName}`);
  114. formual = formual.replace(itemName, "(0)");
  115. } else {
  116. formual = formual.replace(itemName, `(${parseFloat(data)})`);
  117. }
  118. } catch (error) {
  119. console.error(`处理 ${itemName} 时出错:`, error);
  120. formual = formual.replace(itemName, "(0)");
  121. }
  122. }
  123. return formual;
  124. },
  125. async getFormualData(formualItem) {
  126. let _this = this;
  127. // console.log("开始获取公式数据:", formualItem);
  128. var pattern = /\[(.*?)\]\[(.*?)\]\[(.*?)\]/;
  129. var reg = new RegExp(pattern);
  130. let items = formualItem.match(reg);
  131. // console.log("解析结果:", items);
  132. let calResult = 0;
  133. if (items && items[1] == "T") {
  134. calResult = await _this.getModuleData(items[2], items[3]);
  135. } else {
  136. console.warn("无法解析公式项:", formualItem);
  137. }
  138. // console.log("计算结果:", calResult);
  139. return calResult;
  140. },
  141. //获取本地文档模块数据
  142. async getModuleData(code, attrName) {
  143. let _this = this;
  144. // console.log(`尝试获取模块数据: code=${code}, attrName=${attrName}`);
  145. // console.log("当前 coms:", _this.coms);
  146. let item = _this.comList.filter((o) => o.name == code);
  147. // console.log(`找到的模块:`, item);
  148. if (item.length > 0) {
  149. let attr = item[0].attrs.filter((o) => o.name == attrName);
  150. // console.log(`找到的属性:`, attr);
  151. if (attr.length > 0) {
  152. // console.log(`返回的值:`, attr[0].content);
  153. return parseFloat(attr[0].content);
  154. }
  155. }
  156. // console.log("未找到匹配的模块或属性,返回 0");
  157. return 0;
  158. },
  159. async getRemote(formual) {
  160. let _this = this;
  161. var pattern = /\[(.*?)\]\[(.*?)\]\[(.*?)\]\[(.*?)\]/;
  162. var reg = new RegExp(pattern);
  163. while (true) {
  164. let items = formual.match(reg);
  165. if (items == null) break;
  166. if (items[1] == "R") {
  167. //表示获取远程数据
  168. var dataIndex = items[4];
  169. dataIndex = items[4].split(",");
  170. let calResult = await _this.getRemoteData(
  171. items[2],
  172. items[3],
  173. dataIndex[0],
  174. dataIndex[1]
  175. );
  176. let itemName = items[0];
  177. // 检查calResult是否为数字
  178. if (!isNaN(calResult)) {
  179. formual = formual.replace(
  180. itemName,
  181. "(" + parseFloat(calResult) + ")"
  182. );
  183. } else {
  184. formual = formual.replace(itemName, `"${calResult}"`);
  185. }
  186. formual = formual.replace(
  187. itemName,
  188. "(" + parseFloat(calResult) + ")"
  189. );
  190. }
  191. }
  192. return formual;
  193. },
  194. async getRemoteData(code, sheet, row, col) {
  195. let _this = this;
  196. let result = 0;
  197. let par = {
  198. code: code,
  199. sheetName: sheet,
  200. row: row,
  201. col: col,
  202. };
  203. /* if (!this.isEditing) {
  204. this.loading = true;
  205. this.progress = 0;
  206. try {
  207. const updateProgress = setInterval(() => {
  208. if (this.progress < 90) {
  209. this.progress += 10;
  210. }
  211. }, 100);
  212. let res = await findData(par);
  213. if (res.status == 200) {
  214. result = res.data.result;
  215. }
  216. this.progress = 100;
  217. clearInterval(updateProgress);
  218. } catch (error) {
  219. console.error("Error fetching remote data:", error);
  220. } finally {
  221. setTimeout(() => {
  222. this.loading = false;
  223. this.progress = 0;
  224. }, 1000);
  225. }
  226. } else { */
  227. // 如果正在编辑,直接获取数据而不显示进度条
  228. try {
  229. let res = await findData(par);
  230. if (res.status == 200) {
  231. result = res.data.result;
  232. // 添加数据类型检查和转换
  233. if (typeof result === "string" && !isNaN(result)) {
  234. result = parseFloat(result);
  235. }
  236. }
  237. } catch (error) {
  238. console.error("获取远程数据错误:", error);
  239. }
  240. /* } */
  241. return result;
  242. },
  243. getComponentType(type) {
  244. if (type === "textarea") {
  245. return TextArea;
  246. }
  247. // Add other component types if needed
  248. return type;
  249. },
  250. onUpdateData(index, data) {
  251. this.comList[index].content = data;
  252. this.rebuildContent();
  253. //this.$emit('update:coms', [...this.comList])
  254. },
  255. rebuildContent() {
  256. this.content = this.comList.map((item) => item.content).join("");
  257. },
  258. async onExport() {
  259. if (this.exporting) return; // 如果正在导出,直接返回
  260. this.exporting = true; // 设置导出状态为 true
  261. try {
  262. let contentClone = this.$refs.content.cloneNode(true);
  263. /* const templateTextareas =
  264. contentClone.querySelectorAll(".template-textarea");
  265. if (templateTextareas.length > 0) {
  266. // 创建目录页
  267. const tocPage = document.createElement("div");
  268. tocPage.className = "toc-page";
  269. tocPage.innerHTML = `
  270. <div style="page-break-before: always; page-break-after: always; height: 100vh; display: flex; flex-direction: column; justify-content: center;">
  271. <h1 style="text-align: center; margin-bottom: 40px;">目录</h1>
  272. <div id="toc" style="margin: 0 auto; min-width: 600px;"></div>
  273. </div>
  274. `;
  275. // 在第一个 template-textarea 之后插入目录页
  276. templateTextareas[0].parentNode.insertBefore(
  277. tocPage,
  278. templateTextareas[0].nextSibling
  279. );
  280. // 生成目录内容
  281. let tocHtml = '<div style="column-count: 1; column-gap: 40px;">';
  282. templateTextareas.forEach((textarea, index) => {
  283. const title = textarea.querySelector("h1, h2, h3, h4, h5, h6");
  284. if (title) {
  285. tocHtml += `
  286. <p style="margin: 0; padding: 5px 0;">
  287. <span style="float: left;">${title.textContent}</span>
  288. <span style="float: right;">${index + 1}</span>
  289. <span style="display: block; overflow: hidden; height: 1.2em; border-bottom: 1px dotted black;"></span>
  290. </p>
  291. `;
  292. title.id = `section-${index + 1}`;
  293. }
  294. });
  295. tocHtml += "</div>";
  296. // 将目录内容插入到目录页中
  297. contentClone.querySelector("#toc").innerHTML = tocHtml;
  298. } */
  299. const templateTextareas =
  300. contentClone.querySelectorAll(".template-textarea");
  301. if (templateTextareas.length > 0) {
  302. // 创建空白页和目录页
  303. const blankPage = document.createElement("div");
  304. blankPage.className = "blank-page";
  305. blankPage.innerHTML = '<div style="page-break-after: always;"></div>';
  306. const tocPage = document.createElement("div");
  307. tocPage.className = "toc-page";
  308. tocPage.innerHTML = `
  309. <div class="toc-content">
  310. <h1 style="text-align: center; margin-bottom: 40px;">目录</h1>
  311. <div id="toc"></div>
  312. </div>
  313. `;
  314. // 在第一个 template-textarea 之后插入空白页和目录页
  315. templateTextareas[0].parentNode.insertBefore(
  316. tocPage,
  317. templateTextareas[0].nextSibling
  318. );
  319. templateTextareas[0].parentNode.insertBefore(blankPage, tocPage);
  320. // 生成目录内容
  321. let tocHtml = "";
  322. templateTextareas.forEach((textarea, index) => {
  323. const title = textarea.querySelector("h1, h2, h3, h4, h5, h6");
  324. if (title) {
  325. tocHtml += `
  326. <div class="toc-item">
  327. <span class="toc-text">${title.textContent}</span>
  328. <span class="toc-page-num">${index + 2}</span>
  329. </div>
  330. `;
  331. title.id = `section-${index + 2}`;
  332. }
  333. });
  334. // 将目录内容插入到目录页中
  335. contentClone.querySelector("#toc").innerHTML = tocHtml;
  336. }
  337. contentClone.querySelectorAll('input[type="text"]').forEach((input) => {
  338. const span = document.createElement("span");
  339. span.textContent = input.value;
  340. input.parentNode.replaceChild(span, input);
  341. });
  342. let contentData = `<!DOCTYPE html><html><head><meta charset="UTF-8"><style>
  343. @page {
  344. size: A4;
  345. margin: 2cm;
  346. }
  347. body {
  348. font-family: Arial, sans-serif;
  349. font-size: 12pt;
  350. line-height: 1.5;
  351. }
  352. table {
  353. width: 100%;
  354. border-collapse: collapse;
  355. }
  356. td, th {
  357. border: 1px solid black;
  358. padding: 5px;
  359. }
  360. .template-textarea {
  361. page-break-after: always;
  362. }
  363. .toc-page {
  364. page-break-before: always;
  365. page-break-after: always;
  366. }
  367. .toc-content {
  368. height: 100vh;
  369. display: flex;
  370. flex-direction: column;
  371. justify-content: flex-start;
  372. padding-top: 100px;
  373. }
  374. .toc-item {
  375. display: flex;
  376. justify-content: space-between;
  377. margin-bottom: 10px;
  378. }
  379. .toc-text {
  380. margin-right: 10px;
  381. }
  382. .toc-page-num {
  383. flex-shrink: 0;
  384. }
  385. .toc-item::after {
  386. content: "";
  387. border-bottom: 1px dotted black;
  388. flex-grow: 1;
  389. order: 2;
  390. margin: 0 5px;
  391. position: relative;
  392. top: -5px;
  393. }
  394. .toc-text {
  395. order: 1;
  396. }
  397. .toc-page-num {
  398. order: 3;
  399. }
  400. .TOC {
  401. display: none !important;
  402. }
  403. </style></head><body>${contentClone.innerHTML}</body></html>`; /*`<!DOCTYPE html><html><head><meta charset="UTF-8"><style>
  404. @media print {
  405. body {
  406. width: 210mm;
  407. height: 297mm;
  408. margin: 0;
  409. padding: 0;
  410. }
  411. .template-textarea {
  412. page-break-after: always;
  413. }
  414. .toc-page {
  415. page-break-before: always;
  416. page-break-after: always;
  417. }
  418. .toc-content {
  419. height: 100vh;
  420. display: flex;
  421. flex-direction: column;
  422. justify-content: flex-start;
  423. padding-top: 100px;
  424. }
  425. #toc {
  426. margin: 0 auto;
  427. max-width: 600px;
  428. }
  429. .toc-item {
  430. display: flex;
  431. justify-content: space-between;
  432. margin-bottom: 10px;
  433. }
  434. .toc-text {
  435. margin-right: 10px;
  436. }
  437. .toc-page-num {
  438. flex-shrink: 0;
  439. }
  440. .toc-item::after {
  441. content: "";
  442. border-bottom: 1px dotted black;
  443. flex-grow: 1;
  444. order: 2;
  445. margin: 0 5px;
  446. position: relative;
  447. top: -5px;
  448. }
  449. .toc-text {
  450. order: 1;
  451. }
  452. .toc-page-num {
  453. order: 3;
  454. }
  455. .TOC {
  456. display: none !important;
  457. }
  458. }
  459. </style></head><body><p class="header1"></p>${contentClone.innerHTML}</body></html>` 移除 WPS 自动添加的目录引用框 */
  460. contentData = contentData
  461. .replaceAll("<table ", `<table style="border-collapse: collapse;" `)
  462. .replaceAll("<td>", `<td style="border: 1px solid black;">`);
  463. const res = await exportDocument({
  464. content: contentData,
  465. title: this.docAttr.title,
  466. });
  467. if (res.status != 200) {
  468. this.$alert(res.errMsg);
  469. return;
  470. }
  471. let a = document.createElement("a");
  472. a.href = res.data.file_path;
  473. a.download = res.data.file_name;
  474. document.body.appendChild(a);
  475. a.click();
  476. document.body.removeChild(a);
  477. } catch (error) {
  478. this.$alert("导出文档时发生错误,请稍后重试。");
  479. } finally {
  480. this.exporting = false; // 无论成功还是失败,都将导出状态设置回 false
  481. }
  482. },
  483. /* 前端导出word */
  484. async onExports() {
  485. if (this.exporting) return;
  486. this.exporting = true;
  487. try {
  488. let contentClone = this.$refs.content.cloneNode(true);
  489. console.log(contentClone);
  490. // 等待所有图片加载完成
  491. await this.waitForImages(contentClone);
  492. // 将图片转换为 Base64
  493. const imgPromises = Array.from(
  494. contentClone.querySelectorAll("img")
  495. ).map((img) => this.convertImageToBase64(img));
  496. await Promise.all(imgPromises);
  497. // 处理输入框
  498. contentClone.querySelectorAll('input[type="text"]').forEach((input) => {
  499. const span = document.createElement("span");
  500. span.textContent = input.value;
  501. input.parentNode.replaceChild(span, input);
  502. });
  503. // 创建HTML内容
  504. let contentData = `
  505. <!DOCTYPE html>
  506. <html>
  507. <head>
  508. <meta charset="UTF-8">
  509. <style>
  510. @page {
  511. size: A4;
  512. margin: 2cm;
  513. }
  514. body {
  515. font-family: Arial, sans-serif;
  516. font-size: 12pt;
  517. line-height: 1.5;
  518. }
  519. table {
  520. width: 100%;
  521. border-collapse: collapse;
  522. }
  523. td, th {
  524. border: 1px solid black;
  525. padding: 5px;
  526. }
  527. .template-textarea {
  528. page-break-after: always;
  529. }
  530. img {
  531. max-width: 100%;
  532. height: auto;
  533. }
  534. </style>
  535. </head>
  536. <body>${contentClone.innerHTML}</body>
  537. </html>
  538. `;
  539. // 创建文档选项
  540. const options = {
  541. margin: {
  542. top: "2cm",
  543. right: "2cm",
  544. bottom: "2cm",
  545. left: "2cm",
  546. },
  547. table: { row: { cantSplit: true } },
  548. footer: {
  549. default:
  550. '<p style="color: #444; font-size: 10pt">页码: {page} / {pages}</p>',
  551. },
  552. pageNumber: true,
  553. };
  554. // 创建文档
  555. const docxBlob = await HTMLtoDOCX(contentData, null, options);
  556. // 保存文件
  557. saveAs(docxBlob, `${this.docAttr.title}.docx`);
  558. this.$message.success("文档导出成功");
  559. } catch (error) {
  560. console.error("导出文档时发生错误:", error);
  561. this.$message.error("导出文档时发生错误,请稍后重试");
  562. } finally {
  563. this.exporting = false;
  564. }
  565. },
  566. // 新增方法:等待所有图片加载完成
  567. async waitForImages(element) {
  568. const images = element.getElementsByTagName("img");
  569. const imagePromises = Array.from(images).map((img) => {
  570. if (img.complete) {
  571. return Promise.resolve();
  572. } else {
  573. return new Promise((resolve) => {
  574. img.onload = img.onerror = resolve;
  575. });
  576. }
  577. });
  578. await Promise.all(imagePromises);
  579. },
  580. // 新增方法:将图片转换为Base64
  581. async convertImageToBase64(img) {
  582. if (img.src.startsWith("data:")) {
  583. return;
  584. }
  585. try {
  586. const response = await fetch(img.src);
  587. const blob = await response.blob();
  588. const base64 = await new Promise((resolve) => {
  589. const reader = new FileReader();
  590. reader.onloadend = () => resolve(reader.result);
  591. reader.readAsDataURL(blob);
  592. });
  593. img.src = base64;
  594. } catch (error) {
  595. console.error("Error converting image to Base64:", error);
  596. }
  597. },
  598. /* 终 */
  599. /* pdf导出 */
  600. async onExportPDF() {
  601. if (this.exporting) return;
  602. this.exporting = true;
  603. this.exportProgress = 0;
  604. try {
  605. const content = this.$refs.content;
  606. // 保存原始样式
  607. const originalStyle = {
  608. height: content.style.height,
  609. overflow: content.style.overflow,
  610. position: content.style.position,
  611. };
  612. // 临时修改样式以确保所有内容可见
  613. content.style.height = "auto";
  614. content.style.overflow = "visible";
  615. content.style.position = "absolute";
  616. // 等待图片加载
  617. await this.waitForImages(content);
  618. const pdf = new jsPDF("p", "pt", "a4");
  619. const pageHeight = pdf.internal.pageSize.getHeight();
  620. const pageWidth = pdf.internal.pageSize.getWidth();
  621. // 设置超时
  622. const timeout = setTimeout(() => {
  623. throw new Error("PDF export timed out");
  624. }, 60000); // 60秒超时
  625. await this.addContentToPDF(content, pdf, pageWidth, pageHeight);
  626. clearTimeout(timeout);
  627. pdf.save(`${this.docAttr.title || "document"}.pdf`);
  628. // 恢复原始样式
  629. Object.assign(content.style, originalStyle);
  630. this.$message.success("PDF导出成功");
  631. } catch (error) {
  632. console.error("Export to PDF failed:", error);
  633. this.$message.error(`PDF导出失败:${error.message}`);
  634. } finally {
  635. this.exporting = false;
  636. this.exportProgress = 0;
  637. }
  638. },
  639. async waitForImages(element) {
  640. const images = element.getElementsByTagName("img");
  641. const imagePromises = Array.from(images).map((img) => {
  642. if (img.complete) return Promise.resolve();
  643. return new Promise((resolve) => {
  644. img.onload = img.onerror = resolve;
  645. });
  646. });
  647. await Promise.all(imagePromises);
  648. },
  649. async addContentToPDF(element, pdf, pageWidth, pageHeight, topOffset = 0) {
  650. const totalHeight = element.scrollHeight;
  651. const canvas = await html2canvas(element, {
  652. scale: 2,
  653. useCORS: true,
  654. logging: false,
  655. windowWidth: pageWidth,
  656. windowHeight: pageHeight,
  657. y: topOffset,
  658. ignoreElements: (element) => {
  659. // 忽略使用已弃用的 -ms-high-contrast 的元素
  660. const style = window.getComputedStyle(element);
  661. return style.getPropertyValue("-ms-high-contrast") !== "none";
  662. },
  663. });
  664. const imgData = canvas.toDataURL("image/jpeg", 1.0);
  665. const imgWidth = pageWidth;
  666. const imgHeight = (canvas.height * imgWidth) / canvas.width;
  667. pdf.addImage(imgData, "JPEG", 0, 0, imgWidth, imgHeight);
  668. this.exportProgress = Math.min(
  669. 100,
  670. Math.round(((topOffset + pageHeight) / totalHeight) * 100)
  671. );
  672. if (canvas.height > pageHeight) {
  673. pdf.addPage();
  674. await this.addContentToPDF(
  675. element,
  676. pdf,
  677. pageWidth,
  678. pageHeight,
  679. topOffset + pageHeight
  680. );
  681. }
  682. },
  683. /* onExport() {
  684. let contentData = `<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body><p class="header1"></p> ${this.$refs.content.innerHTML} </body></html>`;
  685. contentData = contentData
  686. .replaceAll("<table ", `<table style="border-collapse: collapse;" `)
  687. .replaceAll("<td>", `<td style="border: 1px solid black;">`);
  688. exportDocument({ content: contentData, title: this.docAttr.title }).then(
  689. (res) => {
  690. if (res.status != 200) {
  691. this.$alert(res.errMsg);
  692. return;
  693. }
  694. let a = document.createElement("a");
  695. a.href = res.data.file_path; // Use the file_path from the response
  696. a.download = res.data.file_name; // Set the download attribute to the file_name
  697. document.body.appendChild(a);
  698. a.click();
  699. document.body.removeChild(a);
  700. }
  701. );
  702. }, */
  703. },
  704. };
  705. </script>
  706. <style lang="scss">
  707. @import "./view.scss";
  708. </style>