Browse Source

新主题,先不要发版

ZaiZai 1 năm trước cách đây
mục cha
commit
552bd46c20

+ 2 - 1
.eslintrc.cjs

@@ -9,7 +9,7 @@ module.exports = {
     parser: "vue-eslint-parser",
     extends: [
         "plugin:vue/vue3-recommended",
-        'eslint:recommended'
+        'eslint:recommended',
     ],
     parserOptions: {
         ecmaVersion: "latest",
@@ -45,6 +45,7 @@ module.exports = {
         'max-statements-per-line': ['error', {
             max: 1
         }],
+        'vue/singleline-html-element-content-newline': 'off',
 
         'sort-imports': [
             'error',

+ 16 - 21
src/App.vue

@@ -1,17 +1,17 @@
 <template>
     <HcAppConfig>
-        <router-view/>
+        <router-view />
     </HcAppConfig>
 </template>
 
 <script setup>
-import {nextTick, ref, watch} from "vue";
-import {useAppStore} from "~src/store";
-import {useOsTheme} from 'hc-vue3-ui'
-import {setElementMainColor, getObjValue} from "js-fast-way"
-import {getStoreValue, setStoreValue} from "~uti/storage";
-import {getVersionJson} from "~api/other";
-import split from "split.js";
+import { nextTick, ref, watch } from 'vue'
+import { useAppStore } from '~src/store'
+import { useOsTheme } from 'hc-vue3-ui'
+import { getObjValue, setElementMainColor } from 'js-fast-way'
+import { getStoreValue, setStoreValue } from '~uti/storage'
+import { getVersionJson } from '~api/other'
+import split from 'split.js'
 
 //初始变量
 const appStore = useAppStore()
@@ -21,20 +21,15 @@ const UserTheme = ref(appStore.getTheme)
 watch(() => [
     appStore.getTheme,
     appStore.getThemeVal,
-    appStore.getColor
-], ([Theme,ThemeVal,ColorVal]) => {
+    appStore.getColor,
+], ([Theme, ThemeVal, ColorVal]) => {
     UserTheme.value = Theme
     setUserTheme(ThemeVal, ColorVal)
 })
 
 nextTick(()=> {
-    window['$split'] = split;
+    window['$split'] = split
     setUserTheme(appStore.getThemeVal, appStore.getColor)
-    //当屏幕分辨率宽度低于1920时,自动折叠菜单
-    const width = document.body.clientWidth
-    if (width < 1920) {
-        appStore.setCollapse(true)
-    }
     //生产环境下,检测更新
     if (import.meta.env.PROD) {
         getVersionJsonApi()
@@ -48,7 +43,7 @@ nextTick(()=> {
 //获取版本更新信息
 const getVersionJsonApi = async () => {
     const cache_version = getStoreValue('version')
-    const {res} = await getVersionJson()
+    const { res } = await getVersionJson()
     const version = getObjValue(res)?.value
     setStoreValue('version', version)
     if (cache_version && cache_version !== version) {
@@ -62,7 +57,7 @@ const getVersionJsonApi = async () => {
                     //刷新页面
                     window.location.reload()
                 }
-            }
+            },
         })
     }
 }
@@ -76,12 +71,12 @@ const setUserTheme = (theme, appColor) => {
     //设置主题
     let val = UserTheme.value
     if (val === 'auto') {
-        theme = useOsTheme();
+        theme = useOsTheme()
     }
     if (theme === '') {
-        theme = val;
+        theme = val
     }
     //挂载相关样式
-    document.documentElement.setAttribute('class',`${theme} color-${colorName}`)
+    document.documentElement.setAttribute('class', `${theme} color-${colorName}`)
 }
 </script>

+ 2 - 2
src/config/app.js

@@ -1,7 +1,7 @@
-import theme from './theme';
+import theme from './theme'
 
 //默认配置
 export default {
     theme: 'dark', //用户可选择类型:auto,light, dark
-    color: theme.color[1]
+    color: theme.color[0],
 }

+ 1 - 1
src/config/index.json

@@ -1,6 +1,6 @@
 {
     "version": "202304141558",
-    "target": "http://192.168.0.136:8090",
+    "target": "http://47.110.251.215:8090",
     "smsPhone": "",
     "vite": {
         "port": 5175,

+ 4 - 6
src/config/theme.js

@@ -1,10 +1,8 @@
 //主题配置
 export default {
     color: [
-        {name: 'green', color: '#1ECC95', label: '森绿'}, {name: 'blue', color: '#0081ff', 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: '橘橙'},
-        {name: 'yellow', color: '#fbbd08', label: '明黄'}, {name: 'brown', color: '#a5673f', label: '棕褐'}
-    ]
+        { name: 'blue', color: '#204DA0', label: '深蓝' },
+        { name: 'light-blue', color: '#409eff', label: '浅蓝' },
+        { name: 'black1', color: '#2c3643', label: '黑色' },
+    ],
 }

+ 483 - 0
src/layout/index.scss

@@ -0,0 +1,483 @@
+.hc-layout-box {
+    position: relative;
+    height: 100vh;
+    width: 100%;
+    .hc-app-bg-box {
+        position: absolute;
+        bottom: 0;
+        left: 0;
+        right: 0;
+        top: 0;
+        z-index: 0;
+        display: flex;
+        img {
+            width: 100%;
+            height: 100%;
+            object-fit: cover;
+        }
+    }
+    .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: 5px;
+                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;
+            .hc-header-project-name-box {
+                position: relative;
+                padding: 4px 15px;
+                border-radius: 100px;
+                background: white;
+                height: 28px;
+                display: flex;
+                align-items: center;
+                margin-right: 20px;
+                cursor: pointer;
+                user-select: none;
+                max-width: 340px;
+                font-size: 14px;
+                color: #606266;
+                .project-icon {
+                    margin-right: 5px;
+                }
+            }
+            .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);
+}

+ 113 - 326
src/layout/index.vue

@@ -1,386 +1,173 @@
 <template>
     <el-container class="hc-layout-box">
-        <div v-show="AppTheme === 'dark'" class="hc-app-bg-box">
+        <div v-show="appTheme === 'dark'" class="hc-app-bg-box">
             <img :src="appViewBg" alt="" crossOrigin="anonymous">
         </div>
-        <el-aside v-show="MenuBarKey !== 'home-index'" :width="isCollapse ? '100px' : '250px'" class="hc-aside-box" :class="[isCollapse ? 'is-collapse' : '']">
-            <div class="hc-aside-logo-box" @click="logoClick">
-                <img id="hc-logo-icon" :src="appLogoIcon" alt="">
-                <img v-show="!isCollapse" id="hc-logo-name" :src="appLogoName" alt="">
+        <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="">
+                <img v-show="!isCollapse" id="logo-name" :src="appLogoName" alt="">
             </div>
-            <div class="hc-aside-menu-box">
-                <el-scrollbar>
-                    <MenuBar :datas="MenuBarData" :cur="MenuBarKey" :collapse="isCollapse" :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="hc-container-view">
-            <el-header v-show="MenuBarKey !== 'home-index'" class="hc-header-view">
-                <div id="hc-header-page-name" class="hc-header-page-name">
-                    {{ RoutesTitle }}
+            <div class="header-content-bar">
+                <HcCascader v-if="userRoleId !== website.role_id" @send="cascaderSend" @change="cascaderChange" />
+                <div v-else class="hc-header-project-name-box" @click="userProjectClick">
+                    <HcIcon name="arrow-right" class="project-icon" />
+                    <div class="truncate">{{ projectInfoData?.projectName ?? '请先选择项目' }}</div>
                 </div>
-                <div class="hc-header-top-menu-bar">
-                    <TopMenuBar />
+                <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-content">
-                    <div v-if="userRoleId !== website.role_id" class="hc-header-cascader-box">
-                        <div class="project-name-box">
-                            {{ projectInfo.projectAlias }} / {{ contractInfo.name }}
-                        </div>
-                        <el-cascader ref="ElCascaderRef" v-model="projectValue" :options="projectContract" :props="projectProps" placeholder="请选择项目" @change="projectContractChange" />
-                    </div>
-                    <div v-else class="hc-header-project-name-box" @click="userProjectClick">
-                        <span class="project-icon">
-                            <HcIcon name="arrow-right" />
-                        </span>
-                        <span class="project-name">{{ projectInfoData?.projectName ?? '请先选择项目' }}</span>
+                <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" :msg-change="msgChange" />
+                                </keep-alive>
+                            </transition>
+                        </router-view>
                     </div>
-                    <HelpInfoBar />
-                    <ConfigBar />
-                    <UserInfoBar />
                 </div>
-            </el-header>
-            <el-main id="hc-main-box" class="hc-main-box" :class="MenuBarKey">
-                <router-view v-if="reloadRouter" v-slot="{ Component }">
-                    <transition name="fade-transform">
-                        <keep-alive include="ProductList">
-                            <component :is="Component" :msg-count="msgCount" :msg-change="msgChange" />
-                        </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 TopMenuBar from './modules/TopMenuBar.vue'
-import HelpInfoBar from './modules/HelpInfoBar.vue'
-import UserInfoBar from './modules/UserInfoBar.vue'
-import ConfigBar from './modules/ConfigBar.vue'
-import { initButtons, initProjectContract } from '~sto/app'
-import website from '~src/config/index'
-import appViewBg from '~src/assets/view/bg.png'
-import { setImageColor, 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'
+import website from '~src/config'
 
 //初始组合式
 const router = useRouter()
-const useRoutes = useRoute()
-const useAppState = useAppStore()
-
-//系统信息
-const appTitle = ref(useAppState.getTitle)
-const appLogoIcon = ref(useAppState.getLogoIcon)
-const appLogoName = ref(useAppState.getLogoName)
+const store = useAppStore()
 
-//路由参数
-const routerQuery = useRoutes?.query
 const reloadRouter = ref(true)
-const BarMenuKey = useRoutes?.name ?? 'home-index'
-const BarMenuTitle = useRoutes?.meta?.title ?? ''
+const userRoleId = ref(store.getRoleId)
+const projectInfoData = ref(store.getProjectInfo)
 
-//主题和色调变量
-const AppColor = ref(useAppState.getColor)
-const AppTheme = ref(useAppState.getTheme)
+//子组件
+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'
+import appViewBg from '~src/assets/view/bg.png'
 
-//顶部菜单数据和相关处理
-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 userRoleId = ref(useAppState.getRoleId)
-const projectInfoData = ref(useAppState.getProjectInfo)
+// logo
+const appLogoIcon = ref(store.getLogoIcon)
+const appLogoName = ref(store.getLogoName)
+const appTheme = ref(store.getTheme)
 
-//项目合同段
-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()
-    if (userRoleId.value !== website.role_id) {
-        const info = useAppState.getProjectContract || []
-        projectContractData(info)
-    }
-    setIsCollapse(RoutesName.value)
-    useAppState.barMenuName = BarMenuTitle
-    setInitSocket()
-    setLogoImageColor()
-    setLogoNameColor(useAppState.getTheme)
-})
-
-//监听
-watch(() => [
-    useAppState.getProjectContract,
-    useAppState.getMenus,
-    useAppState.getTheme,
-    useAppState.getColor,
-    useRoutes?.name,
-    useRoutes?.meta?.title,
-    useAppState.getCollapse,
-    useAppState.getProjectInfo,
-], ([projectContractArr, userMenus, theme, ColorVal, RouteName, RouteTitle, collapse, projectInfo]) => {
-    MenuBarData.value = userMenus
-    AppColor.value = ColorVal
-    AppTheme.value = theme
-    RoutesName.value = RouteName ?? 'home-index'
-    MenuBarKey.value = RouteName ?? 'home-index'
-    RoutesTitle.value = RouteTitle ?? ''
-    isCollapse.value = collapse
-    setIsCollapse(RoutesName.value)
-    if (userRoleId.value !== website.role_id) {
-        projectContractData(projectContractArr || [])
-    } else {
-        projectInfoData.value = projectInfo
-    }
-    useAppState.barMenuName = RouteTitle ?? ''
-    setAppName(appTitle.value)
-    setLogoNameColor(theme)
-})
-
-//监听
-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('hc-logo-icon', AppColor.value?.color)
+//路由信息
+const routerMenuLoad = ({ key }) => {
+    menuBarKey.value = key
 }
 
-//设置Logo图片颜色
-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('hc-logo-name').style.filter = filter
-    } catch {}
+// 是否折叠
+const isCollapse = ref(false)
+const collapseChange = () => {
+    const bool = !isCollapse.value
+    isCollapse.value = bool
+    store.setCollapse(bool)
 }
 
+//顶部菜单导航
+const isAsideMenu = ref(true)
+const topMenuLoad = () => {
+    isAsideMenu.value = false
+}
 
-//设置折叠
-const setIsCollapse = (key) => {
-    if (key === 'data-fill-wbs') {
-        isCollapse.value = true
-        useAppState.setCollapse(true)
+//顶部菜单导航被点击
+const topMenuChange = (data) => {
+    if (!isNullES(data)) {
+        menuBarData.value = data
+        isAsideMenu.value = true
     }
 }
 
-//是否折叠
-const collapseChange = (bool) => {
-    isCollapse.value = bool
-    useAppState.setCollapse(bool)
+//菜单被点击
+const menuBarChange = ({ code }) => {
+    menuBarKey.value = code
+    router.push({ name: code })
 }
 
-//处理项目合同段数据
-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)
-        } else {
-            projectValue.value = String(contractId)
-            projectInfo.value = UserProjectInfo
-            contractInfo.value = UserContractInfo
-        }
-    } else {
-        projectContract.value = []
-        projectValue.value = null
-        projectInfo.value = {}
-        contractInfo.value = {}
-    }
+//消息数量
+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 msgChange = ref(0)
+
+//用户信息
+const userInfoLoad = ({ user_id }) => {
+    HcSocket.create(user_id, (data) => {
+        msgCount.value = getObjValue(data)
+        msgChange.value++
+    })
+}
+
+//项目合同段的ID
+const cascaderSend = ({ projectId, contractId }) => {
+    HcSocket.send(projectId + ',' + contractId)
 }
 
-//项目被选择
-const ElCascaderRef = ref(null)
-const projectContractChange = (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('切换了项目,数据更新中')
-    //刷新路由
+// 项目切换
+const cascaderChange = () => {
     reloadRouter.value = false
-    nextTick(()=>{
+    nextTick(() => {
         reloadRouter.value = true
     })
 }
 
-//菜单被点击
-const MenuBarChange = (item) => {
-    MenuBarKey.value = item?.code
-    setIsCollapse(item?.code)
-    router.push({ name: item?.code })
-}
-
 //首页
 const logoClick = () => {
-    router.push({
-        name: useAppState.homeUrl,
-    })
+    router.push({ name: store.homeUrl })
 }
 
-//项目档案汇总
 const userProjectClick = () => {
     router.push({ path: '/user/project' })
 }
-const msgCount = ref({
-})
-const msgChange = ref(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 = data
-                console.log(data, '消息信息')
-                msgChange.value++
-
-            }
-        }
-        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;
-            }
-        }
-    }
-}
+@import "./index.scss";
 </style>

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

@@ -0,0 +1,158 @@
+<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 { 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(() => {
+    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;
+        height: 1px;
+    }
+    .el-cascader.hc-header-cascader {
+        width: 100%;
+        .el-input .el-input__wrapper {
+            border-radius: 104px;
+            height: 28px;
+        }
+    }
+}
+</style>

+ 3 - 29
src/layout/modules/ConfigBar.vue

@@ -1,43 +1,17 @@
 <template>
     <div class="header-icon-bar" @click="toConfigClick">
-        <HcIcon name="settings" class="header-icon"/>
+        <HcIcon name="settings" class="header-icon" />
     </div>
 </template>
 
 <script setup>
 import { useRouter } from 'vue-router'
-
 const router = useRouter()
 
 //跳转到系统设置页面
 const toConfigClick = () => {
     router.push({
-        path: '/config/theme'
-    });
+        path: '/config/theme',
+    })
 }
 </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>

