|
@@ -0,0 +1,243 @@
|
|
|
|
+<template>
|
|
|
|
+ <div class="hc-contract-info-wbs relative h-full">
|
|
|
|
+ <div class="wbs-template-body">
|
|
|
|
+ <div v-loading="leftLoading" class="left">
|
|
|
|
+ <hc-card-item scrollbar :title="`${leftNum}项`">
|
|
|
|
+ <el-tree
|
|
|
|
+ ref="leftTreeRef" :data="leftTreeData" show-checkbox node-key="id" :props="treeProps"
|
|
|
|
+ highlight-current :expand-on-click-node="false" @node-expand="nodeLeftTreeExpand"
|
|
|
|
+ @check-change="checkLeftTreeChange"
|
|
|
|
+ />
|
|
|
|
+ </hc-card-item>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="btn-action">
|
|
|
|
+ <div class="relative">
|
|
|
|
+ <div class="relative">
|
|
|
|
+ <el-button hc-btn :type="leftNum <= 0 ? '' : 'primary'" :disabled="leftNum <= 0" @click="addTreeClick">
|
|
|
|
+ <i class="i-ri-arrow-right-line" />
|
|
|
|
+ </el-button>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="relative mt-14px">
|
|
|
|
+ <el-button hc-btn :type="rightNum <= 0 ? '' : 'primary'" :disabled="rightNum <= 0" @click="delTreeClick">
|
|
|
|
+ <i class="i-ri-arrow-left-line" />
|
|
|
|
+ </el-button>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div v-loading="rightLoading" class="right">
|
|
|
|
+ <hc-card-item scrollbar :title="`${rightNum}项`">
|
|
|
|
+ <el-tree
|
|
|
|
+ ref="rightTreeRef" :data="rightTreeData" show-checkbox node-key="id" :props="treeProps"
|
|
|
|
+ highlight-current :expand-on-click-node="false" :default-expanded-keys="rightExpands"
|
|
|
|
+ @check-change="checkRightTreeChange"
|
|
|
|
+ />
|
|
|
|
+ </hc-card-item>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="action">
|
|
|
|
+ <el-button hc-btn class="mr-4" :loading="submitLoading" @click="saveAndExit">保存并退出</el-button>
|
|
|
|
+ <el-button hc-btn type="primary" :loading="submitLoading" @click="saveAndNextStep">保存并进入下一步</el-button>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+</template>
|
|
|
|
+
|
|
|
|
+<script setup>
|
|
|
|
+import { nextTick, onMounted, ref, watch } from 'vue'
|
|
|
|
+import { useAppStore } from '~src/store'
|
|
|
|
+import { deepClone, getArrValue, getObjValue, isNullES } from 'js-fast-way'
|
|
|
|
+import mainApi from '~api/project/project'
|
|
|
|
+import treeApi from '~api/wbs/tree'
|
|
|
|
+
|
|
|
|
+//缓存
|
|
|
|
+const store = useAppStore()
|
|
|
|
+
|
|
|
|
+//双向绑定
|
|
|
|
+const modelData = defineModel('modelValue', {
|
|
|
|
+ default: {},
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+//监听数据
|
|
|
|
+const formModel = ref({})
|
|
|
|
+watch(() => modelData.value, (data) => {
|
|
|
|
+ formModel.value = data
|
|
|
|
+}, { immediate: true, deep: true })
|
|
|
|
+
|
|
|
|
+//监听
|
|
|
|
+const userInfo = ref(store.getUserInfo)
|
|
|
|
+watch(() => store.getUserInfo, (info) => {
|
|
|
|
+ userInfo.value = info
|
|
|
|
+}, { immediate: true, deep: true })
|
|
|
|
+
|
|
|
|
+//渲染完成
|
|
|
|
+onMounted(() => {
|
|
|
|
+ getWbsTreeList()
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+//树配置
|
|
|
|
+const leftTreeRef = ref(null)
|
|
|
|
+const rightTreeRef = ref(null)
|
|
|
|
+const treeProps = {
|
|
|
|
+ children: 'children',
|
|
|
|
+ label: 'title',
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+//获取WBS树列表
|
|
|
|
+const wbsId = ref('')
|
|
|
|
+const wbsTreeList = ref([])
|
|
|
|
+const isLoading = ref(false)
|
|
|
|
+const getWbsTreeList = async () => {
|
|
|
|
+
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+//左边树
|
|
|
|
+const leftLoading = ref(false)
|
|
|
|
+const leftTreeData = ref([])
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+//获取右边数据
|
|
|
|
+const rightLoading = ref(false)
|
|
|
|
+const rightTreeData = ref([])
|
|
|
|
+const getRightTreeApi = async () => {
|
|
|
|
+
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+//左边树被展开
|
|
|
|
+const rightExpands = ref([])
|
|
|
|
+const nodeLeftTreeExpand = (data) => {
|
|
|
|
+ rightExpands.value = rightExpands.value.concat([data.id])
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+//左边树复选框被点击
|
|
|
|
+const leftNum = ref(0)
|
|
|
|
+const checkLeftTreeChange = () => {
|
|
|
|
+ const e = leftTreeRef.value
|
|
|
|
+ let checkNum = e.getCheckedKeys().length
|
|
|
|
+ let halfNum = e.getHalfCheckedKeys().length
|
|
|
|
+ leftNum.value = checkNum + halfNum
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+//右边树复选框被点击
|
|
|
|
+const rightNum = ref(0)
|
|
|
|
+const checkRightTreeChange = () => {
|
|
|
|
+ const e = rightTreeRef.value
|
|
|
|
+ let checkNum = e.getCheckedKeys().length
|
|
|
|
+ let halfNum = e.getHalfCheckedKeys().length
|
|
|
|
+ rightNum.value = checkNum + halfNum
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+//左边树新增到右边
|
|
|
|
+const addTreeClick = () => {
|
|
|
|
+ const left = leftTreeRef.value, right = rightTreeRef.value
|
|
|
|
+ if (rightTreeData.value.length < 1) {
|
|
|
|
+ //直接把左边勾选的树复制到右侧
|
|
|
|
+ let allTree = deepClone(leftTreeData.value)
|
|
|
|
+ const halfKeys = right.getHalfCheckedKeys()
|
|
|
|
+ const keys = right.getCheckedKeys().concat(halfKeys)
|
|
|
|
+ getRightTree(allTree, keys)
|
|
|
|
+ rightTreeData.value = allTree
|
|
|
|
+ } else {
|
|
|
|
+ //只增加右侧树没有的节点,不会覆盖右侧树多余的节点
|
|
|
|
+ let checkNodes = left.getCheckedNodes(false, true)
|
|
|
|
+
|
|
|
|
+ let rIdMap = new Map()
|
|
|
|
+ checkNodes.forEach((data) => {
|
|
|
|
+ rIdMap.set(data.id, data)
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ let lIdSet = new Set()
|
|
|
|
+ getRightTreeData(lIdSet, rightTreeData.value)
|
|
|
|
+
|
|
|
|
+ //在右侧id减去左侧有的id,剩下的就是新增的id
|
|
|
|
+ lIdSet.forEach((id) => {
|
|
|
|
+ rIdMap.delete(id)
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ let addMap = new Map()
|
|
|
|
+ rIdMap.forEach((data) => {
|
|
|
|
+ if (data.parentId !== '0' && data.parentId !== 0) {
|
|
|
|
+ //在左侧树能找到父节点的,新增
|
|
|
|
+ let lNode = right.getNode(data.parentId)
|
|
|
|
+ if (lNode) addMap.set(data, lNode.data.id)
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ //把半选和选中的数组key合并
|
|
|
|
+ const halfKeys = left.getHalfCheckedKeys()
|
|
|
|
+ const keys = left.getCheckedKeys().concat(halfKeys)
|
|
|
|
+ const myArray = Array.from(addMap.keys())
|
|
|
|
+ let myright = deepClone(myArray)
|
|
|
|
+ getRightTree(myright, keys)
|
|
|
|
+ myright.forEach((data) => {
|
|
|
|
+ right.append(data, data.parentId)
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+//获取右边树
|
|
|
|
+const getRightTree = (arr, keys) => {
|
|
|
|
+ //对比所有的node和选中的key
|
|
|
|
+ for (let i = arr.length - 1; i >= 0; i--) {
|
|
|
|
+ let isIn = false
|
|
|
|
+ for (let j = keys.length - 1; j >= 0; j--) {
|
|
|
|
+ if (keys[j] === arr[i].id) {
|
|
|
|
+ isIn = true
|
|
|
|
+ //已经匹配到的key移除,节省性能
|
|
|
|
+ keys.splice(j, 1)
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if (isIn) {
|
|
|
|
+ //包含在选中的节点,如果有childer继续递归判断
|
|
|
|
+ if (arr[i].children && arr[i].children.length) {
|
|
|
|
+ getRightTree(arr[i].children, keys)
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ //不包含在选中key的node删除
|
|
|
|
+ arr.splice(i, 1)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const getRightTreeData = (set, arr) => {
|
|
|
|
+ arr.forEach((data) => {
|
|
|
|
+ set.add(data.id)
|
|
|
|
+ if (data.children && data.children.length) {
|
|
|
|
+ getRightTreeData(set, data.children)
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+//右边树移除
|
|
|
|
+const delTreeClick = () => {
|
|
|
|
+ const left = leftTreeRef.value, right = rightTreeRef.value
|
|
|
|
+ let delNodes = right.getCheckedNodes()
|
|
|
|
+ //只把选中的节点移除
|
|
|
|
+ delNodes.forEach((node) => {
|
|
|
|
+ right.remove(node)
|
|
|
|
+ left.setChecked(node.id, false)
|
|
|
|
+ })
|
|
|
|
+ checkLeftTreeChange()
|
|
|
|
+ checkRightTreeChange()
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+//保存并退出
|
|
|
|
+const saveAndExit = async () => {
|
|
|
|
+ /*const isRes = await saveDataApi()
|
|
|
|
+ if (!isRes) return
|
|
|
|
+ emit('close', dataInfo.value)*/
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+//保存并进入下一步
|
|
|
|
+const saveAndNextStep = async () => {
|
|
|
|
+ /*const isRes = await saveDataApi()
|
|
|
|
+ if (!isRes) return
|
|
|
|
+ emit('next', dataInfo.value)*/
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const submitLoading = ref(false)
|
|
|
|
+</script>
|
|
|
|
+
|
|
|
|
+<style lang="scss">
|
|
|
|
+@import './style/wbs';
|
|
|
|
+</style>
|