16 changed files with 215 additions and 218 deletions
@ -1,4 +1,5 @@ |
|||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; |
|||
export const MarkDown = createAsyncComponent(() => import('./src/Markdown.vue')); |
|||
import { withInstall } from '/@/utils'; |
|||
import markDown from './src/Markdown.vue'; |
|||
|
|||
export * from './src/types'; |
|||
export const MarkDown = withInstall(markDown); |
|||
export * from './src/typing'; |
|||
|
|||
@ -1 +1,4 @@ |
|||
export { default as StrengthMeter } from './src/StrengthMeter.vue'; |
|||
import { withInstall } from '/@/utils'; |
|||
import strengthMeter from './src/StrengthMeter.vue'; |
|||
|
|||
export const StrengthMeter = withInstall(strengthMeter); |
|||
|
|||
@ -1 +1,4 @@ |
|||
export { default as Time } from './src/Time.vue'; |
|||
import { withInstall } from '/@/utils/index'; |
|||
import time from './src/Time.vue'; |
|||
|
|||
export const Time = withInstall(time); |
|||
|
|||
@ -1,2 +1,4 @@ |
|||
import Tinymce from './src/Editor.vue'; |
|||
export { Tinymce }; |
|||
import { withInstall } from '/@/utils/index'; |
|||
import tinymce from './src/Editor.vue'; |
|||
|
|||
export const Tinymce = withInstall(tinymce); |
|||
|
|||
@ -1,3 +1,4 @@ |
|||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; |
|||
import { withInstall } from '/@/utils/index'; |
|||
import vScroll from './src/VirtualScroll.vue'; |
|||
|
|||
export const VScroll = createAsyncComponent(() => import('./src/VirtualScroll')); |
|||
export const VScroll = withInstall(vScroll); |
|||
|
|||
@ -1,148 +0,0 @@ |
|||
import { |
|||
defineComponent, |
|||
computed, |
|||
ref, |
|||
unref, |
|||
reactive, |
|||
onMounted, |
|||
watch, |
|||
nextTick, |
|||
CSSProperties, |
|||
} from 'vue'; |
|||
import { useEventListener } from '/@/hooks/event/useEventListener'; |
|||
|
|||
import { props as basicProps } from './props'; |
|||
import { getSlot } from '/@/utils/helper/tsxHelper'; |
|||
import './index.less'; |
|||
|
|||
const prefixCls = 'virtual-scroll'; |
|||
|
|||
function convertToUnit(str: string | number | null | undefined, unit = 'px'): string | undefined { |
|||
if (str == null || str === '') { |
|||
return undefined; |
|||
} else if (isNaN(+str!)) { |
|||
return String(str); |
|||
} else { |
|||
return `${Number(str)}${unit}`; |
|||
} |
|||
} |
|||
|
|||
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((): CSSProperties => { |
|||
return { |
|||
height: convertToUnit((props.items || []).length * unref(getItemHeightRef)), |
|||
}; |
|||
}); |
|||
|
|||
const getWrapStyleRef = computed((): CSSProperties => { |
|||
const styles: Recordable<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; |
|||
} |
|||
useEventListener({ |
|||
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> |
|||
); |
|||
}, |
|||
}); |
|||
@ -0,0 +1,190 @@ |
|||
<script lang="tsx"> |
|||
import { |
|||
defineComponent, |
|||
computed, |
|||
ref, |
|||
unref, |
|||
reactive, |
|||
onMounted, |
|||
watch, |
|||
nextTick, |
|||
CSSProperties, |
|||
} from 'vue'; |
|||
import { useEventListener } from '/@/hooks/event/useEventListener'; |
|||
import { getSlot } from '/@/utils/helper/tsxHelper'; |
|||
|
|||
type NumberOrNumberString = PropType<string | number | undefined>; |
|||
|
|||
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: () => [], |
|||
}, |
|||
}; |
|||
|
|||
const prefixCls = 'virtual-scroll'; |
|||
|
|||
function convertToUnit(str: string | number | null | undefined, unit = 'px'): string | undefined { |
|||
if (str == null || str === '') { |
|||
return undefined; |
|||
} else if (isNaN(+str!)) { |
|||
return String(str); |
|||
} else { |
|||
return `${Number(str)}${unit}`; |
|||
} |
|||
} |
|||
|
|||
export default defineComponent({ |
|||
name: 'VirtualScroll', |
|||
props, |
|||
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((): CSSProperties => { |
|||
return { |
|||
height: convertToUnit((props.items || []).length * unref(getItemHeightRef)), |
|||
}; |
|||
}); |
|||
|
|||
const getWrapStyleRef = computed((): CSSProperties => { |
|||
const styles: Recordable<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; |
|||
} |
|||
useEventListener({ |
|||
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> |
|||
); |
|||
}, |
|||
}); |
|||
</script> |
|||
<style scoped lang="less"> |
|||
.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; |
|||
} |
|||
} |
|||
</style> |
|||
@ -1,18 +0,0 @@ |
|||
.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; |
|||
} |
|||
} |
|||
@ -1,27 +0,0 @@ |
|||
// 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: () => [], |
|||
}, |
|||
}; |
|||
Loading…
Reference in new issue