47 3 weeks ago
parent
commit
039d891bc3

+ 8 - 0
package.json

@@ -44,13 +44,19 @@
     "crypto-js": "^4.2.0",
     "dayjs": "^1.11.10",
     "diagram-js": "^12.8.0",
+    "docxtemplater": "^3.55.8",
     "driver.js": "^1.3.1",
     "echarts": "^5.5.0",
     "echarts-wordcloud": "^2.1.0",
     "element-plus": "2.8.0",
     "fast-xml-parser": "^4.3.2",
+    "file-saver": "^2.0.5",
     "highlight.js": "^11.9.0",
+    "html2canvas": "^1.4.1",
     "jsencrypt": "^3.3.2",
+    "jspdf": "^2.5.2",
+    "jszip": "^3.10.1",
+    "jszip-utils": "^0.1.0",
     "lodash-es": "^4.17.21",
     "markdown-it": "^14.1.0",
     "markmap-common": "^0.16.0",
@@ -62,6 +68,8 @@
     "nprogress": "^0.2.0",
     "pinia": "^2.1.7",
     "pinia-plugin-persistedstate": "^3.2.1",
+    "pizzip": "^3.1.7",
+    "print-js": "^1.6.0",
     "qrcode": "^1.5.3",
     "qs": "^6.12.0",
     "steady-xml": "^0.1.0",

+ 264 - 19
pnpm-lock.yaml

@@ -65,6 +65,9 @@ importers:
       diagram-js:
         specifier: ^12.8.0
         version: 12.8.1
+      docxtemplater:
+        specifier: ^3.55.8
+        version: 3.55.8
       driver.js:
         specifier: ^1.3.1
         version: 1.3.1
@@ -80,12 +83,27 @@ importers:
       fast-xml-parser:
         specifier: ^4.3.2
         version: 4.5.0
+      file-saver:
+        specifier: ^2.0.5
+        version: 2.0.5
       highlight.js:
         specifier: ^11.9.0
         version: 11.10.0
+      html2canvas:
+        specifier: ^1.4.1
+        version: 1.4.1
       jsencrypt:
         specifier: ^3.3.2
         version: 3.3.2
+      jspdf:
+        specifier: ^2.5.2
+        version: 2.5.2
+      jszip:
+        specifier: ^3.10.1
+        version: 3.10.1
+      jszip-utils:
+        specifier: ^0.1.0
+        version: 0.1.0
       lodash-es:
         specifier: ^4.17.21
         version: 4.17.21
@@ -119,6 +137,12 @@ importers:
       pinia-plugin-persistedstate:
         specifier: ^3.2.1
         version: 3.2.3(pinia@2.2.6(typescript@5.3.3)(vue@3.4.21(typescript@5.3.3)))
+      pizzip:
+        specifier: ^3.1.7
+        version: 3.1.7
+      print-js:
+        specifier: ^1.6.0
+        version: 1.6.0
       qrcode:
         specifier: ^1.5.3
         version: 1.5.4
@@ -1343,42 +1367,36 @@ packages:
     engines: {node: '>= 10.0.0'}
     cpu: [arm]
     os: [linux]
-    libc: [glibc]
 
   '@parcel/watcher-linux-arm-musl@2.5.0':
     resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==}
     engines: {node: '>= 10.0.0'}
     cpu: [arm]
     os: [linux]
-    libc: [musl]
 
   '@parcel/watcher-linux-arm64-glibc@2.5.0':
     resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==}
     engines: {node: '>= 10.0.0'}
     cpu: [arm64]
     os: [linux]
-    libc: [glibc]
 
   '@parcel/watcher-linux-arm64-musl@2.5.0':
     resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==}
     engines: {node: '>= 10.0.0'}
     cpu: [arm64]
     os: [linux]
-    libc: [musl]
 
   '@parcel/watcher-linux-x64-glibc@2.5.0':
     resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==}
     engines: {node: '>= 10.0.0'}
     cpu: [x64]
     os: [linux]
-    libc: [glibc]
 
   '@parcel/watcher-linux-x64-musl@2.5.0':
     resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==}
     engines: {node: '>= 10.0.0'}
     cpu: [x64]
     os: [linux]
-    libc: [musl]
 
   '@parcel/watcher-win32-arm64@2.5.0':
     resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==}
@@ -1478,55 +1496,46 @@ packages:
     resolution: {integrity: sha512-6npqOKEPRZkLrMcvyC/32OzJ2srdPzCylJjiTJT2c0bwwSGm7nz2F9mNQ1WrAqCBZROcQn91Fno+khFhVijmFA==}
     cpu: [arm]
     os: [linux]
-    libc: [glibc]
 
   '@rollup/rollup-linux-arm-musleabihf@4.27.2':
     resolution: {integrity: sha512-V9Xg6eXtgBtHq2jnuQwM/jr2mwe2EycnopO8cbOvpzFuySCGtKlPCI3Hj9xup/pJK5Q0388qfZZy2DqV2J8ftw==}
     cpu: [arm]
     os: [linux]
-    libc: [musl]
 
   '@rollup/rollup-linux-arm64-gnu@4.27.2':
     resolution: {integrity: sha512-uCFX9gtZJoQl2xDTpRdseYuNqyKkuMDtH6zSrBTA28yTfKyjN9hQ2B04N5ynR8ILCoSDOrG/Eg+J2TtJ1e/CSA==}
     cpu: [arm64]
     os: [linux]
-    libc: [glibc]
 
   '@rollup/rollup-linux-arm64-musl@4.27.2':
     resolution: {integrity: sha512-/PU9P+7Rkz8JFYDHIi+xzHabOu9qEWR07L5nWLIUsvserrxegZExKCi2jhMZRd0ATdboKylu/K5yAXbp7fYFvA==}
     cpu: [arm64]
     os: [linux]
-    libc: [musl]
 
   '@rollup/rollup-linux-powerpc64le-gnu@4.27.2':
     resolution: {integrity: sha512-eCHmol/dT5odMYi/N0R0HC8V8QE40rEpkyje/ZAXJYNNoSfrObOvG/Mn+s1F/FJyB7co7UQZZf6FuWnN6a7f4g==}
     cpu: [ppc64]
     os: [linux]
-    libc: [glibc]
 
   '@rollup/rollup-linux-riscv64-gnu@4.27.2':
     resolution: {integrity: sha512-DEP3Njr9/ADDln3kNi76PXonLMSSMiCir0VHXxmGSHxCxDfQ70oWjHcJGfiBugzaqmYdTC7Y+8Int6qbnxPBIQ==}
     cpu: [riscv64]
     os: [linux]
-    libc: [glibc]
 
   '@rollup/rollup-linux-s390x-gnu@4.27.2':
     resolution: {integrity: sha512-NHGo5i6IE/PtEPh5m0yw5OmPMpesFnzMIS/lzvN5vknnC1sXM5Z/id5VgcNPgpD+wHmIcuYYgW+Q53v+9s96lQ==}
     cpu: [s390x]
     os: [linux]
-    libc: [glibc]
 
   '@rollup/rollup-linux-x64-gnu@4.27.2':
     resolution: {integrity: sha512-PaW2DY5Tan+IFvNJGHDmUrORadbe/Ceh8tQxi8cmdQVCCYsLoQo2cuaSj+AU+YRX8M4ivS2vJ9UGaxfuNN7gmg==}
     cpu: [x64]
     os: [linux]
-    libc: [glibc]
 
   '@rollup/rollup-linux-x64-musl@4.27.2':
     resolution: {integrity: sha512-dOlWEMg2gI91Qx5I/HYqOD6iqlJspxLcS4Zlg3vjk1srE67z5T2Uz91yg/qA8sY0XcwQrFzWWiZhMNERylLrpQ==}
     cpu: [x64]
     os: [linux]
-    libc: [musl]
 
   '@rollup/rollup-win32-arm64-msvc@4.27.2':
     resolution: {integrity: sha512-euMIv/4x5Y2/ImlbGl88mwKNXDsvzbWUlT7DFky76z2keajCtcbAsN9LUdmk31hAoVmJJYSThgdA0EsPeTr1+w==}
@@ -1569,28 +1578,24 @@ packages:
     engines: {node: '>=10'}
     cpu: [arm64]
     os: [linux]
-    libc: [glibc]
 
   '@swc/core-linux-arm64-musl@1.9.2':
     resolution: {integrity: sha512-8xzrOmsyCC1zrx2Wzx/h8dVsdewO1oMCwBTLc1gSJ/YllZYTb04pNm6NsVbzUX2tKddJVRgSJXV10j/NECLwpA==}
     engines: {node: '>=10'}
     cpu: [arm64]
     os: [linux]
