commit:升级到vue3,更新最近工作流技术栈,支持sa-token

This commit is contained in:
Jerry
2024-07-05 22:42:33 +08:00
parent bbcc608584
commit 565ecb6371
1751 changed files with 236790 additions and 0 deletions

View File

@@ -0,0 +1,73 @@
import { Delete, Search, Edit, Plus, Refresh, Picture } from '@element-plus/icons-vue';
import { EpPropMergeType } from 'element-plus/es/utils';
import { useDate } from '@/common/hooks/useDate';
import { usePermissions } from '@/common/hooks/usePermission';
import { Dialog } from '@/components/Dialog';
import { useDropdown } from '@/common/hooks/useDropdown';
import { useTable } from '@/common/hooks/useTable';
/**
* 大部分管理页面需要的组件及公共属性和方法
*
* @returns 图标Plus、Delete、Edit、Search、对话框组件Dialog、defaultFormItemSize、mainContextHeight、checkPermCodeExist、下拉数据勾子useDropdown、表格数据勾子useTable
*/
export const useCommon = () => {
const mainContextHeight = inject('mainContextHeight', ref(200));
const clientHeight = inject('documentClientHeight', ref(200));
const { checkPermCodeExist } = usePermissions();
const { formatDateByStatsType, getDateRangeFilter } = useDate();
/**
* 将输入的值转换成指定的类型
* @param {Any} value
* @param {String} type 要转换的类型integer、float、boolean、string
*/
function parseParams(value: number | string | boolean | undefined, type = 'string') {
if (value == null) return value;
switch (type) {
case 'integer':
return Number.parseInt(value as string);
case 'float':
return Number.parseFloat(value as string);
case 'boolean':
return value === 'true' || value;
default:
return String(value);
}
}
/**
* 将输入值转换为执行的类型数组
* @param {Array} value 输入数组
* @param {String} type 要转换的类型integer、float、boolean、string
*/
function parseArrayParams(value: Array<number | string | boolean>, type = 'string') {
if (Array.isArray(value)) {
return value.map(item => {
return parseParams(item, type);
});
} else {
return [];
}
}
return {
Delete,
Search,
Edit,
Plus,
Refresh,
Picture,
Dialog,
mainContextHeight,
clientHeight,
checkPermCodeExist,
formatDateByStatsType,
getDateRangeFilter,
useDropdown,
useTable,
parseParams,
parseArrayParams,
};
};

View File

@@ -0,0 +1,92 @@
import { formatDate, parseDate } from '../utils';
const allowStatsType = ['time', 'datetime', 'day', 'month', 'year'];
export const useDate = () => {
/**
* 格式化日期
* @param {Date|String} date 要格式化的日期
* @param {String} statsType 输出日期类型
* @param {String} format 输入日期的格式
*/
const formatDateByStatsType = (
date: string | number | Date,
statsType = 'day',
format = 'YYYY-MM-DD',
) => {
if (date == null) return undefined;
if (statsType == null) return date;
statsType = allowStatsType.indexOf(statsType) === -1 ? 'day' : statsType;
if (statsType === 'datetime') format = 'YYYY-MM-DD HH:mm:ss';
//console.log('date', statsType, format, date);
const tempDate = date instanceof Date ? date : parseDate(date, format);
//console.log('tempDate', tempDate);
if (!tempDate) return undefined;
switch (statsType) {
case 'time':
return formatDate(tempDate, 'HH:mm:ss');
case 'datetime':
return formatDate(tempDate, 'YYYY-MM-DD HH:mm:ss');
case 'day':
return formatDate(tempDate, 'YYYY-MM-DD');
case 'month':
return formatDate(tempDate, 'YYYY-MM');
case 'year':
return formatDate(tempDate, 'YYYY');
default:
return formatDate(tempDate, 'YYYY-MM-DD');
}
};
/**
* 根据输入的日期获得日期范围例如输入2019-12-12输出['2019-12-12 00:00:00', '2019-12-12 23:59:59']
* @param {Date|String} date 要转换的日期
* @param {String} statsType 转换类型day, month, year
* @param {String} format 输出格式
*/
const getDateRangeFilter = (date: string, statsType = 'day', format = 'YYYY-MM-dd HH:mm:ss') => {
if (date == null) return [];
statsType = allowStatsType.indexOf(statsType) === -1 ? 'day' : statsType;
date = date.substring(0, date.indexOf(' '));
const tempList = date.split('-');
const year = Number.parseInt(tempList[0]);
const month = Number.parseInt(tempList[1]);
const day = Number.parseInt(tempList[2]);
if (isNaN(year) || isNaN(month) || isNaN(day)) {
return [];
}
const tempDate = new Date(year, month - 1, day);
// 判断是否正确的日期
if (isNaN(tempDate.getTime())) return [];
tempDate.setHours(0, 0, 0, 0);
let retDate: Date[] = [];
// TODO 如果类型为'time', 'datetime'会出错
switch (statsType) {
case 'day':
retDate = [new Date(tempDate), new Date(tempDate.setDate(tempDate.getDate() + 1))];
break;
case 'month':
tempDate.setDate(1);
retDate = [new Date(tempDate), new Date(tempDate.setMonth(tempDate.getMonth() + 1))];
break;
case 'year':
tempDate.setDate(1);
tempDate.setMonth(0);
retDate = [new Date(tempDate), new Date(tempDate.setFullYear(tempDate.getFullYear() + 1))];
break;
}
retDate[1] = new Date(retDate[1].getTime() - 1);
return [formatDate(retDate[0], format), formatDate(retDate[1], format)];
};
return {
formatDateByStatsType,
getDateRangeFilter,
};
};

View File

@@ -0,0 +1,60 @@
import { ElMessage } from 'element-plus';
import { get } from '../http/request';
/**
* 文件下载Hooks
*
* @returns downloadFile
*/
export const useDownload = () => {
/**
* 下载上传的文件
* @param {*} url 下载文件的url
* @param {*} fileName 下载文件名
*/
const downloadFile = (url: string, fileName: string) => {
get<Blob>(
url,
{},
{},
{
responseType: 'blob',
transformResponse: function (data) {
return data;
},
},
)
.then(res => {
console.log('============= download', res);
const data = res instanceof Blob ? res : res.data;
if (data instanceof Blob) {
const url = window.URL.createObjectURL(data);
const link = document.createElement('a');
link.style.display = 'none';
link.href = url;
link.setAttribute('download', fileName);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
} else {
ElMessage.error('下载文件失败');
}
})
.catch(e => {
console.error('============= download', e);
if (e instanceof Blob) {
const reader = new FileReader();
reader.onload = function () {
ElMessage.error(
reader.result ? JSON.parse(reader.result.toString()).errorMessage : '下载文件失败',
);
};
reader.readAsText(e);
} else {
ElMessage.error('下载文件失败');
}
});
};
return { downloadFile };
};

View File

@@ -0,0 +1,85 @@
import { DropdownOptions } from '../types/list';
import { treeDataTranslate } from '../utils';
const defaultOptions = {
isTree: false,
idKey: 'id',
parentIdKey: 'parentId',
};
export const useDropdown = <T>(options: DropdownOptions<T>) => {
const loading = ref(false);
let loaded = false;
const dropdownList: Ref<T[]> = ref([]);
const finalOptions = { ...defaultOptions, ...options };
const { loadData, isTree, idKey, parentIdKey } = finalOptions;
//console.log('dropdown', loadData, isTree, idKey, parentIdKey);
const loadDropdownData = (): Promise<T[]> => {
return new Promise((resolve, reject) => {
if (!loaded && !loading.value) {
loadData()
.then(res => {
console.log(`loadDropdownData 加载了${res.dataList.length}条数据`);
loaded = true;
dropdownList.value = isTree
? treeDataTranslate(res.dataList, idKey, parentIdKey)
: res.dataList;
resolve(dropdownList.value);
})
.catch(e => {
reject(e);
})
.finally(() => {
loading.value = false;
});
} else {
resolve(dropdownList.value);
}
});
};
/**
* 下拉框显示或隐藏时调用
* @param {Boolean} isShow 正在显示或者隐藏
*/
const onVisibleChange = (isShow: boolean): Promise<T[]> => {
return new Promise((resolve, reject) => {
if (isShow && !loaded && !loading.value) {
loadDropdownData()
.then(res => {
resolve(res);
})
.catch(e => {
reject(e);
});
} else {
resolve(dropdownList.value);
}
});
};
/**
* 刷新列表
* @param immediate 是否立即刷新默认为true
* @return Promise<T[] | void> 立即执行时返回最新数据
*/
const refresh = (immediate = true): Promise<T[] | void> => {
loaded = false;
if (immediate) {
return loadDropdownData();
}
dropdownList.value = [];
return Promise.resolve();
};
return {
loading,
dropdownList,
onVisibleChange,
refresh,
};
};

View File

@@ -0,0 +1,25 @@
import { useLoginStore } from '@/store';
import { getAppId } from '../utils';
export const usePermissions = () => {
const loginStorage = useLoginStore();
const checkPermCodeExist = (permCode: string) => {
//console.log(permCode);
if (getAppId() != null && getAppId() !== '') return true;
if (loginStorage.userInfo == null) {
return false;
}
if (loginStorage.userInfo.permCodeList != null) {
return loginStorage.userInfo.permCodeList.indexOf(permCode) != -1;
} else {
return loginStorage.userInfo.isAdmin;
}
};
return {
checkPermCodeExist,
};
};

View File

