mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-08-28 05:39:34 +08:00
initial commit
This commit is contained in:
1
src/components/VirtualScroll/index.ts
Normal file
1
src/components/VirtualScroll/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as VirtualScroll } from './src/index';
|
18
src/components/VirtualScroll/src/index.less
Normal file
18
src/components/VirtualScroll/src/index.less
Normal file
@@ -0,0 +1,18 @@
|
||||
.virtual-scroll {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
overflow: auto;
|
||||
flex: 1 1 auto;
|
||||
|
||||
&__container {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&__item {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
left: 0;
|
||||
}
|
||||
}
|
124
src/components/VirtualScroll/src/index.tsx
Normal file
124
src/components/VirtualScroll/src/index.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
import { defineComponent, computed, ref, unref, reactive, onMounted, watch, nextTick } from 'vue';
|
||||
import { useEvent } from '/@/hooks/event/useEvent';
|
||||
|
||||
import { convertToUnit } from '/@/components/util';
|
||||
import { props as basicProps } from './props';
|
||||
import { getSlot } from '/@/utils/helper/tsxHelper';
|
||||
import './index.less';
|
||||
|
||||
const prefixCls = 'virtual-scroll';
|
||||
export default defineComponent({
|
||||
name: 'VirtualScroll',
|
||||
props: basicProps,
|
||||
setup(props, { slots }) {
|
||||
const wrapElRef = ref<HTMLDivElement | null>(null);
|
||||
const state = reactive({
|
||||
first: 0,
|
||||
last: 0,
|
||||
scrollTop: 0,
|
||||
});
|
||||
|
||||
const getBenchRef = computed(() => {
|
||||
return parseInt(props.bench as string, 10);
|
||||
});
|
||||
|
||||
const getItemHeightRef = computed(() => {
|
||||
return parseInt(props.itemHeight as string, 10);
|
||||
});
|
||||
|
||||
const getFirstToRenderRef = computed(() => {
|
||||
return Math.max(0, state.first - unref(getBenchRef));
|
||||
});
|
||||
|
||||
const getLastToRenderRef = computed(() => {
|
||||
return Math.min((props.items || []).length, state.last + unref(getBenchRef));
|
||||
});
|
||||
|
||||
const getContainerStyleRef = computed(() => {
|
||||
return {
|
||||
height: convertToUnit((props.items || []).length * unref(getItemHeightRef)),
|
||||
};
|
||||
});
|
||||
|
||||
const getWrapStyleRef = computed((): object => {
|
||||
const styles: Record<string, string> = {};
|
||||
const height = convertToUnit(props.height);
|
||||
const minHeight = convertToUnit(props.minHeight);
|
||||
const minWidth = convertToUnit(props.minWidth);
|
||||
const maxHeight = convertToUnit(props.maxHeight);
|
||||
const maxWidth = convertToUnit(props.maxWidth);
|
||||
const width = convertToUnit(props.width);
|
||||
|
||||
if (height) styles.height = height;
|
||||
if (minHeight) styles.minHeight = minHeight;
|
||||
if (minWidth) styles.minWidth = minWidth;
|
||||
if (maxHeight) styles.maxHeight = maxHeight;
|
||||
if (maxWidth) styles.maxWidth = maxWidth;
|
||||
if (width) styles.width = width;
|
||||
return styles;
|
||||
});
|
||||
|
||||
watch([() => props.itemHeight, () => props.height], () => {
|
||||
onScroll();
|
||||
});
|
||||
|
||||
function getLast(first: number): number {
|
||||
const wrapEl = unref(wrapElRef);
|
||||
if (!wrapEl) {
|
||||
return 0;
|
||||
}
|
||||
const height = parseInt(props.height || 0, 10) || wrapEl.clientHeight;
|
||||
|
||||
return first + Math.ceil(height / unref(getItemHeightRef));
|
||||
}
|
||||
|
||||
function getFirst(): number {
|
||||
return Math.floor(state.scrollTop / unref(getItemHeightRef));
|
||||
}
|
||||
function onScroll() {
|
||||
const wrapEl = unref(wrapElRef);
|
||||
if (!wrapEl) {
|
||||
return;
|
||||
}
|
||||
state.scrollTop = wrapEl.scrollTop;
|
||||
state.first = getFirst();
|
||||
state.last = getLast(state.first);
|
||||
}
|
||||
function renderChildren() {
|
||||
const { items = [] } = props;
|
||||
return items.slice(unref(getFirstToRenderRef), unref(getLastToRenderRef)).map(genChild);
|
||||
}
|
||||
function genChild(item: any, index: number) {
|
||||
index += unref(getFirstToRenderRef);
|
||||
|
||||
const top = convertToUnit(index * unref(getItemHeightRef));
|
||||
return (
|
||||
<div class={`${prefixCls}__item`} style={{ top }} key={index}>
|
||||
{getSlot(slots, 'default', { index, item })}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
onMounted(() => {
|
||||
state.last = getLast(0);
|
||||
nextTick(() => {
|
||||
const wrapEl = unref(wrapElRef);
|
||||
if (!wrapEl) {
|
||||
return;
|
||||
}
|
||||
useEvent({
|
||||
el: wrapEl,
|
||||
name: 'scroll',
|
||||
listener: onScroll,
|
||||
wait: 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
return () => (
|
||||
<div class={prefixCls} style={unref(getWrapStyleRef)} ref={wrapElRef}>
|
||||
<div class={`${prefixCls}__container`} style={unref(getContainerStyleRef)}>
|
||||
{renderChildren()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
27
src/components/VirtualScroll/src/props.ts
Normal file
27
src/components/VirtualScroll/src/props.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
// Helpers
|
||||
|
||||
import type { PropType } from 'vue';
|
||||
// Types
|
||||
|
||||
export type NumberOrNumberString = PropType<string | number | undefined>;
|
||||
|
||||
export const props = {
|
||||
height: [Number, String] as NumberOrNumberString,
|
||||
maxHeight: [Number, String] as NumberOrNumberString,
|
||||
maxWidth: [Number, String] as NumberOrNumberString,
|
||||
minHeight: [Number, String] as NumberOrNumberString,
|
||||
minWidth: [Number, String] as NumberOrNumberString,
|
||||
width: [Number, String] as NumberOrNumberString,
|
||||
bench: {
|
||||
type: [Number, String] as NumberOrNumberString,
|
||||
default: 0,
|
||||
},
|
||||
itemHeight: {
|
||||
type: [Number, String] as NumberOrNumberString,
|
||||
required: true,
|
||||
},
|
||||
items: {
|
||||
type: Array as PropType<any[]>,
|
||||
default: () => [],
|
||||
},
|
||||
};
|
Reference in New Issue
Block a user