From a496b6d24c37eef17bab091235a131ad6f708ee5 Mon Sep 17 00:00:00 2001 From: Qiuxia Fan Date: Mon, 10 Jan 2022 17:53:53 +0800 Subject: [PATCH] feat: add hooks and utils --- src/hooks/data.ts | 44 +++++++++++ src/hooks/useBreakpoint.ts | 106 +++++++++++++++++++++++++ src/hooks/useEcharts.ts | 144 ++++++++++++++++++++++++++++++++++ src/hooks/useEventListener.ts | 76 ++++++++++++++++++ src/hooks/useTimeout.ts | 65 +++++++++++++++ src/types/index.d.ts | 44 +++++++++++ src/utils/echarts.ts | 48 ++++++++++++ src/utils/is.ts | 110 ++++++++++++++++++++++++++ tsconfig.json | 2 +- 9 files changed, 638 insertions(+), 1 deletion(-) create mode 100644 src/hooks/data.ts create mode 100644 src/hooks/useBreakpoint.ts create mode 100644 src/hooks/useEcharts.ts create mode 100644 src/hooks/useEventListener.ts create mode 100644 src/hooks/useTimeout.ts create mode 100644 src/types/index.d.ts create mode 100644 src/utils/echarts.ts create mode 100644 src/utils/is.ts diff --git a/src/hooks/data.ts b/src/hooks/data.ts new file mode 100644 index 00000000..17b8a1f4 --- /dev/null +++ b/src/hooks/data.ts @@ -0,0 +1,44 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export enum sizeEnum { + XS = "XS", + SM = "SM", + MD = "MD", + LG = "LG", + XL = "XL", + XXL = "XXL", +} + +export enum screenEnum { + XS = 480, + SM = 576, + MD = 768, + LG = 992, + XL = 1200, + XXL = 1600, +} + +const screenMap = new Map(); + +screenMap.set(sizeEnum.XS, screenEnum.XS); +screenMap.set(sizeEnum.SM, screenEnum.SM); +screenMap.set(sizeEnum.MD, screenEnum.MD); +screenMap.set(sizeEnum.LG, screenEnum.LG); +screenMap.set(sizeEnum.XL, screenEnum.XL); +screenMap.set(sizeEnum.XXL, screenEnum.XXL); + +export { screenMap }; diff --git a/src/hooks/useBreakpoint.ts b/src/hooks/useBreakpoint.ts new file mode 100644 index 00000000..2ed4df76 --- /dev/null +++ b/src/hooks/useBreakpoint.ts @@ -0,0 +1,106 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ref, computed, ComputedRef, unref } from "vue"; +import { useEventListener } from "./useEventListener"; +import { screenMap, sizeEnum, screenEnum } from "./data"; + +let globalScreenRef: ComputedRef; +let globalWidthRef: ComputedRef; +let globalRealWidthRef: ComputedRef; + +export interface CreateCallbackParams { + screen: ComputedRef; + width: ComputedRef; + realWidth: ComputedRef; + screenEnum: typeof screenEnum; + screenMap: Map; + sizeEnum: typeof sizeEnum; +} + +export function useBreakpoint(): any { + return { + screenRef: computed(() => unref(globalScreenRef)), + widthRef: globalWidthRef, + screenEnum, + realWidthRef: globalRealWidthRef, + }; +} + +export function createBreakpointListen( + fn?: (opt: CreateCallbackParams) => void +): any { + const screenRef = ref(sizeEnum.XL || ""); + const realWidthRef = ref(window.innerWidth); + + function getWindowWidth() { + const width = document.body.clientWidth; + const xs = screenMap.get(sizeEnum.XS) || ""; + const sm = screenMap.get(sizeEnum.SM) || ""; + const md = screenMap.get(sizeEnum.MD) || ""; + const lg = screenMap.get(sizeEnum.LG) || ""; + const xl = screenMap.get(sizeEnum.XL) || ""; + if (width < xs) { + screenRef.value = sizeEnum.XS; + } else if (width < sm) { + screenRef.value = sizeEnum.SM; + } else if (width < md) { + screenRef.value = sizeEnum.MD; + } else if (width < lg) { + screenRef.value = sizeEnum.LG; + } else if (width < xl) { + screenRef.value = sizeEnum.XL; + } else { + screenRef.value = sizeEnum.XXL; + } + realWidthRef.value = width; + } + + useEventListener({ + el: window, + name: "resize", + + listener: () => { + getWindowWidth(); + resizeFn(); + }, + // wait: 100, + }); + + getWindowWidth(); + globalScreenRef = computed(() => unref(screenRef)); + globalWidthRef = computed((): number => screenMap.get(unref(screenRef)) || 0); + globalRealWidthRef = computed((): number => unref(realWidthRef)); + + function resizeFn() { + fn?.({ + screen: globalScreenRef, + width: globalWidthRef, + realWidth: globalRealWidthRef, + screenEnum, + screenMap, + sizeEnum, + }); + } + + resizeFn(); + return { + screenRef: globalScreenRef, + screenEnum, + widthRef: globalWidthRef, + realWidthRef: globalRealWidthRef, + }; +} diff --git a/src/hooks/useEcharts.ts b/src/hooks/useEcharts.ts new file mode 100644 index 00000000..7e661d9c --- /dev/null +++ b/src/hooks/useEcharts.ts @@ -0,0 +1,144 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { BarSeriesOption, LineSeriesOption } from "echarts/charts"; +import { + TitleComponentOption, + TooltipComponentOption, + GridComponentOption, + DatasetComponentOption, +} from "echarts/components"; +import type { Ref } from "vue"; +import { useTimeoutFn } from "./useTimeout"; +import { tryOnUnmounted } from "@vueuse/core"; +import { unref, nextTick, watch, computed, ref } from "vue"; +import { useDebounceFn } from "@vueuse/core"; +import { useEventListener } from "./useEventListener"; +import { useBreakpoint } from "./useBreakpoint"; +import echarts from "@/utils/echarts"; + +export type ECOption = echarts.ComposeOption< + | BarSeriesOption + | LineSeriesOption + | TitleComponentOption + | TooltipComponentOption + | GridComponentOption + | DatasetComponentOption +>; + +export function useECharts( + elRef: Ref, + theme: "light" | "dark" | "default" = "default" +): any { + const getDarkMode = computed(() => { + return theme === "default" ? "light" : theme; + }); + let chartInstance: echarts.ECharts | null = null; + let resizeFn: Fn = resize; + const cacheOptions = ref({}) as Ref; + let removeResizeFn: Fn = () => ({}); + + resizeFn = useDebounceFn(resize, 200); + + const getOptions = computed(() => { + if (getDarkMode.value !== "dark") { + return cacheOptions.value as ECOption; + } + return { + backgroundColor: "transparent", + ...cacheOptions.value, + } as ECOption; + }); + + function initCharts(t = theme) { + const el = unref(elRef); + if (!el || !unref(el)) { + return; + } + + chartInstance = echarts.init(el, t); + const { removeEvent } = useEventListener({ + el: window, + name: "resize", + listener: resizeFn, + }); + removeResizeFn = removeEvent; + const { widthRef, screenEnum } = useBreakpoint(); + if (unref(widthRef) <= screenEnum.MD || el.offsetHeight === 0) { + useTimeoutFn(() => { + resizeFn(); + }, 30); + } + } + + function setOptions(options: ECOption, clear = true) { + cacheOptions.value = options; + if (unref(elRef)?.offsetHeight === 0) { + useTimeoutFn(() => { + setOptions(unref(getOptions)); + }, 30); + return; + } + nextTick(() => { + useTimeoutFn(() => { + if (!chartInstance) { + initCharts(getDarkMode.value as "default"); + + if (!chartInstance) return; + } + clear && chartInstance?.clear(); + + chartInstance?.setOption(unref(getOptions)); + }, 30); + }); + } + + function resize() { + chartInstance?.resize(); + } + + watch( + () => getDarkMode.value, + (theme) => { + if (chartInstance) { + chartInstance.dispose(); + initCharts(theme as "default"); + setOptions(cacheOptions.value); + } + } + ); + + tryOnUnmounted(() => { + if (!chartInstance) return; + removeResizeFn(); + chartInstance.dispose(); + chartInstance = null; + }); + + function getInstance(): echarts.ECharts | null { + if (!chartInstance) { + initCharts(getDarkMode.value as "default"); + } + return chartInstance; + } + + return { + setOptions, + resize, + echarts, + getInstance, + }; +} diff --git a/src/hooks/useEventListener.ts b/src/hooks/useEventListener.ts new file mode 100644 index 00000000..fd219f9a --- /dev/null +++ b/src/hooks/useEventListener.ts @@ -0,0 +1,76 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import type { Ref } from "vue"; +import { ref, watch, unref } from "vue"; +import { useThrottleFn, useDebounceFn } from "@vueuse/core"; + +export type RemoveEventFn = () => void; +export interface UseEventParams { + el?: Element | Ref | Window | any; + name: string; + listener: EventListener; + options?: boolean | AddEventListenerOptions; + autoRemove?: boolean; + isDebounce?: boolean; + wait?: number; +} +export function useEventListener({ + el = window, + name, + listener, + options, + autoRemove = true, + isDebounce = true, + wait = 80, +}: UseEventParams): { removeEvent: RemoveEventFn } { + let remove: RemoveEventFn = () => ({}); + const isAddRef = ref(false); + + if (el) { + const element = ref(el as Element) as Ref; + + const handler = isDebounce + ? useDebounceFn(listener, wait) + : useThrottleFn(listener, wait); + const realHandler = wait ? handler : listener; + const removeEventListener = (e: Element) => { + isAddRef.value = true; + e.removeEventListener(name, realHandler, options); + }; + const addEventListener = (e: Element) => + e.addEventListener(name, realHandler, options); + + const removeWatch = watch( + element, + (v, _ov, cleanUp) => { + if (v) { + !unref(isAddRef) && addEventListener(v); + cleanUp(() => { + autoRemove && removeEventListener(v); + }); + } + }, + { immediate: true } + ); + + remove = () => { + removeEventListener(element.value); + removeWatch(); + }; + } + return { removeEvent: remove }; +} diff --git a/src/hooks/useTimeout.ts b/src/hooks/useTimeout.ts new file mode 100644 index 00000000..3f8503a1 --- /dev/null +++ b/src/hooks/useTimeout.ts @@ -0,0 +1,65 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ref, watch } from "vue"; +import { tryOnUnmounted } from "@vueuse/core"; +import { isFunction } from "@/utils/is"; + +export function useTimeoutFn( + handle: Fn, + wait: number, + native = false +): any { + if (!isFunction(handle)) { + throw new Error("handle is not Function!"); + } + + const { readyRef, stop, start } = useTimeoutRef(wait); + if (native) { + handle(); + } else { + watch( + readyRef, + (maturity) => { + maturity && handle(); + }, + { immediate: false } + ); + } + return { readyRef, stop, start }; +} + +export function useTimeoutRef(wait: number): any { + const readyRef = ref(false); + + let timer: TimeoutHandle; + function stop(): void { + readyRef.value = false; + timer && window.clearTimeout(timer); + } + function start(): void { + stop(); + timer = setTimeout(() => { + readyRef.value = true; + }, wait); + } + + start(); + + tryOnUnmounted(stop); + + return { readyRef, stop, start }; +} diff --git a/src/types/index.d.ts b/src/types/index.d.ts new file mode 100644 index 00000000..23de95bc --- /dev/null +++ b/src/types/index.d.ts @@ -0,0 +1,44 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +declare interface Fn { + (...arg: T[]): R; +} + +declare interface PromiseFn { + (...arg: T[]): Promise; +} + +declare type RefType = T | null; + +declare type LabelValueOptions = { + label: string; + value: any; + [key: string]: string | number | boolean; +}[]; + +declare type EmitType = (event: string, ...args: any[]) => void; + +declare type TargetContext = "_self" | "_blank"; + +declare interface ComponentElRef { + $el: T; +} + +declare type ComponentRef = + ComponentElRef | null; + +declare type ElRef = Nullable; diff --git a/src/utils/echarts.ts b/src/utils/echarts.ts new file mode 100644 index 00000000..7795e094 --- /dev/null +++ b/src/utils/echarts.ts @@ -0,0 +1,48 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import * as echarts from "echarts/core"; + +import { BarChart, LineChart, PieChart, HeatmapChart } from "echarts/charts"; + +import { + TitleComponent, + TooltipComponent, + GridComponent, + LegendComponent, + DataZoomComponent, + VisualMapComponent, + TimelineComponent, +} from "echarts/components"; + +import { SVGRenderer } from "echarts/renderers"; + +echarts.use([ + LegendComponent, + TitleComponent, + TooltipComponent, + GridComponent, + BarChart, + LineChart, + PieChart, + HeatmapChart, + SVGRenderer, + DataZoomComponent, + VisualMapComponent, + TimelineComponent, +]); + +export default echarts; diff --git a/src/utils/is.ts b/src/utils/is.ts new file mode 100644 index 00000000..e55b1925 --- /dev/null +++ b/src/utils/is.ts @@ -0,0 +1,110 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const toString = Object.prototype.toString; + +export function is(val: unknown, type: string): boolean { + return toString.call(val) === `[object ${type}]`; +} + +export function isDef(val?: T): val is T { + return typeof val !== "undefined"; +} + +export function isUnDef(val?: T): val is T { + return !isDef(val); +} + +export function isObject(val: unknown): val is Record { + return val !== null && is(val, "Object"); +} + +export function isEmpty(val: any): val is T { + if (isArray(val) || isString(val)) { + return val.length === 0; + } + + if (val instanceof Map || val instanceof Set) { + return val.size === 0; + } + + if (isObject(val)) { + return Object.keys(val).length === 0; + } + + return false; +} + +export function isDate(val: unknown): val is Date { + return is(val, "Date"); +} + +export function isNull(val: unknown): val is null { + return val === null; +} + +export function isNullAndUnDef(val: unknown): val is null | undefined { + return isUnDef(val) && isNull(val); +} + +export function isNullOrUnDef(val: unknown): val is null | undefined { + return isUnDef(val) || isNull(val); +} + +export function isNumber(val: unknown): val is number { + return is(val, "Number"); +} + +export function isPromise(val: unknown): val is Promise { + return ( + is(val, "Promise") && + isObject(val) && + isFunction(val.then) && + isFunction(val.catch) + ); +} + +export function isString(val: unknown): val is string { + return is(val, "String"); +} + +export function isFunction(val: unknown): boolean { + return typeof val === "function"; +} + +export function isBoolean(val: unknown): val is boolean { + return is(val, "Boolean"); +} + +export function isRegExp(val: unknown): val is RegExp { + return is(val, "RegExp"); +} + +export function isArray(val: any): boolean { + return val && Array.isArray(val); +} + +export function isWindow(val: unknown): val is Window { + return typeof window !== "undefined" && is(val, "Window"); +} + +export function isElement(val: unknown): val is Element { + return isObject(val) && !!val.tagName; +} + +export function isMap(val: unknown): val is Map { + return is(val, "Map"); +} diff --git a/tsconfig.json b/tsconfig.json index 3587ae2e..045b4baf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -34,7 +34,7 @@ "webpack-env", "jest" ], - "typeRoots": ["./node_modules/@types/", "./types"], + "typeRoots": ["./node_modules/@types/"], "paths": { "@/*": [ "src/*"