@@ -0,0 +1,197 @@
/*
* 表格数据(分页)钩子
* 提供表格数据查询、分页基础数据和回调方法
*/
import { ElMessage } from 'element-plus';
import { OrderInfo, RequestParam, TableOptions } from '../types/pagination';
import { SortInfo } from '../types/sortinfo';
// 默认分页大小
const DEFAULT_PAGE_SIZE = 10;
export const useTable = <T>(options: TableOptions<T>) => {
const orderInfo: OrderInfo = {
fieldName: options.orderFieldName,
asc: options.ascending || false,
dateAggregateBy: options.dateAggregateBy,
};
const loading = ref(false);
const currentPage = ref(1);
const totalCount = ref(0);
const dataList: Ref<T[]> = ref([]);
const pageSize: Ref<number> = ref(options.pageSize || DEFAULT_PAGE_SIZE);
if (!options.verifyTableParameter) {
options.verifyTableParameter = () => true;
}
const { loadTableData, paged, verifyTableParameter } = options;
let oldPage = 0;
let oldPageSize: number = options.pageSize || DEFAULT_PAGE_SIZE;
if (pageSize.value <= 0) {
console.warn(`pagesize的值不能小于等于0被设置为默认值${DEFAULT_PAGE_SIZE}`);
pageSize.value = DEFAULT_PAGE_SIZE;
}
// 监听pageSize变化
watch(pageSize, (newVal, oldVal) => {
//console.log('pageSize change', newVal, oldVal);
if (newVal != oldVal) {
loadData(1, newVal)
.then(() => {
oldPage = 1;
oldPageSize = newVal;
currentPage.value = 1;
})
.catch(() => {
currentPage.value = oldPage;
pageSize.value = oldVal;
});
}
});
// 监听currentPage变化
watch(currentPage, (newVal, oldVal) => {
if (newVal != oldVal) {
loadData(newVal, pageSize.value)
.then(() => {
oldPage = newVal;
})
.catch(() => {
currentPage.value = oldVal;
});
}
});
/**
* 获取表格数据
* @param pageNum 当前分页
* @param pageSize 每页数量
* @param reload 是否重新获取数据
*/
const loadData = (pageNum: number, pageSize: number, reload = false): Promise<void> => {
if (paged && !reload && oldPage == pageNum && oldPageSize == pageSize) {
console.log('数据已加载,无须重复执行');
return Promise.resolve();
}
if (paged) {
console.log(`开始加载数据, 第${pageNum}页,每页${pageSize}, 强制加载:${reload}`);
} else {
console.log(`开始加载数据, 无分页, 强制加载:${reload}`);
}
const params = {} as RequestParam;
if (orderInfo.fieldName != null) params.orderParam = [orderInfo];
if (paged) {
params.pageParam = {
pageNum,
pageSize,
};
}
return new Promise((resolve, reject) => {
loading.value = true;
loadTableData(params)
.then(res => {
//console.log(res.dataList, res.totalCount);
// vxetable需要用到对象的hasOwnerProperty方法因此需要重新构造对象
dataList.value = res.dataList.map((item: T) => {
return { ...item };
});
totalCount.value = res.totalCount;
console.log(`本次加载${res.dataList.length}条数据,共有${res.totalCount}条数据`);
resolve();
})
.catch(e => {
reject(e);
})
.finally(() => {
loading.value = false;
//console.log('加载数据完毕');
});
});
};
const onPageSizeChange = (size: number) => {
pageSize.value = size;
};
const onCurrentPageChange = (newVal: number) => {
currentPage.value = newVal;
};
/**
* 表格排序字段变化
* @param {String} prop 排序字段的字段名
* @param {string} field 排序字段的字段名
* @param {String} order 正序还是倒序
*/
const onSortChange = ({ prop, field, order }: SortInfo) => {
//console.log(prop, field, order);
orderInfo.fieldName = prop || field;
orderInfo.asc = order == 'ascending' || order == 'asc';
refreshTable();
};
/**
* 刷新表格数据
* @param {Boolean} research 是否按照新的查询条件重新查询调用verify函数
* @param {Integer} pageNum 当前页面
* @param showMsg 是否显示查询结果成功与否消息
*/
const refreshTable = (research = false, pageNum = 0, showMsg = false) => {
//console.log(research, pageNum, showMsg);
let reload = false;
if (research) {
if (!verifyTableParameter()) return;
reload = true;
}
if (pageNum && pageNum != currentPage.value) {
loadData(pageNum, pageSize.value, reload)
.then(() => {
oldPage = currentPage.value = pageNum;
if (showMsg) ElMessage.success('查询成功');
})
.catch((e: Error) => {
console.warn('获取表格数据出错了', e);
currentPage.value = oldPage;
if (showMsg) ElMessage.error('查询失败' + e.message);
});
} else {
loadData(currentPage.value, pageSize.value, true)
.then(() => {
if (showMsg) ElMessage.success('查询成功');
})
.catch((e: Error) => {
console.warn('获取表格数据出错了', e);
if (showMsg) ElMessage.error('查询失败' + e.message);
});
}
};
/**
* 获取每一行的index信息
* @param {Integer} index 表格在本页位置
*/
const getTableIndex = (index: number) => {
return paged ? (currentPage.value - 1) * pageSize.value + (index + 1) : index + 1;
};
const clearTable = () => {
oldPage = 0;
currentPage.value = 1;
totalCount.value = 0;
dataList.value = [];
};
return {
loading,
currentPage,
totalCount,
pageSize,
dataList,
clearTable,
getTableIndex,
onPageSizeChange,
onCurrentPageChange,
onSortChange,
refreshTable,
};
};

View File

@@ -0,0 +1,160 @@
import { useLayoutStore } from '@/store';
import { ANY_OBJECT } from '@/types/generic';
import { getAppId, getToken } from '../utils';
import { post } from '../http/request';
import { useUrlBuilder } from './useUrl';
export const useUpload = () => {
const { buildGetUrl, requestUrl } = useUrlBuilder();
/**
* 解析返回的上传文件数据
* @param {String} jsonData 上传文件数据,[{name, downloadUri, filename}]
* @param {Object} params 上传文件的参数
* @returns {Array} 上传文件信息,[{name, downloadUri, filename, url}]
*/
const parseUploadData = (jsonData: string, params: ANY_OBJECT) => {
let pathList = [];
if (jsonData != null) {
try {
pathList = JSON.parse(jsonData);
} catch (e) {
console.error(e);
}
}
return Array.isArray(pathList)
? pathList.map(item => {
const downloadParams = { ...params };
downloadParams.filename = item.filename;
return {
...item,
url: getUploadFileUrl(item, downloadParams),
};
})
: [];
};
/**
* 获得上传文件url
* @param {*} item 上传文件
* @param {*} params 上传文件的参数
*/
const getUploadFileUrl = (item: { downloadUri: string }, params?: ANY_OBJECT) => {
if (item == null || item.downloadUri == null) {
return null;
} else {
const currentMenuId = useLayoutStore().currentMenuId;
const query = { ...params };
query.Authorization = getToken();
query.MenuId = currentMenuId;
query.AppCode = getAppId();
return buildGetUrl(item.downloadUri, query);
}
};
const getUploadHeaders = computed(() => {
const token = getToken();
const appId = getAppId();
const currentMenuId = useLayoutStore().currentMenuId;
const header: ANY_OBJECT = {
Authorization: token,
MenuId: currentMenuId,
};
if (appId) header.AppCode = appId;
return header;
});
/**
* 获得上传接口
* @param {*} url 上传路径
*/
const getUploadActionUrl = (url: string) => {
return buildGetUrl(url, null);
};
/**
* 上传文件
* @param {*} url 请求的url
* @param {*} params 请求参数
*/
const fetchUpload = (url: string, params: ANY_OBJECT) => {
return new Promise<ANY_OBJECT>((resolve, reject) => {
post(
requestUrl(url),
{},
{ showError: true },
{
data: params,
headers: {
'Content-Type': 'multipart/form-data',
},
transformRequest: [
function (data: ANY_OBJECT) {
const formData = new FormData();
Object.keys(data).map(key => {
formData.append(key, data[key]);
});
return formData;
},
],
},
)
.then((res: ANY_OBJECT) => {
console.log('uploaded file fetchUpload', res);
if (res.data && res.success) {
resolve(res.data);
}
})
.catch(e => {
reject(e);
});
});
};
/**
* 获得上传文件url列表
* @param {*} jsonData 上传文件数据,[{name, downloadUri, filename}]
* @param {*} params 上传文件的参数
* @returns {Array} 文件url列表
*/
const getPictureList = (jsonData: string, params: ANY_OBJECT) => {
const tempList = parseUploadData(jsonData, params);
if (Array.isArray(tempList)) {
return tempList.map(item => item.url);
} else {
return [];
}
};
/**
* 将选中文件信息格式化成json信息
* @param {Array} fileList 上传文件列表,[{name, fileUrl, data}]
*/
const fileListToJson = (fileList: ANY_OBJECT[]) => {
if (Array.isArray(fileList)) {
return JSON.stringify(
fileList.map(item => {
return {
name: item.name,
downloadUri: item.downloadUri || item.response.data.downloadUri,
filename: item.filename || item.response.data.filename,
uploadPath: item.uploadPath || item.response.data.uploadPath,
};
}),
);
} else {
return undefined;
}
};
return {
getUploadFileUrl,
parseUploadData,
getUploadHeaders,
getUploadActionUrl,
fetchUpload,
getPictureList,
fileListToJson,
};
};

View File

@@ -0,0 +1,25 @@
import { UploadFile } from 'element-plus';
export const useUploadWidget = (maxFileCount = 1) => {
const fileList = ref<UploadFile[]>([]);
const maxCount = ref(maxFileCount);
/**
* 上传文件列表改变
* @param {Object} uploadFile 改变的文件
* @param {Array} uploadFiles 改变后的文件列表
*/
const onFileChange = (uploadFile: UploadFile | null, uploadFiles: UploadFile[] | null) => {
if (uploadFiles && uploadFiles.length > 0) {
if (maxFileCount == 1) {
fileList.value = [uploadFiles[uploadFiles.length - 1]];
} else {
fileList.value = uploadFiles;
}
} else {
fileList.value = [];
}
};
return { fileList, onFileChange, maxCount };
};

View File

@@ -0,0 +1,40 @@
import { ANY_OBJECT } from '@/types/generic';
import { objectToQueryString } from '../utils';
export const useUrlBuilder = () => {
/**
* 请求地址统一处理/组装
* @param actionName 方法名称
* @param params 请求参数
* @returns 请求全路径(含参数)
*/
const buildGetUrl = (actionName: string, params: ANY_OBJECT | null = null) => {
console.log('getUrl', actionName);
const queryString = objectToQueryString(params);
if (actionName != null && actionName !== '') {
if (actionName.substring(0, 1) === '/') actionName = actionName.substring(1);
}
return (
import.meta.env.VITE_SERVER_HOST + actionName + (queryString == null ? '' : '?' + queryString)
);
};
/**
* 请求地址统一处理/组装
* @param actionName action方法名称
*/
const requestUrl = (actionName: string) => {
console.log('requestUrl', actionName);
if (actionName) {
if (actionName.substring(0, 1) === '/') actionName = actionName.substring(1);
}
if (actionName.indexOf('http://') === 0 || actionName.indexOf('https://') === 0) {
return actionName;
} else {
return import.meta.env.VITE_SERVER_HOST + actionName;
}
};
return { buildGetUrl, requestUrl };
};

View File

@@ -0,0 +1,50 @@
import { onMounted, onUnmounted, provide, ref } from 'vue';
import { useLayoutStore } from '@/store';
import { getAppId } from '../utils';
// 屏幕宽度分界值,影响整体样式
const WIDTH = 1900;
const documentClientHeight = ref(0);
/**
* 在最顶层使用该hook一方面监听窗口大小变化同时向下面提供一个计算属性
*/
export const useWindowResize = () => {
const windowResize = () => {
//console.log('窗口尺寸发生变化');
documentClientHeight.value = document.documentElement.clientHeight;
if (window.innerWidth <= WIDTH) {
layoutStore.defaultFormItemSize = 'default';
document.body.className = 'orange-project container-default';
} else {
layoutStore.defaultFormItemSize = 'large';
document.body.className = 'orange-project container-large';
}
layoutStore.documentClientHeight = document.documentElement.clientHeight;
layoutStore.mainContextHeight = mainContextHeight.value;
};
const layoutStore = useLayoutStore();
const mainContextHeight = computed(() => {
const appId = getAppId();
if (appId == null) {
return documentClientHeight.value - (layoutStore.supportTags ? 110 : 60);
} else {
return documentClientHeight.value;
}
});
provide('documentClientHeight', documentClientHeight);
provide('mainContextHeight', mainContextHeight);
onMounted(() => {
windowResize();
window.addEventListener('resize', windowResize);
});
onUnmounted(() => {
window.removeEventListener('resize', windowResize);
});
};

View File

