mirror of
https://gitee.com/wot-design-uni/wot-design-uni.git
synced 2025-12-06 17:18:40 +08:00
parent
8419564e50
commit
314c2e8c9d
@ -127,16 +127,31 @@ const tab = ref('例子')
|
||||
</wd-tabs>
|
||||
```
|
||||
|
||||
## 左对齐超出即可滚动 <el-tag text style="vertical-align: middle;margin-left:8px;" effect="plain">1.3.15</el-tag>
|
||||
|
||||
`slidable`设置为`always`时,所有的标签会向左侧收缩对齐,超出即可滑动。
|
||||
|
||||
```html
|
||||
<wd-tabs v-model="tab" slidable="always">
|
||||
<block v-for="item in 5" :key="item">
|
||||
<wd-tab :title="`超大标签${item}`">
|
||||
<view class="content">内容{{ item }}</view>
|
||||
</wd-tab>
|
||||
</block>
|
||||
</wd-tabs>
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
标签页在标签数大于等于 6 个时,可以滑动;当标签数大于等于 10 个时,将会显示导航地图,便于快速定位到某个标签。可以通过设置 `slidable-num` 修改可滑动的数量阈值;设置 `map-num` 修改显示导航地图的阈值。
|
||||
标签页在标签数大于等于 6 个时,可以滑动;当标签数大于等于 10 个时,将会显示导航地图,便于快速定位到某个标签。可以通过设置 `slidable-num` 修改可滑动的数量阈值;设置 `map-num` 修改显示导航地图的阈值。`slidable`设置为`always`时,所有的标签会向左侧收缩对齐,超出即可滑动。
|
||||
|
||||
## Tabs Attributes
|
||||
|
||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 | 最低版本 |
|
||||
| ------------- | -------------------------------- | --------------- | ------ | ------ | -------- |
|
||||
| v-model | 绑定值 | string / number | - | - | - |
|
||||
| slidable-num | 可滑动的标签数阈值 | number | - | 6 | - |
|
||||
| slidable-num | 可滑动的标签数阈值,`slidable`设置为`auto`时生效 | number | - | 6 | - |
|
||||
| map-num | 显示导航地图的标签数阈值 | number | - | 10 | - |
|
||||
| sticky | 粘性布局 | boolean | - | false | - |
|
||||
| offset-top | 粘性布局时距离窗口顶部距离 | number | - | 0 | - |
|
||||
@ -147,6 +162,7 @@ const tab = ref('例子')
|
||||
| inactiveColor | 非活动标签文字颜色 | string | - | - | - |
|
||||
| animated | 是否开启切换标签内容时的转场动画 | boolean | - | false | - |
|
||||
| duration | 切换动画过渡时间,单位毫秒 | number | - | 300 | - |
|
||||
| slidable | 是否开启滚动导航 | TabsSlidable | `always` | `auto` | $LOWEST_VERSION$ |
|
||||
|
||||
## Tab Attributes
|
||||
|
||||
|
||||
@ -74,7 +74,7 @@
|
||||
</demo-block>
|
||||
|
||||
<demo-block title="数量大于6时可滚动" transparent>
|
||||
<wd-tabs v-model="tab6" lazy-render @change="handleChange">
|
||||
<wd-tabs v-model="tab6" @change="handleChange">
|
||||
<block v-for="item in 7" :key="item">
|
||||
<wd-tab :title="`标签${item}`">
|
||||
<view class="content">内容{{ tab6 + 1 }}</view>
|
||||
@ -83,6 +83,16 @@
|
||||
</wd-tabs>
|
||||
</demo-block>
|
||||
|
||||
<demo-block title="左对齐超出即可滚动" transparent>
|
||||
<wd-tabs v-model="tab9" slidable="always" @change="handleChange">
|
||||
<block v-for="item in 5" :key="item">
|
||||
<wd-tab :title="`超大标签${item}`">
|
||||
<view class="content">内容{{ tab9 + 1 }}</view>
|
||||
</wd-tab>
|
||||
</block>
|
||||
</wd-tabs>
|
||||
</demo-block>
|
||||
|
||||
<demo-block title="数量大于10时出现导航地图" transparent>
|
||||
<wd-tabs v-model="tab7" @change="handleChange">
|
||||
<block v-for="item in 11" :key="item">
|
||||
@ -108,6 +118,8 @@ const tab5 = ref<number>(0)
|
||||
const tab6 = ref<number>(0)
|
||||
const tab7 = ref<number>(0)
|
||||
const tab8 = ref<number>(0)
|
||||
const tab9 = ref<number>(0)
|
||||
|
||||
const toast = useToast()
|
||||
function handleClick({ index, name }: any) {
|
||||
console.log('event', { index, name })
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<view :class="`wd-tab ${customClass}`" :style="customStyle">
|
||||
<view v-if="painted" class="wd-tab__body" :style="isShow ? '' : 'display: none;'">
|
||||
<view v-if="painted" class="wd-tab__body" :style="tabBodyStyle">
|
||||
<slot />
|
||||
</view>
|
||||
</view>
|
||||
@ -16,8 +16,8 @@ export default {
|
||||
}
|
||||
</script>
|
||||
<script lang="ts" setup>
|
||||
import { getCurrentInstance, ref, watch } from 'vue'
|
||||
import { isDef, isNumber, isString } from '../common/util'
|
||||
import { getCurrentInstance, ref, watch, type CSSProperties } from 'vue'
|
||||
import { isDef, isNumber, isString, objToStyle } from '../common/util'
|
||||
import { useParent } from '../composables/useParent'
|
||||
import { TABS_KEY } from '../wd-tabs/types'
|
||||
import { computed } from 'vue'
|
||||
@ -35,6 +35,14 @@ const activeIndex = computed(() => {
|
||||
return isDef(tabs) ? tabs.state.activeIndex : 0
|
||||
})
|
||||
|
||||
const tabBodyStyle = computed(() => {
|
||||
const style: CSSProperties = {}
|
||||
if (!isShow.value && (!isDef(tabs) || !tabs.props.animated)) {
|
||||
style.display = 'none'
|
||||
}
|
||||
return objToStyle(style)
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.name,
|
||||
(newValue) => {
|
||||
|
||||
@ -91,6 +91,7 @@
|
||||
}
|
||||
|
||||
@include e(nav-item) {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
text-align: center;
|
||||
@ -119,6 +120,11 @@
|
||||
width: $-tabs-nav-line-width;
|
||||
background: $-tabs-nav-line-bg-color;
|
||||
border-radius: calc($-tabs-nav-line-height / 2);
|
||||
|
||||
@include m(inner){
|
||||
left: 50%;
|
||||
transform: translateX(-50%)
|
||||
}
|
||||
}
|
||||
|
||||
@include e(container) {
|
||||
|
||||
@ -1,12 +1,20 @@
|
||||
import { type ExtractPropTypes, type InjectionKey } from 'vue'
|
||||
import { type ComponentPublicInstance, type ExtractPropTypes, type InjectionKey } from 'vue'
|
||||
import { baseProps, makeBooleanProp, makeNumberProp, makeNumericProp, makeStringProp, numericProp } from '../common/props'
|
||||
|
||||
export type TabsProvide = {
|
||||
state: {
|
||||
activeIndex: number
|
||||
lineStyle: string // 激活项边框线样式
|
||||
inited: boolean // 是否初始化
|
||||
animating: boolean // 是否动画中
|
||||
mapShow: boolean // map的开关
|
||||
scrollLeft: number // scroll-view偏移量
|
||||
}
|
||||
props: Partial<TabsProps>
|
||||
}
|
||||
|
||||
export type TabsSlidable = 'auto' | 'always'
|
||||
|
||||
export const TABS_KEY: InjectionKey<TabsProvide> = Symbol('wd-tabs')
|
||||
|
||||
export const tabsProps = {
|
||||
@ -58,7 +66,24 @@ export const tabsProps = {
|
||||
/**
|
||||
* 切换动画过渡时间,单位毫秒
|
||||
*/
|
||||
duration: makeNumberProp(300)
|
||||
duration: makeNumberProp(300),
|
||||
/**
|
||||
* 是否开启滚动导航
|
||||
* 可选值:'auto' | 'always'
|
||||
* @default auto
|
||||
*/
|
||||
slidable: makeStringProp<TabsSlidable>('auto')
|
||||
}
|
||||
|
||||
export type TabsExpose = {
|
||||
// 修改选中的tab Index
|
||||
setActive: (value: number | string, init: boolean, setScroll: boolean) => void
|
||||
// scroll-view滑动到active的tab_nav
|
||||
scrollIntoView: () => void
|
||||
// 更新底部条样式
|
||||
updateLineStyle: (animation: boolean) => void
|
||||
}
|
||||
|
||||
export type TabsProps = ExtractPropTypes<typeof tabsProps>
|
||||
|
||||
export type TabsInstance = ComponentPublicInstance<TabsProps, TabsExpose>
|
||||
|
||||
@ -2,14 +2,14 @@
|
||||
<template v-if="sticky">
|
||||
<wd-sticky-box>
|
||||
<view
|
||||
:class="`wd-tabs ${customClass} ${slidableNum < items.length ? 'is-slide' : ''} ${mapNum < items.length && mapNum !== 0 ? 'is-map' : ''}`"
|
||||
:class="`wd-tabs ${customClass} ${innerSlidable ? 'is-slide' : ''} ${mapNum < items.length && mapNum !== 0 ? 'is-map' : ''}`"
|
||||
:style="customStyle"
|
||||
>
|
||||
<wd-sticky :offset-top="offsetTop">
|
||||
<!--头部导航容器-->
|
||||
<view class="wd-tabs__nav wd-tabs__nav--sticky">
|
||||
<view class="wd-tabs__nav--wrap">
|
||||
<scroll-view :scroll-x="slidableNum < items.length" scroll-with-animation :scroll-left="state.scrollLeft">
|
||||
<scroll-view :scroll-x="innerSlidable" scroll-with-animation :scroll-left="state.scrollLeft">
|
||||
<view class="wd-tabs__nav-container">
|
||||
<!--nav列表-->
|
||||
<view
|
||||
@ -20,6 +20,7 @@
|
||||
:style="state.activeIndex === index ? (color ? 'color:' + color : '') : inactiveColor ? 'color:' + inactiveColor : ''"
|
||||
>
|
||||
{{ item.title }}
|
||||
<view class="wd-tabs__line wd-tabs__line--inner" v-if="state.activeIndex === index && state.useInnerLine"></view>
|
||||
</view>
|
||||
<!--下划线-->
|
||||
<view class="wd-tabs__line" :style="state.lineStyle"></view>
|
||||
@ -76,11 +77,11 @@
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<view :class="`wd-tabs ${customClass} ${slidableNum < items.length ? 'is-slide' : ''} ${mapNum < items.length && mapNum !== 0 ? 'is-map' : ''}`">
|
||||
<view :class="`wd-tabs ${customClass} ${innerSlidable ? 'is-slide' : ''} ${mapNum < items.length && mapNum !== 0 ? 'is-map' : ''}`">
|
||||
<!--头部导航容器-->
|
||||
<view class="wd-tabs__nav">
|
||||
<view class="wd-tabs__nav--wrap">
|
||||
<scroll-view :scroll-x="slidableNum < items.length" scroll-with-animation :scroll-left="state.scrollLeft">
|
||||
<scroll-view :scroll-x="innerSlidable" scroll-with-animation :scroll-left="state.scrollLeft">
|
||||
<view class="wd-tabs__nav-container">
|
||||
<!--nav列表-->
|
||||
<view
|
||||
@ -91,6 +92,7 @@
|
||||
:style="state.activeIndex === index ? (color ? 'color:' + color : '') : inactiveColor ? 'color:' + inactiveColor : ''"
|
||||
>
|
||||
{{ item.title }}
|
||||
<view class="wd-tabs__line wd-tabs__line--inner" v-if="state.activeIndex === index && state.useInnerLine"></view>
|
||||
</view>
|
||||
<!--下划线-->
|
||||
<view class="wd-tabs__line" :style="state.lineStyle"></view>
|
||||
@ -143,7 +145,6 @@ export default {
|
||||
import wdIcon from '../wd-icon/wd-icon.vue'
|
||||
import wdSticky from '../wd-sticky/wd-sticky.vue'
|
||||
import wdStickyBox from '../wd-sticky-box/wd-sticky-box.vue'
|
||||
|
||||
import { computed, getCurrentInstance, onMounted, watch, nextTick, reactive, type CSSProperties } from 'vue'
|
||||
import { addUnit, checkNumRange, debounce, getRect, isDef, isNumber, isString, objToStyle } from '../common/util'
|
||||
import { useTouch } from '../composables/useTouch'
|
||||
@ -162,6 +163,7 @@ const { translate } = useTranslate('tabs')
|
||||
const state = reactive({
|
||||
activeIndex: 0, // 选中值的索引,默认第一个
|
||||
lineStyle: 'display:none;', // 激活项边框线样式
|
||||
useInnerLine: false, // 是否使用内部激活项边框线,当外部激活下划线未成功渲染时显示内部定位的
|
||||
inited: false, // 是否初始化
|
||||
animating: false, // 是否动画中
|
||||
mapShow: false, // map的开关
|
||||
@ -171,12 +173,16 @@ const state = reactive({
|
||||
// map的开关
|
||||
|
||||
const { children, linkChildren } = useChildren(TABS_KEY)
|
||||
linkChildren({ state })
|
||||
linkChildren({ state, props })
|
||||
|
||||
const { proxy } = getCurrentInstance() as any
|
||||
|
||||
const touch = useTouch()
|
||||
|
||||
const innerSlidable = computed(() => {
|
||||
return props.slidable === 'always' || items.value.length > props.slidableNum
|
||||
})
|
||||
|
||||
// tabs数据
|
||||
const items = computed(() => {
|
||||
return children.map((child, index) => {
|
||||
@ -197,12 +203,12 @@ const bodyStyle = computed(() => {
|
||||
})
|
||||
|
||||
/**
|
||||
* @description 修改选中的tab Index
|
||||
* @param {String |Number } value - radio绑定的value或者tab索引,默认值0
|
||||
* @param {Boolean } init - 是否伴随初始化操作
|
||||
* 更新激活项
|
||||
* @param value 激活值
|
||||
* @param init 是否已初始化
|
||||
* @param setScroll // 是否设置scroll-view滚动
|
||||
*/
|
||||
const setActive = debounce(
|
||||
function (value: number | string = 0, init: boolean = false, setScroll: boolean = true) {
|
||||
const updateActive = (value: number | string = 0, init: boolean = false, setScroll: boolean = true) => {
|
||||
// 没有tab子元素,不执行任何操作
|
||||
if (items.value.length === 0) return
|
||||
|
||||
@ -215,10 +221,14 @@ const setActive = debounce(
|
||||
scrollIntoView()
|
||||
}
|
||||
setActiveTab()
|
||||
},
|
||||
100,
|
||||
{ leading: false }
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 修改选中的tab Index
|
||||
* @param {String |Number } value - radio绑定的value或者tab索引,默认值0
|
||||
* @param {Boolean } init - 是否伴随初始化操作
|
||||
*/
|
||||
const setActive = debounce(updateActive, 100, { leading: false })
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
@ -282,7 +292,8 @@ watch(
|
||||
onMounted(() => {
|
||||
state.inited = true
|
||||
nextTick(() => {
|
||||
setActive(props.modelValue, true)
|
||||
updateActive(props.modelValue, true)
|
||||
state.useInnerLine = true
|
||||
})
|
||||
})
|
||||
|
||||
@ -305,15 +316,15 @@ function toggleMap() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 更新navBar underline的偏移量
|
||||
* @param {Boolean} animation 是否伴随动画
|
||||
* 更新 underline的偏移量
|
||||
* @param animation 是否开启动画
|
||||
*/
|
||||
function updateLineStyle(animation: boolean = true) {
|
||||
if (!state.inited) return
|
||||
const { lineWidth, lineHeight } = props
|
||||
|
||||
getRect($item, true, proxy).then((rects) => {
|
||||
const lineStyle: CSSProperties = {}
|
||||
|
||||
if (isDef(lineWidth)) {
|
||||
lineStyle.width = addUnit(lineWidth)
|
||||
}
|
||||
@ -323,11 +334,14 @@ function updateLineStyle(animation: boolean = true) {
|
||||
}
|
||||
const rect = rects[state.activeIndex]
|
||||
let left = rects.slice(0, state.activeIndex).reduce((prev, curr) => prev + Number(curr.width), 0) + Number(rect.width) / 2
|
||||
if (left) {
|
||||
lineStyle.transform = `translateX(${left}px) translateX(-50%)`
|
||||
if (animation) {
|
||||
lineStyle.transition = 'width 300ms ease, transform 300ms ease'
|
||||
}
|
||||
state.useInnerLine = false
|
||||
state.lineStyle = objToStyle(lineStyle)
|
||||
}
|
||||
})
|
||||
}
|
||||
/**
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user