ZaiZai 10 ヶ月 前
コミット
69ce7a78b0

+ 3 - 0
src/views/project/info/detail.vue

@@ -4,6 +4,7 @@
             <hc-tab-card :scrollbar="tabsKey === '1'" :tabs="tabsData" :tab-key="tabsKey" is-action-btn :disabled="isDisabled" @change="tabsChange">
                 <HcInfo v-if="tabsKey === '1'" ref="infoRef" v-model="basicForm" />
                 <HcTemplate v-if="tabsKey === '2'" ref="tempRef" v-model="basicForm" />
+                <HcLogTemp v-if="tabsKey === '3'" ref="logRef" v-model="basicForm" />
                 <template #action>
                     <el-button hc-btn class="mr-4" :loading="submitLoading" @click="saveAndExit">保存并退出</el-button>
                     <el-button v-if="tabsKey > '1'" hc-btn type="success" :loading="submitLoading" @click="saveAndBackStep">保存并返回上一步</el-button>
@@ -18,6 +19,7 @@
 import { ref, watch } from 'vue'
 import HcInfo from './info.vue'
 import HcTemplate from './template.vue'
+import HcLogTemp from './log.vue'
 import mainApi from '~api/project/project'
 import { getObjValue, isNullES } from 'js-fast-way'
 
