华西海圻ELN前端工程
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

587 lines
20 KiB

  1. <template>
  2. <div>
  3. <div class="edit-container" v-if="open">
  4. <div class="edit-top">
  5. <div class="left-top">
  6. <img src="@/assets/images/back.png" @click="cancel()" />
  7. <div class="left-title"></div>
  8. </div>
  9. <div class="center-top">
  10. </div>
  11. <div class="right-top">
  12. <el-button @click="cancel()">{{ $t('form.close') }}</el-button>
  13. <el-button type="primary" v-if="form.bdzt === 5 && form.tbzt === 1" @click="openApprove = true">{{
  14. $t('page.business.study.studyFormFill.tb') }}</el-button>
  15. </div>
  16. </div>
  17. <!-- 1:流程3编辑5人员7修改9补充说明 -->
  18. <el-button type="primary" @click="exportExcel(-1)">{{ $t('page.business.study.studyFormFill.dcqbjcgj') }}
  19. </el-button>
  20. <el-button type="primary" @click="exportExcel(1)">{{ $t('page.business.study.studyFormFill.dclcjcgj') }}
  21. </el-button>
  22. <el-button type="primary" @click="exportExcel(3)">{{ $t('page.business.study.studyFormFill.dcbjjcgj') }}
  23. </el-button>
  24. <el-button type="primary" @click="exportExcel(7)">{{ $t('page.business.study.studyFormFill.dcxgjcgj') }}
  25. </el-button>
  26. <el-button type="primary" @click="exportExcel(999)">{{ $t('page.business.study.studyFormFill.dcbhsjgj') }}
  27. </el-button>
  28. <div class="edit-content">
  29. <div class="detail-content" style="width: 100%; height: 100%; padding: 0px 10px;">
  30. <vue-html2pdf :show-layout="true" pdf-content-width="100%" :pdf-format="form.templatePdfSize" :pdf-quality="2"
  31. :float-layout="false" pdf-orientation="landscape" :paginate-elements-by-height="0" :enable-download="false"
  32. :preview-modal="false" :filename="form.bdmc" :html-to-pdf-options="{
  33. margin: [10, 5, 10, 5], // 格式: [上, 右, 下, 左] (单位: mm)
  34. // 或者使用对象格式
  35. // margin: { top: 20, right: 15, bottom: 20, left: 15 },
  36. filename: 'document.pdf',
  37. image: {
  38. type: 'jpeg',
  39. quality: 2
  40. },
  41. enableLinks: false,
  42. html2canvas: {
  43. scale: 2,
  44. useCORS: true
  45. },
  46. jsPDF: {
  47. unit: 'mm', // 单位: mm
  48. format: form.templatePdfSize, // 纸张大小
  49. orientation: 'landscape' // 方向
  50. }
  51. }" @beforeDownload="handleBeforeDownload" ref="html2Pdf" @progress="onProgress">
  52. <section slot="pdf-content">
  53. <div class="pdf-content">
  54. <TemplateTable ref="templateTable" :sn="form.templateSn" :templateData="form" fillType="detail" />
  55. <div v-if="showExport" style="width: 100%; padding: 0px 30px ;">
  56. <div class="content-title" style="margin-bottom: 10px;">
  57. <div class="line"></div>
  58. <div class="subtitle"> {{ $t('page.business.study.studyFormFill.qmxx') }}</div>
  59. </div>
  60. <table class="datatable">
  61. <thead>
  62. <tr>
  63. <th style="width: 20%;">{{ $t('page.business.study.studyFormFill.qmr') }}</th>
  64. <th style="width: 20%;">{{ $t('page.business.study.studyFormFill.qmyy') }}</th>
  65. <th style="width: 20%;">{{ $t('page.business.study.studyFormFill.qmsj') }}</th>
  66. <th style="width: 40%;">{{ $t('page.business.study.studyFormFill.bzyy') }}</th>
  67. </tr>
  68. </thead>
  69. <tbody>
  70. <tr v-for="(item, index) in qmxxExportList" :key="index">
  71. <td>{{ item.qmrMc }}</td>
  72. <td>{{ $i18n.locale === 'zh_CN' ? item.qmyy : item.qmyyEn }}</td>
  73. <td>{{ item.createTime }}</td>
  74. <td>{{ item.remark }}</td>
  75. </tr>
  76. </tbody>
  77. </table>
  78. <div class="content-title" style="margin-top: 10px;" v-show="jcgjlxExport != 999">
  79. <div class="line"></div>
  80. <div class="subtitle"> {{ $t('page.business.study.studyFormFill.jcgj') }}</div>
  81. </div>
  82. <JcgjExportList ref="jcgjExportList" :readonly="true" v-show="jcgjlxExport != 999" />
  83. </div>
  84. </div>
  85. <div v-if="showExport" id="watermark-overlay" ref="watermarkContainer" :style="{
  86. '--watermark-text': `'${watermarkText}'`,
  87. '--watermark-opacity': opacity,
  88. '--watermark-size': '14px',
  89. '--watermark-color': 'red'
  90. }"></div>
  91. </section>
  92. </vue-html2pdf>
  93. <div style="margin-left: 20px;">
  94. <div class="content-title">
  95. <div class="line"></div>
  96. <div class="subtitle"> {{ $t('page.business.study.studyFormFill.qmxx') }}</div>
  97. </div>
  98. <div class="pal">
  99. <el-table :data="qmxxList" v-loading="loadingQmxx" style="width: 100%;">
  100. <el-table-column :label="$t('page.business.study.studyFormFill.qmr')" align="center" prop="qmrMc" />
  101. <el-table-column :label="$t('page.business.study.studyFormFill.qmyy')" align="center"
  102. :prop="$i18n.locale === 'zh_CN' ? 'qmyy' : 'qmyyEn'" />
  103. <el-table-column :label="$t('page.business.study.studyFormFill.qmsj')" align="center"
  104. prop="createTime" />
  105. <el-table-column :label="$t('page.business.study.studyFormFill.bzyy')" align="center" prop="remark" />
  106. </el-table>
  107. </div>
  108. <pagination v small layout="prev, pager, next" :total="totalQmxx" :page.sync="queryParamsQmxx.pageNum"
  109. :limit.sync="queryParamsQmxx.pageSize" @pagination="getQmxxList" />
  110. <div class="content-title" style="margin-top: 10px;">
  111. <div class="line"></div>
  112. <div class="subtitle"> {{ $t('page.business.study.studyFormFill.jcgj') }}</div>
  113. </div>
  114. <JcgjList ref="jcgjList" @handleQuery="getJjcgjList" :showXg="true" />
  115. <pagination v small layout="prev, pager, next" :page.sync="queryParamsJcgj.pageNum"
  116. :limit.sync="queryParamsJcgj.pageSize" :total="jcgjTotal" @pagination="getJjcgjList" />
  117. </div>
  118. </div>
  119. </div>
  120. </div>
  121. <!-- 填报 -->
  122. <el-dialog :title="$t('page.business.study.studyFormFill.cjjl')" :visible.sync="openApprove" width="500px"
  123. append-to-body :close-on-click-modal="false">
  124. <el-form ref="formApprove" :model="formApprove" :rules="rulesApprove" label-width="120px" v-if="openApprove">
  125. <el-alert :title="$t('page.business.study.studyFormFill.ts')" :closable="false" type="success">
  126. </el-alert>
  127. <el-row>
  128. <el-col :span="24">
  129. <el-form-item :label="$t('form.qmyy')" prop="qmyy">
  130. <el-input type="text" :value="formApprove.qmyy" maxlength="50" disabled
  131. :placeholder="$t('form.placeholderInput')" />
  132. </el-form-item>
  133. </el-col>
  134. </el-row>
  135. <el-row>
  136. <el-col :span="24">
  137. <el-form-item :label="$t('form.remark')" prop="remark">
  138. <el-input type="textarea" v-model="formApprove.remark" :rows="5" maxlength="500"
  139. :placeholder="$t('form.placeholderInput')">
  140. </el-input>
  141. </el-form-item>
  142. </el-col>
  143. </el-row>
  144. <el-row>
  145. <el-col :span="24">
  146. <el-form-item :label="$t('form.signer')">
  147. <el-input type="text" v-model="nickName" maxlength="50" disabled
  148. :placeholder="$t('form.placeholderInput')" />
  149. </el-form-item>
  150. </el-col>
  151. </el-row>
  152. <el-row>
  153. <el-col :span="24">
  154. <el-form-item :label="$t('form.password')" prop="qmrmm">
  155. <el-input type="password" show-password v-model="formApprove.qmrmm" maxlength="20"
  156. :placeholder="$t('form.placeholderInput')" />
  157. </el-form-item>
  158. </el-col>
  159. </el-row>
  160. </el-form>
  161. <div slot="footer" class="dialog-footer">
  162. <el-button type="primary" @click="approve">{{ $t('form.confirm') }}</el-button>
  163. <el-button @click="openApprove = false">{{ $t('form.cancel') }}</el-button>
  164. </div>
  165. </el-dialog>
  166. </div>
  167. </template>
  168. <script>
  169. import { studyFormFill_jcgjqmxxList, studyFormFill_uploadFile, studyFormFill_exportByFileUrl, studyFormFill_tb, studyFormFill_info, studyFormFill_jcgj, studyFormFill_qmxx, studyFormFill_exportDetail } from "@/api/business/study/studyFormFill"
  170. import { getToken } from "@/utils/auth"
  171. import { mapGetters } from 'vuex'
  172. import JcgjList from "@/views/business/comps/common/JcgjList";
  173. import JcgjExportList from "@/views/business/comps/common/JcgjExportList";
  174. import TemplateTable from '@/views/business/comps/template/TemplateTable';
  175. import moment from "moment";
  176. import VueHtml2pdf from 'vue-html2pdf'
  177. export default {
  178. name: "Xq",
  179. components: { JcgjExportList, JcgjList, TemplateTable, VueHtml2pdf },
  180. data() {
  181. return {
  182. watermarkText: '',
  183. appTitle: process.env.VUE_APP_TITLE,
  184. baseUrl: process.env.VUE_APP_FILE_DOMAIN,
  185. uploadFileUrl: process.env.VUE_APP_BASE_API + '/file/upload', // 上传文件服务器地址
  186. opacity: 0.8,
  187. watermarkOpacity: 0.8,
  188. watermarkSize: 40,
  189. openApprove: false,
  190. formApprove: {
  191. id: null,
  192. qmyy: this.$t('page.business.study.studyFormFill.cjjl'),
  193. remark: '',
  194. qmrmm: '',
  195. },
  196. rulesApprove: {
  197. qmrmm: [{
  198. required: true,
  199. message: ' ',
  200. trigger: 'blur'
  201. }]
  202. },
  203. qmxxList: [],
  204. totalQmxx: 0,
  205. loadingQmxx: true,
  206. queryParamsQmxx: {
  207. formId: null,
  208. pageNum: 1,
  209. pageSize: 5
  210. },
  211. open: false,
  212. showIndex: 1,
  213. form: {},
  214. rules: {
  215. bdmc: [{
  216. required: true,
  217. message: ' ',
  218. trigger: 'blur'
  219. }],
  220. templateId: [{
  221. required: true,
  222. message: ' ',
  223. trigger: 'blur'
  224. }]
  225. },
  226. jcgjTotal: 0,
  227. jcgjList: [],
  228. queryParamsJcgj: {
  229. pageNum: 1,
  230. formId: null,
  231. pageSize: 5,
  232. },
  233. showExport: false,
  234. qmxxExportList: [],
  235. jcgjExportList: [],
  236. jcgjlxExport: 0
  237. }
  238. },
  239. computed: {
  240. ...mapGetters([
  241. 'nickName', 'name'
  242. ]),
  243. },
  244. created() {
  245. },
  246. methods: {
  247. //创建水印-弃用
  248. updateWatermark() {
  249. // 创建水印背景
  250. const text = this.nickName + ' ' + moment().format("YYYY-MM-DD HH:mm:ss");
  251. const canvas = document.createElement('canvas');
  252. const ctx = canvas.getContext('2d');
  253. // 设置canvas尺寸
  254. canvas.width = 300;
  255. canvas.height = 300;
  256. // 绘制水印
  257. ctx.fillStyle = `rgba(255, 0, 0, ${this.opacity})`;
  258. ctx.font = '12px Arial';
  259. ctx.textAlign = 'center';
  260. ctx.textBaseline = 'middle';
  261. // 旋转45度
  262. ctx.translate(canvas.width / 2, canvas.height / 2);
  263. ctx.rotate(-45 * Math.PI / 180);
  264. ctx.fillText(text, 0, 0);
  265. // 设置为背景
  266. const container = this.$refs.watermarkContainer;
  267. container.style.backgroundImage = `url(${canvas.toDataURL()})`;
  268. container.style.backgroundRepeat = 'repeat';
  269. },
  270. //添加水印-弃用
  271. async addDynamicWatermark({ pdfContent }) {
  272. return new Promise((resolve) => {
  273. const canvas = document.createElement('canvas')
  274. const ctx = canvas.getContext('2d')
  275. // 设置canvas尺寸
  276. canvas.width = 300;
  277. canvas.height = 300;
  278. // 绘制动态水印
  279. ctx.fillStyle = `rgba(100, 100, 100, ${this.watermarkOpacity})`
  280. ctx.font = `${this.watermarkSize}px Arial`
  281. ctx.textAlign = 'center'
  282. ctx.textBaseline = 'middle'
  283. // 计算水印密度
  284. const stepX = 300
  285. const stepY = 200
  286. // 绘制倾斜水印
  287. ctx.save()
  288. ctx.translate(canvas.width / 2, canvas.height / 2)
  289. ctx.rotate(-Math.PI / 4) // 45度倾斜
  290. let time = moment().format("YYYY-MM-DD HH:mm:ss")
  291. for (let x = -canvas.width; x < canvas.width * 2; x += stepX) {
  292. for (let y = -canvas.height; y < canvas.height * 2; y += stepY) {
  293. // 动态水印内容
  294. const dynamicText = `${this.watermarkText} - ${time}`
  295. ctx.fillText(dynamicText, x, y)
  296. }
  297. }
  298. ctx.restore()
  299. // 创建水印层
  300. const watermarkLayer = document.createElement('div')
  301. watermarkLayer.className = 'watermark-layer'
  302. watermarkLayer.style.cssText = `
  303. position: absolute;
  304. top: 0;
  305. left: 0;
  306. width: 100%;
  307. height: 100%;
  308. pointer-events: none;
  309. background-image: url(${canvas.toDataURL('image/png')});
  310. background-repeat: repeat;
  311. z-index: 9999;
  312. `
  313. // 等待渲染
  314. setTimeout(() => resolve(), 100)
  315. })
  316. },
  317. //生成进度
  318. onProgress(progress) {
  319. console.log(`生成进度: ${progress}%`)
  320. this.removePageBreak()
  321. if (progress == 100) {
  322. this.showExport = false
  323. // this.$modal.closeLoading()
  324. } else {
  325. this.$modal.loading()
  326. }
  327. },
  328. //导出
  329. exportExcel(jcgjlx) {
  330. this.$modal.loading()
  331. debugger
  332. this.jcgjlxExport = jcgjlx
  333. // this.showExport = true
  334. // studyFormFill_jcgjqmxxList({ jcgjlx: jcgjlx, id: this.form.id }).then(response => {
  335. // this.jcgjExportList = response.data.jcgj
  336. // this.qmxxExportList = response.data.qmxx
  337. // // this.updateWatermark()
  338. // this.$refs.jcgjExportList.init(this.jcgjExportList)
  339. setTimeout(() => {
  340. this.$refs.html2Pdf.generatePdf()
  341. this.saveSimpleLog({ jcmc: '填报表单详情导出', jcmcEn: 'Record Detail Export', name: this.form.bdmc + '(' + this.form.bdbh + ')', nameEn: this.form.bdmc + '(' + this.form.bdbh + ')' })
  342. }, 200);
  343. // })
  344. },
  345. //获取文件blog
  346. async handleBeforeDownload({ html2pdf, options, pdfContent }) {
  347. this.$modal.loading()
  348. // 1. 使用 html2pdf 手动构建 PDF,并获取底层的 jsPDF 实例
  349. const pdf = await html2pdf()
  350. .set(options) // 应用你的配置选项
  351. .from(pdfContent) // 指定 PDF 内容
  352. .toPdf() // 转换为 PDF
  353. .get('pdf'); // 获取 jsPDF 实例
  354. // 2. 从 jsPDF 实例中输出 Blob 对象
  355. const blob = pdf.output('blob');
  356. // 3. 将 Blob 上传到你的服务器
  357. await this.uploadPdfToServer(blob);
  358. // 注意:如果你还想让文件自动下载,可以在这里调用 .save()
  359. // pdf.save('my-document.pdf');
  360. },
  361. //上传到服务器
  362. async uploadPdfToServer(blob) {
  363. const formData = new FormData();
  364. formData.append('file', blob, 'hxhq-export-form.pdf');
  365. try {
  366. const response = await fetch(this.uploadFileUrl, {
  367. method: 'POST',
  368. headers: {
  369. Authorization: "Bearer " + getToken(),
  370. },
  371. body: formData
  372. });
  373. // 检查响应状态
  374. if (!response.ok) {
  375. // 如果响应不成功,可以尝试获取错误信息
  376. const errorData = await response.json().catch(() => ({}));
  377. throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
  378. }
  379. // 获取 JSON 数据
  380. const data = await response.json();
  381. // console.log('返回的JSON数据:', data);
  382. if (data.code == 200) {
  383. studyFormFill_exportByFileUrl(
  384. {
  385. url: data.data.url,
  386. studyFormFillId: this.form.id,
  387. jcgjlx: this.jcgjlxExport,
  388. version:this.appTitle,
  389. lang: this.$store.getters.language.split("_")[0]
  390. }
  391. ).then(response => {
  392. window.open(this.baseUrl + response.msg)
  393. }).finally(() => {
  394. this.$modal.closeLoading()
  395. })
  396. } else {
  397. this.$modal.msgError(data.msg)
  398. this.$modal.closeLoading()
  399. }
  400. } catch (error) {
  401. this.$modal.msgError("导出失败,稍后再试")
  402. this.$modal.closeLoading()
  403. }
  404. },
  405. //移除分页空白
  406. removePageBreak() {
  407. document.querySelectorAll('.html2pdf__page-break').forEach(el => {
  408. el.remove()
  409. })
  410. },
  411. //导出服务器-弃用
  412. exportExcel_bak(jcgjlx) {
  413. this.$modal.loading()
  414. studyFormFill_exportDetail(_.merge({}, this.queryParamsJcgj, { jcgjlx: jcgjlx, lang: this.$store.getters.language.split("_")[0] })).then(response => {
  415. window.open(process.env.VUE_APP_FILE_DOMAIN + response.msg)
  416. }).finally(() => {
  417. this.$modal.closeLoading()
  418. })
  419. },
  420. getJjcgjList(val) {
  421. this.$modal.loading()
  422. if (val) {
  423. this.queryParamsJcgj = _.merge({}, this.queryParamsJcgj, val)
  424. }
  425. studyFormFill_jcgj(this.queryParamsJcgj).then(response => {
  426. this.jcgjList = response.rows
  427. this.jcgjTotal = response.total
  428. this.$refs.jcgjList.init(this.jcgjList)
  429. }).finally(() => {
  430. this.$modal.closeLoading()
  431. })
  432. },
  433. getQmxxList() {
  434. this.loadingQmxx = true
  435. studyFormFill_qmxx(this.queryParamsQmxx).then(response => {
  436. this.qmxxList = response.rows
  437. this.totalQmxx = response.total
  438. this.loadingQmxx = false
  439. })
  440. },
  441. cancel() {
  442. this.$emit('close')
  443. this.open = false
  444. },
  445. reset() {
  446. this.form = {
  447. id: null,
  448. studyId: null,
  449. bdbh: null,
  450. bdmc: null,
  451. bdsm: null,
  452. templateId: null,
  453. templateMc: null,
  454. bdnr: null
  455. }
  456. this.resetForm("form")
  457. },
  458. show(row) {
  459. this.reset()
  460. this.$modal.loading()
  461. this.formApprove.id = row.id
  462. this.queryParamsJcgj.formId = row.id
  463. this.queryParamsQmxx.formId = row.id
  464. studyFormFill_info({ id: row.id }).then(response => {
  465. this.form = response.data
  466. this.open = true
  467. this.getQmxxList()
  468. this.getJjcgjList()
  469. this.saveSimpleLog({ jcmc: '填报详情', jcmcEn: 'Record Detail', name: this.form.bdmc + '(' + this.form.bdbh + ')', nameEn: this.form.bdmc + '(' + this.form.bdbh + ')' })
  470. })
  471. },
  472. approve() {
  473. this.$refs["formApprove"].validate(valid => {
  474. if (valid) {
  475. this.$modal.loading()
  476. studyFormFill_tb(this.formApprove).then(response => {
  477. this.openApprove = false
  478. this.$emit('close')
  479. this.open = false
  480. }).finally(() => {
  481. this.$modal.closeLoading()
  482. })
  483. }
  484. })
  485. }
  486. }
  487. }
  488. </script>
  489. <style scoped>
  490. .content-title {
  491. width: 100%;
  492. background: #f9f9ff;
  493. font-size: 0.96rem;
  494. font-weight: bold;
  495. padding-left: 10px;
  496. height: 40px;
  497. page-break-inside: avoid;
  498. line-height: 40px;
  499. display: flex;
  500. justify-content: flex-start;
  501. text-align: left;
  502. }
  503. .line {
  504. width: 2px;
  505. float: left;
  506. height: 16px;
  507. margin-top: 12px;
  508. margin-right: 8px;
  509. border-left: #3178ff 3px solid;
  510. }
  511. .subtitle {
  512. height: 40px;
  513. line-height: 40px;
  514. color: #464647 !important;
  515. }
  516. .pdf-content {
  517. padding: 0px;
  518. font-family: Arial, sans-serif;
  519. }
  520. #watermark-overlay {
  521. position: absolute;
  522. top: -80px;
  523. left: 0;
  524. width: 100%;
  525. height: 100%;
  526. opacity: 0.6;
  527. font-size: 12px;
  528. pointer-events: none;
  529. z-index: 10;
  530. color: red;
  531. }
  532. .pdf-content h1 {
  533. color: #333;
  534. border-bottom: 2px solid #4CAF50;
  535. padding-bottom: 10px;
  536. }
  537. .upload-file-list .el-upload-list__item {
  538. margin-bottom: 0px !important;
  539. }
  540. .html2pdf__page-break {
  541. display: none !important;
  542. }
  543. .content {
  544. position: relative;
  545. z-index: 1;
  546. }
  547. .controls {
  548. margin-bottom: 20px;
  549. padding: 10px;
  550. background: #f5f5f5;
  551. }
  552. input {
  553. margin: 0 10px;
  554. padding: 8px;
  555. }
  556. </style>