feat: 添加PageContainer;完善动态路由
This commit is contained in:
parent
92c4901750
commit
6d701c5204
|
@ -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>
|
||||||
|
|
|
@ -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%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
|
@ -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'
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) });
|
||||||
|
|
|
@ -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/, '')
|
||||||
|
|
Loading…
Reference in New Issue