@@ -135,6 +137,7 @@ const saveAndNextStep = async () => {
 //ref
 const infoRef = ref(null)
 const tempRef = ref(null)
+const logRef = ref(null)
 
 //保存数据
 const submitLoading = ref(false)

+ 464 - 0
src/views/project/info/log.vue

@@ -0,0 +1,464 @@
+<template>
+    <div class="hc-project-wbs-log relative h-full">
+        <div v-loading="isLoading" class="hc-flex-center relative mb-14px">
+            <span>选择WBS:</span>
+            <el-select v-model="wbsId" placeholder="请选择WBS" class="w-500px" size="large" :disabled="isDisabled" @change="wbsChange">
+                <el-option-group v-for="group in wbsTreeList" :key="group.label" :label="group.label">
+                    <el-option v-for="item in group.data" :key="item.value" :label="item.label" :value="item.value" />
+                </el-option-group>
+            </el-select>
+        </div>
+        <div class="wbs-template-body">
+            <div v-loading="leftLoading" class="left">
+                <hc-card-item scrollbar :title="`${leftNum}项`">
+                    <template #extra>
+                        <el-checkbox v-model="allChecked" label="默认全部引用" size="large" @change="checkChang" />
+                    </template>
+                    <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="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>
+</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 () => {
+    /*isLoading.value = true
+    const type = templateType.value
+    const { data } = await mainApi.findWbsTreeList(type)
+    // 格式化数据
+    const res = getObjValue(data)
+    let arr = [{ label: '公有库', data: [] }, { label: '私有库', data: [] }]
+    const wbsInfos = getArrValue(res.wbsInfos), wbsPrivates = getArrValue(res.wbsTreePrivates)
+    // 公有库
+    wbsInfos.forEach((item) => {
+        item.value = item.id
+        item.label = item.wbsName
+    })
+    arr[0].data = wbsInfos
+    // 私有库
+    wbsPrivates.forEach((item) => {
+        item.label = item.projectName
+        item.value = item.wbsId + ',' + item.projectId
+    })
+    arr[1].data = wbsPrivates
+    wbsTreeList.value = arr
+    //处理选中
+    const form = getObjValue(formModel.value)
+    wbsId.value = type === 1 ? form.referenceWbsTemplateId : form.referenceWbsTemplateIdTrial
+    if (type === 3) wbsId.value = form.referenceWbsTemplateIdMeter
+    if (type === 5) wbsId.value = form.referenceWbsTemplateIdLar
+    if (type === -1) wbsId.value = ''
+    isLoading.value = false
+    await wbsChange(wbsId.value)*/
+}
+
+//左边树
+const leftLoading = ref(false)
+const leftTreeData = ref([])
+
+//切换wbs树
+const isDisabled = ref(false)
+const referenceWbsId = ref('')
+const referenceWbsType = ref('')
+const wbsChange = async (val) => {
+    /*isDisabled.value = false
+    leftLoading.value = true
+    referenceWbsId.value = val
+    //无值
+    if (isNullES(val)) {
+        await getRightTreeApi()
+        return
+    }
+    //私有库
+    if (val.toString().indexOf(',') >= 0) {
+        referenceWbsType.value = 'private'
+        let ids = val.toString().split(',')
+        const { data } = await mainApi.findProjectTree({
+            projectId: ids[1],
+            wbsId: ids[0],
+        })
+        leftTreeData.value = getArrValue(data)
+        leftLoading.value = false
+        await getRightTreeApi()
+        return
+    }
+    //公有库
+    if (val.length > 0) {
+        referenceWbsType.value = 'public'
+        const { tenant_id } = getObjValue(userInfo.value)
+        const { data } = await treeApi.getAlltree({
+            tenantId: tenant_id,
+            type: '1',
+            wbsId: val,
+        })
+        leftTreeData.value = getArrValue(data)
+        leftLoading.value = false
+        await getRightTreeApi()
+    }*/
+}
+
+//获取右边数据
+const rightLoading = ref(false)
+const rightTreeData = ref([])
+const getRightTreeApi = async () => {
+    /*rightLoading.value = true
+    isDisabled.value = true
+    let refId = referenceWbsId.value
+    if (isNullES(refId)) {
+        isDisabled.value = false
+        rightLoading.value = false
+        return
+    }
+    //私有
+    if (refId.toString().length > 0 && referenceWbsType.value === 'private') {
+        let ids = refId.toString().split(',')
+        if (ids.length > 1) {
+            refId = ids[0]
+            let list = wbsTreeList.value[1].data
+            refId = list.filter(({ projectId }) => projectId === ids[1])[0].pkeyId
+        }
+    }
+    //引用被删会出现 wbsType -1
+    if (refId && refId > 0) {
+        let priv = {}
+        //私有
+        if (referenceWbsType.value === 'private') {
+            let list = wbsTreeList.value[1].data
+            for (let i = 0; i < list.length; i++) {
+                if (list[i].pkeyId === refId) {
+                    refId = list[i].wbsId + ',' + list[i].projectId
+                    priv.wbsId = list[i].wbsId
+                    priv.projectId = list[i].projectId
+                    priv.value = list[i].value
+                    priv.pkeyId = list[i].pkeyId
+                    break
+                }
+            }
+        } else {
+            //公有
+            refId = referenceWbsId.value
+        }
+    }
+    const form = getObjValue(formModel.value)
+    const { data } = await mainApi.findProjectTree({
+        projectId: form.id,
+        wbsId: refId,
+    })
+    const projectTree = getArrValue(data)
+    isDisabled.value = projectTree.length > 0
+    rightTreeData.value = projectTree
+    setRightTree()
+    rightLoading.value = false*/
+}
+
+//默认全部引用
+const allChecked = ref(false)
+const checkChang = () => {
+    setCheckTreeChange()
+}
+
+//左边树被展开
+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
+        setCheckTreeChange(rightTreeData.value)
+    } else {
+        //只增加右侧树没有的节点,不会覆盖右侧树多余的节点
+        let checkNodes = right.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()
+    setCheckTreeChange(delNodes)
+}
+
+const setRightTree = () => {
+    let ids = []
+    const data = rightTreeData.value
+    for (let i = 0; i < data.length; i++) {
+        getLeafIds(ids, data[i])
+    }
+    //在左边把右边的节点勾选上
+    const e = leftTreeRef.value
+    nextTick(() => {
+        e.setCheckedKeys(ids, true)
+    })
+}
+
+const getLeafIds = (ids, data) => {
+    if (data.children && data.children.length) {
+        for (let i = 0; i < data.children.length; i++) {
+            getLeafIds(ids, data.children[i])
+        }
+    } else {
+        ids.push(data.id)
+    }
+}
+
+const getTreeAllId = async (name) => {
+    let tree
+    if (name === 'left') {
+        tree = leftTreeRef.value
+    } else if (name === 'right') {
+        tree = rightTreeRef.value
+    }
+    if (allChecked.value) {
+        tree = leftTreeRef.value
+    }
+    let ids = []
+    for (let i = 0; i < tree.data.length; i++) {
+        await getIds(ids, tree.data[i])
+    }
+    return ids.join(',')
+}
+
+const getIds = async (ids, data) => {
+    ids.push(data.id)
+    if (data.children && data.children.length) {
+        for (let i = 0; i < data.children.length; i++) {
+            await getIds(ids, data.children[i])
+        }
+    }
+}
+
+//格式化wbsid
+const formatWbsId = (rid) => {
+    let refId = rid
+    if (isNullES(refId)) {
+        return refId
+    }
+    if (referenceWbsType.value === 'private' && refId.toString().length > 0) {
+        // 私有库右边树形数据是通过 pkeyId 查询的
+        let ids = refId.toString().split(',')
+        if (ids.length > 1) {
+            refId = ids[0]
+            let list = wbsTreeList.value[1].data
+            refId = list.filter(({ projectId }) => projectId === ids[1])[0].pkeyId
+            return refId
+        } else {
+            return ''
+        }
+    } else {
+        return refId
+    }
+}
+
+const rightObj = ref({})
+const setCheckTreeChange = () => {
+    let recordId = formatWbsId(wbsId.value)
+    rightObj.value = {
+        wbsId: recordId,
+        data: rightTreeData.value,
+    }
+}
+
+const getTreeObj = async () => {
+    /*const form = getObjValue(formModel.value)
+    const ids = await getTreeAllId('right')
+    const wbs_id = wbsId.value
+    let obj = {
+        wbsId: wbs_id,
+        projectId: form.id,
+        wbsType: templateType.value,
+        wbsTreeIds: ids,
+    }
+    if (wbs_id.toString().indexOf(',') >= 0) {
+        //私有库
+        obj.referenceType = 'private'
+        let ids = wbs_id.toString().split(',')
+        obj.wbsId = ids[0]
+        obj.referencePrivateWbsProjectId = ids[1]
+        obj.primaryKeyId = leftTreeData.value[0].primaryKeyId
+    } else {
+        //公有库
+        obj.referenceType = 'public'
+        obj.wbsId = wbs_id
+    }
+    return obj*/
+}
+
+defineExpose({
+    getTreeObj,
+})
+</script>
+
+<style lang="scss">
+@import './style/log';
+</style>

+ 44 - 0
src/views/project/info/style/log.scss

@@ -0,0 +1,44 @@
+.hc-project-wbs-log {
+    padding: 0 12px;
+    .wbs-template-body {
+        position: relative;
+        height: calc(100% - 65px);
+        display: flex;
+        .hc-card-item-box {
+            background: white !important;
+            border: 1px solid #eee;
+            border-radius: 5px;
+            padding: 0;
+            .hc-card-item-header {
+                height: 40px;
+                padding: 0 12px;
+                margin-bottom: 0;
+                background: #f9f9f9;
+                border-radius: 4px 4px 0 0;
+                border-bottom: 1px solid #eee;
+            }
+            .hc-card-item-body {
+                padding: 12px;
+                height: calc(100% - 40px) !important;
+            }
+        }
+        .left {
+            position: relative;
+            flex: 1;
+        }
+        .action {
+            position: relative;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            padding: 0 14px;
+            .el-button[hc-btn] {
+                border-radius: 3px
+            }
+        }
+        .right {
+            position: relative;
+            flex: 1;
+        }
+    }
+}