refactor: timeline
							parent
							
								
									e5275f742c
								
							
						
					
					
						commit
						899757a054
					
				|  | @ -0,0 +1,5 @@ | |||
| timeline: 新增 label | ||||
| 
 | ||||
| tree、tree-slelct: 新增虚拟滚动、title 逻辑变动 | ||||
| 
 | ||||
| date 相关组件: dayjs, UI 变动 | ||||
|  | @ -868,6 +868,7 @@ | |||
| @timeline-dot-border-width: 2px; | ||||
| @timeline-dot-color: @primary-color; | ||||
| @timeline-dot-bg: @component-background; | ||||
| @timeline-item-padding-bottom: 20px; | ||||
| 
 | ||||
| // Typography | ||||
| // --- | ||||
|  |  | |||
|  | @ -1,98 +1,92 @@ | |||
| import type { ExtractPropTypes } from 'vue'; | ||||
| import { inject, cloneVNode, defineComponent } from 'vue'; | ||||
| import { cloneVNode, defineComponent } from 'vue'; | ||||
| import classNames from '../_util/classNames'; | ||||
| import PropTypes from '../_util/vue-types'; | ||||
| import { getOptionProps, getPropsData, filterEmpty, getComponent } from '../_util/props-util'; | ||||
| import { filterEmpty } from '../_util/props-util'; | ||||
| import initDefaultProps from '../_util/props-util/initDefaultProps'; | ||||
| import type { TimelineItemProps } from './TimelineItem'; | ||||
| import TimelineItem from './TimelineItem'; | ||||
| import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined'; | ||||
| import { defaultConfigProvider } from '../config-provider'; | ||||
| import { tuple } from '../_util/type'; | ||||
| import useConfigInject from '../_util/hooks/useConfigInject'; | ||||
| 
 | ||||
| export const timelineProps = { | ||||
| export const timelineProps = () => ({ | ||||
|   prefixCls: PropTypes.string, | ||||
|   /** 指定最后一个幽灵节点是否存在或内容 */ | ||||
|   pending: PropTypes.any, | ||||
|   pendingDot: PropTypes.string, | ||||
|   pendingDot: PropTypes.any, | ||||
|   reverse: PropTypes.looseBool, | ||||
|   mode: PropTypes.oneOf(tuple('left', 'alternate', 'right', '')), | ||||
| }; | ||||
| }); | ||||
| 
 | ||||
