You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
172 lines
5.2 KiB
172 lines
5.2 KiB
import type { PropType, ExtractPropTypes } from 'vue';
|
|
import { cloneVNode, defineComponent } from 'vue';
|
|
import PropTypes from '../_util/vue-types';
|
|
import { flattenChildren, getPropsSlot } from '../_util/props-util';
|
|
import warning from '../_util/warning';
|
|
import type { BreadcrumbItemProps } from './BreadcrumbItem';
|
|
import BreadcrumbItem from './BreadcrumbItem';
|
|
import Menu from '../menu';
|
|
import useConfigInject from '../config-provider/hooks/useConfigInject';
|
|
import useStyle from './style';
|
|
import type { CustomSlotsType, VueNode } from '../_util/type';
|
|
|
|
export interface Route {
|
|
path: string;
|
|
breadcrumbName: string;
|
|
children?: Omit<Route, 'children'>[];
|
|
}
|
|
|
|
export const breadcrumbProps = () => ({
|
|
prefixCls: String,
|
|
routes: { type: Array as PropType<Route[]> },
|
|
params: PropTypes.any,
|
|
separator: PropTypes.any,
|
|
itemRender: {
|
|
type: Function as PropType<
|
|
(opt: { route: Route; params: unknown; routes: Route[]; paths: string[] }) => VueNode
|
|
>,
|
|
},
|
|
});
|
|
|
|
export type BreadcrumbProps = Partial<ExtractPropTypes<ReturnType<typeof breadcrumbProps>>>;
|
|
|
|
function getBreadcrumbName(route: Route, params: unknown) {
|
|
if (!route.breadcrumbName) {
|
|
return null;
|
|
}
|
|
const paramsKeys = Object.keys(params).join('|');
|
|
const name = route.breadcrumbName.replace(
|
|
new RegExp(`:(${paramsKeys})`, 'g'),
|
|
(replacement, key) => params[key] || replacement,
|
|
);
|
|
return name;
|
|
}
|
|
function defaultItemRender(opt: {
|
|
route: Route;
|
|
params: unknown;
|
|
routes: Route[];
|
|
paths: string[];
|
|
}): VueNode {
|
|
const { route, params, routes, paths } = opt;
|
|
const isLastItem = routes.indexOf(route) === routes.length - 1;
|
|
const name = getBreadcrumbName(route, params);
|
|
return isLastItem ? <span>{name}</span> : <a href={`#/${paths.join('/')}`}>{name}</a>;
|
|
}
|
|
|
|
export default defineComponent({
|
|
compatConfig: { MODE: 3 },
|
|
name: 'ABreadcrumb',
|
|
inheritAttrs: false,
|
|
props: breadcrumbProps(),
|
|
slots: Object as CustomSlotsType<{
|
|
separator: any;
|
|
itemRender: { route: Route; params: any; routes: Route[]; paths: string[] };
|
|
default: any;
|
|
}>,
|
|
setup(props, { slots, attrs }) {
|
|
const { prefixCls, direction } = useConfigInject('breadcrumb', props);
|
|
const [wrapSSR, hashId] = useStyle(prefixCls);
|
|
const getPath = (path: string, params: unknown) => {
|
|
path = (path || '').replace(/^\//, '');
|
|
Object.keys(params).forEach(key => {
|
|
path = path.replace(`:${key}`, params[key]);
|
|
});
|
|
return path;
|
|
};
|
|
|
|
const addChildPath = (paths: string[], childPath: string, params: unknown) => {
|
|
const originalPaths = [...paths];
|
|
const path = getPath(childPath || '', params);
|
|
if (path) {
|
|
originalPaths.push(path);
|
|
}
|
|
return originalPaths;
|
|
};
|
|
|
|
const genForRoutes = ({
|
|
routes = [],
|
|
params = {},
|
|
separator,
|
|
itemRender = defaultItemRender,
|
|
}: any) => {
|
|
const paths = [];
|
|
return routes.map((route: Route) => {
|
|
const path = getPath(route.path, params);
|
|
|
|
if (path) {
|
|
paths.push(path);
|
|
}
|
|
const tempPaths = [...paths];
|
|
// generated overlay by route.children
|
|
let overlay = null;
|
|
if (route.children && route.children.length) {
|
|
overlay = (
|
|
<Menu
|
|
items={route.children.map(child => ({
|
|
key: child.path || child.breadcrumbName,
|
|
label: itemRender({
|
|
route: child,
|
|
params,
|
|
routes,
|
|
paths: addChildPath(tempPaths, child.path, params),
|
|
}),
|
|
}))}
|
|
></Menu>
|
|
);
|
|
}
|
|
const itemProps: BreadcrumbItemProps = { separator };
|
|
if (overlay) {
|
|
itemProps.overlay = overlay;
|
|
}
|
|
return (
|
|
<BreadcrumbItem {...itemProps} key={path || route.breadcrumbName}>
|
|
{itemRender({ route, params, routes, paths: tempPaths })}
|
|
</BreadcrumbItem>
|
|
);
|
|
});
|
|
};
|
|
return () => {
|
|
let crumbs: VueNode[];
|
|
|
|
const { routes, params = {} } = props;
|
|
|
|
const children = flattenChildren(getPropsSlot(slots, props));
|
|
const separator = getPropsSlot(slots, props, 'separator') ?? '/';
|
|
|
|
const itemRender = props.itemRender || slots.itemRender || defaultItemRender;
|
|
if (routes && routes.length > 0) {
|
|
// generated by route
|
|
crumbs = genForRoutes({
|
|
routes,
|
|
params,
|
|
separator,
|
|
itemRender,
|
|
});
|
|
} else if (children.length) {
|
|
crumbs = children.map((element, index) => {
|
|
warning(
|
|
typeof element.type === 'object' &&
|
|
(element.type.__ANT_BREADCRUMB_ITEM || element.type.__ANT_BREADCRUMB_SEPARATOR),
|
|
'Breadcrumb',
|
|
"Only accepts Breadcrumb.Item and Breadcrumb.Separator as it's children",
|
|
);
|
|
return cloneVNode(element, { separator, key: index });
|
|
});
|
|
}
|
|
|
|
const breadcrumbClassName = {
|
|
[prefixCls.value]: true,
|
|
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
|
|
[`${attrs.class}`]: !!attrs.class,
|
|
[hashId.value]: true,
|
|
};
|
|
|
|
return wrapSSR(
|
|
<nav {...attrs} class={breadcrumbClassName}>
|
|
<ol>{crumbs}</ol>
|
|
</nav>,
|
|
);
|
|
};
|
|
},
|
|
});
|