前端和后端源码,合并到一个git仓库中,方便用户下载,避免前后端不匹配的问题

This commit is contained in:
JEECG
2024-06-23 10:39:52 +08:00
parent bb918b742e
commit 0325e34dcb
1439 changed files with 171106 additions and 0 deletions

View File

@ -0,0 +1,78 @@
import { on } from '/@/utils/domUtils';
import { isServer } from '/@/utils/is';
import type { ComponentPublicInstance, DirectiveBinding, ObjectDirective } from 'vue';
type DocumentHandler = <T extends MouseEvent>(mouseup: T, mousedown: T) => void;
type FlushList = Map<
HTMLElement,
{
documentHandler: DocumentHandler;
bindingFn: (...args: unknown[]) => unknown;
}
>;
const nodeList: FlushList = new Map();
let startClick: MouseEvent;
if (!isServer) {
on(document, 'mousedown', (e: MouseEvent) => (startClick = e));
on(document, 'mouseup', (e: MouseEvent) => {
for (const { documentHandler } of nodeList.values()) {
documentHandler(e, startClick);
}
});
}
function createDocumentHandler(el: HTMLElement, binding: DirectiveBinding): DocumentHandler {
let excludes: HTMLElement[] = [];
if (Array.isArray(binding.arg)) {
excludes = binding.arg;
} else {
// due to current implementation on binding type is wrong the type casting is necessary here
excludes.push(binding.arg as unknown as HTMLElement);
}
return function (mouseup, mousedown) {
const popperRef = (
binding.instance as ComponentPublicInstance<{
popperRef: Nullable<HTMLElement>;
}>
).popperRef;
const mouseUpTarget = mouseup.target as Node;
const mouseDownTarget = mousedown.target as Node;
const isBound = !binding || !binding.instance;
const isTargetExists = !mouseUpTarget || !mouseDownTarget;
const isContainedByEl = el.contains(mouseUpTarget) || el.contains(mouseDownTarget);
const isSelf = el === mouseUpTarget;
const isTargetExcluded =
(excludes.length && excludes.some((item) => item?.contains(mouseUpTarget))) ||
(excludes.length && excludes.includes(mouseDownTarget as HTMLElement));
const isContainedByPopper = popperRef && (popperRef.contains(mouseUpTarget) || popperRef.contains(mouseDownTarget));
if (isBound || isTargetExists || isContainedByEl || isSelf || isTargetExcluded || isContainedByPopper) {
return;
}
binding.value();
};
}
const ClickOutside: ObjectDirective = {
beforeMount(el, binding) {
nodeList.set(el, {
documentHandler: createDocumentHandler(el, binding),
bindingFn: binding.value,
});
},
updated(el, binding) {
nodeList.set(el, {
documentHandler: createDocumentHandler(el, binding),
bindingFn: binding.value,
});
},
unmounted(el) {
nodeList.delete(el);
},
};
export default ClickOutside;

View File

@ -0,0 +1,11 @@
/**
* Configure and register global directives
*/
import type { App } from 'vue';
import { setupPermissionDirective } from './permission';
import { setupLoadingDirective } from './loading';
export function setupGlobDirectives(app: App) {
setupPermissionDirective(app);
setupLoadingDirective(app);
}

View File

@ -0,0 +1,41 @@
import { createLoading } from '/@/components/Loading';
import type { Directive, App } from 'vue';
const loadingDirective: Directive = {
mounted(el, binding) {
const tip = el.getAttribute('loading-tip');
const background = el.getAttribute('loading-background');
const size = el.getAttribute('loading-size');
const fullscreen = !!binding.modifiers.fullscreen;
const instance = createLoading(
{
tip,
background,
size: size || 'large',
loading: !!binding.value,
absolute: !fullscreen,
},
fullscreen ? document.body : el
);
el.instance = instance;
},
updated(el, binding) {
const instance = el.instance;
if (!instance) return;
instance.setTip(el.getAttribute('loading-tip'));
if (binding.oldValue !== binding.value) {
if (binding.oldValue !== binding.value) {
instance.setLoading?.(binding.value && !instance.loading);
}
}
},
unmounted(el) {
el?.instance?.close();
},
};
export function setupLoadingDirective(app: App) {
app.directive('loading', loadingDirective);
}
export default loadingDirective;

