update:antdesign-vue重构中
This commit is contained in:
parent
544ddeae22
commit
1fcd938cf9
|
|
@ -0,0 +1,8 @@
|
|||
/**
|
||||
* copy from element-ui
|
||||
*/
|
||||
|
||||
import Scrollbar from './src/Scrollbar.vue';
|
||||
|
||||
export { Scrollbar };
|
||||
export type { ScrollbarType } from './src/types';
|
||||
|
|
@ -0,0 +1,207 @@
|
|||
<template>
|
||||
<div class="scrollbar">
|
||||
<div
|
||||
ref="wrap"
|
||||
:class="[wrapClass, 'scrollbar__wrap', native ? '' : 'scrollbar__wrap--hidden-default']"
|
||||
:style="style"
|
||||
@scroll="handleScroll"
|
||||
>
|
||||
<component :is="tag" ref="resize" :class="['scrollbar__view', viewClass]" :style="viewStyle">
|
||||
<slot></slot>
|
||||
</component>
|
||||
</div>
|
||||
<template v-if="!native">
|
||||
<bar :move="moveX" :size="sizeWidth" />
|
||||
<bar vertical :move="moveY" :size="sizeHeight" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { addResizeListener, removeResizeListener } from './event';
|
||||
// import componentSetting from '/@/settings/componentSetting';
|
||||
// const { scrollbar } = componentSetting;
|
||||
import { toObject } from './util';
|
||||
import {
|
||||
defineComponent,
|
||||
ref,
|
||||
onMounted,
|
||||
onBeforeUnmount,
|
||||
nextTick,
|
||||
provide,
|
||||
computed,
|
||||
unref,
|
||||
} from 'vue';
|
||||
import Bar from './bar';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Scrollbar',
|
||||
// inheritAttrs: false,
|
||||
components: { Bar },
|
||||
props: {
|
||||
native: {
|
||||
type: Boolean,
|
||||
// default: scrollbar?.native ?? false,
|
||||
default: false,
|
||||
},
|
||||
wrapStyle: {
|
||||
type: [String, Array],
|
||||
default: '',
|
||||
},
|
||||
wrapClass: {
|
||||
type: [String, Array],
|
||||
default: '',
|
||||
},
|
||||
viewClass: {
|
||||
type: [String, Array],
|
||||
default: '',
|
||||
},
|
||||
viewStyle: {
|
||||
type: [String, Array],
|
||||
default: '',
|
||||
},
|
||||
noresize: Boolean, // 如果 container 尺寸不会发生变化,最好设置它可以优化性能
|
||||
tag: {
|
||||
type: String,
|
||||
default: 'div',
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const sizeWidth = ref('0');
|
||||
const sizeHeight = ref('0');
|
||||
const moveX = ref(0);
|
||||
const moveY = ref(0);
|
||||
const wrap = ref();
|
||||
const resize = ref();
|
||||
|
||||
provide('scroll-bar-wrap', wrap);
|
||||
|
||||
const style = computed(() => {
|
||||
if (Array.isArray(props.wrapStyle)) {
|
||||
return toObject(props.wrapStyle);
|
||||
}
|
||||
return props.wrapStyle;
|
||||
});
|
||||
|
||||
const handleScroll = () => {
|
||||
if (!props.native) {
|
||||
moveY.value = (unref(wrap).scrollTop * 100) / unref(wrap).clientHeight;
|
||||
moveX.value = (unref(wrap).scrollLeft * 100) / unref(wrap).clientWidth;
|
||||
}
|
||||
};
|
||||
|
||||
const update = () => {
|
||||
if (!unref(wrap)) return;
|
||||
|
||||
const heightPercentage = (unref(wrap).clientHeight * 100) / unref(wrap).scrollHeight;
|
||||
const widthPercentage = (unref(wrap).clientWidth * 100) / unref(wrap).scrollWidth;
|
||||
|
||||
sizeHeight.value = heightPercentage < 100 ? heightPercentage + '%' : '';
|
||||
sizeWidth.value = widthPercentage < 100 ? widthPercentage + '%' : '';
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (props.native) return;
|
||||
nextTick(update);
|
||||
if (!props.noresize) {
|
||||
addResizeListener(unref(resize), update);
|
||||
addResizeListener(unref(wrap), update);
|
||||
addEventListener('resize', update);
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (props.native) return;
|
||||
if (!props.noresize) {
|
||||
removeResizeListener(unref(resize), update);
|
||||
removeResizeListener(unref(wrap), update);
|
||||
removeEventListener('resize', update);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
moveX,
|
||||
moveY,
|
||||
sizeWidth,
|
||||
sizeHeight,
|
||||
style,
|
||||
wrap,
|
||||
resize,
|
||||
update,
|
||||
handleScroll,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.scrollbar {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
&__wrap {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
|
||||
&--hidden-default {
|
||||
scrollbar-width: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 0%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__thumb {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
cursor: pointer;
|
||||
background-color: rgb(144 147 153 / 30%);
|
||||
border-radius: inherit;
|
||||
transition: 0.3s background-color;
|
||||
|
||||
&:hover {
|
||||
background-color: rgb(144 147 153 / 50%);
|
||||
}
|
||||
}
|
||||
|
||||
&__bar {
|
||||
position: absolute;
|
||||
right: 2px;
|
||||
bottom: 2px;
|
||||
z-index: 1;
|
||||
border-radius: 4px;
|
||||
opacity: 0%;
|
||||
transition: opacity 80ms ease;
|
||||
|
||||
&.is-vertical {
|
||||
top: 2px;
|
||||
width: 6px;
|
||||
|
||||
& > div {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-horizontal {
|
||||
left: 2px;
|
||||
height: 6px;
|
||||
|
||||
& > div {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.scrollbar:active > .scrollbar__bar,
|
||||
.scrollbar:focus > .scrollbar__bar,
|
||||
.scrollbar:hover > .scrollbar__bar {
|
||||
opacity: 100%;
|
||||
transition: opacity 340ms ease-out;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
import {
|
||||
defineComponent,
|
||||
h,
|
||||
computed,
|
||||
ref,
|
||||
getCurrentInstance,
|
||||
onUnmounted,
|
||||
inject,
|
||||
Ref,
|
||||
} from 'vue';
|
||||
import { on, off } from './domUtils';
|
||||
|
||||
import { renderThumbStyle, BAR_MAP } from './util';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Bar',
|
||||
|
||||
props: {
|
||||
vertical: Boolean,
|
||||
size: String,
|
||||
move: Number,
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const instance = getCurrentInstance();
|
||||
const thumb = ref();
|
||||
const wrap = inject('scroll-bar-wrap', {} as Ref<Nullable<HTMLElement>>) as any;
|
||||
const bar = computed(() => {
|
||||
return BAR_MAP[props.vertical ? 'vertical' : 'horizontal'];
|
||||
});
|
||||
const barStore = ref<Recordable>({});
|
||||
const cursorDown = ref();
|
||||
const clickThumbHandler = (e: any) => {
|
||||
// prevent click event of right button
|
||||
if (e.ctrlKey || e.button === 2) {
|
||||
return;
|
||||
}
|
||||
window.getSelection()?.removeAllRanges();
|
||||
startDrag(e);
|
||||
barStore.value[bar.value.axis] =
|
||||
e.currentTarget[bar.value.offset] -
|
||||
(e[bar.value.client] - e.currentTarget.getBoundingClientRect()[bar.value.direction]);
|
||||
};
|
||||
|
||||
const clickTrackHandler = (e: any) => {
|
||||
const offset = Math.abs(
|
||||
e.target.getBoundingClientRect()[bar.value.direction] - e[bar.value.client],
|
||||
);
|
||||
const thumbHalf = thumb.value[bar.value.offset] / 2;
|
||||
const thumbPositionPercentage =
|
||||
((offset - thumbHalf) * 100) / instance?.vnode.el?.[bar.value.offset];
|
||||
|
||||
wrap.value[bar.value.scroll] =
|
||||
(thumbPositionPercentage * wrap.value[bar.value.scrollSize]) / 100;
|
||||
};
|
||||
const startDrag = (e: any) => {
|
||||
e.stopImmediatePropagation();
|
||||
cursorDown.value = true;
|
||||
on(document, 'mousemove', mouseMoveDocumentHandler);
|
||||
on(document, 'mouseup', mouseUpDocumentHandler);
|
||||
document.onselectstart = () => false;
|
||||
};
|
||||
|
||||
const mouseMoveDocumentHandler = (e: any) => {
|
||||
if (cursorDown.value === false) return;
|
||||
const prevPage = barStore.value[bar.value.axis];
|
||||
|
||||
if (!prevPage) return;
|
||||
|
||||
const offset =
|
||||
(instance?.vnode.el?.getBoundingClientRect()[bar.value.direction] - e[bar.value.client]) *
|
||||
-1;
|
||||
const thumbClickPosition = thumb.value[bar.value.offset] - prevPage;
|
||||
const thumbPositionPercentage =
|
||||
((offset - thumbClickPosition) * 100) / instance?.vnode.el?.[bar.value.offset];
|
||||
wrap.value[bar.value.scroll] =
|
||||
(thumbPositionPercentage * wrap.value[bar.value.scrollSize]) / 100;
|
||||
};
|
||||
|
||||
function mouseUpDocumentHandler() {
|
||||
cursorDown.value = false;
|
||||
barStore.value[bar.value.axis] = 0;
|
||||
off(document, 'mousemove', mouseMoveDocumentHandler);
|
||||
document.onselectstart = null;
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
off(document, 'mouseup', mouseUpDocumentHandler);
|
||||
});
|
||||
|
||||
return () =>
|
||||
h(
|
||||
'div',
|
||||
{
|
||||
class: ['scrollbar__bar', 'is-' + bar.value.key],
|
||||
onMousedown: clickTrackHandler,
|
||||
},
|
||||
h('div', {
|
||||
ref: thumb,
|
||||
class: 'scrollbar__thumb',
|
||||
onMousedown: clickThumbHandler,
|
||||
style: renderThumbStyle({
|
||||
size: props.size,
|
||||
move: props.move,
|
||||
bar: bar.value,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
import type { FunctionArgs } from '@vueuse/core';
|
||||
import { upperFirst } from 'lodash-es';
|
||||
|
||||
export interface ViewportOffsetResult {
|
||||
left: number;
|
||||
top: number;
|
||||
right: number;
|
||||
bottom: number;
|
||||
rightIncludeBody: number;
|
||||
bottomIncludeBody: number;
|
||||
}
|
||||
|
||||
export function getBoundingClientRect(element: Element): DOMRect | number {
|
||||
if (!element || !element.getBoundingClientRect) {
|
||||
return 0;
|
||||
}
|
||||
return element.getBoundingClientRect();
|
||||
}
|
||||
|
||||
function trim(string: string) {
|
||||
return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, '');
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function hasClass(el: Element, cls: string) {
|
||||
if (!el || !cls) return false;
|
||||
if (cls.indexOf(' ') !== -1) throw new Error('className should not contain space.');
|
||||
if (el.classList) {
|
||||
return el.classList.contains(cls);
|
||||
} else {
|
||||
return (' ' + el.className + ' ').indexOf(' ' + cls + ' ') > -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function addClass(el: Element, cls: string) {
|
||||
if (!el) return;
|
||||
let curClass = el.className;
|
||||
const classes = (cls || '').split(' ');
|
||||
|
||||
for (let i = 0, j = classes.length; i < j; i++) {
|
||||
const clsName = classes[i];
|
||||
if (!clsName) continue;
|
||||
|
||||
if (el.classList) {
|
||||
el.classList.add(clsName);
|
||||
} else if (!hasClass(el, clsName)) {
|
||||
curClass += ' ' + clsName;
|
||||
}
|
||||
}
|
||||
if (!el.classList) {
|
||||
el.className = curClass;
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function removeClass(el: Element, cls: string) {
|
||||
if (!el || !cls) return;
|
||||
const classes = cls.split(' ');
|
||||
let curClass = ' ' + el.className + ' ';
|
||||
|
||||
for (let i = 0, j = classes.length; i < j; i++) {
|
||||
const clsName = classes[i];
|
||||
if (!clsName) continue;
|
||||
|
||||
if (el.classList) {
|
||||
el.classList.remove(clsName);
|
||||
} else if (hasClass(el, clsName)) {
|
||||
curClass = curClass.replace(' ' + clsName + ' ', ' ');
|
||||
}
|
||||
}
|
||||
if (!el.classList) {
|
||||
el.className = trim(curClass);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get the left and top offset of the current element
|
||||
* left: the distance between the leftmost element and the left side of the document
|
||||
* top: the distance from the top of the element to the top of the document
|
||||
* right: the distance from the far right of the element to the right of the document
|
||||
* bottom: the distance from the bottom of the element to the bottom of the document
|
||||
* rightIncludeBody: the distance between the leftmost element and the right side of the document
|
||||
* bottomIncludeBody: the distance from the bottom of the element to the bottom of the document
|
||||
*
|
||||
* @description:
|
||||
*/
|
||||
export function getViewportOffset(element: Element): ViewportOffsetResult {
|
||||
const doc = document.documentElement;
|
||||
|
||||
const docScrollLeft = doc.scrollLeft;
|
||||
const docScrollTop = doc.scrollTop;
|
||||
const docClientLeft = doc.clientLeft;
|
||||
const docClientTop = doc.clientTop;
|
||||
|
||||
const pageXOffset = window.pageXOffset;
|
||||
const pageYOffset = window.pageYOffset;
|
||||
|
||||
const box = getBoundingClientRect(element);
|
||||
|
||||
const { left: retLeft, top: rectTop, width: rectWidth, height: rectHeight } = box as DOMRect;
|
||||
|
||||
const scrollLeft = (pageXOffset || docScrollLeft) - (docClientLeft || 0);
|
||||
const scrollTop = (pageYOffset || docScrollTop) - (docClientTop || 0);
|
||||
const offsetLeft = retLeft + pageXOffset;
|
||||
const offsetTop = rectTop + pageYOffset;
|
||||
|
||||
const left = offsetLeft - scrollLeft;
|
||||
const top = offsetTop - scrollTop;
|
||||
|
||||
const clientWidth = window.document.documentElement.clientWidth;
|
||||
const clientHeight = window.document.documentElement.clientHeight;
|
||||
return {
|
||||
left: left,
|
||||
top: top,
|
||||
right: clientWidth - rectWidth - left,
|
||||
bottom: clientHeight - rectHeight - top,
|
||||
rightIncludeBody: clientWidth - left,
|
||||
bottomIncludeBody: clientHeight - top,
|
||||
};
|
||||
}
|
||||
|
||||
export function hackCss(attr: string, value: string) {
|
||||
const prefix: string[] = ['webkit', 'Moz', 'ms', 'OT'];
|
||||
|
||||
const styleObj: any = {};
|
||||
prefix.forEach((item) => {
|
||||
styleObj[`${item}${upperFirst(attr)}`] = value;
|
||||
});
|
||||
return {
|
||||
...styleObj,
|
||||
[attr]: value,
|
||||
};
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function on(
|
||||
element: Element | HTMLElement | Document | Window,
|
||||
event: string,
|
||||
handler: EventListenerOrEventListenerObject,
|
||||
): void {
|
||||
if (element && event && handler) {
|
||||
element.addEventListener(event, handler, false);
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function off(
|
||||
element: Element | HTMLElement | Document | Window,
|
||||
event: string,
|
||||
handler: Fn,
|
||||
): void {
|
||||
if (element && event && handler) {
|
||||
element.removeEventListener(event, handler, false);
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function once(el: HTMLElement, event: string, fn: EventListener): void {
|
||||
const listener = function (this: any, ...args: unknown[]) {
|
||||
if (fn) {
|
||||
fn.apply(this, args);
|
||||
}
|
||||
off(el, event, listener);
|
||||
};
|
||||
on(el, event, listener);
|
||||
}
|
||||
|
||||
export function useRafThrottle<T extends FunctionArgs>(fn: T): T {
|
||||
let locked = false;
|
||||
// @ts-ignore
|
||||
return function (...args: any[]) {
|
||||
if (locked) return;
|
||||
locked = true;
|
||||
window.requestAnimationFrame(() => {
|
||||
// @ts-ignore
|
||||
fn.apply(this, args);
|
||||
locked = false;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
|
||||
const isServer = typeof window === 'undefined';
|
||||
|
||||
/* istanbul ignore next */
|
||||
function resizeHandler(entries: any[]) {
|
||||
for (const entry of entries) {
|
||||
const listeners = entry.target.__resizeListeners__ || [];
|
||||
if (listeners.length) {
|
||||
listeners.forEach((fn: () => any) => {
|
||||
fn();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function addResizeListener(element: any, fn: () => any) {
|
||||
if (isServer) return;
|
||||
if (!element.__resizeListeners__) {
|
||||
element.__resizeListeners__ = [];
|
||||
element.__ro__ = new ResizeObserver(resizeHandler);
|
||||
element.__ro__.observe(element);
|
||||
}
|
||||
element.__resizeListeners__.push(fn);
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function removeResizeListener(element: any, fn: () => any) {
|
||||
if (!element || !element.__resizeListeners__) return;
|
||||
element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1);
|
||||
if (!element.__resizeListeners__.length) {
|
||||
element.__ro__.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
export function triggerWindowResize() {
|
||||
const event = document.createEvent('HTMLEvents');
|
||||
event.initEvent('resize', true, true);
|
||||
(event as any).eventType = 'message';
|
||||
window.dispatchEvent(event);
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
export interface BarMapItem {
|
||||
offset: string;
|
||||
scroll: string;
|
||||
scrollSize: string;
|
||||
size: string;
|
||||
key: string;
|
||||
axis: string;
|
||||
client: string;
|
||||
direction: string;
|
||||
}
|
||||
export interface BarMap {
|
||||
vertical: BarMapItem;
|
||||
horizontal: BarMapItem;
|
||||
}
|
||||
|
||||
export interface ScrollbarType {
|
||||
wrap: ElRef;
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
import type { BarMap } from './types';
|
||||
export const BAR_MAP: BarMap = {
|
||||
vertical: {
|
||||
offset: 'offsetHeight',
|
||||
scroll: 'scrollTop',
|
||||
scrollSize: 'scrollHeight',
|
||||
size: 'height',
|
||||
key: 'vertical',
|
||||
axis: 'Y',
|
||||
client: 'clientY',
|
||||
direction: 'top',
|
||||
},
|
||||
horizontal: {
|
||||
offset: 'offsetWidth',
|
||||
scroll: 'scrollLeft',
|
||||
scrollSize: 'scrollWidth',
|
||||
size: 'width',
|
||||
key: 'horizontal',
|
||||
axis: 'X',
|
||||
client: 'clientX',
|
||||
direction: 'left',
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
export function renderThumbStyle({ move, size, bar }) {
|
||||
const style = {} as any;
|
||||
const translate = `translate${bar.axis}(${move}%)`;
|
||||
|
||||
style[bar.size] = size;
|
||||
style.transform = translate;
|
||||
style.msTransform = translate;
|
||||
style.webkitTransform = translate;
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
function extend<T, K>(to: T, _from: K): T & K {
|
||||
return Object.assign(to, _from);
|
||||
}
|
||||
|
||||
export function toObject<T>(arr: Array<T>): Recordable<T> {
|
||||
const res = {};
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (arr[i]) {
|
||||
extend(res, arr[i]);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div style="width: 210px">
|
||||
<div>
|
||||
<a-menu
|
||||
v-model:openKeys="state.openKeys"
|
||||
v-model:selectedKeys="state.selectedKeys"
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
<template>
|
||||
<div :class="{ 'has-logo': themeConfig.showLogo }">
|
||||
<logo :isCollapse="isCollapse" v-if="themeConfig.showLogo"/>
|
||||
<!-- <el-scrollbar wrap-class="scrollbar-wrapper">-->
|
||||
<Scrollbar wrap-class="scrollbar-wrapper">
|
||||
<u-menu/>
|
||||
<!-- </el-scrollbar>-->
|
||||
</Scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import UMenu from './components/Menu.vue'
|
||||
import logo from './components/Logo.vue'
|
||||
import {Scrollbar} from '@/components/Scrollbar'
|
||||
import {useSettingStore} from "@/store/modules/setting"
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue