mirror of
https://github.com/jeecgboot/JeecgBoot.git
synced 2025-12-26 16:26:41 +08:00
前端和后端源码,合并到一个git仓库中,方便用户下载,避免前后端不匹配的问题
This commit is contained in:
80
jeecgboot-vue3/src/utils/auth/index.ts
Normal file
80
jeecgboot-vue3/src/utils/auth/index.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import { Persistent, BasicKeys } from '/@/utils/cache/persistent';
|
||||
import { CacheTypeEnum } from '/@/enums/cacheEnum';
|
||||
import projectSetting from '/@/settings/projectSetting';
|
||||
import { TOKEN_KEY, LOGIN_INFO_KEY, TENANT_ID } from '/@/enums/cacheEnum';
|
||||
|
||||
const { permissionCacheType } = projectSetting;
|
||||
const isLocal = permissionCacheType === CacheTypeEnum.LOCAL;
|
||||
|
||||
/**
|
||||
* 获取token
|
||||
*/
|
||||
export function getToken() {
|
||||
return getAuthCache<string>(TOKEN_KEY);
|
||||
}
|
||||
/**
|
||||
* 获取登录信息
|
||||
*/
|
||||
export function getLoginBackInfo() {
|
||||
return getAuthCache(LOGIN_INFO_KEY);
|
||||
}
|
||||
/**
|
||||
* 获取租户id
|
||||
*/
|
||||
export function getTenantId() {
|
||||
return getAuthCache<string>(TENANT_ID);
|
||||
}
|
||||
|
||||
export function getAuthCache<T>(key: BasicKeys) {
|
||||
const fn = isLocal ? Persistent.getLocal : Persistent.getSession;
|
||||
return fn(key) as T;
|
||||
}
|
||||
|
||||
export function setAuthCache(key: BasicKeys, value) {
|
||||
const fn = isLocal ? Persistent.setLocal : Persistent.setSession;
|
||||
return fn(key, value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置动态key
|
||||
* @param key
|
||||
* @param value
|
||||
*/
|
||||
export function setCacheByDynKey(key, value) {
|
||||
const fn = isLocal ? Persistent.setLocal : Persistent.setSession;
|
||||
return fn(key, value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取动态key
|
||||
* @param key
|
||||
*/
|
||||
export function getCacheByDynKey<T>(key) {
|
||||
const fn = isLocal ? Persistent.getLocal : Persistent.getSession;
|
||||
return fn(key) as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除动态key
|
||||
* @param key
|
||||
*/
|
||||
export function removeCacheByDynKey<T>(key) {
|
||||
const fn = isLocal ? Persistent.removeLocal : Persistent.removeSession;
|
||||
return fn(key) as T;
|
||||
}
|
||||
/**
|
||||
* 移除缓存中的某个属性
|
||||
* @param key
|
||||
* @update:移除缓存中的某个属性
|
||||
* @updateBy:lsq
|
||||
* @updateDate:2021-09-07
|
||||
*/
|
||||
export function removeAuthCache<T>(key: BasicKeys) {
|
||||
const fn = isLocal ? Persistent.removeLocal : Persistent.removeSession;
|
||||
return fn(key) as T;
|
||||
}
|
||||
|
||||
export function clearAuthCache(immediate = true) {
|
||||
const fn = isLocal ? Persistent.clearLocal : Persistent.clearSession;
|
||||
return fn(immediate);
|
||||
}
|
||||
52
jeecgboot-vue3/src/utils/bem.ts
Normal file
52
jeecgboot-vue3/src/utils/bem.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { prefixCls } from '/@/settings/designSetting';
|
||||
|
||||
type Mod = string | { [key: string]: any };
|
||||
type Mods = Mod | Mod[];
|
||||
|
||||
export type BEM = ReturnType<typeof createBEM>;
|
||||
|
||||
function genBem(name: string, mods?: Mods): string {
|
||||
if (!mods) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (typeof mods === 'string') {
|
||||
return ` ${name}--${mods}`;
|
||||
}
|
||||
|
||||
if (Array.isArray(mods)) {
|
||||
return mods.reduce<string>((ret, item) => ret + genBem(name, item), '');
|
||||
}
|
||||
|
||||
return Object.keys(mods).reduce((ret, key) => ret + (mods[key] ? genBem(name, key) : ''), '');
|
||||
}
|
||||
|
||||
/**
|
||||
* bem helper
|
||||
* b() // 'button'
|
||||
* b('text') // 'button__text'
|
||||
* b({ disabled }) // 'button button--disabled'
|
||||
* b('text', { disabled }) // 'button__text button__text--disabled'
|
||||
* b(['disabled', 'primary']) // 'button button--disabled button--primary'
|
||||
*/
|
||||
export function buildBEM(name: string) {
|
||||
return (el?: Mods, mods?: Mods): Mods => {
|
||||
if (el && typeof el !== 'string') {
|
||||
mods = el;
|
||||
el = '';
|
||||
}
|
||||
|
||||
el = el ? `${name}__${el}` : name;
|
||||
|
||||
return `${el}${genBem(el, mods)}`;
|
||||
};
|
||||
}
|
||||
|
||||
export function createBEM(name: string) {
|
||||
return [buildBEM(`${prefixCls}-${name}`)];
|
||||
}
|
||||
|
||||
export function createNamespace(name: string) {
|
||||
const prefixedName = `${prefixCls}-${name}`;
|
||||
return [prefixedName, buildBEM(prefixedName)] as const;
|
||||
}
|
||||
37
jeecgboot-vue3/src/utils/browser.js
Normal file
37
jeecgboot-vue3/src/utils/browser.js
Normal file
@ -0,0 +1,37 @@
|
||||
//判断是否IE<11浏览器
|
||||
export function isIE() {
|
||||
return navigator.userAgent.indexOf('compatible') > -1 && navigator.userAgent.indexOf('MSIE') > -1;
|
||||
}
|
||||
|
||||
export function isIE11() {
|
||||
return navigator.userAgent.indexOf('Trident') > -1 && navigator.userAgent.indexOf('rv:11.0') > -1;
|
||||
}
|
||||
|
||||
//判断是否IE的Edge浏览器
|
||||
export function isEdge() {
|
||||
return navigator.userAgent.indexOf('Edge') > -1 && !isIE();
|
||||
}
|
||||
|
||||
export function getIEVersion() {
|
||||
let userAgent = navigator.userAgent; //取得浏览器的userAgent字符串
|
||||
let isIE = isIE();
|
||||
let isIE11 = isIE11();
|
||||
let isEdge = isEdge();
|
||||
|
||||
if (isIE) {
|
||||
let reIE = new RegExp('MSIE (\\d+\\.\\d+);');
|
||||
reIE.test(userAgent);
|
||||
let fIEVersion = parseFloat(RegExp['$1']);
|
||||
if (fIEVersion === 7 || fIEVersion === 8 || fIEVersion === 9 || fIEVersion === 10) {
|
||||
return fIEVersion;
|
||||
} else {
|
||||
return 6; //IE版本<7
|
||||
}
|
||||
} else if (isEdge) {
|
||||
return 'edge';
|
||||
} else if (isIE11) {
|
||||
return 11;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
32
jeecgboot-vue3/src/utils/cache/index.ts
vendored
Normal file
32
jeecgboot-vue3/src/utils/cache/index.ts
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
import { getStorageShortName } from '/@/utils/env';
|
||||
import { createStorage as create, CreateStorageParams } from './storageCache';
|
||||
import { enableStorageEncryption } from '/@/settings/encryptionSetting';
|
||||
import { DEFAULT_CACHE_TIME } from '/@/settings/encryptionSetting';
|
||||
|
||||
export type Options = Partial<CreateStorageParams>;
|
||||
|
||||
const createOptions = (storage: Storage, options: Options = {}): Options => {
|
||||
return {
|
||||
// No encryption in debug mode
|
||||
hasEncrypt: enableStorageEncryption,
|
||||
storage,
|
||||
prefixKey: getStorageShortName(),
|
||||
...options,
|
||||
};
|
||||
};
|
||||
|
||||
export const WebStorage = create(createOptions(sessionStorage));
|
||||
|
||||
export const createStorage = (storage: Storage = sessionStorage, options: Options = {}) => {
|
||||
return create(createOptions(storage, options));
|
||||
};
|
||||
|
||||
export const createSessionStorage = (options: Options = {}) => {
|
||||
return createStorage(sessionStorage, { ...options, timeout: DEFAULT_CACHE_TIME });
|
||||
};
|
||||
|
||||
export const createLocalStorage = (options: Options = {}) => {
|
||||
return createStorage(localStorage, { ...options, timeout: DEFAULT_CACHE_TIME });
|
||||
};
|
||||
|
||||
export default WebStorage;
|
||||
110
jeecgboot-vue3/src/utils/cache/memory.ts
vendored
Normal file
110
jeecgboot-vue3/src/utils/cache/memory.ts
vendored
Normal file
@ -0,0 +1,110 @@
|
||||
import { TOKEN_KEY, ROLES_KEY, USER_INFO_KEY, DB_DICT_DATA_KEY, TENANT_ID, LOGIN_INFO_KEY, PROJ_CFG_KEY } from '/@/enums/cacheEnum';
|
||||
import { omit } from 'lodash-es';
|
||||
|
||||
export interface Cache<V = any> {
|
||||
value?: V;
|
||||
timeoutId?: ReturnType<typeof setTimeout>;
|
||||
time?: number;
|
||||
alive?: number;
|
||||
}
|
||||
|
||||
const NOT_ALIVE = 0;
|
||||
|
||||
export class Memory<T = any, V = any> {
|
||||
private cache: { [key in keyof T]?: Cache<V> } = {};
|
||||
private alive: number;
|
||||
|
||||
constructor(alive = NOT_ALIVE) {
|
||||
// Unit second
|
||||
this.alive = alive * 1000;
|
||||
}
|
||||
|
||||
get getCache() {
|
||||
return this.cache;
|
||||
}
|
||||
|
||||
setCache(cache) {
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
// get<K extends keyof T>(key: K) {
|
||||
// const item = this.getItem(key);
|
||||
// const time = item?.time;
|
||||
// if (!isNullOrUnDef(time) && time < new Date().getTime()) {
|
||||
// this.remove(key);
|
||||
// }
|
||||
// return item?.value ?? undefined;
|
||||
// }
|
||||
|
||||
get<K extends keyof T>(key: K) {
|
||||
return this.cache[key];
|
||||
}
|
||||
|
||||
set<K extends keyof T>(key: K, value: V, expires?: number) {
|
||||
let item = this.get(key);
|
||||
|
||||
if (!expires || (expires as number) <= 0) {
|
||||
expires = this.alive;
|
||||
}
|
||||
if (item) {
|
||||
if (item.timeoutId) {
|
||||
clearTimeout(item.timeoutId);
|
||||
item.timeoutId = undefined;
|
||||
}
|
||||
item.value = value;
|
||||
} else {
|
||||
item = { value, alive: expires };
|
||||
this.cache[key] = item;
|
||||
}
|
||||
|
||||
if (!expires) {
|
||||
return value;
|
||||
}
|
||||
const now = new Date().getTime();
|
||||
item.time = now + this.alive;
|
||||
item.timeoutId = setTimeout(
|
||||
() => {
|
||||
this.remove(key);
|
||||
},
|
||||
expires > now ? expires - now : expires
|
||||
);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
remove<K extends keyof T>(key: K) {
|
||||
const item = this.get(key);
|
||||
Reflect.deleteProperty(this.cache, key);
|
||||
if (item) {
|
||||
clearTimeout(item.timeoutId!);
|
||||
return item.value;
|
||||
}
|
||||
}
|
||||
|
||||
resetCache(cache: { [K in keyof T]: Cache }) {
|
||||
Object.keys(cache).forEach((key) => {
|
||||
const k = key as any as keyof T;
|
||||
const item = cache[k];
|
||||
if (item && item.time) {
|
||||
const now = new Date().getTime();
|
||||
const expire = item.time;
|
||||
if (expire > now) {
|
||||
this.set(k, item.value, expire);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
clear() {
|
||||
console.log('------clear------进入clear方法');
|
||||
Object.keys(this.cache).forEach((key) => {
|
||||
const item = this.cache[key];
|
||||
item.timeoutId && clearTimeout(item.timeoutId);
|
||||
});
|
||||
//update-begin---author:liusq Date:20220108 for:不删除登录用户的租户id,其他缓存信息都清除----
|
||||
this.cache = {
|
||||
...omit(this.cache, [TOKEN_KEY, USER_INFO_KEY, ROLES_KEY, DB_DICT_DATA_KEY, TENANT_ID, LOGIN_INFO_KEY, PROJ_CFG_KEY]),
|
||||
};
|
||||
//update-end---author:liusq Date:20220108 for:不删除登录用户的租户id,其他缓存信息都清除----
|
||||
}
|
||||
}
|
||||
146
jeecgboot-vue3/src/utils/cache/persistent.ts
vendored
Normal file
146
jeecgboot-vue3/src/utils/cache/persistent.ts
vendored
Normal file
@ -0,0 +1,146 @@
|
||||
import type { LockInfo, UserInfo, LoginInfo } from '/#/store';
|
||||
import type { ProjectConfig } from '/#/config';
|
||||
import type { RouteLocationNormalized } from 'vue-router';
|
||||
|
||||
import { createLocalStorage, createSessionStorage } from '/@/utils/cache';
|
||||
import { Memory } from './memory';
|
||||
import {
|
||||
TOKEN_KEY,
|
||||
USER_INFO_KEY,
|
||||
ROLES_KEY,
|
||||
LOCK_INFO_KEY,
|
||||
PROJ_CFG_KEY,
|
||||
APP_LOCAL_CACHE_KEY,
|
||||
APP_SESSION_CACHE_KEY,
|
||||
MULTIPLE_TABS_KEY,
|
||||
DB_DICT_DATA_KEY,
|
||||
TENANT_ID,
|
||||
LOGIN_INFO_KEY,
|
||||
OAUTH2_THIRD_LOGIN_TENANT_ID,
|
||||
} from '/@/enums/cacheEnum';
|
||||
import { DEFAULT_CACHE_TIME } from '/@/settings/encryptionSetting';
|
||||
import { toRaw } from 'vue';
|
||||
import { pick, omit } from 'lodash-es';
|
||||
|
||||
interface BasicStore {
|
||||
[TOKEN_KEY]: string | number | null | undefined;
|
||||
[USER_INFO_KEY]: UserInfo;
|
||||
[ROLES_KEY]: string[];
|
||||
[LOCK_INFO_KEY]: LockInfo;
|
||||
[PROJ_CFG_KEY]: ProjectConfig;
|
||||
[MULTIPLE_TABS_KEY]: RouteLocationNormalized[];
|
||||
[DB_DICT_DATA_KEY]: string;
|
||||
[TENANT_ID]: string;
|
||||
[LOGIN_INFO_KEY]: LoginInfo;
|
||||
[OAUTH2_THIRD_LOGIN_TENANT_ID]: string
|
||||
}
|
||||
|
||||
type LocalStore = BasicStore;
|
||||
|
||||
type SessionStore = BasicStore;
|
||||
|
||||
export type BasicKeys = keyof BasicStore;
|
||||
type LocalKeys = keyof LocalStore;
|
||||
type SessionKeys = keyof SessionStore;
|
||||
|
||||
const ls = createLocalStorage();
|
||||
const ss = createSessionStorage();
|
||||
|
||||
const localMemory = new Memory(DEFAULT_CACHE_TIME);
|
||||
const sessionMemory = new Memory(DEFAULT_CACHE_TIME);
|
||||
|
||||
function initPersistentMemory() {
|
||||
const localCache = ls.get(APP_LOCAL_CACHE_KEY);
|
||||
const sessionCache = ss.get(APP_SESSION_CACHE_KEY);
|
||||
localCache && localMemory.resetCache(localCache);
|
||||
sessionCache && sessionMemory.resetCache(sessionCache);
|
||||
}
|
||||
|
||||
export class Persistent {
|
||||
static getLocal<T>(key: LocalKeys) {
|
||||
//update-begin---author:scott ---date:2022-10-27 for:token过期退出重新登录,online菜单还是提示token过期----------
|
||||
const globalCache = ls.get(APP_LOCAL_CACHE_KEY);
|
||||
if(globalCache){
|
||||
localMemory.setCache(globalCache);
|
||||
}
|
||||
//update-end---author:scott ---date::2022-10-27 for:token过期退出重新登录,online菜单还是提示token过期----------
|
||||
return localMemory.get(key)?.value as Nullable<T>;
|
||||
}
|
||||
|
||||
static setLocal(key: LocalKeys, value: LocalStore[LocalKeys], immediate = false): void {
|
||||
localMemory.set(key, toRaw(value));
|
||||
immediate && ls.set(APP_LOCAL_CACHE_KEY, localMemory.getCache);
|
||||
}
|
||||
|
||||
static removeLocal(key: LocalKeys, immediate = false): void {
|
||||
localMemory.remove(key);
|
||||
immediate && ls.set(APP_LOCAL_CACHE_KEY, localMemory.getCache);
|
||||
}
|
||||
|
||||
static clearLocal(immediate = false): void {
|
||||
localMemory.clear();
|
||||
immediate && ls.clear();
|
||||
}
|
||||
|
||||
static getSession<T>(key: SessionKeys) {
|
||||
return sessionMemory.get(key)?.value as Nullable<T>;
|
||||
}
|
||||
|
||||
static setSession(key: SessionKeys, value: SessionStore[SessionKeys], immediate = false): void {
|
||||
sessionMemory.set(key, toRaw(value));
|
||||
immediate && ss.set(APP_SESSION_CACHE_KEY, sessionMemory.getCache);
|
||||
}
|
||||
|
||||
static removeSession(key: SessionKeys, immediate = false): void {
|
||||
sessionMemory.remove(key);
|
||||
immediate && ss.set(APP_SESSION_CACHE_KEY, sessionMemory.getCache);
|
||||
}
|
||||
static clearSession(immediate = false): void {
|
||||
sessionMemory.clear();
|
||||
immediate && ss.clear();
|
||||
}
|
||||
|
||||
static clearAll(immediate = false) {
|
||||
sessionMemory.clear();
|
||||
localMemory.clear();
|
||||
if (immediate) {
|
||||
ls.clear();
|
||||
ss.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('beforeunload', function () {
|
||||
// TOKEN_KEY 在登录或注销时已经写入到storage了,此处为了解决同时打开多个窗口时token不同步的问题
|
||||
// LOCK_INFO_KEY 在锁屏和解锁时写入,此处也不应修改
|
||||
ls.set(APP_LOCAL_CACHE_KEY, {
|
||||
...omit(localMemory.getCache, LOCK_INFO_KEY),
|
||||
...pick(ls.get(APP_LOCAL_CACHE_KEY), [TOKEN_KEY, USER_INFO_KEY, LOCK_INFO_KEY]),
|
||||
});
|
||||
ss.set(APP_SESSION_CACHE_KEY, {
|
||||
...omit(sessionMemory.getCache, LOCK_INFO_KEY),
|
||||
...pick(ss.get(APP_SESSION_CACHE_KEY), [TOKEN_KEY, USER_INFO_KEY, LOCK_INFO_KEY]),
|
||||
});
|
||||
});
|
||||
|
||||
function storageChange(e: any) {
|
||||
const { key, newValue, oldValue } = e;
|
||||
|
||||
if (!key) {
|
||||
Persistent.clearAll();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!!newValue && !!oldValue) {
|
||||
if (APP_LOCAL_CACHE_KEY === key) {
|
||||
Persistent.clearLocal();
|
||||
}
|
||||
if (APP_SESSION_CACHE_KEY === key) {
|
||||
Persistent.clearSession();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('storage', storageChange);
|
||||
|
||||
initPersistentMemory();
|
||||
112
jeecgboot-vue3/src/utils/cache/storageCache.ts
vendored
Normal file
112
jeecgboot-vue3/src/utils/cache/storageCache.ts
vendored
Normal file
@ -0,0 +1,112 @@
|
||||
import { cacheCipher } from '/@/settings/encryptionSetting';
|
||||
|
||||
import type { EncryptionParams } from '/@/utils/cipher';
|
||||
|
||||
import { AesEncryption } from '/@/utils/cipher';
|
||||
|
||||
import { isNullOrUnDef } from '/@/utils/is';
|
||||
|
||||
export interface CreateStorageParams extends EncryptionParams {
|
||||
prefixKey: string;
|
||||
storage: Storage;
|
||||
hasEncrypt: boolean;
|
||||
timeout?: Nullable<number>;
|
||||
}
|
||||
export const createStorage = ({
|
||||
prefixKey = '',
|
||||
storage = sessionStorage,
|
||||
key = cacheCipher.key,
|
||||
iv = cacheCipher.iv,
|
||||
timeout = null,
|
||||
hasEncrypt = true,
|
||||
}: Partial<CreateStorageParams> = {}) => {
|
||||
if (hasEncrypt && [key.length, iv.length].some((item) => item !== 16)) {
|
||||
throw new Error('When hasEncrypt is true, the key or iv must be 16 bits!');
|
||||
}
|
||||
|
||||
const encryption = new AesEncryption({ key, iv });
|
||||
|
||||
/**
|
||||
*Cache class
|
||||
*Construction parameters can be passed into sessionStorage, localStorage,
|
||||
* @class Cache
|
||||
* @example
|
||||
*/
|
||||
const WebStorage = class WebStorage {
|
||||
private storage: Storage;
|
||||
private prefixKey?: string;
|
||||
private encryption: AesEncryption;
|
||||
private hasEncrypt: boolean;
|
||||
/**
|
||||
*
|
||||
* @param {*} storage
|
||||
*/
|
||||
constructor() {
|
||||
this.storage = storage;
|
||||
this.prefixKey = prefixKey;
|
||||
this.encryption = encryption;
|
||||
this.hasEncrypt = hasEncrypt;
|
||||
}
|
||||
|
||||
private getKey(key: string) {
|
||||
return `${this.prefixKey}${key}`.toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Set cache
|
||||
* @param {string} key
|
||||
* @param {*} value
|
||||
* @expire Expiration time in seconds
|
||||
* @memberof Cache
|
||||
*/
|
||||
set(key: string, value: any, expire: number | null = timeout) {
|
||||
const stringData = JSON.stringify({
|
||||
value,
|
||||
time: Date.now(),
|
||||
expire: !isNullOrUnDef(expire) ? new Date().getTime() + expire * 1000 : null,
|
||||
});
|
||||
const stringifyValue = this.hasEncrypt ? this.encryption.encryptByAES(stringData) : stringData;
|
||||
this.storage.setItem(this.getKey(key), stringifyValue);
|
||||
}
|
||||
|
||||
/**
|
||||
*Read cache
|
||||
* @param {string} key
|
||||
* @memberof Cache
|
||||
*/
|
||||
get(key: string, def: any = null): any {
|
||||
const val = this.storage.getItem(this.getKey(key));
|
||||
if (!val) return def;
|
||||
|
||||
try {
|
||||
const decVal = this.hasEncrypt ? this.encryption.decryptByAES(val) : val;
|
||||
const data = JSON.parse(decVal);
|
||||
const { value, expire } = data;
|
||||
if (isNullOrUnDef(expire) || expire >= new Date().getTime()) {
|
||||
return value;
|
||||
}
|
||||
this.remove(key);
|
||||
} catch (e) {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete cache based on key
|
||||
* @param {string} key
|
||||
* @memberof Cache
|
||||
*/
|
||||
remove(key: string) {
|
||||
this.storage.removeItem(this.getKey(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all caches of this instance
|
||||
*/
|
||||
clear(): void {
|
||||
this.storage.clear();
|
||||
}
|
||||
};
|
||||
return new WebStorage();
|
||||
};
|
||||
55
jeecgboot-vue3/src/utils/cipher.ts
Normal file
55
jeecgboot-vue3/src/utils/cipher.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { encrypt, decrypt } from 'crypto-js/aes';
|
||||
import { parse } from 'crypto-js/enc-utf8';
|
||||
import pkcs7 from 'crypto-js/pad-pkcs7';
|
||||
import ECB from 'crypto-js/mode-ecb';
|
||||
import md5 from 'crypto-js/md5';
|
||||
import UTF8 from 'crypto-js/enc-utf8';
|
||||
import Base64 from 'crypto-js/enc-base64';
|
||||
|
||||
export interface EncryptionParams {
|
||||
key: string;
|
||||
iv: string;
|
||||
}
|
||||
|
||||
export class AesEncryption {
|
||||
private key;
|
||||
private iv;
|
||||
|
||||
constructor(opt: Partial<EncryptionParams> = {}) {
|
||||
const { key, iv } = opt;
|
||||
if (key) {
|
||||
this.key = parse(key);
|
||||
}
|
||||
if (iv) {
|
||||
this.iv = parse(iv);
|
||||
}
|
||||
}
|
||||
|
||||
get getOptions() {
|
||||
return {
|
||||
mode: ECB,
|
||||
padding: pkcs7,
|
||||
iv: this.iv,
|
||||
};
|
||||
}
|
||||
|
||||
encryptByAES(cipherText: string) {
|
||||
return encrypt(cipherText, this.key, this.getOptions).toString();
|
||||
}
|
||||
|
||||
decryptByAES(cipherText: string) {
|
||||
return decrypt(cipherText, this.key, this.getOptions).toString(UTF8);
|
||||
}
|
||||
}
|
||||
|
||||
export function encryptByBase64(cipherText: string) {
|
||||
return UTF8.parse(cipherText).toString(Base64);
|
||||
}
|
||||
|
||||
export function decodeByBase64(cipherText: string) {
|
||||
return Base64.parse(cipherText).toString(UTF8);
|
||||
}
|
||||
|
||||
export function encryptByMd5(password: string) {
|
||||
return md5(password).toString();
|
||||
}
|
||||
145
jeecgboot-vue3/src/utils/color.ts
Normal file
145
jeecgboot-vue3/src/utils/color.ts
Normal file
@ -0,0 +1,145 @@
|
||||
/**
|
||||
* 判断是否 十六进制颜色值.
|
||||
* 输入形式可为 #fff000 #f00
|
||||
*
|
||||
* @param String color 十六进制颜色值
|
||||
* @return Boolean
|
||||
*/
|
||||
export function isHexColor(color: string) {
|
||||
const reg = /^#([0-9a-fA-F]{3}|[0-9a-fA-f]{6})$/;
|
||||
return reg.test(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* RGB 颜色值转换为 十六进制颜色值.
|
||||
* r, g, 和 b 需要在 [0, 255] 范围内
|
||||
*
|
||||
* @return String 类似#ff00ff
|
||||
* @param r
|
||||
* @param g
|
||||
* @param b
|
||||
*/
|
||||
export function rgbToHex(r: number, g: number, b: number) {
|
||||
// tslint:disable-next-line:no-bitwise
|
||||
const hex = ((r << 16) | (g << 8) | b).toString(16);
|
||||
return '#' + new Array(Math.abs(hex.length - 7)).join('0') + hex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a HEX color to its RGB representation
|
||||
* @param {string} hex The color to transform
|
||||
* @returns The RGB representation of the passed color
|
||||
*/
|
||||
export function hexToRGB(hex: string) {
|
||||
let sHex = hex.toLowerCase();
|
||||
if (isHexColor(hex)) {
|
||||
if (sHex.length === 4) {
|
||||
let sColorNew = '#';
|
||||
for (let i = 1; i < 4; i += 1) {
|
||||
sColorNew += sHex.slice(i, i + 1).concat(sHex.slice(i, i + 1));
|
||||
}
|
||||
sHex = sColorNew;
|
||||
}
|
||||
const sColorChange: number[] = [];
|
||||
for (let i = 1; i < 7; i += 2) {
|
||||
sColorChange.push(parseInt('0x' + sHex.slice(i, i + 2)));
|
||||
}
|
||||
return 'RGB(' + sColorChange.join(',') + ')';
|
||||
}
|
||||
return sHex;
|
||||
}
|
||||
|
||||
export function colorIsDark(color: string) {
|
||||
if (!isHexColor(color)) return;
|
||||
const [r, g, b] = hexToRGB(color)
|
||||
.replace(/(?:\(|\)|rgb|RGB)*/g, '')
|
||||
.split(',')
|
||||
.map((item) => Number(item));
|
||||
return r * 0.299 + g * 0.578 + b * 0.114 < 192;
|
||||
}
|
||||
|
||||
/**
|
||||
* Darkens a HEX color given the passed percentage
|
||||
* @param {string} color The color to process
|
||||
* @param {number} amount The amount to change the color by
|
||||
* @returns {string} The HEX representation of the processed color
|
||||
*/
|
||||
export function darken(color: string, amount: number) {
|
||||
color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color;
|
||||
amount = Math.trunc((255 * amount) / 100);
|
||||
return `#${subtractLight(color.substring(0, 2), amount)}${subtractLight(color.substring(2, 4), amount)}${subtractLight(
|
||||
color.substring(4, 6),
|
||||
amount
|
||||
)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lightens a 6 char HEX color according to the passed percentage
|
||||
* @param {string} color The color to change
|
||||
* @param {number} amount The amount to change the color by
|
||||
* @returns {string} The processed color represented as HEX
|
||||
*/
|
||||
export function lighten(color: string, amount: number) {
|
||||
color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color;
|
||||
amount = Math.trunc((255 * amount) / 100);
|
||||
return `#${addLight(color.substring(0, 2), amount)}${addLight(color.substring(2, 4), amount)}${addLight(color.substring(4, 6), amount)}`;
|
||||
}
|
||||
|
||||
/* Suma el porcentaje indicado a un color (RR, GG o BB) hexadecimal para aclararlo */
|
||||
/**
|
||||
* Sums the passed percentage to the R, G or B of a HEX color
|
||||
* @param {string} color The color to change
|
||||
* @param {number} amount The amount to change the color by
|
||||
* @returns {string} The processed part of the color
|
||||
*/
|
||||
function addLight(color: string, amount: number) {
|
||||
const cc = parseInt(color, 16) + amount;
|
||||
const c = cc > 255 ? 255 : cc;
|
||||
return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates luminance of an rgb color
|
||||
* @param {number} r red
|
||||
* @param {number} g green
|
||||
* @param {number} b blue
|
||||
*/
|
||||
function luminanace(r: number, g: number, b: number) {
|
||||
const a = [r, g, b].map((v) => {
|
||||
v /= 255;
|
||||
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
|
||||
});
|
||||
return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates contrast between two rgb colors
|
||||
* @param {string} rgb1 rgb color 1
|
||||
* @param {string} rgb2 rgb color 2
|
||||
*/
|
||||
function contrast(rgb1: string[], rgb2: number[]) {
|
||||
return (luminanace(~~rgb1[0], ~~rgb1[1], ~~rgb1[2]) + 0.05) / (luminanace(rgb2[0], rgb2[1], rgb2[2]) + 0.05);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines what the best text color is (black or white) based con the contrast with the background
|
||||
* @param hexColor - Last selected color by the user
|
||||
*/
|
||||
export function calculateBestTextColor(hexColor: string) {
|
||||
const rgbColor = hexToRGB(hexColor.substring(1));
|
||||
const contrastWithBlack = contrast(rgbColor.split(','), [0, 0, 0]);
|
||||
|
||||
return contrastWithBlack >= 12 ? '#000000' : '#FFFFFF';
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtracts the indicated percentage to the R, G or B of a HEX color
|
||||
* @param {string} color The color to change
|
||||
* @param {number} amount The amount to change the color by
|
||||
* @returns {string} The processed part of the color
|
||||
*/
|
||||
function subtractLight(color: string, amount: number) {
|
||||
const cc = parseInt(color, 16) - amount;
|
||||
const c = cc < 0 ? 0 : cc;
|
||||
return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`;
|
||||
}
|
||||
577
jeecgboot-vue3/src/utils/common/compUtils.ts
Normal file
577
jeecgboot-vue3/src/utils/common/compUtils.ts
Normal file
@ -0,0 +1,577 @@
|
||||
import { useGlobSetting } from '/@/hooks/setting';
|
||||
import { merge, random } from 'lodash-es';
|
||||
import { isArray } from '/@/utils/is';
|
||||
import { FormSchema } from '/@/components/Form';
|
||||
import { reactive } from "vue";
|
||||
import { getTenantId, getToken } from "/@/utils/auth";
|
||||
import { useUserStoreWithOut } from "/@/store/modules/user";
|
||||
|
||||
import { Modal } from "ant-design-vue";
|
||||
import { defHttp } from "@/utils/http/axios";
|
||||
import { useI18n } from "@/hooks/web/useI18n";
|
||||
|
||||
const globSetting = useGlobSetting();
|
||||
const baseApiUrl = globSetting.domainUrl;
|
||||
/**
|
||||
* 获取文件服务访问路径
|
||||
* @param fileUrl 文件路径
|
||||
* @param prefix(默认http) 文件路径前缀 http/https
|
||||
*/
|
||||
export const getFileAccessHttpUrl = (fileUrl, prefix = 'http') => {
|
||||
let result = fileUrl;
|
||||
try {
|
||||
if (fileUrl && fileUrl.length > 0 && !fileUrl.startsWith(prefix)) {
|
||||
//判断是否是数组格式
|
||||
let isArray = fileUrl.indexOf('[') != -1;
|
||||
if (!isArray) {
|
||||
let prefix = `${baseApiUrl}/sys/common/static/`;
|
||||
// 判断是否已包含前缀
|
||||
if (!fileUrl.startsWith(prefix)) {
|
||||
result = `${prefix}${fileUrl}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 触发 window.resize
|
||||
*/
|
||||
export function triggerWindowResizeEvent() {
|
||||
let event: any = document.createEvent('HTMLEvents');
|
||||
event.initEvent('resize', true, true);
|
||||
event.eventType = 'message';
|
||||
window.dispatchEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取随机数
|
||||
* @param length 数字位数
|
||||
*/
|
||||
export const getRandom = (length: number = 1) => {
|
||||
return '-' + parseInt(String(Math.random() * 10000 + 1), length);
|
||||
};
|
||||
|
||||
/**
|
||||
* 随机生成字符串
|
||||
* @param length 字符串的长度
|
||||
* @param chats 可选字符串区间(只会生成传入的字符串中的字符)
|
||||
* @return string 生成的字符串
|
||||
*/
|
||||
export function randomString(length: number, chats?: string) {
|
||||
if (!length) length = 1;
|
||||
if (!chats) {
|
||||
// noinspection SpellCheckingInspection
|
||||
chats = '0123456789qwertyuioplkjhgfdsazxcvbnm';
|
||||
}
|
||||
let str = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
let num = random(0, chats.length - 1);
|
||||
str += chats[num];
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将普通列表数据转化为tree结构
|
||||
* @param array tree数据
|
||||
* @param opt 配置参数
|
||||
* @param startPid 父节点
|
||||
*/
|
||||
export const listToTree = (array, opt, startPid) => {
|
||||
const obj = {
|
||||
primaryKey: opt.primaryKey || 'key',
|
||||
parentKey: opt.parentKey || 'parentId',
|
||||
titleKey: opt.titleKey || 'title',
|
||||
startPid: opt.startPid || '',
|
||||
currentDept: opt.currentDept || 0,
|
||||
maxDept: opt.maxDept || 100,
|
||||
childKey: opt.childKey || 'children',
|
||||
};
|
||||
if (startPid) {
|
||||
obj.startPid = startPid;
|
||||
}
|
||||
return toTree(array, obj.startPid, obj.currentDept, obj);
|
||||
};
|
||||
/**
|
||||
* 递归构建tree
|
||||
* @param list
|
||||
* @param startPid
|
||||
* @param currentDept
|
||||
* @param opt
|
||||
* @returns {Array}
|
||||
*/
|
||||
export const toTree = (array, startPid, currentDept, opt) => {
|
||||
if (opt.maxDept < currentDept) {
|
||||
return [];
|
||||
}
|
||||
let child = [];
|
||||
if (array && array.length > 0) {
|
||||
child = array
|
||||
.map((item) => {
|
||||
// 筛查符合条件的数据(主键 = startPid)
|
||||
if (typeof item[opt.parentKey] !== 'undefined' && item[opt.parentKey] === startPid) {
|
||||
// 满足条件则递归
|
||||
const nextChild = toTree(array, item[opt.primaryKey], currentDept + 1, opt);
|
||||
// 节点信息保存
|
||||
if (nextChild.length > 0) {
|
||||
item['isLeaf'] = false;
|
||||
item[opt.childKey] = nextChild;
|
||||
} else {
|
||||
item['isLeaf'] = true;
|
||||
}
|
||||
item['title'] = item[opt.titleKey];
|
||||
item['label'] = item[opt.titleKey];
|
||||
item['key'] = item[opt.primaryKey];
|
||||
item['value'] = item[opt.primaryKey];
|
||||
return item;
|
||||
}
|
||||
})
|
||||
.filter((item) => {
|
||||
return item !== undefined;
|
||||
});
|
||||
}
|
||||
return child;
|
||||
};
|
||||
|
||||
/**
|
||||
* 表格底部合计工具方法
|
||||
* @param tableData 表格数据
|
||||
* @param fieldKeys 要计算合计的列字段
|
||||
*/
|
||||
export function mapTableTotalSummary(tableData: Recordable[], fieldKeys: string[]) {
|
||||
let totals: any = { _row: '合计', _index: '合计' };
|
||||
fieldKeys.forEach((key) => {
|
||||
totals[key] = tableData.reduce((prev, next) => {
|
||||
// update-begin--author:liaozhiyang---date:20240118---for:【QQYUN-7891】PR 合计工具方法,转换为Nuber类型再计算
|
||||
const value = Number(next[key]);
|
||||
if (!Number.isNaN(value)) {
|
||||
prev += value;
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240118---for:【QQYUN-7891】PR 合计工具方法,转换为Nuber类型再计算
|
||||
return prev;
|
||||
}, 0);
|
||||
});
|
||||
return totals;
|
||||
}
|
||||
|
||||
/**
|
||||
* 简单实现防抖方法
|
||||
*
|
||||
* 防抖(debounce)函数在第一次触发给定的函数时,不立即执行函数,而是给出一个期限值(delay),比如100ms。
|
||||
* 如果100ms内再次执行函数,就重新开始计时,直到计时结束后再真正执行函数。
|
||||
* 这样做的好处是如果短时间内大量触发同一事件,只会执行一次函数。
|
||||
*
|
||||
* @param fn 要防抖的函数
|
||||
* @param delay 防抖的毫秒数
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function simpleDebounce(fn, delay = 100) {
|
||||
let timer: any | null = null;
|
||||
return function () {
|
||||
let args = arguments;
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
timer = setTimeout(() => {
|
||||
// @ts-ignore
|
||||
fn.apply(this, args);
|
||||
}, delay);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 日期格式化
|
||||
* @param date 日期
|
||||
* @param block 格式化字符串
|
||||
*/
|
||||
export function dateFormat(date, block) {
|
||||
if (!date) {
|
||||
return '';
|
||||
}
|
||||
let format = block || 'yyyy-MM-dd';
|
||||
date = new Date(date);
|
||||
const map = {
|
||||
M: date.getMonth() + 1, // 月份
|
||||
d: date.getDate(), // 日
|
||||
h: date.getHours(), // 小时
|
||||
m: date.getMinutes(), // 分
|
||||
s: date.getSeconds(), // 秒
|
||||
q: Math.floor((date.getMonth() + 3) / 3), // 季度
|
||||
S: date.getMilliseconds(), // 毫秒
|
||||
};
|
||||
format = format.replace(/([yMdhmsqS])+/g, (all, t) => {
|
||||
let v = map[t];
|
||||
if (v !== undefined) {
|
||||
if (all.length > 1) {
|
||||
v = `0${v}`;
|
||||
v = v.substr(v.length - 2);
|
||||
}
|
||||
return v;
|
||||
} else if (t === 'y') {
|
||||
return date
|
||||
.getFullYear()
|
||||
.toString()
|
||||
.substr(4 - all.length);
|
||||
}
|
||||
return all;
|
||||
});
|
||||
return format;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取事件冒泡路径,兼容 IE11,Edge,Chrome,Firefox,Safari
|
||||
* 目前使用的地方:JVxeTable Span模式
|
||||
*/
|
||||
export function getEventPath(event) {
|
||||
let target = event.target;
|
||||
let path = (event.composedPath && event.composedPath()) || event.path;
|
||||
|
||||
if (path != null) {
|
||||
return path.indexOf(window) < 0 ? path.concat(window) : path;
|
||||
}
|
||||
|
||||
if (target === window) {
|
||||
return [window];
|
||||
}
|
||||
|
||||
let getParents = (node, memo) => {
|
||||
const parentNode = node.parentNode;
|
||||
|
||||
if (!parentNode) {
|
||||
return memo;
|
||||
} else {
|
||||
return getParents(parentNode, memo.concat(parentNode));
|
||||
}
|
||||
};
|
||||
return [target].concat(getParents(target, []), window);
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果值不存在就 push 进数组,反之不处理
|
||||
* @param array 要操作的数据
|
||||
* @param value 要添加的值
|
||||
* @param key 可空,如果比较的是对象,可能存在地址不一样但值实际上是一样的情况,可以传此字段判断对象中唯一的字段,例如 id。不传则直接比较实际值
|
||||
* @returns {boolean} 成功 push 返回 true,不处理返回 false
|
||||
*/
|
||||
export function pushIfNotExist(array, value, key?) {
|
||||
for (let item of array) {
|
||||
if (key && item[key] === value[key]) {
|
||||
return false;
|
||||
} else if (item === value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
array.push(value);
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* 过滤对象中为空的属性
|
||||
* @param obj
|
||||
* @returns {*}
|
||||
*/
|
||||
export function filterObj(obj) {
|
||||
if (!(typeof obj == 'object')) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let key in obj) {
|
||||
if (obj.hasOwnProperty(key) && (obj[key] == null || obj[key] == undefined || obj[key] === '')) {
|
||||
delete obj[key];
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 下划线转驼峰
|
||||
* @param string
|
||||
*/
|
||||
export function underLine2CamelCase(string: string) {
|
||||
return string.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找树结构
|
||||
* @param treeList
|
||||
* @param fn 查找方法
|
||||
* @param childrenKey
|
||||
*/
|
||||
export function findTree(treeList: any[], fn: Fn, childrenKey = 'children') {
|
||||
for (let i = 0; i < treeList.length; i++) {
|
||||
let item = treeList[i];
|
||||
if (fn(item, i, treeList)) {
|
||||
return item;
|
||||
}
|
||||
let children = item[childrenKey];
|
||||
if (isArray(children)) {
|
||||
let findResult = findTree(children, fn, childrenKey);
|
||||
if (findResult) {
|
||||
return findResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 获取 mapFormSchema 方法 */
|
||||
export function bindMapFormSchema<T>(spanMap, spanTypeDef: T) {
|
||||
return function (s: FormSchema, spanType: T = spanTypeDef) {
|
||||
return merge(
|
||||
{
|
||||
disabledLabelWidth: true,
|
||||
} as FormSchema,
|
||||
spanMap[spanType],
|
||||
s
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 字符串是否为null或null字符串
|
||||
* @param str
|
||||
* @return {boolean}
|
||||
*/
|
||||
export function stringIsNull(str) {
|
||||
// 两个 == 可以同时判断 null 和 undefined
|
||||
return str == null || str === 'null' || str === 'undefined';
|
||||
}
|
||||
|
||||
/**
|
||||
* 【组件多了可能存在性能问题】获取弹窗div,将下拉框、日期等组件挂载到modal上,解决弹窗遮盖问题
|
||||
* @param node
|
||||
*/
|
||||
export function getAutoScrollContainer(node: HTMLElement) {
|
||||
let element: Nullable<HTMLElement> = node
|
||||
while (element != null) {
|
||||
if (element.classList.contains('scrollbar__view')) {
|
||||
// 判断是否有滚动条
|
||||
if (element.clientHeight < element.scrollHeight) {
|
||||
// 有滚动条时,挂载到父级,解决滚动问题
|
||||
return node.parentElement
|
||||
} else {
|
||||
// 无滚动条时,挂载到body上,解决下拉框遮盖问题
|
||||
return document.body
|
||||
}
|
||||
} else {
|
||||
element = element.parentElement
|
||||
}
|
||||
}
|
||||
// 不在弹窗内,走默认逻辑
|
||||
return node.parentElement
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断子菜单是否全部隐藏
|
||||
* @param menuTreeItem
|
||||
*/
|
||||
export function checkChildrenHidden(menuTreeItem){
|
||||
//是否是聚合路由
|
||||
let alwaysShow=menuTreeItem.alwaysShow;
|
||||
if(alwaysShow){
|
||||
return false;
|
||||
}
|
||||
if(!menuTreeItem.children){
|
||||
return false
|
||||
}
|
||||
return menuTreeItem.children?.find((item) => item.hideMenu == false) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算文件大小
|
||||
* @param fileSize
|
||||
* @param unit
|
||||
* @return 返回大小及后缀
|
||||
*/
|
||||
export function calculateFileSize(fileSize, unit?) {
|
||||
let unitArr = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
if (unit && unit.length > 0) {
|
||||
unitArr = unit;
|
||||
}
|
||||
let size = fileSize;
|
||||
let unitIndex = 0;
|
||||
while (size >= 1024 && unitIndex < unitArr.length - 1) {
|
||||
size /= 1024;
|
||||
unitIndex++;
|
||||
}
|
||||
//保留两位小数,四舍五入
|
||||
size = Math.round(size * 100) / 100;
|
||||
return size + unitArr[unitIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上传header
|
||||
*/
|
||||
export function getHeaders() {
|
||||
let tenantId = getTenantId();
|
||||
return reactive({
|
||||
'X-Access-Token': getToken(),
|
||||
'X-Tenant-Id': tenantId ? tenantId : '0',
|
||||
});
|
||||
}
|
||||
|
||||
/** 根据表达式获取相应的用户信息 */
|
||||
export function getUserInfoByExpression(expression) {
|
||||
if (!expression) {
|
||||
return expression;
|
||||
}
|
||||
const userStore = useUserStoreWithOut();
|
||||
let userInfo = userStore.getUserInfo;
|
||||
if (userInfo) {
|
||||
switch (expression) {
|
||||
case 'sysUserId':
|
||||
return userInfo.id;
|
||||
// 当前登录用户登录账号
|
||||
case 'sysUserCode':
|
||||
case 'sys_user_code':
|
||||
return userInfo.username;
|
||||
// 当前登录用户真实名称
|
||||
case 'sysUserName':
|
||||
return userInfo.realname;
|
||||
// 当前登录用户部门编号
|
||||
case 'sysOrgCode':
|
||||
case 'sys_org_code':
|
||||
return userInfo.orgCode;
|
||||
}
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* 替换表达式(#{xxx})为用户信息
|
||||
* @param expression
|
||||
*/
|
||||
export function replaceUserInfoByExpression(expression: string | any[]) {
|
||||
if (!expression) {
|
||||
return expression;
|
||||
}
|
||||
const isString = typeof expression === 'string';
|
||||
const isArray = Array.isArray(expression)
|
||||
if (!isString && !isArray) {
|
||||
return expression;
|
||||
}
|
||||
const reg = /#{(.*?)}/g;
|
||||
const replace = (str) => {
|
||||
if (typeof str !== 'string') {
|
||||
return str;
|
||||
}
|
||||
let result = str.match(reg);
|
||||
if (result && result.length > 0) {
|
||||
result.forEach((item) => {
|
||||
let userInfo = getUserInfoByExpression(item.substring(2, item.length - 1));
|
||||
str = str.replace(item, userInfo);
|
||||
});
|
||||
}
|
||||
return str;
|
||||
};
|
||||
// @ts-ignore
|
||||
return isString ? replace(expression) : expression.map(replace);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置租户缓存,当租户退出的时候
|
||||
*
|
||||
* @param tenantId
|
||||
*/
|
||||
export async function userExitChangeLoginTenantId(tenantId){
|
||||
const userStore = useUserStoreWithOut();
|
||||
//step 1 获取用户租户
|
||||
const url = '/sys/tenant/getCurrentUserTenant'
|
||||
let currentTenantId = null;
|
||||
const data = await defHttp.get({ url });
|
||||
if(data && data.list){
|
||||
let arr = data.list;
|
||||
if(arr.length>0){
|
||||
//step 2.判断当前id是否存在用户租户中
|
||||
let filterTenantId = arr.filter((item) => item.id == tenantId);
|
||||
//存在说明不是退出的不是当前租户,还用用来的租户即可
|
||||
if(filterTenantId && filterTenantId.length>0){
|
||||
currentTenantId = tenantId;
|
||||
}else{
|
||||
//不存在默认第一个
|
||||
currentTenantId = arr[0].id
|
||||
}
|
||||
}
|
||||
}
|
||||
let loginTenantId = getTenantId();
|
||||
userStore.setTenant(currentTenantId);
|
||||
|
||||
//update-begin---author:wangshuai---date:2023-11-07---for:【QQYUN-7005】退租户,判断退出的租户ID与当前租户ID一致,再刷新---
|
||||
//租户为空,说明没有租户了,需要刷新页面。或者当前租户和退出的租户一致则需要刷新浏览器
|
||||
if(!currentTenantId || tenantId == loginTenantId){
|
||||
window.location.reload();
|
||||
}
|
||||
//update-end---author:wangshuai---date:2023-11-07---for:【QQYUN-7005】退租户,判断退出的租户ID与当前租户ID一致,再刷新---
|
||||
}
|
||||
|
||||
/**
|
||||
* 我的租户模块需要开启多租户提示
|
||||
*
|
||||
* @param title 标题
|
||||
*/
|
||||
export function tenantSaasMessage(title){
|
||||
let tenantId = getTenantId();
|
||||
if(!tenantId){
|
||||
Modal.confirm({
|
||||
title:title,
|
||||
content: '此菜单需要在多租户模式下使用,否则数据会出现混乱',
|
||||
okText: '确认',
|
||||
okType: 'danger',
|
||||
// @ts-ignore
|
||||
cancelButtonProps: { style: { display: 'none' } },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断日期和当前时间是否为同一天
|
||||
* @param dateStr
|
||||
*/
|
||||
export function sameDay(dateStr) {
|
||||
if (!dateStr) {
|
||||
return false;
|
||||
}
|
||||
// 获取当前日期
|
||||
let currentDate = new Date();
|
||||
let currentDay = currentDate.getDate();
|
||||
let currentMonth = currentDate.getMonth();
|
||||
let currentYear = currentDate.getFullYear();
|
||||
|
||||
//创建另一个日期进行比较
|
||||
let otherDate = new Date(dateStr);
|
||||
let otherDay = otherDate.getDate();
|
||||
let otherMonth = otherDate.getMonth();
|
||||
let otherYear = otherDate.getFullYear();
|
||||
|
||||
//比较日期
|
||||
if (currentDay === otherDay && currentMonth === otherMonth && currentYear === otherYear) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 翻译菜单名称
|
||||
* 2024-02-28
|
||||
* liaozhiyang
|
||||
* @param data
|
||||
*/
|
||||
export function translateTitle(data) {
|
||||
if (data?.length) {
|
||||
const { t } = useI18n();
|
||||
data.forEach((item) => {
|
||||
if (item.slotTitle) {
|
||||
if (item.slotTitle.includes("t('") && t) {
|
||||
item.slotTitle = new Function('t', `return ${item.slotTitle}`)(t);
|
||||
}
|
||||
}
|
||||
if (item.children?.length) {
|
||||
translateTitle(item.children);
|
||||
}
|
||||
});
|
||||
}
|
||||
return data;
|
||||
}
|
||||
178
jeecgboot-vue3/src/utils/common/renderUtils.ts
Normal file
178
jeecgboot-vue3/src/utils/common/renderUtils.ts
Normal file
@ -0,0 +1,178 @@
|
||||
import { h } from 'vue';
|
||||
import { Avatar, Tag, Tooltip, Image } from 'ant-design-vue';
|
||||
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
|
||||
import { Tinymce } from '/@/components/Tinymce';
|
||||
import Icon from '/@/components/Icon';
|
||||
import { getDictItemsByCode } from '/@/utils/dict/index';
|
||||
import { filterMultiDictText } from '/@/utils/dict/JDictSelectUtil.js';
|
||||
import { isEmpty } from '/@/utils/is';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
const { createMessage } = useMessage();
|
||||
|
||||
const render = {
|
||||
/**
|
||||
* 渲染列表头像
|
||||
*/
|
||||
renderAvatar: ({ record }) => {
|
||||
if (record.avatar) {
|
||||
let avatarList = record.avatar.split(',');
|
||||
return h(
|
||||
'span',
|
||||
avatarList.map((item) => {
|
||||
return h(Avatar, {
|
||||
src: getFileAccessHttpUrl(item),
|
||||
shape: 'square',
|
||||
size: 'default',
|
||||
style: { marginRight: '5px' },
|
||||
});
|
||||
})
|
||||
);
|
||||
} else {
|
||||
return h(
|
||||
Avatar,
|
||||
{ shape: 'square', size: 'default' },
|
||||
{
|
||||
icon: () => h(Icon, { icon: 'ant-design:file-image-outlined', size: 30 }),
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 根据字典编码 渲染
|
||||
* @param v 值
|
||||
* @param code 字典编码
|
||||
* @param renderTag 是否使用tag渲染
|
||||
*/
|
||||
renderDict: (v, code, renderTag = false) => {
|
||||
let text = '';
|
||||
let array = getDictItemsByCode(code) || [];
|
||||
let obj = array.filter((item) => {
|
||||
return item.value == v;
|
||||
});
|
||||
if (obj.length > 0) {
|
||||
text = obj[0].text;
|
||||
}
|
||||
//【jeecgboot-vue3/issues/903】render.renderDict使用tag渲染报警告问题 #903
|
||||
return isEmpty(text) || !renderTag ? h('span', text) : h(Tag, () => text);
|
||||
},
|
||||
/**
|
||||
* 渲染图片
|
||||
* @param text
|
||||
*/
|
||||
renderImage: ({ text }) => {
|
||||
if (!text) {
|
||||
return h(Image, {
|
||||
width: 30,
|
||||
height: 30,
|
||||
src: '',
|
||||
fallback:
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMIAAADDCAYAAADQvc6UAAABRWlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGASSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8LAwSDCIMogwMCcmFxc4BgQ4ANUwgCjUcG3awyMIPqyLsis7PPOq3QdDFcvjV3jOD1boQVTPQrgSkktTgbSf4A4LbmgqISBgTEFyFYuLykAsTuAbJEioKOA7DkgdjqEvQHEToKwj4DVhAQ5A9k3gGyB5IxEoBmML4BsnSQk8XQkNtReEOBxcfXxUQg1Mjc0dyHgXNJBSWpFCYh2zi+oLMpMzyhRcASGUqqCZ16yno6CkYGRAQMDKMwhqj/fAIcloxgHQqxAjIHBEugw5sUIsSQpBobtQPdLciLEVJYzMPBHMDBsayhILEqEO4DxG0txmrERhM29nYGBddr//5/DGRjYNRkY/l7////39v///y4Dmn+LgeHANwDrkl1AuO+pmgAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAwqADAAQAAAABAAAAwwAAAAD9b/HnAAAHlklEQVR4Ae3dP3PTWBSGcbGzM6GCKqlIBRV0dHRJFarQ0eUT8LH4BnRU0NHR0UEFVdIlFRV7TzRksomPY8uykTk/zewQfKw/9znv4yvJynLv4uLiV2dBoDiBf4qP3/ARuCRABEFAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghgg0Aj8i0JO4OzsrPv69Wv+hi2qPHr0qNvf39+iI97soRIh4f3z58/u7du3SXX7Xt7Z2enevHmzfQe+oSN2apSAPj09TSrb+XKI/f379+08+A0cNRE2ANkupk+ACNPvkSPcAAEibACyXUyfABGm3yNHuAECRNgAZLuYPgEirKlHu7u7XdyytGwHAd8jjNyng4OD7vnz51dbPT8/7z58+NB9+/bt6jU/TI+AGWHEnrx48eJ/EsSmHzx40L18+fLyzxF3ZVMjEyDCiEDjMYZZS5wiPXnyZFbJaxMhQIQRGzHvWR7XCyOCXsOmiDAi1HmPMMQjDpbpEiDCiL358eNHurW/5SnWdIBbXiDCiA38/Pnzrce2YyZ4//59F3ePLNMl4PbpiL2J0L979+7yDtHDhw8vtzzvdGnEXdvUigSIsCLAWavHp/+qM0BcXMd/q25n1vF57TYBp0a3mUzilePj4+7k5KSLb6gt6ydAhPUzXnoPR0dHl79WGTNCfBnn1uvSCJdegQhLI1vvCk+fPu2ePXt2tZOYEV6/fn31dz+shwAR1sP1cqvLntbEN9MxA9xcYjsxS1jWR4AIa2Ibzx0tc44fYX/16lV6NDFLXH+YL32jwiACRBiEbf5KcXoTIsQSpzXx4N28Ja4BQoK7rgXiydbHjx/P25TaQAJEGAguWy0+2Q8PD6/Ki4R8EVl+bzBOnZY95fq9rj9zAkTI2SxdidBHqG9+skdw43borCXO/ZcJdraPWdv22uIEiLA4q7nvvCug8WTqzQveOH26fodo7g6uFe/a17W3+nFBAkRYENRdb1vkkz1CH9cPsVy/jrhr27PqMYvENYNlHAIesRiBYwRy0V+8iXP8+/fvX11Mr7L7ECueb/r48eMqm7FuI2BGWDEG8cm+7G3NEOfmdcTQw4h9/55lhm7DekRYKQPZF2ArbXTAyu4kDYB2YxUzwg0gi/41ztHnfQG26HbGel/crVrm7tNY+/1btkOEAZ2M05r4FB7r9GbAIdxaZYrHdOsgJ/wCEQY0J74TmOKnbxxT9n3FgGGWWsVdowHtjt9Nnvf7yQM2aZU/TIAIAxrw6dOnAWtZZcoEnBpNuTuObWMEiLAx1HY0ZQJEmHJ3HNvGCBBhY6jtaMoEiJB0Z29vL6ls58vxPcO8/zfrdo5qvKO+d3Fx8Wu8zf1dW4p/cPzLly/dtv9Ts/EbcvGAHhHyfBIhZ6NSiIBTo0LNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiEC/wGgKKC4YMA4TAAAAABJRU5ErkJggg==',
|
||||
});
|
||||
}
|
||||
let avatarList = text.split(',');
|
||||
return h(
|
||||
'span',
|
||||
avatarList.map((item) => {
|
||||
return h(Image, {
|
||||
src: getFileAccessHttpUrl(item),
|
||||
width: 30,
|
||||
height: 30,
|
||||
style: { marginRight: '5px' },
|
||||
previewMask: () => {
|
||||
return h(Icon, { icon: 'ant-design:eye-outlined', size: 20 });
|
||||
},
|
||||
});
|
||||
})
|
||||
);
|
||||
//update-end-author:taoyan date:2022-5-24 for: VUEN-1084 【vue3】online表单测试发现的新问题 41、生成的代码,树默认图大小未改
|
||||
},
|
||||
/**
|
||||
* 渲染 Tooltip
|
||||
* @param text
|
||||
* @param len
|
||||
*/
|
||||
renderTip: (text, len = 20) => {
|
||||
if (text) {
|
||||
let showText = text + '';
|
||||
if (showText.length > len) {
|
||||
showText = showText.substr(0, len) + '...';
|
||||
}
|
||||
return h(Tooltip, { title: text }, () => showText);
|
||||
}
|
||||
return text;
|
||||
},
|
||||
/**
|
||||
* 渲染a标签
|
||||
* @param text
|
||||
*/
|
||||
renderHref: ({ text }) => {
|
||||
if (!text) {
|
||||
return '';
|
||||
}
|
||||
const len = 20;
|
||||
if (text.length > len) {
|
||||
text = text.substr(0, len);
|
||||
}
|
||||
return h('a', { href: text, target: '_blank' }, text);
|
||||
},
|
||||
/**
|
||||
* 根据字典渲染
|
||||
* @param v
|
||||
* @param array
|
||||
*/
|
||||
renderDictNative: (v, array, renderTag = false) => {
|
||||
let text = '';
|
||||
let color = '';
|
||||
let obj = array.filter((item) => {
|
||||
return item.value == v;
|
||||
});
|
||||
if (obj.length > 0) {
|
||||
text = obj[0].label;
|
||||
color = obj[0].color;
|
||||
}
|
||||
return isEmpty(text) || !renderTag ? h('span', text) : h(Tag, { color }, () => text);
|
||||
},
|
||||
/**
|
||||
* 渲染富文本
|
||||
*/
|
||||
renderTinymce: ({ model, field }) => {
|
||||
return h(Tinymce, {
|
||||
showImageUpload: false,
|
||||
height: 300,
|
||||
value: model[field],
|
||||
onChange: (value: string) => {
|
||||
model[field] = value;
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
renderSwitch: (text, arr) => {
|
||||
return text ? filterMultiDictText(arr, text) : '';
|
||||
},
|
||||
renderCategoryTree: (text, code) => {
|
||||
let array = getDictItemsByCode(code);
|
||||
return filterMultiDictText(array, text);
|
||||
},
|
||||
renderTag(text, color) {
|
||||
return isEmpty(text) ? h('span', text) : h(Tag, { color }, () => text);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 文件下载
|
||||
*/
|
||||
function downloadFile(url) {
|
||||
if (!url) {
|
||||
createMessage.warning('未知的文件');
|
||||
return;
|
||||
}
|
||||
if (url.indexOf(',') > 0) {
|
||||
url = url.substring(0, url.indexOf(','));
|
||||
}
|
||||
url = getFileAccessHttpUrl(url.split(',')[0]);
|
||||
if (url) {
|
||||
window.open(url);
|
||||
}
|
||||
}
|
||||
|
||||
export { render, downloadFile };
|
||||
104
jeecgboot-vue3/src/utils/common/vxeUtils.ts
Normal file
104
jeecgboot-vue3/src/utils/common/vxeUtils.ts
Normal file
@ -0,0 +1,104 @@
|
||||
import { getValueType } from '/@/utils';
|
||||
|
||||
export const VALIDATE_FAILED = Symbol();
|
||||
/**
|
||||
* 一次性验证主表单和所有的次表单(新版本)
|
||||
* @param form 主表单 form 对象
|
||||
* @param cases 接收一个数组,每项都是一个JEditableTable实例
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
export async function validateFormModelAndTables(validate, formData, cases, props, autoJumpTab?) {
|
||||
if (!(validate && typeof validate === 'function')) {
|
||||
throw `validate 参数需要的是一个方法,而传入的却是${typeof validate}`;
|
||||
}
|
||||
let dataMap = {};
|
||||
let values = await new Promise((resolve, reject) => {
|
||||
// 验证主表表单
|
||||
validate()
|
||||
.then(() => {
|
||||
//update-begin---author:wangshuai ---date:20220507 for:[VUEN-912]一对多用户组件(所有风格,单表和树没问题)保存报错------------
|
||||
for (let data in formData) {
|
||||
//如果该数据是数组
|
||||
if (formData[data] instanceof Array) {
|
||||
let valueType = getValueType(props, data);
|
||||
//如果是字符串类型的需要变成以逗号分割的字符串
|
||||
if (valueType === 'string') {
|
||||
formData[data] = formData[data].join(',');
|
||||
}
|
||||
}
|
||||
}
|
||||
//update-end---author:wangshuai ---date:20220507 for:[VUEN-912]一对多用户组件(所有风格,单表和树没问题)保存报错--------------
|
||||
resolve(formData);
|
||||
})
|
||||
.catch(() => {
|
||||
reject({ error: VALIDATE_FAILED, index: 0 });
|
||||
});
|
||||
});
|
||||
Object.assign(dataMap, { formValue: values });
|
||||
// 验证所有子表的表单
|
||||
let subData = await validateTables(cases, autoJumpTab);
|
||||
// 合并最终数据
|
||||
dataMap = Object.assign(dataMap, { tablesValue: subData });
|
||||
return dataMap;
|
||||
}
|
||||
/**
|
||||
* 验证并获取一个或多个表格的所有值
|
||||
* @param cases 接收一个数组,每项都是一个JEditableTable实例
|
||||
* @param autoJumpTab 是否自动跳转到报错的tab
|
||||
*/
|
||||
export function validateTables(cases, autoJumpTab = true) {
|
||||
if (!(cases instanceof Array)) {
|
||||
throw `'validateTables'函数的'cases'参数需要的是一个数组,而传入的却是${typeof cases}`;
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
let tablesData: any = [];
|
||||
let index = 0;
|
||||
if (!cases || cases.length === 0) {
|
||||
resolve(tablesData);
|
||||
}
|
||||
(function next() {
|
||||
let vm = cases[index];
|
||||
vm.value.validateTable().then((errMap) => {
|
||||
// 校验通过
|
||||
if (!errMap) {
|
||||
tablesData[index] = { tableData: vm.value.getTableData() };
|
||||
// 判断校验是否全部完成,完成返回成功,否则继续进行下一步校验
|
||||
if (++index === cases.length) {
|
||||
resolve(tablesData);
|
||||
} else next();
|
||||
} else {
|
||||
// 尝试获取tabKey,如果在ATab组件内即可获取
|
||||
let paneKey;
|
||||
let tabPane = getVmParentByName(vm.value, 'ATabPane');
|
||||
if (tabPane) {
|
||||
paneKey = tabPane.$.vnode.key;
|
||||
// 自动跳转到该表格
|
||||
if (autoJumpTab) {
|
||||
let tabs = getVmParentByName(tabPane, 'Tabs');
|
||||
tabs && tabs.setActiveKey && tabs.setActiveKey(paneKey);
|
||||
}
|
||||
}
|
||||
// 出现未验证通过的表单,不再进行下一步校验,直接返回失败
|
||||
//update-begin-author:liusq date:2024-06-12 for: TV360X-478 一对多tab,校验未通过时,tab没有跳转
|
||||
reject({ error: VALIDATE_FAILED, index, paneKey, errMap, subIndex: index });
|
||||
//update-end-author:liusq date:2024-06-12 for: TV360X-478 一对多tab,校验未通过时,tab没有跳转
|
||||
}
|
||||
});
|
||||
})();
|
||||
});
|
||||
}
|
||||
|
||||
export function getVmParentByName(vm, name) {
|
||||
let parent = vm.$parent;
|
||||
if (parent && parent.$options) {
|
||||
if (parent.$options.name === name) {
|
||||
return parent;
|
||||
} else {
|
||||
let res = getVmParentByName(parent, name);
|
||||
if (res) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
17
jeecgboot-vue3/src/utils/dateUtil.ts
Normal file
17
jeecgboot-vue3/src/utils/dateUtil.ts
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Independent time operation tool to facilitate subsequent switch to dayjs
|
||||
*/
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';
|
||||
const DATE_FORMAT = 'YYYY-MM-DD';
|
||||
|
||||
export function formatToDateTime(date: dayjs.Dayjs | undefined = undefined, format = DATE_TIME_FORMAT): string {
|
||||
return dayjs(date).format(format);
|
||||
}
|
||||
|
||||
export function formatToDate(date: dayjs.Dayjs | undefined = undefined, format = DATE_FORMAT): string {
|
||||
return dayjs(date).format(format);
|
||||
}
|
||||
|
||||
export const dateUtil = dayjs;
|
||||
30
jeecgboot-vue3/src/utils/desform/customExpression.ts
Normal file
30
jeecgboot-vue3/src/utils/desform/customExpression.ts
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
*
|
||||
* 这里填写用户自定义的表达式
|
||||
* 可用在Online表单的默认值表达式中使用
|
||||
* 需要外部使用的变量或方法一定要 export,否则无法识别
|
||||
* 示例:
|
||||
* export const name = '张三'; // const 是常量
|
||||
* export let age = 17; // 看情况 export const 还是 let ,两者都可正常使用
|
||||
* export function content(arg) { // export 方法,可传参数,使用时要加括号,值一定要return回去,可以返回Promise
|
||||
* return 'content' + arg;
|
||||
* }
|
||||
* export const address = (arg) => content(arg) + ' | 北京市'; // export 箭头函数也可以
|
||||
*
|
||||
*/
|
||||
|
||||
/** 字段默认值官方示例:获取地址 */
|
||||
export function demoFieldDefVal_getAddress(arg) {
|
||||
if (!arg) {
|
||||
arg = '朝阳区';
|
||||
}
|
||||
return `北京市 ${arg}`;
|
||||
}
|
||||
|
||||
/** 自定义JS函数示例 */
|
||||
export function sayHi(name) {
|
||||
if (!name) {
|
||||
name = '张三';
|
||||
}
|
||||
return `您好,我叫: ${name}`;
|
||||
}
|
||||
65
jeecgboot-vue3/src/utils/dict/DictColors.js
Normal file
65
jeecgboot-vue3/src/utils/dict/DictColors.js
Normal file
@ -0,0 +1,65 @@
|
||||
const whiteColor = '#ffffff'
|
||||
const blackColor = '#666666'
|
||||
|
||||
export const Colors = [
|
||||
// 背景颜色,文字颜色
|
||||
['#2196F3', whiteColor],
|
||||
['#08C9C9', whiteColor],
|
||||
['#00C345', whiteColor],
|
||||
['#FAD714', whiteColor],
|
||||
['#FF9300', whiteColor],
|
||||
['#F52222', whiteColor],
|
||||
['#EB2F96', whiteColor],
|
||||
['#7500EA', whiteColor],
|
||||
['#2D46C4', whiteColor],
|
||||
['#484848', whiteColor],
|
||||
// --------------------
|
||||
['#C9E6FC', blackColor],
|
||||
['#C3F2F2', blackColor],
|
||||
['#C2F1D2', blackColor],
|
||||
['#FEF6C6', blackColor],
|
||||
['#FFE5C2', blackColor],
|
||||
['#FDCACA', blackColor],
|
||||
['#FACDE6', blackColor],
|
||||
['#DEC2FA', blackColor],
|
||||
['#CCD2F1', blackColor],
|
||||
['#D3D3D3', blackColor],
|
||||
]
|
||||
|
||||
export const NONE_COLOR = ['#e9e9e9', blackColor]
|
||||
|
||||
/**
|
||||
* 返回一个颜色迭代器,每次调用返回一个颜色,当颜色用完后,再从头开始
|
||||
* @param {number} initIndex 初始颜色索引
|
||||
* @returns {{getIndex: function, next: function}}
|
||||
*/
|
||||
export function getColorIterator(initIndex = 0) {
|
||||
let index = initIndex;
|
||||
if (index < 0 || index >= Colors.length) {
|
||||
index = 0;
|
||||
}
|
||||
return {
|
||||
getIndex: () => index,
|
||||
next() {
|
||||
const color = Colors[index];
|
||||
index = (index + 1) % Colors.length;
|
||||
return color;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据颜色获取当前坐标和颜色
|
||||
*/
|
||||
export function getItemColor(color) {
|
||||
if(!color){
|
||||
return NONE_COLOR[1];
|
||||
}
|
||||
let colorIndex = Colors.findIndex((value)=>{
|
||||
return value[0] === color;
|
||||
})
|
||||
if(colorIndex === -1){
|
||||
return NONE_COLOR[1];
|
||||
}
|
||||
return Colors[colorIndex][1];
|
||||
}
|
||||
161
jeecgboot-vue3/src/utils/dict/JDictSelectUtil.js
Normal file
161
jeecgboot-vue3/src/utils/dict/JDictSelectUtil.js
Normal file
@ -0,0 +1,161 @@
|
||||
/**
|
||||
* 字典 util
|
||||
* author: scott
|
||||
* date: 20190109
|
||||
*/
|
||||
|
||||
import { ajaxGetDictItems, getDictItemsByCode } from './index';
|
||||
|
||||
/**
|
||||
* 获取字典数组
|
||||
* 【目前仅表单设计器页面使用该方法】
|
||||
* @param dictCode 字典Code
|
||||
* @param isTransformResponse 是否转换返回结果
|
||||
* @return List<Map>
|
||||
*/
|
||||
export async function initDictOptions(dictCode, isTransformResponse = true) {
|
||||
if (!dictCode) {
|
||||
return '字典Code不能为空!';
|
||||
}
|
||||
//优先从缓存中读取字典配置
|
||||
if (getDictItemsByCode(dictCode)) {
|
||||
let res = {};
|
||||
res.result = getDictItemsByCode(dictCode);
|
||||
res.success = true;
|
||||
if (isTransformResponse) {
|
||||
return res.result;
|
||||
} else {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
//获取字典数组
|
||||
return await ajaxGetDictItems(dictCode, {}, { isTransformResponse });
|
||||
}
|
||||
|
||||
/**
|
||||
* 字典值替换文本通用方法
|
||||
* @param dictOptions 字典数组
|
||||
* @param text 字典值
|
||||
* @return String
|
||||
*/
|
||||
export function filterDictText(dictOptions, text) {
|
||||
// --update-begin----author:sunjianlei---date:20200323------for: 字典翻译 text 允许逗号分隔 ---
|
||||
if (text != null && Array.isArray(dictOptions)) {
|
||||
let result = [];
|
||||
// 允许多个逗号分隔,允许传数组对象
|
||||
let splitText;
|
||||
if (Array.isArray(text)) {
|
||||
splitText = text;
|
||||
} else {
|
||||
splitText = text.toString().trim().split(',');
|
||||
}
|
||||
for (let txt of splitText) {
|
||||
let dictText = txt;
|
||||
for (let dictItem of dictOptions) {
|
||||
// update-begin--author:liaozhiyang---date:20240524---for:【TV360X-469】兼容数据null值防止报错
|
||||
if (dictItem == null) break;
|
||||
// update-end--author:liaozhiyang---date:20240524---for:【TV360X-469】兼容数据null值防止报错
|
||||
if (txt.toString() === dictItem.value.toString()) {
|
||||
dictText = dictItem.text || dictItem.title || dictItem.label;
|
||||
break;
|
||||
}
|
||||
}
|
||||
result.push(dictText);
|
||||
}
|
||||
return result.join(',');
|
||||
}
|
||||
return text;
|
||||
// --update-end----author:sunjianlei---date:20200323------for: 字典翻译 text 允许逗号分隔 ---
|
||||
}
|
||||
|
||||
/**
|
||||
* 字典值替换文本通用方法(多选)
|
||||
* @param dictOptions 字典数组
|
||||
* @param text 字典值
|
||||
* @return String
|
||||
*/
|
||||
export function filterMultiDictText(dictOptions, text) {
|
||||
//js “!text” 认为0为空,所以做提前处理
|
||||
if (text === 0 || text === '0') {
|
||||
if (dictOptions) {
|
||||
for (let dictItem of dictOptions) {
|
||||
if (text == dictItem.value) {
|
||||
return dictItem.text;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!text || text == 'undefined' || text == 'null' || !dictOptions || dictOptions.length == 0) {
|
||||
return '';
|
||||
}
|
||||
let re = '';
|
||||
text = text.toString();
|
||||
let arr = text.split(',');
|
||||
dictOptions.forEach(function (option) {
|
||||
if (option) {
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (arr[i] === option.value) {
|
||||
re += option.text + ',';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (re == '') {
|
||||
return text;
|
||||
}
|
||||
return re.substring(0, re.length - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 翻译字段值对应的文本
|
||||
* @param children
|
||||
* @returns string
|
||||
*/
|
||||
export function filterDictTextByCache(dictCode, key) {
|
||||
if (key == null || key.length == 0) {
|
||||
return;
|
||||
}
|
||||
if (!dictCode) {
|
||||
return '字典Code不能为空!';
|
||||
}
|
||||
//优先从缓存中读取字典配置
|
||||
if (getDictItemsByCode(dictCode)) {
|
||||
let item = getDictItemsByCode(dictCode).filter((t) => t['value'] == key);
|
||||
if (item && item.length > 0) {
|
||||
return item[0]['text'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 通过code获取字典数组 */
|
||||
export async function getDictItems(dictCode, params) {
|
||||
// update-begin--author:liaozhiyang---date:20230809---for:【issues/668】JDictSelectUtil数据字典工具类中的getDictItems方法出错
|
||||
//优先从缓存中读取字典配置
|
||||
if (getDictItemsByCode(dictCode)) {
|
||||
let desformDictItems = getDictItemsByCode(dictCode).map((item) => ({
|
||||
...item,
|
||||
label: item.text,
|
||||
}));
|
||||
return Promise.resolve(desformDictItems);
|
||||
}
|
||||
|
||||
//缓存中没有,就请求后台
|
||||
return await ajaxGetDictItems(dictCode, params)
|
||||
.then((result) => {
|
||||
if (result.length) {
|
||||
let res = result.map((item) => ({ ...item, label: item.text }));
|
||||
console.log('------- 从DB中获取到了字典-------dictCode : ', dictCode, res);
|
||||
return Promise.resolve(res);
|
||||
} else {
|
||||
console.error('getDictItems error: : ', res);
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
})
|
||||
.catch((res) => {
|
||||
console.error('getDictItems error: ', res);
|
||||
return Promise.resolve([]);
|
||||
});
|
||||
// update-end--author:liaozhiyang---date:20230809---for:【issues/668】JDictSelectUtil数据字典工具类中的getDictItems方法出错
|
||||
}
|
||||
55
jeecgboot-vue3/src/utils/dict/index.ts
Normal file
55
jeecgboot-vue3/src/utils/dict/index.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { useUserStore } from '/@/store/modules/user';
|
||||
import { getAuthCache } from '/@/utils/auth';
|
||||
import { DB_DICT_DATA_KEY } from '/@/enums/cacheEnum';
|
||||
|
||||
/**
|
||||
* 从缓存中获取字典配置
|
||||
* @param code
|
||||
*/
|
||||
export const getDictItemsByCode = (code) => {
|
||||
// update-begin--author:liaozhiyang---date:20230908---for:【QQYUN-6417】生产环境字典慢的问题
|
||||
const userStore = useUserStore();
|
||||
const dictItems = userStore.getAllDictItems;
|
||||
if (null != dictItems && typeof dictItems === 'object' && dictItems[code]) {
|
||||
return dictItems[code];
|
||||
}
|
||||
//update-begin-author:liusq---date:2023-10-13--for: 【issues/777】列表 分类字典不显示
|
||||
//兼容以前的旧写法
|
||||
if (getAuthCache(DB_DICT_DATA_KEY) && getAuthCache(DB_DICT_DATA_KEY)[code]) {
|
||||
return getAuthCache(DB_DICT_DATA_KEY)[code];
|
||||
}
|
||||
//update-end-author:liusq---date:2023-10-13--for:【issues/777】列表 分类字典不显示
|
||||
|
||||
// update-end--author:liaozhiyang---date:20230908---for:【QQYUN-6417】生产环境字典慢的问题
|
||||
|
||||
};
|
||||
/**
|
||||
* 获取字典数组
|
||||
* @param dictCode 字典Code
|
||||
* @return List<Map>
|
||||
*/
|
||||
export const initDictOptions = (code) => {
|
||||
//1.优先从缓存中读取字典配置
|
||||
if (getDictItemsByCode(code)) {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve(getDictItemsByCode(code));
|
||||
});
|
||||
}
|
||||
//2.获取字典数组
|
||||
//update-begin-author:taoyan date:2022-6-21 for: 字典数据请求前将参数编码处理,但是不能直接编码,因为可能之前已经编码过了
|
||||
if (code.indexOf(',') > 0 && code.indexOf(' ') > 0) {
|
||||
// 编码后类似sys_user%20where%20username%20like%20xxx' 是不包含空格的,这里判断如果有空格和逗号说明需要编码处理
|
||||
code = encodeURI(code);
|
||||
}
|
||||
//update-end-author:taoyan date:2022-6-21 for: 字典数据请求前将参数编码处理,但是不能直接编码,因为可能之前已经编码过了
|
||||
return defHttp.get({ url: `/sys/dict/getDictItems/${code}` });
|
||||
};
|
||||
/**
|
||||
* 获取字典数组
|
||||
* @param code 字典Code
|
||||
* @param params 查询参数
|
||||
* @param options 查询配置
|
||||
* @return List<Map>
|
||||
*/
|
||||
export const ajaxGetDictItems = (code, params, options?) => defHttp.get({ url: `/sys/dict/getDictItems/${code}`, params }, options);
|
||||
192
jeecgboot-vue3/src/utils/domUtils.ts
Normal file
192
jeecgboot-vue3/src/utils/domUtils.ts
Normal file
@ -0,0 +1,192 @@
|
||||
import type { FunctionArgs } from '@vueuse/core';
|
||||
import { upperFirst } from 'lodash-es';
|
||||
|
||||
export interface ViewportOffsetResult {
|
||||
left: number;
|
||||
top: number;
|
||||
right: number;
|
||||
bottom: number;
|
||||
rightIncludeBody: number;
|
||||
bottomIncludeBody: number;
|
||||
}
|
||||
|
||||
export function getBoundingClientRect(element: Element): DOMRect | number {
|
||||
if (!element || !element.getBoundingClientRect) {
|
||||
return 0;
|
||||
}
|
||||
return element.getBoundingClientRect();
|
||||
}
|
||||
|
||||
function trim(string: string) {
|
||||
return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, '');
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function hasClass(el: Element, cls: string) {
|
||||
if (!el || !cls) return false;
|
||||
if (cls.indexOf(' ') !== -1) throw new Error('className should not contain space.');
|
||||
if (el.classList) {
|
||||
return el.classList.contains(cls);
|
||||
} else {
|
||||
return (' ' + el.className + ' ').indexOf(' ' + cls + ' ') > -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function addClass(el: Element, cls: string) {
|
||||
if (!el) return;
|
||||
let curClass = el.className;
|
||||
const classes = (cls || '').split(' ');
|
||||
|
||||
for (let i = 0, j = classes.length; i < j; i++) {
|
||||
const clsName = classes[i];
|
||||
if (!clsName) continue;
|
||||
|
||||
if (el.classList) {
|
||||
el.classList.add(clsName);
|
||||
} else if (!hasClass(el, clsName)) {
|
||||
curClass += ' ' + clsName;
|
||||
}
|
||||
}
|
||||
if (!el.classList) {
|
||||
el.className = curClass;
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function removeClass(el: Element, cls: string) {
|
||||
if (!el || !cls) return;
|
||||
const classes = cls.split(' ');
|
||||
let curClass = ' ' + el.className + ' ';
|
||||
|
||||
for (let i = 0, j = classes.length; i < j; i++) {
|
||||
const clsName = classes[i];
|
||||
if (!clsName) continue;
|
||||
|
||||
if (el.classList) {
|
||||
el.classList.remove(clsName);
|
||||
} else if (hasClass(el, clsName)) {
|
||||
curClass = curClass.replace(' ' + clsName + ' ', ' ');
|
||||
}
|
||||
}
|
||||
if (!el.classList) {
|
||||
el.className = trim(curClass);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get the left and top offset of the current element
|
||||
* left: the distance between the leftmost element and the left side of the document
|
||||
* top: the distance from the top of the element to the top of the document
|
||||
* right: the distance from the far right of the element to the right of the document
|
||||
* bottom: the distance from the bottom of the element to the bottom of the document
|
||||
* rightIncludeBody: the distance between the leftmost element and the right side of the document
|
||||
* bottomIncludeBody: the distance from the bottom of the element to the bottom of the document
|
||||
*
|
||||
* @description:
|
||||
*/
|
||||
export function getViewportOffset(element: Element): ViewportOffsetResult {
|
||||
const doc = document.documentElement;
|
||||
|
||||
const docScrollLeft = doc.scrollLeft;
|
||||
const docScrollTop = doc.scrollTop;
|
||||
const docClientLeft = doc.clientLeft;
|
||||
const docClientTop = doc.clientTop;
|
||||
|
||||
const pageXOffset = window.pageXOffset;
|
||||
const pageYOffset = window.pageYOffset;
|
||||
|
||||
const box = getBoundingClientRect(element);
|
||||
|
||||
const { left: retLeft, top: rectTop, width: rectWidth, height: rectHeight } = box as DOMRect;
|
||||
|
||||
const scrollLeft = (pageXOffset || docScrollLeft) - (docClientLeft || 0);
|
||||
const scrollTop = (pageYOffset || docScrollTop) - (docClientTop || 0);
|
||||
const offsetLeft = retLeft + pageXOffset;
|
||||
const offsetTop = rectTop + pageYOffset;
|
||||
|
||||
const left = offsetLeft - scrollLeft;
|
||||
const top = offsetTop - scrollTop;
|
||||
|
||||
const clientWidth = window.document.documentElement.clientWidth;
|
||||
const clientHeight = window.document.documentElement.clientHeight;
|
||||
return {
|
||||
left: left,
|
||||
top: top,
|
||||
right: clientWidth - rectWidth - left,
|
||||
bottom: clientHeight - rectHeight - top,
|
||||
rightIncludeBody: clientWidth - left,
|
||||
bottomIncludeBody: clientHeight - top,
|
||||
};
|
||||
}
|
||||
|
||||
export function hackCss(attr: string, value: string) {
|
||||
const prefix: string[] = ['webkit', 'Moz', 'ms', 'OT'];
|
||||
|
||||
const styleObj: any = {};
|
||||
prefix.forEach((item) => {
|
||||
styleObj[`${item}${upperFirst(attr)}`] = value;
|
||||
});
|
||||
return {
|
||||
...styleObj,
|
||||
[attr]: value,
|
||||
};
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function on(element: Element | HTMLElement | Document | Window, event: string, handler: EventListenerOrEventListenerObject): void {
|
||||
if (element && event && handler) {
|
||||
element.addEventListener(event, handler, false);
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function off(element: Element | HTMLElement | Document | Window, event: string, handler: Fn): void {
|
||||
if (element && event && handler) {
|
||||
element.removeEventListener(event, handler, false);
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function once(el: HTMLElement, event: string, fn: EventListener): void {
|
||||
const listener = function (this: any, ...args: unknown[]) {
|
||||
if (fn) {
|
||||
fn.apply(this, args);
|
||||
}
|
||||
off(el, event, listener);
|
||||
};
|
||||
on(el, event, listener);
|
||||
}
|
||||
|
||||
export function useRafThrottle<T extends FunctionArgs>(fn: T): T {
|
||||
let locked = false;
|
||||
// @ts-ignore
|
||||
return function (...args: any[]) {
|
||||
if (locked) return;
|
||||
locked = true;
|
||||
window.requestAnimationFrame(() => {
|
||||
// @ts-ignore
|
||||
fn.apply(this, args);
|
||||
locked = false;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找父级元素,直到找到符合条件的元素
|
||||
* @param element 当前元素
|
||||
* @param checkFn 判断条件
|
||||
*/
|
||||
export function queryParentElement(element: HTMLElement, checkFn: (node: HTMLElement) => boolean): HTMLElement | null {
|
||||
let ele: HTMLElement | null = element;
|
||||
while (ele) {
|
||||
try {
|
||||
if (checkFn(ele)) {
|
||||
return ele;
|
||||
}
|
||||
ele = ele.parentElement;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
149
jeecgboot-vue3/src/utils/encryption/signMd5Utils.js
Normal file
149
jeecgboot-vue3/src/utils/encryption/signMd5Utils.js
Normal file
@ -0,0 +1,149 @@
|
||||
import md5 from 'md5';
|
||||
//签名密钥串(前后端要一致,正式发布请自行修改)
|
||||
const signatureSecret = 'dd05f1c54d63749eda95f9fa6d49v442a';
|
||||
|
||||
export default class signMd5Utils {
|
||||
/**
|
||||
* json参数升序
|
||||
* @param jsonObj 发送参数
|
||||
*/
|
||||
|
||||
static sortAsc(jsonObj) {
|
||||
let arr = new Array();
|
||||
let num = 0;
|
||||
for (let i in jsonObj) {
|
||||
arr[num] = i;
|
||||
num++;
|
||||
}
|
||||
let sortArr = arr.sort();
|
||||
let sortObj = {};
|
||||
for (let i in sortArr) {
|
||||
sortObj[sortArr[i]] = jsonObj[sortArr[i]];
|
||||
}
|
||||
return sortObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param url 请求的url,应该包含请求参数(url的?后面的参数)
|
||||
* @param requestParams 请求参数(@RequestParam(get)的JSON参数)
|
||||
* @param requestBodyParams 请求参数(@RequestBody(post)参数)
|
||||
* @returns {string} 获取签名
|
||||
*/
|
||||
static getSign(url, requestParams, requestBodyParams) {
|
||||
let urlParams = this.parseQueryString(url);
|
||||
let jsonObj = this.mergeObject(urlParams, requestParams);
|
||||
//update-begin---author:wangshuai---date:2024-04-16---for:【QQYUN-9005】发送短信加签---
|
||||
if(requestBodyParams){
|
||||
jsonObj = this.mergeObject(jsonObj, requestBodyParams)
|
||||
}
|
||||
//update-end---author:wangshuai---date:2024-04-16---for:【QQYUN-9005】发送短信加签---
|
||||
let requestBody = this.sortAsc(jsonObj);
|
||||
delete requestBody._t;
|
||||
console.log('sign requestBody:', requestBody);
|
||||
return md5(JSON.stringify(requestBody) + signatureSecret).toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param url 请求的url
|
||||
* @returns {{}} 将url中请求参数组装成json对象(url的?后面的参数)
|
||||
*/
|
||||
static parseQueryString(url) {
|
||||
let urlReg = /^[^\?]+\?([\w\W]+)$/,
|
||||
paramReg = /([^&=]+)=([\w\W]*?)(&|$|#)/g,
|
||||
urlArray = urlReg.exec(url),
|
||||
result = {};
|
||||
|
||||
// 获取URL上最后带逗号的参数变量 sys/dict/getDictItems/sys_user,realname,username
|
||||
//【这边条件没有encode】带条件参数例子:/sys/dict/getDictItems/sys_user,realname,id,username!='admin'%20order%20by%20create_time
|
||||
let lastpathVariable = url.substring(url.lastIndexOf('/') + 1);
|
||||
if (lastpathVariable.includes(',')) {
|
||||
if (lastpathVariable.includes('?')) {
|
||||
lastpathVariable = lastpathVariable.substring(0, lastpathVariable.indexOf('?'));
|
||||
}
|
||||
//update-begin---author:wangshuai ---date:20221103 for:[issues/183]下拉搜索,使用动态字典,在线页面不报错,生成的代码报错 ------------
|
||||
//解决Sign 签名校验失败 #2728
|
||||
//decodeURI对特殊字符没有没有编码和解码的能力,需要使用decodeURIComponent
|
||||
result['x-path-variable'] = decodeURIComponent(lastpathVariable);
|
||||
//update-end---author:wangshuai ---date:20221103 for:[issues/183]下拉搜索,使用动态字典,在线页面不报错,生成的代码报错 ------------
|
||||
}
|
||||
if (urlArray && urlArray[1]) {
|
||||
let paramString = urlArray[1],
|
||||
paramResult;
|
||||
while ((paramResult = paramReg.exec(paramString)) != null) {
|
||||
//数字值转为string类型,前后端加密规则保持一致
|
||||
if (this.myIsNaN(paramResult[2])) {
|
||||
paramResult[2] = paramResult[2].toString();
|
||||
}
|
||||
result[paramResult[1]] = paramResult[2];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {*} 将两个对象合并成一个
|
||||
*/
|
||||
static mergeObject(objectOne, objectTwo) {
|
||||
if (objectTwo && Object.keys(objectTwo).length > 0) {
|
||||
for (let key in objectTwo) {
|
||||
if (objectTwo.hasOwnProperty(key) === true) {
|
||||
//数字值转为string类型,前后端加密规则保持一致
|
||||
if (this.myIsNaN(objectTwo[key])) {
|
||||
objectTwo[key] = objectTwo[key].toString();
|
||||
}
|
||||
//布尔类型转成string类型,前后端加密规则保持一致
|
||||
if (typeof objectTwo[key] === 'boolean') {
|
||||
objectTwo[key] = objectTwo[key].toString();
|
||||
}
|
||||
objectOne[key] = objectTwo[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
return objectOne;
|
||||
}
|
||||
|
||||
static urlEncode(param, key, encode) {
|
||||
if (param == null) return '';
|
||||
let paramStr = '';
|
||||
let t = typeof param;
|
||||
if (t == 'string' || t == 'number' || t == 'boolean') {
|
||||
paramStr += '&' + key + '=' + (encode == null || encode ? encodeURIComponent(param) : param);
|
||||
} else {
|
||||
for (let i in param) {
|
||||
let k = key == null ? i : key + (param instanceof Array ? '[' + i + ']' : '.' + i);
|
||||
paramStr += this.urlEncode(param[i], k, encode);
|
||||
}
|
||||
}
|
||||
return paramStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 接口签名用 生成header中的时间戳
|
||||
* @returns {number}
|
||||
*/
|
||||
static getTimestamp() {
|
||||
return new Date().getTime();
|
||||
}
|
||||
|
||||
// static getDateTimeToString() {
|
||||
// const date_ = new Date()
|
||||
// const year = date_.getFullYear()
|
||||
// let month = date_.getMonth() + 1
|
||||
// let day = date_.getDate()
|
||||
// if (month < 10) month = '0' + month
|
||||
// if (day < 10) day = '0' + day
|
||||
// let hours = date_.getHours()
|
||||
// let mins = date_.getMinutes()
|
||||
// let secs = date_.getSeconds()
|
||||
// const msecs = date_.getMilliseconds()
|
||||
// if (hours < 10) hours = '0' + hours
|
||||
// if (mins < 10) mins = '0' + mins
|
||||
// if (secs < 10) secs = '0' + secs
|
||||
// if (msecs < 10) secs = '0' + msecs
|
||||
// return year + '' + month + '' + day + '' + hours + '' + mins + '' + secs
|
||||
// }
|
||||
// true:数值型的,false:非数值型
|
||||
static myIsNaN(value) {
|
||||
return typeof value === 'number' && !isNaN(value);
|
||||
}
|
||||
}
|
||||
93
jeecgboot-vue3/src/utils/env.ts
Normal file
93
jeecgboot-vue3/src/utils/env.ts
Normal file
@ -0,0 +1,93 @@
|
||||
import type { GlobEnvConfig } from '/#/config';
|
||||
|
||||
import { warn } from '/@/utils/log';
|
||||
import pkg from '../../package.json';
|
||||
import { getConfigFileName } from '../../build/getConfigFileName';
|
||||
|
||||
export function getCommonStoragePrefix() {
|
||||
const { VITE_GLOB_APP_SHORT_NAME } = getAppEnvConfig();
|
||||
return `${VITE_GLOB_APP_SHORT_NAME}__${getEnv()}`.toUpperCase();
|
||||
}
|
||||
|
||||
// Generate cache key according to version
|
||||
export function getStorageShortName() {
|
||||
return `${getCommonStoragePrefix()}${`__${pkg.version}`}__`.toUpperCase();
|
||||
}
|
||||
|
||||
export function getAppEnvConfig() {
|
||||
const ENV_NAME = getConfigFileName(import.meta.env);
|
||||
|
||||
const ENV = (import.meta.env.DEV
|
||||
? // Get the global configuration (the configuration will be extracted independently when packaging)
|
||||
(import.meta.env as unknown as GlobEnvConfig)
|
||||
: window[ENV_NAME as any]) as unknown as GlobEnvConfig;
|
||||
|
||||
const {
|
||||
VITE_GLOB_APP_TITLE,
|
||||
VITE_GLOB_API_URL,
|
||||
VITE_USE_MOCK,
|
||||
VITE_GLOB_APP_SHORT_NAME,
|
||||
VITE_GLOB_API_URL_PREFIX,
|
||||
VITE_GLOB_APP_OPEN_SSO,
|
||||
VITE_GLOB_APP_OPEN_QIANKUN,
|
||||
VITE_GLOB_APP_CAS_BASE_URL,
|
||||
VITE_GLOB_DOMAIN_URL,
|
||||
VITE_GLOB_ONLINE_VIEW_URL,
|
||||
} = ENV;
|
||||
|
||||
// if (!/^[a-zA-Z\_]*$/.test(VITE_GLOB_APP_SHORT_NAME)) {
|
||||
// warn(
|
||||
// `VITE_GLOB_APP_SHORT_NAME 变量只能是字符/下划线,请在环境变量中修改并重新运行.`
|
||||
// );
|
||||
// }
|
||||
|
||||
return {
|
||||
VITE_GLOB_APP_TITLE,
|
||||
VITE_GLOB_API_URL,
|
||||
VITE_USE_MOCK,
|
||||
VITE_GLOB_APP_SHORT_NAME,
|
||||
VITE_GLOB_API_URL_PREFIX,
|
||||
VITE_GLOB_APP_OPEN_SSO,
|
||||
VITE_GLOB_APP_OPEN_QIANKUN,
|
||||
VITE_GLOB_APP_CAS_BASE_URL,
|
||||
VITE_GLOB_DOMAIN_URL,
|
||||
VITE_GLOB_ONLINE_VIEW_URL,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Development mode
|
||||
*/
|
||||
export const devMode = 'development';
|
||||
|
||||
/**
|
||||
* @description: Production mode
|
||||
*/
|
||||
export const prodMode = 'production';
|
||||
|
||||
/**
|
||||
* @description: Get environment variables
|
||||
* @returns:
|
||||
* @example:
|
||||
*/
|
||||
export function getEnv(): string {
|
||||
return import.meta.env.MODE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Is it a development mode
|
||||
* @returns:
|
||||
* @example:
|
||||
*/
|
||||
export function isDevMode(): boolean {
|
||||
return import.meta.env.DEV;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Is it a production mode
|
||||
* @returns:
|
||||
* @example:
|
||||
*/
|
||||
export function isProdMode(): boolean {
|
||||
return import.meta.env.PROD;
|
||||
}
|
||||
42
jeecgboot-vue3/src/utils/event/index.ts
Normal file
42
jeecgboot-vue3/src/utils/event/index.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
|
||||
const isServer = typeof window === 'undefined';
|
||||
|
||||
/* istanbul ignore next */
|
||||
function resizeHandler(entries: any[]) {
|
||||
for (const entry of entries) {
|
||||
const listeners = entry.target.__resizeListeners__ || [];
|
||||
if (listeners.length) {
|
||||
listeners.forEach((fn: () => any) => {
|
||||
fn();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function addResizeListener(element: any, fn: () => any) {
|
||||
if (isServer) return;
|
||||
if (!element.__resizeListeners__) {
|
||||
element.__resizeListeners__ = [];
|
||||
element.__ro__ = new ResizeObserver(resizeHandler);
|
||||
element.__ro__.observe(element);
|
||||
}
|
||||
element.__resizeListeners__.push(fn);
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function removeResizeListener(element: any, fn: () => any) {
|
||||
if (!element || !element.__resizeListeners__) return;
|
||||
element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1);
|
||||
if (!element.__resizeListeners__.length) {
|
||||
element.__ro__.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
export function triggerWindowResize() {
|
||||
const event = document.createEvent('HTMLEvents');
|
||||
event.initEvent('resize', true, true);
|
||||
(event as any).eventType = 'message';
|
||||
window.dispatchEvent(event);
|
||||
}
|
||||
63
jeecgboot-vue3/src/utils/factory/createAsyncComponent.tsx
Normal file
63
jeecgboot-vue3/src/utils/factory/createAsyncComponent.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
import {
|
||||
defineAsyncComponent,
|
||||
// FunctionalComponent, CSSProperties
|
||||
} from 'vue';
|
||||
import { Spin } from 'ant-design-vue';
|
||||
import { noop } from '/@/utils/index';
|
||||
|
||||
// const Loading: FunctionalComponent<{ size: 'small' | 'default' | 'large' }> = (props) => {
|
||||
// const style: CSSProperties = {
|
||||
// position: 'absolute',
|
||||
// display: 'flex',
|
||||
// justifyContent: 'center',
|
||||
// alignItems: 'center',
|
||||
// };
|
||||
// return (
|
||||
// <div style={style}>
|
||||
// <Spin spinning={true} size={props.size} />
|
||||
// </div>
|
||||
// );
|
||||
// };
|
||||
|
||||
interface Options {
|
||||
size?: 'default' | 'small' | 'large';
|
||||
delay?: number;
|
||||
timeout?: number;
|
||||
loading?: boolean;
|
||||
retry?: boolean;
|
||||
}
|
||||
|
||||
export function createAsyncComponent(loader: Fn, options: Options = {}) {
|
||||
const { size = 'small', delay = 100, timeout = 30000, loading = false, retry = true } = options;
|
||||
return defineAsyncComponent({
|
||||
loader,
|
||||
loadingComponent: loading ? <Spin spinning={true} size={size} /> : undefined,
|
||||
// The error component will be displayed if a timeout is
|
||||
// provided and exceeded. Default: Infinity.
|
||||
// TODO
|
||||
timeout,
|
||||
// errorComponent
|
||||
// Defining if component is suspensible. Default: true.
|
||||
// suspensible: false,
|
||||
delay,
|
||||
/**
|
||||
*
|
||||
* @param {*} error Error message object
|
||||
* @param {*} retry A function that indicating whether the async component should retry when the loader promise rejects
|
||||
* @param {*} fail End of failure
|
||||
* @param {*} attempts Maximum allowed retries number
|
||||
*/
|
||||
onError: !retry
|
||||
? noop
|
||||
: (error, retry, fail, attempts) => {
|
||||
if (error.message.match(/fetch/) && attempts <= 3) {
|
||||
// retry on fetch errors, 3 max attempts
|
||||
retry();
|
||||
} else {
|
||||
// Note that retry/fail are like resolve/reject of a promise:
|
||||
// one of them must be called for the error handling to continue.
|
||||
fail();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
41
jeecgboot-vue3/src/utils/file/base64Conver.ts
Normal file
41
jeecgboot-vue3/src/utils/file/base64Conver.ts
Normal file
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @description: base64 to blob
|
||||
*/
|
||||
export function dataURLtoBlob(base64Buf: string): Blob {
|
||||
const arr = base64Buf.split(',');
|
||||
const typeItem = arr[0];
|
||||
const mime = typeItem.match(/:(.*?);/)![1];
|
||||
const bstr = atob(arr[1]);
|
||||
let n = bstr.length;
|
||||
const u8arr = new Uint8Array(n);
|
||||
while (n--) {
|
||||
u8arr[n] = bstr.charCodeAt(n);
|
||||
}
|
||||
return new Blob([u8arr], { type: mime });
|
||||
}
|
||||
|
||||
/**
|
||||
* img url to base64
|
||||
* @param url
|
||||
*/
|
||||
export function urlToBase64(url: string, mineType?: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let canvas = document.createElement('CANVAS') as Nullable<HTMLCanvasElement>;
|
||||
const ctx = canvas!.getContext('2d');
|
||||
|
||||
const img = new Image();
|
||||
img.crossOrigin = '';
|
||||
img.onload = function () {
|
||||
if (!canvas || !ctx) {
|
||||
return reject();
|
||||
}
|
||||
canvas.height = img.height;
|
||||
canvas.width = img.width;
|
||||
ctx.drawImage(img, 0, 0);
|
||||
const dataURL = canvas.toDataURL(mineType || 'image/png');
|
||||
canvas = null;
|
||||
resolve(dataURL);
|
||||
};
|
||||
img.src = url;
|
||||
});
|
||||
}
|
||||
91
jeecgboot-vue3/src/utils/file/download.ts
Normal file
91
jeecgboot-vue3/src/utils/file/download.ts
Normal file
@ -0,0 +1,91 @@
|
||||
import { openWindow } from '..';
|
||||
import { dataURLtoBlob, urlToBase64 } from './base64Conver';
|
||||
|
||||
/**
|
||||
* Download online pictures
|
||||
* @param url
|
||||
* @param filename
|
||||
* @param mime
|
||||
* @param bom
|
||||
*/
|
||||
export function downloadByOnlineUrl(url: string, filename: string, mime?: string, bom?: BlobPart) {
|
||||
urlToBase64(url).then((base64) => {
|
||||
downloadByBase64(base64, filename, mime, bom);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Download pictures based on base64
|
||||
* @param buf
|
||||
* @param filename
|
||||
* @param mime
|
||||
* @param bom
|
||||
*/
|
||||
export function downloadByBase64(buf: string, filename: string, mime?: string, bom?: BlobPart) {
|
||||
const base64Buf = dataURLtoBlob(buf);
|
||||
downloadByData(base64Buf, filename, mime, bom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Download according to the background interface file stream
|
||||
* @param {*} data
|
||||
* @param {*} filename
|
||||
* @param {*} mime
|
||||
* @param {*} bom
|
||||
*/
|
||||
export function downloadByData(data: BlobPart, filename: string, mime?: string, bom?: BlobPart) {
|
||||
const blobData = typeof bom !== 'undefined' ? [bom, data] : [data];
|
||||
const blob = new Blob(blobData, { type: mime || 'application/octet-stream' });
|
||||
if (typeof window.navigator.msSaveBlob !== 'undefined') {
|
||||
window.navigator.msSaveBlob(blob, filename);
|
||||
} else {
|
||||
const blobURL = window.URL.createObjectURL(blob);
|
||||
const tempLink = document.createElement('a');
|
||||
tempLink.style.display = 'none';
|
||||
tempLink.href = blobURL;
|
||||
tempLink.setAttribute('download', filename);
|
||||
if (typeof tempLink.download === 'undefined') {
|
||||
tempLink.setAttribute('target', '_blank');
|
||||
}
|
||||
document.body.appendChild(tempLink);
|
||||
tempLink.click();
|
||||
document.body.removeChild(tempLink);
|
||||
window.URL.revokeObjectURL(blobURL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download file according to file address
|
||||
* @param {*} sUrl
|
||||
*/
|
||||
export function downloadByUrl({ url, target = '_blank', fileName }: { url: string; target?: TargetContext; fileName?: string }): boolean {
|
||||
const isChrome = window.navigator.userAgent.toLowerCase().indexOf('chrome') > -1;
|
||||
const isSafari = window.navigator.userAgent.toLowerCase().indexOf('safari') > -1;
|
||||
|
||||
if (/(iP)/g.test(window.navigator.userAgent)) {
|
||||
console.error('Your browser does not support download!');
|
||||
return false;
|
||||
}
|
||||
if (isChrome || isSafari) {
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.target = target;
|
||||
|
||||
if (link.download !== undefined) {
|
||||
link.download = fileName || url.substring(url.lastIndexOf('/') + 1, url.length);
|
||||
}
|
||||
|
||||
if (document.createEvent) {
|
||||
const e = document.createEvent('MouseEvents');
|
||||
e.initEvent('click', true, true);
|
||||
link.dispatchEvent(e);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (url.indexOf('?') === -1) {
|
||||
url += '?download';
|
||||
}
|
||||
|
||||
openWindow(url, { target });
|
||||
return true;
|
||||
}
|
||||
197
jeecgboot-vue3/src/utils/helper/treeHelper.ts
Normal file
197
jeecgboot-vue3/src/utils/helper/treeHelper.ts
Normal file
@ -0,0 +1,197 @@
|
||||
interface TreeHelperConfig {
|
||||
id: string;
|
||||
children: string;
|
||||
pid: string;
|
||||
}
|
||||
|
||||
// 默认配置
|
||||
const DEFAULT_CONFIG: TreeHelperConfig = {
|
||||
id: 'id',
|
||||
children: 'children',
|
||||
pid: 'pid',
|
||||
};
|
||||
|
||||
// 获取配置。 Object.assign 从一个或多个源对象复制到目标对象
|
||||
const getConfig = (config: Partial<TreeHelperConfig>) => Object.assign({}, DEFAULT_CONFIG, config);
|
||||
|
||||
// tree from list
|
||||
// 列表中的树
|
||||
export function listToTree<T = any>(list: any[], config: Partial<TreeHelperConfig> = {}): T[] {
|
||||
const conf = getConfig(config) as TreeHelperConfig;
|
||||
const nodeMap = new Map();
|
||||
const result: T[] = [];
|
||||
const { id, children, pid } = conf;
|
||||
|
||||
for (const node of list) {
|
||||
node[children] = node[children] || [];
|
||||
nodeMap.set(node[id], node);
|
||||
}
|
||||
for (const node of list) {
|
||||
const parent = nodeMap.get(node[pid]);
|
||||
(parent ? parent[children] : result).push(node);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function treeToList<T = any>(tree: any, config: Partial<TreeHelperConfig> = {}): T {
|
||||
config = getConfig(config);
|
||||
const { children } = config;
|
||||
const result: any = [...tree];
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
if (!result[i][children!]) continue;
|
||||
result.splice(i + 1, 0, ...result[i][children!]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function findNode<T = any>(tree: any, func: Fn, config: Partial<TreeHelperConfig> = {}): T | null {
|
||||
config = getConfig(config);
|
||||
const { children } = config;
|
||||
const list = [...tree];
|
||||
for (const node of list) {
|
||||
if (func(node)) return node;
|
||||
node[children!] && list.push(...node[children!]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function findNodeAll<T = any>(tree: any, func: Fn, config: Partial<TreeHelperConfig> = {}): T[] {
|
||||
config = getConfig(config);
|
||||
const { children } = config;
|
||||
const list = [...tree];
|
||||
const result: T[] = [];
|
||||
for (const node of list) {
|
||||
func(node) && result.push(node);
|
||||
node[children!] && list.push(...node[children!]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function findPath<T = any>(tree: any, func: Fn, config: Partial<TreeHelperConfig> = {}): T | T[] | null {
|
||||
config = getConfig(config);
|
||||
const path: T[] = [];
|
||||
const list = [...tree];
|
||||
const visitedSet = new Set();
|
||||
const { children } = config;
|
||||
while (list.length) {
|
||||
const node = list[0];
|
||||
if (visitedSet.has(node)) {
|
||||
path.pop();
|
||||
list.shift();
|
||||
} else {
|
||||
visitedSet.add(node);
|
||||
node[children!] && list.unshift(...node[children!]);
|
||||
path.push(node);
|
||||
if (func(node)) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function findPathAll(tree: any, func: Fn, config: Partial<TreeHelperConfig> = {}) {
|
||||
config = getConfig(config);
|
||||
const path: any[] = [];
|
||||
const list = [...tree];
|
||||
const result: any[] = [];
|
||||
const visitedSet = new Set(),
|
||||
{ children } = config;
|
||||
while (list.length) {
|
||||
const node = list[0];
|
||||
if (visitedSet.has(node)) {
|
||||
path.pop();
|
||||
list.shift();
|
||||
} else {
|
||||
visitedSet.add(node);
|
||||
node[children!] && list.unshift(...node[children!]);
|
||||
path.push(node);
|
||||
func(node) && result.push([...path]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function filter<T = any>(
|
||||
tree: T[],
|
||||
func: (n: T) => boolean,
|
||||
// Partial 将 T 中的所有属性设为可选
|
||||
config: Partial<TreeHelperConfig> = {}
|
||||
): T[] {
|
||||
// 获取配置
|
||||
config = getConfig(config);
|
||||
const children = config.children as string;
|
||||
|
||||
function listFilter(list: T[]) {
|
||||
return list
|
||||
.map((node: any) => ({ ...node }))
|
||||
.filter((node) => {
|
||||
// 递归调用 对含有children项 进行再次调用自身函数 listFilter
|
||||
node[children] = node[children] && listFilter(node[children]);
|
||||
// 执行传入的回调 func 进行过滤
|
||||
return func(node) || (node[children] && node[children].length);
|
||||
});
|
||||
}
|
||||
|
||||
return listFilter(tree);
|
||||
}
|
||||
|
||||
export function forEach<T = any>(tree: T[], func: (n: T) => any, config: Partial<TreeHelperConfig> = {}): void {
|
||||
config = getConfig(config);
|
||||
const list: any[] = [...tree];
|
||||
const { children } = config;
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
//func 返回true就终止遍历,避免大量节点场景下无意义循环,引起浏览器卡顿
|
||||
if (func(list[i])) {
|
||||
return;
|
||||
}
|
||||
children && list[i][children] && list.splice(i + 1, 0, ...list[i][children]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Extract tree specified structure
|
||||
* @description: 提取树指定结构
|
||||
*/
|
||||
export function treeMap<T = any>(treeData: T[], opt: { children?: string; conversion: Fn }): T[] {
|
||||
return treeData.map((item) => treeMapEach(item, opt));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Extract tree specified structure
|
||||
* @description: 提取树指定结构
|
||||
*/
|
||||
export function treeMapEach(data: any, { children = 'children', conversion }: { children?: string; conversion: Fn }) {
|
||||
const haveChildren = Array.isArray(data[children]) && data[children].length > 0;
|
||||
const conversionData = conversion(data) || {};
|
||||
if (haveChildren) {
|
||||
return {
|
||||
...conversionData,
|
||||
[children]: data[children].map((i: number) =>
|
||||
treeMapEach(i, {
|
||||
children,
|
||||
conversion,
|
||||
})
|
||||
),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...conversionData,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归遍历树结构
|
||||
* @param treeDatas 树
|
||||
* @param callBack 回调
|
||||
* @param parentNode 父节点
|
||||
*/
|
||||
export function eachTree(treeDatas: any[], callBack: Fn, parentNode = {}) {
|
||||
treeDatas.forEach((element) => {
|
||||
const newNode = callBack(element, parentNode) || element;
|
||||
if (element.children) {
|
||||
eachTree(element.children, callBack, newNode);
|
||||
}
|
||||
});
|
||||
}
|
||||
35
jeecgboot-vue3/src/utils/helper/tsxHelper.tsx
Normal file
35
jeecgboot-vue3/src/utils/helper/tsxHelper.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import { Slots } from 'vue';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
|
||||
/**
|
||||
* @description: Get slot to prevent empty error
|
||||
*/
|
||||
export function getSlot(slots: Slots, slot = 'default', data?: any) {
|
||||
if (!slots || !Reflect.has(slots, slot)) {
|
||||
return null;
|
||||
}
|
||||
if (!isFunction(slots[slot])) {
|
||||
console.error(`${slot} is not a function!`);
|
||||
return null;
|
||||
}
|
||||
const slotFn = slots[slot];
|
||||
if (!slotFn) return null;
|
||||
return slotFn(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* extends slots
|
||||
* @param slots
|
||||
* @param excludeKeys
|
||||
*/
|
||||
export function extendSlots(slots: Slots, excludeKeys: string[] = []) {
|
||||
const slotKeys = Object.keys(slots);
|
||||
const ret: any = {};
|
||||
slotKeys.map((key) => {
|
||||
if (excludeKeys.includes(key)) {
|
||||
return null;
|
||||
}
|
||||
ret[key] = () => getSlot(slots, key);
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
155
jeecgboot-vue3/src/utils/helper/validator.ts
Normal file
155
jeecgboot-vue3/src/utils/helper/validator.ts
Normal file
@ -0,0 +1,155 @@
|
||||
import { dateUtil } from '/@/utils/dateUtil';
|
||||
import { duplicateCheck } from '/@/views/system/user/user.api';
|
||||
|
||||
export const rules = {
|
||||
rule(type, required) {
|
||||
if (type === 'email') {
|
||||
return this.email(required);
|
||||
}
|
||||
if (type === 'phone') {
|
||||
return this.phone(required);
|
||||
}
|
||||
},
|
||||
email(required) {
|
||||
return [
|
||||
{
|
||||
required: required ? required : false,
|
||||
validator: async (_rule, value) => {
|
||||
if (required == true && !value) {
|
||||
return Promise.reject('请输入邮箱!');
|
||||
}
|
||||
if (
|
||||
value &&
|
||||
!new RegExp(
|
||||
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||
).test(value)
|
||||
) {
|
||||
return Promise.reject('请输入正确邮箱格式!');
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
trigger: 'change',
|
||||
},
|
||||
] as ArrayRule;
|
||||
},
|
||||
phone(required) {
|
||||
return [
|
||||
{
|
||||
required: required,
|
||||
validator: async (_, value) => {
|
||||
if (required && !value) {
|
||||
return Promise.reject('请输入手机号码!');
|
||||
}
|
||||
if (!/^1[3456789]\d{9}$/.test(value)) {
|
||||
return Promise.reject('手机号码格式有误');
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
trigger: 'change',
|
||||
},
|
||||
];
|
||||
},
|
||||
startTime(endTime, required) {
|
||||
return [
|
||||
{
|
||||
required: required ? required : false,
|
||||
validator: (_, value) => {
|
||||
if (required && !value) {
|
||||
return Promise.reject('请选择开始时间');
|
||||
}
|
||||
if (endTime && value && dateUtil(endTime).isBefore(value)) {
|
||||
return Promise.reject('开始时间需小于结束时间');
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
trigger: 'change',
|
||||
},
|
||||
];
|
||||
},
|
||||
endTime(startTime, required) {
|
||||
return [
|
||||
{
|
||||
required: required ? required : false,
|
||||
validator: (_, value) => {
|
||||
if (required && !value) {
|
||||
return Promise.reject('请选择结束时间');
|
||||
}
|
||||
if (startTime && value && dateUtil(value).isBefore(startTime)) {
|
||||
return Promise.reject('结束时间需大于开始时间');
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
trigger: 'change',
|
||||
},
|
||||
];
|
||||
},
|
||||
confirmPassword(values, required) {
|
||||
return [
|
||||
{
|
||||
required: required ? required : false,
|
||||
validator: (_, value) => {
|
||||
if (!value) {
|
||||
return Promise.reject('密码不能为空');
|
||||
}
|
||||
if (value !== values.password) {
|
||||
return Promise.reject('两次输入的密码不一致!');
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
duplicateCheckRule(tableName, fieldName, model, schema, required?) {
|
||||
return [
|
||||
{
|
||||
validator: (_, value) => {
|
||||
if (!value && required) {
|
||||
return Promise.reject(`请输入${schema.label}`);
|
||||
}
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
duplicateCheck({
|
||||
tableName,
|
||||
fieldName,
|
||||
fieldVal: value,
|
||||
dataId: model.id,
|
||||
})
|
||||
.then((res) => {
|
||||
res.success ? resolve() : reject(res.message || '校验失败');
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err.message || '验证失败');
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
] as ArrayRule;
|
||||
},
|
||||
};
|
||||
|
||||
//update-begin-author:taoyan date:2022-6-16 for: 代码生成-原生表单用
|
||||
/**
|
||||
* 唯一校验函数,给原生<a-form>使用,vben的表单校验建议使用上述rules
|
||||
* @param tableName 表名
|
||||
* @param fieldName 字段名
|
||||
* @param fieldVal 字段值
|
||||
* @param dataId 数据ID
|
||||
*/
|
||||
export async function duplicateValidate(tableName, fieldName, fieldVal, dataId) {
|
||||
try {
|
||||
let params = {
|
||||
tableName,
|
||||
fieldName,
|
||||
fieldVal,
|
||||
dataId: dataId,
|
||||
};
|
||||
const res = await duplicateCheck(params);
|
||||
if (res.success) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return Promise.reject(res.message || '校验失败');
|
||||
}
|
||||
} catch (e) {
|
||||
return Promise.reject('校验失败,可能是断网等问题导致的校验失败');
|
||||
}
|
||||
}
|
||||
//update-end-author:taoyan date:2022-6-16 for: 代码生成-原生表单用
|
||||
270
jeecgboot-vue3/src/utils/http/axios/Axios.ts
Normal file
270
jeecgboot-vue3/src/utils/http/axios/Axios.ts
Normal file
@ -0,0 +1,270 @@
|
||||
import type { AxiosRequestConfig, AxiosInstance, AxiosResponse, AxiosError } from 'axios';
|
||||
import type { RequestOptions, Result, UploadFileParams, UploadFileCallBack } from '/#/axios';
|
||||
import type { CreateAxiosOptions } from './axiosTransform';
|
||||
import axios from 'axios';
|
||||
import qs from 'qs';
|
||||
import { AxiosCanceler } from './axiosCancel';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { ContentTypeEnum } from '/@/enums/httpEnum';
|
||||
import { RequestEnum } from '/@/enums/httpEnum';
|
||||
import { useGlobSetting } from '/@/hooks/setting';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
|
||||
const { createMessage } = useMessage();
|
||||
export * from './axiosTransform';
|
||||
|
||||
/**
|
||||
* @description: axios module
|
||||
*/
|
||||
export class VAxios {
|
||||
private axiosInstance: AxiosInstance;
|
||||
private readonly options: CreateAxiosOptions;
|
||||
|
||||
constructor(options: CreateAxiosOptions) {
|
||||
this.options = options;
|
||||
this.axiosInstance = axios.create(options);
|
||||
this.setupInterceptors();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Create axios instance
|
||||
*/
|
||||
private createAxios(config: CreateAxiosOptions): void {
|
||||
this.axiosInstance = axios.create(config);
|
||||
}
|
||||
|
||||
private getTransform() {
|
||||
const { transform } = this.options;
|
||||
return transform;
|
||||
}
|
||||
|
||||
getAxios(): AxiosInstance {
|
||||
return this.axiosInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Reconfigure axios
|
||||
*/
|
||||
configAxios(config: CreateAxiosOptions) {
|
||||
if (!this.axiosInstance) {
|
||||
return;
|
||||
}
|
||||
this.createAxios(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Set general header
|
||||
*/
|
||||
setHeader(headers: any): void {
|
||||
if (!this.axiosInstance) {
|
||||
return;
|
||||
}
|
||||
Object.assign(this.axiosInstance.defaults.headers, headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Interceptor configuration
|
||||
*/
|
||||
private setupInterceptors() {
|
||||
const transform = this.getTransform();
|
||||
if (!transform) {
|
||||
return;
|
||||
}
|
||||
const { requestInterceptors, requestInterceptorsCatch, responseInterceptors, responseInterceptorsCatch } = transform;
|
||||
|
||||
const axiosCanceler = new AxiosCanceler();
|
||||
|
||||
// 请求侦听器配置处理
|
||||
this.axiosInstance.interceptors.request.use((config: AxiosRequestConfig) => {
|
||||
// If cancel repeat request is turned on, then cancel repeat request is prohibited
|
||||
// @ts-ignore
|
||||
const { ignoreCancelToken } = config.requestOptions;
|
||||
|
||||
const ignoreCancel = ignoreCancelToken !== undefined ? ignoreCancelToken : this.options.requestOptions?.ignoreCancelToken;
|
||||
|
||||
!ignoreCancel && axiosCanceler.addPending(config);
|
||||
if (requestInterceptors && isFunction(requestInterceptors)) {
|
||||
config = requestInterceptors(config, this.options);
|
||||
}
|
||||
return config;
|
||||
}, undefined);
|
||||
|
||||
// 请求拦截器错误捕获
|
||||
requestInterceptorsCatch &&
|
||||
isFunction(requestInterceptorsCatch) &&
|
||||
this.axiosInstance.interceptors.request.use(undefined, requestInterceptorsCatch);
|
||||
|
||||
// 响应结果拦截器处理
|
||||
this.axiosInstance.interceptors.response.use((res: AxiosResponse<any>) => {
|
||||
res && axiosCanceler.removePending(res.config);
|
||||
if (responseInterceptors && isFunction(responseInterceptors)) {
|
||||
res = responseInterceptors(res);
|
||||
}
|
||||
return res;
|
||||
}, undefined);
|
||||
|
||||
// 响应结果拦截器错误捕获
|
||||
responseInterceptorsCatch &&
|
||||
isFunction(responseInterceptorsCatch) &&
|
||||
this.axiosInstance.interceptors.response.use(undefined, responseInterceptorsCatch);
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件上传
|
||||
*/
|
||||
//--@updateBy-begin----author:liusq---date:20211117------for:增加上传回调参数callback------
|
||||
uploadFile<T = any>(config: AxiosRequestConfig, params: UploadFileParams, callback?: UploadFileCallBack) {
|
||||
//--@updateBy-end----author:liusq---date:20211117------for:增加上传回调参数callback------
|
||||
const formData = new window.FormData();
|
||||
const customFilename = params.name || 'file';
|
||||
|
||||
if (params.filename) {
|
||||
formData.append(customFilename, params.file, params.filename);
|
||||
} else {
|
||||
formData.append(customFilename, params.file);
|
||||
}
|
||||
const glob = useGlobSetting();
|
||||
config.baseURL = glob.uploadUrl;
|
||||
if (params.data) {
|
||||
Object.keys(params.data).forEach((key) => {
|
||||
const value = params.data![key];
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((item) => {
|
||||
formData.append(`${key}[]`, item);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
formData.append(key, params.data[key]);
|
||||
});
|
||||
}
|
||||
|
||||
return this.axiosInstance
|
||||
.request<T>({
|
||||
...config,
|
||||
method: 'POST',
|
||||
data: formData,
|
||||
headers: {
|
||||
'Content-type': ContentTypeEnum.FORM_DATA,
|
||||
ignoreCancelToken: true,
|
||||
},
|
||||
})
|
||||
.then((res: any) => {
|
||||
//--@updateBy-begin----author:liusq---date:20210914------for:上传判断是否包含回调方法------
|
||||
if (callback?.success && isFunction(callback?.success)) {
|
||||
callback?.success(res?.data);
|
||||
//--@updateBy-end----author:liusq---date:20210914------for:上传判断是否包含回调方法------
|
||||
} else if (callback?.isReturnResponse) {
|
||||
//--@updateBy-begin----author:liusq---date:20211117------for:上传判断是否返回res信息------
|
||||
return Promise.resolve(res?.data);
|
||||
//--@updateBy-end----author:liusq---date:20211117------for:上传判断是否返回res信息------
|
||||
} else {
|
||||
if (res.data.success == true && res.data.code == 200) {
|
||||
createMessage.success(res.data.message);
|
||||
} else {
|
||||
createMessage.error(res.data.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 支持表单数据
|
||||
supportFormData(config: AxiosRequestConfig) {
|
||||
const headers = config.headers || this.options.headers;
|
||||
const contentType = headers?.['Content-Type'] || headers?.['content-type'];
|
||||
|
||||
if (contentType !== ContentTypeEnum.FORM_URLENCODED || !Reflect.has(config, 'data') || config.method?.toUpperCase() === RequestEnum.GET) {
|
||||
return config;
|
||||
}
|
||||
|
||||
return {
|
||||
...config,
|
||||
data: qs.stringify(config.data, { arrayFormat: 'brackets' }),
|
||||
};
|
||||
}
|
||||
|
||||
get<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
|
||||
return this.request({ ...config, method: 'GET' }, options);
|
||||
}
|
||||
|
||||
post<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
|
||||
return this.request({ ...config, method: 'POST' }, options);
|
||||
}
|
||||
|
||||
put<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
|
||||
return this.request({ ...config, method: 'PUT' }, options);
|
||||
}
|
||||
|
||||
delete<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
|
||||
return this.request({ ...config, method: 'DELETE' }, options);
|
||||
}
|
||||
|
||||
request<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
|
||||
let conf: CreateAxiosOptions = cloneDeep(config);
|
||||
const transform = this.getTransform();
|
||||
|
||||
const { requestOptions } = this.options;
|
||||
|
||||
const opt: RequestOptions = Object.assign({}, requestOptions, options);
|
||||
|
||||
const { beforeRequestHook, requestCatchHook, transformRequestHook } = transform || {};
|
||||
if (beforeRequestHook && isFunction(beforeRequestHook)) {
|
||||
conf = beforeRequestHook(conf, opt);
|
||||
}
|
||||
conf.requestOptions = opt;
|
||||
|
||||
conf = this.supportFormData(conf);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.axiosInstance
|
||||
.request<any, AxiosResponse<Result>>(conf)
|
||||
.then((res: AxiosResponse<Result>) => {
|
||||
if (transformRequestHook && isFunction(transformRequestHook)) {
|
||||
try {
|
||||
const ret = transformRequestHook(res, opt);
|
||||
//zhangyafei---添加回调方法
|
||||
config.success && config.success(res.data);
|
||||
//zhangyafei---添加回调方法
|
||||
resolve(ret);
|
||||
} catch (err) {
|
||||
reject(err || new Error('request error!'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
resolve(res as unknown as Promise<T>);
|
||||
})
|
||||
.catch((e: Error | AxiosError) => {
|
||||
if (requestCatchHook && isFunction(requestCatchHook)) {
|
||||
reject(requestCatchHook(e, opt));
|
||||
return;
|
||||
}
|
||||
if (axios.isAxiosError(e)) {
|
||||
// 在此处重写来自axios的错误消息
|
||||
}
|
||||
reject(e);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 【用于评论功能】自定义文件上传-请求
|
||||
* @param url
|
||||
* @param formData
|
||||
*/
|
||||
uploadMyFile<T = any>(url, formData) {
|
||||
const glob = useGlobSetting();
|
||||
return this.axiosInstance
|
||||
.request<T>({
|
||||
url: url,
|
||||
baseURL: glob.uploadUrl,
|
||||
method: 'POST',
|
||||
data: formData,
|
||||
headers: {
|
||||
'Content-type': ContentTypeEnum.FORM_DATA,
|
||||
ignoreCancelToken: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
60
jeecgboot-vue3/src/utils/http/axios/axiosCancel.ts
Normal file
60
jeecgboot-vue3/src/utils/http/axios/axiosCancel.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import type { AxiosRequestConfig, Canceler } from 'axios';
|
||||
import axios from 'axios';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
|
||||
// Used to store the identification and cancellation function of each request
|
||||
let pendingMap = new Map<string, Canceler>();
|
||||
|
||||
export const getPendingUrl = (config: AxiosRequestConfig) => [config.method, config.url].join('&');
|
||||
|
||||
export class AxiosCanceler {
|
||||
/**
|
||||
* Add request
|
||||
* @param {Object} config
|
||||
*/
|
||||
addPending(config: AxiosRequestConfig) {
|
||||
this.removePending(config);
|
||||
const url = getPendingUrl(config);
|
||||
config.cancelToken =
|
||||
config.cancelToken ||
|
||||
new axios.CancelToken((cancel) => {
|
||||
if (!pendingMap.has(url)) {
|
||||
// If there is no current request in pending, add it
|
||||
pendingMap.set(url, cancel);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Clear all pending
|
||||
*/
|
||||
removeAllPending() {
|
||||
pendingMap.forEach((cancel) => {
|
||||
cancel && isFunction(cancel) && cancel();
|
||||
});
|
||||
pendingMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removal request
|
||||
* @param {Object} config
|
||||
*/
|
||||
removePending(config: AxiosRequestConfig) {
|
||||
const url = getPendingUrl(config);
|
||||
|
||||
if (pendingMap.has(url)) {
|
||||
// If there is a current request identifier in pending,
|
||||
// the current request needs to be cancelled and removed
|
||||
const cancel = pendingMap.get(url);
|
||||
cancel && cancel(url);
|
||||
pendingMap.delete(url);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: reset
|
||||
*/
|
||||
reset(): void {
|
||||
pendingMap = new Map<string, Canceler>();
|
||||
}
|
||||
}
|
||||
49
jeecgboot-vue3/src/utils/http/axios/axiosTransform.ts
Normal file
49
jeecgboot-vue3/src/utils/http/axios/axiosTransform.ts
Normal file
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Data processing class, can be configured according to the project
|
||||
*/
|
||||
import type { AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||
import type { RequestOptions, Result } from '/#/axios';
|
||||
|
||||
export interface CreateAxiosOptions extends AxiosRequestConfig {
|
||||
authenticationScheme?: string;
|
||||
transform?: AxiosTransform;
|
||||
requestOptions?: RequestOptions;
|
||||
}
|
||||
|
||||
export abstract class AxiosTransform {
|
||||
/**
|
||||
* @description: Process configuration before request
|
||||
* @description: Process configuration before request
|
||||
*/
|
||||
beforeRequestHook?: (config: AxiosRequestConfig, options: RequestOptions) => AxiosRequestConfig;
|
||||
|
||||
/**
|
||||
* @description: Request successfully processed
|
||||
*/
|
||||
transformRequestHook?: (res: AxiosResponse<Result>, options: RequestOptions) => any;
|
||||
|
||||
/**
|
||||
* @description: 请求失败处理
|
||||
*/
|
||||
requestCatchHook?: (e: Error, options: RequestOptions) => Promise<any>;
|
||||
|
||||
/**
|
||||
* @description: 请求之前的拦截器
|
||||
*/
|
||||
requestInterceptors?: (config: AxiosRequestConfig, options: CreateAxiosOptions) => AxiosRequestConfig;
|
||||
|
||||
/**
|
||||
* @description: 请求之后的拦截器
|
||||
*/
|
||||
responseInterceptors?: (res: AxiosResponse<any>) => AxiosResponse<any>;
|
||||
|
||||
/**
|
||||
* @description: 请求之前的拦截器错误处理
|
||||
*/
|
||||
requestInterceptorsCatch?: (error: Error) => void;
|
||||
|
||||
/**
|
||||
* @description: 请求之后的拦截器错误处理
|
||||
*/
|
||||
responseInterceptorsCatch?: (error: Error) => void;
|
||||
}
|
||||
76
jeecgboot-vue3/src/utils/http/axios/checkStatus.ts
Normal file
76
jeecgboot-vue3/src/utils/http/axios/checkStatus.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import type { ErrorMessageMode } from '/#/axios';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
// import router from '/@/router';
|
||||
// import { PageEnum } from '/@/enums/pageEnum';
|
||||
import { useUserStoreWithOut } from '/@/store/modules/user';
|
||||
import projectSetting from '/@/settings/projectSetting';
|
||||
import { SessionTimeoutProcessingEnum } from '/@/enums/appEnum';
|
||||
|
||||
const { createMessage, createErrorModal } = useMessage();
|
||||
const error = createMessage.error!;
|
||||
const stp = projectSetting.sessionTimeoutProcessing;
|
||||
|
||||
export function checkStatus(status: number, msg: string, errorMessageMode: ErrorMessageMode = 'message'): void {
|
||||
const { t } = useI18n();
|
||||
const userStore = useUserStoreWithOut();
|
||||
let errMessage = '';
|
||||
|
||||
switch (status) {
|
||||
case 400:
|
||||
errMessage = `${msg}`;
|
||||
break;
|
||||
// 401: Not logged in
|
||||
// Jump to the login page if not logged in, and carry the path of the current page
|
||||
// Return to the current page after successful login. This step needs to be operated on the login page.
|
||||
case 401:
|
||||
userStore.setToken(undefined);
|
||||
errMessage = msg || t('sys.api.errMsg401');
|
||||
if (stp === SessionTimeoutProcessingEnum.PAGE_COVERAGE) {
|
||||
userStore.setSessionTimeout(true);
|
||||
} else {
|
||||
userStore.logout(true);
|
||||
}
|
||||
break;
|
||||
case 403:
|
||||
errMessage = t('sys.api.errMsg403');
|
||||
break;
|
||||
// 404请求不存在
|
||||
case 404:
|
||||
errMessage = t('sys.api.errMsg404');
|
||||
break;
|
||||
case 405:
|
||||
errMessage = t('sys.api.errMsg405');
|
||||
break;
|
||||
case 408:
|
||||
errMessage = t('sys.api.errMsg408');
|
||||
break;
|
||||
case 500:
|
||||
errMessage = t('sys.api.errMsg500');
|
||||
break;
|
||||
case 501:
|
||||
errMessage = t('sys.api.errMsg501');
|
||||
break;
|
||||
case 502:
|
||||
errMessage = t('sys.api.errMsg502');
|
||||
break;
|
||||
case 503:
|
||||
errMessage = t('sys.api.errMsg503');
|
||||
break;
|
||||
case 504:
|
||||
errMessage = t('sys.api.errMsg504');
|
||||
break;
|
||||
case 505:
|
||||
errMessage = t('sys.api.errMsg505');
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
if (errMessage) {
|
||||
if (errorMessageMode === 'modal') {
|
||||
createErrorModal({ title: t('sys.api.errorTip'), content: errMessage });
|
||||
} else if (errorMessageMode === 'message') {
|
||||
error({ content: errMessage, key: `global_error_message_status_${status}` });
|
||||
}
|
||||
}
|
||||
}
|
||||
47
jeecgboot-vue3/src/utils/http/axios/helper.ts
Normal file
47
jeecgboot-vue3/src/utils/http/axios/helper.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { isObject, isString } from '/@/utils/is';
|
||||
import dayjs from "dayjs";
|
||||
// update-begin--author:liaozhiyang---date:20240426---for:【QQYUN-9138】系统用户保存的时间没有秒
|
||||
const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';
|
||||
// update-end--author:liaozhiyang---date:20240426---for:【QQYUN-9138】系统用户保存的时间没有秒
|
||||
|
||||
export function joinTimestamp<T extends boolean>(join: boolean, restful: T): T extends true ? string : object;
|
||||
|
||||
export function joinTimestamp(join: boolean, restful = false): string | object {
|
||||
if (!join) {
|
||||
return restful ? '' : {};
|
||||
}
|
||||
const now = new Date().getTime();
|
||||
if (restful) {
|
||||
return `?_t=${now}`;
|
||||
}
|
||||
return { _t: now };
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Format request parameter time
|
||||
*/
|
||||
export function formatRequestDate(params: Recordable) {
|
||||
if (Object.prototype.toString.call(params) !== '[object Object]') {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const key in params) {
|
||||
// 判断是否是dayjs实例
|
||||
if (dayjs.isDayjs(params[key])) {
|
||||
params[key] = params[key].format(DATE_TIME_FORMAT);
|
||||
}
|
||||
if (isString(key)) {
|
||||
const value = params[key];
|
||||
if (value) {
|
||||
try {
|
||||
params[key] = isString(value) ? value.trim() : value;
|
||||
} catch (error) {
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isObject(params[key])) {
|
||||
formatRequestDate(params[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
311
jeecgboot-vue3/src/utils/http/axios/index.ts
Normal file
311
jeecgboot-vue3/src/utils/http/axios/index.ts
Normal file
@ -0,0 +1,311 @@
|
||||
// axios配置 可自行根据项目进行更改,只需更改该文件即可,其他文件可以不动
|
||||
// The axios configuration can be changed according to the project, just change the file, other files can be left unchanged
|
||||
|
||||
import type { AxiosResponse } from 'axios';
|
||||
import type { RequestOptions, Result } from '/#/axios';
|
||||
import type { AxiosTransform, CreateAxiosOptions } from './axiosTransform';
|
||||
import { VAxios } from './Axios';
|
||||
import { checkStatus } from './checkStatus';
|
||||
import { router } from '/@/router';
|
||||
import { useGlobSetting } from '/@/hooks/setting';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { RequestEnum, ResultEnum, ContentTypeEnum, ConfigEnum } from '/@/enums/httpEnum';
|
||||
import { isString } from '/@/utils/is';
|
||||
import { getToken, getTenantId } from '/@/utils/auth';
|
||||
import { setObjToUrlParams, deepMerge } from '/@/utils';
|
||||
import signMd5Utils from '/@/utils/encryption/signMd5Utils';
|
||||
import { useErrorLogStoreWithOut } from '/@/store/modules/errorLog';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { joinTimestamp, formatRequestDate } from './helper';
|
||||
import { useUserStoreWithOut } from '/@/store/modules/user';
|
||||
import { cloneDeep } from "lodash-es";
|
||||
const globSetting = useGlobSetting();
|
||||
const urlPrefix = globSetting.urlPrefix;
|
||||
const { createMessage, createErrorModal } = useMessage();
|
||||
|
||||
/**
|
||||
* @description: 数据处理,方便区分多种处理方式
|
||||
*/
|
||||
const transform: AxiosTransform = {
|
||||
/**
|
||||
* @description: 处理请求数据。如果数据不是预期格式,可直接抛出错误
|
||||
*/
|
||||
transformRequestHook: (res: AxiosResponse<Result>, options: RequestOptions) => {
|
||||
const { t } = useI18n();
|
||||
const { isTransformResponse, isReturnNativeResponse } = options;
|
||||
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
|
||||
if (isReturnNativeResponse) {
|
||||
return res;
|
||||
}
|
||||
// 不进行任何处理,直接返回
|
||||
// 用于页面代码可能需要直接获取code,data,message这些信息时开启
|
||||
if (!isTransformResponse) {
|
||||
return res.data;
|
||||
}
|
||||
// 错误的时候返回
|
||||
|
||||
const { data } = res;
|
||||
if (!data) {
|
||||
// return '[HTTP] Request has no return value';
|
||||
throw new Error(t('sys.api.apiRequestFailed'));
|
||||
}
|
||||
// 这里 code,result,message为 后台统一的字段,需要在 types.ts内修改为项目自己的接口返回格式
|
||||
const { code, result, message, success } = data;
|
||||
// 这里逻辑可以根据项目进行修改
|
||||
const hasSuccess = data && Reflect.has(data, 'code') && (code === ResultEnum.SUCCESS || code === 200);
|
||||
if (hasSuccess) {
|
||||
if (success && message && options.successMessageMode === 'success') {
|
||||
//信息成功提示
|
||||
createMessage.success(message);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// 在此处根据自己项目的实际情况对不同的code执行不同的操作
|
||||
// 如果不希望中断当前请求,请return数据,否则直接抛出异常即可
|
||||
let timeoutMsg = '';
|
||||
switch (code) {
|
||||
case ResultEnum.TIMEOUT:
|
||||
timeoutMsg = t('sys.api.timeoutMessage');
|
||||
const userStore = useUserStoreWithOut();
|
||||
userStore.setToken(undefined);
|
||||
userStore.logout(true);
|
||||
break;
|
||||
default:
|
||||
if (message) {
|
||||
timeoutMsg = message;
|
||||
}
|
||||
}
|
||||
|
||||
// errorMessageMode=‘modal’的时候会显示modal错误弹窗,而不是消息提示,用于一些比较重要的错误
|
||||
// errorMessageMode='none' 一般是调用时明确表示不希望自动弹出错误提示
|
||||
if (options.errorMessageMode === 'modal') {
|
||||
createErrorModal({ title: t('sys.api.errorTip'), content: timeoutMsg });
|
||||
} else if (options.errorMessageMode === 'message') {
|
||||
createMessage.error(timeoutMsg);
|
||||
}
|
||||
|
||||
throw new Error(timeoutMsg || t('sys.api.apiRequestFailed'));
|
||||
},
|
||||
|
||||
// 请求之前处理config
|
||||
beforeRequestHook: (config, options) => {
|
||||
const { apiUrl, joinPrefix, joinParamsToUrl, formatDate, joinTime = true, urlPrefix } = options;
|
||||
|
||||
//update-begin---author:scott ---date:2024-02-20 for:以http开头的请求url,不拼加前缀--
|
||||
// http开头的请求url,不加前缀
|
||||
let isStartWithHttp = false;
|
||||
const requestUrl = config.url;
|
||||
if(requestUrl!=null && (requestUrl.startsWith("http:") || requestUrl.startsWith("https:"))){
|
||||
isStartWithHttp = true;
|
||||
}
|
||||
if (!isStartWithHttp && joinPrefix) {
|
||||
config.url = `${urlPrefix}${config.url}`;
|
||||
}
|
||||
|
||||
if (!isStartWithHttp && apiUrl && isString(apiUrl)) {
|
||||
config.url = `${apiUrl}${config.url}`;
|
||||
}
|
||||
//update-end---author:scott ---date::2024-02-20 for:以http开头的请求url,不拼加前缀--
|
||||
|
||||
const params = config.params || {};
|
||||
const data = config.data || false;
|
||||
formatDate && data && !isString(data) && formatRequestDate(data);
|
||||
if (config.method?.toUpperCase() === RequestEnum.GET) {
|
||||
if (!isString(params)) {
|
||||
// 给 get 请求加上时间戳参数,避免从缓存中拿数据。
|
||||
config.params = Object.assign(params || {}, joinTimestamp(joinTime, false));
|
||||
} else {
|
||||
// 兼容restful风格
|
||||
config.url = config.url + params + `${joinTimestamp(joinTime, true)}`;
|
||||
config.params = undefined;
|
||||
}
|
||||
} else {
|
||||
if (!isString(params)) {
|
||||
formatDate && formatRequestDate(params);
|
||||
if (Reflect.has(config, 'data') && config.data && Object.keys(config.data).length > 0) {
|
||||
config.data = data;
|
||||
config.params = params;
|
||||
} else {
|
||||
// 非GET请求如果没有提供data,则将params视为data
|
||||
config.data = params;
|
||||
config.params = undefined;
|
||||
}
|
||||
if (joinParamsToUrl) {
|
||||
config.url = setObjToUrlParams(config.url as string, Object.assign({}, config.params, config.data));
|
||||
}
|
||||
} else {
|
||||
// 兼容restful风格
|
||||
config.url = config.url + params;
|
||||
config.params = undefined;
|
||||
}
|
||||
}
|
||||
return config;
|
||||
},
|
||||
|
||||
/**
|
||||
* @description: 请求拦截器处理
|
||||
*/
|
||||
requestInterceptors: (config: Recordable, options) => {
|
||||
// 请求之前处理config
|
||||
const token = getToken();
|
||||
let tenantId: string | number = getTenantId();
|
||||
|
||||
//update-begin---author:wangshuai---date:2024-04-16---for:【QQYUN-9005】发送短信加签。解决没有token无法加签---
|
||||
// 将签名和时间戳,添加在请求接口 Header
|
||||
config.headers[ConfigEnum.TIMESTAMP] = signMd5Utils.getTimestamp();
|
||||
//update-begin---author:wangshuai---date:2024-04-25---for: 生成签名的时候复制一份,避免影响原来的参数---
|
||||
config.headers[ConfigEnum.Sign] = signMd5Utils.getSign(config.url, cloneDeep(config.params), cloneDeep(config.data));
|
||||
//update-end---author:wangshuai---date:2024-04-25---for: 生成签名的时候复制一份,避免影响原来的参数---
|
||||
//update-end---author:wangshuai---date:2024-04-16---for:【QQYUN-9005】发送短信加签。解决没有token无法加签---
|
||||
// update-begin--author:liaozhiyang---date:20240509---for:【issues/1220】登录时,vue3版本不加载字典数据设置无效
|
||||
//--update-begin--author:liusq---date:20220325---for: 增加vue3标记
|
||||
config.headers[ConfigEnum.VERSION] = 'v3';
|
||||
//--update-end--author:liusq---date:20220325---for:增加vue3标记
|
||||
// update-end--author:liaozhiyang---date:20240509---for:【issues/1220】登录时,vue3版本不加载字典数据设置无效
|
||||
if (token && (config as Recordable)?.requestOptions?.withToken !== false) {
|
||||
// jwt token
|
||||
config.headers.Authorization = options.authenticationScheme ? `${options.authenticationScheme} ${token}` : token;
|
||||
config.headers[ConfigEnum.TOKEN] = token;
|
||||
|
||||
// 将签名和时间戳,添加在请求接口 Header
|
||||
//config.headers[ConfigEnum.TIMESTAMP] = signMd5Utils.getTimestamp();
|
||||
//config.headers[ConfigEnum.Sign] = signMd5Utils.getSign(config.url, config.params);
|
||||
if (!tenantId) {
|
||||
tenantId = 0;
|
||||
}
|
||||
|
||||
// update-begin--author:sunjianlei---date:220230428---for:【QQYUN-5279】修复分享的应用租户和当前登录租户不一致时,提示404的问题
|
||||
const userStore = useUserStoreWithOut();
|
||||
// 判断是否有临时租户id
|
||||
if (userStore.hasShareTenantId && userStore.shareTenantId !== 0) {
|
||||
// 临时租户id存在,使用临时租户id
|
||||
tenantId = userStore.shareTenantId!;
|
||||
}
|
||||
// update-end--author:sunjianlei---date:220230428---for:【QQYUN-5279】修复分享的应用租户和当前登录租户不一致时,提示404的问题
|
||||
|
||||
config.headers[ConfigEnum.TENANT_ID] = tenantId;
|
||||
//--update-end--author:liusq---date:20211105---for:将多租户id,添加在请求接口 Header
|
||||
|
||||
// ========================================================================================
|
||||
// update-begin--author:sunjianlei---date:20220624--for: 添加低代码应用ID
|
||||
let routeParams = router.currentRoute.value.params;
|
||||
if (routeParams.appId) {
|
||||
config.headers[ConfigEnum.X_LOW_APP_ID] = routeParams.appId;
|
||||
// lowApp自定义筛选条件
|
||||
if (routeParams.lowAppFilter) {
|
||||
config.params = { ...config.params, ...JSON.parse(routeParams.lowAppFilter as string) };
|
||||
delete routeParams.lowAppFilter;
|
||||
}
|
||||
}
|
||||
// update-end--author:sunjianlei---date:20220624--for: 添加低代码应用ID
|
||||
// ========================================================================================
|
||||
|
||||
}
|
||||
return config;
|
||||
},
|
||||
|
||||
/**
|
||||
* @description: 响应拦截器处理
|
||||
*/
|
||||
responseInterceptors: (res: AxiosResponse<any>) => {
|
||||
return res;
|
||||
},
|
||||
|
||||
/**
|
||||
* @description: 响应错误处理
|
||||
*/
|
||||
responseInterceptorsCatch: (error: any) => {
|
||||
const { t } = useI18n();
|
||||
const errorLogStore = useErrorLogStoreWithOut();
|
||||
errorLogStore.addAjaxErrorInfo(error);
|
||||
const { response, code, message, config } = error || {};
|
||||
const errorMessageMode = config?.requestOptions?.errorMessageMode || 'none';
|
||||
//scott 20211022 token失效提示信息
|
||||
//const msg: string = response?.data?.error?.message ?? '';
|
||||
const msg: string = response?.data?.message ?? '';
|
||||
const err: string = error?.toString?.() ?? '';
|
||||
let errMessage = '';
|
||||
|
||||
try {
|
||||
if (code === 'ECONNABORTED' && message.indexOf('timeout') !== -1) {
|
||||
errMessage = t('sys.api.apiTimeoutMessage');
|
||||
}
|
||||
if (err?.includes('Network Error')) {
|
||||
errMessage = t('sys.api.networkExceptionMsg');
|
||||
}
|
||||
|
||||
if (errMessage) {
|
||||
if (errorMessageMode === 'modal') {
|
||||
createErrorModal({ title: t('sys.api.errorTip'), content: errMessage });
|
||||
} else if (errorMessageMode === 'message') {
|
||||
createMessage.error(errMessage);
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
checkStatus(error?.response?.status, msg, errorMessageMode);
|
||||
return Promise.reject(error);
|
||||
},
|
||||
};
|
||||
|
||||
function createAxios(opt?: Partial<CreateAxiosOptions>) {
|
||||
return new VAxios(
|
||||
deepMerge(
|
||||
{
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#authentication_schemes
|
||||
// authentication schemes,e.g: Bearer
|
||||
// authenticationScheme: 'Bearer',
|
||||
authenticationScheme: '',
|
||||
//接口超时设置
|
||||
timeout: 10 * 1000,
|
||||
// 基础接口地址
|
||||
// baseURL: globSetting.apiUrl,
|
||||
headers: { 'Content-Type': ContentTypeEnum.JSON },
|
||||
// 如果是form-data格式
|
||||
// headers: { 'Content-Type': ContentTypeEnum.FORM_URLENCODED },
|
||||
// 数据处理方式
|
||||
transform,
|
||||
// 配置项,下面的选项都可以在独立的接口请求中覆盖
|
||||
requestOptions: {
|
||||
// 默认将prefix 添加到url
|
||||
joinPrefix: true,
|
||||
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
|
||||
isReturnNativeResponse: false,
|
||||
// 需要对返回数据进行处理
|
||||
isTransformResponse: true,
|
||||
// post请求的时候添加参数到url
|
||||
joinParamsToUrl: false,
|
||||
// 格式化提交参数时间
|
||||
formatDate: true,
|
||||
// 异常消息提示类型
|
||||
errorMessageMode: 'message',
|
||||
// 成功消息提示类型
|
||||
successMessageMode: 'success',
|
||||
// 接口地址
|
||||
apiUrl: globSetting.apiUrl,
|
||||
// 接口拼接地址
|
||||
urlPrefix: urlPrefix,
|
||||
// 是否加入时间戳
|
||||
joinTime: true,
|
||||
// 忽略重复请求
|
||||
ignoreCancelToken: true,
|
||||
// 是否携带token
|
||||
withToken: true,
|
||||
},
|
||||
},
|
||||
opt || {}
|
||||
)
|
||||
);
|
||||
}
|
||||
export const defHttp = createAxios();
|
||||
|
||||
// other api url
|
||||
// export const otherHttp = createAxios({
|
||||
// requestOptions: {
|
||||
// apiUrl: 'xxx',
|
||||
// },
|
||||
// });
|
||||
440
jeecgboot-vue3/src/utils/index.ts
Normal file
440
jeecgboot-vue3/src/utils/index.ts
Normal file
@ -0,0 +1,440 @@
|
||||
import type { RouteLocationNormalized, RouteRecordNormalized } from 'vue-router';
|
||||
import type { App, Plugin } from 'vue';
|
||||
|
||||
import { unref } from 'vue';
|
||||
import { isObject } from '/@/utils/is';
|
||||
|
||||
// update-begin--author:sunjianlei---date:20220408---for: 【VUEN-656】配置外部网址打不开,原因是带了#号,需要替换一下
|
||||
export const URL_HASH_TAB = `__AGWE4H__HASH__TAG__PWHRG__`;
|
||||
// update-end--author:sunjianlei---date:20220408---for: 【VUEN-656】配置外部网址打不开,原因是带了#号,需要替换一下
|
||||
|
||||
export const noop = () => {};
|
||||
|
||||
/**
|
||||
* @description: Set ui mount node
|
||||
*/
|
||||
export function getPopupContainer(node?: HTMLElement): HTMLElement {
|
||||
return (node?.parentNode as HTMLElement) ?? document.body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the object as a parameter to the URL
|
||||
* @param baseUrl url
|
||||
* @param obj
|
||||
* @returns {string}
|
||||
* eg:
|
||||
* let obj = {a: '3', b: '4'}
|
||||
* setObjToUrlParams('www.baidu.com', obj)
|
||||
* ==>www.baidu.com?a=3&b=4
|
||||
*/
|
||||
export function setObjToUrlParams(baseUrl: string, obj: any): string {
|
||||
let parameters = '';
|
||||
for (const key in obj) {
|
||||
parameters += key + '=' + encodeURIComponent(obj[key]) + '&';
|
||||
}
|
||||
parameters = parameters.replace(/&$/, '');
|
||||
return /\?$/.test(baseUrl) ? baseUrl + parameters : baseUrl.replace(/\/?$/, '?') + parameters;
|
||||
}
|
||||
|
||||
export function deepMerge<T = any>(src: any = {}, target: any = {}): T {
|
||||
let key: string;
|
||||
for (key in target) {
|
||||
// update-begin--author:liaozhiyang---date:20240329---for:【QQYUN-7872】online表单label较长优化
|
||||
src[key] = isObject(src[key]) && isObject(target[key]) ? deepMerge(src[key], target[key]) : (src[key] = target[key]);
|
||||
// update-end--author:liaozhiyang---date:20240329---for:【QQYUN-7872】online表单label较长优化
|
||||
}
|
||||
return src;
|
||||
}
|
||||
|
||||
export function openWindow(url: string, opt?: { target?: TargetContext | string; noopener?: boolean; noreferrer?: boolean }) {
|
||||
const { target = '__blank', noopener = true, noreferrer = true } = opt || {};
|
||||
const feature: string[] = [];
|
||||
|
||||
noopener && feature.push('noopener=yes');
|
||||
noreferrer && feature.push('noreferrer=yes');
|
||||
|
||||
window.open(url, target, feature.join(','));
|
||||
}
|
||||
|
||||
// dynamic use hook props
|
||||
export function getDynamicProps<T, U>(props: T): Partial<U> {
|
||||
const ret: Recordable = {};
|
||||
|
||||
Object.keys(props).map((key) => {
|
||||
ret[key] = unref((props as Recordable)[key]);
|
||||
});
|
||||
|
||||
return ret as Partial<U>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表单字段值数据类型
|
||||
* @param props
|
||||
* @param field
|
||||
* @updateBy:zyf
|
||||
*/
|
||||
export function getValueType(props, field) {
|
||||
let formSchema = unref(unref(props)?.schemas);
|
||||
let valueType = 'string';
|
||||
if (formSchema) {
|
||||
let schema = formSchema.filter((item) => item.field === field)[0];
|
||||
valueType = schema.componentProps && schema.componentProps.valueType ? schema.componentProps.valueType : valueType;
|
||||
}
|
||||
return valueType;
|
||||
}
|
||||
export function getRawRoute(route: RouteLocationNormalized): RouteLocationNormalized {
|
||||
if (!route) return route;
|
||||
const { matched, ...opt } = route;
|
||||
return {
|
||||
...opt,
|
||||
matched: (matched
|
||||
? matched.map((item) => ({
|
||||
meta: item.meta,
|
||||
name: item.name,
|
||||
path: item.path,
|
||||
}))
|
||||
: undefined) as RouteRecordNormalized[],
|
||||
};
|
||||
}
|
||||
/**
|
||||
* 深度克隆对象、数组
|
||||
* @param obj 被克隆的对象
|
||||
* @return 克隆后的对象
|
||||
*/
|
||||
export function cloneObject(obj) {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
|
||||
export const withInstall = <T>(component: T, alias?: string) => {
|
||||
//console.log("---初始化---", component)
|
||||
|
||||
const comp = component as any;
|
||||
comp.install = (app: App) => {
|
||||
app.component(comp.name || comp.displayName, component);
|
||||
if (alias) {
|
||||
app.config.globalProperties[alias] = component;
|
||||
}
|
||||
};
|
||||
return component as T & Plugin;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取url地址参数
|
||||
* @param paraName
|
||||
*/
|
||||
export function getUrlParam(paraName) {
|
||||
let url = document.location.toString();
|
||||
let arrObj = url.split('?');
|
||||
|
||||
if (arrObj.length > 1) {
|
||||
let arrPara = arrObj[1].split('&');
|
||||
let arr;
|
||||
|
||||
for (let i = 0; i < arrPara.length; i++) {
|
||||
arr = arrPara[i].split('=');
|
||||
|
||||
if (arr != null && arr[0] == paraName) {
|
||||
return arr[1];
|
||||
}
|
||||
}
|
||||
return '';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 休眠(setTimeout的promise版)
|
||||
* @param ms 要休眠的时间,单位:毫秒
|
||||
* @param fn callback,可空
|
||||
* @return Promise
|
||||
*/
|
||||
export function sleep(ms: number, fn?: Fn) {
|
||||
return new Promise<void>((resolve) =>
|
||||
setTimeout(() => {
|
||||
fn && fn();
|
||||
resolve();
|
||||
}, ms)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 不用正则的方式替换所有值
|
||||
* @param text 被替换的字符串
|
||||
* @param checker 替换前的内容
|
||||
* @param replacer 替换后的内容
|
||||
* @returns {String} 替换后的字符串
|
||||
*/
|
||||
export function replaceAll(text, checker, replacer) {
|
||||
let lastText = text;
|
||||
text = text.replace(checker, replacer);
|
||||
if (lastText !== text) {
|
||||
return replaceAll(text, checker, replacer);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取URL上参数
|
||||
* @param url
|
||||
*/
|
||||
export function getQueryVariable(url) {
|
||||
if (!url) return;
|
||||
|
||||
var t,
|
||||
n,
|
||||
r,
|
||||
i = url.split('?')[1],
|
||||
s = {};
|
||||
(t = i.split('&')), (r = null), (n = null);
|
||||
for (var o in t) {
|
||||
var u = t[o].indexOf('=');
|
||||
u !== -1 && ((r = t[o].substr(0, u)), (n = t[o].substr(u + 1)), (s[r] = n));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
/**
|
||||
* 判断是否显示办理按钮
|
||||
* @param bpmStatus
|
||||
* @returns {*}
|
||||
*/
|
||||
export function showDealBtn(bpmStatus) {
|
||||
if (bpmStatus != '1' && bpmStatus != '3' && bpmStatus != '4') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 数字转大写
|
||||
* @param value
|
||||
* @returns {*}
|
||||
*/
|
||||
export function numToUpper(value) {
|
||||
if (value != '') {
|
||||
let unit = new Array('仟', '佰', '拾', '', '仟', '佰', '拾', '', '角', '分');
|
||||
const toDx = (n) => {
|
||||
switch (n) {
|
||||
case '0':
|
||||
return '零';
|
||||
case '1':
|
||||
return '壹';
|
||||
case '2':
|
||||
return '贰';
|
||||
case '3':
|
||||
return '叁';
|
||||
case '4':
|
||||
return '肆';
|
||||
case '5':
|
||||
return '伍';
|
||||
case '6':
|
||||
return '陆';
|
||||
case '7':
|
||||
return '柒';
|
||||
case '8':
|
||||
return '捌';
|
||||
case '9':
|
||||
return '玖';
|
||||
}
|
||||
};
|
||||
let lth = value.toString().length;
|
||||
value *= 100;
|
||||
value += '';
|
||||
let length = value.length;
|
||||
if (lth <= 8) {
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
if (i == 2) {
|
||||
result = '元' + result;
|
||||
} else if (i == 6) {
|
||||
result = '万' + result;
|
||||
}
|
||||
if (value.charAt(length - i - 1) == 0) {
|
||||
if (i != 0 && i != 1) {
|
||||
if (result.charAt(0) != '零' && result.charAt(0) != '元' && result.charAt(0) != '万') {
|
||||
result = '零' + result;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
result = toDx(value.charAt(length - i - 1)) + unit[unit.length - i - 1] + result;
|
||||
}
|
||||
result += result.charAt(result.length - 1) == '元' ? '整' : '';
|
||||
return result;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//update-begin-author:taoyan date:2022-6-8 for:解决老的vue2动态导入文件语法 vite不支持的问题
|
||||
const allModules = import.meta.glob('../views/**/*.vue');
|
||||
export function importViewsFile(path): Promise<any> {
|
||||
if (path.startsWith('/')) {
|
||||
path = path.substring(1);
|
||||
}
|
||||
let page = '';
|
||||
if (path.endsWith('.vue')) {
|
||||
page = `../views/${path}`;
|
||||
} else {
|
||||
page = `../views/${path}.vue`;
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
let flag = true;
|
||||
for (const path in allModules) {
|
||||
if (path == page) {
|
||||
flag = false;
|
||||
allModules[path]().then((mod) => {
|
||||
console.log(path, mod);
|
||||
resolve(mod);
|
||||
});
|
||||
}
|
||||
}
|
||||
if (flag) {
|
||||
reject('该文件不存在:' + page);
|
||||
}
|
||||
});
|
||||
}
|
||||
//update-end-author:taoyan date:2022-6-8 for:解决老的vue2动态导入文件语法 vite不支持的问题
|
||||
|
||||
|
||||
/**
|
||||
* 跳转至积木报表的 预览页面
|
||||
* @param url
|
||||
* @param id
|
||||
* @param token
|
||||
*/
|
||||
export function goJmReportViewPage(url, id, token) {
|
||||
// update-begin--author:liaozhiyang---date:20230904---for:【QQYUN-6390】eval替换成new Function,解决build警告
|
||||
// URL支持{{ window.xxx }}占位符变量
|
||||
url = url.replace(/{{([^}]+)?}}/g, (_s1, s2) => _eval(s2))
|
||||
// update-end--author:liaozhiyang---date:20230904---for:【QQYUN-6390】eval替换成new Function,解决build警告
|
||||
if (url.includes('?')) {
|
||||
url += '&'
|
||||
} else {
|
||||
url += '?'
|
||||
}
|
||||
url += `id=${id}`
|
||||
url += `&token=${token}`
|
||||
window.open(url)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取随机颜色
|
||||
*/
|
||||
export function getRandomColor(index?) {
|
||||
|
||||
const colors = [
|
||||
'rgb(100, 181, 246)',
|
||||
'rgb(77, 182, 172)',
|
||||
'rgb(255, 183, 77)',
|
||||
'rgb(229, 115, 115)',
|
||||
'rgb(149, 117, 205)',
|
||||
'rgb(161, 136, 127)',
|
||||
'rgb(144, 164, 174)',
|
||||
'rgb(77, 208, 225)',
|
||||
'rgb(129, 199, 132)',
|
||||
'rgb(255, 138, 101)',
|
||||
'rgb(133, 202, 205)',
|
||||
'rgb(167, 214, 118)',
|
||||
'rgb(254, 225, 89)',
|
||||
'rgb(251, 199, 142)',
|
||||
'rgb(239, 145, 139)',
|
||||
'rgb(169, 181, 255)',
|
||||
'rgb(231, 218, 202)',
|
||||
'rgb(252, 128, 58)',
|
||||
'rgb(254, 161, 172)',
|
||||
'rgb(194, 163, 205)',
|
||||
];
|
||||
return index && index < 19 ? colors[index] : colors[Math.floor((Math.random()*(colors.length-1)))];
|
||||
}
|
||||
|
||||
export function getRefPromise(componentRef) {
|
||||
return new Promise((resolve) => {
|
||||
(function next() {
|
||||
const ref = componentRef.value;
|
||||
if (ref) {
|
||||
resolve(ref);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
next();
|
||||
}, 100);
|
||||
}
|
||||
})();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 2023-09-04
|
||||
* liaozhiyang
|
||||
* 用new Function替换eval
|
||||
*/
|
||||
export function _eval(str: string) {
|
||||
return new Function(`return ${str}`)();
|
||||
}
|
||||
|
||||
/**
|
||||
* 2024-04-30
|
||||
* liaozhiyang
|
||||
* 通过时间或者时间戳获取对应antd的年、月、周、季度。
|
||||
*/
|
||||
export function getWeekMonthQuarterYear(date) {
|
||||
// 获取 ISO 周数的函数
|
||||
const getISOWeek = (date) => {
|
||||
const jan4 = new Date(date.getFullYear(), 0, 4);
|
||||
const oneDay = 86400000; // 一天的毫秒数
|
||||
return Math.ceil(((date - jan4.getTime()) / oneDay + jan4.getDay() + 1) / 7);
|
||||
};
|
||||
// 将时间戳转换为日期对象
|
||||
const dateObj = new Date(date);
|
||||
// 计算周
|
||||
const week = getISOWeek(dateObj);
|
||||
// 计算月
|
||||
const month = dateObj.getMonth() + 1; // 月份是从0开始的,所以要加1
|
||||
// 计算季度
|
||||
const quarter = Math.floor(dateObj.getMonth() / 3) + 1;
|
||||
// 计算年
|
||||
const year = dateObj.getFullYear();
|
||||
return {
|
||||
year: `${year}`,
|
||||
month: `${year}-${month.toString().padStart(2, '0')}`,
|
||||
week: `${year}-${week}周`,
|
||||
quarter: `${year}-Q${quarter}`,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 2024-05-17
|
||||
* liaozhiyang
|
||||
* 设置挂载的modal元素有可能会有多个,需要找到对应的。
|
||||
*/
|
||||
export const setPopContainer = (node, selector) => {
|
||||
if (typeof selector === 'string') {
|
||||
const targetEles = Array.from(document.querySelectorAll(selector));
|
||||
if (targetEles.length > 1) {
|
||||
const retrospect = (node, elems) => {
|
||||
let ele = node.parentNode;
|
||||
while (ele) {
|
||||
const findParentNode = elems.find(item => item === ele);
|
||||
if (findParentNode) {
|
||||
ele = null;
|
||||
return findParentNode;
|
||||
} else {
|
||||
ele = ele.parentNode;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
const elem = retrospect(node, targetEles);
|
||||
if (elem) {
|
||||
return elem;
|
||||
} else {
|
||||
return document.querySelector(selector);
|
||||
}
|
||||
} else {
|
||||
return document.querySelector(selector);
|
||||
}
|
||||
} else {
|
||||
return selector;
|
||||
}
|
||||
};
|
||||
108
jeecgboot-vue3/src/utils/is.ts
Normal file
108
jeecgboot-vue3/src/utils/is.ts
Normal file
@ -0,0 +1,108 @@
|
||||
const toString = Object.prototype.toString;
|
||||
|
||||
export function is(val: unknown, type: string) {
|
||||
return toString.call(val) === `[object ${type}]`;
|
||||
}
|
||||
|
||||
export function isDef<T = unknown>(val?: T): val is T {
|
||||
return typeof val !== 'undefined';
|
||||
}
|
||||
|
||||
export function isUnDef<T = unknown>(val?: T): val is T {
|
||||
return !isDef(val);
|
||||
}
|
||||
|
||||
export function isObject(val: any): val is Record<any, any> {
|
||||
return val !== null && is(val, 'Object');
|
||||
}
|
||||
|
||||
export function isEmpty<T = unknown>(val: T): val is T {
|
||||
if (isArray(val) || isString(val)) {
|
||||
return val.length === 0;
|
||||
}
|
||||
|
||||
if (val instanceof Map || val instanceof Set) {
|
||||
return val.size === 0;
|
||||
}
|
||||
|
||||
if (isObject(val)) {
|
||||
return Object.keys(val).length === 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isDate(val: unknown): val is Date {
|
||||
return is(val, 'Date');
|
||||
}
|
||||
|
||||
export function isNull(val: unknown): val is null {
|
||||
return val === null;
|
||||
}
|
||||
|
||||
export function isNullAndUnDef(val: unknown): val is null | undefined {
|
||||
return isUnDef(val) && isNull(val);
|
||||
}
|
||||
|
||||
export function isNullOrUnDef(val: unknown): val is null | undefined {
|
||||
return isUnDef(val) || isNull(val);
|
||||
}
|
||||
|
||||
export function isNumber(val: unknown): val is number {
|
||||
return is(val, 'Number');
|
||||
}
|
||||
|
||||
export function isPromise<T = any>(val: any): val is Promise<T> {
|
||||
// update-begin--author:sunjianlei---date:20211022---for: 不能既是 Promise 又是 Object --------
|
||||
return is(val, 'Promise') && isFunction(val.then) && isFunction(val.catch);
|
||||
// update-end--author:sunjianlei---date:20211022---for: 不能既是 Promise 又是 Object --------
|
||||
}
|
||||
|
||||
export function isString(val: unknown): val is string {
|
||||
return is(val, 'String');
|
||||
}
|
||||
|
||||
export function isJsonObjectString(val: string): val is string {
|
||||
if (!val) {
|
||||
return false;
|
||||
}
|
||||
return val.startsWith('{') && val.endsWith('}');
|
||||
}
|
||||
|
||||
export function isFunction(val: unknown): val is Function {
|
||||
return typeof val === 'function';
|
||||
}
|
||||
|
||||
export function isBoolean(val: unknown): val is boolean {
|
||||
return is(val, 'Boolean');
|
||||
}
|
||||
|
||||
export function isRegExp(val: unknown): val is RegExp {
|
||||
return is(val, 'RegExp');
|
||||
}
|
||||
|
||||
export function isArray(val: any): val is Array<any> {
|
||||
return val && Array.isArray(val);
|
||||
}
|
||||
|
||||
export function isWindow(val: any): val is Window {
|
||||
return typeof window !== 'undefined' && is(val, 'Window');
|
||||
}
|
||||
|
||||
export function isElement(val: unknown): val is Element {
|
||||
return isObject(val) && !!val.tagName;
|
||||
}
|
||||
|
||||
export function isMap(val: unknown): val is Map<any, any> {
|
||||
return is(val, 'Map');
|
||||
}
|
||||
|
||||
export const isServer = typeof window === 'undefined';
|
||||
|
||||
export const isClient = !isServer;
|
||||
|
||||
export function isUrl(path: string): boolean {
|
||||
const reg =
|
||||
/(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/;
|
||||
return reg.test(path);
|
||||
}
|
||||
51
jeecgboot-vue3/src/utils/lib/echarts.ts
Normal file
51
jeecgboot-vue3/src/utils/lib/echarts.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import * as echarts from 'echarts/core';
|
||||
|
||||
import { BarChart, LineChart, PieChart, MapChart, PictorialBarChart, RadarChart } from 'echarts/charts';
|
||||
|
||||
import {
|
||||
TitleComponent,
|
||||
TooltipComponent,
|
||||
GridComponent,
|
||||
PolarComponent,
|
||||
AriaComponent,
|
||||
ParallelComponent,
|
||||
LegendComponent,
|
||||
RadarComponent,
|
||||
ToolboxComponent,
|
||||
DataZoomComponent,
|
||||
VisualMapComponent,
|
||||
TimelineComponent,
|
||||
CalendarComponent,
|
||||
GraphicComponent,
|
||||
} from 'echarts/components';
|
||||
|
||||
// TODO 如果想换成SVG渲染,就导出SVGRenderer,
|
||||
// 并且放到 echarts.use 里,注释掉 CanvasRenderer
|
||||
import { /*SVGRenderer*/ CanvasRenderer } from 'echarts/renderers';
|
||||
|
||||
echarts.use([
|
||||
LegendComponent,
|
||||
TitleComponent,
|
||||
TooltipComponent,
|
||||
GridComponent,
|
||||
PolarComponent,
|
||||
AriaComponent,
|
||||
ParallelComponent,
|
||||
BarChart,
|
||||
LineChart,
|
||||
PieChart,
|
||||
MapChart,
|
||||
RadarChart,
|
||||
// TODO 因为要兼容Online图表自适应打印,所以改成 CanvasRenderer,可能会模糊
|
||||
CanvasRenderer,
|
||||
PictorialBarChart,
|
||||
RadarComponent,
|
||||
ToolboxComponent,
|
||||
DataZoomComponent,
|
||||
VisualMapComponent,
|
||||
TimelineComponent,
|
||||
CalendarComponent,
|
||||
GraphicComponent,
|
||||
]);
|
||||
|
||||
export default echarts;
|
||||
9
jeecgboot-vue3/src/utils/log.ts
Normal file
9
jeecgboot-vue3/src/utils/log.ts
Normal file
@ -0,0 +1,9 @@
|
||||
const projectName = import.meta.env.VITE_GLOB_APP_TITLE;
|
||||
|
||||
export function warn(message: string) {
|
||||
console.warn(`[${projectName} warn]:${message}`);
|
||||
}
|
||||
|
||||
export function error(message: string) {
|
||||
throw new Error(`[${projectName} error]:${message}`);
|
||||
}
|
||||
101
jeecgboot-vue3/src/utils/mitt.ts
Normal file
101
jeecgboot-vue3/src/utils/mitt.ts
Normal file
@ -0,0 +1,101 @@
|
||||
/**
|
||||
* copy to https://github.com/developit/mitt
|
||||
* Expand clear method
|
||||
*/
|
||||
|
||||
export type EventType = string | symbol;
|
||||
|
||||
// An event handler can take an optional event argument
|
||||
// and should not return a value
|
||||
export type Handler<T = any> = (event?: T) => void;
|
||||
export type WildcardHandler = (type: EventType, event?: any) => void;
|
||||
|
||||
// An array of all currently registered event handlers for a type
|
||||
export type EventHandlerList = Array<Handler>;
|
||||
export type WildCardEventHandlerList = Array<WildcardHandler>;
|
||||
|
||||
// A map of event types and their corresponding event handlers.
|
||||
export type EventHandlerMap = Map<EventType, EventHandlerList | WildCardEventHandlerList>;
|
||||
|
||||
export interface Emitter {
|
||||
all: EventHandlerMap;
|
||||
|
||||
on<T = any>(type: EventType, handler: Handler<T>): void;
|
||||
on(type: '*', handler: WildcardHandler): void;
|
||||
|
||||
off<T = any>(type: EventType, handler: Handler<T>): void;
|
||||
off(type: '*', handler: WildcardHandler): void;
|
||||
|
||||
emit<T = any>(type: EventType, event?: T): void;
|
||||
emit(type: '*', event?: any): void;
|
||||
clear(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mitt: Tiny (~200b) functional event emitter / pubsub.
|
||||
* @name mitt
|
||||
* @returns {Mitt}
|
||||
*/
|
||||
export default function mitt(all?: EventHandlerMap): Emitter {
|
||||
all = all || new Map();
|
||||
|
||||
return {
|
||||
/**
|
||||
* A Map of event names to registered handler functions.
|
||||
*/
|
||||
all,
|
||||
|
||||
/**
|
||||
* Register an event handler for the given type.
|
||||
* @param {string|symbol} type Type of event to listen for, or `"*"` for all events
|
||||
* @param {Function} handler Function to call in response to given event
|
||||
* @memberOf mitt
|
||||
*/
|
||||
on<T = any>(type: EventType, handler: Handler<T>) {
|
||||
const handlers = all?.get(type);
|
||||
const added = handlers && handlers.push(handler);
|
||||
if (!added) {
|
||||
all?.set(type, [handler]);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove an event handler for the given type.
|
||||
* @param {string|symbol} type Type of event to unregister `handler` from, or `"*"`
|
||||
* @param {Function} handler Handler function to remove
|
||||
* @memberOf mitt
|
||||
*/
|
||||
off<T = any>(type: EventType, handler: Handler<T>) {
|
||||
const handlers = all?.get(type);
|
||||
if (handlers) {
|
||||
handlers.splice(handlers.indexOf(handler) >>> 0, 1);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Invoke all handlers for the given type.
|
||||
* If present, `"*"` handlers are invoked after type-matched handlers.
|
||||
*
|
||||
* Note: Manually firing "*" handlers is not supported.
|
||||
*
|
||||
* @param {string|symbol} type The event type to invoke
|
||||
* @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler
|
||||
* @memberOf mitt
|
||||
*/
|
||||
emit<T = any>(type: EventType, evt: T) {
|
||||
((all?.get(type) || []) as EventHandlerList).slice().map((handler) => {
|
||||
handler(evt);
|
||||
});
|
||||
((all?.get('*') || []) as WildCardEventHandlerList).slice().map((handler) => {
|
||||
handler(type, evt);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear all
|
||||
*/
|
||||
clear() {
|
||||
this.all.clear();
|
||||
},
|
||||
};
|
||||
}
|
||||
19
jeecgboot-vue3/src/utils/monorepo/dynamicRouter.ts
Normal file
19
jeecgboot-vue3/src/utils/monorepo/dynamicRouter.ts
Normal file
@ -0,0 +1,19 @@
|
||||
export type DynamicViewsRecord = Record<string, () => Promise<Recordable>>;
|
||||
|
||||
/** 已注册模块的动态页面 */
|
||||
export const packageViews: DynamicViewsRecord = {};
|
||||
|
||||
/**
|
||||
* 注册动态路由页面
|
||||
* @param getViews 获取该模块下所有页面的方法
|
||||
*/
|
||||
export function registerDynamicRouter(getViews: () => DynamicViewsRecord) {
|
||||
if (typeof getViews === 'function') {
|
||||
let dynamicViews = getViews();
|
||||
Object.keys(dynamicViews).forEach((key) => {
|
||||
// 处理动态页面的key,使其可以让路由识别
|
||||
let newKey = key.replace('./src/views', '../../views');
|
||||
packageViews[newKey] = dynamicViews[key];
|
||||
});
|
||||
}
|
||||
}
|
||||
47
jeecgboot-vue3/src/utils/monorepo/registerPackages.ts
Normal file
47
jeecgboot-vue3/src/utils/monorepo/registerPackages.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import type { App } from 'vue';
|
||||
import { warn } from '/@/utils/log';
|
||||
import { registerDynamicRouter } from '/@/utils/monorepo/dynamicRouter';
|
||||
// 引入模块
|
||||
import PACKAGE_TEST_JEECG_ONLINE from '@jeecg/online';
|
||||
|
||||
export function registerPackages(app: App) {
|
||||
use(app, PACKAGE_TEST_JEECG_ONLINE);
|
||||
}
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
const installOptions = {
|
||||
baseImport,
|
||||
};
|
||||
|
||||
/** 注册模块 */
|
||||
function use(app: App, pkg) {
|
||||
app.use(pkg, installOptions);
|
||||
registerDynamicRouter(pkg.getViews);
|
||||
}
|
||||
|
||||
// 模块里可使用的import
|
||||
const importGlobs = [import.meta.glob('../../utils/**/*.{ts,js,tsx}'), import.meta.glob('../../hooks/**/*.{ts,js,tsx}')];
|
||||
|
||||
/**
|
||||
* 基础项目导包
|
||||
* 目前支持导入如下
|
||||
* /@/utils/**
|
||||
* /@/hooks/**
|
||||
*
|
||||
* @param path 文件路径,ts无需输入后缀名。如:/@/utils/common/compUtils
|
||||
*/
|
||||
async function baseImport(path: string) {
|
||||
if (path) {
|
||||
// 将 /@/ 替换成 ../../
|
||||
path = path.replace(/^\/@\//, '../../');
|
||||
for (const glob of importGlobs) {
|
||||
for (const key of Object.keys(glob)) {
|
||||
if (path === key || `${path}.ts` === key || `${path}.tsx` === key) {
|
||||
return glob[key]();
|
||||
}
|
||||
}
|
||||
}
|
||||
warn(`引入失败:${path} 不存在`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
35
jeecgboot-vue3/src/utils/propTypes.ts
Normal file
35
jeecgboot-vue3/src/utils/propTypes.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { CSSProperties, VNodeChild } from 'vue';
|
||||
import { createTypes, VueTypeValidableDef, VueTypesInterface, toValidableType } from 'vue-types';
|
||||
|
||||
export type VueNode = VNodeChild | JSX.Element;
|
||||
|
||||
type PropTypes = VueTypesInterface & {
|
||||
readonly style: VueTypeValidableDef<CSSProperties>;
|
||||
readonly VNodeChild: VueTypeValidableDef<VueNode>;
|
||||
// readonly trueBool: VueTypeValidableDef<boolean>;
|
||||
};
|
||||
const newPropTypes = createTypes({
|
||||
func: undefined,
|
||||
bool: undefined,
|
||||
string: undefined,
|
||||
number: undefined,
|
||||
object: undefined,
|
||||
integer: undefined,
|
||||
}) as PropTypes;
|
||||
|
||||
// 从 vue-types v5.0 开始,extend()方法已经废弃,当前已改为官方推荐的ES6+方法 https://dwightjack.github.io/vue-types/advanced/extending-vue-types.html#the-extend-method
|
||||
class propTypes extends newPropTypes {
|
||||
// a native-like validator that supports the `.validable` method
|
||||
static get style() {
|
||||
return toValidableType('style', {
|
||||
type: [String, Object],
|
||||
});
|
||||
}
|
||||
|
||||
static get VNodeChild() {
|
||||
return toValidableType('VNodeChild', {
|
||||
type: undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
export { propTypes };
|
||||
185
jeecgboot-vue3/src/utils/props.ts
Normal file
185
jeecgboot-vue3/src/utils/props.ts
Normal file
@ -0,0 +1,185 @@
|
||||
// copy from element-plus
|
||||
|
||||
import { warn } from 'vue';
|
||||
import { isObject } from '@vue/shared';
|
||||
import { fromPairs } from 'lodash-es';
|
||||
import type { ExtractPropTypes, PropType } from 'vue';
|
||||
import type { Mutable } from './types';
|
||||
|
||||
const wrapperKey = Symbol();
|
||||
export type PropWrapper<T> = { [wrapperKey]: T };
|
||||
|
||||
export const propKey = Symbol();
|
||||
|
||||
type ResolveProp<T> = ExtractPropTypes<{
|
||||
key: { type: T; required: true };
|
||||
}>['key'];
|
||||
type ResolvePropType<T> = ResolveProp<T> extends { type: infer V } ? V : ResolveProp<T>;
|
||||
type ResolvePropTypeWithReadonly<T> = Readonly<T> extends Readonly<Array<infer A>>
|
||||
? ResolvePropType<A[]>
|
||||
: ResolvePropType<T>;
|
||||
|
||||
type IfUnknown<T, V> = [unknown] extends [T] ? V : T;
|
||||
|
||||
export type BuildPropOption<T, D extends BuildPropType<T, V, C>, R, V, C> = {
|
||||
type?: T;
|
||||
values?: readonly V[];
|
||||
required?: R;
|
||||
default?: R extends true
|
||||
? never
|
||||
: D extends Record<string, unknown> | Array<any>
|
||||
? () => D
|
||||
: (() => D) | D;
|
||||
validator?: ((val: any) => val is C) | ((val: any) => boolean);
|
||||
};
|
||||
|
||||
type _BuildPropType<T, V, C> =
|
||||
| (T extends PropWrapper<unknown>
|
||||
? T[typeof wrapperKey]
|
||||
: [V] extends [never]
|
||||
? ResolvePropTypeWithReadonly<T>
|
||||
: never)
|
||||
| V
|
||||
| C;
|
||||
export type BuildPropType<T, V, C> = _BuildPropType<
|
||||
IfUnknown<T, never>,
|
||||
IfUnknown<V, never>,
|
||||
IfUnknown<C, never>
|
||||
>;
|
||||
|
||||
type _BuildPropDefault<T, D> = [T] extends [
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
Record<string, unknown> | Array<any> | Function,
|
||||
]
|
||||
? D
|
||||
: D extends () => T
|
||||
? ReturnType<D>
|
||||
: D;
|
||||
|
||||
export type BuildPropDefault<T, D, R> = R extends true
|
||||
? { readonly default?: undefined }
|
||||
: {
|
||||
readonly default: Exclude<D, undefined> extends never
|
||||
? undefined
|
||||
: Exclude<_BuildPropDefault<T, D>, undefined>;
|
||||
};
|
||||
export type BuildPropReturn<T, D, R, V, C> = {
|
||||
readonly type: PropType<BuildPropType<T, V, C>>;
|
||||
readonly required: IfUnknown<R, false>;
|
||||
readonly validator: ((val: unknown) => boolean) | undefined;
|
||||
[propKey]: true;
|
||||
} & BuildPropDefault<BuildPropType<T, V, C>, IfUnknown<D, never>, IfUnknown<R, false>>;
|
||||
|
||||
/**
|
||||
* @description Build prop. It can better optimize prop types
|
||||
* @description 生成 prop,能更好地优化类型
|
||||
* @example
|
||||
// limited options
|
||||
// the type will be PropType<'light' | 'dark'>
|
||||
buildProp({
|
||||
type: String,
|
||||
values: ['light', 'dark'],
|
||||
} as const)
|
||||
* @example
|
||||
// limited options and other types
|
||||
// the type will be PropType<'small' | 'medium' | number>
|
||||
buildProp({
|
||||
type: [String, Number],
|
||||
values: ['small', 'medium'],
|
||||
validator: (val: unknown): val is number => typeof val === 'number',
|
||||
} as const)
|
||||
@link see more: https://github.com/element-plus/element-plus/pull/3341
|
||||
*/
|
||||
export function buildProp<
|
||||
T = never,
|
||||
D extends BuildPropType<T, V, C> = never,
|
||||
R extends boolean = false,
|
||||
V = never,
|
||||
C = never,
|
||||
>(option: BuildPropOption<T, D, R, V, C>, key?: string): BuildPropReturn<T, D, R, V, C> {
|
||||
// filter native prop type and nested prop, e.g `null`, `undefined` (from `buildProps`)
|
||||
if (!isObject(option) || !!option[propKey]) return option as any;
|
||||
|
||||
const { values, required, default: defaultValue, type, validator } = option;
|
||||
|
||||
const _validator =
|
||||
values || validator
|
||||
? (val: unknown) => {
|
||||
let valid = false;
|
||||
let allowedValues: unknown[] = [];
|
||||
|
||||
if (values) {
|
||||
allowedValues = [...values, defaultValue];
|
||||
valid ||= allowedValues.includes(val);
|
||||
}
|
||||
if (validator) valid ||= validator(val);
|
||||
|
||||
if (!valid && allowedValues.length > 0) {
|
||||
const allowValuesText = [...new Set(allowedValues)]
|
||||
.map((value) => JSON.stringify(value))
|
||||
.join(', ');
|
||||
warn(
|
||||
`Invalid prop: validation failed${
|
||||
key ? ` for prop "${key}"` : ''
|
||||
}. Expected one of [${allowValuesText}], got value ${JSON.stringify(val)}.`,
|
||||
);
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
type:
|
||||
typeof type === 'object' && Object.getOwnPropertySymbols(type).includes(wrapperKey)
|
||||
? type[wrapperKey]
|
||||
: type,
|
||||
required: !!required,
|
||||
default: defaultValue,
|
||||
validator: _validator,
|
||||
[propKey]: true,
|
||||
} as unknown as BuildPropReturn<T, D, R, V, C>;
|
||||
}
|
||||
|
||||
type NativePropType = [((...args: any) => any) | { new (...args: any): any } | undefined | null];
|
||||
|
||||
export const buildProps = <
|
||||
O extends {
|
||||
[K in keyof O]: O[K] extends BuildPropReturn<any, any, any, any, any>
|
||||
? O[K]
|
||||
: [O[K]] extends NativePropType
|
||||
? O[K]
|
||||
: O[K] extends BuildPropOption<infer T, infer D, infer R, infer V, infer C>
|
||||
? D extends BuildPropType<T, V, C>
|
||||
? BuildPropOption<T, D, R, V, C>
|
||||
: never
|
||||
: never;
|
||||
},
|
||||
>(
|
||||
props: O,
|
||||
) =>
|
||||
fromPairs(
|
||||
Object.entries(props).map(([key, option]) => [key, buildProp(option as any, key)]),
|
||||
) as unknown as {
|
||||
[K in keyof O]: O[K] extends { [propKey]: boolean }
|
||||
? O[K]
|
||||
: [O[K]] extends NativePropType
|
||||
? O[K]
|
||||
: O[K] extends BuildPropOption<
|
||||
infer T,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
infer _D,
|
||||
infer R,
|
||||
infer V,
|
||||
infer C
|
||||
>
|
||||
? BuildPropReturn<T, O[K]['default'], R, V, C>
|
||||
: never;
|
||||
};
|
||||
|
||||
export const definePropType = <T>(val: any) => ({ [wrapperKey]: val } as PropWrapper<T>);
|
||||
|
||||
export const keyOf = <T>(arr: T) => Object.keys(arr) as Array<keyof T>;
|
||||
export const mutable = <T extends readonly any[] | Record<string, unknown>>(val: T) =>
|
||||
val as Mutable<typeof val>;
|
||||
|
||||
export const componentSize = ['large', 'medium', 'small', 'mini'] as const;
|
||||
42
jeecgboot-vue3/src/utils/types.ts
Normal file
42
jeecgboot-vue3/src/utils/types.ts
Normal file
@ -0,0 +1,42 @@
|
||||
// copy from element-plus
|
||||
|
||||
import type { CSSProperties, Plugin } from 'vue';
|
||||
|
||||
type OptionalKeys<T extends Record<string, unknown>> = {
|
||||
[K in keyof T]: T extends Record<K, T[K]> ? never : K;
|
||||
}[keyof T];
|
||||
|
||||
type RequiredKeys<T extends Record<string, unknown>> = Exclude<keyof T, OptionalKeys<T>>;
|
||||
|
||||
type MonoArgEmitter<T, Keys extends keyof T> = <K extends Keys>(evt: K, arg?: T[K]) => void;
|
||||
|
||||
type BiArgEmitter<T, Keys extends keyof T> = <K extends Keys>(evt: K, arg: T[K]) => void;
|
||||
|
||||
export type EventEmitter<T extends Record<string, unknown>> = MonoArgEmitter<T, OptionalKeys<T>> &
|
||||
BiArgEmitter<T, RequiredKeys<T>>;
|
||||
|
||||
export type AnyFunction<T> = (...args: any[]) => T;
|
||||
|
||||
export type PartialReturnType<T extends (...args: unknown[]) => unknown> = Partial<ReturnType<T>>;
|
||||
|
||||
export type SFCWithInstall<T> = T & Plugin;
|
||||
|
||||
export type Nullable<T> = T | null;
|
||||
|
||||
export type RefElement = Nullable<HTMLElement>;
|
||||
|
||||
export type CustomizedHTMLElement<T> = HTMLElement & T;
|
||||
|
||||
export type Indexable<T> = {
|
||||
[key: string]: T;
|
||||
};
|
||||
|
||||
export type Hash<T> = Indexable<T>;
|
||||
|
||||
export type TimeoutHandle = ReturnType<typeof global.setTimeout>;
|
||||
|
||||
export type ComponentSize = 'large' | 'medium' | 'small' | 'mini';
|
||||
|
||||
export type StyleValue = string | CSSProperties | Array<StyleValue>;
|
||||
|
||||
export type Mutable<T> = { -readonly [P in keyof T]: T[P] };
|
||||
28
jeecgboot-vue3/src/utils/uuid.ts
Normal file
28
jeecgboot-vue3/src/utils/uuid.ts
Normal file
@ -0,0 +1,28 @@
|
||||
const hexList: string[] = [];
|
||||
for (let i = 0; i <= 15; i++) {
|
||||
hexList[i] = i.toString(16);
|
||||
}
|
||||
|
||||
export function buildUUID(): string {
|
||||
let uuid = '';
|
||||
for (let i = 1; i <= 36; i++) {
|
||||
if (i === 9 || i === 14 || i === 19 || i === 24) {
|
||||
uuid += '-';
|
||||
} else if (i === 15) {
|
||||
uuid += 4;
|
||||
} else if (i === 20) {
|
||||
uuid += hexList[(Math.random() * 4) | 8];
|
||||
} else {
|
||||
uuid += hexList[(Math.random() * 16) | 0];
|
||||
}
|
||||
}
|
||||
return uuid.replace(/-/g, '');
|
||||
}
|
||||
|
||||
let unique = 0;
|
||||
export function buildShortUUID(prefix = ''): string {
|
||||
const time = Date.now();
|
||||
const random = Math.floor(Math.random() * 1000000000);
|
||||
unique++;
|
||||
return prefix + '_' + random + unique + String(time);
|
||||
}
|
||||
Reference in New Issue
Block a user