Bladeren bron

feat(frontend): 左侧导航改为案卷目录式分组并重设业务图标

- 导航按业务域分组,子项常驻展开,折叠态展示实际页面图标
- 底部折叠条恢复原交互
- 全部菜单替换为语义化 MDI 图标并补充 UnoCSS 白名单
cc 2 dagen geleden
bovenliggende
commit
e3a4aee319

+ 9 - 1
ai-electron/frontend/src/core/components/SimpleMenu/src/SimpleMenu.vue

@@ -1,4 +1,5 @@
 <template>
+  <!-- 子菜单常驻展开:forceExpanded 由 LayoutSider 传入 -->
   <Menu
     v-bind="getBindValues"
     :activeName="activeName"
@@ -9,7 +10,13 @@
   >
     <slot name="menuBefore"></slot>
     <template v-for="item in items" :key="item.path">
-      <SimpleSubMenu :item="item" :parent="true" :collapsedShowTitle="collapsedShowTitle" :collapse="collapse" />
+      <SimpleSubMenu
+        :item="item"
+        :parent="true"
+        :collapsedShowTitle="collapsedShowTitle"
+        :collapse="collapse"
+        :forceExpanded="forceExpanded"
+      />
     </template>
     <slot name="menuAfter"></slot>
   </Menu>
@@ -45,6 +52,7 @@
       type: Function as PropType<(key: string) => Promise<boolean>>,
     },
     isSplitMenu: propTypes.bool,
+    forceExpanded: propTypes.bool,
   };
 
   export default defineComponent({

+ 4 - 0
ai-electron/frontend/src/core/components/SimpleMenu/src/SimpleSubMenu.vue

@@ -1,4 +1,5 @@
 <template>
+  <!-- forceExpanded 由 SimpleMenu 逐级透传到 SubMenu -->
   <MenuItem
     :name="item.path"
     v-if="!menuHasChildren(item) && getShowMenu"
@@ -24,6 +25,8 @@
     v-if="menuHasChildren(item) && getShowMenu"
     :class="[getLevelClass, theme]"
     :collapsedShowTitle="collapsedShowTitle"
+    :collapse="collapse"
+    :forceExpanded="forceExpanded"
     :style="`color: ${getColor}`"
     :title="getI18nName"
   >
@@ -66,6 +69,7 @@
     parent: propTypes.bool,
     collapsedShowTitle: propTypes.bool,
     collapse: propTypes.bool,
+    forceExpanded: propTypes.bool,
     theme: propTypes.oneOf(['dark', 'light']),
   };
 

+ 16 - 8
ai-electron/frontend/src/core/components/SimpleMenu/src/components/SubMenuItem.vue

@@ -3,10 +3,15 @@
     <template v-if="!getCollapse">
       <div :class="`${prefixCls}-submenu-title`" @click.stop="handleClick" :style="getItemStyle">
         <slot name="title"></slot>
-        <Icon icon="i-eva:arrow-ios-downward-outline" :size="14" :class="`${prefixCls}-submenu-title-icon`" />
+        <Icon
+          v-if="!forceExpanded"
+          icon="i-eva:arrow-ios-downward-outline"
+          :size="14"
+          :class="`${prefixCls}-submenu-title-icon`"
+        />
       </div>
       <CollapseTransition>
-        <ul :class="prefixCls" v-show="opened">
+        <ul :class="prefixCls" v-show="opened || forceExpanded">
           <slot></slot>
         </ul>
       </CollapseTransition>
@@ -33,7 +38,7 @@
           <slot name="title"></slot>
         </div>
         <Icon
-          v-if="getParentSubMenu"
+          v-if="getParentSubMenu && !forceExpanded"
           icon="i-eva:arrow-ios-downward-outline"
           :size="14"
           :class="`${prefixCls}-submenu-title-icon`"
@@ -84,6 +89,7 @@
     },
     disabled: propTypes.bool,
     collapsedShowTitle: propTypes.bool,
