Appearance
VirtualList 虚拟列表
AuVirtualList 面向固定行高数据,只渲染视口与 overscan 缓冲区内的项目。
基础用法
共 1000 条,当前渲染 1–0
1Aurora 项目 0001设计团队已同步
2Aurora 项目 0002前端团队待处理
3Aurora 项目 0003编辑器团队仅本地
4Aurora 项目 0004质量团队已同步
5Aurora 项目 0005设计团队待处理
6Aurora 项目 0006前端团队仅本地
7Aurora 项目 0007编辑器团队已同步
8Aurora 项目 0008质量团队待处理
9Aurora 项目 0009设计团队仅本地
10Aurora 项目 0010前端团队已同步
11Aurora 项目 0011编辑器团队待处理
12Aurora 项目 0012质量团队仅本地
13Aurora 项目 0013设计团队已同步
14Aurora 项目 0014前端团队待处理
15Aurora 项目 0015编辑器团队仅本地
16Aurora 项目 0016质量团队已同步
17Aurora 项目 0017设计团队待处理
18Aurora 项目 0018前端团队仅本地
19Aurora 项目 0019编辑器团队已同步
20Aurora 项目 0020质量团队待处理
21Aurora 项目 0021设计团队仅本地
22Aurora 项目 0022前端团队已同步
<template>
<div>
<div class="virtual-list-demo__controls">
<input v-model.trim="keyword" type="search" placeholder="按名称或状态筛选" />
<AuButton size="small" @click="listRef?.scrollToTop()">回到顶部</AuButton>
<AuButton size="small" @click="scrollToMiddle">定位到中间</AuButton>
<span>共 {{ filteredItems.length }} 条,当前渲染 {{ renderedRangeText }}</span>
</div>
<div class="virtual-list-demo">
<AuVirtualList
ref="listRef"
:items="filteredItems"
:item-height="36"
:overscan="6"
plain
fill
@range-change="renderedRange = $event"
>
<template #default="{ item, index }">
<div
class="virtual-list-demo__row"
:class="{ 'is-last': index === filteredItems.length - 1 }"
>
<span class="virtual-list-demo__index">{{ index + 1 }}</span>
<span class="virtual-list-demo__identity">
<strong>{{ item.name }}</strong>
<small>{{ item.owner }}</small>
</span>
<span class="virtual-list-demo__status" :class="`is-${item.statusType}`">
{{ item.status }}
</span>
</div>
</template>
<template #empty>
<div class="virtual-list-demo__empty">没有匹配的数据</div>
</template>
</AuVirtualList>
</div>
</div>
</template>
<script setup>
import { computed, ref } from 'vue';
import { AuButton, AuVirtualList } from 'aurora-plus';
const owners = ['设计团队', '前端团队', '编辑器团队', '质量团队'];
const statuses = [
{ label: '已同步', type: 'success' },
{ label: '待处理', type: 'warning' },
{ label: '仅本地', type: 'info' },
];
const items = Array.from({ length: 1000 }, (_, index) => {
const status = statuses[index % statuses.length];
return {
id: index + 1,
name: `Aurora 项目 ${String(index + 1).padStart(4, '0')}`,
owner: owners[index % owners.length],
status: status.label,
statusType: status.type,
};
});
const keyword = ref('');
const listRef = ref(null);
const renderedRange = ref({ start: 0, end: 0 });
const filteredItems = computed(() => {
const query = keyword.value.toLowerCase();
if (!query) return items;
return items.filter((item) =>
[item.name, item.owner, item.status].some((value) => value.toLowerCase().includes(query))
);
});
const renderedRangeText = computed(() => {
if (filteredItems.value.length === 0) return '0';
return `${renderedRange.value.start + 1}–${renderedRange.value.end}`;
});
function scrollToMiddle() {
const middleIndex = Math.floor(filteredItems.value.length / 2);
listRef.value?.scrollToIndex(middleIndex, 'center');
}
</script>
<style scoped>
.virtual-list-demo {
height: 330px;
overflow: hidden;
border: 1px solid var(--au-color-border-muted);
border-radius: 8px;
background: transparent;
}
.virtual-list-demo__controls {
display: flex;
align-items: center;
margin-bottom: 14px;
flex-wrap: wrap;
gap: 8px;
}
.virtual-list-demo__controls input {
min-width: 210px;
height: 28px;
padding: 0 9px;
border: 1px solid var(--au-color-border-default);
border-radius: var(--au-radius-control);
color: var(--au-color-text-primary);
background: transparent;
font: inherit;
font-size: 13px;
outline: none;
}
.virtual-list-demo__controls span {
margin-left: auto;
color: var(--au-color-text-secondary);
font-size: 12px;
}
.virtual-list-demo__row {
display: grid;
grid-template-columns: 32px minmax(0, 1fr) auto;
align-items: center;
height: 36px;
padding: 0 10px;
border-bottom: 1px solid var(--au-color-border-muted);
color: var(--au-color-text-default);
font-size: 13px;
}
.virtual-list-demo__row.is-last {
border-bottom-color: transparent;
}
.virtual-list-demo__identity {
display: flex;
align-items: center;
gap: 10px;
min-width: 0;
}
.virtual-list-demo__row strong {
min-width: 0;
overflow: hidden;
color: var(--au-color-text-primary);
font-weight: var(--au-font-weight-medium);
line-height: 18px;
text-overflow: ellipsis;
white-space: nowrap;
}
.virtual-list-demo__index {
color: var(--au-color-text-secondary);
}
.virtual-list-demo__identity small {
position: relative;
flex: 0 1 7em;
min-width: 0;
padding-left: 10px;
overflow: hidden;
color: var(--au-color-text-secondary);
font-size: 12px;
line-height: 16px;
text-overflow: ellipsis;
white-space: nowrap;
}
.virtual-list-demo__identity small::before {
position: absolute;
top: 2px;
bottom: 2px;
left: 0;
width: 1px;
background: var(--au-color-border-subtle);
content: '';
}
.virtual-list-demo__status {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 46px;
height: 20px;
padding: 0 7px;
border-radius: 999px;
font-size: 11px;
font-weight: var(--au-font-weight-medium);
line-height: 1;
}
.virtual-list-demo__status.is-success {
color: var(--au-color-success);
background: color-mix(in srgb, var(--au-color-success) 11%, transparent);
}
.virtual-list-demo__status.is-warning {
color: var(--au-color-warning);
background: color-mix(in srgb, var(--au-color-warning) 12%, transparent);
}
.virtual-list-demo__status.is-info {
color: var(--au-color-info);
background: color-mix(in srgb, var(--au-color-info) 11%, transparent);
}
.virtual-list-demo__empty {
display: grid;
height: 100%;
min-height: 180px;
color: var(--au-color-text-secondary);
place-items: center;
}
</style>VirtualList API
容器需有可计算高度,列表项高度须与 itemHeight 一致。
Attributes
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| items | 完整数据数组 | T[] | [] |
| itemHeight | 固定行高,单位 px,必须大于 0 | number | 28 |
| overscan | 视口上下额外渲染的行数 | number | 8 |
| keyField | 从对象数据读取 key 的字段 | string | id |
| itemKey | 自定义 key 计算函数,优先级高于 keyField | (item: T, index: number) => string | number | — |
| plain | 移除容器边框、内边距和表面阴影,供复合组件承载内容 | boolean | false |
| fill | 填满父级可用宽高并参与弹性布局 | boolean | false |
Events
| 事件名 | 说明 | 参数 |
|---|---|---|
| scroll | 原生滚动事件 | (event: Event) |
| range-change | 实际渲染范围变化;end 为不包含的结束索引 | ({ start, end }) |
Slots
| 插槽名 | 作用域参数 | 说明 |
|---|---|---|
| default | 每一个当前可见列表项 | |
| empty | — | items 为空时的内容 |
Exposes
| 属性或方法 | 说明 |
|---|---|
| scrollToTop() | 滚动到顶部 |
| scrollToIndex(index, align?) | 滚动到指定索引;align 支持 auto / start / center / end |
| scrollContainerRef | 内部滚动容器元素引用 |