feat: add Virtual List demo

This commit is contained in:
xiaoxian521 2022-05-19 01:16:08 +08:00
parent 6ea020c8d2
commit 348916e567
11 changed files with 434 additions and 2 deletions

View File

@ -82,5 +82,6 @@ menus:
hsQrcode: Qrcode
hsCascader: Area Cascader
hsSwiper: Swiper Plugin
hsVirtualList: Virtual List
status:
hsLoad: Loading...

View File

@ -82,5 +82,6 @@ menus:
hsQrcode: 二维码
hsCascader: 区域级联选择器
hsSwiper: Swiper插件
hsVirtualList: 虚拟列表
status:
hsLoad: 加载中...

View File

@ -70,6 +70,7 @@
"vue-json-pretty": "^2.0.2",
"vue-router": "^4.0.15",
"vue-types": "^4.1.1",
"vue-virtual-scroller": "^2.0.0-alpha.1",
"vuedraggable": "4.1.0",
"vxe-table": "^4.2.3",
"xe-utils": "^3.5.4",
@ -78,6 +79,7 @@
"devDependencies": {
"@commitlint/cli": "13.1.0",
"@commitlint/config-conventional": "13.1.0",
"@faker-js/faker": "^6.3.1",
"@iconify-icons/carbon": "^1.2.4",
"@iconify-icons/ep": "^1.2.4",
"@iconify-icons/fa": "^1.2.2",

55
pnpm-lock.yaml generated
View File

@ -5,6 +5,7 @@ specifiers:
"@commitlint/cli": 13.1.0
"@commitlint/config-conventional": 13.1.0
"@ctrl/tinycolor": ^3.4.1
"@faker-js/faker": ^6.3.1
"@iconify-icons/carbon": ^1.2.4
"@iconify-icons/ep": ^1.2.4
"@iconify-icons/fa": ^1.2.2
@ -105,6 +106,7 @@ specifiers:
vue-json-pretty: ^2.0.2
vue-router: ^4.0.15
vue-types: ^4.1.1
vue-virtual-scroller: ^2.0.0-alpha.1
vuedraggable: 4.1.0
vxe-table: ^4.2.3
xe-utils: ^3.5.4
@ -154,6 +156,7 @@ dependencies:
vue-json-pretty: 2.0.6_vue@3.2.33
vue-router: 4.0.15_vue@3.2.33
vue-types: 4.1.1_vue@3.2.33
vue-virtual-scroller: 2.0.0-alpha.1_vue@3.2.33
vuedraggable: 4.1.0_vue@3.2.33
vxe-table: 4.2.3_vue@3.2.33+xe-utils@3.5.4
xe-utils: 3.5.4
@ -162,6 +165,7 @@ dependencies:
devDependencies:
"@commitlint/cli": 13.1.0
"@commitlint/config-conventional": 13.1.0
"@faker-js/faker": 6.3.1
"@iconify-icons/carbon": 1.2.5
"@iconify-icons/ep": 1.2.4
"@iconify-icons/fa": 1.2.2
@ -938,6 +942,14 @@ packages:
- supports-color
dev: true
/@faker-js/faker/6.3.1:
resolution:
{
integrity: sha512-8YXBE2ZcU/pImVOHX7MWrSR/X5up7t6rPWZlk34RwZEcdr3ua6X+32pSd6XuOQRN+vbuvYNfA6iey8NbrjuMFQ==
}
engines: { node: ">=14.0.0", npm: ">=6.0.0" }
dev: true
/@floating-ui/core/0.6.2:
resolution:
{
@ -5989,6 +6001,13 @@ packages:
kind-of: 6.0.3
dev: true
/mitt/2.1.0:
resolution:
{
integrity: sha512-ILj2TpLiysu2wkBbWjAmww7TkZb65aiQO+DkVdUTBpBXq+MHYiETENkKFMtsJZX1Lf4pe4QOrTSjIfUwN5lRdg==
}
dev: false
/mitt/3.0.0:
resolution:
{
@ -8471,6 +8490,28 @@ packages:
vue: 3.2.33
dev: false
/vue-observe-visibility/2.0.0-alpha.1_vue@3.2.33:
resolution:
{
integrity: sha512-flFbp/gs9pZniXR6fans8smv1kDScJ8RS7rEpMjhVabiKeq7Qz3D9+eGsypncjfIyyU84saU88XZ0zjbD6Gq/g==
}
peerDependencies:
vue: ^3.0.0
dependencies:
vue: 3.2.33
dev: false
/vue-resize/2.0.0-alpha.1_vue@3.2.33:
resolution:
{
integrity: sha512-7+iqOueLU7uc9NrMfrzbG8hwMqchfVfSzpVlCMeJQe4pyibqyoifDNbKTZvwxZKDvGkB+PdFeKvnGZMoEb8esg==
}
peerDependencies:
vue: ^3.0.0
dependencies:
vue: 3.2.33
dev: false
/vue-router/4.0.15_vue@3.2.33:
resolution:
{
@ -8509,6 +8550,20 @@ packages:
vue: 3.2.33
dev: false
/vue-virtual-scroller/2.0.0-alpha.1_vue@3.2.33:
resolution:
{
integrity: sha512-Mn5w3Qe06t7c3Imm2RHD43RACab1CCWplpdgzq+/FWJcpQtcGKd5vDep8i+nIwFtzFLsWAqEK0RzM7KrfAcBng==
}
peerDependencies:
vue: ^3.0.11
dependencies:
mitt: 2.1.0
vue: 3.2.33
vue-observe-visibility: 2.0.0-alpha.1_vue@3.2.33
vue-resize: 2.0.0-alpha.1_vue@3.2.33
dev: false
/vue/3.2.33:
resolution:
{

View File

@ -6,6 +6,7 @@ import { getServerConfig } from "./config";
import { createApp, Directive } from "vue";
import { useI18n } from "../src/plugins/i18n";
import { MotionPlugin } from "@vueuse/motion";
import VirtualScroller from "vue-virtual-scroller";
import { useTable } from "../src/plugins/vxe-table";
import { injectResponsiveStorage } from "/@/utils/storage/responsive";
@ -22,6 +23,7 @@ import "@pureadmin/components/dist/theme.css";
import "./assets/iconfont/iconfont.js";
import "./assets/iconfont/iconfont.css";
import "v-contextmenu/dist/themes/default.css";
import "vue-virtual-scroller/dist/vue-virtual-scroller.css";
const app = createApp(App);
@ -46,6 +48,11 @@ getServerConfig(app).then(async config => {
await router.isReady();
injectResponsiveStorage(app, config);
setupStore(app);
app.use(MotionPlugin).use(useI18n).use(ElementPlus).use(useTable);
app
.use(MotionPlugin)
.use(useI18n)
.use(ElementPlus)
.use(useTable)
.use(VirtualScroller);
app.mount("#app");
});

View File

@ -122,6 +122,14 @@ const ableRouter = {
meta: {
title: $t("menus.hsSwiper")
}
},
{
path: "/able/virtualList",
name: "reVirtualList",
component: () => import("/@/views/able/virtual-list/index.vue"),
meta: {
title: $t("menus.hsVirtualList")
}
}
]
};

View 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()
};
}

View 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>

View 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>

View 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>

View File

@ -2,7 +2,7 @@
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"moduleResolution": "Node",
"strict": false,
"jsx": "preserve",
"importHelpers": true,