+    forceExpanded: propTypes.bool,
   };
 
   export default defineComponent({
@@ -99,7 +105,7 @@
 
       const state = reactive({
         active: false,
-        opened: false,
+        opened: !!props.forceExpanded,
       });
 
       const data = reactive({
@@ -133,7 +139,7 @@
           `${prefixCls}-submenu`,
           {
             [`${prefixCls}-item-active`]: state.active,
-            [`${prefixCls}-opened`]: state.opened,
+            [`${prefixCls}-opened`]: state.opened || props.forceExpanded,
             [`${prefixCls}-submenu-disabled`]: props.disabled,
             [`${prefixCls}-submenu-has-parent-submenu`]: unref(getParentSubMenu),
             [`${prefixCls}-child-item-active`]: state.active,
@@ -152,6 +158,7 @@
       });
 
       const getIsOpend = computed(() => {
+        if (props.forceExpanded) return true;
         const name = props.name;
         if (unref(getCollapse)) {
           return parentGetOpenNames().includes(name as string);
@@ -183,7 +190,7 @@
 
       function handleClick() {
         const { disabled } = props;
-        if (disabled || unref(getCollapse)) return;
+        if (disabled || props.forceExpanded || unref(getCollapse)) return;
         const opened = state.opened;
 
         if (unref(getAccordion)) {
@@ -218,7 +225,7 @@
         }
         data.isChild = parentGetOpenNames().includes(props.name as string);
         clearTimeout(data.timeout!);
-        
+
         if (!parentGetOpenNames().includes(props.name as string)) {
           data.timeout = setTimeout(() => {
             parentAddSubmenu(props.name as string);
@@ -267,7 +274,7 @@
         });
 
         rootMenuEmitter.on('on-update-opened', (data: boolean | (string | number)[] | Recordable) => {
-          if (unref(getCollapse)) return;
+          if (props.forceExpanded || unref(getCollapse)) return;
           if (isBoolean(data)) {
             state.opened = data;
             return;
@@ -295,6 +302,7 @@
       });
 
       function handleOpenChange(open: boolean) {
+        if (props.forceExpanded) return;
         if (state.opened !== open) {
           state.opened = open;
         }

+ 13 - 2
ai-electron/frontend/src/core/layouts/default/menu/index.vue

@@ -20,7 +20,6 @@
   import { useAppInject } from '@/core/hooks/web/useAppInject';
   import { useDesign } from '@/core/hooks/web/useDesign';
 
-
   export default defineComponent({
     name: 'LayoutMenu',
     props: {
@@ -37,8 +36,19 @@
         type: [String] as PropType<Nullable<MenuModeEnum>>,
         default: '',
       },
+
+      collapse: {
+        type: Boolean as PropType<boolean | undefined>,
+        default: undefined,
+      },
+
+      forceExpanded: {
+        type: Boolean as PropType<boolean | undefined>,
+        default: undefined,
+      },
     },
     setup(props) {
+      // 侧栏目录支持 forceExpanded:所有分组常驻展开,无折叠交互
       const go = useGo();
 
       const {
@@ -101,7 +111,8 @@
           items: menus,
           theme: unref(getComputedMenuTheme),
           accordion: unref(getAccordion),
-          collapse: unref(getCollapsed),
+          collapse: props.collapse ?? unref(getCollapsed),
+          forceExpanded: props.forceExpanded,
           collapsedShowTitle: unref(getCollapsedShowTitle),
           onMenuClick: handleMenuClick,
         };

+ 301 - 66
ai-electron/frontend/src/core/layouts/default/sider/LayoutSider.vue

@@ -2,27 +2,33 @@
   <div v-if="getMenuFixed && !getIsMobile" :style="getHiddenDomStyle" v-show="showClassSideBarRef"></div>
   <Sider
     v-show="showClassSideBarRef"
-    ref="sideRef"
-    breakpoint="lg"
-    collapsible
     :class="getSiderClass"
     :width="getMenuWidth"
     :collapsed="getCollapsed"
     :collapsedWidth="getCollapsedWidth"
-    :theme="getMenuTheme"
-    @breakpoint="onBreakpointChange"
+    theme="light"
+    collapsible
+    breakpoint="lg"
     :trigger="getTrigger"
+    @breakpoint="onBreakpointChange"
     v-bind="getTriggerAttr"
   >
     <template #trigger v-if="getShowTrigger">
       <LayoutTrigger />
     </template>
-    <LayoutMenu :theme="getMenuTheme" :menuMode="getMode" :splitType="getSplitType" />
-    <DragBar ref="dragBarRef" />
+    <div class="sidebar-menu-wrap">
+      <LayoutMenu
+        theme="light"
+        :menuMode="getMode"
+        :splitType="getSplitType"
+        :collapse="false"
+        :force-expanded="true"
+      />
+    </div>
   </Sider>
 </template>
 <script lang="ts">
-  import { computed, defineComponent, ref, unref, CSSProperties, h } from 'vue';
+  import { computed, defineComponent, h, unref, CSSProperties } from 'vue';
 
   import { Layout } from 'ant-design-vue';
   import LayoutMenu from '../menu/index.vue';
@@ -31,39 +37,22 @@
   import { MenuModeEnum, MenuSplitTyeEnum } from '@/core/enums/menuEnum';
 
   import { useMenuSetting } from '@/core/hooks/setting/useMenuSetting';
-  import { useTrigger, useDragLine, useSiderEvent } from './useLayoutSider';
   import { useAppInject } from '@/core/hooks/web/useAppInject';
   import { useDesign } from '@/core/hooks/web/useDesign';
+  import { useSiderEvent, useTrigger } from './useLayoutSider';
 
-  import DragBar from './DragBar.vue';
   export default defineComponent({
     name: 'LayoutSideBar',
-    components: { Sider: Layout.Sider, LayoutMenu, DragBar, LayoutTrigger },
+    components: { Sider: Layout.Sider, LayoutMenu, LayoutTrigger },
     setup() {
-      const dragBarRef = ref<ElRef>(null);
-      const sideRef = ref<ElRef>(null);
-
-      const {
-        getCollapsed,
-        getMenuWidth,
-        getSplit,
-        getMenuTheme,
-        getRealWidth,
-        getMenuHidden,
-        getMenuFixed,
-        getIsMixMode,
-        toggleCollapsed,
-      } = useMenuSetting();
+      const { getMenuWidth, getSplit, getMenuHidden, getMenuFixed, getIsMixMode, getCollapsed, getRealWidth } =
+        useMenuSetting();
 
       const { prefixCls } = useDesign('layout-sideBar');
-
       const { getIsMobile } = useAppInject();
-
       const { getTriggerAttr, getShowTrigger } = useTrigger(getIsMobile);
-
-      useDragLine(sideRef, dragBarRef);
-
       const { getCollapsedWidth, onBreakpointChange } = useSiderEvent();
+      const getTrigger = h(LayoutTrigger);
 
       const getMode = computed(() => {
         return unref(getSplit) ? MenuModeEnum.INLINE : null;
@@ -99,30 +88,22 @@
         };
       });
 
-      // 在此处使用计算量可能会导致sider异常
-      // andv 更新后,如果trigger插槽可用,则此处代码可废弃
-      const getTrigger = h(LayoutTrigger);
-
       return {
         prefixCls,
-        sideRef,
-        dragBarRef,
         getIsMobile,
         getHiddenDomStyle,
         getSiderClass,
-        getTrigger,
-        getTriggerAttr,
-        getCollapsedWidth,
         getMenuFixed,
         showClassSideBarRef,
         getMenuWidth,
         getCollapsed,
-        getMenuTheme,
+        getCollapsedWidth,
+        getTrigger,
+        getShowTrigger,
+        getTriggerAttr,
         onBreakpointChange,
         getMode,
         getSplitType,
-        getShowTrigger,
-        toggleCollapsed,
       };
     },
   });
@@ -132,6 +113,9 @@
 
   .ant-layout .ant-layout-sider.@{prefix-cls} {
     z-index: @layout-sider-fixed-z-index;
+    background: #f8fafc;
+    border-right: 1px solid #e7ebf3;
+    box-shadow: none;
 
     &--fixed {
       position: fixed;
@@ -145,42 +129,293 @@
       height: calc(100% - @header-height);
     }
 
-    // &.ant-layout-sider-light {
-    //   // border-right: 1px solid @border-color-base;
-    //   box-shadow: 1px 0 0 0 @header-light-bottom-border-color;
-    // }
+    .ant-layout-sider-children {
+      display: flex;
+      flex-direction: column;
+      height: 100%;
+      min-height: 0;
+      background: #f8fafc;
+    }
 
-    &.ant-layout-sider-dark {
-      background-color: @sider-dark-bg-color;
+    .sidebar-menu-wrap {
+      display: flex;
+      flex: 1 1 auto;
+      flex-direction: column;
+      min-height: 0;
+    }
 
-      .ant-layout-sider-trigger {
-        color: darken(@white, 25%);
-        background-color: @trigger-dark-bg-color;
+    /* 隐藏滚动条,但保留容器滚轮滚动 */
+    .scrollbar__bar {
+      display: none !important;
+    }
 
-        &:hover {
-          color: @white;
-          background-color: @trigger-dark-hover-bg-color;
-        }
-      }
+    .scrollbar__wrap,
+    .scrollbar__view {
+      scrollbar-width: none;
+      -ms-overflow-style: none;
     }
 
-    &:not(.ant-layout-sider-dark) {
-      // box-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.05);
+    .scrollbar__wrap::-webkit-scrollbar,
+    .scrollbar__view::-webkit-scrollbar {
+      display: none;
+      width: 0;
+      height: 0;
+    }
 
-      .ant-layout-sider-trigger {
-        color: @text-color-base;
-        border-top: 1px solid @header-light-bottom-border-color;
-      }
+    /* ---------------- 菜单容器 ---------------- */
+    .jeesite-menu.jeesite-menu-light.jeesite-menu-vertical {
+      padding: 14px 0 10px;
+      background: transparent;
+    }
+
+    .jeesite-simple-menu > li {
+      position: relative;
+    }
+
+    /* 分组标题:跟随当前菜单结构,菜单标题调整时需同步 */
+    .jeesite-simple-menu > li[title='案件数据'],
+    .jeesite-simple-menu > li[title='通信分析'],
+    .jeesite-simple-menu > li[title='交易分析'],
+    .jeesite-simple-menu > li[title='活动轨迹分析'],
+    .jeesite-simple-menu > li[title='行为时空分析'] {
+      margin-top: 34px;
+    }
+
+    /* 叶子项默认 overflow:hidden 会裁掉分组标题 */
+    .jeesite-simple-menu > li[title='案件数据'],
+    .jeesite-simple-menu > li[title='行为时空分析'] {
+      overflow: visible;
+    }
+
+    /* 有子项的分组只保留分组标题,父级行不再显示 */
+    .jeesite-menu-submenu[title='通信分析'] > .jeesite-menu-submenu-title,
+    .jeesite-menu-submenu[title='交易分析'] > .jeesite-menu-submenu-title,
+    .jeesite-menu-submenu[title='活动轨迹分析'] > .jeesite-menu-submenu-title {
+      display: none;
+    }
+
+    .jeesite-simple-menu > li[title='案件数据']::after,
+    .jeesite-simple-menu > li[title='通信分析']::after,
+    .jeesite-simple-menu > li[title='交易分析']::after,
+    .jeesite-simple-menu > li[title='活动轨迹分析']::after,
+    .jeesite-simple-menu > li[title='行为时空分析']::after {
+      position: absolute;
+      top: -26px;
+      left: 14px;
+      right: 14px;
+      height: 16px;
+      border-bottom: 1px solid #e7ebf3;
+      color: #98a2b3;
+      font-size: 9px;
+      font-weight: 500;
+      letter-spacing: 0.1em;
+      line-height: 15px;
+      font-variant-numeric: tabular-nums;
+      pointer-events: none;
+    }
+
+    .jeesite-simple-menu > li[title='案件数据']::after {
+      content: '01  /  总览';
+    }
+
+    .jeesite-simple-menu > li[title='通信分析']::after {
+      content: '02  /  通信分析';
+    }
+
+    .jeesite-simple-menu > li[title='交易分析']::after {
+      content: '03  /  交易分析';
+    }
+
+    .jeesite-simple-menu > li[title='活动轨迹分析']::after {
+      content: '04  /  活动轨迹分析';
     }
 
-    .ant-layout-sider-zero-width-trigger {
-      top: 40%;
-      z-index: 10;
+    .jeesite-simple-menu > li[title='行为时空分析']::after {
+      content: '05  /  关联与工具';
     }
 
-    & .ant-layout-sider-trigger {
+    /* ---------------- 菜单项 ---------------- */
+    .jeesite-menu-item,
+    .jeesite-menu-submenu-title {
+      position: relative;
+      display: flex;
+      align-items: center;
+      min-height: 34px;
+      margin: 1px 0;
+      padding: 0 14px !important;
+      border-radius: 0;
+      background: transparent;
+      color: #1f2937;
+      font-size: 12px;
+      font-weight: 500;
+      line-height: 20px;
+      transition:
+        background-color 0.15s ease,
+        color 0.15s ease;
+    }
+
+    .jeesite-menu-item > .jeesite-icon,
+    .jeesite-menu-submenu-title > .jeesite-icon:not(.jeesite-menu-submenu-title-icon) {
+      display: inline-flex;
+      align-items: center;
+      justify-content: center;
+      width: 18px;
+      height: 18px;
+      flex: 0 0 18px;
+      margin-right: 2px;
+      color: #94a3b8;
+      font-size: 18px !important;
+      transition: color 0.15s ease;
+    }
+
+    .jeesite-menu-item:hover,
+    .jeesite-menu-submenu-title:hover {
+      background: #f1f5f9;
+      color: #111827;
+    }
+
+    .jeesite-menu-item:hover > .jeesite-icon,
+    .jeesite-menu-submenu-title:hover > .jeesite-icon:not(.jeesite-menu-submenu-title-icon) {
+      color: #475569;
+    }
+
+    .jeesite-menu-submenu-title > .jeesite-menu-submenu-title-icon {
+      right: 12px;
+      width: 14px;
+      color: #98a2b3;
+      transform: translateY(-50%) rotate(0deg);
+    }
+
+    .jeesite-menu-opened > .jeesite-menu-submenu-title > .jeesite-menu-submenu-title-icon {
+      transform: translateY(-50%) rotate(180deg);
+    }
+
+    .jeesite-menu-submenu-title.jeesite-menu-submenu-active {
+      background: transparent;
+      color: #2448c9;
+    }
+
+    .jeesite-menu-submenu-title.jeesite-menu-submenu-active > .jeesite-icon:not(.jeesite-menu-submenu-title-icon) {
+      color: #2a50ec;
+    }
+
+    .jeesite-menu-submenu-title.jeesite-menu-submenu-active > .jeesite-menu-submenu-title-icon {
+      color: #2a50ec;
+    }
+
+    /* 当前页:细线标记,不使用圆角胶囊 */
+    .jeesite-menu-item.jeesite-menu-item-active,
+    .jeesite-menu-item.jeesite-menu-item-selected {
+      background: #dbe5ff !important;
+      border-radius: 0 !important;
+      box-shadow: none;
+      color: #2448c9 !important;
+      font-weight: 600;
+    }
+
+    .jeesite-menu-item.jeesite-menu-item-active::before,
+    .jeesite-menu-item.jeesite-menu-item-selected::before {
+      position: absolute;
+      top: 4px;
+      bottom: 4px;
+      left: 0;
+      width: 2px;
+      border-radius: 0 2px 2px 0;
+      background: #2a50ec;
+      content: '';
+    }
+
+    .jeesite-menu-item.jeesite-menu-item-active > .jeesite-icon,
+    .jeesite-menu-item.jeesite-menu-item-selected > .jeesite-icon {
+      color: #2a50ec;
+    }
+
+    /* ---------------- 二级菜单 ---------------- */
+    .jeesite-menu-submenu > .jeesite-menu {
+      width: auto;
+      box-sizing: border-box;
+      margin: 0;
+      padding: 0;
+      border-left: 0;
+      background: transparent;
+    }
+
+    .jeesite-simple-menu__children {
+      min-height: 32px;
+      font-weight: 500;
+    }
+
+    /* ---------------- 底部折叠条 ---------------- */
+    .ant-layout-sider-trigger {
       height: 36px;
       line-height: 36px;
+      background: #f8fafc;
+      border-top: 1px solid #e7ebf3;
+      color: #98a2b3;
+      transition:
+        background-color 0.15s ease,
+        color 0.15s ease;
+
+      &:hover {
+        background: #f1f5f9;
+        color: #2a50ec;
+      }
+    }
+
+    /* ---------------- 折叠态:图标栏 ---------------- */
+    &.ant-layout-sider-collapsed {
+      .jeesite-menu.jeesite-menu-light.jeesite-menu-vertical {
+        padding: 6px 4px 8px;
+        box-sizing: border-box;
+      }
+
+      .jeesite-simple-menu > li {
+        margin-top: 0 !important;
+      }
+
+      .jeesite-simple-menu > li::after {
+        display: none !important;
+      }
+
+      .jeesite-menu-item,
+      .jeesite-menu-submenu-title {
+        min-height: 40px;
+        justify-content: center;
+        padding: 7px !important;
+      }
+
+      .jeesite-menu-item > .jeesite-icon,
+      .jeesite-menu-submenu-title > .jeesite-icon:not(.jeesite-menu-submenu-title-icon) {
+        width: 22px !important;
+        height: 22px !important;
+        flex: 0 0 22px !important;
+        margin-right: 0;
+        font-size: 22px !important;
+        line-height: 1;
+      }
+
+      .jeesite-simple-menu-sub-title,
+      .jeesite-menu-submenu-title-icon {
+        display: none;
+      }
+
+      .jeesite-menu-item.jeesite-menu-item-active,
+      .jeesite-menu-item.jeesite-menu-item-selected {
+        background: #dbe5ff !important;
+        border-radius: 8px !important;
+        box-shadow: inset 0 0 0 1px #bfcdfb;
+        color: #2448c9 !important;
+      }
+
+      .jeesite-menu-item.jeesite-menu-item-active::before,
+      .jeesite-menu-item.jeesite-menu-item-selected::before {
+        display: none;
+      }
+
+      /* 折叠后只保留实际页面项,不显示通信/交易/活动轨迹父级图标 */
+      .jeesite-simple-menu .jeesite-menu-submenu > .jeesite-menu-submenu-title {
+        display: none !important;
+      }
     }
   }
 </style>

+ 28 - 28
ai-electron/frontend/src/core/store/modules/menu.json

@@ -15,7 +15,7 @@
         "extendMap": {}
       },
       "affix": true,
-      "icon": "icon-folder",
+      "icon": "i-mdi:briefcase-outline",
       "hideMenu": false,
       "color": "",
       "title": "案件数据"
@@ -41,7 +41,7 @@
         "extendS4": "",
         "extendMap": {}
       },
-      "icon": "icon-user",
+      "icon": "i-mdi:account-details-outline",
       "hideMenu": false,
       "color": "",
       "title": "数据画像"
@@ -57,7 +57,7 @@
     "path": "/1002",
     "component": "LAYOUT",
     "meta": {
-      "icon": "icon-speech",
+      "icon": "i-mdi:message-text-outline",
       "hideMenu": false,
       "color": "",
       "title": "通信分析"
@@ -73,7 +73,7 @@
         "component": "/continuous/index",
         "children": [],
         "meta": {
-          "icon": "svg:contact-continuous-light",
+          "icon": "i-mdi:phone-sync-outline",
           "hideMenu": false,
           "color": "",
           "title": "持续联系"
@@ -100,7 +100,7 @@
             "extendS4": "",
             "extendMap": {}
           },
-          "icon": "svg:contact-night-light",
+          "icon": "i-mdi:weather-night",
           "hideMenu": false,
           "color": "",
           "title": "夜间联系"
@@ -127,7 +127,7 @@
             "extendS4": "",
             "extendMap": {}
           },
-          "icon": "svg:contact-sensitive-time-light",
+          "icon": "i-mdi:calendar-alert",
           "hideMenu": false,
           "color": "",
           "title": "敏感日期联系"
@@ -145,7 +145,7 @@
     "path": "/1006",
     "component": "LAYOUT",
     "meta": {
-      "icon": "icon-paypal",
+      "icon": "i-mdi:bank-transfer",
       "hideMenu": false,
       "color": "",
       "title": "交易分析"
@@ -161,7 +161,7 @@
         "component": "/transSensitive/index",
         "children": [],
         "meta": {
-          "icon": "svg:trade-sensitive-time-light",
+          "icon": "i-mdi:calendar-star",
           "hideMenu": false,
           "color": "",
           "title": "敏感日期交易"
@@ -188,7 +188,7 @@
             "extendS4": "",
             "extendMap": {}
           },
-          "icon": "svg:trade-monthly-rhythm-light",
+          "icon": "i-mdi:calendar-sync-outline",
           "hideMenu": false,
           "color": "",
           "title": "每月规律交易"
@@ -215,7 +215,7 @@
             "extendS4": "",
             "extendMap": {}
           },
-          "icon": "svg:trade-frequency-light",
+          "icon": "i-mdi:chart-timeline-variant",
           "hideMenu": false,
           "color": "",
           "title": "交易频率分析"
@@ -242,7 +242,7 @@
             "extendS4": "",
             "extendMap": {}
           },
-          "icon": "svg:trade-large-amount-light",
+          "icon": "i-mdi:cash-multiple",
           "hideMenu": false,
           "color": "",
           "title": "大额交易分析"
@@ -269,7 +269,7 @@
             "extendS4": "",
             "extendMap": {}
           },
-          "icon": "svg:trade-capital-flow-light",
+          "icon": "i-mdi:swap-horizontal",
           "hideMenu": false,
           "color": "",
           "title": "资金流向分析"
@@ -296,7 +296,7 @@
             "extendS4": "",
             "extendMap": {}
           },
-          "icon": "svg:trade-cash-light",
+          "icon": "i-mdi:cash",
           "hideMenu": false,
           "color": "",
           "title": "现金交易分析"
@@ -323,7 +323,7 @@
             "extendS4": "",
             "extendMap": {}
           },
-          "icon": "svg:trade-fast-transfer-light",
+          "icon": "i-mdi:flash-outline",
           "hideMenu": false,
           "color": "",
           "title": "资金快转分析"
@@ -350,7 +350,7 @@
             "extendS4": "",
             "extendMap": {}
           },
-          "icon": "svg:trade-card-holding-light",
+          "icon": "i-mdi:credit-card-outline",
           "hideMenu": false,
           "color": "",
           "title": "银行卡代持分析"
@@ -377,7 +377,7 @@
             "extendS4": "",
             "extendMap": {}
           },
-          "icon": "svg:trade-fixed-deposit-light",
+          "icon": "i-mdi:bank-outline",
           "hideMenu": false,
           "color": "",
           "title": "定期存取分析"
@@ -404,7 +404,7 @@
             "extendS4": "",
             "extendMap": {}
           },
-          "icon": "svg:trade-financial-wealth-light",
+          "icon": "i-mdi:finance",
           "hideMenu": false,
           "color": "",
           "title": "金融理财分析"
@@ -422,7 +422,7 @@
     "path": "/1017",
     "component": "LAYOUT",
     "meta": {
-      "icon": "icon-map",
+      "icon": "i-mdi:map-marker-path",
       "hideMenu": false,
       "color": "",
       "title": "活动轨迹分析"
@@ -438,7 +438,7 @@
         "component": "/peerAnalysis/index",
         "children": [],
         "meta": {
-          "icon": "svg:track-peer-light",
+          "icon": "i-mdi:account-group-outline",
           "hideMenu": false,
           "color": "",
           "title": "同行分析"
@@ -454,7 +454,7 @@
         "component": "/coResidence/index",
         "children": [],
         "meta": {
-          "icon": "svg:track-co-residence-light",
+          "icon": "i-mdi:bed-outline",
           "hideMenu": false,
           "color": "",
           "title": "同住分析"
@@ -470,7 +470,7 @@
         "component": "/faceToFace/index",
         "children": [],
         "meta": {
-          "icon": "svg:track-face-to-face-light",
+          "icon": "i-mdi:handshake-outline",
           "hideMenu": false,
           "color": "",
           "title": "碰面分析"
@@ -486,7 +486,7 @@
         "component": "/morningEveningCall/index",
         "children": [],
         "meta": {
-          "icon": "svg:track-morning-evening-call-light",
+          "icon": "i-mdi:phone-in-talk-outline",
           "hideMenu": false,
           "color": "",
           "title": "早晚通话地"
@@ -502,7 +502,7 @@
         "component": "/expressDelivery/index",
         "children": [],
         "meta": {
-          "icon": "svg:track-express-delivery-light",
+          "icon": "i-mdi:package-variant-closed",
           "hideMenu": false,
           "color": "",
           "title": "快递收发地"
@@ -518,7 +518,7 @@
         "component": "/baseStation/index",
         "children": [],
         "meta": {
-          "icon": "svg:track-base-station-light",
+          "icon": "i-mdi:signal-variant",
           "hideMenu": false,
           "color": "",
           "title": "通话基站分析"
@@ -535,7 +535,7 @@
     "path": "/behaviorSpacetime/index",
     "component": "/behaviorSpacetime/index",
     "meta": {
-      "icon": "icon-directions",
+      "icon": "i-mdi:map-clock-outline",
       "hideMenu": false,
       "color": "",
       "title": "行为时空分析"
@@ -550,7 +550,7 @@
     "path": "/graph/index",
     "component": "/graph/index",
     "meta": {
-      "icon": "icon-people",
+      "icon": "i-mdi:graph-outline",
       "hideMenu": true,
       "color": "",
       "title": "对象关系分析"
@@ -565,7 +565,7 @@
     "path": "/graph/g6-editor",
     "component": "/graph/g6-editor",
     "meta": {
-      "icon": "icon-people",
+      "icon": "i-mdi:graph-outline",
       "hideMenu": false,
       "color": "",
       "title": "对象关系分析"
@@ -595,7 +595,7 @@
     "path": "/aiBaseStation/index",
     "component": "/aiBaseStation/index",
     "meta": {
-      "icon": "svg:track-base-station-light",
+      "icon": "i-mdi:radio-tower",
       "hideMenu": false,
       "color": "",
       "title": "基站查询"

+ 28 - 0
ai-electron/frontend/uno.config.ts

@@ -24,6 +24,34 @@ export default defineConfig({
     'i-mdi:chart-box-outline',
     'i-mdi:brain',
     'i-mdi:history',
+    // 左侧研判目录语义图标(menu.json 运行时拼接)
+    'i-mdi:briefcase-outline',
+    'i-mdi:account-details-outline',
+    'i-mdi:message-text-outline',
+    'i-mdi:phone-sync-outline',
+    'i-mdi:weather-night',
+    'i-mdi:calendar-alert',
+    'i-mdi:calendar-star',
+    'i-mdi:calendar-sync-outline',
+    'i-mdi:chart-timeline-variant',
+    'i-mdi:cash-multiple',
+    'i-mdi:swap-horizontal',
+    'i-mdi:cash',
+    'i-mdi:flash-outline',
+    'i-mdi:credit-card-outline',
+    'i-mdi:bank-outline',
+    'i-mdi:finance',
+    'i-mdi:account-group-outline',
+    'i-mdi:bed-outline',
+    'i-mdi:handshake-outline',
+    'i-mdi:phone-in-talk-outline',
+    'i-mdi:package-variant-closed',
+    'i-mdi:signal-variant',
+    'i-mdi:radio-tower',
+    'i-mdi:map-marker-path',
+    'i-mdi:map-clock-outline',
+    'i-mdi:graph-outline',
+    'i-mdi:bank-transfer',
     // AI 模块组件内通过 Icon 组件动态拼接的图标
     'i-mdi:refresh',
     'i-mdi:plus',