-    libc: [musl]
 
   '@swc/core-linux-x64-gnu@1.9.2':
     resolution: {integrity: sha512-kZrNz/PjRQKcchWF6W292jk3K44EoVu1ad5w+zbS4jekIAxsM8WwQ1kd+yjUlN9jFcF8XBat5NKIs9WphJCVXg==}
     engines: {node: '>=10'}
     cpu: [x64]
     os: [linux]
-    libc: [glibc]
 
   '@swc/core-linux-x64-musl@1.9.2':
     resolution: {integrity: sha512-TTIpR4rjMkhX1lnFR+PSXpaL83TrQzp9znRdp2TzYrODlUd/R20zOwSo9vFLCyH6ZoD47bccY7QeGZDYT3nlRg==}
     engines: {node: '>=10'}
     cpu: [x64]
     os: [linux]
-    libc: [musl]
 
   '@swc/core-win32-arm64-msvc@1.9.2':
     resolution: {integrity: sha512-+Eg2d4icItKC0PMjZxH7cSYFLWk0aIp94LNmOw6tPq0e69ax6oh10upeq0D1fjWsKLmOJAWEvnXlayZcijEXDw==}
@@ -1767,6 +1772,9 @@ packages:
   '@types/qs@6.9.17':
     resolution: {integrity: sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==}
 
+  '@types/raf@3.4.3':
+    resolution: {integrity: sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==}
+
   '@types/semver@7.5.8':
     resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==}
 
@@ -2228,6 +2236,10 @@ packages:
     resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==}
     engines: {node: '>=10.0.0'}
 
+  '@xmldom/xmldom@0.9.6':
+    resolution: {integrity: sha512-Su4xcxR0CPGwlDHNmVP09fqET9YxbyDXHaSob6JlBH7L6reTYaeim6zbk9o08UarO0L5GTRo3uzl0D+9lSxmvw==}
+    engines: {node: '>=14.6'}
+
   '@zxcvbn-ts/core@3.0.4':
     resolution: {integrity: sha512-aQeiT0F09FuJaAqNrxynlAwZ2mW/1MdXakKWNmGM1Qp/VaY6CnB/GfnMS2T8gB2231Esp1/maCWd8vTG4OuShw==}
 
@@ -2393,6 +2405,10 @@ packages:
   balanced-match@2.0.0:
     resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==}
 
+  base64-arraybuffer@1.0.2:
+    resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==}
+    engines: {node: '>= 0.6.0'}
+
   base@0.11.2:
     resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==}
     engines: {node: '>=0.10.0'}
@@ -2456,6 +2472,11 @@ packages:
     engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
     hasBin: true
 
+  btoa@1.2.1:
+    resolution: {integrity: sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==}
+    engines: {node: '>= 0.4.0'}
+    hasBin: true
+
   buffer-from@1.1.2:
     resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
 
@@ -2485,6 +2506,10 @@ packages:
   caniuse-lite@1.0.30001680:
     resolution: {integrity: sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==}
 
+  canvg@3.0.10:
+    resolution: {integrity: sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==}
+    engines: {node: '>=10.0.0'}
+
   chalk@1.1.3:
     resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==}
     engines: {node: '>=0.10.0'}
@@ -2658,6 +2683,9 @@ packages:
   core-js@3.39.0:
     resolution: {integrity: sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==}
 
+  core-util-is@1.0.3:
+    resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
+
   cors@2.8.5:
     resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
     engines: {node: '>= 0.10'}
@@ -2696,6 +2724,9 @@ packages:
     resolution: {integrity: sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==}
     engines: {node: '>=12 || >=16'}
 
+  css-line-break@2.1.0:
+    resolution: {integrity: sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==}
+
   css-select@4.3.0:
     resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==}
 
@@ -2989,6 +3020,10 @@ packages:
     resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
     engines: {node: '>=6.0.0'}
 
+  docxtemplater@3.55.8:
+    resolution: {integrity: sha512-sDfZA//rrycVK2nSL763ek5+q9O3yzBhJTB+GWG+NBERL/PbYZGVeFl1+nj+fkFblMWo07NCVqM+HVyuHYbQ9Q==}
+    engines: {node: '>=0.10'}
+
   dom-serializer@0.2.2:
     resolution: {integrity: sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==}
 
@@ -3024,6 +3059,9 @@ packages:
   domify@1.4.2:
     resolution: {integrity: sha512-m4yreHcUWHBncGVV7U+yQzc12vIlq0jMrtHZ5mW6dQMiL/7skSYNVX9wqKwOtyO9SGCgevrAFEgOCAHmamHTUA==}
 
+  dompurify@2.5.8:
+    resolution: {integrity: sha512-o1vSNgrmYMQObbSSvF/1brBYEQPHhV1+gsmrusO7/GXtp1T9rCS8cXFqVxK/9crT1jA6Ccv+5MTSjBNqr7Sovw==}
+
   dompurify@3.2.0:
     resolution: {integrity: sha512-AMdOzK44oFWqHEi0wpOqix/fUNY707OmoeFDnbi3Q5I8uOpy21ufUA5cDJPr0bosxrflOVD/H2DMSvuGKJGfmQ==}
 
@@ -3318,6 +3356,9 @@ packages:
   fastq@1.17.1:
     resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
 
+  fflate@0.8.2:
+    resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
+
   file-entry-cache@6.0.1:
     resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
     engines: {node: ^10.12.0 || >=12.0.0}
@@ -3326,6 +3367,9 @@ packages:
     resolution: {integrity: sha512-/pqPFG+FdxWQj+/WSuzXSDaNzxgTLr/OrR1QuqfEZzDakpdYE70PwUxL7BPUa8hpjbvY1+qvCl8k+8Tq34xJgg==}
     engines: {node: '>=18'}
 
+  file-saver@2.0.5:
+    resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==}
+
   filelist@1.0.4:
     resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==}
 
@@ -3584,6 +3628,10 @@ packages:
   html-void-elements@2.0.1:
     resolution: {integrity: sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==}
 
+  html2canvas@1.4.1:
+    resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==}
+    engines: {node: '>=8.0.0'}
+
   htmlparser2@3.10.1:
     resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==}
 
@@ -3617,6 +3665,9 @@ packages:
     engines: {node: '>=0.10.0'}
     hasBin: true
 
+  immediate@3.0.6:
+    resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
+
   immer@9.0.21:
     resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==}
 
@@ -3918,6 +3969,15 @@ packages:
     resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==}
     engines: {'0': node >= 0.2.0}
 
+  jspdf@2.5.2:
+    resolution: {integrity: sha512-myeX9c+p7znDWPk0eTrujCzNjT+CXdXyk7YmJq5nD5V7uLLKmSXnlQ/Jn/kuo3X09Op70Apm0rQSnFWyGK8uEQ==}
+
+  jszip-utils@0.1.0:
+    resolution: {integrity: sha512-tBNe0o3HAf8vo0BrOYnLPnXNo5A3KsRMnkBFYjh20Y3GPYGfgyoclEMgvVchx0nnL+mherPi74yLPIusHUQpZg==}
+
+  jszip@3.10.1:
+    resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==}
+
   katex@0.16.11:
     resolution: {integrity: sha512-RQrI8rlHY92OLf3rho/Ts8i/XvjgguEjOkO1BEXcU3N8BqPpSzBNwV/G0Ukr+P/l3ivvJUE/Fa/CwbS6HesGNQ==}
     hasBin: true
@@ -3954,6 +4014,9 @@ packages:
     resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
     engines: {node: '>= 0.8.0'}
 
+  lie@3.3.0:
+    resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==}
+
   lilconfig@3.1.2:
     resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==}
     engines: {node: '>=14'}
@@ -4407,6 +4470,12 @@ packages:
   package-manager-detector@0.2.2:
     resolution: {integrity: sha512-VgXbyrSNsml4eHWIvxxG/nTL4wgybMTXCV2Un/+yEc3aDKKU6nQBZjbeP3Pl3qm9Qg92X/1ng4ffvCeD/zwHgg==}
 
+  pako@1.0.11:
+    resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
+
+  pako@2.1.0:
+    resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==}
+
   parent-module@1.0.1:
     resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
     engines: {node: '>=6'}
@@ -4471,6 +4540,9 @@ packages:
   perfect-debounce@1.0.0:
     resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
 
+  performance-now@2.1.0:
+    resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
+
   picocolors@1.1.1:
     resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
 
@@ -4504,6 +4576,9 @@ packages:
       typescript:
         optional: true
 