+ 26 - 66
src/layout/modules/HelpInfoBar.vue

@@ -1,77 +1,67 @@
 <template>
-    <el-popover placement="bottom" :width="220" trigger="hover">
+    <el-popover :width="220" placement="bottom" trigger="hover">
         <template #reference>
             <div class="header-icon-bar">
-                <HcIcon name="question" class="header-icon"/>
+                <HcIcon class="header-icon" name="question" />
             </div>
         </template>
         <div class="header-pover-menu-list">
             <div class="list-item">
                 <span class="text">开启功能气泡提示</span>
-                <el-switch v-model="bubbleVal" @change="bubbleUpdate"/>
+                <el-switch v-model="bubbleVal" @change="bubbleUpdate" />
             </div>
-            <div class="list-item" @click="excelPreviewClick" v-if="excelUrl">
+            <div v-if="excelUrl" class="list-item" @click="excelPreviewClick">
                 <span class="text">查看系统操作文档</span>
-                <img class="icon" :src="getAssetsHomeFile('word.png')" alt=""/>
-                <img class="icon1" :src="getAssetsHomeFile('word1.png')" alt=""/>
+                <img :src="getAssetsHomeFile('word.png')" alt="" class="icon">
+                <img :src="getAssetsHomeFile('word1.png')" alt="" class="icon1">
             </div>
-            <div class="list-item" @click="videoPreviewModal = true" v-if="videoUrl">
+            <div v-if="videoUrl" class="list-item" @click="videoPreviewModal = true">
                 <span class="text">当前页面功能讲解</span>
-                <img class="icon" :src="getAssetsHomeFile('video.png')" alt=""/>
-                <img class="icon1" :src="getAssetsHomeFile('video1.png')" alt=""/>
+                <img :src="getAssetsHomeFile('video.png')" alt="" class="icon">
+                <img :src="getAssetsHomeFile('video1.png')" alt="" class="icon1">
             </div>
         </div>
     </el-popover>
-    <!--当前页面功能讲解-->
-    <el-dialog v-model="videoPreviewModal" width="62rem" destroy-on-close :before-close="videoPreviewModalClose">
-        <video class="preview-video" :src="videoUrl" controls="controls" autoplay="autoplay">
+    <!-- 当前页面功能讲解 -->
+    <el-dialog v-model="videoPreviewModal" :before-close="videoPreviewModalClose" destroy-on-close width="62rem">
+        <video :src="videoUrl" autoplay="autoplay" class="preview-video" controls="controls">
             您的浏览器不支持 video
         </video>
     </el-dialog>
 </template>
 
 <script setup>
