|
|
@@ -10,194 +10,196 @@ export class Title {
|
|
|
constructor() {}
|
|
|
}
|
|
|
export type TabsSize = 'large' | 'normal' | 'small';
|
|
|
-export const component = {
|
|
|
- props: {
|
|
|
- modelValue: {
|
|
|
- type: [String, Number],
|
|
|
- default: 0
|
|
|
- },
|
|
|
- color: {
|
|
|
- type: String,
|
|
|
- default: ''
|
|
|
- },
|
|
|
- direction: {
|
|
|
- type: String,
|
|
|
- default: 'horizontal' //vertical
|
|
|
- },
|
|
|
- size: {
|
|
|
- type: String as import('vue').PropType<TabsSize>,
|
|
|
- default: 'normal'
|
|
|
- },
|
|
|
- type: {
|
|
|
- type: String,
|
|
|
- default: 'line' //card、line、smile
|
|
|
- },
|
|
|
- titleScroll: {
|
|
|
- type: Boolean,
|
|
|
- default: false
|
|
|
- },
|
|
|
- ellipsis: {
|
|
|
- type: Boolean,
|
|
|
- default: true
|
|
|
- },
|
|
|
- autoHeight: {
|
|
|
- type: Boolean,
|
|
|
- default: false
|
|
|
- },
|
|
|
- background: {
|
|
|
- type: String,
|
|
|
- default: ''
|
|
|
- },
|
|
|
- animatedTime: {
|
|
|
- type: [Number, String],
|
|
|
- default: 300
|
|
|
- },
|
|
|
- titleGutter: {
|
|
|
- type: [Number, String],
|
|
|
- default: 0
|
|
|
- },
|
|
|
- sticky: {
|
|
|
- type: Boolean,
|
|
|
- default: false
|
|
|
+export const component = (components: any) => {
|
|
|
+ return {
|
|
|
+ props: {
|
|
|
+ modelValue: {
|
|
|
+ type: [String, Number],
|
|
|
+ default: 0
|
|
|
+ },
|
|
|
+ color: {
|
|
|
+ type: String,
|
|
|
+ default: ''
|
|
|
+ },
|
|
|
+ direction: {
|
|
|
+ type: String,
|
|
|
+ default: 'horizontal' //vertical
|
|
|
+ },
|
|
|
+ size: {
|
|
|
+ type: String as import('vue').PropType<TabsSize>,
|
|
|
+ default: 'normal'
|
|
|
+ },
|
|
|
+ type: {
|
|
|
+ type: String,
|
|
|
+ default: 'line' //card、line、smile
|
|
|
+ },
|
|
|
+ titleScroll: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false
|
|
|
+ },
|
|
|
+ ellipsis: {
|
|
|
+ type: Boolean,
|
|
|
+ default: true
|
|
|
+ },
|
|
|
+ autoHeight: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false
|
|
|
+ },
|
|
|
+ background: {
|
|
|
+ type: String,
|
|
|
+ default: ''
|
|
|
+ },
|
|
|
+ animatedTime: {
|
|
|
+ type: [Number, String],
|
|
|
+ default: 300
|
|
|
+ },
|
|
|
+ titleGutter: {
|
|
|
+ type: [Number, String],
|
|
|
+ default: 0
|
|
|
+ },
|
|
|
+ sticky: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false
|
|
|
+ },
|
|
|
+ top: {
|
|
|
+ type: Number,
|
|
|
+ default: 0
|
|
|
+ }
|
|
|
},
|
|
|
- top: {
|
|
|
- type: Number,
|
|
|
- default: 0
|
|
|
- }
|
|
|
- },
|
|
|
|
|
|
- components: {},
|
|
|
- emits: ['update:modelValue', 'click', 'change'],
|
|
|
+ components,
|
|
|
+ emits: ['update:modelValue', 'click', 'change'],
|
|
|
|
|
|
- setup(props: any, { emit, slots }: any) {
|
|
|
- const container = ref(null);
|
|
|
- let stickyFixed: boolean;
|
|
|
- provide('activeKey', { activeKey: computed(() => props.modelValue) });
|
|
|
- provide('autoHeight', { autoHeight: computed(() => props.autoHeight) });
|
|
|
- const titles: Ref<Title[]> = ref([]);
|
|
|
- const renderTitles = (vnodes: VNode[]) => {
|
|
|
- vnodes.forEach((vnode: VNode, index: number) => {
|
|
|
- let type = vnode.type;
|
|
|
- type = (type as any).name || type;
|
|
|
- if (type == 'nut-tabpane') {
|
|
|
- let title = new Title();
|
|
|
- if (vnode.props?.title || vnode.props?.['pane-key'] || vnode.props?.['paneKey']) {
|
|
|
- let paneKeyType = TypeOfFun(vnode.props?.['pane-key']);
|
|
|
- let paneIndex =
|
|
|
- paneKeyType == 'number' || paneKeyType == 'string' ? String(vnode.props?.['pane-key']) : null;
|
|
|
- let camelPaneKeyType = TypeOfFun(vnode.props?.['paneKey']);
|
|
|
- let camelPaneIndex =
|
|
|
- camelPaneKeyType == 'number' || camelPaneKeyType == 'string' ? String(vnode.props?.['paneKey']) : null;
|
|
|
- title.title = vnode.props?.title;
|
|
|
- title.paneKey = paneIndex || camelPaneIndex || String(index);
|
|
|
- title.disabled = vnode.props?.disabled;
|
|
|
+ setup(props: any, { emit, slots }: any) {
|
|
|
+ const container = ref(null);
|
|
|
+ let stickyFixed: boolean;
|
|
|
+ provide('activeKey', { activeKey: computed(() => props.modelValue) });
|
|
|
+ provide('autoHeight', { autoHeight: computed(() => props.autoHeight) });
|
|
|
+ const titles: Ref<Title[]> = ref([]);
|
|
|
+ const renderTitles = (vnodes: VNode[]) => {
|
|
|
+ vnodes.forEach((vnode: VNode, index: number) => {
|
|
|
+ let type = vnode.type;
|
|
|
+ type = (type as any).name || type;
|
|
|
+ if (type == 'nut-tabpane') {
|
|
|
+ let title = new Title();
|
|
|
+ if (vnode.props?.title || vnode.props?.['pane-key'] || vnode.props?.['paneKey']) {
|
|
|
+ let paneKeyType = TypeOfFun(vnode.props?.['pane-key']);
|
|
|
+ let paneIndex =
|
|
|
+ paneKeyType == 'number' || paneKeyType == 'string' ? String(vnode.props?.['pane-key']) : null;
|
|
|
+ let camelPaneKeyType = TypeOfFun(vnode.props?.['paneKey']);
|
|
|
+ let camelPaneIndex =
|
|
|
+ camelPaneKeyType == 'number' || camelPaneKeyType == 'string' ? String(vnode.props?.['paneKey']) : null;
|
|
|
+ title.title = vnode.props?.title;
|
|
|
+ title.paneKey = paneIndex || camelPaneIndex || String(index);
|
|
|
+ title.disabled = vnode.props?.disabled;
|
|
|
+ } else {
|
|
|
+ // title.titleSlot = vnode.children?.title() as VNode[];
|
|
|
+ }
|
|
|
+ titles.value.push(title);
|
|
|
} else {
|
|
|
- // title.titleSlot = vnode.children?.title() as VNode[];
|
|
|
+ if (vnode.children == ' ') {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ renderTitles(vnode.children as VNode[]);
|
|
|
}
|
|
|
- titles.value.push(title);
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ const currentIndex = ref((props.modelValue as number) || 0);
|
|
|
+ const findTabsIndex = (value: string | number) => {
|
|
|
+ let index = titles.value.findIndex((item) => item.paneKey == value);
|
|
|
+ if (titles.value.length == 0) {
|
|
|
+ console.error('[NutUI] <Tabs> 当前未找到 TabPane 组件元素 , 请检查 .');
|
|
|
+ } else if (index == -1) {
|
|
|
+ console.error('[NutUI] <Tabs> 请检查 v-model 值是否为 paneKey ,如 paneKey 未设置,请采用下标控制 .');
|
|
|
} else {
|
|
|
- if (vnode.children == ' ') {
|
|
|
- return;
|
|
|
- }
|
|
|
- renderTitles(vnode.children as VNode[]);
|
|
|
+ currentIndex.value = index;
|
|
|
}
|
|
|
- });
|
|
|
- };
|
|
|
-
|
|
|
- const currentIndex = ref((props.modelValue as number) || 0);
|
|
|
- const findTabsIndex = (value: string | number) => {
|
|
|
- let index = titles.value.findIndex((item) => item.paneKey == value);
|
|
|
- if (titles.value.length == 0) {
|
|
|
- console.error('[NutUI] <Tabs> 当前未找到 TabPane 组件元素 , 请检查 .');
|
|
|
- } else if (index == -1) {
|
|
|
- console.error('[NutUI] <Tabs> 请检查 v-model 值是否为 paneKey ,如 paneKey 未设置,请采用下标控制 .');
|
|
|
- } else {
|
|
|
- currentIndex.value = index;
|
|
|
- }
|
|
|
- };
|
|
|
- const init = (vnodes: VNode[] = slots.default?.()) => {
|
|
|
- titles.value = [];
|
|
|
- vnodes = vnodes?.filter((item) => typeof item.children !== 'string');
|
|
|
- if (vnodes && vnodes.length) {
|
|
|
- renderTitles(vnodes);
|
|
|
- }
|
|
|
- findTabsIndex(props.modelValue);
|
|
|
- };
|
|
|
- const onStickyScroll = (params: { top: number; fixed: boolean }) => {
|
|
|
- stickyFixed = params.fixed;
|
|
|
- };
|
|
|
-
|
|
|
- watch(
|
|
|
- () => slots.default?.(),
|
|
|
- (vnodes: VNode[]) => {
|
|
|
- init(vnodes);
|
|
|
- }
|
|
|
- );
|
|
|
- const getScrollTopRoot = () => {
|
|
|
- return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
|
|
|
- };
|
|
|
- watch(
|
|
|
- () => props.modelValue,
|
|
|
- (value: string | number) => {
|
|
|
- findTabsIndex(value);
|
|
|
- if (stickyFixed) {
|
|
|
- let top = useRect(container.value!).top + getScrollTopRoot();
|
|
|
- let value = Math.ceil(top - props.top);
|
|
|
- window.scrollTo({
|
|
|
- top: value,
|
|
|
- behavior: 'smooth'
|
|
|
- });
|
|
|
+ };
|
|
|
+ const init = (vnodes: VNode[] = slots.default?.()) => {
|
|
|
+ titles.value = [];
|
|
|
+ vnodes = vnodes?.filter((item) => typeof item.children !== 'string');
|
|
|
+ if (vnodes && vnodes.length) {
|
|
|
+ renderTitles(vnodes);
|
|
|
}
|
|
|
- }
|
|
|
- );
|
|
|
- onMounted(init);
|
|
|
- onActivated(init);
|
|
|
- const contentStyle = computed(() => {
|
|
|
- return {
|
|
|
- transform:
|
|
|
- props.direction == 'horizontal'
|
|
|
- ? `translate3d(-${currentIndex.value * 100}%, 0, 0)`
|
|
|
- : `translate3d( 0,-${currentIndex.value * 100}%, 0)`,
|
|
|
- transitionDuration: `${props.animatedTime}ms`
|
|
|
+ findTabsIndex(props.modelValue);
|
|
|
};
|
|
|
- });
|
|
|
- const tabsNavStyle = computed(() => {
|
|
|
- return {
|
|
|
- background: props.background
|
|
|
+ const onStickyScroll = (params: { top: number; fixed: boolean }) => {
|
|
|
+ stickyFixed = params.fixed;
|
|
|
};
|
|
|
- });
|
|
|
- const tabsActiveStyle = computed(() => {
|
|
|
- return {
|
|
|
- color: props.type == 'smile' ? props.color : '',
|
|
|
- background: props.type == 'line' ? props.color : ''
|
|
|
+
|
|
|
+ watch(
|
|
|
+ () => slots.default?.(),
|
|
|
+ (vnodes: VNode[]) => {
|
|
|
+ init(vnodes);
|
|
|
+ }
|
|
|
+ );
|
|
|
+ const getScrollTopRoot = () => {
|
|
|
+ return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
|
|
|
+ };
|
|
|
+ watch(
|
|
|
+ () => props.modelValue,
|
|
|
+ (value: string | number) => {
|
|
|
+ findTabsIndex(value);
|
|
|
+ if (stickyFixed) {
|
|
|
+ let top = useRect(container.value!).top + getScrollTopRoot();
|
|
|
+ let value = Math.ceil(top - props.top);
|
|
|
+ window.scrollTo({
|
|
|
+ top: value,
|
|
|
+ behavior: 'smooth'
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ );
|
|
|
+ onMounted(init);
|
|
|
+ onActivated(init);
|
|
|
+ const contentStyle = computed(() => {
|
|
|
+ return {
|
|
|
+ transform:
|
|
|
+ props.direction == 'horizontal'
|
|
|
+ ? `translate3d(-${currentIndex.value * 100}%, 0, 0)`
|
|
|
+ : `translate3d( 0,-${currentIndex.value * 100}%, 0)`,
|
|
|
+ transitionDuration: `${props.animatedTime}ms`
|
|
|
+ };
|
|
|
+ });
|
|
|
+ const tabsNavStyle = computed(() => {
|
|
|
+ return {
|
|
|
+ background: props.background
|
|
|
+ };
|
|
|
+ });
|
|
|
+ const tabsActiveStyle = computed(() => {
|
|
|
+ return {
|
|
|
+ color: props.type == 'smile' ? props.color : '',
|
|
|
+ background: props.type == 'line' ? props.color : ''
|
|
|
+ };
|
|
|
+ });
|
|
|
+ const titleStyle = computed(() => {
|
|
|
+ return {
|
|
|
+ marginLeft: pxCheck(props.titleGutter),
|
|
|
+ marginRight: pxCheck(props.titleGutter)
|
|
|
+ };
|
|
|
+ });
|
|
|
+ const methods = {
|
|
|
+ tabChange: (item: Title, index: number) => {
|
|
|
+ emit('click', item);
|
|
|
+ if (item.disabled) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ currentIndex.value = index;
|
|
|
+ emit('update:modelValue', item.paneKey);
|
|
|
+ emit('change', item);
|
|
|
+ }
|
|
|
};
|
|
|
- });
|
|
|
- const titleStyle = computed(() => {
|
|
|
return {
|
|
|
- marginLeft: pxCheck(props.titleGutter),
|
|
|
- marginRight: pxCheck(props.titleGutter)
|
|
|
+ titles,
|
|
|
+ contentStyle,
|
|
|
+ tabsNavStyle,
|
|
|
+ titleStyle,
|
|
|
+ tabsActiveStyle,
|
|
|
+ container,
|
|
|
+ onStickyScroll,
|
|
|
+ ...methods
|
|
|
};
|
|
|
- });
|
|
|
- const methods = {
|
|
|
- tabChange: (item: Title, index: number) => {
|
|
|
- emit('click', item);
|
|
|
- if (item.disabled) {
|
|
|
- return;
|
|
|
- }
|
|
|
- currentIndex.value = index;
|
|
|
- emit('update:modelValue', item.paneKey);
|
|
|
- emit('change', item);
|
|
|
- }
|
|
|
- };
|
|
|
- return {
|
|
|
- titles,
|
|
|
- contentStyle,
|
|
|
- tabsNavStyle,
|
|
|
- titleStyle,
|
|
|
- tabsActiveStyle,
|
|
|
- container,
|
|
|
- onStickyScroll,
|
|
|
- ...methods
|
|
|
- };
|
|
|
- }
|
|
|
+ }
|
|
|
+ };
|
|
|
};
|