@@ -0,0 +1,147 @@
import axios, { AxiosInstance, AxiosPromise, AxiosResponse } from 'axios';
import JSONbig from 'json-bigint';
import { router } from '@/router';
import { getToken, setToken, getAppId } from '@/common/utils/index';
import { useLayoutStore } from '@/store';
import { Dialog } from '@/components/Dialog';
import { serverDefaultCfg } from './config';
import { ResponseDataType } from './types';
// 创建 axios 请求实例
const axiosInstance: AxiosInstance = axios.create({
baseURL: serverDefaultCfg.baseURL, // 基础请求地址
timeout: 30000, // 请求超时设置
withCredentials: true, // 跨域请求是否需要携带 cookie
headers: {
//Accept: 'application/json, text/plain, */*',
//'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/json; charset=utf-8',
deviceType: '4',
},
validateStatus: status => {
return status === 200 || status === 401; // 放行哪些状态的请求
},
transformResponse: [
function (data) {
//console.log('transformResponse', data);
if (typeof data === 'string') {
return JSONbig({ storeAsString: true }).parse(data);
} else {
return data;
}
},
],
});
// 创建请求拦截
axiosInstance.interceptors.request.use(
config => {
const token = getToken();
const appId = getAppId();
const currentMenuId = useLayoutStore().currentMenuId;
if (token != null) config.headers['Authorization'] = token;
if (appId != null && appId !== '') config.headers['AppCode'] = appId;
if (currentMenuId != null) config.headers['MenuId'] = currentMenuId;
return config;
},
error => {
Promise.reject(error);
},
);
const loginInvalid = () => {
setToken(null);
Dialog.closeAll();
router.push({ name: 'login' });
};
// 创建响应拦截
axiosInstance.interceptors.response.use(
<T>(response: AxiosResponse): AxiosPromise<ResponseDataType<T>> => {
//console.log('axios response => ', response);
const { data, status } = response;
// 如果401响应放行至此执行此逻辑
if (status === 401) {
const appId = getAppId();
if (appId == null) {
loginInvalid();
}
return Promise.reject(new Error('您未登录,或者登录已经超时,请先登录!'));
}
if (response.data && response.data.errorCode === 'UNAUTHORIZED_LOGIN') {
// 401, token失效
const appId = getAppId();
if (appId == null) {
loginInvalid();
} else {
return Promise.reject(new Error(response.data.errorMessage));
}
} else {
if (response.headers['refreshedtoken'] != null) {
setToken(response.headers['refreshedtoken']);
}
//console.log('response', response, 'blob', response.data instanceof Blob);
// 判断请求是否成功
if (!(response.data instanceof Blob) && !response.data.success) {
return Promise.reject(new Error(response.data.errorMessage || 'error'));
}
}
return data;
},
error => {
//console.warn(error);
let message = '';
if (error && error.response) {
switch (error.response.status) {
case 302:
message = '接口重定向了!';
break;
case 400:
message = '参数不正确!';
break;
case 401:
message = '您未登录,或者登录已经超时,请先登录!';
break;
case 403:
message = '您没有权限操作!';
break;
case 404:
message = `请求地址出错: ${error.response.config.url}`;
break;
case 408:
message = '请求超时!';
break;
case 409:
message = '系统已存在相同数据!';
break;
case 500:
message = '服务器内部错误!';
break;
case 501:
message = '服务未实现!';
break;
case 502:
message = '网关错误!';
break;
case 503:
message = '服务不可用!';
break;
case 504:
message = '服务暂时无法访问,请稍后再试!';
break;
case 505:
message = 'HTTP 版本不受支持!';
break;
default:
message = '异常问题,请联系管理员!';
break;
}
console.log('请求异常 ==>', message);
return Promise.reject(new Error(message));
} else {
return Promise.reject(new Error(error.message));
}
},
);
export default axiosInstance;

View File

@@ -0,0 +1,4 @@
export const serverDefaultCfg = {
baseURL: import.meta.env.VITE_SERVER_HOST, // 请求基础地址,可根据环境自定义
mockURL: '',
};

View File

@@ -0,0 +1,384 @@
import { ElMessage, ElLoading } from 'element-plus';
import { AxiosRequestConfig } from 'axios';
import { getAppId } from '@/common/utils/index';
import { ANY_OBJECT } from '@/types/generic';
import { useUrlBuilder } from '../hooks/useUrl';
import axiosInstance from './axios';
import { serverDefaultCfg } from './config';
import { RequestMethods, RequestOption, ResponseDataType } from './types';
const { requestUrl } = useUrlBuilder();
const globalConfig = {
requestOption: {
mock: false,
// 调用的时候是否显示蒙版
showMask: true,
// 是否显示公共的错误提示
showError: true,
// 是否开启节流功能同一个url不能频繁重复调用
throttleFlag: false,
// 节流间隔
throttleTimeout: 50,
} as RequestOption,
axiosOption: {
responseType: 'json',
} as AxiosRequestConfig,
};
function showErrorMessage(message: string | { showClose: boolean; message: string }) {
const appId = getAppId();
if (appId != null && appId !== '') {
if (window.parent) {
window.parent.postMessage(
{
type: 'message',
data: {
type: 'error',
text: message,
},
},
'*',
);
}
} else {
ElMessage.error(message);
}
}
/**
* 遮罩管理,多次调用支持引用计数
*/
class LoadingManager {
private options: ANY_OBJECT;
private refCount: number;
private loading: ReturnType<typeof ElLoading.service> | null;
constructor(options: ANY_OBJECT) {
this.options = options;
this.refCount = 0;
this.loading = null;
}
showMask() {
this.refCount++;
//console.log('loading >>>', this.refCount);
if (this.refCount <= 1 && this.loading == null) {
//console.log('loading do create serice');
this.loading = ElLoading.service(this.options);
}
}
hideMask() {
//console.log('loading hideMask', this.refCount);
if (this.refCount <= 1 && this.loading != null) {
this.loading.close();
this.loading = null;
//console.log('loading hideMask do close');
}
this.refCount--;
this.refCount = Math.max(0, this.refCount);
}
}
//console.log('new LoadingManager');
const loadingManager = new LoadingManager({
fullscreen: true,
background: 'rgba(0, 0, 0, 0.1)',
});
// url调用节流Set
const ajaxThrottleSet = new Set();
/**
* 核心函数,可通过它处理一切请求数据,并做横向扩展
* @param url 请求地址
* @param params 请求参数
* @param method 请求方法,只接受"get" | "delete" | "head" | "post" | "put" | "patch"
* @param requestOption 请求配置(针对当前本次请求)
* @param axiosOption axios配置(针对当前本次请求)
*/
export async function commonRequest<D>(
url: string,
params: ANY_OBJECT,
method: RequestMethods,
requestOption?: RequestOption,
axiosOption?: AxiosRequestConfig,
): Promise<ResponseDataType<D>> {
const finalOption = {
...globalConfig.requestOption,
...requestOption,
};
const { showMask, showError, throttleFlag, throttleTimeout } = finalOption;
let finalUrl = url;
// 通过mock平台可对局部接口进行mock设置
if (finalOption.mock) finalUrl = serverDefaultCfg.mockURL;
if (ajaxThrottleSet.has(finalUrl) && throttleFlag) {
return Promise.reject();
} else {
if (throttleFlag) {
ajaxThrottleSet.add(url);
setTimeout(() => {
ajaxThrottleSet.delete(url);
}, throttleTimeout || 50);
}
const finalAxiosOption = {
...globalConfig.axiosOption,
...axiosOption,
};
// get请求使用params字段
let data: ANY_OBJECT = { params };
// 非get请求使用data字段
if (method !== 'get') data = { data: params };
if (showMask) loadingManager.showMask();
try {
const result: ResponseDataType<D> = await axiosInstance({
url: finalUrl,
method,
...data,
...finalAxiosOption,
});
//console.log('result:', result);
if (result instanceof Blob || result.success) {
return Promise.resolve(result);
} else {
if (showError) {
showErrorMessage({
showClose: true,
message: result.errorMessage ? result.errorMessage : '数据请求失败',
});
}
return Promise.reject(result);
}
} catch (error) {
console.warn('请求异常', error);
if (showError) {
const err = error as Error;
showErrorMessage({
showClose: true,
message: err ? err.message : '网络请求错误',
});
}
return Promise.reject(error);
} finally {
loadingManager.hideMask();
}
}
}
/***
* @param url 请求地址
* @param params 请求参数
* @param options 请求设置(showMask-是否显示Loading层默认为trueshowError-是否显示错误信息默认为truethrottleFlag-是否开户节流默认为falsethrottleTimeout-节流时效默认为50毫秒)
* @param axiosOption
*/
export const get = async <D>(
url: string,
params: ANY_OBJECT,
options?: RequestOption,
axiosOption?: AxiosRequestConfig,
) => {
return await commonRequest<D>(url, params, 'get', options, axiosOption);
};
/***
* @param url 请求地址
* @param params 请求参数
* @param options 请求设置(showMask-是否显示Loading层默认为trueshowError-是否显示错误信息默认为truethrottleFlag-是否开户节流默认为falsethrottleTimeout-节流时效默认为50毫秒)
* @param axiosOption
*/
export const post = async <D>(
url: string,
params: ANY_OBJECT,
options?: RequestOption,
axiosOption?: AxiosRequestConfig,
) => {
return await commonRequest<D>(url, params, 'post', options, axiosOption);
};
/***
* @param url 请求地址
* @param params 请求参数
* @param options 请求设置(showMask-是否显示Loading层默认为trueshowError-是否显示错误信息默认为truethrottleFlag-是否开户节流默认为falsethrottleTimeout-节流时效默认为50毫秒)
* @param axiosOption
*/
export const put = async <D>(
url: string,
params: ANY_OBJECT,
options?: RequestOption,
axiosOption?: AxiosRequestConfig,
) => {
return await commonRequest<D>(url, params, 'put', options, axiosOption);
};
/***
* @param url 请求地址
* @param params 请求参数
* @param options 请求设置(showMask-是否显示Loading层默认为trueshowError-是否显示错误信息默认为truethrottleFlag-是否开户节流默认为falsethrottleTimeout-节流时效默认为50毫秒)
* @param axiosOption
*/
export const patch = async <D>(
url: string,
params: ANY_OBJECT,
options?: RequestOption,
axiosOption?: AxiosRequestConfig,
) => {
return await commonRequest<D>(url, params, 'patch', options, axiosOption);
};
/***
* @param url 请求地址
* @param params 请求参数
* @param options 请求设置(showMask-是否显示Loading层默认为trueshowError-是否显示错误信息默认为truethrottleFlag-是否开户节流默认为falsethrottleTimeout-节流时效默认为50毫秒)
* @param axiosOption
*/
export const del = async <D>(
url: string,
params: ANY_OBJECT,
options?: RequestOption,
axiosOption?: AxiosRequestConfig,
) => {
return await commonRequest<D>(url, params, 'delete', options, axiosOption);
};
/**
*
* @param url 请求地址
* @param params 请求参数
* @param filename 文件名
* @param options 请求设置(showMask-是否显示Loading层默认为trueshowError-是否显示错误信息默认为truethrottleFlag-是否开户节流默认为falsethrottleTimeout-节流时效默认为50毫秒)
*/
export const download = async (
url: string,
params: ANY_OBJECT,
filename: string,
method?: RequestMethods,
options?: RequestOption,
) => {
// console.log(
// 'download file url=%s, params:%s, filename:%s, options:%s',
// url,
// params,
// filename,
// options,
// );
return new Promise((resolve, reject) => {
downloadBlob(url, params, method, options)
.then(blobData => {
const blobUrl = window.URL.createObjectURL(blobData);
const linkDom = document.createElement('a');
linkDom.style.display = 'none';
linkDom.href = blobUrl;
linkDom.setAttribute('download', filename);
if (typeof linkDom.download === 'undefined') {
linkDom.setAttribute('target', '_blank');
}
document.body.appendChild(linkDom);
linkDom.click();
document.body.removeChild(linkDom);
window.URL.revokeObjectURL(blobUrl);
resolve(true);
})
.catch(e => {
reject(e);
});
});
};
/**
* 下载文件返回blob
* @param {String} url 请求的url
* @param {Object} params 请求参数
* @param {RequestMethods} method 请求方法
* @returns {Promise}
*/
export const downloadBlob = (
url: string,
params: ANY_OBJECT,
method: RequestMethods = 'post',
options?: RequestOption,
) => {
return new Promise<Blob>((resolve, reject) => {
const axiosOption: AxiosRequestConfig = {
responseType: 'blob',
transformResponse: function (data) {
//console.log(data);
return data instanceof Blob && data.size > 0 ? data : undefined;
},
};
commonRequest<Blob>(requestUrl(url), params, method, options, axiosOption)
.then(res => {
//console.log('download blob response >>>', res);
if (res instanceof Blob) {
const blobData = new Blob([res.data], { type: 'application/octet-stream' });
resolve(blobData);
} else {
console.warn('下载文件失败', res);
reject(new Error('下载文件失败'));
}
})
.catch(e => {
if (e instanceof Blob) {
const reader = new FileReader();
reader.onload = () => {
reject(
reader.result ? JSON.parse(reader.result.toString()).errorMessage : '下载文件失败',
);
};
reader.readAsText(e);
} else {
reject('下载文件失败');
}
});
});
};
/**
* 上传
* @param url 请求地址
* @param params 请求参数
* @param options 请求设置(showMask-是否显示Loading层默认为trueshowError-是否显示错误信息默认为truethrottleFlag-是否开户节流默认为falsethrottleTimeout-节流时效默认为50毫秒)
*/
export const upload = async (url: string, params: ANY_OBJECT, options?: RequestOption) => {
//console.log('upload file url=%s, params:%s, options:%s', url, params, options);
const axiosOption: AxiosRequestConfig = {
headers: {
'Content-Type': 'multipart/form-data',
},
transformRequest: [
function (data) {
const formData = new FormData();
Object.keys(data).forEach(key => {
formData.append(key, data[key]);
});
return formData;
},
],
};
const finalOption = {
...globalConfig.requestOption,
...options,
};
const { showError } = finalOption;
return new Promise((resolve, reject) => {
commonRequest<ANY_OBJECT>(requestUrl(url), params, 'post', options, axiosOption)
.then(res => {
if (res?.success) {
resolve(res);
} else {
if (showError)
showErrorMessage({
showClose: true,
message: res.data.errorMessage ? res.data.errorMessage : '数据请求失败',
});
reject('数据请求失败');
}
})
.catch(e => {
if (showError)
showErrorMessage({
showClose: true,
message: e.errorMessage ? e.errorMessage : '网络请求错误',
});
reject(e);
});
});
};