View File

@ -0,0 +1,33 @@
/**
* Global authority directive
* Used for fine-grained control of component permissions
* @Example v-auth="RoleEnum.TEST"
*/
import type { App, Directive, DirectiveBinding } from 'vue';
import { usePermission } from '/@/hooks/web/usePermission';
function isAuth(el: Element, binding: any) {
// update-begin--author:liaozhiyang---date:20240529---for【TV360X-460】basicForm支持v-auth指令(权限控制显隐)
const value = binding.value;
if (!value) return;
// update-end--author:liaozhiyang---date:20240529---for【TV360X-460】basicForm支持v-auth指令(权限控制显隐)
const { hasPermission } = usePermission();
if (!hasPermission(value)) {
el.parentNode?.removeChild(el);
}
}
const mounted = (el: Element, binding: DirectiveBinding<any>) => {
isAuth(el, binding);
};
const authDirective: Directive = {
mounted,
};
export function setupPermissionDirective(app: App) {
app.directive('auth', authDirective);
}
export default authDirective;

View File

@ -0,0 +1,31 @@
/**
* Prevent repeated clicks
* @Example v-repeat-click="()=>{}"
*/
import { on, once } from '/@/utils/domUtils';
import type { Directive, DirectiveBinding } from 'vue';
const repeatDirective: Directive = {
beforeMount(el: Element, binding: DirectiveBinding<any>) {
let interval: Nullable<IntervalHandle> = null;
let startTime = 0;
const handler = (): void => binding?.value();
const clear = (): void => {
if (Date.now() - startTime < 100) {
handler();
}
interval && clearInterval(interval);
interval = null;
};
on(el, 'mousedown', (e: MouseEvent): void => {
if ((e as any).button !== 0) return;
startTime = Date.now();
once(document as any, 'mouseup', clear);
interval && clearInterval(interval);
interval = setInterval(handler, 100);
});
},
};
export default repeatDirective;

View File

@ -0,0 +1,21 @@
.ripple-container {
position: absolute;
top: 0;
left: 0;
width: 0;
height: 0;
overflow: hidden;
pointer-events: none;
}
.ripple-effect {
position: relative;
z-index: 9999;
width: 1px;
height: 1px;
margin-top: 0;
margin-left: 0;
pointer-events: none;
border-radius: 50%;
transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1);
}

View File

