wbs.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. <template>
  2. <div class="hc-contract-info-wbs relative h-full">
  3. <div class="wbs-template-body">
  4. <div v-loading="leftLoading" class="left">
  5. <hc-card-item scrollbar :title="`${leftNum}项`">
  6. <el-tree
  7. ref="leftTreeRef" :data="leftTreeData" show-checkbox node-key="id" :props="treeProps"
  8. highlight-current :expand-on-click-node="false" @node-expand="nodeLeftTreeExpand"
  9. @check-change="checkLeftTreeChange"
  10. />
  11. </hc-card-item>
  12. </div>
  13. <div class="btn-action">
  14. <div class="relative">
  15. <div class="relative">
  16. <el-button hc-btn :type="leftNum <= 0 ? '' : 'primary'" :disabled="leftNum <= 0" @click="addTreeClick">
  17. <i class="i-ri-arrow-right-line" />
  18. </el-button>
  19. </div>
  20. <div class="relative mt-14px">
  21. <el-button hc-btn :type="rightNum <= 0 ? '' : 'primary'" :disabled="rightNum <= 0" @click="delTreeClick">
  22. <i class="i-ri-arrow-left-line" />
  23. </el-button>
  24. </div>
  25. </div>
  26. </div>
  27. <div v-loading="rightLoading" class="right">
  28. <hc-card-item scrollbar :title="`${rightNum}项`">
  29. <el-tree
  30. ref="rightTreeRef" :data="rightTreeData" show-checkbox node-key="id" :props="treeProps"
  31. highlight-current :expand-on-click-node="false" :default-expanded-keys="rightExpands"
  32. @check-change="checkRightTreeChange"
  33. />
  34. </hc-card-item>
  35. </div>
  36. </div>
  37. <div class="action">
  38. <el-button hc-btn class="mr-4" :loading="submitLoading" @click="saveAndExit">保存并退出</el-button>
  39. <el-button hc-btn type="success" :loading="submitLoading" @click="saveAndBackStep">保存并返回上一步</el-button>
  40. <el-button hc-btn type="primary" :loading="submitLoading" @click="saveAndNextStep">保存并进入下一步</el-button>
  41. </div>
  42. </div>
  43. </template>
  44. <script setup>
  45. import { nextTick, onMounted, ref, watch } from 'vue'
  46. import { deepClone, getArrValue, getObjValue, isNullES } from 'js-fast-way'
  47. import projectApi from '~api/project/project'
  48. import contractApi from '~api/project/contract'
  49. const props = defineProps({
  50. data: {
  51. type: Object,
  52. default: () => ({}),
  53. },
  54. })
  55. //事件
  56. const emit = defineEmits(['close', 'next', 'back'])
  57. //监听数据
  58. const dataInfo = ref(props.data)
  59. watch(() => props.data, (data) => {
  60. dataInfo.value = data
  61. getDataApi()
  62. }, { deep: true })
  63. //渲染完成
  64. onMounted(() => {
  65. getDataApi()
  66. })
  67. //树配置
  68. const leftTreeRef = ref(null)
  69. const rightTreeRef = ref(null)
  70. const treeProps = {
  71. children: 'children',
  72. label: 'title',
  73. }
  74. //获取数据
  75. const getDataApi = async () => {
  76. await getProjectDeatil()
  77. if (isNullES(wbsId.value)) {
  78. return
  79. }
  80. await getLeftTreeApi()
  81. await getRightTreeApi()
  82. }
  83. //获取项目详情
  84. const wbsId = ref('')
  85. const projectInfo = ref({})
  86. const getProjectDeatil = async () => {
  87. wbsId.value = ''
  88. projectInfo.value = {}
  89. const { pid } = getObjValue(dataInfo.value)
  90. if (isNullES(pid)) return
  91. const { data } = await projectApi.detail(pid)
  92. const res = getObjValue(data)
  93. projectInfo.value = res
  94. wbsId.value = res.referenceWbsTemplateId
  95. }
  96. //获取WBS树列表
  97. const leftLoading = ref(false)
  98. const leftTreeData = ref([])
  99. const getLeftTreeApi = async () => {
  100. leftLoading.value = true
  101. const { pid } = getObjValue(dataInfo.value)
  102. const { data } = await projectApi.findProjectTree({
  103. projectId: pid,
  104. wbsId: wbsId.value,
  105. })
  106. leftTreeData.value = getArrValue(data)
  107. leftLoading.value = false
  108. }
  109. //获取右边数据
  110. const rightLoading = ref(false)
  111. const rightTreeData = ref([])
  112. const getRightTreeApi = async () => {
  113. rightLoading.value = true
  114. const { pid, cid } = getObjValue(dataInfo.value)
  115. const { data } = await contractApi.getContractInfoTree({
  116. wbsId: wbsId.value,
  117. projectId: pid,
  118. contractId: cid,
  119. })
  120. rightTreeData.value = getArrValue(data)
  121. rightLoading.value = false
  122. setRightTree()
  123. }
  124. //左边树被展开
  125. const rightExpands = ref([])
  126. const nodeLeftTreeExpand = (data) => {
  127. rightExpands.value = rightExpands.value.concat([data.id])
  128. }
  129. //左边树复选框被点击
  130. const leftNum = ref(0)
  131. const checkLeftTreeChange = () => {
  132. const e = leftTreeRef.value
  133. let checkNum = e.getCheckedKeys().length
  134. let halfNum = e.getHalfCheckedKeys().length
  135. leftNum.value = checkNum + halfNum
  136. }
  137. //右边树复选框被点击
  138. const rightNum = ref(0)
  139. const checkRightTreeChange = () => {
  140. const e = rightTreeRef.value
  141. let checkNum = e.getCheckedKeys().length
  142. let halfNum = e.getHalfCheckedKeys().length
  143. rightNum.value = checkNum + halfNum
  144. }
  145. //左边树新增到右边
  146. const addTreeClick = () => {
  147. const left = leftTreeRef.value, right = rightTreeRef.value
  148. if (rightTreeData.value.length < 1) {
  149. //直接把左边勾选的树复制到右侧
  150. let allTree = deepClone(leftTreeData.value)
  151. const halfKeys = left.getHalfCheckedKeys()
  152. const keys = left.getCheckedKeys().concat(halfKeys)
  153. getRightTree(allTree, keys)
  154. rightTreeData.value = allTree
  155. } else {
  156. //只增加右侧树没有的节点,不会覆盖右侧树多余的节点
  157. let checkNodes = left.getCheckedNodes(false, true)
  158. let rIdMap = new Map()
  159. checkNodes.forEach((data) => {
  160. rIdMap.set(data.id, data)
  161. })
  162. let lIdSet = new Set()
  163. getRightTreeData(lIdSet, rightTreeData.value)
  164. //在右侧id减去左侧有的id,剩下的就是新增的id
  165. lIdSet.forEach((id) => {
  166. rIdMap.delete(id)
  167. })
  168. let addMap = new Map()
  169. rIdMap.forEach((data) => {
  170. if (data.parentId !== '0' && data.parentId !== 0) {
  171. //在左侧树能找到父节点的,新增
  172. let lNode = right.getNode(data.parentId)
  173. if (lNode) addMap.set(data, lNode.data.id)
  174. }
  175. })
  176. //把半选和选中的数组key合并
  177. const halfKeys = left.getHalfCheckedKeys()
  178. const keys = left.getCheckedKeys().concat(halfKeys)
  179. const myArray = Array.from(addMap.keys())
  180. let myright = deepClone(myArray)
  181. getRightTree(myright, keys)
  182. myright.forEach((data) => {
  183. right.append(data, data.parentId)
  184. })
  185. }
  186. }
  187. //获取右边树
  188. const getRightTree = (arr, keys) => {
  189. //对比所有的node和选中的key
  190. for (let i = arr.length - 1; i >= 0; i--) {
  191. let isIn = false
  192. for (let j = keys.length - 1; j >= 0; j--) {
  193. if (keys[j] === arr[i].id) {
  194. isIn = true
  195. //已经匹配到的key移除,节省性能
  196. keys.splice(j, 1)
  197. break
  198. }
  199. }
  200. if (isIn) {
  201. //包含在选中的节点,如果有childer继续递归判断
  202. if (arr[i].children && arr[i].children.length) {
  203. getRightTree(arr[i].children, keys)
  204. }
  205. } else {
  206. //不包含在选中key的node删除
  207. arr.splice(i, 1)
  208. }
  209. }
  210. }
  211. const getRightTreeData = (set, arr) => {
  212. arr.forEach((data) => {
  213. set.add(data.id)
  214. if (data.children && data.children.length) {
  215. getRightTreeData(set, data.children)
  216. }
  217. })
  218. }
  219. //右边树移除
  220. const delTreeClick = () => {
  221. const left = leftTreeRef.value, right = rightTreeRef.value
  222. let delNodes = right.getCheckedNodes()
  223. //只把选中的节点移除
  224. delNodes.forEach((node) => {
  225. right.remove(node)
  226. left.setChecked(node.id, false)
  227. })
  228. checkLeftTreeChange()
  229. checkRightTreeChange()
  230. }
  231. const setRightTree = () => {
  232. let ids = []
  233. const data = rightTreeData.value
  234. for (let i = 0; i < data.length; i++) {
  235. getLeafIds(ids, data[i])
  236. }
  237. //在左边把右边的节点勾选上
  238. const e = leftTreeRef.value
  239. nextTick(() => {
  240. e.setCheckedKeys(ids, true)
  241. })
  242. }
  243. const getLeafIds = (ids, data) => {
  244. if (data.children && data.children.length) {
  245. for (let i = 0; i < data.children.length; i++) {
  246. getLeafIds(ids, data.children[i])
  247. }
  248. } else {
  249. ids.push(data.id)
  250. }
  251. }
  252. const getTreeAllId = async (name) => {
  253. let tree
  254. if (name === 'left') {
  255. tree = leftTreeRef.value
  256. } else if (name === 'right') {
  257. tree = rightTreeRef.value
  258. }
  259. let ids = []
  260. for (let i = 0; i < tree.data.length; i++) {
  261. await getIds(ids, tree.data[i])
  262. }
  263. return ids.join(',')
  264. }
  265. const getIds = async (ids, data) => {
  266. ids.push(data.id)
  267. if (data.children && data.children.length) {
  268. for (let i = 0; i < data.children.length; i++) {
  269. await getIds(ids, data.children[i])
  270. }
  271. }
  272. }
  273. //保存并退出
  274. const saveAndExit = async () => {
  275. const isRes = await saveDataApi()
  276. if (!isRes) return
  277. emit('close', dataInfo.value)
  278. }
  279. //保存并返回上一步
  280. const saveAndBackStep = async () => {
  281. const isRes = await saveDataApi()
  282. if (!isRes) return
  283. emit('back', dataInfo.value)
  284. }
  285. //保存并进入下一步
  286. const saveAndNextStep = async () => {
  287. const isRes = await saveDataApi()
  288. if (!isRes) return
  289. emit('next', dataInfo.value)
  290. }
  291. //保存
  292. const submitLoading = ref(false)
  293. const saveDataApi = async () => {
  294. submitLoading.value = true
  295. const ids = await getTreeAllId('right')
  296. const { pid, cid } = getObjValue(dataInfo.value)
  297. const { code } = await contractApi.submitWbsTreeInContract({
  298. wbsId: wbsId.value,
  299. projectId: pid,
  300. contractId: cid,
  301. wbsTreeIds: ids,
  302. })
  303. submitLoading.value = false
  304. if (code === 200) {
  305. window?.$message?.success('保存成功')
  306. }
  307. return code === 200
  308. }
  309. </script>
  310. <style lang="scss">
  311. @import './style/wbs';
  312. </style>