-import { ref,watch,nextTick } from "vue";
-import { useRouter,useRoute } from 'vue-router'
-import { useAppStore } from "~src/store";
-import ScreenShot from "js-web-screen-shot";
-import { getStoreValue }  from '~src/utils/storage'
-import {getObjValue, getToObjVal} from "js-fast-way"
+import { nextTick, ref, watch } from 'vue'
+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()
 
 //相关变量
-const route = getObjValue(getStoreValue('route'))
-const bubbleVal = ref(useAppState.getBubble);
+const route = getObjValue(getStore('route'))
+const bubbleVal = ref(useAppState.getBubble)
 const videoUrl = ref('')
 const excelUrl = ref('')
 const videoPreviewModal = ref(false)
-const webRtcVal = ref(false)
-const fullScreenVal = ref(true)
 
 //监听
 watch(() => [
     useAppState.getBubble,
     useRoutes.name,
-    useAppState.getShotWebRtc,
-    useAppState.getFullScreen,
-], ([Bubble,routeName,webRtc,fullScreen]) => {
+], ([Bubble, routeName]) => {
     bubbleVal.value = Bubble
     getVideoUrl(routeName)
     getExcelUrl(routeName)
-    setScreenShotData(webRtc,fullScreen)
 })
 
 //渲染完成
 nextTick(() => {
     getVideoUrl(useRoutes?.name)
     getExcelUrl(useRoutes?.name)
-    const webRtc = useAppState.getShotWebRtc
-    const fullScreen = useAppState.getFullScreen
-    setScreenShotData(webRtc,fullScreen)
 })
 
 //取文档地址
@@ -84,24 +74,17 @@ const getVideoUrl = (name) => {
     videoUrl.value = getToObjVal(route, name, 'videoUrl')
 }
 
-//处理屏幕截图的配置
-const setScreenShotData = (webRtc,fullScreen) => {
-    webRtcVal.value = parseInt(webRtc) === 1
-    fullScreenVal.value = parseInt(fullScreen) === 1
-}
-
 //开关值改变
 const bubbleUpdate = (val) => {
-    bubbleVal.value = val;
-    useAppState.setBubble(val);
+    bubbleVal.value = val
+    useAppState.setBubble(val)
 }
 
-
 // 获取assets静态资源
 const getAssetsHomeFile = (url) => {
-    const path = `../../assets/images/${url}`;
-    const modules = import.meta.globEager("../../assets/images/*");
-    return modules[path].default;
+    const path = `../../assets/images/${url}`
+    const modules = import.meta.globEager('../../assets/images/*')
+    return modules[path].default
 }
 
 //关闭视频弹窗
