소스 검색

新主题

ZaiZai 1 년 전
부모
커밋
3a1a5a9b5b

+ 0 - 5
src/App.vue

@@ -28,11 +28,6 @@ watch(() => [
 
 nextTick(() => {
     setUserTheme(appStore.getThemeVal, appStore.getColor)
-    //当屏幕分辨率宽度低于1920时,自动折叠菜单
-    const width = document.body.clientWidth
-    if (width < 1920) {
-        appStore.setCollapse(true)
-    }
     //生产环境下,检测更新
     if (import.meta.env.PROD && appStore.isSource !== 'app') {
         getVersionJsonApi()

+ 1 - 1
src/config/theme.js

@@ -13,7 +13,7 @@ import bg11 from '~src/assets/home/fd8d568edf61d6824a5a2e3e8e4a0ec4.jpg'
 //主题配置
 export default {
     color: [
-        { name: 'green', color: '#1ECC95', label: '森绿' }, { name: 'blue', color: '#0081ff', label: '蓝' },
+        { name: 'green', color: '#1ECC95', label: '森绿' }, { name: 'blue', color: '#204DA0', label: '蓝' },
         { name: 'cyan', color: '#37c0fe', label: '天青' }, { name: 'purple', color: '#8044de', label: '姹紫' },
         { name: 'mauve', color: '#b745cb', label: '木槿' }, { name: 'pink', color: '#e03997', label: '桃粉' },
         { name: 'red', color: '#e54d42', label: '嫣红' }, { name: 'orange', color: '#f37b1d', label: '橘橙' },

+ 440 - 0
src/layout/index.scss

@@ -0,0 +1,440 @@
+.hc-layout-box {
+    position: relative;
+    height: 100vh;
+    width: 100%;
+    .hc-layout-header {
+        position: relative;
+        display: flex;
+        align-items: center;
+        flex-direction: row;
+        --el-header-padding: 0;
+        --el-header-height: 44px;
+        background: var(--el-color-primary);
+        color: white;
+        .hc-layout-header-logo {
+            position: relative;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            transition: opacity 0.3s;
+            cursor: pointer;
+            height: 100%;
+            box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.4);
+            z-index: 222;
+            #logo-icon {
+                height: 28px;
+                width: 28px;
+                filter: invert(85%) sepia(91%) saturate(0%) hue-rotate(233deg) brightness(114%) contrast(101%)
+            }
+            #logo-name {
+                height: 32px;
+                margin-left: 4px;
+                filter: invert(85%) sepia(91%) saturate(0%) hue-rotate(233deg) brightness(114%) contrast(101%)
+            }
+            &:hover {
+                opacity: .8;
+            }
+        }
+        .header-top-collapse-bar {
+            position: relative;
+            height: 100%;
+            font-size: 20px;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            padding: 0 10px;
+            cursor: pointer;
+            transition: opacity .2s;
+            &:hover {
+                opacity: 0.7;
+            }
+        }
+        .header-top-menu-bar {
+            position: relative;
+            padding: 0 4px;
+            height: 100%;
+            flex: 1;
+            .el-scrollbar__view {
+                height: 100%;
+            }
+        }
+        .header-content-bar {
+            position: relative;
+            padding: 0 20px;
+            height: 100%;
+            display: flex;
+            align-items: center;
+            .header-icon-bar {
+                position: relative;
+                height: 100%;
+                display: flex;
+                justify-content: center;
+                align-items: center;
+                cursor: pointer;
+                font-size: 25px;
+                margin-right: 8px;
+                color: #efefef;
+                transition: color .2s;
+                &:hover {
+                    color: white;
+                }
+            }
+        }
+    }
+    .hc-layout-container {
+        position: relative;
+        .hc-layout-aside {
+            position: relative;
+            color: white;
+            padding: 3px 0;
+            background: var(--el-color-primary);
+        }
+        .hc-layout-main {
+            position: relative;
+            overflow: hidden;
+            height: 100%;
+            --el-main-padding: 0;
+            .hc-router-menu-bar {
+                position: relative;
+                height: 36px;
+                padding: 0 10px;
+                background: white;
+                box-shadow: 0 2px 6px 0 rgba(0, 0, 0, .1);
+                z-index: 222;
+            }
+            .hc-main-page {
+                position: relative;
+                height: calc(100% - 30px);
+                overflow: hidden;
+                padding: 12px;
+            }
+        }
+    }
+}
+
+//左侧菜单
+.hc-layout-box .hc-layout-container .hc-layout-aside .el-menu {
+    --el-menu-bg-color: transparent;
+    --el-menu-text-color: #ffffff;
+    --el-menu-active-color: var(--el-menu-text-color);
+    --el-menu-hover-text-color: var(--el-menu-text-color);
+    --el-menu-hover-bg-color: var(--el-color-primary);
+    --el-menu-item-font-size: 16px;
+    --el-menu-item-height: 48px;
+    border-right: 0;
+    &.el-menu--vertical:not(.el-menu--collapse):not(.el-menu--popup-container) .el-menu-item,
+    &.el-menu--vertical:not(.el-menu--collapse):not(.el-menu--popup-container) .el-menu-item-group__title,
+    &.el-menu--vertical:not(.el-menu--collapse):not(.el-menu--popup-container) .el-sub-menu__title {
+        white-space: nowrap;
+        padding-left: 0;
+    }
+    &.el-menu--vertical:not(.el-menu--collapse):not(.el-menu--popup-container) .el-sub-menu__title {
+        padding-right: 1px;
+        border: 0;
+    }
+    .el-sub-menu__title {
+        padding: 0;
+    }
+    .el-menu-item, .el-sub-menu {
+        min-width: initial;
+        transition: 0.2s;
+        .hc-aside-menu-item {
+            flex: 1;
+            position: relative;
+            padding: 0 20px;
+            display: flex;
+            align-items: center;
+            transition: 0.2s;
+            .menu---item {
+                display: contents;
+            }
+            .hc-menu-icon {
+                position: relative;
+                font-size: 18px;
+                margin-right: 8px;
+                line-height: initial;
+            }
+            .name {
+                flex: 1;
+                width: 0;
+            }
+        }
+    }
+    .el-sub-menu .el-menu .el-menu-item {
+        padding-left: 24px !important;
+        padding-right: 1px;
+        font-size: 14px;
+        height: 46px;
+        line-height: initial;
+        .hc-aside-menu-item .hc-menu-icon {
+            margin-right: 6px;
+            font-size: 14px;
+        }
+    }
+    .el-sub-menu .el-icon {
+        display: none;
+    }
+    .el-sub-menu .el-icon.hc-icon-i {
+        position: relative;
+        display: inline-block;
+        font-size: 16px;
+        right: 15px;
+        top: initial;
+        height: initial;
+        width: initial;
+        margin-top: 0;
+        vertical-align: initial;
+    }
+    .el-sub-menu.is-active > .el-sub-menu__title {
+        background-color: var(--el-color-primary-light-3);
+    }
+    .el-menu-item.is-active {
+        background-color: var(--el-color-primary-dark-2);
+        &::after {
+            content: '';
+            position: absolute;
+            right: 0;
+            top: 0;
+            width: 3px;
+            height: 100%;
+            background-color: white;
+        }
+    }
+    .el-sub-menu .el-sub-menu__title:hover,
+    .el-menu-item:not(.is-active):hover {
+        background-color: var(--el-color-primary-dark-2);
+    }
+    //折叠状态
+    &.el-menu--collapse {
+        margin-left: 0;
+        width: 90px;
+        .el-sub-menu__title {
+            height: inherit;
+            line-height: initial;
+            width: 90px;
+            justify-content: center;
+            transition: 0.2s;
+        }
+        .el-menu-item, .el-sub-menu {
+            padding: 0 !important;
+            height: 60px;
+            line-height: initial;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            width: 90px;
+            transition: 0.2s;
+            .hc-aside-menu-item {
+                display: inline-flex;
+                align-items: center;
+                justify-content: center;
+                width: 60px;
+                height: 60px;
+                text-align: center;
+                border-radius: 10px;
+                padding: 5px;
+                flex: initial;
+                transition: 0.2s;
+                .menu---item {
+                    position: relative;
+                    display: block;
+                }
+                .hc-menu-icon {
+                    margin-right: 0;
+                }
+                .name {
+                    flex: initial;
+                    width: 100%;
+                }
+                .el-badge, .el-badge .el-badge__content {
+                    vertical-align: initial;
+                }
+                .el-badge {
+                    position: absolute;
+                    top: -20px;
+                    right: -24px;
+                }
+            }
+        }
+        .el-sub-menu .el-icon.hc-icon-i {
+            display: none;
+        }
+        .el-menu-item + .el-menu-item,
+        .el-menu-item + .el-sub-menu,
+        .el-sub-menu + .el-menu-item,
+        .el-sub-menu + .el-sub-menu {
+            margin-top: 12px;
+        }
+        .el-sub-menu.is-active > .el-sub-menu__title {
+            background-color: initial;
+        }
+        .el-menu-item.is-active {
+            background-color: initial;
+        }
+        .el-sub-menu .el-sub-menu__title:hover,
+        .el-menu-item:not(.is-active):hover {
+            background-color: initial;
+        }
+        .el-sub-menu:not(.is-active):hover,
+        .el-menu-item:not(.is-active):hover {
+            .hc-aside-menu-item {
+                background-color: var(--el-color-primary-light-9);
+                color: var(--el-color-primary);
+            }
+        }
+        .el-menu-item.is-active, .el-sub-menu.is-active {
+            .hc-aside-menu-item {
+                color: #ffffff !important;
+                background: var(--el-color-primary-dark-2);
+            }
+        }
+    }
+}
+
+//菜单路由
+.hc-layout-box .hc-layout-container .hc-layout-main .hc-router-menu-bar {
+    .el-scrollbar__view {
+        height: 100%;
+    }
+    .hc-router-tab-item {
+        position: relative;
+        height: 100%;
+        padding: 0 8px;
+        display: inline-flex;
+        align-items: center;
+        cursor: pointer;
+        color: #8F8F8F;
+        user-select: none;
+        transition: .3s;
+        .close-icon {
+            height: 30px;
+            width: 18px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            margin-left: 6px;
+            font-size: 16px;
+            cursor: pointer;
+            transition: color 0.3s;
+            &:hover {
+                color: var(--el-color-primary);
+            }
+        }
+        &::after{
+            content: '';
+            left: 0;
+            bottom: 0;
+            height: 2.5px;
+            width: 100%;
+            position: absolute;
+            background-size: 200%;
+        }
+        &:hover:not([class*='cur']) {
+            color: var(--el-color-primary);
+        }
+        &.cur {
+            color: var(--el-color-primary-dark-2);
+            &::after {
+                background: linear-gradient(to right, var(--el-color-primary-light-5), var(--el-color-primary), var(--el-color-primary-dark-2));
+            }
+        }
+    }
+    .el-scrollbar__bar.is-horizontal {
+        bottom: -10px;
+    }
+    .el-scrollbar__bar.is-vertical {
+        display: none;
+    }
+}
+
+
+.aside-menu-popper.el-popper.is-light {
+    background: initial !important;
+    border: 0 !important;
+    outline: none;
+}
+.aside-menu-popper.el-popper .el-menu--vertical .el-menu {
+    --el-menu-bg-color: #f1f5f8;
+    --el-menu-text-color: #838791;
+    --el-menu-active-color: #ffffff;
+    --el-menu-hover-bg-color: initial;
+    --el-menu-item-font-size: 16px;
+    background-color: #f1f5f8;
+    color: #838791;
+    .el-sub-menu__title {
+        padding: 0;
+        justify-content: center;
+        transition: 0.2s;
+    }
+    .el-menu-item, .el-sub-menu {
+        color: inherit;
+        padding: 0;
+        transition: 0.2s;
+        .hc-aside-menu-item {
+            flex: 1;
+            position: relative;
+            padding: 0 16px;
+            display: flex;
+            align-items: center;
+            transition: 0.2s;
+            .menu---item {
+                display: contents;
+            }
+            .hc-menu-icon {
+                font-size: 22px;
+                margin-right: 10px;
+                line-height: initial;
+            }
+            .name {
+                flex: 1;
+                width: 0;
+            }
+            .el-badge, .el-badge .el-badge__content {
+                vertical-align: initial;
+            }
+        }
+        &.is-active {
+            color: white;
+        }
+    }
+    .el-sub-menu .el-icon {
+        display: none;
+    }
+    .el-sub-menu .el-icon.hc-icon-i {
+        position: relative;
+        display: inline-block;
+        font-size: 16px;
+        right: 10px;
+        top: initial;
+        height: initial;
+        width: initial;
+        margin-top: 0;
+        vertical-align: initial;
+    }
+    .el-sub-menu:not(.is-active) .el-sub-menu__title:hover {
+        background-color: var(--el-color-primary-light-9);
+        color: var(--el-color-primary);
+    }
+    .el-menu-item:not(.is-active):hover {
+        .hc-aside-menu-item {
+            background-color: var(--el-color-primary-light-9);
+            color: var(--el-color-primary);
+        }
+    }
+    .el-menu-item.is-active {
+        .hc-aside-menu-item {
+            background-color: var(--el-color-primary);
+        }
+    }
+    .el-sub-menu.is-active .el-sub-menu__title {
+        background-color: var(--el-color-primary-light-9);
+        color: var(--el-color-primary);
+    }
+}
+
+.aside-menu-popper.el-popper .el-menu--vertical.home-index .el-menu {
+    --el-menu-bg-color: initial;
+    --el-menu-text-color: initial;
+    color: white;
+    background-color: var(--el-color-primary-dark-2);
+}

+ 80 - 346
src/layout/index.vue

@@ -1,292 +1,106 @@
 <template>
     <el-container class="hc-layout-box">
-        <div v-show="MenuBarKey === 'home-index'" class="hc-layout-bg-box">
-            <img v-if="HomeTheme.bg" id="imagebox" :src="HomeTheme.bg" alt="" crossOrigin="anonymous">
-        </div>
-        <el-aside
-            :class="[isCollapse ? 'is-collapse' : '', MenuBarKey]" :width="isCollapse ? '90px' : '250px'"
-            class="hc-aside-box"
-        >
-            <div class="hc-aside-logo-box" @click="logoClick">
+        <el-header class="hc-layout-header">
+            <div class="hc-layout-header-logo" style="width: 200px">
                 <img id="logo-icon" :src="appLogoIcon" alt="">
                 <img v-show="!isCollapse" id="logo-name" :src="appLogoName" alt="">
             </div>
-            <div class="hc-aside-menu-box">
-                <el-scrollbar>
-                    <MenuBar
-                        :collapse="isCollapse" :cur="MenuBarKey" :datas="MenuBarData" :msg-count="msgCount"
-                        @change="MenuBarChange"
-                    />
-                </el-scrollbar>
+            <div class="header-top-collapse-bar" @click="collapseChange">
+                <HcIcon v-if="isCollapse" name="menu-unfold" />
+                <HcIcon v-else name="menu-fold" />
             </div>
-            <div class="hc-aside-bar-box">
-                <div :class="isCollapse ? '' : 'active'" @click="collapseChange(false)">
-                    <HcIcon name="menu-unfold" />
-                </div>
-                <div v-show="!isCollapse" :class="isCollapse ? 'active' : ''" @click="collapseChange(true)">
-                    <HcIcon name="menu-fold" />
-                </div>
+            <div class="header-top-menu-bar">
+                <HcTopMenuBar @load="topMenuLoad" @change="topMenuChange" />
             </div>
-        </el-aside>
-        <el-container :class="MenuBarKey === 'home-index' ? 'home' : ''" class="hc-container-view">
-            <el-header class="hc-header-view">
-                <div id="hc-header-page-name" class="hc-header-page-name">
-                    {{ RoutesTitle }}
-                </div>
-                <div class="hc-header-top-menu-bar">
-                    <TopMenuBar />
+            <div class="header-content-bar">
+                <HcCascader @send="cascaderSend" @change="cascaderChange" />
+                <HelpInfoBar />
+                <ConfigBar />
+                <UserInfoBar @load="userInfoLoad" />
+            </div>
+        </el-header>
+        <el-container class="hc-layout-container">
+            <el-aside class="hc-layout-aside" :class="[isCollapse ? 'is-collapse' : '']" :width="isCollapse ? '90px' : '200px'">
+                <MenuBar :collapse="isCollapse" :cur="menuBarKey" :datas="menuBarData" :msg-count="msgCount" @change="menuBarChange" />
+            </el-aside>
+            <el-main class="hc-layout-main">
+                <div class="hc-router-menu-bar">
+                    <RouterMenu />
                 </div>
-                <div class="hc-header-content">
-                    <div class="hc-header-cascader-box">
-                        <div class="project-name-box">
-                            {{ projectInfo.projectAlias }} / {{ contractInfo.name }}
-                        </div>
-                        <el-cascader
-                            ref="ElCascaderRef" v-model="projectValue"
-                            :clearable="userInfo?.role_id === '1123598816738675201'"
-                            :filterable="userInfo?.role_id === '1123598816738675201'"
-                            :options="projectContract"
-                            :props="projectProps" placeholder="请选择项目"
-                            @change="projectContractChange"
-                        />
+                <div id="hc-main-box" class="hc-main-page">
+                    <div class="h-full">
+                        <router-view v-if="reloadRouter" v-slot="{ Component }">
+                            <transition name="fade-transform">
+                                <keep-alive :max="10">
+                                    <component :is="Component" :msg-count="msgCount" />
+                                </keep-alive>
+                            </transition>
+                        </router-view>
                     </div>
-                    <HelpInfoBar />
-                    <ConfigBar />
-                    <UserInfoBar />
                 </div>
-            </el-header>
-            <el-main id="hc-main-box" class="hc-main-box">
-                <router-view v-if="reloadRouter" v-slot="{ Component }">
-                    <transition name="fade-transform">
-                        <keep-alive :max="10" exclude="home,home-index,home-config,other-file-view,other-file-form">
-                            <component :is="Component" :msg-count="msgCount" />
-                        </keep-alive>
-                    </transition>
-                </router-view>
             </el-main>
         </el-container>
     </el-container>
 </template>
 
 <script setup>
-import { nextTick, onMounted, ref, watch } from 'vue'
-import { useRoute, useRouter } from 'vue-router'
+import { nextTick, onMounted, ref } from 'vue'
 import { useAppStore } from '~src/store'
-import MenuBar from './modules/MenuBar.vue'
-import HelpInfoBar from './modules/HelpInfoBar.vue'
-import UserInfoBar from './modules/UserInfoBar.vue'
-import ConfigBar from './modules/ConfigBar.vue'
-import TopMenuBar from './modules/TopMenuBar.vue'
-import { initButtons, initProjectContract } from '~sto/app'
-import website from '~src/config/index'
-import { setImageColorStyle } from 'js-fast-way'
-import { setAppName } from '~uti/tools'
+import { useRoute, useRouter } from 'vue-router'
+import HcSocket from '~src/plugins/HcSocket'
+import { getObjValue } from 'js-fast-way'
 
 //初始组合式
 const router = useRouter()
 const useRoutes = useRoute()
-const useAppState = useAppStore()
+const store = useAppStore()
 
-//路由参数
-const routerQuery = useRoutes?.query
 const reloadRouter = ref(true)
-const BarMenuKey = useRoutes?.name ?? 'home-index'
-const BarMenuTitle = useRoutes?.meta?.title ?? ''
-
-//系统信息
-const appTitle = ref(useAppState.getTitle)
-const appLogoIcon = ref(useAppState.getLogoIcon)
-const appLogoName = ref(useAppState.getLogoName)
-
-//主题和色调变量
-const HomeTheme = ref(useAppState.getHomeTheme)
-const AppColor = ref(useAppState.getColor)
 
+//子组件
+import HcTopMenuBar from './modules/HcTopMenu.vue'
+import HcCascader from './modules/Cascader.vue'
+import UserInfoBar from './modules/UserInfoBar.vue'
+import HelpInfoBar from './modules/HelpInfoBar.vue'
+import ConfigBar from './modules/ConfigBar.vue'
+import RouterMenu from './modules/RouterMenu.vue'
+import MenuBar from '~src/layout/modules/MenuBar.vue'
 
-//顶部菜单数据和相关处理
-const MenuBarKey = ref(BarMenuKey)
-const RoutesName = ref(BarMenuKey)
-const RoutesTitle = ref(BarMenuTitle)
-const MenuBarData = ref(useAppState.getMenus)
-const isCollapse = ref(useAppState.getCollapse)
-const userInfo = ref(useAppState.getUserInfo)
+// logo
+const appLogoIcon = ref(store.getLogoIcon)
+const appLogoName = ref(store.getLogoName)
 
-//项目合同段
-const projectInfo = ref({})
-const contractInfo = ref({})
-const projectContract = ref([])
-const projectValue = ref(null)
-const projectProps = ref({
-    value: 'id',
-    label: 'projectAlias',
-    children: 'contractInfoList',
-})
+//菜单数据
+const menuBarKey = ref('')
+const menuBarData = ref([])
 
 //渲染完成
 onMounted(() => {
-    initButtons()
-    initProjectContract()
-    const info = useAppState.getProjectContract || []
-    projectContractData(info)
-    useAppState.barMenuName = BarMenuTitle
-    setIsCollapse(RoutesName.value)
-    setInitSocket()
-    setLogoImageColor()
-    setLogoNameColor(useAppState.getTheme)
-})
 
-//监听
-watch(() => [
-    useAppState.getProjectContract,
-    useAppState.getMenus,
-    useAppState.getHomeTheme,
-    useRoutes?.name,
-    useRoutes?.meta?.title,
-    useAppState.getCollapse,
-], ([projectContractArr, userMenus, theme, RouteName, RouteTitle, collapse]) => {
-    HomeTheme.value = theme
-    MenuBarData.value = userMenus
-    RoutesName.value = RouteName ?? 'home-index'
-    MenuBarKey.value = RouteName ?? 'home-index'
-    RoutesTitle.value = RouteTitle ?? ''
-    isCollapse.value = collapse
-    setIsCollapse(RoutesName.value)
-    projectContractData(projectContractArr || [])
-    useAppState.barMenuName = RouteTitle ?? ''
-    setAppName(appTitle.value)
 })
 
-//监听
-watch(() => [
-    useAppState.getTitle,
-    useAppState.getLogoIcon,
-    useAppState.getLogoName,
-    useAppState.getColor,
-], ([Title, LogoIcon, LogoName, ColorVal]) => {
-    appTitle.value = Title
-    appLogoIcon.value = LogoIcon
-    appLogoName.value = LogoName
-    AppColor.value = ColorVal
-    setLogoImageColor()
-})
-
-//设置Logo图片颜色
-const setLogoImageColor = () => {
-    setImageColorStyle('logo-icon', AppColor.value?.color)
-}
-
-const setLogoNameColor = (theme) => {
-    try {
-        let filter = 'invert(85%) sepia(91%) saturate(0%) hue-rotate(233deg) brightness(114%) contrast(101%)'
-        if (theme === 'light') {
-            filter = 'invert(0%) sepia(100%) saturate(0%) hue-rotate(235deg) brightness(107%) contrast(103%)'
-        }
-        document.getElementById('logo-name').style.filter = filter
-    } catch {
-    }
-}
-
-//设置折叠
-const setIsCollapse = (key) => {
-    if (key === 'data-fill-wbs') {
-        isCollapse.value = true
-        useAppState.setCollapse(true)
-    }
-}
-
-//是否折叠
-const collapseChange = (bool) => {
+// 是否折叠
+const isCollapse = ref(false)
+const collapseChange = () => {
+    const bool = !isCollapse.value
     isCollapse.value = bool
-    useAppState.setCollapse(bool)
+    store.setCollapse(bool)
 }
 
-//处理项目合同段数据
-const projectContractData = (projectContractData) => {
-    if (projectContractData.length > 0) {
-        //处理别名
-        projectContractData.forEach(item => {
-            let contractArr = item['contractInfoList'] || []
-            contractArr.forEach(items => {
-                items['projectAlias'] = items['name']
-            })
-        })
-        //处理其他数据
-        projectContract.value = projectContractData
-        const projectId = useAppState.getProjectId //项目ID
-        const contractId = useAppState.getContractId //合同段ID
-        const UserProjectInfo = useAppState.getProjectInfo
-        const UserContractInfo = useAppState.getContractInfo
-        //查询缓存的选中ID是否存在
-        const pid = projectContractData.findIndex(item => Number(item.id) === Number(projectId))
-        const contractList = projectContractData[pid]?.contractInfoList || []
-        const cid = contractList.findIndex(item => Number(item.id) === Number(contractId))
-        //如果缓存的选中ID不存在
-        if (cid === -1) {
-            //取项目数组中的第一个数据
-            let letProjectInfo = projectContractData[0]
-            let contractInfoList = letProjectInfo?.contractInfoList || []
-            let letContractInfo = contractInfoList[0] || {}
-            projectValue.value = letContractInfo?.id
-            projectInfo.value = letProjectInfo
-            contractInfo.value = letContractInfo
-            //设置缓存
-            useAppState.setProjectInfo(letProjectInfo)
-            useAppState.setContractInfo(letContractInfo)
-            useAppState.setProjectId(letProjectInfo?.id)
-            useAppState.setContractId(letContractInfo?.id)
-            //发送消息
-            socketSend(letProjectInfo?.id + ',' + letContractInfo?.id)
-        } else {
-            projectValue.value = String(contractId)
-            projectInfo.value = UserProjectInfo
-            contractInfo.value = UserContractInfo
-            //发送消息
-            socketSend(projectId + ',' + contractId)
-        }
-    } else {
-        projectContract.value = []
-        projectValue.value = null
-        projectInfo.value = {}
-        contractInfo.value = {}
-    }
+//顶部菜单导航
+const topMenuLoad = (key) => {
+    console.log(key)
 }
 
-//项目被选择
-const ElCascaderRef = ref(null)
-const projectContractChange = (val) => {
-    if (val) {
-        const Nodes = ElCascaderRef.value.getCheckedNodes()
-        const UserProjectInfo = Nodes[0].parent.data
-        const UserContractInfo = Nodes[0].data
-        //缓存项目数据
-        useAppState.setProjectId(val[0])
-        useAppState.setContractId(val[1])
-        useAppState.setProjectInfo(UserProjectInfo)
-        useAppState.setContractInfo(UserContractInfo)
-        //更改界面更新
-        projectInfo.value = UserProjectInfo
-        contractInfo.value = UserContractInfo
-        window.$message?.info('切换了项目,数据更新中')
-        //发送消息
-        socketSend(val[0] + ',' + val[1])
-        //刷新路由
-        reloadRouter.value = false
-        nextTick(() => {
-            reloadRouter.value = true
-        })
-    }
+//顶部菜单导航被点击
+const topMenuChange = (data) => {
+    menuBarData.value = data
 }
 
 //菜单被点击
-const MenuBarChange = (item) => {
-    MenuBarKey.value = item?.code
-    setIsCollapse(item?.code)
-    router.push({ name: item?.code })
-}
-
-//首页
-const logoClick = () => {
-    router.push({ name: 'home-index' })
+const menuBarChange = ({ code }) => {
+    menuBarKey.value = code
+    router.push({ name: code })
 }
 
 //消息数量
@@ -301,107 +115,27 @@ const msgCount = ref({
     messageCount_5: 0,
 })
 
-//推送系统
-let socket
-const setInitSocket = () => {
-    const user_id = userInfo.value.user_id
-    socket = new WebSocket(website.socket + user_id)
-    try {
-        socket.onopen = function (evt) {
-            console.log('websocket链接成功')
-        }
-        socket.onclose = function (evt) {
-            console.log('websocket连接已断开')
-        }
-        socket.onmessage = function ({ data }) {
-            if (data) {
-                msgCount.value = JSON.parse(data)
-            }
-            console.log(data, '消息信息')
-        }
-        socket.onerror = function ({ data }) {
-            console.log('发生错误:', data)
-        }
-    } catch {
-    }
+//用户信息
+const userInfoLoad = ({ user_id }) => {
+    /*HcSocket.create(user_id, (data) => {
+        msgCount.value = getObjValue(data)
+    })*/
 }
 
-//发送消息
-const socketSend = (msg) => {
-    try {
-        if (socket) {
-            socket.send(msg)
-        } else {
-            setTimeout(() => {
-                socket.send(msg)
-            }, 1000)
-        }
-    } catch {
-    }
+//项目合同段的ID
+const cascaderSend = ({ projectId, contractId }) => {
+    //HcSocket.send(projectId + ',' + contractId)
 }
-</script>
 
-<style lang="scss" scoped>
-@import "./layout.scss";
-</style>
+// 项目切换
+const cascaderChange = () => {
+    reloadRouter.value = false
+    nextTick(() => {
+        reloadRouter.value = true
+    })
+}
+</script>
 
 <style lang="scss">
-.hc-layout-box .hc-container-view {
-    .hc-header-view .hc-header-content .hc-header-cascader-box {
-        .el-cascader {
-            width: 100%;
-        }
-        .el-cascader .el-input .el-input__wrapper {
-            padding: 4px 15px;
-            border: 1px solid #00000000;
-            border-radius: 100px;
-            background: #f1f5f8;
-            color: #202532;
-            box-shadow: var(--hc-shadow);
-            .el-input__inner, .el-input__suffix {
-                color: #202532;
-            }
-        }
-        .el-cascader .el-input.is-focus .el-input__wrapper {
-            box-shadow: 4px 4px 8px 0 rgba(54, 92, 167, 0.15), -4px -4px 8px 0px #ffffff;
-        }
-        .el-cascader .el-input .icon-arrow-down {
-            font-size: 18px;
-            font-weight: bold;
-        }
-    }
-    &.home {
-        .hc-header-view .hc-header-content .hc-header-cascader-box {
-            .el-cascader .el-input .el-input__wrapper {
-                background-color: #00000000;
-                border: 1px solid white;
-                box-shadow: initial;
-                .el-input__inner, .el-input__suffix {
-                    color: white;
-                }
-            }
-            .el-cascader .el-input.is-focus .el-input__wrapper {
-                box-shadow: initial;
-            }
-        }
-        .hc-header-view .hc-header-top-menu-bar {
-            padding-left: 0;
-            .hc-top-menu-bar .bar-menu-content .bar-menu-btn {
-                background: rgba(255, 255, 255, .2);
-                border-color: rgba(255, 255, 255, .2);
-                color: rgba(255, 255, 255, .7);
-                backdrop-filter: blur(20px);
-                -webkit-backdrop-filter: blur(20px);
-                &:hover:not([class*='cur']) {
-                    background: var(--el-color-primary-light-9);
-                    color: #838791;
-                }
-                &:active:not([class*='cur']) {
-                    background: var(--el-color-primary-light-8);
-                    color: #838791;
-                }
-            }
-        }
-    }
-}
+@import "./index.scss";
 </style>

+ 407 - 0
src/layout/index_bak.vue

@@ -0,0 +1,407 @@
+<template>
+    <el-container class="hc-layout-box">
+        <div v-show="MenuBarKey === 'home-index'" class="hc-layout-bg-box">
+            <img v-if="HomeTheme.bg" id="imagebox" :src="HomeTheme.bg" alt="" crossOrigin="anonymous">
+        </div>
+        <el-aside
+            :class="[isCollapse ? 'is-collapse' : '', MenuBarKey]" :width="isCollapse ? '90px' : '250px'"
+            class="hc-aside-box"
+        >
+            <div class="hc-aside-logo-box" @click="logoClick">
+                <img id="logo-icon" :src="appLogoIcon" alt="">
+                <img v-show="!isCollapse" id="logo-name" :src="appLogoName" alt="">
+            </div>
+            <div class="hc-aside-menu-box">
+                <el-scrollbar>
+                    <MenuBar
+                        :collapse="isCollapse" :cur="MenuBarKey" :datas="MenuBarData" :msg-count="msgCount"
+                        @change="MenuBarChange"
+                    />
+                </el-scrollbar>
+            </div>
+            <div class="hc-aside-bar-box">
+                <div :class="isCollapse ? '' : 'active'" @click="collapseChange(false)">
+                    <HcIcon name="menu-unfold" />
+                </div>
+                <div v-show="!isCollapse" :class="isCollapse ? 'active' : ''" @click="collapseChange(true)">
+                    <HcIcon name="menu-fold" />
+                </div>
+            </div>
+        </el-aside>
+        <el-container :class="MenuBarKey === 'home-index' ? 'home' : ''" class="hc-container-view">
+            <el-header class="hc-header-view">
+                <div id="hc-header-page-name" class="hc-header-page-name">
+                    {{ RoutesTitle }}
+                </div>
+                <div class="hc-header-top-menu-bar">
+                    <TopMenuBar />
+                </div>
+                <div class="hc-header-content">
+                    <div class="hc-header-cascader-box">
+                        <div class="project-name-box">
+                            {{ projectInfo.projectAlias }} / {{ contractInfo.name }}
+                        </div>
+                        <el-cascader
+                            ref="ElCascaderRef" v-model="projectValue"
+                            :clearable="userInfo?.role_id === '1123598816738675201'"
+                            :filterable="userInfo?.role_id === '1123598816738675201'"
+                            :options="projectContract"
+                            :props="projectProps" placeholder="请选择项目"
+                            @change="projectContractChange"
+                        />
+                    </div>
+                    <HelpInfoBar />
+                    <ConfigBar />
+                    <UserInfoBar />
+                </div>
+            </el-header>
+            <el-main id="hc-main-box" class="hc-main-box">
+                <router-view v-if="reloadRouter" v-slot="{ Component }">
+                    <transition name="fade-transform">
+                        <keep-alive :max="10" exclude="home,home-index,home-config,other-file-view,other-file-form">
+                            <component :is="Component" :msg-count="msgCount" />
+                        </keep-alive>
+                    </transition>
+                </router-view>
+            </el-main>
+        </el-container>
+    </el-container>
+</template>
+
+<script setup>
+import { nextTick, onMounted, ref, watch } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import { useAppStore } from '~src/store'
+import MenuBar from './modules/MenuBar.vue'
+import HelpInfoBar from './modules/HelpInfoBar.vue'
+import UserInfoBar from './modules/UserInfoBar.vue'
+import ConfigBar from './modules/ConfigBar.vue'
+import TopMenuBar from './modules/TopMenuBar.vue'
+import { initButtons, initProjectContract } from '~sto/app'
+import website from '~src/config/index'
+import { setImageColorStyle } from 'js-fast-way'
+import { setAppName } from '~uti/tools'
+
+//初始组合式
+const router = useRouter()
+const useRoutes = useRoute()
+const useAppState = useAppStore()
+
+//路由参数
+const routerQuery = useRoutes?.query
+const reloadRouter = ref(true)
+const BarMenuKey = useRoutes?.name ?? 'home-index'
+const BarMenuTitle = useRoutes?.meta?.title ?? ''
+
+//系统信息
+const appTitle = ref(useAppState.getTitle)
+const appLogoIcon = ref(useAppState.getLogoIcon)
+const appLogoName = ref(useAppState.getLogoName)
+
+//主题和色调变量
+const HomeTheme = ref(useAppState.getHomeTheme)
+const AppColor = ref(useAppState.getColor)
+
+
+//顶部菜单数据和相关处理
+const MenuBarKey = ref(BarMenuKey)
+const RoutesName = ref(BarMenuKey)
+const RoutesTitle = ref(BarMenuTitle)
+const MenuBarData = ref(useAppState.getMenus)
+const isCollapse = ref(useAppState.getCollapse)
+const userInfo = ref(useAppState.getUserInfo)
+
+//项目合同段
+const projectInfo = ref({})
+const contractInfo = ref({})
+const projectContract = ref([])
+const projectValue = ref(null)
+const projectProps = ref({
+    value: 'id',
+    label: 'projectAlias',
+    children: 'contractInfoList',
+})
+
+//渲染完成
+onMounted(() => {
+    initButtons()
+    initProjectContract()
+    const info = useAppState.getProjectContract || []
+    projectContractData(info)
+    useAppState.barMenuName = BarMenuTitle
+    setIsCollapse(RoutesName.value)
+    setInitSocket()
+    setLogoImageColor()
+    setLogoNameColor(useAppState.getTheme)
+})
+
+//监听
+watch(() => [
+    useAppState.getProjectContract,
+    useAppState.getMenus,
+    useAppState.getHomeTheme,
+    useRoutes?.name,
+    useRoutes?.meta?.title,
+    useAppState.getCollapse,
+], ([projectContractArr, userMenus, theme, RouteName, RouteTitle, collapse]) => {
+    HomeTheme.value = theme
+    MenuBarData.value = userMenus
+    RoutesName.value = RouteName ?? 'home-index'
+    MenuBarKey.value = RouteName ?? 'home-index'
+    RoutesTitle.value = RouteTitle ?? ''
+    isCollapse.value = collapse
+    setIsCollapse(RoutesName.value)
+    projectContractData(projectContractArr || [])
+    useAppState.barMenuName = RouteTitle ?? ''
+    setAppName(appTitle.value)
+})
+
+//监听
+watch(() => [
+    useAppState.getTitle,
+    useAppState.getLogoIcon,
+    useAppState.getLogoName,
+    useAppState.getColor,
+], ([Title, LogoIcon, LogoName, ColorVal]) => {
+    appTitle.value = Title
+    appLogoIcon.value = LogoIcon
+    appLogoName.value = LogoName
+    AppColor.value = ColorVal
+    setLogoImageColor()
+})
+
+//设置Logo图片颜色
+const setLogoImageColor = () => {
+    setImageColorStyle('logo-icon', AppColor.value?.color)
+}
+
+const setLogoNameColor = (theme) => {
+    try {
+        let filter = 'invert(85%) sepia(91%) saturate(0%) hue-rotate(233deg) brightness(114%) contrast(101%)'
+        if (theme === 'light') {
+            filter = 'invert(0%) sepia(100%) saturate(0%) hue-rotate(235deg) brightness(107%) contrast(103%)'
+        }
+        document.getElementById('logo-name').style.filter = filter
+    } catch {
+    }
+}
+
+//设置折叠
+const setIsCollapse = (key) => {
+    if (key === 'data-fill-wbs') {
+        isCollapse.value = true
+        useAppState.setCollapse(true)
+    }
+}
+
+//是否折叠
+const collapseChange = (bool) => {
+    isCollapse.value = bool
+    useAppState.setCollapse(bool)
+}
+
+//处理项目合同段数据
+const projectContractData = (projectContractData) => {
+    if (projectContractData.length > 0) {
+        //处理别名
+        projectContractData.forEach(item => {
+            let contractArr = item['contractInfoList'] || []
+            contractArr.forEach(items => {
+                items['projectAlias'] = items['name']
+            })
+        })
+        //处理其他数据
+        projectContract.value = projectContractData
+        const projectId = useAppState.getProjectId //项目ID
+        const contractId = useAppState.getContractId //合同段ID
+        const UserProjectInfo = useAppState.getProjectInfo
+        const UserContractInfo = useAppState.getContractInfo
+        //查询缓存的选中ID是否存在
+        const pid = projectContractData.findIndex(item => Number(item.id) === Number(projectId))
+        const contractList = projectContractData[pid]?.contractInfoList || []
+        const cid = contractList.findIndex(item => Number(item.id) === Number(contractId))
+        //如果缓存的选中ID不存在
+        if (cid === -1) {
+            //取项目数组中的第一个数据
+            let letProjectInfo = projectContractData[0]
+            let contractInfoList = letProjectInfo?.contractInfoList || []
+            let letContractInfo = contractInfoList[0] || {}
+            projectValue.value = letContractInfo?.id
+            projectInfo.value = letProjectInfo
+            contractInfo.value = letContractInfo
+            //设置缓存
+            useAppState.setProjectInfo(letProjectInfo)
+            useAppState.setContractInfo(letContractInfo)
+            useAppState.setProjectId(letProjectInfo?.id)
+            useAppState.setContractId(letContractInfo?.id)
+            //发送消息
+            socketSend(letProjectInfo?.id + ',' + letContractInfo?.id)
+        } else {
+            projectValue.value = String(contractId)
+            projectInfo.value = UserProjectInfo
+            contractInfo.value = UserContractInfo
+            //发送消息
+            socketSend(projectId + ',' + contractId)
+        }
+    } else {
+        projectContract.value = []
+        projectValue.value = null
+        projectInfo.value = {}
+        contractInfo.value = {}
+    }
+}
+
+//项目被选择
+const ElCascaderRef = ref(null)
+const projectContractChange = (val) => {
+    if (val) {
+        const Nodes = ElCascaderRef.value.getCheckedNodes()
+        const UserProjectInfo = Nodes[0].parent.data
+        const UserContractInfo = Nodes[0].data
+        //缓存项目数据
+        useAppState.setProjectId(val[0])
+        useAppState.setContractId(val[1])
+        useAppState.setProjectInfo(UserProjectInfo)
+        useAppState.setContractInfo(UserContractInfo)
+        //更改界面更新
+        projectInfo.value = UserProjectInfo
+        contractInfo.value = UserContractInfo
+        window.$message?.info('切换了项目,数据更新中')
+        //发送消息
+        socketSend(val[0] + ',' + val[1])
+        //刷新路由
+        reloadRouter.value = false
+        nextTick(() => {
+            reloadRouter.value = true
+        })
+    }
+}
+
+//菜单被点击
+const MenuBarChange = (item) => {
+    MenuBarKey.value = item?.code
+    setIsCollapse(item?.code)
+    router.push({ name: item?.code })
+}
+
+//首页
+const logoClick = () => {
+    router.push({ name: 'home-index' })
+}
+
+//消息数量
+const msgCount = ref({
+    allCount: 0,
+    taskCount: 0,
+    messageCount: 0,
+    messageCount_1: 0,
+    messageCount_2: 0,
+    messageCount_3: 0,
+    messageCount_4: 0,
+    messageCount_5: 0,
+})
+
+//推送系统
+let socket
+const setInitSocket = () => {
+    const user_id = userInfo.value.user_id
+    socket = new WebSocket(website.socket + user_id)
+    try {
+        socket.onopen = function (evt) {
+            console.log('websocket链接成功')
+        }
+        socket.onclose = function (evt) {
+            console.log('websocket连接已断开')
+        }
+        socket.onmessage = function ({ data }) {
+            if (data) {
+                msgCount.value = JSON.parse(data)
+            }
+            console.log(data, '消息信息')
+        }
+        socket.onerror = function ({ data }) {
+            console.log('发生错误:', data)
+        }
+    } catch {
+    }
+}
+
+//发送消息
+const socketSend = (msg) => {
+    try {
+        if (socket) {
+            socket.send(msg)
+        } else {
+            setTimeout(() => {
+                socket.send(msg)
+            }, 1000)
+        }
+    } catch {
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+@import "./layout.scss";
+</style>
+
+<style lang="scss">
+.hc-layout-box .hc-container-view {
+    .hc-header-view .hc-header-content .hc-header-cascader-box {
+        .el-cascader {
+            width: 100%;
+        }
+        .el-cascader .el-input .el-input__wrapper {
+            padding: 4px 15px;
+            border: 1px solid #00000000;
+            border-radius: 100px;
+            background: #f1f5f8;
+            color: #202532;
+            box-shadow: var(--hc-shadow);
+            .el-input__inner, .el-input__suffix {
+                color: #202532;
+            }
+        }
+        .el-cascader .el-input.is-focus .el-input__wrapper {
+            box-shadow: 4px 4px 8px 0 rgba(54, 92, 167, 0.15), -4px -4px 8px 0px #ffffff;
+        }
+        .el-cascader .el-input .icon-arrow-down {
+            font-size: 18px;
+            font-weight: bold;
+        }
+    }
+    &.home {
+        .hc-header-view .hc-header-content .hc-header-cascader-box {
+            .el-cascader .el-input .el-input__wrapper {
+                background-color: #00000000;
+                border: 1px solid white;
+                box-shadow: initial;
+                .el-input__inner, .el-input__suffix {
+                    color: white;
+                }
+            }
+            .el-cascader .el-input.is-focus .el-input__wrapper {
+                box-shadow: initial;
+            }
+        }
+        .hc-header-view .hc-header-top-menu-bar {
+            padding-left: 0;
+            .hc-top-menu-bar .bar-menu-content .bar-menu-btn {
+                background: rgba(255, 255, 255, .2);
+                border-color: rgba(255, 255, 255, .2);
+                color: rgba(255, 255, 255, .7);
+                backdrop-filter: blur(20px);
+                -webkit-backdrop-filter: blur(20px);
+                &:hover:not([class*='cur']) {
+                    background: var(--el-color-primary-light-9);
+                    color: #838791;
+                }
+                &:active:not([class*='cur']) {
+                    background: var(--el-color-primary-light-8);
+                    color: #838791;
+                }
+            }
+        }
+    }
+}
+</style>

+ 160 - 0
src/layout/modules/Cascader.vue

@@ -0,0 +1,160 @@
+<template>
+    <div class="hc-header-cascader-box">
+        <div class="project-name-box">
+            {{ projectInfo.projectAlias }} / {{ contractInfo.name }}
+        </div>
+        <el-cascader
+            ref="ElCascaderRef"
+            v-model="projectValue" class="hc-header-cascader"
+            :clearable="userInfo?.role_id === '1123598816738675201'"
+            :filterable="userInfo?.role_id === '1123598816738675201'"
+            :options="projectContract"
+            :props="projectProps" placeholder="请选择项目"
+            @change="projectContractChange"
+        />
+    </div>
+</template>
+
+<script setup>
+import { onMounted, ref, watch } from 'vue'
+import { useAppStore } from '~src/store'
+import { getArrValue } from 'js-fast-way'
+import { initButtons, initProjectContract } from '~sto/app'
+
+//事件
+const emit = defineEmits(['change', 'send'])
+
+//状态
+const store = useAppStore()
+const userInfo = ref(store.getUserInfo)
+
+//项目合同段
+const projectInfo = ref({})
+const contractInfo = ref({})
+const projectContract = ref([])
+const projectValue = ref(null)
+const projectProps = ref({
+    value: 'id',
+    label: 'projectAlias',
+    children: 'contractInfoList',
+})
+
+//监听
+watch(() => store.getProjectContract, (val) => {
+    projectContractData(getArrValue(val))
+})
+
+//渲染完成
+onMounted(() => {
+    initButtons()
+    initProjectContract()
+    const info = store.getProjectContract || []
+    projectContractData(info)
+})
+
+//处理项目合同段数据
+const projectContractData = (projectContractData) => {
+    if (projectContractData.length > 0) {
+        //处理别名
+        projectContractData.forEach(item => {
+            let contractArr = item['contractInfoList'] || []
+            contractArr.forEach(items => {
+                items['projectAlias'] = items['name']
+            })
+        })
+        //处理其他数据
+        projectContract.value = projectContractData
+        const projectId = store.getProjectId //项目ID
+        const contractId = store.getContractId //合同段ID
+        const UserProjectInfo = store.getProjectInfo
+        const UserContractInfo = store.getContractInfo
+        //查询缓存的选中ID是否存在
+        const pid = projectContractData.findIndex(item => Number(item.id) === Number(projectId))
+        const contractList = projectContractData[pid]?.contractInfoList || []
+        const cid = contractList.findIndex(item => Number(item.id) === Number(contractId))
+        //如果缓存的选中ID不存在
+        if (cid === -1) {
+            //取项目数组中的第一个数据
+            let letProjectInfo = projectContractData[0]
+            let contractInfoList = letProjectInfo?.contractInfoList || []
+            let letContractInfo = contractInfoList[0] || {}
+            projectValue.value = letContractInfo?.id
+            projectInfo.value = letProjectInfo
+            contractInfo.value = letContractInfo
+            //设置缓存
+            store.setProjectInfo(letProjectInfo)
+            store.setContractInfo(letContractInfo)
+            store.setProjectId(letProjectInfo?.id)
+            store.setContractId(letContractInfo?.id)
+            emit('send', {
+                projectId: letProjectInfo?.id,
+                contractId: letContractInfo?.id,
+            })
+        } else {
+            projectValue.value = String(contractId)
+            projectInfo.value = UserProjectInfo
+            contractInfo.value = UserContractInfo
+            emit('send', {
+                projectId: projectId,
+                contractId: contractId,
+            })
+        }
+    } else {
+        projectContract.value = []
+        projectValue.value = null
+        projectInfo.value = {}
+        contractInfo.value = {}
+        emit('send', {
+            projectId: '',
+            contractId: '',
+        })
+    }
+}
+
+//项目被选择
+const ElCascaderRef = ref(null)
+const projectContractChange = (val) => {
+    if (val) {
+        const Nodes = ElCascaderRef.value.getCheckedNodes()
+        const UserProjectInfo = Nodes[0].parent.data
+        const UserContractInfo = Nodes[0].data
+        //缓存项目数据
+        store.setProjectId(val[0])
+        store.setContractId(val[1])
+        store.setProjectInfo(UserProjectInfo)
+        store.setContractInfo(UserContractInfo)
+        //更改界面更新
+        projectInfo.value = UserProjectInfo
+        contractInfo.value = UserContractInfo
+        window.$message?.info('切换了项目,数据更新中')
+        emit('send', {
+            projectId: val[0],
+            contractId: val[1],
+        })
+        emit('change')
+    }
+}
+</script>
+
+<style lang="scss">
+.hc-header-cascader-box {
+    position: relative;
+    margin-right: 20px;
+    .project-name-box {
+        position: relative;
+        max-width: 340px;
+        padding-right: 20px;
+        overflow: hidden;
+        z-index: -1;
+    }
+    .el-cascader.hc-header-cascader {
+        position: absolute;
+        top: 2px;
+        width: 100%;
+        .el-input .el-input__wrapper {
+            border-radius: 104px;
+            height: 28px;
+        }
+    }
+}
+</style>

+ 0 - 26
src/layout/modules/ConfigBar.vue

@@ -6,7 +6,6 @@
 
 <script setup>
 import { useRouter } from 'vue-router'
-
 const router = useRouter()
 
 //跳转到系统设置页面
@@ -16,28 +15,3 @@ const toConfigClick = () => {
     })
 }
 </script>
-
-<style lang="scss" scoped>
-.header-icon-bar {
-    position: relative;
-    height: 40px;
-    width: 40px;
-    border-radius: 100px;
-    display: flex;
-    justify-content: center;
-    align-items: center;
-    cursor: pointer;
-    margin-right: 30px;
-    font-size: 26px;
-    border: 1px solid #00000000;
-    background: #f1f5f8;
-    color: #202532;
-    box-shadow: var(--hc-shadow);
-}
-.hc-layout-box .hc-container-view.home .hc-header-view .hc-header-content .header-icon-bar {
-    border: 1px solid white;
-    color: inherit;
-    box-shadow: initial;
-    background: initial;
-}
-</style>

+ 84 - 0
src/layout/modules/HcTopMenu.vue

@@ -0,0 +1,84 @@
+<template>
+    <el-scrollbar>
+        <div class="hc-header-top-menu-bar">
+            <template v-for="(item, index) in topMenuData" :key="index">
+                <div class="item" :class="curKey === item?.code ? 'cur' : '' " @click="topMenuClick(item)">{{ item?.name }}</div>
+            </template>
+        </div>
+    </el-scrollbar>
+</template>
+
+<script setup>
+import { ref, watch } from 'vue'
+import { useRoute } from 'vue-router'
+import { useAppStore } from '~src/store'
+import HcTopMenu from '~src/plugins/HcTopMenu'
+import { getArrValue } from 'js-fast-way'
+
+const emit = defineEmits(['change', 'load'])
+
+//初始组合式
+const useRoutes = useRoute()
+const store = useAppStore()
+
+//处理菜单数据
+const setMenuItem = async (item) => {
+    emit('change', await HcTopMenu.setMenuItem(item))
+}
+
+//监听菜单数据
+const topMenuData = ref([])
+watch(() => store.getMenus, (val) => {
+    topMenuData.value = getArrValue(val)
+}, { immediate: true, deep: true })
+
+//监听路由数据
+const curKey = ref('')
+watch(() => useRoutes, (val) => {
+    HcTopMenu.initMenu({
+        routes: val,
+        menu: topMenuData.value,
+        load: (key) => {
+            curKey.value = key
+            emit('load', key)
+        },
+        change: (key, item) => {
+            curKey.value = key
+            setMenuItem(item)
+        },
+    })
+}, { immediate: true, deep: true })
+
+//菜单被点击
+const topMenuClick = (item) => {
+    setMenuItem(item)
+}
+</script>
+
+<style lang="scss">
+.hc-header-top-menu-bar {
+    position: relative;
+    height: 100%;
+    display: flex;
+    align-items: center;
+    .item {
+        position: relative;
+        cursor: pointer;
+        padding: 6px 8px;
+        border-radius: 3px;
+        color: #efefef;
+        transition: background .2s, color .2s;
+        &:hover {
+            color: white;
+            background: var(--el-color-primary-dark-2);
+        }
+        &.cur {
+            color: white;
+            background: var(--el-color-primary-dark-2);
+        }
+    }
+    .item + .item {
+        margin-left: 2px;
+    }
+}
+</style>

+ 0 - 24
src/layout/modules/HelpInfoBar.vue

@@ -112,7 +112,6 @@ const screenShortClick = () => {
             completeCallback: getScreenShotImg,
             noScroll: false,
         })
-        /*setTimeout(() => {}, 800)*/
     })
 }
 
@@ -155,29 +154,6 @@ const excelPreviewClick = () => {
 </script>
 
 <style lang="scss" scoped>
-.header-icon-bar {
-    position: relative;
-    height: 40px;
-    width: 40px;
-    border-radius: 100px;
-    display: flex;
-    justify-content: center;
-    align-items: center;
-    font-size: 26px;
-    cursor: pointer;
-    margin-right: 30px;
-    border: 1px solid #00000000;
-    background: #f1f5f8;
-    color: #202532;
-    box-shadow: var(--hc-shadow);
-}
-.hc-layout-box .hc-container-view.home .hc-header-view .hc-header-content .header-icon-bar {
-    border: 1px solid white;
-    color: inherit;
-    box-shadow: initial;
-    background: initial;
-}
-
 .header-pover-menu-list {
     position: relative;
     margin: -5px -12px;

+ 17 - 333
src/layout/modules/MenuBar.vue

@@ -1,38 +1,14 @@
 <template>
-    <el-menu :collapse="isCollapse" :default-active="curKey" class="hc-aside-menu" unique-opened>
-        <el-menu-item v-if="indexModel === '1'" index="home-index" @click="MenuClick({ code: 'home-index' })">
-            <div class="hc-aside-menu-item">
-                <div class="menu---item">
-                    <HcIcon :fill="curKey === 'home-index'" class="hc-menu-icon" name="home-3" />
-                    <div class="name">
-                        首页
-                    </div>
-                </div>
-            </div>
-        </el-menu-item>
-        <el-menu-item v-if="indexModel === '2'" index="home-index" @click="MenuClick({ code: 'home-index-static' })">
-            <div class="hc-aside-menu-item">
-                <div class="menu---item">
-                    <HcIcon :fill="curKey === 'home-index-static'" class="hc-menu-icon" name="home-3" />
-                    <div class="name">
-                        首页
-                    </div>
-                </div>
-            </div>
-        </el-menu-item>
-        <MenuItem :collapse="isCollapse" :cur="curKey" :datas="datas" :msg-count="msgCount" @change="MenuClick" />
-    </el-menu>
+    <el-scrollbar>
+        <el-menu :collapse="isCollapse" :default-active="curKey" class="hc-aside-menu" unique-opened text-color="#fff">
+            <MenuItem :collapse="isCollapse" :cur="curKey" :datas="datas" :msg-count="msgCount" @change="MenuClick" />
+        </el-menu>
+    </el-scrollbar>
 </template>
 
 <script setup>
 import { ref, watch } from 'vue'
 import MenuItem from './MenuItem.vue'
-import { getToken } from '~src/api/util/auth'
-import { getStoreValue } from '~uti/storage'
-import { getTenantDetail } from '~api/other'
-import { getObjValue } from 'js-fast-way'
-import { useAppStore } from '~src/store'
-import { isPathUrl } from '~uti/tools'
 
 const props = defineProps({
     datas: {
@@ -52,323 +28,31 @@ const props = defineProps({
         default: () => ({}),
     },
 })
+
 //事件
 const emit = defineEmits(['change'])
-const useAppState = useAppStore()
+
 //初始变量
 const curKey = ref(props.cur)
 const isCollapse = ref(props.collapse)
-//首页模式
-const indexModel = ref(useAppState.getIndexModel)
+
 //监听
 watch(() => [
     props.cur,
     props.collapse,
-    useAppState.getIndexModel,
-], ([cur, collapse, IndexModel]) => {
+], ([cur, collapse]) => {
     curKey.value = cur
     isCollapse.value = collapse
-    indexModel.value = IndexModel
 })
 
-
-const MenuClick = async (item) => {
-    if (isPathUrl(item?.path)) {
-        let token = getToken(), domain = item?.path
-        if (item?.code === 'to-archives-url') {
-            const tenantId = getStoreValue('tenantId')
-            if (tenantId === '000000' || !tenantId) {
-                domain = item?.path
-            } else {
-                const { error, code, data } = await getTenantDetail(tenantId)
-                if (!error && code === 200) {
-                    const url = getObjValue(data).domainUrl
-                    domain = url ? url : item?.path
-                }
-            }
-        }
-        window.open(domain + '/#/auth?token=' + token, '_blank')
-    } else {
-        curKey.value = item?.code || ''
-        emit('change', item)
-    }
+//处理菜单数据
+const setMenuItem = async (item) => {
+    curKey.value = item?.code || ''
+    emit('change', item)
 }
-</script>
 
-<style lang="scss">
-.hc-aside-menu.el-menu {
-    --el-menu-bg-color: initial;
-    --el-menu-text-color: #838791;
-    --el-menu-active-color: #ffffff;
-    --el-menu-hover-text-color: var(--el-color-primary);
-    --el-menu-hover-bg-color: initial;
-    --el-menu-item-font-size: 16px;
-    --el-menu-item-height: 48px;
-    margin-left: -10px;
-    border-right: 0;
-    padding: 8px 0;
-    &.el-menu--vertical:not(.el-menu--collapse):not(.el-menu--popup-container) .el-menu-item,
-    &.el-menu--vertical:not(.el-menu--collapse):not(.el-menu--popup-container) .el-menu-item-group__title,
-    &.el-menu--vertical:not(.el-menu--collapse):not(.el-menu--popup-container) .el-sub-menu__title {
-        white-space: nowrap;
-        padding-left: 0;
-    }
-    &.el-menu--vertical:not(.el-menu--collapse):not(.el-menu--popup-container) .el-sub-menu__title {
-        padding-right: 10px;
-    }
-    .el-sub-menu__title {
-        padding: 0;
-    }
-    .el-menu-item, .el-sub-menu {
-        padding: 15px 0 0 30px !important;
-        min-width: initial;
-        transition: 0.2s;
-        .hc-aside-menu-item {
-            flex: 1;
-            position: relative;
-            border-radius: 50px 0 0 50px;
-            padding: 0 16px;
-            display: flex;
-            align-items: center;
-            transition: 0.2s;
-            .menu---item {
-                display: contents;
-            }
-            .hc-menu-icon {
-                font-size: 22px;
-                margin-right: 10px;
-                line-height: initial;
-            }
-            .name {
-                flex: 1;
-                width: 0;
-            }
-            .el-badge, .el-badge .el-badge__content {
-                vertical-align: initial;
-            }
-        }
-    }
-    .el-sub-menu .el-icon {
-        display: none;
-    }
-    .el-sub-menu .el-icon.hc-icon-i {
-        position: relative;
-        display: inline-block;
-        font-size: 16px;
-        right: 15px;
-        top: initial;
-        height: initial;
-        width: initial;
-        margin-top: 0;
-        vertical-align: initial;
-    }
-    .el-sub-menu .el-sub-menu__title .hc-aside-menu-item:hover,
-    .el-menu-item:not(.is-active) .hc-aside-menu-item:hover {
-        color: var(--el-color-primary);
-    }
-    .el-sub-menu.is-active > .el-sub-menu__title > .hc-aside-menu-item {
-        color: var(--el-color-primary);
-    }
-    .el-menu-item.is-active {
-        .hc-aside-menu-item {
-            --radius-size: 20px;
-            background-color: var(--el-color-primary);
-            box-shadow: 0 2px 8px 0 var(--hc-shadow-color-5);
-            &::before, &::after {
-                content: '';
-                display: block;
-                height: var(--radius-size);
-                width: var(--radius-size);
-                position: absolute;
-                background: radial-gradient(
-                        var(--radius-size) at var(--radius-size) 0px,
-                        transparent var(--radius-size),
-                        var(--el-color-primary) var(--radius-size)
-                );
-            }
-            &::before {
-                right: 0;
-                transform: scaleX(-1);
-                top: calc(-1 * var(--radius-size));
-                z-index: 999;
-            }
-            &::after {
-                right: 0;
-                bottom: calc(-1 * var(--radius-size));
-                transform: scale(-1);
-                z-index: 999;
-            }
-        }
-    }
-}
-.hc-aside-menu.el-menu--collapse {
-    margin-left: 0;
-    width: 90px;
-    .el-sub-menu__title {
-        height: inherit;
-        line-height: initial;
-        width: 90px;
-        justify-content: center;
-        transition: 0.2s;
-    }
-    .el-menu-item, .el-sub-menu {
-        padding: 0 !important;
-        height: 60px;
-        line-height: initial;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-        width: 90px;
-        transition: 0.2s;
-        .hc-aside-menu-item {
-            display: inline-flex;
-            align-items: center;
-            justify-content: center;
-            width: 60px;
-            height: 60px;
-            text-align: center;
-            border-radius: 10px;
-            padding: 5px;
-            flex: initial;
-            transition: 0.2s;
-            .menu---item {
-                position: relative;
-                display: block;
-            }
-            .hc-menu-icon {
-                margin-right: 0;
-            }
-            .name {
-                flex: initial;
-                width: 100%;
-            }
-            .el-badge, .el-badge .el-badge__content {
-                vertical-align: initial;
-            }
-            .el-badge {
-                position: absolute;
-                top: -20px;
-                right: -24px;
-            }
-        }
-    }
-    .el-sub-menu .el-icon.hc-icon-i {
-        display: none;
-    }
-    .el-menu-item + .el-menu-item,
-    .el-menu-item + .el-sub-menu,
-    .el-sub-menu + .el-menu-item,
-    .el-sub-menu + .el-sub-menu {
-        margin-top: 24px;
-    }
-    .el-sub-menu:not(.is-active):hover,
-    .el-menu-item:not(.is-active):hover {
-        .hc-aside-menu-item {
-            background-color: var(--el-color-primary-light-9);
-            color: var(--el-color-primary);
-        }
-    }
-    .el-menu-item.is-active, .el-sub-menu.is-active {
-        .hc-aside-menu-item {
-            color: #ffffff !important;
-            background: linear-gradient(90deg, var(--el-color-primary-light-5), var(--el-color-primary) 100%);
-            box-shadow: 0 2px 8px 0 var(--hc-shadow-color-5);
-            &::before, &::after {
-                content: '';
-                display: none;
-            }
-        }
-    }
+//菜单被点击
+const MenuClick = (item) => {
+    setMenuItem(item)
 }
-.aside-menu-popper.el-popper.is-light {
-    background: initial !important;
-    border: 0 !important;
-    outline: none;
-}
-.aside-menu-popper.el-popper .el-menu--vertical .el-menu {
-    --el-menu-bg-color: #f1f5f8;
-    --el-menu-text-color: #838791;
-    --el-menu-active-color: #ffffff;
-    --el-menu-hover-bg-color: initial;
-    --el-menu-item-font-size: 16px;
-    background-color: #f1f5f8;
-    color: #838791;
-    .el-sub-menu__title {
-        padding: 0;
-        justify-content: center;
-        transition: 0.2s;
-    }
-    .el-menu-item, .el-sub-menu {
-        color: inherit;
-        padding: 0;
-        transition: 0.2s;
-        .hc-aside-menu-item {
-            flex: 1;
-            position: relative;
-            padding: 0 16px;
-            display: flex;
-            align-items: center;
-            transition: 0.2s;
-            .menu---item {
-                display: contents;
-            }
-            .hc-menu-icon {
-                font-size: 22px;
-                margin-right: 10px;
-                line-height: initial;
-            }
-            .name {
-                flex: 1;
-                width: 0;
-            }
-            .el-badge, .el-badge .el-badge__content {
-                vertical-align: initial;
-            }
-        }
-        &.is-active {
-            color: white;
-        }
-    }
-    .el-sub-menu .el-icon {
-        display: none;
-    }
-    .el-sub-menu .el-icon.hc-icon-i {
-        position: relative;
-        display: inline-block;
-        font-size: 16px;
-        right: 10px;
-        top: initial;
-        height: initial;
-        width: initial;
-        margin-top: 0;
-        vertical-align: initial;
-    }
-    .el-sub-menu:not(.is-active) .el-sub-menu__title:hover {
-        background-color: var(--el-color-primary-light-9);
-        color: var(--el-color-primary);
-    }
-    .el-menu-item:not(.is-active):hover {
-        .hc-aside-menu-item {
-            background-color: var(--el-color-primary-light-9);
-            color: var(--el-color-primary);
-        }
-    }
-    .el-menu-item.is-active {
-        .hc-aside-menu-item {
-            background-color: var(--el-color-primary);
-        }
-    }
-    .el-sub-menu.is-active .el-sub-menu__title {
-        background-color: var(--el-color-primary-light-9);
-        color: var(--el-color-primary);
-    }
-}
-
-.aside-menu-popper.el-popper .el-menu--vertical.home-index .el-menu {
-    --el-menu-bg-color: initial;
-    --el-menu-text-color: initial;
-    color: white;
-    background-color: rgba(255, 255, 255, 0.25);
-    backdrop-filter: blur(4px);
-}
-</style>
+</script>

+ 10 - 32
src/layout/modules/MenuItem.vue

@@ -1,22 +1,12 @@
 <template>
     <template v-for="item in datas" :key="item?.code">
-        <el-sub-menu
-            v-if="item?.children && item?.children.length > 0" :index="item?.code" :popper-offset="0"
-            :popper-class="`aside-menu-popper ${curKey}`"
-        >
+        <el-sub-menu v-if="item?.children && item?.children.length > 0" :index="item?.code" :popper-offset="0" :popper-class="`aside-menu-popper ${curKey}`">
             <template #title>
                 <div class="hc-aside-menu-item">
                     <div class="menu---item">
-                        <HcIcon
-                            v-if="item?.source" :name="item?.source" :fill="curKey === item?.code"
-                            class="hc-menu-icon"
-                        />
-                        <div v-if="isCollapse" class="name truncate">
-                            {{ item?.name.substring(0, 2) }}
-                        </div>
-                        <div v-else class="name truncate">
-                            {{ item?.name }}
-                        </div>
+                        <HcIcon v-if="item?.source" :name="item?.source" :fill="curKey === item?.code" class="hc-menu-icon" />
+                        <div v-if="isCollapse" class="name truncate">{{ item?.name.substring(0, 2) }}</div>
+                        <div v-else class="name truncate">{{ item?.name }}</div>
                         <el-badge v-if="item?.code === 'tasks' && msgCount?.allCount > 0" :value="msgCount.allCount" />
                     </div>
                 </div>
@@ -27,24 +17,11 @@
         <el-menu-item v-else :index="item?.code" @click="MenuClick(item)">
             <div class="hc-aside-menu-item">
                 <div class="menu---item">
-                    <HcIcon
-                        v-if="item?.source" :name="item?.source" :fill="curKey === item?.code"
-                        class="hc-menu-icon"
-                    />
-                    <div v-if="isCollapse" class="name truncate">
-                        {{ item?.name.substring(0, 2) }}
-                    </div>
-                    <div v-else class="name truncate">
-                        {{ item?.name }}
-                    </div>
-                    <el-badge
-                        v-if="item?.code === 'tasks-data' && msgCount?.taskCount > 0"
-                        :value="msgCount.taskCount"
-                    />
-                    <el-badge
-                        v-if="item?.code === 'message-data' && msgCount?.messageCount > 0"
-                        :value="msgCount.messageCount"
-                    />
+                    <HcIcon v-if="item?.source" :name="item?.source" :fill="curKey === item?.code" class="hc-menu-icon" />
+                    <div v-if="isCollapse" class="name truncate">{{ item?.name.substring(0, 2) }}</div>
+                    <div v-else class="name truncate">{{ item?.name }}</div>
+                    <el-badge v-if="item?.code === 'tasks-data' && msgCount?.taskCount > 0" :value="msgCount.taskCount" />
+                    <el-badge v-if="item?.code === 'message-data' && msgCount?.messageCount > 0" :value="msgCount.messageCount" />
                 </div>
             </div>
         </el-menu-item>
@@ -84,6 +61,7 @@ const props = defineProps({
 })
 //事件
 const emit = defineEmits(['change'])
+
 //初始变量
 const curKey = ref(props.cur)
 const isCollapse = ref(props.collapse)

+ 18 - 90
src/layout/modules/TopMenuBar.vue → src/layout/modules/RouterMenu.vue

@@ -1,35 +1,30 @@
 <template>
-    <div class="hc-top-menu-bar">
-        <el-scrollbar always>
-            <div class="bar-menu-content">
-                <div
-                    v-for="(item, index) in barMenuData" :key="item.key"
-                    :class="item.key === barRoutes.key ? 'cur' : ''"
-                    class="bar-menu-btn"
-                    @click="barMenuClick(item)" @contextmenu.prevent="barMenuContextMenu($event, item, index)"
-                >
-                    <span>{{ item.title }}</span>
-                    <div class="bar-close-icon" @click.stop="barMenuCloseClick(item, index)">
-                        <HcIcon name="close" />
-                    </div>
+    <el-scrollbar>
+        <div class="hc-router-tab-item" :class="(barRoutes.key === 'home' || barRoutes.key === 'home-index') ? 'cur' : ''" @click="toHomeClick">首页</div>
+        <template v-for="(item, index) in barMenuData" :key="item.key">
+            <div
+                :class="item.key === barRoutes.key ? 'cur' : ''" class="hc-router-tab-item"
+                @click="barMenuClick(item)" @contextmenu.prevent="barMenuContextMenu($event, item, index)"
+            >
+                <span>{{ item.title }}</span>
+                <div class="close-icon" @click.stop="barMenuCloseClick(item, index)">
+                    <HcIcon name="close" />
                 </div>
             </div>
-        </el-scrollbar>
-        <!-- 右键菜单 -->
-        <HcContextMenu ref="contextMenuRef" :datas="menusData" @item-click="handleMenuSelect" />
-    </div>
+        </template>
+    </el-scrollbar>
+    <!-- 右键菜单 -->
+    <HcContextMenu ref="contextMenuRef" :datas="menusData" @item-click="handleMenuSelect" />
 </template>
 
 <script setup>
 import { onMounted, ref, watch } from 'vue'
-import { useAppStore } from '~src/store'
 import { useRoute, useRouter } from 'vue-router'
 import { getStoreValue, setStoreValue } from '~src/utils/storage'
 
 //初始组合式
 const router = useRouter()
 const useRoutes = useRoute()
-const useAppState = useAppStore()
 
 //初始变量
 const barMenuData = ref(getStoreValue('bar-menu-datas') || [])
@@ -129,76 +124,9 @@ const barMenuCloseClick = (item, index) => {
         setStoreValue('bar-menu-datas', barMenuData.value)
     }
 }
-</script>
 
-<style lang="scss">
-.hc-top-menu-bar {
-    position: relative;
-    width: 100%;
-    padding-bottom: 10px;
-    margin-top: 10px;
-    .bar-menu-content {
-        display: flex;
-        position: relative;
-        .bar-menu-btn {
-            position: relative;
-            color: #b3b3b3;
-            padding-left: 10px;
-            padding-right: 6px;
-            height: 32px;
-            font-size: 14px;
-            display: flex;
-            align-items: center;
-            justify-content: center;
-            background: #ffffff;
-            border: 1px solid #ffffff;
-            border-radius: 4px;
-            user-select: none;
-            cursor: pointer;
-            white-space: nowrap;
-            transition: background 0.3s, color 0.3s;
-            &:hover:not([class*='cur']) {
-                background: var(--el-color-primary-light-9);
-                color: #838791;
-            }
-            &:active:not([class*='cur']) {
-                background: var(--el-color-primary-light-8);
-                color: #838791;
-            }
-            &.cur {
-                color: #ffffff;
-                cursor: default;
-                background: linear-gradient(to right, var(--el-color-primary-light-5), var(--el-color-primary), var(--el-color-primary-dark-2));
-                background-size: 200%;
-                transition: background-position 0.5s;
-            }
-            .bar-close-icon {
-                height: 30px;
-                width: 18px;
-                display: flex;
-                align-items: center;
-                justify-content: center;
-                margin-left: 6px;
-                font-size: 16px;
-                cursor: pointer;
-                transition: color 0.3s;
-                &:hover {
-                    color: var(--el-color-primary);
-                }
-            }
-        }
-        .bar-menu-btn.cur .bar-close-icon:hover {
-            color: #000000;
-        }
-        .bar-menu-btn + .bar-menu-btn {
-            margin-left: 10px;
-        }
-    }
-    .el-scrollbar__bar.is-horizontal {
-        bottom: -10px;
-    }
-    .el-scrollbar__bar.is-vertical {
-        display: none;
-    }
+//点击了首页
+const toHomeClick = () => {
+    router.push({ name: 'home' })
 }
-</style>
+</script>

+ 15 - 13
src/layout/modules/UserInfoBar.vue

@@ -29,22 +29,23 @@ import { getStoreValue } from '~src/utils/storage'
 import { calcDate, isNullES } from 'js-fast-way'
 
 //事件
-const emit = defineEmits(['change'])
+const emit = defineEmits(['load'])
+
 //变量
 const router = useRouter()
-const userStore = useAppStore()
-const userInfo = ref(userStore.getUserInfo)
+const store = useAppStore()
+const userInfo = ref(store.getUserInfo)
 const refreshLock = ref(false)
 
 //监听
-watch(() => [
-    userStore.getUserInfo,
-], ([info]) => {
+watch(() => store.getUserInfo, (info) => {
     userInfo.value = info
+    emit('load', info)
 })
 
 onMounted(() => {
     getRefreshToken()
+    emit('load', userInfo.value)
 })
 
 //刷新token
@@ -97,11 +98,12 @@ const handleSelect = (key) => {
     align-items: center;
     height: 100%;
     cursor: pointer;
-    padding-left: 24px;
+    padding-left: 16px;
+    margin-left: 4px;
     outline: none;
     .user-avatar {
-        width: 40px;
-        height: 40px;
+        width: 26px;
+        height: 26px;
         border-radius: 50%;
         background: white;
         object-fit: cover;
@@ -109,20 +111,20 @@ const handleSelect = (key) => {
     .user-name {
         font-size: 16px;
         margin-left: 10px;
-        color: #202532;
+        color: white;
     }
     .arrow-icon {
         margin-left: 5px;
         font-size: 20px;
-        color: #202532;
+        color: white;
     }
     &::before {
         position: absolute;
         content: '';
         left: 0;
         width: 0;
-        height: 24px;
-        border-left: 1px solid #ccd0de;
+        height: 20px;
+        border-left: 1px solid #7291ff;
     }
 }
 .hc-layout-box .hc-container-view.home .hc-header-view .hc-header-content .user-info-bar {

+ 56 - 0
src/plugins/HcSocket.js

@@ -0,0 +1,56 @@
+import website from '~src/config/index'
+import { isNullES } from 'js-fast-way'
+
+// 长链接推送插件
+export default class HcSocket {
+
+    static socket = null
+
+    static create(data, change) {
+        const socket = new WebSocket(website.socket + data)
+        socket.onopen = function () {
+            console.log('websocket 链接成功')
+        }
+        socket.onclose = function () {
+            console.log('websocket 链接已断开')
+        }
+        socket.onmessage = function ({ data }) {
+            if (typeof change !== 'function') {
+                return false
+            } else {
+                change(JSON.parse(data))
+            }
+        }
+        socket.onerror = function ({ data }) {
+            console.log('websocket 发生错误:', data)
+        }
+        this.socket = socket
+    }
+
+    //发送消息
+    static async send(data) {
+        const is_socket = await this.isSocket()
+        if (!is_socket) return false
+        this.socket.send(data)
+    }
+
+    //链接是否存在
+    static async isSocket(i = 0) {
+        let _this = this
+        return new Promise((resolve) => {
+            if (isNullES(_this.socket)) {
+                if (i <= 30) {
+                    setTimeout(async () => {
+                        i++
+                        resolve(await _this.isSocket(i))
+                    }, 1000)
+                } else {
+                    resolve(false)
+                }
+            } else {
+                resolve(true)
+            }
+        })
+    }
+
+}

+ 47 - 0
src/plugins/HcTopMenu.js

@@ -0,0 +1,47 @@
+import { isPathUrl } from '~uti/tools'
+import { getToken } from '~src/api/util/auth'
+import { getStoreValue } from '~uti/storage'
+import { getTenantDetail } from '~api/other'
+import { getArrValue, getObjValue } from 'js-fast-way'
+
+export default class HcTopMenu {
+
+    // 基础菜单
+    static baseMenu = [
+        'home', 'home-index', 'home-index-static', 'home-config', 'order-service', 'user-index', '403', '404', '500',
+    ]
+
+    static initMenu({ routes, menu, load, change }) {
+        const topName = routes.matched[0]?.name
+        if (this.baseMenu.includes(topName)) {
+            load(topName)
+            return false
+        }
+        for (let i = 0; i < menu.length; i++) {
+            if (menu[i].code === topName) {
+                change(topName, menu[i])
+            }
+        }
+    }
+
+    static async setMenuItem(item) {
+        if (isPathUrl(item?.path)) {
+            let token = getToken(), domain = item?.path
+            if (item?.code === 'to-archives-url') {
+                const tenantId = getStoreValue('tenantId')
+                if (tenantId === '000000' || !tenantId) {
+                    domain = item?.path
+                } else {
+                    const { error, code, data } = await getTenantDetail(tenantId)
+                    if (!error && code === 200) {
+                        const url = getObjValue(data).domainUrl
+                        domain = url ? url : item?.path
+                    }
+                }
+            }
+            window.open(domain + '/#/auth?token=' + token, '_blank')
+        } else {
+            return getArrValue(item?.children)
+        }
+    }
+}

+ 290 - 289
src/router/modules/base.js

@@ -52,150 +52,307 @@ export default [
         ],
     },
     {
-        path: '/data-fill',
-        name: 'data-fill',
+        path: '/quality-control',
+        name: 'qualityControl',
         redirect: '/data-fill/wbs',
-        meta: { title: '资料管理' },
+        meta: { title: '质量管理' },
         component: Layout,
         children: [
             {
-                path: '/data-fill/wbs',
-                name: 'data-fill-wbs',
-                meta: { title: '资料填报' },
-                component: () => import('~src/views/data-fill/wbs.vue'),
-            },
-            {
-                path: '/data-fill/query',
-                name: 'data-query',
-                meta: { title: '资料查询' },
-                component: () => import('~src/views/data-fill/query.vue'),
-            },
-            {
-                path: '/data-fill/division',
-                name: 'data-division',
-                meta: { title: '系统分部分项划分' },
-                component: () => import('~src/views/data-fill/division.vue'),
-            },
-        ],
-    },
-    {
-        path: '/ledger',
-        name: 'ledger',
-        redirect: '/ledger/query',
-        meta: { title: '台账日志' },
-        component: Layout,
-        children: [
-            {
-                path: '/ledger/query',
-                name: 'ledger-write',
-                meta: { title: '日志填报' },
-                component: () => import('~src/views/ledger/query.vue'),
-            },
-            {
-                path: '/ledger/write',
-                name: 'ledger-query',
-                meta: { title: '台账管理' },
-                component: () => import('~src/views/ledger/write.vue'),
-            },
-        ],
-    },
-    {
-        path: '/schedule',
-        name: 'schedule',
-        redirect: '/schedule/write',
-        meta: { title: '进度查询' },
-        component: Layout,
-        children: [
-            {
-                path: '/schedule/write',
-                name: 'schedule-internal',
-                meta: { title: '内外业进度' },
-                component: () => import('~src/views/schedule/write.vue'),
-            },
-            {
-                path: '/schedule/data',
-                name: 'schedule-data',
-                meta: { title: '资料进度' },
-                component: () => import('~src/views/schedule/hc-data.vue'),
-            },
-            {
-                path: '/schedule/table',
-                name: 'schedule-table',
-                meta: { title: 'WBS节点进度' },
-                component: () => import('~src/views/schedule/hc-table.vue'),
+                path: '/data-fill',
+                name: 'data-fill',
+                redirect: '/data-fill/wbs',
+                meta: { title: '资料管理' },
+                children: [
+                    {
+                        path: '/data-fill/wbs',
+                        name: 'data-fill-wbs',
+                        meta: { title: '资料填报' },
+                        component: () => import('~src/views/data-fill/wbs.vue'),
+                    },
+                    {
+                        path: '/data-fill/query',
+                        name: 'data-query',
+                        meta: { title: '资料查询' },
+                        component: () => import('~src/views/data-fill/query.vue'),
+                    },
+                    {
+                        path: '/data-fill/division',
+                        name: 'data-division',
+                        meta: { title: '系统分部分项划分' },
+                        component: () => import('~src/views/data-fill/division.vue'),
+                    },
+                ],
+            },
+            {
+                path: '/ledger',
+                name: 'ledger',
+                redirect: '/ledger/query',
+                meta: { title: '台账日志' },
+                children: [
+                    {
+                        path: '/ledger/query',
+                        name: 'ledger-write',
+                        meta: { title: '日志填报' },
+                        component: () => import('~src/views/ledger/query.vue'),
+                    },
+                    {
+                        path: '/ledger/write',
+                        name: 'ledger-query',
+                        meta: { title: '台账管理' },
+                        component: () => import('~src/views/ledger/write.vue'),
+                    },
+                ],
+            },
+            {
+                path: '/schedule',
+                name: 'schedule',
+                redirect: '/schedule/write',
+                meta: { title: '进度查询' },
+                children: [
+                    {
+                        path: '/schedule/write',
+                        name: 'schedule-internal',
+                        meta: { title: '内外业进度' },
+                        component: () => import('~src/views/schedule/write.vue'),
+                    },
+                    {
+                        path: '/schedule/data',
+                        name: 'schedule-data',
+                        meta: { title: '资料进度' },
+                        component: () => import('~src/views/schedule/hc-data.vue'),
+                    },
+                    {
+                        path: '/schedule/table',
+                        name: 'schedule-table',
+                        meta: { title: 'WBS节点进度' },
+                        component: () => import('~src/views/schedule/hc-table.vue'),
+                    },
+                ],
+            },
+            {
+                path: '/other-file',
+                name: 'other-file',
+                redirect: '/other-file/image-data',
+                meta: { title: '其他文件' },
+                children: [
+                    {
+                        path: '/other-file/image-data',
+                        name: 'image-data',
+                        meta: { title: '影像资料' },
+                        component: () => import('~src/views/other-file/image-data.vue'),
+                    },
+                    {
+                        path: '/other-file/image-view',
+                        name: 'other-file-view',
+                        meta: { title: '查看影像资料' },
+                        component: () => import('~src/views/other-file/image-view.vue'),
+                    },
+                    {
+                        path: '/other-file/image-form',
+                        name: 'other-file-form',
+                        meta: { title: '影像资料上传' },
+                        component: () => import('~src/views/other-file/image-form.vue'),
+                    },
+                    {
+                        path: '/other-file/project-scanning',
+                        name: 'project-scanning',
+                        meta: { title: '工程文件扫描、上传' },
+                        component: () => import('~src/views/other-file/project-scanning.vue'),
+                    },
+                ],
+            },
+            {
+                path: '/gauge',
+                name: 'gauge-base',
+                redirect: '/gauge/station',
+                meta: { title: '综合管理' },
+                children: [
+                    {
+                        path: '/gauge/testdata',
+                        name: 'gauge-testdata',
+                        meta: { title: '试验数据' },
+                        component: () => import('~src/views/gauge/testdata.vue'),
+                    },
+                    {
+                        path: '/gauge/station',
+                        name: 'gauge-station',
+                        meta: { title: '测站点' },
+                        component: () => import('~src/views/gauge/station.vue'),
+                    },
+                    {
+                        path: '/gauge/bezier',
+                        name: 'gauge-bezier',
+                        meta: { title: '平曲线' },
+                        component: () => import('~src/views/gauge/bezier.vue'),
+                    },
+                    {
+                        path: '/other/first-item',
+                        name: 'other-first-item',
+                        meta: { title: '首件工程' },
+                        component: () => import('~src/views/other/first-item.vue'),
+                    },
+                ],
             },
         ],
     },
     {
-        path: '/other-file',
-        name: 'other-file',
-        redirect: '/other-file/image-data',
-        meta: { title: '其他文件' },
+        path: '/tentative',
+        name: 'tentative-menu',
+        redirect: '/tentative/material',
+        meta: { title: '试验管理' },
         component: Layout,
         children: [
             {
-                path: '/other-file/image-data',
-                name: 'image-data',
-                meta: { title: '影像资料' },
-                component: () => import('~src/views/other-file/image-data.vue'),
-            },
-            {
-                path: '/other-file/image-view',
-                name: 'other-file-view',
-                meta: { title: '查看影像资料' },
-                component: () => import('~src/views/other-file/image-view.vue'),
-            },
-            {
-                path: '/other-file/image-form',
-                name: 'other-file-form',
-                meta: { title: '影像资料上传' },
-                component: () => import('~src/views/other-file/image-form.vue'),
-            },
-            {
-                path: '/other-file/project-scanning',
-                name: 'project-scanning',
-                meta: { title: '工程文件扫描、上传' },
-                component: () => import('~src/views/other-file/project-scanning.vue'),
-            },
-        ],
-    },
-    {
-        path: '/gauge',
-        name: 'gauge-base',
-        redirect: '/gauge/station',
-        meta: { title: '综合管理' },
-        component: Layout,
-        children: [
-            {
-                path: '/gauge/testdata',
-                name: 'gauge-testdata',
-                meta: { title: '试验数据' },
-                component: () => import('~src/views/gauge/testdata.vue'),
-            },
-            {
-                path: '/gauge/station',
-                name: 'gauge-station',
-                meta: { title: '测站点' },
-                component: () => import('~src/views/gauge/station.vue'),
-            },
-            {
-                path: '/gauge/bezier',
-                name: 'gauge-bezier',
-                meta: { title: '平曲线' },
-                component: () => import('~src/views/gauge/bezier.vue'),
-            },
-            {
-                path: '/other/first-item',
-                name: 'other-first-item',
-                meta: { title: '首件工程' },
-                component: () => import('~src/views/other/first-item.vue'),
+                path: '/tentative/material',
+                name: 'tentative-material',
+                redirect: '/tentative/material/approach',
+                meta: { title: '材料管理' },
+                children: [
+                    {
+                        path: '/tentative/material/approach',
+                        name: 'tentative-material-approach',
+                        meta: { title: '材料进场' },
+                        component: () => import('~src/views/tentative/material/approach.vue'),
+                    },
+                    {
+                        path: '/tentative/material/sampling',
+                        name: 'tentative-material-sampling',
+                        meta: { title: '材料取样' },
+                        component: () => import('~src/views/tentative/material/sampling.vue'),
+                    },
+                ],
+            },
+            {
+                path: '/tentative/detect',
+                name: 'tentative-detect',
+                redirect: '/tentative/detect/approach',
+                meta: { title: '试验检测' },
+                children: [
+                    {
+                        path: '/tentative/detect/third',
+                        name: 'tentative-detect-third',
+                        meta: { title: '外委检测' },
+                        component: () => import('~src/views/tentative/detect/third.vue'),
+                    },
+                    {
+                        path: '/tentative/detect/outside',
+                        name: 'tentative-detect-outside',
+                        meta: { title: '第三方检测' },
+                        component: () => import('~src/views/tentative/detect/outside.vue'),
+                    },
+                    {
+                        path: '/tentative/detect/test',
+                        name: 'tentative-detect-test',
+                        meta: { title: '试验检测' },
+                        component: () => import('~src/views/tentative/detect/test.vue'),
+                    },
+                    {
+                        path: '/tentative/detect/test-form',
+                        name: 'tentative-detect-test-form',
+                        meta: { title: '试验检测表单' },
+                        component: () => import('~src/views/tentative/detect/test-form.vue'),
+                    },
+                ],
+            },
+            {
+                path: '/tentative/collect',
+                name: 'tentative-collect',
+                redirect: '/tentative/collect/approach',
+                meta: { title: '汇总管理' },
+                children: [
+                    {
+                        path: '/tentative/collect/test',
+                        name: 'tentative-collect-test',
+                        meta: { title: '试验汇总' },
+                        component: () => import('~src/views/tentative/collect/test.vue'),
+                    },
+                    {
+                        path: '/tentative/collect/monthly',
+                        name: 'tentative-collect-monthly',
+                        meta: { title: '月报汇总' },
+                        component: () => import('~src/views/tentative/collect/monthly.vue'),
+                    },
+                ],
+            },
+            {
+                path: '/tentative/device',
+                name: 'tentative-device',
+                redirect: '/tentative/device/approach',
+                meta: { title: '设备管理' },
+                children: [
+                    {
+                        path: '/tentative/device/approach',
+                        name: 'tentative-device-approach',
+                        meta: { title: '设备进场管理' },
+                        component: () => import('~src/views/tentative/device/approach.vue'),
+                    },
+                    {
+                        path: '/tentative/device/employ',
+                        name: 'tentative-device-employ',
+                        meta: { title: '设备使用管理' },
+                        component: () => import('~src/views/tentative/device/employ.vue'),
+                    },
+                    {
+                        path: '/tentative/device/overhaul',
+                        name: 'tentative-device-overhaul',
+                        meta: { title: '设备检修管理' },
+                        component: () => import('~src/views/tentative/device/overhaul.vue'),
+                    },
+                ],
+            },
+            {
+                path: '/tentative/parameter',
+                name: 'tentative-parameter',
+                redirect: '/tentative/parameter/container',
+                meta: { title: '参数设置' },
+                children: [
+                    {
+                        path: '/tentative/parameter/container',
+                        name: 'tentative-parameter-container',
+                        meta: { title: '试验容器' },
+                        component: () => import('~src/views/tentative/parameter/container.vue'),
+                    },
+                    {
+                        path: '/tentative/parameter/sieve',
+                        name: 'tentative-parameter-sieve',
+                        meta: { title: '自定义筛孔类型' },
+                        component: () => import('~src/views/tentative/parameter/sieve.vue'),
+                    },
+                    {
+                        path: '/tentative/parameter/density',
+                        name: 'tentative-parameter-density',
+                        meta: { title: '温度及密度参数' },
+                        component: () => import('~src/views/tentative/parameter/density.vue'),
+                    },
+                    {
+                        path: '/tentative/parameter/compactness',
+                        name: 'tentative-parameter-compactness',
+                        meta: { title: '压实度评标参数' },
+                        component: () => import('~src/views/tentative/parameter/compactness.vue'),
+                    },
+                ],
+            },
+            {
+                path: '/tentative/laboratory',
+                name: 'tentative-laboratory',
+                redirect: '/tentative/laboratory/container',
+                meta: { title: '实验室管理' },
+                children: [
+                    {
+                        path: '/tentative/laboratory/user',
+                        name: 'tentative-laboratory-user',
+                        meta: { title: '人员档案' },
+                        component: () => import('~src/views/tentative/laboratory/user.vue'),
+                    },
+                    {
+                        path: '/tentative/laboratory/print',
+                        name: 'tentative-laboratory-print',
+                        meta: { title: '打印空表' },
+                        component: () => import('~src/views/tentative/laboratory/print.vue'),
+                    },
+                ],
             },
-            // {
-            //     path: '/other/first-item',
-            //     name: 'other-first-item',
-            //     meta: {title: '首件工程'},
-            //     component: () => import('~src/views/other/first-item copy.vue')
-            // }
         ],
     },
     {
@@ -208,7 +365,7 @@ export default [
             {
                 path: '/tasks/hc-data',
                 name: 'tasks-data',
-                meta: { title: '待办任务、已办任务、任务查看' },
+                meta: { title: '任务查看' },
                 component: () => import('~src/views/tasks/hc-data.vue'),
             },
             {
@@ -231,162 +388,6 @@ export default [
             },
         ],
     },
-    {
-        path: '/tentative/material',
-        name: 'tentative-material',
-        redirect: '/tentative/material/approach',
-        meta: { title: '材料管理' },
-        component: Layout,
-        children: [
-            {
-                path: '/tentative/material/approach',
-                name: 'tentative-material-approach',
-                meta: { title: '材料进场' },
-                component: () => import('~src/views/tentative/material/approach.vue'),
-            },
-            {
-                path: '/tentative/material/sampling',
-                name: 'tentative-material-sampling',
-                meta: { title: '材料取样' },
-                component: () => import('~src/views/tentative/material/sampling.vue'),
-            },
-        ],
-    },
-    {
-        path: '/tentative/detect',
-        name: 'tentative-detect',
-        redirect: '/tentative/detect/approach',
-        meta: { title: '试验检测' },
-        component: Layout,
-        children: [
-            {
-                path: '/tentative/detect/third',
-                name: 'tentative-detect-third',
-                meta: { title: '外委检测' },
-                component: () => import('~src/views/tentative/detect/third.vue'),
-            },
-            {
-                path: '/tentative/detect/outside',
-                name: 'tentative-detect-outside',
-                meta: { title: '第三方检测' },
-                component: () => import('~src/views/tentative/detect/outside.vue'),
-            },
-            {
-                path: '/tentative/detect/test',
-                name: 'tentative-detect-test',
-                meta: { title: '试验检测' },
-                component: () => import('~src/views/tentative/detect/test.vue'),
-            },
-            {
-                path: '/tentative/detect/test-form',
-                name: 'tentative-detect-test-form',
-                meta: { title: '试验检测表单' },
-                component: () => import('~src/views/tentative/detect/test-form.vue'),
-            },
-        ],
-    },
-    {
-        path: '/tentative/collect',
-        name: 'tentative-collect',
-        redirect: '/tentative/collect/approach',
-        meta: { title: '汇总管理' },
-        component: Layout,
-        children: [
-            {
-                path: '/tentative/collect/test',
-                name: 'tentative-collect-test',
-                meta: { title: '试验汇总' },
-                component: () => import('~src/views/tentative/collect/test.vue'),
-            },
-            {
-                path: '/tentative/collect/monthly',
-                name: 'tentative-collect-monthly',
-                meta: { title: '月报汇总' },
-                component: () => import('~src/views/tentative/collect/monthly.vue'),
-            },
-        ],
-    },
-    {
-        path: '/tentative/device',
-        name: 'tentative-device',
-        redirect: '/tentative/device/approach',
-        meta: { title: '设备管理' },
-        component: Layout,
-        children: [
-            {
-                path: '/tentative/device/approach',
-                name: 'tentative-device-approach',
-                meta: { title: '设备进场管理' },
-                component: () => import('~src/views/tentative/device/approach.vue'),
-            },
-            {
-                path: '/tentative/device/employ',
-                name: 'tentative-device-employ',
-                meta: { title: '设备使用管理' },
-                component: () => import('~src/views/tentative/device/employ.vue'),
-            },
-            {
-                path: '/tentative/device/overhaul',
-                name: 'tentative-device-overhaul',
-                meta: { title: '设备检修管理' },
-                component: () => import('~src/views/tentative/device/overhaul.vue'),
-            },
-        ],
-    },
-    {
-        path: '/tentative/parameter',
-        name: 'tentative-parameter',
-        redirect: '/tentative/parameter/container',
-        meta: { title: '参数设置' },
-        component: Layout,
-        children: [
-            {
-                path: '/tentative/parameter/container',
-                name: 'tentative-parameter-container',
-                meta: { title: '试验容器' },
-                component: () => import('~src/views/tentative/parameter/container.vue'),
-            },
-            {
-                path: '/tentative/parameter/sieve',
-                name: 'tentative-parameter-sieve',
-                meta: { title: '自定义筛孔类型' },
-                component: () => import('~src/views/tentative/parameter/sieve.vue'),
-            },
-            {
-                path: '/tentative/parameter/density',
-                name: 'tentative-parameter-density',
-                meta: { title: '温度及密度参数' },
-                component: () => import('~src/views/tentative/parameter/density.vue'),
-            },
-            {
-                path: '/tentative/parameter/compactness',
-                name: 'tentative-parameter-compactness',
-                meta: { title: '压实度评标参数' },
-                component: () => import('~src/views/tentative/parameter/compactness.vue'),
-            },
-        ],
-    },
-    {
-        path: '/tentative/laboratory',
-        name: 'tentative-laboratory',
-        redirect: '/tentative/laboratory/container',
-        meta: { title: '实验室管理' },
-        component: Layout,
-        children: [
-            {
-                path: '/tentative/laboratory/user',
-                name: 'tentative-laboratory-user',
-                meta: { title: '人员档案' },
-                component: () => import('~src/views/tentative/laboratory/user.vue'),
-            },
-            {
-                path: '/tentative/laboratory/print',
-                name: 'tentative-laboratory-print',
-                meta: { title: '打印空表' },
-                component: () => import('~src/views/tentative/laboratory/print.vue'),
-            },
-        ],
-    },
     {
         path: '/other',
         name: 'other',

+ 2 - 1
src/styles/app/main.scss

@@ -1,6 +1,7 @@
 html, body, #app {
     height: 100%;
-    background-color: #F1F5F8;
+    font-size: 14px;
+    background-color: #F0F2F5;
 }
 
 embed {