import './index.less' import { PageHeader, Tabs, Affix, Breadcrumb } from 'ant-design-vue' import 'ant-design-vue/es/page-header/style/index.less' import 'ant-design-vue/es/tabs/style/index.less' import 'ant-design-vue/es/affix/style/index.less' import 'ant-design-vue/es/breadcrumb/style/index.less' import type { TabsProps, AffixProps, PageHeaderProps, TabPaneProps, SpinProps, BreadcrumbProps } from 'ant-design-vue' import GridContent from '../GridContent' import FooterToolbar from '../FooterToolbar' import PageLoading from '../PageLoading' import type { WithFalse } from '../../types' import type { WaterMarkProps } from '../WaterMark' import WaterMark from '../WaterMark' import type { CSSProperties, ExtractPropTypes, FunctionalComponent, PropType } from 'vue' import type { VueNode } from 'ant-design-vue/es/_util/type' import { getPrefixCls, routeContextInjectKey } from '../../RouteContext' import { reactiveOmit, reactivePick } from '@vueuse/core' import type { VueNodeOrRender } from '#/types' import { VueNodeOrRenderPropType, WithFalseVueNodeOrRenderPropType } from '#/types' import { pageHeaderProps } from 'ant-design-vue/es/page-header' import omit from 'ant-design-vue/es/_util/omit' const antvPageHeaderPropsKeys = Object.keys(pageHeaderProps()) export const pageHeaderTabConfig = () => ({ /** tabs 的列表 */ tabList: Array as PropType<(TabPaneProps & { key?: string | number })[]>, /** 当前选中 tab 的 key */ tabActiveKey: [String, Number] as PropType, /** tab 修改时触发 */ onTabChange: Function as PropType, /** tab 上额外的区域 */ tabBarExtraContent: VueNodeOrRenderPropType as PropType, /** tabs 的其他配置 */ tabProps: Object as PropType, /** 固定 PageHeader 到页面顶部 */ fixedHeader: { type: Boolean, default: undefined } }) export type PageHeaderTabConfig = Partial>> type PageHeaderRender = (props: PageContainerProps) => VueNodeOrRender export const pageContainerProps = () => ({ ...pageHeaderTabConfig(), ...omit(pageHeaderProps(), ['title', 'footer', 'breadcrumb']), title: { type: WithFalseVueNodeOrRenderPropType as PropType>, default: undefined }, content: VueNodeOrRenderPropType as PropType, extraContent: VueNodeOrRenderPropType as PropType, prefixCls: String, footer: VueNodeOrRenderPropType as PropType, /** 是否显示背景色 */ ghost: { type: Boolean, default: undefined }, /** * PageHeader 的配置(与 antd 完全相同 ) */ header: Object as PropType>, /** pageHeader */ pageHeaderRender: { type: WithFalseVueNodeOrRenderPropType as PropType>, default: undefined }, /** 固钉的配置 (与 antd 完全相同) */ affixProps: Object as PropType, /** 内容是否加载中 (只加载内容区域) */ loading: { type: [Object, Function, Boolean] as PropType, default: false }, // TODO 由于 ant-design-vue 暂时不支持这个属性,所以先注释 /** 自定义 breadcrumb,返回false不展示 */ // breadcrumbRender: { // type: WithFalseCustomRenderPropType as PropType>, // default: undefined // }, /** 水印的配置 */ waterMarkProps: Object as PropType, /** 配置面包屑 */ breadcrumb: Object as PropType }) export type PageContainerProps = Partial>> function genLoading(spinProps: boolean | SpinProps) { if (typeof spinProps === 'object') { return spinProps } return { spinning: spinProps } } /** * Render Footer tabList In order to be compatible with the old version of the PageHeader basically * all the functions are implemented. */ const renderFooter = (props: Omit) => { if (Array.isArray(props.tabList) || props.tabBarExtraContent) { return ( { if (props.onTabChange) { props.onTabChange(key) } }} tabBarExtraContent={props.tabBarExtraContent} {...props.tabProps} > {props.tabList?.map((item, index) => ( ))} ) } return null } const renderPageHeader = ( content: VueNodeOrRender, extraContent: VueNodeOrRender, prefixedClassName: string ): VueNode => { if (!content && !extraContent) { return null } return (
{content &&
{content}
} {extraContent &&
{extraContent}
}
) } /** * 配置与面包屑相同,只是增加了自动根据路由计算面包屑的功能。此功能必须要在 ProLayout 中使用。 * * @param props * @returns */ const ProBreadcrumb: FunctionalComponent = props => { const routeContext = inject(routeContextInjectKey) return (
{/* @ts-ignore TODO 面包屑透传处理 */}
) } // eslint-disable-next-line vue/one-component-per-file const ProPageHeader = defineComponent({ name: 'ProPageHeader', inheritAttrs: false, props: { ...pageContainerProps(), prefixedClassName: { type: String, default: '' } }, setup(props, { slots }) { const routeContext = inject(routeContextInjectKey, {}) const restProps = reactiveOmit( props, 'title', 'content', 'pageHeaderRender', 'header', 'prefixedClassName', 'extraContent', 'prefixCls' ) if (props.pageHeaderRender === false) { return null } if (props.pageHeaderRender) { return <> {props.pageHeaderRender({ ...props, ...routeContext })} } const pageHeaderTitle = computed(() => { if (!props.title && props.title !== false) { return routeContext.title } else { return props.title } }) // @ts-ignore const antdPageHeaderProps = reactivePick(restProps, antvPageHeaderPropsKeys) // @ts-ignore const localPageHeaderProps: PageHeaderProps = { ...antdPageHeaderProps, footer: renderFooter({ ...restProps, prefixedClassName: props.prefixedClassName }), ...props.header, title: pageHeaderTitle.value } const { breadcrumb } = localPageHeaderProps as { breadcrumb: BreadcrumbProps } const noHasBreadCrumb = !breadcrumb || (!breadcrumb?.itemRender && !breadcrumb?.routes?.length) if ( ['title', 'subTitle', 'extra', 'tags', 'footer', 'avatar', 'backIcon'].every( // @ts-ignore item => !localPageHeaderProps[item] ) && noHasBreadCrumb && !props.content && !props.extraContent ) { return null } return () => (
{{ ...slots, default: () => slots.headerContent?.(props) || renderPageHeader(props.content, props.extraContent, props.prefixedClassName) }}
) } }) const pageHeaderSlot = [ 'backIcon', 'avatar', 'breadcrumb', 'title', 'subTitle', 'tags', 'extra', 'footer' ] // eslint-disable-next-line vue/one-component-per-file const PageContainer = defineComponent({ name: 'PageContainer', inheritAttrs: false, props: pageContainerProps(), slots: [...pageHeaderSlot, 'loading', 'pageHeaderRender', 'headerContent', 'extraContent'], setup(props, { attrs, slots }) { const restProps = reactiveOmit(props, 'loading', 'footer', 'affixProps', 'ghost', 'fixedHeader') const value = inject(routeContextInjectKey, {}) const prefixCls = props.prefixCls || getPrefixCls('pro') const prefixedClassName = computed(() => `${prefixCls}-page-container`) const containerClassName = computed(() => [ prefixedClassName.value, attrs.class, { [`${prefixCls}-page-container-ghost`]: props.ghost, [`${prefixCls}-page-container-with-footer`]: props.footer } ]) const renderLoading = (): VueNode => { // 当loading时一个合法的ReactNode时,说明用户使用了自定义loading,直接返回改自定义loading if (slots.loading) { return slots.loading() } // 当传递过来的是布尔值,并且为false时,说明不需要显示loading,返回null if (typeof props.loading === 'boolean' && !props.loading) { return null } // 如非上述两种情况,那么要么用户传了一个true,要么用户传了loading配置,使用genLoading生成loading配置后返回PageLoading const spinProps = genLoading(props.loading as boolean | SpinProps) // 如果传的是loading配置,但spinning传的是false,也不需要显示loading return spinProps.spinning ? : null } function renderContent(loadingDom: VueNode, content: VueNode): VueNode { // 只要loadingDom非空我们就渲染loadingDom,否则渲染内容 const dom = loadingDom || content if (props.waterMarkProps || value.waterMarkProps) { const waterMarkProps = { ...value.waterMarkProps, ...props.waterMarkProps } return {dom} } return dom } return () => { const pageHeaderDom = ( {reactiveOmit(slots, 'default', 'loading')} ) const content = slots.default ? ( <>
{slots.default()}
{value.hasFooterToolbar &&
} ) : null const loadingDom = renderLoading() const renderContentDom = renderContent(loadingDom, content) return (
{props.fixedHeader && pageHeaderDom ? ( // 在 hasHeader 且 fixedHeader 的情况下,才需要设置高度 // @ts-ignore {pageHeaderDom} ) : ( pageHeaderDom )} {renderContentDom && {renderContentDom}} {props.footer && {props.footer}}
) } } }) export { ProPageHeader, ProBreadcrumb } export default PageContainer