mirror of
https://github.com/pure-admin/vue-pure-admin.git
synced 2025-11-03 13:44:47 +08:00
feat: add Virtual List demo
This commit is contained in:
70
src/views/able/virtual-list/data.ts
Normal file
70
src/views/able/virtual-list/data.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
|
||||
let uid = 0;
|
||||
|
||||
function generateItem() {
|
||||
return {
|
||||
name: faker.name.findName(),
|
||||
avatar: faker.internet.avatar()
|
||||
};
|
||||
}
|
||||
|
||||
export function getData(count, letters) {
|
||||
const raw = {};
|
||||
|
||||
const alphabet = "abcdefghijklmnopqrstuvwxyz".split("");
|
||||
|
||||
for (const l of alphabet) {
|
||||
raw[l] = [];
|
||||
}
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const item = generateItem();
|
||||
const letter = item.name.charAt(0).toLowerCase();
|
||||
raw[letter].push(item);
|
||||
}
|
||||
|
||||
const list = [];
|
||||
let index = 1;
|
||||
|
||||
for (const l of alphabet) {
|
||||
raw[l] = raw[l].sort((a, b) => (a.name < b.name ? -1 : 1));
|
||||
if (letters) {
|
||||
list.push({
|
||||
id: uid++,
|
||||
index: index++,
|
||||
type: "letter",
|
||||
value: l,
|
||||
height: 200
|
||||
});
|
||||
}
|
||||
for (const item of raw[l]) {
|
||||
list.push({
|
||||
id: uid++,
|
||||
index: index++,
|
||||
type: "person",
|
||||
value: item,
|
||||
height: 50
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
export function addItem(list) {
|
||||
list.push({
|
||||
id: uid++,
|
||||
index: list.length + 1,
|
||||
type: "person",
|
||||
value: generateItem(),
|
||||
height: 50
|
||||
});
|
||||
}
|
||||
|
||||
export function generateMessage() {
|
||||
return {
|
||||
avatar: faker.internet.avatar(),
|
||||
message: faker.lorem.text()
|
||||
};
|
||||
}
|
||||
138
src/views/able/virtual-list/horizontal.vue
Normal file
138
src/views/able/virtual-list/horizontal.vue
Normal file
@@ -0,0 +1,138 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from "vue";
|
||||
import { generateMessage } from "./data";
|
||||
|
||||
const items = ref([]);
|
||||
const search = ref("");
|
||||
|
||||
for (let i = 0; i < 10000; i++) {
|
||||
items.value.push({
|
||||
id: i,
|
||||
...generateMessage()
|
||||
});
|
||||
}
|
||||
|
||||
const filteredItems = computed(() => {
|
||||
if (!search.value) return items.value;
|
||||
const lowerCaseSearch = search.value.toLowerCase();
|
||||
return items.value.filter(i =>
|
||||
i.message.toLowerCase().includes(lowerCaseSearch)
|
||||
);
|
||||
});
|
||||
|
||||
function changeMessage(message) {
|
||||
Object.assign(message, generateMessage());
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dynamic-scroller-demo">
|
||||
<div class="flex justify-around mb-4">
|
||||
<el-input
|
||||
class="mr-2 !w-1/1.5"
|
||||
clearable
|
||||
v-model="search"
|
||||
placeholder="Filter..."
|
||||
style="width: 300px"
|
||||
/>
|
||||
<el-tag effect="dark">水平模式horizontal</el-tag>
|
||||
</div>
|
||||
|
||||
<DynamicScroller
|
||||
:items="filteredItems"
|
||||
:min-item-size="54"
|
||||
direction="horizontal"
|
||||
class="scroller"
|
||||
>
|
||||
<template #default="{ item, index, active }">
|
||||
<DynamicScrollerItem
|
||||
:item="item"
|
||||
:active="active"
|
||||
:size-dependencies="[item.message]"
|
||||
:data-index="index"
|
||||
:data-active="active"
|
||||
:title="`Click to change message ${index}`"
|
||||
:style="{
|
||||
width: `${Math.max(
|
||||
130,
|
||||
Math.round((item.message.length / 20) * 20)
|
||||
)}px`
|
||||
}"
|
||||
class="message"
|
||||
@click="changeMessage(item)"
|
||||
>
|
||||
<div class="avatar">
|
||||
<IconifyIconOnline
|
||||
icon="openmoji:beaming-face-with-smiling-eyes"
|
||||
width="40"
|
||||
/>
|
||||
</div>
|
||||
<div class="text">
|
||||
{{ item.message }}
|
||||
</div>
|
||||
<div class="index">
|
||||
<span>{{ item.id }} (id)</span>
|
||||
<span>{{ index }} (index)</span>
|
||||
</div>
|
||||
</DynamicScrollerItem>
|
||||
</template>
|
||||
</DynamicScroller>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.dynamic-scroller-demo {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.scroller {
|
||||
flex: auto 1 1;
|
||||
}
|
||||
|
||||
.notice {
|
||||
padding: 24px;
|
||||
font-size: 20px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.message {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 32px;
|
||||
padding: 12px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
flex: auto 0 0;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.avatar .image {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.index,
|
||||
.text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.text {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.index {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.index span {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
26
src/views/able/virtual-list/index.vue
Normal file
26
src/views/able/virtual-list/index.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import verticalList from "./vertical.vue";
|
||||
import horizontalList from "./horizontal.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="font-medium">
|
||||
虚拟列表组件(
|
||||
<el-link
|
||||
href="https://github.com/Akryum/vue-virtual-scroller/tree/next/packages/vue-virtual-scroller"
|
||||
target="_blank"
|
||||
style="font-size: 16px; margin: 0 5px 4px 0"
|
||||
>
|
||||
github地址
|
||||
</el-link>
|
||||
)
|
||||
</div>
|
||||
</template>
|
||||
<div class="w-full flex justify-around flex-wrap">
|
||||
<vertical-list class="h-500px w-500px" />
|
||||
<horizontal-list class="h-500px w-500px" />
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
124
src/views/able/virtual-list/vertical.vue
Normal file
124
src/views/able/virtual-list/vertical.vue
Normal file
@@ -0,0 +1,124 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from "vue";
|
||||
import { generateMessage } from "./data";
|
||||
|
||||
const items = ref([]);
|
||||
const search = ref("");
|
||||
|
||||
for (let i = 0; i < 10000; i++) {
|
||||
items.value.push({
|
||||
id: i,
|
||||
...generateMessage()
|
||||
});
|
||||
}
|
||||
|
||||
const filteredItems = computed(() => {
|
||||
if (!search.value) return items.value;
|
||||
const lowerCaseSearch = search.value.toLowerCase();
|
||||
return items.value.filter(i =>
|
||||
i.message.toLowerCase().includes(lowerCaseSearch)
|
||||
);
|
||||
});
|
||||
|
||||
function changeMessage(message) {
|
||||
Object.assign(message, generateMessage());
|
||||
}
|
||||
|
||||
function onResize() {
|
||||
console.log("resize");
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dynamic-scroller-demo">
|
||||
<div class="flex justify-around mb-4">
|
||||
<el-input
|
||||
class="mr-2 !w-1/1.5"
|
||||
clearable
|
||||
v-model="search"
|
||||
placeholder="Filter..."
|
||||
/>
|
||||
<el-tag effect="dark">垂直模式vertical</el-tag>
|
||||
</div>
|
||||
|
||||
<DynamicScroller
|
||||
:items="filteredItems"
|
||||
:min-item-size="54"
|
||||
class="scroller"
|
||||
@resize="onResize"
|
||||
>
|
||||
<template #default="{ item, index, active }">
|
||||
<DynamicScrollerItem
|
||||
:item="item"
|
||||
:active="active"
|
||||
:size-dependencies="[item.message]"
|
||||
:data-index="index"
|
||||
:data-active="active"
|
||||
:title="`Click to change message ${index}`"
|
||||
class="message"
|
||||
@click="changeMessage(item)"
|
||||
>
|
||||
<div class="avatar">
|
||||
<IconifyIconOnline
|
||||
icon="openmoji:beaming-face-with-smiling-eyes"
|
||||
width="40"
|
||||
/>
|
||||
</div>
|
||||
<div class="text">
|
||||
{{ item.message }}
|
||||
</div>
|
||||
<div class="index">
|
||||
<span>{{ item.id }} (id)</span>
|
||||
<span>{{ index }} (index)</span>
|
||||
</div>
|
||||
</DynamicScrollerItem>
|
||||
</template>
|
||||
</DynamicScroller>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.dynamic-scroller-demo {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.scroller {
|
||||
flex: auto 1 1;
|
||||
}
|
||||
|
||||
.message {
|
||||
display: flex;
|
||||
min-height: 32px;
|
||||
padding: 12px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
flex: auto 0 0;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.index,
|
||||
.text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.text {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.index {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.index span {
|
||||
display: inline-block;
|
||||
width: 160px;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user