division.vue 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162
  1. <template>
  2. <div class="hc-page-layout-box">
  3. <div class="hc-layout-left-box" :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. <el-scrollbar>
  15. <WbsTree ui="page-division-tree" :menus="ElTreeMenu" :autoExpandKeys="treeAutoExpandKeys" :projectId="projectId" :contractId="contractId" isColor @nodeTap="wbsElTreeClick" @menuTap="ElTreeMenuClick"/>
  16. </el-scrollbar>
  17. </div>
  18. <!--左右拖动-->
  19. <div class="horizontal-drag-line" @mousedown="onmousedown"/>
  20. </div>
  21. <div class="hc-page-content-box hc-division-page">
  22. <div class="basic-info">
  23. <HcCard title="当前节点基础信息">
  24. <template #extra>
  25. <el-checkbox v-model="treeItemInfo.isConcealedWorksNode" label="标记为隐蔽工程节点" :true-label="1" :false-label="0" size="large" @change="concealedChange"/>
  26. </template>
  27. <HcTable :column="tableBasicColumn" :datas="tableBasicData" :isIndex="false" border>
  28. <template #type="{row}">{{getRowType(row['type'])}}</template>
  29. <template #majorDataType="{row}">{{getRowMajorType(row['majorDataType'])}}</template>
  30. </HcTable>
  31. </HcCard>
  32. </div>
  33. <div class="project-info">
  34. <HcCard title="当前节点工程用表信息">
  35. <template #extra>
  36. <el-button type="primary" hc-btn @click="addingFormClick">
  37. <HcIcon name="add"/>
  38. <span>引用元素表</span>
  39. </el-button>
  40. </template>
  41. <HcTable :column="tableProjectColumn" :datas="tableProjectData" :isIndex="false" border>
  42. <template #tableType="{row}">{{getRowTableType(row['tableType'])}}</template>
  43. <template #tableOwner="{row}">{{getRowTableOwnerType(row['tableOwner'])}}</template>
  44. </HcTable>
  45. </HcCard>
  46. </div>
  47. <div class="footer-box">
  48. <el-button hc-btn @click="downloadXlsx">
  49. <HcIcon name="download-2"/>
  50. <span>下载导入划分模板</span>
  51. </el-button>
  52. <el-button type="primary" hc-btn :disabled="!treePrimaryKeyId" @click="toImportTempClick">
  53. <HcIcon name="folder-upload"/>
  54. <span>导入划分模板</span>
  55. </el-button>
  56. <el-button hc-btn @click="toBackClick">
  57. <HcIcon name="arrow-go-back"/>
  58. <span>返回</span>
  59. </el-button>
  60. </div>
  61. </div>
  62. <!--编辑节点-->
  63. <HcDialog :show="editNodeModal" title="编辑节点" widths="600px" :loading="editNodeLoading" @close="editNodeModal = false" @save="editNodeClick">
  64. <el-form ref="formEditNodeRef" :model="formEditNodeModel" :rules="formEditNodeRules" label-width="auto" size="large">
  65. <el-form-item label="节点名称" prop="title">
  66. <el-input v-model="formEditNodeModel.title" placeholder="请输入节点名称"/>
  67. </el-form-item>
  68. <el-form-item label="上级节点">
  69. <el-input v-model="formEditNodeModel.parent.title" disabled/>
  70. </el-form-item>
  71. <el-form-item label="节点类型">
  72. <el-select v-model="formEditNodeModel.type" block disabled>
  73. <el-option v-for="item in nodeTypeData" :label="item['label']" :value="item['value']"/>
  74. </el-select>
  75. </el-form-item>
  76. <el-form-item label="划分编号">
  77. <el-input v-model="formEditNodeModel.partitionCode" placeholder="请输入划分编号"/>
  78. </el-form-item>
  79. </el-form>
  80. </HcDialog>
  81. <!--复制节点-->
  82. <HcDialog :show="copyNodeModal" title="复制节点" :widths="copyNodeTabKey === '1'?'600px':'1200px'" @close="copyNodeModal = false">
  83. <el-form ref="formCopyNodeModelRef" :model="formCopyNodeModel" :rules="formCopyNodeModelRules" label-width="auto" size="large" v-if="copyNodeTabKey !== '3'">
  84. <el-form-item label="节点名称" prop="title" style="margin-bottom: 0;">
  85. <el-input v-model="formCopyNodeModel.title" placeholder="请输入节点名称"/>
  86. </el-form-item>
  87. </el-form>
  88. <div class="copy-node-many-box" v-if="copyNodeTabKey !== '1'">
  89. <div class="copy-node-many-tree">
  90. <el-scrollbar>
  91. <WbsTree :autoExpandKeys="treeAutoExpandKeys" :projectId="projectId" :contractId="contractId" idPrefix="tree-node-copy-" :isAutoClick="false" :isAutoKeys="false" @nodeTap="copyNodeElTreeClick"/>
  92. </el-scrollbar>
  93. </div>
  94. <div class="copy-node-many-table">
  95. <el-scrollbar>
  96. <el-table :data="copyNodeTable" border stripe>
  97. <el-table-column prop="title" label="复制到的位置"/>
  98. <el-table-column prop="nodeName" label="节点名称" v-if="copyNodeTabKey === '2'">
  99. <template #default="{row}">
  100. <el-form ref="copyNodeTableRef" :model="row" :rules="copyNodeTableRules" label-width="0" size="large">
  101. <el-form-item prop="nodeName" style="margin-bottom: 0;">
  102. <el-input v-model="row.nodeName" placeholder="请输入节点名称"/>
  103. </el-form-item>
  104. </el-form>
  105. </template>
  106. </el-table-column>
  107. <el-table-column prop="action" label="操作" width="120" align="center">
  108. <template #default="{_,$index}">
  109. <el-button type="danger" plain @click="copyNodeTableDel($index)">删除</el-button>
  110. </template>
  111. </el-table-column>
  112. </el-table>
  113. </el-scrollbar>
  114. </div>
  115. </div>
  116. <template #footer>
  117. <div class="lr-dialog-footer">
  118. <div class="left">
  119. <template v-for="item in copyNodeTab">
  120. <el-button size="large" type="primary" plain v-if="item?.key === copyNodeTabKey" @click="copyNodeTabChange(item?.key)">{{item.name}}</el-button>
  121. <el-button size="large" text bg @click="copyNodeTabChange(item?.key)" v-else>{{item.name}}</el-button>
  122. </template>
  123. </div>
  124. <div class="right">
  125. <el-button size="large" @click="copyNodeModal = false">取消</el-button>
  126. <el-button type="primary" hc-btn :loading="copyNodeLoading" @click="copyNodeClick">提交</el-button>
  127. </div>
  128. </div>
  129. </template>
  130. </HcDialog>
  131. <!--新增子节点-->
  132. <HcDialog :show="addNodeModal" title="新增子节点" widths="720px" @close="addNodeModal = false">
  133. <el-alert title="双击节点,可编辑节点名称,编辑完成后,请按回车或输入框消失后,再点提交" type="warning" :closable="false"/>
  134. <HcTreeNode :projectId="projectId" :nodeId="addTreeNodeId" :oldId="addTreeNodeOldId" @check-change="addTreeNodeCheckChange" v-if="addTreeNodeType === '1'"/>
  135. <HcTreeNode :projectId="projectId" :nodeId="addTreeNodeId" :oldId="addTreeNodeOldId" strictly @check-change="addTreeNodeCheckChange" v-if="addTreeNodeType === '2'"/>
  136. <template #footer>
  137. <div class="lr-dialog-footer">
  138. <div class="left flex items-center">
  139. <div class="mr-4">选中方式:</div>
  140. <el-radio-group v-model="addTreeNodeType">
  141. <el-radio label="1">当前及子节点</el-radio>
  142. <el-radio label="2" class="ml-4">仅当前节点</el-radio>
  143. </el-radio-group>
  144. </div>
  145. <div class="right">
  146. <el-button size="large" @click="addNodeModal = false">取消</el-button>
  147. <el-button type="primary" hc-btn :loading="addNodeLoading" @click="addNodeClick">提交</el-button>
  148. </div>
  149. </div>
  150. </template>
  151. </HcDialog>
  152. <!--调整排序-->
  153. <HcDialog :show="sortNodeModal" title="调整排序" widths="700px" :loading="sortNodeLoading" @close="sortNodeModal = false" @save="sortNodeClick">
  154. <el-alert title="可拖动排序,也可在后面点击图标,切换排序" type="warning" :closable="false"/>
  155. <div class="sort-node-body-box list-group header">
  156. <div class="list-group-item">
  157. <div class="index-box">序号</div>
  158. <div class="title-box">节点名称</div>
  159. <div class="icon-box">排序</div>
  160. </div>
  161. </div>
  162. <Draggable class="sort-node-body-box list-group" ghost-class="ghost" :list="sortNodeData" item-key="id" @start="sortNodeDrag = true" @end="sortNodeDrag = false">
  163. <template #item="{element, index}">
  164. <div class="list-group-item">
  165. <div class="index-box">{{index + 1}}</div>
  166. <div class="title-box">{{element.title}}</div>
  167. <div class="icon-box">
  168. <span class="icon" @click="downSortClick(index)">
  169. <HcIcon name="arrow-down" ui="text-lg"/>
  170. </span>
  171. <span class="icon" @click="upSortClick(index)">
  172. <HcIcon name="arrow-up" ui="text-lg"/>
  173. </span>
  174. </div>
  175. </div>
  176. </template>
  177. </Draggable>
  178. </HcDialog>
  179. <!--导入划分模板-->
  180. <HcDialog :show="importTempModal" title="导入划分模板" widths="84%" ui="hc-modal-table" @close="importTempModal = false">
  181. <div class="hc-import-temp-box">
  182. <!--div class="hc-choose-type-box">
  183. <div class="text-title text-orange">请选择需要导入的工程类别:</div>
  184. <div class="hc-type-box">
  185. <el-radio-group v-model="importRadio" size="large">
  186. <el-radio v-for="item in importRadioData" :label="item.key">{{item.name}}</el-radio>
  187. </el-radio-group>
  188. </div>
  189. </div-->
  190. <div class="hc-import-node-tree-box" v-loading="uploadLoading" element-loading-text="Loading...">
  191. <div class="import-node-tree-box">
  192. <div class="hc-tree-title-box">导入并识别成功</div>
  193. <div class="hc-tree-bar-box">
  194. <el-scrollbar>
  195. <HcTreeData :datas="matchedData"/>
  196. </el-scrollbar>
  197. </div>
  198. </div>
  199. <div class="import-node-tree-box">
  200. <div class="hc-tree-title-box">未被系统识别:手动关联</div>
  201. <div class="hc-tree-bar-box">
  202. <el-scrollbar>
  203. <HcTreeData1 :datas="unmatchedData" @relationTap="unmatchedTreeTap"/>
  204. </el-scrollbar>
  205. </div>
  206. </div>
  207. </div>
  208. </div>
  209. <template #footer>
  210. <div class="lr-dialog-footer">
  211. <div class="left">
  212. <HcUpload ref="uploadRef" :contractId="contractId" :type="importRadio" :primaryKeyId="treePrimaryKeyId" :isSplicingNumber="isSplicingNumber"
  213. @change="uploadChange" @progress="uploadprogress" @finished="uploadFinished"/>
  214. </div>
  215. <div class="right flex">
  216. <div class="mr-6">
  217. <span class="text-grey">是否拼接划分编号:</span>
  218. <el-switch v-model="isSplicingNumber" inline-prompt :active-value="1" active-text="是" :inactive-value="0" inactive-text="否" size="large"/>
  219. </div>
  220. <el-button size="large" @click="importTempModalClose">
  221. <HcIcon name="close"/>
  222. <span>取消</span>
  223. </el-button>
  224. <el-button type="primary" hc-btn :disabled="uploadLoading" :loading="uploadLoading" @click="importTempFolder">
  225. <HcIcon name="folder-upload"/>
  226. <span>导入模板</span>
  227. </el-button>
  228. </div>
  229. </div>
  230. </template>
  231. </HcDialog>
  232. <!--替换并关联节点-->
  233. <HcDialog :show="relationModal" title="替换并关联节点" widths="47rem" ui="hc-modal-table" saveText="确认关联" :loading="relationLoading" @close="relationModal = false" @save="relationSaveClick">
  234. <el-scrollbar>
  235. <DivisionTree :datas="unmatchedTreeData" @nodeTap="divisionTreeClick"/>
  236. </el-scrollbar>
  237. </HcDialog>
  238. <!--添加独立表单-->
  239. <HcDialog :show="addingFormModal" title="引用元素表" widths="84%" isTable :padding="false" :loading="addingFormLoading" @close="addingFormClose" @save="addingFormSave">
  240. <div class="adding-form-dialog-box">
  241. <div class="dialog-tree-box">
  242. <el-scrollbar>
  243. <ElTree class="hc-tree-node"
  244. ref="addingFormTreeRef"
  245. :props="addingFormTreeProps"
  246. :load="addingFormTreeLoadNode"
  247. lazy highlight-current accordion
  248. @node-click="addingFormTreeClick">
  249. </ElTree>
  250. </el-scrollbar>
  251. </div>
  252. <div class="dialog-table-box">
  253. <div class="dialog-search">
  254. <el-autocomplete
  255. class="block" clearable
  256. v-model="searchTreeTitle"
  257. :fetch-suggestions="querySearchTree"
  258. placeholder="请输入关键词检索"
  259. node-key="primaryKeyId"
  260. value-key="title"
  261. @select="searchTreeSelect"/>
  262. </div>
  263. <div class="dialog-table">
  264. <HcTable ref="dialogTableRef" :loading="dialogTableLoading" :column="dialogTableColumn" :datas="dialogTableData" isCheck @selection-change="dialogTableSelection"/>
  265. </div>
  266. <div class="dialog-pages">
  267. <HcPages :pages="searchFormPage" @change="searchFormPageChange"/>
  268. </div>
  269. </div>
  270. </div>
  271. </HcDialog>
  272. </div>
  273. </template>
  274. <script setup>
  275. import {ref,watch,onMounted} from "vue";
  276. import {useAppStore} from "~src/store";
  277. import {useRouter, useRoute} from 'vue-router'
  278. import WbsTree from "./components/WbsTree.vue"
  279. import HcUpload from "./components/division/HcUpload.vue"
  280. import HcTreeData from "./components/division/HcTreeData.vue"
  281. import HcTreeData1 from "./components/division/HcTreeData1.vue"
  282. import DivisionTree from "./components/division/DivisionTree.vue"
  283. import HcTreeNode from "./components/HcTreeNode.vue"
  284. import {isType, getIndex, deepClone, formValidate} from "vue-utils-plus"
  285. import {getStoreData, setStoreData} from '~src/utils/storage'
  286. import {getDictionary} from "~api/other"
  287. import wbsApi from "~api/data-fill/wbs";
  288. import divisionApi from "~api/data-fill/division";
  289. import Draggable from "vuedraggable";
  290. //初始变量
  291. const router = useRouter()
  292. const useRoutes = useRoute()
  293. const useAppState = useAppStore()
  294. const {getObjValue, getArrValue} = isType()
  295. //全局变量
  296. const projectId = ref(useAppState.getProjectId);
  297. const contractId = ref(useAppState.getContractId);
  298. const projectInfo = ref(useAppState.getProjectInfo);
  299. const isCollapse = ref(useAppState.getCollapse)
  300. //监听
  301. watch(() => [
  302. useAppState.getCollapse
  303. ], ([Collapse]) => {
  304. isCollapse.value = Collapse
  305. })
  306. //自动展开缓存
  307. const treeAutoExpandKeys = ref(getStoreData('wbsTreeExpandKeys') || [])
  308. //渲染完成
  309. onMounted(() => {
  310. //setElTreeMenu()
  311. getWbsNodeTypeApi()
  312. getMajorDataTypeApi()
  313. getWbsNodeTableTypeApi()
  314. getTableOwnerTypeApi()
  315. })
  316. //获取节点类型
  317. const nodeTypeData = ref([])
  318. const getWbsNodeTypeApi = async () => {
  319. const { data } = await getDictionary({
  320. code: 'wbs_node_type'
  321. })
  322. //处理数据
  323. let newArr = []
  324. const newData = getArrValue(data)
  325. for (let i = 0; i < newData.length; i++) {
  326. newArr.push({
  327. label: newData[i]['dictValue'],
  328. value: Number(newData[i]['dictKey']),
  329. })
  330. }
  331. nodeTypeData.value = newArr
  332. }
  333. //获取类型名称
  334. const getRowType = (type) => {
  335. const nodeData = nodeTypeData.value
  336. const index = getIndex(nodeData, 'value', type)
  337. return nodeData[index]?.label ?? type
  338. }
  339. //获取资料类型
  340. const majorDataTypeData = ref([])
  341. const getMajorDataTypeApi = async () => {
  342. const { data } = await getDictionary({
  343. code: 'major_data_type'
  344. })
  345. //处理数据
  346. majorDataTypeData.value = getArrValue(data)
  347. }
  348. //获取资料类型名称
  349. const getRowMajorType = (type) => {
  350. if (type) {
  351. const nodeData = majorDataTypeData.value
  352. const index = getIndex(nodeData, 'dictKey', type)
  353. return nodeData[index]?.dictValue ?? type
  354. }
  355. }
  356. //获取表类型数据
  357. const nodeTableTypeData = ref([])
  358. const getWbsNodeTableTypeApi = async () => {
  359. const { data } = await getDictionary({
  360. code: 'table_type'
  361. })
  362. nodeTableTypeData.value = getArrValue(data)
  363. }
  364. //获取表类型
  365. const getRowTableType = (type) => {
  366. if (type > 0) {
  367. const nodeData = nodeTableTypeData.value
  368. const index = getIndex(nodeData, 'dictKey', type)
  369. return nodeData[index]?.dictValue ?? type
  370. } else {
  371. return ''
  372. }
  373. }
  374. //获取表类型数据
  375. const tableOwnerTypeData = ref([])
  376. const getTableOwnerTypeApi = async () => {
  377. const { data } = await getDictionary({
  378. code: 'owner_type'
  379. })
  380. tableOwnerTypeData.value = getArrValue(data)
  381. }
  382. //获取表类型
  383. const getRowTableOwnerType = (type) => {
  384. if (type > 0) {
  385. const nodeData = tableOwnerTypeData.value
  386. const index = getIndex(nodeData, 'dictKey', type)
  387. return nodeData[index]?.dictValue ?? type
  388. } else {
  389. return ''
  390. }
  391. }
  392. //树被点击
  393. const treeNodeInfo = ref({})
  394. const treeItemInfo = ref({})
  395. const treePrimaryKeyId = ref('')
  396. const wbsElTreeClick = ({node, data, keys}) => {
  397. treeNodeInfo.value = node
  398. treeItemInfo.value = data
  399. tableBasicData.value = [data]
  400. setStoreData('wbsTreeExpandKeys',keys)
  401. treeAutoExpandKeys.value = keys || []
  402. if (node.level === 3) {
  403. treePrimaryKeyId.value = data['primaryKeyId']
  404. } else {
  405. treePrimaryKeyId.value = ''
  406. }
  407. if (node.level > 1) {
  408. searchNodeAllTableApi(data['primaryKeyId'])
  409. }
  410. }
  411. //当前节点基础信息
  412. const tableBasicColumn = ref([
  413. {key:'title', name: '节点名称'},
  414. {key:'partitionCode', name: '划分编号'},
  415. {key:'type', name: '节点类型'},
  416. {key:'majorDataType', name: '资料类型'}
  417. ])
  418. const tableBasicData = ref([])
  419. //标记为隐蔽工程节点
  420. const concealedChange = async () => {
  421. const { primaryKeyId, isConcealedWorksNode } = treeItemInfo.value
  422. if (primaryKeyId) {
  423. const {error, code, msg} = await divisionApi.concealedWorksNnode({
  424. pKeyId: primaryKeyId,
  425. type: isConcealedWorksNode
  426. }, false)
  427. if (!error && code === 200) {
  428. window?.$message?.success(msg)
  429. } else {
  430. window?.$message?.warning(msg)
  431. }
  432. } else {
  433. window?.$message?.warning('请先在左侧选择节点')
  434. }
  435. }
  436. //当前节点工程用表信息
  437. const tableProjectColumn = ref([
  438. {key:'nodeName', name: '工程用表名称'},
  439. {key:'tableType', name: '用表类型'},
  440. {key:'tableOwner', name: '用表单位'},
  441. {key:'fillRate', name: '填报完整率'}
  442. ])
  443. const tableProjectData = ref([])
  444. //获取数据
  445. const searchNodeAllTableApi = async (pid) => {
  446. const {error, code, data} = await wbsApi.searchNodeAllTable({
  447. projectId: projectId.value,
  448. contractId: contractId.value,
  449. primaryKeyId: pid
  450. })
  451. if (!error && code === 200) {
  452. tableProjectData.value = getArrValue(data)
  453. } else {
  454. tableProjectData.value = []
  455. }
  456. }
  457. //树菜单配置
  458. const ElTreeMenu = ref([
  459. {icon: 'add-circle', label: '新增节点', key: "add"},
  460. {icon: 'file-copy-2', label: '复制节点', key: "copy"},
  461. {icon: 'draft', label: '修改节点', key: "edit"},
  462. {icon: 'sort-asc', label: '调整排序', key: "sort"},
  463. {icon: 'delete-bin', label: '删除节点', key: "del"}
  464. ])
  465. //树菜单被点击
  466. const ElTreeMenuClick = async ({key,node,data}) => {
  467. treeNodeInfo.value = node
  468. treeItemInfo.value = data
  469. if (key === 'add') {
  470. addTreeNodeId.value = data?.id
  471. addTreeNodeOldId.value = data?.oldId
  472. addNodeLoading.value = false
  473. addNodeModal.value = true
  474. } else if (key === 'copy') {
  475. const parent = deepClone(node?.parent?.data || {})
  476. formCopyNodeModel.value = {...deepClone(data), parent: parent}
  477. copyNodeTabKey.value = '1'
  478. copyNodeTable.value = []
  479. copyNodeLoading.value = false
  480. copyNodeModal.value = true
  481. } else if (key === 'edit') {
  482. const parent = deepClone(node?.parent?.data || {})
  483. formEditNodeModel.value = {...deepClone(data), parent: parent}
  484. editNodeModal.value = true
  485. } else if (key === 'sort') {
  486. let nodes = [], childNodes = []
  487. childNodes = node?.parent?.childNodes || []
  488. for (let i = 0; i < childNodes.length; i++) {
  489. const res = childNodes[i]?.data
  490. nodes.push({
  491. id: res?.primaryKeyId,
  492. title: res?.title
  493. })
  494. }
  495. sortNodeData.value = nodes
  496. sortNodeModal.value = true
  497. } else if (key === 'del') {
  498. delModalClick()
  499. }
  500. }
  501. //编辑节点
  502. const editNodeModal = ref(false)
  503. const formEditNodeRef = ref(null)
  504. const formEditNodeModel = ref({})
  505. const formEditNodeRules = {
  506. title: {
  507. required: true,
  508. trigger: 'blur',
  509. message: "请输入节点名称"
  510. },
  511. }
  512. const editNodeLoading = ref(false)
  513. //保存编辑节点数据
  514. const editNodeClick = async () => {
  515. const validate = await formValidate(formEditNodeRef.value)
  516. if (validate) {
  517. //发起请求
  518. editNodeLoading.value = true
  519. const { primaryKeyId, title, partitionCode } = formEditNodeModel.value
  520. const { error, code } = await wbsApi.wbsTreeUpdateNode({
  521. nodeName: title || '',
  522. pKeyId: primaryKeyId || '',
  523. partitionCode: partitionCode || ''
  524. })
  525. //处理数据
  526. editNodeLoading.value = false
  527. if (!error && code === 200) {
  528. window?.$message?.success('修改成功')
  529. treeItemInfo.value['title'] = title || ''
  530. treeItemInfo.value['partitionCode'] = partitionCode || ''
  531. editNodeModal.value = false
  532. window?.location?.reload() //刷新页面
  533. }
  534. }
  535. }
  536. //复制节点
  537. const copyNodeModal = ref(false)
  538. //复制节点类型tab数据和相关处理
  539. const copyNodeTabKey = ref('1')
  540. const copyNodeTab = ref([
  541. {key:'1', name: '单份复制'},
  542. {key:'2', name: '多份复制'},
  543. {key:'3', name: '复制数据'}
  544. ]);
  545. const copyNodeTabChange = (key) => {
  546. if (key !== copyNodeTabKey.value) {
  547. copyNodeTabKey.value = key;
  548. copyNodeTable.value = []
  549. copyNodeLoading.value = false
  550. }
  551. }
  552. //复制节点变量
  553. const copyNodeLoading = ref(false)
  554. const formCopyNodeModel = ref({})
  555. const copyNodeTable = ref([])
  556. //复制树被点击
  557. const copyNodeElTreeClick = ({data}) => {
  558. const TabKey = copyNodeTabKey.value;
  559. const {title, type} = formCopyNodeModel.value;
  560. if (TabKey === '2') {
  561. //1 单位工程,2 分部工程,3 子分部工程,4 分项工程, 5 子分项工程,6 工序
  562. if (type === 6 && (data['type'] === 4 || data['type'] === 5 || data['type'] === 6)) {
  563. setCopyNodeTable(data, title)
  564. }
  565. if (type === 5 && data['type'] === 4) {
  566. setCopyNodeTable(data, title)
  567. }
  568. if (type === 4 && (data['type'] === 2 || data['type'] === 3)) {
  569. setCopyNodeTable(data, title)
  570. }
  571. if (type === 3 && data['type'] === 2) {
  572. setCopyNodeTable(data, title)
  573. }
  574. if (type === 2 && data['type'] === 1) {
  575. setCopyNodeTable(data, title)
  576. }
  577. if (type === 1 && data['type'] === 1) {
  578. setCopyNodeTable(data, title)
  579. }
  580. } else if (TabKey === '3') {
  581. if (data['notExsitChild']) {
  582. setCopyNodeTable(data, data?.title)
  583. }
  584. }
  585. }
  586. const setCopyNodeTable = (data, title) => {
  587. copyNodeTable.value.push({
  588. title: data?.title || '',
  589. nodeName: title || '',
  590. primaryKeyId: data?.primaryKeyId || ''
  591. })
  592. }
  593. //节点表单
  594. const formCopyNodeModelRef = ref(null)
  595. const formCopyNodeModelRules = {
  596. title: {
  597. required: true,
  598. trigger: "blur",
  599. message: "请输入节点名称"
  600. }
  601. }
  602. //表格节点表单
  603. const copyNodeTableRef = ref(null)
  604. const copyNodeTableRules = {
  605. nodeName: {
  606. required: true,
  607. trigger: "blur",
  608. message: "请输入节点名称"
  609. }
  610. }
  611. //删除选中的节点
  612. const copyNodeTableDel = (index) => {
  613. copyNodeTable.value.splice(index,1)
  614. }
  615. //复制节点
  616. const copyNodeClick = async () => {
  617. const type = copyNodeTabKey.value
  618. const form = formCopyNodeModel.value
  619. const table = copyNodeTable.value
  620. //效验数据
  621. if (type === '1') {
  622. const validate = await formValidate(formCopyNodeModelRef.value)
  623. if (validate) await copyContractTreeNode(type, form,[])
  624. } else if (type === '2') {
  625. if (table.length > 0) {
  626. const validate = await formValidate(copyNodeTableRef.value)
  627. if (validate) await copyContractTreeNode(type, form,table)
  628. } else {
  629. window?.$message?.warning('请先在左侧选择要复制到的节点')
  630. }
  631. } else if (type === '3') {
  632. if (table.length > 0) {
  633. await copyContractNodeSubmitBusinessData(form,table)
  634. } else {
  635. window?.$message?.warning('请先在左侧选择要复制的节点')
  636. }
  637. }
  638. }
  639. //单个复制、多份复制请求
  640. const copyContractTreeNode = async (type, form,table) => {
  641. copyNodeLoading.value = true
  642. const {error, code} = await wbsApi.copyContractTreeNode({
  643. copyType: type,
  644. needCopyNodeName: form?.title || '',
  645. needCopyPrimaryKeyId: form?.primaryKeyId || '',
  646. parentPrimaryKeyId: form?.parent?.primaryKeyId || '',
  647. copyBatchToPaths: table
  648. })
  649. //判断状态
  650. if (!error && code === 200) {
  651. window?.$message?.success('复制成功')
  652. copyNodeLoading.value = false
  653. copyNodeModal.value = false
  654. window?.location?.reload() //刷新页面
  655. }
  656. }
  657. //复制数据
  658. const copyContractNodeSubmitBusinessData = async (form,table) => {
  659. copyNodeLoading.value = true
  660. const {error, code} = await wbsApi.copyContractNodeSubmitBusinessData({
  661. needCopyPrimaryKeyId: form?.primaryKeyId || '',
  662. copyBatchToPaths: table
  663. })
  664. //判断状态
  665. if (!error && code === 200) {
  666. window?.$message?.success('复制成功')
  667. copyNodeLoading.value = false
  668. copyNodeModal.value = false
  669. window?.location?.reload() //刷新页面
  670. }
  671. }
  672. //新增节点
  673. const addNodeModal = ref(false)
  674. const addTreeNodeId = ref('')
  675. const addTreeNodeOldId = ref('')
  676. const addTreeNodeType = ref('1')
  677. //选中的节点
  678. const allSelectedList = ref([])
  679. const halfSelectedList = ref([])
  680. const addTreeNodeCheckChange = (nodes) => {
  681. let NodesArr = [], halfArr = []
  682. //全选数据
  683. const keys = nodes.checkedNodes || []
  684. for (let i = 0; i < keys.length; i++) {
  685. NodesArr.push({
  686. nodeName: keys[i].title,
  687. primaryKeyId: keys[i].primaryKeyId
  688. })
  689. }
  690. allSelectedList.value = NodesArr
  691. //半选数据
  692. const halfNodes = nodes.halfCheckedNodes || []
  693. for (let i = 0; i < halfNodes.length; i++) {
  694. halfArr.push({
  695. nodeName: halfNodes[i].title,
  696. primaryKeyId: halfNodes[i].primaryKeyId
  697. })
  698. }
  699. halfSelectedList.value = halfArr
  700. }
  701. //新增节点
  702. const addNodeLoading = ref(false)
  703. const addNodeClick = async () => {
  704. const keys = allSelectedList.value || []
  705. if (keys.length <= 0) {
  706. window?.$message?.warning('请先选择节点')
  707. } else {
  708. //发起请求
  709. addNodeLoading.value = true
  710. const primaryKeyId = treeItemInfo.value?.primaryKeyId || ''
  711. const {error, code} = await wbsApi.saveContractTreeNode({
  712. projectId: projectId.value,
  713. contractId: contractId.value,
  714. saveType: addTreeNodeType.value,
  715. allSelectedList: allSelectedList.value,
  716. halfSelectedList: halfSelectedList.value,
  717. currentNodePrimaryKeyId: primaryKeyId
  718. })
  719. //判断状态
  720. addNodeLoading.value = false
  721. if (!error && code === 200) {
  722. window?.$message?.success('新增成功')
  723. addNodeModal.value = false
  724. window?.location?.reload() //刷新页面
  725. }
  726. }
  727. }
  728. //删除节点
  729. const delModalClick = () => {
  730. window?.$messageBox?.alert('请谨慎考虑后,确认是否需要删除?', '删除节点', {
  731. showCancelButton: true,
  732. confirmButtonText: '确认删除',
  733. cancelButtonText: '取消',
  734. callback: (action) => {
  735. if (action === 'confirm') {
  736. removeContractTreeNode()
  737. }
  738. }
  739. })
  740. }
  741. const removeContractTreeNode = async () => {
  742. const {error, code} = await wbsApi.removeContractTreeNode({
  743. ids: treeItemInfo.value?.primaryKeyId || ''
  744. })
  745. if (!error && code === 200) {
  746. window?.$message?.success('删除成功')
  747. window?.location?.reload() //刷新页面
  748. }
  749. }
  750. //调整排序
  751. const sortNodeModal = ref(false)
  752. const sortNodeLoading = ref(false)
  753. const sortNodeData = ref([])
  754. const sortNodeDrag = ref(false)
  755. //向下
  756. const downSortClick = (index) => {
  757. const indexs = index + 1
  758. const data = sortNodeData.value || []
  759. if(indexs !== data.length) {
  760. const tmp = data.splice(indexs,1);
  761. sortNodeData.value.splice(index,0,tmp[0]);
  762. } else {
  763. window?.$message?.warning('已经处于置底,无法下移')
  764. }
  765. }
  766. //向上
  767. const upSortClick = (index) => {
  768. const data = sortNodeData.value || []
  769. if(index !== 0) {
  770. const tmp = data.splice(index - 1,1);
  771. sortNodeData.value.splice(index,0,tmp[0]);
  772. } else {
  773. window?.$message?.warning('已经处于置顶,无法上移')
  774. }
  775. }
  776. //确认排序
  777. const sortNodeClick = async () => {
  778. const sortList = [];
  779. const nodes = sortNodeData.value || []
  780. nodes.forEach(item => {
  781. sortList.push(item?.id)
  782. })
  783. //发起请求
  784. sortNodeLoading.value = true
  785. const { error, code } = await wbsApi.diySortTreeNode({sortList})
  786. sortNodeLoading.value = false
  787. //判断状态
  788. if (!error && code === 200) {
  789. window?.$message?.success('保存成功')
  790. sortNodeModal.value = false
  791. window?.location?.reload() //刷新页面
  792. }
  793. }
  794. //导入模板
  795. const uploadRef = ref(null)
  796. const importTempModal = ref(false)
  797. const toImportTempClick = () => {
  798. importTempModal.value = true
  799. getContractInfoTreeApi()
  800. }
  801. //导入类型
  802. const importRadio = ref(1)
  803. const importRadioData = ref([
  804. {key: 1, name: '路基工程'},
  805. {key: 2, name: '路面工程'},
  806. {key: 3, name: '桥梁工程'},
  807. {key: 4, name: '机电工程'},
  808. {key: 5, name: '绿化工程'},
  809. {key: 6, name: '隧道工程'},
  810. {key: 7, name: '声屏障工程'},
  811. {key: 8, name: '交通与安全工程'},
  812. {key: 9, name: '特大斜拉桥、特大悬索桥'},
  813. ])
  814. const isSplicingNumber = ref(0)
  815. //上传文件被改变
  816. const uploadFile = ref(null)
  817. const uploadChange = (file) => {
  818. uploadFile.value = file
  819. }
  820. //上传中
  821. const uploadLoading = ref(false)
  822. const uploadprogress = (state) => {
  823. console.log(state)
  824. uploadLoading.value = state
  825. }
  826. //上传完成
  827. const matchedData = ref([])
  828. const unmatchedData = ref([])
  829. const uploadFinished = ({type, data}) => {
  830. uploadRef.value?.clearFiles()
  831. if (type === 'success') {
  832. uploadFile.value = null
  833. window?.$message?.success('导入成功');
  834. matchedData.value = getArrValue(data['matchedData'])
  835. unmatchedData.value = getArrValue(data['unmatchedData'])
  836. } else {
  837. uploadFile.value = null
  838. matchedData.value = []
  839. unmatchedData.value = []
  840. }
  841. }
  842. //关闭导入弹窗
  843. const importTempModalClose = () => {
  844. uploadFile.value = null
  845. importTempModal.value = false
  846. }
  847. //确认导入
  848. const importTempFolder = () => {
  849. if (uploadFile.value) {
  850. uploadRef.value?.submit()
  851. } else {
  852. window?.$message?.warning('请先选择文件');
  853. }
  854. }
  855. //关联被点击
  856. const relationModal = ref(false)
  857. const relationItemInfo = ref({})
  858. const unmatchedTreeTap = ({data}) => {
  859. relationItemInfo.value = data
  860. relationModal.value = true
  861. }
  862. //获取导入树
  863. const unmatchedTreeData = ref([])
  864. const getContractInfoTreeApi = async () => {
  865. const {error, code, data} = await divisionApi.getContractInfoTree({
  866. projectId: projectId.value,
  867. contractId: contractId.value,
  868. wbsId: ''
  869. })
  870. //判断状态
  871. if (!error && code === 200) {
  872. unmatchedTreeData.value = getArrValue(data)
  873. } else {
  874. unmatchedTreeData.value = []
  875. }
  876. }
  877. //关联树
  878. const divisionTreeItemInfo = ref({})
  879. const divisionTreeClick = ({data}) => {
  880. divisionTreeItemInfo.value = data
  881. }
  882. //确认关联
  883. const relationLoading = ref(false)
  884. const relationSaveClick = () => {
  885. const item = relationItemInfo.value
  886. const info = divisionTreeItemInfo.value
  887. const primaryKeyId = info?.primaryKeyId ?? ''
  888. if (!primaryKeyId) {
  889. window?.$message?.warning('请先选择一个树节点')
  890. } else {
  891. setImportRelationApi({
  892. pKeyIdOld: info?.primaryKeyId,
  893. wbsTreeContractVO: {
  894. ...item,
  895. id: ''
  896. }
  897. })
  898. }
  899. }
  900. const setImportRelationApi = async (form) => {
  901. relationLoading.value = true
  902. const { error, code } = await divisionApi.setImportRelation(form)
  903. relationLoading.value = false
  904. if (!error && code === 200) {
  905. window.$message?.success('关联成功')
  906. relationModal.value = false
  907. }
  908. }
  909. //下载模板
  910. const downloadXlsx = () => {
  911. window.open('https://bladex-test-info.oss-cn-chengdu.aliyuncs.com//upload/20221017/854463fdfdf90843e6783fbcb4d7d00c.xlsx','_blank')
  912. }
  913. //返回上页
  914. const toBackClick = () => {
  915. router.push({path: '/data-fill/wbs'})
  916. }
  917. //添加独立表单
  918. const addingFormModal = ref(false)
  919. const addingFormClick = () => {
  920. const { isLeaf } = treeNodeInfo.value
  921. if (isLeaf) {
  922. addingFormLoading.value = false
  923. addingFormModal.value = true
  924. } else {
  925. window?.$message?.warning('请先选择一个最子级的节点')
  926. }
  927. }
  928. //树配置
  929. const addingFormTreeRef = ref(null)
  930. const addingFormTreeProps = {
  931. label: 'title',
  932. children: 'children',
  933. isLeaf: 'isLeaf'
  934. }
  935. //树加载
  936. const addingFormTreeLoadNode = async (node, resolve) => {
  937. if (node.level === 0) {
  938. const resData = await tabTypeLazyTree()
  939. resolve(resData?.data)
  940. } else {
  941. const resData = await tabTypeLazyTree(node?.data?.primaryKeyId)
  942. resolve(resData?.data)
  943. }
  944. }
  945. //树被点击
  946. const addingFormTreeItem = ref({})
  947. const addingFormTreeClick = async (data, node) => {
  948. searchTreeTitle.value = ''
  949. addingFormTreeItem.value = data
  950. dialogTableData.value = []
  951. searchFormPage.value.current = 1
  952. if (node?.level === 1) {
  953. getDialogTableData().then()
  954. } else if (node?.level === 2) {
  955. searchFormPage.value.total = 1
  956. dialogTableData.value = [data]
  957. }
  958. }
  959. //搜索
  960. const searchTreeTitle = ref('')
  961. const querySearchTree = async (key, resolve) => {
  962. if(key) {
  963. const primaryKeyId = addingFormTreeItem.value?.primaryKeyId ?? ''
  964. const resData = await tabTypeLazyTree(primaryKeyId, key)
  965. resolve(resData?.data)
  966. } else{
  967. resolve([])
  968. }
  969. }
  970. const searchTreeSelect = (item) => {
  971. dialogTableRef.value?.clearSelection()
  972. dialogTableKeys.value = []
  973. dialogTableData.value = [item]
  974. searchFormPage.value.current = 1
  975. searchFormPage.value.total = 0
  976. }
  977. //分页
  978. const searchFormPage = ref({current: 1, size: 20, total: 0})
  979. const searchFormPageChange = ({current, size}) => {
  980. searchFormPage.value.current = current
  981. searchFormPage.value.size = size
  982. getDialogTableData()
  983. }
  984. //表格数据
  985. const dialogTableRef = ref(null)
  986. const dialogTableColumn = ref([
  987. {key:'title', name: '表单名称'},
  988. {key:'tabType', name: '元素表类型'},
  989. {key:'elementTotal', name: '元素总量'},
  990. {key:'tabOwner', name: '所属方'},
  991. {key:'fillRate', name: '填报率'}
  992. ])
  993. const dialogTableData = ref([])
  994. const dialogTableLoading = ref(false)
  995. const getDialogTableData = async () => {
  996. //处理初始数据
  997. dialogTableLoading.value = true
  998. dialogTableRef.value?.clearSelection()
  999. dialogTableKeys.value = []
  1000. const primaryKeyId = addingFormTreeItem.value?.primaryKeyId ?? ''
  1001. const searchTitle = searchTreeTitle.value ?? ''
  1002. //获取数据
  1003. const resData = await tabTypeLazyTree(primaryKeyId, searchTitle, true, true)
  1004. const records = getArrValue(resData?.data)
  1005. //处理返回的数据
  1006. dialogTableData.value = records
  1007. dialogTableLoading.value = false
  1008. if (records.length > 0) {
  1009. searchFormPage.value.total = resData.total || 0
  1010. } else {
  1011. searchFormPage.value.total = 0
  1012. }
  1013. }
  1014. //多选
  1015. const dialogTableKeys = ref([]);
  1016. const dialogTableSelection = (rows) => {
  1017. dialogTableKeys.value = rows
  1018. }
  1019. //获取数据
  1020. const tabTypeLazyTree = async (parentId = '12345678910', titleName = '', search = false) => {
  1021. let obj = {}, searchObj = {}
  1022. if(parentId) obj.parentId = parentId
  1023. if(titleName) obj.titleName = titleName
  1024. if(search) searchObj = searchFormPage.value
  1025. //发起请求
  1026. const { data } = await divisionApi.tabTypeLazyTree({
  1027. projectId: projectId.value,
  1028. contractId: contractId.value,
  1029. ...obj,
  1030. ...searchObj
  1031. })
  1032. const records = getArrValue(data?.records)
  1033. records.forEach(item => {
  1034. item.isLeaf = !item.hasChildren
  1035. })
  1036. return {data: records, total: data?.total}
  1037. }
  1038. //保存
  1039. const addingFormLoading = ref(false)
  1040. const addingFormSave = async () => {
  1041. const rows = dialogTableKeys.value
  1042. if (rows.length > 0) {
  1043. addingFormLoading.value = true
  1044. const { primaryKeyId } = treeItemInfo.value
  1045. const ids = rowsToId(rows, 'primaryKeyId')
  1046. //发起请求
  1047. const { error, code } = await divisionApi.addWbsContractInfo({
  1048. projectId: projectId.value,
  1049. contractId: contractId.value,
  1050. nodeId: primaryKeyId,
  1051. primaryKeyIds: ids
  1052. })
  1053. //处理结果
  1054. addingFormLoading.value = false
  1055. if (!error && code === 200) {
  1056. window.$message?.success('操作成功')
  1057. addingFormModal.value = false
  1058. window?.location?.reload() //刷新页面
  1059. }
  1060. } else {
  1061. window.$message?.warning('请先勾选数据')
  1062. }
  1063. }
  1064. //关闭
  1065. const addingFormClose = () => {
  1066. addingFormModal.value = false
  1067. dialogTableKeys.value = []
  1068. }
  1069. //拼接ID
  1070. const rowsToId = (rows, key = 'id') => {
  1071. return rows.map((obj) => {
  1072. return obj[key];
  1073. }).join(",")
  1074. }
  1075. //左右拖动,改变树形结构宽度
  1076. const leftWidth = ref(382);
  1077. const onmousedown = () => {
  1078. const leftNum = isCollapse.value ? 142 : 272
  1079. document.onmousemove = (ve) => {
  1080. let diffVal = ve.clientX - leftNum;
  1081. if(diffVal >= 310 && diffVal <= 900) {
  1082. leftWidth.value = diffVal;
  1083. }
  1084. }
  1085. document.onmouseup = () => {
  1086. document.onmousemove = null;
  1087. document.onmouseup = null;
  1088. }
  1089. }
  1090. </script>
  1091. <style lang="scss" scoped>
  1092. @import "../../styles/data-fill/division.scss";
  1093. </style>