介绍
用于页面中信息快速检索,可以根据目录中的页码快速找到所需的内容。
引入
js
import Indexes from 'sard-uniapp/components/indexes/indexes.vue'
import IndexesAnchor from 'sard-uniapp/components/indexes-anchor/indexes-anchor.vue'代码演示
基础使用
Indexes 里面的 IndexesAnchor 组件会被收集起来,用于放置锚点和自动生成右侧的导航。导航和锚点会有一个联动效果。
vue
<template>
<sar-form :model="model" direction="vertical">
<sar-form-item>
<sar-input v-model="model.phone" inlaid placeholder="请输入手机号">
<template #prepend>
<sar-button
type="pale-text"
color="inherit"
root-style="padding-left: 0"
@click="areaVisible = true"
>
+{{ model.areaCode }}
<sar-icon
name="caret-down"
root-style="top: 4rpx; margin-left: 4rpx"
/>
</sar-button>
</template>
</sar-input>
</sar-form-item>
</sar-form>
<AreaCode v-model:visible="areaVisible" @select="onSelect" />
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import AreaCode from './AreaCode.vue'
const model = reactive({
areaCode: '86',
phone: '',
})
const areaVisible = ref(false)
const onSelect = (code: string) => {
model.areaCode = code
}
</script>vue
<template>
<sar-form :model="model" direction="vertical">
<sar-form-item>
<sar-input v-model="model.phone" inlaid placeholder="请输入手机号">
<template #prepend>
<sar-button
type="pale-text"
color="inherit"
root-style="padding-left: 0"
@click="areaVisible = true"
>
+{{ model.areaCode }}
<sar-icon
name="caret-down"
root-style="top: 4rpx; margin-left: 4rpx"
/>
</sar-button>
</template>
</sar-input>
</sar-form-item>
</sar-form>
<AreaCode v-model:visible="areaVisible" @select="onSelect" />
</template>
<script setup lang="js">
import { reactive, ref } from "vue";
const model = reactive({
areaCode: "86",
phone: ""
});
const areaVisible = ref(false);
const onSelect = (code) => {
model.areaCode = code;
};
</script>AreaCode.vue
vue
<template>
<sar-popout
v-model:visible="innerVisible"
title="请选择区号"
:show-footer="false"
keep-render
>
<template #visible="{ already }">
<template v-if="already">
<sar-search
root-style="cursor: pointer"
placeholder="搜索"
shape="round"
readonly
align="center"
@click="searchVisible = true"
/>
<sar-indexes root-style="height: 70vh">
<view v-for="section in sectionList" :key="section.anchor">
<sar-indexes-anchor
:name="section.anchor"
root-style="height: 0; overflow: hidden;"
/>
<sar-indexes-anchor>
{{ section.anchor }}
</sar-indexes-anchor>
<sar-list inlaid>
<sar-list-item
v-for="item in section.children"
:key="item.code"
:title="item.title"
hover
@click="onSelect(item.code)"
/>
</sar-list>
</view>
</sar-indexes>
</template>
</template>
</sar-popout>
<AreaCodeSearch v-model:visible="searchVisible" @select="onSelect" />
</template>
<script setup lang="ts">
import areaCode from 'tel-area-code'
import { computed, ref } from 'vue'
import AreaCodeSearch from './AreaCodeSearch.vue'
const props = defineProps<{
visible?: boolean
}>()
const emit = defineEmits<{
(e: 'update:visible', visible: boolean): void
(e: 'select', code: string): void
}>()
interface SectionItem {
title: string
code: string
}
interface Section {
anchor: string
children: SectionItem[]
}
const getSectionList = () => {
const list: Section[] = []
const map: Record<string, Section> = {}
areaCode.forEach((item) => {
const firstLetter = item.pinyin[0]
let section = map[firstLetter]
if (!section) {
section = map[firstLetter] = {
anchor: firstLetter.toUpperCase(),
children: [],
}
list.push(section)
}
section.children.push({
title: `${item.name} +${item.code}`,
code: item.code,
})
})
list.sort((a, b) => a.anchor.charCodeAt(0) - b.anchor.charCodeAt(0))
return list
}
const sectionList = ref<Section[]>(getSectionList())
// visible
const innerVisible = computed({
get() {
return props.visible
},
set(value) {
emit('update:visible', value)
},
})
const searchVisible = ref(false)
const onSelect = (code: string) => {
innerVisible.value = false
emit('select', code)
}
</script>vue
<template>
<sar-popout
v-model:visible="innerVisible"
title="请选择区号"
:show-footer="false"
keep-render
>
<template #visible="{ already }">
<template v-if="already">
<sar-search
root-style="cursor: pointer"
placeholder="搜索"
shape="round"
readonly
align="center"
@click="searchVisible = true"
/>
<sar-indexes root-style="height: 70vh">
<view v-for="section in sectionList" :key="section.anchor">
<sar-indexes-anchor
:name="section.anchor"
root-style="height: 0; overflow: hidden;"
/>
<sar-indexes-anchor>
{{ section.anchor }}
</sar-indexes-anchor>
<sar-list inlaid>
<sar-list-item
v-for="item in section.children"
:key="item.code"
:title="item.title"
hover
@click="onSelect(item.code)"
/>
</sar-list>
</view>
</sar-indexes>
</template>
</template>
</sar-popout>
<AreaCodeSearch v-model:visible="searchVisible" @select="onSelect" />
</template>
<script setup lang="js">
import areaCode from "tel-area-code";
import { computed, ref } from "vue";
const props = defineProps();
const emit = defineEmits();
const getSectionList = () => {
const list = [];
const map = {};
areaCode.forEach((item) => {
const firstLetter = item.pinyin[0];
let section = map[firstLetter];
if (!section) {
section = map[firstLetter] = {
anchor: firstLetter.toUpperCase(),
children: []
};
list.push(section);
}
section.children.push({
title: `${item.name} +${item.code}`,
code: item.code
});
});
list.sort((a, b) => a.anchor.charCodeAt(0) - b.anchor.charCodeAt(0));
return list;
};
const sectionList = ref(getSectionList());
const innerVisible = computed({
get() {
return props.visible;
},
set(value) {
emit("update:visible", value);
}
});
const searchVisible = ref(false);
const onSelect = (code) => {
innerVisible.value = false;
emit("select", code);
};
</script>AreaCodeSearch.vue
vue
<template>
<sar-popout
v-model:visible="innerVisible"
title="搜索区号"
:show-footer="false"
@visible-hook="onVisibleHook"
>
<sar-search
v-model="searchValue"
placeholder="搜索"
shape="round"
:focus="focused"
/>
<scroll-view scroll-y style="height: 70vh">
<sar-list inlaid>
<sar-list-item
v-for="item in searchResult"
:key="item.title"
hover
@click="onSelect(item)"
>
<template #title>
<view>
<template v-for="(frag, i) in item.titleFrag" :key="i">
<text
:style="`color: ${i % 2 !== 0 ? 'var(--sar-primary)' : ''}`"
>
{{ frag }}
</text>
</template>
</view>
</template>
</sar-list-item>
</sar-list>
<view style="height: env(safe-area-inset-bottom)"></view>
</scroll-view>
</sar-popout>
</template>
<script setup lang="ts">
import { throttle, TransitionHookName } from 'sard-uniapp'
import areaCode from 'tel-area-code'
import { computed, ref, watch } from 'vue'
const props = defineProps<{
visible?: boolean
}>()
const emit = defineEmits<{
(e: 'update:visible', visible: boolean): void
(e: 'select', code: string): void
}>()
// search
interface SearchItem {
title: string
code: string
pinyin: string
}
interface SearchResultItem extends SearchItem {
titleFrag: string[]
}
const searchList = computed(() => {
const list: SearchItem[] = []
areaCode.forEach((item) => {
list.push({
title: `${item.name} +${item.code}`,
code: item.code,
pinyin: item.pinyin,
})
})
list.sort((a, b) => a.pinyin[0].charCodeAt(0) - b.pinyin[0].charCodeAt(0))
return list
})
const searchValue = ref('')
const searchResult = ref<SearchResultItem[]>([])
const search = throttle(() => {
searchResult.value = searchValue.value
? searchList.value
.filter((item) => item.title.includes(searchValue.value))
.map((item) => ({
...item,
titleFrag: item.title.split(
new RegExp(`((?:${searchValue.value})+)`),
),
}))
: []
}, 350)
watch(searchValue, () => {
search()
})
// visible
const innerVisible = computed({
get() {
return props.visible
},
set(value) {
emit('update:visible', value)
},
})
const focused = ref(false)
const onVisibleHook = (hook: TransitionHookName) => {
if (hook === 'enter') {
searchValue.value = ''
}
if (hook === 'after-enter') {
setTimeout(() => {
focused.value = true
}, 150)
} else if (hook === 'before-leave') {
focused.value = false
}
}
const onSelect = (item: SearchItem) => {
innerVisible.value = false
emit('select', item.code)
}
</script>vue
<template>
<sar-popout
v-model:visible="innerVisible"
title="搜索区号"
:show-footer="false"
@visible-hook="onVisibleHook"
>
<sar-search
v-model="searchValue"
placeholder="搜索"
shape="round"
:focus="focused"
/>
<scroll-view scroll-y style="height: 70vh">
<sar-list inlaid>
<sar-list-item
v-for="item in searchResult"
:key="item.title"
hover
@click="onSelect(item)"
>
<template #title>
<view>
<template v-for="(frag, i) in item.titleFrag" :key="i">
<text
:style="`color: ${i % 2 !== 0 ? 'var(--sar-primary)' : ''}`"
>
{{ frag }}
</text>
</template>
</view>
</template>
</sar-list-item>
</sar-list>
<view style="height: env(safe-area-inset-bottom)"></view>
</scroll-view>
</sar-popout>
</template>
<script setup lang="js">
import { throttle } from "sard-uniapp";
import areaCode from "tel-area-code";
import { computed, ref, watch } from "vue";
const props = defineProps();
const emit = defineEmits();
const searchList = computed(() => {
const list = [];
areaCode.forEach((item) => {
list.push({
title: `${item.name} +${item.code}`,
code: item.code,
pinyin: item.pinyin
});
});
list.sort((a, b) => a.pinyin[0].charCodeAt(0) - b.pinyin[0].charCodeAt(0));
return list;
});
const searchValue = ref("");
const searchResult = ref([]);
const search = throttle(() => {
searchResult.value = searchValue.value ? searchList.value.filter((item) => item.title.includes(searchValue.value)).map((item) => ({
...item,
titleFrag: item.title.split(
new RegExp(`((?:${searchValue.value})+)`)
)
})) : [];
}, 350);
watch(searchValue, () => {
search();
});
const innerVisible = computed({
get() {
return props.visible;
},
set(value) {
emit("update:visible", value);
}
});
const focused = ref(false);
const onVisibleHook = (hook) => {
if (hook === "enter") {
searchValue.value = "";
}
if (hook === "after-enter") {
setTimeout(() => {
focused.value = true;
}, 150);
} else if (hook === "before-leave") {
focused.value = false;
}
};
const onSelect = (item) => {
innerVisible.value = false;
emit("select", item.code);
};
</script>全屏
vue
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="Indexes 索引">
<sar-indexes
:root-style="{
height: indexesHeight,
}"
>
<view v-for="section in sectionList" :key="section.anchor">
<sar-indexes-anchor
:name="section.anchor"
root-style="height: 0; overflow: hidden;"
/>
<sar-indexes-anchor>
{{ section.anchor }}
</sar-indexes-anchor>
<sar-list inlaid>
<sar-list-item
v-for="item in section.children"
:key="item.code"
:title="item.title"
hover
@click="onSelect(item.code)"
/>
</sar-list>
</view>
</sar-indexes>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { getWindowInfo, toast } from 'sard-uniapp'
import areaCode from 'tel-area-code'
import { ref } from 'vue'
const statusBarHeight = getWindowInfo().statusBarHeight + 'px'
const navbarHeight = `calc(${statusBarHeight} + var(--sar-navbar-height))`
const indexesHeight = `calc(100vh - ${navbarHeight})`
interface SectionItem {
title: string
code: string
}
interface Section {
anchor: string
children: SectionItem[]
}
const getSectionList = () => {
const list: Section[] = []
const map: Record<string, Section> = {}
areaCode.forEach((item) => {
const firstLetter = item.pinyin[0]
let section = map[firstLetter]
if (!section) {
section = map[firstLetter] = {
anchor: firstLetter.toUpperCase(),
children: [],
}
list.push(section)
}
section.children.push({
title: `${item.name} +${item.code}`,
code: item.code,
})
})
list.sort((a, b) => a.anchor.charCodeAt(0) - b.anchor.charCodeAt(0))
return list
}
const sectionList = ref<Section[]>(getSectionList())
const onSelect = (code: string) => {
toast(code)
}
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script>vue
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="Indexes 索引">
<sar-indexes
:root-style="{
height: indexesHeight,
}"
>
<view v-for="section in sectionList" :key="section.anchor">
<sar-indexes-anchor
:name="section.anchor"
root-style="height: 0; overflow: hidden;"
/>
<sar-indexes-anchor>
{{ section.anchor }}
</sar-indexes-anchor>
<sar-list inlaid>
<sar-list-item
v-for="item in section.children"
:key="item.code"
:title="item.title"
hover
@click="onSelect(item.code)"
/>
</sar-list>
</view>
</sar-indexes>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { getWindowInfo, toast } from "sard-uniapp";
import areaCode from "tel-area-code";
import { ref } from "vue";
const statusBarHeight = getWindowInfo().statusBarHeight + "px";
const navbarHeight = `calc(${statusBarHeight} + var(--sar-navbar-height))`;
const indexesHeight = `calc(100vh - ${navbarHeight})`;
const getSectionList = () => {
const list = [];
const map = {};
areaCode.forEach((item) => {
const firstLetter = item.pinyin[0];
let section = map[firstLetter];
if (!section) {
section = map[firstLetter] = {
anchor: firstLetter.toUpperCase(),
children: []
};
list.push(section);
}
section.children.push({
title: `${item.name} +${item.code}`,
code: item.code
});
});
list.sort((a, b) => a.anchor.charCodeAt(0) - b.anchor.charCodeAt(0));
return list;
};
const sectionList = ref(getSectionList());
const onSelect = (code) => {
toast(code);
};
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>API
IndexesProps
| 属性 | 描述 | 类型 | 默认值 |
|---|---|---|---|
| root-class | 组件根元素类名 | string | - |
| root-style | 组件根元素样式 | StyleValue | - |
| current | 设置当前活动的锚点 | number | string | - |
IndexesSlots
| 插槽 | 描述 | 属性 |
|---|---|---|
| default | 自定义默认内容 | - |
IndexesEmits
| 事件 | 描述 | 类型 |
|---|---|---|
| update:current 1.12+ | 索引发生变更时触发 | (name: number | string) => void |
| change | 索引发生变更时触发 | (name: number | string) => void |
IndexesExpose
| 属性 | 描述 | 类型 |
|---|---|---|
| scrollTo | 滚动到指定锚点 | (name: string | number) => void |
| update | 更新锚点位置 | () => void |
IndexesAnchorProps
| 属性 | 描述 | 类型 | 默认值 |
|---|---|---|---|
| root-class | 组件根元素类名 | string | - |
| root-style | 组件根元素样式 | StyleValue | - |
| name | 索引锚点名称 | number | string | - |
IndexesAnchorSlots
| 插槽 | 描述 | 属性 |
|---|---|---|
| default | 自定义锚点内容,会覆盖 name 属性 | - |
主题定制
CSS 变量
scss
page,
.sar-portal {
--sar-indexes-anchor-padding: 0 32rpx;
--sar-indexes-anchor-font-size: var(--sar-text-base);
--sar-indexes-anchor-line-height: 64rpx;
--sar-indexes-anchor-color: var(--sar-tertiary-color);
--sar-indexes-anchor-bg: var(--sar-body-bg);
--sar-indexes-nav-padding-right: 8rpx;
--sar-indexes-nav-item-font-size: 24rpx;
--sar-indexes-nav-item-size: 40rpx;
--sar-indexes-nav-item-color: var(--sar-body-color);
--sar-indexes-nav-item-active-color: var(--sar-white);
--sar-indexes-nav-item-active-bg: var(--sar-primary);
--sar-indexes-hint-size: 100rpx;
--sar-indexes-hint-margin-right: 8rpx;
--sar-indexes-hint-bg: var(--sar-mask);
--sar-indexes-hint-duration: var(--sar-duration);
--sar-indexes-hint-text-font-size: 48rpx;
--sar-indexes-hint-text-color: var(--sar-white);
}