iZaiZaiA %!s(int64=3) %!d(string=hai) anos
pai
achega
b092f9d691

+ 4 - 4
package.json

@@ -28,13 +28,13 @@
         "autoprefixer": "^10.4.7",
         "nprogress": "^0.2.0",
         "postcss": "^8.4.16",
-        "sass": "^1.54.3",
+        "sass": "^1.54.4",
         "tailwindcss": "^3.1.8",
         "unplugin-auto-import": "^0.11.1",
-        "unplugin-vue-components": "^0.22.3",
+        "unplugin-vue-components": "^0.22.4",
         "vfonts": "^0.0.3",
-        "vite": "^3.0.3",
+        "vite": "^3.0.5",
         "vooks": "^0.2.12",
-        "vue-utils-plus": "^0.0.5"
+        "vue-utils-plus": "^0.0.5-beta.2"
     }
 }

+ 27 - 16
src/components/data-fill/nodeTree/children.vue

@@ -22,14 +22,17 @@
         </div>
     </div>
     <!--菜单-->
-    <!--n-dropdown placement="bottom" trigger="manual" :x="x" :y="y" size="huge" :options="menusData" :show="showDropdown" @clickoutside="onClickoutside" @select="handleMenuSelect"/-->
+    <n-dropdown placement="bottom" trigger="manual" :x="x" :y="y" size="huge" :options="menusData" :show="showDropdown" @clickoutside="onClickoutside" @select="handleMenuSelect"/>
 </template>
 
 <script setup>
 import {ref,watch,onMounted,nextTick} from "vue";
 import TreeNodeChildren from "./children.vue"
-import {isObject,isBoolean} from "~src/utils/lib/isApp";
+import {hIconJs} from "~src/plugins/renderele";
+import {isType} from "vue-utils-plus"
+import { NDropdown } from 'naive-ui';
 
