feat: 添加PageContainer;完善动态路由

This commit is contained in:
xieyonghong 2023-01-30 16:52:33 +08:00
parent 92c4901750
commit 6d701c5204
9 changed files with 366 additions and 9 deletions

View File

@ -5,7 +5,12 @@
v-model:collapsed="state.collapsed" v-model:collapsed="state.collapsed"
v-model:selectedKeys="state.selectedKeys" v-model:selectedKeys="state.selectedKeys"
:pure='state.pure' :pure='state.pure'
:breadcrumb='{ routes: breadcrumb }'
> >
<template #breadcrumbRender='slotProps'>
<a v-if='slotProps.route.index !== 0'>{{slotProps.route.breadcrumbName}}</a>
<span v-else>{{slotProps.route.breadcrumbName}}</span>
</template>
<router-view v-slot='{ Component}'> <router-view v-slot='{ Component}'>
<component :is='Component' /> <component :is='Component' />
</router-view> </router-view>
@ -25,6 +30,7 @@ type StateType = {
} }
const router = useRouter() const router = useRouter()
const route = useRoute()
const menu = useMenuStore() const menu = useMenuStore()
@ -43,6 +49,16 @@ const state = reactive<StateType>({
selectedKeys: [], selectedKeys: [],
}); });
const breadcrumb = computed(() =>
router.currentRoute.value.matched.concat().map((item, index) => {
return {
index,
path: item.path,
breadcrumbName: item.meta.title || ''
}
})
)
watchEffect(() => { watchEffect(() => {
if (router.currentRoute) { if (router.currentRoute) {
const matched = router.currentRoute.value.matched.concat() const matched = router.currentRoute.value.matched.concat()
@ -53,6 +69,14 @@ watchEffect(() => {
// TODO pure // TODO pure
}) })
watchEffect(() => {
if (route.query && 'layout' in route.query && route.query.layout === 'false') {
state.pure = true
} else {
state.pure = false
}
})
</script> </script>
<style scoped> <style scoped>

View File

@ -0,0 +1,23 @@
.page-container {
min-height: 100%;
display: flex;
flex-direction: column;
.page-container-grid-content {
padding: 24px;
flex: 1 1 auto;
display: flex;
flex-direction: column;
> div, .page-container-children-content, .page-container-full-height {
min-height: 100%;
display: flex;
flex-direction: column;
}
.children-full-height {
> :nth-child(1) {
min-height: 100%;
}
}
}
}

View File

@ -0,0 +1,248 @@
import { TabPaneProps } from 'ant-design-vue'
import type { ExtractPropTypes, FunctionalComponent, PropType, VNodeChild } from 'vue'
import { pageHeaderProps } from 'ant-design-vue/es/page-header';
import type { DefaultPropRender, PageHeaderRender } from 'components/Layout/typings'
import type { AffixProps, TabBarExtraContent } from 'components/Layout/components/PageContainer/types'
import { useRouteContext } from 'components/Layout/RouteContext'
import { getSlotVNode } from '@/utils/comm'
import { Affix, Spin, PageHeader, Tabs } from 'ant-design-vue';
import './index.less'
export const pageHeaderTabConfig = {
/**
* @name tabs
*/
tabList: {
type: [Object, Function, Array] as PropType<(Omit<TabPaneProps, 'id'> & { key?: string })[]>,
default: () => undefined,
},
/**
* @name tab key
*/
tabActiveKey: String, //PropTypes.string,
/**
* @name tab
*/
tabBarExtraContent: {
type: [Object, Function] as PropType<TabBarExtraContent>,
default: () => undefined,
},
/**
* @name tabs
*/
tabProps: {
type: Object, //as PropType<TabsProps>,
default: () => undefined,
},
/**
* @name PageHeader
*/
fixedHeader: Boolean, //PropTypes.looseBool,
// events
onTabChange: Function, //PropTypes.func,
};
export type PageHeaderTabConfig = Partial<ExtractPropTypes<typeof pageHeaderTabConfig>>;
export const pageContainerProps = {
...pageHeaderTabConfig,
...pageHeaderProps,
prefixCls: {
type: String,
default: 'ant-pro',
}, //PropTypes.string.def('ant-pro'),
title: {
type: [Object, String, Boolean, Function] as PropType<DefaultPropRender>,
default: () => null,
},
subTitle: {
type: [Object, String, Boolean, Function] as PropType<DefaultPropRender>,
default: () => null,
},
content: {
type: [Object, String, Boolean, Function] as PropType<DefaultPropRender>,
default: () => null,
},
extra: {
type: [Object, String, Boolean, Function] as PropType<DefaultPropRender>,
default: () => null,
},
extraContent: {
type: [Object, String, Boolean, Function] as PropType<DefaultPropRender>,
default: () => null,
},
header: {
type: [Object, String, Boolean, Function] as PropType<DefaultPropRender>,
default: () => null,
},
pageHeaderRender: {
type: [Object, Function, Boolean] as PropType<PageHeaderRender>,
default: () => undefined,
},
affixProps: {
type: [Object, Function] as PropType<AffixProps>,
},
ghost: {
type: Boolean,
default: () => false,
}, //PropTypes.looseBool,
loading: {
type: Boolean,
default: () => undefined,
}, //PropTypes.looseBool,
childrenFullHeight: {
type: Boolean,
default: () => true,
}
};
export type PageContainerProps = Partial<ExtractPropTypes<typeof pageContainerProps>>;
const renderFooter = (
props: Omit< PageContainerProps, 'title' >
): VNodeChild | JSX.Element => {
const { tabList, tabActiveKey, onTabChange, tabBarExtraContent, tabProps } = props;
if (tabList && tabList.length) {
return (
<Tabs
class={`page-container-tabs`}
activeKey={tabActiveKey}
onChange={(key: string | number) => {
if (onTabChange) {
onTabChange(key);
}
}}
tabBarExtraContent={tabBarExtraContent}
{...tabProps}
>
{tabList.map((item) => (
<Tabs.TabPane {...item} tab={item.tab} key={item.key} />
))}
</Tabs>
);
}
return null;
}
const ProPageHeader: FunctionalComponent<PageContainerProps> = (props) => {
const {
title,
tabList,
tabActiveKey,
content,
pageHeaderRender,
header,
extraContent,
prefixCls,
fixedHeader: _,
...restProps
} = props;
const value = useRouteContext()
if (pageHeaderRender === false) {
return null;
}
if (pageHeaderRender) {
return pageHeaderRender({ ...props });
}
let pageHeaderTitle = title;
if (!title && title !== false) {
pageHeaderTitle = value.title;
}
const unrefBreadcrumb = unref(value.breadcrumb || {});
const breadcrumb = (props as any).breadcrumb || {
...unrefBreadcrumb,
routes: unrefBreadcrumb.routes,
itemRender: unrefBreadcrumb.itemRender,
};
return (
<div class={`page-container-wrap`}>
<PageHeader
{...restProps}
// {...value}
title={pageHeaderTitle}
breadcrumb={breadcrumb}
footer={renderFooter({
...restProps,
tabList,
tabActiveKey
})}
prefixCls={prefixCls}
>
{/*{header || renderPageHeader(content, extraContent)}*/}
{ header }
</PageHeader>
</div>
);
}
const PageContainer = defineComponent({
name: 'PageContainer',
inheritAttrs: false,
props: pageContainerProps,
setup(props, { slots }) {
const { loading, affixProps, ghost, childrenFullHeight } = toRefs(props);
const value = useRouteContext();
const headerDom = computed(() => {
// const tags = getSlotVNode<DefaultPropRender>(slots, props, 'tags');
const headerContent = getSlotVNode<DefaultPropRender>(slots, props, 'content');
const extra = getSlotVNode<DefaultPropRender>(slots, props, 'extra');
const extraContent = getSlotVNode<DefaultPropRender>(slots, props, 'extraContent');
const subTitle = getSlotVNode<DefaultPropRender>(slots, props, 'subTitle');
// @ts-ignore
return (
<ProPageHeader
{...props}
prefixCls={undefined}
ghost={ghost.value}
subTitle={subTitle}
content={headerContent}
// tags={tags}
extra={extra}
extraContent={extraContent}
/>
);
})
return () => {
const { fixedHeader } = props;
return (
<div class={'page-container'}>
{fixedHeader && headerDom.value ? (
<Affix {...affixProps.value} offsetTop={value.hasHeader && value.fixedHeader ? value.headerHeight : 0}>
{headerDom.value}
</Affix>
) : (
headerDom.value
)}
<div class={'page-container-grid-content'}>
{loading.value ? (
<Spin />
) : slots.default ? (
<div>
<div class={`page-container-children-content ${childrenFullHeight.value ? 'children-full-height' : ''}`}>{slots.default()}</div>
{value.hasFooterToolbar && (
<div
style={{
height: 48,
marginTop: 24,
}}
/>
)}
</div>
) : null}
</div>
</div>
);
};
}
})
export default PageContainer

View File

@ -0,0 +1,56 @@
import type { VNodeChild, CSSProperties, VNode } from 'vue';
export interface Tab {
key: string;
tab: string | VNode | JSX.Element;
}
export type TabBarType = 'line' | 'card' | 'editable-card';
export type TabSize = 'default' | 'large' | 'small';
export type TabPosition = 'left' | 'right';
export type TabBarExtraPosition = TabPosition;
export type TabBarExtraMap = Partial<Record<TabBarExtraPosition, VNodeChild>>;
export type TabBarExtraContent = VNodeChild | TabBarExtraMap;
export interface TabsProps {
prefixCls?: string;
class?: string | string[];
style?: CSSProperties;
id?: string;
activeKey?: string;
hideAdd?: boolean;
// Unchangeable
// size?: TabSize;
tabBarStyle?: CSSProperties;
tabPosition?: TabPosition;
type?: TabBarType;
tabBarGutter?: number;
}
export interface AffixProps {
offsetBottom: number;
offsetTop: number;
target?: () => HTMLElement;
onChange?: (affixed: boolean) => void;
}
export interface TabPaneProps {
tab?: string | VNodeChild | JSX.Element;
class?: string | string[];
style?: CSSProperties;
disabled?: boolean;
forceRender?: boolean;
closable?: boolean;
closeIcon?: VNodeChild | JSX.Element;
prefixCls?: string;
tabKey?: string;
id: string;
animated?: boolean;
active?: boolean;
destroyInactiveTabPane?: boolean;
}

View File

@ -1,3 +1,4 @@
export { default as ProLayout } from './BasicLayout'; export { default as ProLayout } from './BasicLayout';
export { default as BasicLayoutPage } from './BasicLayoutPage.vue' export { default as BasicLayoutPage } from './BasicLayoutPage.vue'
export { default as BlankLayoutPage } from './BlankLayoutPage.vue' export { default as BlankLayoutPage } from './BlankLayoutPage.vue'
export { default as PageContainer } from './components/PageContainer'

View File

@ -9,7 +9,7 @@ import Search from './Search'
import NormalUpload from './NormalUpload/index.vue' import NormalUpload from './NormalUpload/index.vue'
import FileFormat from './FileFormat/index.vue' import FileFormat from './FileFormat/index.vue'
import JUpload from './JUpload/index.vue' import JUpload from './JUpload/index.vue'
import { BasicLayoutPage, BlankLayoutPage } from './Layout' import { BasicLayoutPage, BlankLayoutPage, PageContainer } from './Layout'
export default { export default {
install(app: App) { install(app: App) {
@ -25,5 +25,6 @@ export default {
.component('JUpload', JUpload) .component('JUpload', JUpload)
.component('BasicLayoutPage', BasicLayoutPage) .component('BasicLayoutPage', BasicLayoutPage)
.component('BlankLayoutPage', BlankLayoutPage) .component('BlankLayoutPage', BlankLayoutPage)
.component('PageContainer', PageContainer)
} }
} }

View File

@ -52,13 +52,13 @@ export const useMenuStore = defineStore({
const resp = await queryOwnThree({ paging: false, terms: params }) const resp = await queryOwnThree({ paging: false, terms: params })
if (resp.success) { if (resp.success) {
const menus = filterAsnycRouter(resp.result) const menus = filterAsnycRouter(resp.result)
// menus.push({ menus.push({
// path: '/', path: '/',
// redirect: menus[0]?.path, redirect: menus[0]?.path,
// meta: { meta: {
// hideInMenu: true hideInMenu: true
// } }
// }) })
this.menus = menus this.menus = menus
res(menus) res(menus)
} }

View File

@ -1,4 +1,5 @@
<template> <template>
<page-container>
<a-card class="basis-container"> <a-card class="basis-container">
<a-form <a-form
layout="vertical" layout="vertical"
@ -277,6 +278,7 @@
>保存</a-button >保存</a-button
> >
</a-card> </a-card>
</page-container>
</template> </template>
<script setup lang="ts" name="Basis"> <script setup lang="ts" name="Basis">
@ -289,6 +291,7 @@ import { LocalStore } from '@/utils/comm';
import { save_api, getDetails_api } from '@/api/system/basis'; import { save_api, getDetails_api } from '@/api/system/basis';
import { usePermissionStore } from '@/store/permission'; import { usePermissionStore } from '@/store/permission';
import PageContainer from 'components/Layout/components/PageContainer'
const action = ref<string>(`${BASE_API_PATH}/file/static`); const action = ref<string>(`${BASE_API_PATH}/file/static`);
const headers = ref({ [TOKEN_KEY]: LocalStore.get(TOKEN_KEY) }); const headers = ref({ [TOKEN_KEY]: LocalStore.get(TOKEN_KEY) });

View File

@ -82,6 +82,7 @@ export default defineConfig(({ mode}) => {
// target: 'http://192.168.33.22:8800', // target: 'http://192.168.33.22:8800',
// target: 'http://192.168.32.244:8881', // target: 'http://192.168.32.244:8881',
// target: 'http://47.112.135.104:5096', // opcua // target: 'http://47.112.135.104:5096', // opcua
// target: 'http://120.77.179.54:8844', // 120测试
target: 'http://47.108.63.174:8845', // 测试 target: 'http://47.108.63.174:8845', // 测试
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '') rewrite: (path) => path.replace(/^\/api/, '')