View File

@@ -0,0 +1,39 @@
import { Method } from 'axios';
import { ANY_OBJECT } from '@/types/generic';
export type RequestMethods = Extract<
Method,
'get' | 'post' | 'put' | 'delete' | 'patch' | 'option' | 'head'
>;
/**
* @param showMask 是否显示Loading层
* @param showError 是否显示错误消息
* @param throttleFlag 是否节流
* @param throttleTimeout 节流限制时长
*/
export type RequestOption = {
/** 是否使用Mock请求 */
mock?: boolean;
showMask?: boolean;
showError?: boolean;
throttleFlag?: boolean;
throttleTimeout?: number;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
};
// 定义返回数据的类型
export type ResponseDataType<T> = {
errorCode: string | null;
data: T;
errorMessage: string | null;
success: boolean;
};
export type TableData<T> = {
dataList: T[];
totalCount: number;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
};

View File

@@ -0,0 +1,5 @@
import * as baseDict from './index';
export default {
...baseDict,
};

View File

@@ -0,0 +1,331 @@
/**
* 流程表单常量字典
*/
import { DictionaryBase } from './types';
const SysFlowEntryBindFormType = new DictionaryBase('流程绑定表单类型', [
{
id: 0,
name: '动态表单',
symbol: 'ONLINE_FORM',
},
{
id: 1,
name: '路由表单',
symbol: 'ROUTER_FORM',
},
]);
const SysFlowEntryPublishedStatus = new DictionaryBase('流程设计发布状态', [
{
id: 0,
name: '未发布',
symbol: 'UNPUBLISHED',
},
{
id: 1,
name: '已发布',
symbol: 'PUBLISHED',
},
]);
const SysFlowEntryStep = new DictionaryBase('流程设计步骤', [
{
id: 0,
name: '编辑基础信息',
symbol: 'BASIC',
},
{
id: 1,
name: '流程变量设置',
symbol: 'PROCESS_VARIABLE',
},
{
id: 2,
name: '设计流程',
symbol: 'PROCESS_DESIGN',
},
{
id: 3,
name: '流程状态设置',
symbol: 'PROCESS_STATUS',
},
]);
const SysFlowTaskOperationType = new DictionaryBase('任务操作类型', [
{
id: 'agree',
name: '同意',
symbol: 'AGREE',
},
{
id: 'refuse',
name: '拒绝',
symbol: 'REFUSE',
},
{
id: 'reject',
name: '驳回',
symbol: 'REJECT',
},
{
id: 'rejectToStart',
name: '驳回到起点',
symbol: 'REJECT_TO_START',
},
{
id: 'revoke',
name: '撤销',
symbol: 'REVOKE',
},
{
id: 'transfer',
name: '转办',
symbol: 'TRANSFER',
},
{
id: 'multi_consign',
name: '加签',
symbol: 'CO_SIGN',
},
{
id: 'multi_minus_sign',
name: '减签',
symbol: 'SIGN_REDUCTION',
},
{
id: 'save',
name: '保存',
symbol: 'SAVE',
},
{
id: 'stop',
name: '终止',
symbol: 'STOP',
},
{
id: 'multi_sign',
name: '会签',
symbol: 'MULTI_SIGN',
},
{
id: 'multi_agree',
name: '同意(会签)',
symbol: 'MULTI_AGREE',
},
{
id: 'multi_refuse',
name: '拒绝(会签)',
symbol: 'MULTI_REFUSE',
},
{
id: 'multi_abstain',
name: '弃权(会签)',
symbol: 'MULTI_ABSTAIN',
},
{
id: 'set_assignee',
name: '指定审批人',
symbol: 'SET_ASSIGNEE',
},
]);
const SysFlowTaskType = new DictionaryBase('工作流任务类型', [
{
id: 0,
name: '其他任务',
symbol: 'OTHER_TASK',
},
{
id: 1,
name: '用户任务',
symbol: 'USER_TASK',
},
]);
const SysFlowVariableType = new DictionaryBase('工作流变量类型', [
{
id: 0,
name: '流程变量',
symbol: 'INSTANCE',
},
{
id: 1,
name: '任务变量',
symbol: 'TASK',
},
]);
const SysFlowWorkOrderStatus = new DictionaryBase('工单状态', [
{
id: 0,
name: '已提交',
symbol: 'SUBMITED',
},
{
id: 1,
name: '审批中',
symbol: 'APPROVING',
},
{
id: 2,
name: '已拒绝',
symbol: 'REFUSED',
},
{
id: 3,
name: '已完成',
symbol: 'FINISHED',
},
{
id: 4,
name: '终止',
symbol: 'STOPPED',
},
{
id: 5,
name: '撤销',
symbol: 'CANCEL',
},
{
id: 6,
name: '草稿',
symbol: 'DRAFT',
},
]);
const SysFlowCopyForType = new DictionaryBase('抄送类型', [
{
id: 'user',
name: '抄送人',
symbol: 'USER',
},
{
id: 'dept',
name: '抄送部门',
symbol: 'DEPT',
},
{
id: 'role',
name: '抄送角色',
symbol: 'ROLE',
},
{
id: 'deptPostLeader',
name: '审批人部门领导',
symbol: 'SELF_DEPT_LEADER',
},
{
id: 'upDeptPostLeader',
name: '审批人上级部门领导',
symbol: 'UP_DEPT_LEADER',
},
{
id: 'allDeptPost',
name: '抄送岗位',
symbol: 'POST',
},
{
id: 'selfDeptPost',
name: '审批人部门岗位',
symbol: 'SELF_DEPT_POST',
},
{
id: 'siblingDeptPost',
name: '审批人同级部门岗位',
symbol: 'SLIBING_DEPT_POST',
},
{
id: 'upDeptPost',
name: '审批人上级部门岗位',
symbol: 'UP_DEPT_POST',
},
{
id: 'deptPost',
name: '指定部门岗位',
symbol: 'DEPT_POST',
},
]);
const FlowNodeType = new DictionaryBase('钉钉节点类型', [
{
id: 0,
name: '发起人',
symbol: 'ORIGINATOR',
},
{
id: 1,
name: '审批人',
symbol: 'APPROVED_BY',
},
{
id: 2,
name: '抄送人',
symbol: 'CC_TO',
},
{
id: 3,
name: '连接线',
symbol: 'CONNECTING_LINE',
},
{
id: 4,
name: '条件分支',
symbol: 'CONDITIONAL_BRANCH',
},
{
id: 5,
name: '并行分支',
symbol: 'PARALLEL_BRANCH',
},
]);
const DiagramType = new DictionaryBase('', [
{
id: 0,
name: '普通流程图',
symbol: 'ORDINARY',
},
{
id: 1,
name: '钉钉风格流程图',
symbol: 'DINGDING',
},
]);
const SysAutoCodeType = new DictionaryBase('自动编码类型', [
{
id: 'DAYS',
name: '精确到日',
symbol: 'DAYS',
},
{
id: 'HOURS',
name: '精确到时',
symbol: 'HOURS',
},
{
id: 'MINUTES',
name: '精确到分',
symbol: 'MINUTES',
},
{
id: 'SECONDS',
name: '精确到秒',
symbol: 'SECONDS',
},
]);
export {
SysFlowEntryPublishedStatus,
SysFlowEntryBindFormType,
SysFlowEntryStep,
SysFlowTaskOperationType,
SysFlowTaskType,
SysFlowVariableType,
SysFlowWorkOrderStatus,
SysFlowCopyForType,
DiagramType,
FlowNodeType,
SysAutoCodeType,
};

View File

