log.vue 10 KB

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