first commit

This commit is contained in:
ye1023
2026-04-09 15:09:34 +08:00
commit d8e3a63df1
2246 changed files with 352109 additions and 0 deletions
+10
View 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)),
};
}
+183
View File
@@ -0,0 +1,183 @@
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
View 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];
}
+64
View File
@@ -0,0 +1,64 @@
import { isRef, unref, watch, Ref, ComputedRef } from 'vue';
import Clipboard from 'clipboard';
import { ModalOptionsEx, useMessage } from '/@/hooks/web/useMessage';
/** 带复制按钮的弹窗 */
interface IOptions extends ModalOptionsEx {
// 要复制的文本,可以是一个 ref 对象,动态更新
copyText: string | Ref<string> | ComputedRef<string>;
}
const COPY_CLASS = 'copy-this-text';
const CLIPBOARD_TEXT = 'data-clipboard-text';
export function useCopyModal() {
return { createCopyModal };
}
const { createMessage, createConfirm } = useMessage();
/** 创建复制弹窗 */
function createCopyModal(options: Partial<IOptions>) {
let modal = createConfirm({
...options,
iconType: options.iconType ?? 'info',
width: options.width ?? 500,
title: options.title ?? '复制',
maskClosable: options.maskClosable ?? true,
okText: options.okText ?? '复制',
okButtonProps: {
...options.okButtonProps,
class: COPY_CLASS,
[CLIPBOARD_TEXT]: unref(options.copyText),
} as any,
onOk() {
return new Promise((resolve: any) => {
const clipboard = new Clipboard('.' + COPY_CLASS);
clipboard.on('success', () => {
clipboard.destroy();
createMessage.success('复制成功');
resolve();
});
clipboard.on('error', () => {
createMessage.error('该浏览器不支持自动复制');
clipboard.destroy();
resolve();
});
});
},
});
// 动态更新 copyText
if (isRef(options.copyText)) {
watch(options.copyText, (copyText) => {
modal.update({
okButtonProps: {
...options.okButtonProps,
class: COPY_CLASS,
[CLIPBOARD_TEXT]: copyText,
} as any,
});
});
}
return modal;
}
+69
View 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) {
throw new Error(e);
}
element.remove();
if (originalRange && selection) {
selection.removeAllRanges();
selection.addRange(originalRange);
}
if (previouslyFocusedElement) {
(previouslyFocusedElement as HTMLElement).focus();
}
return isSuccess;
}
+22
View 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,
};
}
+115
View File
@@ -0,0 +1,115 @@
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') {
console.log("---useECharts---初始化加载---")
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,
};
}
+28
View 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 };
};
+55
View File
@@ -0,0 +1,55 @@
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 '';
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
View 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 {};
}
});
}
+158
View File
@@ -0,0 +1,158 @@
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';
import { h } from 'vue';
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) {
try {
if (iconType === 'warning') {
return h(InfoCircleFilled,{"class":"modal-icon-warning"})
} else if (iconType === 'success') {
return h(CheckCircleFilled,{"class": "modal-icon-success"});
} else if (iconType === 'info') {
return h(InfoCircleFilled,{"class": "modal-icon-info"});
} else {
return h(CloseCircleFilled,{"class":"modal-icon-error"});
}
} catch (e) {
console.log(e);
}
}
function renderContent({ content }: Pick<ModalOptionsEx, 'content'>) {
try {
if (isString(content)) {
return h('div', h('div', {'innerHTML':content as string}));
} else {
return content;
}
} catch (e) {
console.log(e);
return content;
}
}
/**
* @description: Create confirmation box
*/
function createConfirm(options: ModalOptionsEx): ReturnType<ModalFunc> {
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);
}
const getBaseOptions = () => {
const { t } = useI18n();
return {
okText: t('common.okText'),
centered: true,
};
};
function createModalOptions(options: ModalOptionsPartial, icon: string): ModalOptionsPartial {
//update-begin-author:taoyan date:2023-1-10 for: 可以自定义图标
let titleIcon:any = ''
if(options.icon){
titleIcon = options.icon;
}else{
titleIcon = getIcon(icon)
}
//update-end-author:taoyan date:2023-1-10 for: 可以自定义图标
return {
...getBaseOptions(),
...options,
content: renderContent(options),
icon: titleIcon
};
}
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'));
}
interface MOE extends Omit<ModalOptionsEx, 'iconType'> {
iconType?: ModalOptionsEx['iconType'];
}
// 提示框,无需传入iconType,默认为warning
function createConfirmSync(options: MOE) {
return new Promise((resolve) => {
createConfirm({
iconType: 'warning',
...options,
onOk: () => resolve(true),
onCancel: () => resolve(false),
});
});
}
notification.config({
placement: 'topRight',
duration: 3,
});
/**
* @description: message
*/
export function useMessage() {
return {
createMessage: Message,
notification: notification as NotifyApi,
createConfirm: createConfirm,
createConfirmSync,
createSuccessModal,
createErrorModal,
createInfoModal,
createWarningModal,
};
}
+123
View 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): ReturnType<ModalFunc> {
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);
}
const getBaseOptions = () => {
const { t } = useI18n();
return {
okText: t('common.okText'),
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,
};
}
+78
View File
@@ -0,0 +1,78 @@
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';
import { useUserStore } from '/@/store/modules/user';
import { useMultipleTabStore } from '/@/store/modules/multipleTab';
export type RouteLocationRawEx = Omit<RouteLocationRaw, 'path'> & { path: PageEnum };
function handleError(e: Error) {
console.error(e);
}
// page switch
export function useGo(_router?: Router) {
// update-begin--author:liaozhiyang---date:20230908---for:【issues/694】404返回首页问题
const userStore = useUserStore();
const homePath = userStore.getUserInfo.homePath || PageEnum.BASE_HOME;
// update-end--author:liaozhiyang---date:20230908---for:【issues/694】404返回首页问题
let router;
if (!_router) {
router = useRouter();
}
const { push, replace } = _router || router;
function go(opt: PageEnum | RouteLocationRawEx | string = homePath, 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;
}
// update-begin--author:liaozhiyang---date:20231123---for:【QQYUN-7099】动态路由匹配右键重新加载404
const tabStore = useMultipleTabStore();
if (name && Object.keys(params).length > 0) {
tabStore.setRedirectPageParam({
redirect_type: 'name',
name: String(name),
params,
query,
});
params['path'] = String(name);
} else {
tabStore.setRedirectPageParam({
redirect_type: 'path',
path: fullPath,
query,
});
params['path'] = fullPath;
}
// update-end--author:liaozhiyang---date:20231123---for:【QQYUN-7099】动态路由匹配右键重新加载404
push({ name: REDIRECT_NAME, params, query }).then(() => resolve(true));
});
}
return redo;
};
+31
View File
@@ -0,0 +1,31 @@
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 };
}
+176
View File
@@ -0,0 +1,176 @@
import type { 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';
// User permissions related operations
export function usePermission() {
const userStore = useUserStore();
const appStore = useAppStore();
const permissionStore = usePermissionStore();
//动态加载流程节点表单权限
let formData: any = {};
function initBpmFormData(_bpmFormData) {
formData = _bpmFormData;
}
const { closeAll } = useTabs(router);
//==================================工作流权限判断-begin=========================================
function hasBpmPermission(code, type) {
// 禁用-type=2
// 显示-type=1
let codeList: string[] = [];
let permissionList = formData.permissionList;
if (permissionList && permissionList.length > 0) {
for (let item of permissionList) {
if (item.type == type) {
codeList.push(item.action);
}
}
}
return codeList.indexOf(code) >= 0;
}
//==================================工作流权限判断-end=========================================
/**
* 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();
}
/**
* 确定是否存在权限
*/
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 allCodeList = permissionStore.getPermCodeList as string[];
if (!isArray(value) && allCodeList && allCodeList.length > 0) {
//=============================工作流权限判断-显示-begin==============================================
if (formData) {
let code = value as string;
if (hasBpmPermission(code, '1') === true) {
return true;
}
}
//=============================工作流权限判断-显示-end==============================================
return allCodeList.includes(value);
}
return (intersection(value, allCodeList) as string[]).length > 0;
}
return true;
}
/**
* 是否禁用组件
*/
function isDisabledAuth(value?: RoleEnum | RoleEnum[] | string | string[], def = true): boolean {
//=============================工作流权限判断-禁用-begin==============================================
if (formData) {
let code = value as string;
if (hasBpmPermission(code, '2') === true) {
return true;
}
//update-begin-author:taoyan date:2022-6-17 for: VUEN-1342【流程】编码方式 节点权限配置好后,未生效
if (isCodingButNoConfig(code) == true) {
return false;
}
//update-end-author:taoyan date:2022-6-17 for: VUEN-1342【流程】编码方式 节点权限配置好后,未生效
}
//=============================工作流权限判断-禁用-end==============================================
return !hasPermission(value);
}
/**
* 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();
}
//update-begin-author:taoyan date:2022-6-17 for: VUEN-1342【流程】编码方式 节点权限配置好后,未生效
/**
* 判断是不是 代码里写了逻辑但是没有配置权限这种情况
*/
function isCodingButNoConfig(code) {
let all = permissionStore.allAuthList;
if (all && all instanceof Array) {
let temp = all.filter((item) => item.action == code);
if (temp && temp.length > 0) {
if (temp[0].status == '0') {
return true;
}
} else {
// update-begin--author:liaozhiyang---date:20240705---for:【TV360X-1604】按钮禁用权限在接口中查不到也禁用
return false;
// update-end--author:liaozhiyang---date:20240705---for:【TV360X-1604】按钮禁用权限在接口中查不到也禁用
}
}
return false;
}
//update-end-author:taoyan date:2022-6-17 for: VUEN-1342【流程】编码方式 节点权限配置好后,未生效
return { changeRole, hasPermission, togglePermissionMode, refreshMenu, isDisabledAuth, initBpmFormData };
}
+42
View File
@@ -0,0 +1,42 @@
import { nextTick } from 'vue';
import $printJS, { Configuration } from 'print-js';
import Print from 'vue-print-nb-jeecg/src/printarea';
/**
* 调用 printJS,如果type = html,就走 printNB 的方法
*/
export function printJS(configuration: Configuration) {
if (configuration?.type === 'html') {
printNb(configuration.printable);
} else {
return $printJS(configuration);
}
}
/** 调用 printNB 打印 */
export function printNb(domId) {
if (domId) {
localPrint(domId);
} else {
window.print();
}
}
let closeBtn = true;
function localPrint(domId) {
if (typeof domId === 'string' && !domId.startsWith('#')) {
domId = '#' + domId;
}
nextTick(() => {
if (closeBtn) {
closeBtn = false;
new Print({
el: domId,
endCallback() {
closeBtn = true;
},
});
}
});
}
+46
View 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
View 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 };
}
+45
View File
@@ -0,0 +1,45 @@
// 单点登录核心类
import { getToken } from '/@/utils/auth';
import { getUrlParam } from '/@/utils';
import { useGlobSetting } from '/@/hooks/setting';
import { validateCasLogin } from '/@/api/sys/user';
import { useUserStore } from '/@/store/modules/user';
const globSetting = useGlobSetting();
const openSso = globSetting.openSso;
export function useSso() {
//update-begin---author:wangshuai---date:2024-01-03---for:【QQYUN-7805】SSO登录强制用http #957---
let locationUrl = document.location.protocol +"//" + window.location.host + '/';
//update-end---author:wangshuai---date:2024-01-03---for:【QQYUN-7805】SSO登录强制用http #957---
/**
* 单点登录
*/
async function ssoLogin() {
if (openSso == 'true') {
let token = getToken();
let ticket = getUrlParam('ticket');
if (!token) {
if (ticket) {
await validateCasLogin({
ticket: ticket,
service: locationUrl,
}).then((res) => {
const userStore = useUserStore();
userStore.setToken(res.token);
return userStore.afterLoginAction(true, {});
});
} else {
window.location.href = globSetting.casBaseUrl + '/login?service=' + encodeURIComponent(locationUrl);
}
}
}
}
/**
* 退出登录
*/
async function ssoLoginOut() {
window.location.href = globSetting.casBaseUrl + '/logout?service=' + encodeURIComponent(locationUrl);
}
return { ssoLogin, ssoLoginOut };
}
+126
View File
@@ -0,0 +1,126 @@
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.path === route.path)!;
}
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:
// update-begin--author:liaozhiyang---date:20240605---for:【TV360X-732】非当前页右键关闭左侧、关闭右侧、关闭其它功能正常使用
await tabStore.closeLeftTabs(tab || currentTab, router);
// update-end--author:liaozhiyang---date:20240605---for:【TV360X-732】非当前页右键关闭左侧、关闭右侧、关闭其它功能正常使用
break;
case TableActionEnum.CLOSE_RIGHT:
// update-begin--author:liaozhiyang---date:20240605---for:【TV360X-732】非当前页右键关闭左侧、关闭右侧、关闭其它功能正常使用
await tabStore.closeRightTabs(tab || currentTab, router);
// update-end--author:liaozhiyang---date:20240605---for:【TV360X-732】非当前页右键关闭左侧、关闭右侧、关闭其它功能正常使用
break;
case TableActionEnum.CLOSE_OTHER:
// update-begin--author:liaozhiyang---date:20240605---for:【TV360X-732】非当前页右键关闭左侧、关闭右侧、关闭其它功能正常使用
await tabStore.closeOtherTabs(tab || currentTab, router);
// update-end--author:liaozhiyang---date:20240605---for:【TV360X-732】非当前页右键关闭左侧、关闭右侧、关闭其它功能正常使用
break;
case TableActionEnum.CLOSE_CURRENT:
case TableActionEnum.CLOSE:
await tabStore.closeTab(tab || currentTab, router);
break;
}
}
/**
* 关闭相同的路由
* @param path
*/
function closeSameRoute(path) {
if(path.indexOf('?')>0){
path = path.split('?')[0];
}
let tab = tabStore.getTabList.find((item) => item.path.indexOf(path)>=0)!;
if(tab){
tabStore.closeTab(tab, router);
}
}
return {
refreshPage: () => handleTabAction(TableActionEnum.REFRESH),
// update-begin--author:liaozhiyang---date:20240605---for:【TV360X-732】非当前页右键关闭左侧、关闭右侧、关闭其它功能正常使用
closeAll: (tab) => handleTabAction(TableActionEnum.CLOSE_ALL, tab),
closeLeft: (tab) => handleTabAction(TableActionEnum.CLOSE_LEFT, tab),
closeRight: (tab) => handleTabAction(TableActionEnum.CLOSE_RIGHT, tab),
closeOther: (tab) => handleTabAction(TableActionEnum.CLOSE_OTHER, tab),
// update-end--author:liaozhiyang---date:20240605---for:【TV360X-732】非当前页右键关闭左侧、关闭右侧、关闭其它功能正常使用
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),
closeSameRoute
};
}
+72
View File
@@ -0,0 +1,72 @@
import type {Menu} from "@/router/types";
import { ref, watch, unref } 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 { REDIRECT_NAME } from '/@/router/constant';
import { getMenus } from '/@/router/menus';
/**
* Listening to page changes and dynamically changing site titles
*/
export function useTitle() {
const { title } = useGlobSetting();
const { t } = useI18n();
const { currentRoute } = useRouter();
const localeStore = useLocaleStore();
const pageTitle = usePageTitle();
const menus = ref<Menu[] | null>(null)
watch(
[() => currentRoute.value.path, () => localeStore.getLocale],
async () => {
const route = unref(currentRoute);
if (route.name === REDIRECT_NAME) {
return;
}
// update-begin--author:liaozhiyang---date:20231110---for:【QQYUN-6938】online菜单名字和页面title不一致
if (route.params && Object.keys(route.params).length) {
if (!menus.value) {
menus.value = await getMenus();
}
const getTitle = getMatchingRouterName(menus.value, route.fullPath);
let tTitle = '';
if (getTitle) {
tTitle = t(getTitle);
} else {
tTitle = t(route?.meta?.title as string);
}
pageTitle.value = tTitle ? ` ${tTitle} - ${title} ` : `${title}`;
} else {
const tTitle = t(route?.meta?.title as string);
pageTitle.value = tTitle ? ` ${tTitle} - ${title} ` : `${title}`;
}
// update-end--author:liaozhiyang---date:20231110---for:【QQYUN-6938】online菜单名字和页面title不一致
},
{ immediate: true }
);
}
/**
2023-11-09
liaozhiyang
获取路由匹配模式的真实页面名字
*/
function getMatchingRouterName(menus, path) {
for (let i = 0, len = menus.length; i < len; i++) {
const item = menus[i];
if (item.path === path && !item.redirect && !item.paramPath) {
return item.meta?.title;
} else if (item.children?.length) {
const result = getMatchingRouterName(item.children, path);
if (result) {
return result;
}
}
}
return '';
}
+98
View File
@@ -0,0 +1,98 @@
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 };
}
+141
View File
@@ -0,0 +1,141 @@
// noinspection JSUnusedGlobalSymbols
import { unref } from 'vue';
import { useWebSocket, WebSocketResult } from '@vueuse/core';
import { getToken } from '/@/utils/auth';
let result: WebSocketResult<any>;
const listeners = new Map();
/**
* 开启 WebSocket 链接,全局只需执行一次
* @param url
*/
export function connectWebSocket(url: string) {
//update-begin-author:taoyan date:2022-4-24 for: v2.4.6 的 websocket 服务端,存在性能和安全问题。 #3278
const token = (getToken() || '') as string;
result = useWebSocket(url, {
// 自动重连 (遇到错误最多重复连接10次)
autoReconnect: {
retries: 10,
// TODO 服务器压力暂改成50秒
delay: 50000,
},
// 心跳检测
heartbeat: {
message: 'ping',
// TODO 服务器压力暂改成50秒
interval: 50000,
},
protocols: [token],
// update-begin--author:liaozhiyang---date:20240726---for[issues/6662] 演示系统socket总断,换一个写法
onConnected: function (ws) {
console.log('[WebSocket] 连接成功', ws);
},
onDisconnected: function (ws, event) {
console.log('[WebSocket] 连接断开:', ws, event);
},
onError: function (ws, event) {
console.log('[WebSocket] 连接发生错误: ', ws, event);
},
onMessage: function (_ws, e) {
console.debug('[WebSocket] -----接收消息-------', e.data);
try {
//update-begin---author:wangshuai---date:2024-05-07---for:【issues/1161】前端websocket因心跳导致监听不起作用---
if (e.data === 'ping') {
return;
}
//update-end---author:wangshuai---date:2024-05-07---for:【issues/1161】前端websocket因心跳导致监听不起作用---
const data = JSON.parse(e.data);
for (const callback of listeners.keys()) {
try {
callback(data);
} catch (err) {
console.error(err);
}
}
} catch (err) {
console.error('[WebSocket] data解析失败:', err);
}
},
// update-end--author:liaozhiyang---date:20240726---for[issues/6662] 演示系统socket总断,换一个写法
});
// update-begin--author:liaozhiyang---date:20240726---for[issues/6662] 演示系统socket总断,换一个写法
//update-end-author:taoyan date:2022-4-24 for: v2.4.6 的 websocket 服务端,存在性能和安全问题。 #3278
// if (result) {
// result.open = onOpen;
// result.close = onClose;
// const ws = unref(result.ws);
// if(ws!=null){
// ws.onerror = onError;
// ws.onmessage = onMessage;
// //update-begin---author:wangshuai---date:2024-04-30---for:【issues/1217】发送测试消息后,铃铛数字没有变化---
// ws.onopen = onOpen;
// ws.onclose = onClose;
// //update-end---author:wangshuai---date:2024-04-30---for:【issues/1217】发送测试消息后,铃铛数字没有变化---
// }
// }
// update-end--author:liaozhiyang---date:20240726---for[issues/6662] 演示系统socket总断,换一个写法
}
function onOpen() {
console.log('[WebSocket] 连接成功');
}
function onClose(e) {
console.log('[WebSocket] 连接断开:', e);
}
function onError(e) {
console.log('[WebSocket] 连接发生错误: ', e);
}
function onMessage(e) {
console.debug('[WebSocket] -----接收消息-------', e.data);
try {
//update-begin---author:wangshuai---date:2024-05-07---for:【issues/1161】前端websocket因心跳导致监听不起作用---
if(e==='ping'){
return;
}
//update-end---author:wangshuai---date:2024-05-07---for:【issues/1161】前端websocket因心跳导致监听不起作用---
const data = JSON.parse(e.data);
for (const callback of listeners.keys()) {
try {
callback(data);
} catch (err) {
console.error(err);
}
}
} catch (err) {
console.error('[WebSocket] data解析失败:', err);
}
}
/**
* 添加 WebSocket 消息监听
* @param callback
*/
export function onWebSocket(callback: (data: object) => any) {
if (!listeners.has(callback)) {
if (typeof callback === 'function') {
listeners.set(callback, null);
} else {
console.debug('[WebSocket] 添加 WebSocket 消息监听失败:传入的参数不是一个方法');
}
}
}
/**
* 解除 WebSocket 消息监听
*
* @param callback
*/
export function offWebSocket(callback: (data: object) => any) {
listeners.delete(callback);
}
export function useMyWebSocket() {
return result;
}