@@ -0,0 +1,946 @@
import { DictionaryBase } from './types';
const SysUserStatus = new DictionaryBase('用户状态', [
{
id: 0,
name: '正常状态',
symbol: 'NORMAL',
},
{
id: 1,
name: '锁定状态',
symbol: 'LOCKED',
},
]);
const SysUserType = new DictionaryBase('用户类型', [
{
id: 0,
name: '管理员',
symbol: 'ADMIN',
},
{
id: 1,
name: '系统操作员',
symbol: 'SYSTEM',
},
{
id: 2,
name: '普通操作员',
symbol: 'OPERATOR',
},
]);
const SysOperationType = new DictionaryBase('操作日志操作类型', [
{
id: 0,
name: '登录',
symbol: 'LOGIN',
},
{
id: 1,
name: '手机登录',
symbol: 'MOBILE_LOGIN',
},
{
id: 5,
name: '登出',
symbol: 'LOGOUT',
},
{
id: 10,
name: '新增',
symbol: 'ADD',
},
{
id: 15,
name: '修改',
symbol: 'UPDATE',
},
{
id: 20,
name: '删除',
symbol: 'DELETE',
},
{
id: 35,
name: '查询',
symbol: 'LIST',
},
{
id: 40,
name: '分组查询',
symbol: 'LIST_WITH_GROUP',
},
{
id: 45,
name: '导出',
symbol: 'EXPORT',
},
{
id: 25,
name: '新增多对多关联',
symbol: 'ADD_M2M',
},
{
id: 30,
name: '移除多对多关联',
symbol: 'DELETE_M2M',
},
{
id: 50,
name: '上传',
symbol: 'UPLOAD',
},
{
id: 55,
name: '下载',
symbol: 'DOWNLOAD',
},
{
id: 60,
name: '重置缓存',
symbol: 'RELOAD_CACHE',
},
{
id: 65,
name: '发布',
symbol: 'PUBLISH',
},
{
id: 70,
name: '取消发布',
symbol: 'UNPUBLISH',
},
{
id: 75,
name: '流程挂起',
symbol: 'SUSPEND',
},
{
id: 80,
name: '流程恢复',
symbol: 'RESUME',
},
{
id: 100,
name: '启动流程',
symbol: 'START_FLOW',
},
{
id: 105,
name: '终止流程',
symbol: 'STOP_FLOW',
},
{
id: 110,
name: '删除流程',
symbol: 'DELETE_FLOW',
},
{
id: 115,
name: '撤销流程',
symbol: 'CANCEL_FLOW',
},
{
id: 120,
name: '提交流程任务',
symbol: 'SUBMIT_TASK',
},
{
id: 125,
name: '催办流程',
symbol: 'REMIND_TASK',
},
{
id: 126,
name: '流程干预',
symbol: 'INTERVENE_FLOW',
},
{
id: 127,
name: '流程数据补偿',
symbol: 'FIX_FLOW_BUSINESS_DATA',
},
]);
const SysPermModuleType = new DictionaryBase('权限分组类型', [
{
id: 0,
name: '分组模块',
symbol: 'GROUP',
},
{
id: 1,
name: '接口模块',
symbol: 'CONTROLLER',
},
]);
const SysPermCodeType = new DictionaryBase('权限字类型', [
{
id: 0,
name: '表单',
symbol: 'FORM',
},
{
id: 1,
name: '片段',
symbol: 'FRAGMENT',
},
{
id: 2,
name: '操作',
symbol: 'OPERATION',
},
]);
/**
* 菜单类型
*
* DIRECTORY(0: 目录)
* MENU(1: 表单)
* FRAGMENT(2: 片段)
* BUTTON(3: 按钮)
*/
const SysMenuType = new DictionaryBase('菜单类型', [
{
id: 0,
name: '目录',
symbol: 'DIRECTORY',
},
{
id: 1,
name: '表单',
symbol: 'MENU',
},
{
id: 2,
name: '片段',
symbol: 'FRAGMENT',
},
{
id: 3,
name: '按钮',
symbol: 'BUTTON',
},
]);
const MobileEntryType = new DictionaryBase('移动端首页配置项类型', [
{
id: 0,
name: '轮播图',
symbol: 'BANNER',
},
{
id: 1,
name: '九宫格',
symbol: 'SUDOKU',
},
{
id: 2,
name: '分组',
symbol: 'GROUP',
},
]);
/**
* 菜单绑定类型
*
* ROUTER(0: 路由菜单)
* ONLINE_FORM(1: 在线表单)
* WORK_ORDER(2: 工单列表)
* REPORT(3: 报表页面)
* THRID_URL(4: 外部链接)
*/
const SysMenuBindType = new DictionaryBase('菜单绑定类型', [
{
id: 0,
name: '路由菜单',
symbol: 'ROUTER',
},
{
id: 1,
name: '在线表单',
symbol: 'ONLINE_FORM',
},
{
id: 2,
name: '工单列表',
symbol: 'WORK_ORDER',
},
{
id: 3,
name: '报表页面',
symbol: 'REPORT',
},
{
id: 4,
name: '外部链接',
symbol: 'THRID_URL',
},
]);
const SysDataPermType = new DictionaryBase('数据权限类型', [
{
id: 0,
name: '查看全部',
symbol: 'ALL',
},
{
id: 1,
name: '仅看自己',
symbol: 'ONLY_USER',
},
{
id: 2,
name: '仅看所在部门',
symbol: 'ONLY_DEPT',
},
{
id: 3,
name: '仅看所在部门及子部门',
symbol: 'ONLY_DEPT_AND_CHILD',
},
{
id: 4,
name: '自选部门及子部门',
symbol: 'CUSTOM_DEPT_AND_CHILD',
},
{
id: 5,
name: '仅自选部门',
symbol: 'CUSTOM_DEPT',
},
{
id: 6,
name: '本部门用户',
symbol: 'DEPT_USER',
},
{
id: 7,
name: '本部门及子部门用户',
symbol: 'DEPT_AND_CHILD_USER',
},
]);
const ScatterSymbolType = new DictionaryBase('纵轴位置', [
{
id: 0,
name: '固定大小',
symbol: 'FIXED',
},
{
id: 1,
name: '值大小',
symbol: 'VALUE',
},
]);
const SysCustomWidgetType = new DictionaryBase('组件类型', [
{
id: 0,
name: '文本显示',
symbol: 'Label',
},
{
id: 1,
name: '文本输入框',
symbol: 'Input',
},
{
id: 3,
name: '数字输入框',
symbol: 'NumberInput',
},
{
id: 4,
name: '数字范围输入框',
symbol: 'NumberRange',
},
{
id: 5,
name: '开关组件',
symbol: 'Switch',
},
{
id: 6,
name: '滑块组件',
symbol: 'Slider',
},
{
id: 7,
name: '单选组件',
symbol: 'Radio',
},
{
id: 8,
name: '复选框',
symbol: 'CheckBox',
},
{
id: 10,
name: '下拉选择框',
symbol: 'Select',
},
{
id: 12,
name: '级联选择框',
symbol: 'Cascader',
},
{
id: 13,
name: '树形选择组件',
symbol: 'Tree',
},
{
id: 14,
name: '评分组件',
symbol: 'Rate',
},
{
id: 15,
name: '进步器',
symbol: 'Stepper',
},
{
id: 16,
name: '日历组件',
symbol: 'Calendar',
},
{
id: 20,
name: '日期选择框',
symbol: 'Date',
},
{
id: 21,
name: '日期范围选择框',
symbol: 'DateRange',
},
{
id: 30,
name: '颜色选择框',
symbol: 'ColorPicker',
},
{
id: 31,
name: '上传组件',
symbol: 'Upload',
},
{
id: 32,
name: '富文本编辑',
symbol: 'RichEditor',
},
{
id: 40,
name: '分割线',
symbol: 'Divider',
},
{
id: 41,
name: '文本',
symbol: 'Text',
},
{
id: 42,
name: '图片',
symbol: 'Image',
},
{
id: 43,
name: '超链接',
symbol: 'Link',
},
{
id: 100,
name: '表格组件',
symbol: 'Table',
},
{
id: 101,
name: '透视表',
symbol: 'PivotTable',
},
{
id: 102,
name: '数据列表',
symbol: 'List',
},
{
id: 103,
name: '查询列表',
symbol: 'QueryList',
},
{
id: 104,
name: '工单列表',
symbol: 'WorkOrderList',
},
{
id: 200,
name: '折线图',
symbol: 'LineChart',
},
{
id: 201,
name: '柱状图',
symbol: 'BarChart',
},
{
id: 202,
name: '饼图',
symbol: 'PieChart',
},
{
id: 203,
name: '散点图',
symbol: 'ScatterChart',
},
{
id: 204,
name: '普通表格',
symbol: 'DataViewTable',
},
{
id: 205,
name: '轮播图',
symbol: 'Carousel',
},
{
id: 206,
name: '富文本',
symbol: 'RichText',
},
{
id: 207,
name: '仪表盘',
symbol: 'GaugeChart',
},
{
id: 208,
name: '漏斗图',
symbol: 'FunnelChart',
},
{
id: 209,
name: '雷达图',
symbol: 'RadarChart',
},
{
id: 210,
name: '普通进度条',
symbol: 'ProgressBar',
},
{
id: 211,
name: '环形进度条',
symbol: 'ProgressCircle',
},
{
id: 212,
name: '通用卡片',
symbol: 'DataCard',
},
{
id: 213,
name: '通用列表',
symbol: 'CommonList',
},
{
id: 214,
name: '进度条卡片',
symbol: 'DataProgressCard',
},
{
id: 300,
name: '基础块',
symbol: 'Block',
},
{
id: 301,
name: '卡片组件',
symbol: 'Card',
},
{
id: 302,
name: 'Tabs 组件',
symbol: 'Tabs',
},
{
id: 303,
name: '图片卡片',
symbol: 'ImageCard',
},
{
id: 304,
name: '分组容器',
symbol: 'CellGroup',
},
{
id: 400,
name: '用户选择',
symbol: 'UserSelect',
},
{
id: 401,
name: '部门选择',
symbol: 'DeptSelect',
},
{
id: 402,
name: '关联选择',
symbol: 'DataSelect',
},
{
id: 403,
name: '表格容器',
symbol: 'TableContainer',
},
{
id: 500,
name: '单选过滤',
symbol: 'MobileRadioFilter',
},
{
id: 501,
name: '多选过滤',
symbol: 'MobileCheckBoxFilter',
},
{
id: 502,
name: '文本过滤',
symbol: 'MobileInputFilter',
},
{
id: 503,
name: '开关过滤',
symbol: 'MobileSwitchFilter',
},
{
id: 504,
name: '日期过滤',
symbol: 'MobileDateRangeFilter',
},
{
id: 505,
name: '数字范围过滤',
symbol: 'MobileNumberRangeFilter',
},
]);
const OnlineFormEventType = new DictionaryBase('在线表单事件类型', [
{
id: 'change',
name: '数据改变',
symbol: 'CHANGE',
},
{
id: 'disable',
name: '是否禁用',
symbol: 'DISABLE',
},
{
id: 'visible',
name: '是否可见',
symbol: 'VISIBLE',
},
{
id: 'dropdownChange',
name: '下拉数据改变',
symbol: 'DROPDOWN_CHANGE',
},
{
id: 'linkHerf',
name: '链接地址',
symbol: 'LINK_HERF',
},
{
id: 'disabledDate',
name: '日期是否可用',
symbol: 'DISABLED_DATE',
},
{
id: 'afterLoadTableData',
name: '表格加载数据后',
symbol: 'AFTER_LOAD_TABLE_DATA',
},
{
id: 'beforeLoadTableData',
name: '表格加载数据前',
symbol: 'BEFORE_LOAD_TABLE_DATA',
},
{
id: 'afterLoadFormData',
name: '页面加载数据后',
symbol: 'AFTER_LOAD_FORM_DATA',
},
{
id: 'beforeLoadFormData',
name: '页面加载数据前',
symbol: 'BEFORE_LOAD_FORM_DATA',
},
{
id: 'beforeCommitFormData',
name: '页面数据提交前',
symbol: 'BEFORE_COMMIT_FORM_DATA',
},
{
id: 'formCreated',
name: '页面创建完毕',
symbol: 'AFTER_CREATE_FORM',
},
{
id: 'tableOperationVisible',
name: '操作是否可见',
symbol: 'OPERATION_VISIBLE',
},
{
id: 'tableOperationDisbled',
name: '操作是否禁用',
symbol: 'OPERATION_DISABLED',
},
]);
/**
* 表单类型
*
* QUERY(1: 查询表单)
* ADVANCE_QUERY(2: 左树右表查询)
* ONE_TO_ONE_QUERY(3: 一对一查询)
* FORM(5: 编辑表单)
* FLOW(10: 流程表单)
* WORK_ORDER(11: 工单列表)
* REPORT(50: 报表页面)
*/
const SysOnlineFormType = new DictionaryBase('表单类型', [
{
id: 1,
name: '查询表单',
symbol: 'QUERY',
},
{
id: 2,
name: '左树右表查询',
symbol: 'ADVANCE_QUERY',
},
{
id: 3,
name: '一对一查询',
symbol: 'ONE_TO_ONE_QUERY',
},
{
id: 5,
name: '编辑表单',
symbol: 'FORM',
},
{
id: 10,
name: '流程表单',
symbol: 'FLOW',
},
{
id: 11,
name: '工单列表',
symbol: 'WORK_ORDER',
},
{
id: 50,
name: '报表页面',
symbol: 'REPORT',
},
]);
const SysCustomWidgetOperationType = new DictionaryBase('操作类型', [
{
id: 0,
name: '新建',
symbol: 'ADD',
},
{
id: 1,
name: '编辑',
symbol: 'EDIT',
},
{
id: 2,
name: '删除',
symbol: 'DELETE',
},
{
id: 3,
name: '导出',
symbol: 'EXPORT',
},
{
id: 10,
name: '批量删除',
symbol: 'BATCH_DELETE',
},
{
id: 20,
name: '表单操作',
symbol: 'FORM',
},
{
id: 22,
name: '复制',
symbol: 'COPY',
},
{
id: 30,
name: '保存',
symbol: 'SAVE',
},
{
id: 31,
name: '取消',
symbol: 'CANCEL',
},
{
id: 50,
name: '脚本操作',
symbol: 'SCRIPT',
},
{
id: 51,
name: '下钻事件',
symbol: 'DRILL',
},
{
id: 52,
name: '路由跳转',
symbol: 'ROUTE',
},
]);
const OnlineSystemVariableType = new DictionaryBase('系统变量类型', [
{
id: 0,
name: '登录用户',
symbol: 'CURRENT_USER',
},
{
id: 1,
name: '所属部门',
symbol: 'CURRENT_DEPT',
},
{
id: 10,
name: '当前日期',
symbol: 'CURRENT_DATE',
},
{
id: 11,
name: '当前时间',
symbol: 'CURRENT_TIME',
},
{
id: 20,
name: '流程发起人',
symbol: 'FLOW_CREATE_USER',
},
]);
const SysCustomWidgetBindDataType = new DictionaryBase('组件绑定数据类型', [
{
id: 0,
name: '字段',
symbol: 'Column',
},
{
id: 5,
name: '系统变量',
symbol: 'SYSTEM_VARIABLE',
},
{
id: 10,
name: '自定义字段',
symbol: 'Custom',
},
{
id: 20,
name: '固定值',
symbol: 'Fixed',
},
]);
const DirectionType = new DictionaryBase('方向', [
{
id: 0,
name: '横轴',
symbol: 'HORIZONTAL',
},
{
id: 1,
name: '纵轴',
symbol: 'VERTICAL',
},
]);
const DblinkType = new DictionaryBase('数据库连接类型', [
{
id: 0,
name: 'MySQL',
symbol: 'MYSQL',
},
/*
{
id: 1,
name: 'PostgreSQL',
symbol: 'POSTGRESQL',
},
{
id: 2,
name: 'Oracle',
symbol: 'ORACLE',
},
{
id: 3,
name: '达梦数据库',
symbol: 'DM_DB',
},
{
id: 4,
name: '人大金仓',
symbol: 'KINGBASE',
},
{
id: 5,
name: '华为高斯',
symbol: 'OPENGAUSS',
},
{
id: 10,
name: 'ClickHouse',
symbol: 'CLICK_HOUSE',
},
{
id: 11,
name: 'Doris',
symbol: 'DORIS',
},
*/
]);
export {
SysUserStatus,
SysUserType,
SysDataPermType,
SysOperationType,
SysPermModuleType,
SysPermCodeType,
SysMenuBindType,
MobileEntryType,
SysMenuType,
ScatterSymbolType,
SysCustomWidgetType,
OnlineFormEventType,
SysOnlineFormType,
SysCustomWidgetOperationType,
OnlineSystemVariableType,
SysCustomWidgetBindDataType,
DirectionType,
DblinkType,
};

