taskFillIn.vue 79 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423
  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: 50,
  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, options = {}) {
  942. this.localTaskInfo = taskInfo || {}
  943. this.initData()
  944. this.drawerVisible = true
  945. // 可选:默认切换指定标签页
  946. if (options && options.activeTab) {
  947. this.activeTab = options.activeTab
  948. // 同步标题
  949. const titleMap = {
  950. projectInfo: '立项信息',
  951. auditDocument: '监审文书',
  952. dataRequirements: '报送资料',
  953. costSurvey: '成本调查表',
  954. auditOpinion: '监审意见',
  955. messageNotice: '消息通知',
  956. }
  957. this.pageTitle = titleMap[options.activeTab] || this.pageTitle
  958. }
  959. // 可选:直接打开补充材料弹窗
  960. if (options && options.openMaterialDialog) {
  961. this.getTaskRequirementPage()
  962. this.materialDialogVisible = true
  963. }
  964. },
  965. // 关闭弹窗方法
  966. close() {
  967. this.drawerVisible = false
  968. },
  969. getAllUnitList() {
  970. getAllUnitList().then((res) => {
  971. this.unitList = res.value || []
  972. })
  973. },
  974. // 获取默认维度
  975. getDefaultDem() {
  976. getDefaultDem().then((res) => {
  977. if (res && res.code === 200) {
  978. const demId = res.value ? res.value.id : null
  979. if (demId) {
  980. this.getOrgListByDemId(demId)
  981. }
  982. }
  983. })
  984. },
  985. // 根据维度ID获取单位列表
  986. getOrgListByDemId(demId) {
  987. getOrgListByDemId({ demId }).then((res) => {
  988. if (res && res.code === 200) {
  989. this.OrgList = res.value || []
  990. }
  991. })
  992. },
  993. // 获取用户信息
  994. getUser() {
  995. getAllUserList()
  996. .then((res) => {
  997. this.userList = res.value || []
  998. })
  999. .catch(() => {})
  1000. },
  1001. // 添加监审年份
  1002. addCostYear() {
  1003. this.formData.basicInfo.auditPeriodArray.push({ value: '' })
  1004. },
  1005. // 删除监审年份
  1006. deleteCostYear(index) {
  1007. // 若只剩1条,禁止删除并提示
  1008. if (this.formData.basicInfo.auditPeriodArray.length <= 1) {
  1009. Message.warning('监审期间至少需保留1条,无法删除')
  1010. return
  1011. }
  1012. this.formData.basicInfo.auditPeriodArray.splice(index, 1)
  1013. },
  1014. // 更新auditPeriod字符串
  1015. updateAuditPeriodString() {
  1016. const values = this.formData.basicInfo.auditPeriodArray
  1017. .map((item) => item.value)
  1018. .filter(Boolean)
  1019. const uniqueValues = [...new Set(values)]
  1020. this.formData.basicInfo.auditPeriod = uniqueValues.join(',')
  1021. },
  1022. // 立项依据文件处理
  1023. saveAccordingFiles(data) {
  1024. this.accordingFileList = data
  1025. this.formData.basicInfo.accordingFileUrl = data[0]?.savePath || ''
  1026. },
  1027. // 移除立项依据文件
  1028. removeAccordingFile() {
  1029. this.accordingFileList = []
  1030. this.formData.basicInfo.accordingFileUrl = ''
  1031. },
  1032. // 其他材料文件处理
  1033. saveOtherFiles(data) {
  1034. this.otherFileList = data
  1035. this.formData.basicInfo.otherFileUrl = data[0]?.savePath || ''
  1036. },
  1037. // 移除其他材料文件
  1038. removeOtherFile() {
  1039. this.otherFileList = []
  1040. this.formData.basicInfo.otherFileUrl = ''
  1041. },
  1042. // 处理地区选择变化
  1043. handleRegionChange(region) {
  1044. this.formData.basicInfo.areaCode = region.code
  1045. },
  1046. // 处理监审目录选择后的回调
  1047. handleCatalogChange(value) {
  1048. // this.formData.catalogId = value.join(',')
  1049. this.formData.basicInfo.catalogId = value
  1050. },
  1051. // 处理立项信息变化
  1052. handleProjectInfoChange(data) {
  1053. this.formData.basicInfo = { ...data }
  1054. },
  1055. // 处理监审意见变化
  1056. handleAuditOpinionChange(data) {
  1057. this.formData.auditOpinion = { ...data }
  1058. },
  1059. // 处理监审意见数据加载完成
  1060. handleAuditOpinionDataLoaded() {
  1061. // 数据加载完成后,关闭加载状态
  1062. this.tabLoading.auditOpinion = false
  1063. },
  1064. // 表格行样式
  1065. getRowClassName({ row }) {
  1066. if (row.isCategoryHeader) {
  1067. return 'category-header-row'
  1068. }
  1069. return ''
  1070. },
  1071. // 标签页切换
  1072. handleTabClick(tab) {
  1073. console.log(tab, 'qiehuan')
  1074. if (tab.name === 'projectInfo') {
  1075. // 立项项目信息
  1076. this.getProjectInformation()
  1077. } else if (tab.name === 'dataRequirements') {
  1078. // 报送资料要求
  1079. this.getTaskRequirementPage()
  1080. } else if (tab.name === 'costSurvey') {
  1081. // 成本调查表
  1082. this.getCostSurveyList()
  1083. } else if (tab.name === 'messageNotice') {
  1084. // 消息通知
  1085. this.getSendMessage()
  1086. } else if (tab.name === 'auditOpinion') {
  1087. // 监审意见标签页,设置加载状态并调用接口
  1088. this.tabLoading.auditOpinion = true
  1089. // 通过 ref 调用子组件方法,确保接口被调用
  1090. this.$nextTick(() => {
  1091. this.$nextTick(() => {
  1092. const auditOpinionTab = this.$refs.auditOpinionTabRef
  1093. if (
  1094. auditOpinionTab &&
  1095. typeof auditOpinionTab.getPreliminaryOpinionData === 'function'
  1096. ) {
  1097. auditOpinionTab.getPreliminaryOpinionData()
  1098. } else {
  1099. // 如果找不到组件,可能是组件还未创建,延迟调用
  1100. setTimeout(() => {
  1101. const tab = this.$refs.auditOpinionTabRef
  1102. if (
  1103. tab &&
  1104. typeof tab.getPreliminaryOpinionData === 'function'
  1105. ) {
  1106. tab.getPreliminaryOpinionData()
  1107. } else {
  1108. this.tabLoading.auditOpinion = false
  1109. }
  1110. }, 200)
  1111. }
  1112. })
  1113. })
  1114. } else if (tab.name === 'auditDocument') {
  1115. // 监审文件
  1116. this.getAuditDocumentList()
  1117. }
  1118. // 其他标签页(如 auditDocument、costSurvey)如果不需要异步加载数据,则不需要设置加载状态
  1119. // 可添加标签页切换时的逻辑
  1120. this.pageTitle = tab.label
  1121. },
  1122. // 获取立项项目信息
  1123. getProjectInformation() {
  1124. this.tabLoading.projectInfo = true
  1125. getProjectInformationInfo(this.projectId)
  1126. .then((res) => {
  1127. console.log('立项项目信息', res)
  1128. if (res && res.value) {
  1129. const data = res.value
  1130. // 处理监审期间数组
  1131. let auditPeriodArray = [{ value: '' }]
  1132. if (data.auditPeriod && typeof data.auditPeriod === 'string') {
  1133. const periods = data.auditPeriod.split(',').filter(Boolean)
  1134. auditPeriodArray = periods.map((p) => ({ value: p }))
  1135. }
  1136. // 赋值并添加转换后的数组
  1137. this.formData.basicInfo = {
  1138. ...data,
  1139. auditPeriodArray: auditPeriodArray,
  1140. }
  1141. }
  1142. })
  1143. .catch((err) => {
  1144. console.error('获取立项项目信息失败', err)
  1145. })
  1146. .finally(() => {
  1147. this.tabLoading.projectInfo = false
  1148. })
  1149. },
  1150. // 获取报送资料要求列表
  1151. getTaskRequirementPage() {
  1152. this.tabLoading.dataRequirements = true
  1153. console.log('送报资料接口', this.taskId)
  1154. getTaskRequirementList(this.taskId)
  1155. .then((res) => {
  1156. console.log('报送资料要求', res)
  1157. if (res && res.code === 200 && res.value) {
  1158. this.formData.dataRequirements = Array.isArray(res.value)
  1159. ? res.value
  1160. : []
  1161. } else {
  1162. this.formData.dataRequirements = []
  1163. }
  1164. })
  1165. .catch((err) => {
  1166. console.error('获取报送资料失败', err)
  1167. this.formData.dataRequirements = []
  1168. })
  1169. .finally(() => {
  1170. this.tabLoading.dataRequirements = false
  1171. })
  1172. },
  1173. // 消息通知
  1174. getSendMessage() {
  1175. this.tabLoading.messageNotice = true
  1176. const params = {
  1177. taskId: this.taskId,
  1178. auditedUnitId: this.auditedUnitId,
  1179. projectId: this.projectId,
  1180. pageNum: this.messageNoticePagination.pageNo,
  1181. pageSize: this.messageNoticePagination.pageSize,
  1182. }
  1183. sendMessage(params)
  1184. .then((res) => {
  1185. console.log('消息通知', res)
  1186. if (res && res.code === 200 && res.value) {
  1187. this.formData.messageNotice = res.value.records || []
  1188. this.messageNoticePagination.total = res.value.total || 0
  1189. } else {
  1190. this.formData.messageNotice = []
  1191. this.messageNoticePagination.total = 0
  1192. }
  1193. })
  1194. .catch((err) => {
  1195. console.error('获取消息通知失败', err)
  1196. this.formData.messageNoticeData = []
  1197. this.messageNoticePagination.total = 0
  1198. })
  1199. .finally(() => {
  1200. this.tabLoading.messageNotice = false
  1201. })
  1202. },
  1203. // 获取成本调查表列表
  1204. getCostSurveyList() {
  1205. this.tabLoading.costSurvey = true
  1206. const params = {
  1207. taskId: this.taskId || (this.taskInfo && this.taskInfo.taskId) || '',
  1208. catalogId: this.taskInfo.catalogId,
  1209. pageNum: this.costSurveyPagination.currentPage,
  1210. pageSize: this.costSurveyPagination.pageSize,
  1211. type: 1,
  1212. }
  1213. // if (this.auditedUnitId) params.auditedUnitId = this.auditedUnitId
  1214. getSurveyList(params)
  1215. .then((res) => {
  1216. console.log('成本调查表列表', res)
  1217. if (res && res.code === 200 && res.value) {
  1218. // 处理返回的数据
  1219. const records = res.value.records || res.value || []
  1220. // 映射数据格式,转换为组件需要的格式
  1221. const mappedData = records.map((item, index) => {
  1222. // 转换表格类型:1=单记录,2=固定表,3=动态表
  1223. let tableType = ''
  1224. if (item.templateType === 1 || item.templateType === '1') {
  1225. tableType = '单记录'
  1226. } else if (
  1227. item.templateType === 2 ||
  1228. item.templateType === '2'
  1229. ) {
  1230. tableType = '固定表'
  1231. } else if (
  1232. item.templateType === 3 ||
  1233. item.templateType === '3'
  1234. ) {
  1235. tableType = '动态表'
  1236. } else if (item.tableType) {
  1237. tableType = item.tableType
  1238. }
  1239. // 转换是否必填:0=否,1=是
  1240. const isRequired =
  1241. item.isRequired === 1 || item.isRequired === '1' ? '是' : '否'
  1242. // 转换是否上传:0=未上传,1=已上传
  1243. const isUploaded = item.isUpload === 1 || item.isUpload === '1'
  1244. return {
  1245. index: index + 1,
  1246. id: item.id,
  1247. name: item.surveyTemplateName || '',
  1248. dataType: item.dataType || '', // 资料类型
  1249. tableType: tableType, // 表格类型(转换后)
  1250. isRequired: isRequired, // 是否必填(转换后)
  1251. isUploaded: isUploaded, // 是否上传(转换后)
  1252. surveyTemplateId: item.surveyTemplateId || '', // 成本调查表模板ID
  1253. isDisabled: item.isDisabled || false,
  1254. fileUrl: item.fileUrl || '',
  1255. dataUrl: item.dataUrl || '',
  1256. templateUrl: item.templateUrl || '',
  1257. ...item, // 保留其他字段
  1258. }
  1259. })
  1260. // 更新 costSurveyData(完整数据)
  1261. this.formData.costSurveyData = mappedData
  1262. console.log(
  1263. 'this.formData.costSurveyData',
  1264. this.formData.costSurveyData
  1265. )
  1266. // 更新 paginatedData(用于传递给子组件,如果接口返回的是分页数据,直接使用;否则使用全部数据)
  1267. this.formData.paginatedData = mappedData
  1268. // 更新分页信息
  1269. this.costSurveyPagination.total =
  1270. res.value.total || mappedData.length || 0
  1271. } else {
  1272. this.formData.costSurveyData = []
  1273. this.formData.paginatedData = []
  1274. this.costSurveyPagination.total = 0
  1275. }
  1276. })
  1277. .catch((err) => {
  1278. console.error('获取成本调查表列表失败', err)
  1279. this.formData.costSurveyData = []
  1280. this.formData.paginatedData = []
  1281. this.costSurveyPagination.total = 0
  1282. })
  1283. .finally(() => {
  1284. this.tabLoading.costSurvey = false
  1285. })
  1286. },
  1287. // 消息通知分页处理
  1288. handleMessageNoticePageChange(pageNo) {
  1289. this.messageNoticePagination.pageNo = pageNo
  1290. this.getSendMessage()
  1291. },
  1292. // 消息通知每页大小变化
  1293. handleMessageNoticeSizeChange(pageSize) {
  1294. this.messageNoticePagination.pageSize = pageSize
  1295. this.messageNoticePagination.pageNo = 1
  1296. this.getSendMessage()
  1297. },
  1298. // 提交
  1299. handleSubmit() {
  1300. // 验证必填的报送资料(仅在 clcs 或 tjcl 节点时验证)
  1301. // if (this.currentNode === 'clcs' || this.currentNode === 'tjcl') {
  1302. // // 验证报送资料必填项
  1303. // const requiredItems = this.formData.dataRequirements.filter(
  1304. // (item) => {
  1305. // // 只检查非类别标题行的数据项
  1306. // if (item.isCategoryHeader) {
  1307. // return false
  1308. // }
  1309. // // 只检查是否为必填项
  1310. // const isRequired =
  1311. // item.isRequired === '1' || item.isRequired === 1
  1312. // return isRequired
  1313. // }
  1314. // )
  1315. // if (requiredItems.length > 0) {
  1316. // // 获取必填项名称列表
  1317. // const itemNames = requiredItems
  1318. // .map((item) => item.informationName || '未命名资料')
  1319. // .join('、')
  1320. // Message.warning(
  1321. // `有必填数据必须先填完,请先完成以下报送资料:${itemNames}`
  1322. // )
  1323. // return
  1324. // }
  1325. // // 验证成本调查表必填项
  1326. // const requiredCostSurveys = this.formData.costSurveyData.filter(
  1327. // (item) => {
  1328. // // 只检查是否为必填项
  1329. // const isRequired =
  1330. // item.isRequired === '1' ||
  1331. // item.isRequired === 1 ||
  1332. // item.isRequired === '是'
  1333. // return isRequired
  1334. // }
  1335. // )
  1336. // if (requiredCostSurveys.length > 0) {
  1337. // // 获取必填项名称列表
  1338. // const itemNames = requiredCostSurveys
  1339. // .map((item) => item.name || '未命名调查表')
  1340. // .join('、')
  1341. // Message.warning(
  1342. // `有必填数据必须先填完,请先完成以下成本调查表:${itemNames}`
  1343. // )
  1344. // return
  1345. // }
  1346. // }
  1347. this.loading.submit = true
  1348. // 判断是否为监审意见提交(currentNode === 'yjfk')
  1349. if (this.currentNode === 'yjfk') {
  1350. // 企业监审意见提交
  1351. // 处理文件列表,提取文件URL
  1352. // let fileUrlList = []
  1353. // if (this.formData.auditOpinion?.feedbackMaterials && Array.isArray(this.formData.auditOpinion.feedbackMaterials)) {
  1354. // fileUrlList = this.formData.auditOpinion.feedbackMaterials.map((file) => {
  1355. // // 如果file是字符串,直接返回
  1356. // if (typeof file === 'string') {
  1357. // return file
  1358. // }
  1359. // // 如果file是对象,提取url字段
  1360. // return file.url || file.response?.savePath || file.savePath || ''
  1361. // }).filter((url) => url) // 过滤空值
  1362. // }
  1363. const submitData = {
  1364. taskId: this.taskId,
  1365. id: this.formData.auditOpinion.id,
  1366. // 成本监审意见数据
  1367. basicSituation: this.formData.auditOpinion?.basicSituation || '',
  1368. currentPriceStandard:
  1369. this.formData.auditOpinion?.currentPriceStandard || '',
  1370. costComposition: this.formData.auditOpinion?.costComposition || '',
  1371. preliminaryOpinion:
  1372. this.formData.auditOpinion?.preliminaryOpinion || '',
  1373. // 被监审单位反馈意见数据
  1374. feedbackOpinion: this.formData.auditOpinion?.feedbackOpinion || '',
  1375. feedbackMaterials:
  1376. this.formData.auditOpinion?.feedbackMaterials[0]?.url || '', // 附件URL
  1377. }
  1378. submitPreliminaryOpinion(submitData)
  1379. .then((res) => {
  1380. this.loading.submit = false
  1381. console.log('企业监审意见提交成功', res)
  1382. if (res && res.code === 200) {
  1383. Message.success('提交成功')
  1384. // 关闭弹窗
  1385. this.handleBack()
  1386. // 通知父组件刷新列表
  1387. this.$emit('refresh')
  1388. } else {
  1389. Message.error(res?.message || '提交失败')
  1390. }
  1391. })
  1392. .catch((err) => {
  1393. this.loading.submit = false
  1394. console.error('企业监审意见提交失败', err)
  1395. // Message.error('提交失败:' + (err.message || '未知错误'))
  1396. })
  1397. } else {
  1398. // 其他情况走原有逻辑
  1399. setTimeout(() => {
  1400. submitTaskRequirement({ taskId: this.taskId })
  1401. .then((res) => {
  1402. this.loading.submit = false
  1403. console.log('提交成功', res)
  1404. if (res && res.code === 200) {
  1405. Message.success('提交成功')
  1406. this.handleBack()
  1407. // 通知父组件刷新列表
  1408. this.$emit('refresh')
  1409. } else {
  1410. Message.error('提交失败')
  1411. }
  1412. })
  1413. .catch((err) => {
  1414. this.loading.submit = false
  1415. console.error('提交失败', err)
  1416. // Message.error('提交失败')
  1417. })
  1418. }, 500)
  1419. }
  1420. },
  1421. // 保存
  1422. handleSave() {
  1423. this.loading.save = true
  1424. // 保存逻辑
  1425. setTimeout(() => {
  1426. this.loading.save = false
  1427. Message.success('保存成功')
  1428. }, 500)
  1429. },
  1430. // 返回
  1431. handleBack() {
  1432. this.drawerVisible = false
  1433. this.$emit('close')
  1434. },
  1435. // 侧边弹窗关闭事件
  1436. handleDrawerClose() {
  1437. this.$emit('update:visible', false)
  1438. this.$emit('close')
  1439. },
  1440. // 监审地区级联选择器
  1441. handleChange(value) {
  1442. console.log(value)
  1443. },
  1444. // 监审地区级联选择器节点点击
  1445. handleNodeClick(data) {
  1446. console.log(data)
  1447. this.formData.basicInfo.projectName = data.label
  1448. },
  1449. // 获取监审文书
  1450. getAuditDocumentList() {
  1451. getCostProjectDocumentPageList({
  1452. pageNum: this.auditDocumentPagination.page,
  1453. pageSize: this.auditDocumentPagination.pageSize,
  1454. projectId: this.projectId,
  1455. documentName: '',
  1456. permissionType: 1,
  1457. }).then((res) => {
  1458. let records = res.value.value.records
  1459. this.formData.auditDocument = records.filter((item) => {
  1460. const excludeTypes = [
  1461. '成本监审工作底稿-送达回证',
  1462. '成本监审提取资料登记表-送达回证',
  1463. '成本审核初步意见表1-送达回证',
  1464. '成本监审集体审议记录-送达回证',
  1465. ]
  1466. return !excludeTypes.includes(item.documentName)
  1467. })
  1468. this.auditDocumentPagination.total = res.value.value.total
  1469. })
  1470. },
  1471. handleAuditDocumentPaginationChange(val) {
  1472. this.auditDocumentPagination.page = val.pageNum
  1473. this.auditDocumentPagination.pageSize = val.pageSize
  1474. this.getAuditDocumentList()
  1475. },
  1476. handleClick(row) {},
  1477. // 下载文书
  1478. handleDownload(row) {
  1479. console.log(row)
  1480. if (!row.fileUrl) {
  1481. this.$message.warning('该文书暂无文件可下载')
  1482. return
  1483. }
  1484. this.downloadByFetch(row.fileUrl, row.name || '文书文件')
  1485. },
  1486. // 上传附件
  1487. handleUpload(row) {
  1488. console.log(row)
  1489. },
  1490. // 报送资料相关操作方法
  1491. handleFileView(row) {
  1492. console.log('查看文件:', row)
  1493. // 实际开发中可添加文件预览逻辑(如打开新窗口、弹窗展示等)
  1494. if (row.fileUrl) {
  1495. window.open(row.fileUrl, '_blank')
  1496. } else {
  1497. this.$message.warning('暂无文件可查看')
  1498. }
  1499. },
  1500. handleFileDownload(row) {
  1501. console.log('下载文件:', row)
  1502. if (!row.fileUrl) {
  1503. this.$message.warning('该资料暂无文件可下载')
  1504. return
  1505. }
  1506. this.downloadByFetch(row.fileUrl, row.informationName || '下载文件')
  1507. },
  1508. async downloadByFetch(rawUrl, fallbackName) {
  1509. const url = this.normalizeUrl(rawUrl)
  1510. let loading
  1511. try {
  1512. loading = this.$baseLoading
  1513. ? this.$baseLoading(1, '文件下载中...')
  1514. : this.$loading({
  1515. lock: true,
  1516. text: '文件下载中...',
  1517. spinner: 'el-icon-loading',
  1518. background: 'rgba(0,0,0,0.7)',
  1519. })
  1520. const res = await fetch(url, { method: 'GET' })
  1521. if (!res.ok) throw new Error('下载失败')
  1522. const blob = await res.blob()
  1523. let fileName =
  1524. this.extractFileName(res.headers.get('content-disposition')) ||
  1525. fallbackName ||
  1526. '下载文件'
  1527. if (!/\.[a-zA-Z0-9]+$/.test(fileName)) {
  1528. const extFromUrl = (
  1529. url.split('?')[0].split('#')[0].split('.').pop() || ''
  1530. ).toLowerCase()
  1531. fileName = extFromUrl ? `${fileName}.${extFromUrl}` : fileName
  1532. }
  1533. const objectUrl = window.URL.createObjectURL(blob)
  1534. const link = document.createElement('a')
  1535. link.style.display = 'none'
  1536. link.href = objectUrl
  1537. link.download = fileName
  1538. document.body.appendChild(link)
  1539. link.click()
  1540. document.body.removeChild(link)
  1541. window.URL.revokeObjectURL(objectUrl)
  1542. this.$message.success('开始下载文件')
  1543. } catch (e) {
  1544. this.$message.error(e.message || '文件下载失败')
  1545. } finally {
  1546. if (loading && loading.close) loading.close()
  1547. }
  1548. },
  1549. normalizeUrl(u) {
  1550. if (!u) return ''
  1551. if (/^https?:\/\//i.test(u)) return u
  1552. const base = (window.context && window.context.form) || ''
  1553. if (!base) return u
  1554. if (u.startsWith('/')) return base + u
  1555. return base.replace(/\/$/, '') + '/' + u
  1556. },
  1557. extractFileName(contentDisposition) {
  1558. if (!contentDisposition) return ''
  1559. const match = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/i.exec(
  1560. contentDisposition
  1561. )
  1562. if (match && match[1]) {
  1563. try {
  1564. return decodeURIComponent(match[1].replace(/['"]/g, ''))
  1565. } catch (e) {
  1566. return match[1].replace(/['"]/g, '')
  1567. }
  1568. }
  1569. return ''
  1570. },
  1571. // 报送资料文件上传
  1572. async handleFileUpload(row, acceptFromChild) {
  1573. // 预置模版:直接打开 CostAuditTable 弹窗选择模版
  1574. const fmt = row && row.formatRequired
  1575. const isTemplate =
  1576. String(fmt) === '3' ||
  1577. (typeof fmt === 'string' && fmt.toLowerCase().includes('模板'))
  1578. if (isTemplate) {
  1579. this.currentDataRequirementRow = row || null
  1580. this.loadCostTemplates()
  1581. this.selectedCostTemplates = []
  1582. this.costAuditDialogVisible = true
  1583. console.log('打开 CostAuditTable 弹窗选择模版')
  1584. return
  1585. }
  1586. // 非预置模版:走本地文件上传
  1587. // 创建文件选择器
  1588. const input = document.createElement('input')
  1589. input.type = 'file'
  1590. input.accept =
  1591. (typeof acceptFromChild === 'string' && acceptFromChild) ||
  1592. '.pdf,.doc,.docx,.xls,.xlsx,.csv' // 允许的文件类型(兜底)
  1593. input.onchange = async (event) => {
  1594. const file = event.target.files[0]
  1595. if (!file) return
  1596. try {
  1597. // 校验文件大小(50MB)
  1598. const maxSize = 50 * 1024 * 1024 // 50MB
  1599. if (file.size > maxSize) {
  1600. this.$message.error('文件大小不能超过50MB!')
  1601. return
  1602. }
  1603. // 校验文件格式
  1604. const allowedFormats = [
  1605. '.pdf',
  1606. '.doc',
  1607. '.docx',
  1608. '.xls',
  1609. '.xlsx',
  1610. '.csv',
  1611. ]
  1612. const fileName = file.name.toLowerCase()
  1613. const isValidFormat = allowedFormats.some((format) =>
  1614. fileName.endsWith(format)
  1615. )
  1616. if (!isValidFormat) {
  1617. this.$message.error(
  1618. '只允许上传.pdf,.doc,.docx,.xls,.xlsx,.csv格式的文件!'
  1619. )
  1620. return
  1621. }
  1622. // 显示上传加载提示
  1623. const loading = this.$baseLoading(1, '文件上传中...')
  1624. // 创建FormData并上传文件
  1625. const formData = new FormData()
  1626. formData.append('file', file)
  1627. // 调用上传API
  1628. const uploadRes = await uploadFile('/api/file/v1/upload', formData)
  1629. // 检查上传结果
  1630. if (!uploadRes || uploadRes.code !== 200 || !uploadRes.value) {
  1631. loading.close()
  1632. this.$message.error('文件上传失败,请稍后重试')
  1633. return
  1634. }
  1635. // 文件上传成功后,更新报送资料项
  1636. const fileInfo = uploadRes.value
  1637. const updateData = {
  1638. // id: row.id,
  1639. // taskId: this.taskId,
  1640. // projectId: this.projectId,
  1641. // informationType: row.informationType,
  1642. // informationName: row.informationName,
  1643. // informationRequire: row.informationRequire,
  1644. // formatRequired: row.formatRequired,
  1645. // isRequired: row.isRequired,
  1646. ...row,
  1647. fileUrl: fileInfo.savePath || fileInfo.url, // 更新文件URL
  1648. }
  1649. // 调用更新API
  1650. const updateRes = await addOrUpdateTaskRequirement(updateData)
  1651. loading.close()
  1652. if (updateRes && updateRes.code === 200) {
  1653. this.$message.success('文件上传并更新成功!')
  1654. // 刷新报送资料列表
  1655. this.getTaskRequirementPage()
  1656. } else {
  1657. this.$message.error('文件上传成功,但更新资料失败')
  1658. }
  1659. } catch (error) {
  1660. console.error('文件上传失败:', error)
  1661. // this.$message.error('操作失败:' + (error.message || '未知错误'))
  1662. }
  1663. }
  1664. // 触发文件选择
  1665. input.click()
  1666. },
  1667. handleTemplateDownload(row) {
  1668. console.log('下载预置模版:', row)
  1669. if (!row.templateUrl && !row.fileUrl) {
  1670. this.$message.warning('该模版暂无文件可下载')
  1671. return
  1672. }
  1673. const downloadUrl = row.templateUrl || row.fileUrl
  1674. const link = document.createElement('a')
  1675. link.style.display = 'none'
  1676. link.href = downloadUrl
  1677. link.download = row.name ? `${row.name}_模版` : '模版文件'
  1678. document.body.appendChild(link)
  1679. link.click()
  1680. document.body.removeChild(link)
  1681. this.$message.success('开始下载模版')
  1682. },
  1683. handleDataUpload(row) {
  1684. // 预置模版:打开 CostAuditTable 弹窗进行模板选择
  1685. this.currentDataRequirementRow = row || null
  1686. this.loadCostTemplates()
  1687. this.selectedCostTemplates = []
  1688. this.costAuditDialogVisible = true
  1689. },
  1690. handleRemove(file, fileList) {
  1691. console.log(file, fileList)
  1692. },
  1693. handlePreview(file) {
  1694. console.log(file)
  1695. },
  1696. handleExceed(files, fileList) {
  1697. this.$message.warning(
  1698. `当前限制选择 3 个文件,本次选择了 ${files.length} 个文件,共选择了 ${
  1699. files.length + fileList.length
  1700. } 个文件`
  1701. )
  1702. },
  1703. beforeRemove(file, fileList) {
  1704. return this.$confirm(`确定移除 ${file.name}?`)
  1705. },
  1706. // 成本调查表点击事件(可点击时触发)
  1707. handleSurveyClick(row) {
  1708. console.log('点击成本调查表:', row.name)
  1709. // 实际逻辑:跳转页面、打开弹窗等
  1710. },
  1711. // 在线填报
  1712. handleOnlineFill(row) {
  1713. console.log('在线填报:', row.name)
  1714. this.currentSurveyData = row
  1715. this.onlineFillDialogVisible = true
  1716. },
  1717. // 修改已上传的表
  1718. handleModify(row) {
  1719. console.log('修改:', row.name)
  1720. // 实际逻辑:打开修改表单
  1721. },
  1722. // 数据下载
  1723. handleDataDownload(row) {
  1724. console.log('数据下载:', row.name)
  1725. if (!row.fileUrl && !row.dataUrl) {
  1726. this.$message.warning('该数据暂无文件可下载')
  1727. return
  1728. }
  1729. const downloadUrl = row.dataUrl || row.fileUrl
  1730. const link = document.createElement('a')
  1731. link.style.display = 'none'
  1732. link.href = downloadUrl
  1733. link.download = row.name ? `${row.name}_数据` : '数据文件'
  1734. document.body.appendChild(link)
  1735. link.click()
  1736. document.body.removeChild(link)
  1737. this.$message.success('开始下载数据')
  1738. },
  1739. // 补充材料相关方法
  1740. handleAddMaterial() {
  1741. this.materialDialogVisible = true
  1742. this.resetMaterialForm()
  1743. },
  1744. // 重置补充材料表单
  1745. resetMaterialForm() {
  1746. this.materialForm = {
  1747. informationType: '',
  1748. informationName: '',
  1749. informationRequire: '',
  1750. formatRequired: '',
  1751. fileList: [],
  1752. fileUrl: '',
  1753. }
  1754. if (this.$refs.materialForm) {
  1755. this.$refs.materialForm.resetFields()
  1756. }
  1757. },
  1758. // 补充材料弹窗取消
  1759. handleMaterialCancel() {
  1760. this.materialDialogVisible = false
  1761. this.resetMaterialForm()
  1762. },
  1763. // 补充材料弹窗确定
  1764. handleMaterialSubmit() {
  1765. this.$refs.materialForm.validate((valid) => {
  1766. if (valid) {
  1767. // 设置 loading 状态
  1768. this.loading.materialSubmit = true
  1769. // 这里可以添加提交逻辑
  1770. console.log('补充材料表单数据:', this.materialForm)
  1771. addOrUpdateTaskRequirement({
  1772. taskId: this.taskId,
  1773. projectId: this.projectId,
  1774. informationType: this.materialForm.informationType,
  1775. informationName: this.materialForm.informationName,
  1776. informationRequire: this.materialForm.informationRequire,
  1777. formatRequired: this.materialForm.formatRequired,
  1778. fileUrl: this.materialForm.fileUrl,
  1779. templateId: this.materialForm.templateId,
  1780. })
  1781. .then((res) => {
  1782. console.log('补充材料提交成功', res)
  1783. if (res && res.code === 200) {
  1784. this.loading.materialSubmit = false
  1785. Message.success('补充材料提交成功')
  1786. this.materialDialogVisible = false
  1787. this.resetMaterialForm()
  1788. this.getTaskRequirementPage()
  1789. } else {
  1790. Message.error('补充材料提交失败')
  1791. }
  1792. })
  1793. .catch((err) => {
  1794. console.error('补充材料提交失败', err)
  1795. Message.error('补充材料提交失败')
  1796. })
  1797. .finally(() => {
  1798. // 重置 loading 状态
  1799. this.loading.materialSubmit = false
  1800. })
  1801. } else {
  1802. Message.error('请完善表单信息')
  1803. return false
  1804. }
  1805. })
  1806. },
  1807. // 上传
  1808. // handleMaterialUpload(row, type) {
  1809. // let loading = null
  1810. // // 第一步:创建文件选择器
  1811. // const input = document.createElement('input')
  1812. // input.type = 'file'
  1813. // input.accept = '.pdf,.doc,.docx,.xls,.xlsx,.csv' // 允许的文件类型
  1814. // input.onchange = async (event) => {
  1815. // const file = event.target.files[0]
  1816. // if (!file) return
  1817. // try {
  1818. // // 校验文件大小(50MB)
  1819. // const maxSize = 50 * 1024 * 1024 // 50MB
  1820. // if (file.size > maxSize) {
  1821. // this.$message.error('文件大小不能超过50MB!')
  1822. // return
  1823. // }
  1824. // // 校验文件格式
  1825. // const allowedFormats = [
  1826. // '.pdf',
  1827. // '.doc',
  1828. // '.docx',
  1829. // '.xls',
  1830. // '.xlsx',
  1831. // 'csv',
  1832. // ]
  1833. // const fileName = file.name.toLowerCase()
  1834. // const isValidFormat = allowedFormats.some((format) =>
  1835. // fileName.endsWith(format)
  1836. // )
  1837. // if (!isValidFormat) {
  1838. // this.$message.error(
  1839. // '只允许上传.pdf,.doc,.docx,.xls,.xlsx,.csv格式的文件!'
  1840. // )
  1841. // return
  1842. // }
  1843. // // 显示遮罩层
  1844. // loading = this.$baseLoading(1, '文件上传中...')
  1845. // // 第三步:创建FormData并上传文件
  1846. // const formData = new FormData()
  1847. // formData.append('file', file)
  1848. // // 先调用上传API
  1849. // const uploadRes = await uploadFile('/api/file/v1/upload', formData)
  1850. // // 第四步:检查上传结果
  1851. // if (!uploadRes || !uploadRes.value) {
  1852. // // this.$message.error('文件上传失败!');
  1853. // return
  1854. // }
  1855. // // 第五步:文件上传成功后,再更新数据
  1856. // const fileInfo = uploadRes.value
  1857. // // 创建更新数据对象
  1858. // const updateData = {
  1859. // ...row,
  1860. // fileUrl: fileInfo?.savePath, // 更新扫描件URL
  1861. // }
  1862. // // 第六步:调用更新API
  1863. // // await updateCostProjectDocument(updateData)
  1864. // // // 第七步:更新成功,显示提示并刷新
  1865. // // this.$message.success('文件上传成功并更新数据!')
  1866. // // this.$emit('refresh', this.project.projectId) // 通知父组件刷新
  1867. // } catch (error) {
  1868. // // 错误处理
  1869. // this.$message.error('操作失败:' + (error.message || '未知错误'))
  1870. // } finally {
  1871. // // 关闭遮罩层
  1872. // loading.close()
  1873. // }
  1874. // }
  1875. // // 触发文件选择
  1876. // input.click()
  1877. // },
  1878. // 文件上传前验证
  1879. beforeMaterialUpload(file) {
  1880. // 预置模版不允许在此上传
  1881. if (this.isTemplateFormat) {
  1882. this.$message.warning(
  1883. '“预置模版”不支持直接上传,请按模版入口添加材料'
  1884. )
  1885. return false
  1886. }
  1887. const ext = '.' + file.name.split('.').pop().toLowerCase()
  1888. const mime = (file.type || '').toLowerCase()
  1889. // 文档格式
  1890. if (this.isDocumentFormat) {
  1891. const allowedExt = ['.pdf', '.doc', '.docx']
  1892. const allowedMime = [
  1893. 'application/pdf',
  1894. 'application/msword',
  1895. 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  1896. ]
  1897. const ok = allowedExt.includes(ext) || allowedMime.includes(mime)
  1898. if (!ok) {
  1899. this.$message.error('当前格式要求为文档,仅支持 pdf, doc, docx')
  1900. return false
  1901. }
  1902. }
  1903. // Excel 格式
  1904. if (this.isExcelFormat) {
  1905. const allowedExt = ['.xls', '.xlsx']
  1906. const allowedMime = [
  1907. 'application/vnd.ms-excel',
  1908. 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  1909. ]
  1910. const ok = allowedExt.includes(ext) || allowedMime.includes(mime)
  1911. if (!ok) {
  1912. this.$message.error('当前格式要求为表格,仅支持 xls, xlsx')
  1913. return false
  1914. }
  1915. }
  1916. // 检查文件大小 (50MB)
  1917. const isLt50M = file.size / 1024 / 1024 < 50
  1918. if (!isLt50M) {
  1919. this.$message.error('文件大小不能超过 50MB!')
  1920. return false
  1921. }
  1922. return true
  1923. },
  1924. // 自定义上传方法
  1925. async handleMaterialUpload(options) {
  1926. const { file, onProgress, onSuccess, onError } = options
  1927. // 检查是否已经上传了文件
  1928. if (
  1929. this.materialForm.fileList &&
  1930. this.materialForm.fileList.length >= 1
  1931. ) {
  1932. this.$message.warning('只能上传一个文件,请先删除已上传的文件')
  1933. // 手动触发onError回调
  1934. if (onError) {
  1935. onError(new Error('只能上传一个文件'))
  1936. }
  1937. return
  1938. }
  1939. const formData = new FormData()
  1940. formData.append('file', file)
  1941. try {
  1942. // 显示上传进度
  1943. if (onProgress) {
  1944. onProgress({ percent: 0 })
  1945. }
  1946. // 调用上传API
  1947. const uploadRes = await uploadFile('/api/file/v1/upload', formData, {
  1948. onUploadProgress: (progressEvent) => {
  1949. // 计算上传进度
  1950. if (progressEvent.total) {
  1951. const percent = Math.round(
  1952. (progressEvent.loaded * 100) / progressEvent.total
  1953. )
  1954. if (onProgress) {
  1955. onProgress({ percent })
  1956. }
  1957. }
  1958. },
  1959. })
  1960. // 检查上传结果
  1961. if (uploadRes && uploadRes.code === 200 && uploadRes.value) {
  1962. const fileInfo = uploadRes.value
  1963. // 构造文件信息对象,符合element-ui upload组件的格式
  1964. const fileObj = {
  1965. uid: file.uid,
  1966. name: file.name,
  1967. status: 'success',
  1968. size: file.size,
  1969. response: fileInfo,
  1970. url: fileInfo.savePath || fileInfo.url,
  1971. }
  1972. if (onSuccess) {
  1973. onSuccess(fileInfo, fileObj)
  1974. this.materialForm.fileUrl = fileInfo.savePath || fileInfo.url
  1975. }
  1976. this.$message.success(`${file.name} 上传成功`)
  1977. } else {
  1978. throw new Error(uploadRes?.message || '上传失败,请稍后重试')
  1979. }
  1980. } catch (error) {
  1981. console.error('文件上传失败:', error)
  1982. this.$message.error(`文件上传失败:${error.message || '未知错误'}`)
  1983. if (onError) {
  1984. onError(error)
  1985. }
  1986. }
  1987. },
  1988. // 打开预置模版弹窗
  1989. openLegalDialog() {
  1990. // 需求:改用 CostAuditTable 弹窗选择模版
  1991. this.loadCostTemplates()
  1992. this.selectedCostTemplates = []
  1993. this.costAuditDialogVisible = true
  1994. console.log(
  1995. '打开 CostAuditTable 弹窗选择模版',
  1996. this.costAuditDialogVisible
  1997. )
  1998. },
  1999. // 接收模版弹窗选择结果
  2000. handleLegalConfirm(payload) {
  2001. // 兼容不同返回字段,将关键信息落到表单中
  2002. const url =
  2003. payload?.templateUrl ||
  2004. payload?.url ||
  2005. payload?.savePath ||
  2006. payload?.fileUrl ||
  2007. ''
  2008. if (url) {
  2009. // 将选中的模版链接记录在fileUrl,后端若有专属字段可按需调整
  2010. this.materialForm.fileUrl = url
  2011. // 在UI上体现已选择(以文件列表形式展示一个只读项)
  2012. this.materialForm.fileList = [
  2013. {
  2014. uid: 'tpl-' + Date.now(),
  2015. name: payload?.name || payload?.templateName || '已选择的模版',
  2016. status: 'success',
  2017. url: url,
  2018. response: payload,
  2019. },
  2020. ]
  2021. this.$message.success('已选择模版')
  2022. } else {
  2023. this.$message.info('未获取到模版链接')
  2024. }
  2025. this.legalDialogVisible = false
  2026. },
  2027. // 拉取模板列表(CostAuditTable用)
  2028. loadCostTemplates() {
  2029. allcurrentCostSurveyList({
  2030. pageNum: this.costTemplatePagination.currentPage,
  2031. pageSize: this.costTemplatePagination.pageSize,
  2032. }).then((res) => {
  2033. this.costTemplateData = res?.value?.records || res?.value || []
  2034. this.costTemplatePagination.total =
  2035. res?.value?.total || this.costTemplateData.length || 0
  2036. })
  2037. },
  2038. handleCostTemplatePaginationChange({ currentPage, pageSize }) {
  2039. this.costTemplatePagination.currentPage = currentPage
  2040. this.costTemplatePagination.pageSize = pageSize
  2041. this.loadCostTemplates()
  2042. },
  2043. handleCostTemplateSelectionChange(val) {
  2044. if (Array.isArray(val) && val.length > 1) {
  2045. this.$message.warning('最多只能选择一个模板')
  2046. return
  2047. }
  2048. this.selectedCostTemplates = val || []
  2049. },
  2050. submitCostAuditTemplate() {
  2051. if (
  2052. !this.selectedCostTemplates ||
  2053. this.selectedCostTemplates.length !== 1
  2054. ) {
  2055. this.$message.error('请选择一个模板')
  2056. return
  2057. }
  2058. const chosen = this.selectedCostTemplates[0]
  2059. const url = chosen?.templateUrl || chosen?.url || chosen?.savePath || ''
  2060. // 记录到表单
  2061. this.materialForm.fileUrl = url
  2062. this.materialForm.templateId =
  2063. chosen?.surveyTemplateId || chosen?.templateId || chosen?.id || ''
  2064. this.materialForm.fileList = [
  2065. {
  2066. uid: 'tpl-' + Date.now(),
  2067. name: chosen?.surveyTemplateName || '已选择的模版',
  2068. status: 'success',
  2069. url: url,
  2070. response: chosen,
  2071. },
  2072. ]
  2073. this.$message.success('已选择模版')
  2074. this.costAuditDialogVisible = false
  2075. },
  2076. // 上传成功回调
  2077. handleMaterialUploadSuccess(response, file, fileList) {
  2078. // 更新文件列表,添加文件URL信息
  2079. this.materialForm.fileList = fileList.map((item) => {
  2080. if (item.uid === file.uid && response) {
  2081. return {
  2082. ...item,
  2083. url: response.savePath || response.url || item.url,
  2084. response: response,
  2085. }
  2086. }
  2087. return item
  2088. })
  2089. },
  2090. // 上传进度回调
  2091. handleMaterialUploadProgress(event, file, fileList) {
  2092. // 可以在这里显示上传进度,element-ui 会自动处理
  2093. },
  2094. // 上传失败回调
  2095. handleMaterialUploadError(err, file, fileList) {
  2096. console.error('文件上传错误:', err)
  2097. this.$message.error(`${file.name} 上传失败`)
  2098. // 从文件列表中移除失败的文件
  2099. this.materialForm.fileList = fileList.filter(
  2100. (item) => item.uid !== file.uid
  2101. )
  2102. },
  2103. // 超出文件数量限制
  2104. handleMaterialExceed(files, fileList) {
  2105. this.$message.warning(
  2106. `当前限制选择 5 个文件,本次选择了 ${files.length} 个文件,共选择了 ${fileList.length} 个文件`
  2107. )
  2108. },
  2109. // 移除文件
  2110. handleMaterialRemove(file, fileList) {
  2111. this.materialForm.fileList = fileList
  2112. this.$message.info(`${file.name} 已移除`)
  2113. },
  2114. // 成本调查表分页 - 页码改变
  2115. handleCostSurveyPageChange(page) {
  2116. this.costSurveyPagination.currentPage = page
  2117. // 调用接口重新获取数据
  2118. this.getCostSurveyList()
  2119. },
  2120. // 成本调查表分页 - 每页条数改变
  2121. handleCostSurveySizeChange(size) {
  2122. this.costSurveyPagination.pageSize = size
  2123. this.costSurveyPagination.currentPage = 1
  2124. // 调用接口重新获取数据
  2125. this.getCostSurveyList()
  2126. },
  2127. // 处理单记录调查表保存
  2128. handleSurveyFormSave(data) {
  2129. // 保存成功后刷新列表
  2130. this.getCostSurveyList()
  2131. },
  2132. // 获取监审期间数组(从立项信息中获取)
  2133. getAuditPeriodArray() {
  2134. // 优先从 formData.basicInfo.auditPeriodArray 获取
  2135. if (
  2136. this.formData &&
  2137. this.formData.basicInfo &&
  2138. this.formData.basicInfo.auditPeriodArray &&
  2139. Array.isArray(this.formData.basicInfo.auditPeriodArray)
  2140. ) {
  2141. const periods = this.formData.basicInfo.auditPeriodArray
  2142. .map((item) => item.value)
  2143. .filter((val) => val && val.trim())
  2144. if (periods.length > 0) {
  2145. return periods
  2146. }
  2147. }
  2148. // 其次从 taskInfo.auditPeriod 获取
  2149. if (this.taskInfo && this.taskInfo.auditPeriod) {
  2150. if (Array.isArray(this.taskInfo.auditPeriod)) {
  2151. return this.taskInfo.auditPeriod
  2152. } else if (typeof this.taskInfo.auditPeriod === 'string') {
  2153. // 如果是字符串,尝试解析
  2154. return this.taskInfo.auditPeriod
  2155. .split(',')
  2156. .map((p) => p.trim())
  2157. .filter((p) => p)
  2158. }
  2159. }
  2160. // 最后从 formData.basicInfo.auditPeriod 获取
  2161. if (
  2162. this.formData &&
  2163. this.formData.basicInfo &&
  2164. this.formData.basicInfo.auditPeriod
  2165. ) {
  2166. if (Array.isArray(this.formData.basicInfo.auditPeriod)) {
  2167. return this.formData.basicInfo.auditPeriod
  2168. } else if (typeof this.formData.basicInfo.auditPeriod === 'string') {
  2169. return this.formData.basicInfo.auditPeriod
  2170. .split(',')
  2171. .map((p) => p.trim())
  2172. .filter((p) => p)
  2173. }
  2174. }
  2175. return null
  2176. },
  2177. },
  2178. }
  2179. </script>
  2180. <style scoped lang="scss">
  2181. .drawer-title {
  2182. font-size: 18px;
  2183. font-weight: 600;
  2184. color: #303133;
  2185. }
  2186. .audit-task-manage-container {
  2187. padding: 20px;
  2188. height: 100%;
  2189. overflow-y: auto;
  2190. h3 {
  2191. text-align: center;
  2192. margin-top: 0;
  2193. margin-bottom: 20px;
  2194. font-size: 18px;
  2195. font-weight: bold;
  2196. }
  2197. }
  2198. .cost-opinion-section,
  2199. .feedback-section {
  2200. border: 1px solid #dcdcdc;
  2201. border-radius: 4px;
  2202. padding: 20px;
  2203. margin-bottom: 20px;
  2204. background-color: #f9f9f9;
  2205. }
  2206. .opinion-item,
  2207. .feedback-item {
  2208. margin-bottom: 20px;
  2209. display: flex;
  2210. align-items: center;
  2211. label {
  2212. width: 120px;
  2213. margin-bottom: 8px;
  2214. font-weight: bold;
  2215. color: #1549ad;
  2216. }
  2217. }
  2218. .file-info {
  2219. margin-bottom: 10px;
  2220. }
  2221. .file-info a {
  2222. color: #0066cc;
  2223. text-decoration: none;
  2224. }
  2225. .file-info a:hover {
  2226. text-decoration: underline;
  2227. }
  2228. .upload-btn {
  2229. background-color: #007bff;
  2230. color: #fff;
  2231. border: none;
  2232. border-radius: 4px;
  2233. padding: 8px 16px;
  2234. cursor: pointer;
  2235. }
  2236. .upload-btn:hover {
  2237. background-color: #0056b3;
  2238. }
  2239. .cost-period-container {
  2240. display: flex;
  2241. align-items: flex-start;
  2242. }
  2243. .add-cost-year-btn {
  2244. margin-right: 10px;
  2245. }
  2246. .delete-cost-year-btn {
  2247. margin-left: 10px;
  2248. }
  2249. .cost-years-wrapper {
  2250. flex: 1;
  2251. }
  2252. .cost-year-item {
  2253. margin-bottom: 10px;
  2254. display: flex;
  2255. align-items: center;
  2256. }
  2257. .ml10 {
  2258. margin-left: 10px;
  2259. }
  2260. .text-danger {
  2261. color: #d9001b;
  2262. }
  2263. // 类别头行样式
  2264. ::v-deep .category-header-row {
  2265. background-color: #f5f7fa !important;
  2266. td {
  2267. background-color: #f5f7fa !important;
  2268. padding: 12px 16px !important;
  2269. }
  2270. }
  2271. .category-header-cell {
  2272. font-weight: 700;
  2273. color: #303133;
  2274. padding-left: 16px;
  2275. }
  2276. // 侧边弹窗样式优化
  2277. ::v-deep .el-drawer__body {
  2278. overflow-y: auto;
  2279. padding: 0;
  2280. }
  2281. ::v-deep .el-drawer__header {
  2282. margin-bottom: 0;
  2283. padding: 20px;
  2284. border-bottom: 1px solid #e4e7ed;
  2285. }
  2286. ::v-deep .el-drawer__body {
  2287. padding: 0;
  2288. }
  2289. .document-edit-container {
  2290. display: flex;
  2291. .document-params {
  2292. width: 55%;
  2293. }
  2294. .document-preview {
  2295. width: 45%;
  2296. }
  2297. }
  2298. </style>