mirror of
https://gitee.com/wot-design-uni/wot-design-uni.git
synced 2025-12-06 17:18:40 +08:00
refactor: ♻️ 索引栏组件兼容钉钉小程序
This commit is contained in:
parent
f84e9affb1
commit
0d18b39ff6
@ -8,11 +8,11 @@
|
||||
|
||||
使用一个固定高度的元素包裹`wd-index-bar`组件,组件的宽高会和包裹元素相同。
|
||||
|
||||
将`wd-index-anchor`作为子组件使用,会根据anchor组件的`index`属性生成锚点以及侧边栏。
|
||||
将`wd-index-anchor`作为子组件使用,会根据 anchor 组件的`index`属性生成锚点以及侧边栏。
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<view class="wrap" :style="{ height: wrapHeight + 'px' }">
|
||||
<view class="wraper">
|
||||
<wd-index-bar>
|
||||
<view v-for="item in data" :key="item.index" class="city-wrap">
|
||||
<wd-index-anchor :index="item.index" />
|
||||
@ -22,50 +22,96 @@
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { onMounted } from 'vue';
|
||||
|
||||
const wrapHeight = ref(400)
|
||||
|
||||
onMounted(() => {
|
||||
const info = uni.getWindowInfo()
|
||||
wrapHeight.value = info.windowHeight
|
||||
})
|
||||
import { ref } from 'vue'
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
const data = ref([
|
||||
{
|
||||
index: 'A',
|
||||
data: ["阿坝", "阿拉善", "阿里", "安康", "安庆", "鞍山", "安顺", "安阳", "澳门",]
|
||||
data: ['阿坝', '阿拉善', '阿里', '安康', '安庆', '鞍山', '安顺', '安阳', '澳门']
|
||||
},
|
||||
{
|
||||
index: 'B',
|
||||
data: ["北京", "白银", "保定", "宝鸡", "保山", "包头", "巴中", "北海", "蚌埠", "本溪", "毕节", "滨州", "百色", "亳州"]
|
||||
data: ['北京', '白银', '保定', '宝鸡', '保山', '包头', '巴中', '北海', '蚌埠', '本溪', '毕节', '滨州', '百色', '亳州']
|
||||
},
|
||||
{
|
||||
index: 'C',
|
||||
data: ["重庆", "成都", "长沙", "长春", "沧州", "常德", "昌都", "长治", "常州", "巢湖", "潮州", "承德", "郴州", "赤峰", "池州", "崇左", "楚雄", "滁州", "朝阳"]
|
||||
data: [
|
||||
'重庆',
|
||||
'成都',
|
||||
'长沙',
|
||||
'长春',
|
||||
'沧州',
|
||||
'常德',
|
||||
'昌都',
|
||||
'长治',
|
||||
'常州',
|
||||
'巢湖',
|
||||
'潮州',
|
||||
'承德',
|
||||
'郴州',
|
||||
'赤峰',
|
||||
'池州',
|
||||
'崇左',
|
||||
'楚雄',
|
||||
'滁州',
|
||||
'朝阳'
|
||||
]
|
||||
},
|
||||
{
|
||||
index: 'D',
|
||||
data: ["大连", "东莞", "大理", "丹东", "大庆", "大同", "大兴安岭", "德宏", "德阳", "德州", "定西", "迪庆", "东营"]
|
||||
data: ['大连', '东莞', '大理', '丹东', '大庆', '大同', '大兴安岭', '德宏', '德阳', '德州', '定西', '迪庆', '东营']
|
||||
},
|
||||
{
|
||||
index: 'E',
|
||||
data: ["鄂尔多斯", "恩施", "鄂州"]
|
||||
data: ['鄂尔多斯', '恩施', '鄂州']
|
||||
},
|
||||
{
|
||||
index: 'F',
|
||||
data: ["福州", "防城港", "佛山", "抚顺", "抚州", "阜新", "阜阳"]
|
||||
data: ['福州', '防城港', '佛山', '抚顺', '抚州', '阜新', '阜阳']
|
||||
},
|
||||
{
|
||||
index: 'G',
|
||||
data: ["广州", "桂林", "贵阳", "甘南", "赣州", "甘孜", "广安", "广元", "贵港", "果洛"]
|
||||
data: ['广州', '桂林', '贵阳', '甘南', '赣州', '甘孜', '广安', '广元', '贵港', '果洛']
|
||||
},
|
||||
{
|
||||
index: 'H',
|
||||
data: ["杭州", "哈尔滨", "合肥", "海口", "呼和浩特", "海北", "海东", "海南", "海西", "邯郸", "汉中", "鹤壁", "河池", "鹤岗", "黑河", "衡水", "衡阳", "河源", "贺州", "红河", "淮安", "淮北", "怀化", "淮南", "黄冈", "黄南", "黄山", "黄石", "惠州", "葫芦岛", "呼伦贝尔", "湖州", "菏泽"]
|
||||
data: [
|
||||
'杭州',
|
||||
'哈尔滨',
|
||||
'合肥',
|
||||
'海口',
|
||||
'呼和浩特',
|
||||
'海北',
|
||||
'海东',
|
||||
'海南',
|
||||
'海西',
|
||||
'邯郸',
|
||||
'汉中',
|
||||
'鹤壁',
|
||||
'河池',
|
||||
'鹤岗',
|
||||
'黑河',
|
||||
'衡水',
|
||||
'衡阳',
|
||||
'河源',
|
||||
'贺州',
|
||||
'红河',
|
||||
'淮安',
|
||||
'淮北',
|
||||
'怀化',
|
||||
'淮南',
|
||||
'黄冈',
|
||||
'黄南',
|
||||
'黄山',
|
||||
'黄石',
|
||||
'惠州',
|
||||
'葫芦岛',
|
||||
'呼伦贝尔',
|
||||
'湖州',
|
||||
'菏泽'
|
||||
]
|
||||
}
|
||||
])
|
||||
</script>
|
||||
@ -78,10 +124,13 @@ const data = ref([
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.city-wrap .city:last-child {
|
||||
margin-bottom: 10px;
|
||||
.wraper {
|
||||
height: calc(100vh - var(--window-top));
|
||||
height: calc(100vh - var(--window-top) - constant(safe-area-inset-bottom));
|
||||
height: calc(100vh - var(--window-top) - env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
|
||||
.wot-theme-dark {
|
||||
.city {
|
||||
color: white;
|
||||
@ -91,6 +140,12 @@ const data = ref([
|
||||
</style>
|
||||
```
|
||||
|
||||
## Attributes
|
||||
|
||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 | 最低版本 |
|
||||
| ------ | ------------ | ------- | ------ | ------ | -------- |
|
||||
| sticky | 索引是否吸顶 | boolean | - | false | - |
|
||||
|
||||
## IndexAnchor Attributes
|
||||
|
||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 | 最低版本 |
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<wd-config-provider :theme="theme" :theme-vars="isRed ? themeVars : {}">
|
||||
<wd-toast />
|
||||
<view :class="['page-wraper', safeAreaInsetBottom ? 'is-safe' : '']">
|
||||
<view class="page-wraper">
|
||||
<wd-cell title="切换暗黑" title-width="240px" center v-if="showDarkMode">
|
||||
<wd-switch v-model="isDark" />
|
||||
</wd-cell>
|
||||
@ -9,9 +9,9 @@
|
||||
<wd-switch v-model="isRed" />
|
||||
</wd-cell>
|
||||
<slot />
|
||||
<wd-gap height="0" v-if="safeAreaInsetBottom" safe-area-bottom></wd-gap>
|
||||
</view>
|
||||
<wd-notify />
|
||||
<wd-gap height="0" safe-area-bottom></wd-gap>
|
||||
</wd-config-provider>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
@ -67,10 +67,5 @@ onMounted(() => {
|
||||
.page-wraper {
|
||||
min-height: calc(100vh - var(--window-top));
|
||||
box-sizing: border-box;
|
||||
.is-safe {
|
||||
padding-bottom: 0;
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<page-wraper>
|
||||
<view class="wrap" :style="{ height: wrapHeight + 'px' }">
|
||||
<wd-index-bar>
|
||||
<view class="wraper">
|
||||
<wd-index-bar sticky>
|
||||
<view v-for="item in data" :key="item.index" class="city-wrap">
|
||||
<wd-index-anchor :index="item.index" />
|
||||
<view v-for="city in item.data" class="city" :key="city">{{ city }}</view>
|
||||
<view v-for="city in item.data" class="city" :key="city" @click="handleClick(item.index, city)">{{ city }}</view>
|
||||
</view>
|
||||
</wd-index-bar>
|
||||
</view>
|
||||
@ -12,15 +12,9 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useToast } from '@/uni_modules/wot-design-uni'
|
||||
import { ref } from 'vue'
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
const wrapHeight = ref(400)
|
||||
|
||||
onMounted(() => {
|
||||
const info = uni.getWindowInfo()
|
||||
wrapHeight.value = info.windowHeight
|
||||
})
|
||||
const { show: showToast } = useToast()
|
||||
|
||||
const data = ref([
|
||||
{
|
||||
@ -110,6 +104,10 @@ const data = ref([
|
||||
]
|
||||
}
|
||||
])
|
||||
|
||||
function handleClick(index: string, city: string) {
|
||||
showToast(`当前点击项:${index},城市:${city}`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@ -120,8 +118,10 @@ const data = ref([
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.city-wrap .city:last-child {
|
||||
margin-bottom: 10px;
|
||||
.wraper {
|
||||
height: calc(100vh - var(--window-top));
|
||||
height: calc(100vh - var(--window-top) - constant(safe-area-inset-bottom));
|
||||
height: calc(100vh - var(--window-top) - env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.wot-theme-dark {
|
||||
|
||||
@ -123,7 +123,7 @@ import { useCell } from '../composables/useCell'
|
||||
import { FORM_KEY, type FormItemRule } from '../wd-form/types'
|
||||
import { useParent } from '../composables/useParent'
|
||||
import { useTranslate } from '../composables/useTranslate'
|
||||
import { calendarProps, CalendarExpose } from './types'
|
||||
import { calendarProps, type CalendarExpose } from './types'
|
||||
import type { CalendarType } from '../wd-calendar-view/types'
|
||||
const { translate } = useTranslate('calendar')
|
||||
|
||||
|
||||
@ -8,9 +8,42 @@
|
||||
}
|
||||
}
|
||||
|
||||
// #ifdef MP-DINGTALK
|
||||
@include b(index-anchor-ding) {
|
||||
|
||||
@include when(sticky){
|
||||
position: sticky;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
|
||||
@include b(index-anchor) {
|
||||
background-color: $-color-gray-3;
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
color: $-color-title;
|
||||
|
||||
@include when(sticky){
|
||||
position: sticky;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@include b(index-anchor) {
|
||||
background-color: $-color-gray-3;
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
color: $-color-title;
|
||||
|
||||
@include when(sticky){
|
||||
position: sticky;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,32 +1,49 @@
|
||||
<template>
|
||||
<view class="wd-index-anchor" :class="customClass" :style="customStyle">
|
||||
<slot>
|
||||
{{ index }}
|
||||
</slot>
|
||||
<!-- #ifdef MP-DINGTALK -->
|
||||
<view :class="`wd-index-anchor-ding ${indexBar && indexBar.props.sticky && indexBar.anchorState.activeIndex === index ? 'is-sticky' : ''}`">
|
||||
<!-- #endif -->
|
||||
<view
|
||||
:class="`wd-index-anchor ${indexBar && indexBar.props.sticky && indexBar.anchorState.activeIndex === index ? 'is-sticky' : ''} ${customClass}`"
|
||||
:style="customStyle"
|
||||
:id="indexAnchorId"
|
||||
>
|
||||
<slot>
|
||||
{{ index }}
|
||||
</slot>
|
||||
</view>
|
||||
<!-- #ifdef MP-DINGTALK -->
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { indexAnchorProps } from './type'
|
||||
import { onMounted, getCurrentInstance, inject } from 'vue'
|
||||
import { onMounted, getCurrentInstance, ref } from 'vue'
|
||||
import { indexBarInjectionKey } from '../wd-index-bar/type'
|
||||
import { getRect } from '../common/util'
|
||||
import { getRect, isDef, uuid } from '../common/util'
|
||||
import { useParent } from '../composables/useParent'
|
||||
|
||||
const props = defineProps(indexAnchorProps)
|
||||
|
||||
const indexBar = inject(indexBarInjectionKey)!
|
||||
const { parent: indexBar } = useParent(indexBarInjectionKey)
|
||||
|
||||
const indexAnchorId = ref<string>(`indexBar${uuid()}`)
|
||||
|
||||
const { proxy } = getCurrentInstance()!
|
||||
|
||||
function getInfo() {
|
||||
getRect('.wd-index-anchor', false, proxy).then((res) => {
|
||||
const anchor = indexBar.anchorList.value.find((v) => v.index === props.index)!
|
||||
anchor.top = res.top!
|
||||
getRect(`#${indexAnchorId.value}`, false, proxy).then((res) => {
|
||||
if (isDef(indexBar)) {
|
||||
const anchor = indexBar.anchorState.anchorList.find((v) => v.index === props.index)!
|
||||
anchor.top = res.top!
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
indexBar.anchorList.value.push({ top: 0, index: props.index })
|
||||
if (isDef(indexBar)) {
|
||||
indexBar.anchorState.anchorList.push({ top: 0, index: props.index })
|
||||
}
|
||||
getInfo()
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import type { Ref } from 'vue'
|
||||
import type { InjectionKey } from 'vue'
|
||||
import type { ExtractPropTypes } from 'vue'
|
||||
import { makeBooleanProp } from '../common/props'
|
||||
|
||||
export type AnchorIndex = number | string
|
||||
|
||||
@ -9,10 +10,21 @@ export interface AnchorItem {
|
||||
index: AnchorIndex
|
||||
}
|
||||
|
||||
export const indexBarProps = {}
|
||||
export const indexBarProps = {
|
||||
/**
|
||||
* @description 索引是否吸顶
|
||||
*/
|
||||
sticky: makeBooleanProp(false)
|
||||
}
|
||||
|
||||
export type IndexBarProps = ExtractPropTypes<typeof indexBarProps>
|
||||
|
||||
export const indexBarInjectionKey = 'wdIndexBar' as unknown as InjectionKey<{
|
||||
anchorList: Ref<AnchorItem[]>
|
||||
}>
|
||||
export type InderBarProvide = {
|
||||
props: { sticky?: boolean }
|
||||
anchorState: {
|
||||
anchorList: AnchorItem[] // 锚点列表
|
||||
activeIndex: AnchorIndex | null // 当前激活的索引
|
||||
}
|
||||
}
|
||||
|
||||
export const indexBarInjectionKey: InjectionKey<InderBarProvide> = Symbol('wd-index-bar')
|
||||
|
||||
@ -1,34 +1,55 @@
|
||||
<template>
|
||||
<view class="wd-index-bar">
|
||||
<scroll-view :scrollTop="scrollTop" :scroll-y="true" class="wd-index-bar__content" @scroll="hanleScroll">
|
||||
<slot></slot>
|
||||
</scroll-view>
|
||||
<view
|
||||
class="wd-index-bar__sidebar"
|
||||
@touchmove.stop.prevent="handleTouchMove"
|
||||
@touchend.stop.prevent="handleTouchEnd"
|
||||
@touchcancel.stop.prevent="handleTouchEnd"
|
||||
>
|
||||
<view class="wd-index-bar__index" :class="{ 'is-active': item.index === currentAnchorIndex }" v-for="item in anchorList" :key="item.index">
|
||||
{{ item.index }}
|
||||
<view class="wd-index-bar" :id="indexBarId">
|
||||
<!-- #ifdef MP-DINGTALK -->
|
||||
<view class="wd-index-bar" :id="indexBarId">
|
||||
<!-- #endif -->
|
||||
<scroll-view :scrollTop="scrollTop" :scroll-y="true" class="wd-index-bar__content" @scroll="hanleScroll">
|
||||
<slot></slot>
|
||||
</scroll-view>
|
||||
<view
|
||||
class="wd-index-bar__sidebar"
|
||||
@touchmove.stop.prevent="handleTouchMove"
|
||||
@touchend.stop.prevent="handleTouchEnd"
|
||||
@touchcancel.stop.prevent="handleTouchEnd"
|
||||
>
|
||||
<view
|
||||
class="wd-index-bar__index"
|
||||
:class="{ 'is-active': item.index === state.activeIndex }"
|
||||
v-for="item in state.anchorList"
|
||||
:key="item.index"
|
||||
>
|
||||
{{ item.index }}
|
||||
</view>
|
||||
</view>
|
||||
<!-- #ifdef MP-DINGTALK -->
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { AnchorItem, AnchorIndex } from './type'
|
||||
import { indexBarInjectionKey } from './type'
|
||||
import { provide, ref, getCurrentInstance, onMounted } from 'vue'
|
||||
import { getRect } from '../common/util'
|
||||
import { indexBarInjectionKey, indexBarProps } from './type'
|
||||
import { ref, getCurrentInstance, onMounted, reactive } from 'vue'
|
||||
import { getRect, isDef, uuid } from '../common/util'
|
||||
import { useChildren } from '../composables/useChildren'
|
||||
|
||||
const props = defineProps(indexBarProps)
|
||||
|
||||
const indexBarId = ref<string>(`indexBar${uuid()}`)
|
||||
|
||||
const { proxy } = getCurrentInstance()!
|
||||
|
||||
const anchorList = ref<AnchorItem[]>([])
|
||||
provide(indexBarInjectionKey, { anchorList })
|
||||
const state = reactive({
|
||||
activeIndex: null as AnchorIndex | null,
|
||||
anchorList: [] as AnchorItem[]
|
||||
})
|
||||
|
||||
const { linkChildren } = useChildren(indexBarInjectionKey)
|
||||
|
||||
linkChildren({ props, anchorState: state })
|
||||
|
||||
const scrollTop = ref(0)
|
||||
const currentAnchorIndex = ref<AnchorIndex | null>()
|
||||
|
||||
// 组件距离页面顶部的高度
|
||||
let offsetTop = 0
|
||||
@ -41,10 +62,10 @@ let sidebarInfo = {
|
||||
|
||||
function init() {
|
||||
setTimeout(() => {
|
||||
currentAnchorIndex.value = anchorList.value[0]?.index
|
||||
state.activeIndex = state.anchorList[0]?.index
|
||||
|
||||
Promise.all([
|
||||
getRect('.wd-index-bar', false, proxy),
|
||||
getRect(`#${indexBarId.value}`, false, proxy),
|
||||
getRect('.wd-index-bar__sidebar', false, proxy),
|
||||
getRect('.wd-index-bar__index', false, proxy)
|
||||
]).then(([bar, sidebar, index]) => {
|
||||
@ -61,26 +82,25 @@ onMounted(() => {
|
||||
|
||||
function hanleScroll(scrollEvent: any) {
|
||||
const { detail } = scrollEvent
|
||||
scrollTop.value = detail.scrollTop
|
||||
const anchor = anchorList.value.find((item, index) => {
|
||||
if (anchorList.value[index + 1] == null) return true
|
||||
if (item.top - offsetTop <= scrollTop.value && anchorList.value[index + 1].top - offsetTop > scrollTop.value) return true
|
||||
const anchor = state.anchorList.find((item, index) => {
|
||||
if (!isDef(state.anchorList[index + 1])) return true
|
||||
if (item.top - offsetTop <= detail.scrollTop && state.anchorList[index + 1].top - offsetTop > detail.scrollTop) return true
|
||||
return false
|
||||
})!
|
||||
currentAnchorIndex.value = anchor.index
|
||||
state.activeIndex = anchor.index
|
||||
}
|
||||
|
||||
function getAnchorByPageY(pageY: number) {
|
||||
const y = pageY - sidebarInfo.offsetTop
|
||||
let idx = Math.floor(y / sidebarInfo.indexHeight)
|
||||
if (idx < 0) idx = 0
|
||||
else if (idx > anchorList.value.length - 1) idx = anchorList.value.length - 1
|
||||
return anchorList.value[idx]
|
||||
else if (idx > state.anchorList.length - 1) idx = state.anchorList.length - 1
|
||||
return state.anchorList[idx]
|
||||
}
|
||||
|
||||
function handleTouchMove(e: TouchEvent) {
|
||||
const clientY = e.touches[0].pageY
|
||||
currentAnchorIndex.value = getAnchorByPageY(clientY).index
|
||||
state.activeIndex = getAnchorByPageY(clientY).index
|
||||
}
|
||||
|
||||
function handleTouchEnd(e: TouchEvent) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user