View File

@@ -0,0 +1,411 @@
/**
* 在线表单常量字典
*/
import { DictionaryBase } from './types';
const SysOnlineFieldKind = new DictionaryBase('字段类型', [
{
id: 1,
name: '文件上传字段',
symbol: 'UPLOAD',
},
{
id: 2,
name: '图片上传字段',
symbol: 'UPLOAD_IMAGE',
},
{
id: 3,
name: '富文本字段',
symbol: 'RICH_TEXT',
},
{
id: 4,
name: '多选字段',
symbol: 'MULTI_SELECT',
},
{
id: 19,
name: '创建人部门',
symbol: 'CREATE_USER_DEPT_ID',
},
{
id: 20,
name: '创建时间字段',
symbol: 'CREATE_TIME',
},
{
id: 21,
name: '创建人字段',
symbol: 'CREATE_USER_ID',
},
{
id: 22,
name: '更新时间字段',
symbol: 'UPDATE_TIME',
},
{
id: 23,
name: '更新人字段',
symbol: 'UPDATE_USER_ID',
},
{
id: 25,
name: '流程审批状态',
symbol: 'FLOW_APPROVAL_STATUS',
},
{
id: 26,
name: '流程状态',
symbol: 'FLOW_FINISHED_STATUS',
},
{
id: 31,
name: '逻辑删除字段',
symbol: 'LOGIC_DELETE',
},
]);
const SysOnlineDataPermFilterType = new DictionaryBase('数据权限过滤类型', [
{
id: 1,
name: '用户过滤字段',
symbol: 'USER_FILTER',
},
{
id: 2,
name: '部门过滤字段',
symbol: 'DEPT_FILTER',
},
]);
const SysOnlineRelationType = new DictionaryBase('关联类型', [
{
id: 0,
name: '一对一关联',
symbol: 'ONE_TO_ONE',
},
{
id: 1,
name: '一对多关联',
symbol: 'ONE_TO_MANY',
},
]);
const SysOnlineFormKind = new DictionaryBase('表单类别', [
{
id: 1,
name: '弹出窗口',
symbol: 'DIALOG',
},
{
id: 5,
name: '跳转页面',
symbol: 'PAGE',
},
]);
const SysOnlinePageType = new DictionaryBase('页面类型', [
{
id: 1,
name: '业务页面',
symbol: 'BIZ',
},
{
id: 10,
name: '流程页面',
symbol: 'FLOW',
},
]);
const SysOnlinePageStatus = new DictionaryBase('页面状态', [
{
id: 0,
name: '基础信息录入',
symbol: 'BASIC',
},
{
id: 1,
name: '数据模型录入',
symbol: 'DATASOURCE',
},
{
id: 2,
name: '表单设计',
symbol: 'DESIGNING',
},
]);
const SysOnlineDictType = new DictionaryBase('字典类型', [
{
id: 1,
name: '数据表字典',
symbol: 'TABLE',
},
{
id: 5,
name: 'URL字典',
symbol: 'URL',
},
{
id: 10,
name: '静态字典',
symbol: 'STATIC',
},
{
id: 20,
name: '编码字典',
symbol: 'CODE',
},
{
id: 15,
name: '自定义字典',
symbol: 'CUSTOM',
},
]);
const SysOnlineRuleType = new DictionaryBase('验证规则类型', [
{
id: 1,
name: '只允许整数',
symbol: 'INTEGER_ONLY',
},
{
id: 2,
name: '只允许数字',
symbol: 'DIGITAL_ONLY',
},
{
id: 3,
name: '只允许英文字符',
symbol: 'LETTER_ONLY',
},
{
id: 4,
name: '范围验证',
symbol: 'RANGE',
},
{
id: 5,
name: '邮箱格式验证',
symbol: 'EMAIL',
},
{
id: 6,
name: '手机格式验证',
symbol: 'MOBILE',
},
{
id: 100,
name: '自定义验证',
symbol: 'CUSTOM',
},
]);
const SysCustomWidgetBindValueType = new DictionaryBase('组件绑定值类型', [
{
id: 1,
name: '字典数据',
symbol: 'DICT_DATA',
},
{
id: 10,
name: '系统变量',
symbol: 'SYSTEM_VARIABLE',
},
{
id: 20,
name: '自定义',
symbol: 'INPUT_DATA',
},
]);
const SysOnlineColumnFilterType = new DictionaryBase('过滤类型', [
{
id: 0,
name: '无过滤',
symbol: 'NONE',
},
{
id: 1,
name: '普通过滤',
symbol: 'EQUAL_FILTER',
},
{
id: 2,
name: '范围过滤',
symbol: 'RANFGE_FILTER',
},
{
id: 3,
name: '模糊过滤',
symbol: 'LIKE_FILTER',
},
{
id: 4,
name: '多选过滤',
symbol: 'MULTI_SELECT_FILTER',
},
]);
const SysOnlinePageDatasourceFieldStatus = new DictionaryBase('数据表状态', [
{
id: 0,
name: '已删除',
symbol: 'DELETED',
},
{
id: 1,
name: '已使用',
symbol: 'USED',
},
{
id: 0,
name: '未使用',
symbol: 'UNUSED',
},
]);
const SysOnlinePageSettingStep = new DictionaryBase('在线表单编辑步骤', [
{
id: 0,
name: '编辑基础信息',
symbol: 'BASIC',
},
{
id: 1,
name: '编辑数据模型',
symbol: 'DATASOURCE',
},
{
id: 2,
name: '设计表单',
symbol: 'FORM_DESIGN',
},
]);
const SysOnlineParamValueType = new DictionaryBase('参数值类型', [
{
id: 1,
name: '数据字段',
symbol: 'TABLE_COLUMN',
},
{
id: 2,
name: '静态字典',
symbol: 'STATIC_DICT',
},
{
id: 3,
name: '直接输入',
symbol: 'INPUT_VALUE',
},
]);
const SysOnlineAggregationType = new DictionaryBase('字段聚合类型', [
{
id: 0,
name: '总数',
symbol: 'SUM',
},
{
id: 1,
name: '个数',
symbol: 'COUNT',
},
{
id: 2,
name: '平均数',
symbol: 'AVG',
},
{
id: 3,
name: '最小值',
symbol: 'MIN',
},
{
id: 4,
name: '最大值',
symbol: 'MAX',
},
]);
const SysOnlineFilterOperationType = new DictionaryBase('过滤条件操作类型', [
{
id: 0,
name: ' = ',
symbol: 'EQUAL',
},
{
id: 1,
name: ' != ',
symbol: 'NOT_EQUAL',
},
{
id: 2,
name: ' >= ',
symbol: 'GREATER_THAN_OR_EQUAL',
},
{
id: 3,
name: ' > ',
symbol: 'GREATER_THAN',
},
{
id: 4,
name: ' <= ',
symbol: 'LESS_THAN_OR_EQUAL',
},
{
id: 5,
name: ' < ',
symbol: 'LESS_THAN',
},
{
id: 6,
name: ' like ',
symbol: 'LIKE',
},
{
id: 7,
name: ' not null ',
symbol: 'NOT_NULL',
},
{
id: 8,
name: ' is null ',
symbol: 'IS_NULL',
},
]);
const SysOnlineVirtualColumnFilterValueType = new DictionaryBase('虚拟字段过滤值类型', [
{
id: 0,
name: '输入值',
symbol: 'CUSTOM_INPUT',
},
{
id: 1,
name: '静态字典',
symbol: 'STATIC_DICT',
},
]);
export {
SysOnlineFieldKind,
SysOnlineDataPermFilterType,
SysOnlineRelationType,
SysOnlineFormKind,
SysOnlinePageType,
SysOnlinePageStatus,
SysOnlineDictType,
SysOnlineRuleType,
SysCustomWidgetBindValueType,
SysOnlineColumnFilterType,
SysOnlinePageSettingStep,
SysOnlinePageDatasourceFieldStatus,
SysOnlineParamValueType,
SysOnlineAggregationType,
SysOnlineFilterOperationType,
SysOnlineVirtualColumnFilterValueType,
};

View File

