mirror of
https://github.com/jeecgboot/JeecgBoot.git
synced 2026-01-03 03:45:28 +08:00
前端和后端源码,合并到一个git仓库中,方便用户下载,避免前后端不匹配的问题
This commit is contained in:
78
jeecgboot-vue3/src/directives/clickOutside.ts
Normal file
78
jeecgboot-vue3/src/directives/clickOutside.ts
Normal 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;
|
||||
11
jeecgboot-vue3/src/directives/index.ts
Normal file
11
jeecgboot-vue3/src/directives/index.ts
Normal 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);
|
||||
}
|
||||
41
jeecgboot-vue3/src/directives/loading.ts
Normal file
41
jeecgboot-vue3/src/directives/loading.ts
Normal 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;
|
||||
33
jeecgboot-vue3/src/directives/permission.ts
Normal file
33
jeecgboot-vue3/src/directives/permission.ts
Normal 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;
|
||||
31
jeecgboot-vue3/src/directives/repeatClick.ts
Normal file
31
jeecgboot-vue3/src/directives/repeatClick.ts
Normal 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;
|
||||
21
jeecgboot-vue3/src/directives/ripple/index.less
Normal file
21
jeecgboot-vue3/src/directives/ripple/index.less
Normal 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);
|
||||
}
|
||||
180
jeecgboot-vue3/src/directives/ripple/index.ts
Normal file
180
jeecgboot-vue3/src/directives/ripple/index.ts
Normal 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;
|
||||
Reference in New Issue
Block a user