@@ -120,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;
@@ -156,7 +116,7 @@ const excelPreviewClick = () => {
         .text {
             flex: auto;
         }
-        .icon,.icon1 {
+        .icon, .icon1 {
             width: 24px;
             height: 24px;
         }

+ 29 - 297
src/layout/modules/MenuBar.vue

@@ -1,326 +1,58 @@
 <template>
-    <el-menu class="hc-aside-menu" :default-active="curKey" :collapse="isCollapse" unique-opened>
-        <!-- <el-menu-item index="home-index" @click="MenuClick({code: 'home-index'})">
-            <div class="hc-aside-menu-item">
-                <div class="menu---item">
-                    <HcIcon name="home-3" :fill="curKey === 'home-index'" class="hc-menu-icon"/>
-                    <div class="name">数据看板</div>
-                </div>
-            </div>
-        </el-menu-item> -->
-        <MenuItem :datas="datas" :cur="curKey" :collapse="isCollapse" @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 { ref, watch } from 'vue'
+import MenuItem from './MenuItem.vue'
+
 const props = defineProps({
     datas: {
         type: Array,
-        default: () => ([])
+        default: () => ([]),
     },
     cur: {
         type: String,
-        default: 'home-index'
+        default: 'home-index',
     },
     collapse: {
         type: Boolean,
-        default: false
+        default: false,
+    },
+    msgCount: {
+        type: Object,
+        default: () => ({}),
     },
 })
 
+//事件
+const emit = defineEmits(['change'])
+
 //初始变量
-const curKey = ref(props.cur);
-const isCollapse = ref(props.collapse);
+const curKey = ref(props.cur)
+const isCollapse = ref(props.collapse)
 
 //监听
 watch(() => [
     props.cur,
-    props.collapse
-], ([cur,collapse]) => {
+    props.collapse,
+], ([cur, collapse]) => {
     curKey.value = cur
     isCollapse.value = collapse
 })
 
-//事件
-const emit = defineEmits(['change'])
-const MenuClick = (item) => {
-    curKey.value = item?.code || '';
+//处理菜单数据
+const setMenuItem = async (item) => {
+    curKey.value = item?.code || ''
     emit('change', item)
 }
-</script>
-
-<style lang="scss">
-.el-menu.hc-aside-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-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: 100px;
-    .el-sub-menu__title {
-        height: inherit;
-        line-height: initial;
-        width: 100px;
-        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: 100px;
-        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;
-}
-.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>

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

@@ -1,26 +1,27 @@
 <template>
-    <template v-for="item in datas">
-        <el-sub-menu :index="item?.code" :popper-offset="0" :popper-class="'aside-menu-popper ' " v-if="item?.children && item?.children.length > 0">
+    <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}`">
             <template #title>
                 <div class="hc-aside-menu-item">
                     <div class="menu---item">
-                        <HcIcon :name="item?.source" :fill="curKey === item?.code" class="hc-menu-icon" v-if="item?.source"/>
-                        <div class="name truncate" v-if="isCollapse">{{ item?.name.substring(0,2) }}</div>
-                        <div class="name truncate" v-else>{{ item?.name }}</div>
-                        <el-badge :value="20" v-if="item?.code === 'tasks'"/>
+                        <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"/>
+                <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 :index="item?.code" v-else @click="MenuClick(item)">
+        <el-menu-item v-else :index="item?.code" @click="MenuClick(item)">
             <div class="hc-aside-menu-item">
                 <div class="menu---item">
-                    <HcIcon :name="item?.source" :fill="curKey === item?.code" class="hc-menu-icon" v-if="item?.source"/>
-                    <div class="name truncate" v-if="isCollapse">{{ item?.name.substring(0,2) }}</div>
-                    <div class="name truncate" v-else>{{ item?.name }}</div>
-                    <el-badge :value="12" v-if="item?.code === 'tasks-data' || item?.code === 'message-data'"/>
+                    <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>
@@ -28,37 +29,52 @@
 </template>
 
 <script setup>
-import {ref,watch} from "vue";
-import MenuItem from "./MenuItem.vue"
+import { ref, watch } from 'vue'
+import MenuItem from './MenuItem.vue'
+
 const props = defineProps({
     datas: {
         type: Array,
-        default: () => []
+        default: () => ([]),
     },
     cur: {
         type: String,
-        default: ''
+        default: '',
     },
     collapse: {
         type: Boolean,
-        default: false
+        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'])
+
 //初始变量
-const curKey = ref(props.cur);
-const isCollapse = ref(props.collapse);
+const curKey = ref(props.cur)
+const isCollapse = ref(props.collapse)
 
 //监听
 watch(() => [
     props.cur,
-    props.collapse
-], ([cur,collapse]) => {
+    props.collapse,
+], ([cur, collapse]) => {
     curKey.value = cur
     isCollapse.value = collapse
 })
 
-//事件
-const emit = defineEmits(['change'])
 const MenuClick = (item) => {
     emit('change', item)
 }

+ 130 - 0
src/layout/modules/RouterMenu.vue

@@ -0,0 +1,130 @@
+<template>
+    <el-scrollbar>
+        <div class="hc-router-tab-box">
+            <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>
+            </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') || [])
+const barRoutes = ref({ key: '', path: '', title: '', query: null })
+
+//渲染完成
+onMounted(() => {
+    const { name, path, meta, query } = useRoutes
+    barRoutes.value = { path, key: name, title: meta?.title, query }
+    setBarMenuData()
+})
+
+//监听
+watch(() => [
+    useRoutes?.name,
+    useRoutes?.path,
+    useRoutes?.query,
+    useRoutes?.meta?.title,
+], ([key, path, query, title]) => {
+    barRoutes.value = { path, key, title, query }
+    setBarMenuData()
+})
+
+//设置菜单数据
+const setBarMenuData = () => {
+    const { key, path, title, query } = barRoutes.value
+    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)
+    }
+    emit('load', barRoutes.value)
+}
+
+//菜单被点击
+const barMenuClick = (item) => {
+    const { key } = barRoutes.value
+    if (key !== item.key) {
+        router.push({ name: item.key, query: item.query })
+    }
+}
+
+//鼠标右键菜单
+const contextMenuRef = ref(null)
+const barItem = ref({})
+const barItemIndex = ref(0)
+const menusData = ref([
+    { label: '关闭当前', key: 'close' },
+    { label: '关闭所有', key: 'all' },
+    { label: '关闭其它', key: 'other' },
+])
+const barMenuContextMenu = (event, item, index) => {
+    event.preventDefault()
+    barItem.value = item
+    barItemIndex.value = index
+    contextMenuRef.value?.showMenu(event)
+}
+
+//鼠标右键菜单被点击
+const handleMenuSelect = ({ key }) => {
+    if (key === 'close') {
+        barMenuCloseClick(barItem.value, barItemIndex.value)
+    } else if (key === 'all') {
+        barMenuData.value = []
+        setStore('bar-menu-datas', [])
+        router.push({ name: store.homeUrl })
+    } else if (key === 'other') {
+        const { key } = barRoutes.value
+        barMenuData.value = barMenuData.value.filter(item => item.key === key)
+        setStore('bar-menu-datas', barMenuData.value)
+    }
+}
+
+//菜单关闭被点击
+const barMenuCloseClick = (item, index) => {
+    const total = barMenuData.value.length - 1
+    const { key } = barRoutes.value
+    barMenuData.value.splice(index, 1)
+    if (key === item.key) {
+        let items = {}
+        const indexs = barMenuData.value.length - 1
+        if (total > index) {
+            items = barMenuData.value[index]
+        } else if (indexs >= 0) {
+            items = barMenuData.value[indexs]
+        }
+        if (indexs < 0) {
+            setStore('bar-menu-datas', barMenuData.value)
+            router.push({ name: store.homeUrl })
+        } else {
+            barRoutes.value = items
+            setStore('bar-menu-datas', barMenuData.value)
+            router.push({ name: items.key, query: items.query })
+        }
+    } else {
+        setStore('bar-menu-datas', barMenuData.value)
+    }
+}
+</script>

+ 53 - 51
src/layout/modules/UserInfoBar.vue

@@ -1,16 +1,16 @@
 <template>
     <el-dropdown size="large">
         <div class="header-bar user-info-bar">
-            <img class="user-avatar" :src="userInfo['avatar'] || avatarPng" :alt="userInfo['account']">
-            <span class="user-name">{{userInfo['real_name']}}</span>
-            <HcIcon name="arrow-down-s" ui="arrow-icon"/>
+            <img :alt="userInfo.account" :src="userInfo.avatar || avatarPng" class="user-avatar">
+            <span class="user-name">{{ userInfo.real_name }}</span>
+            <HcIcon name="arrow-down-s" ui="arrow-icon" />
         </div>
         <template #dropdown>
             <el-dropdown-menu>
-                <el-dropdown-item v-for="item in options">
+                <el-dropdown-item v-for="item in options" :key="item.key">
                     <div class="hc-dropdown-item" @click="handleSelect(item.key)">
-                        <HcIcon :name="item.icon" class="icon"/>
-                        <span class="label">{{item.label}}</span>
+                        <HcIcon :name="item.icon" class="icon" />
+                        <span class="label">{{ item.label }}</span>
                     </div>
                 </el-dropdown-item>
             </el-dropdown-menu>
@@ -19,73 +19,74 @@
 </template>
 
 <script setup>
-import {onMounted, ref, watch} from "vue";
-import {useRouter} from 'vue-router'
-import {useAppStore} from "~src/store";
-import website from "~src/config/index";
-import avatarPng from '~src/assets/images/avatar.png';
-import {RefreshToken,LogOut} from "~sto/user";
-import {getStoreValue} from '~src/utils/storage'
-import {calcDate, isNullES} from "js-fast-way"
+import { onMounted, ref, watch } from 'vue'
+import { useRouter } from 'vue-router'
+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 { 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 refreshLock = ref(false);
+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
 const getRefreshToken = () => {
     setInterval(() => {
-        const token = getStoreValue("token",true) || {};
-        const date = calcDate(token.datetime, new Date().getTime());
-        if (isNullES(date)) return;
+        const token = getStore('token', true) || {}
+        const date = calcDate(token.datetime, new Date().getTime())
+        if (isNullES(date)) return
         if (date.seconds >= website.tokenTime && !refreshLock.value) {
-            refreshLock.value = true;
+            refreshLock.value = true
             console.log('刷新token')
             RefreshToken().then(() => {
-                refreshLock.value = false;
+                refreshLock.value = false
             }).catch(() => {
-                refreshLock.value = false;
-                router.push({name: 'login'});
-            });
+                refreshLock.value = false
+                router.push({ name: 'login' })
+            })
         }
     }, 10000)
 }
 
 const options = [
     {
-        key: "my",
-        label: "个人中心",
-        icon: 'user-3'
+        key: 'my',
+        label: '个人中心',
+        icon: 'user-3',
     },
     {
-        key: "logout",
-        label: "退出登录",
-        icon: 'login-box'
-    }
-];
+        key: 'logout',
+        label: '退出登录',
+        icon: 'login-box',
+    },
+]
 
-//事件
-const emit = defineEmits(['change'])
-const handleSelect = async (key) => {
+const handleSelect = (key) => {
     if (key === 'my') {
-        router.push({name:'user-index'});
+        router.push({ name: 'user-index' })
     } else if (key === 'logout') {
-        await LogOut();
-        window.$message?.info('退出成功');
-        router.push({name:'login'});
+        LogOut().then()
+        window.$message?.info('退出成功')
+        router.push({ name: 'login' })
     }
 }
 </script>
@@ -97,10 +98,12 @@ const handleSelect = async (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;
@@ -108,20 +111,20 @@ const handleSelect = async (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 {
@@ -137,7 +140,6 @@ const handleSelect = async (key) => {
     }
 }
 
-
 .hc-dropdown-item {
     display: flex;
     align-items: center;

+ 386 - 0
src/layout111/index.vue

@@ -0,0 +1,386 @@
+<template>
+    <el-container class="hc-layout-box">
+        <div v-show="AppTheme === 'dark'" class="hc-app-bg-box">
+            <img :src="appViewBg" alt="" crossOrigin="anonymous">
+        </div>
+        <el-aside v-show="MenuBarKey !== 'home-index'" :width="isCollapse ? '100px' : '250px'" class="hc-aside-box" :class="[isCollapse ? 'is-collapse' : '']">
+            <div class="hc-aside-logo-box" @click="logoClick">
+                <img id="hc-logo-icon" :src="appLogoIcon" alt="">
+                <img v-show="!isCollapse" id="hc-logo-name" :src="appLogoName" alt="">
+            </div>
+            <div class="hc-aside-menu-box">
+                <el-scrollbar>
+                    <MenuBar :datas="MenuBarData" :cur="MenuBarKey" :collapse="isCollapse" :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="hc-container-view">
+            <el-header v-show="MenuBarKey !== 'home-index'" 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 v-if="userRoleId !== website.role_id" class="hc-header-cascader-box">
+                        <div class="project-name-box">
+                            {{ projectInfo.projectAlias }} / {{ contractInfo.name }}
+                        </div>
+                        <el-cascader ref="ElCascaderRef" v-model="projectValue" :options="projectContract" :props="projectProps" placeholder="请选择项目" @change="projectContractChange" />
+                    </div>
+                    <div v-else class="hc-header-project-name-box" @click="userProjectClick">
+                        <span class="project-icon">
+                            <HcIcon name="arrow-right" />
+                        </span>
+                        <span class="project-name">{{ projectInfoData?.projectName ?? '请先选择项目' }}</span>
+                    </div>
+                    <HelpInfoBar />
+                    <ConfigBar />
+                    <UserInfoBar />
+                </div>
+            </el-header>
+            <el-main id="hc-main-box" class="hc-main-box" :class="MenuBarKey">
+                <router-view v-if="reloadRouter" v-slot="{ Component }">
+                    <transition name="fade-transform">
+                        <keep-alive include="ProductList">
+                            <component :is="Component" :msg-count="msgCount" :msg-change="msgChange" />
+                        </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 TopMenuBar from './modules/TopMenuBar.vue'
+import HelpInfoBar from './modules/HelpInfoBar.vue'
+import UserInfoBar from './modules/UserInfoBar.vue'
+import ConfigBar from './modules/ConfigBar.vue'
+import { initButtons, initProjectContract } from '~sto/app'
+import website from '~src/config/index'
+import appViewBg from '~src/assets/view/bg.png'
+import { setImageColor, setImageColorStyle } from 'js-fast-way'
+import { setAppName } from '~uti/tools'
+
+//初始组合式
+const router = useRouter()
+const useRoutes = useRoute()
+const useAppState = useAppStore()
+
+//系统信息
+const appTitle = ref(useAppState.getTitle)
+const appLogoIcon = ref(useAppState.getLogoIcon)
+const appLogoName = ref(useAppState.getLogoName)
+
+//路由参数
+const routerQuery = useRoutes?.query
+const reloadRouter = ref(true)
+const BarMenuKey = useRoutes?.name ?? 'home-index'
+const BarMenuTitle = useRoutes?.meta?.title ?? ''
+
+//主题和色调变量
+const AppColor = ref(useAppState.getColor)
+const AppTheme = ref(useAppState.getTheme)
+
+//顶部菜单数据和相关处理
+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 userRoleId = ref(useAppState.getRoleId)
+const projectInfoData = ref(useAppState.getProjectInfo)
+
+//项目合同段
+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()
+    if (userRoleId.value !== website.role_id) {
+        const info = useAppState.getProjectContract || []
+        projectContractData(info)
+    }
+    setIsCollapse(RoutesName.value)
+    useAppState.barMenuName = BarMenuTitle
+    setInitSocket()
+    setLogoImageColor()
+    setLogoNameColor(useAppState.getTheme)
+})
+
+//监听
+watch(() => [
+    useAppState.getProjectContract,
+    useAppState.getMenus,
+    useAppState.getTheme,
+    useAppState.getColor,
+    useRoutes?.name,
+    useRoutes?.meta?.title,
+    useAppState.getCollapse,
+    useAppState.getProjectInfo,
+], ([projectContractArr, userMenus, theme, ColorVal, RouteName, RouteTitle, collapse, projectInfo]) => {
+    MenuBarData.value = userMenus
+    AppColor.value = ColorVal
+    AppTheme.value = theme
+    RoutesName.value = RouteName ?? 'home-index'
+    MenuBarKey.value = RouteName ?? 'home-index'
+    RoutesTitle.value = RouteTitle ?? ''
+    isCollapse.value = collapse
+    setIsCollapse(RoutesName.value)
+    if (userRoleId.value !== website.role_id) {
+        projectContractData(projectContractArr || [])
+    } else {
+        projectInfoData.value = projectInfo
+    }
+    useAppState.barMenuName = RouteTitle ?? ''
+    setAppName(appTitle.value)
+    setLogoNameColor(theme)
+})
+
+//监听
+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('hc-logo-icon', AppColor.value?.color)
+}
+
+//设置Logo图片颜色
+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('hc-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)
+        } else {
+            projectValue.value = String(contractId)
+            projectInfo.value = UserProjectInfo
+            contractInfo.value = UserContractInfo
+        }
+    } else {
+        projectContract.value = []
+        projectValue.value = null
+        projectInfo.value = {}
+        contractInfo.value = {}
+    }
+}
+
+//项目被选择
+const ElCascaderRef = ref(null)
+const projectContractChange = (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('切换了项目,数据更新中')
+    //刷新路由
+    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: useAppState.homeUrl,
+    })
+}
+
+//项目档案汇总
+const userProjectClick = () => {
+    router.push({ path: '/user/project' })
+}
+const msgCount = ref({
+})
+const msgChange = ref(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 = data
+                console.log(data, '消息信息')
+                msgChange.value++
+
+            }
+        }
+        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;
+            }
+        }
+    }
+}
+</style>

+ 0 - 0
src/layout/layout.scss → src/layout111/layout.scss


+ 43 - 0
src/layout111/modules/ConfigBar.vue

@@ -0,0 +1,43 @@
+<template>
+    <div class="header-icon-bar" @click="toConfigClick">
+        <HcIcon name="settings" class="header-icon"/>
+    </div>
+</template>
+
+<script setup>
+import { useRouter } from 'vue-router'
+
+const router = useRouter()
+
+//跳转到系统设置页面
+const toConfigClick = () => {
+    router.push({
+        path: '/config/theme'
+    });
+}
+</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>

+ 181 - 0
src/layout111/modules/HelpInfoBar.vue

@@ -0,0 +1,181 @@
+<template>
+    <el-popover placement="bottom" :width="220" trigger="hover">
+        <template #reference>
+            <div class="header-icon-bar">
+                <HcIcon name="question" class="header-icon"/>
+            </div>
+        </template>
+        <div class="header-pover-menu-list">
+            <div class="list-item">
+                <span class="text">开启功能气泡提示</span>
+                <el-switch v-model="bubbleVal" @change="bubbleUpdate"/>
+            </div>
+            <div class="list-item" @click="excelPreviewClick" v-if="excelUrl">
+                <span class="text">查看系统操作文档</span>
+                <img class="icon" :src="getAssetsHomeFile('word.png')" alt=""/>
+                <img class="icon1" :src="getAssetsHomeFile('word1.png')" alt=""/>
+            </div>
+            <div class="list-item" @click="videoPreviewModal = true" v-if="videoUrl">
+                <span class="text">当前页面功能讲解</span>
+                <img class="icon" :src="getAssetsHomeFile('video.png')" alt=""/>
+                <img class="icon1" :src="getAssetsHomeFile('video1.png')" alt=""/>
+            </div>
+        </div>
+    </el-popover>
+    <!--当前页面功能讲解-->
+    <el-dialog v-model="videoPreviewModal" width="62rem" destroy-on-close :before-close="videoPreviewModalClose">
+        <video class="preview-video" :src="videoUrl" controls="controls" autoplay="autoplay">
+            您的浏览器不支持 video
+        </video>
+    </el-dialog>
+</template>
+
+<script setup>
+import { ref,watch,nextTick } from "vue";
+import { useRouter,useRoute } from 'vue-router'
+import { useAppStore } from "~src/store";
+import ScreenShot from "js-web-screen-shot";
+import { getStoreValue }  from '~src/utils/storage'
+import {getObjValue, getToObjVal} from "js-fast-way"
+
+//初始变量
+const router = useRouter()
+const useRoutes = useRoute()
+const useAppState = useAppStore()
+
+//相关变量
+const route = getObjValue(getStoreValue('route'))
+const bubbleVal = ref(useAppState.getBubble);
+const videoUrl = ref('')
+const excelUrl = ref('')
+const videoPreviewModal = ref(false)
+const webRtcVal = ref(false)
+const fullScreenVal = ref(true)
+
+//监听
+watch(() => [
+    useAppState.getBubble,
+    useRoutes.name,
+    useAppState.getShotWebRtc,
+    useAppState.getFullScreen,
+], ([Bubble,routeName,webRtc,fullScreen]) => {
+    bubbleVal.value = Bubble
+    getVideoUrl(routeName)
+    getExcelUrl(routeName)
+    setScreenShotData(webRtc,fullScreen)
+})
+
+//渲染完成
+nextTick(() => {
+    getVideoUrl(useRoutes?.name)
+    getExcelUrl(useRoutes?.name)
+    const webRtc = useAppState.getShotWebRtc
+    const fullScreen = useAppState.getFullScreen
+    setScreenShotData(webRtc,fullScreen)
+})
+
+//取文档地址
+const getExcelUrl = (name) => {
+    excelUrl.value = getToObjVal(route, name, 'excelUrl')
+}
+
+//取视频地址
+const getVideoUrl = (name) => {
+    videoUrl.value = getToObjVal(route, name, 'videoUrl')
+}
+
+//处理屏幕截图的配置
+const setScreenShotData = (webRtc,fullScreen) => {
+    webRtcVal.value = parseInt(webRtc) === 1
+    fullScreenVal.value = parseInt(fullScreen) === 1
+}
+
+//开关值改变
+const bubbleUpdate = (val) => {
+    bubbleVal.value = val;
+    useAppState.setBubble(val);
+}
+
+
+// 获取assets静态资源
+const getAssetsHomeFile = (url) => {
+    const path = `../../assets/images/${url}`;
+    const modules = import.meta.globEager("../../assets/images/*");
+    return modules[path].default;
+}
+
+//关闭视频弹窗
+const videoPreviewModalClose = () => {
+    videoPreviewModal.value = false
+}
+
+//查看系统操作文档
+const excelPreviewClick = () => {
+    if (excelUrl.value) {
+        window.open(excelUrl.value, '_blank')
+    } else {
+        window?.$message.warning('暂无文档')
+    }
+}
+</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;
+    .list-item {
+        position: relative;
+        padding: 10px 24px;
+        cursor: pointer;
+        display: flex;
+        align-items: center;
+        transition: background-color 0.2s;
+        .text {
+            flex: auto;
+        }
+        .icon,.icon1 {
+            width: 24px;
+            height: 24px;
+        }
+        .icon1 {
+            display: none;
+        }
+        &:hover {
+            color: #ffffff;
+            background-color: var(--el-color-primary);
+            .icon {
+                display: none;
+            }
+            .icon1 {
+                display: block;
+            }
+        }
+    }
+}
+.preview-video {
+    width: 100%;
+}
+</style>

+ 326 - 0
src/layout111/modules/MenuBar.vue

@@ -0,0 +1,326 @@
+<template>
+    <el-menu class="hc-aside-menu" :default-active="curKey" :collapse="isCollapse" unique-opened>
+        <!-- <el-menu-item index="home-index" @click="MenuClick({code: 'home-index'})">
+            <div class="hc-aside-menu-item">
+                <div class="menu---item">
+                    <HcIcon name="home-3" :fill="curKey === 'home-index'" class="hc-menu-icon"/>
+                    <div class="name">数据看板</div>
+                </div>
+            </div>
+        </el-menu-item> -->
+        <MenuItem :datas="datas" :cur="curKey" :collapse="isCollapse" @change="MenuClick"/>
+    </el-menu>
+</template>
+
+<script setup>
+import {ref,watch} from "vue";
+import MenuItem from "./MenuItem.vue"
+const props = defineProps({
+    datas: {
+        type: Array,
+        default: () => ([])
+    },
+    cur: {
+        type: String,
+        default: 'home-index'
+    },
+    collapse: {
+        type: Boolean,
+        default: false
+    },
+})
+
+//初始变量
+const curKey = ref(props.cur);
+const isCollapse = ref(props.collapse);
+
+//监听
+watch(() => [
+    props.cur,
+    props.collapse
+], ([cur,collapse]) => {
+    curKey.value = cur
+    isCollapse.value = collapse
+})
+
+//事件
+const emit = defineEmits(['change'])
+const MenuClick = (item) => {
+    curKey.value = item?.code || '';
+    emit('change', item)
+}
+</script>
+
+<style lang="scss">
+.el-menu.hc-aside-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-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: 100px;
+    .el-sub-menu__title {
+        height: inherit;
+        line-height: initial;
+        width: 100px;
+        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: 100px;
+        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;
+}
+.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>

+ 65 - 0
src/layout111/modules/MenuItem.vue

@@ -0,0 +1,65 @@
+<template>
+    <template v-for="item in datas">
+        <el-sub-menu :index="item?.code" :popper-offset="0" :popper-class="'aside-menu-popper ' " v-if="item?.children && item?.children.length > 0">
+            <template #title>
+                <div class="hc-aside-menu-item">
+                    <div class="menu---item">
+                        <HcIcon :name="item?.source" :fill="curKey === item?.code" class="hc-menu-icon" v-if="item?.source"/>
+                        <div class="name truncate" v-if="isCollapse">{{ item?.name.substring(0,2) }}</div>
+                        <div class="name truncate" v-else>{{ item?.name }}</div>
+                        <el-badge :value="20" v-if="item?.code === 'tasks'"/>
+                    </div>
+                </div>
+                <HcIcon name="arrow-down-s" ui="el-icon el-sub-menu__icon-arrow"/>
+            </template>
+            <MenuItem :datas="item?.children" :cur="curKey" @change="MenuClick"/>
+        </el-sub-menu>
+        <el-menu-item :index="item?.code" v-else @click="MenuClick(item)">
+            <div class="hc-aside-menu-item">
+                <div class="menu---item">
+                    <HcIcon :name="item?.source" :fill="curKey === item?.code" class="hc-menu-icon" v-if="item?.source"/>
+                    <div class="name truncate" v-if="isCollapse">{{ item?.name.substring(0,2) }}</div>
+                    <div class="name truncate" v-else>{{ item?.name }}</div>
+                    <el-badge :value="12" v-if="item?.code === 'tasks-data' || item?.code === 'message-data'"/>
+                </div>
+            </div>
+        </el-menu-item>
+    </template>
+</template>
+
+<script setup>
+import {ref,watch} from "vue";
+import MenuItem from "./MenuItem.vue"
+const props = defineProps({
+    datas: {
+        type: Array,
+        default: () => []
+    },
+    cur: {
+        type: String,
+        default: ''
+    },
+    collapse: {
+        type: Boolean,
+        default: false
+    },
+})
+//初始变量
+const curKey = ref(props.cur);
+const isCollapse = ref(props.collapse);
+
+//监听
+watch(() => [
+    props.cur,
+    props.collapse
+], ([cur,collapse]) => {
+    curKey.value = cur
+    isCollapse.value = collapse
+})
+
+//事件
+const emit = defineEmits(['change'])
+const MenuClick = (item) => {
+    emit('change', item)
+}
+</script>

+ 0 - 0
src/layout/modules/TopMenuBar.vue → src/layout111/modules/TopMenuBar.vue


+ 149 - 0
src/layout111/modules/UserInfoBar.vue

@@ -0,0 +1,149 @@
+<template>
+    <el-dropdown size="large">
+        <div class="header-bar user-info-bar">
+            <img class="user-avatar" :src="userInfo['avatar'] || avatarPng" :alt="userInfo['account']">
+            <span class="user-name">{{userInfo['real_name']}}</span>
+            <HcIcon name="arrow-down-s" ui="arrow-icon"/>
+        </div>
+        <template #dropdown>
+            <el-dropdown-menu>
+                <el-dropdown-item v-for="item in options">
+                    <div class="hc-dropdown-item" @click="handleSelect(item.key)">
+                        <HcIcon :name="item.icon" class="icon"/>
+                        <span class="label">{{item.label}}</span>
+                    </div>
+                </el-dropdown-item>
+            </el-dropdown-menu>
+        </template>
+    </el-dropdown>
+</template>
+
+<script setup>
+import {onMounted, ref, watch} from "vue";
+import {useRouter} from 'vue-router'
+import {useAppStore} from "~src/store";
+import website from "~src/config/index";
+import avatarPng from '~src/assets/images/avatar.png';
+import {RefreshToken,LogOut} from "~sto/user";
+import {getStoreValue} from '~src/utils/storage'
+import {calcDate, isNullES} from "js-fast-way"
+
+//变量
+const router = useRouter()
+const userStore = useAppStore()
+const userInfo = ref(userStore.getUserInfo);
+const refreshLock = ref(false);
+
+//监听
+watch(() => [
+    userStore.getUserInfo
+], ([info]) => {
+    userInfo.value = info
+})
+
+onMounted(() => {
+    getRefreshToken()
+})
+
+//刷新token
+const getRefreshToken = () => {
+    setInterval(() => {
+        const token = getStoreValue("token",true) || {};
+        const date = calcDate(token.datetime, new Date().getTime());
+        if (isNullES(date)) return;
+        if (date.seconds >= website.tokenTime && !refreshLock.value) {
+            refreshLock.value = true;
+            console.log('刷新token')
+            RefreshToken().then(() => {
+                refreshLock.value = false;
+            }).catch(() => {
+                refreshLock.value = false;
+                router.push({name: 'login'});
+            });
+        }
+    }, 10000)
+}
+
+const options = [
+    {
+        key: "my",
+        label: "个人中心",
+        icon: 'user-3'
+    },
+    {
+        key: "logout",
+        label: "退出登录",
+        icon: 'login-box'
+    }
+];
+
+//事件
+const emit = defineEmits(['change'])
+const handleSelect = async (key) => {
+    if (key === 'my') {
+        router.push({name:'user-index'});
+    } else if (key === 'logout') {
+        await LogOut();
+        window.$message?.info('退出成功');
+        router.push({name:'login'});
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+.user-info-bar {
+    position: relative;
+    display: flex;
+    align-items: center;
+    height: 100%;
+    cursor: pointer;
+    padding-left: 24px;
+    .user-avatar {
+        width: 40px;
+        height: 40px;
+        border-radius: 50%;
+        background: white;
+        object-fit: cover;
+    }
+    .user-name {
+        font-size: 16px;
+        margin-left: 10px;
+        color: #202532;
+    }
+    .arrow-icon {
+        margin-left: 5px;
+        font-size: 20px;
+        color: #202532;
+    }
+    &::before {
+        position: absolute;
+        content: '';
+        left: 0;
+        width: 0;
+        height: 24px;
+        border-left: 1px solid #ccd0de;
+    }
+}
+.hc-layout-box .hc-container-view.home .hc-header-view .hc-header-content .user-info-bar {
+    color: inherit;
+    .user-name {
+        color: white;
+    }
+    .arrow-icon {
+        color: white;
+    }
+    &::before {
+        border-left: 1px solid white;
+    }
+}
+
+
+.hc-dropdown-item {
+    display: flex;
+    align-items: center;
+    .icon {
+        font-size: 14px;
+        margin-right: 8px;
+    }
+}
+</style>

+ 2 - 0
src/main.js

@@ -24,9 +24,11 @@ import 'hc-vue3-ui/style/index.scss'
 import './styles/app/main.scss'
 import './styles/app/element.scss'
 import './styles/app/theme.scss'
+
 //虚拟列表
 import 'vue-virtual-scroller/dist/vue-virtual-scroller.css' // 引入它的 css
 import VueVirtualScroller from 'vue-virtual-scroller' // 引入它
+
 //创建实例
 async function bootstrap() {
     const app = createApp(App)

+ 55 - 0
src/plugins/HcSocket.js

@@ -0,0 +1,55 @@
+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(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)
+    }
+}

+ 37 - 51
src/store/index.js

@@ -1,10 +1,10 @@
 import { defineStore } from 'pinia'
-import pinia from "~src/store/init"
-import appConfig from '~src/config/app';
-import logoIcon from "~src/assets/logo/icon.png";
-import logoName from "~src/assets/logo/name.png";
-import {getStoreValue, setStoreValue, clearStoreAll}  from '~src/utils/storage'
-import {setToken, setRefreshToken, removeToken,removeRefreshToken} from '~src/api/util/auth'
+import pinia from '~src/store/init'
+import appConfig from '~src/config/app'
+import logoIcon from '~src/assets/logo/icon.png'
+import logoName from '~src/assets/logo/name.png'
+import { clearStoreAll, getStoreValue, setStoreValue } from '~src/utils/storage'
+import { removeRefreshToken, removeToken, setRefreshToken, setToken } from '~src/api/util/auth'
 
 export const useAppStore = defineStore('main', {
     state: () =>({
@@ -13,8 +13,8 @@ export const useAppStore = defineStore('main', {
         logoIcon: getStoreValue('logoIcon') || logoIcon,
         logoName: getStoreValue('logoName') || logoName,
         //主题信息
-        theme: getStoreValue('theme') || appConfig.theme,    //用户可选择类型:auto,light, dark
-        themeVal: getStoreValue('themeVal') || '',           //实际主题:light, dark
+        theme: getStoreValue('theme') || appConfig.theme, //用户可选择类型:auto,light, dark
+        themeVal: getStoreValue('themeVal') || '', //实际主题:light, dark
         color: getStoreValue('color') || appConfig.color,
         //用户信息
         token: getStoreValue( 'token') || '',
@@ -35,8 +35,6 @@ export const useAppStore = defineStore('main', {
         //其他配置信息
         bubble: getStoreValue('bubble') || false,
         orderServiceTipModal: getStoreValue('orderServiceTipModal') ?? 1, //0不弹出,1弹出
-        shotWebRtc: getStoreValue('shotWebRtc') || 0, //WebRtc截图方式: 0关闭,1开启
-        fullScreen: getStoreValue('fullScreen') || 0, //全屏截图:0关闭,1开启
         isCollapse: getStoreValue('isCollapse') || false, //菜单折叠
         isScreenShort: false,
         barMenuName: '',
@@ -70,9 +68,7 @@ export const useAppStore = defineStore('main', {
         getBubble: state => state.bubble,
         getScreenShort: state => state.isScreenShort,
         getOrderServiceTipModal: state => state.orderServiceTipModal,
-        getShotWebRtc: state => state.shotWebRtc,
-        getFullScreen: state => state.fullScreen,
-        getCollapse: state => state.isCollapse
+        getCollapse: state => state.isCollapse,
     },
     actions: {
         //系统信息
@@ -91,50 +87,50 @@ export const useAppStore = defineStore('main', {
         //主题信息
         setTheme(value) {
             this.theme = value
-            setStoreValue('theme',value)
+            setStoreValue('theme', value)
         },
         setThemeVal(value) {
             this.themeVal = value
-            setStoreValue('themeVal',value)
+            setStoreValue('themeVal', value)
         },
         setColor(value) {
             this.color = value
-            setStoreValue('color',value)
+            setStoreValue('color', value)
         },
         //用户信息
-        setTokenVal(value){
+        setTokenVal(value) {
             this.token = value
             setToken(value)
-            setStoreValue('token',value)
+            setStoreValue('token', value)
         },
-        setRefreshTokenVal(value){
+        setRefreshTokenVal(value) {
             this.refreshToken = value
             setRefreshToken(value)
-            setStoreValue('refreshToken',value)
+            setStoreValue('refreshToken', value)
         },
-        setTenantId(value){
+        setTenantId(value) {
             this.tenantId = value
-            setStoreValue('tenantId',value)
+            setStoreValue('tenantId', value)
         },
-        setUserInfo(value){
+        setUserInfo(value) {
             this.userInfo = value
-            setStoreValue('userInfo',value)
+            setStoreValue('userInfo', value)
         },
-        setRoleId(value){
+        setRoleId(value) {
             this.role_id = value
-            setStoreValue('role_id',value)
+            setStoreValue('role_id', value)
         },
         //菜单信息
-        setMenus(value){
+        setMenus(value) {
             this.menus = value
-            setStoreValue('menus',value)
+            setStoreValue('menus', value)
         },
-        setButtons(value){
+        setButtons(value) {
             this.buttons = value
-            setStoreValue('buttons',value)
+            setStoreValue('buttons', value)
         },
         getButtonsVal(value) {
-            return this.buttons[value] || false;
+            return this.buttons[value] || false
         },
         setHomeUrl(value) {
             this.homeUrl = value
@@ -143,47 +139,39 @@ export const useAppStore = defineStore('main', {
         //项目合同段数据
         setProjectContract(value) {
             this.projectContract = value
-            setStoreValue('projectContract',value)
+            setStoreValue('projectContract', value)
         },
         setProjectInfo(value) {
             this.projectInfo = value
-            setStoreValue('projectInfo',value)
+            setStoreValue('projectInfo', value)
         },
         setContractInfo(value) {
             this.contractInfo = value
-            setStoreValue('contractInfo',value)
+            setStoreValue('contractInfo', value)
         },
         setProjectId(value) {
             this.projectId = value
-            setStoreValue('projectId',value)
+            setStoreValue('projectId', value)
         },
         setContractId(value) {
             this.contractId = value
-            setStoreValue('contractId',value)
+            setStoreValue('contractId', value)
         },
         //其他配置信息
         setBubble(value) {
             this.bubble = value
-            setStoreValue('bubble',value)
+            setStoreValue('bubble', value)
         },
         setOrderServiceTipModal(value) {
             this.orderServiceTipModal = value
-            setStoreValue('orderServiceTipModal',value)
+            setStoreValue('orderServiceTipModal', value)
         },
         setScreenShort(value) {
             this.isScreenShort = value
         },
-        setShotWebRtc(value) {
-            this.shotWebRtc = value
-            setStoreValue('shotWebRtc',value)
-        },
-        setFullScreen(value) {
-            this.fullScreen = value
-            setStoreValue('fullScreen',value)
-        },
         setCollapse(value) { //菜单折叠
             this.isCollapse = value
-            setStoreValue('isCollapse',value)
+            setStoreValue('isCollapse', value)
         },
         //清除缓存和token
         clearStoreData() {
@@ -204,17 +192,15 @@ export const useAppStore = defineStore('main', {
             this.projectId = null
             this.contractId = null
             this.orderServiceTipModal = null
-            this.shotWebRtc = null
-            this.fullScreen = null
             this.isCollapse = false
             //清除缓存
             clearStoreAll()
             removeToken()
             removeRefreshToken()
         },
-    }
+    },
 })
 
 export default function useUserStoreWidthOut() {
-    return useAppStore(pinia);
+    return useAppStore(pinia)
 }

+ 36 - 40
src/store/modules/user.js

@@ -1,21 +1,21 @@
-import pinia from "~src/store/init"
-import appConfig from '~src/config/app';
-import {useAppStore} from "~src/store";
-import {getRoutes} from '~api/menu';
-import themeData from '~src/config/theme';
-import tokenData from "~src/router/modules/token";
-import {setStoreValue} from "~src/utils/storage";
-import {userLogin,refreshToken,logout} from '~api/user';
-import {userConfigInfo, userConfigSave} from "~api/other";
-import {ArrToOneObj, getArrValue, getObjValue, arrIndex} from "js-fast-way"
-import {useOsTheme} from 'hc-vue3-ui'
+import pinia from '~src/store/init'
+import appConfig from '~src/config/app'
+import { useAppStore } from '~src/store'
+import { getRoutes } from '~api/menu'
+import themeData from '~src/config/theme'
+import tokenData from '~src/router/modules/token'
+import { setStoreValue } from '~src/utils/storage'
+import { logout, refreshToken, userLogin } from '~api/user'
+import { userConfigInfo, userConfigSave } from '~api/other'
+import { ArrToOneObj, arrIndex, getArrValue, getObjValue } from 'js-fast-way'
+import { useOsTheme } from 'hc-vue3-ui'
 
 //初始变量
 const store = useAppStore(pinia)
 
 //登录
 export const useAppLogin = async (form) => {
-    const { error, status, res } = await userLogin(form);
+    const { error, status, res } = await userLogin(form)
     if (!error && status === 200) {
         store.setTokenVal(res['access_token'])
         store.setRefreshTokenVal(res['refresh_token'])
@@ -24,24 +24,24 @@ export const useAppLogin = async (form) => {
         store.setUserInfo(res)
         const routerRes = await setRouterData()
         if (!routerRes) {
-            return Promise.reject({msg:'路由异常'});
+            return Promise.reject({ msg:'路由异常' })
         }
         await initUserConfigInfo()
-        return Promise.resolve(res);
+        return Promise.resolve(res)
     } else {
-        return Promise.reject(res);
+        return Promise.reject(res)
     }
 }
 
 //用户信息初始化
 export const initUserConfigInfo = async () => {
-    const {error, data} = await userConfigInfo();
+    const { error, data } = await userConfigInfo()
     if (error) {
-        return Promise.reject(false);
+        return Promise.reject(false)
     }
     const res = getObjValue(data)
     if (res?.theme) {
-        const {theme,color,shotWebRtc,fullScreen,opinionView} = res
+        const { theme, color, opinionView } = res
         //设置主题
         store.setTheme(theme)
         //设置模式
@@ -51,35 +51,31 @@ export const initUserConfigInfo = async () => {
             store.setThemeVal(theme)
         }
         //获取主色调和首页主题数据
-        let themeColor = themeData.color;
-        let colorIndex = arrIndex(themeColor,'name',color)
+        let themeColor = themeData.color
+        let colorIndex = arrIndex(themeColor, 'name', color)
         //设置主色调
         if (colorIndex !== -1) {
             store.setColor(themeColor[colorIndex])
         }
         //设置工单服务的弹出框
         store.setOrderServiceTipModal(opinionView ?? 1)
-        store.setShotWebRtc(shotWebRtc)
-        store.setShotWebRtc(fullScreen)
-        return Promise.resolve(true);
+        return Promise.resolve(true)
     } else {
         await userConfigSave({
             theme: appConfig?.theme || '',
             color: appConfig?.color?.name || '',
-            shotWebRtc: '0',
-            fullScreen: '1',
-            opinionView: 1
+            opinionView: 1,
         })
-        return Promise.resolve(true);
+        return Promise.resolve(true)
     }
 }
 
 //设置路由信息
 export const setRouterData = async () => {
     //请求接口,获取路由数据
-    const {error, data} = await getRoutes()
+    const { error, data } = await getRoutes()
     if (error) {
-        return Promise.reject(false);
+        return Promise.reject(false)
     }
     const resData = getArrValue(data)
     //数据转换
@@ -90,33 +86,33 @@ export const setRouterData = async () => {
     store.setMenus(resData)
     setStoreValue('route', routesObj)
     setStoreValue('routes', routes)
-    return Promise.resolve(true);
+    return Promise.resolve(true)
 }
 
 //刷新token
 export const RefreshToken = async () => {
     try {
-        window.console.log('刷新 token');
-        const { dept_id, role_id } = store.getUserInfo;
-        const refresh = store.getRefreshToken;
-        const tenantId = store.getTenantId;
-        const { error, status, res } = await refreshToken(refresh, tenantId, dept_id, role_id);
+        window.console.log('刷新 token')
+        const { dept_id, role_id } = store.getUserInfo
+        const refresh = store.getRefreshToken
+        const tenantId = store.getTenantId
+        const { error, status, res } = await refreshToken(refresh, tenantId, dept_id, role_id)
         if (!error && status === 200) {
             store.setTokenVal(res['access_token'])
             store.setRefreshTokenVal(res['refresh_token'])
             store.setTenantId(res['tenant_id'])
             store.setUserInfo(res)
-            return Promise.resolve(res);
+            return Promise.resolve(res)
         } else {
-            return Promise.reject(res);
+            return Promise.reject(res)
         }
     } catch (e) {
-        window.console.warn('token 刷新失败: ', e);
-        return Promise.reject(e);
+        window.console.warn('token 刷新失败: ', e)
+        return Promise.reject(e)
     }
 }
 
 //登出
 export const LogOut = async () => {
-    return await logout();
+    return await logout()
 }

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

@@ -7,3 +7,29 @@
         margin-left: 20px;
     }
 }
+
+//新改变版按钮
+.el-button[hc-btn] {
+    border-radius: 0;
+    padding: 0 10px;
+    font-weight: initial;
+    height: 28px;
+    font-size: 14px;
+    border: 0;
+    box-shadow: none;
+    outline: 0;
+}
+
+.el-button + .el-button {
+    margin-left: 10px;
+}
+
+.el-button + .el-dropdown {
+    margin-left: 10px;
+}
+
+.el-dropdown .el-button .hc-icon-i {
+    margin-left: 4px;
+    margin-right: 0;
+}
+

+ 15 - 0
src/styles/app/main.scss

@@ -2,3 +2,18 @@ html, body, #app {
     height: 100%;
     background-color: #F1F5F8;
 }
+
+.bg-blue {
+    background-color: #004ca7 !important;
+    color: white !important;
+}
+
+.bg-light-blue {
+    background-color: #409eff !important;
+    color: white !important;
+}
+.bg-black1 {
+    background-color: #2c3643 !important;
+    color: white !important;
+}
+

+ 26 - 26
src/views/config/index.vue

@@ -1,5 +1,5 @@
 <template>
-    <HcCard class="hc-config-index" actionSize="lg" scrollbar>
+    <HcCard class="hc-config-index" action-size="lg" scrollbar>
         <div class="text-lg font-medium mb-4">主题模式</div>
         <div class="hc-theme-box mb-8">
             <el-radio-group v-model="UserTheme" @change="ThemeTabsUpdate">
@@ -9,7 +9,7 @@
                             <img :src="ImgTheme" alt="">
                         </div>
                         <div class="action" @click="ThemeConfigClick(item)">
-                            <el-radio :label="item?.key" size="large" class="size-xl">{{item?.name}}</el-radio>
+                            <el-radio :label="item?.key" size="large" class="size-xl">{{ item?.name }}</el-radio>
                         </div>
                     </div>
                 </template>
@@ -18,31 +18,31 @@
 
         <div class="text-lg font-medium mb-4">主题色</div>
         <div class="hc-theme-box color-box mb-4">
-            <template v-for="(item,index) in ColorConfigData">
-                <div class="item" :class="UserColorNmae === item.name ? 'active' : ''" v-if="index < 5">
+            <template v-for="(item, index) in ColorConfigData">
+                <div v-if="index < 5" class="item" :class="UserColorNmae === item.name ? 'active' : ''">
                     <div class="demo" :class="`bg-${item.name}`">
                         <img :src="ImgColor" alt="">
                     </div>
                     <div class="action" @click="ColorConfigClick(item)">
-                        <el-radio v-model="UserColorNmae" :label="item?.name" size="large" class="size-xl">{{item?.label}}</el-radio>
+                        <el-radio v-model="UserColorNmae" :label="item?.name" size="large" class="size-xl">{{ item?.label }}</el-radio>
                     </div>
                 </div>
             </template>
         </div>
         <div class="hc-theme-box color-box mb-8">
-            <template v-for="(item,index) in ColorConfigData">
-                <div class="item" :class="UserColorNmae === item.name ? 'active' : ''" v-if="index >= 5">
+            <template v-for="(item, index) in ColorConfigData">
+                <div v-if="index >= 5" class="item" :class="UserColorNmae === item.name ? 'active' : ''">
                     <div class="demo" :class="`bg-${item.name}`">
                         <img :src="ImgColor" alt="">
                     </div>
                     <div class="action" @click="ColorConfigClick(item)">
-                        <el-radio v-model="UserColorNmae" :label="item?.name" size="large" class="size-xl">{{item?.label}}</el-radio>
+                        <el-radio v-model="UserColorNmae" :label="item?.name" size="large" class="size-xl">{{ item?.label }}</el-radio>
                     </div>
                 </div>
             </template>
         </div>
 
-        <div class="text-lg font-medium mb-4">截图设置</div>
+        <!-- div class="text-lg font-medium mb-4">截图设置</div>
         <div class="hc-screenshot-box mb-4">
             <div class="item">
                 <div class="label">WebRtc:</div>
@@ -66,12 +66,12 @@
                     <div>单击截全屏的启用状态</div>
                 </el-popover>
             </div>
-        </div>
+        </div -->
         <template #action>
             <el-popover placement="top" trigger="hover" :width="180">
                 <template #reference>
                     <el-button type="primary" hc-btn :loading="saveLoading" @click="SaveConfigClick">
-                        <HcIcon name="save"/>
+                        <HcIcon name="save" />
                         <span>保存配置</span>
                     </el-button>
                 </template>
@@ -80,7 +80,7 @@
             <el-popover placement="top-start" trigger="hover" :width="180">
                 <template #reference>
                     <el-button hc-btn @click="CancelClick">
-                        <HcIcon name="arrow-go-back"/>
+                        <HcIcon name="arrow-go-back" />
                         <span>取消</span>
                     </el-button>
                 </template>
@@ -91,15 +91,15 @@
 </template>
 
 <script setup>
-import {ref,nextTick} from "vue";
-import {useRouter, useRoute} from 'vue-router'
-import {useAppStore} from "~src/store";
-import themeData from '~src/config/theme';
-import {userConfigSave} from "~api/other";
-import ImgTheme from "~src/assets/images/theme.png";
-import ImgColor from "~src/assets/images/color.png";
-import {useOsTheme} from 'hc-vue3-ui'
-import {setElementMainColor} from "js-fast-way"
+import { nextTick, ref } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import { useAppStore } from '~src/store'
+import themeData from '~src/config/theme'
+import { userConfigSave } from '~api/other'
+import ImgTheme from '~src/assets/images/theme.png'
+import ImgColor from '~src/assets/images/color.png'
+import { useOsTheme } from 'hc-vue3-ui'
+import { setElementMainColor } from 'js-fast-way'
 
 //初始变量
 const router = useRouter()
@@ -129,9 +129,9 @@ const ColorConfigClick = (item) => {
 
 //更改主题设置
 const ThemeDatas = ref([
-    {key: 'auto', name: '跟随系统'},
-    {key: 'light', name: '浅色模式'},
-    {key: 'dark', name: '深色模式'}
+    { key: 'auto', name: '跟随系统' },
+    { key: 'light', name: '浅色模式' },
+    { key: 'dark', name: '深色模式' },
 ])
 const ThemeConfigClick = (item) => {
     ThemeTabsUpdate(item?.key)
@@ -145,7 +145,7 @@ const ThemeTabsUpdate = (val) => {
         useAppState.setThemeVal(val)
     }
     let colorName = UserColorNmae.value || 'green'
-    document.documentElement.setAttribute('class',`${val} color-${colorName}`)
+    document.documentElement.setAttribute('class', `${val} color-${colorName}`)
 }
 
 //更改截图方式
@@ -171,7 +171,7 @@ const SaveConfigClick = async () => {
         theme: UserTheme.value,
         color: UserColorNmae.value,
         shotWebRtc: webRtcVal.value,
-        fullScreen: fullScreenVal.value
+        fullScreen: fullScreenVal.value,
     })
     //判断状态
     saveLoading.value = false

+ 3 - 3
src/views/using/stats.vue

@@ -1,6 +1,6 @@
 <template>
     <div class="hc-page-box hc-using-stats-page">
-        <HcCard scrollbar>
+        <hc-new-card scrollbar>
             <template #header>
                 <div class="hc-project-box">
                     <HcIcon name="stack" class="project-icon" />
@@ -71,7 +71,7 @@
                 <el-table-column prop="auto" label="已组卷" align="center" width="100" />
                 <el-table-column prop="deleted" label="已销毁" align="center" width="100" />
             </el-table>
-        </HcCard>
+        </hc-new-card>
     </div>
 </template>
 
@@ -213,7 +213,7 @@ const getfixedChartData = async () => {
             }
 
         })
-       
+
         fixedChartData.value = obj
     } else {
         fixedChartData.value = {}