zhb 2 ヶ月 前
コミット
483f847b46

+ 0 - 1
components/PrefectInfo.vue

@@ -44,7 +44,6 @@
 
   const isPerfectInfo = computed(() => {
     const {status,applyRealStatus} = userInfo.customInfo
-    console.log(route)
     return !(status == 2 || applyRealStatus == 1|| applyRealStatus ==2) && route.path != '/pages/mine/improveImmediately'
   })
   const info = computed(() => {

+ 65 - 182
components/cwg-sidebar.vue

@@ -1,195 +1,41 @@
 <template>
     <view class="cwg-sidebar">
-        <view class="menu" v-for="(item, index) in menu" :key="item.path">
-            <view class="menu-item" @click="handleClick(index)">
-                <cwg-icon :name="item.icon" :size="20" color="#6c8595" />
-                <view class="menu-label" v-t="item.label" />
-                <view class="chevron-icon" :class="{ 'expanded': item.isOpenMenu }">
-                    <cwg-icon v-if="item.children && item.children.length" name="crm-chevron-down" :size="20"
-                        color="#6c8595" />
+        <view class="menu-list">
+            <view class="menu" v-for="(item, index) in menus" :key="item.path">
+                <view class="menu-item" @click="handleClick(index)">
+                    <cwg-icon :name="item.icon" :size="20" color="#6c8595" />
+                    <view class="menu-label" v-t="item.label" />
+                    <view class="chevron-icon" :class="{ 'expanded': item.isOpenMenu }">
+                        <cwg-icon v-if="item.children && item.children.length" name="crm-chevron-down" :size="20"
+                            color="#6c8595" />
+                    </view>
                 </view>
+                <view :ref="(el) => setSubmenuRef(index, el)" class="submenu-box" :a="index" :key1="item.path + index"
+                    :b="item" :style="{
+                        height: !(item.children && item.children.length) ? '0px' : item.isOpenMenu ? item.submenuHeight + 'px' : '0px',
+                        transition: 'height 281ms cubic-bezier(0.4, 0, 0.2, 1)'
+                    }" :class="{ 'active': item.isOpenMenu }">
+                    <cwg-submenu v-if="item.children && item.children.length" :submenu-items="item.children"
+                        @submenu-click="handleSubmenuClick" />
+                </view>
+            </view>
+        </view>
+        <view class="menu fixed">
+            <view class="menu-item ib-box" @click="setMode('ib')" v-if="mode !== 'ib'">
+                <cwg-icon name="crm-ib" :size="20" color="#6c8595" />
+                <view class="menu-label" v-t="'Home.msg.Ib'" />
             </view>
-            <view ref="submenuRefs" class="submenu-box" :style="{
-                height: item.isOpenMenu ? item.submenuHeight + 'px' : '0px',
-                transition: 'height 281ms cubic-bezier(0.4, 0, 0.2, 1)'
-            }" :class="{ 'active': item.isOpenMenu }">
-                <cwg-submenu v-if="item.children" :submenu-items="item.children" @submenu-click="handleSubmenuClick" />
+            <view class="menu-item ib-box" @click="setMode('customer')" v-if="mode !== 'customer'">
+                <cwg-icon name="crm-trade" :size="20" color="#6c8595" />
+                <view class="menu-label" v-t="'Home.msg.Custom'" />
             </view>
         </view>
     </view>
 </template>
 
 <script lang="ts" setup>
-import { ref, onMounted, nextTick, watch, computed } from 'vue';
-import Config from '@/config/index'
-import useRouter from "@/hooks/useRouter";
-const router = useRouter();
-import useRoute from '@/hooks/useRoute'
-const route = useRoute()
-import { useI18n } from "vue-i18n";
-const { t, locale } = useI18n();
-import { localesList, LANG_MAP } from '@/locale/index'
-import { lang } from '@/composables/config'
-interface MenuItem {
-    key: string;
-    label: string;
-    icon?: string;
-    children?: MenuItem[];
-}
-const emit = defineEmits(['menu-click']);
-const currentLang = computed(() => lang.value || locale.value)
-const customMenuList = computed(() =>
-    localesList.map((code) => ({
-        label: `language.${code}`,
-        lang: code,
-        type: "lang",
-        path: '/'
-    })),
-)
-const menu = ref<MenuItem[]>(
-    [
-        {
-            isOpenMenu: false, submenuHeight: 0,
-            path: '/pages/customer/index', label: 'Shop.Index.Transaction', icon: 'crm-trade',
-            children: [
-                { path: '/pages/customer/index', label: 'Custom.Index.AccountList', icon: 'icon-client' },
-                { path: '/pages/customer/trade-history', label: 'Ib.Report.Tit1', icon: 'icon-transfer' },
-                { path: '/pages/customer/trade-position', label: 'Ib.Report.Tit4', icon: 'icon-transfer' },
-                { path: '/pages/customer/recording-history', label: 'Home.page_customer.item7', icon: 'icon-application' }
-            ]
-        },
-        {
-            isOpenMenu: false, submenuHeight: 0,
-            path: '/pages/customer/index', label: 'Latest.PaymentWallet', icon: 'crm-payment',
-            children: [
-                { path: '/pages/customer/deposit', label: 'Home.page_customer.item2', icon: 'icon-deposit' },
-                { path: '/pages/customer/withdrawal', label: 'Home.page_customer.item3', icon: 'icon-withdrawal' },
-                { path: '/pages/customer/payment-history', label: 'Home.page_customer.item4', icon: 'icon-payment' },
-                { path: '/pages/customer/transfer', label: 'Custom.Index.Transfer', icon: 'icon-transfer' }
-            ]
-        },
-        {
-            isOpenMenu: false,
-            path: '/pages/ib/index', label: 'Home.msg.Ib', icon: 'crm-ib',
-            children: [
-                { path: '/pages/ib/index', label: 'Home.page_ib.item1', icon: 'icon-client' },
-              //  客户管理
-                { path: '/pages/ib/customer', label: 'Home.page_ib.item2', icon: 'icon-deposit' },
-              //代理管理
-                { path: '/pages/ib/subsList', label: 'Home.page_ib.item12', icon: 'icon-deposit' },
-              // 信号源列表
-                { path: '/pages/ib/agentList', label: 'Home.page_ib.item11', icon: 'icon-deposit' },
-              // 账户管理
-                { path: '/pages/ib/accountList', label: 'Home.page_ib.item10', icon: 'icon-deposit' },
-                { path: '/pages/ib/report', label: 'Home.page_ib.item3', icon: 'icon-withdrawal' },
-                { path: '/pages/ib/transfer', label: 'Home.page_ib.item4', icon: 'icon-payment' },
-                { path: '/pages/ib/withdraw', label: 'Home.page_ib.item5', icon: 'icon-transfer' },
-                { path: '/pages/ib/agent-transfer', label: 'Home.page_ib.item9', icon: 'icon-transfer' },
-                { path: '/pages/ib/recording', label: 'Home.page_ib.item7', icon: 'icon-application' }
-            ]
-        },
-        {
-            path: '/pages/analytics/analystViews', isOpenMenu: false, label: 'News.News', icon: 'crm-chart-area',
-            children: [
-                { path: '/pages/analytics/analystViews', label: 'News.Announcement', icon: 'icon-application' },
-                { path: '/pages/analytics/news', label: 'News.NewsInformation', icon: 'icon-application' },
-                { path: `https://www.${Config.host}.com/${locale.value}/economic-calendar`, label: 'News.FinancialCalendar', icon: 'icon-application', isExternal: true },
-            ]
-        },
-        {
-            path: '/pages/customer/withdrawal', isOpenMenu: false, label: 'Downloadpage.item1', icon: 'crm-download',
-            children: []
-        },
-        {
-            path: '/pages/common/chat', isOpenMenu: false, label: 'Downloadpage.item16', icon: 'crm-headset',
-            children: [],
-            type: 'chat'
-        },
-        {
-            path: '/pages/customer/support', isOpenMenu: false, label: 'Custom.Index.Settings', icon: 'crm-sz',
-            children: [
-                { path: '/pages/mine/info?type=1', isOpenMenu: false, label: 'PersonalManagement.Title.PersonalInformation', icon: 'crm-headset' },
-                { path: '/pages/mine/info?type=2', isOpenMenu: false, label: 'PersonalManagement.Title.BankInformation', icon: 'crm-headset' },
-                { path: '/pages/mine/info?type=3', isOpenMenu: false, label: 'PersonalManagement.Title.FileManagement', icon: 'crm-headset' },
-                { path: '/pages/mine/info?type=4', isOpenMenu: false, label: 'PersonalManagement.Title.SecurityCenter', icon: 'crm-headset' },
-                { path: '/pages/common/notice', isOpenMenu: false, label: 'News.Notice', icon: 'crm-headset' },
-            ]
-        },
-        {
-            path: '/', isOpenMenu: false, label: 'language.index', icon: 'cwg-lang',
-            children: customMenuList.value
-        },
-    ]);
-const submenuRefs = ref<any[]>([]);
-const measureHeight = (element: HTMLElement): number => {
-    const originalDisplay = element.style.display;
-    const originalPosition = element.style.position;
-    const originalVisibility = element.style.visibility;
-    const originalWidth = element.style.width;
-    element.style.display = 'block';
-    element.style.position = 'absolute';
-    element.style.visibility = 'hidden';
-    element.style.width = '100%';
-    const height = element.scrollHeight || element.offsetHeight;
-    element.style.display = originalDisplay;
-    element.style.position = originalPosition;
-    element.style.visibility = originalVisibility;
-    element.style.width = originalWidth;
-    return height;
-};
-const updateSubmenuHeight = (index: number) => {
-    const refs = submenuRefs.value;
-    if (refs && refs[index]) {
-        const el = refs[index].$el || refs[index];
-        const height = measureHeight(el);
-        if (height > 0) {
-            menu.value[index].submenuHeight = height;
-        }
-    }
-};
-function handleClick(index: number) {
-    if (!menu.value[index].children || menu.value[index].children.length == 0) {
-        // #ifdef H5
-        if (menu.value[index].type === 'chat') {
-            if (window.LiveChatWidget) {
-                window.LiveChatWidget.call("maximize");
-            }
-        } else {
-            router.push(menu.value[index].path);
-        }
-        // #endif
-        // #ifdef APP-VUE
-        router.push(menu.value[index].path);
-        // #endif
-        return;
-    }
-    menu.value[index].isOpenMenu = !menu.value[index].isOpenMenu;
-}
-watch(route, () => {
-    const currentPath = route.path;
-    menu.value.forEach((item, index) => {
-        if (item.children) {
-            const isActive = item.children.some(child => child.path.includes(currentPath));
-            menu.value[index].isOpenMenu = isActive;
-            if (isActive) {
-                nextTick(() => {
-                    updateSubmenuHeight(index);
-                });
-            }
-        }
-    });
-}, { immediate: true })
-
-
-onMounted(() => {
-    nextTick(() => {
-        menu.value.forEach((item, index) => {
-            if (item.children) {
-                updateSubmenuHeight(index);
-            }
-        });
-    });
-});
+import { useMenuSplit } from '@/composables/useMenuSplit'
+const { menus, setSubmenuRef, setMode, handleClick, handleSubmenuClick, mode } = useMenuSplit()
 </script>
 
 <style scoped lang="scss">
