feat: 添加PageContainer;完善动态路由
This commit is contained in:
parent
92c4901750
commit
6d701c5204
|
@ -5,7 +5,12 @@
|
|||
v-model:collapsed="state.collapsed"
|
||||
v-model:selectedKeys="state.selectedKeys"
|
||||
: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}'>
|
||||
<component :is='Component' />
|
||||
</router-view>
|
||||
|
@ -25,6 +30,7 @@ type StateType = {
|
|||
}
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const menu = useMenuStore()
|
||||
|
||||
|
@ -43,6 +49,16 @@ const state = reactive<StateType>({
|
|||
selectedKeys: [],
|
||||
});
|
||||
|
||||
const breadcrumb = computed(() =>
|
||||
router.currentRoute.value.matched.concat().map((item, index) => {
|
||||
return {
|
||||
index,
|
||||
path: item.path,
|
||||
breadcrumbName: item.meta.title || ''
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
watchEffect(() => {
|
||||
if (router.currentRoute) {
|
||||
const matched = router.currentRoute.value.matched.concat()
|
||||
|
@ -53,6 +69,14 @@ watchEffect(() => {
|
|||
// TODO 获取当前路由中参数,用于控制pure
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
if (route.query && 'layout' in route.query && route.query.layout === 'false') {
|
||||
state.pure = true
|
||||
} else {
|
||||
state.pure = false
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<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 BasicLayoutPage } from './BasicLayoutPage.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 FileFormat from './FileFormat/index.vue'
|
||||
import JUpload from './JUpload/index.vue'
|
||||
import { BasicLayoutPage, BlankLayoutPage } from './Layout'
|
||||
import { BasicLayoutPage, BlankLayoutPage, PageContainer } from './Layout'
|
||||
|
||||
export default {
|
||||
install(app: App) {
|
||||
|
@ -25,5 +25,6 @@ export default {
|
|||
.component('JUpload', JUpload)
|
||||
.component('BasicLayoutPage', BasicLayoutPage)
|
||||
.component('BlankLayoutPage', BlankLayoutPage)
|
||||
.component('PageContainer', PageContainer)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,13 +52,13 @@ export const useMenuStore = defineStore({
|
|||
const resp = await queryOwnThree({ paging: false, terms: params })
|
||||
if (resp.success) {
|
||||
const menus = filterAsnycRouter(resp.result)
|
||||
// menus.push({
|
||||
// path: '/',
|
||||
// redirect: menus[0]?.path,
|
||||
// meta: {
|
||||
// hideInMenu: true
|
||||
// }
|
||||
// })
|
||||
menus.push({
|
||||
path: '/',
|
||||
redirect: menus[0]?.path,
|
||||
meta: {
|
||||
hideInMenu: true
|
||||
}
|
||||
})
|
||||
this.menus = menus
|
||||
res(menus)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<a-card class="basis-container">
|
||||
<a-form
|
||||
layout="vertical"
|
||||
|
@ -277,6 +278,7 @@
|
|||
>保存</a-button
|
||||
>
|
||||
</a-card>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<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 { usePermissionStore } from '@/store/permission';
|
||||
import PageContainer from 'components/Layout/components/PageContainer'
|
||||
|
||||
const action = ref<string>(`${BASE_API_PATH}/file/static`);
|
||||
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.32.244:8881',
|
||||
// target: 'http://47.112.135.104:5096', // opcua
|
||||
// target: 'http://120.77.179.54:8844', // 120测试
|
||||
target: 'http://47.108.63.174:8845', // 测试
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api/, '')
|
||||
|
|
Loading…
Reference in New Issue