2026-07-23 11:27:33 +08:00
|
|
|
import { computed, defineComponent, h, ref } from 'vue';
|
2026-07-14 10:31:17 +08:00
|
|
|
import { useRouter, useRoute, RouterView } from 'vue-router';
|
|
|
|
|
import { Layout, Menu } from 'ant-design-vue';
|
|
|
|
|
import type { MenuProps } from 'ant-design-vue';
|
|
|
|
|
import {
|
|
|
|
|
DashboardOutlined,
|
|
|
|
|
InfoCircleOutlined,
|
|
|
|
|
MenuFoldOutlined,
|
|
|
|
|
MenuUnfoldOutlined,
|
2026-07-23 11:27:33 +08:00
|
|
|
SettingOutlined,
|
2026-07-14 10:31:17 +08:00
|
|
|
} from '@ant-design/icons-vue';
|
2026-07-23 11:27:33 +08:00
|
|
|
import { useMenuStore } from '@/stores/menuStore';
|
2026-07-14 10:31:17 +08:00
|
|
|
import styles from './BasicLayout.module.less';
|
|
|
|
|
|
|
|
|
|
const { Header, Sider, Content, Footer } = Layout;
|
|
|
|
|
|
|
|
|
|
/**
|
2026-07-23 11:27:33 +08:00
|
|
|
* 图标名 → 组件映射
|
|
|
|
|
* 后端下发的 icon 字符串在此映射为 antd icon 组件
|
|
|
|
|
* 扩展新图标只需在此添加一行
|
|
|
|
|
*/
|
|
|
|
|
const ICON_MAP: Record<string, any> = {
|
|
|
|
|
DashboardOutlined,
|
|
|
|
|
InfoCircleOutlined,
|
|
|
|
|
SettingOutlined,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 将 menuStore 中的 menuItems 转为 antd Menu 的 items 格式
|
|
|
|
|
* 递归处理 iconName → icon VNode
|
|
|
|
|
*/
|
|
|
|
|
function resolveMenuItems(items: any[]): MenuProps['items'] {
|
|
|
|
|
return items.map((item) => {
|
|
|
|
|
const resolved: any = {
|
|
|
|
|
key: item.key,
|
|
|
|
|
label: item.label,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// iconName 字符串转为 VNode
|
|
|
|
|
if (item.iconName && ICON_MAP[item.iconName]) {
|
|
|
|
|
resolved.icon = () => h(ICON_MAP[item.iconName]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (item.children?.length) {
|
|
|
|
|
resolved.children = resolveMenuItems(item.children);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return resolved;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 基础布局:左侧导航(后端菜单驱动) + 顶部栏 + 内容区 + 页脚
|
2026-07-14 10:31:17 +08:00
|
|
|
*/
|
|
|
|
|
export default defineComponent({
|
|
|
|
|
name: 'BasicLayout',
|
|
|
|
|
setup() {
|
|
|
|
|
const route = useRoute();
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
const collapsed = ref(false);
|
|
|
|
|
|
2026-07-23 11:27:33 +08:00
|
|
|
const { menuItems } = useMenuStore();
|
|
|
|
|
|
2026-07-14 10:31:17 +08:00
|
|
|
const selectedKeys = computed<string[]>(() => [route.path]);
|
|
|
|
|
|
2026-07-23 11:27:33 +08:00
|
|
|
const resolvedItems = computed(() => resolveMenuItems(menuItems.value));
|
2026-07-14 10:31:17 +08:00
|
|
|
|
|
|
|
|
const handleMenuClick: MenuProps['onClick'] = ({ key }) => {
|
|
|
|
|
router.push(key as string);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const toggleCollapsed = () => {
|
|
|
|
|
collapsed.value = !collapsed.value;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return () => (
|
|
|
|
|
<Layout class={styles.container}>
|
|
|
|
|
<Sider class={styles.sider} collapsed={collapsed.value} trigger={null} collapsible>
|
|
|
|
|
<div class={styles.logo}>
|
|
|
|
|
<span class={styles.logoText}>{collapsed.value ? 'CPMS' : 'CPMS 运营平台'}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<Menu
|
|
|
|
|
theme="dark"
|
|
|
|
|
mode="inline"
|
|
|
|
|
selectedKeys={selectedKeys.value}
|
2026-07-23 11:27:33 +08:00
|
|
|
items={resolvedItems.value}
|
2026-07-14 10:31:17 +08:00
|
|
|
onClick={handleMenuClick}
|
|
|
|
|
/>
|
|
|
|
|
</Sider>
|
|
|
|
|
|
|
|
|
|
<Layout>
|
|
|
|
|
<Header class={styles.header}>
|
|
|
|
|
<span class={styles.trigger} onClick={toggleCollapsed}>
|
|
|
|
|
{collapsed.value ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
|
|
|
|
</span>
|
|
|
|
|
</Header>
|
|
|
|
|
|
|
|
|
|
<Content class={styles.content}>
|
|
|
|
|
<RouterView />
|
|
|
|
|
</Content>
|
|
|
|
|
|
|
|
|
|
<Footer class={styles.footer}>CPMS 运营平台 ©{new Date().getFullYear()}</Footer>
|
|
|
|
|
</Layout>
|
|
|
|
|
</Layout>
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
});
|