@ -0,0 +1,180 @@
import type { Directive } from 'vue';
import './index.less';
export interface RippleOptions {
event: string;
transition: number;
}
export interface RippleProto {
background?: string;
zIndex?: string;
}
export type EventType = Event & MouseEvent & TouchEvent;
const options: RippleOptions = {
event: 'mousedown',
transition: 400,
};
const RippleDirective: Directive & RippleProto = {
beforeMount: (el: HTMLElement, binding) => {
if (binding.value === false) return;
const bg = el.getAttribute('ripple-background');
setProps(Object.keys(binding.modifiers), options);
const background = bg || RippleDirective.background;
const zIndex = RippleDirective.zIndex;
el.addEventListener(options.event, (event: EventType) => {
rippler({
event,
el,
background,
zIndex,
});
});
},
updated(el, binding) {
if (!binding.value) {
el?.clearRipple?.();
return;
}
const bg = el.getAttribute('ripple-background');
el?.setBackground?.(bg);
},
};
function rippler({ event, el, zIndex, background }: { event: EventType; el: HTMLElement } & RippleProto) {
const targetBorder = parseInt(getComputedStyle(el).borderWidth.replace('px', ''));
const clientX = event.clientX || event.touches[0].clientX;
const clientY = event.clientY || event.touches[0].clientY;
const rect = el.getBoundingClientRect();
const { left, top } = rect;
const { offsetWidth: width, offsetHeight: height } = el;
const { transition } = options;
const dx = clientX - left;
const dy = clientY - top;
const maxX = Math.max(dx, width - dx);
const maxY = Math.max(dy, height - dy);
const style = window.getComputedStyle(el);
const radius = Math.sqrt(maxX * maxX + maxY * maxY);
const border = targetBorder > 0 ? targetBorder : 0;
const ripple = document.createElement('div');
const rippleContainer = document.createElement('div');
// Styles for ripple
ripple.className = 'ripple';
Object.assign(ripple.style ?? {}, {
marginTop: '0px',
marginLeft: '0px',
width: '1px',
height: '1px',
transition: `all ${transition}ms cubic-bezier(0.4, 0, 0.2, 1)`,
borderRadius: '50%',
pointerEvents: 'none',
position: 'relative',
zIndex: zIndex ?? '9999',
backgroundColor: background ?? 'rgba(0, 0, 0, 0.12)',
});
// Styles for rippleContainer
rippleContainer.className = 'ripple-container';
Object.assign(rippleContainer.style ?? {}, {
position: 'absolute',
left: `${0 - border}px`,
top: `${0 - border}px`,
height: '0',
width: '0',
pointerEvents: 'none',
overflow: 'hidden',
});
const storedTargetPosition = el.style.position.length > 0 ? el.style.position : getComputedStyle(el).position;
if (storedTargetPosition !== 'relative') {
el.style.position = 'relative';
}
rippleContainer.appendChild(ripple);
el.appendChild(rippleContainer);
Object.assign(ripple.style, {
marginTop: `${dy}px`,
marginLeft: `${dx}px`,
});
const { borderTopLeftRadius, borderTopRightRadius, borderBottomLeftRadius, borderBottomRightRadius } = style;
Object.assign(rippleContainer.style, {
width: `${width}px`,
height: `${height}px`,
direction: 'ltr',
borderTopLeftRadius,
borderTopRightRadius,
borderBottomLeftRadius,
borderBottomRightRadius,
});
setTimeout(() => {
const wh = `${radius * 2}px`;
Object.assign(ripple.style ?? {}, {
width: wh,
height: wh,
marginLeft: `${dx - radius}px`,
marginTop: `${dy - radius}px`,
});
}, 0);
function clearRipple() {
setTimeout(() => {
ripple.style.backgroundColor = 'rgba(0, 0, 0, 0)';
}, 250);
setTimeout(() => {
rippleContainer?.parentNode?.removeChild(rippleContainer);
}, 850);
el.removeEventListener('mouseup', clearRipple, false);
el.removeEventListener('mouseleave', clearRipple, false);
el.removeEventListener('dragstart', clearRipple, false);
setTimeout(() => {
let clearPosition = true;
for (let i = 0; i < el.childNodes.length; i++) {
if ((el.childNodes[i] as Recordable).className === 'ripple-container') {
clearPosition = false;
}
}
if (clearPosition) {
el.style.position = storedTargetPosition !== 'static' ? storedTargetPosition : '';
}
}, options.transition + 260);
}
if (event.type === 'mousedown') {
el.addEventListener('mouseup', clearRipple, false);
el.addEventListener('mouseleave', clearRipple, false);
el.addEventListener('dragstart', clearRipple, false);
} else {
clearRipple();
}
(el as Recordable).setBackground = (bgColor: string) => {
if (!bgColor) {
return;
}
ripple.style.backgroundColor = bgColor;
};
}
function setProps(modifiers: Recordable, props: Recordable) {
modifiers.forEach((item: Recordable) => {
if (isNaN(Number(item))) props.event = item;
else props.transition = item;
});
}
export default RippleDirective;