+  pizzip@3.1.7:
+    resolution: {integrity: sha512-VemVeAQtdIA74AN1Fsd5OmbMbEeS4YOwwlcudgzvmUrOIOPrk1idYC5Tw5FUFq/I0c26ziNOw9z//iPmGfp1jA==}
+
   pkcs7@1.0.4:
     resolution: {integrity: sha512-afRERtHn54AlwaF2/+LFszyAANTCggGilmcmILUzEjvs3XgFZT+xE6+QWQcAGmu4xajy+Xtj7acLOPdx5/eXWQ==}
     hasBin: true
@@ -4622,10 +4697,16 @@ packages:
     resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
 
+  print-js@1.6.0:
+    resolution: {integrity: sha512-BfnOIzSKbqGRtO4o0rnj/K3681BSd2QUrsIZy/+WdCIugjIswjmx3lDEZpXB2ruGf9d4b3YNINri81+J0FsBWg==}
+
   prismjs@1.29.0:
     resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==}
     engines: {node: '>=6'}
 
+  process-nextick-args@2.0.1:
+    resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
+
   process@0.11.10:
     resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
     engines: {node: '>= 0.6.0'}
@@ -4664,12 +4745,18 @@ packages:
   queue-microtask@1.2.3:
     resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
 
+  raf@3.4.1:
+    resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==}
+
   rd@2.0.1:
     resolution: {integrity: sha512-/XdKU4UazUZTXFmI0dpABt8jSXPWcEyaGdk340KdHnsEOdkTctlX23aAK7ChQDn39YGNlAJr1M5uvaKt4QnpNw==}
 
   react-is@18.3.1:
     resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
 
+  readable-stream@2.3.8:
+    resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
+
   readable-stream@3.6.2:
     resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
     engines: {node: '>= 6'}
@@ -4689,6 +4776,9 @@ packages:
   regenerate@1.4.2:
     resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==}
 
+  regenerator-runtime@0.13.11:
+    resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
+
   regenerator-runtime@0.14.1:
     resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
 
@@ -4775,6 +4865,10 @@ packages:
   rfdc@1.4.1:
     resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
 
+  rgbcolor@1.0.1:
+    resolution: {integrity: sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==}
+    engines: {node: '>= 0.8.15'}
+
   rimraf@3.0.2:
     resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
     deprecated: Rimraf versions prior to v4 are no longer supported
@@ -4814,6 +4908,9 @@ packages:
     resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==}
     engines: {node: '>=0.4'}
 
+  safe-buffer@5.1.2:
+    resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
+
   safe-buffer@5.2.1:
     resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
 
@@ -4877,6 +4974,9 @@ packages:
     resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==}
     engines: {node: '>=0.10.0'}
 
+  setimmediate@1.0.5:
+    resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==}
+
   shebang-command@2.0.0:
     resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
     engines: {node: '>=8'}
@@ -4981,6 +5081,10 @@ packages:
     resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==}
     deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility'
 
+  stackblur-canvas@2.7.0:
+    resolution: {integrity: sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==}
+    engines: {node: '>=0.1.14'}
+
   static-extend@0.1.2:
     resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==}
     engines: {node: '>=0.10.0'}
@@ -5020,6 +5124,9 @@ packages:
     resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
     engines: {node: '>= 0.4'}
 
+  string_decoder@1.1.1:
+    resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
+
   string_decoder@1.3.0:
     resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
 
@@ -5101,6 +5208,10 @@ packages:
   svg-baker@1.7.0:
     resolution: {integrity: sha512-nibslMbkXOIkqKVrfcncwha45f97fGuAOn1G99YwnwTj8kF9YiM6XexPcUso97NxOm6GsP0SIvYVIosBis1xLg==}
 
+  svg-pathdata@6.0.3:
+    resolution: {integrity: sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==}
+    engines: {node: '>=12.0.0'}
+
   svg-tags@1.0.0:
     resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==}
 
@@ -5136,6 +5247,9 @@ packages:
     resolution: {integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==}
     engines: {node: '>=8'}
 
+  text-segmentation@1.0.3:
+    resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==}
+
   text-table@0.2.0:
     resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
 
@@ -5356,6 +5470,9 @@ packages:
   util-deprecate@1.0.2:
     resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
 
+  utrie@1.0.2:
+    resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==}
+
   uuid@10.0.0:
     resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
     hasBin: true
@@ -7177,6 +7294,9 @@ snapshots:
 
   '@types/qs@6.9.17': {}
 
+  '@types/raf@3.4.3':
+    optional: true
+
   '@types/semver@7.5.8': {}
 
   '@types/svgo@2.6.4':
@@ -7886,6 +8006,8 @@ snapshots:
 
   '@xmldom/xmldom@0.8.10': {}
 
+  '@xmldom/xmldom@0.9.6': {}
+
   '@zxcvbn-ts/core@3.0.4':
     dependencies:
       fastest-levenshtein: 1.0.16
@@ -8055,6 +8177,8 @@ snapshots:
 
   balanced-match@2.0.0: {}
 
+  base64-arraybuffer@1.0.2: {}
+
   base@0.11.2:
     dependencies:
       cache-base: 1.0.1
@@ -8157,6 +8281,8 @@ snapshots:
       node-releases: 2.0.18
       update-browserslist-db: 1.1.1(browserslist@4.24.2)
 
+  btoa@1.2.1: {}
+
   buffer-from@1.1.2: {}
 
   cac@6.7.14: {}
@@ -8189,6 +8315,18 @@ snapshots:
 
   caniuse-lite@1.0.30001680: {}
 
+  canvg@3.0.10:
+    dependencies:
+      '@babel/runtime': 7.26.0
+      '@types/raf': 3.4.3
+      core-js: 3.39.0
+      raf: 3.4.1
+      regenerator-runtime: 0.13.11
+      rgbcolor: 1.0.1
+      stackblur-canvas: 2.7.0
+      svg-pathdata: 6.0.3
+    optional: true
+
   chalk@1.1.3:
     dependencies:
       ansi-styles: 2.2.1
@@ -8372,6 +8510,8 @@ snapshots:
 
   core-js@3.39.0: {}
 
+  core-util-is@1.0.3: {}
+
   cors@2.8.5:
     dependencies:
       object-assign: 4.1.1
@@ -8411,6 +8551,10 @@ snapshots:
 
   css-functions-list@3.2.3: {}
 
+  css-line-break@2.1.0:
+    dependencies:
+      utrie: 1.0.2
+
   css-select@4.3.0:
     dependencies:
       boolbase: 1.0.0
@@ -8740,6 +8884,10 @@ snapshots:
     dependencies:
       esutils: 2.0.3
 
+  docxtemplater@3.55.8:
+    dependencies:
+      '@xmldom/xmldom': 0.9.6
+
   dom-serializer@0.2.2:
     dependencies:
       domelementtype: 2.3.0
@@ -8781,6 +8929,9 @@ snapshots:
 
   domify@1.4.2: {}
 
+  dompurify@2.5.8:
+    optional: true
+
   dompurify@3.2.0: {}
 
   domutils@1.7.0:
@@ -9224,6 +9375,8 @@ snapshots:
     dependencies:
       reusify: 1.0.4
 
+  fflate@0.8.2: {}
+
   file-entry-cache@6.0.1:
     dependencies:
       flat-cache: 3.2.0
@@ -9232,6 +9385,8 @@ snapshots:
     dependencies:
       flat-cache: 5.0.0
 
+  file-saver@2.0.5: {}
+
   filelist@1.0.4:
     dependencies:
       minimatch: 5.1.6
@@ -9493,6 +9648,11 @@ snapshots:
 
   html-void-elements@2.0.1: {}
 
+  html2canvas@1.4.1:
+    dependencies:
+      css-line-break: 2.1.0
+      text-segmentation: 1.0.3
+
   htmlparser2@3.10.1:
     dependencies:
       domelementtype: 1.3.1
@@ -9527,6 +9687,8 @@ snapshots:
 
   image-size@0.5.5: {}
 
+  immediate@3.0.6: {}
+
   immer@9.0.21: {}
 
   immutable@5.0.2: {}
@@ -9778,6 +9940,27 @@ snapshots:
 
   jsonparse@1.3.1: {}
 
+  jspdf@2.5.2:
+    dependencies:
+      '@babel/runtime': 7.26.0
+      atob: 2.1.2
+      btoa: 1.2.1
+      fflate: 0.8.2
+    optionalDependencies:
+      canvg: 3.0.10
+      core-js: 3.39.0
+      dompurify: 2.5.8
+      html2canvas: 1.4.1
+
+  jszip-utils@0.1.0: {}
+
+  jszip@3.10.1:
+    dependencies:
+      lie: 3.3.0
+      pako: 1.0.11
+      readable-stream: 2.3.8
+      setimmediate: 1.0.5
+
   katex@0.16.11:
     dependencies:
       commander: 8.3.0
