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);
});
};