im 管理后台

This commit is contained in:
richjohnrambo
2026-08-11 23:13:51 +08:00
parent 0397fe5f22
commit 331a55751d
1407 changed files with 162069 additions and 2 deletions
+8
View File
@@ -0,0 +1,8 @@
import useContext from './useContext';
import useBoolean from './useBoolean';
import useLoading from './useLoading';
import useLoadingEmpty from './useLoadingEmpty';
import useSendCode from './useSendCode';
import useSorter from './useSorter';
export { useContext, useBoolean, useLoading, useLoadingEmpty, useSendCode, useSorter };
+26
View File
@@ -0,0 +1,26 @@
import { ref } from 'vue';
export default function useBoolean(initValue = false) {
const bool = ref(initValue);
function setBool(value: boolean) {
bool.value = value;
}
function setTrue() {
setBool(true);
}
function setFalse() {
setBool(false);
}
function toggle() {
setBool(!bool.value);
}
return {
bool,
setBool,
setTrue,
setFalse,
toggle,
};
}
+20
View File
@@ -0,0 +1,20 @@
import { inject, provide } from 'vue';
import type { InjectionKey } from 'vue';
export default function useContext<T>(contextName = 'context') {
const injectKey: InjectionKey<T> = Symbol(contextName);
function useProvide(context: T) {
provide(injectKey, context);
return context;
}
function useInject() {
return inject(injectKey) as T;
}
return {
useProvide,
useInject,
};
}
+54
View File
@@ -0,0 +1,54 @@
import { computed, onScopeDispose, ref } from 'vue';
import useBoolean from './useBoolean';
/**
* 倒计时
* @param second - 倒计时的时间(s)
*/
export default function useCountDown(second: number) {
if (second <= 0 && second % 1 !== 0) {
throw new Error('倒计时的时间应该为一个正整数!');
}
const { bool: isComplete, setTrue, setFalse } = useBoolean(false);
const counts = ref(0);
const isCounting = computed(() => Boolean(counts.value));
let intervalId: any;
/**
* 开始计时
* @param updateSecond - 更改初时传入的倒计时时间
*/
function start(updateSecond: number = second) {
if (!counts.value) {
setFalse();
counts.value = updateSecond;
intervalId = setInterval(() => {
counts.value -= 1;
if (counts.value <= 0) {
clearInterval(intervalId);
setTrue();
}
}, 1000);
}
}
/**
* 停止计时
*/
function stop() {
intervalId = clearInterval(intervalId);
counts.value = 0;
}
onScopeDispose(stop);
return {
counts,
isCounting,
start,
stop,
isComplete,
};
}
+11
View File
@@ -0,0 +1,11 @@
import useBoolean from './useBoolean';
export default function useLoading(initValue = false) {
const { bool: loading, setTrue: startLoading, setFalse: endLoading } = useBoolean(initValue);
return {
loading,
startLoading,
endLoading,
};
}
+14
View File
@@ -0,0 +1,14 @@
import useBoolean from './useBoolean';
export default function useLoadingEmpty(initLoading = false, initEmpty = false) {
const { bool: loading, setTrue: startLoading, setFalse: endLoading } = useBoolean(initLoading);
const { bool: empty, setBool: setEmpty } = useBoolean(initEmpty);
return {
loading,
startLoading,
endLoading,
empty,
setEmpty,
};
}
+43
View File
@@ -0,0 +1,43 @@
import { computed } from 'vue';
import useLoading from './useLoading';
import useCountDown from './useCountDown';
export default function useSmsCode() {
const { loading, startLoading, endLoading } = useLoading();
const { counts, start, isCounting } = useCountDown(60);
const initLabel = '获取验证码';
const countingLabel = (second: number) => `重新获取(${second})`;
const sendLabel = computed(() => {
let text = initLabel;
if (loading.value) {
text = '';
}
if (isCounting.value) {
text = countingLabel(counts.value);
}
return text;
});
/**
* 激活发送
*/
function activateSend(request: Promise<any>) {
startLoading();
request
.then((_res) => {
window['$message']?.success('验证码发送成功!');
start();
})
.finally(() => {
endLoading();
});
}
return {
sendLabel,
start,
isCounting,
activateSend,
loading,
};
}
+40
View File
@@ -0,0 +1,40 @@
import { ref } from 'vue';
import { DataTableSortState } from 'naive-ui';
import { SorterMultiple } from 'naive-ui/es/data-table/src/interface';
export default function useSorter(callback: Function = function () {}) {
const sortStatesRef = ref<DataTableSortState[]>([]);
function updateSorter(sorter: DataTableSortState | DataTableSortState[] | null) {
if (sorter === null) {
// 默认排序
sortStatesRef.value = [];
} else if (Array.isArray(sorter)) {
// 多字段排序
sortStatesRef.value = filterMultipleSorters(sorter);
} else {
// 单字段排序
sortStatesRef.value = [];
sortStatesRef.value.push(sorter);
}
callback();
}
// 多字段排序,将不参与排序的字段过滤掉,再根据设置的优先级对排序器重新排列
function filterMultipleSorters(sorters: DataTableSortState[]): DataTableSortState[] {
const filter = sorters.filter((item) => item.order !== false);
return filter.sort((a, b) => {
if (typeof a.sorter === 'object' && typeof b.sorter === 'object') {
const aSorter = a.sorter as SorterMultiple;
const bSorter = b.sorter as SorterMultiple;
return bSorter.multiple - aSorter.multiple;
}
return 0;
});
}
return {
updateSorter,
sortStatesRef,
};
}
+47
View File
@@ -0,0 +1,47 @@
import { ref, watch } from 'vue';
import { tryOnUnmounted } from '@vueuse/core';
import { isFunction } from '@/utils/is';
export function useTimeoutFn(handle: Fn<any>, wait: number, native = false) {
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) {
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 };
}
+89
View File
@@ -0,0 +1,89 @@
import { ref, computed, ComputedRef, unref } from 'vue';
import { useEventListener } from '@/hooks/event/useEventListener';
import { screenMap, sizeEnum, screenEnum } from '@/enums/breakpointEnum';
let globalScreenRef: ComputedRef<sizeEnum | undefined>;
let globalWidthRef: ComputedRef<number>;
let globalRealWidthRef: ComputedRef<number>;
export interface CreateCallbackParams {
screen: ComputedRef<sizeEnum | undefined>;
width: ComputedRef<number>;
realWidth: ComputedRef<number>;
screenEnum: typeof screenEnum;
screenMap: Map<sizeEnum, number>;
sizeEnum: typeof sizeEnum;
}
export function useBreakpoint() {
return {
screenRef: computed(() => unref(globalScreenRef)),
widthRef: globalWidthRef,
screenEnum,
realWidthRef: globalRealWidthRef,
};
}
// Just call it once
export function createBreakpointListen(fn?: (opt: CreateCallbackParams) => void) {
const screenRef = ref<sizeEnum>(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)!)!);
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,
};
}
+62
View File
@@ -0,0 +1,62 @@
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<Element | undefined> | 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 } {
/* eslint-disable-next-line */
let remove: RemoveEventFn = () => {
};
const isAddRef = ref(false);
if (el) {
const element: Ref<Element> = ref(el as Element);
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 };
}
+36
View File
@@ -0,0 +1,36 @@
import { tryOnMounted, tryOnUnmounted } from '@vueuse/core';
import { useDebounceFn } from '@vueuse/core';
interface WindowSizeOptions {
once?: boolean;
immediate?: boolean;
listenerOptions?: AddEventListenerOptions | boolean;
}
export function useWindowSizeFn<T>(fn: Fn<T>, wait = 150, options?: WindowSizeOptions) {
let handler = () => {
fn();
};
const handleSize = useDebounceFn(handler, wait);
handler = handleSize;
const start = () => {
if (options && options.immediate) {
handler();
}
window.addEventListener('resize', handler);
};
const stop = () => {
window.removeEventListener('resize', handler);
};
tryOnMounted(() => {
start();
});
tryOnUnmounted(() => {
stop();
});
return [start, stop];
}
+3
View File
@@ -0,0 +1,3 @@
import { useAsync } from './use-async';
export { useAsync };
+31
View File
@@ -0,0 +1,31 @@
import type { GlobConfig } from '/#/config';
import { warn } from '@/utils/log';
import { getAppEnvConfig } from '@/utils/env';
export const useGlobSetting = (): Readonly<GlobConfig> => {
const {
VITE_GLOB_APP_TITLE,
VITE_GLOB_API_URL,
VITE_GLOB_APP_SHORT_NAME,
VITE_GLOB_API_URL_PREFIX,
VITE_GLOB_UPLOAD_URL,
VITE_GLOB_IMG_URL,
} = getAppEnvConfig();
if (!/[a-zA-Z\_]*/.test(VITE_GLOB_APP_SHORT_NAME)) {
warn(
`VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.`
);
}
// Take global configuration
const glob: Readonly<GlobConfig> = {
title: VITE_GLOB_APP_TITLE,
apiUrl: VITE_GLOB_API_URL,
shortName: VITE_GLOB_APP_SHORT_NAME,
urlPrefix: VITE_GLOB_API_URL_PREFIX,
uploadUrl: VITE_GLOB_UPLOAD_URL,
imgUrl: VITE_GLOB_IMG_URL,
};
return glob as Readonly<GlobConfig>;
};
+18
View File
@@ -0,0 +1,18 @@
import { computed } from 'vue';
import { useDesignSettingStore } from '@/store/modules/designSetting';
export function useDesignSetting() {
const designStore = useDesignSettingStore();
const getDarkTheme = computed(() => designStore.darkTheme);
const getAppTheme = computed(() => designStore.appTheme);
const getAppThemeList = computed(() => designStore.appThemeList);
return {
getDarkTheme,
getAppTheme,
getAppThemeList,
};
}
@@ -0,0 +1,42 @@
import { computed } from 'vue';
import { useProjectSettingStore } from '@/store/modules/projectSetting';
export function useProjectSetting() {
const projectStore = useProjectSettingStore();
const getNavMode = computed(() => projectStore.navMode);
const getNavTheme = computed(() => projectStore.navTheme);
const getIsMobile = computed(() => projectStore.isMobile);
const getHeaderSetting = computed(() => projectStore.headerSetting);
const getMultiTabsSetting = computed(() => projectStore.multiTabsSetting);
const getMenuSetting = computed(() => projectStore.menuSetting);
const getCrumbsSetting = computed(() => projectStore.crumbsSetting);
const getPermissionMode = computed(() => projectStore.permissionMode);
const getShowFooter = computed(() => projectStore.showFooter);
const getIsPageAnimate = computed(() => projectStore.isPageAnimate);
const getPageAnimateType = computed(() => projectStore.pageAnimateType);
return {
getNavMode,
getNavTheme,
getIsMobile,
getHeaderSetting,
getMultiTabsSetting,
getMenuSetting,
getCrumbsSetting,
getPermissionMode,
getShowFooter,
getIsPageAnimate,
getPageAnimateType,
};
}
+15
View File
@@ -0,0 +1,15 @@
import { isReactive, isRef } from 'vue';
function setLoading(loading, val) {
if (loading != undefined && isRef(loading)) {
loading.value = val;
} else if (loading != undefined && isReactive(loading)) {
loading.loading = val;
}
}
export const useAsync = async (func: Promise<any>, loading: any): Promise<any> => {
setLoading(loading, true);
return await func.finally(() => setLoading(loading, false));
};
+93
View File
@@ -0,0 +1,93 @@
import { computed, onMounted, reactive, toRefs } from 'vue';
interface Battery {
charging: boolean; // 当前电池是否正在充电
chargingTime: number; // 距离充电完毕还需多少秒,如果为0则充电完毕
dischargingTime: number; // 代表距离电池耗电至空且挂起需要多少秒
level: number; // 代表电量的放大等级,这个值在 0.0 至 1.0 之间
[key: string]: any;
}
export const useBattery = () => {
const state = reactive({
battery: {
charging: false,
chargingTime: 0,
dischargingTime: 0,
level: 100,
},
});
// 更新电池使用状态
const updateBattery = (target) => {
for (const key in state.battery) {
state.battery[key] = target[key];
}
state.battery.level = state.battery.level * 100;
};
// 计算电池剩余可用时间
const calcDischargingTime = computed(() => {
const hour = state.battery.dischargingTime / 3600;
const minute = (state.battery.dischargingTime / 60) % 60;
return `${~~hour}小时${~~minute}分钟`;
});
// 计算电池充满剩余时间
const calcChargingTime = computed(() => {
console.log(state.battery);
const hour = state.battery.chargingTime / 3600;
const minute = (state.battery.chargingTime / 60) % 60;
return `${~~hour}小时${~~minute}分钟`;
});
// 电池状态
const batteryStatus = computed(() => {
if (state.battery.charging && state.battery.level >= 100) {
return '已充满';
} else if (state.battery.charging) {
return '充电中';
} else {
return '已断开电源';
}
});
onMounted(async () => {
const BatteryManager: Battery = await (window.navigator as any).getBattery();
updateBattery(BatteryManager);
// 电池充电状态更新时被调用
BatteryManager.onchargingchange = ({ target }) => {
updateBattery(target);
};
// 电池充电时间更新时被调用
BatteryManager.onchargingtimechange = ({ target }) => {
updateBattery(target);
};
// 电池断开充电时间更新时被调用
BatteryManager.ondischargingtimechange = ({ target }) => {
updateBattery(target);
};
// 电池电量更新时被调用
BatteryManager.onlevelchange = ({ target }) => {
updateBattery(target);
};
// new Intl.DateTimeFormat('zh', {
// year: 'numeric',
// month: '2-digit',
// day: '2-digit',
// hour: '2-digit',
// minute: '2-digit',
// second: '2-digit',
// hour12: false
// }).format(new Date())
});
return {
...toRefs(state),
batteryStatus,
calcDischargingTime,
calcChargingTime,
};
};
+49
View File
@@ -0,0 +1,49 @@
import { ref, Ref } from "vue";
type Resettable =
/**
* 基础形式为一个元组,包含:
* 1. `Ref<boolean>`:状态引用,可以直接访问和修改状态值
* 2. `() => void`:切换真假
* 3. `(value: boolean) => void`:设置状态
* 4.`() => void`:设置为`true`方法
* 5.`() => void`:设置为`false`方法
*/
[Ref<boolean>, () => void, () => void] & /**
* 同时扩展为一个对象形式,提供以下属性和方法:
* - `loading: Ref<T>`:状态引用,与元组中的第一个元素相同
* - `startLoading: () => void`:开始加载方法,与元组中的第二个元素相同
* - `endLoading: () => void`:结束加载方法,与元组中的第三个元素相同
*/ {
/** 加载状态引用 */
bool: Ref<boolean>;
toggle: () => void;
setBool: (value: boolean) => void;
setTrue: () => void;
setFalse: () => void;
};
export default function useBoolean(initValue = false): Resettable {
const bool = ref(initValue);
function setBool(value: boolean) {
bool.value = value;
}
function setTrue() {
setBool(true);
}
function setFalse() {
setBool(false);
}
function toggle() {
setBool(!bool.value);
}
// 返回带加载状态引用、开始加载和结束加载方法的扩展数组
return (Object.assign([bool, toggle, setBool, setTrue, setFalse], {
bool,
toggle,
setBool,
setTrue,
setFalse,
}) as unknown) as Resettable;
}
+21
View File
@@ -0,0 +1,21 @@
import { onMounted } from 'vue';
export default function userCreateScript(src: string) {
const createScriptPromise = new Promise((resolve, reject) => {
onMounted(() => {
const script = document.createElement('script');
script.type = 'text/javascript';
script.onload = () => {
resolve('');
};
script.onerror = (error) => {
reject(error);
};
script.src = src;
document.head.appendChild(script);
});
});
return {
createScriptPromise,
};
}
+23
View File
@@ -0,0 +1,23 @@
import { ref, onMounted, onUnmounted } from 'vue';
import { debounce } from 'lodash-es';
/**
* description: 获取页面宽度
*/
export function useDomWidth() {
const domWidth = ref(window.innerWidth);
function resize() {
domWidth.value = document.body.clientWidth;
}
onMounted(() => {
window.addEventListener('resize', debounce(resize, 80));
});
onUnmounted(() => {
window.removeEventListener('resize', resize);
});
return domWidth;
}
+8
View File
@@ -0,0 +1,8 @@
import * as echarts from 'echarts';
export default function useEcharts(dom: HTMLElement, theme?: string) {
let instance = echarts.getInstanceByDom(dom);
if (!instance) {
instance = echarts.init(dom, theme);
}
return instance;
}
+30
View File
@@ -0,0 +1,30 @@
import { ref, onMounted, onUnmounted } from 'vue';
/**
* @description 用户网络是否可用
* */
export function useOnline() {
const online = ref(true);
const showStatus = (val) => {
online.value = typeof val == 'boolean' ? val : val.target.online;
};
// 在页面加载后,设置正确的网络状态
navigator.onLine ? showStatus(true) : showStatus(false);
onMounted(() => {
// 开始监听网络状态的变化
window.addEventListener('online', showStatus);
window.addEventListener('offline', showStatus);
});
onUnmounted(() => {
// 移除监听网络状态的变化
window.removeEventListener('online', showStatus);
window.removeEventListener('offline', showStatus);
});
return { online };
}
+44
View File
@@ -0,0 +1,44 @@
import { ref, onMounted, onUnmounted } from 'vue';
/**
* @description 获取本地时间
*/
export function useTime() {
let timer; // 定时器
const year = ref(0); // 年份
const month = ref(0); // 月份
const week = ref(''); // 星期几
const day = ref(0); // 天数
const hour = ref<number | string>(0); // 小时
const minute = ref<number | string>(0); // 分钟
const second = ref(0); // 秒
// 更新时间
const updateTime = () => {
const date = new Date();
year.value = date.getFullYear();
month.value = date.getMonth() + 1;
week.value = '日一二三四五六'.charAt(date.getDay());
day.value = date.getDate();
hour.value =
(date.getHours() + '').padStart(2, '0') ||
new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getHours());
minute.value =
(date.getMinutes() + '').padStart(2, '0') ||
new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getMinutes());
second.value = date.getSeconds();
};
updateTime();
onMounted(() => {
clearInterval(timer);
timer = setInterval(() => updateTime(), 1000);
});
onUnmounted(() => {
clearInterval(timer);
});
return { month, day, hour, minute, second, week };
}
+117
View File
@@ -0,0 +1,117 @@
import type { EChartsOption } from 'echarts';
import type { Ref } from 'vue';
import { useTimeoutFn } from '@/hooks/core/useTimeout';
import { tryOnUnmounted } from '@vueuse/core';
import { unref, nextTick, watch, computed, ref } from 'vue';
import { useDebounceFn } from '@vueuse/core';
import { useEventListener } from '@/hooks/event/useEventListener';
import { useBreakpoint } from '@/hooks/event/useBreakpoint';
import echarts from '@/utils/lib/echarts';
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
export function useECharts(
elRef: Ref<HTMLDivElement>,
theme: 'light' | 'dark' | 'default' = 'default'
) {
const { getDarkTheme: getSysDarkTheme } = useDesignSetting();
const getDarkTheme = computed(() => {
const sysTheme: string = getSysDarkTheme.value ? 'dark' : 'light';
return theme === 'default' ? sysTheme : theme;
});
let chartInstance: echarts.ECharts | null = null;
let resizeFn: Fn = resize;
const cacheOptions = ref({});
let removeResizeFn: Fn = () => {};
resizeFn = useDebounceFn(resize, 200);
const getOptions = computed((): EChartsOption => {
if (getDarkTheme.value !== 'dark') {
return cacheOptions.value;
}
return {
backgroundColor: 'transparent',
...cacheOptions.value,
};
});
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: EChartsOption, clear = true) {
cacheOptions.value = options;
if (unref(elRef)?.offsetHeight === 0) {
useTimeoutFn(() => {
setOptions(unref(getOptions));
}, 30);
return;
}
nextTick(() => {
useTimeoutFn(() => {
if (!chartInstance) {
initCharts(getDarkTheme.value as 'default');
if (!chartInstance) return;
}
clear && chartInstance?.clear();
chartInstance?.setOption(unref(getOptions));
}, 30);
});
}
function resize() {
chartInstance?.resize();
}
watch(
() => getDarkTheme.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(getDarkTheme.value as 'default');
}
return chartInstance;
}
return {
setOptions,
resize,
echarts,
getInstance,
};
}
+62
View File
@@ -0,0 +1,62 @@
import type { RouteLocationRaw, Router } from 'vue-router';
import { PageEnum } from '@/enums/pageEnum';
import { RedirectName } from '@/router/constant';
import { useRouter } from 'vue-router';
import { isString } from '@/utils/is';
import { unref } from 'vue';
export type RouteLocationRawEx = Omit<RouteLocationRaw, 'path'> & { path: PageEnum };
function handleError(e: Error) {
console.error(e);
}
/**
* 页面切换
*/
export function useGo(_router?: Router) {
let router;
if (!_router) {
router = useRouter();
}
const { push, replace } = _router || router;
function go(opt: PageEnum | RouteLocationRawEx | string = PageEnum.BASE_HOME, isReplace = false) {
if (!opt) {
return;
}
if (isString(opt)) {
isReplace ? replace(opt).catch(handleError) : push(opt).catch(handleError);
} else {
const o = opt as RouteLocationRaw;
isReplace ? replace(o).catch(handleError) : push(o).catch(handleError);
}
}
return go;
}
/**
* 重做当前页面
*/
export const useRedo = (_router?: Router) => {
const { push, currentRoute } = _router || useRouter();
const { query, params = {}, name, fullPath } = unref(currentRoute.value);
function redo(): Promise<boolean> {
return new Promise((resolve) => {
if (name === RedirectName) {
resolve(false);
return;
}
if (name && Object.keys(params).length > 0) {
params['_redirect_type'] = 'name';
params['path'] = String(name);
} else {
params['_redirect_type'] = 'path';
params['path'] = fullPath;
}
push({ name: RedirectName, params, query }).then(() => resolve(true));
});
}
return redo;
};
+52
View File
@@ -0,0 +1,52 @@
import { useUserStore } from '@/store/modules/user';
export function usePermission() {
const userStore = useUserStore();
/**
* 检查权限
* @param accesses
*/
function _somePermissions(accesses: string[]) {
return userStore.getPermissions.some((item) => {
// @ts-ignore
return accesses.includes(item);
});
}
/**
* 判断是否存在权限
* 可用于 v-if 显示逻辑
* */
function hasPermission(accesses: string[]): boolean {
if (!accesses || !accesses.length) return true;
return _somePermissions(accesses);
}
/**
* 是否包含指定的所有权限
* @param accesses
*/
function hasEveryPermission(accesses: string[]): boolean {
const permissionsList = userStore.getPermissions;
if (Array.isArray(accesses)) {
return permissionsList.every((access: any) => accesses.includes(access));
}
throw new Error(`[hasEveryPermission]: ${accesses} should be a array !`);
}
/**
* 是否包含其中某个权限
* @param accesses
* @param accessMap
*/
function hasSomePermission(accesses: string[]): boolean {
const permissionsList = userStore.getPermissions;
if (Array.isArray(accesses)) {
return permissionsList.some((access: any) => accesses.includes(access));
}
throw new Error(`[hasSomePermission]: ${accesses} should be a array !`);
}
return { hasPermission, hasEveryPermission, hasSomePermission };
}