@@ -0,0 +1,78 @@
/**
* 字典数据类型
*/
export type DictDataIdType = string | number;
export type DictDataPropertyType = string | number | undefined | boolean;
export type DictData = {
id: DictDataIdType;
name: string;
symbol?: string;
[key: string]: DictDataPropertyType;
};
/**
* 常量字典数据
*/
export class DictionaryBase extends Map<DictDataIdType, DictData> {
public showName: string;
// 动态属性
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[name: string]: any;
constructor(name: string, dataList: DictData[], keyId = 'id', symbolId = 'symbol') {
super();
this.showName = name;
this.setList(dataList, keyId, symbolId);
}
setList(dataList: DictData[], keyId = 'id', symbolId = 'symbol') {
this.clear();
if (Array.isArray(dataList)) {
dataList.forEach(item => {
this.set(item[keyId] as DictDataIdType, item);
if (item[symbolId] != null) {
Object.defineProperty(this, item[symbolId] as PropertyKey, {
get: function () {
return item[keyId];
},
});
}
});
}
}
getList(
valueId = 'name',
parentIdKey = 'parentId',
filter?: (o: DictData) => DictData,
): DictData[] {
const temp: DictData[] = [];
this.forEach((value, key: DictDataPropertyType) => {
let obj: DictData = {
id: key as string | number,
name: typeof value === 'string' ? value : String(value[valueId]),
parentId: value[parentIdKey],
};
if (typeof value !== 'string') {
obj = {
...value,
...obj,
};
}
if (typeof filter !== 'function' || filter(obj)) {
temp.push(obj);
}
});
return temp;
}
getValue(id: DictDataIdType, valueId = 'name'): string {
// 如果id为boolean类型则自动转换为0和1
if (typeof id === 'boolean') {
id = id ? 1 : 0;
}
const obj = this.get(id);
return obj == null ? '' : (obj[valueId] as string);
}
}

View File

@@ -0,0 +1,21 @@
/**
* 列表数据
*
* @param dataList 数据集合
*/
export interface ListData<T> {
dataList: T[];
}
/**
* 下拉列表初始化参数
* @param loadData 加载数据函数
* @param isTree 是否树型数据
* @param idKey ID字段名默认id
* @param parentIdKey 父ID字段名默认parentId
*/
export interface DropdownOptions<T> {
loadData: () => Promise<ListData<T>>;
isTree?: boolean;
idKey?: string;
parentIdKey?: string;
}

View File

@@ -0,0 +1,53 @@
/**
* 分页参数
* @param pageNum 查询页
* @param pagesize 分页大小
*/
export interface PageParam {
pageNum: number;
pageSize: number;
}
/**
* 排序参数
* @param fieldName 排序字段名
* @param asc 是否正序
* @param dateAggregateBy 默认排序字段的日期统计类型
*/
export interface OrderInfo {
fieldName?: string;
asc?: boolean;
dateAggregateBy?: string;
}
/**
* 分页查询请求参数
* @param pageParam 分页参数
* @param orderParam 排序参数
*/
export interface RequestParam {
pageParam?: PageParam;
orderParam?: OrderInfo[];
}
/**
* 表格初始化参数
*
* @param loadTableData 表数据获取函数返回Promise
* @param verifyTableParameter 表数据获取检验函数
* @param paged 是否支持分页
* @param rowSelection 是否支持行选择
* @param orderFieldName 默认排序字段
* @param ascending 默认排序方式true为正序false为倒序默认值true
* @param dateAggregateBy 默认排序字段的日期统计类型
* @param pageSize 分页大小默认值10
*/
export type TableOptions<T> = {
loadTableData: (params: RequestParam) => Promise<TableData<T>>;
verifyTableParameter?: () => boolean;
paged?: boolean;
rowSelection?: boolean;
orderFieldName?: string;
ascending?: boolean;
dateAggregateBy?: string;
pageSize?: number;
};

View File

@@ -0,0 +1,5 @@
export interface SortInfo {
prop?: string;
field?: string;
order: string;
}

View File

@@ -0,0 +1,11 @@
import { ListData } from './list';
/**
* 表格数据
*
* @param dataList 数据集合
* @param totalCount 数据集合总数
*/
export interface TableData<T> extends ListData<T> {
totalCount: number;
}

View File