+const { isObject, isBoolean } = isType()
 const props = defineProps({
     data: {
         type: Array,
@@ -79,7 +82,6 @@ const menuRefItem = ref(null)
 const x = ref(0);
 const y = ref(0);
 
-
 //监听
 watch(() => [
     props.data,
@@ -125,7 +127,7 @@ const setDatasToNodes = () => {
     const {key,label,children} = props.format
     const deepData = datas.value
     for (let i = 0; i < deepData.length; i++) {
-        let childNodes = deepData[i][children] //深拷贝子级数据
+        let childNodes = deepData[i][children] //子级数据
         let ifChildren = !!(childNodes && childNodes.length > 0);
         nodesArr.push({
             childNodes: childNodes, //子节点原始数据
@@ -216,18 +218,7 @@ const nodeLabelContextMenu = (e,item) => {
         e.preventDefault();
         menuRefItem.value = item;
         if (menuMark.value) {
-            for (let i = 0; i < rows.length; i++) {
-                if (rows[i].key === 'mark') {
-                    if (item?.data?.isFirst) {
-                        menusData.value[i].label = '取消标记为首件';
-                        menusData.value[i].key = 'cancel_mark';
-                    } else {
-                        menusData.value[i].label = '标记为首件';
-                        menusData.value[i].key = 'mark';
-                    }
-                    break;
-                }
-            }
+            setMenuMarkVal(rows, item)
         }
         nextTick().then(() => {
             showDropdown.value = true;
@@ -237,6 +228,26 @@ const nodeLabelContextMenu = (e,item) => {
     }
 }
 
+//设置菜单标记状态
+const setMenuMarkVal = (rows,item) => {
+    for (let i = 0; i < rows.length; i++) {
+        if (rows[i].key === 'mark' || rows[i].key === 'cancel_mark') {
+            if (item?.data?.isFirst) {
+                menusData.value[i].label = '取消标记为首件';
+                menusData.value[i].key = 'cancel_mark';
+                menusData.value[i].icon = hIconJs({
+                    name: 'grade', fill: true
+                });
+            } else {
+                menusData.value[i].label = '标记为首件';
+                menusData.value[i].key = 'mark';
+                menusData.value[i].icon = hIconJs({name: 'grade'});
+            }
+            break;
+        }
+    }
+}
+
 const onClickoutside = () => {
     menuRefItem.value = null;
     showDropdown.value = false;

+ 4 - 3
src/components/data-fill/nodeTree/index.vue

@@ -18,10 +18,11 @@
 
 <script setup>
 import {watch,ref,onMounted} from "vue";
-import {isObject} from "~src/utils/lib/isApp";
 import TreeNodeChildren from "./children.vue";
-import {getRandom} from "~src/utils/lib/tools"
+import {isType,utilsRandom} from "vue-utils-plus"
 
+const { isObject } = isType()
+const { getRandom } = utilsRandom()
 const props = defineProps({
     data: {
         type: Object,
@@ -144,7 +145,7 @@ const setTreeAutoExpandKey = () => {
 const zoomRef = ref(100)
 const treeNodeMousewheel = (event) => {
     /* 获取当前页面的缩放比 若未设置zoom缩放比,则为默认100%,即1,原图大小 */
-    let zoom = parseInt(zoomRef.value) || 100
+    let zoom = parseInt(zoomRef.value + '') || 100
     /* event.wheelDelta 获取滚轮滚动值并将滚动值叠加给缩放比zoom wheelDelta统一为±120,其中正数表示为向上滚动,负数表示向下滚动 */
     zoom += event.wheelDelta / 12
     /* 最小范围 和 最大范围 的图片缩放尺度 */

+ 7 - 7
src/components/data-fill/nodeTree/style.scss

@@ -46,11 +46,11 @@
             }
             &:after {
                 left: 50%;
-                border-left: 2px solid #999999;
+                border-left: 1.5px solid #999999;
             }
             &:not(:first-child):before,
             &:not(:last-child):after {
-                border-top: 2px solid #999999;
+                border-top: 1.5px solid #999999;
             }
             .cu-tree-node-label {
                 position: relative;
@@ -133,7 +133,7 @@
                 left: 50%;
                 width: 0;
                 height: 40px;
-                border-left: 2px solid #999999;
+                border-left: 1.5px solid #999999;
             }
             &:after {
                 content: '';
@@ -162,15 +162,15 @@
                 }
                 &:only-child:before {
                     top: 1px;
-                    border-bottom: 2px solid #999999;
+                    border-bottom: 1.5px solid #999999;
                 }
                 &:not(:first-child):before,
                 &:not(:last-child):after {
                     border-top: 0;
-                    border-left: 2px solid #999999;
+                    border-left: 1.5px solid #999999;
                 }
                 &:not(:only-child):after {
-                    border-top: 2px solid #999999;
+                    border-top: 1.5px solid #999999;
                 }
             }
             .cu-tree-node-label {
@@ -190,7 +190,7 @@
                     width: 40px;
                     height: 0;
                     border-left: 0;
-                    border-top: 2px solid #999999;
+                    border-top: 1.5px solid #999999;
                 }
                 &:after {
                     display: none;

+ 10 - 7
src/layout/index.vue

@@ -80,6 +80,7 @@ const MenuBarKey = ref(BarMenuKey);
 const RoutesName = ref(BarMenuKey);
 const RoutesTitle = ref(BarMenuTitle);
 const MenuBarData = ref(useAppState.getMenus)
+const isCollapse = ref(useAppState.getCollapse)
 
 //项目合同段
 const projectInfo = ref({});
@@ -108,16 +109,24 @@ watch(() => [
     useAppState.getColor,
     useRoutes?.name,
     useRoutes?.meta?.title,
-], ([projectContractArr, userMenus, theme, ColorVal, RouteName, RouteTitle]) => {
+    useAppState.getCollapse,
+], ([projectContractArr, userMenus, theme, ColorVal, RouteName, RouteTitle,collapse]) => {
     MenuBarData.value = userMenus
     HomeTheme.value = theme
     AppColor.value = ColorVal
     RoutesName.value = RouteName ?? 'home-index'
     MenuBarKey.value = RouteName ?? 'home-index'
     RoutesTitle.value = RouteTitle ?? ''
+    isCollapse.value = collapse
     projectContractData(projectContractArr || []);
 })
 
+//是否折叠
+const collapseChange = (bool) => {
+    isCollapse.value = bool
+    useAppState.setCollapse(bool)
+}
+
 //处理项目合同段数据
 const projectContractData = (projectContractData) => {
     if (projectContractData.length > 0) {
@@ -187,12 +196,6 @@ const projectContractChange = (val) => {
     })
 }
 
-//是否折叠
-const isCollapse = ref(false)
-const collapseChange = (bool) => {
-    isCollapse.value = bool
-}
-
 //菜单被点击
 const MenuBarChange = (item) => {
     MenuBarKey.value = item.key;

+ 3 - 0
src/layout/modules/MenuBar.vue

@@ -84,6 +84,7 @@ const MenuClick = (item) => {
             .hc-menu-icon {
                 font-size: 22px;
                 margin-right: 10px;
+                line-height: initial;
             }
             .name {
                 flex: 1;
@@ -210,6 +211,7 @@ const MenuClick = (item) => {
     }
     .el-menu-item.is-active, .el-sub-menu.is-active {
         .hc-aside-menu-item {
+            color: #ffffff !important;
             background-color: var(--el-color-primary);
             box-shadow: 3px 3px 6px 0 rgba(0, 0, 0, 0.5), -3px -3px 6px 0 rgba(153, 153, 153, 0.8);
             &::before, &::after {
@@ -247,6 +249,7 @@ const MenuClick = (item) => {
             .hc-menu-icon {
                 font-size: 22px;
                 margin-right: 10px;
+                line-height: initial;
             }
             .name {
                 flex: 1;

+ 10 - 213
src/plugins/renderele.js

@@ -1,217 +1,14 @@
-import {h,defineComponent, watch, ref} from 'vue'
-import {NButton, NPopover, NPopconfirm, NInput} from "naive-ui";
+import { h } from 'vue'
+import HcIcon from '../global/components/hc-icon/index.vue'
 
-export const hIcon = (name) => {
-    return h('i', { class: name })
+export const HcIconJs = ({ui,fill,name}) => {
+    return h(HcIcon, {
+        ui: ui ?? '',
+        fill: fill || false,
+        name: name ?? ''
+    })
 }
 
-export const HcIcon = (name) => {
-    return () => h('i', { class: name });
+export const hIconJs = (obj) => {
+    return () => HcIconJs(obj);
 }
-
-//渲染删除确认提示框
-export const renderDelPopconfirm = ({row,obj,view,text,event}) => {
-    row = row || {}; obj = obj || {}; text = text || '请谨慎考虑此操作,确认要删除吗?';
-    return h(NPopconfirm, {
-        ...obj,
-        onPositiveClick: () => event ? event(row): null
-    }, {
-        icon: () => hIcon('hcicon-bangzhu text-red'),
-        trigger: () => view,
-        default: () => text
-    });
-}
-
-//渲染气泡框
-export const renderPopover = ({disabled,view,trigger,obj}) => {
-    return h(NPopover, {
-        trigger: "hover",
-        disabled: disabled,
-        ...obj
-    }, {
-        trigger: () => trigger,
-        default: () => h('span', {}, {
-            default: () => view
-        }),
-    });
-}
-
-//渲染按钮
-export const renderButton = ({name,event,row,obj}) => {
-    row = row || {}; obj = obj || {};
-    return h(NButton, {
-        size: "small",
-        type: 'primary',
-        strong: true,
-        secondary: true,
-        ...obj,
-        onClick: () => event?event(row): null
-    }, { default: () => name });
-}
-
-//渲染表格编辑和删除按钮
-export const renderTableEditDelButton = ({bubble,btn_edit,btn_del,edit_event,del_event,row}) => {
-    return h('div', {}, {
-        default: () =>  [
-            btn_edit?renderPopover({
-                disabled: (!bubble || !btn_edit?.textInfo),
-                view: btn_edit?.textInfo,
-                trigger: renderButton({
-                    name: '编辑',
-                    event: edit_event,
-                    row: row
-                }),
-            }):renderButton({
-                name: '编辑',
-                event: edit_event,
-                row: row
-            }),
-            btn_del?renderPopover({
-                disabled: (!bubble || !btn_del?.textInfo),
-                view: btn_del?.textInfo,
-                trigger: renderDelPopconfirm({
-                    row: row,
-                    event: del_event,
-                    view: renderButton({
-                        name: '删除',
-                        obj: {
-                            color:'#e54d42',
-                            class:'ml-3'
-                        }
-                    }),
-                }),
-            }):renderDelPopconfirm({
-                row: row,
-                event: del_event,
-                view: renderButton({
-                    name: '删除',
-                    obj: {
-                        color:'#e54d42',
-                        class:'ml-3'
-                    }
-                }),
-            }),
-        ]
-    });
-}
-
-//表格输入框
-export const renderTableEditInput = defineComponent({
-    props: {
-        value: [String, Number],
-        isEdit: [Boolean],
-        onUpdateValue: [Function, Array]
-    },
-    setup (props) {
-        const isEdit = ref(props.isEdit || false)
-        const inputValue = ref(props.value)
-        watch(() => [props.value, props.isEdit], ([value,edit]) => {
-            inputValue.value = value
-            isEdit.value = edit
-        })
-        function handleChange () {
-            props.onUpdateValue(inputValue.value)
-        }
-        return () => h('div', {},
-            isEdit.value ? h(NInput, {
-                value: inputValue.value,
-                onUpdateValue: (v) => {
-                    inputValue.value = v
-                },
-                onChange: handleChange,
-                onBlur: handleChange
-            }) : props.value)
-    }
-})
-
-//表格输入框按钮
-export const renderTableEditInputButton = defineComponent({
-    props: {
-        row: [Object],
-        onSave: [Function, Array],
-        onDel: [Function, Array]
-    },
-    setup (props) {
-        const isEdit = ref(props.row?.isEdit || false)
-        watch(() => [props.row?.isEdit], ([edit]) => {
-            isEdit.value = edit
-        })
-        function handleChange (row) {
-            row.isEdit = true
-            isEdit.value = true
-        }
-        function handleSaveChange (row) {
-            props.onSave(row)
-        }
-        function handleDelChange (row) {
-            props.onDel(row)
-        }
-        return () => h('div', {},
-            {
-                default: () =>  [
-                    isEdit.value ? renderButton({
-                        row: props.row,
-                        name: '保存',
-                        obj: {
-                            strong: false,
-                            secondary: false
-                        },
-                        event: handleSaveChange
-                    }):renderButton({
-                        row: props.row,
-                        name: '编辑',
-                        event: handleChange,
-                    }),
-                    renderDelPopconfirm({
-                        row: props.row,
-                        event: handleDelChange,
-                        view: renderButton({
-                            name: '删除',
-                            obj: {
-                                color:'#e54d42',
-                                class:'ml-3'
-                            }
-                        })
-                    })
-                ]
-            })
-    }
-})
-
-
-
-
-
-//---------- 准备废弃以下的方法 -----------------
-export const smallButton = (name, row, event, obj={}) => {
-    return h(NButton, {
-        size: "small",
-        type: 'primary',
-        ...obj,
-        onClick: () => event(row)
-    }, { default: () => name });
-}
-
-export const smallThinButton = (name, row, event,obj={}) => {
-    return h(NButton, {
-        size: "small",
-        type: 'primary',
-        strong: true,
-        secondary: true,
-        ...obj,
-        onClick: () => event(row)
-    }, { default: () => name });
-}
-
-export const smallPopover = (disabled, value, trigger) => {
-    return h(NPopover, {
-        trigger: "hover",
-        disabled: disabled,
-    }, {
-        trigger: () => trigger,
-        default: () => h('span', {}, {
-            default: () => value
-        }),
-    });
-}
-

+ 8 - 1
src/store/index.js

@@ -30,6 +30,7 @@ export const useAppStore = defineStore('main', {
         orderServiceTipModal: appStore.getStoreData('orderServiceTipModal') ?? 1, //0不弹出,1弹出
         shotWebRtc: appStore.getStoreData('shotWebRtc') || 0, //WebRtc截图方式: 0关闭,1开启
         fullScreen: appStore.getStoreData('fullScreen') || 0, //全屏截图:0关闭,1开启
+        isCollapse: appStore.getStoreData('isCollapse') || false, //菜单折叠
         isScreenShort: false,
     }),
     getters: {
@@ -57,7 +58,8 @@ export const useAppStore = defineStore('main', {
         getScreenShort: state => state.isScreenShort,
         getOrderServiceTipModal: state => state.orderServiceTipModal,
         getShotWebRtc: state => state.shotWebRtc,
-        getFullScreen: state => state.fullScreen
+        getFullScreen: state => state.fullScreen,
+        getCollapse: state => state.isCollapse
     },
     actions: {
         //主题信息
@@ -149,6 +151,10 @@ export const useAppStore = defineStore('main', {
             this.fullScreen = value
             appStore.setStoreData('fullScreen',value)
         },
+        setCollapse(value) { //菜单折叠
+            this.isCollapse = value
+            appStore.setStoreData('isCollapse',value)
+        },
         //清除缓存和token
         clearStoreData() {
             //清除状态
@@ -166,6 +172,7 @@ export const useAppStore = defineStore('main', {
             this.orderServiceTipModal = null
             this.shotWebRtc = null
             this.fullScreen = null
+            this.isCollapse = false
             //清除缓存
             appStore.clearAllStore()
             authStore.removeToken()

+ 23 - 0
src/styles/app/element.scss

@@ -265,3 +265,26 @@
     width: 100%;
 }
 
+//naiveui 鼠标右键菜单的图标
+.n-dropdown-menu .n-dropdown-option .n-dropdown-option-body .n-dropdown-option-body__prefix.n-dropdown-option-body__prefix--show-icon {
+    font-size: 22px;
+}
+
+//树
+.el-tree.hc-tree-node {
+    --el-fill-color-blank: transparent;
+    --el-tree-node-hover-bg-color: var(--el-color-primary-light-9);
+    --el-tree-text-color: #50545E;
+    --el-tree-expand-icon-color: #838791;
+    background: var(--el-fill-color-blank);
+    color: var(--el-tree-text-color);
+    .el-tree-node__content {
+        border-radius: 2px;
+    }
+    &.el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content {
+        background-color: var(--el-color-primary-light-7);
+    }
+    .el-tree-node__expand-icon {
+        font-size: 16px;
+    }
+}

+ 66 - 56
src/styles/data-fill/wbs.scss

@@ -1,14 +1,13 @@
 .hc-layout-box {
     display: flex;
     position: relative;
-    height: calc(100% - 60px);
+    height: 100%;
     .hc-layout-left-box {
-        position: relative;
-        background: white;
-        overflow: auto;
-        border-top: 1px solid #EEEEEE;
-        border-right: 1px solid #EEEEEE;
         width: 382px;
+        position: relative;
+        background: #f1f5f8;
+        border-radius: 10px;
+        box-shadow: -2px 0 10px 0 rgba(32,37,50,0.03), 0 10px 21px 20px rgba(32,37,50,0.03);
         .horizontal-drag-line {
             position: absolute;
             right: 0;
@@ -17,74 +16,85 @@
             height: 100%;
             user-select: none;
             cursor: col-resize;
-            background-color: #e4e4e4;
-            transition: background-color 0.2s;
-            &:hover {
-                background-color: rgba(119, 119, 119, .5);
-            }
+            background-color: #00000000;
         }
         .hc-project-box {
             position: relative;
             padding: 15px 24px;
-            border-bottom: 1px solid #EEEEEE;
-            .project-alias-box {
-                position: relative;
-                color: var(--hc-primary);
+            display: flex;
+            align-items: flex-start;
+            border-bottom: 1px solid #E9E9E9;
+            .hc-project-icon-box {
+                font-size: 30px;
+                color: var(--el-color-primary);
             }
-            .project-name {
+            .project-name-box {
+                flex: auto;
                 position: relative;
-                color: #999999;
-                margin-top: 10px;
+                overflow: hidden;
+                .project-alias {
+                    color: var(--el-color-primary);
+                }
+                .project-name {
+                    margin-top: 6px;
+                    color: #838791;
+                }
             }
         }
-        .hc-el-tree-box {
+        .hc-tree-box {
             position: relative;
             padding: 15px 20px;
-            height: calc(100% - 191px);
-            overflow: auto;
-        }
-    }
-    .hc-tree-foot-tip-box {
-        position: absolute;
-        border-top: 1px solid #EEEEEE;
-        padding: 15px 24px;
-        width: 100%;
-        bottom: 0;
-        .dot-view {
-            position: relative;
-            display: inline-flex;
-            width: 50%;
-            align-items: center;
-            padding-left: 24px;
-            margin-top: 15px;
-            &:before {
-                position: absolute;
-                left: 0;
-                content: "";
-                width: 15px;
-                height: 15px;
-                background-color: inherit;
-                border-radius: 25px;
-            }
-            &.green:before {
-                background-color: #3eb93b;
-            }
-            &.black:before {
-                background-color: #111111;
+            height: calc(100% - 187px);
+            .hc-search-tree-val {
+                position: relative;
+                margin-bottom: 24px;
             }
-            &.orange:before {
-                background-color: #f37b1d;
+            .hc-tree-scrollbar {
+                position: relative;
+                height: calc(100% - 68px);
             }
-            &.blue:before {
-                background-color: #0081ff;
+        }
+        .hc-tree-foot-tip-box {
+            position: absolute;
+            border-top: 1px solid #E9E9E9;
+            padding: 15px 24px;
+            width: 100%;
+            bottom: 0;
+            .dot-view {
+                position: relative;
+                display: inline-flex;
+                width: 50%;
+                align-items: center;
+                padding-left: 24px;
+                margin-top: 15px;
+                &:before {
+                    position: absolute;
+                    left: 0;
+                    content: "";
+                    width: 15px;
+                    height: 15px;
+                    background-color: inherit;
+                    border-radius: 25px;
+                }
+                &.green:before {
+                    background-color: #1ECC95;
+                }
+                &.black:before {
+                    background-color: #111111;
+                }
+                &.orange:before {
+                    background-color: #f37b1d;
+                }
+                &.blue:before {
+                    background-color: #0081ff;
+                }
             }
         }
     }
     .hc-layout-content-box {
         flex: 1;
-        overflow: auto;
         position: relative;
-        padding: 0 24px 15px 20px;
+        margin-left: 24px;
         .hc-card-max-h-box {
             position: relative;
             height: calc(100% - 56px);

+ 1 - 7
src/styles/icon/material.scss

@@ -14,6 +14,7 @@
     word-wrap: normal;
     direction: ltr;
     -webkit-font-smoothing: antialiased;
+    line-height: 0;
 }
 
 @font-face {
@@ -25,11 +26,4 @@
 
 .material-symbols-rounded[class*='fill'] {
     font-family: 'Material Symbols Rounded Fill';
-    letter-spacing: normal;
-    text-transform: none;
-    display: inline-block;
-    white-space: nowrap;
-    word-wrap: normal;
-    direction: ltr;
-    -webkit-font-smoothing: antialiased;
 }

+ 263 - 0
src/views/data-fill/components/WbsTree.vue

@@ -0,0 +1,263 @@
+<template>
+    <ElTree class="hc-tree-node" ref="ElTreeRef" :props="ElTreeProps" :load="ElTreeLoadNode" node-key="primaryKeyId" lazy highlight-current accordion @node-click="ElTreeClick" @node-contextmenu="ElTreeLabelContextMenu">
+        <template #default="{ node, data }">
+            <div class="data-custom-tree-node" :id="'wbs-tree-' + data['primaryKeyId']">
+                <!--树组件,节点名称-->
+                <div class="label" :class="node.level === 1?'level-name':''">
+                    <span :class="data?.colorStatus === 2?'text-blue':data?.colorStatus === 3?'text-orange':data?.colorStatus === 4?'text-green':''">{{ node.label }}</span>
+                </div>
+                <!--树组件,操作菜单-->
+                <div class="menu-icon" :class="node.showTreeMenu?'show':''" v-if="node.level !== 1 && menusData.length > 0" @click.stop>
+                    <n-dropdown placement="bottom-end" trigger="click" size="huge" :options="menusData" @select="ElTreeMenuClick($event,node,data)" @update:show="ElTreeMenuShow($event,node)">
+                        <div class="cu-tree-node-popover-menu-icon">
+                            <HcIcon name="menu" ui="text-2xl"/>
+                        </div>
+                    </n-dropdown>
+                </div>
+                <!--树组件,操作菜单 END-->
+            </div>
+        </template>
+    </ElTree>
+    <n-dropdown placement="bottom" trigger="manual" :x="x" :y="y" size="huge" :options="menusData" :show="showDropdown" @clickoutside="onClickoutside" @select="handleMenuSelect" v-if="menusData.length > 0"/>
+</template>
+
+<script setup>
+import {ref,nextTick,watch} from "vue";
+import {hIconJs} from "~src/plugins/renderele";
+import dataFillQuery from '~api/data-fill/query';
+import { NDropdown } from 'naive-ui';
+
+//参数
+const props = defineProps({
+    menus: {
+        type: Array,
+        default: () => ([])
+    },
+    props: {
+        type: Object,
+        default: () => ({
+            label: 'label',
+            children: 'children',
+            isLeaf: 'leaf'
+        })
+    },
+    params: {
+        type: Object,
+        default: () => ({})
+    },
+    autoExpandKeys: {
+        type: Array,
+        default: () => ([])
+    },
+    isMark: {
+        type: Boolean,
+        default: false
+    },
+})
+
+//变量
+const ElTreeRef = ref(null)
+const showDropdown = ref(false)
+const treeRefNode = ref(null)
+const treeRefData = ref(null)
+const ElTreeProps = ref({
+    label: props.props?.label || 'title',
+    children: props.props?.children || 'children',
+    isLeaf: props.props?.isLeaf || 'leaf'
+})
+const menusData = ref(props.menus)
+const x = ref(0);
+const y = ref(0);
+
+const menuMark = ref(props.isMark)
+const TreeExpandKey = ref(props.autoExpandKeys)
+
+//监听
+watch(() => [
+    props.menus,
+    props.isMark,
+    props.autoExpandKeys
+], ([menus,isMark,expandKeys]) => {
+    menusData.value = menus
+    menuMark.value = isMark
+    TreeExpandKey.value = expandKeys
+    if (expandKeys?.length > 0) {
+        setTreeAutoExpandKey()
+    }
+})
+
+nextTick().then(() => {
+    //1551464734866276744
+    //console.log(ElTreeRef.value)
+    if (TreeExpandKey.value?.length > 0) {
+        setTreeAutoExpandKey()
+    }
+});
+
+//懒加载tree,自动展开上次记忆的节点
+const setTreeAutoExpandKey = () => {
+    const keys = TreeExpandKey.value || []
+    let num = 0, numMax = keys.length;
+    let timer = setInterval(() => {
+        if(num < numMax) {
+            document.getElementById('wbs-tree-' + keys[num])?.click()
+            num++;
+        } else {
+            clearInterval(timer);
+        }
+    }, 800);
+}
+
+//事件
+const emit = defineEmits(['menuTap','node-click','load-click'])
+
+//树形结构异步加载数据
+const ElTreeLoadNode = (node, resolve) => {
+    let contractIdRelation = '', parentId = '';
+    if (node.level !== 0) {
+        const nodeData = node?.data ?? {};
+        contractIdRelation = nodeData?.contractIdRelation ?? ''
+        parentId = contractIdRelation ? nodeData?.primaryKeyId : nodeData?.id
+    }
+    emit('load-click', {node, data: node.data})
+    //获取数据
+    dataFillQuery.queryWbsTreeData({
+        contractId: props.params?.contractId || '',
+        contractIdRelation,
+        parentId
+    }).then(res => {
+        resolve(res?.data?.data || [])
+    }).catch(() => {
+        resolve([])
+    })
+}
+
+//节点被点击
+const ElTreeClick = (data,node,e) => {
+    emit('node-click', {node, data, e})
+}
+
+//鼠标右键事件
+const ElTreeLabelContextMenu = (e,data,node) => {
+    const rows = menusData.value || [];
+    if (node.level !== 1 && rows.length > 0) {
+        e.preventDefault();
+        treeRefNode.value = node;
+        treeRefData.value = data;
+        if (menuMark.value) {
+            setMenuMarkVal(rows,data)
+        }
+        nextTick().then(() => {
+            showDropdown.value = true;
+            x.value = e.clientX;
+            y.value = e.clientY;
+        });
+    }
+}
+
+//设置菜单标记状态
+const setMenuMarkVal = (rows,item) => {
+    for (let i = 0; i < rows.length; i++) {
+        if (rows[i].key === 'mark' || rows[i].key === 'cancel_mark') {
+            if (item.isFirst) {
+                menusData.value[i].label = '取消标记为首件';
+                menusData.value[i].key = 'cancel_mark';
+                menusData.value[i].icon = hIconJs({
+                    name: 'grade', fill: true
+                });
+            } else {
+                menusData.value[i].label = '标记为首件';
+                menusData.value[i].key = 'mark';
+                menusData.value[i].icon = hIconJs({name: 'grade'});
+            }
+            break;
+        }
+    }
+}
+
+//鼠标右键菜单被点击
+const handleMenuSelect = (key) => {
+    const node = treeRefNode.value;
+    const data = treeRefData.value;
+    showDropdown.value = false;
+    emit('menuTap', {key,node,data})
+}
+const onClickoutside = () => {
+    treeRefNode.value = null;
+    treeRefData.value = null;
+    showDropdown.value = false;
+}
+//菜单被点击
+const ElTreeMenuClick = (key,node,data) => {
+    emit('menuTap', {key,node,data})
+}
+//菜单是否显示
+const ElTreeMenuShow = (key,node) => {
+    node.showTreeMenu = key
+}
+
+//设置树菜单的标记数据
+const setElTreeMenuMark = (keys,isFirst) => {
+    keys.forEach(item => {
+        //根据 data 或者 key 拿到 Tree 组件中的 node
+        let node = ElTreeRef.value.getNode(item)
+        if (!!node) node.data.isFirst = isFirst;
+    })
+}
+
+//设置树菜单的标记数据
+const removeElTreeNode = (key) => {
+    //根据 data 或者 key 拿到 Tree 组件中的 node
+    let node = ElTreeRef.value.getNode(key)
+    //删除 Tree 中的一个节点,使用此方法必须设置 node-key 属性
+    ElTreeRef.value.remove(node)
+}
+
+// 暴露出去
+defineExpose({
+    setElTreeMenuMark,
+    removeElTreeNode
+})
+</script>
+
+<style lang="scss" scoped>
+.data-custom-tree-node {
+    position: relative;
+    display: flex;
+    align-items: center;
+    width: 100%;
+    color: var(--ui-TC);
+    .label {
+        flex: auto;
+        font-size: 16px;
+    }
+    .label.level-name {
+        font-size: 18px;
+        font-weight: bold;
+    }
+    .menu-icon {
+        position: relative;
+        font-size: 20px;
+        opacity: 0;
+        pointer-events: none;
+        transition: opacity 0.2s;
+        .cu-tree-node-popover-menu-icon {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+        }
+    }
+    &:hover {
+        .menu-icon {
+            opacity: 1;
+            pointer-events: all;
+            cursor: context-menu;
+        }
+    }
+    .menu-icon.show {
+        opacity: 1;
+        pointer-events: all;
+        cursor: context-menu;
+    }
+}
+</style>

+ 84 - 35
src/views/data-fill/wbs.vue

@@ -1,20 +1,67 @@
 <template>
-    <HcCard title="您好" :scrollbar="false" v-if="wbsTypeTabKey === 'map'">
-        <template #extra>
-            <HcNewSwitch :datas="wbsTypeTab" :keys="wbsTypeTabKey" @change="wbsTypeTabChange"/>
-        </template>
-        <NodeTree ref="NodeTreeRef" :data="NodeTreeData" :format="NodeTreeFormat" :autoExpandKeys="TreeAutoExpandKeys" :menus="ElTreeMenu" :isMark="TreeMark" isColor
-                  :accordion='NodeTreeAccordion' @nodeClick="NodeTreeClick" @expand="expandClick" @nodeDblClick="NodeTreeDblClick" @menuClick="NodeTreeMenuClick"/>
-        <template #action>
-            <div class="hc-tree-mp-tip-box">
-                <div class="dot-view green">已审批</div>
-                <div class="dot-view black">未填报</div>
-                <div class="dot-view orange">已填报-待审批</div>
-                <div class="dot-view blue">已填报-未上报</div>
+    <div class="h-full">
+        <HcCard :scrollbar="false" v-if="wbsTypeTabKey === 'map'">
+            <template #extra>
+                <HcNewSwitch :datas="wbsTypeTab" :keys="wbsTypeTabKey" @change="wbsTypeTabChange"/>
+            </template>
+            <NodeTree ref="NodeTreeRef" :data="NodeTreeData" :format="NodeTreeFormat" :autoExpandKeys="TreeAutoExpandKeys" :menus="ElTreeMenu" :isMark="TreeMark" isColor
+                      :accordion='NodeTreeAccordion' @nodeClick="NodeTreeClick" @expand="expandClick" @nodeDblClick="NodeTreeDblClick" @menuClick="NodeTreeMenuClick"/>
+            <template #action>
+                <div class="hc-tree-mp-tip-box">
+                    <div class="dot-view green">已审批</div>
+                    <div class="dot-view black">未填报</div>
+                    <div class="dot-view orange">已填报-待审批</div>
+                    <div class="dot-view blue">已填报-未上报</div>
+                </div>
+            </template>
+        </HcCard>
+        <div class="hc-layout-box" v-if="wbsTypeTabKey === 'tree'">
+            <div class="hc-layout-left-box" id="wbs-left-tree" :style="'width:' + leftWidth + 'px;'">
+                <div class="hc-project-box">
+                    <div class="hc-project-icon-box">
+                        <HcIcon name="layers"/>
+                    </div>
+                    <div class="ml-2 project-name-box">
+                        <span class="text-xl text-cut project-alias">{{projectInfo['projectAlias']}}</span>
+                        <div class="text-xs text-cut project-name">{{projectInfo['name']}}</div>
+                    </div>
+                </div>
+                <div class="hc-tree-box">
+                    <div class="hc-search-tree-val">
+                        <el-input v-model="searchTreeVal" block size="large" placeholder="请输入名称关键词检索" clearable>
+                            <template #suffix>
+                                <HcIcon name="search" ui="text-2xl"/>
+                            </template>
+                        </el-input>
+                    </div>
+                    <div class="hc-tree-scrollbar">
+                        <el-scrollbar>
+                            <WbsTree ref="HcTreeRef" :params="treeParams" :props="wbsElTreeProps" :menus="ElTreeMenu" :isMark="TreeMark" :autoExpandKeys="TreeAutoExpandKeys" @node-click="wbsElTreeClick" @menuTap="ElTreeMenuClick"/>
+                        </el-scrollbar>
+                    </div>
+                </div>
+                <div class="hc-tree-foot-tip-box">
+                    <div class="dot-view green">已审批</div>
+                    <div class="dot-view black">未填报</div>
+                    <div class="dot-view orange">已填报-待审批</div>
+                    <div class="dot-view blue">已填报-未上报</div>
+                </div>
+                <!--左右拖动-->
+                <div class="horizontal-drag-line" @mousedown="onmousedown"/>
             </div>
-        </template>
-    </HcCard>
-
+            <div class="hc-layout-content-box">
+                <HcCard :scrollbar="false">
+                    <template #extra>
+                        <HcNewSwitch :datas="wbsTypeTab" :keys="wbsTypeTabKey" @change="wbsTypeTabChange"/>
+                    </template>
+                    456789
+                    <template #action>
+                        123456
+                    </template>
+                </HcCard>
+            </div>
+        </div>
+    </div>
 </template>
 
 <script setup>
@@ -22,7 +69,7 @@ import {ref,watch,nextTick,onMounted} from "vue";
 import {useRoute} from 'vue-router'
 import router from '~src/router/index';
 import {useAppStore} from "~src/store/index";
-import {HcIcon} from "~src/plugins/renderele";
+import {hIconJs} from "~src/plugins/renderele";
 import HcTabs from "~com/plugins/naive/HcTabs.vue"
 import HcTree from "~com/plugins/element/HcTree.vue"
 import BtnTab from "~com/btnTab/index.vue"
@@ -32,6 +79,7 @@ import DragModal from "~com/dragModal/index.vue"
 import ImgPreview from "~com/imgPreview/index.vue"
 import HcReportModal from "~com/reportModal/index.vue"
 import HcTreeNode from "./components/HcTreeNode.vue"
+import WbsTree from "./components/WbsTree.vue"
 import wbsApi from "~api/data-fill/wbs"
 import {deepClone} from "~src/utils/lib/util";
 import {getTokenHeader} from '~src/api/request/header';
@@ -46,17 +94,17 @@ const projectId = ref(useAppState.getProjectId);
 const contractId = ref(useAppState.getContractId);
 const projectInfo = ref(useAppState.getProjectInfo);
 const contractInfo = ref(useAppState.getContractInfo);
+const isCollapse = ref(useAppState.getCollapse)
+const bubbleVal = ref(useAppState.getBubble);
 
 //路由参数
 const routerQuery = useRoutes?.query;
 const MenuBarKey = routerQuery?.MenuBarKey || '';
 const typeName = routerQuery?.type || 'map'
 
+//自动展开缓存
 const TreeAutoExpandKeys = ref(getStore({name: 'wbsTreeExpandKeys'}) || [])
 
-//按钮气泡开关
-const bubbleVal = ref(useAppState.getBubble);
-
 //监听
 watch(() => [
     useAppState.getProjectId,
@@ -64,14 +112,14 @@ watch(() => [
     useAppState.getProjectInfo,
     useAppState.getContractInfo,
     useAppState.getBubble,
-], ([UserProjectId, UserContractId, UserProjectInfo,UserContractInfo,Bubble]) => {
-    //项目合同数据
+    useAppState.getCollapse
+], ([UserProjectId, UserContractId, UserProjectInfo, UserContractInfo, Bubble, Collapse]) => {
     projectId.value = UserProjectId
     contractId.value = UserContractId
     projectInfo.value = UserProjectInfo
     contractInfo.value = UserContractInfo
-    //按钮气泡开关
     bubbleVal.value = Bubble
+    isCollapse.value = Collapse
     setContractType(UserContractInfo?.contractType)
 })
 //获取气泡数据
@@ -135,20 +183,19 @@ const setContractType = (contractType) => {
     }
 }
 
-const wbsTypeTabChange = async (value) => {
-    wbsTypeTabKey.value = value;
+const wbsTypeTabChange = async (item) => {
+    wbsTypeTabKey.value = item?.key;
     isDrawer.value = false;
     const keys = await NodeExpandKeys(true)
     router.push({
         path: useRoutes.path,
         query: {
-            MenuBarKey: MenuBarKey,
-            type: value
+            type: item?.key
         }
     })
     nextTick(() => {
         TreeAutoExpandKeys.value = keys
-        if (value === 'map') {
+        if (item?.key === 'map') {
             queryMappingStructureTree()
         }
     })
@@ -162,31 +209,32 @@ const TreeMark = ref(false)
 const setElTreeMenu = () => {
     let newArr = [];
     if (tree_menu_edit.value) {
-        newArr.push({icon: HcIcon('hcicon-bianji'), label: '编辑节点', key: "edit"})
+        newArr.push({icon: hIconJs({name:'border_color'}), label: '编辑节点', key: "edit"})
     }
     if (tree_menu_mark.value) {
-        newArr.push({icon: HcIcon('cicon-star-o'), label: '标记为首件', key: "mark"})
+        newArr.push({icon: hIconJs({name:'grade'}), label: '标记为首件', key: "mark"})
         TreeMark.value = true
     }
     if (tree_menu_copy.value) {
-        newArr.push({icon: HcIcon('cicon-file-copy-o'), label: '复制节点', key: "copy"})
+        newArr.push({icon: hIconJs({name:'difference'}), label: '复制节点', key: "copy"})
     }
     if (tree_menu_add.value) {
-        newArr.push({icon: HcIcon('cicon-add-round-o'), label: '新增节点', key: "add"})
+        newArr.push({icon: hIconJs({name:'add_box'}), label: '新增节点', key: "add"})
     }
     if (tree_menu_up.value) {
-        newArr.push({icon: HcIcon('hcicon-shangchuan'), label: '上传图纸', key: "upload"})
+        newArr.push({icon: hIconJs({name:'backup'}), label: '上传图纸', key: "upload"})
     }
     if (tree_menu_del.value) {
-        newArr.push({icon: HcIcon('cicon-delete-line-o'), label: '删除节点', key: "del"})
+        newArr.push({icon: hIconJs({name:'delete'}), label: '删除节点', key: "del"})
     }
     if (tree_menu_sort.value) {
-        newArr.push({icon: HcIcon('cicon-sort-order'), label: '调整排序', key: "sort"})
+        newArr.push({icon: hIconJs({name:'sort'}), label: '调整排序', key: "sort"})
     }
     ElTreeMenu.value = newArr
 }
 
 //树的配置
+const searchTreeVal = ref('')
 const HcTreeRef = ref(null)
 const wbsElTreeProps = {label: 'title', children: 'children', isLeaf: 'exsitChild'}
 const treeParams = ref({contractId: contractId.value})
@@ -836,8 +884,9 @@ const ListItemOffsetTop = (offsetTop) => {
 //左右拖动,改变树形结构宽度
 const leftWidth = ref(382);
 const onmousedown = () => {
+    const leftNum = isCollapse.value ? 142 : 272
     document.onmousemove = (ve) => {
-        let diffVal = ve.clientX + 2;
+        let diffVal = ve.clientX - leftNum;
         if(diffVal >= 310 && diffVal <= 900) {
             leftWidth.value = diffVal;
         }

+ 2 - 13
src/views/other/order-service.vue

@@ -181,7 +181,7 @@ import oss from "~api/oss";
 
 //初始变量
 const { isArray } = isType()
-const { isSize } = utilsFile()
+const { isSize, base64ToFile } = utilsFile()
 const { getIndex } = utilsArray()
 const useAppState = useAppStore()
 const projectId = ref(useAppState.getProjectId);
@@ -443,21 +443,10 @@ const removeUpload = (file) => {
     })
 }
 
-//base64转成blob
-const base64toFile = (base64) => {
-    let binary = atob(base64.split(",")[1]);
-    let array = [];
-    for (let i = 0; i < binary.length; i++) {
-        array.push(binary.charCodeAt(i));
-    }
-    let blob = new Blob([new Uint8Array(array)], { type: "image/jpeg" });
-    return new File([blob], new Date() + ".jpg");
-}
-
 //上传截图文件
 const spinShow = ref(false)
 const uploadImgFile = (base64) => {
-    let fileOfBlob = base64toFile(base64);
+    let fileOfBlob = base64ToFile(base64);
     let formData = new FormData();
     formData.append("file", fileOfBlob);
     //上传文件

+ 17 - 17
yarn.lock

@@ -1125,10 +1125,10 @@ run-parallel@^1.1.9:
   dependencies:
     queue-microtask "^1.2.2"
 
-sass@^1.54.3:
-  version "1.54.3"
-  resolved "https://registry.yarnpkg.com/sass/-/sass-1.54.3.tgz#37baa2652f7f1fdadb73240ee9a2b9b81fabb5c4"
-  integrity sha512-fLodey5Qd41Pxp/Tk7Al97sViYwF/TazRc5t6E65O7JOk4XF8pzwIW7CvCxYVOfJFFI/1x5+elDyBIixrp+zrw==
+sass@^1.54.4:
+  version "1.54.4"
+  resolved "https://registry.yarnpkg.com/sass/-/sass-1.54.4.tgz#803ff2fef5525f1dd01670c3915b4b68b6cba72d"
+  integrity sha512-3tmF16yvnBwtlPrNBHw/H907j8MlOX8aTBnlNX1yrKx24RKcJGPyLhFUwkoKBKesR3unP93/2z14Ll8NicwQUA==
   dependencies:
     chokidar ">=3.0.0 <4.0.0"
     immutable "^4.0.0"
@@ -1256,10 +1256,10 @@ unplugin-auto-import@^0.11.1:
     unimport "^0.6.5"
     unplugin "^0.9.0"
 
-unplugin-vue-components@^0.22.3:
-  version "0.22.3"
-  resolved "https://registry.yarnpkg.com/unplugin-vue-components/-/unplugin-vue-components-0.22.3.tgz#e799e49486476b5413d3b0983874bc8a92429ad4"
-  integrity sha512-f31VCJF0K9oXCzKizJqNpmQz2XYTA0gjq+E5zM3hB8JxZ6cy5sXxO91fK2pI1TFGeM3JCe6yC9BJDymkMbXnNg==
+unplugin-vue-components@^0.22.4:
+  version "0.22.4"
+  resolved "https://registry.yarnpkg.com/unplugin-vue-components/-/unplugin-vue-components-0.22.4.tgz#774a96339368f3b8436fa7c20e059d9fee42c983"
+  integrity sha512-2rRZcM9OnJGXnYxQNfaceEYuPeVACcWySIjy8WBwIiN3onr980TmA3XE5pRJFt8zoQrUA+c46oyIq96noLqrEQ==
   dependencies:
     "@antfu/utils" "^0.5.2"
     "@rollup/pluginutils" "^4.2.1"
@@ -1324,13 +1324,13 @@ vfonts@^0.0.3:
   resolved "https://registry.yarnpkg.com/vfonts/-/vfonts-0.0.3.tgz#999d66fecea18efee3f2b966c81101ae8ce01a29"
   integrity sha512-nguyw8L6Un8eelg1vQ31vIU2ESxqid7EYmy8V+MDeMaHBqaRSkg3dTBToC1PR00D89UzS/SLkfYPnx0Wf23IQQ==
 
-vite@^3.0.3:
-  version "3.0.4"
-  resolved "https://registry.yarnpkg.com/vite/-/vite-3.0.4.tgz#c61688d6b97573e96cf5ac25f2d68597b5ce68e8"
-  integrity sha512-NU304nqnBeOx2MkQnskBQxVsa0pRAH5FphokTGmyy8M3oxbvw7qAXts2GORxs+h/2vKsD+osMhZ7An6yK6F1dA==
+vite@^3.0.5:
+  version "3.0.5"
+  resolved "https://registry.yarnpkg.com/vite/-/vite-3.0.5.tgz#56b8d52e00bbbd5f21d02f0868dc613b3246ecc6"
+  integrity sha512-bRvrt9Tw8EGW4jj64aYFTnVg134E8hgDxyl/eEHnxiGqYk7/pTPss6CWlurqPOUzqvEoZkZ58Ws+Iu8MB87iMA==
   dependencies:
     esbuild "^0.14.47"
-    postcss "^8.4.14"
+    postcss "^8.4.16"
     resolve "^1.22.1"
     rollup "^2.75.6"
   optionalDependencies:
@@ -1355,10 +1355,10 @@ vue-router@^4.1.3:
   dependencies:
     "@vue/devtools-api" "^6.1.4"
 
-vue-utils-plus@^0.0.5:
-  version "0.0.5"
-  resolved "https://registry.yarnpkg.com/vue-utils-plus/-/vue-utils-plus-0.0.5.tgz#456002f606fa6af628b2c821dbcb96b2c426138e"
-  integrity sha512-07LyaAQ1ytH169s0OAzDRzT9ewX4J7IWoJSDW3jDlzeVU9YPP31eOBjawnZqDCHHYRyXka44QwStul5xBqr1WQ==
+vue-utils-plus@^0.0.5-beta.2:
+  version "0.0.5-beta.2"
+  resolved "https://registry.yarnpkg.com/vue-utils-plus/-/vue-utils-plus-0.0.5-beta.2.tgz#6b0e8bc4c024cf2681ceaf5aff881bc969a78ff2"
+  integrity sha512-IY65u6W7XUhtXmhkQHW9CENzOXm4VmbDOExv06RIcFBKLXrSHHGrvy7lll6caYmlI/dwVAaM9E8upE7c6kJ36w==
 
 vue@^3.2.37:
   version "3.2.37"