@@ -9809,6 +9992,10 @@ snapshots:
       prelude-ls: 1.2.1
       type-check: 0.4.0
 
+  lie@3.3.0:
+    dependencies:
+      immediate: 3.0.6
+
   lilconfig@3.1.2: {}
 
   lines-and-columns@1.2.4: {}
@@ -10294,6 +10481,10 @@ snapshots:
 
   package-manager-detector@0.2.2: {}
 
+  pako@1.0.11: {}
+
+  pako@2.1.0: {}
+
   parent-module@1.0.1:
     dependencies:
       callsites: 3.1.0
@@ -10345,6 +10536,9 @@ snapshots:
 
   perfect-debounce@1.0.0: {}
 
+  performance-now@2.1.0:
+    optional: true
+
   picocolors@1.1.1: {}
 
   picomatch@2.3.1: {}
@@ -10365,6 +10559,10 @@ snapshots:
     optionalDependencies:
       typescript: 5.3.3
 
+  pizzip@3.1.7:
+    dependencies:
+      pako: 2.1.0
+
   pkcs7@1.0.4:
     dependencies:
       '@babel/runtime': 7.26.0
@@ -10486,8 +10684,12 @@ snapshots:
       ansi-styles: 5.2.0
       react-is: 18.3.1
 
+  print-js@1.6.0: {}
+
   prismjs@1.29.0: {}
 
+  process-nextick-args@2.0.1: {}
+
   process@0.11.10: {}
 
   progress@2.0.3: {}
@@ -10517,12 +10719,27 @@ snapshots:
 
   queue-microtask@1.2.3: {}
 
+  raf@3.4.1:
+    dependencies:
+      performance-now: 2.1.0
+    optional: true
+
   rd@2.0.1:
     dependencies:
       '@types/node': 10.17.60
 
   react-is@18.3.1: {}
 
+  readable-stream@2.3.8:
+    dependencies:
+      core-util-is: 1.0.3
+      inherits: 2.0.4
+      isarray: 1.0.0
+      process-nextick-args: 2.0.1
+      safe-buffer: 5.1.2
+      string_decoder: 1.1.1
+      util-deprecate: 1.0.2
+
   readable-stream@3.6.2:
     dependencies:
       inherits: 2.0.4
@@ -10541,6 +10758,9 @@ snapshots:
 
   regenerate@1.4.2: {}
 
+  regenerator-runtime@0.13.11:
+    optional: true
+
   regenerator-runtime@0.14.1: {}
 
   regenerator-transform@0.15.2:
@@ -10616,6 +10836,9 @@ snapshots:
 
   rfdc@1.4.1: {}
 
+  rgbcolor@1.0.1:
+    optional: true
+
   rimraf@3.0.2:
     dependencies:
       glob: 7.2.3
@@ -10679,6 +10902,8 @@ snapshots:
       has-symbols: 1.0.3
       isarray: 2.0.5
 
+  safe-buffer@5.1.2: {}
+
   safe-buffer@5.2.1: {}
 
   safe-json-parse@4.0.0:
@@ -10752,6 +10977,8 @@ snapshots:
       is-plain-object: 2.0.4
       split-string: 3.1.0
 
+  setimmediate@1.0.5: {}
+
   shebang-command@2.0.0:
     dependencies:
       shebang-regex: 3.0.0
@@ -10862,6 +11089,9 @@ snapshots:
 
   stable@0.1.8: {}
 
+  stackblur-canvas@2.7.0:
+    optional: true
+
   static-extend@0.1.2:
     dependencies:
       define-property: 0.2.5
@@ -10910,6 +11140,10 @@ snapshots:
       define-properties: 1.2.1
       es-object-atoms: 1.0.0
 
+  string_decoder@1.1.1:
+    dependencies:
+      safe-buffer: 5.1.2
+
   string_decoder@1.3.0:
     dependencies:
       safe-buffer: 5.2.1
@@ -11035,6 +11269,9 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  svg-pathdata@6.0.3:
+    optional: true
+
   svg-tags@1.0.0: {}
 
   svg.js@2.7.1: {}
@@ -11078,6 +11315,10 @@ snapshots:
 
   text-extensions@2.4.0: {}
 
+  text-segmentation@1.0.3:
+    dependencies:
+      utrie: 1.0.2
+
   text-table@0.2.0: {}
 
   through@2.3.8: {}
@@ -11345,6 +11586,10 @@ snapshots:
 
   util-deprecate@1.0.2: {}
 
+  utrie@1.0.2:
+    dependencies:
+      base64-arraybuffer: 1.0.2
+
   uuid@10.0.0: {}
 
   vary@1.1.2: {}

+ 16 - 0
public/templete.docx

