query.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  1. <template>
  2. <div class="hc-layout-box">
  3. <div class="hc-layout-left-box" id="wbs-left-tree" :style="'width:' + leftWidth + 'px;'">
  4. <div class="hc-project-box">
  5. <div class="hc-project-icon-box">
  6. <HcIcon name="stack"/>
  7. </div>
  8. <div class="ml-2 project-name-box">
  9. <span class="text-xl text-cut project-alias">{{projectInfo['projectAlias']}}</span>
  10. <div class="text-xs text-cut project-name">{{projectInfo['name']}}</div>
  11. </div>
  12. </div>
  13. <div class="hc-tree-box">
  14. <div class="hc-search-tree-val">
  15. <el-input v-model="searchTreeVal" block size="large" placeholder="请输入名称关键词检索" clearable @keyup="searchTreeKeyUp">
  16. <template #suffix>
  17. <HcIcon name="search-2" ui="text-xl"/>
  18. </template>
  19. </el-input>
  20. </div>
  21. <div class="hc-tree-scrollbar" v-loading="treeLoading" element-loading-text="获取数据中...">
  22. <el-scrollbar>
  23. <KeepAlive>
  24. <template v-if="isSearchTree">
  25. <HcTreeData :datas="searchTreeData" :autoExpandKeys="treeAutoExpandKeys" isColor @nodeTap="wbsElTreeClick"/>
  26. </template>
  27. <template v-else>
  28. <WbsTree :autoExpandKeys="treeAutoExpandKeys" :projectId="projectId" :contractId="contractId" isColor @nodeTap="wbsElTreeClick"/>
  29. </template>
  30. </KeepAlive>
  31. </el-scrollbar>
  32. </div>
  33. </div>
  34. <div class="hc-tree-foot-tip-box">
  35. <div class="dot-view green">已审批</div>
  36. <div class="dot-view black">未填报</div>
  37. <div class="dot-view orange">已填报-待审批</div>
  38. <div class="dot-view blue">已填报-未上报</div>
  39. </div>
  40. <!--左右拖动-->
  41. <div class="horizontal-drag-line" @mousedown="onmousedown"/>
  42. </div>
  43. <div class="hc-layout-content-box">
  44. <HcCard :scrollbar="false" actionSize="lg">
  45. <template #header>
  46. <HcTooltip keys="query_report">
  47. <el-button type="primary" hc-btn :disabled="tableCheckedKeys.length <= 0" :loading="reportLoading" @click="reportModalClick">
  48. <HcIcon name="send-plane-2"/>
  49. <span>上报</span>
  50. </el-button>
  51. </HcTooltip>
  52. <HcTooltip keys="query_download">
  53. <el-button hc-btn :disabled="tableCheckedKeys.length <= 0" :loading="downloadLoading" @click="batchDownload">
  54. <HcIcon name="download"/>
  55. <span>下载</span>
  56. </el-button>
  57. </HcTooltip>
  58. <HcTooltip keys="query_print">
  59. <el-button hc-btn :disabled="tableCheckedKeys.length <= 0" :loading="printLoading" @click="batchPrint">
  60. <HcIcon name="printer"/>
  61. <span>打印</span>
  62. </el-button>
  63. </HcTooltip>
  64. <HcTooltip keys="query_abolish">
  65. <el-button hc-btn :disabled="tableCheckedKeys.length <= 0" @click="batchAbolishClick">
  66. <HcIcon name="delete-bin-3"/>
  67. <span>废除</span>
  68. </el-button>
  69. </HcTooltip>
  70. <HcTooltip keys="query_local_attestation">
  71. <el-button hc-btn :disabled="tableCheckedKeys.length <= 0" :loading="localLoading" @click="batchLocal">
  72. <HcIcon name="folder-download"/>
  73. <span>本地验签</span>
  74. </el-button>
  75. </HcTooltip>
  76. <HcTooltip keys="query_online_attestation">
  77. <el-button hc-btn :disabled="tableCheckedKeys.length <= 0" :loading="onlineLoading" @click="batchOnline">
  78. <HcIcon name="cloud"/>
  79. <span>在线验签</span>
  80. </el-button>
  81. </HcTooltip>
  82. </template>
  83. <template #search>
  84. <div class="flex items-center">
  85. <div class="w-40">
  86. <el-select v-model="searchForm.taskStatus" placeholder="流程状态" clearable>
  87. <el-option v-for="item in processStatusData" :key="item.value" :label="item['dictValue']" :value="item['dictKey']"/>
  88. </el-select>
  89. </div>
  90. <div class="w-40 ml-2">
  91. <el-select v-model="searchForm.fileUserIdAndName" placeholder="填报人" clearable>
  92. <el-option v-for="item in reportingPersonData" :key="item.value" :label="item['label']" :value="item['value']"/>
  93. </el-select>
  94. </div>
  95. <div class="w-40 ml-2">
  96. <el-select v-model="searchForm.sourceType" placeholder="文件类型" clearable>
  97. <el-option v-for="item in fileTypeData" :key="item.value" :label="item['dictValue']" :value="item['dictKey']"/>
  98. </el-select>
  99. </div>
  100. <div class="w-32 ml-2">
  101. <el-select v-model="searchForm.reportNumber" placeholder="上报批次" clearable>
  102. <el-option v-for="item in reportBatchData" :key="item.value" :label="item['label']" :value="item['value']"/>
  103. </el-select>
  104. </div>
  105. <div class="w-64 ml-2">
  106. <HcDatePicker :dates="betweenTime" clearable @change="betweenTimeUpdate"/>
  107. </div>
  108. <div class="w-60 ml-2">
  109. <el-input v-model="searchForm.queryValue" placeholder="请输入名称关键词检索" clearable @keyup="keyUpEvent"/>
  110. </div>
  111. <div class="ml-2">
  112. <el-button type="primary" @click="searchClick">
  113. <HcIcon name="search-2"/>
  114. <span>搜索</span>
  115. </el-button>
  116. </div>
  117. </div>
  118. </template>
  119. <template #extra>
  120. <template v-if="contractInfo?.contractType === 2 || contractInfo?.contractType === 3">
  121. <HcNewSwitch :datas="contractTypeTab" :keys="contractTypeTabKey" @change="contractTypeTabChange"/>
  122. </template>
  123. </template>
  124. <HcTable ref="tableListRef" :column="tableListColumn" :datas="tableListData" :loading="tableLoading" isCheck @selection-change="tableSelectionChange">
  125. <template #name="{row}">
  126. <span class="text-link" @click="tableRowName(row)">{{row?.name}}</span>
  127. </template>
  128. <template #waitingUserList="{row}">
  129. <template v-for="item in row['waitingUserList']">
  130. <el-tag :type="`${item.status === 2 ? 'success' : item.status === 3 ? 'warning' : item.status === 999 ? 'danger' : 'info'}`"
  131. class="mx-1" effect="dark" v-if="item['waitingUserName']">{{item['waitingUserName']}}</el-tag>
  132. </template>
  133. </template>
  134. </HcTable>
  135. <template #action>
  136. <div class="lr-dialog-footer">
  137. <div class="left">
  138. <span class="text-success">任务人员中:</span>
  139. <el-tag type="info" class="mx-1" effect="dark">未签字</el-tag>
  140. <el-tag type="success" class="mx-1" effect="dark">已签字</el-tag>
  141. <el-tag type="warning" class="mx-1" effect="dark">已废除</el-tag>
  142. <el-tag type="danger" class="mx-1" effect="dark">签字异常</el-tag>
  143. </div>
  144. <div class="right">
  145. <HcPages :pages="searchForm" @change="pageChange"/>
  146. </div>
  147. </div>
  148. </template>
  149. </HcCard>
  150. </div>
  151. <!--批量上报审批-->
  152. <HcReportModal title="批量上报审批"
  153. url="informationWriteQuery/batchTask"
  154. :show="showReportModal"
  155. :projectId="projectId"
  156. :contractId="contractId"
  157. :taskName="reportTaskName"
  158. :ids="reportIds"
  159. isDatas
  160. :datas="reportDatas"
  161. @hide="showReportModal = false"
  162. @finish="showReportFinish"
  163. @tagClose="reportTaskTagClose"
  164. />
  165. </div>
  166. </template>
  167. <script setup>
  168. import {ref, watch, onMounted} from "vue";
  169. import {useAppStore} from "~src/store";
  170. import WbsTree from "./components/WbsTree.vue"
  171. import HcTreeData from "./components/HcTreeData.vue"
  172. import {getStoreData, setStoreData} from '~src/utils/storage'
  173. import {isType, downloadBlob} from "vue-utils-plus"
  174. import queryApi from '~api/data-fill/query';
  175. import {eVisaTaskCheckApi} from "~api/other"
  176. //变量
  177. const useAppState = useAppStore()
  178. const {getObjValue, getArrValue, isObjNull} = isType()
  179. const projectId = ref(useAppState.getProjectId);
  180. const contractId = ref(useAppState.getContractId);
  181. const projectInfo = ref(useAppState.getProjectInfo);
  182. const contractInfo = ref(useAppState.getContractInfo);
  183. const isCollapse = ref(useAppState.getCollapse)
  184. //监听
  185. watch(() => [
  186. useAppState.getCollapse
  187. ], ([Collapse]) => {
  188. isCollapse.value = Collapse
  189. })
  190. //自动展开缓存
  191. const treeAutoExpandKeys = ref(getStoreData('wbsTreeExpandKeys') || [])
  192. //渲染完成
  193. onMounted(() => {
  194. getFileUser()
  195. getReportNumber()
  196. getFirstTaskStatus()
  197. getDictBizClassify()
  198. })
  199. //树搜索
  200. const isSearchTree = ref(false)
  201. const searchTreeVal = ref('')
  202. const searchTreeData = ref([])
  203. //回车
  204. const treeLoading = ref(false)
  205. const searchTreeKeyUp = (e) => {
  206. if (e.key === "Enter") {
  207. searchTreeClick()
  208. }
  209. }
  210. const searchTreeClick = async () => {
  211. if (searchTreeVal.value) {
  212. isSearchTree.value = true
  213. treeLoading.value = true
  214. const {error, code, data} = await queryApi.searchContractTree({
  215. contractId: contractId.value,
  216. queryValue: searchTreeVal.value
  217. })
  218. //判断状态
  219. if (!error && code === 200) {
  220. searchTreeData.value = getArrValue(data)
  221. treeLoading.value = false
  222. } else {
  223. treeLoading.value = false
  224. searchTreeData.value = []
  225. }
  226. } else {
  227. treeLoading.value = false
  228. isSearchTree.value = false
  229. }
  230. }
  231. //树相关的变量
  232. const primaryKeyId = ref('')
  233. const nodeItemInfo = ref({})
  234. const nodeDataInfo = ref({})
  235. //树被点击
  236. const wbsElTreeClick = ({node, data, keys}) => {
  237. nodeItemInfo.value = node
  238. nodeDataInfo.value = data
  239. primaryKeyId.value = data['primaryKeyId'] || ''
  240. //缓存自动展开
  241. treeAutoExpandKeys.value = keys
  242. setStoreData('wbsTreeExpandKeys',keys)
  243. //改变搜索表单数据
  244. searchForm.value.wbsId = data['primaryKeyId']
  245. searchForm.value.contractIdRelation = data['contractIdRelation']
  246. searchForm.value.current = 1;
  247. getTableData()
  248. }
  249. //搜索条件
  250. const processStatusData = ref([]) //流程状态
  251. const reportingPersonData = ref([]) //填报人
  252. const fileTypeData = ref([]) //文件类型
  253. const reportBatchData = ref([]) //上报批次
  254. //获取所有填报人
  255. const getFileUser = async () => {
  256. const {error, code, data} = await queryApi.getFileUser({
  257. contractId: contractId.value
  258. })
  259. //判断状态
  260. if (!error && code === 200) {
  261. let res = getArrValue(data), userArr = [];
  262. res.forEach(item => {
  263. userArr.push({label: item['userName'], value: `${item['userId']}-${item['userName']}`})
  264. })
  265. reportingPersonData.value = userArr
  266. } else {
  267. reportingPersonData.value = []
  268. }
  269. }
  270. //获取上报批次
  271. const getReportNumber = async () => {
  272. const {error, code, data} = await queryApi.getReportNumber({
  273. contractId: contractId.value
  274. })
  275. //判断状态
  276. if (!error && code === 200) {
  277. reportBatchData.value = getArrValue(data)
  278. } else {
  279. reportBatchData.value = []
  280. }
  281. }
  282. //获取流程状态
  283. const getFirstTaskStatus = async () => {
  284. const {error, code, data} = await queryApi.getFirstTaskStatus()
  285. //判断状态
  286. if (!error && code === 200) {
  287. processStatusData.value = getArrValue(data)
  288. } else {
  289. processStatusData.value = []
  290. }
  291. }
  292. //获取流程状态分类和文件类型分类
  293. const getDictBizClassify = async () => {
  294. const {error, code, data} = await queryApi.getDictBizClassify({
  295. contractId: contractId.value,
  296. code: 'fileType'
  297. })
  298. //判断状态
  299. if (!error && code === 200) {
  300. fileTypeData.value = getArrValue(data)
  301. } else {
  302. fileTypeData.value = []
  303. }
  304. }
  305. //搜索表单
  306. const searchForm = ref({
  307. taskStatus: null, fileUserIdAndName: null, sourceType: null, reportNumber: null, betweenTime: null,
  308. queryValue: null, contractIdRelation: null, wbsId: null, current: 1, size: 20, total: 0
  309. })
  310. //结构类型tab数据和相关处理
  311. const contractTypeTabKey = ref(1)
  312. const contractTypeTab = ref([
  313. {key:'1', name: '施工数据'},
  314. {key:'2', name: '监理数据'}
  315. ]);
  316. const contractTypeTabChange = (item) => {
  317. contractTypeTabKey.value = item?.key;
  318. searchClick()
  319. }
  320. //获取合同段类型
  321. const getContractTypeKey = () => {
  322. const { contractType } = contractInfo.value;
  323. if (contractType === 2 || contractType === 3) {
  324. return contractTypeTabKey.value ?? '1'
  325. } else {
  326. return null
  327. }
  328. }
  329. //日期时间被选择
  330. const betweenTime = ref(null)
  331. const betweenTimeUpdate = ({arr,query}) => {
  332. betweenTime.value = arr
  333. searchForm.value.betweenTime = query
  334. }
  335. //回车搜索
  336. const keyUpEvent = (e) => {
  337. if (e.key === "Enter") {
  338. searchForm.value.current = 1;
  339. getTableData()
  340. }
  341. }
  342. //搜索
  343. const searchClick = () => {
  344. searchForm.value.current = 1;
  345. getTableData()
  346. }
  347. //分页被点击
  348. const pageChange = ({current, size}) => {
  349. searchForm.value.current = current
  350. searchForm.value.size = size
  351. getTableData()
  352. }
  353. //获取数据
  354. const tableListRef = ref(null)
  355. const tableLoading = ref(false)
  356. const tableListColumn = ref([
  357. {key:'name', name: '文件名称'},
  358. {key:'startTime', name: '开始时间'},
  359. {key:'taskStatusStr', name: '流程状态'},
  360. {key:'reportNumber', name: '上报批次'},
  361. {key:'fileUserIdAndName', name: '填报人'},
  362. {key:'waitingUserList', name: '任务人'}
  363. ])
  364. const tableListData = ref([])
  365. const getTableData = async () => {
  366. if (!!searchForm.value.wbsId) {
  367. tableListRef.value?.clearSelection()
  368. tableCheckedKeys.value = []
  369. tableLoading.value = true
  370. const classifyType = getContractTypeKey();
  371. const { error, code, data } = await queryApi.getPageData({
  372. projectId: projectId.value,
  373. contractId: contractId.value,
  374. ...searchForm.value,
  375. classifyType: classifyType
  376. })
  377. //处理数据
  378. tableLoading.value = false
  379. if (!error && code === 200) {
  380. tableListData.value = getArrValue(data['records'])
  381. searchForm.value.total = data.total || 0
  382. } else {
  383. tableListData.value = []
  384. searchForm.value.total = 0
  385. }
  386. } else {
  387. window?.$message?.warning('请先选择一个树节点')
  388. }
  389. }
  390. //多选
  391. const tableCheckedKeys = ref([]);
  392. const tableSelectionChange = (rows) => {
  393. tableCheckedKeys.value = rows.filter((item) => {
  394. return (item??'') !== '';
  395. })
  396. }
  397. //名称被点击
  398. const tableRowName = (row) => {
  399. //如果 evisaPdfUrl 不为空,使用evisaPdfUrl,反之使用pdfUrl
  400. if (row['evisaPdfUrl']) {
  401. window.open(row['evisaPdfUrl'],'_blank')
  402. } else if (row['pdfUrl']) {
  403. window.open(row['pdfUrl'],'_blank')
  404. } else {
  405. window.$message?.warning('文件不存在')
  406. }
  407. }
  408. //上报
  409. const reportIds = ref('')
  410. const reportTaskName = ref('')
  411. const reportDatas = ref([])
  412. const showReportModal = ref(false)
  413. const reportLoading = ref(false)
  414. const reportModalClick = async () => {
  415. const rows = tableCheckedKeys.value;
  416. //判断是否满足条件
  417. const result = rows.every(({status})=> {
  418. return status === 0 || status === 3
  419. })
  420. //判断状态
  421. if (result) {
  422. reportLoading.value = true
  423. const taskCheck = await eVisaTaskCheckApi({
  424. projectId: projectId.value,
  425. contractId: contractId.value
  426. })
  427. if (taskCheck) {
  428. //初始ID
  429. const row = getObjValue(rows[0])
  430. reportIds.value = rowsToId(rows)
  431. //设置任务数据
  432. let reportDataArr = []
  433. rows.forEach(item => {
  434. reportDataArr.push({
  435. id: item?.id,
  436. name: item?.name
  437. })
  438. })
  439. reportDatas.value = reportDataArr
  440. //设置任务名称
  441. reportTaskName.value = rows.length > 1 ? `${row.name}等${rows.length}个文件` : row.name
  442. reportLoading.value = false
  443. showReportModal.value = true
  444. } else {
  445. reportLoading.value = false
  446. }
  447. } else {
  448. window.$message?.warning('已上报的文件不能进行再次上报,若要重新上报,要先撤回之前的上报,再重新上报')
  449. }
  450. }
  451. //上报的审批内容移除
  452. const reportTaskTagClose = (index) => {
  453. const row = tableCheckedKeys.value[index];
  454. tableListRef.value?.toggleRowSelection(row,false)
  455. }
  456. //上报完成
  457. const showReportFinish = () => {
  458. showReportModal.value = false
  459. getTableData()
  460. }
  461. //下载
  462. const downloadLoading = ref(false)
  463. const batchDownload = async () => {
  464. const rows = tableCheckedKeys.value;
  465. const ids = rowsToId(rows)
  466. //批量下载
  467. downloadLoading.value = true
  468. const { error, disposition, res } = await queryApi.batchDownloadFileToZip({ids: ids})
  469. //处理数据
  470. downloadLoading.value = false
  471. if (!error) {
  472. if (disposition) {
  473. downloadBlob(res,disposition)
  474. } else {
  475. window.$message?.error('数据异常')
  476. }
  477. }
  478. }
  479. //打印
  480. const printLoading = ref(false)
  481. const batchPrint = async () => {
  482. const rows = tableCheckedKeys.value;
  483. const ids = rowsToId(rows)
  484. //批量下载
  485. printLoading.value = true
  486. const { error, code, data } = await queryApi.batchPrint({ids: ids})
  487. //处理数据
  488. printLoading.value = false
  489. if (!error && code === 200) {
  490. console.log(data)
  491. }
  492. }
  493. //废除
  494. const batchAbolishClick = () => {
  495. const rows = tableCheckedKeys.value;
  496. //判断是否满足条件
  497. const result = rows.every(({status})=> {
  498. return status !== 0 && status !== 3
  499. })
  500. //判断状态
  501. if (result) {
  502. //拼接ID
  503. const ids = rowsToId(rows)
  504. window?.$messageBox?.alert('是否废除勾选的已上报文件?', '废除文件', {
  505. showCancelButton: true,
  506. confirmButtonText: '确定废除',
  507. cancelButtonText: '取消',
  508. callback: (action) => {
  509. if (action === 'confirm') {
  510. batchAbolishSave(ids)
  511. }
  512. }
  513. })
  514. } else {
  515. window.$message?.warning('未上报的文件不能废除')
  516. }
  517. }
  518. //废除勾选的已上报文件
  519. const batchAbolishSave = async (ids) => {
  520. const { error, code } = await queryApi.batchAbolish({ids: ids})
  521. //处理数据
  522. if (!error && code === 200) {
  523. window.$message?.success('批量废除成功')
  524. tableCheckedKeys.value = []
  525. getTableData()
  526. }
  527. }
  528. //本地验签
  529. const localLoading = ref(false)
  530. const batchLocal = async () => {
  531. const rows = tableCheckedKeys.value;
  532. //判断是否满足条件
  533. const result = rows.every(({status})=> {
  534. return status === 2
  535. })
  536. //判断状态
  537. if (result) {
  538. const ids = rowsToId(rows)
  539. //请求数据
  540. localLoading.value = true
  541. const { error, code, data } = await queryApi.localVerify({
  542. ids: ids
  543. })
  544. //处理数据
  545. localLoading.value = false
  546. if (!error && code === 200) {
  547. console.log(data)
  548. }
  549. } else {
  550. window.$message?.warning('存在未审批或未上报数据')
  551. }
  552. }
  553. //在线验签
  554. const onlineLoading = ref(false)
  555. const batchOnline = async () => {
  556. const rows = tableCheckedKeys.value;
  557. if (rows.length > 1) {
  558. window.$message?.warning('在线验签只能勾选一条数据进行验签')
  559. return;
  560. }
  561. if (rows[0].status !== 2) {
  562. window.$message?.warning('存在未审批或未上报数据')
  563. return;
  564. }
  565. //发起
  566. onlineLoading.value = true
  567. const { error, code, data } = await queryApi.onlineVerify({
  568. ids: rows[0]['id']
  569. })
  570. //处理数据
  571. localLoading.value = false
  572. if (!error && code === 200) {
  573. console.log(data)
  574. }
  575. }
  576. //拼接ID
  577. const rowsToId = (rows) => {
  578. return rows.map((obj) => {
  579. return obj.id;
  580. }).join(",")
  581. }
  582. //左右拖动,改变树形结构宽度
  583. const leftWidth = ref(382);
  584. const onmousedown = () => {
  585. const leftNum = isCollapse.value ? 142 : 272
  586. document.onmousemove = (ve) => {
  587. let diffVal = ve.clientX - leftNum;
  588. if(diffVal >= 310 && diffVal <= 900) {
  589. leftWidth.value = diffVal;
  590. }
  591. }
  592. document.onmouseup = () => {
  593. document.onmousemove = null;
  594. document.onmouseup = null;
  595. }
  596. }
  597. </script>
  598. <style lang="scss" scoped>
  599. @import "../../styles/data-fill/query.scss";
  600. </style>