refactor(hooks): introduce vueuse, delete duplicate hooks

This commit is contained in:
vben 2020-11-12 22:40:16 +08:00
parent ecfb702b09
commit d9b1960030
48 changed files with 135 additions and 610 deletions

View File

@ -1,5 +1,10 @@
## Wip ## Wip
### ✨ Refactor
- 重构 hook,引入 `@vueuse`,删除其中已有的`hook`,优化现有的 hook
- `useEvent` 更名->`useEventListener`
### ✨ Features ### ✨ Features
- 表单项的`componentsProps`支持函数类型 - 表单项的`componentsProps`支持函数类型
@ -16,6 +21,7 @@
- 修复多个富文本编辑器只显示一个 - 修复多个富文本编辑器只显示一个
- 修复登录过期后重新登录未跳转原来页面的 - 修复登录过期后重新登录未跳转原来页面的
- 修复 window 系统动态引入错误 - 修复 window 系统动态引入错误
- 修复页面类型错误
## 2.0.0-rc.9 (2020-11-9) ## 2.0.0-rc.9 (2020-11-9)

View File

@ -22,6 +22,7 @@
}, },
"dependencies": { "dependencies": {
"@iconify/iconify": "^2.0.0-rc.2", "@iconify/iconify": "^2.0.0-rc.2",
"@vueuse/core": "^4.0.0-beta.40",
"ant-design-vue": "^2.0.0-beta.15", "ant-design-vue": "^2.0.0-beta.15",
"apexcharts": "3.22.0", "apexcharts": "3.22.0",
"axios": "^0.21.0", "axios": "^0.21.0",

View File

@ -13,7 +13,7 @@
import moment from 'moment'; import moment from 'moment';
import 'moment/dist/locale/zh-cn'; import 'moment/dist/locale/zh-cn';
import { useConfigProvider, useInitAppConfigStore, useListenerNetWork } from './useApp'; import { useConfigProvider, useInitAppConfigStore } from './useApp';
import { useLockPage } from '/@/hooks/web/useLockPage'; import { useLockPage } from '/@/hooks/web/useLockPage';
import { useSetting } from '/@/hooks/core/useSetting'; import { useSetting } from '/@/hooks/core/useSetting';
@ -25,8 +25,6 @@
setup() { setup() {
// Initialize application settings // Initialize application settings
useInitAppConfigStore(); useInitAppConfigStore();
// Initialize network monitoring
useListenerNetWork();
// Initialize breakpoint monitoring // Initialize breakpoint monitoring
createBreakpointListen(); createBreakpointListen();
// Get system configuration // Get system configuration

View File

@ -11,7 +11,7 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, inject, ref, onMounted, unref } from 'vue'; import { defineComponent, inject, ref, onMounted, unref } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useEvent } from '/@/hooks/event/useEvent'; import { useEventListener } from '/@/hooks/event/useEventListener';
export default defineComponent({ export default defineComponent({
name: 'BreadcrumbItem', name: 'BreadcrumbItem',
@ -42,7 +42,7 @@
onMounted(() => { onMounted(() => {
const link = unref(linkRef); const link = unref(linkRef);
if (!link) return; if (!link) return;
useEvent({ useEventListener({
el: link, el: link,
listener: () => { listener: () => {
const { to } = props; const { to } = props;

View File

@ -22,8 +22,7 @@
import { defineComponent, reactive, onMounted, ref, toRef, toRefs } from 'vue'; import { defineComponent, reactive, onMounted, ref, toRef, toRefs } from 'vue';
import { Skeleton } from 'ant-design-vue'; import { Skeleton } from 'ant-design-vue';
import { useTimeout } from '/@/hooks/core/useTimeout'; import { useTimeoutFn, useIntersectionObserver } from '@vueuse/core';
import { useIntersectionObserver } from '/@/hooks/event/useIntersectionObserver';
interface State { interface State {
isInit: boolean; isInit: boolean;
loading: boolean; loading: boolean;
@ -93,7 +92,7 @@
function immediateInit() { function immediateInit() {
const { timeout } = props; const { timeout } = props;
timeout && timeout &&
useTimeout(() => { useTimeoutFn(() => {
init(); init();
}, timeout); }, timeout);
} }
@ -101,7 +100,7 @@
function init() { function init() {
state.loading = true; state.loading = true;
useTimeout(() => { useTimeoutFn(() => {
if (state.isInit) return; if (state.isInit) return;
state.isInit = true; state.isInit = true;
emit('init'); emit('init');

View File

@ -32,7 +32,8 @@
import { triggerWindowResize } from '/@/utils/event/triggerWindowResizeEvent'; import { triggerWindowResize } from '/@/utils/event/triggerWindowResizeEvent';
// hook // hook
import { useTimeout } from '/@/hooks/core/useTimeout'; import { useTimeoutFn } from '@vueuse/core';
export default defineComponent({ export default defineComponent({
components: { components: {
Skeleton, Skeleton,
@ -89,7 +90,7 @@
if (props.triggerWindowResize) { if (props.triggerWindowResize) {
// 200, // 200,
useTimeout(triggerWindowResize, 200); useTimeoutFn(triggerWindowResize, 200);
} }
} }
return { return {

View File

@ -6,15 +6,13 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, reactive, computed, watch, onMounted, unref, toRef } from 'vue'; import { defineComponent, reactive, computed, watch, onMounted, unref, toRef } from 'vue';
import { countToProps } from './props'; import { countToProps } from './props';
import { useRaf } from '/@/hooks/event/useRaf';
import { isNumber } from '/@/utils/is'; import { isNumber } from '/@/utils/is';
import { requestAnimationFrame, cancelAnimationFrame } from '/@/utils/animation';
export default defineComponent({ export default defineComponent({
name: 'CountTo', name: 'CountTo',
props: countToProps, props: countToProps,
emits: ['mounted', 'callback'], emits: ['mounted', 'callback'],
setup(props, { emit }) { setup(props, { emit }) {
const { requestAnimationFrame, cancelAnimationFrame } = useRaf();
const state = reactive<{ const state = reactive<{
localStartVal: number; localStartVal: number;
printVal: number | null; printVal: number | null;

View File

@ -1,7 +1,7 @@
import { Modal } from 'ant-design-vue'; import { Modal } from 'ant-design-vue';
import { defineComponent, watchEffect } from 'vue'; import { defineComponent, watchEffect } from 'vue';
import { basicProps } from './props'; import { basicProps } from './props';
import { useTimeout } from '/@/hooks/core/useTimeout'; import { useTimeoutFn } from '@vueuse/core';
import { extendSlots } from '/@/utils/helper/tsxHelper'; import { extendSlots } from '/@/utils/helper/tsxHelper';
export default defineComponent({ export default defineComponent({
@ -99,7 +99,7 @@ export default defineComponent({
if (!props.visible) { if (!props.visible) {
return; return;
} }
useTimeout(() => { useTimeoutFn(() => {
handleDrag(); handleDrag();
}, 30); }, 30);
}); });

View File

@ -14,8 +14,7 @@ import {
} from 'vue'; } from 'vue';
import { Spin } from 'ant-design-vue'; import { Spin } from 'ant-design-vue';
import { useWindowSizeFn } from '/@/hooks/event/useWindowSize'; import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
// import { useTimeout } from '/@/hooks/core/useTimeout';
import { getSlot } from '/@/utils/helper/tsxHelper'; import { getSlot } from '/@/utils/helper/tsxHelper';
import { useElResize } from '/@/hooks/event/useElResize'; import { useElResize } from '/@/hooks/event/useElResize';
@ -126,17 +125,6 @@ export default defineComponent({
} }
await nextTick(); await nextTick();
const spinEl = unref(spinRef); const spinEl = unref(spinRef);
// if (!spinEl) {
// useTimeout(() => {
// // retry
// if (tryCount < 3) {
// setModalHeight();
// }
// tryCount++;
// }, 10);
// return;
// }
// tryCount = 0;
const spinContainerEl = spinEl.$el.querySelector('.ant-spin-container') as HTMLElement; const spinContainerEl = spinEl.$el.querySelector('.ant-spin-container') as HTMLElement;
if (!spinContainerEl) return; if (!spinContainerEl) return;

View File

@ -64,7 +64,7 @@
import { useTableScroll } from './hooks/useTableScroll'; import { useTableScroll } from './hooks/useTableScroll';
import { provideTable } from './hooks/useProvinceTable'; import { provideTable } from './hooks/useProvinceTable';
import { useEvent } from '/@/hooks/event/useEvent'; import { useEventListener } from '/@/hooks/event/useEventListener';
import { basicProps } from './props'; import { basicProps } from './props';
import { ROW_KEY } from './const'; import { ROW_KEY } from './const';
import './style/index.less'; import './style/index.less';
@ -245,7 +245,7 @@
} }
const bodyDomList = tableEl.$el.querySelectorAll('.ant-table-body') as HTMLDivElement[]; const bodyDomList = tableEl.$el.querySelectorAll('.ant-table-body') as HTMLDivElement[];
const bodyDom = bodyDomList[0]; const bodyDom = bodyDomList[0];
useEvent({ useEventListener({
el: bodyDom, el: bodyDom,
name: 'scroll', name: 'scroll',
listener: () => { listener: () => {

View File

@ -3,7 +3,7 @@ import type { PaginationProps } from '../types/pagination';
import { watch, ref, unref, ComputedRef, computed, onMounted, Ref } from 'vue'; import { watch, ref, unref, ComputedRef, computed, onMounted, Ref } from 'vue';
import { useTimeout } from '/@/hooks/core/useTimeout'; import { useTimeoutFn } from '@vueuse/core';
import { buildUUID } from '/@/utils/uuid'; import { buildUUID } from '/@/utils/uuid';
import { isFunction, isBoolean } from '/@/utils/is'; import { isFunction, isBoolean } from '/@/utils/is';
@ -145,7 +145,7 @@ export function useDataSource(
} }
onMounted(() => { onMounted(() => {
// 转异步任务 // 转异步任务
useTimeout(() => { useTimeoutFn(() => {
unref(propsRef).immediate && fetch(); unref(propsRef).immediate && fetch();
}, 0); }, 0);
}); });

View File

@ -6,7 +6,7 @@ import { injectModal } from '/@/components/Modal/src/provideModal';
import { getViewportOffset } from '/@/utils/domUtils'; import { getViewportOffset } from '/@/utils/domUtils';
import { isBoolean } from '/@/utils/is'; import { isBoolean } from '/@/utils/is';
import { useWindowSizeFn } from '/@/hooks/event/useWindowSize'; import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
import { useProps } from './useProps'; import { useProps } from './useProps';
export function useTableScroll(refProps: ComputedRef<BasicTableProps>, tableElRef: Ref<any>) { export function useTableScroll(refProps: ComputedRef<BasicTableProps>, tableElRef: Ref<any>) {
@ -110,17 +110,6 @@ export function useTableScroll(refProps: ComputedRef<BasicTableProps>, tableElRe
nextTick(() => { nextTick(() => {
calcTableHeight(); calcTableHeight();
}); });
// const hasFixedLeft = (unref(propsRef).columns || []).some((item) => item.fixed === 'left');
// // TODO antv table问题情况太多只能先用下面方式定时器hack
// useTimeout(() => {
// calcTableHeight(() => {
// // 有左侧固定列的时候才有问题
// hasFixedLeft &&
// useTimeout(() => {
// triggerWindowResize();
// }, 300);
// });
// }, 200);
} }
}); });
const getScrollRef = computed(() => { const getScrollRef = computed(() => {

View File

@ -1,6 +1,6 @@
import { defineComponent, ref, computed, unref, reactive, watch, watchEffect } from 'vue'; import { defineComponent, ref, computed, unref, reactive, watch, watchEffect } from 'vue';
import { useTimeout } from '/@/hooks/core/useTimeout'; import { useTimeoutFn } from '@vueuse/core';
import { useEvent } from '/@/hooks/event/useEvent'; import { useEventListener } from '/@/hooks/event/useEventListener';
import { basicProps } from './props'; import { basicProps } from './props';
import { getSlot } from '/@/utils/helper/tsxHelper'; import { getSlot } from '/@/utils/helper/tsxHelper';
import './DragVerify.less'; import './DragVerify.less';
@ -91,7 +91,7 @@ export default defineComponent({
return (e as MouseEvent).pageX || (e as TouchEvent).touches[0].pageX; return (e as MouseEvent).pageX || (e as TouchEvent).touches[0].pageX;
} }
useEvent({ useEventListener({
el: document, el: document,
name: 'mouseup', name: 'mouseup',
listener: () => { listener: () => {
@ -201,7 +201,7 @@ export default defineComponent({
const contentEl = unref(contentElRef); const contentEl = unref(contentElRef);
if (!actionEl || !barEl || !contentEl) return; if (!actionEl || !barEl || !contentEl) return;
state.toLeft = true; state.toLeft = true;
useTimeout(() => { useTimeoutFn(() => {
state.toLeft = false; state.toLeft = false;
actionEl.style.left = '0'; actionEl.style.left = '0';
barEl.style.width = '0'; barEl.style.width = '0';

View File

@ -1,7 +1,7 @@
import type { MoveData, DragVerifyActionType } from './types'; import type { MoveData, DragVerifyActionType } from './types';
import { defineComponent, computed, unref, reactive, watch, ref, getCurrentInstance } from 'vue'; import { defineComponent, computed, unref, reactive, watch, ref, getCurrentInstance } from 'vue';
import { useTimeout } from '/@/hooks/core/useTimeout'; import { useTimeoutFn } from '@vueuse/core';
import BasicDragVerify from './DragVerify'; import BasicDragVerify from './DragVerify';
@ -86,7 +86,7 @@ export default defineComponent({
if (Math.abs(randomRotate - currentRotate) >= (diffDegree || 20)) { if (Math.abs(randomRotate - currentRotate) >= (diffDegree || 20)) {
state.imgStyle = hackCss('transform', `rotateZ(${randomRotate}deg)`); state.imgStyle = hackCss('transform', `rotateZ(${randomRotate}deg)`);
state.toOrigin = true; state.toOrigin = true;
useTimeout(() => { useTimeoutFn(() => {
state.toOrigin = false; state.toOrigin = false;
state.showTip = true; state.showTip = true;
// 时间与动画时间保持一致 // 时间与动画时间保持一致

View File

@ -1,7 +1,7 @@
<script lang="tsx"> <script lang="tsx">
import { defineComponent, ref, unref } from 'vue'; import { defineComponent, ref, unref } from 'vue';
import { BasicModal } from '/@/components/Modal/index'; import { BasicModal } from '/@/components/Modal/index';
import { useTimeout } from '/@/hooks/core/useTimeout'; import { useTimeoutFn } from '@vueuse/core';
import { RotateDragVerify, DragVerifyActionType } from '/@/components/Verify/index'; import { RotateDragVerify, DragVerifyActionType } from '/@/components/Verify/index';
export default defineComponent({ export default defineComponent({
@ -11,7 +11,7 @@
const dragRef = ref<DragVerifyActionType | null>(null); const dragRef = ref<DragVerifyActionType | null>(null);
function handleSuccess() { function handleSuccess() {
useTimeout(() => { useTimeoutFn(() => {
emit('success'); emit('success');
const dragEl = unref(dragRef); const dragEl = unref(dragRef);
if (dragEl) { if (dragEl) {

View File

@ -1,5 +1,5 @@
import { defineComponent, computed, ref, unref, reactive, onMounted, watch, nextTick } from 'vue'; import { defineComponent, computed, ref, unref, reactive, onMounted, watch, nextTick } from 'vue';
import { useEvent } from '/@/hooks/event/useEvent'; import { useEventListener } from '/@/hooks/event/useEventListener';
import { convertToUnit } from '/@/components/util'; import { convertToUnit } from '/@/components/util';
import { props as basicProps } from './props'; import { props as basicProps } from './props';
@ -109,7 +109,7 @@ export default defineComponent({
if (!wrapEl) { if (!wrapEl) {
return; return;
} }
useEvent({ useEventListener({
el: wrapEl, el: wrapEl,
name: 'scroll', name: 'scroll',
listener: onScroll, listener: onScroll,

View File

@ -1,56 +0,0 @@
import { ref, watchEffect, Ref } from 'vue';
/**
* Handle overlapping async evaluations
*
* @param cancelCallback The provided callback is invoked when a re-evaluation of the computed value is triggered before the previous one finished
*/
export type AsyncComputedOnCancel = (cancelCallback: () => void) => void;
/**
* A two-item tuple with the first item being a ref to the computed value and the second item holding a boolean ref, indicating whether the async computed value is currently (re-)evaluated
*/
export type AsyncComputedResult<T> = [Ref<T>, Ref<boolean>];
/**
* Create an asynchronous computed dependency
*
* @param evaluationCallback The promise-returning callback which generates the computed value
* @param defaultValue A default value, used until the first evaluation finishes
*/
export function asyncComputed<T>(
evaluationCallback: (onCancel: AsyncComputedOnCancel) => T | Promise<T>,
defaultValue?: T
): AsyncComputedResult<T> {
let counter = 0;
const current = ref(defaultValue) as Ref<T>;
const evaluating = ref<boolean>(false);
watchEffect(async (onInvalidate: Fn) => {
counter++;
const counterAtBeginning = counter;
let hasFinished = false;
try {
// Defer initial setting of `evaluating` ref
// to avoid having it as a dependency
Promise.resolve().then(() => {
evaluating.value = true;
});
const result = await evaluationCallback((cancelCallback) => {
onInvalidate(() => {
evaluating.value = false;
if (!hasFinished) cancelCallback();
});
});
if (counterAtBeginning === counter) current.value = result;
} finally {
evaluating.value = false;
hasFinished = true;
}
});
return [current, evaluating];
}

View File

@ -1,16 +0,0 @@
import { ref } from 'vue';
export function useCounter(initialValue = 0) {
const count = ref(initialValue);
const inc = (delta = 1) => (count.value += delta);
const dec = (delta = 1) => (count.value -= delta);
const get = () => count.value;
const set = (val: number) => (count.value = val);
const reset = (val = initialValue) => {
initialValue = val;
return set(val);
};
return { count, inc, dec, get, set, reset };
}

View File

@ -1,23 +0,0 @@
import { isFunction } from '/@/utils/is';
import { Ref, watch } from 'vue';
import { useTimeoutRef } from '/@/hooks/core/useTimeoutRef';
type TimeoutFnResult = [Fn<void>, Fn<void>, Ref<boolean>];
export function useTimeout(handle: Fn<any>, wait: number): TimeoutFnResult {
if (!isFunction(handle)) {
throw new Error('handle is not Function!');
}
const [readyRef, clear, runAgain] = useTimeoutRef(wait);
watch(
readyRef,
(maturity) => {
maturity && handle();
},
{ immediate: false }
);
return [clear, runAgain, readyRef];
}

View File

@ -1,24 +0,0 @@
import { Ref, ref } from 'vue';
import { tryOnUnmounted } from '/@/utils/helper/vueHelper';
export type TimeoutResult = [Ref<boolean>, Fn<void>, Fn<void>];
export function useTimeoutRef(wait: number): TimeoutResult {
const readyRef = ref(false);
let timer: ReturnType<typeof setTimeout> | undefined;
function clear(): void {
readyRef.value = false;
timer && window.clearTimeout(timer);
}
function openTimer(): void {
clear();
timer = setTimeout(() => {
readyRef.value = true;
}, wait);
}
openTimer();
tryOnUnmounted(clear);
return [readyRef, clear, openTimer];
}

View File

@ -1,10 +1,10 @@
import { ref, computed, Ref, unref } from 'vue'; import { ref, computed, ComputedRef, unref } from 'vue';
import { useEvent } from './useEvent'; import { useEventListener } from '/@/hooks/event/useEventListener';
import { screenMap, sizeEnum, screenEnum } from '/@/enums/breakpointEnum'; import { screenMap, sizeEnum, screenEnum } from '/@/enums/breakpointEnum';
let globalScreenRef: Ref<sizeEnum | undefined>; let globalScreenRef: ComputedRef<sizeEnum | undefined>;
let globalWidthRef: Ref<number>; let globalWidthRef: ComputedRef<number>;
let globalRealWidthRef: Ref<number>; let globalRealWidthRef: ComputedRef<number>;
export function useBreakpoint() { export function useBreakpoint() {
return { return {
@ -43,7 +43,7 @@ export function createBreakpointListen(fn?: Fn) {
realWidthRef.value = width; realWidthRef.value = width;
} }
useEvent({ useEventListener({
el: window, el: window,
name: 'resize', name: 'resize',
listener: () => { listener: () => {

View File

@ -1,10 +0,0 @@
import { tryOnUnmounted } from '/@/utils/helper/vueHelper';
import EventHub from '/@/utils/eventHub';
const eventHub = new EventHub();
export function useEventHub(): EventHub {
tryOnUnmounted(() => {
eventHub.clear();
});
return eventHub;
}

View File

@ -15,7 +15,7 @@ export interface UseEventParams {
isDebounce?: boolean; isDebounce?: boolean;
wait?: number; wait?: number;
} }
export function useEvent({ export function useEventListener({
el = window, el = window,
name, name,
listener, listener,

View File

@ -1,48 +0,0 @@
import { Ref, watchEffect, ref } from 'vue';
interface IntersectionObserverProps {
target: Ref<Element | null | undefined>;
root?: Ref<Element | null | undefined>;
onIntersect: IntersectionObserverCallback;
rootMargin?: string;
threshold?: number;
}
export function useIntersectionObserver({
target,
root,
onIntersect,
rootMargin = '0px',
threshold = 0.1,
}: IntersectionObserverProps) {
let cleanup = () => {};
const observer: Ref<Nullable<IntersectionObserver>> = ref(null);
const stopEffect = watchEffect(() => {
cleanup();
observer.value = new IntersectionObserver(onIntersect, {
root: root ? root.value : null,
rootMargin,
threshold,
});
const current = target.value;
current && observer.value.observe(current);
cleanup = () => {
if (observer.value) {
observer.value.disconnect();
target.value && observer.value.unobserve(target.value);
}
};
});
return {
observer,
stop: () => {
cleanup();
stopEffect();
},
};
}

View File

@ -1,35 +0,0 @@
import { ref } from 'vue';
import { tryOnUnmounted } from '/@/utils/helper/vueHelper';
function getTimestamp() {
return +Date.now();
}
export function useNow() {
const now = ref(getTimestamp());
let started = false;
const update = () => {
requestAnimationFrame(() => {
now.value = getTimestamp();
if (started) update();
});
};
const start = () => {
if (!started) {
started = true;
update();
}
};
const stop = () => {
started = false;
};
start();
tryOnUnmounted(stop);
return now;
}

View File

@ -1,95 +0,0 @@
import { isServer } from '/@/utils/is';
import { onUnmounted, getCurrentInstance } from 'vue';
let lastTime = 0;
const prefixes = 'webkit moz ms o'.split(' '); // Each browser prefix
let requestAnimationFrame: any;
let cancelAnimationFrame: any;
/* eslint-disable-next-line */
const NO_LOOP = () => {};
const getWindowFrame = (name: string) => {
return name as any;
};
if (isServer) {
requestAnimationFrame = cancelAnimationFrame = NO_LOOP;
} else {
requestAnimationFrame = window.requestAnimationFrame;
cancelAnimationFrame = window.cancelAnimationFrame;
let prefix;
for (let i = 0; i < prefixes.length; i++) {
if (requestAnimationFrame && cancelAnimationFrame) {
break;
}
prefix = prefixes[i];
requestAnimationFrame =
requestAnimationFrame || window[getWindowFrame(prefix + 'RequestAnimationFrame')];
cancelAnimationFrame =
cancelAnimationFrame ||
window[getWindowFrame(prefix + 'CancelAnimationFrame')] ||
window[getWindowFrame(prefix + 'CancelRequestAnimationFrame')];
}
// If the current browser does not support requestAnimationFrame and cancelAnimationFrame, it will fall back to setTimeout
if (!requestAnimationFrame || !cancelAnimationFrame) {
requestAnimationFrame = function (callback: Fn) {
const currTime = new Date().getTime();
const timeToCall = Math.max(0, 16 - (currTime - lastTime));
const id = window.setTimeout(() => {
/* eslint-disable-next-line */
callback(currTime + timeToCall);
}, timeToCall);
lastTime = currTime + timeToCall;
return id;
};
cancelAnimationFrame = function (id: number) {
window.clearTimeout(id);
};
}
}
export function useRaf() {
// if (getCurrentInstance()) {
// onUnmounted(() => {
// cancelAnimationFrame();
// });
// }
return { requestAnimationFrame, cancelAnimationFrame };
}
export function useRafFn(fn: (...arg: any) => any, options: { immediate?: boolean } = {}) {
const { immediate = false } = options;
let started = false;
let id: ReturnType<typeof window.requestAnimationFrame>;
function loop() {
if (!started) return;
fn();
id = requestAnimationFrame(loop);
}
function start() {
if (!started) {
started = true;
loop();
}
}
function stop() {
started = false;
}
if (immediate) {
start();
}
if (getCurrentInstance()) {
onUnmounted(() => {
cancelAnimationFrame(id);
stop();
});
}
return { stop, start };
}

View File

@ -1,6 +1,7 @@
import { useRaf } from '/@/hooks/event/useRaf';
import { isFunction, isUnDef } from '/@/utils/is'; import { isFunction, isUnDef } from '/@/utils/is';
import { ref, unref } from 'vue'; import { ref, unref } from 'vue';
import { requestAnimationFrame } from '/@/utils/animation';
export interface ScrollToParams { export interface ScrollToParams {
el: HTMLElement; el: HTMLElement;
to: number; to: number;
@ -30,7 +31,6 @@ export function useScrollTo({ el, to, duration = 500, callback }: ScrollToParams
const increment = 20; const increment = 20;
let currentTime = 0; let currentTime = 0;
duration = isUnDef(duration) ? 500 : duration; duration = isUnDef(duration) ? 500 : duration;
const { requestAnimationFrame } = useRaf();
const animateScroll = function () { const animateScroll = function () {
if (!unref(isActiveRef)) { if (!unref(isActiveRef)) {

View File

@ -1,5 +1,4 @@
import { tryOnMounted, tryOnUnmounted } from '/@/utils/helper/vueHelper'; import { tryOnMounted, tryOnUnmounted } from '/@/utils/helper/vueHelper';
import { ref } from 'vue';
import { useDebounce } from '/@/hooks/core/useDebounce'; import { useDebounce } from '/@/hooks/core/useDebounce';
@ -37,22 +36,3 @@ export function useWindowSizeFn<T>(fn: Fn<T>, wait = 150, options?: WindowSizeOp
}); });
return [start, stop]; return [start, stop];
} }
export const useWindowSize = (wait = 150, options?: WindowSizeOptions) => {
const widthRef = ref(0);
const heightRef = ref(0);
function setSize() {
widthRef.value = window.innerWidth;
heightRef.value = window.innerHeight;
}
setSize();
const handler = () => {
setSize();
};
useWindowSizeFn(handler, wait, options);
return { widthRef: widthRef, heightRef: heightRef };
};

View File

@ -1,12 +0,0 @@
import { reactive } from 'vue';
export function createGlobalState<T extends object>(factory: () => T) {
let state: T;
return () => {
if (!state) {
state = reactive(factory()) as T;
}
return state;
};
}

View File

@ -1,4 +1,4 @@
import { useTimeout } from '/@/hooks/core/useTimeout'; import { useTimeoutFn } from '@vueuse/core';
import { tryOnUnmounted } from '/@/utils/helper/vueHelper'; import { tryOnUnmounted } from '/@/utils/helper/vueHelper';
import { unref, Ref, nextTick } from 'vue'; import { unref, Ref, nextTick } from 'vue';
@ -9,7 +9,7 @@ export function useApexCharts(elRef: Ref<HTMLDivElement>) {
function setOptions(options: any) { function setOptions(options: any) {
nextTick(() => { nextTick(() => {
useTimeout(() => { useTimeoutFn(() => {
const el = unref(elRef); const el = unref(elRef);
if (!el || !unref(el)) return; if (!el || !unref(el)) return;

View File

@ -1,17 +1,17 @@
import { ref, Ref, unref } from 'vue'; import { ref, Ref, unref } from 'vue';
import { useEvent } from '/@/hooks/event/useEvent'; import { useEventListener } from '/@/hooks/event/useEventListener';
export function useClickOutside<T extends HTMLElement>( export function useClickOutside<T extends HTMLElement>(
containerRef: Ref<T>, containerRef: Ref<T>,
onClickOutside: (e: MouseEvent | TouchEvent) => void onClickOutside: (e: MouseEvent | TouchEvent) => void
) { ) {
const isTouchRef = ref(false); const isTouchRef = ref(false);
useEvent({ useEventListener({
el: document, el: document,
name: 'touchend', name: 'touchend',
listener: handler, listener: handler,
options: true, options: true,
}); });
useEvent({ useEventListener({
el: document, el: document,
name: 'click', name: 'click',
listener: handler, listener: handler,

View File

@ -1,40 +0,0 @@
// import { ref, Ref, isRef, watch } from '@vue/runtime-dom';
// TODO 打开注释会造成热更新失效,待排查
// export default function useCssVar(prop: string, refEl?: Ref<HTMLElement | null>) {
// const refVar = ref('');
// let el: HTMLElement = document.documentElement;
// if (isRef(refEl)) {
// watch(
// refEl,
// () => {
// if (refEl.value) {
// el = refEl.value as HTMLElement;
// refVar.value = getComputedStyle(el).getPropertyValue(prop);
// }
// },
// { immediate: true }
// );
// } else {
// refVar.value = getComputedStyle(el).getPropertyValue(prop);
// }
// watch(
// refVar,
// (val) => {
// el && el.style.setProperty(prop, val);
// },
// { immediate: true }
// );
// return refVar;
// }
export function getCssVar(prop: string, dom = document.documentElement) {
return getComputedStyle(dom).getPropertyValue(prop);
}
export function setCssVar(prop: string, val: any, dom = document.documentElement) {
dom.style.setProperty(prop, val);
}

View File

@ -1,10 +1,10 @@
import { useTimeout } from '/@/hooks/core/useTimeout'; import { useTimeoutFn } from '@vueuse/core';
import { tryOnUnmounted } from '/@/utils/helper/vueHelper'; import { tryOnUnmounted } from '/@/utils/helper/vueHelper';
import { unref, Ref, nextTick } from 'vue'; import { unref, Ref, nextTick } from 'vue';
import type { EChartOption, ECharts } from 'echarts'; import type { EChartOption, ECharts } from 'echarts';
import echarts from 'echarts'; import echarts from 'echarts';
import { useDebounce } from '/@/hooks/core/useDebounce'; import { useDebounce } from '/@/hooks/core/useDebounce';
import { useEvent } from '/@/hooks/event/useEvent'; import { useEventListener } from '/@/hooks/event/useEventListener';
import { useBreakpoint } from '/@/hooks/event/useBreakpoint'; import { useBreakpoint } from '/@/hooks/event/useBreakpoint';
export type { EChartOption, ECharts }; export type { EChartOption, ECharts };
@ -26,7 +26,7 @@ export function useECharts(
return; return;
} }
chartInstance = echarts.init(el, theme); chartInstance = echarts.init(el, theme);
const { removeEvent } = useEvent({ const { removeEvent } = useEventListener({
el: window, el: window,
name: 'resize', name: 'resize',
listener: resizeFn, listener: resizeFn,
@ -34,7 +34,7 @@ export function useECharts(
removeResizeFn = removeEvent; removeResizeFn = removeEvent;
const { widthRef, screenEnum } = useBreakpoint(); const { widthRef, screenEnum } = useBreakpoint();
if (unref(widthRef) <= screenEnum.MD) { if (unref(widthRef) <= screenEnum.MD) {
useTimeout(() => { useTimeoutFn(() => {
resizeFn(); resizeFn();
}, 30); }, 30);
} }
@ -42,7 +42,7 @@ export function useECharts(
function setOptions(options: any, clear = true) { function setOptions(options: any, clear = true) {
nextTick(() => { nextTick(() => {
useTimeout(() => { useTimeoutFn(() => {
if (!chartInstance) { if (!chartInstance) {
init(); init();

View File

@ -1,5 +0,0 @@
import { createStorage } from '/@/utils/storage';
export function useLocalStorage() {
return createStorage(localStorage);
}

View File

@ -1,53 +0,0 @@
import type { Ref } from 'vue';
import { ref, watch } from 'vue';
import { tryOnMounted, tryOnUnmounted } from '/@/utils/helper/vueHelper';
import { isBoolean } from '/@/utils/is';
const ON_LINE = 'online';
const OFF_LINE = 'offline';
export function useNetWork({
onLineFn,
offLineFn,
}: {
onLineFn?: () => void;
offLineFn?: () => void;
}) {
const onLineRef = ref(navigator.onLine);
// Disconnect time
const offlineAt: Ref<number | undefined> = ref(undefined);
watch(
() => onLineRef.value,
(onLine, oldValue): void => {
if (isBoolean(oldValue) && !oldValue && onLine) {
onLineFn && onLineFn();
} else if (isBoolean(onLine) && !onLine && oldValue) {
// Network to no network
offlineAt.value = Date.now();
offLineFn && offLineFn();
}
},
{
immediate: false,
}
);
const handler = (e: Event) => {
const { type } = e;
onLineRef.value = type === ON_LINE;
};
tryOnMounted(() => {
window.addEventListener(ON_LINE, handler);
window.addEventListener(OFF_LINE, handler);
});
tryOnUnmounted(() => {
window.removeEventListener(ON_LINE, handler);
window.removeEventListener(OFF_LINE, handler);
});
return {
onLineRef,
};
}

View File

@ -1,5 +0,0 @@
import { createStorage } from '/@/utils/storage';
export function useSessionStorage() {
return createStorage(sessionStorage);
}

View File

@ -1,4 +1,4 @@
import { useTimeout } from '/@/hooks/core/useTimeout'; import { useTimeoutFn } from '@vueuse/core';
import { PageEnum } from '/@/enums/pageEnum'; import { PageEnum } from '/@/enums/pageEnum';
import { TabItem, tabStore } from '/@/store/modules/tab'; import { TabItem, tabStore } from '/@/store/modules/tab';
import { appStore } from '/@/store/modules/app'; import { appStore } from '/@/store/modules/app';
@ -98,7 +98,7 @@ export function useTabs() {
const to = getTo(path); const to = getTo(path);
if (!to) return; if (!to) return;
useTimeout(() => { useTimeoutFn(() => {
tabStore.addTabByPathAction(); tabStore.addTabByPathAction();
}, 0); }, 0);
const { replace, query = {}, params = {} } = opt || {}; const { replace, query = {}, params = {} } = opt || {};

View File

@ -1,32 +0,0 @@
import type { Ref } from 'vue';
import { ref, watch } from 'vue';
import { tryOnUnmounted } from '/@/utils/helper/vueHelper';
import { isString } from '/@/utils/is';
export function useTitle(overrideTitle: string | null = null): Ref<string | null> {
const title = ref<string | null>(isString(overrideTitle) ? overrideTitle : document.title);
const observer = new MutationObserver((m) => {
title.value = m[0].target.textContent;
});
watch(
title,
(t, o) => {
if (isString(t) && t !== o) {
document.title = t;
}
},
{
immediate: true,
flush: 'sync',
}
);
const titleElement = document.querySelector('title')!;
observer.observe(titleElement, { childList: true });
tryOnUnmounted(() => {
observer.disconnect();
});
return title;
}

View File

@ -1,20 +0,0 @@
import { ref, onUnmounted, computed } from '@vue/runtime-dom';
import { isDef } from '/@/utils/is';
export default function useVisibilityState() {
const refVisibility = ref(true);
if (isDef(document) && isDef(document.visibilityState)) {
const setVisibility = () => {
refVisibility.value = document.visibilityState === 'visible';
};
document.addEventListener('visibilitychange', setVisibility, false);
onUnmounted(() => {
document.removeEventListener('visibilitychange', setVisibility);
});
}
return computed(() => refVisibility.value);
}

View File

@ -19,7 +19,7 @@ import {
import { useFullscreen } from '/@/hooks/web/useFullScreen'; import { useFullscreen } from '/@/hooks/web/useFullScreen';
import { useTabs } from '/@/hooks/web/useTabs'; import { useTabs } from '/@/hooks/web/useTabs';
import { useWindowSizeFn } from '/@/hooks/event/useWindowSize'; import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useModal } from '/@/components/Modal'; import { useModal } from '/@/components/Modal';

View File

@ -8,7 +8,7 @@
import { computed, defineComponent, PropType, ref, watch } from 'vue'; import { computed, defineComponent, PropType, ref, watch } from 'vue';
// hooks // hooks
import { useSetting } from '/@/hooks/core/useSetting'; import { useSetting } from '/@/hooks/core/useSetting';
import { useTimeout } from '/@/hooks/core/useTimeout'; import { useTimeoutFn } from '@vueuse/core';
import { useGo } from '/@/hooks/web/usePage'; import { useGo } from '/@/hooks/web/usePage';
import { PageEnum } from '/@/enums/pageEnum'; import { PageEnum } from '/@/enums/pageEnum';
@ -41,7 +41,7 @@
() => props.showTitle, () => props.showTitle,
(show: boolean) => { (show: boolean) => {
if (show) { if (show) {
useTimeout(() => { useTimeoutFn(() => {
showRef.value = show; showRef.value = show;
}, 280); }, 280);
} else { } else {

View File

@ -128,9 +128,6 @@ const setting: ProjectConfig = {
// 是否开启登录安全校验 // 是否开启登录安全校验
openLoginVerify: true, openLoginVerify: true,
// 是否监听网络变化
listenNetWork: false,
// 是否开启页面切换loading // 是否开启页面切换loading
openPageLoading: true, openPageLoading: true,

View File

@ -1,4 +1,3 @@
import { setCssVar } from '/@/hooks/web/useCssVar';
import { isHexColor, colorIsDark, lighten, darken } from '/@/utils/color'; import { isHexColor, colorIsDark, lighten, darken } from '/@/utils/color';
import { appStore } from '/@/store/modules/app'; import { appStore } from '/@/store/modules/app';
import { MenuThemeEnum } from '/@/enums/menuEnum'; import { MenuThemeEnum } from '/@/enums/menuEnum';
@ -12,6 +11,10 @@ const SIDER_DARK_DARKEN_BG_COLOR = '--sider-dark-darken-bg-color';
const SIDER_LIGHTEN_1_BG_COLOR = '--sider-dark-lighten-1-bg-color'; const SIDER_LIGHTEN_1_BG_COLOR = '--sider-dark-lighten-1-bg-color';
const SIDER_LIGHTEN_2_BG_COLOR = '--sider-dark-lighten-2-bg-color'; const SIDER_LIGHTEN_2_BG_COLOR = '--sider-dark-lighten-2-bg-color';
export function setCssVar(prop: string, val: any, dom = document.documentElement) {
dom.style.setProperty(prop, val);
}
function toggleClass(flag: boolean, clsName: string) { function toggleClass(flag: boolean, clsName: string) {
const body = document.body; const body = document.body;
let { className } = body; let { className } = body;

View File

@ -101,8 +101,6 @@ export interface ProjectConfig {
routerTransition: RouterTransitionEnum; routerTransition: RouterTransitionEnum;
// 是否开启登录安全校验 // 是否开启登录安全校验
openLoginVerify: boolean; openLoginVerify: boolean;
// 是否监听网络变化
listenNetWork: boolean;
// 是否开启页面切换loading // 是否开启页面切换loading
openPageLoading: boolean; openPageLoading: boolean;
// 是否开启回到顶部 // 是否开启回到顶部

View File

@ -17,11 +17,6 @@ import {
} from '/@/setup/theme'; } from '/@/setup/theme';
import { appStore } from '/@/store/modules/app'; import { appStore } from '/@/store/modules/app';
import { useNetWork } from '/@/hooks/web/useNetWork';
import { useRouter } from 'vue-router';
import { PageEnum } from '/@/enums/pageEnum';
import { useTimeout } from '/@/hooks/core/useTimeout';
import { ExceptionEnum } from '/@/enums/exceptionEnum';
let app: App; let app: App;
export function setApp(_app: App): void { export function setApp(_app: App): void {
@ -84,28 +79,3 @@ export function useConfigProvider() {
transformCellText, transformCellText,
}; };
} }
// Initialize network monitoring
export function useListenerNetWork() {
const { listenNetWork } = appStore.getProjectConfig;
if (!listenNetWork) return;
const { replace } = useRouter();
// Check network status
useNetWork({
onLineFn: () => {
replace(PageEnum.BASE_HOME).then(() => {
useTimeout(() => {
appStore.commitPageLoadingState(false);
}, 200);
});
},
offLineFn: () => {
replace({
path: PageEnum.ERROR_PAGE,
query: {
status: String(ExceptionEnum.NET_WORK_ERROR),
},
});
},
});
}

51
src/utils/animation.ts Normal file
View File

@ -0,0 +1,51 @@
import { isServer } from '/@/utils/is';
let lastTime = 0;
const prefixes = 'webkit moz ms o'.split(' ');
let requestAnimationFrame: typeof window.requestAnimationFrame;
let cancelAnimationFrame: typeof window.cancelAnimationFrame;
(() => {
const NO_LOOP: any = () => {};
const getWindowFrame = (name: string) => {
return name as any;
};
if (isServer) {
requestAnimationFrame = cancelAnimationFrame = NO_LOOP;
} else {
requestAnimationFrame = window.requestAnimationFrame;
cancelAnimationFrame = window.cancelAnimationFrame;
let prefix;
for (let i = 0; i < prefixes.length; i++) {
if (requestAnimationFrame && cancelAnimationFrame) {
break;
}
prefix = prefixes[i];
requestAnimationFrame =
requestAnimationFrame || window[getWindowFrame(prefix + 'RequestAnimationFrame')];
cancelAnimationFrame =
cancelAnimationFrame ||
window[getWindowFrame(prefix + 'CancelAnimationFrame')] ||
window[getWindowFrame(prefix + 'CancelRequestAnimationFrame')];
}
// If the current browser does not support requestAnimationFrame and cancelAnimationFrame, it will fall back to setTimeout
if (!requestAnimationFrame || !cancelAnimationFrame) {
requestAnimationFrame = function (callback: Fn) {
const currTime = new Date().getTime();
const timeToCall = Math.max(0, 16 - (currTime - lastTime));
const id = window.setTimeout(() => {
/* eslint-disable-next-line */
callback(currTime + timeToCall);
}, timeToCall);
lastTime = currTime + timeToCall;
return id;
};
cancelAnimationFrame = function (id: number) {
window.clearTimeout(id);
};
}
}
})();
export { requestAnimationFrame, cancelAnimationFrame };

View File

@ -10,7 +10,7 @@
import { Spin } from 'ant-design-vue'; import { Spin } from 'ant-design-vue';
import { getViewportOffset } from '/@/utils/domUtils'; import { getViewportOffset } from '/@/utils/domUtils';
import { useWindowSizeFn } from '/@/hooks/event/useWindowSize'; import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
export default defineComponent({ export default defineComponent({
name: 'IFrame', name: 'IFrame',

View File

@ -1725,6 +1725,21 @@
vscode-languageserver-textdocument "^1.0.1" vscode-languageserver-textdocument "^1.0.1"
vscode-uri "^2.1.2" vscode-uri "^2.1.2"
"@vueuse/core@^4.0.0-beta.40":
version "4.0.0-beta.40"
resolved "https://registry.npmjs.org/@vueuse/core/-/core-4.0.0-beta.40.tgz#7efdc15c1b994647dff7ae65c0ca573d96ce9b28"
integrity sha512-FOTOUrXAAp0NOmy8hMlP1HpUhnB8LeRJZDOEUl/A9gKMDwWvPTEvxKsDAIzSa4s7I0MapVzfeP3soNCNfl9+vQ==
dependencies:
"@vueuse/shared" "4.0.0-beta.40"
vue-demi latest
"@vueuse/shared@4.0.0-beta.40":
version "4.0.0-beta.40"
resolved "https://registry.npmjs.org/@vueuse/shared/-/shared-4.0.0-beta.40.tgz#76e9b52228159e7ec88df2c8f4eea8fce1a42ec3"
integrity sha512-Ay71viUTXs0XX2hQ04kEExhpsCrw3KankBMP7euorsPjuQmIZjUA4NNOb45UAudg+uF5HXLpgWLvwb4cMOLHnQ==
dependencies:
vue-demi latest
JSONStream@^1.0.4: JSONStream@^1.0.4:
version "1.3.5" version "1.3.5"
resolved "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" resolved "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
@ -8233,6 +8248,11 @@ vscode-uri@^2.1.2:
resolved "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.1.2.tgz#c8d40de93eb57af31f3c715dd650e2ca2c096f1c" resolved "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.1.2.tgz#c8d40de93eb57af31f3c715dd650e2ca2c096f1c"
integrity sha512-8TEXQxlldWAuIODdukIb+TR5s+9Ds40eSJrw+1iDDA9IFORPjMELarNQE3myz5XIkWWpdprmJjm1/SxMlWOC8A== integrity sha512-8TEXQxlldWAuIODdukIb+TR5s+9Ds40eSJrw+1iDDA9IFORPjMELarNQE3myz5XIkWWpdprmJjm1/SxMlWOC8A==
vue-demi@latest:
version "0.4.3"
resolved "https://registry.npmjs.org/vue-demi/-/vue-demi-0.4.3.tgz#6aaa9b52f02c32b4f9d4d11f02a1ae71031453c3"
integrity sha512-1DzLcZgHC9ZyFEYR4qZ83TdS1u9DglG8XVesBXqtbbmqFuO7sb8KG36kMfZCszieAweRDwAAVSAzjmEMG0+WwA==
vue-eslint-parser@^7.1.1: vue-eslint-parser@^7.1.1:
version "7.1.1" version "7.1.1"
resolved "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.1.1.tgz#c43c1c715ff50778b9a7e9a4e16921185f3425d3" resolved "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.1.1.tgz#c43c1c715ff50778b9a7e9a4e16921185f3425d3"