---初始化后台管理web页面项目
This commit is contained in:
61
src/hooks/component/useFormItem.ts
Normal file
61
src/hooks/component/useFormItem.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import type { UnwrapRef, Ref, WritableComputedRef, DeepReadonly } from 'vue';
|
||||
import {
|
||||
reactive,
|
||||
readonly,
|
||||
computed,
|
||||
getCurrentInstance,
|
||||
watchEffect,
|
||||
unref,
|
||||
// nextTick,
|
||||
toRaw,
|
||||
} from 'vue';
|
||||
|
||||
import { isEqual } from 'lodash-es';
|
||||
|
||||
export function useRuleFormItem<T extends Recordable, K extends keyof T, V = UnwrapRef<T[K]>>(
|
||||
props: T,
|
||||
key?: K,
|
||||
changeEvent?,
|
||||
emitData?: Ref<any[]>,
|
||||
): [WritableComputedRef<V>, (val: V) => void, DeepReadonly<V>];
|
||||
|
||||
export function useRuleFormItem<T extends Recordable>(
|
||||
props: T,
|
||||
key: keyof T = 'value',
|
||||
changeEvent = 'change',
|
||||
emitData?: Ref<any[]>,
|
||||
) {
|
||||
const instance = getCurrentInstance();
|
||||
const emit = instance?.emit;
|
||||
|
||||
const innerState = reactive({
|
||||
value: props[key],
|
||||
});
|
||||
|
||||
const defaultState = readonly(innerState);
|
||||
|
||||
const setState = (val: UnwrapRef<T[keyof T]>): void => {
|
||||
innerState.value = val as T[keyof T];
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
innerState.value = props[key];
|
||||
});
|
||||
|
||||
const state: any = computed({
|
||||
get() {
|
||||
return innerState.value;
|
||||
},
|
||||
set(value) {
|
||||
if (isEqual(value, defaultState.value)) return;
|
||||
|
||||
innerState.value = value as T[keyof T];
|
||||
// nextTick(() => {
|
||||
// emit?.(changeEvent, value, ...(toRaw(unref(emitData)) || []));
|
||||
// });
|
||||
emit?.(changeEvent, value, ...(toRaw(unref(emitData)) || []));
|
||||
},
|
||||
});
|
||||
|
||||
return [state, setState, defaultState];
|
||||
}
|
||||
18
src/hooks/component/usePageContext.ts
Normal file
18
src/hooks/component/usePageContext.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import type { InjectionKey, ComputedRef, Ref } from 'vue';
|
||||
import { createContext, useContext } from '/@/hooks/core/useContext';
|
||||
|
||||
export interface PageContextProps {
|
||||
contentHeight: ComputedRef<number>;
|
||||
pageHeight: Ref<number>;
|
||||
setPageHeight: (height: number) => Promise<void>;
|
||||
}
|
||||
|
||||
const key: InjectionKey<PageContextProps> = Symbol();
|
||||
|
||||
export function createPageContext(context: PageContextProps) {
|
||||
return createContext<PageContextProps>(context, key, { native: true });
|
||||
}
|
||||
|
||||
export function usePageContext() {
|
||||
return useContext<PageContextProps>(key);
|
||||
}
|
||||
18
src/hooks/core/onMountedOrActivated.ts
Normal file
18
src/hooks/core/onMountedOrActivated.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { nextTick, onMounted, onActivated } from 'vue';
|
||||
|
||||
export function onMountedOrActivated(hook: Fn) {
|
||||
let mounted: boolean;
|
||||
|
||||
onMounted(() => {
|
||||
hook();
|
||||
nextTick(() => {
|
||||
mounted = true;
|
||||
});
|
||||
});
|
||||
|
||||
onActivated(() => {
|
||||
if (mounted) {
|
||||
hook();
|
||||
}
|
||||
});
|
||||
}
|
||||
40
src/hooks/core/useAttrs.ts
Normal file
40
src/hooks/core/useAttrs.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { getCurrentInstance, reactive, shallowRef, watchEffect } from 'vue';
|
||||
import type { Ref } from 'vue';
|
||||
interface Params {
|
||||
excludeListeners?: boolean;
|
||||
excludeKeys?: string[];
|
||||
excludeDefaultKeys?: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_EXCLUDE_KEYS = ['class', 'style'];
|
||||
const LISTENER_PREFIX = /^on[A-Z]/;
|
||||
|
||||
export function entries<T>(obj: Recordable<T>): [string, T][] {
|
||||
return Object.keys(obj).map((key: string) => [key, obj[key]]);
|
||||
}
|
||||
|
||||
export function useAttrs(params: Params = {}): Ref<Recordable> | {} {
|
||||
const instance = getCurrentInstance();
|
||||
if (!instance) return {};
|
||||
|
||||
const { excludeListeners = false, excludeKeys = [], excludeDefaultKeys = true } = params;
|
||||
const attrs = shallowRef({});
|
||||
const allExcludeKeys = excludeKeys.concat(excludeDefaultKeys ? DEFAULT_EXCLUDE_KEYS : []);
|
||||
|
||||
// Since attrs are not reactive, make it reactive instead of doing in `onUpdated` hook for better performance
|
||||
instance.attrs = reactive(instance.attrs);
|
||||
|
||||
watchEffect(() => {
|
||||
const res = entries(instance.attrs).reduce((acm, [key, val]) => {
|
||||
if (!allExcludeKeys.includes(key) && !(excludeListeners && LISTENER_PREFIX.test(key))) {
|
||||
acm[key] = val;
|
||||
}
|
||||
|
||||
return acm;
|
||||
}, {} as Recordable);
|
||||
|
||||
attrs.value = res;
|
||||
});
|
||||
|
||||
return attrs;
|
||||
}
|
||||
45
src/hooks/core/useContext.ts
Normal file
45
src/hooks/core/useContext.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import {
|
||||
InjectionKey,
|
||||
provide,
|
||||
inject,
|
||||
reactive,
|
||||
readonly as defineReadonly,
|
||||
// defineComponent,
|
||||
UnwrapRef,
|
||||
} from 'vue';
|
||||
|
||||
export interface CreateContextOptions {
|
||||
readonly?: boolean;
|
||||
createProvider?: boolean;
|
||||
native?: boolean;
|
||||
}
|
||||
|
||||
type ShallowUnwrap<T> = {
|
||||
[P in keyof T]: UnwrapRef<T[P]>;
|
||||
};
|
||||
|
||||
export function createContext<T>(
|
||||
context: any,
|
||||
key: InjectionKey<T> = Symbol(),
|
||||
options: CreateContextOptions = {},
|
||||
) {
|
||||
const { readonly = true, createProvider = false, native = false } = options;
|
||||
|
||||
const state = reactive(context);
|
||||
const provideData = readonly ? defineReadonly(state) : state;
|
||||
!createProvider && provide(key, native ? context : provideData);
|
||||
|
||||
return {
|
||||
state,
|
||||
};
|
||||
}
|
||||
|
||||
export function useContext<T>(key: InjectionKey<T>, native?: boolean): T;
|
||||
export function useContext<T>(key: InjectionKey<T>, defaultValue?: any, native?: boolean): T;
|
||||
|
||||
export function useContext<T>(
|
||||
key: InjectionKey<T> = Symbol(),
|
||||
defaultValue?: any,
|
||||
): ShallowUnwrap<T> {
|
||||
return inject(key, defaultValue || {});
|
||||
}
|
||||
22
src/hooks/core/useGlobalFlag.ts
Normal file
22
src/hooks/core/useGlobalFlag.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { ref } from 'vue';
|
||||
|
||||
const isEditorOpen = ref(false);
|
||||
const hasAjaxError = ref(false);
|
||||
const isSingleLogin = ref(false);
|
||||
|
||||
function ajaxError() {
|
||||
hasAjaxError.value = true;
|
||||
setTimeout(() => {
|
||||
hasAjaxError.value = false;
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
export default function() {
|
||||
return {
|
||||
isEditorOpen,
|
||||
hasAjaxError,
|
||||
ajaxError,
|
||||
isSingleLogin
|
||||
}
|
||||
}
|
||||
|
||||
17
src/hooks/core/useLockFn.ts
Normal file
17
src/hooks/core/useLockFn.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { ref, unref } from 'vue';
|
||||
|
||||
export function useLockFn<P extends any[] = any[], V = any>(fn: (...args: P) => Promise<V>) {
|
||||
const lockRef = ref(false);
|
||||
return async function (...args: P) {
|
||||
if (unref(lockRef)) return;
|
||||
lockRef.value = true;
|
||||
try {
|
||||
const ret = await fn(...args);
|
||||
lockRef.value = false;
|
||||
return ret;
|
||||
} catch (e) {
|
||||
lockRef.value = false;
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
}
|
||||
16
src/hooks/core/useRefs.ts
Normal file
16
src/hooks/core/useRefs.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import type { Ref } from 'vue';
|
||||
import { ref, onBeforeUpdate } from 'vue';
|
||||
|
||||
export function useRefs(): [Ref<HTMLElement[]>, (index: number) => (el: HTMLElement) => void] {
|
||||
const refs = ref([]) as Ref<HTMLElement[]>;
|
||||
|
||||
onBeforeUpdate(() => {
|
||||
refs.value = [];
|
||||
});
|
||||
|
||||
const setRefs = (index: number) => (el: HTMLElement) => {
|
||||
refs.value[index] = el;
|
||||
};
|
||||
|
||||
return [refs, setRefs];
|
||||
}
|
||||
45
src/hooks/core/useTimeout.ts
Normal file
45
src/hooks/core/useTimeout.ts
Normal file
@ -0,0 +1,45 @@
|
||||
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 };
|
||||
}
|
||||
45
src/hooks/event/useApiRequest.ts
Normal file
45
src/hooks/event/useApiRequest.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { httpRequest } from '/@/api/sys/api';
|
||||
import { getToken } from '/@/utils/auth';
|
||||
|
||||
export default function () {
|
||||
async function changeApiOptions(val) {
|
||||
if (val.path) {
|
||||
try {
|
||||
const headers = { Authorization: `Bearer ${getToken()}` };
|
||||
if (val.requestHeaderConfigs.length > 0) {
|
||||
val.requestHeaderConfigs.forEach((element) => {
|
||||
if (element.name) headers[element.name] = element.value;
|
||||
});
|
||||
}
|
||||
let path = val.path;
|
||||
if (val.requestParamsConfigs.length > 0) {
|
||||
path += '?';
|
||||
val.requestParamsConfigs.forEach((element) => {
|
||||
if (element.name) path += `${element.name}=${element.value}&`;
|
||||
});
|
||||
}
|
||||
const apiData = {};
|
||||
if (val.requestBodyConfigs.length > 0) {
|
||||
val.requestBodyConfigs.forEach((element) => {
|
||||
if (element.name) apiData[element.name] = element.value;
|
||||
});
|
||||
}
|
||||
const res = await httpRequest(
|
||||
{
|
||||
requestUrl: path,
|
||||
requestType: val.method,
|
||||
},
|
||||
{
|
||||
headers,
|
||||
data: apiData,
|
||||
},
|
||||
);
|
||||
|
||||
return res;
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
return {
|
||||
changeApiOptions,
|
||||
};
|
||||
}
|
||||
89
src/hooks/event/useBreakpoint.ts
Normal file
89
src/hooks/event/useBreakpoint.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
11
src/hooks/event/useEventBus.js
Normal file
11
src/hooks/event/useEventBus.js
Normal file
@ -0,0 +1,11 @@
|
||||
import mitt from '../../utils/mitt';
|
||||
const bus = new mitt();
|
||||
|
||||
export default function () {
|
||||
return {
|
||||
bus,
|
||||
FLOW_PROCESSED: 'flow_processed',
|
||||
FORM_LIST_MODIFIED: 'form_list_modified',
|
||||
CREATE_FLOW: 'create_flow'
|
||||
};
|
||||
}
|
||||
58
src/hooks/event/useEventListener.ts
Normal file
58
src/hooks/event/useEventListener.ts
Normal file
@ -0,0 +1,58 @@
|
||||
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(el as Element) as Ref<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 };
|
||||
}
|
||||
48
src/hooks/event/useIntersectionObserver.ts
Normal file
48
src/hooks/event/useIntersectionObserver.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { Ref, watchEffect, ref } from 'vue';
|
||||
|
||||
interface IntersectionObserverProps {
|
||||
target: Ref<Element | null | undefined>;
|
||||
root?: Ref<any>;
|
||||
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();
|
||||
},
|
||||
};
|
||||
}
|
||||
65
src/hooks/event/useScroll.ts
Normal file
65
src/hooks/event/useScroll.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import { ref, onMounted, watch, onUnmounted } from 'vue';
|
||||
import { isWindow, isObject } from '/@/utils/is';
|
||||
import { useThrottleFn } from '@vueuse/core';
|
||||
|
||||
export function useScroll(
|
||||
refEl: Ref<Element | Window | null>,
|
||||
options?: {
|
||||
wait?: number;
|
||||
leading?: boolean;
|
||||
trailing?: boolean;
|
||||
},
|
||||
) {
|
||||
const refX = ref(0);
|
||||
const refY = ref(0);
|
||||
let handler = () => {
|
||||
if (isWindow(refEl.value)) {
|
||||
refX.value = refEl.value.scrollX;
|
||||
refY.value = refEl.value.scrollY;
|
||||
} else if (refEl.value) {
|
||||
refX.value = (refEl.value as Element).scrollLeft;
|
||||
refY.value = (refEl.value as Element).scrollTop;
|
||||
}
|
||||
};
|
||||
|
||||
if (isObject(options)) {
|
||||
let wait = 0;
|
||||
if (options.wait && options.wait > 0) {
|
||||
wait = options.wait;
|
||||
Reflect.deleteProperty(options, 'wait');
|
||||
}
|
||||
|
||||
handler = useThrottleFn(handler, wait);
|
||||
}
|
||||
|
||||
let stopWatch: () => void;
|
||||
onMounted(() => {
|
||||
stopWatch = watch(
|
||||
refEl,
|
||||
(el, prevEl, onCleanup) => {
|
||||
if (el) {
|
||||
el.addEventListener('scroll', handler);
|
||||
} else if (prevEl) {
|
||||
prevEl.removeEventListener('scroll', handler);
|
||||
}
|
||||
onCleanup(() => {
|
||||
refX.value = refY.value = 0;
|
||||
el && el.removeEventListener('scroll', handler);
|
||||
});
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
refEl.value && refEl.value.removeEventListener('scroll', handler);
|
||||
});
|
||||
|
||||
function stop() {
|
||||
stopWatch && stopWatch();
|
||||
}
|
||||
|
||||
return { refX, refY, stop };
|
||||
}
|
||||
59
src/hooks/event/useScrollTo.ts
Normal file
59
src/hooks/event/useScrollTo.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { isFunction, isUnDef } from '/@/utils/is';
|
||||
import { ref, unref } from 'vue';
|
||||
|
||||
export interface ScrollToParams {
|
||||
el: any;
|
||||
to: number;
|
||||
duration?: number;
|
||||
callback?: () => any;
|
||||
}
|
||||
|
||||
const easeInOutQuad = (t: number, b: number, c: number, d: number) => {
|
||||
t /= d / 2;
|
||||
if (t < 1) {
|
||||
return (c / 2) * t * t + b;
|
||||
}
|
||||
t--;
|
||||
return (-c / 2) * (t * (t - 2) - 1) + b;
|
||||
};
|
||||
const move = (el: HTMLElement, amount: number) => {
|
||||
el.scrollTop = amount;
|
||||
};
|
||||
|
||||
const position = (el: HTMLElement) => {
|
||||
return el.scrollTop;
|
||||
};
|
||||
export function useScrollTo({ el, to, duration = 500, callback }: ScrollToParams) {
|
||||
const isActiveRef = ref(false);
|
||||
const start = position(el);
|
||||
const change = to - start;
|
||||
const increment = 20;
|
||||
let currentTime = 0;
|
||||
duration = isUnDef(duration) ? 500 : duration;
|
||||
|
||||
const animateScroll = function () {
|
||||
if (!unref(isActiveRef)) {
|
||||
return;
|
||||
}
|
||||
currentTime += increment;
|
||||
const val = easeInOutQuad(currentTime, start, change, duration);
|
||||
move(el, val);
|
||||
if (currentTime < duration && unref(isActiveRef)) {
|
||||
requestAnimationFrame(animateScroll);
|
||||
} else {
|
||||
if (callback && isFunction(callback)) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
};
|
||||
const run = () => {
|
||||
isActiveRef.value = true;
|
||||
animateScroll();
|
||||
};
|
||||
|
||||
const stop = () => {
|
||||
isActiveRef.value = false;
|
||||
};
|
||||
|
||||
return { start: run, stop };
|
||||
}
|
||||
35
src/hooks/event/useWindowSizeFn.ts
Normal file
35
src/hooks/event/useWindowSizeFn.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { tryOnMounted, tryOnUnmounted, 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];
|
||||
}
|
||||
36
src/hooks/setting/index.ts
Normal file
36
src/hooks/setting/index.ts
Normal file
@ -0,0 +1,36 @@
|
||||
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_REQUEST_TIMEOUT,
|
||||
VITE_GLOB_APP_SHORT_NAME,
|
||||
VITE_GLOB_API_URL_PREFIX,
|
||||
VITE_GLOB_UPLOAD_URL,
|
||||
VITE_GLOB_OUT_LINK_URL,
|
||||
VITE_GLOB_PRINT_BASE_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,
|
||||
requestTimeout:VITE_GLOB_REQUEST_TIMEOUT,
|
||||
shortName: VITE_GLOB_APP_SHORT_NAME,
|
||||
urlPrefix: VITE_GLOB_API_URL_PREFIX,
|
||||
uploadUrl: VITE_GLOB_UPLOAD_URL,
|
||||
outLink: VITE_GLOB_OUT_LINK_URL,
|
||||
printBaseUrl: VITE_GLOB_PRINT_BASE_URL,
|
||||
};
|
||||
return glob as Readonly<GlobConfig>;
|
||||
};
|
||||
105
src/hooks/setting/useHeaderSetting.ts
Normal file
105
src/hooks/setting/useHeaderSetting.ts
Normal file
@ -0,0 +1,105 @@
|
||||
import type { HeaderSetting } from '/#/config';
|
||||
|
||||
import { computed, unref } from 'vue';
|
||||
|
||||
import { useAppStore } from '/@/store/modules/app';
|
||||
|
||||
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
|
||||
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
|
||||
import { useFullContent } from '/@/hooks/web/useFullContent';
|
||||
import { MenuModeEnum } from '/@/enums/menuEnum';
|
||||
|
||||
export function useHeaderSetting() {
|
||||
const { getFullContent } = useFullContent();
|
||||
const appStore = useAppStore();
|
||||
|
||||
const getShowFullHeaderRef = computed(() => {
|
||||
return (
|
||||
!unref(getFullContent) &&
|
||||
unref(getShowMixHeaderRef) &&
|
||||
unref(getShowHeader) &&
|
||||
!unref(getIsTopMenu) &&
|
||||
!unref(getIsMixSidebar)
|
||||
);
|
||||
});
|
||||
|
||||
const getUnFixedAndFull = computed(() => !unref(getFixed) && !unref(getShowFullHeaderRef));
|
||||
|
||||
const getShowInsetHeaderRef = computed(() => {
|
||||
const need = !unref(getFullContent) && unref(getShowHeader);
|
||||
return (
|
||||
(need && !unref(getShowMixHeaderRef)) ||
|
||||
(need && unref(getIsTopMenu)) ||
|
||||
(need && unref(getIsMixSidebar))
|
||||
);
|
||||
});
|
||||
|
||||
const {
|
||||
getMenuMode,
|
||||
getSplit,
|
||||
getShowHeaderTrigger,
|
||||
getIsSidebarType,
|
||||
getIsMixSidebar,
|
||||
getIsTopMenu,
|
||||
} = useMenuSetting();
|
||||
const { getShowBreadCrumb, getShowLogo } = useRootSetting();
|
||||
|
||||
const getShowMixHeaderRef = computed(() => !unref(getIsSidebarType) && unref(getShowHeader));
|
||||
|
||||
const getShowDoc = computed(() => appStore.getHeaderSetting.showDoc);
|
||||
|
||||
const getHeaderTheme = computed(() => appStore.getHeaderSetting.theme);
|
||||
|
||||
const getShowHeader = computed(() => appStore.getHeaderSetting.show);
|
||||
|
||||
const getFixed = computed(() => appStore.getHeaderSetting.fixed);
|
||||
|
||||
const getHeaderBgColor = computed(() => appStore.getHeaderSetting.bgColor);
|
||||
|
||||
const getShowSearch = computed(() => appStore.getHeaderSetting.showSearch);
|
||||
|
||||
const getUseLockPage = computed(() => appStore.getHeaderSetting.useLockPage);
|
||||
|
||||
const getShowFullScreen = computed(() => appStore.getHeaderSetting.showFullScreen);
|
||||
|
||||
const getShowNotice = computed(() => appStore.getHeaderSetting.showNotice);
|
||||
|
||||
const getShowBread = computed(() => {
|
||||
return (
|
||||
unref(getMenuMode) !== MenuModeEnum.HORIZONTAL && unref(getShowBreadCrumb) && !unref(getSplit)
|
||||
);
|
||||
});
|
||||
|
||||
const getShowHeaderLogo = computed(() => {
|
||||
return unref(getShowLogo) && !unref(getIsSidebarType) && !unref(getIsMixSidebar);
|
||||
});
|
||||
|
||||
const getShowContent = computed(() => {
|
||||
return unref(getShowBread) || unref(getShowHeaderTrigger);
|
||||
});
|
||||
|
||||
// Set header configuration
|
||||
function setHeaderSetting(headerSetting: Partial<HeaderSetting>) {
|
||||
appStore.setProjectConfig({ headerSetting });
|
||||
}
|
||||
return {
|
||||
setHeaderSetting,
|
||||
|
||||
getShowDoc,
|
||||
getShowSearch,
|
||||
getHeaderTheme,
|
||||
getUseLockPage,
|
||||
getShowFullScreen,
|
||||
getShowNotice,
|
||||
getShowBread,
|
||||
getShowContent,
|
||||
getShowHeaderLogo,
|
||||
getShowHeader,
|
||||
getFixed,
|
||||
getShowMixHeaderRef,
|
||||
getShowFullHeaderRef,
|
||||
getShowInsetHeaderRef,
|
||||
getUnFixedAndFull,
|
||||
getHeaderBgColor,
|
||||
};
|
||||
}
|
||||
166
src/hooks/setting/useMenuSetting.ts
Normal file
166
src/hooks/setting/useMenuSetting.ts
Normal file
@ -0,0 +1,166 @@
|
||||
import type { MenuSetting } from '/#/config';
|
||||
|
||||
import { computed, unref, ref } from 'vue';
|
||||
|
||||
import { useAppStore } from '/@/store/modules/app';
|
||||
|
||||
import { SIDE_BAR_MINI_WIDTH, SIDE_BAR_SHOW_TIT_MINI_WIDTH } from '/@/enums/appEnum';
|
||||
import { MenuModeEnum, MenuTypeEnum, TriggerEnum } from '/@/enums/menuEnum';
|
||||
import { useFullContent } from '/@/hooks/web/useFullContent';
|
||||
|
||||
const mixSideHasChildren = ref(false);
|
||||
|
||||
export function useMenuSetting() {
|
||||
const { getFullContent: fullContent } = useFullContent();
|
||||
const appStore = useAppStore();
|
||||
|
||||
const getShowSidebar = computed(() => {
|
||||
return (
|
||||
unref(getSplit) ||
|
||||
(unref(getShowMenu) && unref(getMenuMode) !== MenuModeEnum.HORIZONTAL && !unref(fullContent))
|
||||
);
|
||||
});
|
||||
|
||||
const getCollapsed = computed(() => appStore.getMenuSetting.collapsed);
|
||||
|
||||
const getMenuType = computed(() => appStore.getMenuSetting.type);
|
||||
|
||||
const getMenuMode = computed(() => appStore.getMenuSetting.mode);
|
||||
|
||||
const getMenuFixed = computed(() => appStore.getMenuSetting.fixed);
|
||||
|
||||
const getShowMenu = computed(() => appStore.getMenuSetting.show);
|
||||
|
||||
const getMenuHidden = computed(() => appStore.getMenuSetting.hidden);
|
||||
|
||||
const getMenuWidth = computed(() => appStore.getMenuSetting.menuWidth);
|
||||
|
||||
const getTrigger = computed(() => appStore.getMenuSetting.trigger);
|
||||
|
||||
const getMenuTheme = computed(() => appStore.getMenuSetting.theme);
|
||||
|
||||
const getSplit = computed(() => appStore.getMenuSetting.split);
|
||||
|
||||
const getMenuBgColor = computed(() => appStore.getMenuSetting.bgColor);
|
||||
|
||||
const getMixSideTrigger = computed(() => appStore.getMenuSetting.mixSideTrigger);
|
||||
|
||||
const getCanDrag = computed(() => appStore.getMenuSetting.canDrag);
|
||||
|
||||
const getAccordion = computed(() => appStore.getMenuSetting.accordion);
|
||||
|
||||
const getMixSideFixed = computed(() => appStore.getMenuSetting.mixSideFixed);
|
||||
|
||||
const getTopMenuAlign = computed(() => appStore.getMenuSetting.topMenuAlign);
|
||||
|
||||
const getCloseMixSidebarOnChange = computed(
|
||||
() => appStore.getMenuSetting.closeMixSidebarOnChange,
|
||||
);
|
||||
|
||||
const getIsSidebarType = computed(() => unref(getMenuType) === MenuTypeEnum.SIDEBAR);
|
||||
|
||||
const getIsTopMenu = computed(() => unref(getMenuType) === MenuTypeEnum.TOP_MENU);
|
||||
|
||||
const getCollapsedShowTitle = computed(() => appStore.getMenuSetting.collapsedShowTitle);
|
||||
|
||||
const getShowTopMenu = computed(() => {
|
||||
return unref(getMenuMode) === MenuModeEnum.HORIZONTAL || unref(getSplit);
|
||||
});
|
||||
|
||||
const getShowHeaderTrigger = computed(() => {
|
||||
if (
|
||||
unref(getMenuType) === MenuTypeEnum.TOP_MENU ||
|
||||
!unref(getShowMenu) ||
|
||||
unref(getMenuHidden)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return unref(getTrigger) === TriggerEnum.HEADER;
|
||||
});
|
||||
|
||||
const getIsHorizontal = computed(() => {
|
||||
return unref(getMenuMode) === MenuModeEnum.HORIZONTAL;
|
||||
});
|
||||
|
||||
const getIsMixSidebar = computed(() => {
|
||||
return unref(getMenuType) === MenuTypeEnum.MIX_SIDEBAR;
|
||||
});
|
||||
|
||||
const getIsMixMode = computed(() => {
|
||||
return unref(getMenuMode) === MenuModeEnum.INLINE && unref(getMenuType) === MenuTypeEnum.MIX;
|
||||
});
|
||||
|
||||
const getRealWidth = computed(() => {
|
||||
if (unref(getIsMixSidebar)) {
|
||||
return unref(getCollapsed) && !unref(getMixSideFixed)
|
||||
? unref(getMiniWidthNumber)
|
||||
: unref(getMenuWidth);
|
||||
}
|
||||
return unref(getCollapsed) ? unref(getMiniWidthNumber) : unref(getMenuWidth);
|
||||
});
|
||||
|
||||
const getMiniWidthNumber = computed(() => {
|
||||
const { collapsedShowTitle } = appStore.getMenuSetting;
|
||||
return collapsedShowTitle ? SIDE_BAR_SHOW_TIT_MINI_WIDTH : SIDE_BAR_MINI_WIDTH;
|
||||
});
|
||||
|
||||
const getCalcContentWidth = computed(() => {
|
||||
const width =
|
||||
unref(getIsTopMenu) || !unref(getShowMenu) || (unref(getSplit) && unref(getMenuHidden))
|
||||
? 0
|
||||
: unref(getIsMixSidebar)
|
||||
? (unref(getCollapsed) ? SIDE_BAR_MINI_WIDTH : SIDE_BAR_SHOW_TIT_MINI_WIDTH) +
|
||||
(unref(getMixSideFixed) && unref(mixSideHasChildren) ? unref(getRealWidth) : 0)
|
||||
: unref(getRealWidth);
|
||||
|
||||
return `calc(100% - ${unref(width)}px)`;
|
||||
});
|
||||
|
||||
// Set menu configuration
|
||||
function setMenuSetting(menuSetting: Partial<MenuSetting>): void {
|
||||
appStore.setProjectConfig({ menuSetting });
|
||||
}
|
||||
|
||||
function toggleCollapsed() {
|
||||
setMenuSetting({
|
||||
collapsed: !unref(getCollapsed),
|
||||
});
|
||||
}
|
||||
return {
|
||||
setMenuSetting,
|
||||
|
||||
toggleCollapsed,
|
||||
|
||||
getMenuFixed,
|
||||
getRealWidth,
|
||||
getMenuType,
|
||||
getMenuMode,
|
||||
getShowMenu,
|
||||
getCollapsed,
|
||||
getMiniWidthNumber,
|
||||
getCalcContentWidth,
|
||||
getMenuWidth,
|
||||
getTrigger,
|
||||
getSplit,
|
||||
getMenuTheme,
|
||||
getCanDrag,
|
||||
getCollapsedShowTitle,
|
||||
getIsHorizontal,
|
||||
getIsSidebarType,
|
||||
getAccordion,
|
||||
getShowTopMenu,
|
||||
getShowHeaderTrigger,
|
||||
getTopMenuAlign,
|
||||
getMenuHidden,
|
||||
getIsTopMenu,
|
||||
getMenuBgColor,
|
||||
getShowSidebar,
|
||||
getIsMixMode,
|
||||
getIsMixSidebar,
|
||||
getCloseMixSidebarOnChange,
|
||||
getMixSideTrigger,
|
||||
getMixSideFixed,
|
||||
mixSideHasChildren,
|
||||
};
|
||||
}
|
||||
28
src/hooks/setting/useMultipleTabSetting.ts
Normal file
28
src/hooks/setting/useMultipleTabSetting.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import type { MultiTabsSetting } from '/#/config';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { useAppStore } from '/@/store/modules/app';
|
||||
|
||||
export function useMultipleTabSetting() {
|
||||
const appStore = useAppStore();
|
||||
|
||||
const getShowMultipleTab = computed(() => appStore.getMultiTabsSetting.show);
|
||||
|
||||
const getShowQuick = computed(() => appStore.getMultiTabsSetting.showQuick);
|
||||
|
||||
const getShowRedo = computed(() => appStore.getMultiTabsSetting.showRedo);
|
||||
|
||||
const getShowFold = computed(() => appStore.getMultiTabsSetting.showFold);
|
||||
|
||||
function setMultipleTabSetting(multiTabsSetting: Partial<MultiTabsSetting>) {
|
||||
appStore.setProjectConfig({ multiTabsSetting });
|
||||
}
|
||||
return {
|
||||
setMultipleTabSetting,
|
||||
getShowMultipleTab,
|
||||
getShowQuick,
|
||||
getShowRedo,
|
||||
getShowFold,
|
||||
};
|
||||
}
|
||||
95
src/hooks/setting/useRootSetting.ts
Normal file
95
src/hooks/setting/useRootSetting.ts
Normal file
@ -0,0 +1,95 @@
|
||||
import type { ProjectConfig } from '/#/config';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { useAppStore } from '/@/store/modules/app';
|
||||
import { ContentEnum, ThemeEnum } from '/@/enums/appEnum';
|
||||
|
||||
type RootSetting = Omit<
|
||||
ProjectConfig,
|
||||
'locale' | 'headerSetting' | 'menuSetting' | 'multiTabsSetting'
|
||||
>;
|
||||
|
||||
export function useRootSetting() {
|
||||
const appStore = useAppStore();
|
||||
|
||||
const getPageLoading = computed(() => appStore.getPageLoading);
|
||||
|
||||
const getOpenKeepAlive = computed(() => appStore.getProjectConfig.openKeepAlive);
|
||||
|
||||
const getSettingButtonPosition = computed(() => appStore.getProjectConfig.settingButtonPosition);
|
||||
|
||||
const getCanEmbedIFramePage = computed(() => appStore.getProjectConfig.canEmbedIFramePage);
|
||||
|
||||
const getPermissionMode = computed(() => appStore.getProjectConfig.permissionMode);
|
||||
|
||||
const getShowLogo = computed(() => appStore.getProjectConfig.showLogo);
|
||||
|
||||
const getContentMode = computed(() => appStore.getProjectConfig.contentMode);
|
||||
|
||||
const getUseOpenBackTop = computed(() => appStore.getProjectConfig.useOpenBackTop);
|
||||
|
||||
const getShowSettingButton = computed(() => appStore.getProjectConfig.showSettingButton);
|
||||
|
||||
const getUseErrorHandle = computed(() => appStore.getProjectConfig.useErrorHandle);
|
||||
|
||||
const getShowFooter = computed(() => appStore.getProjectConfig.showFooter);
|
||||
|
||||
const getShowBreadCrumb = computed(() => appStore.getProjectConfig.showBreadCrumb);
|
||||
|
||||
const getThemeColor = computed(() => appStore.getProjectConfig.themeColor);
|
||||
|
||||
const getShowBreadCrumbIcon = computed(() => appStore.getProjectConfig.showBreadCrumbIcon);
|
||||
|
||||
const getFullContent = computed(() => appStore.getProjectConfig.fullContent);
|
||||
|
||||
const getColorWeak = computed(() => appStore.getProjectConfig.colorWeak);
|
||||
|
||||
const getGrayMode = computed(() => appStore.getProjectConfig.grayMode);
|
||||
|
||||
const getLockTime = computed(() => appStore.getProjectConfig.lockTime);
|
||||
|
||||
const getShowDarkModeToggle = computed(() => appStore.getProjectConfig.showDarkModeToggle);
|
||||
|
||||
const getDarkMode = computed(() => appStore.getDarkMode);
|
||||
|
||||
const getLayoutContentMode = computed(() =>
|
||||
appStore.getProjectConfig.contentMode === ContentEnum.FULL
|
||||
? ContentEnum.FULL
|
||||
: ContentEnum.FIXED,
|
||||
);
|
||||
|
||||
function setRootSetting(setting: Partial<RootSetting>) {
|
||||
appStore.setProjectConfig(setting);
|
||||
}
|
||||
|
||||
function setDarkMode(mode: ThemeEnum) {
|
||||
appStore.setDarkMode(mode);
|
||||
}
|
||||
return {
|
||||
setRootSetting,
|
||||
|
||||
getSettingButtonPosition,
|
||||
getFullContent,
|
||||
getColorWeak,
|
||||
getGrayMode,
|
||||
getLayoutContentMode,
|
||||
getPageLoading,
|
||||
getOpenKeepAlive,
|
||||
getCanEmbedIFramePage,
|
||||
getPermissionMode,
|
||||
getShowLogo,
|
||||
getUseErrorHandle,
|
||||
getShowBreadCrumb,
|
||||
getShowBreadCrumbIcon,
|
||||
getUseOpenBackTop,
|
||||
getShowSettingButton,
|
||||
getShowFooter,
|
||||
getContentMode,
|
||||
getLockTime,
|
||||
getThemeColor,
|
||||
getDarkMode,
|
||||
setDarkMode,
|
||||
getShowDarkModeToggle,
|
||||
};
|
||||
}
|
||||
31
src/hooks/setting/useTransitionSetting.ts
Normal file
31
src/hooks/setting/useTransitionSetting.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import type { TransitionSetting } from '/#/config';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { useAppStore } from '/@/store/modules/app';
|
||||
|
||||
export function useTransitionSetting() {
|
||||
const appStore = useAppStore();
|
||||
|
||||
const getEnableTransition = computed(() => appStore.getTransitionSetting?.enable);
|
||||
|
||||
const getOpenNProgress = computed(() => appStore.getTransitionSetting?.openNProgress);
|
||||
|
||||
const getOpenPageLoading = computed((): boolean => {
|
||||
return !!appStore.getTransitionSetting?.openPageLoading;
|
||||
});
|
||||
|
||||
const getBasicTransition = computed(() => appStore.getTransitionSetting?.basicTransition);
|
||||
|
||||
function setTransitionSetting(transitionSetting: Partial<TransitionSetting>) {
|
||||
appStore.setProjectConfig({ transitionSetting });
|
||||
}
|
||||
return {
|
||||
setTransitionSetting,
|
||||
|
||||
getEnableTransition,
|
||||
getOpenNProgress,
|
||||
getOpenPageLoading,
|
||||
getBasicTransition,
|
||||
};
|
||||
}
|
||||
26
src/hooks/setting/userTableScrollHeight.ts
Normal file
26
src/hooks/setting/userTableScrollHeight.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { reactive, nextTick } from 'vue';
|
||||
export default function () {
|
||||
const tableOptions: {
|
||||
scrollHeight: number;
|
||||
} = reactive({
|
||||
scrollHeight: 600,
|
||||
});
|
||||
async function scrollHeight() {
|
||||
await nextTick();
|
||||
const conTable = document.getElementsByClassName('vben-layout-content');
|
||||
if (conTable && conTable.length > 0 && conTable[0]) {
|
||||
const searchTable = document.getElementsByClassName('search');
|
||||
if (searchTable && searchTable.length > 0 && searchTable[0]) {
|
||||
tableOptions.scrollHeight = conTable[0].clientHeight - 220 - searchTable[0].clientHeight;
|
||||
} else {
|
||||
tableOptions.scrollHeight = conTable[0].clientHeight - 180;
|
||||
}
|
||||
} else {
|
||||
tableOptions.scrollHeight = 400;
|
||||
}
|
||||
}
|
||||
return {
|
||||
tableOptions,
|
||||
scrollHeight,
|
||||
};
|
||||
}
|
||||
10
src/hooks/web/useAppInject.ts
Normal file
10
src/hooks/web/useAppInject.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { useAppProviderContext } from '/@/components/Application';
|
||||
import { computed, unref } from 'vue';
|
||||
|
||||
export function useAppInject() {
|
||||
const values = useAppProviderContext();
|
||||
|
||||
return {
|
||||
getIsMobile: computed(() => unref(values.isMobile)),
|
||||
};
|
||||
}
|
||||
190
src/hooks/web/useContentHeight.ts
Normal file
190
src/hooks/web/useContentHeight.ts
Normal file
@ -0,0 +1,190 @@
|
||||
import { ComputedRef, isRef, nextTick, Ref, ref, unref, watch } from 'vue';
|
||||
import { onMountedOrActivated } from '/@/hooks/core/onMountedOrActivated';
|
||||
import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
|
||||
import { useLayoutHeight } from '/@/layouts/default/content/useContentViewHeight';
|
||||
import { getViewportOffset } from '/@/utils/domUtils';
|
||||
import { isNumber, isString } from '/@/utils/is';
|
||||
|
||||
export interface CompensationHeight {
|
||||
// 使用 layout Footer 高度作为判断补偿高度的条件
|
||||
useLayoutFooter: boolean;
|
||||
// refs HTMLElement
|
||||
elements?: Ref[];
|
||||
}
|
||||
|
||||
type Upward = number | string | null | undefined;
|
||||
|
||||
/**
|
||||
* 动态计算内容高度,根据锚点dom最下坐标到屏幕最下坐标,根据传入dom的高度、padding、margin等值进行动态计算
|
||||
* 最终获取合适的内容高度
|
||||
*
|
||||
* @param flag 用于开启计算的响应式标识
|
||||
* @param anchorRef 锚点组件 Ref<ElRef | ComponentRef>
|
||||
* @param subtractHeightRefs 待减去高度的组件列表 Ref<ElRef | ComponentRef>
|
||||
* @param substractSpaceRefs 待减去空闲空间(margins/paddings)的组件列表 Ref<ElRef | ComponentRef>
|
||||
* @param offsetHeightRef 计算偏移的响应式高度,计算高度时将直接减去此值
|
||||
* @param upwardSpace 向上递归减去空闲空间的 层级 或 直到指定class为止 数值为2代表向上递归两次|数值为ant-layout表示向上递归直到碰见.ant-layout为止
|
||||
* @returns 响应式高度
|
||||
*/
|
||||
export function useContentHeight(
|
||||
flag: ComputedRef<Boolean>,
|
||||
anchorRef: Ref,
|
||||
subtractHeightRefs: Ref[],
|
||||
substractSpaceRefs: Ref[],
|
||||
upwardSpace: Ref<Upward> | ComputedRef<Upward> | Upward = 0,
|
||||
offsetHeightRef: Ref<number> = ref(0),
|
||||
) {
|
||||
const contentHeight: Ref<Nullable<number>> = ref(null);
|
||||
const { footerHeightRef: layoutFooterHeightRef } = useLayoutHeight();
|
||||
let compensationHeight: CompensationHeight = {
|
||||
useLayoutFooter: true,
|
||||
};
|
||||
|
||||
const setCompensation = (params: CompensationHeight) => {
|
||||
compensationHeight = params;
|
||||
};
|
||||
|
||||
function redoHeight() {
|
||||
nextTick(() => {
|
||||
calcContentHeight();
|
||||
});
|
||||
}
|
||||
|
||||
function calcSubtractSpace(
|
||||
element: Element | null | undefined,
|
||||
direction: 'all' | 'top' | 'bottom' = 'all',
|
||||
): number {
|
||||
function numberPx(px: string) {
|
||||
return Number(px.replace(/[^\d]/g, ''));
|
||||
}
|
||||
let subtractHeight = 0;
|
||||
const ZERO_PX = '0px';
|
||||
if (element) {
|
||||
const cssStyle = getComputedStyle(element);
|
||||
const marginTop = numberPx(cssStyle?.marginTop ?? ZERO_PX);
|
||||
const marginBottom = numberPx(cssStyle?.marginBottom ?? ZERO_PX);
|
||||
const paddingTop = numberPx(cssStyle?.paddingTop ?? ZERO_PX);
|
||||
const paddingBottom = numberPx(cssStyle?.paddingBottom ?? ZERO_PX);
|
||||
if (direction === 'all') {
|
||||
subtractHeight += marginTop;
|
||||
subtractHeight += marginBottom;
|
||||
subtractHeight += paddingTop;
|
||||
subtractHeight += paddingBottom;
|
||||
} else if (direction === 'top') {
|
||||
subtractHeight += marginTop;
|
||||
subtractHeight += paddingTop;
|
||||
} else {
|
||||
subtractHeight += marginBottom;
|
||||
subtractHeight += paddingBottom;
|
||||
}
|
||||
}
|
||||
return subtractHeight;
|
||||
}
|
||||
|
||||
function getEl(element: any): Nullable<HTMLDivElement> {
|
||||
if (element == null) {
|
||||
return null;
|
||||
}
|
||||
return (element instanceof HTMLDivElement ? element : element.$el) as HTMLDivElement;
|
||||
}
|
||||
|
||||
async function calcContentHeight() {
|
||||
if (!flag.value) {
|
||||
return;
|
||||
}
|
||||
// Add a delay to get the correct height
|
||||
await nextTick();
|
||||
|
||||
const anchorEl = getEl(unref(anchorRef));
|
||||
if (!anchorEl) {
|
||||
return;
|
||||
}
|
||||
const { bottomIncludeBody } = getViewportOffset(anchorEl);
|
||||
|
||||
// substract elements height
|
||||
let substractHeight = 0;
|
||||
subtractHeightRefs.forEach((item) => {
|
||||
substractHeight += getEl(unref(item))?.offsetHeight ?? 0;
|
||||
});
|
||||
|
||||
// subtract margins / paddings
|
||||
let substractSpaceHeight = calcSubtractSpace(anchorEl) ?? 0;
|
||||
substractSpaceRefs.forEach((item) => {
|
||||
substractSpaceHeight += calcSubtractSpace(getEl(unref(item)));
|
||||
});
|
||||
|
||||
// upwardSpace
|
||||
let upwardSpaceHeight = 0;
|
||||
function upward(element: Element | null, upwardLvlOrClass: number | string | null | undefined) {
|
||||
if (element && upwardLvlOrClass) {
|
||||
const parent = element.parentElement;
|
||||
if (parent) {
|
||||
if (isString(upwardLvlOrClass)) {
|
||||
if (!parent.classList.contains(upwardLvlOrClass)) {
|
||||
upwardSpaceHeight += calcSubtractSpace(parent, 'bottom');
|
||||
upward(parent, upwardLvlOrClass);
|
||||
} else {
|
||||
upwardSpaceHeight += calcSubtractSpace(parent, 'bottom');
|
||||
}
|
||||
} else if (isNumber(upwardLvlOrClass)) {
|
||||
if (upwardLvlOrClass > 0) {
|
||||
upwardSpaceHeight += calcSubtractSpace(parent, 'bottom');
|
||||
upward(parent, --upwardLvlOrClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isRef(upwardSpace)) {
|
||||
upward(anchorEl, unref(upwardSpace));
|
||||
} else {
|
||||
upward(anchorEl, upwardSpace);
|
||||
}
|
||||
|
||||
let height =
|
||||
bottomIncludeBody -
|
||||
unref(layoutFooterHeightRef) -
|
||||
unref(offsetHeightRef) -
|
||||
substractHeight -
|
||||
substractSpaceHeight -
|
||||
upwardSpaceHeight;
|
||||
// compensation height
|
||||
const calcCompensationHeight = () => {
|
||||
compensationHeight.elements?.forEach((item) => {
|
||||
height += getEl(unref(item))?.offsetHeight ?? 0;
|
||||
});
|
||||
};
|
||||
if (compensationHeight.useLayoutFooter && unref(layoutFooterHeightRef) > 0) {
|
||||
calcCompensationHeight();
|
||||
} else {
|
||||
calcCompensationHeight();
|
||||
}
|
||||
|
||||
contentHeight.value = height;
|
||||
}
|
||||
|
||||
onMountedOrActivated(() => {
|
||||
nextTick(() => {
|
||||
calcContentHeight();
|
||||
});
|
||||
});
|
||||
useWindowSizeFn(
|
||||
() => {
|
||||
calcContentHeight();
|
||||
},
|
||||
50,
|
||||
{ immediate: true },
|
||||
);
|
||||
watch(
|
||||
() => [layoutFooterHeightRef.value],
|
||||
() => {
|
||||
calcContentHeight();
|
||||
},
|
||||
{
|
||||
flush: 'post',
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
return { redoHeight, setCompensation, contentHeight };
|
||||
}
|
||||
12
src/hooks/web/useContextMenu.ts
Normal file
12
src/hooks/web/useContextMenu.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { onUnmounted, getCurrentInstance } from 'vue';
|
||||
import { createContextMenu, destroyContextMenu } from '/@/components/ContextMenu';
|
||||
import type { ContextMenuItem } from '/@/components/ContextMenu';
|
||||
export type { ContextMenuItem };
|
||||
export function useContextMenu(authRemove = true) {
|
||||
if (getCurrentInstance() && authRemove) {
|
||||
onUnmounted(() => {
|
||||
destroyContextMenu();
|
||||
});
|
||||
}
|
||||
return [createContextMenu, destroyContextMenu];
|
||||
}
|
||||
69
src/hooks/web/useCopyToClipboard.ts
Normal file
69
src/hooks/web/useCopyToClipboard.ts
Normal file
@ -0,0 +1,69 @@
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
import { isDef } from '/@/utils/is';
|
||||
interface Options {
|
||||
target?: HTMLElement;
|
||||
}
|
||||
export function useCopyToClipboard(initial?: string) {
|
||||
const clipboardRef = ref(initial || '');
|
||||
const isSuccessRef = ref(false);
|
||||
const copiedRef = ref(false);
|
||||
|
||||
watch(
|
||||
clipboardRef,
|
||||
(str?: string) => {
|
||||
if (isDef(str)) {
|
||||
copiedRef.value = true;
|
||||
isSuccessRef.value = copyTextToClipboard(str);
|
||||
}
|
||||
},
|
||||
{ immediate: !!initial, flush: 'sync' },
|
||||
);
|
||||
|
||||
return { clipboardRef, isSuccessRef, copiedRef };
|
||||
}
|
||||
|
||||
export function copyTextToClipboard(input: string, { target = document.body }: Options = {}) {
|
||||
const element = document.createElement('textarea');
|
||||
const previouslyFocusedElement = document.activeElement;
|
||||
|
||||
element.value = input;
|
||||
|
||||
element.setAttribute('readonly', '');
|
||||
|
||||
(element.style as any).contain = 'strict';
|
||||
element.style.position = 'absolute';
|
||||
element.style.left = '-9999px';
|
||||
element.style.fontSize = '12pt';
|
||||
|
||||
const selection = document.getSelection();
|
||||
let originalRange;
|
||||
if (selection && selection.rangeCount > 0) {
|
||||
originalRange = selection.getRangeAt(0);
|
||||
}
|
||||
|
||||
target.append(element);
|
||||
element.select();
|
||||
|
||||
element.selectionStart = 0;
|
||||
element.selectionEnd = input.length;
|
||||
|
||||
let isSuccess = false;
|
||||
try {
|
||||
isSuccess = document.execCommand('copy');
|
||||
} catch (e: any) {
|
||||
throw new Error(e);
|
||||
}
|
||||
|
||||
element.remove();
|
||||
|
||||
if (originalRange && selection) {
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(originalRange);
|
||||
}
|
||||
|
||||
if (previouslyFocusedElement) {
|
||||
(previouslyFocusedElement as HTMLElement).focus();
|
||||
}
|
||||
return isSuccess;
|
||||
}
|
||||
22
src/hooks/web/useDesign.ts
Normal file
22
src/hooks/web/useDesign.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { useAppProviderContext } from '/@/components/Application';
|
||||
// import { computed } from 'vue';
|
||||
// import { lowerFirst } from 'lodash-es';
|
||||
export function useDesign(scope: string) {
|
||||
const values = useAppProviderContext();
|
||||
// const $style = cssModule ? useCssModule() : {};
|
||||
|
||||
// const style: Record<string, string> = {};
|
||||
// if (cssModule) {
|
||||
// Object.keys($style).forEach((key) => {
|
||||
// // const moduleCls = $style[key];
|
||||
// const k = key.replace(new RegExp(`^${values.prefixCls}-?`, 'ig'), '');
|
||||
// style[lowerFirst(k)] = $style[key];
|
||||
// });
|
||||
// }
|
||||
return {
|
||||
// prefixCls: computed(() => `${values.prefixCls}-${scope}`),
|
||||
prefixCls: `${values.prefixCls}-${scope}`,
|
||||
prefixVar: values.prefixCls,
|
||||
// style,
|
||||
};
|
||||
}
|
||||
116
src/hooks/web/useECharts.ts
Normal file
116
src/hooks/web/useECharts.ts
Normal file
@ -0,0 +1,116 @@
|
||||
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 { useRootSetting } from '/@/hooks/setting/useRootSetting';
|
||||
|
||||
export function useECharts(
|
||||
elRef: Ref<HTMLDivElement>,
|
||||
theme: 'light' | 'dark' | 'default' = 'default',
|
||||
) {
|
||||
const { getDarkMode: getSysDarkMode } = useRootSetting();
|
||||
|
||||
const getDarkMode = computed(() => {
|
||||
return theme === 'default' ? getSysDarkMode.value : theme;
|
||||
});
|
||||
let chartInstance: echarts.ECharts | null = null;
|
||||
let resizeFn: Fn = resize;
|
||||
const cacheOptions = ref({}) as Ref<EChartsOption>;
|
||||
let removeResizeFn: Fn = () => {};
|
||||
|
||||
resizeFn = useDebounceFn(resize, 200);
|
||||
|
||||
const getOptions = computed(() => {
|
||||
if (getDarkMode.value !== 'dark') {
|
||||
return cacheOptions.value as EChartsOption;
|
||||
}
|
||||
return {
|
||||
backgroundColor: 'transparent',
|
||||
...cacheOptions.value,
|
||||
} as EChartsOption;
|
||||
});
|
||||
|
||||
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(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,
|
||||
};
|
||||
}
|
||||
152
src/hooks/web/useFormConfig.ts
Normal file
152
src/hooks/web/useFormConfig.ts
Normal file
@ -0,0 +1,152 @@
|
||||
|
||||
import { FormSchema } from '/@/components/Form';
|
||||
import { BasicColumn } from "/@/components/Table";
|
||||
import { getFormTemplateUsingCache } from '/@/api/form/design';
|
||||
import { getMenuByIdComponentPath } from '/@/api/system/menu';
|
||||
import { deepMerge } from '/@/utils';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
|
||||
const { t } = useI18n();
|
||||
const { notification } = useMessage();
|
||||
|
||||
export function useFormConfig() {
|
||||
async function mergeFormSchemas(obj:{formSchema: FormSchema[],formId:String,formPath:String}){
|
||||
try{
|
||||
let formProps;
|
||||
if(obj.formId){
|
||||
formProps=await queryConfig(obj.formId,'formProps');
|
||||
}else{
|
||||
formProps=await queryConfigByFormPath(obj.formPath,'formProps');
|
||||
}
|
||||
|
||||
let fschemas=formProps?.schemas;
|
||||
if(fschemas&&fschemas.length>0){
|
||||
return deepMerge(obj.formSchema,fschemas);
|
||||
}else{
|
||||
return formSchema;
|
||||
}
|
||||
}catch(e){
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function mergeFormEventConfigs(obj:{formEventConfigs,formId:String,formPath:String}){
|
||||
try{
|
||||
let fEConfigs;
|
||||
if(obj.formId){
|
||||
fEConfigs=await queryConfig(obj.formId,'formEventConfigs');
|
||||
}else{
|
||||
fEConfigs=await queryConfigByFormPath(obj.formPath,'formEventConfigs');
|
||||
}
|
||||
|
||||
return deepMerge(obj.formEventConfigs,fEConfigs);
|
||||
}catch(e){
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function mergeColumns(columns: BasicColumn[],formId:String){
|
||||
try{
|
||||
const cols=await queryConfig(formId,'columns');
|
||||
if(cols&&cols.length>0){
|
||||
const filteredCol = cols.filter(colItem =>
|
||||
columns.some(column => column.dataIndex === colItem.dataIndex)
|
||||
);
|
||||
return filteredCol;
|
||||
}else{
|
||||
return columns;
|
||||
}
|
||||
}catch(e){
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function mergeSearchFormSchema(searchFormSchema: FormSchema[],formId:String){
|
||||
try{
|
||||
const sFormSchema=await queryConfig(formId,'searchFormSchema');
|
||||
if(sFormSchema&&sFormSchema.length>0){
|
||||
const filteredsFormSchema = sFormSchema.filter(sItem =>
|
||||
searchFormSchema.some( searchItem => searchItem.field === sItem.field)
|
||||
);
|
||||
return filteredsFormSchema;
|
||||
}else{
|
||||
return searchFormSchema;
|
||||
}
|
||||
}catch(e){
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function mergeButtons(buttons,formId:String){
|
||||
try{
|
||||
const btns=await queryConfig(formId,'buttons');
|
||||
if(btns&&btns.length>0){
|
||||
return btns;
|
||||
}else{
|
||||
return buttons;
|
||||
}
|
||||
}catch(e){
|
||||
return[];
|
||||
}
|
||||
}
|
||||
|
||||
async function queryConfigByFormPath(componentPath:String,configName:String){
|
||||
if(componentPath==null){
|
||||
notification.error({
|
||||
message: t('提示'),
|
||||
description: `发生异常,组件路径不能为空!`,
|
||||
});
|
||||
throw new Error('发生异常,组件路径不能为空!');
|
||||
}
|
||||
const menu=await getMenuByIdComponentPath("/"+componentPath+"/index");
|
||||
if(menu==null){
|
||||
notification.error({
|
||||
message: t('提示'),
|
||||
description: `发生异常,根据组件路径找不到菜单!`,
|
||||
});
|
||||
throw new Error('发生异常,根据组件路径找不到菜单!');
|
||||
}
|
||||
const formId=menu.formId;
|
||||
|
||||
return await queryConfig(formId,configName);
|
||||
|
||||
}
|
||||
|
||||
async function queryConfig(formId:String,configName:String){
|
||||
const formTemplate = await getFormTemplateUsingCache(formId);
|
||||
const renderConfig=formTemplate.renderConfig;
|
||||
if(renderConfig==null){
|
||||
return null;
|
||||
}
|
||||
const exportMatches = renderConfig.matchAll(/export\s+const\s+(\w+)\s*(?::\s*\w+(?:\[\])?)?\s*=\s*([\s\S]*?)(?=\n\s*export\s+const|\n\s*export\s+function|\n\s*function|$)/g);
|
||||
for (const match of exportMatches) {
|
||||
const varName = match[1];
|
||||
const valueCode = match[2].trim();
|
||||
const cleanCode = valueCode.endsWith(';') ? valueCode.slice(0, -1) : valueCode;
|
||||
if(varName==configName) {
|
||||
try {
|
||||
const value = new Function(`return ${cleanCode}`)();
|
||||
return value;
|
||||
} catch (e) {
|
||||
console.error(`Failed to parse ${varName}:`, e);
|
||||
notification.error({
|
||||
message: t('提示'),
|
||||
description: `解析表单渲染覆盖配置出错[解析${configName}发生异常]`,
|
||||
});
|
||||
throw new Error('解析表单渲染覆盖配置出错,请联系管理员处理');
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
mergeFormSchemas,
|
||||
mergeColumns,
|
||||
mergeSearchFormSchema,
|
||||
mergeButtons,
|
||||
mergeFormEventConfigs
|
||||
};
|
||||
}
|
||||
129
src/hooks/web/useFormEvent.ts
Normal file
129
src/hooks/web/useFormEvent.ts
Normal file
@ -0,0 +1,129 @@
|
||||
import { apiConfigFunc } from '/@/utils/event/design';
|
||||
import { execute } from '/@/api/liteflow';
|
||||
export enum EventType {
|
||||
CREATE = 0, //初始化表单
|
||||
GET, // 获取表单数据
|
||||
LOAD, //加载表单
|
||||
SUBMIT, //提交表单
|
||||
}
|
||||
//初始化表单
|
||||
export async function createFormEvent(
|
||||
formEventConfig,
|
||||
formModels,
|
||||
formRef,
|
||||
schemas,
|
||||
isCustomForm = false,
|
||||
) {
|
||||
try {
|
||||
if (
|
||||
formEventConfig &&
|
||||
formEventConfig[EventType.CREATE] &&
|
||||
Array.isArray(formEventConfig[EventType.CREATE])
|
||||
) {
|
||||
await executeFormEvent(
|
||||
formEventConfig[EventType.CREATE],
|
||||
formModels,
|
||||
formRef,
|
||||
schemas,
|
||||
isCustomForm,
|
||||
);
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
//获取表单数据
|
||||
export async function getFormDataEvent(
|
||||
formEventConfig,
|
||||
formModels,
|
||||
formRef,
|
||||
schemas,
|
||||
isCustomForm = false,
|
||||
) {
|
||||
try {
|
||||
if (
|
||||
formEventConfig &&
|
||||
formEventConfig[EventType.GET] &&
|
||||
Array.isArray(formEventConfig[EventType.GET])
|
||||
) {
|
||||
await executeFormEvent(
|
||||
formEventConfig[EventType.GET],
|
||||
formModels,
|
||||
formRef,
|
||||
schemas,
|
||||
isCustomForm,
|
||||
);
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
//加载表单
|
||||
export async function loadFormEvent(
|
||||
formEventConfig,
|
||||
formModels,
|
||||
formRef,
|
||||
schemas,
|
||||
isCustomForm = false,
|
||||
) {
|
||||
try {
|
||||
if (
|
||||
formEventConfig &&
|
||||
formEventConfig[EventType.LOAD] &&
|
||||
Array.isArray(formEventConfig[EventType.LOAD])
|
||||
) {
|
||||
await executeFormEvent(
|
||||
formEventConfig[EventType.LOAD],
|
||||
formModels,
|
||||
formRef,
|
||||
schemas,
|
||||
isCustomForm,
|
||||
);
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
//提交表单
|
||||
export async function submitFormEvent(
|
||||
formEventConfig,
|
||||
formModels,
|
||||
formRef,
|
||||
schemas,
|
||||
isCustomForm = false,
|
||||
) {
|
||||
try {
|
||||
if (
|
||||
formEventConfig &&
|
||||
formEventConfig[EventType.SUBMIT] &&
|
||||
Array.isArray(formEventConfig[EventType.SUBMIT])
|
||||
) {
|
||||
await executeFormEvent(
|
||||
formEventConfig[EventType.SUBMIT],
|
||||
formModels,
|
||||
formRef,
|
||||
schemas,
|
||||
isCustomForm,
|
||||
);
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
export async function executeFormEvent(
|
||||
formEventConfig,
|
||||
formModel,
|
||||
formRef,
|
||||
schemas,
|
||||
isCustomForm = false,
|
||||
) {
|
||||
if (!formEventConfig.length) return;
|
||||
formEventConfig.map((x) => {
|
||||
x.nodeInfo?.processEvent?.map(async (config) => {
|
||||
if (config.operateType === 'api') {
|
||||
await apiConfigFunc(config.operateConfig, isCustomForm, formModel);
|
||||
} else if (config.operateType === 'liteflow') {
|
||||
await execute(config.operateConfig, formModel);
|
||||
} else if (config.operateType === 'js') {
|
||||
const event = new Function('schema', 'formModel', 'formActionType', config.operateConfig);
|
||||
event(schemas, formModel, formRef);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
28
src/hooks/web/useFullContent.ts
Normal file
28
src/hooks/web/useFullContent.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { computed, unref } from 'vue';
|
||||
|
||||
import { useAppStore } from '/@/store/modules/app';
|
||||
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
/**
|
||||
* @description: Full screen display content
|
||||
*/
|
||||
export const useFullContent = () => {
|
||||
const appStore = useAppStore();
|
||||
const router = useRouter();
|
||||
const { currentRoute } = router;
|
||||
|
||||
// Whether to display the content in full screen without displaying the menu
|
||||
const getFullContent = computed(() => {
|
||||
// Query parameters, the full screen is displayed when the address bar has a full parameter
|
||||
const route = unref(currentRoute);
|
||||
const query = route.query;
|
||||
if (query && Reflect.has(query, '__full__')) {
|
||||
return true;
|
||||
}
|
||||
// Return to the configuration in the configuration file
|
||||
return appStore.getProjectConfig.fullContent;
|
||||
});
|
||||
|
||||
return { getFullContent };
|
||||
};
|
||||
56
src/hooks/web/useI18n.ts
Normal file
56
src/hooks/web/useI18n.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { i18n } from '/@/locales/setupI18n';
|
||||
|
||||
type I18nGlobalTranslation = {
|
||||
(key: string): string;
|
||||
(key: string, locale: string): string;
|
||||
(key: string, locale: string, list: unknown[]): string;
|
||||
(key: string, locale: string, named: Record<string, unknown>): string;
|
||||
(key: string, list: unknown[]): string;
|
||||
(key: string, named: Record<string, unknown>): string;
|
||||
};
|
||||
|
||||
type I18nTranslationRestParameters = [string, any];
|
||||
|
||||
function getKey(namespace: string | undefined, key: string) {
|
||||
if (!namespace) {
|
||||
return key;
|
||||
}
|
||||
if (key.startsWith(namespace)) {
|
||||
return key;
|
||||
}
|
||||
return `${namespace}.${key}`;
|
||||
}
|
||||
|
||||
export function useI18n(namespace?: string): {
|
||||
t: I18nGlobalTranslation;
|
||||
} {
|
||||
const normalFn = {
|
||||
t: (key: string) => {
|
||||
return getKey(namespace, key);
|
||||
},
|
||||
};
|
||||
|
||||
if (!i18n) {
|
||||
return normalFn;
|
||||
}
|
||||
|
||||
const { t, ...methods } = i18n.global;
|
||||
|
||||
const tFn: I18nGlobalTranslation = (key: string, ...arg: any[]) => {
|
||||
if (!key) return '';
|
||||
//TODO 后端获取翻译 注释下一行代码
|
||||
// if (!key.includes('.') && !namespace) return key;
|
||||
return t(getKey(namespace, key), ...(arg as I18nTranslationRestParameters));
|
||||
};
|
||||
return {
|
||||
...methods,
|
||||
t: tFn,
|
||||
};
|
||||
}
|
||||
|
||||
// Why write this function?
|
||||
// Mainly to configure the vscode i18nn ally plugin. This function is only used for routing and menus. Please use useI18n for other places
|
||||
|
||||
// 为什么要编写此函数?
|
||||
// 主要用于配合vscode i18nn ally插件。此功能仅用于路由和菜单。请在其他地方使用useI18n
|
||||
export const t = (key: string) => key;
|
||||
72
src/hooks/web/useLockPage.ts
Normal file
72
src/hooks/web/useLockPage.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import { computed, onUnmounted, unref, watchEffect } from 'vue';
|
||||
import { useThrottleFn } from '@vueuse/core';
|
||||
|
||||
import { useAppStore } from '/@/store/modules/app';
|
||||
import { useLockStore } from '/@/store/modules/lock';
|
||||
|
||||
import { useUserStore } from '/@/store/modules/user';
|
||||
import { useRootSetting } from '../setting/useRootSetting';
|
||||
|
||||
export function useLockPage() {
|
||||
const { getLockTime } = useRootSetting();
|
||||
const lockStore = useLockStore();
|
||||
const userStore = useUserStore();
|
||||
const appStore = useAppStore();
|
||||
|
||||
let timeId: TimeoutHandle;
|
||||
|
||||
function clear(): void {
|
||||
window.clearTimeout(timeId);
|
||||
}
|
||||
|
||||
function resetCalcLockTimeout(): void {
|
||||
// not login
|
||||
if (!userStore.getToken) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
const lockTime = appStore.getProjectConfig.lockTime;
|
||||
if (!lockTime || lockTime < 1) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
clear();
|
||||
|
||||
timeId = setTimeout(() => {
|
||||
lockPage();
|
||||
}, lockTime * 60 * 1000);
|
||||
}
|
||||
|
||||
function lockPage(): void {
|
||||
lockStore.setLockInfo({
|
||||
isLock: true,
|
||||
pwd: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
watchEffect((onClean) => {
|
||||
if (userStore.getToken) {
|
||||
resetCalcLockTimeout();
|
||||
} else {
|
||||
clear();
|
||||
}
|
||||
onClean(() => {
|
||||
clear();
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
clear();
|
||||
});
|
||||
|
||||
const keyupFn = useThrottleFn(resetCalcLockTimeout, 2000);
|
||||
|
||||
return computed(() => {
|
||||
if (unref(getLockTime)) {
|
||||
return { onKeyup: keyupFn, onMousemove: keyupFn };
|
||||
} else {
|
||||
clear();
|
||||
return {};
|
||||
}
|
||||
});
|
||||
}
|
||||
123
src/hooks/web/useMessage.tsx
Normal file
123
src/hooks/web/useMessage.tsx
Normal file
@ -0,0 +1,123 @@
|
||||
import type { ModalFunc, ModalFuncProps } from 'ant-design-vue/lib/modal/Modal';
|
||||
|
||||
import { Modal, message as Message, notification } from 'ant-design-vue';
|
||||
import { InfoCircleFilled, CheckCircleFilled, CloseCircleFilled } from '@ant-design/icons-vue';
|
||||
|
||||
import { NotificationArgsProps, ConfigProps } from 'ant-design-vue/lib/notification';
|
||||
import { useI18n } from './useI18n';
|
||||
import { isString } from '/@/utils/is';
|
||||
|
||||
export interface NotifyApi {
|
||||
info(config: NotificationArgsProps): void;
|
||||
success(config: NotificationArgsProps): void;
|
||||
error(config: NotificationArgsProps): void;
|
||||
warn(config: NotificationArgsProps): void;
|
||||
warning(config: NotificationArgsProps): void;
|
||||
open(args: NotificationArgsProps): void;
|
||||
close(key: String): void;
|
||||
config(options: ConfigProps): void;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
export declare type NotificationPlacement = 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight';
|
||||
export declare type IconType = 'success' | 'info' | 'error' | 'warning';
|
||||
export interface ModalOptionsEx extends Omit<ModalFuncProps, 'iconType'> {
|
||||
iconType: 'warning' | 'success' | 'error' | 'info';
|
||||
}
|
||||
export type ModalOptionsPartial = Partial<ModalOptionsEx> & Pick<ModalOptionsEx, 'content'>;
|
||||
|
||||
interface ConfirmOptions {
|
||||
info: ModalFunc;
|
||||
success: ModalFunc;
|
||||
error: ModalFunc;
|
||||
warn: ModalFunc;
|
||||
warning: ModalFunc;
|
||||
}
|
||||
|
||||
function getIcon(iconType: string) {
|
||||
if (iconType === 'warning') {
|
||||
return <InfoCircleFilled class="modal-icon-warning" />;
|
||||
} else if (iconType === 'success') {
|
||||
return <CheckCircleFilled class="modal-icon-success" />;
|
||||
} else if (iconType === 'info') {
|
||||
return <InfoCircleFilled class="modal-icon-info" />;
|
||||
} else {
|
||||
return <CloseCircleFilled class="modal-icon-error" />;
|
||||
}
|
||||
}
|
||||
|
||||
function renderContent({ content }: Pick<ModalOptionsEx, 'content'>) {
|
||||
if (isString(content)) {
|
||||
return <div innerHTML={`<div>${content as string}</div>`}></div>;
|
||||
} else {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Create confirmation box
|
||||
*/
|
||||
function createConfirm(options: ModalOptionsEx): ConfirmOptions {
|
||||
const iconType = options.iconType || 'warning';
|
||||
Reflect.deleteProperty(options, 'iconType');
|
||||
const opt: ModalFuncProps = {
|
||||
centered: true,
|
||||
icon: getIcon(iconType),
|
||||
...options,
|
||||
content: renderContent(options),
|
||||
};
|
||||
return Modal.confirm(opt) as unknown as ConfirmOptions;
|
||||
}
|
||||
|
||||
const getBaseOptions = () => {
|
||||
const { t } = useI18n();
|
||||
return {
|
||||
okText: t('确认'),
|
||||
centered: true,
|
||||
};
|
||||
};
|
||||
|
||||
function createModalOptions(options: ModalOptionsPartial, icon: string): ModalOptionsPartial {
|
||||
return {
|
||||
...getBaseOptions(),
|
||||
...options,
|
||||
content: renderContent(options),
|
||||
icon: getIcon(icon),
|
||||
};
|
||||
}
|
||||
|
||||
function createSuccessModal(options: ModalOptionsPartial) {
|
||||
return Modal.success(createModalOptions(options, 'success'));
|
||||
}
|
||||
|
||||
function createErrorModal(options: ModalOptionsPartial) {
|
||||
return Modal.error(createModalOptions(options, 'close'));
|
||||
}
|
||||
|
||||
function createInfoModal(options: ModalOptionsPartial) {
|
||||
return Modal.info(createModalOptions(options, 'info'));
|
||||
}
|
||||
|
||||
function createWarningModal(options: ModalOptionsPartial) {
|
||||
return Modal.warning(createModalOptions(options, 'warning'));
|
||||
}
|
||||
|
||||
notification.config({
|
||||
placement: 'topRight',
|
||||
duration: 3,
|
||||
});
|
||||
|
||||
/**
|
||||
* @description: message
|
||||
*/
|
||||
export function useMessage() {
|
||||
return {
|
||||
createMessage: Message,
|
||||
notification: notification as NotifyApi,
|
||||
createConfirm: createConfirm,
|
||||
createSuccessModal,
|
||||
createErrorModal,
|
||||
createInfoModal,
|
||||
createWarningModal,
|
||||
};
|
||||
}
|
||||
62
src/hooks/web/usePage.ts
Normal file
62
src/hooks/web/usePage.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import type { RouteLocationRaw, Router } from 'vue-router';
|
||||
|
||||
import { PageEnum } from '/@/enums/pageEnum';
|
||||
import { isString } from '/@/utils/is';
|
||||
import { unref } from 'vue';
|
||||
|
||||
import { useRouter } from 'vue-router';
|
||||
import { REDIRECT_NAME } from '/@/router/constant';
|
||||
|
||||
export type RouteLocationRawEx = Omit<RouteLocationRaw, 'path'> & { path: PageEnum };
|
||||
|
||||
function handleError(e: Error) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
// page switch
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: redo current page
|
||||
*/
|
||||
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 === REDIRECT_NAME) {
|
||||
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: REDIRECT_NAME, params, query }).then(() => resolve(true));
|
||||
});
|
||||
}
|
||||
return redo;
|
||||
};
|
||||
34
src/hooks/web/usePagination.ts
Normal file
34
src/hooks/web/usePagination.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import type { Ref } from 'vue';
|
||||
import { ref, unref, computed } from 'vue';
|
||||
|
||||
function pagination<T = any>(list: T[], pageNo: number, pageSize: number): T[] {
|
||||
const offset = (pageNo - 1) * Number(pageSize);
|
||||
const ret =
|
||||
offset + Number(pageSize) >= list.length
|
||||
? list.slice(offset, list.length)
|
||||
: list.slice(offset, offset + Number(pageSize));
|
||||
return ret;
|
||||
}
|
||||
|
||||
export function usePagination<T = any>(list: Ref<T[]>, pageSize: number) {
|
||||
const currentPage = ref(1);
|
||||
const pageSizeRef = ref(pageSize);
|
||||
|
||||
const getPaginationList = computed(() => {
|
||||
return pagination(unref(list), unref(currentPage), unref(pageSizeRef));
|
||||
});
|
||||
|
||||
const getTotal = computed(() => {
|
||||
return unref(list).length;
|
||||
});
|
||||
|
||||
function setCurrentPage(page: number) {
|
||||
currentPage.value = page;
|
||||
}
|
||||
|
||||
function setPageSize(pageSize: number) {
|
||||
pageSizeRef.value = pageSize;
|
||||
}
|
||||
|
||||
return { setCurrentPage, getTotal, setPageSize, getPaginationList };
|
||||
}
|
||||
279
src/hooks/web/usePermission.ts
Normal file
279
src/hooks/web/usePermission.ts
Normal file
@ -0,0 +1,279 @@
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { useAppStore } from '/@/store/modules/app';
|
||||
import { usePermissionStore } from '/@/store/modules/permission';
|
||||
import { useUserStore } from '/@/store/modules/user';
|
||||
|
||||
import { useTabs } from './useTabs';
|
||||
|
||||
import { router, resetRouter } from '/@/router';
|
||||
// import { RootRoute } from '/@/router/routes';
|
||||
|
||||
import projectSetting from '/@/settings/projectSetting';
|
||||
import { PermissionModeEnum } from '/@/enums/appEnum';
|
||||
import { RoleEnum } from '/@/enums/roleEnum';
|
||||
|
||||
import { intersection } from 'lodash-es';
|
||||
import { isArray } from '/@/utils/is';
|
||||
import { useMultipleTabStore } from '/@/store/modules/multipleTab';
|
||||
import { MenuAuthModel } from '/@/api/system/login/model';
|
||||
import { BasicColumn, FormSchema } from '/@/components/Table';
|
||||
import { toRaw } from 'vue';
|
||||
import { camelCaseString } from '/@/utils/event/design';
|
||||
import { ButtonConfig } from '/@/model/generator/listConfig';
|
||||
|
||||
// User permissions related operations
|
||||
export function usePermission() {
|
||||
const userStore = useUserStore();
|
||||
const appStore = useAppStore();
|
||||
//获取到当前路由
|
||||
const currentRoute = router.currentRoute;
|
||||
const permissionStore = usePermissionStore();
|
||||
const { closeAll } = useTabs(router);
|
||||
|
||||
/**
|
||||
* Change permission mode
|
||||
*/
|
||||
async function togglePermissionMode() {
|
||||
appStore.setProjectConfig({
|
||||
permissionMode:
|
||||
projectSetting.permissionMode === PermissionModeEnum.BACK
|
||||
? PermissionModeEnum.ROUTE_MAPPING
|
||||
: PermissionModeEnum.BACK,
|
||||
});
|
||||
location.reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset and regain authority resource information
|
||||
* @param id
|
||||
*/
|
||||
async function resume() {
|
||||
const tabStore = useMultipleTabStore();
|
||||
tabStore.clearCacheTabs();
|
||||
resetRouter();
|
||||
const routes = await permissionStore.buildRoutesAction();
|
||||
routes.forEach((route) => {
|
||||
router.addRoute(route as unknown as RouteRecordRaw);
|
||||
});
|
||||
permissionStore.setLastBuildMenuTime();
|
||||
closeAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether there is permission
|
||||
*/
|
||||
function hasPermission(value?: RoleEnum | RoleEnum[] | string | string[], def = true): boolean {
|
||||
// Visible by default
|
||||
if (!value) {
|
||||
return def;
|
||||
}
|
||||
|
||||
const permMode = projectSetting.permissionMode;
|
||||
|
||||
if ([PermissionModeEnum.ROUTE_MAPPING, PermissionModeEnum.ROLE].includes(permMode)) {
|
||||
if (!isArray(value)) {
|
||||
return userStore.getRoleList?.includes(value as RoleEnum);
|
||||
}
|
||||
return (intersection(value, userStore.getRoleList) as RoleEnum[]).length > 0;
|
||||
}
|
||||
|
||||
if (PermissionModeEnum.BACK === permMode) {
|
||||
const allPerm = permissionStore.getPermCodeList as MenuAuthModel[];
|
||||
const { menuId } = currentRoute.value.meta;
|
||||
const thisMenuAuth = allPerm.find((p) => p.menuId === menuId);
|
||||
return thisMenuAuth?.buttonAuthCode?.includes(value as string) || false;
|
||||
// if (!isArray(allPerm)) {
|
||||
// const { menuId } = currentRoute.value.meta;
|
||||
|
||||
// const auth = allPerm.find((p) => p.menuId === menuId);
|
||||
// return allCodeList.includes(value);
|
||||
// }
|
||||
// return (intersection(value, allCodeList) as string[]).length > 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change roles
|
||||
* @param roles
|
||||
*/
|
||||
async function changeRole(roles: RoleEnum | RoleEnum[]): Promise<void> {
|
||||
if (projectSetting.permissionMode !== PermissionModeEnum.ROUTE_MAPPING) {
|
||||
throw new Error(
|
||||
'Please switch PermissionModeEnum to ROUTE_MAPPING mode in the configuration to operate!',
|
||||
);
|
||||
}
|
||||
|
||||
if (!isArray(roles)) {
|
||||
roles = [roles];
|
||||
}
|
||||
userStore.setRoleList(roles);
|
||||
await resume();
|
||||
}
|
||||
|
||||
/**
|
||||
* refresh menu data
|
||||
*/
|
||||
async function refreshMenu() {
|
||||
resume();
|
||||
}
|
||||
|
||||
/**
|
||||
* refresh menu data
|
||||
*/
|
||||
async function changeMenu() {}
|
||||
|
||||
/**
|
||||
* button
|
||||
* @param buttonList 未过滤的
|
||||
* @returns 过滤后的
|
||||
*/
|
||||
function filterButtonAuth(buttonList: ButtonConfig[]) {
|
||||
//获取到当前菜单id
|
||||
const menuId = currentRoute.value.meta.menuId;
|
||||
const perm = permissionStore.getPermCodeList.find((x) => x.menuId === menuId);
|
||||
if (!perm) return buttonList;
|
||||
let buttonAuth = perm?.buttonAuthCode || [];
|
||||
if (!buttonAuth || buttonAuth?.length === 0) {
|
||||
return [];
|
||||
}
|
||||
buttonAuth = buttonAuth.map((x) => {
|
||||
const btnList = x.split(':');
|
||||
btnList.shift();
|
||||
return btnList.join('');
|
||||
});
|
||||
const result = buttonList.filter((btn) => buttonAuth.includes(btn.code));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* BasicColumn
|
||||
* @param columns 未过滤的
|
||||
* @param convertCamel 是否需要转驼峰
|
||||
* @returns 过滤后的
|
||||
*/
|
||||
function filterColumnAuth(columns: BasicColumn[], convertCamel = false): BasicColumn[] {
|
||||
//获取到当前菜单id
|
||||
const menuId = currentRoute.value.meta.menuId;
|
||||
const perm = permissionStore.getPermCodeList.find((x) => x.menuId === menuId);
|
||||
|
||||
if (!perm) return columns;
|
||||
|
||||
let columnAuth = perm?.columnAuthCode || [];
|
||||
if (!columnAuth || columnAuth?.length === 0) {
|
||||
return [];
|
||||
}
|
||||
if (convertCamel) {
|
||||
columnAuth = columnAuth.map((x) => camelCaseString(x)!);
|
||||
}
|
||||
const result = columns.filter(
|
||||
(col) => col.dataIndex && toRaw(columnAuth).includes(col.dataIndex as string),
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* FormSchema权限过滤
|
||||
* @param formSchema 未过滤的FormSchema
|
||||
* @param convertCamel 是否需要转换驼峰
|
||||
* @returns 返回过滤过的FormSchema
|
||||
*/
|
||||
function filterFormSchemaAuth(formSchema: FormSchema[], convertCamel = false): FormSchema[] {
|
||||
const menuId = currentRoute.value.meta.menuId;
|
||||
const perm = permissionStore.getPermCodeList.find((x) => x.menuId === menuId);
|
||||
if (!perm) return formSchema;
|
||||
let formAuth: any[] = perm?.formAuthCode || [];
|
||||
if (!formAuth || formAuth?.length === 0) {
|
||||
return [];
|
||||
}
|
||||
if (convertCamel) {
|
||||
formAuth = formAuth.map((x) => {
|
||||
if (typeof x === 'object' && !!Object.keys(x)[0]) {
|
||||
const subformField = camelCaseString(Object.keys(x)[0])!;
|
||||
x[subformField!] = x[subformField!]?.map((field) => camelCaseString(field));
|
||||
return {
|
||||
[subformField]: x[subformField!],
|
||||
};
|
||||
} else {
|
||||
return camelCaseString(x)!;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const result = formSchema.filter((schema) => {
|
||||
return findLayoutComponent(schema, formAuth);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function findLayoutComponent(schema, formAuth) {
|
||||
if (['card', 'tab', 'grid'].includes(schema.type)) {
|
||||
if (schema.children && schema.children.length > 0) {
|
||||
return schema.children.filter((children) => {
|
||||
children.list = children.list.filter((x) => findLayoutComponent(x, formAuth));
|
||||
return children.list;
|
||||
});
|
||||
}
|
||||
} else if (schema.type === 'table-layout') {
|
||||
if (schema.children && schema.children.length > 0) {
|
||||
return schema.children.filter((children) => {
|
||||
children.list = children.list.filter((chil) => {
|
||||
chil.children = chil.children.filter((x) => findLayoutComponent(x, formAuth));
|
||||
return chil.children;
|
||||
});
|
||||
return children.list;
|
||||
});
|
||||
}
|
||||
} else if (schema.type === 'form') {
|
||||
if (schema.componentProps.columns && schema.componentProps.columns.length) {
|
||||
const subformField = schema.field.substring(0, schema.field.length - 4);
|
||||
const currentSubFormAuth = formAuth.find((x) => {
|
||||
return typeof x === 'object' && Object.keys(x)[0] === subformField;
|
||||
});
|
||||
if (!currentSubFormAuth) {
|
||||
return false;
|
||||
} else {
|
||||
schema.componentProps.columns = schema.componentProps.columns.filter((x) => {
|
||||
return x.componentType
|
||||
? findLayoutComponent(x, Object.values(currentSubFormAuth)[0])
|
||||
: true;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (schema.type === 'one-for-one') {
|
||||
if (schema.componentProps.childSchemas && schema.componentProps.childSchemas.length) {
|
||||
const subformField = schema.field.substring(0, schema.field.length - 4);
|
||||
const currentSubFormAuth = formAuth.find((x) => {
|
||||
return typeof x === 'object' && Object.keys(x)[0] === subformField;
|
||||
});
|
||||
if (!currentSubFormAuth) {
|
||||
return false;
|
||||
} else {
|
||||
schema.componentProps.childSchemas = schema.componentProps.childSchemas.filter((x) => {
|
||||
return x.component
|
||||
? findLayoutComponent(x, Object.values(currentSubFormAuth)[0])
|
||||
: true;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const field = schema.field || schema.dataIndex || schema.key;
|
||||
return formAuth?.includes(field) || schema.ignorePermission;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
changeRole,
|
||||
hasPermission,
|
||||
togglePermissionMode,
|
||||
refreshMenu,
|
||||
changeMenu,
|
||||
filterButtonAuth,
|
||||
filterColumnAuth,
|
||||
filterFormSchemaAuth,
|
||||
};
|
||||
}
|
||||
46
src/hooks/web/useScript.ts
Normal file
46
src/hooks/web/useScript.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { onMounted, onUnmounted, ref } from 'vue';
|
||||
|
||||
interface ScriptOptions {
|
||||
src: string;
|
||||
}
|
||||
|
||||
export function useScript(opts: ScriptOptions) {
|
||||
const isLoading = ref(false);
|
||||
const error = ref(false);
|
||||
const success = ref(false);
|
||||
let script: HTMLScriptElement;
|
||||
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
onMounted(() => {
|
||||
script = document.createElement('script');
|
||||
script.type = 'text/javascript';
|
||||
script.onload = function () {
|
||||
isLoading.value = false;
|
||||
success.value = true;
|
||||
error.value = false;
|
||||
resolve('');
|
||||
};
|
||||
|
||||
script.onerror = function (err) {
|
||||
isLoading.value = false;
|
||||
success.value = false;
|
||||
error.value = true;
|
||||
reject(err);
|
||||
};
|
||||
|
||||
script.src = opts.src;
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
script && script.remove();
|
||||
});
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
error,
|
||||
success,
|
||||
toPromise: () => promise,
|
||||
};
|
||||
}
|
||||
21
src/hooks/web/useSortable.ts
Normal file
21
src/hooks/web/useSortable.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { nextTick, unref } from 'vue';
|
||||
import type { Ref } from 'vue';
|
||||
import type { Options } from 'sortablejs';
|
||||
|
||||
export function useSortable(el: HTMLElement | Ref<HTMLElement>, options?: Options) {
|
||||
function initSortable() {
|
||||
nextTick(async () => {
|
||||
if (!el) return;
|
||||
|
||||
const Sortable = (await import('sortablejs')).default;
|
||||
Sortable.create(unref(el), {
|
||||
animation: 500,
|
||||
delay: 400,
|
||||
delayOnTouchOnly: true,
|
||||
...options,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return { initSortable };
|
||||
}
|
||||
103
src/hooks/web/useTabs.ts
Normal file
103
src/hooks/web/useTabs.ts
Normal file
@ -0,0 +1,103 @@
|
||||
import type { RouteLocationNormalized, Router } from 'vue-router';
|
||||
|
||||
import { useRouter } from 'vue-router';
|
||||
import { unref } from 'vue';
|
||||
|
||||
import { useMultipleTabStore } from '/@/store/modules/multipleTab';
|
||||
import { useAppStore } from '/@/store/modules/app';
|
||||
|
||||
enum TableActionEnum {
|
||||
REFRESH,
|
||||
CLOSE_ALL,
|
||||
CLOSE_LEFT,
|
||||
CLOSE_RIGHT,
|
||||
CLOSE_OTHER,
|
||||
CLOSE_CURRENT,
|
||||
CLOSE,
|
||||
}
|
||||
|
||||
export function useTabs(_router?: Router) {
|
||||
const appStore = useAppStore();
|
||||
|
||||
function canIUseTabs(): boolean {
|
||||
const { show } = appStore.getMultiTabsSetting;
|
||||
if (!show) {
|
||||
throw new Error('The multi-tab page is currently not open, please open it in the settings!');
|
||||
}
|
||||
return !!show;
|
||||
}
|
||||
|
||||
const tabStore = useMultipleTabStore();
|
||||
const router = _router || useRouter();
|
||||
|
||||
const { currentRoute } = router;
|
||||
|
||||
function getCurrentTab() {
|
||||
const route = unref(currentRoute);
|
||||
return tabStore.getTabList.find((item) => item.fullPath === route.fullPath)!;
|
||||
}
|
||||
|
||||
async function updateTabTitle(title: string, tab?: RouteLocationNormalized) {
|
||||
const canIUse = canIUseTabs;
|
||||
if (!canIUse) {
|
||||
return;
|
||||
}
|
||||
const targetTab = tab || getCurrentTab();
|
||||
await tabStore.setTabTitle(title, targetTab);
|
||||
}
|
||||
|
||||
async function updateTabPath(path: string, tab?: RouteLocationNormalized) {
|
||||
const canIUse = canIUseTabs;
|
||||
if (!canIUse) {
|
||||
return;
|
||||
}
|
||||
const targetTab = tab || getCurrentTab();
|
||||
await tabStore.updateTabPath(path, targetTab);
|
||||
}
|
||||
|
||||
async function handleTabAction(action: TableActionEnum, tab?: RouteLocationNormalized) {
|
||||
const canIUse = canIUseTabs;
|
||||
if (!canIUse) {
|
||||
return;
|
||||
}
|
||||
const currentTab = getCurrentTab();
|
||||
switch (action) {
|
||||
case TableActionEnum.REFRESH:
|
||||
await tabStore.refreshPage(router);
|
||||
break;
|
||||
|
||||
case TableActionEnum.CLOSE_ALL:
|
||||
await tabStore.closeAllTab(router);
|
||||
break;
|
||||
|
||||
case TableActionEnum.CLOSE_LEFT:
|
||||
await tabStore.closeLeftTabs(currentTab, router);
|
||||
break;
|
||||
|
||||
case TableActionEnum.CLOSE_RIGHT:
|
||||
await tabStore.closeRightTabs(currentTab, router);
|
||||
break;
|
||||
|
||||
case TableActionEnum.CLOSE_OTHER:
|
||||
await tabStore.closeOtherTabs(currentTab, router);
|
||||
break;
|
||||
|
||||
case TableActionEnum.CLOSE_CURRENT:
|
||||
case TableActionEnum.CLOSE:
|
||||
await tabStore.closeTab(tab || currentTab, router);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
refreshPage: () => handleTabAction(TableActionEnum.REFRESH),
|
||||
closeAll: () => handleTabAction(TableActionEnum.CLOSE_ALL),
|
||||
closeLeft: () => handleTabAction(TableActionEnum.CLOSE_LEFT),
|
||||
closeRight: () => handleTabAction(TableActionEnum.CLOSE_RIGHT),
|
||||
closeOther: () => handleTabAction(TableActionEnum.CLOSE_OTHER),
|
||||
closeCurrent: () => handleTabAction(TableActionEnum.CLOSE_CURRENT),
|
||||
close: (tab?: RouteLocationNormalized) => handleTabAction(TableActionEnum.CLOSE, tab),
|
||||
setTitle: (title: string, tab?: RouteLocationNormalized) => updateTabTitle(title, tab),
|
||||
updatePath: (fullPath: string, tab?: RouteLocationNormalized) => updateTabPath(fullPath, tab),
|
||||
};
|
||||
}
|
||||
38
src/hooks/web/useTitle.ts
Normal file
38
src/hooks/web/useTitle.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { watch, unref, ref } from 'vue';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { useTitle as usePageTitle } from '@vueuse/core';
|
||||
import { useGlobSetting } from '/@/hooks/setting';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useLocaleStore } from '/@/store/modules/locale';
|
||||
import { useAppStore } from '/@/store/modules/app';
|
||||
import { REDIRECT_NAME } from '/@/router/constant';
|
||||
|
||||
/**
|
||||
* Listening to page changes and dynamically changing site titles
|
||||
*/
|
||||
export function useTitle() {
|
||||
const title = import.meta.env.VITE_GLOB_APP_TITLE;
|
||||
const { t } = useI18n();
|
||||
const { currentRoute } = useRouter();
|
||||
const localeStore = useLocaleStore();
|
||||
const appStore = useAppStore();
|
||||
const pageTitle = usePageTitle();
|
||||
|
||||
watch(
|
||||
[() => currentRoute.value.path, () => localeStore.getLocale],
|
||||
() => {
|
||||
const route = unref(currentRoute);
|
||||
|
||||
if (route.name === REDIRECT_NAME) {
|
||||
return;
|
||||
}
|
||||
|
||||
const metaTitle=typeof route?.meta?.title=='function'?route.meta.title(route):route?.meta?.title;
|
||||
const tTitle = t(metaTitle);
|
||||
pageTitle.value = tTitle
|
||||
? ` ${tTitle} - ${title} `
|
||||
: `${title}`;
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
}
|
||||
100
src/hooks/web/useWatermark.ts
Normal file
100
src/hooks/web/useWatermark.ts
Normal file
@ -0,0 +1,100 @@
|
||||
import { getCurrentInstance, onBeforeUnmount, ref, Ref, shallowRef, unref } from 'vue';
|
||||
import { useRafThrottle } from '/@/utils/domUtils';
|
||||
import { addResizeListener, removeResizeListener } from '/@/utils/event';
|
||||
import { isDef } from '/@/utils/is';
|
||||
|
||||
const domSymbol = Symbol('watermark-dom');
|
||||
|
||||
export function useWatermark(
|
||||
appendEl: Ref<HTMLElement | null> = ref(document.body) as Ref<HTMLElement>,
|
||||
) {
|
||||
const func = useRafThrottle(function () {
|
||||
const el = unref(appendEl);
|
||||
if (!el) return;
|
||||
const { clientHeight: height, clientWidth: width } = el;
|
||||
updateWatermark({ height, width });
|
||||
});
|
||||
const id = domSymbol.toString();
|
||||
const watermarkEl = shallowRef<HTMLElement>();
|
||||
|
||||
const clear = () => {
|
||||
const domId = unref(watermarkEl);
|
||||
watermarkEl.value = undefined;
|
||||
const el = unref(appendEl);
|
||||
if (!el) return;
|
||||
domId && el.removeChild(domId);
|
||||
removeResizeListener(el, func);
|
||||
};
|
||||
|
||||
function createBase64(str: string) {
|
||||
const can = document.createElement('canvas');
|
||||
const width = 300;
|
||||
const height = 240;
|
||||
Object.assign(can, { width, height });
|
||||
|
||||
const cans = can.getContext('2d');
|
||||
if (cans) {
|
||||
cans.rotate((-20 * Math.PI) / 120);
|
||||
cans.font = '15px Vedana';
|
||||
cans.fillStyle = 'rgba(0, 0, 0, 0.15)';
|
||||
cans.textAlign = 'left';
|
||||
cans.textBaseline = 'middle';
|
||||
cans.fillText(str, width / 20, height);
|
||||
}
|
||||
return can.toDataURL('image/png');
|
||||
}
|
||||
|
||||
function updateWatermark(
|
||||
options: {
|
||||
width?: number;
|
||||
height?: number;
|
||||
str?: string;
|
||||
} = {},
|
||||
) {
|
||||
const el = unref(watermarkEl);
|
||||
if (!el) return;
|
||||
if (isDef(options.width)) {
|
||||
el.style.width = `${options.width}px`;
|
||||
}
|
||||
if (isDef(options.height)) {
|
||||
el.style.height = `${options.height}px`;
|
||||
}
|
||||
if (isDef(options.str)) {
|
||||
el.style.background = `url(${createBase64(options.str)}) left top repeat`;
|
||||
}
|
||||
}
|
||||
|
||||
const createWatermark = (str: string) => {
|
||||
if (unref(watermarkEl)) {
|
||||
updateWatermark({ str });
|
||||
return id;
|
||||
}
|
||||
const div = document.createElement('div');
|
||||
watermarkEl.value = div;
|
||||
div.id = id;
|
||||
div.style.pointerEvents = 'none';
|
||||
div.style.top = '0px';
|
||||
div.style.left = '0px';
|
||||
div.style.position = 'absolute';
|
||||
div.style.zIndex = '100000';
|
||||
const el = unref(appendEl);
|
||||
if (!el) return id;
|
||||
const { clientHeight: height, clientWidth: width } = el;
|
||||
updateWatermark({ str, width, height });
|
||||
el.appendChild(div);
|
||||
return id;
|
||||
};
|
||||
|
||||
function setWatermark(str: string) {
|
||||
createWatermark(str);
|
||||
addResizeListener(document.documentElement, func);
|
||||
const instance = getCurrentInstance();
|
||||
if (instance) {
|
||||
onBeforeUnmount(() => {
|
||||
clear();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { setWatermark, clear };
|
||||
}
|
||||
509
src/hooks/web/useWorkFlowForm.ts
Normal file
509
src/hooks/web/useWorkFlowForm.ts
Normal file
@ -0,0 +1,509 @@
|
||||
import { buildOption } from '/@/utils/helper/designHelper';
|
||||
import { FormProps } from '/@/components/Form';
|
||||
import { separator } from '/@/views/workflow/design/bpmn/config/info';
|
||||
import { TaskApproveOpinion, WorkFlowFormParams } from '/@/model/workflow/bpmnConfig';
|
||||
import { FormConfigItem } from '/@/model/workflow/formSetting';
|
||||
import { FormSchema } from '/@/components/Form';
|
||||
import { TableItem } from '/@/model/workflow/formSetting';
|
||||
import { disableTypes, hiddenComponentType, requiredDisabled } from '/@bpmn/config/formPermission';
|
||||
/*
|
||||
**根据权限 修改JSON配置**
|
||||
data{
|
||||
formJson 表单json配置
|
||||
formConfigChildren 工作流节点表单权限配置
|
||||
formConfigKey 唯一节点表单key
|
||||
opinions 意见簿意见
|
||||
opinionsComponents 意见簿组件
|
||||
}
|
||||
isViewProcess 是否查看流程
|
||||
uploadComponentIds: 所有上传文件的文件夹id 【因为流程需要统计每个节点的附件汇总】
|
||||
*/
|
||||
export function changeFormJson(
|
||||
data: {
|
||||
formJson: any; // 表单json配置 FormJson | FormProps
|
||||
formConfigChildren: Array<FormConfigItem>; // 工作流节点表单权限配置
|
||||
formConfigKey: String; //唯一节点表单key
|
||||
opinions?: Array<TaskApproveOpinion> | undefined; //意见簿意见
|
||||
opinionsComponents?: Array<string> | undefined; //意见簿组件
|
||||
},
|
||||
isViewProcess: Boolean,
|
||||
uploadComponentIds: Array<string>,
|
||||
) {
|
||||
const returnData = handlerFormPermission(
|
||||
data.formJson.list ? buildOption(data.formJson, false) : data.formJson,
|
||||
data.formConfigChildren,
|
||||
data.formConfigKey,
|
||||
isViewProcess,
|
||||
{ uploadComponentIds, opinions: data.opinions, opinionsComponents: data.opinionsComponents },
|
||||
);
|
||||
// uploadComponent 带上所有上传文件的文件夹id
|
||||
return returnData;
|
||||
}
|
||||
// 获取工作流表单权限map
|
||||
/*
|
||||
返回Map
|
||||
[
|
||||
字段唯一Key:{
|
||||
{
|
||||
edit:是否可编辑权限,
|
||||
view:是否可查看权限,
|
||||
disabled:是否禁用权限,
|
||||
children:[] 子表数据
|
||||
}
|
||||
}
|
||||
]
|
||||
*/
|
||||
export function getPermissionConfigMap(formConfigChildren) {
|
||||
const map = new Map();
|
||||
formConfigChildren.forEach((element) => {
|
||||
map.set(element.key, element);
|
||||
});
|
||||
|
||||
return map;
|
||||
}
|
||||
function handlerFormPermission(
|
||||
buildOptionJson: FormProps,
|
||||
formConfigChildren,
|
||||
formKey,
|
||||
isViewProcess,
|
||||
otherParams,
|
||||
) {
|
||||
const permissionConfigMap = getPermissionConfigMap(formConfigChildren);
|
||||
const formShow: Number = 0; //表单是否有显示的组件,有则大于0
|
||||
buildOptionJson.schemas = schemeList(
|
||||
buildOptionJson.schemas,
|
||||
permissionConfigMap,
|
||||
formKey,
|
||||
formShow,
|
||||
isViewProcess,
|
||||
otherParams,
|
||||
);
|
||||
return {
|
||||
buildOptionJson,
|
||||
uploadComponentIds: otherParams.uploadComponentIds,
|
||||
};
|
||||
}
|
||||
|
||||
function schemeList(
|
||||
schemas,
|
||||
permissionConfigMap,
|
||||
formKey,
|
||||
formShow,
|
||||
isViewProcess,
|
||||
otherParams,
|
||||
tableName?,
|
||||
) {
|
||||
schemas = schemas.map((schema) => {
|
||||
if (['Card', 'Grid', 'Tab'].includes(schema.component)) {
|
||||
formShow += 1;
|
||||
if (schema.children && schema.children.length > 0) {
|
||||
schema.children.forEach((ele2) => {
|
||||
if (ele2.list && ele2.list.length > 0) {
|
||||
ele2.list = schemeList(
|
||||
ele2.list,
|
||||
permissionConfigMap,
|
||||
formKey,
|
||||
formShow,
|
||||
isViewProcess,
|
||||
otherParams,
|
||||
tableName,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (schema.component === 'TableLayout') {
|
||||
formShow += 1;
|
||||
if (schema.children && schema.children.length > 0) {
|
||||
schema.children.forEach((ele2) => {
|
||||
if (ele2.list && ele2.list.length > 0) {
|
||||
ele2.list.forEach((ele3) => {
|
||||
if (ele3.children && ele3.children.length > 0) {
|
||||
ele3.children = schemeList(
|
||||
ele3.children,
|
||||
permissionConfigMap,
|
||||
formKey,
|
||||
formShow,
|
||||
isViewProcess,
|
||||
otherParams,
|
||||
tableName,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (schema.component == 'SubForm') {
|
||||
if (
|
||||
schema.componentProps &&
|
||||
schema.componentProps.columns &&
|
||||
schema.componentProps.columns.length > 0
|
||||
) {
|
||||
const permissionConfig = permissionConfigMap.has(schema.key)
|
||||
? permissionConfigMap.get(schema.key)
|
||||
: null;
|
||||
|
||||
// 查看流程
|
||||
if (isViewProcess) {
|
||||
schema.dynamicDisabled = true;
|
||||
}
|
||||
if ((permissionConfig?.children || []).find((item) => item.fieldId === '_row_ctrl_' && !item.edit)) {
|
||||
schema.componentProps.disableAddRow = true;
|
||||
}
|
||||
if (!permissionConfig?.view) {
|
||||
schema.show = false;
|
||||
} else {
|
||||
formShow += 1;
|
||||
if (!permissionConfig?.edit) {
|
||||
schema.dynamicDisabled = true;
|
||||
}
|
||||
}
|
||||
schema.componentProps.columns = schema.componentProps.columns.map((ele3) => {
|
||||
const tableItemPermissionConfig = permissionConfig.children.find((x) => {
|
||||
if (x.key) {
|
||||
return x.key === ele3.key;
|
||||
}
|
||||
if (x.fieldId) {
|
||||
return x.fieldId === ele3.dataIndex;
|
||||
}
|
||||
});
|
||||
|
||||
return getSchemePermissionItem(
|
||||
ele3,
|
||||
tableItemPermissionConfig,
|
||||
formKey,
|
||||
isViewProcess,
|
||||
otherParams,
|
||||
);
|
||||
});
|
||||
}
|
||||
} else if (schema.component == 'OneForOne') {
|
||||
const permissionConfig = permissionConfigMap.has(schema.key)
|
||||
? permissionConfigMap.get(schema.key)
|
||||
: null;
|
||||
if (!permissionConfig?.view) {
|
||||
schema.show = false;
|
||||
} else {
|
||||
formShow += 1;
|
||||
}
|
||||
if (schema.componentProps?.childSchemas?.length > 0) {
|
||||
const onePermissionConfigMap = getPermissionConfigMap(permissionConfig.children);
|
||||
schema.componentProps.childSchemas = schemeList(
|
||||
schema.componentProps.childSchemas,
|
||||
onePermissionConfigMap,
|
||||
formKey,
|
||||
formShow,
|
||||
isViewProcess,
|
||||
otherParams,
|
||||
schema.field,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const permissionConfig = permissionConfigMap.has(schema.key)
|
||||
? permissionConfigMap.get(schema.key)
|
||||
: null;
|
||||
|
||||
schema = getSchemePermissionItem(
|
||||
schema,
|
||||
permissionConfig,
|
||||
formKey,
|
||||
isViewProcess,
|
||||
otherParams,
|
||||
tableName,
|
||||
);
|
||||
if (permissionConfig?.view) {
|
||||
formShow += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return schema;
|
||||
});
|
||||
|
||||
return formShow > 0 ? schemas : null;
|
||||
}
|
||||
|
||||
function getSchemePermissionItem(
|
||||
schema,
|
||||
permissionConfig,
|
||||
formKey,
|
||||
isViewProcess,
|
||||
otherParams,
|
||||
tableName?,
|
||||
) {
|
||||
if (permissionConfig) {
|
||||
//查看
|
||||
schema.show = permissionConfig.view;
|
||||
// 必填
|
||||
|
||||
if (schema.componentProps) schema.componentProps.required = permissionConfig.required;
|
||||
schema.required = permissionConfig.required;
|
||||
//编辑
|
||||
|
||||
if (schema.componentProps) schema.componentProps.disabled = !permissionConfig.edit;
|
||||
schema.dynamicDisabled = !permissionConfig.edit;
|
||||
|
||||
// 查看流程
|
||||
if (isViewProcess) {
|
||||
schema.dynamicDisabled = true;
|
||||
}
|
||||
} else {
|
||||
schema.show = false;
|
||||
schema.dynamicDisabled = true;
|
||||
}
|
||||
// 修改意见簿
|
||||
if (schema.component == 'Opinion') {
|
||||
const key = formKey + separator + schema.key;
|
||||
schema.defaultValue = [];
|
||||
if (otherParams.opinionsComponents?.includes(key)) {
|
||||
schema.defaultValue = otherParams.opinions;
|
||||
}
|
||||
}
|
||||
// 上传组件集合
|
||||
if (schema.component == 'Upload') {
|
||||
let key = '';
|
||||
if (tableName) {
|
||||
key = formKey + separator + tableName + separator + schema.field;
|
||||
} else {
|
||||
key = formKey + separator + schema.field;
|
||||
}
|
||||
|
||||
otherParams.uploadComponentIds.push(key);
|
||||
}
|
||||
if (permissionConfig?.isSubTable) {
|
||||
// 子表单上传组件集合
|
||||
if (schema.componentType == 'Upload') {
|
||||
const key =
|
||||
formKey + separator + permissionConfig.tableName + separator + permissionConfig.fieldId;
|
||||
|
||||
otherParams.uploadComponentIds.push(key);
|
||||
}
|
||||
}
|
||||
return schema;
|
||||
}
|
||||
|
||||
// 根据工作流页面权限,设置表单属性(必填,禁用,显示)
|
||||
export function changeWorkFlowForm(formProps: FormProps, obj: WorkFlowFormParams) {
|
||||
const {
|
||||
formConfigChildren,
|
||||
formConfigKey,
|
||||
opinions,
|
||||
opinionsComponents,
|
||||
isViewProcess,
|
||||
uploadIds,
|
||||
formModels,
|
||||
} = obj;
|
||||
const returnData: {
|
||||
buildOptionJson: FormProps;
|
||||
uploadComponentIds: Array<string>;
|
||||
formModels: any;
|
||||
isViewProcess: boolean;
|
||||
} = {
|
||||
buildOptionJson: {}, //重新的配置Json文件
|
||||
uploadComponentIds: [], //
|
||||
formModels,
|
||||
isViewProcess: false,
|
||||
};
|
||||
try {
|
||||
const { buildOptionJson, uploadComponentIds } = changeFormJson(
|
||||
{
|
||||
formJson: formProps,
|
||||
formConfigChildren,
|
||||
formConfigKey: formConfigKey,
|
||||
opinions,
|
||||
opinionsComponents,
|
||||
},
|
||||
isViewProcess,
|
||||
uploadIds,
|
||||
);
|
||||
returnData.buildOptionJson = buildOptionJson;
|
||||
returnData.uploadComponentIds = uploadComponentIds;
|
||||
return returnData;
|
||||
} catch (error) {
|
||||
return returnData;
|
||||
}
|
||||
}
|
||||
// 根据表单json 生成权限配置
|
||||
export function getWorkflowPermissionConfig(schema) {
|
||||
let children: Array<TableItem> = [];
|
||||
if (schema.length > 0) {
|
||||
children = getSchemasConfig(schema);
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
function getSchemasConfig(list: FormSchema[]) {
|
||||
const arr: Array<TableItem> = [];
|
||||
if (list && list.length > 0) {
|
||||
list.forEach((ele1) => {
|
||||
if (['Card', 'Grid', 'Tab'].includes(ele1.component)) {
|
||||
if (ele1.children && ele1.children.length > 0) {
|
||||
ele1.children.forEach((ele2) => {
|
||||
if (ele2.list && ele2.list.length > 0) {
|
||||
arr.push(...getSchemasConfig(ele2.list));
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (ele1.component === 'TableLayout') {
|
||||
if (ele1.children && ele1.children.length > 0) {
|
||||
ele1.children.forEach((ele2) => {
|
||||
if (ele2.list && ele2.list.length > 0) {
|
||||
ele2.list.forEach((ele3) => {
|
||||
if (ele3.children && ele3.children.length > 0) {
|
||||
arr.push(...getSchemasConfig(ele3.children));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (ele1.component == 'SubForm') {
|
||||
arr.push(getTableConfig(ele1));
|
||||
} else if (ele1.component === 'OneForOne') {
|
||||
const obj: TableItem = getItemConfig(ele1);
|
||||
obj.isSubTable = true;
|
||||
obj.showChildren = false;
|
||||
const tempList: FormSchema[] = ele1.componentProps?.childSchemas || [];
|
||||
obj.children.push(...getSchemasConfig(tempList));
|
||||
arr.push(obj);
|
||||
} else {
|
||||
arr.push(getItemConfig(ele1));
|
||||
}
|
||||
});
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
function getItemConfig(element) {
|
||||
const children: Array<TableItem> = [];
|
||||
let required = true;
|
||||
let view = true;
|
||||
let edit = true;
|
||||
let disabled = false;
|
||||
let isSaveTable = false; //组件是否存表
|
||||
const type = element.type;
|
||||
if (disableTypes.includes(element.type)) {
|
||||
required = false;
|
||||
view = true;
|
||||
edit = false;
|
||||
disabled = true;
|
||||
}
|
||||
if (requiredDisabled.includes(element.type)) {
|
||||
required = false;
|
||||
}
|
||||
// 隐藏组件权限不要设置
|
||||
if (element.type === hiddenComponentType) {
|
||||
required = false;
|
||||
view = false;
|
||||
edit = false;
|
||||
disabled = true;
|
||||
}
|
||||
if (element.type === 'input' && element.componentProps.isSave) {
|
||||
required = false;
|
||||
view = true;
|
||||
edit = false;
|
||||
disabled = true;
|
||||
isSaveTable = true;
|
||||
}
|
||||
return {
|
||||
required,
|
||||
view,
|
||||
edit,
|
||||
disabled,
|
||||
isSaveTable,
|
||||
tableName: '',
|
||||
fieldName: element.label,
|
||||
fieldId: element.field,
|
||||
isSubTable: false,
|
||||
showChildren: true,
|
||||
type,
|
||||
key: element.key,
|
||||
children,
|
||||
};
|
||||
}
|
||||
function getTableConfig(element) {
|
||||
const children: Array<TableItem> = [];
|
||||
if (
|
||||
element.componentProps &&
|
||||
element.componentProps.columns &&
|
||||
element.componentProps.columns.length > 0
|
||||
) {
|
||||
element.componentProps.columns.forEach((ele2) => {
|
||||
if (ele2.dataIndex) children.push(getTableItemConfig(element.field, ele2));
|
||||
});
|
||||
}
|
||||
return {
|
||||
required: true,
|
||||
view: true,
|
||||
edit: true,
|
||||
disabled: false,
|
||||
isSubTable: true,
|
||||
showChildren: false,
|
||||
tableName: element.field,
|
||||
fieldName: element.label,
|
||||
fieldId: element.field,
|
||||
type: element.type,
|
||||
key: element.key,
|
||||
children,
|
||||
};
|
||||
}
|
||||
function getTableItemConfig(tableName: string, element) {
|
||||
let isSaveTable = false; //组件是否存表
|
||||
if (element.type === 'input' && element.componentProps.isSave) {
|
||||
isSaveTable = true;
|
||||
}
|
||||
return {
|
||||
required: true,
|
||||
view: true,
|
||||
edit: true,
|
||||
disabled: false,
|
||||
isSubTable: true,
|
||||
isSaveTable,
|
||||
showChildren: false,
|
||||
tableName: tableName,
|
||||
fieldName: element.title,
|
||||
fieldId: element.dataIndex,
|
||||
type: element.type,
|
||||
key: element.key,
|
||||
children: [],
|
||||
};
|
||||
}
|
||||
|
||||
// 辅助设置表单Disabled
|
||||
export function changeSchemaDisabled(schemas,isDisabled=true) {
|
||||
const layoutComponents = ['tab', 'grid', 'card'];
|
||||
schemas?.map((info) => {
|
||||
if (layoutComponents.includes(info.type!)) {
|
||||
info.children?.map((childInfo) => {
|
||||
childInfo.list.map((com) => {
|
||||
if (layoutComponents.includes(com.type)) {
|
||||
changeSchemaDisabled(childInfo.list,isDisabled);
|
||||
} else {
|
||||
com.dynamicDisabled = isDisabled;
|
||||
}
|
||||
});
|
||||
});
|
||||
} else if (info.type == 'table-layout') {
|
||||
info.children?.map((childInfo) => {
|
||||
childInfo.list.map((com) => {
|
||||
com.children.map((el) => {
|
||||
if (layoutComponents.includes(el.type) || el.type == 'table-layout') {
|
||||
changeSchemaDisabled(com.children,isDisabled);
|
||||
} else {
|
||||
el.dynamicDisabled = isDisabled;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
} else if (info.type == 'one-for-one') {
|
||||
changeSchemaDisabled(info.componentProps.childSchemas,isDisabled);
|
||||
} else if (info.type == 'form') {
|
||||
info.dynamicDisabled = isDisabled;
|
||||
info.componentProps.disabled = isDisabled;
|
||||
info.componentProps.columns?.forEach((column) => {
|
||||
column.dynamicDisabled = isDisabled;
|
||||
if(column?.componentProps) column.componentProps.disabled = isDisabled
|
||||
})
|
||||
} else {
|
||||
info.dynamicDisabled = isDisabled;
|
||||
}
|
||||
});
|
||||
return schemas;
|
||||
}
|
||||
Reference in New Issue
Block a user