ZaiZai 1 jaar geleden
bovenliggende
commit
db0f837c14

+ 0 - 6
src/App.vue

@@ -14,7 +14,6 @@ import { getVersionJson } from '~api/other'
 import split from 'split.js'
 
 //初始变量
-
 const appStore = useAppStore()
 const UserTheme = ref(appStore.getTheme)
 
@@ -30,11 +29,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) {
         getVersionJsonApi()

+ 451 - 0
src/layout/index.scss

@@ -0,0 +1,451 @@
+.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 {
+                color: white;
+                font-size: 22px;
+                margin-left: 6px;
+            }
+            &: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: 8px 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% - 36px);
+                overflow: hidden;
+                .hc-main-body {
+                    position: absolute;
+                    padding: 12px;
+                    inset: 0;
+                }
+            }
+        }
+    }
+}
+
+//左侧菜单
+.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-box {
+        position: relative;
+        display: flex;
+        align-items: center;
+        white-space: nowrap;
+        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);
+}

+ 99 - 131
src/layout/index.vue

@@ -1,186 +1,154 @@
 <template>
     <el-container class="hc-layout-box">
-        <el-aside :class="[isCollapse ? 'is-collapse' : '']" :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: ${isCollapse ? '90px' : '200px'};`" @click="logoClick">
                 <img id="logo-icon" :src="appLogoIcon" alt="">
                 <span v-show="!isCollapse" class="logo-name">计量系统</span>
             </div>
-            <div class="hc-aside-menu-box">
-                <el-scrollbar>
-                    <MenuBar :collapse="isCollapse" :cur="MenuBarKey" :datas="MenuBarData" @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="hc-container-view">
-            <el-header class="hc-header-view">
-                <div class="hc-header-box">
-                    <div class="hc-header-menu">
-                        <HcTopMenuBar @change="TopMenuChange" />
-                    </div>
-                    <div class="hc-header-content">
-                        <HcCascader @change="cascaderChange" />
-                        <HelpInfoBar />
-                        <ConfigBar />
-                        <UserInfoBar />
-                    </div>
+            <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 v-if="isAsideMenu" 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 @load="routerMenuLoad" />
                 </div>
-                <div class="hc-header-top-menu-bar">
-                    <TopMenuBar />
+                <div id="hc-main-box" class="hc-main-page">
+                    <div class="hc-main-body">
+                        <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>
                 </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">
-                            <component :is="Component" />
-                        </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 HcCascader from './modules/cascader.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 HcTopMenuBar from './modules/HcTopMenu.vue'
-import { setImageColorStyle } from 'js-fast-way'
-import { setAppName } from '~uti/tools'
+import { useRouter } from 'vue-router'
+import { initButtons } from '~sto/app'
+import HcSocket from '~src/plugins/HcSocket'
+import { getObjValue, isNullES } from 'js-fast-way'
 
 //初始组合式
 const router = useRouter()
-const useRoutes = useRoute()
 const store = useAppStore()
 
-//路由参数
 const reloadRouter = ref(true)
-const BarMenuKey = useRoutes?.name ?? 'home-index'
-const BarMenuTitle = useRoutes?.meta?.title ?? ''
 
-//系统信息
-const appTitle = ref(store.getTitle)
+//子组件
+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'
+
+// logo
 const appLogoIcon = ref(store.getLogoIcon)
-const appLogoName = ref(store.getLogoName)
-const AppColor = ref(store.getColor)
 
-//顶部菜单数据和相关处理
-const MenuBarKey = ref(BarMenuKey)
-const RoutesTitle = ref(BarMenuTitle)
-const isCollapse = ref(store.getCollapse)
+//菜单数据
+const menuBarKey = ref('')
+const menuBarData = ref([])
 
 //渲染完成
 onMounted(() => {
-    setLogoImageColor()
-    store.barMenuName = BarMenuTitle
-    setLogoNameColor(store.getTheme)
-})
-
-//监听
-watch(() => [
-    useRoutes?.name,
-    useRoutes?.meta?.title,
-    store.getCollapse,
-], ([RouteName, RouteTitle, collapse]) => {
-    MenuBarKey.value = RouteName ?? 'home-index'
-    RoutesTitle.value = RouteTitle ?? ''
-    isCollapse.value = collapse
-    setIsCollapse(MenuBarKey.value)
-    store.barMenuName = RouteTitle ?? ''
-    setAppName(appTitle.value)
-})
-
-//监听
-watch(() => [
-    store.getTitle,
-    store.getLogoIcon,
-    store.getLogoName,
-    store.getColor,
-], ([Title, LogoIcon, LogoName, ColorVal]) => {
-    appTitle.value = Title
-    appLogoIcon.value = LogoIcon
-    appLogoName.value = LogoName
-    AppColor.value = ColorVal
-    setLogoImageColor()
+    initButtons()
 })
 
-//设置Logo图片颜色
-const setLogoImageColor = () => {
-    setImageColorStyle('logo-icon', AppColor.value?.color)
+//路由信息
+const routerMenuLoad = ({ key }) => {
+    menuBarKey.value = key
 }
 
-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 { /* empty */ }
+// 是否折叠
+const isCollapse = ref(false)
+const collapseChange = () => {
+    const bool = !isCollapse.value
+    isCollapse.value = bool
+    store.setCollapse(bool)
 }
 
-//设置折叠
-const setIsCollapse = (key) => {
-    if (key === 'data-fill-wbs') {
-        isCollapse.value = true
-        store.setCollapse(true)
-    }
+//顶部菜单导航
+const isAsideMenu = ref(true)
+const topMenuLoad = () => {
+    isAsideMenu.value = false
 }
 
-//是否折叠
-const collapseChange = (bool) => {
-    isCollapse.value = bool
-    store.setCollapse(bool)
+//顶部菜单导航被点击
+const topMenuChange = (data) => {
+    if (!isNullES(data)) {
+        menuBarData.value = data
+        isAsideMenu.value = true
+    }
 }
 
-//菜单数据
-const MenuBarData = ref([])
-const TopMenuChange = (data) => {
-    MenuBarData.value = data
+//菜单被点击
+const menuBarChange = ({ code }) => {
+    menuBarKey.value = code
+    router.push({ name: code })
 }
 
+//消息数量
+const msgCount = ref({
+    allCount: 0,
+    taskCount: 0,
+    messageCount: 0,
+    messageCount_1: 0,
+    messageCount_2: 0,
+    messageCount_3: 0,
+    messageCount_4: 0,
+    messageCount_5: 0,
+})
 
-//菜单被点击
-const MenuBarChange = (item) => {
-    MenuBarKey.value = item?.code
-    setIsCollapse(item?.code)
-    router.push({ name: item?.code })
+//用户信息
+const userInfoLoad = ({ user_id }) => {
+    HcSocket.create(user_id, (data) => {
+        msgCount.value = getObjValue(data)
+    })
 }
 
-//首页
-const logoClick = () => {
-    router.push({ name: store.homeUrl })
+//项目合同段的ID
+const cascaderSend = ({ projectId, contractId }) => {
+    HcSocket.send(projectId + ',' + contractId)
 }
 
-//项目被改变
+// 项目切换
 const cascaderChange = () => {
-    //刷新路由
     reloadRouter.value = false
     nextTick(() => {
         reloadRouter.value = true
     })
 }
-</script>
 
-<style lang="scss" scoped>
-@import "./layout.scoped.scss";
-</style>
+//首页
+const logoClick = () => {
+    router.push({ name: 'home-index' })
+}
+</script>
 
 <style lang="scss">
-@import "./layout.scss";
+@import "./index.scss";
 </style>

+ 0 - 136
src/layout/layout.scoped.scss

@@ -1,136 +0,0 @@
-.hc-layout-box {
-    position: relative;
-    height: 100vh;
-    width: 100%;
-    .hc-aside-box {
-        position: relative;
-        color: #838791;
-        background: #f1f5f8;
-        transition: 0.2s;
-        border-radius: 0;
-        z-index: 1;
-        box-shadow: -2px 0 10px 0 rgba(32, 37, 50, 0.03), 0 10px 21px 20px rgba(32, 37, 50, 0.03);
-        .hc-aside-logo-box {
-            position: relative;
-            display: flex;
-            align-items: center;
-            justify-content: center;
-            transition: opacity 0.3s;
-            cursor: pointer;
-            margin: 32px 0;
-            left: -10px;
-            #logo-icon {
-                height: 35px;
-                width: 35px;
-            }
-            .logo-name {
-                color: #101010;
-                font-size: 28px;
-                margin-left: 5px;
-            }
-            &:hover {
-                opacity: .8;
-            }
-        }
-        .hc-aside-menu-box {
-            position: relative;
-            height: calc(100% - 216px);
-            width: 100%;
-            overflow: hidden;
-            user-select: none;
-        }
-        .hc-aside-bar-box {
-            position: relative;
-            display: flex;
-            align-items: center;
-            justify-content: center;
-            padding: 24px 0;
-            margin-top: 24px;
-            transition: 0.2s;
-            &:before {
-                position: absolute;
-                content: '';
-                top: 0;
-                width: 100%;
-                height: 1px;
-                background-image: linear-gradient(90deg, rgba(102, 102, 102, 0.00) 11%, #dbe8f3 35%, #dbe8f3 64%, rgba(102, 102, 102, 0.00) 86%);
-            }
-            div {
-                position: relative;
-                width: 40px;
-                height: 40px;
-                display: flex;
-                justify-content: center;
-                align-items: center;
-                font-size: 26px;
-                transition: 0.1s;
-                &.active {
-                    color: white;
-                    background: var(--el-color-primary);
-                    border-radius: 6px;
-                    box-shadow: 3px 2px 8px 0 var(--hc-shadow-color-5);
-                }
-                &:not(.active) {
-                    cursor: pointer;
-                    &:hover {
-                        color: var(--el-color-primary);
-                    }
-                }
-                & + div {
-                    margin-left: 26px;
-                }
-            }
-        }
-        &.is-collapse .hc-aside-logo-box {
-            left: initial;
-        }
-    }
-    .hc-container-view {
-        position: relative;
-        z-index: 1;
-        .hc-header-view {
-            position: relative;
-            --el-header-padding: 0;
-            --el-header-height: 111px;
-            .hc-header-box {
-                position: relative;
-                display: flex;
-                align-items: center;
-                flex-direction: row;
-                padding: 12px 20px;
-                .hc-header-menu {
-                    position: relative;
-                    display: flex;
-                    align-items: center;
-                    flex: 1;
-                }
-                .hc-header-content {
-                    position: relative;
-                    text-align: right;
-                    display: flex;
-                    align-items: center;
-                    justify-content: flex-end;
-                    max-width: 60%;
-
-                }
-            }
-            .hc-header-top-menu-bar {
-                position: relative;
-                display: flex;
-                align-items: center;
-                padding: 6px 20px;
-                overflow: hidden;
-                border-top: 1px solid #ebeaea;
-                //border-bottom: 1px solid #ebeaea;
-            }
-        }
-        .hc-main-box {
-            position: relative;
-            overflow: hidden;
-            height: 100%;
-            margin-top: -16px;
-            --el-main-padding: 20px;
-        }
-    }
-}
-

+ 0 - 58
src/layout/layout.scss

@@ -1,58 +0,0 @@
-.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;
-                }
-            }
-        }
-    }
-}

+ 52 - 33
src/layout/modules/cascader.vue → src/layout/modules/Cascader.vue

@@ -1,8 +1,11 @@
 <template>
     <div class="hc-header-cascader-box">
-        <div class="project-name-box">{{ projectInfo.projectAlias }} / {{ contractInfo.name }}</div>
+        <div class="project-name-box">
+            {{ projectInfo.projectAlias }} / {{ contractInfo.name }}
+        </div>
         <el-cascader
-            ref="ElCascaderRef" v-model="projectValue"
+            ref="ElCascaderRef"
+            v-model="projectValue" class="hc-header-cascader"
             :clearable="userInfo?.role_id === '1123598816738675201'"
             :filterable="userInfo?.role_id === '1123598816738675201'"
             :options="projectContract"
@@ -14,15 +17,16 @@
 
 <script setup>
 import { onMounted, ref, watch } from 'vue'
-import { initButtons, initProjectContract } from '~sto/app'
 import { useAppStore } from '~src/store'
 import { getArrValue } from 'js-fast-way'
+import { initProjectContract } from '~sto/app'
 
 //事件
-const emit = defineEmits(['change'])
+const emit = defineEmits(['change', 'send'])
 
-const useAppState = useAppStore()
-const userInfo = ref(useAppState.getUserInfo)
+//状态
+const store = useAppStore()
+const userInfo = ref(store.getUserInfo)
 
 //项目合同段
 const projectInfo = ref({})
@@ -36,15 +40,14 @@ const projectProps = ref({
 })
 
 //监听
-watch(() => useAppState.getProjectContract, (val) => {
+watch(() => store.getProjectContract, (val) => {
     projectContractData(getArrValue(val))
 })
 
 //渲染完成
 onMounted(() => {
-    initButtons()
     initProjectContract()
-    const info = useAppState.getProjectContract || []
+    const info = store.getProjectContract || []
     projectContractData(info)
 })
 
@@ -60,10 +63,10 @@ const projectContractData = (projectContractData) => {
         })
         //处理其他数据
         projectContract.value = projectContractData
-        const projectId = useAppState.getProjectId //项目ID
-        const contractId = useAppState.getContractId //合同段ID
-        const UserProjectInfo = useAppState.getProjectInfo
-        const UserContractInfo = useAppState.getContractInfo
+        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 || []
@@ -78,24 +81,32 @@ const projectContractData = (projectContractData) => {
             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)
+            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
-            //发送消息
-            //socketSend(projectId + ',' + contractId)
+            emit('send', {
+                projectId: projectId,
+                contractId: contractId,
+            })
         }
     } else {
         projectContract.value = []
         projectValue.value = null
         projectInfo.value = {}
         contractInfo.value = {}
+        emit('send', {
+            projectId: '',
+            contractId: '',
+        })
     }
 }
 
@@ -107,33 +118,41 @@ const projectContractChange = (val) => {
         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)
+        store.setProjectId(val[0])
+        store.setContractId(val[1])
+        store.setProjectInfo(UserProjectInfo)
+        store.setContractInfo(UserContractInfo)
         //更改界面更新
         projectInfo.value = UserProjectInfo
         contractInfo.value = UserContractInfo
         window.$message?.info('切换了项目,数据更新中')
-        //发送消息
-        //socketSend(val[0] + ',' + val[1])
+        emit('send', {
+            projectId: val[0],
+            contractId: val[1],
+        })
         emit('change')
     }
 }
 </script>
 
-<style lang="scss" scoped>
+<style lang="scss">
 .hc-header-cascader-box {
     position: relative;
-    margin-right: 30px;
-    border-radius: 100px;
-    flex: 1;
+    margin-right: 20px;
     .project-name-box {
-        padding-right: 55px;
         position: relative;
-        visibility: hidden;
+        max-width: 340px;
+        padding-right: 20px;
+        overflow: hidden;
         z-index: -1;
         height: 1px;
     }
+    .el-cascader.hc-header-cascader {
+        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>

+ 46 - 33
src/layout/modules/HcTopMenu.vue

@@ -1,71 +1,84 @@
 <template>
-    <div class="hc-top-menu-box">
-        <el-scrollbar>
-            <div class="hc-flex">
-                <template v-for="(item, index) in topMenuData" :key="index">
-                    <div class="hc-top-menu-item hc-flex text-4.5" :class="curKey === item?.code ? 'cur' : '' " @click="topMenuClick(item)">
-                        <HcIcon v-if="item?.source" :name="item?.source" :fill="curKey === item?.code" class="mr-1" />
-                        <span class="name">{{ item?.name }}</span>
-                    </div>
-                </template>
-            </div>
-        </el-scrollbar>
-    </div>
+    <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'])
+const emit = defineEmits(['change', 'load'])
+
 //初始组合式
 const useRoutes = useRoute()
-const useAppState = useAppStore()
+const store = useAppStore()
+
+//处理菜单数据
+const setMenuItem = async (item) => {
+    emit('change', await HcTopMenu.setMenuItem(item))
+}
 
 //监听菜单数据
 const topMenuData = ref([])
-watch(() => useAppState.getMenus, (val) => {
+watch(() => store.getMenus, (val) => {
     topMenuData.value = getArrValue(val)
 }, { immediate: true, deep: true })
 
 //监听路由数据
 const curKey = ref('')
-watch(() => useRoutes, (obj) => {
-    const topMenuName = obj.matched[0]?.name
-    const menus = topMenuData.value
-    for (let i = 0; i < menus.length; i++) {
-        if (menus[i].code === topMenuName) {
-            emit('change', menus[i].children)
-        }
-    }
-    curKey.value = topMenuName
+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) => {
-    emit('change', item.children)
+    setMenuItem(item)
 }
 </script>
 
 <style lang="scss">
-.hc-top-menu-box {
+.hc-header-top-menu-bar {
     position: relative;
     height: 100%;
-    .hc-top-menu-item {
-        color: #303133;
+    display: flex;
+    align-items: center;
+    .item {
+        position: relative;
         cursor: pointer;
-        transition: color .2s;
+        padding: 6px 8px;
+        border-radius: 3px;
+        color: #efefef;
+        transition: background .2s, color .2s;
         &:hover {
-            color: var(--el-color-primary-light-5);
+            color: white;
+            background: var(--el-color-primary-dark-2);
         }
         &.cur {
-            color: var(--el-color-primary);
+            color: white;
+            background: var(--el-color-primary-dark-2);
         }
     }
-    .hc-top-menu-item + .hc-top-menu-item {
-        margin-left: 24px;
+    .item + .item {
+        margin-left: 2px;
     }
 }
 </style>

+ 1 - 25
src/layout/modules/HelpInfoBar.vue

@@ -32,13 +32,12 @@
 
 <script setup>
 import { nextTick, ref, watch } from 'vue'
-import { useRoute, useRouter } from 'vue-router'
+import { useRoute } from 'vue-router'
 import { useAppStore } from '~src/store'
 import { getObjValue, getToObjVal } from 'js-fast-way'
 import { getStore } from 'hc-vue3-ui'
 
 //初始变量
-const router = useRouter()
 const useRoutes = useRoute()
 const useAppState = useAppStore()
 
@@ -104,29 +103,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 - 283
src/layout/modules/MenuBar.vue

@@ -1,7 +1,9 @@
 <template>
-    <el-menu :collapse="isCollapse" :default-active="curKey" class="hc-aside-menu" unique-opened>
-        <MenuItem :collapse="isCollapse" :cur="curKey" :datas="datas" @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>
@@ -15,13 +17,18 @@ const props = defineProps({
     },
     cur: {
         type: String,
-        default: '',
+        default: 'home-index',
     },
     collapse: {
         type: Boolean,
         default: false,
     },
+    msgCount: {
+        type: Object,
+        default: () => ({}),
+    },
 })
+
 //事件
 const emit = defineEmits(['change'])
 
@@ -38,287 +45,14 @@ watch(() => [
     isCollapse.value = collapse
 })
 
-//菜单被点击
-const MenuClick = async (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;
-            }
-        }
-    }
-}
-.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);
+//菜单被点击
+const MenuClick = (item) => {
+    setMenuItem(item)
 }
-</style>
+</script>

+ 24 - 25
src/layout/modules/MenuItem.vue

@@ -1,41 +1,27 @@
 <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>
                 <HcIcon name="arrow-down-s" ui="el-icon el-sub-menu__icon-arrow" />
             </template>
-            <MenuItem :datas="item?.children" :cur="curKey" @change="MenuClick" />
+            <MenuItem :datas="item?.children" :cur="curKey" :msg-count="msgCount" @change="MenuClick" />
         </el-sub-menu>
         <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>
+                    <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>
@@ -59,6 +45,19 @@ const props = defineProps({
         type: Boolean,
         default: false,
     },
+    msgCount: {
+        type: Object,
+        default: () => ({
+            allCount: 0,
+            taskCount: 0,
+            messageCount: 0,
+            messageCount_1: 0,
+            messageCount_2: 0,
+            messageCount_3: 0,
+            messageCount_4: 0,
+            messageCount_5: 0,
+        }),
+    },
 })
 //事件
 const emit = defineEmits(['change'])

+ 25 - 89
src/layout/modules/TopMenuBar.vue → src/layout/modules/RouterMenu.vue

@@ -1,35 +1,33 @@
 <template>
-    <div class="hc-top-menu-bar">
-        <el-scrollbar always>
-            <div class="bar-menu-content">
+    <el-scrollbar>
+        <div class="hc-router-tab-box">
+            <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
-                    v-for="(item, index) in barMenuData" :key="item.key"
-                    :class="item.key === barRoutes.key ? 'cur' : ''"
-                    class="bar-menu-btn"
+                    :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="bar-close-icon" @click.stop="barMenuCloseClick(item, index)">
+                    <div class="close-icon" @click.stop="barMenuCloseClick(item, index)">
                         <HcIcon name="close" />
                     </div>
                 </div>
-            </div>
-        </el-scrollbar>
-        <!-- 右键菜单 -->
-        <HcContextMenu ref="contextMenuRef" :datas="menusData" @item-click="handleMenuSelect" />
-    </div>
+            </template>
+        </div>
+    </el-scrollbar>
+    <!-- 右键菜单 -->
+    <HcContextMenu ref="contextMenuRef" :datas="menusData" @item-click="handleMenuSelect" />
 </template>
 
 <script setup>
 import { onMounted, ref, watch } from 'vue'
 import { useRoute, useRouter } from 'vue-router'
 import { getStore, setStore } from 'hc-vue3-ui'
-import { useAppStore } from '~src/store'
 
+const emit = defineEmits(['load'])
 //初始组合式
 const router = useRouter()
 const useRoutes = useRoute()
-const store = useAppStore()
 
 //初始变量
 const barMenuData = ref(getStore('bar-menu-datas') || [])
@@ -56,11 +54,14 @@ watch(() => [
 //设置菜单数据
 const setBarMenuData = () => {
     const { key, path, title, query } = barRoutes.value
-    const index = barMenuData.value.findIndex(item => item.key === key)
-    if (index === -1) {
-        barMenuData.value.push({ path, key: key, title, query })
+    if (['home', 'home-index'].indexOf(key) === -1) {
+        const index = barMenuData.value.findIndex(item => item.key === key)
+        if (index === -1) {
+            barMenuData.value.push({ path, key: key, title, query })
+        }
+        setStore('bar-menu-datas', barMenuData.value)
     }
-    setStore('bar-menu-datas', barMenuData.value)
+    emit('load', barRoutes.value)
 }
 
 //菜单被点击
@@ -94,7 +95,7 @@ const handleMenuSelect = ({ key }) => {
     } else if (key === 'all') {
         barMenuData.value = []
         setStore('bar-menu-datas', [])
-        router.push({ name: store.homeUrl })
+        router.push({ name: 'home-index' })
     } else if (key === 'other') {
         const { key } = barRoutes.value
         barMenuData.value = barMenuData.value.filter(item => item.key === key)
@@ -117,7 +118,7 @@ const barMenuCloseClick = (item, index) => {
         }
         if (indexs < 0) {
             setStore('bar-menu-datas', barMenuData.value)
-            router.push({ name: store.homeUrl })
+            router.push({ name: 'home-index' })
         } else {
             barRoutes.value = items
             setStore('bar-menu-datas', barMenuData.value)
@@ -127,74 +128,9 @@ const barMenuCloseClick = (item, index) => {
         setStore('bar-menu-datas', barMenuData.value)
     }
 }
-</script>
 
-<style lang="scss">
-.hc-top-menu-bar {
-    position: relative;
-    width: 100%;
-    .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>

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

@@ -25,24 +25,27 @@ import { useAppStore } from '~src/store'
 import website from '~src/config/index'
 import avatarPng from '~src/assets/images/avatar.png'
 import { LogOut, RefreshToken } from '~sto/user'
-import { getStore } from 'hc-vue3-ui'
 import { calcDate, isNullES } from 'js-fast-way'
+import { getStore } from 'hc-vue3-ui'
+
+//事件
+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
@@ -95,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;
@@ -107,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)
+            }
+        })
+    }
+
+}

+ 26 - 0
src/plugins/HcTopMenu.js

@@ -0,0 +1,26 @@
+import { getArrValue } 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) {
+        return getArrValue(item?.children)
+    }
+}

+ 5 - 0
src/utils/tools.js

@@ -8,6 +8,11 @@ export const setDomSplit = (ele = [], options = {}) => {
     }, 500)
 }
 
+//判断是否为网址
+export const isPathUrl = (path) => {
+    return /^(https?:|mailto:|tel:)/.test(path)
+}
+
 
 //获取当前域名
 export const getTopUrl = () => {