@@ -213,6 +59,15 @@ onMounted(() => {
         width: px2rpx(54);
     }
 
+    .menu-list {
+        flex: 1;
+        width: 100%;
+        overflow-y: auto;
+        display: flex;
+        flex-direction: column;
+        gap: px2rpx(8);
+    }
+
     .submenu-box {
         width: 100%;
         height: 0;
@@ -255,5 +110,33 @@ onMounted(() => {
             transform: rotate(180deg);
         }
     }
+
+    .ib-box {
+        background: rgba(140, 69, 246, 0.08) !important;
+        border: 1px solid rgba(140, 69, 246, 0.2) !important;
+        font-size: px2rpx(18);
+        font-weight: 600;
+        color: #141d22;
+
+        &:hover {
+            background: rgba(140, 69, 246, 0.08) !important;
+            border: 1px solid rgba(140, 69, 246, 0.2) !important;
+        }
+    }
+
+    .zy-box {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+    }
+
+    .fixed {
+        position: relative;
+        width: 100%;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        gap: px2rpx(8);
+    }
 }
 </style>

+ 335 - 0
composables/useMenuSplit.ts

@@ -0,0 +1,335 @@
+import { ref, computed, watch, nextTick, onMounted } from 'vue'
+import { useI18n } from 'vue-i18n'
+import Config from '@/config/index'
+import { localesList } from '@/locale/index'
+import { useWindowWidth } from '@/composables/useWindowWidth'
+import useGlobalStore from '@/stores/use-global-store'
+import useRouter from '@/hooks/useRouter'
+import useRoute from '@/hooks/useRoute'
+
+export interface MenuItem {
+    path: string
+    label: string
+    icon: string
+    children?: MenuItem[]
+    isOpenMenu?: boolean
+    submenuHeight?: number
+    isExternal?: boolean
+    type?: string
+    lang?: string
+}
+
+function cloneMenu(menus: MenuItem[]): MenuItem[] {
+    return menus.map(item => ({
+        ...item,
+        children: item.children ? cloneMenu(item.children) : [],
+        isOpenMenu: item.isOpenMenu ?? false,
+    }))
+}
+
+export function useMenuSplit() {
+    const { locale } = useI18n()
+    const globalStore = useGlobalStore()
+    const mode = computed(() => globalStore.mode)
+    const windowWidth = useWindowWidth(300)
+    const shouldShowLanguageMenu = computed(() => windowWidth.value <= 991)
+    const { host } = Config
+    const router = useRouter()
+    const route = useRoute()
+    // 子菜单 DOM 引用
+    const submenuRefs = ref<any[]>([])
+    function setSubmenuRef(index: number, el: HTMElement | null) {
+        if (el) {
+            submenuRefs.value[index] = el
+        }
+    }
+    // 设置全局模式
+    function setMode(code: string) {
+        globalStore.setMode(code);
+        const homePath = mode.value === 'customer' ? '/pages/customer/index' : '/pages/ib/index'
+        router.push(homePath)
+        nextTick(() => {
+            submenuRefs.value = [] // 重置,等待重新绑定
+            menus.value.forEach((item, index) => {
+                if (item.isOpenMenu && item.children && item.children.length) {
+                    updateSubmenuHeight(index)
+                }
+            })
+        })
+    }
+    // 测量元素实际高度(用于过渡动画)
+    const measureHeight = (element: HTMLElement): number => {
+        const originalDisplay = element.style.display
+        const originalPosition = element.style.position
+        const originalVisibility = element.style.visibility
+        const originalWidth = element.style.width
+        element.style.display = 'block'
+        element.style.position = 'absolute'
+        element.style.visibility = 'hidden'
+        element.style.width = '100%'
+        const height = element.scrollHeight || element.offsetHeight
+        element.style.display = originalDisplay
+        element.style.position = originalPosition
+        element.style.visibility = originalVisibility
+        element.style.width = originalWidth
+        return height
+    }
+    // 更新指定索引的子菜单高度
+    const updateSubmenuHeight = (index: number) => {
+        const refs = submenuRefs.value
+        nextTick(() => {
+            if (refs && refs[index]) {
+                const el = refs[index].$el || refs[index]
+                const height = measureHeight(el)
+                if (height > 0) {
+                    menus.value[index].submenuHeight = height
+                }
+            }
+        })
+    }
+    let clickTimer: ReturnType<typeof setTimeout> | null = null
+    // 点击菜单项(切换展开/折叠或跳转)
+    function handleClick(index: number) {
+        if (clickTimer) return
+        clickTimer = setTimeout(() => {
+            clickTimer = null
+        }, 300)
+        const item = menus.value[index]
+        // 无子菜单:执行跳转或特殊操作
+        if (!item.children || item.children.length === 0) {
+            // #ifdef H5
+            if (item.type === 'chat') {
+                if (window.LiveChatWidget) {
+                    window.LiveChatWidget.call('maximize')
+                }
+                return
+            }
+            // #endif
+            router.push(item.path)
+            return
+        }
+        // 有子菜单:切换展开/折叠状态
+        item.isOpenMenu = !item.isOpenMenu
+        if (item.isOpenMenu) {
+            nextTick(() => updateSubmenuHeight(index))
+        }
+    }
+    // 子菜单项点击事件(由 cwg-submenu 组件发出)
+    function handleSubmenuClick(subItem: any) {
+        // 处理语言切换
+        if (subItem.type === 'lang') {
+            locale.value = subItem.lang
+            return
+        }
+        // 处理外部链接
+        if (subItem.isExternal) {
+            // #ifdef H5
+            window.open(subItem.path, '_blank')
+            // #endif
+            // #ifdef APP-PLUS
+            plus.runtime.openURL(subItem.path)
+            // #endif
+            return
+        }
+        // 内部页面跳转
+        router.push(subItem.path)
+    }
+
+    // 窗口大小变化时重新计算所有已展开子菜单的高度
+    const handleResize = () => {
+        menus.value.forEach((item, index) => {
+            if (item.isOpenMenu && item.children && item.children.length) {
+                updateSubmenuHeight(index)
+            }
+        })
+    }
+    const customMenuList = computed(() =>
+        localesList.map((code) => ({
+            label: `language.${code}`,
+            lang: code,
+            type: "lang",
+            path: '/'
+        }))
+    )
+
+    const languageMenuItem = computed<MenuItem>(() => ({
+        path: '/',
+        isOpenMenu: false,
+        label: 'language.index',
+        icon: 'cwg-lang',
+        children: customMenuList.value,
+        submenuHeight: 0,
+    }))
+    const customerBaseMenus = computed<MenuItem[]>(() => [
+        {
+            isOpenMenu: false,
+            submenuHeight: 0,
+            path: '/',
+            label: 'Shop.Index.Transaction',
+            icon: 'crm-trade',
+            children: [
+                { path: '/pages/customer/index', label: 'Custom.Index.AccountList', icon: 'icon-client' },
+                { path: '/pages/customer/trade-history', label: 'Ib.Report.Tit1', icon: 'icon-transfer' },
+                { path: '/pages/customer/trade-position', label: 'Ib.Report.Tit4', icon: 'icon-transfer' },
+                { path: '/pages/customer/recording-history', label: 'Home.page_customer.item7', icon: 'icon-application' },
+            ],
+        },
+        {
+            isOpenMenu: false,
+            submenuHeight: 0,
+            path: '/',
+            label: 'Latest.PaymentWallet',
+            icon: 'crm-payment',
+            children: [
+                { path: '/pages/customer/deposit', label: 'Home.page_customer.item2', icon: 'icon-deposit' },
+                { path: '/pages/customer/withdrawal', label: 'Home.page_customer.item3', icon: 'icon-withdrawal' },
+                { path: '/pages/customer/payment-history', label: 'Home.page_customer.item4', icon: 'icon-payment' },
+                { path: '/pages/customer/transfer', label: 'Custom.Index.Transfer', icon: 'icon-transfer' },
+            ],
+        },
+        {
+            path: '/',
+            isOpenMenu: false,
+            label: 'News.News',
+            icon: 'crm-chart-area',
+            children: [
+                { path: '/pages/analytics/analystViews', label: 'News.Announcement', icon: 'icon-application' },
+                { path: '/pages/analytics/news', label: 'News.NewsInformation', icon: 'icon-application' },
+                {
+                    path: `https://www.${host}.com/${locale.value}/economic-calendar`,
+                    label: 'News.FinancialCalendar',
+                    icon: 'icon-application',
+                    isExternal: true,
+                },
+            ],
+        },
+        {
+            path: '/pages/customer/withdrawal',
+            isOpenMenu: false,
+            label: 'Downloadpage.item1',
+            icon: 'crm-download',
+            children: [],
+        },
+        {
+            path: '/pages/common/chat',
+            isOpenMenu: false,
+            label: 'Downloadpage.item16',
+            icon: 'crm-headset',
+            children: [],
+            type: 'chat',
+        },
+        {
+            path: '/',
+            isOpenMenu: false,
+            label: 'Custom.Index.Settings',
+            icon: 'crm-sz',
+            children: [
+                { path: '/pages/mine/info?type=1', label: 'PersonalManagement.Title.PersonalInformation', icon: 'crm-headset' },
+                { path: '/pages/mine/info?type=2', label: 'PersonalManagement.Title.BankInformation', icon: 'crm-headset' },
+                { path: '/pages/mine/info?type=3', label: 'PersonalManagement.Title.FileManagement', icon: 'crm-headset' },
+                { path: '/pages/mine/info?type=4', label: 'PersonalManagement.Title.SecurityCenter', icon: 'crm-headset' },
+                { path: '/pages/common/notice', label: 'News.Notice', icon: 'crm-headset' },
+            ],
+        },
+    ])
+    const ibBaseMenus = computed<MenuItem[]>(() => [
+        {
+            isOpenMenu: false,
+            path: '/',
+            label: 'Home.page_ib.item1',
+            icon: 'crm-mb',
+        },
+        {
+            path: '/',
+            label: 'Home.page_ib.item2',
+            icon: 'crm-bg',
+            children: [
+                { path: '/pages/ib/customer', label: 'Home.page_ib.item2', icon: 'icon-deposit' },
+                { path: '/pages/ib/subsList', label: 'Home.page_ib.item12', icon: 'icon-deposit' },
+                { path: '/pages/ib/agentList', label: 'Home.page_ib.item11', icon: 'icon-deposit' },
+                { path: '/pages/ib/accountList', label: 'Home.page_ib.item10', icon: 'icon-deposit' }
+            ],
+        },
+        {
+            isOpenMenu: false,
+            submenuHeight: 0,
+            path: '/',
+            label: 'Latest.PaymentWallet',
+            icon: 'crm-payment',
+            children: [
+                { path: '/pages/ib/transfer', label: 'Home.page_ib.item4', icon: 'icon-payment' },
+                { path: '/pages/ib/withdraw', label: 'Home.page_ib.item5', icon: 'icon-transfer' },
+                { path: '/pages/ib/agent-transfer', label: 'Home.page_ib.item9', icon: 'icon-transfer' },
+                { path: '/pages/ib/recording', label: 'Home.page_ib.item7', icon: 'icon-application' },
+            ],
+        },
+        {
+            isOpenMenu: false,
+            path: '/',
+            label: 'Home.page_ib.item3',
+            icon: 'crm-newspaper',
+            children: [
+                { path: '/pages/ib/report', label: 'Home.page_ib.item3', icon: 'icon-withdrawal' },
+            ],
+        },
+    ])
+    const menus = ref<MenuItem[]>([])
+
+    // 监听 mode 变化,自动导航到对应首页
+    watch(mode, (newMode, oldMode) => {
+        if (newMode !== oldMode) {
+            const base = newMode === 'customer' ? [...customerBaseMenus.value] : [...ibBaseMenus.value]
+            if (shouldShowLanguageMenu.value) {
+                base.push(languageMenuItem.value)
+            }
+            menus.value = cloneMenu(base)
+        }
+    }, { immediate: true })
+    // 监听路由变化:自动展开包含当前路径的父菜单,不自动关闭其他菜单
+    watch(route, () => {
+        const currentPath = route.path
+        const shouldOpenIndices: number[] = []
+        menus.value.forEach((item, idx) => {
+            if (item.children && item.children.length) {
+                const isActive = item.children.some(child => {
+                    if (child.isExternal || child.type === 'lang') return false
+                    return currentPath === child.path || currentPath.startsWith(child.path + '?') || currentPath.startsWith(child.path + '/')
+                })
+                if (isActive && !item.isOpenMenu) {
+                    shouldOpenIndices.push(idx)
+                }
+            }
+        })
+        if (shouldOpenIndices.length) {
+            shouldOpenIndices.forEach(idx => {
+                menus.value[idx].isOpenMenu = true
+            })
+            nextTick(() => {
+                shouldOpenIndices.forEach(idx => updateSubmenuHeight(idx))
+            })
+        }
+    }, { immediate: true })
+    watch(windowWidth, handleResize)
+
+    onMounted(() => {
+        nextTick(() => {
+            menus.value.forEach((item, index) => {
+                if (item.isOpenMenu && item.children && item.children.length) {
+                    updateSubmenuHeight(index)
+                }
+            })
+        })
+    })
+    return {
+        menus,          // 最终菜单(已克隆,可直接修改 isOpenMenu 等)
+        mode,           // 只读或按需使用
+        shouldShowLanguageMenu, // 可选,供外部获取状态
+        windowWidth,          // 可选,供外部获取宽度(300)
+        setMode,              // 可选,供外部设置模式
+        setSubmenuRef,        // 可选,供外部设置子菜单引用
+        updateSubmenuHeight,  // 可选,供外部更新子菜单高度
+        handleClick,          // 可选,供外部处理点击事件
+        handleSubmenuClick,   // 可选,供外部处理子菜单点击事件
+
+    }
+}

+ 85 - 0
composables/useWindowWidth.ts

@@ -0,0 +1,85 @@
+// composables/useWindowWidth.ts
+import { ref, onMounted, onBeforeUnmount, type Ref } from 'vue'
+
+// 同步获取当前窗口宽度
+function getCurrentWidth(): number {
+  // #ifdef H5
+  if (typeof window !== 'undefined') {
+    return window.innerWidth
+  }
+  // #endif
+
+  // #ifdef APP-PLUS
+  // @ts-ignore
+  if (typeof uni !== 'undefined' && uni.getSystemInfoSync) {
+    // @ts-ignore
+    const systemInfo = uni.getSystemInfoSync()
+    return systemInfo.windowWidth
+  }
+  // #endif
+
+  return 0 // 兜底值
+}
+
+export function useWindowWidth(debounceMs: number = 0): Ref<number> {
+  // ✅ 关键:立即同步获取宽度,不再初始为 0
+  const width = ref(getCurrentWidth())
+
+  let timer: ReturnType<typeof setTimeout> | null = null
+  let resizeHandler: (res?: any) => void
+
+  const updateWidth = () => {
+    width.value = getCurrentWidth()
+  }
+
+  if (debounceMs > 0) {
+    resizeHandler = () => {
+      if (timer) clearTimeout(timer)
+      timer = setTimeout(() => {
+        updateWidth()
+        timer = null
+      }, debounceMs)
+    }
+  } else {
+    resizeHandler = updateWidth
+  }
+
+  onMounted(() => {
+    // 确保宽度最新(如屏幕旋转后组件才挂载的极端情况)
+    updateWidth()
+
+    // #ifdef H5
+    if (typeof window !== 'undefined') {
+      window.addEventListener('resize', resizeHandler)
+    }
+    // #endif
+
+    // #ifdef APP-PLUS
+    // @ts-ignore
+    if (typeof uni !== 'undefined' && uni.onWindowResize) {
+      // @ts-ignore
+      uni.onWindowResize(resizeHandler)
+    }
+    // #endif
+  })
+
+  onBeforeUnmount(() => {
+    if (timer) clearTimeout(timer)
+
+    // #ifdef H5
+    if (typeof window !== 'undefined') {
+      window.removeEventListener('resize', resizeHandler)
+    }
+    // #endif
+
+    // #ifdef APP-PLUS
+    // @ts-ignore
+    if (typeof uni !== 'undefined' && uni.offWindowResize) {
+      // @ts-ignore
+      uni.offWindowResize(resizeHandler)
+    }
+    // #endif
+  })
+
+  return width
+}

+ 0 - 2
hooks/useRouter.ts

@@ -53,8 +53,6 @@ export default function useRouter() {
     const url = normalizeUrl(to);
     const animation = getAnimationOptions(to);
 
-    console.log(url, animation, 23123123);
-
     uni.navigateTo({
       url,
       "animationType": "fade-in",  // 全局默认动画

+ 1 - 0
static/icons/crm-bg.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-presentation "><path d="M3 4l18 0"></path><path d="M4 4v10a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2v-10"></path><path d="M12 16l0 4"></path><path d="M9 20l6 0"></path><path d="M8 12l3 -3l2 2l3 -3"></path></svg>

+ 1 - 0
static/icons/crm-mb.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-layout-dashboard "><path d="M5 4h4a1 1 0 0 1 1 1v6a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1v-6a1 1 0 0 1 1 -1"></path><path d="M5 16h4a1 1 0 0 1 1 1v2a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1v-2a1 1 0 0 1 1 -1"></path><path d="M15 12h4a1 1 0 0 1 1 1v6a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1v-6a1 1 0 0 1 1 -1"></path><path d="M15 4h4a1 1 0 0 1 1 1v2a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1v-2a1 1 0 0 1 1 -1"></path></svg>

+ 1 - 0
static/icons/crm-newspaper.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M64 480L64 184C64 170.7 74.7 160 88 160C101.3 160 112 170.7 112 184L112 472C112 485.3 122.7 496 136 496C149.3 496 160 485.3 160 472L160 160C160 124.7 188.7 96 224 96L512 96C547.3 96 576 124.7 576 160L576 480C576 515.3 547.3 544 512 544L128 544C92.7 544 64 515.3 64 480zM224 192L224 256C224 273.7 238.3 288 256 288L320 288C337.7 288 352 273.7 352 256L352 192C352 174.3 337.7 160 320 160L256 160C238.3 160 224 174.3 224 192zM248 432C234.7 432 224 442.7 224 456C224 469.3 234.7 480 248 480L488 480C501.3 480 512 469.3 512 456C512 442.7 501.3 432 488 432L248 432zM224 360C224 373.3 234.7 384 248 384L488 384C501.3 384 512 373.3 512 360C512 346.7 501.3 336 488 336L248 336C234.7 336 224 346.7 224 360zM424 240C410.7 240 400 250.7 400 264C400 277.3 410.7 288 424 288L488 288C501.3 288 512 277.3 512 264C512 250.7 501.3 240 488 240L424 240z"/></svg>

+ 1 - 0
static/icons/crm-zy.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-chevrons-left"><path d="M11 7l-5 5l5 5"></path><path d="M17 7l-5 5l5 5"></path></svg>

+ 28 - 0
static/svg-icons-lib.js

@@ -63,6 +63,10 @@ const collections = {
         "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path fill=\"#22ac38\" d=\"M64 160c0-17.7 14.3-32 32-32h384c17.7 0 32 14.3 32 32s-14.3 32-32 32H96c-17.7 0-32-14.3-32-32m64 160c0-17.7 14.3-32 32-32h384c17.7 0 32 14.3 32 32s-14.3 32-32 32H160c-17.7 0-32-14.3-32-32m384 160c0 17.7-14.3 32-32 32H96c-17.7 0-32-14.3-32-32s14.3-32 32-32h384c17.7 0 32 14.3 32 32\"/></svg>",
         2
       ],
+      "crm-bg": [
+        "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\" stroke=\"#22ac38\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"tabler-icon tabler-icon-presentation\" viewBox=\"0 0 24 24\"><path d=\"M3 4h18M4 4v10a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V4m-8 12v4m-3 0h6\"/><path d=\"m8 12 3-3 2 2 3-3\"/></svg>",
+        2
+      ],
       "crm-building-columns": [
         "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path fill=\"#22ac38\" d=\"M335.9 84.2c-9.8-5.6-21.9-5.6-31.8 0l-224 128c-12.6 7.2-18.8 22-15.1 36S81.5 272 96 272h32v208l-51.2 38.4c-8.1 6-12.8 15.5-12.8 25.6 0 17.7 14.3 32 32 32h448c17.7 0 32-14.3 32-32 0-10.1-4.7-19.6-12.8-25.6L512 480V272h32c14.5 0 27.2-9.8 30.9-23.8s-2.5-28.8-15.1-36l-224-128zM464 272v208h-64V272zm-112 0v208h-64V272zm-112 0v208h-64V272zm80-112c17.7 0 32 14.3 32 32s-14.3 32-32 32-32-14.3-32-32 14.3-32 32-32\"/></svg>",
         2
@@ -99,6 +103,10 @@ const collections = {
         "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path fill=\"#22ac38\" d=\"M64 192v32h512v-32c0-35.3-28.7-64-64-64H128c-35.3 0-64 28.7-64 64m0 80v176c0 35.3 28.7 64 64 64h384c35.3 0 64-28.7 64-64V272zm64 152c0-13.3 10.7-24 24-24h48c13.3 0 24 10.7 24 24s-10.7 24-24 24h-48c-13.3 0-24-10.7-24-24m144 0c0-13.3 10.7-24 24-24h64c13.3 0 24 10.7 24 24s-10.7 24-24 24h-64c-13.3 0-24-10.7-24-24\"/></svg>",
         2
       ],
+      "crm-custom": [
+        "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path fill=\"#22ac38\" d=\"M240 192c0-44.2 35.8-80 80-80s80 35.8 80 80-35.8 80-80 80-80-35.8-80-80m208 0c0-70.7-57.3-128-128-128s-128 57.3-128 128 57.3 128 128 128 128-57.3 128-128M144 544c0-70.7 57.3-128 128-128h96c70.7 0 128 57.3 128 128v8c0 13.3 10.7 24 24 24s24-10.7 24-24v-8c0-97.2-78.8-176-176-176h-96c-97.2 0-176 78.8-176 176v8c0 13.3 10.7 24 24 24s24-10.7 24-24z\"/></svg>",
+        2
+      ],
       "crm-deposit": [
         "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\" stroke=\"#22ac38\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><path d=\"M3 12a9 9 0 1 0 18 0 9 9 0 0 0-18 0m5 0 4 4m0-8v8m4-4-4 4\"/></svg>",
         2
@@ -119,6 +127,10 @@ const collections = {
         "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path fill=\"#22ac38\" d=\"M320 208c-30.9 0-56-25.1-56-56s25.1-56 56-56 56 25.1 56 56-25.1 56-56 56m0 224c30.9 0 56 25.1 56 56s-25.1 56-56 56-56-25.1-56-56 25.1-56 56-56m56-112c0 30.9-25.1 56-56 56s-56-25.1-56-56 25.1-56 56-56 56 25.1 56 56\"/></svg>",
         2
       ],
+      "crm-ellipsis": [
+        "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path fill=\"#22ac38\" d=\"M96 320c0-30.9 25.1-56 56-56s56 25.1 56 56-25.1 56-56 56-56-25.1-56-56m168 0c0-30.9 25.1-56 56-56s56 25.1 56 56-25.1 56-56 56-56-25.1-56-56m224-56c30.9 0 56 25.1 56 56s-25.1 56-56 56-56-25.1-56-56 25.1-56 56-56\"/></svg>",
+        2
+      ],
       "crm-file": [
         "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path fill=\"#22ac38\" d=\"M192 64c-35.3 0-64 28.7-64 64v384c0 35.3 28.7 64 64 64h256c35.3 0 64-28.7 64-64V234.5c0-17-6.7-33.3-18.7-45.3L386.7 82.7c-12-12-28.2-18.7-45.2-18.7zm261.5 176H360c-13.3 0-24-10.7-24-24v-93.5z\"/></svg>",
         2
@@ -155,6 +167,10 @@ const collections = {
         "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path fill=\"#22ac38\" d=\"M256 160v64h128v-64c0-35.3-28.7-64-64-64s-64 28.7-64 64m-64 64v-64c0-70.7 57.3-128 128-128s128 57.3 128 128v64c35.3 0 64 28.7 64 64v224c0 35.3-28.7 64-64 64H192c-35.3 0-64-28.7-64-64V288c0-35.3 28.7-64 64-64\"/></svg>",
         2
       ],
+      "crm-mb": [
+        "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\" stroke=\"#22ac38\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"tabler-icon tabler-icon-layout-dashboard\" viewBox=\"0 0 24 24\"><path d=\"M5 4h4a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1m0 12h4a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1m10-4h4a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-4a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1m0-8h4a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-4a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1\"/></svg>",
+        2
+      ],
       "crm-menu": [
         "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"200\" height=\"200\" class=\"icon\" viewBox=\"0 0 1024 1024\"><path fill=\"#fff\" fill-opacity=\".01\" d=\"M0 0h1024v1024H0z\"/><path fill=\"#666\" d=\"M853.333 192v64H170.667v-64zm-128 298.667v64H170.667v-64zm128 298.666v64H170.667v-64z\"/></svg>",
         1,
@@ -164,6 +180,10 @@ const collections = {
         "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path fill=\"#22ac38\" d=\"M31 169c-9.4-9.4-9.4-24.6 0-33.9L103 63c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-31 31h406c35.3 0 64 28.7 64 64v178.2l-5.1-5.1c-28.1-28.1-73.7-28.1-101.8 0-27.2 27.2-28.1 70.6-2.8 98.9h67.7l-31-31c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l72 72c9.4 9.4 9.4 24.6 0 33.9l-72 72c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l31-31H127.8c-35.3 0-64-28.7-64-64V269.8l5.1 5.1c28.1 28.1 73.7 28.1 101.8 0 27.2-27.2 28.1-70.6 2.8-98.9h-67.7l31 31c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0zm385 151c0-53-43-96-96-96s-96 43-96 96 43 96 96 96 96-43 96-96m88-64.5c4.4.5 8-3.1 8-7.5v-48c0-4.4-3.6-8-8-8h-48c-4.4 0-8.1 3.6-7.5 8 3.6 29 26.6 51.9 55.5 55.5m-368 129c-4.4-.5-8 3.1-8 7.5v48c0 4.4 3.6 8 8 8h48c4.4 0 8.1-3.6 7.5-8-3.6-29-26.6-51.9-55.5-55.5\"/></svg>",
         2
       ],
+      "crm-newspaper": [
+        "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path fill=\"#22ac38\" d=\"M64 480V184c0-13.3 10.7-24 24-24s24 10.7 24 24v288c0 13.3 10.7 24 24 24s24-10.7 24-24V160c0-35.3 28.7-64 64-64h288c35.3 0 64 28.7 64 64v320c0 35.3-28.7 64-64 64H128c-35.3 0-64-28.7-64-64m160-288v64c0 17.7 14.3 32 32 32h64c17.7 0 32-14.3 32-32v-64c0-17.7-14.3-32-32-32h-64c-17.7 0-32 14.3-32 32m24 240c-13.3 0-24 10.7-24 24s10.7 24 24 24h240c13.3 0 24-10.7 24-24s-10.7-24-24-24zm-24-72c0 13.3 10.7 24 24 24h240c13.3 0 24-10.7 24-24s-10.7-24-24-24H248c-13.3 0-24 10.7-24 24m200-120c-13.3 0-24 10.7-24 24s10.7 24 24 24h64c13.3 0 24-10.7 24-24s-10.7-24-24-24z\"/></svg>",
+        2
+      ],
       "crm-payment": [
         "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\" stroke=\"#22ac38\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"tabler-icon tabler-icon-wallet\" viewBox=\"0 0 24 24\"><path d=\"M17 8V5a1 1 0 0 0-1-1H6a2 2 0 0 0 0 4h12a1 1 0 0 1 1 1v3m0 4v3a1 1 0 0 1-1 1H6a2 2 0 0 1-2-2V6\"/><path d=\"M20 12v4h-4a2 2 0 0 1 0-4z\"/></svg>",
         2
@@ -180,6 +200,10 @@ const collections = {
         "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path fill=\"#22ac38\" d=\"M129.9 292.5C143.2 199.5 223.3 128 320 128c53 0 101 21.5 135.8 56.2l.6.6 7.6 7.2h-47.9c-17.7 0-32 14.3-32 32s14.3 32 32 32h128c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32s-32 14.3-32 32v53.4l-11.3-10.7C454.5 92.6 390.5 64 320 64 191 64 84.3 159.4 66.6 283.5c-2.5 17.5 9.6 33.7 27.1 36.2s33.7-9.7 36.2-27.1zm443.5 64c2.5-17.5-9.7-33.7-27.1-36.2s-33.7 9.7-36.2 27.1c-13.3 93-93.4 164.5-190.1 164.5-53 0-101-21.5-135.8-56.2l-.6-.6-7.6-7.2h47.9c17.7 0 32-14.3 32-32s-14.3-32-32-32L96 384c-8.5 0-16.7 3.4-22.7 9.5s-9.4 14.2-9.3 22.8l1 127c.1 17.7 14.6 31.9 32.3 31.7s31.9-14.6 31.7-32.3l-.4-51.5 10.7 10.1C185.6 547.4 249.5 576 320 576c129 0 235.7-95.4 253.4-219.5\"/></svg>",
         2
       ],
+      "crm-share-nodes": [
+        "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path fill=\"#22ac38\" d=\"M448 256c53 0 96-43 96-96s-43-96-96-96-96 43-96 96c0 5.4.5 10.8 1.3 16l-129.7 72.1c-16.9-15-39.2-24.1-63.6-24.1-53 0-96 43-96 96s43 96 96 96c24.4 0 46.6-9.1 63.6-24.1L353.3 464c-.9 5.2-1.3 10.5-1.3 16 0 53 43 96 96 96s96-43 96-96-43-96-96-96c-24.4 0-46.6 9.1-63.6 24.1L254.7 336c.9-5.2 1.3-10.5 1.3-16s-.5-10.8-1.3-16l129.7-72.1c16.9 15 39.2 24.1 63.6 24.1\"/></svg>",
+        2
+      ],
       "crm-star": [
         "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path fill=\"#22ac38\" d=\"M341.5 45.1c-4.1-8-12.4-13.1-21.4-13.1s-17.3 5.1-21.4 13.1l-73.6 144.2-159.9 25.4c-8.9 1.4-16.3 7.7-19.1 16.3s-.5 18 5.8 24.4l114.4 114.5-25.2 159.9c-1.4 8.9 2.3 17.9 9.6 23.2s16.9 6.1 25 2l144.4-73.4L464.4 555c8 4.1 17.7 3.3 25-2s11-14.2 9.6-23.2l-25.3-159.9 114.4-114.5c6.4-6.4 8.6-15.8 5.8-24.4s-10.1-14.9-19.1-16.3L415 189.3z\"/></svg>",
         2
@@ -224,6 +248,10 @@ const collections = {
         "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"#22ac38\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><path d=\"M4 20h4L18.5 9.5a2.828 2.828 0 1 0-4-4L4 16zm9.5-13.5 4 4\"/></svg>",
         2
       ],
+      "crm-zy": [
+        "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\" stroke=\"#22ac38\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"tabler-icon tabler-icon-chevrons-left\" viewBox=\"0 0 24 24\"><path d=\"m11 7-5 5 5 5m6-10-5 5 5 5\"/></svg>",
+        2
+      ],
       "cwg-calendar": [
         "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\"><path fill=\"#22ac38\" d=\"M5 22q-.824 0-1.412-.587A1.93 1.93 0 0 1 3 20V6q0-.824.587-1.412A1.93 1.93 0 0 1 5 4h1V3q0-.424.287-.712A.97.97 0 0 1 7 2q.424 0 .713.288Q8 2.575 8 3v1h8V3q0-.424.288-.712A.97.97 0 0 1 17 2q.424 0 .712.288Q18 2.575 18 3v1h1q.824 0 1.413.588Q21 5.175 21 6v14q0 .824-.587 1.413A1.93 1.93 0 0 1 19 22zm0-2h14V10H5zM5 8h14V6H5zm7 6a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 13q0-.424.287-.713A.97.97 0 0 1 12 12q.424 0 .713.287.287.288.287.713 0 .424-.287.713A.97.97 0 0 1 12 14m-4 0a.97.97 0 0 1-.713-.287A.97.97 0 0 1 7 13q0-.424.287-.713A.97.97 0 0 1 8 12q.424 0 .713.287Q9 12.576 9 13t-.287.713A.97.97 0 0 1 8 14m8 0a.97.97 0 0 1-.713-.287A.97.97 0 0 1 15 13q0-.424.287-.713A.97.97 0 0 1 16 12q.424 0 .712.287.288.288.288.713 0 .424-.288.713A.97.97 0 0 1 16 14m-4 4a.97.97 0 0 1-.713-.288A.97.97 0 0 1 11 17q0-.424.287-.712A.97.97 0 0 1 12 16q.424 0 .713.288.287.287.287.712 0 .424-.287.712A.97.97 0 0 1 12 18m-4 0a.97.97 0 0 1-.713-.288A.97.97 0 0 1 7 17q0-.424.287-.712A.97.97 0 0 1 8 16q.424 0 .713.288Q9 16.575 9 17q0 .424-.287.712A.97.97 0 0 1 8 18m8 0a.97.97 0 0 1-.713-.288A.97.97 0 0 1 15 17q0-.424.287-.712A.97.97 0 0 1 16 16q.424 0 .712.288.288.287.288.712 0 .424-.288.712A.97.97 0 0 1 16 18\"/></svg>",
         2

+ 1 - 0
stores/pinia.types.ts

@@ -10,4 +10,5 @@ export interface GlobalState {
     isPageSwitching: boolean
     theme: string
     statusBarHeight: number
+    mode: 'customer' | 'ib'
 }

+ 12 - 1
stores/use-global-store.ts

@@ -1,5 +1,6 @@
 import type { GlobalState } from "./pinia.types";
 import { defineStore } from "pinia";
+import ls from "@/utils/store2";
 import { toRefs, reactive } from "vue";
 const useStore = defineStore("globalStore", () => {
   const state: GlobalState = reactive({
@@ -9,12 +10,21 @@ const useStore = defineStore("globalStore", () => {
     fullScreenLoading: false,
     isPageSwitching: false,
     statusBarHeight: 0,
+    mode: 'customer',
     theme: "light"
   });
 
   const setGlobalTheme = (payload: string) => {
     state.theme = payload;
   };
+  const initMode = () => {
+    const encryptedMode = ls.get('mode');
+    state.mode = encryptedMode || 'customer';
+  };
+  const setMode = (payload: 'customer' | 'ib') => {
+    ls.set('mode', payload)
+    state.mode = payload;
+  };
   const setBarHeight = (payload: number) => {
     state.statusBarHeight = payload;
   };
@@ -30,10 +40,11 @@ const useStore = defineStore("globalStore", () => {
   const setFullScreenLoading = (payload: boolean) => {
     state.fullScreenLoading = payload;
   };
-
+  initMode()
   return {
     ...toRefs(state),
     setGlobalLoading,
+    setMode,
     setBarHeight,
     setGlobalTheme,
     setRouterLoading,

+ 129 - 269
windows/left-window.vue

@@ -1,310 +1,170 @@
 <template>
   <view class="cwg-sidebar">
-    <view class="menu" v-for="(item, index) in menu" :key="item.path">
-      <view class="menu-item" @click="handleClick(index)">
-        <cwg-icon :name="item.icon" :size="20" color="#6c8595" />
-        <view class="menu-label" v-t="item.label" />
-        <view class="chevron-icon" :class="{ 'expanded': item.isOpenMenu }">
-          <cwg-icon v-if="item.children && item.children.length" name="crm-chevron-down" :size="20"
-                    color="#6c8595" />
+    <view class="menu-list">
+      <view class="menu" v-for="(item, index) in menus" :key="item.path + index">
+        <view class="menu-item" @click="handleClick(index)">
+          <cwg-icon :name="item.icon" :size="20" color="#6c8595" />
+          <view class="menu-label" v-t="item.label" />
+          <view class="chevron-icon" :class="{ 'expanded': item.isOpenMenu }">
+            <cwg-icon v-if="item.children && item.children.length" name="crm-chevron-down" :size="20" color="#6c8595" />
+          </view>
         </view>
+        <view :ref="(el) => setSubmenuRef(index, el)" class="submenu-box" :a="index" :key1="item.path + index" :b="item"
+          :style="{
+            height: !(item.children && item.children.length) ? '0px' : item.isOpenMenu ? item.submenuHeight + 'px' : '0px',
+            transition: 'height 281ms cubic-bezier(0.4, 0, 0.2, 1)'
+          }" :class="{ 'active': item.isOpenMenu }">
+          <cwg-submenu v-if="item.children && item.children.length" :submenu-items="item.children"
+            @submenu-click="handleSubmenuClick" />
+        </view>
+      </view>
+    </view>
+    <view class="menu fixed">
+      <view class="menu-item ib-box" @click="setMode('ib')" v-if="mode !== 'ib'">
+        <cwg-icon name="crm-ib" :size="20" color="#6c8595" />
+        <view class="menu-label" v-t="'Home.msg.Ib'" />
+      </view>
+      <view class="menu-item ib-box" @click="setMode('customer')" v-if="mode !== 'customer'">
+        <cwg-icon name="crm-trade" :size="20" color="#6c8595" />
+        <view class="menu-label" v-t="'Home.msg.Custom'" />
       </view>
-      <view ref="submenuRefs" class="submenu-box" :style="{
-                height: item.isOpenMenu ? item.submenuHeight + 'px' : '0px',
-                transition: 'height 281ms cubic-bezier(0.4, 0, 0.2, 1)'
-            }" :class="{ 'active': item.isOpenMenu }">
-        <cwg-submenu v-if="item.children" :submenu-items="item.children" @submenu-click="handleSubmenuClick" />
+      <view class="menu-item zy-box" @click="handleFixedMenuClick">
+        <cwg-icon name="crm-zy" :size="20" color="#6c8595" />
       </view>
     </view>
   </view>
 </template>
 
 <script lang="ts" setup>
-  import { ref, onMounted, nextTick, watch, onBeforeUnmount } from 'vue'
-  import useRouter from '@/hooks/useRouter'
+import { useMenuSplit } from '@/composables/useMenuSplit'
+const { menus, setSubmenuRef, setMode, handleClick, handleSubmenuClick, mode } = useMenuSplit()
+// 固定菜单项点击事件
+function handleFixedMenuClick() {
+  // 这里可以添加固定菜单的点击处理逻辑
+  console.log('Fixed menu clicked')
+}
 
-  const router = useRouter()
-  import useRoute from '@/hooks/useRoute'
-
-  const route = useRoute()
-  import Config from '@/config/index'
-  import { useI18n } from 'vue-i18n'
-
-  const { t, locale } = useI18n()
+</script>
 
-  interface MenuItem {
-    key: string;
-    label: string;
-    icon?: string;
-    children?: MenuItem[];
+<style scoped lang="scss">
+@import "@/uni.scss";
+
+.cwg-sidebar {
+  width: 100%;
+  color: #6c8595;
+  height: calc(100vh - 56px);
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: px2rpx(8);
+  padding-top: px2rpx(20);
+  padding-bottom: px2rpx(8);
+  box-sizing: border-box;
+  gap: px2rpx(8);
+  border-right: 1px solid rgba(108, 133, 149, 0.12);
+
+  .logo {
+    width: px2rpx(54);
   }
 
-  const emit = defineEmits(['menu-click'])
-  // 菜单数据
-  const menu = ref<MenuItem[]>(
-    [
-      {
-        isOpenMenu: false, submenuHeight: 0,
-        path: '/pages/customer/index', label: 'Shop.Index.Transaction', icon: 'crm-trade',
-        children: [
-          { path: '/pages/customer/index', label: 'Custom.Index.AccountList', icon: 'icon-client' },
-          { path: '/pages/customer/trade-history', label: 'Ib.Report.Tit1', icon: 'icon-transfer' },
-          { path: '/pages/customer/trade-position', label: 'Ib.Report.Tit4', icon: 'icon-transfer' },
-          { path: '/pages/customer/recording-history', label: 'Home.page_customer.item7', icon: 'icon-application' },
-        ],
-      },
-      {
-        isOpenMenu: false, submenuHeight: 0,
-        path: '/pages/customer/index', label: 'Latest.PaymentWallet', icon: 'crm-payment',
-        children: [
-          { path: '/pages/customer/deposit', label: 'Home.page_customer.item2', icon: 'icon-deposit' },
-          { path: '/pages/customer/withdrawal', label: 'Home.page_customer.item3', icon: 'icon-withdrawal' },
-          { path: '/pages/customer/payment-history', label: 'Home.page_customer.item4', icon: 'icon-payment' },
-          { path: '/pages/customer/transfer', label: 'Custom.Index.Transfer', icon: 'icon-transfer' },
-        ],
-      },
-      {
-        isOpenMenu: false,
-        path: '/pages/ib/index', label: 'Home.msg.Ib', icon: 'crm-ib',
-        children: [
-          { path: '/pages/ib/index', label: 'Home.page_ib.item1', icon: 'icon-client' },
-          //  客户管理
-          { path: '/pages/ib/customer', label: 'Home.page_ib.item2', icon: 'icon-deposit' },
-          //代理管理
-          { path: '/pages/ib/subsList', label: 'Home.page_ib.item12', icon: 'icon-deposit' },
-          // 信号源列表
-          { path: '/pages/ib/agentList', label: 'Home.page_ib.item11', icon: 'icon-deposit' },
-          // 账户管理
-          { path: '/pages/ib/accountList', label: 'Home.page_ib.item10', icon: 'icon-deposit' },
-          { path: '/pages/ib/report', label: 'Home.page_ib.item3', icon: 'icon-withdrawal' },
-          { path: '/pages/ib/transfer', label: 'Home.page_ib.item4', icon: 'icon-payment' },
-          { path: '/pages/ib/withdraw', label: 'Home.page_ib.item5', icon: 'icon-transfer' },
-          { path: '/pages/ib/agent-transfer', label: 'Home.page_ib.item9', icon: 'icon-transfer' },
-          { path: '/pages/ib/recording', label: 'Home.page_ib.item7', icon: 'icon-application' },
-        ],
-      },
-      {
-        path: '/pages/analytics/analystViews', isOpenMenu: false, label: 'News.News', icon: 'crm-chart-area',
-        children: [
-          { path: '/pages/analytics/analystViews', label: 'News.Announcement', icon: 'icon-application' },
-          { path: '/pages/analytics/news', label: 'News.NewsInformation', icon: 'icon-application' },
-          {
-            path: `https://www.${Config.host}.com/${locale.value}/economic-calendar`,
-            label: 'News.FinancialCalendar',
-            icon: 'icon-application',
-            isExternal: true,
-          },
-        ],
-      },
-      {
-        path: '/pages/customer/withdrawal', isOpenMenu: false, label: 'Downloadpage.item1', icon: 'crm-download',
-        children: [],
-      },
-      {
-        path: '/pages/common/chat', isOpenMenu: false, label: 'Downloadpage.item16', icon: 'crm-headset',
-        children: [],
-        type: 'chat',
-      },
-      {
-        path: '/pages/customer/support', isOpenMenu: false, label: 'Custom.Index.Settings', icon: 'crm-sz',
-        children: [
-          {
-            path: '/pages/mine/info?type=1',
-            isOpenMenu: false,
-            label: 'PersonalManagement.Title.PersonalInformation',
-            icon: 'crm-headset',
-          },
-          {
-            path: '/pages/mine/info?type=2',
-            isOpenMenu: false,
-            label: 'PersonalManagement.Title.BankInformation',
-            icon: 'crm-headset',
-          },
-          {
-            path: '/pages/mine/info?type=3',
-            isOpenMenu: false,
-            label: 'PersonalManagement.Title.FileManagement',
-            icon: 'crm-headset',
-          },
-          {
-            path: '/pages/mine/info?type=4',
-            isOpenMenu: false,
-            label: 'PersonalManagement.Title.SecurityCenter',
-            icon: 'crm-headset',
-          },
-          { path: '/pages/common/notice', isOpenMenu: false, label: 'News.Notice', icon: 'crm-headset' },
-        ],
-      },
-    ])
-  const submenuRefs = ref<any[]>([])
-  const measureHeight = (element: HTMLElement): number => {
-    const originalDisplay = element.style.display
-    const originalPosition = element.style.position
-    const originalVisibility = element.style.visibility
-    const originalWidth = element.style.width
-    element.style.display = 'block'
-    element.style.position = 'absolute'
-    element.style.visibility = 'hidden'
-    element.style.width = '100%'
-    const height = element.scrollHeight || element.offsetHeight
-    element.style.display = originalDisplay
-    element.style.position = originalPosition
-    element.style.visibility = originalVisibility
-    element.style.width = originalWidth
-
-    return height
-  }
-  const updateSubmenuHeight = (index: number) => {
-    const refs = submenuRefs.value
-    if (refs && refs[index]) {
-      const el = refs[index].$el || refs[index]
-      const height = measureHeight(el)
-      if (height > 0) {
-        menu.value[index].submenuHeight = height
-      }
-    }
+  .menu-list {
+    flex: 1;
+    width: 100%;
+    overflow-y: auto;
+    display: flex;
+    flex-direction: column;
+    gap: px2rpx(8);
   }
 
-  function handleClick(index: number) {
-    if (!menu.value[index].children || menu.value[index].children.length == 0) {
-      // #ifdef H5
-      if (menu.value[index].type === 'chat') {
-        if (window.LiveChatWidget) {
-          window.LiveChatWidget.call('maximize')
-        }
-      } else {
-        router.push(menu.value[index].path)
-      }
-      // #endif
-
-      // #ifdef APP-VUE
-      router.push(menu.value[index].path)
-      // #endif
-      return
-    }
-    menu.value[index].isOpenMenu = !menu.value[index].isOpenMenu
+  .submenu-box {
+    width: 100%;
+    height: 0;
+    overflow: hidden;
   }
 
-  watch(route, () => {
-    const currentPath = route.path
-    menu.value.forEach((item, index) => {
-      if (item.children) {
-        const isActive = item.children.some(child => child.path.includes(currentPath))
-        menu.value[index].isOpenMenu = isActive
-        if (isActive) {
-          nextTick(() => {
-            updateSubmenuHeight(index)
-          })
-        }
-      }
-    })
-  }, { immediate: true })
+  .menu {
+    width: 100%;
+    position: relative;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    box-sizing: border-box;
 
-  // 添加窗口大小变化监听
-  const handleResize = () => {
-    menu.value.forEach((item, index) => {
-      if (item.children) {
-        updateSubmenuHeight(index)
-      }
-    })
   }
 
-  onMounted(() => {
-    nextTick(() => {
-      menu.value.forEach((item, index) => {
-        if (item.children) {
-          updateSubmenuHeight(index)
-        }
-      })
-    })
-
-    // 添加窗口resize监听
-    window.addEventListener('resize', handleResize)
-  })
-
-  // 组件卸载时移除监听
-  onBeforeUnmount(() => {
-    window.removeEventListener('resize', handleResize)
-  })
-</script>
-
-<style scoped lang="scss">
-  @import "@/uni.scss";
-
-  .cwg-sidebar {
+  .menu-item {
     width: 100%;
-    color: #6c8595;
-    height: calc(100vh - 56px);
-    overflow: auto;
+    height: px2rpx(40);
+    cursor: pointer;
     display: flex;
-    flex-direction: column;
     align-items: center;
-    padding: px2rpx(8);
-    padding-top: px2rpx(20);
-    box-sizing: border-box;
+    justify-content: space-between;
     gap: px2rpx(8);
-    border-right: 1px solid rgba(108, 133, 149, 0.12);
+    padding: px2rpx(10);
+    box-sizing: border-box;
+    font-size: 14px;
 
-    .logo {
-      width: px2rpx(54);
+    .menu-label {
+      flex: 1;
     }
 
-    .submenu-box {
-      width: 100%;
-      height: 0;
-      overflow: hidden;
+    &:hover {
+      background: rgba(108, 133, 149, 0.12) !important;
+      border: 1px solid rgb(145, 163, 176) !important;
+      border-radius: px2rpx(4);
     }
 
-    .menu {
-      width: 100%;
-      position: relative;
-      display: flex;
-      flex-direction: column;
-      align-items: center;
-      box-sizing: border-box;
-
+    .expanded .icon {
+      transform: rotate(180deg);
     }
+  }
 
-    .menu-item {
-      width: 100%;
-      height: px2rpx(40);
-      cursor: pointer;
-      display: flex;
-      align-items: center;
-      justify-content: space-between;
-      gap: px2rpx(8);
-      padding: px2rpx(10);
-      box-sizing: border-box;
-      font-size: 14px;
+  .ib-box {
+    background: rgba(140, 69, 246, 0.08) !important;
+    border: 1px solid rgba(140, 69, 246, 0.2) !important;
+    font-size: px2rpx(18);
+    font-weight: 600;
+    color: #141d22;
 
-      .menu-label {
-        flex: 1;
-      }
+    &:hover {
+      background: rgba(140, 69, 246, 0.08) !important;
+      border: 1px solid rgba(140, 69, 246, 0.2) !important;
+    }
+  }
 
-      &:hover {
-        background: rgba(108, 133, 149, 0.12) !important;
-        border: 1px solid rgb(145, 163, 176) !important;
-        border-radius: px2rpx(4);
-      }
+  .zy-box {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
 
-      .expanded .icon {
-        transform: rotate(180deg);
-      }
-    }
+  .fixed {
+    position: relative;
+    width: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: px2rpx(8);
   }
+}
 
-  @media screen and (max-width: 1100px) {
+@media screen and (max-width: 1100px) {
 
-    .cwg-sidebar:not(:hover) .menu-label,
-    .cwg-sidebar:not(:hover) .submenu-box,
-    .cwg-sidebar:not(:hover) .chevron-icon {
-      display: none;
-    }
+  .cwg-sidebar:not(:hover) .menu-label,
+  .cwg-sidebar:not(:hover) .submenu-box,
+  .cwg-sidebar:not(:hover) .chevron-icon {
+    display: none;
+  }
 
-    .cwg-sidebar:hover .menu-label,
-    .cwg-sidebar:hover .submenu-box,
-    .cwg-sidebar:hover .chevron-icon {
-      display: block;
-    }
+  .cwg-sidebar:hover .menu-label,
+  .cwg-sidebar:hover .submenu-box,
+  .cwg-sidebar:hover .chevron-icon {
+    display: block;
+  }
+
+  .cwg-sidebar .fixed {
+    display: flex;
   }
+}
 </style>