init
This commit is contained in:
2025-06-27 17:42:38 +08:00
commit bd6402478b
5317 changed files with 785994 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
// 组织机构管理
export enum orgManageApi {
departManageList = 'system:depart-manage-list:manage',
departUserList = 'system:depart-user-list:user',
}
+82
View File
@@ -0,0 +1,82 @@
import { useUserStore } from '/@/store/modules/user';
export enum RoleCodes {
second_manager = 'second_manager',
third_manager = 'third_manager',
}
type CallBack = (params: any) => any;
/**
* @Description:二级管理员及三级管理员单位部门查询禁用控制
*/
export class FormDepartment {
private store: any;
private readonly userInfo: any;
constructor() {
this.store = useUserStore() || {};
this.userInfo = this.store?.getUserInfo || {};
}
//登录身份级别
getLevel() {
const roleCodes = this.userInfo?.roleCodes || '';
return roleCodes.includes(RoleCodes.second_manager)
? RoleCodes.second_manager
: roleCodes.includes(RoleCodes.third_manager)
? RoleCodes.third_manager
: '';
}
//单位是否禁用
secondSelectDisabled(): boolean {
const level = this.getLevel();
return level.includes(RoleCodes.second_manager) || level.includes(RoleCodes.third_manager);
}
secondSelectValue(callback: CallBack) {
if (this.secondSelectDisabled()) {
return callback(this.userInfo?.secondDepart);
}
}
//部门是否禁用
thirdSelectDisabled(): boolean {
return this.getLevel().includes(RoleCodes.third_manager);
}
thirdSelectValue(callback: CallBack) {
if (this.thirdSelectDisabled()) {
return callback(this.userInfo?.threeDepart);
}
}
}
/**
* @Description:二级管理员及三级管理员单位部门查询禁用控制
* @date 2023/8/28
* @param schema form的schema
* @param key id|orgCode
*/
export function useDepartment({ schema, key }) {
const auth = new FormDepartment();
const secondSelectValue = () =>
auth.secondSelectValue((data) => {
schema.defaultValue = data?.[key];
});
const secondSelectDisabled = auth.secondSelectDisabled();
const thirdSelectValue = () =>
auth.thirdSelectValue((data) => {
console.log('data', data);
schema.defaultValue = data?.[key];
});
const thirdSelectDisabled = auth.thirdSelectDisabled();
return {
auth,
secondSelectValue,
secondSelectDisabled,
thirdSelectValue,
thirdSelectDisabled,
};
}
+85
View File
@@ -0,0 +1,85 @@
import { BasicKeys, Persistent } from '/@/utils/cache/persistent';
import { CacheTypeEnum, LOGIN_INFO_KEY, TENANT_ID, TOKEN_KEY } from '/@/enums/cacheEnum';
import projectSetting from '/@/settings/projectSetting';
import { useUserStoreWithOut } from '/@/store/modules/user';
import { storeToRefs } from 'pinia';
import { unref } from 'vue';
const { permissionCacheType } = projectSetting;
const isLocal = permissionCacheType === CacheTypeEnum.LOCAL;
/**
* 获取token
*/
export function getToken() {
const userStore = useUserStoreWithOut();
const { token } = storeToRefs(userStore);
return unref(token) || getAuthCache<string>(TOKEN_KEY);
}
/**
* 获取登录信息
*/
export function getLoginBackInfo() {
return getAuthCache(LOGIN_INFO_KEY);
}
/**
* 获取租户id
*/
export function getTenantId() {
const userStore = useUserStoreWithOut();
const { tenantid } = storeToRefs(userStore);
return unref(tenantid) || 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, immediate = false) {
const fn = isLocal ? Persistent.setLocal : Persistent.setSession;
return fn(key, value, immediate);
}
/**
* 设置动态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);
}
+81
View File
@@ -0,0 +1,81 @@
import { useUserStore } from '/@/store/modules/user';
export enum RoleCodes {
second_manager = 'second_manager',
third_manager = 'third_manager',
}
type CallBack = (params: any) => any;
/**
* @Description:二级管理员及三级管理员单位部门查询禁用控制
*/
export class FormDepartment {
private store: any;
private readonly userInfo: any;
constructor() {
this.store = useUserStore() || {};
this.userInfo = this.store?.getUserInfo || {};
}
//登录身份级别
getLevel() {
const roleCodes = this.userInfo?.roleCodes || '';
return roleCodes.includes(RoleCodes.second_manager)
? RoleCodes.second_manager
: roleCodes.includes(RoleCodes.third_manager)
? RoleCodes.third_manager
: '';
}
//单位是否禁用
secondSelectDisabled(): boolean {
const level = this.getLevel();
return level.includes(RoleCodes.second_manager) || level.includes(RoleCodes.third_manager);
}
secondSelectValue(callback: CallBack) {
if (this.secondSelectDisabled()) {
return callback(this.userInfo?.secondDepart);
}
}
//部门是否禁用
thirdSelectDisabled(): boolean {
return this.getLevel().includes(RoleCodes.third_manager);
}
thirdSelectValue(callback: CallBack) {
if (this.thirdSelectDisabled()) {
return callback(this.userInfo?.depart);
}
}
}
/**
* @Description:二级管理员及三级管理员单位部门查询禁用控制
* @date 2023/8/28
* @param schema form的schema
* @param key id|orgCode
*/
export function useDepartment({ schema, key }) {
const auth = new FormDepartment();
const secondSelectValue = () =>
auth.secondSelectValue((data) => {
return (schema = data?.[key]);
});
const secondSelectDisabled = auth.secondSelectDisabled();
const thirdSelectValue = () =>
auth.thirdSelectValue((data) => {
return (schema = data?.[key]);
});
const thirdSelectDisabled = auth.thirdSelectDisabled();
return {
auth,
secondSelectValue,
secondSelectDisabled,
thirdSelectValue,
thirdSelectDisabled,
};
}
+1
View File
@@ -0,0 +1 @@
export const mapKey = '371e1dd32fe4cf91fdf7df3d157ca761';
+52
View 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((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
View 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;
}
}
+72
View File
@@ -0,0 +1,72 @@
import { createLocalStorage } from '/@/utils/cache';
import { Memory } from './memory';
import { DB_DICT_DATA_KEY } from '/@/enums/cacheEnum';
import { DEFAULT_CACHE_TIME } from '/@/settings/encryptionSetting';
import { toRaw } from 'vue';
import { useUserStoreWithOut } from '/@/store/modules/user';
const ls = createLocalStorage({ hasEncrypt: false });
const localMemory = new Memory(DEFAULT_CACHE_TIME);
export function initDictMemory() {
const localCache = ls.get(DB_DICT_DATA_KEY);
localCache && localMemory.resetCache(localCache);
localCache && initStore(localCache);
}
function initStore(cache: any) {
const userStore = useUserStoreWithOut();
const { setAllDictItems } = userStore;
setAllDictItems(cache);
}
export class Dict {
static getDict<T>(key = '') {
if (!key) {
return localMemory.getCache as Nullable<T>;
} else {
return (localMemory.get(key)?.value as Nullable<T>) || (localMemory.get(key) as Nullable<T>);
}
}
static setDict(key, value: object, immediate = false): void {
if (!key) {
localMemory.setCache(value);
} else {
localMemory.set(key, toRaw(value));
}
immediate && ls.set(DB_DICT_DATA_KEY, localMemory.getCache);
}
static store() {
ls.set(DB_DICT_DATA_KEY, localMemory.getCache);
}
static removeDict(key, immediate = false): void {
localMemory.remove(key);
immediate && ls.set(DB_DICT_DATA_KEY, localMemory.getCache);
}
static clearDict(immediate = false): void {
localMemory.clear();
immediate && ls.remove(DB_DICT_DATA_KEY);
}
}
function storageChange(e: any) {
const { key, newValue, oldValue } = e;
if (!key) {
Dict.clearDict();
return;
}
if (!!newValue && !!oldValue) {
if (DB_DICT_DATA_KEY === key) {
Dict.clearDict();
}
}
}
window.addEventListener('storage', storageChange);
+31
View File
@@ -0,0 +1,31 @@
import { getStorageShortName } from '/@/utils/env';
import { createStorage as create, CreateStorageParams } from './storageCache';
import { DEFAULT_CACHE_TIME, enableStorageEncryption } 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;
+7
View File
@@ -0,0 +1,7 @@
import { initPersistentMemory } from '/@/utils/cache/persistent';
import { initDictMemory } from '/@/utils/cache/dict';
export const initCache = () => {
initPersistentMemory();
initDictMemory();
};
+109
View File
@@ -0,0 +1,109 @@
import { FAKE_USER_INFO_KEY, LOGIN_INFO_KEY, PROJ_CFG_KEY, ROLES_KEY, TENANT_ID, TOKEN_KEY, USER_INFO_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() {
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, FAKE_USER_INFO_KEY, ROLES_KEY, TENANT_ID, LOGIN_INFO_KEY, PROJ_CFG_KEY]),
};
//update-end---author:liusq Date:20220108 for:不删除登录用户的租户id,其他缓存信息都清除----
}
}
+195
View File
@@ -0,0 +1,195 @@
import type { LockInfo, LoginInfo, UserInfo } from '/#/store';
import type { ProjectConfig } from '/#/config';
import type { RouteLocationNormalized } from 'vue-router';
import { createLocalStorage, createSessionStorage } from '/@/utils/cache';
import { Memory } from './memory';
import {
APP_LOCAL_CACHE_KEY,
APP_SESSION_CACHE_KEY,
CacheTypeEnum,
LOCK_INFO_KEY,
LOGIN_INFO_KEY,
MULTIPLE_TABS_KEY,
PROJ_CFG_KEY,
ROLES_KEY,
TENANT_ID,
TOKEN_KEY,
USER_INFO_KEY,
FAKE_USER_INFO_KEY,
SPECIAL_PATH,
SPECIAL_DOWN_PATH,
} from '/@/enums/cacheEnum';
import { DEFAULT_CACHE_TIME } from '/@/settings/encryptionSetting';
import { toRaw } from 'vue';
import { omit, pick } from 'lodash-es';
import projectSetting from '/@/settings/projectSetting';
import { useUserStoreWithOut } from '/@/store/modules/user';
const { permissionCacheType } = projectSetting;
const isLocal = permissionCacheType === CacheTypeEnum.LOCAL;
interface BasicStore {
[TOKEN_KEY]: string | number | null | undefined;
[USER_INFO_KEY]: UserInfo;
[USER_INFO_KEY]: UserInfo;
[FAKE_USER_INFO_KEY]: UserInfo;
[ROLES_KEY]: string[];
[LOCK_INFO_KEY]: LockInfo;
[PROJ_CFG_KEY]: ProjectConfig;
[MULTIPLE_TABS_KEY]: RouteLocationNormalized[];
[TENANT_ID]: string;
[LOGIN_INFO_KEY]: LoginInfo;
[SPECIAL_PATH]: string;
[SPECIAL_DOWN_PATH]: 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);
export function initPersistentMemory() {
const localCache = ls.get(APP_LOCAL_CACHE_KEY);
const sessionCache = ss.get(APP_SESSION_CACHE_KEY);
localCache &&
localMemory.resetCache({
...pick(localCache, [
TOKEN_KEY,
USER_INFO_KEY,
FAKE_USER_INFO_KEY,
ROLES_KEY,
LOGIN_INFO_KEY,
PROJ_CFG_KEY,
MULTIPLE_TABS_KEY,
TENANT_ID,
SPECIAL_PATH,
SPECIAL_DOWN_PATH,
]),
});
sessionCache &&
sessionMemory.resetCache({
...pick(sessionCache, [
TOKEN_KEY,
USER_INFO_KEY,
FAKE_USER_INFO_KEY,
ROLES_KEY,
LOGIN_INFO_KEY,
PROJ_CFG_KEY,
MULTIPLE_TABS_KEY,
TENANT_ID,
SPECIAL_PATH,
SPECIAL_DOWN_PATH,
]),
});
if (isLocal) {
localCache && initStore(localCache);
} else {
sessionCache && initStore(sessionCache);
}
}
function initStore(cache: any) {
const userStore = useUserStoreWithOut();
const { setToken, setTenant, setLoginInfo, setUserInfo, setRoleList, setFakeUserInfo, setSpecialPath, setSpecialDownPath } = userStore;
setToken(cache[TOKEN_KEY]?.value || '');
setTenant(cache[TENANT_ID]?.value || null);
setLoginInfo(cache[LOGIN_INFO_KEY]?.value || null);
setUserInfo(cache[USER_INFO_KEY]?.value || null);
setFakeUserInfo(cache[FAKE_USER_INFO_KEY]?.value || '');
setRoleList(cache[ROLES_KEY]?.value || []);
setSpecialPath(cache[SPECIAL_PATH]?.value || '');
setSpecialDownPath(cache[SPECIAL_DOWN_PATH]?.value || '');
}
export class Persistent {
static getLocal<T>(key: LocalKeys) {
//update-begin---author:scott ---date:2022-10-27 fortoken过期退出重新登录,online菜单还是提示token过期----------
const globalCache = ls.get(APP_LOCAL_CACHE_KEY);
if (globalCache) {
localMemory.setCache(globalCache);
}
//update-end---author:scott ---date::2022-10-27 fortoken过期退出重新登录,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.remove(APP_LOCAL_CACHE_KEY);
}
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.remove(APP_LOCAL_CACHE_KEY);
}
static clearAll(immediate = false) {
this.clearLocal(immediate);
this.clearSession(immediate);
}
}
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, FAKE_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, FAKE_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);
+112
View 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
View 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
View 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)}`;
}
+572
View File
@@ -0,0 +1,572 @@
import { useGlobSetting } from '/@/hooks/setting';
import { cloneDeep, merge, random } from 'lodash-es';
import { isArray } from '/@/utils/is';
import { FormSchema } from '/@/components/Form';
import defaultImg from '/@/assets/images/defaultImg.png';
import defaultMale from '/@/assets/images/default_male.png';
import defaultFemale from '/@/assets/images/default_female.png';
import defaultExpert from '/@/assets/images/default_expert.png';
const globSetting = useGlobSetting();
const baseApiUrl = globSetting.domainUrl;
/**
* 获取文件服务访问路径
* @param fileUrl 文件路径
* @param prefix(默认http) 文件路径前缀 http/https
*/
export const getFileAccessHttpUrl = (fileUrl, prefix = 'http') => {
let result = fileUrl;
if (result && result.indexOf(',') === result.length - 1) {
result = result.substring(0, result.length - 1);
}
fileUrl = result;
try {
if (fileUrl && fileUrl.length > 0 && !fileUrl.startsWith(prefix)) {
//判断是否是数组格式
const isArray = fileUrl.indexOf('[') != -1;
if (!isArray) {
const prefix = `${baseApiUrl}/file/show/`;
// 判断是否已包含前缀
if (!fileUrl.startsWith(prefix)) {
result = `${prefix}${fileUrl}`;
}
}
}
} catch (err) {
console.log(err);
}
if (!result) result = '';
return result;
};
export const getFileAccessHttpUrlDown = (fileUrl, prefix = 'http') => {
let result = fileUrl;
if (result && result.indexOf(',') === result.length - 1) {
result = result.substring(0, result.length - 1);
}
fileUrl = result;
try {
if (fileUrl && fileUrl.length > 0 && !fileUrl.startsWith(prefix)) {
//判断是否是数组格式
const isArray = fileUrl.indexOf('[') != -1;
if (!isArray) {
const prefix = `${baseApiUrl}/file/down/`;
// 判断是否已包含前缀
if (!fileUrl.startsWith(prefix)) {
result = `${prefix}${fileUrl}`;
}
}
}
} catch (err) {
console.log(err);
}
if (!result) result = '';
return result;
};
/**
* 不拼接直接下载
* @param fileUrl
* @param prefix
*/
export const downFile = (fileUrl) => {
let result = fileUrl;
try {
const prefix = `${baseApiUrl}`;
// 判断是否已包含前缀
result = `${prefix}${result}`;
} catch (err) {}
if (!result) result = 'asdkadwkhq';
return result;
};
/**
* 触发 window.resize
*/
export function triggerWindowResizeEvent() {
const event: any = document.createEvent('HTMLEvents');
event.initEvent('resize', true, true);
event.eventType = 'message';
window.dispatchEvent(event);
}
/**
* 获取随机数
* @param length 数字位数
*/
export const getRandom = (length = 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++) {
const 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[]) {
const totals: any = { _row: '合计', _index: '合计' };
fieldKeys.forEach((key) => {
totals[key] = tableData.reduce((prev, next) => {
prev += next[key];
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 () {
const 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;
}
/**
* 获取事件冒泡路径,兼容 IE11EdgeChromeFirefoxSafari
* 目前使用的地方:JVxeTable Span模式
*/
export function getEventPath(event) {
const target = event.target;
const path = (event.composedPath && event.composedPath()) || event.path;
if (path != null) {
return path.indexOf(window) < 0 ? path.concat(window) : path;
}
if (target === window) {
return [window];
}
const 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 (const 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 (const 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++) {
const item = treeList[i];
if (fn(item, i, treeList)) {
return item;
}
const children = item[childrenKey];
if (isArray(children)) {
const findResult = findTree(children, fn, childrenKey);
if (findResult) {
return findResult;
}
}
}
return null;
}
/**
* @description 根据自定义规则筛选树结构
* @param treeList
* @param rule 判断规则
* @param fieldNames 替换 treeNode 中 label,value,children 字段为 treeList 中对应的字段
*/
export function filterTree<T>(treeList: T[], rule: (item: T) => boolean, fieldNames: { children: string } = { children: 'children' }): T[] {
if (!Array.isArray(treeList) || treeList?.length == 0) {
return [];
}
let tree = cloneDeep(treeList);
let { children: childrenField } = fieldNames;
return tree.filter((item) => {
if (item[childrenField] && item[childrenField].length > 0) {
item[childrenField] = filterTree(item[childrenField], rule, fieldNames);
return true;
} else {
return rule(item);
}
});
}
/** 获取 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) {
//是否是聚合路由
const alwaysShow = menuTreeItem.alwaysShow;
if (alwaysShow) {
return false;
}
if (!menuTreeItem.children) {
return false;
}
return menuTreeItem.children?.find((item) => item.hideMenu == false) != null;
}
export function getFamaleDefaultImage(sex?: string) {
if (sex === '2') {
return defaultMale;
} else {
return defaultFemale;
}
}
export function getExpertDefaultImage() {
return defaultExpert;
}
export function getDefaultImage() {
// return (
// 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMIAAADDCAYAAADQvc6UAAA' +
// 'BRWlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGASSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8LAwSDCIMo' +
// 'gwMCcmFxc4BgQ4ANUwgCjUcG3awyMIPqyLsis7PPOq3QdDFcvjV3jOD1boQVTPQrgSkktTgb' +
// 'Sf4A4LbmgqISBgTEFyFYuLykAsTuAbJEioKOA7DkgdjqEvQHEToKwj4DVhAQ5A9k3gGyB5Ix' +
// 'EoBmML4BsnSQk8XQkNtReEOBxcfXxUQg1Mjc0dyHgXNJBSWpFCYh2zi+oLMpMzyhRcASGUqq' +
// 'CZ16yno6CkYGRAQMDKMwhqj/fAIcloxgHQqxAjIHBEugw5sUIsSQpBobtQPdLciLEVJYzMPB' +
// 'HMDBsayhILEqEO4DxG0txmrERhM29nYGBddr//5/DGRjYNRkY/l7////39v///y4Dmn+LgeH' +
// 'ANwDrkl1AuO+pmgAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAA' +
// 'BAAAAwqADAAQAAAABAAAAwwAAAAD9b/HnAAAHlklEQVR4Ae3dP3PTWBSGcbGzM6GCKqlIBRV' +
// '0dHRJFarQ0eUT8LH4BnRU0NHR0UEFVdIlFRV7TzRksomPY8uykTk/zewQfKw/9znv4yvJynL' +
// 'v4uLiV2dBoDiBf4qP3/ARuCRABEFAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBEl' +
// 'AoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASK' +
// 'IAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQ' +
// 'ZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJ' +
// 'mBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQ' +
// 'aASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghg' +
// 'gQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAE' +
// 'EegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghgg0Aj8i0JO4Oz' +
// 'srPv69Wv+hi2qPHr0qNvf39+iI97soRIh4f3z58/u7du3SXX7Xt7Z2enevHmzfQe+oSN2apS' +
// 'APj09TSrb+XKI/f379+08+A0cNRE2ANkupk+ACNPvkSPcAAEibACyXUyfABGm3yNHuAECRNg' +
// 'AZLuYPgEirKlHu7u7XdyytGwHAd8jjNyng4OD7vnz51dbPT8/7z58+NB9+/bt6jU/TI+AGWH' +
// 'Enrx48eJ/EsSmHzx40L18+fLyzxF3ZVMjEyDCiEDjMYZZS5wiPXnyZFbJaxMhQIQRGzHvWR7' +
// 'XCyOCXsOmiDAi1HmPMMQjDpbpEiDCiL358eNHurW/5SnWdIBbXiDCiA38/Pnzrce2YyZ4//5' +
// '9F3ePLNMl4PbpiL2J0L979+7yDtHDhw8vtzzvdGnEXdvUigSIsCLAWavHp/+qM0BcXMd/q25' +
// 'n1vF57TYBp0a3mUzilePj4+7k5KSLb6gt6ydAhPUzXnoPR0dHl79WGTNCfBnn1uvSCJdegQh' +
// 'LI1vvCk+fPu2ePXt2tZOYEV6/fn31dz+shwAR1sP1cqvLntbEN9MxA9xcYjsxS1jWR4AIa2I' +
// 'bzx0tc44fYX/16lV6NDFLXH+YL32jwiACRBiEbf5KcXoTIsQSpzXx4N28Ja4BQoK7rgXiydb' +
// 'Hjx/P25TaQAJEGAguWy0+2Q8PD6/Ki4R8EVl+bzBOnZY95fq9rj9zAkTI2SxdidBHqG9+skd' +
// 'w43borCXO/ZcJdraPWdv22uIEiLA4q7nvvCug8WTqzQveOH26fodo7g6uFe/a17W3+nFBAkR' +
// 'YENRdb1vkkz1CH9cPsVy/jrhr27PqMYvENYNlHAIesRiBYwRy0V+8iXP8+/fvX11Mr7L7ECu' +
// 'eb/r48eMqm7FuI2BGWDEG8cm+7G3NEOfmdcTQw4h9/55lhm7DekRYKQPZF2ArbXTAyu4kDYB' +
// '2YxUzwg0gi/41ztHnfQG26HbGel/crVrm7tNY+/1btkOEAZ2M05r4FB7r9GbAIdxaZYrHdOs' +
// 'gJ/wCEQY0J74TmOKnbxxT9n3FgGGWWsVdowHtjt9Nnvf7yQM2aZU/TIAIAxrw6dOnAWtZZco' +
// 'EnBpNuTuObWMEiLAx1HY0ZQJEmHJ3HNvGCBBhY6jtaMoEiJB0Z29vL6ls58vxPcO8/zfrdo5' +
// 'qvKO+d3Fx8Wu8zf1dW4p/cPzLly/dtv9Ts/EbcvGAHhHyfBIhZ6NSiIBTo0LNNtScABFyNiq' +
// 'FCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjY' +
// 'qhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI' +
// '2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABF' +
// 'yNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAA' +
// 'RcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021Jw' +
// 'AEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtS' +
// 'cABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTb' +
// 'UnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs0' +
// '21JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhEL' +
// 'NNtScABFyNiqFCBChULMNNSdAhJyNSiEC/wGgKKC4YMA4TAAAAABJRU5ErkJggg=='
// );
return defaultImg;
}
/**
* 提取年龄和性别
* @param idCard
* @param today
*/
export function extractAgeAndGender(idCard: string, today = new Date()) {
if (idCard.length !== 18) {
return { age: '', gender: '' };
}
let birthDateString = idCard.substring(6, 14);
let gender = parseInt(idCard.substring(16, 17), 10) % 2 === 0 ? '女' : '男';
let birthDate = new Date(
Number(birthDateString.substring(0, 4)),
parseInt(birthDateString.substring(4, 6), 10) - 1,
Number(birthDateString.substring(6, 8))
);
let age = today.getFullYear() - birthDate.getFullYear();
let m = today.getMonth() - birthDate.getMonth();
if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) age--;
return { age, gender };
}
/**
* @description 秒转小时
* */
export function convertSeconds(seconds: number) {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const remainingSeconds = seconds % 60;
return {
hours: hours,
minutes: minutes,
seconds: remainingSeconds,
};
}
export function getTimeStr(time: number) {
if (!time) return '';
const { minutes, seconds, hours } = convertSeconds(time);
if (hours) {
return `${hours}${minutes}${parseInt(seconds)}`;
}
if (seconds && minutes) {
return `${minutes}${parseInt(seconds)}`;
}
if (minutes) {
return minutes + '分';
}
if (seconds) {
return parseInt(seconds) + '秒';
}
}
export function isNull(val: string | boolean | number) {
if (typeof val == 'number') return true;
if (typeof val == undefined || val == null || val == '') {
return false;
}
return true;
}
export const nameAndPhone = [
{
name: '徐磊',
phone: '13681448856',
export: 'designer',
},
{
name: '陈天宇',
phone: '13591557014',
export: 'charge',
},
];
+213
View File
@@ -0,0 +1,213 @@
import { h } from 'vue';
import { Avatar, Tag, Tooltip } 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) {
const 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 = '';
const array = getDictItemsByCode(code) || [];
const obj = array.filter((item) => {
return item.value == v;
});
if (obj.length > 0) {
text = obj[0].text;
}
return isEmpty(text) || !renderTag ? h('span', text) : h(Tag, text);
},
/**
* 根据字典编码 渲染(多个)
* @param v 值
* @param code 字典编码
* @param renderTag 是否使用tag渲染
*/
renderDicts: (v, code, renderTag = false) => {
let text = '';
const array = getDictItemsByCode(code) || [];
// 将 v 用逗号分割成数组
const vArray = v.split(',');
// 遍历分割后的数组,处理每个元素
vArray.forEach((value, index) => {
const obj = array.filter((item) => {
return item.value == value.trim(); // 使用 trim() 方法去除空格
});
if (obj.length > 0) {
text += obj[0].text;
if (index < vArray.length - 1) {
text += ','; // 添加逗号分隔符,但不在最后一个元素后添加逗号
}
}
});
return isEmpty(text) || !renderTag ? h('span', text) : h(Tag, text);
},
/**
* 渲染图片
* @param text
*/
renderImage: ({ text }) => {
if (!text) {
//update-begin-author:taoyan date:2022-5-24 for: VUEN-1084 【vue3】online表单测试发现的新问题 41、生成的代码,树默认图大小未改
return h(
Avatar,
{ shape: 'square', size: 25 },
{
icon: () => h(Icon, { icon: 'ant-design:file-image-outlined', size: 25 }),
}
);
}
const avatarList = text.split(',');
return h(
'span',
avatarList.map((item) => {
return h(Avatar, {
src: getFileAccessHttpUrl(item),
shape: 'square',
size: 25,
style: { marginRight: '5px' },
});
})
);
//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 = '';
const 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);
},
renderCost: (text) => {
if (text == 0) {
return h('span', { style: { paddingRight: '16px' } }, 0);
}
const cost = text ? Number(text).toFixed(2) : '';
return h('span', { style: { paddingRight: '16px' } }, cost);
},
inputCost: (text) => {
const cost = text ? Number(text).toFixed(2) : '';
return cost;
},
/**
* 渲染富文本
*/
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) => {
const 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 };
+102
View File
@@ -0,0 +1,102 @@
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 = {};
const values = await new Promise((resolve, reject) => {
// 验证主表表单
validate()
.then(() => {
//update-begin---author:wangshuai ---date:20220507 for[VUEN-912]一对多用户组件(所有风格,单表和树没问题)保存报错------------
for (const data in formData) {
//如果该数据是数组
if (formData[data] instanceof Array) {
const 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 });
});
});
Object.assign(dataMap, { formValue: values });
// 验证所有子表的表单
const 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) => {
const tablesData: any = [];
let index = 0;
if (!cases || cases.length === 0) {
resolve(tablesData);
}
(function next() {
const 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;
const tabPane = getVmParentByName(vm.value, 'ATabPane');
if (tabPane) {
paneKey = tabPane.$.vnode.key;
// 自动跳转到该表格
if (autoJumpTab) {
const tabs = getVmParentByName(tabPane, 'Tabs');
tabs && tabs.setActiveKey && tabs.setActiveKey(paneKey);
}
}
// 出现未验证通过的表单,不再进行下一步校验,直接返回失败
reject({ error: VALIDATE_FAILED, index, paneKey, errMap });
}
});
})();
});
}
export function getVmParentByName(vm, name) {
const parent = vm.$parent;
if (parent && parent.$options) {
if (parent.$options.name === name) {
return parent;
} else {
const res = getVmParentByName(parent, name);
if (res) {
return res;
}
}
}
return null;
}
+31
View File
@@ -0,0 +1,31 @@
/**
* Independent time operation tool to facilitate subsequent switch to dayjs
*/
import dayjs from 'dayjs';
export const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';
export 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 function msToTime(duration: number) {
// eslint-disable-next-line prefer-const
let milliseconds: string | number = parseInt(duration % 1000);
let seconds: string | number = parseInt((duration / 1000) % 60);
let minutes: string | number = parseInt((duration / (1000 * 60)) % 60);
let hours: string | number = (duration / (1000 * 60 * 60)) % 24;
hours = hours < 1 ? 0 : parseInt(hours);
hours = hours < 10 ? '0' + hours : hours;
minutes = minutes < 10 ? '0' + minutes : minutes;
seconds = seconds < 10 ? '0' + seconds : seconds;
milliseconds = milliseconds < 100 ? (milliseconds < 10 ? '00' + milliseconds : '0' + milliseconds) : milliseconds;
return hours + ':' + minutes + ':' + seconds + '.' + milliseconds;
}
export const dateUtil = dayjs;
+30
View 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}`;
}
+156
View File
@@ -0,0 +1,156 @@
/**
* 字典 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) {
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) {
//优先从缓存中读取字典配置
if (getDictItemsByCode(dictCode)) {
let desformDictItems = getDictItemsByCode(dictCode).map((item) => ({
...item,
label: item.text,
}));
return desformDictItems;
}
//缓存中没有,就请求后台
return await ajaxGetDictItems(dictCode, params)
.then(({ success, result }) => {
if (success) {
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([]);
});
}
+89
View File
@@ -0,0 +1,89 @@
import { defHttp } from '/@/utils/http/axios';
import { useUserStoreWithOut } from '/@/store/modules/user';
import { storeToRefs } from 'pinia';
import { unref } from 'vue';
import { Dict } from '/@/utils/cache/dict';
/**
* 从缓存中获取字典配置
* @param code
*/
export const getDictItemsByCode = (code) => {
const userStore = useUserStoreWithOut();
const { dictItems } = storeToRefs(userStore);
const dictCache = unref(dictItems) || getDictCache();
if (dictCache && dictCache[code]) {
return dictCache[code];
}
};
/**
* 获取字典数组
* @param dictCode 字典Code
* @return List<Map>
*/
export const initDictOptions = (code) => {
//1.优先从缓存中读取字典配置
if (getDictItemsByCode(code)) {
return new Promise((resolve) => {
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);
export function getDictCache<T>(code?: string) {
return Dict.getDict(code) as T;
}
export function setDictCache(code: string, value, immediate = true) {
Dict.setDict(code, value, immediate);
}
export function storeDictCache() {
Dict.store();
}
export function removeDictCache(code: string) {
return Dict.removeDict(code);
}
export function clearDictCache(immediate = true) {
return Dict.clearDict(immediate);
}
interface listInfo {
value: string | number;
label: string;
desc?: object | string;
}
export function getDictInfoByValue(list: listInfo[], value = '') {
for (let i = 0; i < list.length; i++) {
if (list[i].value == value) {
return list[i];
}
}
return {} as listInfo;
}
export function getDictLabelByValue(list: listInfo[], value = '') {
for (let i = 0; i < list.length; i++) {
if (list[i].value == value) {
return list[i].label;
}
}
return '';
}
+174
View File
@@ -0,0 +1,174 @@
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;
});
};
}
export const BODY_CONTAINER = document.body;
+35
View File
@@ -0,0 +1,35 @@
export function preEditorValue(value: string, url = '') {
if (!value) return value;
let arr: any[] = [];
function preContent(c: string) {
if (c.indexOf('<img') !== -1) {
const s = c.substring(c.indexOf('<img') + 5, c.indexOf('/>') + 2);
arr.push(s);
preContent(c.substring(c.indexOf(s) + s.length));
}
}
preContent(value);
function preStr(str: string) {
function getPath(s, url) {
if (url) {
if (s.indexOf('/file/show/') !== -1) {
return str.substring(str.indexOf('/file/show/'));
} else {
return '/file/show/' + str.substring(str.indexOf('"') + 1);
}
} else {
if (s.indexOf('/file/show/') !== -1) {
return str.substring(str.indexOf('/file/show/') + 11);
} else {
return str.substring(str.indexOf('"') + 1);
}
}
}
return str.substring(0, str.indexOf('"') + 1) + url + getPath(str, url);
}
arr.map((item) => {
value = value.replace(item, preStr(item));
});
return value;
}
+138
View File
@@ -0,0 +1,138 @@
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 请求参数(POST的JSON参数)
* @returns {string} 获取签名
*/
static getSign(url, requestParams) {
let urlParams = this.parseQueryString(url);
let jsonObj = this.mergeObject(urlParams, requestParams);
let requestBody = this.sortAsc(jsonObj);
delete requestBody._t;
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();
}
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);
}
}
+44
View File
@@ -0,0 +1,44 @@
/**
* 枚举定义工具
* 示例:
* const STATUS = createEnum({
* AUDIT_WAIT: [1, '审核中'],
* AUDIT_PASS: [2, '审核通过']
* })
* 获取枚举值:STATUS.AUDIT_WAIT
* 获取枚举描述:STATUS.getDesc('AUDIT_WAIT')
* 通过枚举值获取描述:STATUS.getDescFromValue(STATUS.AUDIT_WAIT)
*
*/
export default function createEnum(definition) {
const strToValueMap = {};
const numToDescMap = {};
for (const enumName of Object.keys(definition)) {
const [value, desc] = definition[enumName];
strToValueMap[enumName] = value;
numToDescMap[value] = desc;
}
return {
/**
* 枚举值定义
*/
...strToValueMap,
/**
* 根据枚举名称获取描述
* @param {*} enumName 枚举名称
* @return {*} 枚举描述文字
*/
getDesc(enumName) {
return (definition[enumName] && definition[enumName][1]) || '';
},
/**
* 根据枚举值获取描述
*
* @param {*} value 枚举值
* @return {*} 枚举描述文字
*/
getDescFromValue(value) {
return numToDescMap[value] || '';
},
};
}
+96
View File
@@ -0,0 +1,96 @@
import type { GlobEnvConfig } from '/#/config';
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_APP_TITLE_JD,
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,
VITE_GLOB_ST_DOMAIN_URL,
VITE_PLATFORM,
} = ENV;
if (!/^[a-zA-Z\_]*$/.test(VITE_GLOB_APP_SHORT_NAME)) {
// warn(
// `VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.`
// );
}
return {
VITE_GLOB_APP_TITLE,
VITE_GLOB_APP_TITLE_JD,
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,
VITE_GLOB_ST_DOMAIN_URL,
VITE_PLATFORM,
};
}
/**
* @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
View 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);
}
+27
View File
@@ -0,0 +1,27 @@
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
const { createConfirm } = useMessage();
enum Api {
exportList = '/sys/common/commonExportsInfo/list',
commonExportsInfoDelete = '/sys/common/commonExportsInfo/delete',
}
export const exportList = (params) => {
return defHttp.get({ url: Api.exportList, params });
};
export const commonExportsInfoDeleteApi = (params, handleSuccess) => {
createConfirm({
iconType: 'warning',
title: '确认删除',
content: '是否删除选中数据',
okText: '确认',
cancelText: '取消',
onOk: () => {
return defHttp.delete({ url: Api.commonExportsInfoDelete + `?id=${params.id}`, params }).then(() => {
handleSuccess();
});
},
});
};
+51
View File
@@ -0,0 +1,51 @@
import { BasicColumn } from '/@/components/Table';
export const columns: BasicColumn[] = [
// {
// title: '批次号',
// align: 'center',
// dataIndex: 'batchNo',
// },
// {
// title: '任务编码',
// align: 'center',
// dataIndex: 'taskCode',
// },
{
title: '文件',
align: 'center',
dataIndex: 'exportUrl',
ifShow: true,
},
{
title: '处理进度',
align: 'center',
dataIndex: 'handleMsg',
},
{
title: '信息提示',
align: 'center',
dataIndex: 'exportMsg',
},
{
title: '状态',
align: 'center',
dataIndex: 'exportStatus_dictText',
},
{
title: '创建日期',
align: 'center',
dataIndex: 'createDate',
},
{
title: '开始处理时间',
align: 'center',
dataIndex: 'handleStartTime',
},
{
title: '处理结束时间',
align: 'center',
dataIndex: 'handleEndTime',
},
];
+149
View File
@@ -0,0 +1,149 @@
<template>
<BasicDrawer :title="props.drawerTitle" :width="drawerInfo.width" destroy-on-close v-bind="$attrs" @closeFunc="closeFunc" @register="register">
<BasicTable v-if="initData" @register="registerTable">
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'exportUrl'">
<a-button type="primary" :disabled="record.exportUrl === null" preIcon="ant-design:vertical-align-bottom-outlined">
<span style="text-decoration: underline" @click="exportFile(record.exportUrl)">点击下载</span>
</a-button>
</template>
<template v-if="column.dataIndex === 'action'">
<TableAction :actions="getTableAction(record)" />
</template>
</template>
</BasicTable>
</BasicDrawer>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import BasicDrawer from '/@/components/Drawer/src/BasicDrawer.vue';
import { useDrawerInner } from '/@/components/Drawer';
import BasicTable from '/@/components/Table/src/BasicTable.vue';
import { columns } from './exportUtil.data';
import { TableAction, useTable } from '/@/components/Table';
import { getFileAccessHttpUrlDown } from '/@/utils/common/compUtils';
import { commonExportsInfoDeleteApi, exportList } from '/@/utils/export/exportUtil.api';
import { TableProps } from '/@/hooks/system/useListPage';
import { cloneDeep, merge } from 'lodash-es';
const props = defineProps({
api: {
type: Function,
default: exportList,
},
taskCode: {
type: String,
required: true,
},
inAllFlag: {
type: Boolean,
required: false,
},
deleteApi: {
type: Function,
default: commonExportsInfoDeleteApi,
},
export: {
type: Boolean,
default: true,
},
drawerTitle: {
type: String,
default: '查看导出记录',
},
params: {
type: Object,
default: () => {},
},
columns: {
type: Array,
default: () => columns,
},
});
const drawerInfo = {
title: '查看导出记录',
width: '50%',
showIndexColumn: true,
indexColumnProps: {
width: 50,
},
};
const initData = ref(true);
let tableProps: TableProps = {
api: props.api as (...arg: any) => Promise<any>,
columns: props.columns,
canResize: true,
beforeFetch: (params) => {
params['taskCode'] = props.taskCode;
params['inAllFlag'] = props.inAllFlag;
params = { ...params, ...props.params };
return params;
},
showTableSetting: true,
tableSetting: {
redo: true,
},
bordered: true,
actionColumn: {
width: 80,
title: '操作',
dataIndex: 'action',
fixed: 'right',
},
};
function getColumns() {
const columnsUsed = cloneDeep(columns);
columnsUsed[0].ifShow = props.export;
return columnsUsed;
}
const [register] = useDrawerInner((data) => {
// data.column && tableProps.value.columns = data.column
initData.value = true;
if (data?.tableInfo) {
merge(tableProps, data.tableInfo);
}
if (data?.drawerInfo) {
merge(drawerInfo, data.drawerInfo);
}
setColumns(getColumns());
});
const [registerTable, { reload, setColumns }] = useTable(tableProps);
const exportFile = (url) => {
window.open(getFileAccessHttpUrlDown(url));
};
const closeFunc = () => {
initData.value = false;
return true;
};
const getTableAction = (record) => {
return [
{
label: '删除',
onClick: handleDelete.bind(null, record),
},
];
};
const handleDelete = async (record) => {
await props.deleteApi({ id: record.id }, reload);
};
</script>
<style scoped lang="less">
:deep(.ant-table-thead:nth-child(1):nth-child(1)) {
opacity: 0;
}
:deep(.ant-popover-buttons) {
display: flex;
}
:deep(.items-center) {
position: relative;
}
.redoIcon {
font-size: 18px;
position: absolute;
right: 20px;
top: -10px;
cursor: pointer;
}
</style>
+18
View File
@@ -0,0 +1,18 @@
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
const { createConfirm } = useMessage();
export const exportExcelRecordStart = (url: string, params: any) => {
if (!url) {
return;
}
createConfirm({
iconType: 'info',
title: '提示',
content: '确定启动导出任务吗?',
okText: '确认',
cancelText: '取消',
onOk: () => {
return defHttp.get({ url, params });
},
});
};
@@ -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
View 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
View 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;
}
+57
View File
@@ -0,0 +1,57 @@
export function getEnvInfo(): ViteEnv {
const res: any = {};
const envInfo: Recordable = import.meta.env;
for (const envName of Object.keys(envInfo)) {
let realName = typeof envInfo[envName] == 'string' ? envInfo[envName].replace(/\\n/g, '\n') : envInfo[envName];
realName = realName === 'true' ? true : realName === 'false' ? false : realName;
if (envName === 'VITE_PORT') {
realName = Number(realName);
}
if (envName === 'VITE_PROXY' && realName) {
try {
realName = JSON.parse(realName.replace(/'/g, '"'));
} catch (error) {
realName = '';
}
}
res[envName] = realName;
}
return res;
}
// 判断是否显示身份证相关信息(JD项目不显示)返回true 则不为交大项目
export function isShowIdCard() {
const res = getEnvInfo();
return (res.VITE_PLATFORM !== 'JD') as boolean;
}
// 判断是否为青海
export function isQH() {
const res = getEnvInfo();
return (res.VITE_PLATFORM === 'QH') as boolean;
}
// 判断是否为油田
export function isYT() {
const res = getEnvInfo();
return (res.VITE_PLATFORM === 'YT') as boolean;
}
export function isShowNewLayout(v) {
if (getEnvInfo().VITE_PLATFORM !== 'YT') return false;
// const code: string = '/intervene24/index,/information,/archives/index';
const code: string = '*';
return code === '*' || (code.split(',').includes(v) && v);
}
export function isShowNewLayoutSpecial(v) {
if (getEnvInfo().VITE_PLATFORM !== 'YT') return false;
// const code: string = '/intervene24/index,/information,/archives/index';
// return !code.split(',').includes(v) && v;
return false;
}
// 判断是否为空,需过滤0
export function isNull(v) {
if (v != 0 && !v) return '-';
return v;
}
+197
View 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
View 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;
}
+279
View File
@@ -0,0 +1,279 @@
import { dateUtil } from '/@/utils/dateUtil';
import { duplicateCheck } from '/@/views/system/user/user.api';
import { Rule } from '/@/components/Form';
import { message } from 'ant-design-vue';
export const rules = {
rule(type, required): Rule[] {
if (type === 'email') {
return this.email(required);
}
if (type === 'phone') {
return this.phone(required);
}
if (type === 'idcard') {
return this.idcard(required);
}
return [];
},
idcard(required): Rule[] {
return [
{
required: required ? required : false,
validator: async (_rule, value) => {
if (required == true && !value) {
return Promise.reject('请输入身份证号!');
}
if (value && !new RegExp(/^\d{6}(18|19|20)?\d{2}(0[1-9]|1[012])(0[1-9]|[12]\d|3[01])\d{3}(\d|[xX])$/).test(value)) {
return Promise.reject('请输入正确的身份证号!');
}
return Promise.resolve();
},
trigger: 'change',
},
];
},
email(required): Rule[] {
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',
},
];
},
phone(required): Rule[] {
return [
{
required: required,
validator: async (_, value) => {
if (!value) {
return required ? Promise.reject('请输入手机号码!') : Promise.resolve();
}
if (!/^1[3456789]\d{9}$/.test(value)) {
return Promise.reject('手机号码格式有误');
}
return Promise.resolve();
},
trigger: 'change',
},
];
},
telPhone(required: boolean, tip?: string = '电话号码格式有误'): Rule[] {
return [
{
required: required,
validator: async (_, value) => {
if (!value) {
return required ? Promise.reject('请输入电话号码!') : Promise.resolve();
}
if (!/^(0\d{2,3}-\d{7,8})$|^(0\d{2,3}\d{7,8})|^(\(\d{3,4}\)-\d{7,8})$|^(\(\d{3,4}\)\d{7,8})$/.test(value)) {
return Promise.reject(tip);
}
return Promise.resolve();
},
trigger: 'change',
},
];
},
phoneAndTelPhone(required: boolean, tip?: string = '电话号码格式有误'): Rule[] {
return [
{
required: required,
validator: async (_, value) => {
if (!value) {
return required ? Promise.reject('请输入号码!') : Promise.resolve();
}
if (!/^(0\d{2,3}-\d{7,8})$|^(0\d{2,3}\d{7,8})|^(\(\d{3,4}\)-\d{7,8})$|^(\(\d{3,4}\)\d{7,8})$|^1[3456789]\d{9}$/.test(value)) {
return Promise.reject(tip);
}
return Promise.resolve();
},
trigger: 'change',
},
];
},
// 多个电话号码验证
multiplePhone(required: boolean): Rule[] {
return [
{
required: required,
validator: async (_, value) => {
if (!value) {
return required ? Promise.reject('请输入号码!') : Promise.resolve();
}
const phoneRegex = /^(?:1[3-9]\d{9}(?:,|$))*$/;
if (!phoneRegex.test(value)) {
return Promise.reject('请检查格式');
}
return Promise.resolve();
},
trigger: 'change',
},
];
},
startTime(endTime, required): Rule[] {
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): Rule[] {
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): Rule[] {
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?): Rule[] {
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 || '验证失败');
});
});
},
trigger: 'blur',
},
];
},
duplicateCheckRuleAndDelFlag(tableName, fieldName, delFlag, model, schema, required?): Rule[] {
return [
{
validator: (_, value) => {
if (!value && required) {
return Promise.reject(`请输入${schema.label}`);
}
return new Promise<void>((resolve, reject) => {
duplicateCheck({
tableName,
fieldName,
delFlag,
fieldVal: value,
dataId: model.id,
})
.then((res) => {
res.success ? resolve() : reject(res.message || '校验失败');
})
.catch((err) => {
reject(err.message || '验证失败');
});
});
},
trigger: 'blur',
},
];
},
//模拟实测中数据验证
//数字大于0
numberGreaterThanZero(value: number) {
const patternNumber = /^\d*(\.\d+)?$/;
if (!patternNumber.test(value) || value < 0) {
message.warn('请输入大于0的数字');
return false;
}
return true;
},
//数字大于0的整数
numberGreaterThanZeroInt(value: number) {
const patternNumber = /^\d+$/;
if (!patternNumber.test(value) || value < 0) {
message.warn('请输入大于0的整数');
return false;
}
return true;
},
};
//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 {
const 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: 代码生成-原生表单用
+169
View File
@@ -0,0 +1,169 @@
// 导出页面为PDF格式
import html2canvas from 'html2canvas';
import JsPDF from 'jspdf';
/**
* @param ele 要生成 pdf 的DOM元素(容器)
* @param padfName PDF文件生成后的文件名字
* */
export function downloadPDF(ele, pdfName) {
if (confirm('您确认下载该PDF文件吗?')) {
html2canvas(ele, {
imageTimeout: 0,
dpi: window.devicePixelRatio * 4, //将分辨率提高到特定的DPI 提高四倍
scale: 4, //按比例增加分辨率
useCORS: true, //允许canvas画布内 可以跨域请求外部链接图片, 允许跨域请求。
}).then((canvas) => {
//未生成pdf的html页面高度
let leftHeight = canvas.height;
let a4Width = 555.28;
let a4Height = 841.89;
//一页pdf显示html页面生成的canvas高度;
let a4HeightRef = Math.floor((canvas.width / a4Width) * a4Height);
//pdf页面偏移
let position = 0;
let pageData = canvas.toDataURL('image/jpeg', 1.0);
let pdf = new JsPDF('x', 'pt', 'a4');
let index = 1;
let canvas1 = document.createElement('canvas');
let height;
pdf.setDisplayMode('fullwidth', 'continuous', 'FullScreen');
function createImpl(canvas) {
if (leftHeight > 0) {
index++;
let checkCount = 0;
if (leftHeight > a4HeightRef) {
let i = position + a4HeightRef;
for (i = position + a4HeightRef; i >= position; i--) {
let isWrite = true;
for (var j = 0; j < canvas.width; j++) {
let c = canvas.getContext('2d').getImageData(j, i, 1, 1).data;
if (c[0] != 0xff || c[1] != 0xff || c[2] != 0xff) {
isWrite = false;
break;
}
}
if (isWrite) {
checkCount++;
if (checkCount >= 10) {
break;
}
} else {
checkCount = 0;
}
}
height = Math.round(i - position) || Math.min(leftHeight, a4HeightRef);
if (height <= 0) {
height = a4HeightRef;
}
} else {
height = leftHeight;
}
canvas1.width = canvas.width;
canvas1.height = height;
let ctx = canvas1.getContext('2d');
ctx.drawImage(canvas, 0, position, canvas.width, height, 0, 0, canvas.width, height);
if (position != 0) {
pdf.addPage();
}
pdf.addImage(canvas1.toDataURL('image/jpeg', 1.0), 'JPEG', 20, 0, a4Width, (a4Width / canvas1.width) * height);
leftHeight -= height;
position += height;
if (leftHeight > 0) {
setTimeout(createImpl, 500, canvas);
} else {
pdf.save(pdfName + '.pdf');
}
}
}
//当内容未超过pdf一页显示的范围,无需分页
if (leftHeight < a4HeightRef) {
pdf.addImage(pageData, 'JPEG', 0, 0, a4Width, (a4Width / canvas.width) * leftHeight);
pdf.save(pdfName + '.pdf');
} else {
try {
pdf.deletePage(0);
setTimeout(createImpl, 500, canvas);
} catch (err) {
console.log(err);
}
}
});
}
}
// let eleW = ele.offsetWidth;// 获得该容器的宽
// let eleH = ele.offsetHeight;// 获得该容器的高
// let eleOffsetTop = ele.offsetTop; // 获得该容器到文档顶部的距离
// let eleOffsetLeft = ele.offsetLeft; // 获得该容器到文档最左的距离
//
// var canvas = document.createElement("canvas");
// var abs = 0;
//
// let win_in = document.documentElement.clientWidth || document.body.clientWidth; // 获得当前可视窗口的宽度(不包含滚动条)
// let win_out = window.innerWidth; // 获得当前窗口的宽度(包含滚动条)
//
// if(win_out>win_in){
// // abs = (win_o - win_i)/2; // 获得滚动条长度的一半
// abs = (win_out - win_in)/2; // 获得滚动条宽度的一半
// // console.log(a, '新abs');
// }
// canvas.width = eleW * 2; // 将画布宽&&高放大两倍
// canvas.height = eleH * 2;
//
// var context = canvas.getContext("2d");
// context.scale(2, 2);
// context.translate(-eleOffsetLeft -abs, -eleOffsetTop);
// // 这里默认横向没有滚动条的情况,因为offset.left(),有无滚动条的时候存在差值,因此
// // translate的时候,要把这个差值去掉
//
// // html2canvas(element).then( (canvas)=>{ //报错
// // html2canvas(element[0]).then( (canvas)=>{
// html2canvas( ele, {
// imageTimeout: 0,
// dpi: window.devicePixelRatio * 4, //将分辨率提高到特定的DPI 提高四倍
// scale: 4, //按比例增加分辨率
// // allowTaint: true, //允许 canvas 污染, allowTaint参数要去掉,否则是无法通过toDataURL导出canvas数据的
// useCORS: true //允许canvas画布内 可以跨域请求外部链接图片, 允许跨域请求。
// } ).then((canvas) => {
// var contentWidth = canvas.width;
// var contentHeight = canvas.height;
// //一页pdf显示html页面生成的canvas高度;
// var pageHeight = contentWidth / 592.28 * 801.89;
// //未生成pdf的html页面高度
// var leftHeight = contentHeight;
// //页面偏移
// var position = 20;
// //a4纸的尺寸[595.28,841.89]html页面生成的canvas在pdf中图片的宽高
// var imgWidth = 555.28;
// var imgHeight = 555.28/contentWidth * contentHeight;
// var pageData = canvas.toDataURL('image/jpeg', 1.0);
// var pdf = new JsPDF('p', 'pt', 'a4');
// //有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89)
// //当内容未超过pdf一页显示的范围,无需分页
// if (leftHeight < pageHeight) {
// //在pdf.addImage(pageData, 'JPEG', 左,上,宽度,高度)设置在pdf中显示;
// pdf.addImage(pageData, 'JPEG', 20, 20, imgWidth, imgHeight);
// // pdf.addImage(pageData, 'JPEG', 20, 40, imgWidth, imgHeight);
// } else { // 分页
// while(leftHeight > 0) {
// pdf.addImage(pageData, 'JPEG', 20, position, imgWidth, imgHeight, {
// bottom: 10,
// top: 10
// });
// leftHeight -= pageHeight;
// position -= 841.89;
// //避免添加空白页
// if(leftHeight > 0) {
// pdf.addPage();
// }
// }
// }
// //可动态生成
// pdf.save(pdfName);
// })
// }
export default {
downloadPDF,
};
+274
View File
@@ -0,0 +1,274 @@
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 { ConfigEnum, 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);
}
// 拓展,如果不为默认C01,给请求头拼接参数
if (this.options.requestOptions?.headParams !== 'C01') {
// @ts-ignore
config.headers[ConfigEnum.X_SERVICE_PLATFORM] = this.options.requestOptions?.headParams;
}
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, headParams?: string) {
//--@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]);
});
}
// @ts-ignore
this.options.requestOptions.headParams = headParams;
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
View 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
View 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
View 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}` });
}
}
}
+46
View File
@@ -0,0 +1,46 @@
import { isObject, isString } from '/@/utils/is';
import dayjs from 'dayjs';
const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm';
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
View 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 { ConfigEnum, ContentTypeEnum, RequestEnum, ResultEnum } from '/@/enums/httpEnum';
import { isString } from '/@/utils/is';
import { getTenantId, getToken } from '/@/utils/auth';
import { deepMerge, setObjToUrlParams } from '/@/utils';
import signMd5Utils from '/@/utils/encryption/signMd5Utils';
import { useErrorLogStoreWithOut } from '/@/store/modules/errorLog';
import { useI18n } from '/@/hooks/web/useI18n';
import { formatRequestDate, joinTimestamp } from './helper';
import { useUserStoreWithOut } from '/@/store/modules/user';
import { getEnvInfo } from '/@/utils/getEnv';
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) {
// if (res.data.code !== 200) {
// return createMessage.error(res.data.message ? res.data.message : res.data.msg);
// }
return res.data;
}
// 错误的时候返回
const { data } = res;
if (!data) {
// return '[HTTP] Request has no return value';
throw new Error(t('sys.api.apiRequestFailed'));
}
// 这里 coderesultmessage为 后台统一的字段,需要在 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' && options.successNeedMessage) {
//信息成功提示
createMessage.success(message);
}
return result;
} else {
// 如果不是默认返回结构,则判断是否为特殊结构
// 修改加入参数ok为真时为成功否则为假,如果有ok字段则返回值取data
const { code, message, data: result } = data;
const hasSuccess = data && code === 200;
if (hasSuccess) {
if (message && options.successNeedMessage) {
//信息成功提示
createMessage.success(message);
}
return result;
}
}
// 在此处根据自己项目的实际情况对不同的code执行不同的操作
// 如果不希望中断当前请求,请return数据,否则直接抛出异常即可
let timeoutMsg = '';
switch (code) {
case ResultEnum.TIMEOUT:
timeoutMsg = t('sys.api.timeoutMessage');
const userStore = useUserStoreWithOut();
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 { isNeedUrl, apiUrl, joinPrefix, joinParamsToUrl, formatDate, joinTime = true, urlPrefix } = options;
if (joinPrefix) {
config.url = `${urlPrefix}${config.url}`;
}
if (apiUrl && isString(apiUrl) && isNeedUrl) {
config.url = `${apiUrl}${config.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 = getTenantId();
if (token && (config as Recordable)?.requestOptions?.withToken !== false) {
// jwt token
config.headers.Authorization = options.authenticationScheme ? `${options.authenticationScheme} ${token}` : token;
config.headers[ConfigEnum.TOKEN] = token;
// C01 是体检答题 C02是癌症答题
config.headers[ConfigEnum.X_SERVICE_PLATFORM] = config.requestOptions.headParams;
//--update-begin--author:liusq---date:20210831---for:将签名和时间戳,添加在请求接口 Header
// update-begin--author:taoyan---date:20220421--for: VUEN-410【签名改造】 X-TIMESTAMP牵扯
config.headers[ConfigEnum.TIMESTAMP] = signMd5Utils.getTimestamp();
// update-end--author:taoyan---date:20220421--for: VUEN-410【签名改造】 X-TIMESTAMP牵扯
config.headers[ConfigEnum.Sign] = signMd5Utils.getSign(config.url, config.params);
//--update-end--author:liusq---date:20210831---for:将签名和时间戳,添加在请求接口 Header
//--update-begin--author:liusq---date:20211105---for: for:将多租户id,添加在请求接口 Header
if (!tenantid) {
tenantid = 0;
}
config.headers[ConfigEnum.TENANT_ID] = tenantid;
//--update-begin--author:liusq---date:20220325---for: 增加vue3标记
config.headers[ConfigEnum.VERSION] = 'v3';
//--update-end--author:liusq---date:20220325---for:增加vue3标记
//--update-end--author:liusq---date:20211105---for:将多租户id,添加在请求接口 Header
// ========================================================================================
// update-begin--author:sunjianlei---date:20220624--for: 添加低代码应用ID
const 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);
},
};
const res = getEnvInfo();
function createAxios(opt?: Partial<CreateAxiosOptions>) {
return new VAxios(
deepMerge(
{
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#authentication_schemes
// authentication schemese.g: Bearer
// authenticationScheme: 'Bearer',
authenticationScheme: '',
// 改为五分钟
timeout: 5 * 60 * 1000,
// 基础接口地址
// baseURL: globSetting.apiUrl,
headers: { 'Content-Type': ContentTypeEnum.JSON, ...(res.VITE_PLATFORM == 'QH' ? { 'X-ClientSign': 'Quarry' } : {}) },
// 如果是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',
// 成功后是否需要提示
successNeedMessage: true,
// 接口地址
apiUrl: globSetting.apiUrl,
// 接口拼接地址
urlPrefix: urlPrefix,
// 是否加入时间戳
joinTime: true,
// 忽略重复请求
ignoreCancelToken: true,
// 是否携带token
withToken: true,
// 头部参数
headParams: 'C01',
// 是否需要默认链接
isNeedUrl: true,
},
},
opt || {}
)
);
}
export const defHttp = createAxios();
// other api url
// export const otherHttp = createAxios({
// requestOptions: {
// apiUrl: 'xxx',
// },
// });
+133
View File
@@ -0,0 +1,133 @@
import md5 from 'md5';
import { stServerUrl } from './serverUrl';
import { defHttp } from '/@/utils/http/axios';
const 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;
};
// 食堂 =》 signatureSecret
const signatureSecret = 'dd05f1c54d63749eda95f9fa6d49v442a';
// 食堂 =》 计算sign
const getSign = (url, requestParams) => {
const urlParams = parseQueryString(url);
const jsonObj = mergeObject(urlParams, requestParams);
const requestBody = sortAsc(jsonObj);
return md5(JSON.stringify(requestBody) + signatureSecret).toUpperCase();
};
/**
* @param url 请求的url
* @returns {{}} 将url中请求参数组装成json对象(url的?后面的参数)
*/
const 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('?'));
}
//解决Sign 签名校验失败 #2728
result['x-path-variable'] = decodeURIComponent(lastpathVariable);
}
if (urlArray && urlArray[1]) {
let paramString = urlArray[1], paramResult;
while ((paramResult = paramReg.exec(paramString)) != null) {
//数字值转为string类型,前后端加密规则保持一致
if(myIsNaN(paramResult[2])){
paramResult[2] = paramResult[2].toString();
}
result[paramResult[1]] = paramResult[2];
}
}
return result;
};
/**
* @returns {*} 将两个对象合并成一个
*/
const mergeObject = (objectOne, objectTwo) => {
if (objectTwo && Object.keys(objectTwo).length > 0) {
for (let key in objectTwo) {
if (objectTwo.hasOwnProperty(key) === true) {
//数字值转为string类型,前后端加密规则保持一致
if (myIsNaN(objectTwo[key])) {
objectTwo[key] = objectTwo[key].toString();
}
objectOne[key] = objectTwo[key];
}
}
}
return objectOne;
};
const 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;
};
const myIsNaN = (value) => {
return typeof value === 'number' && !isNaN(value);
};
// 食堂 =》 获取token
export const getStToken = async (params) => {
const url = '/foodNourishmentReport/getToken';
return new Promise((resolve, reject) => {
defHttp
.post(
{
url: url,
params,
headers: {
'X-Sign': getSign(url, params),
'X-TIMESTAMP': getDateTimeToString(),
// 'X-TIMESTAMP': 123123123123,
},
},
{
apiUrl: stServerUrl,
withToken: false,
isTransformResponse: false,
}
)
.then((res) => {
if (res.success) {
resolve(res.result);
} else {
reject();
}
});
});
};
@@ -0,0 +1,5 @@
import { useGlobSetting } from '/@/hooks/setting';
const glob = useGlobSetting();
export const stServerUrl = glob.stDomainUrl;
+7
View File
@@ -0,0 +1,7 @@
import { getEnvInfo } from '/@/utils/getEnv';
export const imAddressSrc = `/static/im-web/index.html?${getUrlP()}`;
// export const imAddressSrc = `http://192.168.1.48:8080?${getUrlP()}`;
function getUrlP() {
return `vitePlatform=${getEnvInfo().VITE_PLATFORM}&`;
}
+72
View File
@@ -0,0 +1,72 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" title="导入详细信息" :showOkBtn="false" width="1000px" destroyOnClose>
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<!--插槽:table标题-->
<template #tableTitle>
<a-button type="primary" preIcon="ant-design:export-outlined" @click="onExportXls"> 导出 </a-button>
</template>
</BasicTable>
</BasicModal>
</template>
<script lang="ts" setup>
import { ref, toRaw, unref } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicTable, useTable, TableAction } from '/@/components/Table';
import { detailColumns, searchFormSchema } from './ImportUtil.data';
import { getImportsDetailList, getExportDetailUrl, exportDetailFile} from './ImportUtil.api';
import { useMessage } from '/@/hooks/web/useMessage';
const { createConfirm } = useMessage();
const checkedKeys = ref<Array<string | number>>([]);
let infoId = ref('999');
const [registerModal] = useModalInner(async (data) => {
//重置表单
checkedKeys.value = [];
infoId = data.infoId;
});
//注册table数据
const [registerTable, { reload, getForm }] = useTable({
api: getImportsDetailList,
columns: detailColumns,
rowKey: 'id',
striped: true,
useSearchForm: true,
showTableSetting: true,
clickToRowSelect: true,
bordered: true,
showIndexColumn: false,
pagination: true,
beforeFetch: (params) => {
params.infoId = infoId;
return params;
},
formConfig: {
schemas: searchFormSchema,
labelWidth: 90,
baseColProps: {span: 12},
},
});
/**
* 选择列配置
*/
const rowSelection = {
type: 'checkbox',
columnWidth: 50,
selectedRowKeys: checkedKeys,
onChange: onSelectChange,
};
/**
* 选择事件
*/
function onSelectChange(selectedRowKeys: (string | number)[]) {
checkedKeys.value = selectedRowKeys;
}
async function onExportXls(){
const form = getForm().getFieldsValue();
form.infoId = infoId;
await exportDetailFile(form);
}
</script>
+66
View File
@@ -0,0 +1,66 @@
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
import { useMethods } from '/@/hooks/system/useMethods';
import { filterObj } from '/@/utils/common/compUtils';
const { handleExportXls } = useMethods();
const { createConfirm } = useMessage();
enum Api {
importList = '/sys/common/commonImports/list',
commonImportsDelete = '/sys/common/commonImports/delete',
importsDetailList = '/sys/common/commonImportsDetail/list',
exportDetailXls = '/sys/common/commonImportsDetail/exportXls',
}
/**
* 导出api
* @param params
*/
export const getExportDetailUrl = Api.exportDetailXls;
/**
* 查看详细列表
* @param params
* @returns {Promise<any>}
*/
export const getImportsDetailList = (params) => defHttp.get({ url: Api.importsDetailList, params });
export const exportDetailFile = (params) => handleExportXls('导入数据详细', Api.exportDetailXls, filterObj(params));
export const importList = (params) => {
return defHttp.get({ url: Api.importList, params });
};
// export const handlePost = (url,params) => {
// return defHttp.post({ url: url, params });
// };
export const handlePost = (url, handleTitle, params, handleSuccess) => {
createConfirm({
iconType: 'warning',
title: '确认操作',
content: '是否确认执行【' + handleTitle + '】',
okText: '确认',
cancelText: '取消',
onOk: () => {
return defHttp.post({ url: url + `?infoId=${params.id}`, params }).then(() => {
handleSuccess();
});
},
});
};
export const commonImportsDeleteApi = (params, handleSuccess) => {
createConfirm({
iconType: 'warning',
title: '确认删除',
content: '是否删除选中数据',
okText: '确认',
cancelText: '取消',
onOk: () => {
return defHttp.delete({ url: Api.commonImportsDelete + `?id=${params.id}`, params }).then(() => {
handleSuccess();
});
},
});
};
+186
View File
@@ -0,0 +1,186 @@
import {BasicColumn, FormSchema} from '/@/components/Table';
export const columns: BasicColumn[] = [
{
title: '处理进度',
align: 'center',
width: 120,
dataIndex: 'handleMsg',
},
{
title: '信息提示',
align: 'center',
dataIndex: 'importMsg',
},
{
title: '状态',
align: 'center',
dataIndex: 'importStatus_dictText',
},
{
title: '创建日期',
align: 'center',
dataIndex: 'createDate',
},
{
title: '开始处理时间',
align: 'center',
dataIndex: 'handleStartTime',
},
{
title: '处理结束时间',
align: 'center',
dataIndex: 'handleEndTime',
},
{
title: '导入文件',
align: 'center',
dataIndex: 'importUrl',
ifShow: true,
},
];
export const searchFormSchema: FormSchema[] = [
{
label: '是否有错误',
field: 'rowErrorFlag',
component: 'JDictSelectTag',
componentProps: {
dictCode: 'sf_10',
placeholder: '请选择',
},
},
];
export const detailColumns: BasicColumn[] = [
{
title: '行号',
align: 'center',
width: 80,
fixed: 'left',
dataIndex: 'rowIndex',
},
{
title: '是否有错误',
align: 'center',
fixed: 'left',
dataIndex: 'rowErrorFlag_dictText',
},
{
title: '错误信息',
align: 'center',
ellipsis: true,
fixed: 'left',
dataIndex: 'rowErrorMsg',
},
// {
// title: '是否已更新',
// align: 'center',
// dataIndex: 'updateFlag_dictText',
// },
{
title: '列A',
align: 'center',
dataIndex: 'col1',
},
{
title: '列B',
align: 'center',
dataIndex: 'col2',
},
{
title: '列C',
align: 'center',
dataIndex: 'col3',
},
{
title: '列D',
align: 'center',
dataIndex: 'col4',
},
{
title: '列E',
align: 'center',
dataIndex: 'col5',
},
{
title: '列F',
align: 'center',
dataIndex: 'col6',
},
{
title: '列G',
align: 'center',
dataIndex: 'col7',
},
{
title: '列H',
align: 'center',
dataIndex: 'col8',
},
{
title: '列I',
align: 'center',
dataIndex: 'col9',
},
{
title: '列J',
align: 'center',
dataIndex: 'col10',
},
{
title: '列K',
align: 'center',
dataIndex: 'col11',
},
{
title: '列L',
align: 'center',
dataIndex: 'col12',
},
{
title: '列M',
align: 'center',
dataIndex: 'col13',
},
{
title: '列N',
align: 'center',
dataIndex: 'col14',
},
{
title: '列O',
align: 'center',
dataIndex: 'col15',
},
{
title: '列P',
align: 'center',
dataIndex: 'col16',
},
{
title: '列Q',
align: 'center',
dataIndex: 'col17',
},
{
title: '列R',
align: 'center',
dataIndex: 'col18',
},
{
title: '列S',
align: 'center',
dataIndex: 'col19',
},
{
title: '列T',
align: 'center',
dataIndex: 'col20',
},
];
+194
View File
@@ -0,0 +1,194 @@
<template>
<BasicDrawer
:title="props.drawerTitle"
:width="drawerInfo.width"
:checkUrl="props.checkUrl"
:updateUrl="props.updateUrl"
destroy-on-close
v-bind="$attrs"
@closeFunc="closeFunc"
@register="register"
>
<BasicTable v-if="initData" @register="registerTable">
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'importUrl'">
<a-button type="primary" :disabled="record.importUrl === null" preIcon="ant-design:vertical-align-bottom-outlined">
<span style="text-decoration: underline" @click="importFile(record.importUrl)">点击下载</span>
</a-button>
</template>
<template v-if="column.dataIndex === 'action'">
<TableAction :actions="getTableAction(record)" />
</template>
</template>
</BasicTable>
</BasicDrawer>
<ImportDetailModal @register="registerModal" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import BasicDrawer from '/@/components/Drawer/src/BasicDrawer.vue';
import { useDrawerInner } from '/@/components/Drawer';
import BasicTable from '/@/components/Table/src/BasicTable.vue';
import { columns } from './ImportUtil.data';
import { TableAction, useTable } from '/@/components/Table';
import { getFileAccessHttpUrlDown } from '/@/utils/common/compUtils';
import { commonImportsDeleteApi, importList, handlePost } from '/@/utils/import/ImportUtil.api';
import { TableProps } from '/@/hooks/system/useListPage';
import { cloneDeep, merge } from 'lodash-es';
import { useModal } from '/@/components/Modal';
import ImportDetailModal from '/@/utils/import/ImportDetailModal.vue';
const [registerModal, { openModal }] = useModal();
const props = defineProps({
api: {
type: Function,
default: importList,
},
taskCode: {
type: String,
required: true,
},
deleteApi: {
type: Function,
default: commonImportsDeleteApi,
},
export: {
type: Boolean,
default: true,
},
drawerTitle: {
type: String,
default: '查看导入记录',
},
checkUrl: {
type: String,
default: '',
},
updateUrl: {
type: String,
default: '',
},
params: {
type: Object,
default: () => {},
},
});
const drawerInfo = {
title: '查看导入记录',
width: '50%',
showIndexColumn: true,
indexColumnProps: {
width: 50,
},
};
const initData = ref(true);
let tableProps: TableProps = {
api: props.api as (...arg: any) => Promise<any>,
columns,
size: 'small',
canResize: true,
beforeFetch: (params) => {
params['taskCode'] = props.taskCode;
params = { ...params, ...props.params };
return params;
},
showTableSetting: true,
tableSetting: {
redo: true,
},
bordered: true,
actionColumn: {
width: 200,
title: '操作',
dataIndex: 'action',
fixed: 'right',
},
//自定义默认排序
defSort: {
column: 'createDate',
order: 'desc',
},
};
function getColumns() {
const columnsUsed = cloneDeep(columns);
columnsUsed[0].ifShow = props.export;
return columnsUsed;
}
const [register] = useDrawerInner((data) => {
// data.column && tableProps.value.columns = data.column
initData.value = true;
if (data?.tableInfo) {
merge(tableProps, data.tableInfo);
}
if (data?.drawerInfo) {
merge(drawerInfo, data.drawerInfo);
}
setColumns(getColumns());
});
const [registerTable, { reload, setColumns }] = useTable(tableProps);
const importFile = (url) => {
window.open(getFileAccessHttpUrlDown(url));
};
const closeFunc = () => {
initData.value = false;
return true;
};
const getTableAction = (record) => {
return [
{
label: '校验',
onClick: handleCheck.bind(null, record),
ifShow: props.checkUrl !== '',
disabled: record.importStatus !== '0',
},
{
label: '更新',
onClick: handleUpdate.bind(null, record),
ifShow: props.updateUrl !== '',
disabled: props.updateUrl == null || record.importStatus !== '3',
},
{
label: '详情',
onClick: handleShowDetailList.bind(null, record),
ifShow: props.updateUrl !== '' || props.checkUrl !== '',
},
{
label: '删除',
color: 'error',
onClick: handleDelete.bind(null, record),
},
];
};
const handleDelete = async (record) => {
await props.deleteApi({ id: record.id }, reload);
};
const handleCheck = async (record) => {
await handlePost(props.checkUrl, '校验', { id: record.id }, reload);
};
const handleUpdate = async (record) => {
await handlePost(props.updateUrl, '更新', { id: record.id }, reload);
};
const handleShowDetailList = (record) => {
openModal(true, { infoId: record.id });
};
</script>
<style scoped lang="less">
:deep(.ant-table-thead:nth-child(1):nth-child(1)) {
opacity: 0;
}
:deep(.ant-popover-buttons) {
display: flex;
}
:deep(.items-center) {
position: relative;
}
.redoIcon {
font-size: 18px;
position: absolute;
right: 20px;
top: -10px;
cursor: pointer;
}
</style>
+362
View File
@@ -0,0 +1,362 @@
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) {
src[key] = isObject(src[key]) ? deepMerge(src[key], target[key]) : (src[key] = target[key]);
}
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) {
const formSchema = unref(unref(props)?.schemas);
let valueType = 'string';
if (formSchema) {
const 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) => {
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) {
const url = document.location.toString();
const arrObj = url.split('?');
if (arrObj.length > 1) {
const 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) {
const 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;
let t,
n,
r,
i = url.split('?')[1],
s = {};
(t = i.split('&')), (r = null), (n = null);
for (const o in t) {
const 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 != '') {
const unit = ['仟', '佰', '拾', '', '仟', '佰', '拾', '', '角', '分'];
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 '玖';
}
};
const lth = value.toString().length;
value *= 100;
value += '';
const 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) {
// URL支持{{ window.xxx }}占位符变量
url = url.replace(/{{([^}]+)?}}/g, (_s1, s2) => eval(s2));
if (url.includes('?')) {
url += '&';
} else {
url += '?';
}
url += `id=${id}`;
url += `&token=${token}`;
window.open(url);
}
/**
* 返回身份证正则
*/
export function getIDCardRegExp() {
return /^[1-9]\d{5}(19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[\d|X|x]$/;
}
/**
* 判断身份证是否在当前日期之后
* @param idCard
*/
export function isBirthdayInFuture(idCard: string): boolean {
let birth = idCard.substring(6, 10) + '-' + idCard.substring(10, 12) + '-' + idCard.substring(12, 14);
let today = new Date();
let birthDate = new Date(birth);
return birthDate > today;
}
/**
*
* @param doctorName
* @param deptName
* @param doctorTitle
*/
// 处理咨询专家拼接问题
export function getDoctorList(doctorName, deptName, doctorTitle) {
let result = '';
if (doctorName) {
result += doctorName;
}
if (doctorName && deptName) {
result += '-' + deptName;
}
if (!doctorName && deptName) {
result += deptName;
}
if ((doctorName || deptName) && doctorTitle) {
result += '-' + doctorTitle;
}
if (!doctorName && !deptName && doctorTitle) {
result += doctorTitle;
}
return result;
}
+118
View File
@@ -0,0 +1,118 @@
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');
}
/**
* @desc 是否为非负数
* @param value
* @param type int:整数 double:小数
*/
export function isNonNegativeString(value: string, type = 'int'): boolean {
// 使用正则表达式检查是否为非负数
const regex = type == 'int' ? /^[0-9]+$/ : /^\d+(\.\d+)?$/;
return regex.test(value);
}
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);
}
+20
View File
@@ -0,0 +1,20 @@
import { JSEncrypt } from 'jsencrypt';
// 公钥
export const publicKey =
'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCmZfR/bA9X3vp86y1aEpvwzXJYKRRF1fLau2+05/ZtaITLpV8bhkmSf3neSy/Q9gAdvG75Fr73E+GWE+K5b0BpvIS1jDGo319+PpZR39SaZTKZ27XFXrosmJTZutN79t819HS1VseleunHAFgMVufE9U5jP6LGzl/wbkSy01GhzwIDAQAB';
/**
* @Description:密码加密
* @date 2023/8/21
* @param password
*/
export function encipher(password): string | undefined {
if (password) {
// 新建JSEncrypt对象
const encryptor = new JSEncrypt();
// 设置公钥
encryptor.setPublicKey(publicKey);
// 加密数据
return encryptor.encrypt(password) as string;
}
}
+51
View 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
View 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}`);
}
+2
View File
@@ -0,0 +1,2 @@
const mapKey = '4bbcb216b889f2d612cbed05ae6979c8';
export default mapKey;
+21
View File
@@ -0,0 +1,21 @@
export const getShort = (map, point = [], array = []) => {
const distanceList: Array<any> = array.map((item: any) => {
return { ...item, ...{ dis: map.GeometryUtil.distance(point, [item.longitude, item.latitude]) } };
});
return sort(distanceList);
};
const sort = (arr) => {
let temp;
for (let i = 0; i < arr.length - 1; i++) {
for (let j = 0; j < arr.length - i - 1; j++) {
if (arr[j].dis > arr[j + 1].dis) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
return arr.length > 0 ? arr[0] : null;
};
+29
View File
@@ -0,0 +1,29 @@
// 根据权限,判断医疗点所调用
import { useUserStore } from '/@/store/modules/user';
import { allSecondaryDepartsNewBack, allSecondaryDepartsNew, getThirdDepartsNew, getThreeDepartByOrgCode } from '/@/utils/orgSearchInfo';
export function getApi() {
const userStore = useUserStore();
const roleCodes = userStore.getUserInfo?.roleCodes;
if (roleCodes && (roleCodes.indexOf('medical_staff_role') !== -1 || roleCodes.indexOf('mc_together_hospital')) !== -1) {
return allSecondaryDepartsNewBack;
}
return allSecondaryDepartsNew;
}
export function getDepartApi() {
const userStore = useUserStore();
const roleCodes = userStore.getUserInfo?.roleCodes;
if (roleCodes && (roleCodes.indexOf('medical_staff_role') !== -1 || roleCodes.indexOf('mc_together_hospital')) !== -1) {
return getThreeDepartByOrgCode;
}
return getThirdDepartsNew;
}
export function getDepartParams() {
const userStore = useUserStore();
const roleCodes = userStore.getUserInfo?.roleCodes;
if (roleCodes && (roleCodes.indexOf('medical_staff_role') !== -1 || roleCodes.indexOf('mc_together_hospital')) !== -1) {
return 'orgCode';
}
return 'idOrCode';
}
+110
View File
@@ -0,0 +1,110 @@
/**
* 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 = unknown> = (event: T) => void;
export type WildcardHandler<T = Record<string, unknown>> = (type: keyof T, event: T[keyof T]) => void;
// An array of all currently registered event handlers for a type
export type EventHandlerList<T = unknown> = Array<Handler<T>>;
export type WildCardEventHandlerList<T = Record<string, unknown>> = Array<WildcardHandler<T>>;
// A map of event types and their corresponding event handlers.
export type EventHandlerMap<Events extends Record<EventType, unknown>> = Map<
keyof Events | '*',
EventHandlerList<Events[keyof Events]> | WildCardEventHandlerList<Events>
>;
export interface Emitter<Events extends Record<EventType, unknown>> {
all: EventHandlerMap<Events>;
on<Key extends keyof Events>(type: Key, handler: Handler<Events[Key]>): void;
on(type: '*', handler: WildcardHandler<Events>): void;
off<Key extends keyof Events>(type: Key, handler?: Handler<Events[Key]>): void;
off(type: '*', handler: WildcardHandler<Events>): void;
emit<Key extends keyof Events>(type: Key, event: Events[Key]): void;
emit<Key extends keyof Events>(type: undefined extends Events[Key] ? Key : never): void;
}
/**
* Mitt: Tiny (~200b) functional event emitter / pubsub.
* @name mitt
* @returns {Mitt}
*/
export default function mitt<Events extends Record<EventType, unknown>>(all?: EventHandlerMap<Events>): Emitter<Events> {
type GenericEventHandler = Handler<Events[keyof Events]> | WildcardHandler<Events>;
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<Key extends keyof Events>(type: Key, handler: GenericEventHandler) {
const handlers: Array<GenericEventHandler> | undefined = all!.get(type);
if (handlers) {
handlers.push(handler);
} else {
all!.set(type, [handler] as EventHandlerList<Events[keyof Events]>);
}
},
/**
* Remove an event handler for the given type.
* If `handler` is omitted, all handlers of the given type are removed.
* @param {string|symbol} type Type of event to unregister `handler` from (`'*'` to remove a wildcard handler)
* @param {Function} [handler] Handler function to remove
* @memberOf mitt
*/
off<Key extends keyof Events>(type: Key, handler?: GenericEventHandler) {
const handlers: Array<GenericEventHandler> | undefined = all!.get(type);
if (handlers) {
if (handler) {
handlers.splice(handlers.indexOf(handler) >>> 0, 1);
} else {
all!.set(type, []);
}
}
},
/**
* 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<Key extends keyof Events>(type: Key, evt?: Events[Key]) {
let handlers = all!.get(type);
if (handlers) {
(handlers as EventHandlerList<Events[keyof Events]>).slice().map((handler) => {
handler(evt!);
});
}
handlers = all!.get('*');
if (handlers) {
(handlers as WildCardEventHandlerList<Events>).slice().map((handler) => {
handler(type, evt!);
});
}
},
};
}
+19
View 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') {
const dynamicViews = getViews();
Object.keys(dynamicViews).forEach((key) => {
// 处理动态页面的key,使其可以让路由识别
const newKey = key.replace('./src/views', '../../views');
packageViews[newKey] = dynamicViews[key];
});
}
}
+47
View 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;
// }
+524
View File
@@ -0,0 +1,524 @@
import { FormSchema } from '/@/components/Form';
import { message } from 'ant-design-vue';
import { defHttp } from '/@/utils/http/axios';
import { getApi, getDepartApi, getDepartParams } from '/@/utils/medicalUtils';
enum Api {
allSecondaryDepartsNew = '/health-system/sys/sysDepart/allSecondaryDepartsNew',
getThirdDepartsNew = '/health-system/sys/sysDepart/getThirdDepartsNew',
allSecondaryDepartsNewBack = '/sys/sysDepart/allSecondaryDepartsNewBack',
getThreeDepartByOrgCode = '/sys/sysDepart/getThreeDepartByOrgCode',
}
export const allSecondaryDepartsNew = (params) => defHttp.get({ url: Api.allSecondaryDepartsNew, params });
export const allSecondaryDepartsNewBack = (params) => defHttp.get({ url: Api.allSecondaryDepartsNewBack, params });
export const getThirdDepartsNew = (params) => defHttp.get({ url: Api.getThirdDepartsNew, params });
export const getThreeDepartByOrgCode = (params) => defHttp.get({ url: Api.getThreeDepartByOrgCode, params });
// 根据orgCode搜索
// @ts-ignore
export const orgSearchInfoByCode = (orgCode1: string, orgCode2: string, orgCode = 'orgCode', code1Required = false, code2Required = false) => {
return [
{
label: '单位名称',
field: orgCode1,
component: 'ApiSelect',
componentProps: ({ formModel }) => {
return {
api: allSecondaryDepartsNew,
resultField: 'result',
labelField: 'departName',
valueField: 'orgCode',
placeholder: '请选择单位',
showSearch: true,
showDefaultValue: false,
filterOption: (input: string, option: any): boolean => {
const str: string = input.trim().toLowerCase();
return option.label.toLowerCase().indexOf(str) >= 0;
},
onChange: (val) => {
formModel[orgCode2] = '';
formModel[orgCode] = val;
},
onDeselect: () => {
formModel[orgCode1] = '';
formModel[orgCode2] = '';
formModel[orgCode] = '';
},
getPopupContainer: () => document.body,
};
},
required: code1Required,
},
{
label: '部门名称',
field: orgCode2,
component: 'ApiSelect',
componentProps: ({ formModel }) => {
return {
api: getThirdDepartsNew,
resultField: 'list',
labelField: 'departName',
valueField: 'orgCode',
placeholder: '请选择部门',
showSearch: true,
showDefaultValue: false,
getPopupContainer: () => document.body,
params: {
idOrCode: formModel[orgCode1] || 'xasd',
},
onFocus: () => {
if (!formModel[orgCode1]) {
return message.warn('请先选择单位!');
}
},
filterOption: (input: string, option: any): boolean => {
const str: string = input.trim().toLowerCase();
return option.label.toLowerCase().indexOf(str) >= 0;
},
onDeselect: () => {
formModel[orgCode] = formModel[orgCode1];
formModel[orgCode2] = '';
},
onChange: (val) => {
if (val) {
formModel[orgCode] = val;
}
},
};
},
required: code2Required,
},
{
label: '',
field: orgCode,
component: 'Input',
show: false,
},
] as FormSchema[];
};
export const formOrgCode = (orgCode1: string, orgCode2: string, orgCode = 'orgCode') => {
return [
{
label: '单位名称',
field: orgCode1,
component: 'ApiSelect',
required: true,
componentProps: ({ formModel }) => {
return {
api: allSecondaryDepartsNew,
resultField: 'result',
labelField: 'departName',
valueField: 'orgCode',
placeholder: '请选择单位',
showSearch: true,
showDefaultValue: false,
filterOption: (input: string, option: any): boolean => {
const str: string = input.trim().toLowerCase();
return option.label.toLowerCase().indexOf(str) >= 0;
},
onChange: (val) => {
formModel[orgCode2] = '';
formModel[orgCode] = val;
},
// afterFetch: (data) => {
// if (data.length === 1) {
// formModel[orgCode1] = data[0].orgCode;
// formModel[orgCode] = data[0].orgCode;
// }
// },
onDeselect: () => {
formModel[orgCode1] = '';
formModel[orgCode2] = '';
formModel[orgCode] = '';
},
};
},
},
{
label: '部门名称',
field: orgCode2,
component: 'ApiSelect',
required: true,
componentProps: ({ formModel }) => {
return {
api: getThirdDepartsNew,
resultField: 'list',
labelField: 'departName',
valueField: 'orgCode',
placeholder: '请选择部门',
showSearch: true,
showDefaultValue: false,
params: {
idOrCode: formModel[orgCode1] || 'xasd',
},
onFocus: () => {
if (!formModel[orgCode1]) {
return message.warn('请先选择单位!');
}
},
filterOption: (input: string, option: any): boolean => {
const str: string = input.trim().toLowerCase();
return option.label.toLowerCase().indexOf(str) >= 0;
},
onDeselect: () => {
formModel[orgCode] = formModel[orgCode1];
},
onChange: (val) => {
if (val) {
formModel[orgCode] = val;
}
},
};
},
},
{
label: '',
field: orgCode,
component: 'Input',
show: false,
},
] as FormSchema[];
};
export const orgSearchInfoById = (orgCode1: string, orgCode2: string, orgCode = 'departId') => {
return [
{
label: '单位名称',
field: 'orgCode1',
component: 'Select',
componentProps: ({ formModel, schema }) => {
return {
api: allSecondaryDepartsNew,
resultField: 'list',
labelField: 'departName',
valueField: 'id',
placeholder: '请选择单位',
showSearch: true,
filterOption: (input: string, option: any): boolean => {
const str: string = input.trim().toLowerCase();
return option.departName.toLowerCase().indexOf(str) >= 0;
},
onChange: (val) => {
if (val) {
formModel[orgCode2] = '';
}
},
// afterFetch: (data) => {
// if (data.length === 1) {
// schema.show = false;
// formModel[orgCode1] = data[0].orgCode;
// }
// },
onDeselect: () => {
formModel[orgCode1] = '';
formModel[orgCode2] = '';
},
};
},
},
{
label: '部门名称',
field: 'orgCode2',
component: 'ApiSelect',
componentProps: ({ formModel }) => {
return {
api: getThirdDepartsNew,
resultField: 'list',
labelField: 'departName',
valueField: 'id',
placeholder: '请选择部门',
showSearch: true,
params: {
idOrCode: formModel?.orgCode1 || '',
},
onFocus: () => {
if (!formModel.orgCode1) {
return message.warn('请先选择单位!');
}
},
filterOption: (input: string, option: any): boolean => {
const str: string = input.trim().toLowerCase();
return option.label.toLowerCase().indexOf(str) >= 0;
},
onDeselect: () => {
formModel.orgCode = formModel.orgCode1;
},
onChange: (val) => {
if (val) {
formModel.orgCode = val;
}
},
};
},
},
{
label: '',
field: orgCode,
component: 'Input',
show: false,
},
] as FormSchema[];
};
interface OrgInfoBase {
orgName?: string;
orgField?: string;
orgParams?: object;
orgResultField?: string;
orgLabelField?: string;
orgValueField?: string;
orgPlaceholder?: string;
orgMessage?: string;
orgDisabled?: boolean;
deptName?: string;
deptFiled?: string;
deptResultField?: string;
deptLabelField?: string;
deptValueField?: string;
deptPlaceholder?: string;
deptMessage?: string;
departParams?: string;
deptDisabled?: boolean;
orgCode?: string;
orgRequired?: boolean;
orgApi?: Function;
deptApi?: Function;
thirdIsShow?: boolean;
deptRequored?: boolean;
}
const OrgInfo: OrgInfoBase = {
orgName: '单位名称',
orgField: 'orgCode1',
orgParams: {},
orgResultField: 'result',
orgLabelField: 'departName',
orgValueField: 'orgCode',
orgPlaceholder: '请选择单位',
orgMessage: '请先选择单位',
orgDisabled: false,
deptName: '部门名称',
deptFiled: 'orgCode2',
deptResultField: 'result',
deptLabelField: 'departName',
deptValueField: 'orgCode',
deptPlaceholder: '请选择部门',
deptMessage: '请先选择部门',
departParams: getDepartParams(),
deptDisabled: false,
thirdIsShow: true,
orgCode: 'orgCode',
orgRequired: false,
deptRequored: false,
orgApi: getApi(),
deptApi: getDepartApi(),
};
export const orgSearchInfo = (orgInfo: OrgInfoBase = {}) => {
const {
orgName,
orgField,
orgParams,
orgResultField,
orgLabelField,
orgValueField,
orgPlaceholder,
orgDisabled,
deptName,
deptFiled,
deptResultField,
deptLabelField,
deptValueField,
deptPlaceholder,
deptDisabled,
orgCode,
orgApi,
deptApi,
deptMessage,
departParams,
thirdIsShow,
orgRequired,
deptRequored,
} = { ...OrgInfo, ...orgInfo };
return [
{
label: orgName,
field: orgField,
component: 'ApiSelect',
required: orgRequired,
componentProps: ({ formModel }) => {
return {
api: orgApi,
resultField: orgResultField,
labelField: orgLabelField,
valueField: orgValueField,
placeholder: orgPlaceholder,
showSearch: true,
showDefaultValue: false,
params: orgParams,
disabled: false,
getPopupContainer: () => document.body,
filterOption: (input: string, option: any): boolean => {
const str: string = input.trim().toLowerCase();
return option.label.toLowerCase().indexOf(str) >= 0;
},
onChange: (val) => {
if (val) {
formModel[deptFiled] = undefined;
formModel[orgCode] = val;
}
},
afterFetch: (data) => {
data.forEach((item) => {
item['disabled'] = orgDisabled;
});
return data;
},
onDeselect: () => {
formModel[orgField] = undefined;
formModel[deptFiled] = undefined;
formModel[orgCode] = undefined;
},
};
},
},
{
label: deptName,
field: deptFiled,
component: 'ApiSelect',
required: deptRequored,
componentProps: ({ formModel }) => {
return {
api: deptApi,
resultField: deptResultField,
labelField: deptLabelField,
valueField: deptValueField,
placeholder: deptPlaceholder,
showSearch: true,
showDefaultValue: false,
disabled: false,
getPopupContainer: () => document.body,
params: {
[departParams]: formModel[orgField] || 'xasd',
},
onFocus: () => {
if (!formModel[orgField]) {
return message.warn(deptMessage);
}
},
filterOption: (input: string, option: any): boolean => {
const str: string = input.trim().toLowerCase();
return option.label.toLowerCase().indexOf(str) >= 0;
},
afterFetch: (data) => {
data.forEach((item) => {
item['disabled'] = deptDisabled;
});
return data;
},
onDeselect: () => {
formModel[orgCode] = formModel[orgField];
},
onChange: (val) => {
if (val) {
formModel[orgCode] = val;
}
},
};
},
},
{
label: '',
field: orgCode,
component: 'Input',
show: false,
ifShow: thirdIsShow,
},
] as FormSchema[];
};
// 根据orgCode搜索
export const orgCodeSearchNotShowDepart = (orgCode1: string, orgCode2: string, orgCode = 'orgCode') => {
return [
{
label: '单位名称',
field: orgCode1,
component: 'ApiSelect',
componentProps: ({ formModel, schema }) => {
return {
api: allSecondaryDepartsNew,
resultField: 'result',
labelField: 'departName',
valueField: 'orgCode',
placeholder: '请选择单位',
showSearch: true,
showDefaultValue: false,
filterOption: (input: string, option: any): boolean => {
const str: string = input.trim().toLowerCase();
return option.label.toLowerCase().indexOf(str) >= 0;
},
onChange: (val) => {
if (val) {
formModel[orgCode2] = '';
formModel[orgCode] = val;
}
},
onDeselect: () => {
formModel[orgCode1] = '';
formModel[orgCode2] = '';
formModel[orgCode] = '';
},
afterFetch: (data) => {
console.log('schema', schema);
if (data.length == 1) {
formModel[orgCode2] = '';
formModel[orgCode1] = data[0].orgCode;
}
},
};
},
},
{
label: '部门名称',
field: orgCode2,
component: 'ApiSelect',
componentProps: ({ formModel }) => {
return {
api: getThirdDepartsNew,
resultField: 'list',
labelField: 'departName',
valueField: 'orgCode',
placeholder: '请选择部门',
showSearch: true,
showDefaultValue: false,
params: {
idOrCode: formModel[orgCode1] || 'xasd',
},
onFocus: () => {
if (!formModel[orgCode1]) {
return message.warn('请先选择单位!');
}
},
filterOption: (input: string, option: any): boolean => {
const str: string = input.trim().toLowerCase();
return option.label.toLowerCase().indexOf(str) >= 0;
},
onDeselect: () => {
formModel[orgCode] = formModel[orgCode1];
},
onChange: (val) => {
if (val) {
formModel[orgCode] = val;
}
},
};
},
},
{
label: '',
field: orgCode,
component: 'Input',
show: false,
},
] as FormSchema[];
};
+34
View File
@@ -0,0 +1,34 @@
import { CSSProperties, VNodeChild } from 'vue';
import { createTypes, VueTypeValidableDef, VueTypesInterface } 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 propTypes = createTypes({
func: undefined,
bool: undefined,
string: undefined,
number: undefined,
object: undefined,
integer: undefined,
}) as PropTypes;
propTypes.extend([
{
name: 'style',
getter: true,
type: [String, Object],
default: undefined,
},
{
name: 'VNodeChild',
getter: true,
type: undefined,
},
]);
export { propTypes };
+158
View File
@@ -0,0 +1,158 @@
// 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;
+12
View File
@@ -0,0 +1,12 @@
import { defHttp } from '/@/utils/http/axios';
enum Api {
allSecondaryDepartsNew = '/health-system/sys/sysDepart/allSecondaryDepartsNew',
getThirdDepartsNew = '/health-system/sys/sysDepart/getThirdDepartsNew',
getFourthDepartsNew = '/health-system/sys/sysDepart/getFourthDepartsNew',
}
export const allSecondaryDepartsNew = (params) => defHttp.get({ url: Api.allSecondaryDepartsNew, params });
export const getThirdDepartsNew = (params) => defHttp.get({ url: Api.getThirdDepartsNew, params });
export const getFourthDepartsNew = (params) => defHttp.get({ url: Api.getFourthDepartsNew, params });
+45
View File
@@ -0,0 +1,45 @@
// 获取方法,再其他地方调用
import { ref } from 'vue';
import { useUserStore } from '/@/store/modules/user';
export class FormDepartment {
public store: any;
public readonly userInfo: any;
constructor() {
this.store = useUserStore() || {};
this.userInfo = this.store?.getUserInfo || {};
}
}
// 获取disabled,并且修改disabled
export function initD(type = false, value = '') {
const user = new FormDepartment();
const disabled = ref(type);
const val = ref(value);
disabled.value = false;
val.value = '';
const getVal = (v) => {
if (user.userInfo.roleCodes.indexOf('admin') === -1 && user.userInfo.roleCodes.indexOf('system') === -1) {
disabled.value = true;
if ('orgCode' === v) {
val.value = user.userInfo?.departCodes;
} else if ('id' === v) {
val.value = user.userInfo?.departIds;
}
} else {
disabled.value = false;
val.value = '';
}
};
// if (user.userInfo.roleCodes !== 'admin' && user.userInfo.roleCodes !== 'system') {
// disabled.value = true;
// }
return {
disabled,
getVal,
val,
};
}
+41
View File
@@ -0,0 +1,41 @@
// 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
View 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);
}