@@ -0,0 +1,16 @@
+ibj: {
+    name: '',
+    conditionCons: [
+        {
+            groupName: '',
+            cont: ''
+        }
+    ]    
+}
+ 
+ 
+{#conditionCons}
+{groupName}
+{cont}
+{/conditionCons}
+ 

+ 50 - 0
src/utils/doc.js

@@ -0,0 +1,50 @@
+import Docxtemplater from 'docxtemplater'
+import PizZip from 'pizzip'
+import JSZipUtils from 'jszip-utils'
+import { saveAs } from 'file-saver'
+ 
+/**
+ 4. 导出docx
+ 5. @param { String } tempDocxPath 模板文件路径
+ 6. @param { Object } data 文件中传入的数据
+ 7. @param { String } fileName 导出文件名称
+*/
+export const exportDocx = (tempDocxPath, data, fileName) => {
+  // 读取并获得模板文件的二进制内容
+  JSZipUtils.getBinaryContent(tempDocxPath, (error, content) => {
+    if (error) {
+        console.error("获取文件时出现错误:", error);
+        throw error;
+    }
+    console.log("Content type:", typeof content);
+    console.log("获取到的内容:", content); 
+    console.log("获取到的内容长度:", content.byteLength);
+    const uint8Array = new Uint8Array(content);
+    console.log("文件内容字节:", uint8Array);
+    const zip = new PizZip(content)
+    const doc = new Docxtemplater().loadZip(zip)
+    console.log(doc)
+    doc.setData(data)
+    try {
+      // render the document (replace all occurences of {first_name} by John, {last_name} by Doe, ...)
+      doc.render()
+    } catch (error) {
+      const e = {
+        message: error.message,
+        name: error.name,
+        stack: error.stack,
+        properties: error.properties
+      }
+      console.log({
+        error: e
+      })
+      // The error thrown here contains additional information when logged with JSON.stringify (it contains a property object).
+      throw error
+    }
+    const out = doc.getZip().generate({
+      type: 'blob',
+      mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
+    }) // Output the document using Data-URI
+    saveAs(out, fileName)
+  })
+}

+ 89 - 0
src/utils/htmlToPDF.js

@@ -0,0 +1,89 @@
+// 页面导出为pdf格式 //title表示为下载的标题,html表示document.querySelector('#myPrintHtml')
+import html2Canvas from 'html2canvas'
+import JsPDF from 'jspdf'
+var noTableHeight = 0 //table外的元素高度
+
+export const htmlPdf = (title, html, fileList, type) => {// type传有效值pdf则为横版
+  if (fileList) {
+    const pageHeight = Math.floor(277 * html.scrollWidth / 190) + 20 //计算pdf高度
+    for (let i = 0; i < fileList.length; i++) { //循环获取的元素
+      const multiple = Math.ceil((fileList[i].offsetTop + fileList[i].offsetHeight) / pageHeight) //元素的高度
+      if (isSplit(fileList, i, multiple * pageHeight)) { //计算是否超出一页
+        var _H = '' //向pdf插入空白块的内容高度
+        if (fileList[i].localName !== 'tr') { //判断是不是表格里的内容
+          _H = multiple * pageHeight - (fileList[i].offsetTop + fileList[i].offsetHeight)
+        } else {
+          _H = multiple * pageHeight - (fileList[i].offsetTop + fileList[i].offsetHeight + noTableHeight) + 20
+        }
+        var newNode = getFooterElement(_H)  //向pdf插入空白块的内容
+        const divParent = fileList[i].parentNode // 获取该div的父节点
+        const next = fileList[i].nextSibling // 获取div的下一个兄弟节点
+        // 判断兄弟节点是否存在
+        if (next) {
+          // 存在则将新节点插入到div的下一个兄弟节点之前,即div之后
+          divParent.insertBefore(newNode, next)
+        } else {
+          // 否则向节点添加最后一个子节点
+          divParent.appendChild(newNode)
+        }
+      }
+    }
+  }
+  html2Canvas(html, {
+    allowTaint: false,
+    taintTest: false,
+    logging: false,
+    useCORS: true,
+    dpi: window.devicePixelRatio * 2, // 增加DPI以提高图像清晰度
+    scale: 2 // 增加比例以提高图像清晰度
+  }).then(canvas => {
+    var pdf = new JsPDF('p', 'mm', 'a4'); // A4纸,纵向
+    var ctx = canvas.getContext('2d');
+    var a4w = type ? 297 : 210; // A4纸的宽度(mm)
+    var a4h = type ? 210 : 297; // A4纸的高度(mm)
+    var imgHeight = Math.floor((a4h / 297) * canvas.height); // 按A4比例计算图像高度
+    var renderedHeight = 0;
+    while (renderedHeight < canvas.height) {
+      var page = document.createElement('canvas');
+      page.width = canvas.width;
+      page.height = Math.min(imgHeight, canvas.height - renderedHeight); // 可能内容不足一页
+      // 用getImageData剪裁指定区域,并画到前面创建的canvas对象中
+      page.getContext('2d').putImageData(ctx.getImageData(0, renderedHeight, canvas.width, Math.min(imgHeight, canvas.height - renderedHeight)), 0, 0);
+      // 添加图像到页面,保留10mm边距
+      pdf.addImage(page.toDataURL('image/jpeg', 1.0), 'JPEG', 10, 10, a4w - 20, Math.min(a4h - 20, (a4w - 20) * page.height / page.width));
+      renderedHeight += imgHeight;
+      if (renderedHeight < canvas.height) {
+        pdf.addPage(); // 如果后面还有内容,添加一个空页
+      }
+    }
+    // 保存文件
+    pdf.save(title + '.pdf');
+  });
+}
+// pdf截断需要一个空白位置来补充
+const getFooterElement = (remainingHeight, fillingHeight = 0) => {
+  const newNode = document.createElement('div')
+  newNode.style.background = '#ffffff'
+  newNode.style.width = 'calc(100% + 8px)'
+  newNode.style.marginLeft = '-4px'
+  newNode.style.marginBottom = '0px'
+  newNode.classList.add('divRemove')
+  newNode.style.height = (remainingHeight + fillingHeight) + 'px'
+  return newNode
+}
+const isSplit = (nodes, index, pageHeight) => {
+  // 判断是不是tr 如果不是高度存起来
+  // 表格里的内容要特殊处理
+  // tr.offsetTop 是tr到table表格的高度
+  // 所以计算高速时候要把表格外的高度加起来
+  // 生成的pdf没有表格了这里可以不做处理 直接计算就行
+  if (nodes[index].localName !== 'tr') {  //判断元素是不是tr
+    noTableHeight += nodes[index].clientHeight
+  }
+
+  if (nodes[index].localName !== 'tr') {
+    return nodes[index].offsetTop + nodes[index].offsetHeight < pageHeight && nodes[index + 1] && nodes[index + 1].offsetTop + nodes[index + 1].offsetHeight > pageHeight
+  } else {
+    return nodes[index].offsetTop + nodes[index].offsetHeight + noTableHeight < pageHeight && nodes[index + 1] && nodes[index + 1].offsetTop + nodes[index + 1].offsetHeight + noTableHeight > pageHeight
+  }
+}

+ 292 - 165
src/views/system/studentSelectSupervisorRecord/record.vue

@@ -1,67 +1,67 @@
 <template>
-  <!-- 搜索工作栏 -->
-  <ContentWrap v-if="userInfo.userType==='4'">
-  <el-form
-    class="-mb-15px"
-    :model="queryParams"
-    ref="queryFormRef"
-    :inline="true"
-    label-width="100px"
-  >
-
-    <el-form-item label="参与导师" prop="supervisorIds">
-      <el-select
-        v-model="queryParams.supervisorId"
-        @change="getRecordList"
-        placeholder="请选择参与导师"
-        clearable
-        filterable
-        class="!w-240px"
-      >
-        <el-option
-          v-for="supervisor in supervisors"
-          :key="supervisor.id"
-          :label="supervisor.nickname"
-          :value="supervisor.id"
-        />
-      </el-select>
-    </el-form-item>
-
-    <el-form-item label="请选择项目" prop="projectId">
-      <el-select
-        v-model="queryParams.projectId"
-        @change="getRecordList"
-        placeholder="请选择项目"
-        clearable
-        filterable
-        class="!w-240px"
-      >
-        <el-option
-          v-for="project in projects"
-          :key="project.id"
-          :label="project.projectName"
-          :value="project.id"
-        />
-      </el-select>
-    </el-form-item>
-
-    <el-form-item label="参与学生年级" prop="studentGrade">
-      <el-select
-        v-model="queryParams.studentGrade"
-        placeholder="请选择参与学生年级"
-        clearable
-        @change="handleQuery"
-        class="!w-240px"
-      >
-        <el-option
-          v-for="year in gradeOptions"
-          :key="year"
-          :label="year"
-          :value="year"
-        />
-      </el-select>
-    </el-form-item>
-
+   <!-- <el-loading :loading="isLoading" text="加载中..."> -->
+      <!-- 搜索工作栏 -->
+      <ContentWrap v-if="userInfo.userType==='4'">
+        <el-form
+          class="-mb-15px"
+          :model="queryParams"
+          ref="queryFormRef"
+          :inline="true"
+          label-width="100px"
+        >
+          <el-form-item label="参与导师" prop="supervisorIds">
+            <el-select
+              v-model="queryParams.supervisorId"
+              @change="getRecordList"
+              placeholder="请选择参与导师"
+              clearable
+              filterable
+              class="!w-240px"
+            >
+              <el-option
+                v-for="supervisor in supervisors"
+                :key="supervisor.id"
+                :label="supervisor.nickname"
+                :value="supervisor.id"
+              />
+            </el-select>
+          </el-form-item>
+        
+          <el-form-item label="请选择项目" prop="projectId">
+            <el-select
+              v-model="queryParams.projectId"
+              @change="getRecordList"
+              placeholder="请选择项目"
+              clearable
+              filterable
+              class="!w-240px"
+            >
+              <el-option
+                v-for="project in projects"
+                :key="project.id"
+                :label="project.projectName"
+                :value="project.id"
+              />
+            </el-select>
+          </el-form-item>
+        
+          <el-form-item label="参与学生年级" prop="studentGrade">
+            <el-select
+              v-model="queryParams.studentGrade"
+              placeholder="请选择参与学生年级"
+              clearable
+              @change="handleQuery"
+              class="!w-240px"
+            >
+              <el-option
+                v-for="year in gradeOptions"
+                :key="year"
+                :label="year"
+                :value="year"
+              />
+            </el-select>
+          </el-form-item>
+        
           <el-form-item label="学生姓名" v-if="userInfo.userType==='4'">
             <el-input
               v-model="queryParams.studentName"
@@ -88,109 +88,110 @@
             </el-select>
           </el-form-item>
 
-<!--          <el-form-item label="创建时间" prop="createTime">-->
-<!--            <el-date-picker-->
-<!--              v-model="queryParams.createTime"-->
-<!--              value-format="YYYY-MM-DD HH:mm:ss"-->
-<!--              type="daterange"-->
-<!--              start-placeholder="开始日期"-->
-<!--              end-placeholder="结束日期"-->
-<!--              :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"-->
-<!--              class="!w-220px"-->
-<!--            />-->
-<!--          </el-form-item>-->
-
-    <el-form-item>
-              <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
-              <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
-
-      <el-button
-        type="success"
-        plain
-        @click="handleExport"
-        :loading="exportLoading"
-        v-hasPermi="['system:student-select-supervisor-record:export']"
-      >
-        <Icon icon="ep:download" class="mr-5px" /> 导出
-      </el-button>
-    </el-form-item>
-  </el-form>
-  </ContentWrap>
-
-  <!---->
-  <ContentWrap v-if="userInfo.userType==='1' ||userInfo.userType==='4'" >
-      <el-table
-        v-loading="loading"
-        :data="selectionList"
-        row-key="id"
-      >
-      <el-table-column type="index" width="50" />
-        <el-table-column label="项目名称" align="center" prop="projectName" />
-      <el-table-column label="学生姓名" align="center" prop="studentName" />
-      <el-table-column label="硕士类型" align="center" prop="masterType" >
-        <template #default="scope">
-          <dict-tag :type="DICT_TYPE.SYSTEM_STUDENT_MASTER_TYPE" :value="scope.row.masterType" />
-        </template>
-      </el-table-column>
-        <el-table-column label="导师姓名" align="center" prop="supervisor" />
-
-        <el-table-column
-          label="导师类型"
-          align="center"
-          prop="supervisorType"
-          :formatter="userTypeFormatter"
-        />
-      <el-table-column label="申请状态" align="center" prop="selectType" >
-        <template #default="scope">
-          <dict-tag :type="DICT_TYPE.SYSTEM_STUDENT_SELECT_RECORD_SELECT_TYPE" :value="scope.row.selectType" />
-        </template>
-      </el-table-column>
-      <el-table-column label="学生电子签名" align="center" prop="studentSignature">
-        <template #default="scope">
-          <img v-if="scope.row.studentSignature" :src="scope.row.studentSignature" style="width: 100px; height: 50px;" />
-        </template>
-      </el-table-column>
-      <el-table-column label="导师电子签名" align="center" prop="supervisorSignature">
-        <template #default="scope">
-          <img v-if="scope.row.supervisorSignature" :src="scope.row.supervisorSignature" style="width: 100px; height: 50px;" />
-        </template>
-      </el-table-column>
-      <el-table-column
-        label="提交时间"
-        align="center"
-        prop="createTime"
-        :formatter="dateFormatter"
-        width="180px"
-      />
-      <el-table-column
-        label="导师审批时间"
-        align="center"
-        prop="supervisorApproveTime"
-        :formatter="dateFormatter"
-        width="180px"
-      />
-        <el-table-column label="操作" align="center" min-width="120px"   v-if="userInfo.userType==='4'">
-<!--          //学院-->
-          <template #default="scope">
+          <el-form-item>
+            <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+            <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
             <el-button
-              link
-              type="primary"
-              @click="openForm('update',  scope.row.projectId,scope.row.supervisorId,scope.row.id,scope.row.studentId)"
-              v-hasPermi="['system:student-select-supervisor-record:update']"
+              type="success"
+              plain
+              @click="handleExport"
+              :loading="exportLoading"
+              v-hasPermi="['system:student-select-supervisor-record:export']"
             >
-              志愿编辑
+              <Icon icon="ep:download" class="mr-5px" /> 导出
             </el-button>
-          </template>
-        </el-table-column>
-    </el-table>
-
-    <Pagination
-      :total="total"
-      v-model:page="queryParams.pageNo"
-      v-model:limit="queryParams.pageSize"
-      @pagination="getRecordList"
-    />
-  </ContentWrap>
+            <el-button type="primary" @click="exportWordTemplate" :loading="exportLoading">批量导出互选表</el-button>
+          </el-form-item>
+        </el-form>
+      </ContentWrap>
+    
+      <ContentWrap v-if="userInfo.userType==='1' ||userInfo.userType==='4'" >
+        <el-table
+          v-loading="loading"
+          :data="selectionList"
+          row-key="id"
+          @selection-change="onRowSelectChange"
+        >
+          <el-table-column
+            type="selection"
+            width="55"
+            :selectable="selectableRows"
+          />
+          <el-table-column type="index" width="50" />
+            <el-table-column label="项目名称" align="center" prop="projectName" />
+          <el-table-column label="学生姓名" align="center" prop="studentName" />
+          <el-table-column label="硕士类型" align="center" prop="masterType" >
+            <template #default="scope">
+              <dict-tag :type="DICT_TYPE.SYSTEM_STUDENT_MASTER_TYPE" :value="scope.row.masterType" />
+            </template>
+          </el-table-column>
+            <el-table-column label="导师姓名" align="center" prop="supervisor" />
+        
+            <el-table-column
+              label="导师类型"
+              align="center"
+              prop="supervisorType"
+              :formatter="userTypeFormatter"
+            />
+          <el-table-column label="申请状态" align="center" prop="selectType" >
+            <template #default="scope">
+              <dict-tag :type="DICT_TYPE.SYSTEM_STUDENT_SELECT_RECORD_SELECT_TYPE" :value="scope.row.selectType" />
+            </template>
+          </el-table-column>
+          <el-table-column label="学生电子签名" align="center" prop="studentSignature">
+            <template #default="scope">
+              <img v-if="scope.row.studentSignature" :src="scope.row.studentSignature" style="width: 100px; height: 50px;" />
+            </template>
+          </el-table-column>
+          <el-table-column label="导师电子签名" align="center" prop="supervisorSignature">
+            <template #default="scope">
+              <img v-if="scope.row.supervisorSignature" :src="scope.row.supervisorSignature" style="width: 100px; height: 50px;" />
+            </template>
+          </el-table-column>
+          <el-table-column
+            label="提交时间"
+            align="center"
+            prop="createTime"
+            :formatter="dateFormatter"
+            width="180px"
+          />
+          <el-table-column
+            label="导师审批时间"
+            align="center"
+            prop="supervisorApproveTime"
+            :formatter="dateFormatter"
+            width="180px"
+          />
+          <el-table-column label="操作" align="center" min-width="120px"   v-if="userInfo.userType==='4'">
+            <template #default="scope">
+              <el-button
+                link
+                type="primary"
+                @click="openForm('update',  scope.row.projectId,scope.row.supervisorId,scope.row.id,scope.row.studentId)"
+                v-hasPermi="['system:student-select-supervisor-record:update']"
+              >
+                志愿编辑
+              </el-button>
+              <el-button
+                link
+                type="primary"
+                v-if="scope.row.selectType === 2"
+                @click="openForm('end',  scope.row.projectId,scope.row.supervisorId,scope.row.id,scope.row.studentId)"
+              >
+                详情
+              </el-button>
+            </template>
+          </el-table-column>
+      </el-table>
+      
+      <Pagination
+        :total="total"
+        v-model:page="queryParams.pageNo"
+        v-model:limit="queryParams.pageSize"
+        @pagination="getRecordList"
+      />
+    </ContentWrap>
+  <!-- </el-loading> -->
   <!-- 表单弹窗:添加/修改 -->
   <studentSelectSupervisorRecordForm ref="formRef" @success="getRecordList" />
 
@@ -207,14 +208,22 @@ import {studentSelectSupervisorRecordApi, studentSelectSupervisorRecordVO} from
 import {DICT_TYPE} from "@/utils/dict";
 import studentSelectSupervisorRecordForm from './studentSelectSupervisorRecordForm.vue'
 import {studentSelectionProjectApi} from "@/api/system/studentSelectionProject"
+// import { exportDocx } from '@/utils/doc.js';
+// import docxtemplater from "docxtemplater"
+// import PizZip from "pizzip"
+// import JSZipUtils from "jszip-utils"
+// import JSZip from "jszip"
+// import { saveAs } from 'file-saver'
+// import { htmlPdf } from "@/utils/htmlToPDF.js"  
 
 /** 导师学硕专硕名额设置 列表 */
-defineOptions({ name: 'recordList' })
+defineOptions({ name: 'RecordList' })
 
 const message = useMessage() // 消息弹窗
 const { t } = useI18n() // 国际化
 
 const loading = ref(true) // 列表的加载中
+const isLoading = ref(false)
 const total = ref(0) // 列表的总页数
 const queryParams = reactive({
   pageNo: 1,
@@ -268,6 +277,124 @@ const openForm = (type: string, projectId?: number,supervisorId? :number,id?:num
   formRef.value.open(type, projectId,supervisorId,id,studentId)
 }
 
+// 添加一个响应式的变量来保存选中的行
+const selectedRows = ref([]);
+const selectableRows = (row) => {
+  return row.selectType === 2;
+};
+// 处理复选框选择改变的函数
+const onRowSelectChange = (selected) => {
+  selectedRows.value = selected; // 直接将选中的行数组赋值给 selectedRows
+  // console.log('选中的行:', selectedRows.value);
+}
+// 处理选择变化的事件
+watch(selectedRows, (newSelection) => {
+  console.log('选中的行数据:', newSelection);
+});
+
+/** 导出按钮操作 */
+const exportWordTemplate = async () => {
+  if (selectedRows.value.length === 0) {
+    message.error('请先选择需要导出的数据!');
+    return;
+  }
+  // isLoading.value = true;
+  // 遍历所有选中的行
+  for (const row of selectedRows.value) {
+    openForm('end', row.projectId, row.supervisorId, row.id, row.studentId);
+    // 等待弹窗渲染完成
+    await nextTick();
+    // 调用弹窗中的导出方法
+    if (formRef.value && typeof formRef.value.handleExport === 'function') {
+      formRef.value.handleExport(); // 调用导出方法
+    }
+  }
+  // isLoading.value = false;
+};
+// const exportWordTemplate = async () => {
+//     const rows = selectedRows.value; 
+//     console.log("rows", rows);
+//     if (rows.length === 0) {
+//       alert('请先选择需要导出的数据!');
+//       return;
+//     }
+
+//     const ibj = rows.map((row) => ({
+//       name: "普通字段",
+//       conditionCons: [
+//         { 
+//           // "no": index + 1,
+//           groupName: row.projectName, 
+//           cont: row.studentName, 
+//           // "Column3": row.masterType, 请求 templete.docx 的项
+//           // "Column4": row.supervisor 
+//         },
+//       ]
+//     }));
+//     console.log("生成的数据对象:", ibj);
+//     const tempDocxpath = `${window.location.origin}/templete.docx?v=${new Date().getTime()}`;
+//     console.log(tempDocxpath);
+//     exportDocx(tempDocxpath, ibj, '模版.docx')
+// }
+// const exportWordTemplate = async () => {
+//   const rows = selectedRows.value; 
+//   console.log("rows", rows);
+//   if (rows.length === 0) {
+//     alert('请先选择需要导出的数据!');
+//     return;
+//   }
+
+//   const objList = rows.map((row, index) => ({
+//     name: "普通字段",
+//     conditionCons: [
+//       { 
+//         // "no": index + 1,
+//         groupName: row.projectName, 
+//         cont: row.studentName, 
+//         // "Column3": row.masterType, 
+//         // "Column4": row.supervisor 
+//       },
+//     ]
+//   }));
+
+//   const tempDocxpath = '/templete.docx';
+//   const zipName = 'word模板压缩包';
+
+//   if (objList.length > 1) {
+//     // 多组数据,生成ZIP文件
+//     const promises = objList.map((element, index) => {
+//       const fileName = `${index + 1}word模板.docx`;
+//       return JSZipUtils.getBinaryContent(tempDocxpath, (error, content) => {
+//         if (error) throw error;
+//         const zip = new PizZip(content);
+//         let doc = new docxtemplater().loadZip(zip);
+//         doc.setData(element);
+//         doc.render();
+//         const out = doc.getZip().generate({ type: 'blob', mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
+//         return { fileName, content: out };
+//       });
+//     });
+
+//     const zip = new JSZip();
+//     await Promise.all(promises).then(files => {
+//       files.forEach(file => zip.file(file.fileName, file.content, { binary: true }));
+//       const content = zip.generateAsync({ type: 'blob' });
+//       saveAs(content, zipName);
+//     });
+//   } else {
+//     // 单组数据,直接生成Word文件
+//     JSZipUtils.getBinaryContent(tempDocxpath, (error, content) => {
+//       if (error) throw error;
+//       const zip = new PizZip(content);
+//       let doc = new docxtemplater().loadZip(zip);
+//       doc.setData(objList[0]);
+//       doc.render();
+//       const out = doc.getZip().generate({ type: 'blob', mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
+//       saveAs(out, zipName);
+//     });
+//   }
+// };
+
 const recordData = ref({
   id: undefined,
   projectId: undefined,
@@ -290,7 +417,7 @@ const getRecordList = async () => {
   try {
     const data = await studentSelectSupervisorRecordApi.getStudentSelectSupervisorRecordPage(queryParams)
     selectionList.value = data
-    console.log("selectionList", selectionList.value);
+    // console.log("selectionList", selectionList.value);
     selectionList.value = data.list
     total.value = data.total
   } finally {

+ 156 - 42
src/views/system/studentSelectSupervisorRecord/studentSelectSupervisorRecordForm.vue

@@ -1,5 +1,5 @@
 <template>
-  <Dialog :title="dialogTitle" v-model="dialogVisible" class="mutual-selection-dialog">
+  <Dialog id="pdfRef" :title="dialogTitle" v-model="dialogVisible" class="mutual-selection-dialog">
     <div class="header">
       <h3>师生互选表</h3>
     </div>
@@ -15,24 +15,24 @@
       <!-- 学生信息 -->
       <div class="section"  v-if="userInfo.userType==='4' ||userInfo.userType==='3'" >
         <el-row>
-          <el-col :span="12">
+          <el-col :span="10">
             <el-form-item label="学生姓名">
               <el-input v-model="studentData.nickname" placeholder="自动链接" disabled />
             </el-form-item>
           </el-col>
-          <el-col :span="12">
+          <el-col :span="14">
             <el-form-item label="学号">
               <el-input v-model="studentData.userNumber" placeholder="自动链接" disabled />
             </el-form-item>
           </el-col>
         </el-row>
         <el-row>
-          <el-col :span="12">
+          <el-col :span="10">
             <el-form-item label="专业">
               <el-input v-model="studentData.major" placeholder="自动链接" disabled />
             </el-form-item>
           </el-col>
-          <el-col :span="12">
+          <el-col :span="14">
             <el-form-item label="联系电话">
               <el-input v-model="studentData.mobile" placeholder="自动链接" disabled />
             </el-form-item>
@@ -41,7 +41,7 @@
       </div>
 
       <!-- 学生志愿 -->
-      <div class="section" v-if="userInfo.userType !== '4'">
+      <div class="section" v-if="userInfo.userType !== '4' || formType === 'end'">
         <h4>学生志愿</h4>
         <el-row>
           <el-col :span="12">
@@ -85,9 +85,9 @@
                   <button type="button" @click="ClearStudentSignature" style="padding: 2px 5px;">清空</button>
                 </div>
               </div>
-              <p v-if="userInfo.userType==='3'">★ 本人签字</p>
-              <img v-if="userInfo.userType==='3'" :src="formData.studentSignature" alt="学生签名" style="width: 300px; height: 100px; margin-right: 10px; background-color: white;"/>
-              <p v-if="userInfo.userType==='3'">日期:{{formData.studentSignDate}}</p>
+              <p v-if="userInfo.userType==='3' || formType === 'end'">★ 本人签字</p>
+              <img v-if="userInfo.userType==='3' || formType === 'end'" :src="formData.studentSignature" alt="学生签名" style="width: 300px; height: 100px; margin-right: 10px; background-color: white;"/>
+              <p v-if="userInfo.userType==='3' || formType === 'end'">日期:{{formattedStudentSignDate}}</p>
             </el-form-item>
           </el-col>
         </el-row>
@@ -114,20 +114,20 @@
         </div>
         <!-- <p v-if="userInfo.userType==='4'">日期:{{formData.supervisorSignDate}}</p> -->
       </div>
-      <!-- <div class="section" v-if="userInfo.userType ==='4' && formData.selectType === 2">
-        <p v-if="userInfo.userType==='4'" style="font-weight: bold;">★ 导师签字</p>
-        <img v-if="userInfo.userType==='4'" :src="formData.supervisorSignature" alt="导师签名" style="width: 300px; height: 100px; margin-right: 10px; background-color: white;"/>
-        <p v-if="userInfo.userType==='4'">日期:{{formData.supervisorSignDate}}</p>
-      </div> -->
-      <!-- <div class="section" v-if="userInfo.userType==='4'">
-        <el-form-item label="参与导师" prop="supervisorIds">
+      <div class="section" v-if="formType === 'end'">
+        <p style="font-weight: bold;">★ 导师签字</p>
+        <img :src="formData.supervisorSignature" alt="导师签名" style="width: 300px; height: 100px; margin-right: 10px; background-color: white;"/>
+        <p>日期:{{formattedSupervisorSignDate}}</p>
+      </div>
+      <div class="section" v-if="userInfo.userType==='4' && formType === 'update'">
+        <el-form-item label="参与导师" prop="supervisorId">
           <el-select
-            v-model="queryParams.supervisorId"
-            @change="getRecordList"
+            v-model="supervisors.supervisorId"
             placeholder="请选择参与导师"
             clearable
             filterable
-            class="!w-240px"
+            class="!w"
+            @change="handleSupervisorChange"
           >
             <el-option
               v-for="supervisor in supervisors"
@@ -137,10 +137,11 @@
             />
           </el-select>
         </el-form-item>
-      </div> -->
+      </div>
       <div style="display: flex; justify-content: center;">
-        <el-button @click="submitForm" type="primary" :disabled="formLoading">{{ submitFormText }}</el-button>
-        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button @click="submitForm" type="primary" :disabled="formLoading" v-if="formType !== 'end'">{{ submitFormText }}</el-button>
+        <el-button @click="dialogVisible = false" v-if="formType !== 'end'">取消</el-button>
+        <el-button @click="handleExport" type="primary" :disabled="formLoading" v-if="showPrintButton">打印</el-button>
       </div>
     </el-form>
   </Dialog>
@@ -153,6 +154,8 @@ import * as UserApi from '@/api/system/user'
 import { defineOptions, defineExpose, ref, reactive, nextTick, onMounted, computed } from 'vue'
 import { getUserProfile, ProfileVO } from '@/api/system/user/profile'
 import {supervisorSelectionSettingApi} from '@/api/system/supervisorSelectionSetting'
+import { htmlPdf } from "@/utils/htmlToPDF.js"  
+
 
 /** 师生互选记录 表单 */
 defineOptions({ name: 'studentSelectSupervisorRecordForm' })
@@ -179,6 +182,26 @@ const formData = ref({
   supervisorSignDate:[],
 })
 
+const formattedStudentSignDate = computed(() => {
+  if (formData.value.studentSignDate) {
+    const year = formData.value.studentSignDate[0];
+    const month = formData.value.studentSignDate[1];
+    const day = formData.value.studentSignDate[2];
+    return `${year}.${month}.${day}`;
+  }
+  return '';
+});
+
+const formattedSupervisorSignDate = computed(() => {
+  if (formData.value.supervisorSignDate) {
+    const year = formData.value.supervisorSignDate[0];
+    const month = formData.value.supervisorSignDate[1];
+    const day = formData.value.supervisorSignDate[2];
+    return `${year}.${month}.${day}`;
+  }
+  return '';
+});
+
 const supervisorData = ref({
   //导师的信息
   nickname: '',
@@ -209,6 +232,20 @@ const getUserInfo = async () => {
   userInfo.value = users
 }
 
+// 获取所有导师
+const supervisors = ref()
+const getSupervisor= async () => {
+  try {
+    const result = await UserApi.getSupervisor()
+    // console.log('getSupervisor',result);
+    supervisors.value = result
+  } catch (error) {
+    console.error('未获取到导师', error)
+  }
+}
+
+const showPrintButton = ref(false); // 控制打印按钮的显示
+
 /** 打开弹窗 */
 const open = async (type: string, projectId?: number ,supervisorId?:number, id?:number,studentId?:number) => {
   dialogVisible.value = true;
@@ -236,9 +273,34 @@ const open = async (type: string, projectId?: number ,supervisorId?:number, id?:
       }
     }
   }
+  if (type === 'agree'){
+    dialogTitle.value = '通过志愿'
+    submitFormText.value = '接受'
+    formType.value = type
+    resetForm()
+    if (studentId) {
+      formLoading.value = true
+      try {
+        const result =await UserApi.getUser(studentId)
+        studentData.value.nickname=result.nickname
+        studentData.value.userNumber=result.userNumber
+        studentData.value.major=result.major
+        studentData.value.mobile=result.mobile
+
+        const supervisor = await supervisorSelectionSettingApi.getSupervisorInfo(supervisorId,projectId);
+        //显示的
+        supervisorData.value.nickname = supervisor.supervisorName;
+        supervisorData.value.title =supervisor.title;
+        supervisorData.value.studentAchievementRequirement = supervisor.studentAchievementRequirement;
+        supervisorData.value.major = supervisor.major;
+        formData.value = await studentSelectSupervisorRecordApi.getStudentSelectSupervisorRecord(id)
+      } finally {
+        formLoading.value = false
+      }
+    }
+  }
   if (type === 'update'){
     dialogTitle.value = '志愿编辑'
-    // dialogTitle.value = '详情'
     submitFormText.value = '编辑'
     formType.value = type
     resetForm()
@@ -250,12 +312,58 @@ const open = async (type: string, projectId?: number ,supervisorId?:number, id?:
         formLoading.value = false
       }
     }
+    if (supervisorId) {
+      formLoading.value = true
+      try {
+        console.log(supervisorId)
+        console.log(projectId)
+        const supervisor = await supervisorSelectionSettingApi.getSupervisorInfo(supervisorId,projectId);
+        //需要传的
+        formData.value.supervisorId =supervisorId;
+        formData.value.projectId = projectId;
+        //显示的
+        supervisorData.value.nickname = supervisor.supervisorName;
+        supervisorData.value.title =supervisor.title;
+        supervisorData.value.studentAchievementRequirement = supervisor.studentAchievementRequirement;
+        supervisorData.value.major = supervisor.major;
+      } finally {
+        formLoading.value = false
+      }
+    }
+    if (studentId) {
+      formLoading.value = true
+      try {
+        const result =await UserApi.getUser(studentId)
+        studentData.value.nickname=result.nickname
+        studentData.value.userNumber=result.userNumber
+        studentData.value.major=result.major
+        studentData.value.mobile=result.mobile
+
+        const supervisor = await supervisorSelectionSettingApi.getSupervisorInfo(supervisorId,projectId);
+        //显示的
+        supervisorData.value.nickname = supervisor.supervisorName;
+        supervisorData.value.title =supervisor.title;
+        supervisorData.value.studentAchievementRequirement = supervisor.studentAchievementRequirement;
+        supervisorData.value.major = supervisor.major;
+        formData.value = await studentSelectSupervisorRecordApi.getStudentSelectSupervisorRecord(id)
+      } finally {
+        formLoading.value = false
+      }
+    }
   }
-  if (type === 'agree'){
-    dialogTitle.value = '通过志愿'
-    submitFormText.value = '接受'
+  if (type === 'end'){
+    dialogTitle.value = '详情'
+    submitFormText.value = '编辑'
     formType.value = type
     resetForm()
+    if (id) {
+      formLoading.value = true
+      try {
+        formData.value = await studentSelectSupervisorRecordApi.getStudentSelectSupervisorRecord(id)
+      } finally {
+        formLoading.value = false
+      }
+    }
     if (studentId) {
       formLoading.value = true
       try {
@@ -271,7 +379,6 @@ const open = async (type: string, projectId?: number ,supervisorId?:number, id?:
         supervisorData.value.title =supervisor.title;
         supervisorData.value.studentAchievementRequirement = supervisor.studentAchievementRequirement;
         supervisorData.value.major = supervisor.major;
-
         formData.value = await studentSelectSupervisorRecordApi.getStudentSelectSupervisorRecord(id)
       } finally {
         formLoading.value = false
@@ -289,6 +396,14 @@ const open = async (type: string, projectId?: number ,supervisorId?:number, id?:
   }
 }
 
+const handleExport = (row) => {
+  var fileName = '师生互选表';
+  const formElement = document.querySelector('.form-container'); 
+  htmlPdf(fileName, formElement, row);
+  dialogVisible.value = false;
+};
+defineExpose({ open, handleExport });
+
 const canvas = ref() // 签名画布
 const isDrawing = ref(false) // 是否正在画
 const context = ref() // 签名画布的上下文
@@ -357,7 +472,7 @@ const handleSupervisorSignatureSave = () => {
   }
 };
 
-defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+// defineExpose({ open }) // 提供 open 方法,用于打开弹窗
 
 /** 提交表单 */
 const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
@@ -371,7 +486,7 @@ const submitForm = async () => {
       formLoading.value = false;
       return;
     }
-    if (!isSupervisorSignatureSaved && (userInfo.value.userType === '3' || userInfo.value.userType === '4')) {
+    if (!isSupervisorSignatureSaved && (userInfo.value.userType === '3')) {
       message.error('请先保存签名');
       formLoading.value = false;
       return;
@@ -401,19 +516,9 @@ const handleFormSubmit = () => {
 
 const isSupervisor = computed(() => userInfo.value.userType === '3');
 
-//获取所有导师
-const supervisors = ref()
-const getSupervisor= async () => {
-  try {
-    const result = await UserApi.getSupervisor()
-    supervisors.value = result
-  } catch (error) {
-    console.error('未获取到导师', error)
-  }
-}
-
-const handleSupervisorChange = (values) => {
-  formData.value.supervisorIds = values;  // values 是选择的导师ID数组
+// 直接改导师(志愿编辑)
+const handleSupervisorChange = (supervisorId?:number) => {
+  supervisorId = supervisors.value.id;
 }
 
 /** 重置表单 */
@@ -491,4 +596,13 @@ onMounted(() => {
   background-color: #66b1ff;
   border-color: #66b1ff;
 }
+
+@media print {
+  .header {
+    display: none; /* 打印时隐藏标题 */
+  }
+
+  /* 可以根据需要隐藏其他不需要的部分 */
+}
+
 </style>