签入版本

This commit is contained in:
2026-09-11 11:39:07 +08:00
commit c381201858
928 changed files with 116443 additions and 0 deletions
+127
View File
@@ -0,0 +1,127 @@
// 默认缓存期限为7天
const DEFAULT_CACHE_TIME = 7 * 24 * 60 * 60 * 1000; // 7 天
/**
* 创建本地缓存对象
* @param {string=} prefixKey -
* @param {Object} [storage=localStorage] - sessionStorage | localStorage
*/
export const createStorage = ({ prefixKey = '', storage = localStorage } = {}) => {
/**
* 本地缓存类
* @class Storage
*/
const Storage = class {
private storage = storage;
private prefixKey?: string = prefixKey;
private getKey(key: string) {
return `${this.prefixKey}${key}`.toUpperCase();
}
/**
* @description 设置缓存
* @param {string} key 缓存键
* @param {*} value 缓存值
* @param expire
*/
set(key: string, value: any, expire: number | null = DEFAULT_CACHE_TIME) {
const stringData = JSON.stringify({
value,
expire: expire !== null ? Date.now() + expire : null,
});
this.storage.setItem(this.getKey(key), stringData);
}
/**
* 读取缓存
* @param {string} key 缓存键
* @param {*=} def 默认值
*/
get(key: string, def: any = null) {
const item = this.storage.getItem(this.getKey(key));
if (item) {
try {
const data = JSON.parse(item);
const { value, expire } = data;
// 在有效期内直接返回
if (expire === null || expire >= Date.now()) {
return value;
}
this.remove(key);
} catch (e) {
return def;
}
}
return def;
}
/**
* 从缓存删除某项
* @param {string} key
*/
remove(key: string) {
this.storage.removeItem(this.getKey(key));
}
/**
* 清空所有缓存
* @memberOf Cache
*/
clear(): void {
this.storage.clear();
}
/**
* 设置cookie
* @param {string} name cookie 名称
* @param {*} value cookie 值
* @param {number=} expire 过期时间
* 如果过期时间未设置,默认关闭浏览器自动删除
* @example
*/
setCookie(name: string, value: any, expire: number | null = DEFAULT_CACHE_TIME) {
document.cookie = `${this.getKey(name)}=${value}; Max-Age=${expire}`;
}
/**
* 根据名字获取cookie值
* @param name
*/
getCookie(name: string): string {
const cookieArr = document.cookie.split('; ');
for (let i = 0, length = cookieArr.length; i < length; i++) {
const kv = cookieArr[i].split('=');
if (kv[0] === this.getKey(name)) {
return kv[1];
}
}
return '';
}
/**
* 根据名字删除指定的cookie
* @param {string} key
*/
removeCookie(key: string) {
this.setCookie(key, 1, -1);
}
/**
* 清空cookie,使所有cookie失效
*/
clearCookie(): void {
const keys = document.cookie.match(/[^ =;]+(?==)/g);
if (keys) {
for (let i = keys.length; i--; ) {
document.cookie = keys[i] + '=0;expire=' + new Date(0).toUTCString();
}
}
}
};
return new Storage();
};
export const storage = createStorage();
export default Storage;
+282
View File
@@ -0,0 +1,282 @@
import { isObject } from '@vue/shared';
import { cloneDeep } from 'lodash';
import type { RouteLocationNormalizedLoaded } from 'vue-router';
import { RouteLocationNormalized, useRoute } from 'vue-router';
import { watch } from 'vue';
import { ElLoading, ElMessage, ElMessageBox } from 'element-plus';
import type { LoadingInstance } from 'element-plus/es/components/loading/src/loading';
export function isExternal(path: string) {
return /^(https?:|mailto:|tel:)/.test(path);
}
/**
* @description 获取正确的路经
* @param {String} path 数据
*/
export function getNormalPath(path: string) {
if (path.length === 0 || !path || path == 'undefined') {
return path;
}
const newPath = path.replace('//', '/');
const length = newPath.length;
if (newPath[length - 1] === '/') {
return newPath.slice(0, length - 1);
}
return newPath;
}
export const isEmpty = (value: unknown) => {
return value == null && typeof value == 'undefined';
};
/**
* @description对象格式化为Query语法
* @param { Object } params
* @return {string} Query语法
*/
export function objectToQuery(params: Record<string, any>): string {
let query = '';
for (const props of Object.keys(params)) {
const value = params[props];
const part = encodeURIComponent(props) + '=';
if (!isEmpty(value)) {
if (isObject(value)) {
for (const key of Object.keys(value)) {
if (!isEmpty(value[key])) {
const params = props + '[' + key + ']';
const subPart = encodeURIComponent(params) + '=';
query += subPart + encodeURIComponent(value[key]) + '&';
}
}
} else {
query += part + encodeURIComponent(value) + '&';
}
}
}
return query.slice(0, -1);
}
export const addUnit = (value: string | number, unit = 'px') => {
return !Object.is(Number(value), NaN) ? `${value}${unit}` : value;
};
export function useWatchRoute(callback: (route: RouteLocationNormalizedLoaded) => void) {
const route = useRoute();
watch(
route,
() => {
callback(route);
},
{
immediate: true,
},
);
return {
route,
};
}
// declare type MessageType = '' | 'success' | 'warning' | 'info' | 'error';
export function confirm(msg: string, type: 'warning') {
return ElMessageBox.confirm(msg, '温馨提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: type,
});
}
export function message(msg: string, type = 'success') {
ElMessage[type](msg);
}
let loadingInstance = null;
// 打开全局loading
export function loading(msg: string) {
loadingInstance = ElLoading.service({
lock: true,
text: msg,
});
}
// 关闭全局loading
export function closeLoading() {
loadingInstance?.close();
}
export const arrayToTree = (
data: any[],
props = { id: 'id', parentId: 'pid', children: 'children' },
) => {
data = cloneDeep(data);
const { id, parentId, children } = props;
const result: any[] = [];
const map = new Map();
data.forEach((item) => {
map.set(item[id], item);
const parent = map.get(item[parentId]);
if (parent) {
parent[children] = parent[children] ?? [];
parent[children].push(item);
} else {
result.push(item);
}
});
return result;
};
/**
* @description 树转数组,队列实现广度优先遍历
* @param {Array} data 数据
* @param {Object} props `{ children: 'children' }`
*/
export const treeToArray = (data: any[], props = { children: 'children' }) => {
data = cloneDeep(data);
const { children } = props;
const newData = [];
const queue: any[] = [];
data.forEach((child: any) => queue.push(child));
while (queue.length) {
const item: any = queue.shift();
if (item[children]) {
item[children].forEach((child: any) => queue.push(child));
delete item[children];
}
newData.push(item);
}
return newData;
};
/**
* 数组转树型结构
*/
export const buildTree = (array) => {
const tree = {}; // 用于存储树形结构的临时对象
const result = []; // 最终的树形结构数组
array.forEach((item) => {
tree[item.id] = { ...item, children: [] }; // 初始化树节点
});
array.forEach((item) => {
if (item.parentId === 0) {
result.push(tree[item.id]); // 根节点直接添加到结果数组
} else if (tree[item.parentId]) {
tree[item.parentId].children.push(tree[item.id]); // 子节点添加到父节点的children数组
}
});
return result;
};
/***
* 递归查找
*/
export const findTreeByPath = (tree, path, result = []) => {
for (const node of tree) {
if (node.path === path) {
result.push(node);
}
if (node.children && node.children.length > 0) {
findTreeByPath(node.children, path, result);
}
}
return result;
};
export const getComponentName = (route: RouteLocationNormalized) => {
return route.matched[route.matched.length - 1]?.components?.default?.name;
};
export function streamFileDownload(file: any, fileName = '文件名称.zip') {
const blob = new Blob([file], { type: 'application/octet-stream;charset=UTF-8' });
const url = window.URL.createObjectURL(blob);
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); // 下载完成移除元素
window.URL.revokeObjectURL(url);
}
export function filterName(list: any, value: any, key = 'value', label = 'label') {
let name = '';
const child = list.find((item) => item[key] == value);
name = child ? child[label] : '';
return name;
}
export function filterDatetime(value: any, format = 'yyyy-MM-dd') {
if (value) {
return dateFtt(format, new Date(parseInt(value)));
} else {
return '';
}
}
export function generateUUID() {
if (typeof crypto === 'object') {
if (typeof crypto.randomUUID === 'function') {
return crypto.randomUUID();
}
if (typeof crypto.getRandomValues === 'function' && typeof Uint8Array === 'function') {
const callback = (c: any) => {
const num = Number(c);
return (num ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (num / 4)))).toString(
16,
);
};
return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, callback);
}
}
let timestamp = new Date().getTime();
let performanceNow =
(typeof performance !== 'undefined' && performance.now && performance.now() * 1000) || 0;
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
let random = Math.random() * 16;
if (timestamp > 0) {
random = (timestamp + random) % 16 | 0;
timestamp = Math.floor(timestamp / 16);
} else {
random = (performanceNow + random) % 16 | 0;
performanceNow = Math.floor(performanceNow / 16);
}
return (c === 'x' ? random : (random & 0x3) | 0x8).toString(16);
});
}
export function toUnderline(str: string) {
return str.replace(/([A-Z])/g, '_$1').toLowerCase();
}
export function downBlobFile(url: any, query: any, fileName: string) {
return request({
url: url,
method: 'get',
responseType: 'blob',
params: query,
}).then((response) => {
handleBlobFile(response, fileName);
});
}
function dateFtt(fmt, date) {
//author: meizz
const o = {
'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(), //毫秒
};
if (/(y+)/.test(fmt))
fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
for (const k in o)
if (new RegExp('(' + k + ')').test(fmt))
fmt = fmt.replace(
RegExp.$1,
RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length),
);
return fmt;
}
+142
View File
@@ -0,0 +1,142 @@
/**
* @description 获取用户浏览器版本及系统信息
* @param {string='zh-cn' | 'en'} lang 返回中文的信息还是英文的
* @constructor
*/
export default function BrowserType(lang: 'zh-cn' | 'en' = 'en') {
// 权重:系统 + 系统版本 > 平台 > 内核 + 载体 + 内核版本 + 载体版本 > 外壳 + 外壳版本
const ua = navigator.userAgent.toLowerCase();
const testUa = (regexp) => regexp.test(ua);
const testVs = (regexp) =>
ua
.match(regexp)
?.toString()
.replace(/[^0-9|_.]/g, '')
.replace(/_/g, '.');
// 系统
const system =
new Map([
[testUa(/windows|win32|win64|wow32|wow64/g), 'windows'], // windows系统
[testUa(/macintosh|macintel/g), 'macos'], // macos系统
[testUa(/x11/g), 'linux'], // linux系统
[testUa(/android|adr/g), 'android'], // android系统
[testUa(/ios|iphone|ipad|ipod|iwatch/g), 'ios'], // ios系统
]).get(true) || 'unknow';
// 系统版本
const systemVs =
new Map([
[
'windows',
new Map([
[testUa(/windows nt 5.0|windows 2000/g), '2000'],
[testUa(/windows nt 5.1|windows xp/g), 'xp'],
[testUa(/windows nt 5.2|windows 2003/g), '2003'],
[testUa(/windows nt 6.0|windows vista/g), 'vista'],
[testUa(/windows nt 6.1|windows 7/g), '7'],
[testUa(/windows nt 6.2|windows 8/g), '8'],
[testUa(/windows nt 6.3|windows 8.1/g), '8.1'],
[testUa(/windows nt 10.0|windows 10/g), '10'],
]).get(true),
],
['macos', testVs(/os x [\d._]+/g)],
['android', testVs(/android [\d._]+/g)],
['ios', testVs(/os [\d._]+/g)],
]).get(system) || 'unknow';
// 平台
let platform = 'unknow';
if (system === 'windows' || system === 'macos' || system === 'linux') {
platform = 'desktop'; // 桌面端
} else if (system === 'android' || system === 'ios' || testUa(/mobile/g)) {
platform = 'mobile'; // 移动端
}
// 内核和载体
const [engine = 'unknow', supporter = 'unknow'] = new Map([
[
testUa(/applewebkit/g),
[
'webkit',
new Map([
// webkit内核
[testUa(/safari/g), 'safari'], // safari浏览器
[testUa(/chrome/g), 'chrome'], // chrome浏览器
[testUa(/opr/g), 'opera'], // opera浏览器
[testUa(/edge/g), 'edge'], // edge浏览器
]).get(true),
] || 'unknow',
], // [webkit内核, xxx浏览器]
[testUa(/gecko/g) && testUa(/firefox/g), ['gecko', 'firefox']], // [gecko内核,firefox浏览器]
[testUa(/presto/g), ['presto', 'opera']], // [presto内核,opera浏览器]
[testUa(/trident|compatible|msie/g), ['trident', 'iexplore']], // [trident内核,iexplore浏览器]
]).get(true) || ['unknow', 'unknow'];
// 内核版本
const engineVs =
new Map([
['webkit', testVs(/applewebkit\/[\d._]+/g)],
['gecko', testVs(/gecko\/[\d._]+/g)],
['presto', testVs(/presto\/[\d._]+/g)],
['trident', testVs(/trident\/[\d._]+/g)],
]).get(engine) || 'unknow';
// 载体版本
const supporterVs =
new Map([
['firefox', testVs(/firefox\/[\d._]+/g)],
['opera', testVs(/opr\/[\d._]+/g)],
['iexplore', testVs(/(msie [\d._]+)|(rv:[\d._]+)/g)],
['edge', testVs(/edge\/[\d._]+/g)],
['safari', testVs(/version\/[\d._]+/g)],
['chrome', testVs(/chrome\/[\d._]+/g)],
]).get(supporter) || 'unknow';
// 外壳和外壳版本
const [shell = 'none', shellVs = 'unknow'] = new Map([
[testUa(/micromessenger/g), ['wechat', testVs(/micromessenger\/[\d._]+/g)]], // [微信浏览器,]
[testUa(/qqbrowser/g), ['qq', testVs(/qqbrowser\/[\d._]+/g)]], // [QQ浏览器,]
[testUa(/ucbrowser/g), ['uc', testVs(/ucbrowser\/[\d._]+/g)]], // [UC浏览器,]
[testUa(/qihu 360se/g), ['360', 'unknow']], // [360浏览器(无版本),]
[testUa(/2345explorer/g), ['2345', testVs(/2345explorer\/[\d._]+/g)]], // [2345浏览器,]
[testUa(/metasr/g), ['sougou', 'unknow']], // [搜狗浏览器(无版本),]
[testUa(/lbbrowser/g), ['liebao', 'unknow']], // [猎豹浏览器(无版本),]
[testUa(/maxthon/g), ['maxthon', testVs(/maxthon\/[\d._]+/g)]], // [遨游浏览器,]
]).get(true) || ['none', 'unknow'];
return {
'zh-cn': Object.assign(
{
内核: engine, // 内核: webkit gecko presto trident
内核版本: engineVs, // 内核版本
平台: platform, // 平台: desktop mobile
载体: supporter, // 载体: chrome safari firefox opera iexplore edge
载体版本: supporterVs, // 载体版本
系统: system, // 系统: windows macos linux android ios
系统版本: systemVs, // 系统版本
},
shell === 'none'
? {}
: {
外壳: shell, // 外壳: wechat qq uc 360 2345 sougou liebao maxthon
外壳版本: shellVs, // 外壳版本
},
),
en: Object.assign(
{
engine, // 内核: webkit gecko presto trident
engineVs, // 内核版本
platform, // 平台: desktop mobile
supporter, // 载体: chrome safari firefox opera iexplore edge
supporterVs, // 载体版本
system, // 系统: windows macos linux android ios
systemVs, // 系统版本
},
shell === 'none'
? {}
: {
shell, // 外壳: wechat qq uc 360 2345 sougou liebao maxthon
shellVs, // 外壳版本
},
),
}[lang];
}
+29
View File
@@ -0,0 +1,29 @@
export default {
//hex颜色转rgb颜色
HexToRgb(str) {
str = str.replace('#', '');
const hxs = str.match(/../g);
for (let i = 0; i < 3; i++) hxs[i] = parseInt(hxs[i], 16);
return hxs;
},
//rgb颜色转hex颜色
RgbToHex(a, b, c) {
const hexs = [a.toString(16), b.toString(16), c.toString(16)];
for (let i = 0; i < 3; i++) {
if (hexs[i].length == 1) hexs[i] = '0' + hexs[i];
}
return '#' + hexs.join('');
},
//加深
darken(color, level) {
const rgbc = this.HexToRgb(color);
for (let i = 0; i < 3; i++) rgbc[i] = Math.floor(rgbc[i] * (1 - level));
return this.RgbToHex(rgbc[0], rgbc[1], rgbc[2]);
},
//变淡
lighten(color, level) {
const rgbc = this.HexToRgb(color);
for (let i = 0; i < 3; i++) rgbc[i] = Math.floor((255 - rgbc[i]) * level + rgbc[i]);
return this.RgbToHex(rgbc[0], rgbc[1], rgbc[2]);
},
};
+12
View File
@@ -0,0 +1,12 @@
import dayjs from 'dayjs';
const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm';
const DATE_FORMAT = 'YYYY-MM-DD ';
export function formatToDateTime(date: Date, formatStr = DATE_TIME_FORMAT): string {
return dayjs(date).format(formatStr);
}
export function formatToDate(date: Date, formatStr = DATE_FORMAT): string {
return dayjs(date).format(formatStr);
}
+165
View File
@@ -0,0 +1,165 @@
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);
}
+75
View File
@@ -0,0 +1,75 @@
/**
* 根据文件url获取文件名
* @param url 文件url
*/
function getFileName(url) {
const num = url.lastIndexOf('/') + 1;
let fileName = url.substring(num);
//把参数和文件名分割开
fileName = decodeURI(fileName.split('?')[0]);
return fileName;
}
/**
* 根据文件地址下载文件
* @param {*} sUrl
*/
export function downloadByUrl({
url,
target = '_blank',
fileName,
}: {
url: string;
target?: '_self' | '_blank';
fileName?: string;
}): Promise<boolean> {
// 是否同源
const isSameHost = new URL(url).host == location.host;
return new Promise<boolean>((resolve, reject) => {
if (isSameHost) {
const link = document.createElement('a');
link.href = url;
link.target = target;
if (link.download !== undefined) {
link.download = fileName || getFileName(url);
}
if (document.createEvent) {
const e = document.createEvent('MouseEvents');
e.initEvent('click', true, true);
link.dispatchEvent(e);
return resolve(true);
}
if (url.indexOf('?') === -1) {
url += '?download';
}
window.open(url, target);
return resolve(true);
} else {
const canvas = document.createElement('canvas');
const img = document.createElement('img');
img.setAttribute('crossOrigin', 'Anonymous');
img.src = url;
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
const context = canvas.getContext('2d')!;
context.drawImage(img, 0, 0, img.width, img.height);
// window.navigator.msSaveBlob(canvas.msToBlob(),'image.jpg');
// saveAs(imageDataUrl, '附件');
canvas.toBlob((blob) => {
const link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = getFileName(url);
link.click();
URL.revokeObjectURL(link.href);
resolve(true);
}, 'image/jpeg');
};
img.onerror = (e) => reject(e);
}
});
}
+87
View File
@@ -0,0 +1,87 @@
import type { GlobEnvConfig } from '/#/config';
import { warn } from '@/utils/log';
import pkg from '../../package.json';
import { getConfigFileName } from '../../build/getConfigFileName';
export function getCommonStoragePrefix() {
const { VITE_GLOB_APP_SHORT_NAME } = getAppEnvConfig();
return `${VITE_GLOB_APP_SHORT_NAME}__${getEnv()}`.toUpperCase();
}
// Generate cache key according to version
export function getStorageShortName() {
return `${getCommonStoragePrefix()}${`__${pkg.version}`}__`.toUpperCase();
}
export function getAppEnvConfig() {
const ENV_NAME = getConfigFileName(import.meta.env);
const ENV = (import.meta.env.DEV
? // Get the global configuration (the configuration will be extracted independently when packaging)
(import.meta.env as unknown as GlobEnvConfig)
: window[ENV_NAME as any]) as unknown as GlobEnvConfig;
const {
VITE_GLOB_APP_TITLE,
VITE_GLOB_API_URL,
VITE_GLOB_APP_SHORT_NAME,
VITE_GLOB_API_URL_PREFIX,
VITE_GLOB_UPLOAD_URL,
VITE_GLOB_PROD_MOCK,
VITE_GLOB_IMG_URL,
} = 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_API_URL,
VITE_GLOB_APP_SHORT_NAME,
VITE_GLOB_API_URL_PREFIX,
VITE_GLOB_UPLOAD_URL,
VITE_GLOB_PROD_MOCK,
VITE_GLOB_IMG_URL,
};
}
/**
* @description: Development model
*/
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;
}
+57
View File
@@ -0,0 +1,57 @@
/**
* @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;
});
}
/**
* base64 to file
* @param dataURL
*/
export function base64ToFile(dataurl:any, filename:any) {
let arr = dataurl.split(",");
let mime = arr[0].match(/:(.*?);/)[1];
let bstr = atob(arr[1]);
let len = bstr.length;
let u8arr = new Uint8Array(len);
while (len--) {
u8arr[len] = bstr.charCodeAt(len);
}
return new File([u8arr], filename, {type: mime});
}
+101
View File
@@ -0,0 +1,101 @@
import { openWindow } from '..';
import { dataURLtoBlob, urlToBase64 } from './base64Conver';
import { TargetContext } from '/#/index';
/**
* 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, name: string, mime?: string, bom?: BlobPart) {
const blobData = typeof bom !== 'undefined' ? [bom, data] : [data];
const blob = new Blob(blobData, { type: mime || 'application/octet-stream' });
const fileName =
name?.match(/filename\*?=(utf-8'')?([^;]+)/i)?.[2]?.replace(/['"]/g, '') || 'export.xlsx';
if (typeof window.navigator.msSaveBlob !== 'undefined') {
window.navigator.msSaveBlob(blob, decodeURIComponent(fileName));
} else {
const blobURL = window.URL.createObjectURL(blob);
const tempLink = document.createElement('a');
tempLink.style.display = 'none';
tempLink.href = blobURL;
tempLink.setAttribute('download', decodeURIComponent(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;
}
+265
View File
@@ -0,0 +1,265 @@
/**
* 时间日期转换
* @param date 当前时间,new Date() 格式
* @param format 需要转换的时间格式字符串
* @description format 字符串随意,如 `YYYY-mm、YYYY-mm-dd`
* @description format 季度:"YYYY-mm-dd HH:MM:SS QQQQ"
* @description format 星期:"YYYY-mm-dd HH:MM:SS WWW"
* @description format 几周:"YYYY-mm-dd HH:MM:SS ZZZ"
* @description format 季度 + 星期 + 几周:"YYYY-mm-dd HH:MM:SS WWW QQQQ ZZZ"
* @returns 返回拼接后的时间字符串
*/
export function formatDate(date: Date, format: string): string {
const we = date.getDay(); // 星期
const z = getWeek(date); // 周
const qut = Math.floor((date.getMonth() + 3) / 3).toString(); // 季度
const opt: { [key: string]: string } = {
'Y+': date.getFullYear().toString(), // 年
'm+': (date.getMonth() + 1).toString(), // 月(月份从0开始,要+1)
'd+': date.getDate().toString(), // 日
'H+': date.getHours().toString(), // 时
'M+': date.getMinutes().toString(), // 分
'S+': date.getSeconds().toString(), // 秒
'q+': qut, // 季度
};
// 中文数字 (星期)
const week: { [key: string]: string } = {
'0': '日',
'1': '一',
'2': '二',
'3': '三',
'4': '四',
'5': '五',
'6': '六',
};
// 中文数字(季度)
const quarter: { [key: string]: string } = {
'1': '一',
'2': '二',
'3': '三',
'4': '四',
};
if (/(W+)/.test(format))
format = format.replace(
RegExp.$1,
RegExp.$1.length > 1
? RegExp.$1.length > 2
? '星期' + week[we]
: '周' + week[we]
: week[we],
);
if (/(Q+)/.test(format))
format = format.replace(
RegExp.$1,
RegExp.$1.length == 4 ? '第' + quarter[qut] + '季度' : quarter[qut],
);
if (/(Z+)/.test(format))
format = format.replace(RegExp.$1, RegExp.$1.length == 3 ? '第' + z + '周' : z + '');
for (const k in opt) {
const r = new RegExp('(' + k + ')').exec(format);
// 若输入的长度不为1,则前面补零
if (r)
format = format.replace(
r[1],
RegExp.$1.length == 1 ? opt[k] : opt[k].padStart(RegExp.$1.length, '0'),
);
}
return format;
}
/**
* 获取当前日期是第几周
* @param dateTime 当前传入的日期值
* @returns 返回第几周数字值
*/
export function getWeek(dateTime: Date): number {
const temptTime = new Date(dateTime.getTime());
// 周几
const weekday = temptTime.getDay() || 7;
// 周1+5天=周六
temptTime.setDate(temptTime.getDate() - weekday + 1 + 5);
let firstDay = new Date(temptTime.getFullYear(), 0, 1);
const dayOfWeek = firstDay.getDay();
let spendDay = 1;
if (dayOfWeek != 0) spendDay = 7 - dayOfWeek + 1;
firstDay = new Date(temptTime.getFullYear(), 0, 1 + spendDay);
const d = Math.ceil((temptTime.valueOf() - firstDay.valueOf()) / 86400000);
const result = Math.ceil(d / 7);
return result;
}
/**
* 将时间转换为 `几秒前`、`几分钟前`、`几小时前`、`几天前`
* @param param 当前时间,new Date() 格式或者字符串时间格式
* @param format 需要转换的时间格式字符串
* @description param 10秒: 10 * 1000
* @description param 1分: 60 * 1000
* @description param 1小时: 60 * 60 * 1000
* @description param 24小时:60 * 60 * 24 * 1000
* @description param 3天: 60 * 60* 24 * 1000 * 3
* @returns 返回拼接后的时间字符串
*/
export function formatPast(param: string | Date, format = 'YYYY-mm-dd'): string {
// 传入格式处理、存储转换值
let t: any, s: number;
// 获取js 时间戳
let time: number = new Date().getTime();
// 是否是对象
typeof param === 'string' || 'object' ? (t = new Date(param).getTime()) : (t = param);
// 当前时间戳 - 传入时间戳
time = Number.parseInt(`${time - t}`);
if (time < 10000) {
// 10秒内
return '刚刚';
} else if (time < 60000 && time >= 10000) {
// 超过10秒少于1分钟内
s = Math.floor(time / 1000);
return `${s}秒前`;
} else if (time < 3600000 && time >= 60000) {
// 超过1分钟少于1小时
s = Math.floor(time / 60000);
return `${s}分钟前`;
} else if (time < 86400000 && time >= 3600000) {
// 超过1小时少于24小时
s = Math.floor(time / 3600000);
return `${s}小时前`;
} else if (time < 259200000 && time >= 86400000) {
// 超过1天少于3天内
s = Math.floor(time / 86400000);
return `${s}天前`;
} else {
// 超过3天
const date = typeof param === 'string' || 'object' ? new Date(param) : param;
return formatDate(date, format);
}
}
/**
* 时间问候语
* @param param 当前时间,new Date() 格式
* @description param 调用 `formatAxis(new Date())` 输出 `上午好`
* @returns 返回拼接后的时间字符串
*/
export function formatAxis(param: Date): string {
const hour: number = new Date(param).getHours();
if (hour < 6) return '凌晨好';
else if (hour < 9) return '早上好';
else if (hour < 12) return '上午好';
else if (hour < 14) return '中午好';
else if (hour < 17) return '下午好';
else if (hour < 19) return '傍晚好';
else if (hour < 22) return '晚上好';
else return '夜里好';
}
// 日期格式化
export function parseTime(time, pattern) {
if (arguments.length === 0 || !time) {
return null;
}
const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}';
let date;
if (typeof time === 'object') {
date = time;
} else {
if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
time = parseInt(time);
} else if (typeof time === 'string') {
time = time
.replace(new RegExp(/-/gm), '/')
.replace('T', ' ')
.replace(new RegExp(/\.[\d]{3}/gm), '');
}
if (typeof time === 'number' && time.toString().length === 10) {
time = time * 1000;
}
date = new Date(time);
}
const formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay(),
};
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
let value = formatObj[key];
// Note: getDay() returns 0 on Sunday
if (key === 'a') {
return ['日', '一', '二', '三', '四', '五', '六'][value];
}
if (result.length > 0 && value < 10) {
value = '0' + value;
}
return value || 0;
});
return time_str;
}
// 日期格式化
export function parseDate(time, pattern) {
if (arguments.length === 0 || !time) {
return null;
}
const format = pattern || '{y}-{m}-{d}';
let date;
if (typeof time === 'object') {
date = time;
} else {
if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
time = parseInt(time);
} else if (typeof time === 'string') {
time = time
.replace(new RegExp(/-/gm), '/')
.replace('T', ' ')
.replace(new RegExp(/\.[\d]{3}/gm), '');
}
if (typeof time === 'number' && time.toString().length === 10) {
time = time * 1000;
}
date = new Date(time);
}
const formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay(),
};
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
let value = formatObj[key];
// Note: getDay() returns 0 on Sunday
if (key === 'a') {
return ['日', '一', '二', '三', '四', '五', '六'][value];
}
if (result.length > 0 && value < 10) {
value = '0' + value;
}
return value || 0;
});
return time_str;
}
export const dateTimeStr = 'YYYY-MM-DD HH:mm:ss';
export const dateStr = 'YYYY-MM-DD';
export const timeStr = 'HH:mm:ss';
export function formatSeconds(second: any) {
second = parseInt(second); // 秒
let duration;
const days = Math.floor(second / 86400);
const hours = Math.floor((second % 86400) / 3600);
const minutes = Math.floor(((second % 86400) % 3600) / 60);
const seconds = Math.floor(((second % 86400) % 3600) % 60);
if (days > 0) duration = days + '天' + hours + '时' + minutes + '分' + seconds + '秒';
else if (hours > 0) duration = hours + '时' + minutes + '分' + seconds + '秒';
else if (minutes > 0) duration = minutes + '分' + seconds + '秒';
else if (seconds > 0) duration = seconds + '秒';
return duration;
}
+83
View File
@@ -0,0 +1,83 @@
interface TreeHelperConfig {
id: string;
children: string;
pid: string;
}
const DEFAULT_CONFIG: TreeHelperConfig = {
id: 'id',
children: 'children',
pid: 'pid',
};
const getConfig = (config: Partial<TreeHelperConfig>) => Object.assign({}, DEFAULT_CONFIG, config);
export function filter<T = any>(tree: T[], func: (n: T) => boolean): T[] {
const children = 'children';
function listFilter(list: T[]) {
return list
.map((node: any) => ({ ...node }))
.filter((node) => {
node[children] = node[children] && listFilter(node[children]);
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]);
}
}
/**
* 获取 Tree 中 value
* @param data 树形数据结构
* @param key 树形中 key 字段
**/
export function getTreeValues<T = any>(data: T[], key = 'key') {
const keys: any[] = [];
data.map((item: any) => {
!keys.includes(item[key]) && keys.push(item[key]);
if (item.children && item.children.length) {
getTreeValues(item.children);
}
});
return keys;
}
/**
* 替换 Tree 中 字段
* @param data 树形数据结构
* @param labelKey 树形中 labe 字段
* @param valueKey 树形中 value 字段
**/
export function replaceParams<T = any>(data: T[], labelKey: string, valueKey: string) {
const newData: any[] = [];
for (const item of data) {
const info: any = item;
if (info.children && info.children.length > 0) {
info.children = replaceParams(info.children, labelKey, valueKey);
}
const param = {
...item,
label: item[labelKey],
value: item[valueKey],
};
newData.push(param);
}
return newData;
}
+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;
}
+200
View File
@@ -0,0 +1,200 @@
import type { AxiosRequestConfig, AxiosInstance, AxiosResponse } from 'axios';
import axios from 'axios';
import { AxiosCanceler } from './axiosCancel';
import { isFunction } from '@/utils/is';
import { cloneDeep } from 'lodash-es';
import type { RequestOptions, CreateAxiosOptions, Result, UploadFileParams } from './types';
import { ContentTypeEnum } from '@/enums/httpEnum';
export * from './axiosTransform';
/**
* @description: axios模块
*/
export class VAxios {
private axiosInstance: AxiosInstance;
private options: CreateAxiosOptions;
constructor(options: CreateAxiosOptions) {
this.options = options;
this.axiosInstance = axios.create(options);
this.setupInterceptors();
}
getAxios(): AxiosInstance {
return this.axiosInstance;
}
/**
* @description: 重新配置axios
*/
configAxios(config: CreateAxiosOptions) {
if (!this.axiosInstance) {
return;
}
this.createAxios(config);
}
/**
* @description: 设置通用header
*/
setHeader(headers: any): void {
if (!this.axiosInstance) {
return;
}
Object.assign(this.axiosInstance.defaults.headers, headers);
}
/**
* @description: 请求方法
*/
request<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
let conf: AxiosRequestConfig = cloneDeep(config);
const transform = this.getTransform();
const { requestOptions } = this.options;
const opt: RequestOptions = Object.assign({}, requestOptions, options);
const { beforeRequestHook, requestCatch, transformRequestData } = transform || {};
if (beforeRequestHook && isFunction(beforeRequestHook)) {
conf = beforeRequestHook(conf, opt);
}
//这里重新 赋值成最新的配置
// @ts-ignore
conf.requestOptions = opt;
return new Promise((resolve, reject) => {
this.axiosInstance
.request<any, AxiosResponse<Result>>(conf)
.then((res: AxiosResponse<Result>) => {
// 请求是否被取消
const isCancel = axios.isCancel(res);
if (transformRequestData && isFunction(transformRequestData) && !isCancel) {
try {
const ret = transformRequestData(res, opt);
resolve(ret);
} catch (err) {
reject(err || new Error('request error!'));
}
return;
}
resolve(res as unknown as Promise<T>);
})
.catch((e: Error) => {
if (requestCatch && isFunction(requestCatch)) {
reject(requestCatch(e));
return;
}
reject(e);
});
});
}
/**
* @description: 创建axios实例
*/
private createAxios(config: CreateAxiosOptions): void {
this.axiosInstance = axios.create(config);
}
private getTransform() {
const { transform } = this.options;
return transform;
}
/**
* @description: 文件上传
*/
uploadFile<T = any>(config: AxiosRequestConfig, params: UploadFileParams) {
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);
}
if (params.data) {
Object.keys(params.data).forEach((key) => {
const value = params.data![key];
if (Array.isArray(value)) {
value.forEach((item) => {
formData.append(`${key}[]`, item);
});
return;
}
formData.append(key, params.data![key]);
});
}
return this.axiosInstance.request<T>({
method: 'POST',
data: formData,
headers: {
'Content-type': ContentTypeEnum.FORM_DATA,
ignoreCancelToken: true,
},
...config,
});
}
/**
* @description: 拦截器配置
*/
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) => {
const {
headers: { ignoreCancelToken },
} = config;
const ignoreCancel =
ignoreCancelToken !== undefined
? ignoreCancelToken
: this.options.requestOptions?.ignoreCancelToken;
!ignoreCancel && axiosCanceler.addPending(config);
if (requestInterceptors && isFunction(requestInterceptors)) {
config = requestInterceptors(config, this.options);
}
return config;
}, undefined);
// 请求拦截器错误捕获
requestInterceptorsCatch &&
isFunction(requestInterceptorsCatch) &&
this.axiosInstance.interceptors.request.use(undefined, requestInterceptorsCatch);
// 响应结果拦截器处理
this.axiosInstance.interceptors.response.use((res: AxiosResponse<any>) => {
res && axiosCanceler.removePending(res.config);
if (responseInterceptors && isFunction(responseInterceptors)) {
res = responseInterceptors(res);
}
return res;
}, undefined);
// 响应结果拦截器错误捕获
responseInterceptorsCatch &&
isFunction(responseInterceptorsCatch) &&
this.axiosInstance.interceptors.response.use(undefined, responseInterceptorsCatch);
}
}
+61
View File
@@ -0,0 +1,61 @@
import axios, { AxiosRequestConfig, Canceler } from 'axios';
import qs from 'qs';
import { isFunction } from '@/utils/is/index';
// 声明一个 Map 用于存储每个请求的标识 和 取消函数
let pendingMap = new Map<string, Canceler>();
export const getPendingUrl = (config: AxiosRequestConfig) =>
[config.method, config.url, qs.stringify(config.data), qs.stringify(config.params)].join('&');
export class AxiosCanceler {
/**
* 添加请求
* @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)) {
// 如果 pending 中不存在当前请求,则添加进去
pendingMap.set(url, cancel);
}
});
}
/**
* @description: 清空所有pending
*/
removeAllPending() {
pendingMap.forEach((cancel) => {
cancel && isFunction(cancel) && cancel();
});
pendingMap.clear();
}
/**
* 移除请求
* @param {Object} config
*/
removePending(config: AxiosRequestConfig) {
const url = getPendingUrl(config);
if (pendingMap.has(url)) {
// 如果在 pending 中存在当前请求标识,需要取消当前请求,并且移除
const cancel = pendingMap.get(url);
cancel && cancel(url);
pendingMap.delete(url);
}
}
/**
* @description: 重置
*/
reset(): void {
pendingMap = new Map<string, Canceler>();
}
}
+52
View File
@@ -0,0 +1,52 @@
/**
* 数据处理类,可以根据项目自行配置
*/
import type { AxiosRequestConfig, AxiosResponse } from 'axios';
import type { RequestOptions, Result } from './types';
export interface CreateAxiosOptions extends AxiosRequestConfig {
authenticationScheme?: string;
transform?: AxiosTransform;
requestOptions?: RequestOptions;
}
export abstract class AxiosTransform {
/**
* @description: 请求之前处理配置
* @description: Process configuration before request
*/
beforeRequestHook?: (config: AxiosRequestConfig, options: RequestOptions) => AxiosRequestConfig;
/**
* @description: 请求成功处理
*/
transformRequestData?: (res: AxiosResponse<Result>, options: RequestOptions) => any;
/**
* @description: 请求失败处理
*/
requestCatch?: (e: Error) => 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;
}
+47
View File
@@ -0,0 +1,47 @@
import { ElMessage } from 'element-plus';
export function checkStatus(status: number, msg: string): void {
switch (status) {
case 400:
ElMessage.error(msg);
break;
// 401: 未登录
// 未登录则跳转登录页面,并携带当前页面的路径
// 在登录成功后返回当前页面,这一步需要在登录页操作。
case 401:
ElMessage.error('用户没有权限(令牌、用户名、密码错误)!');
break;
case 403:
ElMessage.error('用户得到授权,但是访问是被禁止的。!');
break;
// 404请求不存在
case 404:
ElMessage.error('网络请求错误,未找到该资源!');
break;
case 405:
ElMessage.error('网络请求错误,请求方法未允许!');
break;
case 408:
ElMessage.error('网络请求超时');
break;
case 500:
ElMessage.error('服务器错误,请联系管理员!');
break;
case 501:
ElMessage.error('网络未实现');
break;
case 502:
ElMessage.error('网络错误');
break;
case 503:
ElMessage.error('服务不可用,服务器暂时过载或维护!');
break;
case 504:
ElMessage.error('网络超时');
break;
case 505:
ElMessage.error('http版本不支持该请求!');
break;
default:
ElMessage.error(msg);
}
}
+47
View File
@@ -0,0 +1,47 @@
import { isObject, isString } from '@/utils/is';
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) {
if (params[key] && params[key]._isAMomentObject) {
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 as any);
}
}
}
if (isObject(params[key])) {
formatRequestDate(params[key]);
}
}
}
+289
View File
@@ -0,0 +1,289 @@
// axios配置 可自行根据项目进行更改,只需更改该文件即可,其他文件可以不动
import { VAxios } from './Axios';
import { AxiosTransform } from './axiosTransform';
import axios, { AxiosResponse } from 'axios';
import { checkStatus } from './checkStatus';
import { joinTimestamp, formatRequestDate } from './helper';
import { RequestEnum, ResultEnum, ContentTypeEnum } from '@/enums/httpEnum';
import { PageEnum } from '@/enums/pageEnum';
import { ElMessage, ElMessageBox } from 'element-plus';
import { useGlobSetting } from '@/hooks/setting';
import { isString } from '@/utils/is/';
import { deepMerge, isUrl } from '@/utils';
import { setObjToUrlParams } from '@/utils/urlUtils';
import { RequestOptions, Result, CreateAxiosOptions } from './types';
const globSetting = useGlobSetting();
const urlPrefix = globSetting.urlPrefix || '';
import router from '@/router';
import { storage } from '@/utils/Storage';
/**
* @description: 数据处理,方便区分多种处理方式
*/
const transform: AxiosTransform = {
/**
* @description: 处理请求数据
*/
transformRequestData: (res: AxiosResponse<Result>, options: RequestOptions) => {
const {
isShowMessage = true,
isShowErrorMessage,
isShowSuccessMessage,
successMessageText,
errorMessageText,
isTransformResponse,
isReturnNativeResponse,
} = options;
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
if (isReturnNativeResponse) {
return res;
}
// 不进行任何处理,直接返回
// 用于页面代码可能需要直接获取code,data,message这些信息时开启
if (!isTransformResponse) {
return res.data;
}
if (!res.data) {
// return '[HTTP] Request has no return value';
throw new Error('请求出错,请稍候重试');
}
// 这里 code,result,message为 后台统一的字段,需要修改为项目自己的接口返回格式
const { code, data, msg } = res.data;
// 请求成功
const hasSuccess = data && code === ResultEnum.SUCCESS;
// 是否显示提示信息
if (isShowMessage) {
if (hasSuccess && (successMessageText || isShowSuccessMessage)) {
// 是否显示自定义信息提示
ElMessage({
type: 'info',
message: successMessageText || message || '操作成功!',
});
} else if (!hasSuccess && (errorMessageText || isShowErrorMessage)) {
// 是否显示自定义信息提示
ElMessage.error(msg || errorMessageText || '操作失败!');
} else if (!hasSuccess && options.errorMessageMode === 'modal') {
// errorMessageMode=‘custom-modal’的时候会显示modal错误弹窗,而不是消息提示,用于一些比较重要的错误
ElMessageBox.confirm(msg, '提示', {
confirmButtonText: '确定',
showCancelButton: false,
closeOnClickModal:false,
type: 'warning',
})
.then(() => {
ElMessage({
type: 'success',
message: 'Delete completed',
});
})
.catch(() => {
ElMessage({
type: 'info',
message: 'Delete canceled',
});
});
}
}
// 接口请求成功,直接返回结果
if (code === ResultEnum.SUCCESS) {
return data;
}
// 接口请求错误,统一提示错误信息 这里逻辑可以根据项目进行修改
let errorMsg = msg;
switch (code) {
// 请求失败
case ResultEnum.ERROR:
ElMessage.error(errorMsg);
break;
// 登录超时
case ResultEnum.TIMEOUT:
const LoginName = PageEnum.BASE_LOGIN_NAME;
const LoginPath = PageEnum.BASE_LOGIN;
if (router.currentRoute.value?.name === LoginName) return;
// 到登录页
errorMsg = '登录超时,请重新登录!';
ElMessageBox.confirm('登录身份已失效,请重新登录!', '提示', {
confirmButtonText: '确定',
showCancelButton: false,
type: 'warning',
})
.then(() => {
storage.clear();
window.location.href = LoginPath;
})
.catch(() => {});
break;
}
throw new Error(errorMsg);
},
// 请求之前处理config
beforeRequestHook: (config, options) => {
const { apiUrl, joinPrefix, joinParamsToUrl, formatDate, joinTime = true, urlPrefix } = options;
const isUrlStr = isUrl(config.url as string);
if (!isUrlStr && joinPrefix) {
config.url = `${urlPrefix}${config.url}`;
}
if (!isUrlStr && apiUrl && isString(apiUrl)) {
config.url = `${apiUrl}${config.url}`;
}
const params = config.params || {};
const data = config.data || false;
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 instanceof FormData)
) {
config.data = data;
config.params = params;
} else {
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, options) => {
// 请求之前处理config
const token = storage.get('ACCESS-TOKEN', '');
if (token && (config as Recordable)?.requestOptions?.withToken !== false) {
// jwt token
(config as Recordable).headers.Authorization = options.authenticationScheme
? `${options.authenticationScheme} ${token}`
: token;
}
return config;
},
/**
* @description: 响应错误处理
*/
responseInterceptorsCatch: (error: any) => {
const { response, code, msg } = error || {};
// TODO 此处要根据后端接口返回格式修改
const message: string =
response && response.data && response.data.msg ? response.data.msg : '';
const err: string = error.toString();
try {
if (code === 'ECONNABORTED' && msg.indexOf('timeout') !== -1) {
ElMessage.error('接口请求超时,请刷新页面重试!');
return;
}
if (err && err.includes('Network Error')) {
ElMessageBox.confirm('请检查您的网络连接是否正常', '网络异常', {
confirmButtonText: '确定',
showCancelButton: false,
type: 'warning',
})
// eslint-disable-next-line prettier/prettier
.then(() => { })
// eslint-disable-next-line prettier/prettier
.catch(() => { });
return Promise.reject(error);
}
} catch (error) {
throw new Error(error as any);
}
// 请求是否被取消
const isCancel = axios.isCancel(error);
if (!isCancel) {
checkStatus(error.response && error.response.status, message);
} else {
console.warn(error, '请求被取消!');
}
//return Promise.reject(error);
return Promise.reject(response?.data);
},
};
function createAxios(opt?: Partial<CreateAxiosOptions>) {
return new VAxios(
deepMerge(
{
timeout: 10 * 1000,
authenticationScheme: 'Bearer',
// 接口前缀
prefixUrl: urlPrefix,
headers: { 'Content-Type': ContentTypeEnum.JSON },
// 数据处理方式
transform,
// 配置项,下面的选项都可以在独立的接口请求中覆盖
requestOptions: {
// 默认将prefix 添加到url
joinPrefix: true,
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
isReturnNativeResponse: false,
// 需要对返回数据进行处理
isTransformResponse: true,
// post请求的时候添加参数到url
joinParamsToUrl: false,
// 格式化提交参数时间
formatDate: true,
// 消息提示类型
errorMessageMode: 'none',
// 接口地址
apiUrl: globSetting.apiUrl,
// 接口拼接地址
urlPrefix: urlPrefix,
// 是否加入时间戳
joinTime: true,
// 忽略重复请求
ignoreCancelToken: true,
// 是否携带token
withToken: true,
},
withCredentials: false,
},
opt || {},
),
);
}
export const http = createAxios();
// 项目,多个不同 api 地址,直接在这里导出多个
// src/api ts 里面接口,就可以单独使用这个请求,
// import { httpTwo } from '@/utils/http/axios'
// export const httpTwo = createAxios({
// requestOptions: {
// apiUrl: 'http://localhost:9001',
// urlPrefix: 'api',
// },
// });
+65
View File
@@ -0,0 +1,65 @@
import { AxiosRequestConfig } from 'axios';
import { AxiosTransform } from './axiosTransform';
export interface CreateAxiosOptions extends AxiosRequestConfig {
transform?: AxiosTransform;
requestOptions?: RequestOptions;
authenticationScheme?: string;
}
// 上传文件
export interface UploadFileParams {
// 其他参数
data?: Recordable;
// 文件参数接口字段名
name?: string;
// 文件
file: File | Blob;
// 文件名称
filename?: string;
[key: string]: any;
}
export interface RequestOptions {
// 请求参数拼接到url
joinParamsToUrl?: boolean;
// 格式化请求参数时间
formatDate?: boolean;
// 是否显示提示信息
isShowMessage?: boolean;
// 是否解析成JSON
isParseToJson?: boolean;
// 成功的文本信息
successMessageText?: string;
// 是否显示成功信息
isShowSuccessMessage?: boolean;
// 是否显示失败信息
isShowErrorMessage?: boolean;
// 错误的文本信息
errorMessageText?: string;
// 是否加入url
joinPrefix?: boolean;
// 接口地址, 不填则使用默认apiUrl
apiUrl?: string;
// 请求拼接路径
urlPrefix?: string;
// 错误消息提示类型
errorMessageMode?: 'none' | 'modal';
// 是否添加时间戳
joinTime?: boolean;
// 不进行任何处理,直接返回
isTransformResponse?: boolean;
// 是否返回原生响应头
isReturnNativeResponse?: boolean;
//忽略重复请求
ignoreCancelToken?: boolean;
// 是否携带token
withToken?: boolean;
}
export interface Result<T = any> {
code: number;
type?: 'success' | 'error' | 'warning';
message: string;
result?: T;
}
+243
View File
@@ -0,0 +1,243 @@
import { h, unref } from 'vue';
import type { App, Plugin } from 'vue';
import { ElIcon, ElTag } from 'element-plus';
import { PageEnum } from '@/enums/pageEnum';
import { isObject, isString, isNumber } from './is/index';
import { cloneDeep } from 'lodash-es';
import { TargetContext } from '/#/index';
/**
* render 图标
* */
export function renderIcon(icon) {
return () => h(ElIcon, null, { default: () => h(icon) });
}
/**
* render new Tag
* */
const newTagColors = { color: '#f90', textColor: '#fff', borderColor: '#f90' };
export function renderNew(type = 'warning', text = 'New', color: object = newTagColors) {
return () =>
h(
ElTag as any,
{
type,
round: true,
size: 'small',
color,
},
{ default: () => text },
);
}
/**
* 递归组装菜单格式
*/
export function generatorMenu(routerMap: Array<any>) {
return filterRouter(routerMap).map((item) => {
const isRoot = isRootRouter(item);
const info = isRoot ? item.children[0] : item;
const currentMenu = {
...info,
...info.meta,
title: info.meta?.title,
key: info.name,
icon: isRoot ? item.meta?.icon : info.meta?.icon,
};
// 是否有子菜单,并递归处理
if (info.children && info.children.length > 0) {
// Recursion
currentMenu.children = generatorMenu(info.children);
}
return currentMenu;
});
}
/**
* 混合菜单
* */
export function generatorMenuMix(routerMap: Array<any>, routerName: string, location: string) {
const cloneRouterMap = cloneDeep(routerMap);
const newRouter = filterRouter(cloneRouterMap);
if (location === 'header') {
const firstRouter: any[] = [];
newRouter.forEach((item) => {
const isRoot = isRootRouter(item);
const info = isRoot ? item.children[0] : item;
info.children = undefined;
const currentMenu = {
...info,
...info.meta,
title: info.meta?.title,
key: info.name,
};
firstRouter.push(currentMenu);
});
return firstRouter;
} else {
const currentRouters = newRouter.filter((item) => item.name === routerName);
const childrenRouter = currentRouters.length ? currentRouters[0].children || [] : [];
return getChildrenRouter(childrenRouter);
}
}
/**
* 递归组装子菜单
* */
export function getChildrenRouter(routerMap: Array<any>) {
return filterRouter(routerMap).map((item) => {
const isRoot = isRootRouter(item);
const info = isRoot ? item.children[0] : item;
const currentMenu = {
...info,
...info.meta,
title: info.meta?.title,
key: info.name,
};
// 是否有子菜单,并递归处理
if (info.children && info.children.length > 0) {
// Recursion
currentMenu.children = getChildrenRouter(info.children);
}
return currentMenu;
});
}
/**
* 判断根路由 Router
* */
export function isRootRouter(item) {
return item.meta?.alwaysShow === true && item.children?.length === 1;
}
/**
* 排除Router
* */
export function filterRouter(routerMap: Array<any>) {
return routerMap.filter((item) => {
return (
(item.meta?.hidden || false) != true &&
!['/:path(.*)*', '/', PageEnum.REDIRECT, PageEnum.BASE_LOGIN].includes(item.path)
);
});
}
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;
};
/**
* 找到对应的节点
* */
let result = null;
export function getTreeItem(data: any[], key?: string | number): any {
data.map((item) => {
if (item.key === key) {
result = item;
} else {
if (item.children && item.children.length) {
getTreeItem(item.children, key);
}
}
});
return result;
}
/**
* 找到所有节点
* */
const treeAll: any[] = [];
export function getTreeAll(data: any[]): any[] {
data.map((item) => {
treeAll.push(item.key);
if (item.children && item.children.length) {
getTreeAll(item.children);
}
});
return treeAll;
}
// 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>;
}
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;
}
/**
* 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)}`;
}
/**
* 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)}`;
}
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');
debugger;
window.open(url, target, feature.join(','));
}
/**
* 处理css单位
* */
export function cssUnit(value: string | number, unit = 'px') {
return isNumber(value) || (isString(value) && value.indexOf(unit as string) === -1)
? `${value}${unit}`
: value;
}
/**
* 判断是否 url
* */
export function isUrl(url: string) {
return /(^http|https:\/\/)/g.test(url);
}
+118
View File
@@ -0,0 +1,118 @@
const toString = Object.prototype.toString;
/**
* @description: 判断值是否未某个类型
*/
export function is(val: unknown, type: string) {
return toString.call(val) === `[object ${type}]`;
}
/**
* @description: 是否为函数
*/
export function isFunction<T = Function>(val: unknown): val is T {
return is(val, 'Function');
}
/**
* @description: 是否已定义
*/
export const isDef = <T = unknown>(val?: T): val is T => {
return typeof val !== 'undefined';
};
export const isUnDef = <T = unknown>(val?: T): val is T => {
return !isDef(val);
};
/**
* @description: 是否为对象
*/
export const isObject = (val: any): val is Record<any, any> => {
return val !== null && is(val, 'Object');
};
/**
* @description: 是否为时间
*/
export function isDate(val: unknown): val is Date {
return is(val, 'Date');
}
/**
* @description: 是否为数值
*/
export function isNumber(val: unknown): val is number {
return is(val, 'Number');
}
/**
* @description: 是否为AsyncFunction
*/
export function isAsyncFunction<T = any>(val: unknown): val is Promise<T> {
return is(val, 'AsyncFunction');
}
/**
* @description: 是否为promise
*/
export function isPromise<T = any>(val: unknown): val is Promise<T> {
return is(val, 'Promise') && isObject(val) && isFunction(val.then) && isFunction(val.catch);
}
/**
* @description: 是否为字符串
*/
export function isString(val: unknown): val is string {
return is(val, 'String');
}
/**
* @description: 是否为boolean类型
*/
export function isBoolean(val: unknown): val is boolean {
return is(val, 'Boolean');
}
/**
* @description: 是否为数组
*/
export function isArray(val: any): val is Array<any> {
return val && Array.isArray(val);
}
/**
* @description: 是否客户端
*/
export const isClient = () => {
return typeof window !== 'undefined';
};
/**
* @description: 是否为浏览器
*/
export const isWindow = (val: any): val is Window => {
return typeof window !== 'undefined' && is(val, 'Window');
};
export const isElement = (val: unknown): val is Element => {
return isObject(val) && !!val.tagName;
};
export const isServer = typeof window === 'undefined';
// 是否为图片节点
export function isImageDom(o: Element) {
return o && ['IMAGE', 'IMG'].includes(o.tagName);
}
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);
}
+55
View File
@@ -0,0 +1,55 @@
import * as echarts from 'echarts/core';
import {
BarChart,
LineChart,
PieChart,
MapChart,
PictorialBarChart,
RadarChart,
GaugeChart,
} from 'echarts/charts';
import {
TitleComponent,
TooltipComponent,
GridComponent,
PolarComponent,
AriaComponent,
ParallelComponent,
LegendComponent,
RadarComponent,
ToolboxComponent,
DataZoomComponent,
VisualMapComponent,
TimelineComponent,
CalendarComponent,
} from 'echarts/components';
import { SVGRenderer } from 'echarts/renderers';
echarts.use([
LegendComponent,
TitleComponent,
TooltipComponent,
GridComponent,
PolarComponent,
AriaComponent,
ParallelComponent,
BarChart,
LineChart,
PieChart,
MapChart,
RadarChart,
GaugeChart,
SVGRenderer,
PictorialBarChart,
RadarComponent,
ToolboxComponent,
DataZoomComponent,
VisualMapComponent,
TimelineComponent,
CalendarComponent,
]);
export default echarts;
+12
View File
@@ -0,0 +1,12 @@
/**
* 这里按需引入lodash的一些方法,方便维护
*/
// export {default as xxx} from 'lodash/xxx'
export { default as cloneDeep } from 'lodash/cloneDeep';
export { default as intersection } from 'lodash/intersection';
export { default as get } from 'lodash/get';
export { default as upperFirst } from 'lodash/upperFirst';
export { default as omit } from 'lodash/omit';
export { default as debounce } from 'lodash/debounce';
+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}`);
}
+33
View File
@@ -0,0 +1,33 @@
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>;
};
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 };
+24
View File
@@ -0,0 +1,24 @@
/**
* 将对象添加当作参数拼接到URL上面
* @param baseUrl 需要拼接的url
* @param obj 参数对象
* @returns {string} 拼接后的对象
* 例子:
* let obj = {a: '3', b: '4'}
* setObjToUrlParams('www.baidu.com', obj)
* ==>www.baidu.com?a=3&b=4
*/
export function setObjToUrlParams(baseUrl: string, obj: object): string {
let parameters = '';
let url = '';
for (const key in obj) {
parameters += key + '=' + encodeURIComponent(obj[key]) + '&';
}
parameters = parameters.replace(/&$/, '');
if (/\?$/.test(baseUrl)) {
url = baseUrl + parameters;
} else {
url = baseUrl.replace(/\/?$/, '?') + parameters;
}
return url;
}
+18
View File
@@ -0,0 +1,18 @@
import { ref } from 'vue';
export function useLockFn(fn: (...args: any[]) => Promise<any>) {
const isLock = ref(false);
const lockFn = async (...args: any[]) => {
if (isLock.value) return;
isLock.value = true;
try {
await fn(...args);
} finally {
isLock.value = false;
}
};
return {
isLock,
lockFn,
};
}
+206
View File
@@ -0,0 +1,206 @@
/**
* 判断是否为空
* @param val 数据
*/
export const validateNull = (val: any) => {
if (typeof val === 'boolean') {
return false;
}
if (typeof val === 'number') {
return false;
}
if (val instanceof Array) {
if (val.length === 0) return true;
} else if (val instanceof Object) {
if (JSON.stringify(val) === '{}') return true;
} else {
if (val === 'null' || val == null || val === 'undefined' || val === undefined || val === '')
return true;
return false;
}
return false;
};
export const rule = {
/**
* 校验 请输入中文、英文、数字包括下划线
* 名称校验
*/
validatorNameCn(rule: any, value: any, callback: any) {
const acount = /^[\u4E00-\u9FA5A-Za-z0-9_]+$/;
if (value && !acount.test(value)) {
callback(new Error('请输入中文、英文、数字包括下划线'));
} else {
callback();
}
},
/**
* 校验 请输入大写英文、下划线
* 名称校验
*/
validatorCapital(rule: any, value: any, callback: any) {
const acount = /^[A-Z_]+$/;
if (value && !acount.test(value)) {
callback(new Error('请输入大写英文、下划线'));
} else {
callback();
}
},
/**
* 校验 请输入小写英文、下划线
* 名称校验
*/
validatorLowercase(rule: any, value: any, callback: any) {
const acount = /^[a-z_]+$/;
if (value && !acount.test(value)) {
callback(new Error('请输入小写英文、下划线'));
} else {
callback();
}
},
/**
* 校验 请输入小写英文
* 名称校验
*/
validatorLower(rule: any, value: any, callback: any) {
const acount = /^[a-z]+$/;
if (value && !acount.test(value)) {
callback(new Error('请输入小写英文'));
} else {
callback();
}
},
/**
* 校验首尾空白字符的正则表达式
*
*/
checkSpace(rule: any, value: any, callback: any) {
const longrg = /[^\s]+$/;
if (!longrg.test(value)) {
callback(new Error('请输入非空格信息'));
} else {
callback();
}
},
/**
* 校验手机号
*/
validatePhone(rule: any, value: any, callback: any) {
const isPhone = /^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$/;
if (value.indexOf('****') >= 0) {
return callback();
}
if (!isPhone.test(value)) {
callback(new Error('请输入合法手机号'));
} else {
callback();
}
},
/* 数字 */
number(rule, value, callback) {
validateFn('number', rule, value, callback, '包含非数字字符');
},
/* 字母 */
letter(rule, value, callback) {
validateFn('letter', rule, value, callback, '包含非字母字符');
},
/* 字母和数字 */
letterAndNumber(rule, value, callback) {
validateFn('letterAndNumber', rule, value, callback, '只能输入字母或数字');
},
/* 手机号码 */
mobilePhone(rule, value, callback) {
validateFn('mobilePhone', rule, value, callback, '手机号码格式有误');
},
/* 字母开头,仅可包含数字 */
letterStartNumberIncluded(rule, value, callback) {
validateFn('letterStartNumberIncluded', rule, value, callback, '必须以字母开头,可包含数字');
},
/* 禁止中文输入 */
noChinese(rule, value, callback) {
validateFn('noChinese', rule, value, callback, '不可输入中文字符');
},
/* 必须中文输入 */
chinese(rule, value, callback) {
validateFn('chinese', rule, value, callback, '只能输入中文字符');
},
/* 电子邮箱 */
email(rule, value, callback) {
validateFn('email', rule, value, callback, '邮箱格式有误');
},
/* URL网址 */
url(rule, value, callback) {
validateFn('url', rule, value, callback, 'URL格式有误');
},
regExp(rule, value, callback) {
if (validateNull(value) || value.length <= 0) {
callback();
return;
}
const pattern = new RegExp(rule.regExp);
if (!pattern.test(value)) {
const errTxt = rule.errorMsg || 'invalid value';
callback(new Error(errTxt));
} else {
callback();
}
},
};
/**
* @desc [自定义校验规则]
* @example
* import { validateRule } from "@/utils/validateRules";
* rules: [
* { validator: validateRule.emailValue, trigger: 'blur'}
* ]
*/
export const getRegExp = function (validatorName) {
const commonRegExp = {
number: '^[-]?\\d+(\\.\\d+)?$',
letter: '^[A-Za-z]+$',
letterAndNumber: '^[A-Za-z0-9]+$',
mobilePhone: '^[1][3-9][0-9]{9}$',
letterStartNumberIncluded: '^[A-Za-z]+[A-Za-z\\d]*$',
noChinese: '^[^\u4e00-\u9fa5]+$',
chinese: '^[\u4e00-\u9fa5]+$',
email: '^([-_A-Za-z0-9.]+)@([_A-Za-z0-9]+\\.)+[A-Za-z0-9]{2,3}$',
url: '(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]',
};
return commonRegExp[validatorName];
};
const validateFn = (validatorName, rule, value, callback, defaultErrorMsg) => {
if (validateNull(value) || value.length <= 0) {
callback();
return;
}
const reg = new RegExp(getRegExp(validatorName));
if (!reg.test(value)) {
const errTxt = rule.errorMsg || defaultErrorMsg;
callback(new Error(errTxt));
} else {
callback();
}
};
+49
View File
@@ -0,0 +1,49 @@
// 页面添加水印效果
const setWatermark = (str: string) => {
const id = '1.23452384164.123412416';
if (document.getElementById(id) !== null)
document.body.removeChild(<HTMLElement>document.getElementById(id));
const can = document.createElement('canvas');
can.width = 250;
can.height = 180;
const cans = <CanvasRenderingContext2D>can.getContext('2d');
cans.rotate((-20 * Math.PI) / 180);
cans.font = '18px Vedana';
cans.fillStyle = 'rgba(200, 200, 200, 0.40)';
cans.textBaseline = 'middle';
cans.fillText(str, can.width / 10, can.height / 2);
const div = document.createElement('div');
div.id = id;
div.style.pointerEvents = 'none';
div.style.top = '0px';
div.style.left = '0px';
div.style.position = 'fixed';
div.style.zIndex = '10000000';
div.style.width = `${document.documentElement.clientWidth}px`;
div.style.height = `${document.documentElement.clientHeight}px`;
div.style.background = `url(${can.toDataURL('image/png')}) left top repeat`;
document.body.appendChild(div);
return id;
};
/**
* 页面添加水印效果
* @method set 设置水印
* @method del 删除水印
*/
const watermark = {
// 设置水印
set: (str: string) => {
let id = setWatermark(str);
if (document.getElementById(id) === null) id = setWatermark(str);
},
// 删除水印
del: () => {
const id = '1.23452384164.123412416';
if (document.getElementById(id) !== null)
document.body.removeChild(<HTMLElement>document.getElementById(id));
},
};
// 导出方法
export default watermark;