Initial commit

This commit is contained in:
tanxin
2026-10-29 10:08:39 +08:00
commit 4c6243a274
124 changed files with 17359 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
## 1.0.1(2026-04-30)
- 更新文档
## 1.0.0(2026-04-30)
- 支持 uni-index-bar 组件
@@ -0,0 +1,384 @@
<template>
<!-- #ifdef MP-ALIPAY -->
<view class="index-text" ref="indexTextRef" v-bind="$attrs" @touchstart="onTouchStart" @touchmove.stop.prevent="onTouchMove"
@touchend="onTouchEnd">
<text v-for="(item, index) in indexList" :key="item" class="index-item">{{ item }}</text>
</view>
<!-- #endif -->
<!-- #ifndef MP-ALIPAY -->
<text class="index-text" ref="indexTextRef" v-bind="$attrs" @touchstart="onTouchStart" @touchmove.stop.prevent="onTouchMove"
@touchend="onTouchEnd">{{ indexs }}</text>
<!-- #endif -->
<!-- indicator with arrow -->
<view v-show="touching" class="indicator" :class="indicatorViewClass" :style="indicatorStyle">
<text class="indicator-text" :class="indicatorTextClass">{{ currentIndex }}</text>
</view>
</template>
<script lang="ts" setup>
/**
* 索引列表
* @description 索引列表组件
* @uniPlatform {
* "app": {
* "android": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.21"
* },
* "ios": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.11"
* },
* "harmony": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.07"
* }
* },
* "mp": {
* "weixin": {
* "uniVer": "x",
* "unixVer": "5.08"
* },
* "alipay": {
* "uniVer": "x",
* "unixVer": "5.31"
* }
* },
* "web": {
* "uniVer": "x",
* "unixVer": "5.07"
* }
* }
*/
defineOptions({
externalClasses: ['indicator-view-class', 'indicator-text-class']
})
type Props = {
/**
* @description 索引字符列表,使用换行符 \n 分隔,每行作为一个索引项
* @default "A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM\nN\nO\nP\nQ\nR\nS\nT\nU\nV\nW\nX\nY\nZ"
* @uniPlatform {
* "app": {
* "android": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.21"
* },
* "ios": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.11"
* },
* "harmony": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.07"
* }
* },
* "mp": {
* "weixin": {
* "uniVer": "x",
* "unixVer": "5.08"
* },
* "alipay": {
* "uniVer": "x",
* "unixVer": "5.31"
* }
* },
* "web": {
* "uniVer": "x",
* "unixVer": "5.07"
* }
* }
*/
indexs? : string,
/**
* @description 指示器容器的自定义样式类,触摸时显示当前索引的气泡
* @default ""
* @uniPlatform {
* "app": {
* "android": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.21"
* },
* "ios": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.11"
* },
* "harmony": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.07"
* }
* },
* "mp": {
* "weixin": {
* "uniVer": "x",
* "unixVer": "5.08"
* },
* "alipay": {
* "uniVer": "x",
* "unixVer": "5.31"
* }
* },
* "web": {
* "uniVer": "x",
* "unixVer": "5.07"
* }
* }
*/
indicatorViewClass? : string.ClassString,
/**
* @description 指示器内文字的自定义样式类
* @default ""
* @uniPlatform {
* "app": {
* "android": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.21"
* },
* "ios": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.11"
* },
* "harmony": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.07"
* }
* },
* "mp": {
* "weixin": {
* "uniVer": "x",
* "unixVer": "5.08"
* },
* "alipay": {
* "uniVer": "x",
* "unixVer": "5.31"
* }
* },
* "web": {
* "uniVer": "x",
* "unixVer": "5.07"
* }
* }
*/
indicatorTextClass? : string.ClassString
}
const props = withDefaults(defineProps<Props>(), {
indexs: 'A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM\nN\nO\nP\nQ\nR\nS\nT\nU\nV\nW\nX\nY\nZ',
indicatorViewClass: '',
indicatorTextClass: ''
})
const emit = defineEmits<{
/**
* @description 选择索引时触发,参数为当前选择的索引字符,类型为 string
* @uniPlatform {
* "app": {
* "android": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.21"
* },
* "ios": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.11"
* },
* "harmony": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.07"
* }
* },
* "mp": {
* "weixin": {
* "uniVer": "x",
* "unixVer": "5.08"
* },
* "alipay": {
* "uniVer": "x",
* "unixVer": "5.31"
* }
* },
* "web": {
* "uniVer": "x",
* "unixVer": "5.07"
* }
* }
*/
select: [index: string]
}>()
// 将索引字符串按换行分割为数组
const indexList = computed(() : string[] => props.indexs.split('\n'))
const currentIndex = ref('')
const touching = ref(false)
const indicatorTop = ref(0)
const indicatorStyle = computed(() : string => {
return `top: ${indicatorTop.value}px;`
})
// 布局缓存变量
let textTop = 0
let itemHeight = 0
let indexCount = 0
let cachedIndexList : string[] = []
const navBarHeight = uni.getWindowInfo().windowTop
// #ifndef MP
const indexTextRef = ref<UniTextElement | null>(null)
// #endif
// 更新布局信息
const updateLayout = () => {
// 缓存索引数组和长度,避免在 touch 事件中重复访问 computed
cachedIndexList = indexList.value
indexCount = cachedIndexList.length
// #ifndef MP
// App/Web 端使用 getBoundingClientRect 同步获取
const textEl = indexTextRef.value
if (textEl != null) {
const rect = textEl.getBoundingClientRect()
textTop = rect.top
itemHeight = rect.height / indexCount
}
// #endif
}
// #ifdef MP
// 小程序需要在 setup 阶段保存 instance,因为异步回调中 getCurrentInstance() 返回 null
const instance = getCurrentInstance()
// 小程序端使用 createSelectorQuery 异步获取布局
const updateLayoutAsync = () => {
cachedIndexList = indexList.value
indexCount = cachedIndexList.length
if (instance == null) return
uni.createSelectorQuery()
.in(instance.proxy)
.select('.index-text')
.boundingClientRect((rect) => {
if (rect != null) {
textTop = rect.top as number
itemHeight = (rect.height as number) / indexCount
}
})
.exec()
}
onMounted(() => {
nextTick(() => {
updateLayoutAsync()
})
})
watch(() => props.indexs, () => {
nextTick(() => {
updateLayoutAsync()
})
})
// #endif
const getIndexFromY = (y : number) : number => {
const relY = y - textTop
let idx = Math.floor(relY / itemHeight)
if (idx < 0) idx = 0
if (idx >= indexCount) idx = indexCount - 1
return idx
}
// 更新指示器位置
const updateIndicator = (idx : number) => {
indicatorTop.value = textTop + idx * itemHeight + itemHeight / 2 - 25 - navBarHeight
}
const onTouchStart = (e : UniTouchEvent) => {
updateLayout()
touching.value = true
const touch = e.touches[0]
const idx = getIndexFromY(touch.clientY)
const selectedIndex = cachedIndexList[idx]
currentIndex.value = selectedIndex
updateIndicator(idx)
emit('select', selectedIndex)
}
const onTouchMove = (e : UniTouchEvent) => {
// #ifdef APP && UNI-APP-X
e.preventDefault()
// #endif
// #ifdef WEB && UNI-APP-X
if (e.cancelable) {
e.preventDefault()
}
// #endif
const touch = e.touches[0]
const idx = getIndexFromY(touch.clientY)
const newIndex = cachedIndexList[idx]
if (newIndex != currentIndex.value) {
currentIndex.value = newIndex
updateIndicator(idx)
emit('select', newIndex)
}
}
const onTouchEnd = (_e : UniTouchEvent) => {
touching.value = false
}
</script>
<style>
.index-text {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
width: 28px;
font-size: 11px;
color: #757575;
text-align: center;
line-height: 1.4;
z-index: 1000;
}
.index-item {
text-align: center;
}
.indicator {
position: absolute;
right: 50px;
width: 50px;
height: 50px;
background-color: #c9c9c9;
border-radius: 25px 25px 25px 0;
transform: rotate(225deg);
align-items: center;
justify-content: center;
z-index: 99;
}
.indicator-text {
font-size: 22px;
color: #ffffff;
text-align: center;
transform: rotate(-225deg);
}
</style>
+75
View File
@@ -0,0 +1,75 @@
{
"id": "uni-index-bar",
"displayName": "uni-index-bar 索引栏",
"version": "1.0.1",
"description": "uni-index-bar 索引栏组件,用于显示索引栏,支持自定义索引栏的样式和内容。",
"isExt": true,
"extUrl": "https://ext.dcloud.net.cn/plugin?id=27847",
"keywords": [
"uni-index-bar",
"索引栏",
"index-bar",
"uni-app-x",
"组件"
],
"repository": "https://gitcode.com/dcloud/uni-ui-x",
"engines": {
"HBuilderX": "^5.08",
"uni-app-x": "^5.08",
"uni-app": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"type": "component-vue",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"darkmode": "x",
"i18n": "x",
"widescreen": "x",
"npmurl": ""
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "x",
"aliyun": "x",
"alipay": "x"
},
"client": {
"uni-app-x": {
"web": {
"safari": "√",
"chrome": "√"
},
"app": {
"android": "√",
"ios": "√",
"harmony": "√"
},
"mp": {
"weixin": "√",
"alipay": "√"
}
}
}
}
}
}
+98
View File
@@ -0,0 +1,98 @@
### 介绍
本组件以竖排方式显示字母索引,常悬浮或固定于列表右侧,用户通过点击或滑动索引条上的字母索引,触发 `select` 事件。在该事件中通过代码驱动指定的列表滚动到该字母索引所在的位置。
本组件为绝对定位,默认垂直居中于父容器右侧。
本组件不包括 list-view 组件,需要配合 list-view 使用。list-view 中需要有对应索引的 list-item,且这些 list-item 需要有 id 属性,可以通过 `scroll-into-view` 属性或 `scrollTop` 属性跳转。
本组件包括一个指示器(indicator)子组件。当用户手指触摸在索引条上的某个字母时,在触摸的字母左边悬浮显示一个水滴形气泡,箭头指向当前触摸的字母。
### 基本用法
```html
<view style="flex: 1; position: relative;">
<list-view ref="listRef" style="flex: 1;">
<!-- 列表内容 -->
</list-view>
<uni-index-bar @select="onSelect"></uni-index-bar>
</view>
```
```ts
const onSelect = (index: string) => {
// 处理索引选择,滚动列表到对应位置
}
```
### 事件
| 事件名 | 说明 | 回调参数 |
|--------|------|----------|
| select | 选择索引时触发 | index: string - 当前选择的索引字符 |
### 外部样式类
| 类名 | 说明 |
|------|------|
| indicator-view-class | 指示器容器样式,可自定义气泡背景色等 |
| indicator-text-class | 指示器文字样式,可自定义文字颜色等 |
### 示例
#### 自定义索引
```html
<uni-index-bar :indexs="'☆\nA\nB\nC\n#'" @select="onSelect"></uni-index-bar>
```
#### 自定义样式
```html
<uni-index-bar
indicator-view-class="custom-indicator"
indicator-text-class="custom-indicator-text"
style="color: aqua;"
@select="onSelect">
</uni-index-bar>
```
```css
.custom-indicator {
background-color: #007aff;
}
.custom-indicator-text {
color: #ffff00;
}
```
#### 配合 list-view 使用
```html
<view style="flex: 1; position: relative;">
<list-view ref="listRef" style="flex: 1;" :scroll-into-view="indexViewID">
<template v-for="group in cityGroups" :key="group.index">
<list-item :id="'idx-' + group.index" type="header">
<view class="group-header">
<text>{{ group.index }}</text>
</view>
</list-item>
<list-item v-for="city in group.cities" :key="city.id" type="city">
<view class="city-item">
<text>{{ city.name }}</text>
</view>
</list-item>
</template>
</list-view>
<uni-index-bar :indexs="indexList" @select="onSelect"></uni-index-bar>
</view>
```
```ts
const indexList = cityGroups.map((g): string => g.index).join('\n')
const indexViewID = ref("")
const onSelect = (index: string) => {
indexViewID.value = 'idx-' + index
}
```