feat: 新增页面标签页组件
This commit is contained in:
@@ -113,3 +113,21 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
// ── 顶部行:原 Header 中的三个区域(折叠按钮 / 面包屑 / 右侧账号信息)整体包成一个 row ──
|
||||
.headerRow {
|
||||
padding: 0 16px;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
// ── 页面标签栏容器(Chrome 风格 PageTabs 的外壳) ──
|
||||
.pageTabs {
|
||||
width: 100%;
|
||||
padding: 4px 16px 0 16px;
|
||||
height: 42px;
|
||||
background: #fff;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
+98
-47
@@ -1,5 +1,6 @@
|
||||
import { computed, defineComponent, h, ref } from 'vue';
|
||||
import type { CSSProperties } from 'vue';
|
||||
import { KeepAlive } from 'vue';
|
||||
import { useRouter, useRoute, RouterView } from 'vue-router';
|
||||
import { Layout, Menu, Modal, Breadcrumb, Dropdown, message } from 'ant-design-vue';
|
||||
import type { MenuProps } from 'ant-design-vue';
|
||||
@@ -16,8 +17,9 @@ import {
|
||||
} from '@ant-design/icons-vue';
|
||||
import { useMenuStore } from '@/stores/menuStore';
|
||||
import { useUserStore } from '@/stores/userStore';
|
||||
import { useTabsStore } from '@/stores/tabsStore';
|
||||
import { useState, useBreadcrumb, auth } from '@/hooks';
|
||||
import { ChangePasswordModal } from '@/components';
|
||||
import { ChangePasswordModal, PageTabs } from '@/components';
|
||||
import logoutSvg from '@/assets/img/icon/logout.svg';
|
||||
import styles from './BasicLayout.module.less';
|
||||
|
||||
@@ -25,11 +27,13 @@ const { Header, Sider, Content, Footer } = Layout;
|
||||
|
||||
const headerStyle: CSSProperties = {
|
||||
background: '#fff',
|
||||
padding: '0 16px',
|
||||
padding: '0',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
color: '#1a1a1a',
|
||||
boxShadow: '0 1px 4px rgba(0, 21, 41, 0.08)',
|
||||
height: 'auto',
|
||||
};
|
||||
|
||||
const siderStyle: CSSProperties = {
|
||||
@@ -93,6 +97,7 @@ export default defineComponent({
|
||||
|
||||
const { menuItems } = useMenuStore();
|
||||
const { phone } = useUserStore();
|
||||
const { tabs, cachedViews, removeTab } = useTabsStore();
|
||||
const { breadcrumbs } = useBreadcrumb();
|
||||
|
||||
const selectedKeys = computed<string[]>(() => [route.path]);
|
||||
@@ -107,6 +112,8 @@ export default defineComponent({
|
||||
collapsed.value = !collapsed.value;
|
||||
};
|
||||
|
||||
const activeTabKey = computed(() => route.path);
|
||||
|
||||
/** ===== 退出登录 ===== */
|
||||
const handleLogout = () => {
|
||||
Modal.confirm({
|
||||
@@ -154,6 +161,17 @@ export default defineComponent({
|
||||
}
|
||||
};
|
||||
|
||||
/** ===== PageTabs 事件 ===== */
|
||||
const handleTabChange = (key: string) => {
|
||||
if (key !== route.path) {
|
||||
router.push(key);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTabClose = (key: string) => {
|
||||
removeTab(key);
|
||||
};
|
||||
|
||||
return () => (
|
||||
<Layout class={styles.container}>
|
||||
<Sider style={siderStyle} collapsed={collapsed.value} trigger={null} collapsible>
|
||||
@@ -171,56 +189,89 @@ export default defineComponent({
|
||||
|
||||
<Layout class={styles.main}>
|
||||
<Header style={headerStyle}>
|
||||
<span class={styles.trigger} onClick={toggleCollapsed}>
|
||||
{collapsed.value ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
||||
</span>
|
||||
{/*
|
||||
── 顶部行:折叠按钮 + 面包屑 + 右侧账号信息 ──
|
||||
按需求把原来平铺在 Header 里的三块统一包到一个 row 容器内,
|
||||
形成「行容器 + tabs 容器」的两层结构
|
||||
*/}
|
||||
<div class={styles.headerRow}>
|
||||
<span class={styles.trigger} onClick={toggleCollapsed}>
|
||||
{collapsed.value ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
||||
</span>
|
||||
|
||||
{/* 面包屑:紧跟在折叠按钮之后 */}
|
||||
<Breadcrumb separator=">" class={styles.breadcrumb}>
|
||||
{breadcrumbs.value.map((item, index) => {
|
||||
const isLast = index === breadcrumbs.value.length - 1;
|
||||
return (
|
||||
<Breadcrumb.Item key={item.path || index}>
|
||||
{isLast || !item.path ? (
|
||||
<span>{item.title}</span>
|
||||
) : (
|
||||
<a onClick={() => router.push(item.path as string)}>{item.title}</a>
|
||||
)}
|
||||
</Breadcrumb.Item>
|
||||
);
|
||||
})}
|
||||
</Breadcrumb>
|
||||
{/* 面包屑:紧跟在折叠按钮之后 */}
|
||||
<Breadcrumb separator=">" class={styles.breadcrumb}>
|
||||
{breadcrumbs.value.map((item, index) => {
|
||||
const isLast = index === breadcrumbs.value.length - 1;
|
||||
return (
|
||||
<Breadcrumb.Item key={item.path || index}>
|
||||
{isLast || !item.path ? (
|
||||
<span>{item.title}</span>
|
||||
) : (
|
||||
<a onClick={() => router.push(item.path as string)}>{item.title}</a>
|
||||
)}
|
||||
</Breadcrumb.Item>
|
||||
);
|
||||
})}
|
||||
</Breadcrumb>
|
||||
|
||||
{/* 右侧: 账号信息 + 退出 */}
|
||||
<div class={styles.headerRight}>
|
||||
<span class={styles.userPhone}>{phone.value}</span>
|
||||
<Dropdown trigger={['hover']} placement="bottomLeft">
|
||||
{{
|
||||
default: () => <img src={logoutSvg} class={styles.logoutIcon} />,
|
||||
overlay: () => (
|
||||
<Menu onClick={handleUserMenuClick}>
|
||||
<Menu.Item key="changePassword">
|
||||
<span class={styles.menuItem}>
|
||||
<EditOutlined />
|
||||
修改密码
|
||||
</span>
|
||||
</Menu.Item>
|
||||
<Menu.Divider />
|
||||
<Menu.Item key="logout">
|
||||
<span class={`${styles.menuItem} ${styles.menuItemDanger}`}>
|
||||
<LogoutOutlined />
|
||||
退出登录
|
||||
</span>
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
),
|
||||
}}
|
||||
</Dropdown>
|
||||
{/* 右侧: 账号信息 + 退出 */}
|
||||
<div class={styles.headerRight}>
|
||||
<span class={styles.userPhone}>{phone.value}</span>
|
||||
<Dropdown trigger={['hover']} placement="bottomLeft">
|
||||
{{
|
||||
default: () => <img src={logoutSvg} class={styles.logoutIcon} />,
|
||||
overlay: () => (
|
||||
<Menu onClick={handleUserMenuClick}>
|
||||
<Menu.Item key="changePassword">
|
||||
<span class={styles.menuItem}>
|
||||
<EditOutlined />
|
||||
修改密码
|
||||
</span>
|
||||
</Menu.Item>
|
||||
<Menu.Divider />
|
||||
<Menu.Item key="logout">
|
||||
<span class={`${styles.menuItem} ${styles.menuItemDanger}`}>
|
||||
<LogoutOutlined />
|
||||
退出登录
|
||||
</span>
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
),
|
||||
}}
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/*
|
||||
── Tabs 行:Chrome 风格页面标签栏 ──
|
||||
数据来自 tabsStore,点击切换路由,关闭按钮走 store.removeTab
|
||||
*/}
|
||||
<div class={styles.pageTabs}>
|
||||
<PageTabs
|
||||
tabs={tabs.value}
|
||||
activeKey={activeTabKey.value}
|
||||
onChange={handleTabChange}
|
||||
onClose={handleTabClose}
|
||||
/>
|
||||
</div>
|
||||
</Header>
|
||||
|
||||
{/*
|
||||
── 路由出口 ──
|
||||
用 <KeepAlive :include="cachedViews"> 缓存已访问过的页面,
|
||||
include 是组件 name 列表,所以页面组件需要在 defineComponent 中显式给出 name
|
||||
*/}
|
||||
<Content class={styles.content}>
|
||||
<RouterView />
|
||||
<RouterView
|
||||
v-slots={{
|
||||
default: ({ Component }: any) => (
|
||||
<KeepAlive include={cachedViews.value}>
|
||||
<Component />
|
||||
</KeepAlive>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Content>
|
||||
|
||||
<Footer style={footerStyle}>六个羽友赛事运营平台 ©{new Date().getFullYear()}</Footer>
|
||||
|
||||
Reference in New Issue
Block a user