From eb9ba619b2950bf8be857aeecf5654b3d951d736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=B8=85?= Date: Mon, 31 Oct 2022 08:38:21 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=EF=BC=9A1=E3=80=81=E6=9A=B4=E9=9C=B2?= =?UTF-8?q?=E5=87=BA=E4=BA=8B=E4=BB=B6=E5=87=BA=E6=9D=A5tabChange\tabRemov?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tabs/src/composition/use-tabs.ts | 9 +- .../components/tabs/src/tabs.component.tsx | 191 ++++++++++++++---- packages/ui-vue/components/tabs/src/tabs.css | 39 ++++ .../ui-vue/components/tabs/src/tabs.props.ts | 2 +- packages/ui-vue/src/components/tabs.vue | 52 ++--- 5 files changed, 222 insertions(+), 71 deletions(-) diff --git a/packages/ui-vue/components/tabs/src/composition/use-tabs.ts b/packages/ui-vue/components/tabs/src/composition/use-tabs.ts index 5ef71faea..db498bb95 100644 --- a/packages/ui-vue/components/tabs/src/composition/use-tabs.ts +++ b/packages/ui-vue/components/tabs/src/composition/use-tabs.ts @@ -40,11 +40,11 @@ export function useTabs( 'farris-title-auto': props.autoTitleWidth }; } - function changeTitleStyle(TitleUlElement: ShallowRef) { + function changeTitleStyle(titleUlElement: ShallowRef) { if (props.autoTitleWidth) { return; } - const $textEls = TitleUlElement.value?.querySelectorAll('.st-tab-text'); + const $textEls = titleUlElement.value?.querySelectorAll('.st-tab-text'); if (!$textEls) { return } @@ -60,7 +60,12 @@ export function useTabs( } } function selectTabByIndex($event: MouseEvent, targetTabId: string) { + const prevId = activeId.value; activeId.value = targetTabId + context.emit('tabChange', { + prevId, + nextId: activeId.value + }) $event.preventDefault(); $event.stopPropagation(); } diff --git a/packages/ui-vue/components/tabs/src/tabs.component.tsx b/packages/ui-vue/components/tabs/src/tabs.component.tsx index a9518ffdc..2376206e9 100644 --- a/packages/ui-vue/components/tabs/src/tabs.component.tsx +++ b/packages/ui-vue/components/tabs/src/tabs.component.tsx @@ -8,36 +8,41 @@ import { TabPageProps } from './components/tab-page.props'; export default defineComponent({ name: 'FTabs', props: tabsProps, - emits: [], + emits: ['tabChange', 'tabRemove'], setup(props: TabsProps, context: SetupContext) { - // 标题Ul元素 - const TitleUlElement = shallowRef(); + const tabsElement = shallowRef(); + // 标题Ul元素 + const titleUlElement = shallowRef(); + // 显示左右滚动按钮 const hideButtons = ref(true); - - const hideDropDown = ref(false); + // 显示下拉面板 + const hideDropDown = ref(true); const hasInHeadClass = computed(() => { return false; }); const menuItemsWidth = ref('atuo') - + // 下拉框后的搜索文本 const searchTabText = ref(''); // 存储tab的props数组 const tabs = ref([]); - + // 激活状态的tabId const activeId = ref(props.activeId); const { setActiveId, getTabStyle, getTabClass, getTabNavLinkClass, getTabTextClass, changeTitleStyle, selectTabByIndex } = useTabs(props, context, activeId); - // 增加一个tab + // 增加一个tab标题 const addTab = (tabContext: TabContext) => { const index = tabs.value.findIndex(tab => tab.props.id === tabContext.props.id) if (index === -1) { tabs.value.push(tabContext) } + nextTick(() => { + setHideButtonsStatus() + }) }; - + // 更新tab标题 const updateTab = (tabContext: TabContext) => { const index = tabs.value.findIndex(tab => tab.props.id === tabContext.props.id) if (index === -1) { @@ -49,26 +54,39 @@ export default defineComponent({ } }) nextTick(() => { - changeTitleStyle(TitleUlElement) + changeTitleStyle(titleUlElement) + setHideButtonsStatus() }); } - - function removeTab($event: Event, targetTabId: string) { + // 移除tab标题 + function removeTab($event: MouseEvent, targetTabId: string, closeDropdown = false) { + const index = tabs.value.findIndex(tab => tab.props.id === targetTabId) tabs.value = tabs.value.filter(tab => tab.props.id !== targetTabId) if (activeId.value === targetTabId) { activeId.value = '' setActiveId(tabs) } - $event.preventDefault(); - $event.stopPropagation(); + stopBubble($event) + nextTick(() => { + changeTitleStyle(titleUlElement) + setHideButtonsStatus() + if (closeDropdown) { + hideButtons.value = true + } + context.emit('tabRemove', { + removeIndex: index, + removeId: targetTabId, + activeId: activeId.value + }) + }); } - + // 提供者tabs,供增加、修改tab标题用 provide('tabs', { activeId, addTab, updateTab, tabs }); - + // 填充模式 const shouldShowNavFill = computed(() => { return props.fill || props.tabType === 'fill' }); - + // 药片模式 const shouldShowNavPills = computed(() => { return props.tabType === 'pills' }); @@ -85,7 +103,7 @@ export default defineComponent({ width: hasInHeadClass.value ? (props.titleWidth ? `${props.titleWidth}%` : '') : '' })); - const tabsTitleButtonClass = computed(() => ({ + const leftArrowButtonClass = computed(() => ({ btn: true, 'sc-nav-btn': true, 'px-1': true, @@ -103,7 +121,7 @@ export default defineComponent({ const dropdownMenuCls = computed(() => ({ 'dropdown-menu': true, 'tabs-pt28': props.searchBoxVisible, - 'show': !hideButtons.value + 'show': !hideDropDown.value })); const rightArrowButtonClass = computed(() => ({ @@ -116,14 +134,14 @@ export default defineComponent({ return { width: menuItemsWidth.value }; }; - const getDropdownItemCls = (tab: any) => { + const getDropdownItemCls = (tab: TabContext) => { return { 'dropdown-item': true, 'text-truncate': true, 'px-2': true, - disabled: tab.disabled, - active: tab.id = activeId.value, - 'd-none': tab.show !== true + disabled: tab.props.disabled, + active: tab.props.id === activeId.value, + 'd-none': tab.props.show !== true } }; @@ -132,14 +150,18 @@ export default defineComponent({ $event.stopPropagation(); }; - const searchTab = () => { - - }; - - const _cpSelectTabByIndex = ($event: MouseEvent, id: string) => { - selectTabByIndex($event, id); + // 选中某个tab + const _cpSelectTabByIndex = ($event: MouseEvent, tab: TabPageProps) => { + if (tab.disabled) { + return + } + selectTabByIndex($event, tab.id); + const index = tabs.value.findIndex(tb => tb.props.id === tab.id); + nextTick(() => { + scrollToActiveTab(index) + }) } - + // 下拉框使用的tabs,包括搜索 const cpTabs = computed(() => { let _cpTabs: TabContext[] = [] if (props.searchBoxVisible) { @@ -153,8 +175,8 @@ export default defineComponent({ const tabParentClass = computed(() => ({ spacer: true, 'f-utils-fill': true, - 'spacer-sides': !hideButtons.value && hideDropDown.value, - 'spacer-sides-dropdown': !hideButtons.value && !hideDropDown.value + // 'spacer-sides': !hideButtons.value && hideDropDown.value, + 'spacer-sides-dropdown': !hideButtons.value })); const tabContainerClass = computed(() => ({ @@ -175,21 +197,96 @@ export default defineComponent({ 'flex-row-reverse': props.position === 'right' })); + // 设置隐藏按钮的状态 + const setHideButtonsStatus = () => { + const titleUlparentElement = titleUlElement.value?.parentElement + if (!titleUlparentElement || !titleUlElement.value) { + return + } + if (titleUlparentElement.offsetWidth < titleUlElement.value.scrollWidth) { + hideButtons.value = false + } else { + hideButtons.value = true + } + } + + /** + * 按照方向,滚动scrollStep距离 + * @param direction 方向 + */ + const scrollTab = (move: number, direction: number) => { + if (!titleUlElement.value) { + return + } + const dist_scrollleft = titleUlElement.value.scrollLeft; + const max_scroll_left = + titleUlElement.value.scrollWidth - titleUlElement.value.offsetWidth; + // 标签页左侧移动 + if (direction > 0) { + if (titleUlElement.value.scrollLeft >= max_scroll_left) { + return; + } + titleUlElement.value.scrollLeft = dist_scrollleft + props.scrollStep + move; + } else if (direction < 0) { + // 标签页右侧移动 + if (titleUlElement.value.scrollLeft <= 0) { + return; + } + titleUlElement.value.scrollLeft = dist_scrollleft - props.scrollStep - move; + } + } + /** + * 定位到某个tab + * @param activeTableIndex 索引 + * @returns + */ + const scrollToActiveTab = (activeTableIndex: number) => { + if (hideButtons.value || !titleUlElement.value) { + return; + } + const tabs = titleUlElement.value.querySelectorAll('.nav-item'); + const parentElement = titleUlElement.value.parentElement; + const scrollTabElement = tabs[activeTableIndex]; + if (scrollTabElement && parentElement) { + const parentElementLeft = parentElement.getBoundingClientRect().left; + const parentElementRight = parentElement.getBoundingClientRect().right; + const activeTabLeft = scrollTabElement.getBoundingClientRect().left; + const activeTabRight = scrollTabElement.getBoundingClientRect().right; + if (activeTabLeft < parentElementLeft) { + scrollTab(parentElementLeft - activeTabLeft, -1) + } else if (parentElementRight < activeTabRight) { + scrollTab(activeTabRight - parentElementRight, 1) + } + } + } + onMounted(() => { nextTick(() => { - changeTitleStyle(TitleUlElement) + changeTitleStyle(titleUlElement) + // 下拉面板之外空白处点击关闭下拉面板 + window.addEventListener('click', (ev: any) => { + if (hideDropDown.value) { + return + } + if (!tabsElement.value?.contains(ev.target)) { + hideDropDown.value = true + } + }) + window.addEventListener('resize', (ev: any) => { + setHideButtonsStatus() + }) }); }) return () => { return ( <> -
+
- +
-
    +
      {tabs.value.map((tabPage: any) => { const tab = tabPage.props const titleSlot = tabPage.slots.title @@ -209,19 +306,25 @@ export default defineComponent({
- - -
+ + +
{props.searchBoxVisible && (
stopBubble($event)} class="pb-1 tabs-li-absolute"> - searchTab()} /> +
)} -
    + {cpTabs.value.length ?
      {cpTabs.value.map(tab => { - return (
    • _cpSelectTabByIndex($event, tab.props.id)}> - {tab.props.title}
    • ) + return ( +
    • _cpSelectTabByIndex($event, tab.props)}> + {tab.props.removeable && removeTab($event, tab.props.id, true)}> + + } + {tab.props.title} +
    • + ) })} -
    +
: }
diff --git a/packages/ui-vue/components/tabs/src/tabs.css b/packages/ui-vue/components/tabs/src/tabs.css index 26019f33c..243b6aa60 100644 --- a/packages/ui-vue/components/tabs/src/tabs.css +++ b/packages/ui-vue/components/tabs/src/tabs.css @@ -1,3 +1,42 @@ .farris-tabs .st-drop-close{ line-height:1; +} +.farris-tabs .tabs-li-absolute{ + padding:4px; + margin: 0; + position: relative; +} +.farris-tabs .farris-tabs-header .dropdown-menu{ + right: 0; + left: auto; + min-height: 80px; +} + +.farris-tabs .tabs-li-absolute input{ + line-height: 26px; + height: 26px; + padding-right: 24px; +} +.farris-tabs .tabs-icon-search{ + position: absolute; + right: 4px; + top: 50%; + font-size: 14px; + padding: 0 4px; + width: auto; + height: 24px; + line-height: 1; + color: rgba(0, 0, 0, 0.25); + margin-top: -12px; + line-height: 20px; +} +.farris-tabs .dropdown-no-data{ + height: 26px; + line-height: 26px; + text-align: center; + padding-top: 5px; +} + +.farris-tabs .farris-tabs-header .farris-tabs-title.scroll-tabs .spacer .farris-nav-tabs .nav-item{ + flex-shrink: 0; } \ No newline at end of file diff --git a/packages/ui-vue/components/tabs/src/tabs.props.ts b/packages/ui-vue/components/tabs/src/tabs.props.ts index 730269da4..55019c537 100644 --- a/packages/ui-vue/components/tabs/src/tabs.props.ts +++ b/packages/ui-vue/components/tabs/src/tabs.props.ts @@ -10,7 +10,7 @@ export const tabsProps = { position: { type: String as PropType, default: 'top' }, showDropDwon: { type: Boolean, default: false }, showTooltips: { type: Boolean, default: false }, - scrollStep: { type: Number, default: 1 }, + scrollStep: { type: Number, default: 10 }, autoResize: { type: Boolean, default: false }, closeable: { type: Boolean, default: false }, selectedTab: { type: String, default: '' }, diff --git a/packages/ui-vue/src/components/tabs.vue b/packages/ui-vue/src/components/tabs.vue index 69a1538fa..0fcfe9af1 100644 --- a/packages/ui-vue/src/components/tabs.vue +++ b/packages/ui-vue/src/components/tabs.vue @@ -29,7 +29,7 @@ const tabs = reactive([ { id: 'tab3', title: 'Tab3', - removeable: true, + removeable: false, content: 'Content of Tab Page 3', tabWidth: 200 }, @@ -54,11 +54,13 @@ function setPosition(pos: string) { position.value = pos } - +function consoleLog(e) { + console.log(e) +} -- Gitee From e9f175095bb83acbcaede784ee000b5b61e41bf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=B8=85?= Date: Mon, 31 Oct 2022 09:20:56 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=EF=BC=9Atabs=E9=A1=B5=E5=87=BA?= =?UTF-8?q?=E7=8E=B0=E4=B8=8B=E6=8B=89=E6=A1=86=E6=97=B6=EF=BC=8C=E7=82=B9?= =?UTF-8?q?=E5=87=BB=E6=9F=90=E9=A1=B9=E5=BA=94=E8=AF=A5=E5=85=B3=E9=97=AD?= =?UTF-8?q?=E8=BF=99=E4=B8=AA=E4=B8=8B=E6=8B=89=E6=A1=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/ui-vue/components/tabs/src/tabs.component.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ui-vue/components/tabs/src/tabs.component.tsx b/packages/ui-vue/components/tabs/src/tabs.component.tsx index 2376206e9..8ad9e76f3 100644 --- a/packages/ui-vue/components/tabs/src/tabs.component.tsx +++ b/packages/ui-vue/components/tabs/src/tabs.component.tsx @@ -157,6 +157,7 @@ export default defineComponent({ } selectTabByIndex($event, tab.id); const index = tabs.value.findIndex(tb => tb.props.id === tab.id); + hideDropDown.value = true; nextTick(() => { scrollToActiveTab(index) }) -- Gitee