介绍
用于页面中信息快速检索,可以根据目录中的页码快速找到所需的内容。
代码演示
区号选择
Indexes 里面的 IndexesAnchor 组件会被收集起来,用于放置锚点和自动生成右侧的导航。导航和锚点会有一个联动效果。
vue
<template>
<s-form :model="model" direction="vertical">
<s-form-item>
<s-input v-model="model.phone" inlaid placeholder="请输入手机号">
<template #prepend>
<s-button
variant="link"
color="secondary"
style="padding-inline-start: 0"
@click="areaVisible = true"
>
+{{ model.areaCode }}
<CaretDown class="ms-1 mt-1" />
</s-button>
</template>
</s-input>
</s-form-item>
</s-form>
<AreaCode v-model:visible="areaVisible" @select="onSelect" />
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import AreaCode from './AreaCode.vue'
import { CaretDown } from '@sard/icons'
const model = reactive({
areaCode: '86',
phone: '',
})
const areaVisible = ref(false)
const onSelect = (code: string) => {
model.areaCode = code
}
</script>vue
<template>
<s-form :model="model" direction="vertical">
<s-form-item>
<s-input v-model="model.phone" inlaid placeholder="请输入手机号">
<template #prepend>
<s-button
variant="link"
color="secondary"
style="padding-inline-start: 0"
@click="areaVisible = true"
>
+{{ model.areaCode }}
<CaretDown class="ms-1 mt-1" />
</s-button>
</template>
</s-input>
</s-form-item>
</s-form>
<AreaCode v-model:visible="areaVisible" @select="onSelect" />
</template>
<script setup lang="js">
import { reactive, ref } from 'vue'
import AreaCode from './AreaCode.vue'
import { CaretDown } from '@sard/icons'
const model = reactive({
areaCode: '86',
phone: '',
})
const areaVisible = ref(false)
const onSelect = (code) => {
model.areaCode = code
}
</script>AreaCode.vue
vue
<template>
<s-popout v-model:visible="innerVisible" title="请选择区号" :show-footer="false">
<s-search
style="cursor: pointer"
placeholder="搜索"
shape="round"
readonly
align="center"
@click="searchVisible = true"
/>
<s-indexes style="height: 70vh">
<div v-for="section in sectionList" :key="section.anchor">
<s-indexes-anchor :name="section.anchor" style="height: 0; overflow: hidden" />
<s-indexes-anchor>
{{ section.anchor }}
</s-indexes-anchor>
<s-list inlaid>
<s-list-item
v-for="item in section.children"
:key="item.code"
:title="item.title"
hover
@click="onSelect(item.code)"
/>
</s-list>
</div>
</s-indexes>
</s-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>
<s-popout v-model:visible="innerVisible" title="请选择区号" :show-footer="false">
<s-search
style="cursor: pointer"
placeholder="搜索"
shape="round"
readonly
align="center"
@click="searchVisible = true"
/>
<s-indexes style="height: 70vh">
<div v-for="section in sectionList" :key="section.anchor">
<s-indexes-anchor :name="section.anchor" style="height: 0; overflow: hidden" />
<s-indexes-anchor>
{{ section.anchor }}
</s-indexes-anchor>
<s-list inlaid>
<s-list-item
v-for="item in section.children"
:key="item.code"
:title="item.title"
hover
@click="onSelect(item.code)"
/>
</s-list>
</div>
</s-indexes>
</s-popout>
<AreaCodeSearch v-model:visible="searchVisible" @select="onSelect" />
</template>
<script setup lang="js">
import areaCode from 'tel-area-code'
import { computed, ref } from 'vue'
import AreaCodeSearch from './AreaCodeSearch.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())
// visible
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>
<s-popout
v-model:visible="innerVisible"
title="搜索区号"
:show-footer="false"
@visible-hook="onVisibleHook"
>
<s-search ref="searchRef" v-model="searchValue" placeholder="搜索" shape="round" />
<div class="overflow-y-auto" style="height: 70vh">
<s-list inlaid>
<s-list-item v-for="item in searchResult" :key="item.title" hover @click="onSelect(item)">
<template #title>
<div>
<template v-for="(frag, i) in item.titleFrag" :key="i">
<span :style="`color: ${i % 2 !== 0 ? 'var(--s-color-primary)' : ''}`">
{{ frag }}
</span>
</template>
</div>
</template>
</s-list-item>
</s-list>
<div style="height: var(--s-safe-bottom)"></div>
</div>
</s-popout>
</template>
<script setup lang="ts">
import { throttle, type MotionHookName } from 'sard'
import areaCode from 'tel-area-code'
import { computed, ref, useTemplateRef, 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 searchRef = useTemplateRef('searchRef')
const onVisibleHook = (hook: MotionHookName) => {
if (hook === 'enter') {
searchValue.value = ''
}
if (hook === 'after-enter') {
searchRef.value?.focus()
}
}
const onSelect = (item: SearchItem) => {
innerVisible.value = false
emit('select', item.code)
}
</script>vue
<template>
<s-popout
v-model:visible="innerVisible"
title="搜索区号"
:show-footer="false"
@visible-hook="onVisibleHook"
>
<s-search ref="searchRef" v-model="searchValue" placeholder="搜索" shape="round" />
<div class="overflow-y-auto" style="height: 70vh">
<s-list inlaid>
<s-list-item v-for="item in searchResult" :key="item.title" hover @click="onSelect(item)">
<template #title>
<div>
<template v-for="(frag, i) in item.titleFrag" :key="i">
<span :style="`color: ${i % 2 !== 0 ? 'var(--s-color-primary)' : ''}`">
{{ frag }}
</span>
</template>
</div>
</template>
</s-list-item>
</s-list>
<div style="height: var(--s-safe-bottom)"></div>
</div>
</s-popout>
</template>
<script setup lang="js">
import { throttle } from 'sard'
import areaCode from 'tel-area-code'
import { computed, ref, useTemplateRef, 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()
})
// visible
const innerVisible = computed({
get() {
return props.visible
},
set(value) {
emit('update:visible', value)
},
})
const searchRef = useTemplateRef('searchRef')
const onVisibleHook = (hook) => {
if (hook === 'enter') {
searchValue.value = ''
}
if (hook === 'after-enter') {
searchRef.value?.focus()
}
}
const onSelect = (item) => {
innerVisible.value = false
emit('select', item.code)
}
</script>全屏
vue
<template>
<doc-page title="Indexes 索引">
<s-indexes
:style="{
height: indexesHeight,
}"
>
<div v-for="section in sectionList" :key="section.anchor">
<s-indexes-anchor :name="section.anchor" style="height: 0; overflow: hidden" />
<s-indexes-anchor>
{{ section.anchor }}
</s-indexes-anchor>
<s-list inlaid>
<s-list-item
v-for="item in section.children"
:key="item.code"
:title="item.title"
hover
@click="onSelect(item.code)"
/>
</s-list>
</div>
</s-indexes>
</doc-page>
</template>
<script setup lang="ts">
import { toast } from 'sard'
import areaCode from 'tel-area-code'
import { ref } from 'vue'
const indexesHeight = `calc(100vh - var(--s-status-bar-height) - var(--s-navbar-height) - var(--s-safe-bottom) - 20px)`
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[]>([])
toast.loading('加载中')
setTimeout(() => {
sectionList.value = getSectionList()
toast.hide()
}, 1000)
const onSelect = (code: string) => {
toast(code)
}
</script>vue
<template>
<doc-page title="Indexes 索引">
<s-indexes
:style="{
height: indexesHeight,
}"
>
<div v-for="section in sectionList" :key="section.anchor">
<s-indexes-anchor :name="section.anchor" style="height: 0; overflow: hidden" />
<s-indexes-anchor>
{{ section.anchor }}
</s-indexes-anchor>
<s-list inlaid>
<s-list-item
v-for="item in section.children"
:key="item.code"
:title="item.title"
hover
@click="onSelect(item.code)"
/>
</s-list>
</div>
</s-indexes>
</doc-page>
</template>
<script setup lang="js">
import { toast } from 'sard'
import areaCode from 'tel-area-code'
import { ref } from 'vue'
const indexesHeight = `calc(100vh - var(--s-status-bar-height) - var(--s-navbar-height) - var(--s-safe-bottom) - 20px)`
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([])
toast.loading('加载中')
setTimeout(() => {
sectionList.value = getSectionList()
toast.hide()
}, 1e3)
const onSelect = (code) => {
toast(code)
}
</script>API
IndexesProps
| 属性 | 描述 | 类型 | 默认值 |
|---|---|---|---|
| current | 设置当前活动的锚点 | number | string | - |
IndexesSlots
| 插槽 | 描述 | 属性 |
|---|---|---|
| default | 自定义默认内容 | - |
IndexesEmits
| 事件 | 描述 | 类型 |
|---|---|---|
| update:current | 索引发生变更时触发 | (name: number | string) => void |
| change | 索引发生变更时触发 | (name: number | string) => void |
IndexesExpose
| 属性 | 描述 | 类型 |
|---|---|---|
| scrollTo | 滚动到指定锚点 | (name: string | number) => void |
| update | 更新锚点位置 | () => void |
IndexesAnchorProps
| 属性 | 描述 | 类型 | 默认值 |
|---|---|---|---|
| name | name 是用于索引导航的标识名称,不传 default 插槽时则显示 name 作为锚点内容。 | number | string | - |
IndexesAnchorSlots
| 插槽 | 描述 | 属性 |
|---|---|---|
| default | 自定义锚点内容,不传则显示 name | - |
主题定制
样式变量
| CSS 变量 | 值 |
|---|---|
--s-indexes-anchor-padding | 0 var(--s-content-padding-x) |
--s-indexes-anchor-font-size | var(--s-font-size) |
--s-indexes-anchor-height | var(--s-size-2xl) |
--s-indexes-anchor-color | var(--s-text-color-tertiary) |
--s-indexes-anchor-bg | var(--s-fill-color-fourth) |
--s-indexes-nav-padding-end | var(--s-size-2xs) |
--s-indexes-nav-item-font-size | var(--s-size-sm) |
--s-indexes-nav-item-size | var(--s-size-lg) |
--s-indexes-nav-item-color | var(--s-text-color-primary) |
--s-indexes-nav-item-color-active | var(--s-white) |
--s-indexes-nav-item-bg-active | var(--s-color-primary) |
--s-indexes-hint-size | var(--s-size-4xl) |
--s-indexes-hint-margin-end | var(--s-size-2xs) |
--s-indexes-hint-bg | var(--s-overlay) |
--s-indexes-hint-text-font-size | var(--s-size-xl) |
--s-indexes-hint-text-color | var(--s-white) |