DataRequirementsTab.vue 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352
  1. <template>
  2. <div>
  3. <!-- 在线填报弹窗(单记录类型,编辑模式) -->
  4. <survey-form-dialog
  5. :visible.sync="surveyFormDialogVisible"
  6. :survey-data="{ ...(currentTemplateRow || {}), ...surveyDetailData }"
  7. :form-fields="formFields"
  8. :is-view-mode="isViewMode"
  9. :audited-unit-id="auditedUnitId"
  10. :request-type="2"
  11. :upload-id="
  12. (currentTemplateRow &&
  13. (currentTemplateRow.uploadId || currentTemplateRow.id)) ||
  14. ''
  15. "
  16. :survey-template-id="getSurveyTemplateId(currentTemplateRow)"
  17. :catalog-id="(currentTemplateRow && currentTemplateRow.catalogId) || ''"
  18. @save="
  19. $emit('handle-survey-form-save', {
  20. row: currentTemplateRow,
  21. formData: $event,
  22. })
  23. "
  24. @refresh="
  25. $emit('handle-survey-form-save', {
  26. row: currentTemplateRow,
  27. formData: {},
  28. })
  29. "
  30. />
  31. <!-- 固定表填报弹窗(编辑模式) -->
  32. <fixed-table-dialog
  33. :visible.sync="fixedTableDialogVisible"
  34. :survey-data="{ ...(currentTemplateRow || {}), fixedHeaders }"
  35. :table-items="tableItems"
  36. :audit-periods="auditPeriods"
  37. :is-view-mode="isViewMode"
  38. :request-type="2"
  39. :audited-unit-id="auditedUnitId"
  40. :upload-id="
  41. (currentTemplateRow &&
  42. (currentTemplateRow.uploadId || currentTemplateRow.id)) ||
  43. ''
  44. "
  45. :survey-template-id="getSurveyTemplateId(currentTemplateRow)"
  46. :catalog-id="(currentTemplateRow && currentTemplateRow.catalogId) || ''"
  47. @save="
  48. $emit('handle-fixed-table-save', {
  49. row: currentTemplateRow,
  50. tableData: $event,
  51. })
  52. "
  53. @refresh="
  54. $emit('handle-fixed-table-save', {
  55. row: currentTemplateRow,
  56. tableData: {},
  57. })
  58. "
  59. />
  60. <!-- 动态表填报弹窗(编辑模式) -->
  61. <dynamic-table-dialog
  62. :key="dynamicDialogKey"
  63. :visible.sync="dynamicTableDialogVisible"
  64. :survey-data="currentTemplateRow || {}"
  65. :table-data="dynamicTableData"
  66. :table-items="tableItems"
  67. :is-view-mode="isViewMode"
  68. :request-type="2"
  69. :audited-unit-id="auditedUnitId"
  70. :upload-id="
  71. (currentTemplateRow &&
  72. (currentTemplateRow.uploadId || currentTemplateRow.id)) ||
  73. ''
  74. "
  75. :survey-template-id="getSurveyTemplateId(currentTemplateRow)"
  76. :catalog-id="(currentTemplateRow && currentTemplateRow.catalogId) || ''"
  77. @save="
  78. $emit('handle-dynamic-table-save', {
  79. row: currentTemplateRow,
  80. tableData: $event,
  81. })
  82. "
  83. @refresh="
  84. $emit('handle-dynamic-table-save', {
  85. row: currentTemplateRow,
  86. tableData: {},
  87. })
  88. "
  89. />
  90. <el-button
  91. type="primary"
  92. :disabled="isViewMode"
  93. @click="$emit('handleAddMaterial')"
  94. >
  95. 补充材料
  96. </el-button>
  97. <el-table
  98. style="margin-top: 20px"
  99. border
  100. :data="dataRequirements"
  101. size="mini"
  102. :show-header="true"
  103. :row-class-name="getRowClassName"
  104. >
  105. <el-table-column prop="seq" label="序号" width="80" align="center">
  106. <template slot-scope="scope">
  107. <span v-if="!scope.row.isCategoryHeader">
  108. {{ scope.row.seq || scope.row.index }}
  109. </span>
  110. </template>
  111. </el-table-column>
  112. <el-table-column
  113. prop="informationName"
  114. label="报送资料"
  115. min-width="280"
  116. align="center"
  117. >
  118. <template slot-scope="scope">
  119. <div v-if="scope.row.isCategoryHeader" class="category-header-cell">
  120. {{ scope.row.categoryName }}
  121. </div>
  122. <span v-else>{{ scope.row.informationName || '-' }}</span>
  123. </template>
  124. </el-table-column>
  125. <el-table-column
  126. prop="formatRequired"
  127. label="资料类型"
  128. width="130"
  129. align="center"
  130. >
  131. <template slot-scope="scope">
  132. <span v-if="!scope.row.isCategoryHeader">
  133. <span
  134. v-if="
  135. scope.row.formatRequired !== null &&
  136. scope.row.formatRequired !== undefined
  137. "
  138. >
  139. {{
  140. getDictName('formatAsk', String(scope.row.formatRequired)) ||
  141. scope.row.formatRequired
  142. }}
  143. </span>
  144. <span v-else>-</span>
  145. </span>
  146. </template>
  147. </el-table-column>
  148. <el-table-column
  149. prop="isRequired"
  150. label="是否必填"
  151. width="110"
  152. align="center"
  153. >
  154. <template slot-scope="scope">
  155. <span v-if="!scope.row.isCategoryHeader">
  156. {{ scope.row.isRequired === '1' ? '是' : '否' }}
  157. </span>
  158. </template>
  159. </el-table-column>
  160. <el-table-column
  161. prop="isUpload"
  162. label="是否上传"
  163. width="110"
  164. align="center"
  165. >
  166. <template slot-scope="scope">
  167. <span v-if="!scope.row.isCategoryHeader">
  168. <span
  169. v-if="scope.row.isUpload === 1 || scope.row.isUpload === '1'"
  170. style="color: #67c23a"
  171. >
  172. 已上传
  173. </span>
  174. <span v-else class="text-danger">未上传</span>
  175. </span>
  176. </template>
  177. </el-table-column>
  178. <el-table-column
  179. prop="isRequired"
  180. label="审核状态"
  181. width="110"
  182. align="center"
  183. >
  184. <template slot-scope="scope">
  185. <span v-if="!scope.row.isCategoryHeader">
  186. <span
  187. v-if="
  188. scope.row.auditedStatus !== null &&
  189. scope.row.auditedStatus !== undefined
  190. "
  191. >
  192. {{
  193. getDictName('clshzt', String(scope.row.auditedStatus)) ||
  194. scope.row.auditedStatus
  195. }}
  196. </span>
  197. <span v-else>-</span>
  198. </span>
  199. </template>
  200. </el-table-column>
  201. <el-table-column prop="operation" label="操作" width="220" align="center">
  202. <template slot-scope="scope">
  203. <template v-if="!scope.row.isCategoryHeader">
  204. <template v-if="String(scope.row.formatRequired) !== '3'">
  205. <el-button
  206. v-if="scope.row.isUpload === 1 || scope.row.isUpload === '1'"
  207. type="text"
  208. size="small"
  209. @click="handleFileView(scope.row)"
  210. >
  211. 查看
  212. </el-button>
  213. <el-button
  214. v-if="scope.row.isUpload === 1 || scope.row.isUpload === '1'"
  215. type="text"
  216. size="small"
  217. @click="$emit('handleFileDownload', scope.row)"
  218. >
  219. 下载
  220. </el-button>
  221. <el-button
  222. v-if="
  223. scope.row.auditedStatus !== '1' &&
  224. (currentNode === 'clcs' || currentNode === 'tjcl')
  225. "
  226. type="text"
  227. size="small"
  228. :disabled="isViewMode"
  229. @click="
  230. $emit(
  231. 'handleFileUpload',
  232. scope.row,
  233. getUploadAccept(scope.row)
  234. )
  235. "
  236. >
  237. 上传
  238. </el-button>
  239. </template>
  240. <template v-else>
  241. <!-- v-if="scope.row.isUpload === 1 || scope.row.isUpload === '1'" -->
  242. <el-button
  243. v-if="scope.row.isUpload === 1 || scope.row.isUpload === '1'"
  244. type="text"
  245. size="small"
  246. @click="handleViewTemplate(scope.row)"
  247. >
  248. 查看
  249. </el-button>
  250. <el-button
  251. v-if="scope.row.formatRequired === '3'"
  252. type="text"
  253. size="small"
  254. @click="handleOnlineSubmission(scope.row)"
  255. >
  256. 在线填报
  257. </el-button>
  258. <el-button
  259. type="text"
  260. size="small"
  261. @click="handleTemplateDownload(scope.row)"
  262. >
  263. 模版下载
  264. </el-button>
  265. <el-button
  266. v-if="
  267. scope.row.auditedStatus !== '1' &&
  268. (currentNode === 'clcs' || currentNode === 'tjcl')
  269. "
  270. type="text"
  271. size="small"
  272. :disabled="isViewMode"
  273. @click="triggerDataUpload(scope.row)"
  274. >
  275. 数据上传
  276. </el-button>
  277. </template>
  278. </template>
  279. </template>
  280. </el-table-column>
  281. </el-table>
  282. <input
  283. ref="dataUploadInput"
  284. type="file"
  285. :accept="dataUploadAccept"
  286. style="display: none"
  287. @change="handleDataFileChange"
  288. />
  289. <!-- 单记录弹窗(查看模式) -->
  290. <survey-form-dialog
  291. :visible.sync="singleDialogVisible"
  292. :survey-data="{}"
  293. :is-view-mode="true"
  294. :request-type="2"
  295. :audited-unit-id="auditedUnitId"
  296. :upload-id="(currentTemplateRow && currentTemplateRow.uploadId) || ''"
  297. :survey-template-id="getSurveyTemplateId(currentTemplateRow)"
  298. :catalog-id="(currentTemplateRow && currentTemplateRow.catalogId) || ''"
  299. />
  300. <!-- 固定表弹窗(查看模式) -->
  301. <fixed-table-dialog
  302. :visible.sync="fixedDialogVisible"
  303. :is-view-mode="true"
  304. :request-type="2"
  305. :audited-unit-id="auditedUnitId"
  306. :upload-id="(currentTemplateRow && currentTemplateRow.uploadId) || ''"
  307. :survey-template-id="getSurveyTemplateId(currentTemplateRow)"
  308. :catalog-id="(currentTemplateRow && currentTemplateRow.catalogId) || ''"
  309. />
  310. <!-- 动态表弹窗(查看模式) -->
  311. <dynamic-table-dialog
  312. :visible.sync="dynamicDialogVisible"
  313. :is-view-mode="true"
  314. :request-type="2"
  315. :audited-unit-id="auditedUnitId"
  316. :upload-id="(currentTemplateRow && currentTemplateRow.uploadId) || ''"
  317. :survey-template-id="getSurveyTemplateId(currentTemplateRow)"
  318. :catalog-id="(currentTemplateRow && currentTemplateRow.catalogId) || ''"
  319. />
  320. </div>
  321. </template>
  322. <script>
  323. import {
  324. downloadPresetTemplate,
  325. uploadPresetTemplate,
  326. } from '@/api/auditTaskProcessing'
  327. import {
  328. getSingleRecordSurveyList,
  329. getSurveyDetail,
  330. getDynamicTableData,
  331. downloadTemplate,
  332. importData,
  333. } from '@/api/audit/survey'
  334. import { getListBySurveyFdTemplateIdAndVersion } from '@/api/costSurveyTemplateHeaders'
  335. import SurveyFormDialog from '@/views/EntDeclaration/auditTaskManagement/components/SurveyFormDialog.vue'
  336. import FixedTableDialog from '@/views/EntDeclaration/auditTaskManagement/components/FixedTableDialog.vue'
  337. import DynamicTableDialog from '@/views/EntDeclaration/auditTaskManagement/components/DynamicTableDialog.vue'
  338. export default {
  339. name: 'DataRequirementsTab',
  340. components: {
  341. SurveyFormDialog,
  342. FixedTableDialog,
  343. DynamicTableDialog,
  344. },
  345. props: {
  346. dataRequirements: {
  347. type: Array,
  348. default: () => [],
  349. },
  350. isViewMode: {
  351. type: Boolean,
  352. default: false,
  353. },
  354. dictData: {
  355. type: Object,
  356. default: () => ({}),
  357. },
  358. currentNode: {
  359. type: String,
  360. default: '',
  361. },
  362. auditedUnitId: {
  363. type: [String, Number],
  364. default: '',
  365. },
  366. taskId: {
  367. type: [String, Number],
  368. default: '',
  369. },
  370. },
  371. data() {
  372. return {
  373. pendingUploadRow: null,
  374. dataUploadAccept: '.xls,.xlsx',
  375. // 模板查看相关
  376. currentTemplateRow: null,
  377. singleDialogVisible: false,
  378. fixedDialogVisible: false,
  379. dynamicDialogVisible: false,
  380. // 在线填报(编辑)相关
  381. surveyFormDialogVisible: false,
  382. fixedTableDialogVisible: false,
  383. dynamicTableDialogVisible: false,
  384. formFields: [],
  385. surveyDetailData: {},
  386. tableItems: [],
  387. auditPeriods: [],
  388. dynamicTableData: [],
  389. dynamicDialogKey: 0,
  390. fixedHeaders: null,
  391. }
  392. },
  393. computed: {},
  394. mounted() {
  395. // 直接使用this.currentNode访问props值,无需在data中重复定义
  396. },
  397. methods: {
  398. // 在线填报入口:与 CostSurveyTab 一致
  399. async handleOnlineSubmission(row) {
  400. if (!row) return
  401. this.currentTemplateRow = row
  402. this.surveyDetailData = {}
  403. const t = String(row.templateType || row.templatetype || '').trim()
  404. // 1=单记录,2=固定表,3=动态表
  405. if (t === '1') {
  406. // 只要有 uploadId/id 就尝试回显数据
  407. if (row.uploadId || row.id) {
  408. try {
  409. const params = {
  410. uploadId: row.uploadId || row.id,
  411. auditedUnitId: this.auditedUnitId,
  412. type: 2,
  413. }
  414. const res = await getSurveyDetail(params)
  415. if (res && res.code === 200 && res.value) {
  416. console.log(res, 'getUploadData')
  417. const detailData = {}
  418. if (Array.isArray(res.value)) {
  419. res.value.forEach((item) => {
  420. if (item.rowid && item.rvalue !== undefined) {
  421. detailData[item.rowid] = item.rvalue
  422. }
  423. })
  424. } else if (res.value && typeof res.value === 'object') {
  425. Object.assign(detailData, res.value)
  426. }
  427. this.surveyDetailData = detailData
  428. }
  429. } catch (err) {
  430. console.error('获取单记录详情失败', err)
  431. }
  432. }
  433. await this.initFormFields()
  434. } else if (t === '2') {
  435. await this.initFixedTableData()
  436. } else if (t === '3') {
  437. this.resetDynamicDialogState()
  438. await this.initDynamicTableData()
  439. }
  440. },
  441. // 预览:与 UploadComponent.vue 的 handlePreview 一致
  442. handleFileView(row) {
  443. console.log(row, '这一行数据')
  444. try {
  445. const filePath =
  446. row?.filePath ||
  447. row?.filepath ||
  448. row?.fileUrl ||
  449. row?.url ||
  450. row?.path ||
  451. ''
  452. if (!filePath) {
  453. this.$message &&
  454. this.$message.warning &&
  455. this.$message.warning('未找到可预览的文件路径')
  456. return
  457. }
  458. const encodedUrl = encodeURIComponent(
  459. Base64.encode((window.context && window.context.form) + filePath)
  460. )
  461. window.open(`${host}:8012/onlinePreview?url=${encodedUrl}`)
  462. // 兼容保留:通知父组件
  463. this.$emit('handleFileView', row)
  464. } catch (e) {
  465. console.error('文件预览失败: ', e)
  466. this.$message &&
  467. this.$message.error &&
  468. this.$message.error('文件预览失败')
  469. }
  470. },
  471. // 模版查看:与 submitData.vue 的 handleViewTemplate 保持一致
  472. handleViewTemplate(row) {
  473. this.currentTemplateRow = row || null
  474. const t = String(
  475. (row && (row.templateType || row.templatetype)) || ''
  476. ).trim()
  477. if (t === '1') {
  478. this.singleDialogVisible = true
  479. } else if (t === '2') {
  480. this.fixedDialogVisible = true
  481. } else if (t === '3') {
  482. this.dynamicDialogVisible = true
  483. } else {
  484. this.$message &&
  485. this.$message.warning &&
  486. this.$message.warning('未知的模板类型,无法打开预置模板')
  487. }
  488. },
  489. // 根据资料类型返回上传accept白名单
  490. getUploadAccept(row) {
  491. const fmt = row && row.formatRequired
  492. const fmtStr = fmt != null ? String(fmt).toLowerCase() : ''
  493. if (
  494. fmtStr === '1' ||
  495. fmtStr.includes('doc') ||
  496. fmtStr.includes('word') ||
  497. fmtStr.includes('pdf') ||
  498. fmtStr.includes('文档')
  499. ) {
  500. return '.pdf,.doc,.docx'
  501. }
  502. if (
  503. fmtStr === '2' ||
  504. fmtStr.includes('excel') ||
  505. fmtStr.includes('xls') ||
  506. fmtStr.includes('xlsx') ||
  507. fmtStr.includes('表格')
  508. ) {
  509. return '.xls,.xlsx'
  510. }
  511. return '.pdf,.doc,.docx,.xls,.xlsx'
  512. },
  513. getDataUploadAccept(row) {
  514. const fmt = row && row.formatRequired
  515. const fmtStr = fmt != null ? String(fmt).toLowerCase() : ''
  516. if (
  517. fmtStr === '3' ||
  518. fmtStr.includes('模版') ||
  519. fmtStr.includes('模板')
  520. ) {
  521. return '.xls,.xlsx'
  522. }
  523. return '.xls,.xlsx'
  524. },
  525. getRowClassName(data) {
  526. if (data.row.isCategoryHeader) {
  527. return 'category-header-row'
  528. }
  529. return ''
  530. },
  531. getDictName(dictType, dictKey) {
  532. const list = (this.dictData && this.dictData[dictType]) || []
  533. if (!Array.isArray(list) || dictKey === undefined || dictKey === null) {
  534. return ''
  535. }
  536. const item = list.find(
  537. (it) =>
  538. String(it.key) === String(dictKey) ||
  539. String(it.value) === String(dictKey)
  540. )
  541. return item ? item.name : ''
  542. },
  543. getSurveyTemplateId(row) {
  544. return (
  545. row?.surveyTemplateId ||
  546. row?.templateId ||
  547. row?.surveyTemplateID ||
  548. row?.templateID ||
  549. ''
  550. )
  551. },
  552. getMaterialId(row) {
  553. return row?.materialId || row?.id || row?.materialID || ''
  554. },
  555. resetDataUploadState() {
  556. const input = this.$refs.dataUploadInput
  557. if (input) {
  558. input.value = ''
  559. }
  560. this.pendingUploadRow = null
  561. },
  562. triggerDataUpload(row) {
  563. console.log(row)
  564. if (!row || this.isViewMode) {
  565. return
  566. }
  567. const surveyTemplateId = this.getSurveyTemplateId(row)
  568. if (!surveyTemplateId) {
  569. this.$message.warning('缺少模板信息,无法上传数据')
  570. return
  571. }
  572. this.pendingUploadRow = row
  573. this.dataUploadAccept = this.getDataUploadAccept(row)
  574. this.$nextTick(() => {
  575. const input = this.$refs.dataUploadInput
  576. if (input) {
  577. input.value = ''
  578. input.click()
  579. }
  580. })
  581. },
  582. async handleDataFileChange(event) {
  583. const files = event?.target?.files
  584. if (!this.pendingUploadRow || !files || !files.length) {
  585. this.resetDataUploadState()
  586. return
  587. }
  588. const file = files[0]
  589. if (!file) {
  590. this.$message.warning('请选择需要上传的文件')
  591. this.resetDataUploadState()
  592. return
  593. }
  594. const surveyTemplateId = this.getSurveyTemplateId(this.pendingUploadRow)
  595. const materialId = this.getMaterialId(this.pendingUploadRow)
  596. if (!surveyTemplateId) {
  597. this.$message.warning('缺少模板信息,无法上传数据')
  598. this.resetDataUploadState()
  599. return
  600. }
  601. if (!materialId) {
  602. this.$message.warning('缺少资料标识,无法上传数据')
  603. this.resetDataUploadState()
  604. return
  605. }
  606. const formData = new FormData()
  607. formData.append('file', file)
  608. formData.append('surveyTemplateId', surveyTemplateId)
  609. formData.append('materialId', materialId)
  610. // const auditedUnitId =this.pendingUploadRow.auditedUnitId
  611. const taskId = this.pendingUploadRow.taskId
  612. // formData.append('auditedUnitId', auditedUnitId)
  613. formData.append('taskId', taskId)
  614. formData.append('type', '2')
  615. const loading = this.$loading({
  616. lock: true,
  617. text: '数据上传中...',
  618. spinner: 'el-icon-loading',
  619. background: 'rgba(0, 0, 0, 0.7)',
  620. })
  621. try {
  622. const response = await uploadPresetTemplate(formData)
  623. loading.close()
  624. const success =
  625. response &&
  626. (response.code === 200 ||
  627. response.success === true ||
  628. response.status === 200)
  629. if (success) {
  630. this.$message.success('数据上传成功')
  631. this.$emit('data-upload-success', {
  632. row: this.pendingUploadRow,
  633. response,
  634. })
  635. } else {
  636. const message =
  637. response?.message ||
  638. response?.msg ||
  639. response?.data?.message ||
  640. '数据上传失败,请稍后重试'
  641. this.$message.error(message)
  642. }
  643. } catch (error) {
  644. loading.close()
  645. console.error('数据上传失败:', error)
  646. // this.$message.error(error.message || '数据上传失败,请稍后重试')
  647. } finally {
  648. this.resetDataUploadState()
  649. }
  650. },
  651. // 处理模板下载
  652. async handleTemplateDownload(row) {
  653. console.log(row)
  654. try {
  655. // 显示加载提示
  656. const loading = this.$loading({
  657. lock: true,
  658. text: '模板下载中...',
  659. spinner: 'el-icon-loading',
  660. background: 'rgba(0, 0, 0, 0.7)',
  661. })
  662. // 构建请求参数,根据实际接口需求调整
  663. const params = { type: 2 }
  664. const surveyTemplateId = this.getSurveyTemplateId(row)
  665. if (surveyTemplateId) {
  666. params.surveyTemplateId = surveyTemplateId
  667. }
  668. // if (row.materialId) {
  669. // params.materialId = row.materialId
  670. // }
  671. // 如果接口需要其他参数,可以继续添加
  672. // 调用下载接口
  673. const response = await downloadPresetTemplate(params)
  674. // 关闭加载提示
  675. loading.close()
  676. // 处理响应数据
  677. // response 可能是 { data, headers } 格式,也可能是直接的 Blob
  678. let blob = response.data || response
  679. let fileName = ''
  680. const headers = response.headers || {}
  681. // 尝试从响应头中获取文件名
  682. if (headers['content-disposition']) {
  683. const contentDisposition = headers['content-disposition']
  684. const fileNameMatch = contentDisposition.match(
  685. /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/
  686. )
  687. if (fileNameMatch && fileNameMatch[1]) {
  688. fileName = decodeURIComponent(
  689. fileNameMatch[1].replace(/['"]/g, '')
  690. )
  691. }
  692. }
  693. // 如果响应头中没有文件名,使用默认文件名
  694. if (!fileName) {
  695. const defaultName = row.informationName || row.name || '模板文件'
  696. fileName = `${defaultName}_模板.xlsx`
  697. }
  698. // 确保文件名有扩展名
  699. if (!/\.[a-zA-Z0-9]+$/.test(fileName)) {
  700. fileName += '.xlsx'
  701. }
  702. // 创建 Blob URL 并触发下载
  703. const url = window.URL.createObjectURL(new Blob([blob]))
  704. const link = document.createElement('a')
  705. link.style.display = 'none'
  706. link.href = url
  707. link.download = fileName
  708. document.body.appendChild(link)
  709. link.click()
  710. document.body.removeChild(link)
  711. // 释放 URL 对象
  712. window.URL.revokeObjectURL(url)
  713. this.$message.success('模板下载成功')
  714. } catch (error) {
  715. console.error('模板下载失败:', error)
  716. this.$message.error(error.message || '模板下载失败,请稍后重试')
  717. }
  718. },
  719. // 以下为在线填报所需方法,与 CostSurveyTab 对齐
  720. async initDynamicTableData() {
  721. try {
  722. const uploadId =
  723. (this.currentTemplateRow &&
  724. (this.currentTemplateRow.uploadId ||
  725. this.currentTemplateRow.id)) ||
  726. ''
  727. const auditedUnitId =
  728. this.auditedUnitId ||
  729. (this.currentTemplateRow &&
  730. this.currentTemplateRow.auditedUnitId) ||
  731. ''
  732. const catalogId =
  733. (this.currentTemplateRow && this.currentTemplateRow.catalogId) || ''
  734. const surveyTemplateId = this.getSurveyTemplateId(
  735. this.currentTemplateRow
  736. )
  737. const params = {
  738. uploadId,
  739. auditedUnitId,
  740. catalogId,
  741. surveyTemplateId,
  742. type: 2,
  743. }
  744. const res = await getDynamicTableData(params)
  745. if (res && res.code === 200) {
  746. const records = res.value?.records || res.value || []
  747. this.dynamicTableData = Array.isArray(records) ? records : []
  748. } else {
  749. this.dynamicTableData =
  750. this.currentTemplateRow?.dynamicTableData || []
  751. }
  752. if (
  753. this.currentTemplateRow &&
  754. this.currentTemplateRow.tableItems &&
  755. this.currentTemplateRow.tableItems.length > 0
  756. ) {
  757. this.tableItems = this.currentTemplateRow.tableItems
  758. } else {
  759. this.tableItems = this.getMockTableItems()
  760. }
  761. this.dynamicTableDialogVisible = true
  762. } catch (error) {
  763. console.error('获取动态表数据失败', error)
  764. this.dynamicTableData =
  765. this.currentTemplateRow?.dynamicTableData || []
  766. this.tableItems =
  767. this.currentTemplateRow?.tableItems || this.getMockTableItems()
  768. this.dynamicTableDialogVisible = true
  769. }
  770. },
  771. async initFormFields() {
  772. if (
  773. this.currentTemplateRow &&
  774. this.getSurveyTemplateId(this.currentTemplateRow)
  775. ) {
  776. try {
  777. const params = {
  778. surveyTemplateId: this.getSurveyTemplateId(
  779. this.currentTemplateRow
  780. ),
  781. type: 2,
  782. }
  783. const res = await getListBySurveyFdTemplateIdAndVersion(params)
  784. if (res && res.code === 200) {
  785. let mapped = []
  786. if (Array.isArray(res.value)) {
  787. mapped = res.value
  788. .map((item, index) =>
  789. this.mapApiFieldToFormField(item, index)
  790. )
  791. .filter(Boolean)
  792. } else if (res.value && typeof res.value === 'object') {
  793. const { fixedFields, fixedFieldids } = res.value
  794. if (fixedFields && fixedFieldids) {
  795. const labels = fixedFields.split(',').map((i) => i.trim())
  796. const ids = fixedFieldids.split(',').map((i) => i.trim())
  797. mapped = labels.map((label, index) => ({
  798. prop: ids[index] || `field_${index}`,
  799. label,
  800. type: 'input',
  801. colSpan: 12,
  802. placeholder: `请输入${label}`,
  803. rules: [],
  804. defaultValue: '',
  805. disabled: false,
  806. clearable: true,
  807. multiple: false,
  808. required: false,
  809. }))
  810. }
  811. }
  812. this.formFields =
  813. mapped.length > 0 ? mapped : this.getMockFormFields()
  814. } else {
  815. this.formFields = this.getMockFormFields()
  816. }
  817. this.surveyFormDialogVisible = true
  818. } catch (err) {
  819. console.error('获取单记录表单字段配置失败', err)
  820. this.formFields = this.getMockFormFields()
  821. this.surveyFormDialogVisible = true
  822. }
  823. } else {
  824. this.formFields = this.getMockFormFields()
  825. this.surveyFormDialogVisible = true
  826. }
  827. },
  828. async initFixedTableData() {
  829. if (
  830. this.currentTemplateRow &&
  831. this.getSurveyTemplateId(this.currentTemplateRow)
  832. ) {
  833. try {
  834. const params = {
  835. surveyTemplateId: this.getSurveyTemplateId(
  836. this.currentTemplateRow
  837. ),
  838. type: 2,
  839. }
  840. const res = await getSingleRecordSurveyList(params)
  841. if (res && res.code === 200 && res.value) {
  842. const { itemlist } = res.value
  843. if (itemlist && Array.isArray(itemlist) && itemlist.length > 0) {
  844. this.tableItems = itemlist.map((item) => ({
  845. id: item.id || item.itemId || '',
  846. rowid: item.rowid || item.id || item.itemId || '',
  847. seq: item.序号,
  848. itemName: item.项目 || '',
  849. unit: item.unit || '',
  850. isCategory: item.isCategory || false,
  851. categorySeq: item.categorySeq || '',
  852. categoryId: item.categoryId || '',
  853. parentid:
  854. item.parentid !== undefined
  855. ? item.parentid
  856. : item.parentId !== undefined
  857. ? item.parentId
  858. : '-1',
  859. validateRules: item.validateRules || {},
  860. linkageRules: item.linkageRules || {},
  861. children: item.children || [],
  862. ...item,
  863. }))
  864. } else {
  865. this.tableItems = this.getMockTableItems()
  866. }
  867. } else {
  868. this.tableItems = this.getMockTableItems()
  869. }
  870. } catch (err) {
  871. console.error('获取固定表配置失败', err)
  872. this.tableItems = this.getMockTableItems()
  873. }
  874. } else if (
  875. this.currentTemplateRow &&
  876. this.currentTemplateRow.tableItems
  877. ) {
  878. this.tableItems = this.currentTemplateRow.tableItems
  879. } else {
  880. this.tableItems = this.getMockTableItems()
  881. }
  882. // 监审期间
  883. const currentYear = new Date().getFullYear()
  884. this.auditPeriods = [
  885. String(currentYear - 2),
  886. String(currentYear - 1),
  887. String(currentYear),
  888. ]
  889. try {
  890. const headerRes = await getListBySurveyFdTemplateIdAndVersion({
  891. surveyTemplateId: this.getSurveyTemplateId(this.currentTemplateRow),
  892. type: 2,
  893. })
  894. if (headerRes && headerRes.code === 200) {
  895. this.fixedHeaders = headerRes.value || null
  896. } else {
  897. this.fixedHeaders = null
  898. }
  899. } catch (e) {
  900. this.fixedHeaders = null
  901. }
  902. this.fixedTableDialogVisible = true
  903. },
  904. extractLengthFromFormat(format) {
  905. if (!format) return undefined
  906. const str = String(format).trim()
  907. if (!str) return undefined
  908. const match = str.match(/\d+/)
  909. if (match && match[0]) {
  910. const len = Number(match[0])
  911. return Number.isNaN(len) ? undefined : len
  912. }
  913. return undefined
  914. },
  915. buildFieldRules(meta) {
  916. const {
  917. type,
  918. label,
  919. required,
  920. totalLength,
  921. decimalLength,
  922. formatLength,
  923. format,
  924. isAuditPeriod,
  925. } = meta || {}
  926. const rules = []
  927. const trigger = type === 'select' ? 'change' : 'blur'
  928. if (required) {
  929. rules.push({
  930. required: true,
  931. message: `${type === 'select' ? '请选择' : '请输入'}${label}`,
  932. trigger,
  933. })
  934. }
  935. const inputMaxLength = formatLength || totalLength
  936. if (type === 'input' && inputMaxLength) {
  937. rules.push({
  938. validator: (_, value, callback) => {
  939. if (value === undefined || value === null || value === '')
  940. return callback()
  941. const str = String(value)
  942. if (str.length > inputMaxLength)
  943. callback(
  944. new Error(`${label}长度不能超过${inputMaxLength}个字符`)
  945. )
  946. else callback()
  947. },
  948. trigger: 'blur',
  949. })
  950. }
  951. const numberTotal = totalLength || formatLength
  952. if (type === 'number') {
  953. rules.push({
  954. validator: (_, value, callback) => {
  955. if (value === undefined || value === null || value === '')
  956. return callback()
  957. if (Number.isNaN(Number(value)))
  958. return callback(new Error(`${label}必须为数字`))
  959. const pure = String(value).replace('-', '')
  960. if (numberTotal && pure.replace('.', '').length > numberTotal)
  961. return callback(
  962. new Error(`${label}总位数不能超过${numberTotal}`)
  963. )
  964. if (decimalLength !== undefined && decimalLength !== null) {
  965. const decimals = pure.split('.')[1] || ''
  966. if (decimals.length > decimalLength)
  967. return callback(
  968. new Error(`${label}小数位不能超过${decimalLength}位`)
  969. )
  970. }
  971. callback()
  972. },
  973. trigger: 'blur',
  974. })
  975. }
  976. if (type === 'datetime' || type === 'date') {
  977. if (format) {
  978. rules.push({
  979. validator: (_, value, callback) => callback(),
  980. trigger: 'change',
  981. })
  982. }
  983. }
  984. if (type === 'year' || isAuditPeriod) {
  985. rules.push({
  986. validator: (_, value, callback) => {
  987. if (value === undefined || value === null || value === '')
  988. return callback()
  989. const pattern = /^\d{4}$/
  990. if (!pattern.test(String(value)))
  991. callback(new Error(`${label}必须是四位年份`))
  992. else callback()
  993. },
  994. trigger: 'change',
  995. })
  996. }
  997. return rules
  998. },
  999. mapApiFieldToFormField(item, index = 0) {
  1000. if (!item) return null
  1001. const getVal = (keys, fallback) => {
  1002. for (const key of keys) {
  1003. if (
  1004. key &&
  1005. item[key] !== undefined &&
  1006. item[key] !== null &&
  1007. item[key] !== ''
  1008. )
  1009. return item[key]
  1010. }
  1011. return fallback
  1012. }
  1013. const toBool = (value) => {
  1014. if (value === undefined || value === null) return false
  1015. if (typeof value === 'boolean') return value
  1016. if (typeof value === 'number') return value === 1
  1017. const str = String(value).trim().toLowerCase()
  1018. return ['1', 'true', 'y', 'yes', '是'].includes(str)
  1019. }
  1020. const toNumber = (value) => {
  1021. if (value === undefined || value === null || value === '')
  1022. return undefined
  1023. const num = Number(value)
  1024. return Number.isNaN(num) ? undefined : num
  1025. }
  1026. const prop =
  1027. getVal(
  1028. [
  1029. 'fieldName',
  1030. 'field_name',
  1031. 'columnName',
  1032. 'column_name',
  1033. 'fieldCode',
  1034. ],
  1035. undefined
  1036. ) || `field_${index}`
  1037. const label =
  1038. getVal(
  1039. [
  1040. 'columnComment',
  1041. 'column_comment',
  1042. 'fieldCname',
  1043. 'field_cname',
  1044. 'fieldLabel',
  1045. 'field_label',
  1046. ],
  1047. prop
  1048. ) || prop
  1049. const columnType =
  1050. (getVal(
  1051. ['columnType', 'column_type', 'fieldType', 'field_type'],
  1052. ''
  1053. ) || '') + ''
  1054. const columnTypeLower = columnType.toLowerCase()
  1055. const totalLength = toNumber(
  1056. getVal(
  1057. ['fieldTypeLen', 'field_typelen', 'length', 'fieldLength'],
  1058. undefined
  1059. )
  1060. )
  1061. const decimalLength = toNumber(
  1062. getVal(
  1063. ['fieldTypeNointLen', 'field_typenointlen', 'scale'],
  1064. undefined
  1065. )
  1066. )
  1067. const isAuditPeriod = toBool(
  1068. getVal(['isAuditPeriod', 'is_audit_period'], false)
  1069. )
  1070. const dictCode =
  1071. getVal(
  1072. [
  1073. 'dictCode',
  1074. 'dict_code',
  1075. 'dictId',
  1076. 'dictid',
  1077. 'dictType',
  1078. 'dict_type',
  1079. ],
  1080. ''
  1081. ) || ''
  1082. const optionsRaw = getVal(['options'], [])
  1083. let options = []
  1084. if (Array.isArray(optionsRaw)) options = optionsRaw
  1085. else if (typeof optionsRaw === 'string' && optionsRaw.trim() !== '') {
  1086. options = optionsRaw
  1087. .split(',')
  1088. .map((value) => ({ label: value.trim(), value: value.trim() }))
  1089. }
  1090. let type = getVal(['componentType', 'type'], '')
  1091. if (!type) {
  1092. if (dictCode || options.length > 0) type = 'select'
  1093. else if (
  1094. columnTypeLower.includes('datetime') ||
  1095. columnTypeLower.includes('timestamp') ||
  1096. columnTypeLower.includes('date time')
  1097. )
  1098. type = 'datetime'
  1099. else if (columnTypeLower.includes('date')) type = 'date'
  1100. else if (columnTypeLower.includes('year')) type = 'year'
  1101. else if (
  1102. columnTypeLower.includes('int') ||
  1103. columnTypeLower.includes('number') ||
  1104. columnTypeLower.includes('decimal') ||
  1105. columnTypeLower.includes('float') ||
  1106. columnTypeLower.includes('double')
  1107. )
  1108. type = 'number'
  1109. else type = 'input'
  1110. }
  1111. const required = toBool(
  1112. getVal(['isRequired', 'is_required', 'required'], false)
  1113. )
  1114. const multiple = toBool(
  1115. getVal(['isMultiple', 'is_multiple', 'multiple'], false)
  1116. )
  1117. const colSpan =
  1118. toNumber(
  1119. getVal(['colSpan', 'colspan', 'columnSpan', 'column_span'], 12)
  1120. ) || 12
  1121. const placeholder =
  1122. getVal(
  1123. ['placeholder', 'columnComment', 'column_comment'],
  1124. undefined
  1125. ) || (type === 'select' ? `请选择${label}` : `请输入${label}`)
  1126. const defaultValue = getVal(
  1127. ['defaultValue', 'default_value', 'defaultVal', 'default_val'],
  1128. undefined
  1129. )
  1130. const precision = toNumber(
  1131. getVal(
  1132. ['fieldTypeNointLen', 'field_typenointlen', 'precision'],
  1133. undefined
  1134. )
  1135. )
  1136. const min = toNumber(getVal(['min'], undefined))
  1137. const max = toNumber(getVal(['max'], undefined))
  1138. const format = getVal(['format'], undefined)
  1139. const valueFormat =
  1140. getVal(['valueFormat', 'value_format'], undefined) ||
  1141. (type === 'datetime'
  1142. ? 'yyyy-MM-dd HH:mm:ss'
  1143. : type === 'date'
  1144. ? 'yyyy-MM-dd'
  1145. : type === 'year'
  1146. ? 'yyyy'
  1147. : undefined)
  1148. const formatLength = this.extractLengthFromFormat(format)
  1149. const rules = this.buildFieldRules({
  1150. type,
  1151. label,
  1152. required,
  1153. totalLength,
  1154. decimalLength,
  1155. formatLength,
  1156. format,
  1157. isAuditPeriod,
  1158. })
  1159. return {
  1160. prop,
  1161. label,
  1162. type,
  1163. colSpan,
  1164. placeholder,
  1165. dictCode,
  1166. dictType: dictCode,
  1167. options,
  1168. required,
  1169. defaultValue,
  1170. multiple,
  1171. precision,
  1172. min,
  1173. max,
  1174. format,
  1175. valueFormat,
  1176. totalLength,
  1177. decimalLength,
  1178. formatLength,
  1179. rules,
  1180. }
  1181. },
  1182. getMockFormFields() {
  1183. // return [
  1184. // {
  1185. // prop: 'institutionName',
  1186. // label: '机构名称',
  1187. // type: 'input',
  1188. // colSpan: 12,
  1189. // defaultValue: '幼儿园基本情况',
  1190. // placeholder: '请输入机构名称',
  1191. // required: true,
  1192. // },
  1193. // {
  1194. // prop: 'institutionNature',
  1195. // label: '机构性质',
  1196. // type: 'select',
  1197. // colSpan: 12,
  1198. // dictType: 'institutionNature',
  1199. // defaultValue: '公办',
  1200. // placeholder: '请选择机构性质',
  1201. // required: true,
  1202. // clearable: true,
  1203. // },
  1204. // {
  1205. // prop: 'institutionLevel',
  1206. // label: '机构评定等级',
  1207. // type: 'select',
  1208. // colSpan: 12,
  1209. // dictType: 'institutionLevel',
  1210. // defaultValue: '省一级',
  1211. // placeholder: '请选择机构评定等级',
  1212. // required: true,
  1213. // clearable: true,
  1214. // },
  1215. // {
  1216. // prop: 'educationMode',
  1217. // label: '机构办学方式',
  1218. // type: 'select',
  1219. // colSpan: 12,
  1220. // dictType: 'educationMode',
  1221. // defaultValue: '全日制',
  1222. // placeholder: '请选择机构办学方式',
  1223. // required: true,
  1224. // clearable: true,
  1225. // },
  1226. // {
  1227. // prop: 'institutionAddress',
  1228. // label: '机构地址',
  1229. // type: 'input',
  1230. // colSpan: 12,
  1231. // placeholder: '请输入机构地址',
  1232. // required: true,
  1233. // },
  1234. // {
  1235. // prop: 'formFiller',
  1236. // label: '机构填表人',
  1237. // type: 'input',
  1238. // colSpan: 12,
  1239. // placeholder: '请输入机构填表人',
  1240. // required: true,
  1241. // },
  1242. // {
  1243. // prop: 'financialManager',
  1244. // label: '机构财务负责人',
  1245. // type: 'input',
  1246. // colSpan: 12,
  1247. // placeholder: '请输入机构财务负责人',
  1248. // required: true,
  1249. // },
  1250. // {
  1251. // prop: 'contactPhone',
  1252. // label: '机构联系电话',
  1253. // type: 'input',
  1254. // colSpan: 12,
  1255. // placeholder: '请输入机构联系电话',
  1256. // required: true,
  1257. // rules: [
  1258. // {
  1259. // required: true,
  1260. // message: '请输入机构联系电话',
  1261. // trigger: 'blur',
  1262. // },
  1263. // {
  1264. // pattern: /^1[3-9]\d{9}$/,
  1265. // message: '请输入正确的手机号码',
  1266. // trigger: 'blur',
  1267. // },
  1268. // ],
  1269. // },
  1270. // ]
  1271. },
  1272. getMockTableItems() {
  1273. // return [
  1274. // {
  1275. // id: '1',
  1276. // itemName: '班级数',
  1277. // unit: '个',
  1278. // isCategory: false,
  1279. // seq: 1,
  1280. // validateRules: { required: true, type: 'number', min: 0 },
  1281. // },
  1282. // {
  1283. // id: '2',
  1284. // itemName: '幼儿学生人数',
  1285. // unit: '人',
  1286. // isCategory: false,
  1287. // seq: 2,
  1288. // validateRules: { required: true, type: 'number', min: 0 },
  1289. // },
  1290. // ]
  1291. },
  1292. resetDynamicDialogState() {
  1293. this.dynamicTableDialogVisible = false
  1294. this.dynamicTableData = []
  1295. this.tableItems = []
  1296. this.dynamicDialogKey = Date.now()
  1297. },
  1298. },
  1299. }
  1300. </script>
  1301. <style scoped>
  1302. .text-danger {
  1303. color: #d9001b;
  1304. }
  1305. /* 类别头行样式 */
  1306. .category-header-row {
  1307. background-color: #f5f7fa !important;
  1308. }
  1309. .category-header-row td {
  1310. background-color: #f5f7fa !important;
  1311. padding: 12px 16px !important;
  1312. }
  1313. .category-header-cell {
  1314. font-weight: 700;
  1315. color: #303133;
  1316. padding-left: 16px;
  1317. }
  1318. </style>