taskFillIn.vue 78 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395
  1. <template>
  2. <el-drawer
  3. :visible.sync="drawerVisible"
  4. :size="drawerSize"
  5. :with-header="true"
  6. :modal="true"
  7. :z-index="9000"
  8. :close-on-click-modal="false"
  9. :show-close="true"
  10. @close="handleDrawerClose"
  11. @update:visible="(val) => $emit('update:visible', val)"
  12. >
  13. <div slot="title" class="drawer-title">
  14. <span>{{ pageTitle || '填报任务' }}</span>
  15. </div>
  16. <div class="audit-task-manage-container">
  17. <!-- 操作按钮 -->
  18. <div class="action-buttons" style="margin-bottom: 20px">
  19. <el-button
  20. type="primary"
  21. :loading="loading.submit"
  22. :disabled="isViewMode"
  23. @click="handleSubmit"
  24. >
  25. 提交
  26. </el-button>
  27. <!-- <el-button type="primary" :loading="loading.save" @click="handleSave" :disabled="isViewMode">
  28. 保存
  29. </el-button> -->
  30. <!-- <el-button type="primary" class="ml10" @click="handleBack">
  31. 返回
  32. </el-button> -->
  33. </div>
  34. <!-- 标签页容器 -->
  35. <div
  36. v-loading="tabLoading[activeTab]"
  37. class="tabs-container"
  38. element-loading-text="数据加载中..."
  39. >
  40. <el-tabs
  41. v-model="activeTab"
  42. type="border-card"
  43. @tab-click="handleTabClick"
  44. >
  45. <!-- 立项信息 -->
  46. <el-tab-pane label="立项信息" name="projectInfo">
  47. <project-info-tab
  48. :form-data="formData.basicInfo"
  49. :is-view-mode="isViewMode"
  50. :unit-list="unitList"
  51. :org-list="OrgList"
  52. :user-list="userList"
  53. :dict-data="dictData"
  54. @change="handleProjectInfoChange"
  55. @handleCatalogChange="handleCatalogChange"
  56. @handleRegionChange="handleRegionChange"
  57. @addCostYear="addCostYear"
  58. @deleteCostYear="deleteCostYear"
  59. />
  60. </el-tab-pane>
  61. <!-- 监审文书 -->
  62. <el-tab-pane label="监审文书" name="auditDocument">
  63. <audit-document-tab
  64. :form-data="formData.auditDocument"
  65. :is-view-mode="isViewMode"
  66. :pagination="auditDocumentPagination"
  67. @handleClick="handleClick"
  68. @handleDownload="handleDownload"
  69. @handleUpload="handleUpload"
  70. @refresh="getAuditDocumentList"
  71. @paginationChange="handleAuditDocumentPaginationChange"
  72. />
  73. </el-tab-pane>
  74. <!-- 报送资料 -->
  75. <el-tab-pane
  76. v-if="currentNode === 'clcs' || currentNode === 'tjcl'"
  77. label="报送资料"
  78. name="dataRequirements"
  79. >
  80. <data-requirements-tab
  81. :data-requirements="groupedDataRequirements"
  82. :is-view-mode="isViewMode"
  83. :dict-data="dictData"
  84. :current-node="currentNode"
  85. :audited-unit-id="auditedUnitId"
  86. :task-id="taskId"
  87. @handleAddMaterial="handleAddMaterial"
  88. @handleFileView="handleFileView"
  89. @handleFileDownload="handleFileDownload"
  90. @handleFileUpload="handleFileUpload"
  91. @handleTemplateDownload="handleTemplateDownload"
  92. @data-upload-success="getTaskRequirementPage"
  93. @handleDataUpload="handleDataUpload"
  94. @handle-survey-form-save="getTaskRequirementPage"
  95. @handle-fixed-table-save="getTaskRequirementPage"
  96. @handle-dynamic-table-save="getTaskRequirementPage"
  97. />
  98. </el-tab-pane>
  99. <!-- 成本调查表 -->
  100. <el-tab-pane
  101. v-if="currentNode === 'clcs' || currentNode === 'tjcl'"
  102. label="成本调查表"
  103. name="costSurvey"
  104. >
  105. <cost-survey-tab
  106. :paginated-data="formData.paginatedData"
  107. :pagination="costSurveyPagination"
  108. :is-view-mode="isViewMode"
  109. :audited-unit-id="auditedUnitId"
  110. :catalog-id="taskInfo.catalogId"
  111. :task-id="taskInfo.taskId"
  112. :project-id="taskInfo.projectId"
  113. :audit-period="getAuditPeriodArray()"
  114. @handle-modify="handleModify"
  115. @handle-data-download="handleDataDownload"
  116. @handle-data-upload="handleDataUpload"
  117. @handle-preview="handlePreview"
  118. @handle-page-change="handleCostSurveyPageChange"
  119. @handle-size-change="handleCostSurveySizeChange"
  120. @handle-survey-form-save="handleSurveyFormSave"
  121. />
  122. </el-tab-pane>
  123. <!-- 监审意见 -->
  124. <el-tab-pane
  125. v-if="currentNode === 'yjfk'"
  126. label="监审意见"
  127. name="auditOpinion"
  128. >
  129. <audit-opinion-tab
  130. :id="taskId"
  131. ref="auditOpinionTabRef"
  132. :form-data="formData.auditOpinion"
  133. :is-view-mode="isViewMode"
  134. @change="handleAuditOpinionChange"
  135. @data-loaded="handleAuditOpinionDataLoaded"
  136. />
  137. </el-tab-pane>
  138. <!-- 消息通知 -->
  139. <el-tab-pane label="消息通知" name="messageNotice">
  140. <message-notice-tab
  141. :form-data="formData.messageNotice"
  142. :pagination="messageNoticePagination"
  143. @handleCurrentChange="handleMessageNoticePageChange"
  144. @handleSizeChange="handleMessageNoticeSizeChange"
  145. />
  146. </el-tab-pane>
  147. </el-tabs>
  148. </div>
  149. <!-- 补充材料弹窗 -->
  150. <el-dialog
  151. title="补充材料"
  152. :visible.sync="materialDialogVisible"
  153. width="600px"
  154. :z-index="10000"
  155. :close-on-click-modal="false"
  156. :modal="true"
  157. append-to-body
  158. >
  159. <el-form
  160. ref="materialForm"
  161. :model="materialForm"
  162. :rules="materialRules"
  163. label-width="100px"
  164. >
  165. <el-form-item label="资料类别" prop="informationType">
  166. <el-select
  167. v-model="materialForm.informationType"
  168. placeholder="请选择资料类别"
  169. style="width: 100%"
  170. :disabled="isViewMode"
  171. >
  172. <el-option
  173. v-for="item in dictData['materialType']"
  174. :key="item.key"
  175. :label="item.name"
  176. :value="item.key"
  177. ></el-option>
  178. </el-select>
  179. </el-form-item>
  180. <el-form-item label="资料名称" prop="informationName">
  181. <el-input
  182. v-model="materialForm.informationName"
  183. placeholder="请输入资料名称"
  184. style="width: 100%"
  185. :disabled="isViewMode"
  186. ></el-input>
  187. </el-form-item>
  188. <el-form-item label="资料要求:" prop="informationRequire">
  189. <el-input
  190. v-model="materialForm.informationRequire"
  191. placeholder="请输入资料要求"
  192. :disabled="isViewMode"
  193. ></el-input>
  194. </el-form-item>
  195. <el-form-item label="格式要求:" prop="formatRequired">
  196. <el-select
  197. v-model="materialForm.formatRequired"
  198. placeholder="请选择格式要求"
  199. style="width: 100%"
  200. :disabled="isViewMode"
  201. >
  202. <el-option
  203. v-for="item in dictData['formatAsk']"
  204. :key="item.id"
  205. :label="item.name"
  206. :value="item.key"
  207. ></el-option>
  208. </el-select>
  209. </el-form-item>
  210. <el-form-item label="上传附件:" prop="fileList">
  211. <el-upload
  212. class="upload-demo"
  213. :action="''"
  214. :http-request="handleMaterialUpload"
  215. :on-remove="handleMaterialRemove"
  216. :before-upload="beforeMaterialUpload"
  217. :on-success="handleMaterialUploadSuccess"
  218. :on-error="handleMaterialUploadError"
  219. :on-progress="handleMaterialUploadProgress"
  220. :file-list="materialForm.fileList"
  221. :limit="1"
  222. :on-exceed="handleMaterialExceed"
  223. :accept="uploadAccept"
  224. >
  225. <el-button
  226. v-show="
  227. !isTemplateFormat &&
  228. (!materialForm.fileList || materialForm.fileList.length === 0)
  229. "
  230. size="small"
  231. type="primary"
  232. :disabled="isViewMode"
  233. >
  234. 选择文件
  235. </el-button>
  236. <el-button
  237. v-if="isTemplateFormat"
  238. size="small"
  239. type="primary"
  240. :disabled="isViewMode"
  241. @click.stop="openLegalDialog"
  242. >
  243. 选择模版
  244. </el-button>
  245. <!-- <div slot="tip" class="el-upload__tip">
  246. 最多上传1个文件,支持 pdf, doc, docx, xls, xlsx, csv 格式,单个文件不超过50MB
  247. </div> -->
  248. </el-upload>
  249. </el-form-item>
  250. <!-- <el-button
  251. type="text"
  252. size="mini"
  253. @click="handleMaterialUpload"
  254. >
  255. 上传附件
  256. </el-button> -->
  257. </el-form>
  258. <div slot="footer" class="dialog-footer">
  259. <el-button
  260. type="primary"
  261. :loading="loading.materialSubmit"
  262. :disabled="isViewMode"
  263. @click="handleMaterialSubmit"
  264. >
  265. 保 存
  266. </el-button>
  267. <el-button :disabled="isViewMode" @click="handleMaterialCancel">
  268. 取 消
  269. </el-button>
  270. </div>
  271. </el-dialog>
  272. <!-- 预置模版选择弹窗 -->
  273. <!-- <legal-dialog
  274. v-if="legalDialogMounted"
  275. :visible.sync="legalDialogVisible"
  276. @confirm="handleLegalConfirm"
  277. @close="legalDialogVisible = false"
  278. /> -->
  279. <!-- 使用 CostAuditTable 的通用选择模版弹窗 -->
  280. <CostAuditDialog
  281. v-if="costAuditDialogVisible"
  282. :title="'选择模板'"
  283. :visible="costAuditDialogVisible"
  284. width="50%"
  285. :z-index="20000"
  286. :close-on-click-modal="false"
  287. :confirm-loading="costAuditSubmitting"
  288. @confirm="submitCostAuditTemplate"
  289. @cancel="costAuditDialogVisible = false"
  290. @close="costAuditDialogVisible = false"
  291. >
  292. <cost-audit-table
  293. :table-data="costTemplateData"
  294. :columns="costTemplateColumns"
  295. :show-selection="true"
  296. :show-pagination="true"
  297. :pagination="costTemplatePagination"
  298. @pagination-change="handleCostTemplatePaginationChange"
  299. @selection-change="handleCostTemplateSelectionChange"
  300. >
  301. <template #createTime="{ row }">
  302. <div>{{ row.createTime ? row.createTime.split(' ')[0] : '-' }}</div>
  303. <div>{{ row.createTime ? row.createTime.split(' ')[1] : '-' }}</div>
  304. </template>
  305. </cost-audit-table>
  306. </CostAuditDialog>
  307. <!-- 在线填报弹窗 -->
  308. <OnlineFillDialog
  309. :visible.sync="onlineFillDialogVisible"
  310. :table-type="currentSurveyData ? currentSurveyData.tableType : '单记录'"
  311. :survey-data="currentSurveyData || {}"
  312. />
  313. </div>
  314. </el-drawer>
  315. </template>
  316. <script>
  317. import { Message } from 'element-ui'
  318. import OnlineFillDialog from './OnlineFill.vue'
  319. import {
  320. getProjectInformationInfo,
  321. getTaskRequirementList,
  322. addOrUpdateTaskRequirement,
  323. submitTaskRequirement,
  324. sendMessage,
  325. } from '@/api/auditTaskProcessing'
  326. // import UploadComponent from '@/components/costAudit/UploadComponent.vue'
  327. import { getAllUnitList } from '@/api/auditEntityManage'
  328. import { getDefaultDem, getOrgListByDemId } from '@/api/annualReviewPlan'
  329. import { getAllUserList } from '@/api/uc'
  330. import { dictMixin } from '@/mixins/useDict'
  331. import { uploadFile } from '@/api/file'
  332. import { submitPreliminaryOpinion } from '@/api/audit/preliminaryOpinion'
  333. import { getSurveyList } from '@/api/audit/survey'
  334. import ProjectInfoTab from './components/ProjectInfoTab.vue'
  335. import AuditDocumentTab from './components/AuditDocumentTab.vue'
  336. import DataRequirementsTab from './components/DataRequirementsTab.vue'
  337. import CostSurveyTab from './components/CostSurveyTab.vue'
  338. import AuditOpinionTab from './components/AuditOpinionTab.vue'
  339. import MessageNoticeTab from './components/MessageNoticeTab.vue'
  340. import LegalDialog from '@/views/costAudit/baseInfo/catalogManage/legalDialog.vue'
  341. import CostAuditDialog from '@/components/costAudit/CostAuditDialog'
  342. import CostAuditTable from '@/components/costAudit/CostAuditTable.vue'
  343. import { allcurrentCostSurveyList } from '@/api/financeSheetManage'
  344. import { getCostProjectDocumentPageList } from '@/api/taskCustomizedRelease.js'
  345. export default {
  346. name: 'TaskFillIn',
  347. components: {
  348. OnlineFillDialog,
  349. // UploadComponent,
  350. ProjectInfoTab,
  351. AuditDocumentTab,
  352. DataRequirementsTab,
  353. CostSurveyTab,
  354. AuditOpinionTab,
  355. MessageNoticeTab,
  356. // LegalDialog,
  357. CostAuditDialog,
  358. CostAuditTable,
  359. },
  360. mixins: [dictMixin],
  361. props: {
  362. // 侧边弹窗是否显示
  363. visible: {
  364. type: Boolean,
  365. default: false,
  366. },
  367. // 侧边弹窗尺寸
  368. drawerSize: {
  369. type: String,
  370. default: '85%',
  371. },
  372. // 任务信息(从props传入,替代路由query)
  373. taskInfo: {
  374. type: Object,
  375. default: () => ({}),
  376. },
  377. // 是否为查看模式
  378. viewMode: {
  379. type: Boolean,
  380. default: false,
  381. },
  382. },
  383. data() {
  384. return {
  385. drawerVisible: false, // 侧边弹窗显示状态
  386. isViewMode: false, // 是否为查看模式
  387. localTaskInfo: {}, // 本地任务信息(用于存储)
  388. unitList: [],
  389. OrgList: [],
  390. userList: [],
  391. dictData: {
  392. attributionYear: [], // 归属年度
  393. projectProposal: [], // 立项来源
  394. auditType: [], //监审形式
  395. materialType: [], // 资料类别
  396. materialCategory: [], // 资料类别
  397. formatAsk: [], // 资料类型
  398. clshzt: [], // 是否通过
  399. },
  400. // 立项依据文件列表
  401. accordingFileList: [],
  402. // 其他材料文件列表
  403. otherFileList: [],
  404. // 页面标题
  405. pageTitle: '立项信息',
  406. projectId: '', // 项目ID
  407. taskId: '', // 任务ID
  408. auditedUnitId: '', // 被监审单位ID
  409. currentNode: '', // 当前节点
  410. // 当前激活的标签页
  411. activeTab: 'projectInfo',
  412. // 加载状态管理
  413. loading: {
  414. submit: false,
  415. save: false,
  416. materialSubmit: false,
  417. },
  418. // 标签页加载状态
  419. tabLoading: {
  420. projectInfo: false,
  421. auditDocument: false,
  422. dataRequirements: false,
  423. costSurvey: false,
  424. auditOpinion: false,
  425. messageNotice: false,
  426. },
  427. isDisabled: false,
  428. tabCheck: '', // 状态判断监审意见是否显示
  429. tabVisiable: true, // 标签页是否显示
  430. // 成本调查表分页相关
  431. costSurveyPagination: {
  432. currentPage: 1,
  433. pageSize: 10,
  434. total: 0,
  435. },
  436. // 消息通知分页相关
  437. messageNoticePagination: {
  438. pageNo: 1,
  439. pageSize: 10,
  440. total: 0,
  441. },
  442. auditDocumentPagination: {
  443. page: 1,
  444. pageSize: 10,
  445. total: 0,
  446. },
  447. // 所有表单数据聚合
  448. formData: {
  449. basicInfo: {
  450. projectName: '',
  451. catalogId: '',
  452. areaCode: '',
  453. auditUnitId: [],
  454. auditUnitName: '',
  455. orgId: '',
  456. orgName: '',
  457. projectYear: '',
  458. sourceType: '',
  459. auditType: '',
  460. auditPeriod: '',
  461. auditPeriodArray: [{ value: '' }],
  462. needHearing: 1,
  463. isEmergency: 1,
  464. establishmentReason: '',
  465. accordingFileUrl: '',
  466. otherFileUrl: '',
  467. auditGroup: '',
  468. auditTeamMembers: [],
  469. expertStr: '',
  470. plannedAuditStartDate: '',
  471. plannedAuditEndDate: '',
  472. },
  473. auditDocument: [],
  474. dataRequirements: [],
  475. costSurveyData: [
  476. {
  477. index: 1,
  478. name: '封面',
  479. dataType: '模版定制',
  480. tableType: '单记录',
  481. isRequired: '是',
  482. isUploaded: false,
  483. isDisabled: false,
  484. },
  485. {
  486. index: 2,
  487. name: '企业基本情况调查表',
  488. dataType: '模版定制',
  489. tableType: '固定表',
  490. isRequired: '是',
  491. isUploaded: true,
  492. isDisabled: false,
  493. },
  494. {
  495. index: 3,
  496. name: '企业成本费用调查表',
  497. dataType: '模版定制',
  498. tableType: '固定表',
  499. isRequired: '是',
  500. isUploaded: true,
  501. isDisabled: false,
  502. },
  503. {
  504. index: 4,
  505. name: '企业期间费用调查表',
  506. dataType: '模版定制',
  507. tableType: '动态表',
  508. isRequired: '是',
  509. isUploaded: false,
  510. isDisabled: true, // 灰色不可点击
  511. },
  512. {
  513. index: 5,
  514. name: '企业职工薪酬调查表',
  515. dataType: '模版定制',
  516. tableType: '动态表',
  517. isRequired: '是',
  518. isUploaded: false,
  519. isDisabled: true, // 灰色不可点击
  520. },
  521. {
  522. index: 6,
  523. name: '……',
  524. dataType: '模版定制',
  525. tableType: '动态表',
  526. isRequired: '是',
  527. isUploaded: false,
  528. isDisabled: false,
  529. },
  530. ],
  531. auditOpinion: {
  532. basicFinancialInfo:
  533. '此处为被监审单位基本情况及主要财务状况的内容...',
  534. priceStandard: '此处为监审项目现行执行的价格标准的内容...',
  535. costComposition:
  536. '此处为监审项目的成本构成、数据核增核减情况、依据及理由的内容...',
  537. preliminaryOpinion: '此处为成本审核初步意见的内容...',
  538. feedbackOpinion: '', // 被监审单位可输入反馈意见
  539. feedbackMaterials: [], // 被监审单位上传的反馈文件
  540. },
  541. messageNotice: [], // 消息通知列表
  542. paginatedData: [
  543. {
  544. name: '封面',
  545. dataType: '模版定制',
  546. tableType: '单记录',
  547. isRequired: '是',
  548. isUploaded: false,
  549. isDisabled: false,
  550. },
  551. {
  552. name: '企业基本情况调查表',
  553. dataType: '模版定制',
  554. tableType: '固定表',
  555. isRequired: '是',
  556. isUploaded: true,
  557. isDisabled: false,
  558. },
  559. {
  560. name: '企业成本费用调查表',
  561. dataType: '模版定制',
  562. tableType: '动态表',
  563. isRequired: '是',
  564. isUploaded: true,
  565. isDisabled: false,
  566. },
  567. ],
  568. },
  569. // 监审地区级联选择器
  570. regionOptions: [
  571. {
  572. value: 'zhinan',
  573. label: '指南',
  574. children: [
  575. {
  576. value: 'shejiyuanze',
  577. label: '设计原则',
  578. children: [
  579. {
  580. value: 'yizhi',
  581. label: '一致',
  582. },
  583. {
  584. value: 'fankui',
  585. label: '反馈',
  586. },
  587. {
  588. value: 'xiaolv',
  589. label: '效率',
  590. },
  591. {
  592. value: 'kekong',
  593. label: '可控',
  594. },
  595. ],
  596. },
  597. ],
  598. },
  599. ],
  600. dialogVisible: false,
  601. catalogueData: [
  602. {
  603. label: '一级 1',
  604. children: [
  605. {
  606. label: '二级 1-1',
  607. children: [
  608. {
  609. label: '三级 1-1-1',
  610. },
  611. ],
  612. },
  613. ],
  614. },
  615. {
  616. label: '一级 2',
  617. children: [
  618. {
  619. label: '二级 2-1',
  620. children: [
  621. {
  622. label: '三级 2-1-1',
  623. },
  624. ],
  625. },
  626. {
  627. label: '二级 2-2',
  628. children: [
  629. {
  630. label: '三级 2-2-1',
  631. },
  632. ],
  633. },
  634. ],
  635. },
  636. {
  637. label: '一级 3',
  638. children: [
  639. {
  640. label: '二级 3-1',
  641. children: [
  642. {
  643. label: '三级 3-1-1',
  644. },
  645. ],
  646. },
  647. {
  648. label: '二级 3-2',
  649. children: [
  650. {
  651. label: '三级 3-2-1',
  652. },
  653. ],
  654. },
  655. ],
  656. },
  657. ],
  658. defaultProps: {
  659. children: 'children',
  660. label: 'label',
  661. },
  662. // 补充材料弹窗相关数据
  663. materialDialogVisible: false,
  664. materialForm: {
  665. informationType: '',
  666. informationName: '',
  667. informationRequire: '',
  668. formatRequired: '',
  669. fileList: [],
  670. fileUrl: '',
  671. templateId: '',
  672. },
  673. materialRules: {
  674. informationType: [
  675. { required: true, message: '请选择资料类别', trigger: 'change' },
  676. ],
  677. informationName: [
  678. { required: true, message: '请输入资料名称', trigger: 'blur' },
  679. ],
  680. formatRequired: [
  681. { required: true, message: '请选择格式要求', trigger: 'change' },
  682. ],
  683. informationRequire: [
  684. { required: true, message: '请输入资料要求', trigger: 'blur' },
  685. ],
  686. },
  687. materialCategoryOptions: [
  688. { label: '综合性资料', value: 'comprehensive' },
  689. { label: '财务会计资料', value: 'financial' },
  690. { label: '其他资料', value: 'other' },
  691. ],
  692. // 修复未定义的变量
  693. value: [], // 监审地区级联选择器的值
  694. value3: null, // 年份选择器的值
  695. // 在线填报弹窗相关数据
  696. onlineFillDialogVisible: false,
  697. currentSurveyData: null,
  698. // 选择模版弹窗
  699. legalDialogVisible: false,
  700. legalDialogMounted: false,
  701. // 使用 CostAuditTable 的选择弹窗
  702. costAuditDialogVisible: false,
  703. costAuditSubmitting: false,
  704. costTemplateData: [],
  705. selectedCostTemplates: [],
  706. costTemplatePagination: {
  707. currentPage: 1,
  708. pageSize: 10,
  709. total: 0,
  710. },
  711. costTemplateColumns: [
  712. { prop: 'surveyTemplateName', label: '财务数据表名称' },
  713. {
  714. prop: 'templateType',
  715. label: '模版类型',
  716. width: '100px',
  717. formatter: (row) => {
  718. return row.templateType == '1'
  719. ? '单记录'
  720. : row.templateType == '2'
  721. ? '固定表'
  722. : '动态表'
  723. },
  724. },
  725. { prop: 'status', label: '状态', width: '100px' },
  726. {
  727. prop: 'createTime',
  728. label: '创建时间',
  729. slotName: 'createTime',
  730. width: '100px',
  731. },
  732. ],
  733. }
  734. },
  735. computed: {
  736. // 是否选择了“预置模版”格式(通过名称或key含义判断,兼容后端字典差异)
  737. isTemplateFormat() {
  738. const raw = this.materialForm.formatRequired
  739. const v = (raw == null ? '' : String(raw)).toLowerCase()
  740. // 显式数值/字符串 3 代表预置模版
  741. if (raw === 3 || raw === '3') return true
  742. // 名称兜底匹配
  743. return (
  744. v.includes('template') ||
  745. v.includes('preset') ||
  746. v.includes('tpl') ||
  747. v.includes('模版') ||
  748. v.includes('模板')
  749. )
  750. },
  751. // 是否为文档(pdf/word)
  752. isDocumentFormat() {
  753. const raw = this.materialForm.formatRequired
  754. const v = (raw == null ? '' : String(raw)).toLowerCase()
  755. // 显式数值/字符串 1 代表文档
  756. if (raw === 1 || raw === '1') return true
  757. return (
  758. v.includes('doc') ||
  759. v.includes('word') ||
  760. v.includes('pdf') ||
  761. v.includes('文档') ||
  762. v.includes('word文档')
  763. )
  764. },
  765. // 是否为Excel
  766. isExcelFormat() {
  767. const raw = this.materialForm.formatRequired
  768. const v = (raw == null ? '' : String(raw)).toLowerCase()
  769. // 显式数值/字符串 2 代表excel
  770. if (raw === 2 || raw === '2') return true
  771. return (
  772. v.includes('excel') ||
  773. v.includes('xls') ||
  774. v.includes('xlsx') ||
  775. v.includes('表格')
  776. )
  777. },
  778. // 根据所选格式动态生成 accept
  779. uploadAccept() {
  780. if (this.isTemplateFormat) return ''
  781. if (this.isDocumentFormat) return '.pdf,.doc,.docx'
  782. if (this.isExcelFormat) return '.xls,.xlsx'
  783. // 默认允许常见类型
  784. return '.pdf,.doc,.docx,.xls,.xlsx,.csv'
  785. },
  786. // 按类别分组的报送资料列表
  787. groupedDataRequirements() {
  788. if (
  789. !this.formData.dataRequirements ||
  790. !Array.isArray(this.formData.dataRequirements) ||
  791. this.formData.dataRequirements.length === 0
  792. ) {
  793. return []
  794. }
  795. // 按类别分组
  796. const groups = {}
  797. this.formData.dataRequirements.forEach((item, index) => {
  798. // 获取类别标识,使用 informationType 作为类别 key
  799. const categoryKey =
  800. item.informationType ||
  801. item.category ||
  802. item.categoryId ||
  803. item.categoryName ||
  804. 'other'
  805. // 优先从字典中获取类别名称
  806. let categoryName = ''
  807. if (categoryKey) {
  808. // 优先使用 getDictNameByValue 根据 value 查找
  809. categoryName = this.getDictNameByValue(
  810. 'materialCategory',
  811. categoryKey
  812. )
  813. // 如果 getDictNameByValue 没找到,尝试使用 getDictName 根据 key 查找
  814. if (!categoryName || categoryName === String(categoryKey)) {
  815. categoryName = this.getDictName('materialCategory', categoryKey)
  816. }
  817. // 如果字典中都找不到,使用 item.categoryName 作为后备
  818. if (!categoryName || categoryName === String(categoryKey)) {
  819. categoryName = item.informationTypeName || categoryKey
  820. }
  821. }
  822. if (!groups[categoryKey]) {
  823. groups[categoryKey] = {
  824. categoryKey,
  825. categoryName: categoryName || String(categoryKey),
  826. items: [],
  827. }
  828. }
  829. // 添加序号(每个类别内从1开始)
  830. const itemWithSeq = {
  831. ...item,
  832. index: index + 1,
  833. seq: groups[categoryKey].items.length + 1,
  834. }
  835. groups[categoryKey].items.push(itemWithSeq)
  836. })
  837. // 将分组转换为扁平数组,插入类别标题行
  838. const result = []
  839. Object.keys(groups).forEach((categoryKey) => {
  840. const group = groups[categoryKey]
  841. // 添加类别标题行
  842. result.push({
  843. isCategoryHeader: true,
  844. categoryName: group.categoryName,
  845. categoryKey: group.categoryKey,
  846. })
  847. // 添加该类别下的所有项目
  848. group.items.forEach((item) => {
  849. result.push({
  850. ...item,
  851. isCategoryHeader: false,
  852. })
  853. })
  854. })
  855. return result
  856. },
  857. // 分页后的成本调查表数据
  858. paginatedCostSurveyData() {
  859. const start =
  860. (this.costSurveyPagination.currentPage - 1) *
  861. this.costSurveyPagination.pageSize
  862. const end = start + this.costSurveyPagination.pageSize
  863. return this.formData.costSurveyData.slice(start, end)
  864. },
  865. },
  866. watch: {
  867. visible: {
  868. immediate: true,
  869. handler(newVal) {
  870. this.drawerVisible = newVal
  871. if (
  872. newVal &&
  873. this.taskInfo &&
  874. Object.keys(this.taskInfo).length > 0
  875. ) {
  876. // 弹窗打开时初始化数据
  877. this.initData()
  878. }
  879. },
  880. },
  881. drawerVisible(newVal) {
  882. if (!newVal) {
  883. this.$emit('update:visible', false)
  884. this.$emit('close')
  885. }
  886. },
  887. taskInfo: {
  888. immediate: true,
  889. deep: true,
  890. handler(newVal) {
  891. // 如果props传入taskInfo且有值,且弹窗已打开,则初始化数据
  892. if (newVal && Object.keys(newVal).length > 0 && this.drawerVisible) {
  893. this.initData()
  894. }
  895. },
  896. },
  897. },
  898. mounted() {
  899. this.drawerVisible = this.visible
  900. this.getAllUnitList()
  901. this.getDefaultDem()
  902. this.getUser()
  903. // 默认设置立项信息标签页
  904. this.activeTab = 'projectInfo'
  905. this.pageTitle = '立项信息'
  906. // 如果有传入taskInfo,初始化数据
  907. if (this.taskInfo && Object.keys(this.taskInfo).length > 0) {
  908. this.initData()
  909. }
  910. // 初始化成本调查表分页总数
  911. this.costSurveyPagination.total = this.formData.costSurveyData.length
  912. },
  913. methods: {
  914. // 初始化数据
  915. initData() {
  916. // 优先使用props传入的taskInfo,其次使用localTaskInfo
  917. const info =
  918. this.taskInfo && Object.keys(this.taskInfo).length > 0
  919. ? this.taskInfo
  920. : this.localTaskInfo
  921. // 判断是否为查看模式
  922. this.isViewMode = this.viewMode || false
  923. this.projectId = info.projectId || ''
  924. this.taskId = (info.userTask && info.userTask.id) || info.taskId || ''
  925. this.tabCheck = info.status || ''
  926. this.auditedUnitId = info.auditedUnitId || ''
  927. console.log(this.auditedUnitId, 'this.auditedUnitId 122')
  928. this.currentNode = info.currentNode || ''
  929. if (this.tabCheck === 'jtsy') {
  930. this.tabVisiable = false
  931. }
  932. // 默认显示立项信息标签页
  933. this.activeTab = 'projectInfo'
  934. this.pageTitle = '立项信息'
  935. // 加载立项信息数据
  936. if (this.projectId) {
  937. this.getProjectInformation()
  938. }
  939. },
  940. // 打开弹窗方法(供外部调用)
  941. open(taskInfo, viewMode = false) {
  942. this.localTaskInfo = taskInfo || {}
  943. this.initData()
  944. this.drawerVisible = true
  945. },
  946. // 关闭弹窗方法
  947. close() {
  948. this.drawerVisible = false
  949. },
  950. getAllUnitList() {
  951. getAllUnitList().then((res) => {
  952. this.unitList = res.value || []
  953. })
  954. },
  955. // 获取默认维度
  956. getDefaultDem() {
  957. getDefaultDem().then((res) => {
  958. if (res && res.code === 200) {
  959. const demId = res.value ? res.value.id : null
  960. if (demId) {
  961. this.getOrgListByDemId(demId)
  962. }
  963. }
  964. })
  965. },
  966. // 根据维度ID获取单位列表
  967. getOrgListByDemId(demId) {
  968. getOrgListByDemId({ demId }).then((res) => {
  969. if (res && res.code === 200) {
  970. this.OrgList = res.value || []
  971. }
  972. })
  973. },
  974. // 获取用户信息
  975. getUser() {
  976. getAllUserList()
  977. .then((res) => {
  978. this.userList = res.value || []
  979. })
  980. .catch(() => {})
  981. },
  982. // 添加监审年份
  983. addCostYear() {
  984. this.formData.basicInfo.auditPeriodArray.push({ value: '' })
  985. },
  986. // 删除监审年份
  987. deleteCostYear(index) {
  988. // 若只剩1条,禁止删除并提示
  989. if (this.formData.basicInfo.auditPeriodArray.length <= 1) {
  990. Message.warning('监审期间至少需保留1条,无法删除')
  991. return
  992. }
  993. this.formData.basicInfo.auditPeriodArray.splice(index, 1)
  994. },
  995. // 更新auditPeriod字符串
  996. updateAuditPeriodString() {
  997. const values = this.formData.basicInfo.auditPeriodArray
  998. .map((item) => item.value)
  999. .filter(Boolean)
  1000. const uniqueValues = [...new Set(values)]
  1001. this.formData.basicInfo.auditPeriod = uniqueValues.join(',')
  1002. },
  1003. // 立项依据文件处理
  1004. saveAccordingFiles(data) {
  1005. this.accordingFileList = data
  1006. this.formData.basicInfo.accordingFileUrl = data[0]?.savePath || ''
  1007. },
  1008. // 移除立项依据文件
  1009. removeAccordingFile() {
  1010. this.accordingFileList = []
  1011. this.formData.basicInfo.accordingFileUrl = ''
  1012. },
  1013. // 其他材料文件处理
  1014. saveOtherFiles(data) {
  1015. this.otherFileList = data
  1016. this.formData.basicInfo.otherFileUrl = data[0]?.savePath || ''
  1017. },
  1018. // 移除其他材料文件
  1019. removeOtherFile() {
  1020. this.otherFileList = []
  1021. this.formData.basicInfo.otherFileUrl = ''
  1022. },
  1023. // 处理地区选择变化
  1024. handleRegionChange(region) {
  1025. this.formData.basicInfo.areaCode = region.code
  1026. },
  1027. // 处理监审目录选择后的回调
  1028. handleCatalogChange(value) {
  1029. // this.formData.catalogId = value.join(',')
  1030. this.formData.basicInfo.catalogId = value
  1031. },
  1032. // 处理立项信息变化
  1033. handleProjectInfoChange(data) {
  1034. this.formData.basicInfo = { ...data }
  1035. },
  1036. // 处理监审意见变化
  1037. handleAuditOpinionChange(data) {
  1038. this.formData.auditOpinion = { ...data }
  1039. },
  1040. // 处理监审意见数据加载完成
  1041. handleAuditOpinionDataLoaded() {
  1042. // 数据加载完成后,关闭加载状态
  1043. this.tabLoading.auditOpinion = false
  1044. },
  1045. // 表格行样式
  1046. getRowClassName({ row }) {
  1047. if (row.isCategoryHeader) {
  1048. return 'category-header-row'
  1049. }
  1050. return ''
  1051. },
  1052. // 标签页切换
  1053. handleTabClick(tab) {
  1054. console.log(tab, 'qiehuan')
  1055. if (tab.name === 'projectInfo') {
  1056. // 立项项目信息
  1057. this.getProjectInformation()
  1058. } else if (tab.name === 'dataRequirements') {
  1059. // 报送资料要求
  1060. this.getTaskRequirementPage()
  1061. } else if (tab.name === 'costSurvey') {
  1062. // 成本调查表
  1063. this.getCostSurveyList()
  1064. } else if (tab.name === 'messageNotice') {
  1065. // 消息通知
  1066. this.getSendMessage()
  1067. } else if (tab.name === 'auditOpinion') {
  1068. // 监审意见标签页,设置加载状态并调用接口
  1069. this.tabLoading.auditOpinion = true
  1070. // 通过 ref 调用子组件方法,确保接口被调用
  1071. this.$nextTick(() => {
  1072. this.$nextTick(() => {
  1073. const auditOpinionTab = this.$refs.auditOpinionTabRef
  1074. if (
  1075. auditOpinionTab &&
  1076. typeof auditOpinionTab.getPreliminaryOpinionData === 'function'
  1077. ) {
  1078. auditOpinionTab.getPreliminaryOpinionData()
  1079. } else {
  1080. // 如果找不到组件,可能是组件还未创建,延迟调用
  1081. setTimeout(() => {
  1082. const tab = this.$refs.auditOpinionTabRef
  1083. if (
  1084. tab &&
  1085. typeof tab.getPreliminaryOpinionData === 'function'
  1086. ) {
  1087. tab.getPreliminaryOpinionData()
  1088. } else {
  1089. this.tabLoading.auditOpinion = false
  1090. }
  1091. }, 200)
  1092. }
  1093. })
  1094. })
  1095. } else if (tab.name === 'auditDocument') {
  1096. // 监审文件
  1097. this.getAuditDocumentList()
  1098. }
  1099. // 其他标签页(如 auditDocument、costSurvey)如果不需要异步加载数据,则不需要设置加载状态
  1100. // 可添加标签页切换时的逻辑
  1101. this.pageTitle = tab.label
  1102. },
  1103. // 获取立项项目信息
  1104. getProjectInformation() {
  1105. this.tabLoading.projectInfo = true
  1106. getProjectInformationInfo(this.projectId)
  1107. .then((res) => {
  1108. console.log('立项项目信息', res)
  1109. if (res && res.value) {
  1110. const data = res.value
  1111. // 处理监审期间数组
  1112. let auditPeriodArray = [{ value: '' }]
  1113. if (data.auditPeriod && typeof data.auditPeriod === 'string') {
  1114. const periods = data.auditPeriod.split(',').filter(Boolean)
  1115. auditPeriodArray = periods.map((p) => ({ value: p }))
  1116. }
  1117. // 赋值并添加转换后的数组
  1118. this.formData.basicInfo = {
  1119. ...data,
  1120. auditPeriodArray: auditPeriodArray,
  1121. }
  1122. }
  1123. })
  1124. .catch((err) => {
  1125. console.error('获取立项项目信息失败', err)
  1126. })
  1127. .finally(() => {
  1128. this.tabLoading.projectInfo = false
  1129. })
  1130. },
  1131. // 获取报送资料要求列表
  1132. getTaskRequirementPage() {
  1133. this.tabLoading.dataRequirements = true
  1134. console.log('送报资料接口', this.taskId)
  1135. getTaskRequirementList(this.taskId)
  1136. .then((res) => {
  1137. console.log('报送资料要求', res)
  1138. if (res && res.code === 200 && res.value) {
  1139. this.formData.dataRequirements = Array.isArray(res.value)
  1140. ? res.value
  1141. : []
  1142. } else {
  1143. this.formData.dataRequirements = []
  1144. }
  1145. })
  1146. .catch((err) => {
  1147. console.error('获取报送资料失败', err)
  1148. this.formData.dataRequirements = []
  1149. })
  1150. .finally(() => {
  1151. this.tabLoading.dataRequirements = false
  1152. })
  1153. },
  1154. // 消息通知
  1155. getSendMessage() {
  1156. this.tabLoading.messageNotice = true
  1157. const params = {
  1158. taskId: this.taskId,
  1159. auditedUnitId: this.auditedUnitId,
  1160. projectId: this.projectId,
  1161. pageNum: this.messageNoticePagination.pageNo,
  1162. pageSize: this.messageNoticePagination.pageSize,
  1163. }
  1164. sendMessage(params)
  1165. .then((res) => {
  1166. console.log('消息通知', res)
  1167. if (res && res.code === 200 && res.value) {
  1168. this.formData.messageNotice = res.value.records || []
  1169. this.messageNoticePagination.total = res.value.total || 0
  1170. } else {
  1171. this.formData.messageNotice = []
  1172. this.messageNoticePagination.total = 0
  1173. }
  1174. })
  1175. .catch((err) => {
  1176. console.error('获取消息通知失败', err)
  1177. this.formData.messageNoticeData = []
  1178. this.messageNoticePagination.total = 0
  1179. })
  1180. .finally(() => {
  1181. this.tabLoading.messageNotice = false
  1182. })
  1183. },
  1184. // 获取成本调查表列表
  1185. getCostSurveyList() {
  1186. this.tabLoading.costSurvey = true
  1187. const params = {
  1188. taskId: this.taskId || (this.taskInfo && this.taskInfo.taskId) || '',
  1189. catalogId: this.taskInfo.catalogId,
  1190. pageNum: this.costSurveyPagination.currentPage,
  1191. pageSize: this.costSurveyPagination.pageSize,
  1192. type: 1,
  1193. }
  1194. // if (this.auditedUnitId) params.auditedUnitId = this.auditedUnitId
  1195. getSurveyList(params)
  1196. .then((res) => {
  1197. console.log('成本调查表列表', res)
  1198. if (res && res.code === 200 && res.value) {
  1199. // 处理返回的数据
  1200. const records = res.value.records || res.value || []
  1201. // 映射数据格式,转换为组件需要的格式
  1202. const mappedData = records.map((item, index) => {
  1203. // 转换表格类型:1=单记录,2=固定表,3=动态表
  1204. let tableType = ''
  1205. if (item.templateType === 1 || item.templateType === '1') {
  1206. tableType = '单记录'
  1207. } else if (
  1208. item.templateType === 2 ||
  1209. item.templateType === '2'
  1210. ) {
  1211. tableType = '固定表'
  1212. } else if (
  1213. item.templateType === 3 ||
  1214. item.templateType === '3'
  1215. ) {
  1216. tableType = '动态表'
  1217. } else if (item.tableType) {
  1218. tableType = item.tableType
  1219. }
  1220. // 转换是否必填:0=否,1=是
  1221. const isRequired =
  1222. item.isRequired === 1 || item.isRequired === '1' ? '是' : '否'
  1223. // 转换是否上传:0=未上传,1=已上传
  1224. const isUploaded = item.isUpload === 1 || item.isUpload === '1'
  1225. return {
  1226. index: index + 1,
  1227. id: item.id,
  1228. name: item.surveyTemplateName || '',
  1229. dataType: item.dataType || '', // 资料类型
  1230. tableType: tableType, // 表格类型(转换后)
  1231. isRequired: isRequired, // 是否必填(转换后)
  1232. isUploaded: isUploaded, // 是否上传(转换后)
  1233. surveyTemplateId: item.surveyTemplateId || '', // 成本调查表模板ID
  1234. isDisabled: item.isDisabled || false,
  1235. fileUrl: item.fileUrl || '',
  1236. dataUrl: item.dataUrl || '',
  1237. templateUrl: item.templateUrl || '',
  1238. ...item, // 保留其他字段
  1239. }
  1240. })
  1241. // 更新 costSurveyData(完整数据)
  1242. this.formData.costSurveyData = mappedData
  1243. console.log(
  1244. 'this.formData.costSurveyData',
  1245. this.formData.costSurveyData
  1246. )
  1247. // 更新 paginatedData(用于传递给子组件,如果接口返回的是分页数据,直接使用;否则使用全部数据)
  1248. this.formData.paginatedData = mappedData
  1249. // 更新分页信息
  1250. this.costSurveyPagination.total =
  1251. res.value.total || mappedData.length || 0
  1252. } else {
  1253. this.formData.costSurveyData = []
  1254. this.formData.paginatedData = []
  1255. this.costSurveyPagination.total = 0
  1256. }
  1257. })
  1258. .catch((err) => {
  1259. console.error('获取成本调查表列表失败', err)
  1260. this.formData.costSurveyData = []
  1261. this.formData.paginatedData = []
  1262. this.costSurveyPagination.total = 0
  1263. })
  1264. .finally(() => {
  1265. this.tabLoading.costSurvey = false
  1266. })
  1267. },
  1268. // 消息通知分页处理
  1269. handleMessageNoticePageChange(pageNo) {
  1270. this.messageNoticePagination.pageNo = pageNo
  1271. this.getSendMessage()
  1272. },
  1273. // 消息通知每页大小变化
  1274. handleMessageNoticeSizeChange(pageSize) {
  1275. this.messageNoticePagination.pageSize = pageSize
  1276. this.messageNoticePagination.pageNo = 1
  1277. this.getSendMessage()
  1278. },
  1279. // 提交
  1280. handleSubmit() {
  1281. // 验证必填的报送资料(仅在 clcs 或 tjcl 节点时验证)
  1282. // if (this.currentNode === 'clcs' || this.currentNode === 'tjcl') {
  1283. // // 验证报送资料必填项
  1284. // const requiredItems = this.formData.dataRequirements.filter(
  1285. // (item) => {
  1286. // // 只检查非类别标题行的数据项
  1287. // if (item.isCategoryHeader) {
  1288. // return false
  1289. // }
  1290. // // 只检查是否为必填项
  1291. // const isRequired =
  1292. // item.isRequired === '1' || item.isRequired === 1
  1293. // return isRequired
  1294. // }
  1295. // )
  1296. // if (requiredItems.length > 0) {
  1297. // // 获取必填项名称列表
  1298. // const itemNames = requiredItems
  1299. // .map((item) => item.informationName || '未命名资料')
  1300. // .join('、')
  1301. // Message.warning(
  1302. // `有必填数据必须先填完,请先完成以下报送资料:${itemNames}`
  1303. // )
  1304. // return
  1305. // }
  1306. // // 验证成本调查表必填项
  1307. // const requiredCostSurveys = this.formData.costSurveyData.filter(
  1308. // (item) => {
  1309. // // 只检查是否为必填项
  1310. // const isRequired =
  1311. // item.isRequired === '1' ||
  1312. // item.isRequired === 1 ||
  1313. // item.isRequired === '是'
  1314. // return isRequired
  1315. // }
  1316. // )
  1317. // if (requiredCostSurveys.length > 0) {
  1318. // // 获取必填项名称列表
  1319. // const itemNames = requiredCostSurveys
  1320. // .map((item) => item.name || '未命名调查表')
  1321. // .join('、')
  1322. // Message.warning(
  1323. // `有必填数据必须先填完,请先完成以下成本调查表:${itemNames}`
  1324. // )
  1325. // return
  1326. // }
  1327. // }
  1328. this.loading.submit = true
  1329. // 判断是否为监审意见提交(currentNode === 'yjfk')
  1330. if (this.currentNode === 'yjfk') {
  1331. // 企业监审意见提交
  1332. // 处理文件列表,提取文件URL
  1333. // let fileUrlList = []
  1334. // if (this.formData.auditOpinion?.feedbackMaterials && Array.isArray(this.formData.auditOpinion.feedbackMaterials)) {
  1335. // fileUrlList = this.formData.auditOpinion.feedbackMaterials.map((file) => {
  1336. // // 如果file是字符串,直接返回
  1337. // if (typeof file === 'string') {
  1338. // return file
  1339. // }
  1340. // // 如果file是对象,提取url字段
  1341. // return file.url || file.response?.savePath || file.savePath || ''
  1342. // }).filter((url) => url) // 过滤空值
  1343. // }
  1344. const submitData = {
  1345. taskId: this.taskId,
  1346. id: this.formData.auditOpinion.id,
  1347. // 成本监审意见数据
  1348. basicSituation: this.formData.auditOpinion?.basicSituation || '',
  1349. currentPriceStandard:
  1350. this.formData.auditOpinion?.currentPriceStandard || '',
  1351. costComposition: this.formData.auditOpinion?.costComposition || '',
  1352. preliminaryOpinion:
  1353. this.formData.auditOpinion?.preliminaryOpinion || '',
  1354. // 被监审单位反馈意见数据
  1355. feedbackOpinion: this.formData.auditOpinion?.feedbackOpinion || '',
  1356. feedbackMaterials:
  1357. this.formData.auditOpinion?.feedbackMaterials[0]?.url || '', // 附件URL
  1358. }
  1359. submitPreliminaryOpinion(submitData)
  1360. .then((res) => {
  1361. this.loading.submit = false
  1362. console.log('企业监审意见提交成功', res)
  1363. if (res && res.code === 200) {
  1364. Message.success('提交成功')
  1365. // 关闭弹窗
  1366. this.handleBack()
  1367. // 通知父组件刷新列表
  1368. this.$emit('refresh')
  1369. } else {
  1370. Message.error(res?.message || '提交失败')
  1371. }
  1372. })
  1373. .catch((err) => {
  1374. this.loading.submit = false
  1375. console.error('企业监审意见提交失败', err)
  1376. // Message.error('提交失败:' + (err.message || '未知错误'))
  1377. })
  1378. } else {
  1379. // 其他情况走原有逻辑
  1380. setTimeout(() => {
  1381. submitTaskRequirement({ taskId: this.taskId })
  1382. .then((res) => {
  1383. this.loading.submit = false
  1384. console.log('提交成功', res)
  1385. if (res && res.code === 200) {
  1386. Message.success('提交成功')
  1387. this.handleBack()
  1388. // 通知父组件刷新列表
  1389. this.$emit('refresh')
  1390. } else {
  1391. Message.error('提交失败')
  1392. }
  1393. })
  1394. .catch((err) => {
  1395. this.loading.submit = false
  1396. console.error('提交失败', err)
  1397. // Message.error('提交失败')
  1398. })
  1399. }, 500)
  1400. }
  1401. },
  1402. // 保存
  1403. handleSave() {
  1404. this.loading.save = true
  1405. // 保存逻辑
  1406. setTimeout(() => {
  1407. this.loading.save = false
  1408. Message.success('保存成功')
  1409. }, 500)
  1410. },
  1411. // 返回
  1412. handleBack() {
  1413. this.drawerVisible = false
  1414. this.$emit('close')
  1415. },
  1416. // 侧边弹窗关闭事件
  1417. handleDrawerClose() {
  1418. this.$emit('update:visible', false)
  1419. this.$emit('close')
  1420. },
  1421. // 监审地区级联选择器
  1422. handleChange(value) {
  1423. console.log(value)
  1424. },
  1425. // 监审地区级联选择器节点点击
  1426. handleNodeClick(data) {
  1427. console.log(data)
  1428. this.formData.basicInfo.projectName = data.label
  1429. },
  1430. // 获取监审文书
  1431. getAuditDocumentList() {
  1432. getCostProjectDocumentPageList({
  1433. pageNum: this.auditDocumentPagination.page,
  1434. pageSize: this.auditDocumentPagination.pageSize,
  1435. projectId: this.projectId,
  1436. documentName: '',
  1437. permissionType: 1,
  1438. }).then((res) => {
  1439. this.formData.auditDocument = res.value.value.records
  1440. this.auditDocumentPagination.total = res.value.value.total
  1441. })
  1442. },
  1443. handleAuditDocumentPaginationChange(val) {
  1444. this.auditDocumentPagination.page = val.pageNum
  1445. this.auditDocumentPagination.pageSize = val.pageSize
  1446. this.getAuditDocumentList()
  1447. },
  1448. handleClick(row) {},
  1449. // 下载文书
  1450. handleDownload(row) {
  1451. console.log(row)
  1452. if (!row.fileUrl) {
  1453. this.$message.warning('该文书暂无文件可下载')
  1454. return
  1455. }
  1456. this.downloadByFetch(row.fileUrl, row.name || '文书文件')
  1457. },
  1458. // 上传附件
  1459. handleUpload(row) {
  1460. console.log(row)
  1461. },
  1462. // 报送资料相关操作方法
  1463. handleFileView(row) {
  1464. console.log('查看文件:', row)
  1465. // 实际开发中可添加文件预览逻辑(如打开新窗口、弹窗展示等)
  1466. if (row.fileUrl) {
  1467. window.open(row.fileUrl, '_blank')
  1468. } else {
  1469. this.$message.warning('暂无文件可查看')
  1470. }
  1471. },
  1472. handleFileDownload(row) {
  1473. console.log('下载文件:', row)
  1474. if (!row.fileUrl) {
  1475. this.$message.warning('该资料暂无文件可下载')
  1476. return
  1477. }
  1478. this.downloadByFetch(row.fileUrl, row.informationName || '下载文件')
  1479. },
  1480. async downloadByFetch(rawUrl, fallbackName) {
  1481. const url = this.normalizeUrl(rawUrl)
  1482. let loading
  1483. try {
  1484. loading = this.$baseLoading
  1485. ? this.$baseLoading(1, '文件下载中...')
  1486. : this.$loading({
  1487. lock: true,
  1488. text: '文件下载中...',
  1489. spinner: 'el-icon-loading',
  1490. background: 'rgba(0,0,0,0.7)',
  1491. })
  1492. const res = await fetch(url, { method: 'GET' })
  1493. if (!res.ok) throw new Error('下载失败')
  1494. const blob = await res.blob()
  1495. let fileName =
  1496. this.extractFileName(res.headers.get('content-disposition')) ||
  1497. fallbackName ||
  1498. '下载文件'
  1499. if (!/\.[a-zA-Z0-9]+$/.test(fileName)) {
  1500. const extFromUrl = (
  1501. url.split('?')[0].split('#')[0].split('.').pop() || ''
  1502. ).toLowerCase()
  1503. fileName = extFromUrl ? `${fileName}.${extFromUrl}` : fileName
  1504. }
  1505. const objectUrl = window.URL.createObjectURL(blob)
  1506. const link = document.createElement('a')
  1507. link.style.display = 'none'
  1508. link.href = objectUrl
  1509. link.download = fileName
  1510. document.body.appendChild(link)
  1511. link.click()
  1512. document.body.removeChild(link)
  1513. window.URL.revokeObjectURL(objectUrl)
  1514. this.$message.success('开始下载文件')
  1515. } catch (e) {
  1516. this.$message.error(e.message || '文件下载失败')
  1517. } finally {
  1518. if (loading && loading.close) loading.close()
  1519. }
  1520. },
  1521. normalizeUrl(u) {
  1522. if (!u) return ''
  1523. if (/^https?:\/\//i.test(u)) return u
  1524. const base = (window.context && window.context.form) || ''
  1525. if (!base) return u
  1526. if (u.startsWith('/')) return base + u
  1527. return base.replace(/\/$/, '') + '/' + u
  1528. },
  1529. extractFileName(contentDisposition) {
  1530. if (!contentDisposition) return ''
  1531. const match = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/i.exec(
  1532. contentDisposition
  1533. )
  1534. if (match && match[1]) {
  1535. try {
  1536. return decodeURIComponent(match[1].replace(/['"]/g, ''))
  1537. } catch (e) {
  1538. return match[1].replace(/['"]/g, '')
  1539. }
  1540. }
  1541. return ''
  1542. },
  1543. // 报送资料文件上传
  1544. async handleFileUpload(row, acceptFromChild) {
  1545. // 预置模版:直接打开 CostAuditTable 弹窗选择模版
  1546. const fmt = row && row.formatRequired
  1547. const isTemplate =
  1548. String(fmt) === '3' ||
  1549. (typeof fmt === 'string' && fmt.toLowerCase().includes('模板'))
  1550. if (isTemplate) {
  1551. this.currentDataRequirementRow = row || null
  1552. this.loadCostTemplates()
  1553. this.selectedCostTemplates = []
  1554. this.costAuditDialogVisible = true
  1555. console.log('打开 CostAuditTable 弹窗选择模版')
  1556. return
  1557. }
  1558. // 非预置模版:走本地文件上传
  1559. // 创建文件选择器
  1560. const input = document.createElement('input')
  1561. input.type = 'file'
  1562. input.accept =
  1563. (typeof acceptFromChild === 'string' && acceptFromChild) ||
  1564. '.pdf,.doc,.docx,.xls,.xlsx,.csv' // 允许的文件类型(兜底)
  1565. input.onchange = async (event) => {
  1566. const file = event.target.files[0]
  1567. if (!file) return
  1568. try {
  1569. // 校验文件大小(50MB)
  1570. const maxSize = 50 * 1024 * 1024 // 50MB
  1571. if (file.size > maxSize) {
  1572. this.$message.error('文件大小不能超过50MB!')
  1573. return
  1574. }
  1575. // 校验文件格式
  1576. const allowedFormats = [
  1577. '.pdf',
  1578. '.doc',
  1579. '.docx',
  1580. '.xls',
  1581. '.xlsx',
  1582. '.csv',
  1583. ]
  1584. const fileName = file.name.toLowerCase()
  1585. const isValidFormat = allowedFormats.some((format) =>
  1586. fileName.endsWith(format)
  1587. )
  1588. if (!isValidFormat) {
  1589. this.$message.error(
  1590. '只允许上传.pdf,.doc,.docx,.xls,.xlsx,.csv格式的文件!'
  1591. )
  1592. return
  1593. }
  1594. // 显示上传加载提示
  1595. const loading = this.$baseLoading(1, '文件上传中...')
  1596. // 创建FormData并上传文件
  1597. const formData = new FormData()
  1598. formData.append('file', file)
  1599. // 调用上传API
  1600. const uploadRes = await uploadFile('/api/file/v1/upload', formData)
  1601. // 检查上传结果
  1602. if (!uploadRes || uploadRes.code !== 200 || !uploadRes.value) {
  1603. loading.close()
  1604. this.$message.error('文件上传失败,请稍后重试')
  1605. return
  1606. }
  1607. // 文件上传成功后,更新报送资料项
  1608. const fileInfo = uploadRes.value
  1609. const updateData = {
  1610. // id: row.id,
  1611. // taskId: this.taskId,
  1612. // projectId: this.projectId,
  1613. // informationType: row.informationType,
  1614. // informationName: row.informationName,
  1615. // informationRequire: row.informationRequire,
  1616. // formatRequired: row.formatRequired,
  1617. // isRequired: row.isRequired,
  1618. ...row,
  1619. fileUrl: fileInfo.savePath || fileInfo.url, // 更新文件URL
  1620. }
  1621. // 调用更新API
  1622. const updateRes = await addOrUpdateTaskRequirement(updateData)
  1623. loading.close()
  1624. if (updateRes && updateRes.code === 200) {
  1625. this.$message.success('文件上传并更新成功!')
  1626. // 刷新报送资料列表
  1627. this.getTaskRequirementPage()
  1628. } else {
  1629. this.$message.error('文件上传成功,但更新资料失败')
  1630. }
  1631. } catch (error) {
  1632. console.error('文件上传失败:', error)
  1633. // this.$message.error('操作失败:' + (error.message || '未知错误'))
  1634. }
  1635. }
  1636. // 触发文件选择
  1637. input.click()
  1638. },
  1639. handleTemplateDownload(row) {
  1640. console.log('下载预置模版:', row)
  1641. if (!row.templateUrl && !row.fileUrl) {
  1642. this.$message.warning('该模版暂无文件可下载')
  1643. return
  1644. }
  1645. const downloadUrl = row.templateUrl || row.fileUrl
  1646. const link = document.createElement('a')
  1647. link.style.display = 'none'
  1648. link.href = downloadUrl
  1649. link.download = row.name ? `${row.name}_模版` : '模版文件'
  1650. document.body.appendChild(link)
  1651. link.click()
  1652. document.body.removeChild(link)
  1653. this.$message.success('开始下载模版')
  1654. },
  1655. handleDataUpload(row) {
  1656. // 预置模版:打开 CostAuditTable 弹窗进行模板选择
  1657. this.currentDataRequirementRow = row || null
  1658. this.loadCostTemplates()
  1659. this.selectedCostTemplates = []
  1660. this.costAuditDialogVisible = true
  1661. },
  1662. handleRemove(file, fileList) {
  1663. console.log(file, fileList)
  1664. },
  1665. handlePreview(file) {
  1666. console.log(file)
  1667. },
  1668. handleExceed(files, fileList) {
  1669. this.$message.warning(
  1670. `当前限制选择 3 个文件,本次选择了 ${files.length} 个文件,共选择了 ${
  1671. files.length + fileList.length
  1672. } 个文件`
  1673. )
  1674. },
  1675. beforeRemove(file, fileList) {
  1676. return this.$confirm(`确定移除 ${file.name}?`)
  1677. },
  1678. // 成本调查表点击事件(可点击时触发)
  1679. handleSurveyClick(row) {
  1680. console.log('点击成本调查表:', row.name)
  1681. // 实际逻辑:跳转页面、打开弹窗等
  1682. },
  1683. // 在线填报
  1684. handleOnlineFill(row) {
  1685. console.log('在线填报:', row.name)
  1686. this.currentSurveyData = row
  1687. this.onlineFillDialogVisible = true
  1688. },
  1689. // 修改已上传的表
  1690. handleModify(row) {
  1691. console.log('修改:', row.name)
  1692. // 实际逻辑:打开修改表单
  1693. },
  1694. // 数据下载
  1695. handleDataDownload(row) {
  1696. console.log('数据下载:', row.name)
  1697. if (!row.fileUrl && !row.dataUrl) {
  1698. this.$message.warning('该数据暂无文件可下载')
  1699. return
  1700. }
  1701. const downloadUrl = row.dataUrl || row.fileUrl
  1702. const link = document.createElement('a')
  1703. link.style.display = 'none'
  1704. link.href = downloadUrl
  1705. link.download = row.name ? `${row.name}_数据` : '数据文件'
  1706. document.body.appendChild(link)
  1707. link.click()
  1708. document.body.removeChild(link)
  1709. this.$message.success('开始下载数据')
  1710. },
  1711. // 补充材料相关方法
  1712. handleAddMaterial() {
  1713. this.materialDialogVisible = true
  1714. this.resetMaterialForm()
  1715. },
  1716. // 重置补充材料表单
  1717. resetMaterialForm() {
  1718. this.materialForm = {
  1719. informationType: '',
  1720. informationName: '',
  1721. informationRequire: '',
  1722. formatRequired: '',
  1723. fileList: [],
  1724. fileUrl: '',
  1725. }
  1726. if (this.$refs.materialForm) {
  1727. this.$refs.materialForm.resetFields()
  1728. }
  1729. },
  1730. // 补充材料弹窗取消
  1731. handleMaterialCancel() {
  1732. this.materialDialogVisible = false
  1733. this.resetMaterialForm()
  1734. },
  1735. // 补充材料弹窗确定
  1736. handleMaterialSubmit() {
  1737. this.$refs.materialForm.validate((valid) => {
  1738. if (valid) {
  1739. // 设置 loading 状态
  1740. this.loading.materialSubmit = true
  1741. // 这里可以添加提交逻辑
  1742. console.log('补充材料表单数据:', this.materialForm)
  1743. addOrUpdateTaskRequirement({
  1744. taskId: this.taskId,
  1745. projectId: this.projectId,
  1746. informationType: this.materialForm.informationType,
  1747. informationName: this.materialForm.informationName,
  1748. informationRequire: this.materialForm.informationRequire,
  1749. formatRequired: this.materialForm.formatRequired,
  1750. fileUrl: this.materialForm.fileUrl,
  1751. templateId: this.materialForm.templateId,
  1752. })
  1753. .then((res) => {
  1754. console.log('补充材料提交成功', res)
  1755. if (res && res.code === 200) {
  1756. this.loading.materialSubmit = false
  1757. Message.success('补充材料提交成功')
  1758. this.materialDialogVisible = false
  1759. this.resetMaterialForm()
  1760. this.getTaskRequirementPage()
  1761. } else {
  1762. Message.error('补充材料提交失败')
  1763. }
  1764. })
  1765. .catch((err) => {
  1766. console.error('补充材料提交失败', err)
  1767. Message.error('补充材料提交失败')
  1768. })
  1769. .finally(() => {
  1770. // 重置 loading 状态
  1771. this.loading.materialSubmit = false
  1772. })
  1773. } else {
  1774. Message.error('请完善表单信息')
  1775. return false
  1776. }
  1777. })
  1778. },
  1779. // 上传
  1780. // handleMaterialUpload(row, type) {
  1781. // let loading = null
  1782. // // 第一步:创建文件选择器
  1783. // const input = document.createElement('input')
  1784. // input.type = 'file'
  1785. // input.accept = '.pdf,.doc,.docx,.xls,.xlsx,.csv' // 允许的文件类型
  1786. // input.onchange = async (event) => {
  1787. // const file = event.target.files[0]
  1788. // if (!file) return
  1789. // try {
  1790. // // 校验文件大小(50MB)
  1791. // const maxSize = 50 * 1024 * 1024 // 50MB
  1792. // if (file.size > maxSize) {
  1793. // this.$message.error('文件大小不能超过50MB!')
  1794. // return
  1795. // }
  1796. // // 校验文件格式
  1797. // const allowedFormats = [
  1798. // '.pdf',
  1799. // '.doc',
  1800. // '.docx',
  1801. // '.xls',
  1802. // '.xlsx',
  1803. // 'csv',
  1804. // ]
  1805. // const fileName = file.name.toLowerCase()
  1806. // const isValidFormat = allowedFormats.some((format) =>
  1807. // fileName.endsWith(format)
  1808. // )
  1809. // if (!isValidFormat) {
  1810. // this.$message.error(
  1811. // '只允许上传.pdf,.doc,.docx,.xls,.xlsx,.csv格式的文件!'
  1812. // )
  1813. // return
  1814. // }
  1815. // // 显示遮罩层
  1816. // loading = this.$baseLoading(1, '文件上传中...')
  1817. // // 第三步:创建FormData并上传文件
  1818. // const formData = new FormData()
  1819. // formData.append('file', file)
  1820. // // 先调用上传API
  1821. // const uploadRes = await uploadFile('/api/file/v1/upload', formData)
  1822. // // 第四步:检查上传结果
  1823. // if (!uploadRes || !uploadRes.value) {
  1824. // // this.$message.error('文件上传失败!');
  1825. // return
  1826. // }
  1827. // // 第五步:文件上传成功后,再更新数据
  1828. // const fileInfo = uploadRes.value
  1829. // // 创建更新数据对象
  1830. // const updateData = {
  1831. // ...row,
  1832. // fileUrl: fileInfo?.savePath, // 更新扫描件URL
  1833. // }
  1834. // // 第六步:调用更新API
  1835. // // await updateCostProjectDocument(updateData)
  1836. // // // 第七步:更新成功,显示提示并刷新
  1837. // // this.$message.success('文件上传成功并更新数据!')
  1838. // // this.$emit('refresh', this.project.projectId) // 通知父组件刷新
  1839. // } catch (error) {
  1840. // // 错误处理
  1841. // this.$message.error('操作失败:' + (error.message || '未知错误'))
  1842. // } finally {
  1843. // // 关闭遮罩层
  1844. // loading.close()
  1845. // }
  1846. // }
  1847. // // 触发文件选择
  1848. // input.click()
  1849. // },
  1850. // 文件上传前验证
  1851. beforeMaterialUpload(file) {
  1852. // 预置模版不允许在此上传
  1853. if (this.isTemplateFormat) {
  1854. this.$message.warning(
  1855. '“预置模版”不支持直接上传,请按模版入口添加材料'
  1856. )
  1857. return false
  1858. }
  1859. const ext = '.' + file.name.split('.').pop().toLowerCase()
  1860. const mime = (file.type || '').toLowerCase()
  1861. // 文档格式
  1862. if (this.isDocumentFormat) {
  1863. const allowedExt = ['.pdf', '.doc', '.docx']
  1864. const allowedMime = [
  1865. 'application/pdf',
  1866. 'application/msword',
  1867. 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  1868. ]
  1869. const ok = allowedExt.includes(ext) || allowedMime.includes(mime)
  1870. if (!ok) {
  1871. this.$message.error('当前格式要求为文档,仅支持 pdf, doc, docx')
  1872. return false
  1873. }
  1874. }
  1875. // Excel 格式
  1876. if (this.isExcelFormat) {
  1877. const allowedExt = ['.xls', '.xlsx']
  1878. const allowedMime = [
  1879. 'application/vnd.ms-excel',
  1880. 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  1881. ]
  1882. const ok = allowedExt.includes(ext) || allowedMime.includes(mime)
  1883. if (!ok) {
  1884. this.$message.error('当前格式要求为表格,仅支持 xls, xlsx')
  1885. return false
  1886. }
  1887. }
  1888. // 检查文件大小 (50MB)
  1889. const isLt50M = file.size / 1024 / 1024 < 50
  1890. if (!isLt50M) {
  1891. this.$message.error('文件大小不能超过 50MB!')
  1892. return false
  1893. }
  1894. return true
  1895. },
  1896. // 自定义上传方法
  1897. async handleMaterialUpload(options) {
  1898. const { file, onProgress, onSuccess, onError } = options
  1899. // 检查是否已经上传了文件
  1900. if (
  1901. this.materialForm.fileList &&
  1902. this.materialForm.fileList.length >= 1
  1903. ) {
  1904. this.$message.warning('只能上传一个文件,请先删除已上传的文件')
  1905. // 手动触发onError回调
  1906. if (onError) {
  1907. onError(new Error('只能上传一个文件'))
  1908. }
  1909. return
  1910. }
  1911. const formData = new FormData()
  1912. formData.append('file', file)
  1913. try {
  1914. // 显示上传进度
  1915. if (onProgress) {
  1916. onProgress({ percent: 0 })
  1917. }
  1918. // 调用上传API
  1919. const uploadRes = await uploadFile('/api/file/v1/upload', formData, {
  1920. onUploadProgress: (progressEvent) => {
  1921. // 计算上传进度
  1922. if (progressEvent.total) {
  1923. const percent = Math.round(
  1924. (progressEvent.loaded * 100) / progressEvent.total
  1925. )
  1926. if (onProgress) {
  1927. onProgress({ percent })
  1928. }
  1929. }
  1930. },
  1931. })
  1932. // 检查上传结果
  1933. if (uploadRes && uploadRes.code === 200 && uploadRes.value) {
  1934. const fileInfo = uploadRes.value
  1935. // 构造文件信息对象,符合element-ui upload组件的格式
  1936. const fileObj = {
  1937. uid: file.uid,
  1938. name: file.name,
  1939. status: 'success',
  1940. size: file.size,
  1941. response: fileInfo,
  1942. url: fileInfo.savePath || fileInfo.url,
  1943. }
  1944. if (onSuccess) {
  1945. onSuccess(fileInfo, fileObj)
  1946. this.materialForm.fileUrl = fileInfo.savePath || fileInfo.url
  1947. }
  1948. this.$message.success(`${file.name} 上传成功`)
  1949. } else {
  1950. throw new Error(uploadRes?.message || '上传失败,请稍后重试')
  1951. }
  1952. } catch (error) {
  1953. console.error('文件上传失败:', error)
  1954. this.$message.error(`文件上传失败:${error.message || '未知错误'}`)
  1955. if (onError) {
  1956. onError(error)
  1957. }
  1958. }
  1959. },
  1960. // 打开预置模版弹窗
  1961. openLegalDialog() {
  1962. // 需求:改用 CostAuditTable 弹窗选择模版
  1963. this.loadCostTemplates()
  1964. this.selectedCostTemplates = []
  1965. this.costAuditDialogVisible = true
  1966. console.log(
  1967. '打开 CostAuditTable 弹窗选择模版',
  1968. this.costAuditDialogVisible
  1969. )
  1970. },
  1971. // 接收模版弹窗选择结果
  1972. handleLegalConfirm(payload) {
  1973. // 兼容不同返回字段,将关键信息落到表单中
  1974. const url =
  1975. payload?.templateUrl ||
  1976. payload?.url ||
  1977. payload?.savePath ||
  1978. payload?.fileUrl ||
  1979. ''
  1980. if (url) {
  1981. // 将选中的模版链接记录在fileUrl,后端若有专属字段可按需调整
  1982. this.materialForm.fileUrl = url
  1983. // 在UI上体现已选择(以文件列表形式展示一个只读项)
  1984. this.materialForm.fileList = [
  1985. {
  1986. uid: 'tpl-' + Date.now(),
  1987. name: payload?.name || payload?.templateName || '已选择的模版',
  1988. status: 'success',
  1989. url: url,
  1990. response: payload,
  1991. },
  1992. ]
  1993. this.$message.success('已选择模版')
  1994. } else {
  1995. this.$message.info('未获取到模版链接')
  1996. }
  1997. this.legalDialogVisible = false
  1998. },
  1999. // 拉取模板列表(CostAuditTable用)
  2000. loadCostTemplates() {
  2001. allcurrentCostSurveyList({
  2002. pageNum: this.costTemplatePagination.currentPage,
  2003. pageSize: this.costTemplatePagination.pageSize,
  2004. }).then((res) => {
  2005. this.costTemplateData = res?.value?.records || res?.value || []
  2006. this.costTemplatePagination.total =
  2007. res?.value?.total || this.costTemplateData.length || 0
  2008. })
  2009. },
  2010. handleCostTemplatePaginationChange({ currentPage, pageSize }) {
  2011. this.costTemplatePagination.currentPage = currentPage
  2012. this.costTemplatePagination.pageSize = pageSize
  2013. this.loadCostTemplates()
  2014. },
  2015. handleCostTemplateSelectionChange(val) {
  2016. if (Array.isArray(val) && val.length > 1) {
  2017. this.$message.warning('最多只能选择一个模板')
  2018. return
  2019. }
  2020. this.selectedCostTemplates = val || []
  2021. },
  2022. submitCostAuditTemplate() {
  2023. if (
  2024. !this.selectedCostTemplates ||
  2025. this.selectedCostTemplates.length !== 1
  2026. ) {
  2027. this.$message.error('请选择一个模板')
  2028. return
  2029. }
  2030. const chosen = this.selectedCostTemplates[0]
  2031. const url = chosen?.templateUrl || chosen?.url || chosen?.savePath || ''
  2032. // 记录到表单
  2033. this.materialForm.fileUrl = url
  2034. this.materialForm.templateId =
  2035. chosen?.surveyTemplateId || chosen?.templateId || chosen?.id || ''
  2036. this.materialForm.fileList = [
  2037. {
  2038. uid: 'tpl-' + Date.now(),
  2039. name: chosen?.surveyTemplateName || '已选择的模版',
  2040. status: 'success',
  2041. url: url,
  2042. response: chosen,
  2043. },
  2044. ]
  2045. this.$message.success('已选择模版')
  2046. this.costAuditDialogVisible = false
  2047. },
  2048. // 上传成功回调
  2049. handleMaterialUploadSuccess(response, file, fileList) {
  2050. // 更新文件列表,添加文件URL信息
  2051. this.materialForm.fileList = fileList.map((item) => {
  2052. if (item.uid === file.uid && response) {
  2053. return {
  2054. ...item,
  2055. url: response.savePath || response.url || item.url,
  2056. response: response,
  2057. }
  2058. }
  2059. return item
  2060. })
  2061. },
  2062. // 上传进度回调
  2063. handleMaterialUploadProgress(event, file, fileList) {
  2064. // 可以在这里显示上传进度,element-ui 会自动处理
  2065. },
  2066. // 上传失败回调
  2067. handleMaterialUploadError(err, file, fileList) {
  2068. console.error('文件上传错误:', err)
  2069. this.$message.error(`${file.name} 上传失败`)
  2070. // 从文件列表中移除失败的文件
  2071. this.materialForm.fileList = fileList.filter(
  2072. (item) => item.uid !== file.uid
  2073. )
  2074. },
  2075. // 超出文件数量限制
  2076. handleMaterialExceed(files, fileList) {
  2077. this.$message.warning(
  2078. `当前限制选择 5 个文件,本次选择了 ${files.length} 个文件,共选择了 ${fileList.length} 个文件`
  2079. )
  2080. },
  2081. // 移除文件
  2082. handleMaterialRemove(file, fileList) {
  2083. this.materialForm.fileList = fileList
  2084. this.$message.info(`${file.name} 已移除`)
  2085. },
  2086. // 成本调查表分页 - 页码改变
  2087. handleCostSurveyPageChange(page) {
  2088. this.costSurveyPagination.currentPage = page
  2089. // 调用接口重新获取数据
  2090. this.getCostSurveyList()
  2091. },
  2092. // 成本调查表分页 - 每页条数改变
  2093. handleCostSurveySizeChange(size) {
  2094. this.costSurveyPagination.pageSize = size
  2095. this.costSurveyPagination.currentPage = 1
  2096. // 调用接口重新获取数据
  2097. this.getCostSurveyList()
  2098. },
  2099. // 处理单记录调查表保存
  2100. handleSurveyFormSave(data) {
  2101. // 保存成功后刷新列表
  2102. this.getCostSurveyList()
  2103. },
  2104. // 获取监审期间数组(从立项信息中获取)
  2105. getAuditPeriodArray() {
  2106. // 优先从 formData.basicInfo.auditPeriodArray 获取
  2107. if (
  2108. this.formData &&
  2109. this.formData.basicInfo &&
  2110. this.formData.basicInfo.auditPeriodArray &&
  2111. Array.isArray(this.formData.basicInfo.auditPeriodArray)
  2112. ) {
  2113. const periods = this.formData.basicInfo.auditPeriodArray
  2114. .map((item) => item.value)
  2115. .filter((val) => val && val.trim())
  2116. if (periods.length > 0) {
  2117. return periods
  2118. }
  2119. }
  2120. // 其次从 taskInfo.auditPeriod 获取
  2121. if (this.taskInfo && this.taskInfo.auditPeriod) {
  2122. if (Array.isArray(this.taskInfo.auditPeriod)) {
  2123. return this.taskInfo.auditPeriod
  2124. } else if (typeof this.taskInfo.auditPeriod === 'string') {
  2125. // 如果是字符串,尝试解析
  2126. return this.taskInfo.auditPeriod
  2127. .split(',')
  2128. .map((p) => p.trim())
  2129. .filter((p) => p)
  2130. }
  2131. }
  2132. // 最后从 formData.basicInfo.auditPeriod 获取
  2133. if (
  2134. this.formData &&
  2135. this.formData.basicInfo &&
  2136. this.formData.basicInfo.auditPeriod
  2137. ) {
  2138. if (Array.isArray(this.formData.basicInfo.auditPeriod)) {
  2139. return this.formData.basicInfo.auditPeriod
  2140. } else if (typeof this.formData.basicInfo.auditPeriod === 'string') {
  2141. return this.formData.basicInfo.auditPeriod
  2142. .split(',')
  2143. .map((p) => p.trim())
  2144. .filter((p) => p)
  2145. }
  2146. }
  2147. return null
  2148. },
  2149. },
  2150. }
  2151. </script>
  2152. <style scoped lang="scss">
  2153. .drawer-title {
  2154. font-size: 18px;
  2155. font-weight: 600;
  2156. color: #303133;
  2157. }
  2158. .audit-task-manage-container {
  2159. padding: 20px;
  2160. height: 100%;
  2161. overflow-y: auto;
  2162. h3 {
  2163. text-align: center;
  2164. margin-top: 0;
  2165. margin-bottom: 20px;
  2166. font-size: 18px;
  2167. font-weight: bold;
  2168. }
  2169. }
  2170. .cost-opinion-section,
  2171. .feedback-section {
  2172. border: 1px solid #dcdcdc;
  2173. border-radius: 4px;
  2174. padding: 20px;
  2175. margin-bottom: 20px;
  2176. background-color: #f9f9f9;
  2177. }
  2178. .opinion-item,
  2179. .feedback-item {
  2180. margin-bottom: 20px;
  2181. display: flex;
  2182. align-items: center;
  2183. label {
  2184. width: 120px;
  2185. margin-bottom: 8px;
  2186. font-weight: bold;
  2187. color: #1549ad;
  2188. }
  2189. }
  2190. .file-info {
  2191. margin-bottom: 10px;
  2192. }
  2193. .file-info a {
  2194. color: #0066cc;
  2195. text-decoration: none;
  2196. }
  2197. .file-info a:hover {
  2198. text-decoration: underline;
  2199. }
  2200. .upload-btn {
  2201. background-color: #007bff;
  2202. color: #fff;
  2203. border: none;
  2204. border-radius: 4px;
  2205. padding: 8px 16px;
  2206. cursor: pointer;
  2207. }
  2208. .upload-btn:hover {
  2209. background-color: #0056b3;
  2210. }
  2211. .cost-period-container {
  2212. display: flex;
  2213. align-items: flex-start;
  2214. }
  2215. .add-cost-year-btn {
  2216. margin-right: 10px;
  2217. }
  2218. .delete-cost-year-btn {
  2219. margin-left: 10px;
  2220. }
  2221. .cost-years-wrapper {
  2222. flex: 1;
  2223. }
  2224. .cost-year-item {
  2225. margin-bottom: 10px;
  2226. display: flex;
  2227. align-items: center;
  2228. }
  2229. .ml10 {
  2230. margin-left: 10px;
  2231. }
  2232. .text-danger {
  2233. color: #d9001b;
  2234. }
  2235. // 类别头行样式
  2236. ::v-deep .category-header-row {
  2237. background-color: #f5f7fa !important;
  2238. td {
  2239. background-color: #f5f7fa !important;
  2240. padding: 12px 16px !important;
  2241. }
  2242. }
  2243. .category-header-cell {
  2244. font-weight: 700;
  2245. color: #303133;
  2246. padding-left: 16px;
  2247. }
  2248. // 侧边弹窗样式优化
  2249. ::v-deep .el-drawer__body {
  2250. overflow-y: auto;
  2251. padding: 0;
  2252. }
  2253. ::v-deep .el-drawer__header {
  2254. margin-bottom: 0;
  2255. padding: 20px;
  2256. border-bottom: 1px solid #e4e7ed;
  2257. }
  2258. ::v-deep .el-drawer__body {
  2259. padding: 0;
  2260. }
  2261. .document-edit-container {
  2262. display: flex;
  2263. .document-params {
  2264. width: 55%;
  2265. }
  2266. .document-preview {
  2267. width: 45%;
  2268. }
  2269. }
  2270. </style>