| export type TimelineProps = Partial<ExtractPropTypes<typeof timelineProps>>; | ||||
| export type TimelineProps = Partial<ExtractPropTypes<ReturnType<typeof timelineProps>>>; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
|   name: 'ATimeline', | ||||
|   props: initDefaultProps(timelineProps, { | ||||
|   props: initDefaultProps(timelineProps(), { | ||||
|     reverse: false, | ||||
|     mode: '', | ||||
|   }), | ||||
|   setup() { | ||||
|     return { | ||||
|       configProvider: inject('configProvider', defaultConfigProvider), | ||||
|     }; | ||||
|   }, | ||||
|   render() { | ||||
|     const { prefixCls: customizePrefixCls, reverse, mode } = getOptionProps(this); | ||||
|     const { getPrefixCls } = this.configProvider; | ||||
|     const prefixCls = getPrefixCls('timeline', customizePrefixCls); | ||||
| 
 | ||||
|     const pendingDot = getComponent(this, 'pendingDot'); | ||||
|     const pending = getComponent(this, 'pending'); | ||||
|     const pendingNode = typeof pending === 'boolean' ? null : pending; | ||||
|     const classString = classNames(prefixCls, { | ||||
|       [`${prefixCls}-pending`]: !!pending, | ||||
|       [`${prefixCls}-reverse`]: !!reverse, | ||||
|       [`${prefixCls}-${mode}`]: !!mode, | ||||
|     }); | ||||
|     const children = filterEmpty(this.$slots.default?.()); | ||||
|     // // Remove falsy items | ||||
|     // const falsylessItems = filterEmpty(this.$slots.default) | ||||
|     // const items = falsylessItems.map((item, idx) => { | ||||
|     //   return cloneElement(item, { | ||||
|     //     props: { | ||||
|     //       last: falsylessItems.length - 1 === idx, | ||||
|     //     }, | ||||
|     //   }) | ||||
|     // }) | ||||
|     const pendingItem = pending ? ( | ||||
|       <TimelineItem pending={!!pending} dot={pendingDot || <LoadingOutlined />}> | ||||
|         {pendingNode} | ||||
|       </TimelineItem> | ||||
|     ) : null; | ||||
| 
 | ||||
|     const timeLineItems = reverse | ||||
|       ? [pendingItem, ...children.reverse()] | ||||
|       : [...children, pendingItem]; | ||||
| 
 | ||||
|   slots: ['pending', 'pendingDot'], | ||||
|   setup(props, { slots }) { | ||||
|     const { prefixCls, direction } = useConfigInject('timeline', props); | ||||
|     const getPositionCls = (ele, idx: number) => { | ||||
|       const eleProps = getPropsData(ele) as TimelineItemProps; | ||||
|       if (mode === 'alternate') { | ||||
|         if (eleProps.position === 'right') return `${prefixCls}-item-right`; | ||||
|         if (eleProps.position === 'left') return `${prefixCls}-item-left`; | ||||
|         return idx % 2 === 0 ? `${prefixCls}-item-left` : `${prefixCls}-item-right`; | ||||
|       const eleProps = ele.props || {}; | ||||
|       if (props.mode === 'alternate') { | ||||
|         if (eleProps.position === 'right') return `${prefixCls.value}-item-right`; | ||||
|         if (eleProps.position === 'left') return `${prefixCls.value}-item-left`; | ||||
|         return idx % 2 === 0 ? `${prefixCls.value}-item-left` : `${prefixCls.value}-item-right`; | ||||
|       } | ||||
|       if (mode === 'left') return `${prefixCls}-item-left`; | ||||
|       if (mode === 'right') return `${prefixCls}-item-right`; | ||||
|       if (eleProps.position === 'right') return `${prefixCls}-item-right`; | ||||
|       if (props.mode === 'left') return `${prefixCls.value}-item-left`; | ||||
|       if (props.mode === 'right') return `${prefixCls.value}-item-right`; | ||||
|       if (eleProps.position === 'right') return `${prefixCls.value}-item-right`; | ||||
|       return ''; | ||||
|     }; | ||||
| 
 | ||||
|     // Remove falsy items | ||||
|     const truthyItems = timeLineItems.filter(item => !!item); | ||||
|     const itemsCount = truthyItems.length; | ||||
|     const lastCls = `${prefixCls}-item-last`; | ||||
|     const items = truthyItems.map((ele, idx) => { | ||||
|       const pendingClass = idx === itemsCount - 2 ? lastCls : ''; | ||||
|       const readyClass = idx === itemsCount - 1 ? lastCls : ''; | ||||
|       return cloneVNode(ele, { | ||||
|         class: classNames([ | ||||
|           !reverse && !!pending ? pendingClass : readyClass, | ||||
|           getPositionCls(ele, idx), | ||||
|         ]), | ||||
|       }); | ||||
|     }); | ||||
|     return () => { | ||||
|       const { | ||||
|         pending = slots.pending?.(), | ||||
|         pendingDot = slots.pendingDot?.(), | ||||
|         reverse, | ||||
|         mode, | ||||
|       } = props; | ||||
|       const pendingNode = typeof pending === 'boolean' ? null : pending; | ||||
|       const children = filterEmpty(slots.default?.()); | ||||
| 
 | ||||
|     return <ul class={classString}>{items}</ul>; | ||||
|       const pendingItem = pending ? ( | ||||
|         <TimelineItem pending={!!pending} dot={pendingDot || <LoadingOutlined />}> | ||||
|           {pendingNode} | ||||
|         </TimelineItem> | ||||
|       ) : null; | ||||
| 
 | ||||
|       if (pendingItem) { | ||||
|         children.push(pendingItem); | ||||
|       } | ||||
| 
 | ||||
|       const timeLineItems = reverse ? children.reverse() : children; | ||||
| 
 | ||||
|       const itemsCount = timeLineItems.length; | ||||
|       const lastCls = `${prefixCls.value}-item-last`; | ||||
|       const items = timeLineItems.map((ele, idx) => { | ||||
|         const pendingClass = idx === itemsCount - 2 ? lastCls : ''; | ||||
|         const readyClass = idx === itemsCount - 1 ? lastCls : ''; | ||||
|         return cloneVNode(ele, { | ||||
|           class: classNames([ | ||||
|             !reverse && !!pending ? pendingClass : readyClass, | ||||
|             getPositionCls(ele, idx), | ||||
|           ]), | ||||
|         }); | ||||
|       }); | ||||
|       const hasLabelItem = timeLineItems.some( | ||||
|         item => !!(item.props?.label || item.children?.label), | ||||
|       ); | ||||
|       const classString = classNames(prefixCls.value, { | ||||
|         [`${prefixCls.value}-pending`]: !!pending, | ||||
|         [`${prefixCls.value}-reverse`]: !!reverse, | ||||
|         [`${prefixCls.value}-${mode}`]: !!mode && !hasLabelItem, | ||||
|         [`${prefixCls.value}-label`]: hasLabelItem, | ||||
|         [`${prefixCls.value}-rtl`]: direction.value === 'rtl', | ||||
|       }); | ||||
|       return <ul class={classString}>{items}</ul>; | ||||
|     }; | ||||
|   }, | ||||
| }); | ||||
|  |  | |||
|  | @ -1,60 +1,56 @@ | |||
| import type { ExtractPropTypes } from 'vue'; | ||||
| import { defineComponent, inject } from 'vue'; | ||||
| import { defineComponent } from 'vue'; | ||||
| import classNames from '../_util/classNames'; | ||||
| import PropTypes from '../_util/vue-types'; | ||||
| import { getOptionProps, getComponent } from '../_util/props-util'; | ||||
| import initDefaultProps from '../_util/props-util/initDefaultProps'; | ||||
| import { defaultConfigProvider } from '../config-provider'; | ||||
| import { tuple } from '../_util/type'; | ||||
| import useConfigInject from '../_util/hooks/useConfigInject'; | ||||
| 
 | ||||
| export const timelineItemProps = { | ||||
| export const timelineItemProps = () => ({ | ||||
|   prefixCls: PropTypes.string, | ||||
|   color: PropTypes.string, | ||||
|   dot: PropTypes.any, | ||||
|   pending: PropTypes.looseBool, | ||||
|   position: PropTypes.oneOf(tuple('left', 'right', '')).def(''), | ||||
| }; | ||||
|   label: PropTypes.any, | ||||
| }); | ||||
| 
 | ||||
| export type TimelineItemProps = Partial<ExtractPropTypes<typeof timelineItemProps>>; | ||||
| export type TimelineItemProps = Partial<ExtractPropTypes<ReturnType<typeof timelineItemProps>>>; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
|   name: 'ATimelineItem', | ||||
|   props: initDefaultProps(timelineItemProps, { | ||||
|   props: initDefaultProps(timelineItemProps(), { | ||||
|     color: 'blue', | ||||
|     pending: false, | ||||
|   }), | ||||
|   setup() { | ||||
|     return { | ||||
|       configProvider: inject('configProvider', defaultConfigProvider), | ||||
|   slots: ['dot', 'label'], | ||||
|   setup(props, { slots }) { | ||||
|     const { prefixCls } = useConfigInject('timeline', props); | ||||
|     return () => { | ||||
|       const { color = '', pending, label = slots.label?.(), dot = slots.dot?.() } = props; | ||||
|       const itemClassName = classNames({ | ||||
|         [`${prefixCls.value}-item`]: true, | ||||
|         [`${prefixCls.value}-item-pending`]: pending, | ||||
|       }); | ||||
| 
 | ||||
|       const dotClassName = classNames({ | ||||
|         [`${prefixCls.value}-item-head`]: true, | ||||
|         [`${prefixCls.value}-item-head-custom`]: dot, | ||||
|         [`${prefixCls.value}-item-head-${color}`]: true, | ||||
|       }); | ||||
|       return ( | ||||
|         <li class={itemClassName}> | ||||
|           {label && <div class={`${prefixCls.value}-item-label`}>{label}</div>} | ||||
|           <div class={`${prefixCls.value}-item-tail`} /> | ||||
|           <div | ||||
|             class={dotClassName} | ||||
|             style={{ borderColor: /blue|red|green|gray/.test(color) ? undefined : color }} | ||||
|           > | ||||
|             {dot} | ||||
|           </div> | ||||
|           <div class={`${prefixCls.value}-item-content`}>{slots.default?.()}</div> | ||||
|         </li> | ||||
|       ); | ||||
|     }; | ||||
|   }, | ||||
|   render() { | ||||
|     const { prefixCls: customizePrefixCls, color = '', pending } = getOptionProps(this); | ||||
|     const { getPrefixCls } = this.configProvider; | ||||
|     const prefixCls = getPrefixCls('timeline', customizePrefixCls); | ||||
| 
 | ||||
|     const dot = getComponent(this, 'dot'); | ||||
|     const itemClassName = classNames({ | ||||
|       [`${prefixCls}-item`]: true, | ||||
|       [`${prefixCls}-item-pending`]: pending, | ||||
|     }); | ||||
| 
 | ||||
|     const dotClassName = classNames({ | ||||
|       [`${prefixCls}-item-head`]: true, | ||||
|       [`${prefixCls}-item-head-custom`]: dot, | ||||
|       [`${prefixCls}-item-head-${color}`]: true, | ||||
|     }); | ||||
|     return ( | ||||
|       <li class={itemClassName}> | ||||
|         <div class={`${prefixCls}-item-tail`} /> | ||||
|         <div | ||||
|           class={dotClassName} | ||||
|           style={{ borderColor: /blue|red|green|gray/.test(color) ? undefined : color }} | ||||
|         > | ||||
|           {dot} | ||||
|         </div> | ||||
|         <div class={`${prefixCls}-item-content`}>{this.$slots.default?.()}</div> | ||||
|       </li> | ||||
|     ); | ||||
|   }, | ||||
| }); | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| import type { App, Plugin } from 'vue'; | ||||
| import Timeline from './Timeline'; | ||||
| import TimelineItem from './TimelineItem'; | ||||
| import Timeline, { timelineProps } from './Timeline'; | ||||
| import TimelineItem, { timelineItemProps } from './TimelineItem'; | ||||
| 
 | ||||
| export type { TimelineProps } from './Timeline'; | ||||
| export type { TimelineItemProps } from './TimelineItem'; | ||||
|  | @ -13,7 +13,7 @@ Timeline.install = function (app: App) { | |||
|   app.component(TimelineItem.name, TimelineItem); | ||||
|   return app; | ||||
| }; | ||||
| export { TimelineItem }; | ||||
| export { TimelineItem, timelineProps, timelineItemProps }; | ||||
| export default Timeline as typeof Timeline & | ||||
|   Plugin & { | ||||
|     readonly Item: typeof TimelineItem; | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ | |||
|   &-item { | ||||
|     position: relative; | ||||
|     margin: 0; | ||||
|     padding: 0 0 20px; | ||||
|     padding-bottom: @timeline-item-padding-bottom; | ||||
|     font-size: @font-size-base; | ||||
|     list-style: none; | ||||
| 
 | ||||
|  | @ -80,8 +80,8 @@ | |||
| 
 | ||||
|     &-content { | ||||
|       position: relative; | ||||
|       top: -((@font-size-base * @line-height-base) - @font-size-base) + 1px; | ||||
|       margin: 0 0 0 18px; | ||||
|       top: -(@font-size-base * @line-height-base - @font-size-base) + 1px; | ||||
|       margin: 0 0 0 @margin-lg + 2px; | ||||
|       word-break: break-word; | ||||
|     } | ||||
| 
 | ||||
|  | @ -96,7 +96,8 @@ | |||
|   } | ||||
| 
 | ||||
|   &.@{timeline-prefix-cls}-alternate, | ||||
|   &.@{timeline-prefix-cls}-right { | ||||
|   &.@{timeline-prefix-cls}-right, | ||||
|   &.@{timeline-prefix-cls}-label { | ||||
|     .@{timeline-prefix-cls}-item { | ||||
|       &-tail, | ||||
|       &-head, | ||||
|  | @ -106,6 +107,7 @@ | |||
| 
 | ||||
|       &-head { | ||||
|         margin-left: -4px; | ||||
| 
 | ||||
|         &-custom { | ||||
|           margin-left: 1px; | ||||
|         } | ||||
|  | @ -164,4 +166,21 @@ | |||
|       min-height: 48px; | ||||
|     } | ||||
|   } | ||||
|   &.@{timeline-prefix-cls}-label { | ||||
|     .@{timeline-prefix-cls}-item-label { | ||||
|       position: absolute; | ||||
|       top: -(@font-size-base * @line-height-base - @font-size-base) + 1px; | ||||
|       width: calc(50% - 12px); | ||||
|       text-align: right; | ||||
|     } | ||||
|     .@{timeline-prefix-cls}-item-right { | ||||
|       .@{timeline-prefix-cls}-item-label { | ||||
|         left: calc(50% + 14px); | ||||
|         width: calc(50% - 14px); | ||||
|         text-align: left; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @import './rtl'; | ||||
|  |  | |||
|  | @ -0,0 +1,135 @@ | |||
| @import '../../style/themes/index'; | ||||
| @import '../../style/mixins/index'; | ||||
| 
 | ||||
| @timeline-prefix-cls: ~'@{ant-prefix}-timeline'; | ||||
| 
 | ||||
| .@{timeline-prefix-cls} { | ||||
|   &-rtl { | ||||
|     direction: rtl; | ||||
|   } | ||||
| 
 | ||||
|   &-item { | ||||
|     &-tail { | ||||
|       .@{timeline-prefix-cls}-rtl & { | ||||
|         right: 4px; | ||||
|         left: auto; | ||||
|         border-right: @timeline-width solid @timeline-color; | ||||
|         border-left: none; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     &-head-custom { | ||||
|       .@{timeline-prefix-cls}-rtl & { | ||||
|         right: 5px; | ||||
|         left: auto; | ||||
|         transform: translate(50%, -50%); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     &-content { | ||||
|       .@{timeline-prefix-cls}-rtl & { | ||||
|         margin: 0 18px 0 0; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &.@{timeline-prefix-cls}-alternate, | ||||
|   &.@{timeline-prefix-cls}-right, | ||||
|   &.@{timeline-prefix-cls}-label { | ||||
|     .@{timeline-prefix-cls}-item { | ||||
|       &-tail, | ||||
|       &-head, | ||||
|       &-head-custom { | ||||
|         .@{timeline-prefix-cls}-rtl& { | ||||
|           right: 50%; | ||||
|           left: auto; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       &-head { | ||||
|         .@{timeline-prefix-cls}-rtl& { | ||||
|           margin-right: -4px; | ||||
|           margin-left: 0; | ||||
|         } | ||||
| 
 | ||||
|         &-custom { | ||||
|           .@{timeline-prefix-cls}-rtl& { | ||||
|             margin-right: 1px; | ||||
|             margin-left: 0; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       &-left { | ||||
|         .@{timeline-prefix-cls}-item-content { | ||||
|           .@{timeline-prefix-cls}-rtl& { | ||||
|             right: calc(50% - 4px); | ||||
|             left: auto; | ||||
|             text-align: right; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       &-right { | ||||
|         .@{timeline-prefix-cls}-item-content { | ||||
|           .@{timeline-prefix-cls}-rtl& { | ||||
|             text-align: left; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &.@{timeline-prefix-cls}-right { | ||||
|     .@{timeline-prefix-cls}-item-right { | ||||
|       .@{timeline-prefix-cls}-item-tail, | ||||
|       .@{timeline-prefix-cls}-item-head, | ||||
|       .@{timeline-prefix-cls}-item-head-custom { | ||||
|         .@{timeline-prefix-cls}-rtl& { | ||||
|           right: 0; | ||||
|           left: auto; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       .@{timeline-prefix-cls}-item-content { | ||||
|         .@{timeline-prefix-cls}-rtl& { | ||||
|           width: 100%; | ||||
|           margin-right: 18px; | ||||
|           text-align: right; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &&-pending &-item-last &-item-tail { | ||||
|     .@{timeline-prefix-cls}-rtl& { | ||||
|       border-right: 2px dotted @timeline-color; | ||||
|       border-left: none; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &&-reverse &-item-pending { | ||||
|     .@{timeline-prefix-cls}-item-tail { | ||||
|       .@{timeline-prefix-cls}-rtl& { | ||||
|         border-right: 2px dotted @timeline-color; | ||||
|         border-left: none; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &.@{timeline-prefix-cls}-label { | ||||
|     .@{timeline-prefix-cls}-item-label { | ||||
|       .@{timeline-prefix-cls}-rtl& { | ||||
|         text-align: left; | ||||
|       } | ||||
|     } | ||||
|     .@{timeline-prefix-cls}-item-right { | ||||
|       .@{timeline-prefix-cls}-item-label { | ||||
|         .@{timeline-prefix-cls}-rtl& { | ||||
|           right: calc(50% + 14px); | ||||
|           text-align: right; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | @ -1,14 +1,14 @@ | |||
| export default { | ||||
|     // Options.jsx
 | ||||
|     items_per_page: '/старонка', | ||||
|     jump_to: 'Перайсці', | ||||
|     jump_to_confirm: 'Пацвердзіць', | ||||
|     page: '', | ||||
|     // Pagination.jsx
 | ||||
|     prev_page: 'Назад', | ||||
|     next_page: 'Наперад', | ||||
|     prev_5: 'Папярэднія 5', | ||||
|     next_5: 'Наступныя 5', | ||||
|     prev_3: 'Папярэднія 3', | ||||
|     next_3: 'Наступныя 3', | ||||
|   }; | ||||
|   // Options.jsx
 | ||||
|   items_per_page: '/старонка', | ||||
|   jump_to: 'Перайсці', | ||||
|   jump_to_confirm: 'Пацвердзіць', | ||||
|   page: '', | ||||
|   // Pagination.jsx
 | ||||
|   prev_page: 'Назад', | ||||
|   next_page: 'Наперад', | ||||
|   prev_5: 'Папярэднія 5', | ||||
|   next_5: 'Наступныя 5', | ||||
|   prev_3: 'Папярэднія 3', | ||||
|   next_3: 'Наступныя 3', | ||||
| }; | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ | |||
| </template> | ||||
| <script> | ||||
| import { defineComponent } from 'vue'; | ||||
| import demo from '../v2-doc/src/docs/pagination/demo/index.vue'; | ||||
| import demo from '../v2-doc/src/docs/timeline/demo/index.vue'; | ||||
| // import Affix from '../components/affix'; | ||||
| export default defineComponent({ | ||||
|   components: { | ||||
|  |  | |||
							
								
								
									
										2
									
								
								v2-doc
								
								
								
								
							
							
								
								
								
								
								
								
							
						
						
									
										2
									
								
								v2-doc
								
								
								
								
							|  | @ -1 +1 @@ | |||
| Subproject commit 17abe8a32a993726902f52e153f5470c1ffba02a | ||||
| Subproject commit 3a14ef175850d1fcb3ccd002c3afd1f871dc3257 | ||||
		Loading…
	
		Reference in New Issue
	
	 tangjinzhou
						tangjinzhou