@@ -0,0 +1,664 @@
import { JSEncrypt } from 'jsencrypt';
import dayjs from 'dayjs';
import { ANY_OBJECT } from '@/types/generic';
/**
* 列表数据转换树形数据
* @param {Array} data 要转换的列表
* @param {String} id 主键字段字段名
* @param {String} pid 父字段字段名
* @returns {Array} 转换后的树数据
*/
export function treeDataTranslate<D>(data: Array<D>, id = 'id', pid = 'parentId'): D[] {
const res: D[] = [];
const temp: ANY_OBJECT = {};
const dataList: ANY_OBJECT[] = data.map(item => {
return { ...item } as ANY_OBJECT;
});
for (let i = 0; i < dataList.length; i++) {
const d = dataList[i];
if (d) {
temp[d[id]] = dataList[i];
}
}
for (let k = 0; k < dataList.length; k++) {
const d = dataList[k];
if (d) {
if (temp[d[pid]] && d[id] !== d[pid]) {
if (!temp[d[pid]]['children']) {
temp[d[pid]]['children'] = [];
}
if (!temp[d[pid]]['_level']) {
temp[d[pid]]['_level'] = 1;
}
d['_level'] = temp[d[pid]]._level + 1;
d['_parent'] = d[pid];
temp[d[pid]]['children'].push(d);
} else {
res.push(d as D);
}
}
}
return res;
}
/**
* 获取字符串字节长度中文算2个字符
* @param {String} str 要获取长度的字符串
*/
export function getStringLength(str: string) {
return str.replace(/[\u4e00-\u9fa5\uff00-\uffff]/g, '**').length;
}
/**
* 获取uuid
*/
export function getUUID(): string {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
const random: number = Math.random() * 16;
return (c === 'x' ? random : (random & 0x3) | 0x8).toString(16);
});
}
export function stringCase(str: string, type: number) {
if (str == null || str === '') return str;
if (type === 0) {
// 首字母小写
return str.slice(0, 1).toLowerCase() + str.slice(1);
} else {
// 首字母大写
return str.slice(0, 1).toUpperCase() + str.slice(1);
}
}
/**
* 大小驼峰变换函数
* @param name 要转换的字符串
* @param type 转换的类型0转换成小驼峰1转换成大驼峰
*/
export function nameTranslate(name: string, type: 0 | 1) {
name = name.toLowerCase();
let nameArray = name.split('_');
nameArray.forEach((item, index) => {
if (index === 0) {
name = type === 1 ? item.slice(0, 1).toUpperCase() + item.slice(1) : item;
} else {
name = name + item.slice(0, 1).toUpperCase() + item.slice(1);
}
});
nameArray = name.split('-');
nameArray.forEach((item, index) => {
if (index === 0) {
name = type === 1 ? item.slice(0, 1).toUpperCase() + item.slice(1) : item;
} else {
name = name + item.slice(0, 1).toUpperCase() + item.slice(1);
}
});
return name;
}
/**
* 通过id从树中获取指定的节点
* @param {Object} node 根节点
* @param {String|Nubmer} id 键值
* @param {Array} list 保存查询路径
* @param {String} idKey 主键字段名
* @param {String} childKey 子节点字段名
*/
function findNode(
node: ANY_OBJECT,
id: string | number | undefined,
list?: Array<ANY_OBJECT> | undefined,
idKey = 'id',
childKey = 'children',
): ANY_OBJECT | undefined {
if (Array.isArray(list)) list.push(node);
if (node[idKey] === id) {
return node;
}
if (node[childKey] != null && Array.isArray(node[childKey])) {
for (let i = 0; i < node[childKey].length; i++) {
const tempNode: ANY_OBJECT | undefined = findNode(
node[childKey][i],
id,
list,
idKey,
childKey,
);
if (tempNode) return tempNode;
}
}
if (Array.isArray(list)) list.pop();
}
/**
* 通过id返回从根节点到指定节点的路径
* @param {Array} treeRoot 树根节点数组
* @param {*} id 要查询的节点的id
* @param {*} idKey 主键字段名
* @param {*} childKey 子节点字段名
*/
export function findTreeNodeObjectPath(
treeRoot: ANY_OBJECT,
id: string | number | undefined,
idKey = 'id',
childKey = 'children',
) {
const tempList: ANY_OBJECT[] = [];
for (let i = 0; i < treeRoot.length; i++) {
if (findNode(treeRoot[i], id, tempList, idKey, childKey)) {
return tempList;
}
}
return [];
}
export function findTreeNodePath<D>(
treeRoot: Array<D>,
id: string | number | undefined,
idKey = 'id',
childKey = 'children',
): Array<string | number> {
return (findTreeNodeObjectPath(treeRoot, id, idKey, childKey) || []).map(item => item[idKey]);
}
/**
* 通过id从树中查找节点
* @param {Array} treeRoot 根节点数组
* @param {*} id 要查找的节点的id
* @param {*} idKey 主键字段名
* @param {*} childKey 子节点字段名
*/
export function findTreeNode(
treeRoot: ANY_OBJECT,
id: string,
idKey = 'id',
childKey = 'children',
) {
for (let i = 0; i < treeRoot.length; i++) {
const tempNode = findNode(treeRoot[i], id, undefined, idKey, childKey);
if (tempNode) return tempNode;
}
}
export function traverseTree(
root: ANY_OBJECT,
callback: (node: ANY_OBJECT) => void,
childKey = 'children',
) {
function traverseNode(node: ANY_OBJECT) {
if (typeof callback === 'function') callback(node);
if (Array.isArray(node[childKey])) {
node[childKey].forEach((suNode: ANY_OBJECT) => {
traverseNode(suNode);
});
}
}
if (Array.isArray(root)) {
root.forEach(node => {
traverseNode(node);
});
}
}
/**
* 把Object转换成query字符串
* @param {Object} params 要转换的Object
*/
export function objectToQueryString(params: ANY_OBJECT | null) {
if (params == null) {
return null;
} else {
return Object.keys(params)
.map(key => {
if (params[key] !== undefined) {
return `${key}=${params[key]}`;
} else {
return undefined;
}
})
.filter(item => item != null)
.join('&');
}
}
/**
* 从数组中查找某一项
* @param {Array} list 要查找的数组
* @param {String} id 要查找的节点id
* @param {String} idKey 主键字段名如果为null则直接比较
* @param {Boolean} removeItem 是否从数组中移除查找到的节点
* @returns {Object} 找到返回节点没找到返回undefined
*/
export function findItemFromList(
list: ANY_OBJECT[],
id: string | number | undefined | null,
idKey: string | null = null,
removeItem = false,
) {
if (Array.isArray(list) && list.length > 0 && id != null) {
for (let i = 0; i < list.length; i++) {
const item = list[i];
if (
((idKey == null || idKey === '') && item.toString() === id) ||
(idKey != null && item[idKey] === id)
) {
if (removeItem) list.splice(i, 1);
return item;
}
}
}
return null;
}
/**
* 将数据保存到sessionStorage
* @param {*} key sessionStorage的键值
* @param {*} value 要保存的数据
*/
export function setObjectToSessionStorage(key: string, value: ANY_OBJECT) {
if (key == null || key === '') return false;
if (value == null) {
window.sessionStorage.removeItem(key);
return true;
} else {
const jsonObj = {
data: value,
};
window.sessionStorage.setItem(key, JSON.stringify(jsonObj));
return true;
}
}
/**
* 从sessionStorage里面获取数据
* @param {String} key 键值
* @param {*} defaultValue 默认值
*/
export function getObjectFromSessionStorage(key: string, defaultValue: ANY_OBJECT): ANY_OBJECT {
let jsonObj = null;
try {
const val: string | null = sessionStorage.getItem(key);
if (val == null) return defaultValue;
jsonObj = JSON.parse(val);
jsonObj = (jsonObj || {}).data;
} catch (e) {
jsonObj = defaultValue;
}
return jsonObj != null ? jsonObj : defaultValue;
}
/**
* 判读字符串是否一个数字
* @param {String} str 要判断的字符串
*/
export function isNumber(str: string) {
const num = Number.parseFloat(str);
if (Number.isNaN(num)) return false;
return num.toString() === str;
}
/**
* 生成随机数
* @param {Integer} min 随机数最小值
* @param {Integer} max 随机数最大值
*/
export function random(min: number, max: number) {
const base = Math.random();
return min + base * (max - min);
}
/**
* 加密
* @param {*} value 要加密的字符串
*/
const publicKey =
'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCpC4QMnbTrQOFriJJCCFFWhlruBJThAEBfRk7pRx1jsAhyNVL3CqJb0tRvpnbCnJhrRAEPdgFHXv5A0RrvFp+5Cw7QoFH6O9rKB8+0H7+aVQeKITMUHf/XMXioymw6Iq4QfWd8RhdtM1KM6eGTy8aU7SO2s69Mc1LXefg/x3yw6wIDAQAB';
export function encrypt(value: string): string | null {
if (value == null || value === '') return null;
const encrypt = new JSEncrypt();
encrypt.setPublicKey(publicKey);
return encodeURIComponent(encrypt.encrypt(value));
}
export function getToken() {
return sessionStorage.getItem('token');
}
export function setToken(token: string | null | undefined) {
if (token == null || token === '') {
sessionStorage.removeItem('token');
} else {
sessionStorage.setItem('token', token);
}
}
export function getAppId() {
const appId = sessionStorage.getItem('appId');
return appId != null ? appId : undefined;
}
export function setAppId(appId: string | null | undefined) {
if (appId == null || appId === '') {
sessionStorage.removeItem('appId');
} else {
sessionStorage.setItem('appId', appId);
}
}
export function traversalTree(
treeNode: ANY_OBJECT,
callback: (treeNode: ANY_OBJECT) => void,
childrenKey = 'children',
) {
if (
treeNode != null &&
Array.isArray(treeNode[childrenKey]) &&
treeNode[childrenKey].length > 0
) {
treeNode[childrenKey].forEach((childNode: ANY_OBJECT) => {
traversalTree(childNode, callback, childrenKey);
});
}
return typeof callback === 'function' ? callback(treeNode) : undefined;
}
export class TreeTableImpl {
private options;
private dataList;
private dataMap;
private checkedRows: Map<string, ANY_OBJECT> | undefined;
constructor(
dataList: Array<ANY_OBJECT>,
options: {
idKey: string;
nameKey: string;
parentIdKey: string;
isLefeCallback: (item: ANY_OBJECT) => boolean | undefined;
checkStrictly: boolean;
},
) {
this.options = {
idKey: options ? options.idKey : 'id',
nameKey: options ? options.nameKey : 'name',
parentIdKey: options ? options.parentIdKey : 'parentId',
isLefeCallback: options ? options.isLefeCallback : undefined,
checkStrictly: options ? options.checkStrictly : false,
};
this.dataList = Array.isArray(dataList) ? dataList : [];
this.dataMap = new Map();
this.dataList.forEach(item => {
this.dataMap.set(item[this.options.idKey], item);
});
// 表格选中行
this.checkedRows = undefined;
this.onCheckedRowChange = this.onCheckedRowChange.bind(this);
}
/**
* 过滤表格数据
* @param {string} filterString 过滤条件字符串
* @param {boolean} onlyChecked 是否只显示选中节点
* @returns {array} 过滤后的表格数据列表
*/
getFilterTableData(filterString: string | null, onlyChecked = false) {
const { idKey, nameKey, parentIdKey, isLefeCallback } = this.options;
const tempMap = new Map();
const parentIdList: ANY_OBJECT[] = [];
this.dataList.forEach(item => {
if (
(filterString == null ||
filterString === '' ||
item[nameKey].indexOf(filterString) !== -1) &&
(!onlyChecked || (this.checkedRows != null && this.checkedRows.get(item[idKey])))
) {
if (!isLefeCallback || !isLefeCallback(item)) {
parentIdList.push(item[idKey]);
}
// 将命中节点以及它的父节点都设置为命中
let tempItem = item;
do {
tempMap.set(tempItem[idKey], tempItem);
tempItem = this.dataMap.get(tempItem[parentIdKey]);
} while (tempItem != null);
}
});
return this.dataList.map(item => {
let disabled = true;
if (parentIdList.indexOf(item[parentIdKey]) !== -1 || tempMap.get(item[idKey]) != null) {
if (
parentIdList.indexOf(item[parentIdKey]) !== -1 &&
(isLefeCallback == null || !isLefeCallback(item))
) {
parentIdList.push(item[idKey]);
}
disabled = false;
}
return {
...item,
__disabled: disabled,
};
});
}
/**
* 获取表格树数据,计算选中状态
* @param {array} dataList 表格列表数据
*/
getTableTreeData(dataList: ANY_OBJECT[], checkedRows: Map<string, ANY_OBJECT>) {
const { idKey, parentIdKey, checkStrictly } = this.options;
let treeData: ANY_OBJECT[] = [];
function calcPermCodeTreeAttribute(treeNode: ANY_OBJECT, checkedRows: Map<string, ANY_OBJECT>) {
const checkedItem = checkedRows == null ? null : checkedRows.get(treeNode[idKey]);
treeNode.__checked = checkedItem != null;
// 是否所有子权限字都被选中
let allChildChecked = true;
// 是否任意子权限字被选中
let hasChildChecked = false;
// 如果存在子权限字
if (Array.isArray(treeNode.children) && treeNode.children.length > 0) {
treeNode.children.forEach((item: ANY_OBJECT) => {
const isChecked = calcPermCodeTreeAttribute(item, checkedRows);
hasChildChecked = hasChildChecked || isChecked;
allChildChecked = allChildChecked && isChecked;
});
} else {
allChildChecked = false;
}
treeNode.__indeterminate = !checkStrictly && hasChildChecked && !allChildChecked;
treeNode.__checked = treeNode.__checked || (allChildChecked && !checkStrictly);
return treeNode.__checked || treeNode.__indeterminate;
}
if (Array.isArray(dataList)) {
treeData = treeDataTranslate(
dataList.map(item => {
return { ...item };
}),
idKey,
parentIdKey,
);
treeData.forEach(item => {
calcPermCodeTreeAttribute(item, checkedRows);
});
}
return treeData;
}
/**
* 树表格行选中状态改变
* @param {object} row 选中状态改变行数据
*/
onCheckedRowChange(row: ANY_OBJECT) {
if (this.checkedRows == null) {
this.checkedRows = new Map();
} else {
const temp = new Map();
this.checkedRows.forEach((item, key) => {
temp.set(key, item);
});
this.checkedRows = temp;
}
const { idKey } = this.options;
if (!row.__checked || row.__indeterminate) {
// 节点之前未被选中或者之前为半选状态,修改当前节点以及子节点为选中状态
this.checkedRows.set(row[idKey], row);
if (Array.isArray(row.children) && !this.options.checkStrictly) {
row.children.forEach((childNode: ANY_OBJECT) => {
traversalTree(childNode, node => {
this.checkedRows?.set(node[idKey], node);
});
});
}
} else {
// 节点之前为选中状态,修改节点以及子节点为未选中状态
this.checkedRows.delete(row[idKey]);
if (Array.isArray(row.children) && !this.options.checkStrictly) {
row.children.forEach((childNode: ANY_OBJECT) => {
traversalTree(childNode, node => {
this.checkedRows?.delete(node[idKey]);
});
});
}
}
}
/**
* 获取所有选中的权限字节点
* @param {array} treeData 树数据
* @param {boolean} includeHalfChecked 是否包含半选节点默认为false
* @returns {array} 选中节点列表
*/
getCheckedRows(treeData: ANY_OBJECT, includeHalfChecked = false) {
const checkedRows: ANY_OBJECT[] = [];
function traversalCallback(node: ANY_OBJECT) {
if (node == null) return;
if (node.__checked || (includeHalfChecked && node.__indeterminate)) {
checkedRows.push(node);
}
}
if (Array.isArray(treeData) && treeData.length > 0) {
treeData.forEach(permCode => {
traversalTree(permCode, traversalCallback, 'children');
});
}
return checkedRows;
}
/**
* 设置选中节点
* @param {array} checkedRows
*/
setCheckedRows(checkedRows: ANY_OBJECT[]) {
this.checkedRows = new Map();
if (Array.isArray(checkedRows)) {
checkedRows.forEach(item => {
const node = this.dataMap.get(item[this.options.idKey]);
if (node != null) {
this.checkedRows?.set(node[this.options.idKey], node);
}
});
}
}
/**
* 根据id获取表格行
* @param {*} id
*/
getTableRow(id: string) {
return this.dataMap.get(id);
}
}
export function formatDate(
date: string | number | Date | dayjs.Dayjs | null | undefined,
formatString: string | undefined,
) {
return dayjs(date).format(formatString);
}
export function parseDate(date: string | number | Date, formatString: string | undefined) {
return dayjs(date, formatString);
}
export function fileToBase64(file: File) {
return new Promise<string | undefined>((resolve, reject) => {
if (file == null) return reject();
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = e => {
console.log('file loaded', e);
resolve(e.target?.result as string);
};
reader.onerror = e => {
console.warn('file load', e);
reject(e);
};
});
}
export function getObjectValue(data: ANY_OBJECT, fieldName: string) {
if (data == null) return undefined;
if (fieldName == null || fieldName === '') return data;
const fieldPath = fieldName.split('.');
let tempValue = data;
if (Array.isArray(fieldPath)) {
fieldPath.forEach(key => {
if (tempValue != null) {
tempValue = tempValue[key];
}
});
}
return tempValue;
}
// 判断输入值是否一个Object
export function isObject(obj: ANY_OBJECT) {
return obj != null && typeof obj === 'object' && obj.toString() === '[object Object]';
}
function copyObject(obj: ANY_OBJECT): ANY_OBJECT {
if (obj == null) return obj;
return JSON.parse(JSON.stringify(obj));
}
export function deepMerge(obj1: ANY_OBJECT, obj2: ANY_OBJECT) {
const tempObj = copyObject(obj1);
if (obj2 != null) {
Object.keys(obj2).forEach(key => {
const val2 = obj2[key];
const val1 = tempObj[key];
if (isObject(val2)) {
// 如果两个值都是对象,则递归合并
if (isObject(val1)) {
tempObj[key] = deepMerge(val1, val2);
} else {
tempObj[key] = copyObject(val2);
}
} else if (Array.isArray(val2)) {
//console.log('......deepMerge.......', val1, val2, obj1, obj2);
// 如果两个值都是数组,则合并数组
if (Array.isArray(val1)) {
tempObj[key] = val2.map((arrVal2, index) => {
const arrVal1 = val1[index];
if (isObject(arrVal1)) {
return deepMerge(arrVal1, arrVal2);
} else {
return arrVal2;
}
});
} else {
tempObj[key] = copyObject(val2);
}
} else {
// 直接覆盖
tempObj[key] = val2;
}
});
}
return tempObj;
}

View File

@@ -0,0 +1,30 @@
export const pattern = {
mobie:
/^((\+?86)|(\(\+86\)))?(13[012356789][0-9]{8}|15[012356789][0-9]{8}|17[012356789][0-9]{8}|19[012356789][0-9]{8}|18[02356789][0-9]{8}|147[0-9]{8}|1349[0-9]{7})$/,
english: /^[a-zA-Z]+$/,
englishAndNumber: /^[a-zA-Z0-9]+$/,
};
/**
* 邮箱
* @param str
*/
export function isEmail(str: string) {
return /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((.[a-zA-Z0-9_-]{2,3}){1,2})$/.test(str);
}
/**
* 手机号码
* @param str
*/
export function isMobile(str: string) {
return pattern.mobie.test(str);
}
/**
* 电话号码
* @param str
*/
export function isPhone(str: string) {
return /^([0-9]{3,4}-)?[0-9]{7,8}$/.test(str);
}