mirror of
https://github.com/soybeanjs/soybean-admin.git
synced 2025-11-14 04:33:41 +08:00
feat(projects): 1.0 beta
This commit is contained in:
22
src/utils/common.ts
Normal file
22
src/utils/common.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* transform record to option
|
||||
* @param record
|
||||
* @example
|
||||
* ```ts
|
||||
* const record = {
|
||||
* key1: 'label1',
|
||||
* key2: 'label2'
|
||||
* };
|
||||
* const options = transformRecordToOption(record);
|
||||
* // [
|
||||
* // { value: 'key1', label: 'label1' },
|
||||
* // { value: 'key2', label: 'label2' }
|
||||
* // ]
|
||||
* ```
|
||||
*/
|
||||
export function transformRecordToOption<T extends Record<string, string>>(record: T) {
|
||||
return Object.entries(record).map(([value, label]) => ({
|
||||
value,
|
||||
label
|
||||
})) as Common.Option<keyof T>[];
|
||||
}
|
||||
@@ -1,223 +0,0 @@
|
||||
import { colord, extend } from 'colord';
|
||||
import namesPlugin from 'colord/plugins/names';
|
||||
import mixPlugin from 'colord/plugins/mix';
|
||||
import type { AnyColor, HsvColor } from 'colord';
|
||||
|
||||
extend([namesPlugin, mixPlugin]);
|
||||
|
||||
/** 色相阶梯 */
|
||||
const hueStep = 2;
|
||||
/** 饱和度阶梯,浅色部分 */
|
||||
const saturationStep = 16;
|
||||
/** 饱和度阶梯,深色部分 */
|
||||
const saturationStep2 = 5;
|
||||
/** 亮度阶梯,浅色部分 */
|
||||
const brightnessStep1 = 5;
|
||||
/** 亮度阶梯,深色部分 */
|
||||
const brightnessStep2 = 15;
|
||||
/** 浅色数量,主色上 */
|
||||
const lightColorCount = 5;
|
||||
/** 深色数量,主色下 */
|
||||
const darkColorCount = 4;
|
||||
|
||||
/**
|
||||
* 调色板的颜色索引
|
||||
* @description 从左至右颜色从浅到深,6为主色号
|
||||
*/
|
||||
type ColorIndex = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
|
||||
|
||||
/**
|
||||
* 根据颜色获取调色板颜色(从左至右颜色从浅到深,6为主色号)
|
||||
* @param color - 颜色
|
||||
* @param index - 调色板的对应的色号(6为主色号)
|
||||
* @returns 返回hex格式的颜色
|
||||
*/
|
||||
export function getColorPalette(color: AnyColor, index: ColorIndex): string {
|
||||
const transformColor = colord(color);
|
||||
|
||||
if (!transformColor.isValid()) {
|
||||
throw Error('invalid input color value');
|
||||
}
|
||||
|
||||
if (index === 6) {
|
||||
return colord(transformColor).toHex();
|
||||
}
|
||||
|
||||
const isLight = index < 6;
|
||||
const hsv = transformColor.toHsv();
|
||||
const i = isLight ? lightColorCount + 1 - index : index - lightColorCount - 1;
|
||||
|
||||
const newHsv: HsvColor = {
|
||||
h: getHue(hsv, i, isLight),
|
||||
s: getSaturation(hsv, i, isLight),
|
||||
v: getValue(hsv, i, isLight)
|
||||
};
|
||||
|
||||
return colord(newHsv).toHex();
|
||||
}
|
||||
|
||||
/** 暗色主题颜色映射关系表 */
|
||||
const darkColorMap = [
|
||||
{ index: 7, opacity: 0.15 },
|
||||
{ index: 6, opacity: 0.25 },
|
||||
{ index: 5, opacity: 0.3 },
|
||||
{ index: 5, opacity: 0.45 },
|
||||
{ index: 5, opacity: 0.65 },
|
||||
{ index: 5, opacity: 0.85 },
|
||||
{ index: 4, opacity: 0.9 },
|
||||
{ index: 3, opacity: 0.95 },
|
||||
{ index: 2, opacity: 0.97 },
|
||||
{ index: 1, opacity: 0.98 }
|
||||
];
|
||||
|
||||
/**
|
||||
* 根据颜色获取调色板颜色所有颜色
|
||||
* @param color - 颜色
|
||||
* @param darkTheme - 暗黑主题的调色板颜色
|
||||
* @param darkThemeMixColor - 暗黑主题的混合颜色,默认 #141414
|
||||
*/
|
||||
export function getColorPalettes(color: AnyColor, darkTheme = false, darkThemeMixColor = '#141414'): string[] {
|
||||
const indexes: ColorIndex[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
|
||||
const patterns = indexes.map(index => getColorPalette(color, index));
|
||||
|
||||
if (darkTheme) {
|
||||
const darkPatterns = darkColorMap.map(({ index, opacity }) => {
|
||||
const darkColor = colord(darkThemeMixColor).mix(patterns[index], opacity);
|
||||
|
||||
return darkColor;
|
||||
});
|
||||
|
||||
return darkPatterns.map(item => colord(item).toHex());
|
||||
}
|
||||
|
||||
return patterns;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取色相渐变
|
||||
* @param hsv - hsv格式颜色值
|
||||
* @param i - 与6的相对距离
|
||||
* @param isLight - 是否是亮颜色
|
||||
*/
|
||||
function getHue(hsv: HsvColor, i: number, isLight: boolean) {
|
||||
let hue: number;
|
||||
|
||||
const hsvH = Math.round(hsv.h);
|
||||
|
||||
if (hsvH >= 60 && hsvH <= 240) {
|
||||
// 冷色调
|
||||
// 减淡变亮 色相顺时针旋转 更暖
|
||||
// 加深变暗 色相逆时针旋转 更冷
|
||||
hue = isLight ? hsvH - hueStep * i : hsvH + hueStep * i;
|
||||
} else {
|
||||
// 暖色调
|
||||
// 减淡变亮 色相逆时针旋转 更暖
|
||||
// 加深变暗 色相顺时针旋转 更冷
|
||||
hue = isLight ? hsvH + hueStep * i : hsvH - hueStep * i;
|
||||
}
|
||||
|
||||
if (hue < 0) {
|
||||
hue += 360;
|
||||
}
|
||||
|
||||
if (hue >= 360) {
|
||||
hue -= 360;
|
||||
}
|
||||
|
||||
return hue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取饱和度渐变
|
||||
* @param hsv - hsv格式颜色值
|
||||
* @param i - 与6的相对距离
|
||||
* @param isLight - 是否是亮颜色
|
||||
*/
|
||||
function getSaturation(hsv: HsvColor, i: number, isLight: boolean) {
|
||||
// 灰色不渐变
|
||||
if (hsv.h === 0 && hsv.s === 0) {
|
||||
return hsv.s;
|
||||
}
|
||||
|
||||
let saturation: number;
|
||||
|
||||
if (isLight) {
|
||||
saturation = hsv.s - saturationStep * i;
|
||||
} else if (i === darkColorCount) {
|
||||
saturation = hsv.s + saturationStep;
|
||||
} else {
|
||||
saturation = hsv.s + saturationStep2 * i;
|
||||
}
|
||||
|
||||
if (saturation > 100) {
|
||||
saturation = 100;
|
||||
}
|
||||
|
||||
if (isLight && i === lightColorCount && saturation > 10) {
|
||||
saturation = 10;
|
||||
}
|
||||
|
||||
if (saturation < 6) {
|
||||
saturation = 6;
|
||||
}
|
||||
|
||||
return saturation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取明度渐变
|
||||
* @param hsv - hsv格式颜色值
|
||||
* @param i - 与6的相对距离
|
||||
* @param isLight - 是否是亮颜色
|
||||
*/
|
||||
function getValue(hsv: HsvColor, i: number, isLight: boolean) {
|
||||
let value: number;
|
||||
|
||||
if (isLight) {
|
||||
value = hsv.v + brightnessStep1 * i;
|
||||
} else {
|
||||
value = hsv.v - brightnessStep2 * i;
|
||||
}
|
||||
|
||||
if (value > 100) {
|
||||
value = 100;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给颜色加透明度
|
||||
* @param color - 颜色
|
||||
* @param alpha - 透明度(0 - 1)
|
||||
*/
|
||||
export function addColorAlpha(color: string, alpha: number) {
|
||||
return colord(color).alpha(alpha).toHex();
|
||||
}
|
||||
|
||||
/**
|
||||
* 颜色混合
|
||||
* @param firstColor - 第一个颜色
|
||||
* @param secondColor - 第二个颜色
|
||||
* @param ratio - 第二个颜色占比
|
||||
*/
|
||||
export function mixColor(firstColor: string, secondColor: string, ratio: number) {
|
||||
return colord(firstColor).mix(secondColor, ratio).toHex();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是白颜色
|
||||
* @param color - 颜色
|
||||
*/
|
||||
export function isWhiteColor(color: string) {
|
||||
return colord(color).isEqual('#ffffff');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取颜色的rgb值
|
||||
* @param color 颜色
|
||||
*/
|
||||
export function getRgbOfColor(color: string) {
|
||||
return colord(color).toRgb();
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
export * from './typeof';
|
||||
export * from './color';
|
||||
export * from './number';
|
||||
export * from './pattern';
|
||||
@@ -1,34 +0,0 @@
|
||||
/**
|
||||
* 根据数字获取对应的汉字
|
||||
* @param num - 数字(0-10)
|
||||
*/
|
||||
export function getHanByNumber(num: number) {
|
||||
const HAN_STR = '零一二三四五六七八九十';
|
||||
return HAN_STR.charAt(num);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将总秒数转换成 分:秒
|
||||
* @param seconds - 秒
|
||||
*/
|
||||
export function transformToTimeCountDown(seconds: number) {
|
||||
const SECONDS_A_MINUTE = 60;
|
||||
function fillZero(num: number) {
|
||||
return num.toString().padStart(2, '0');
|
||||
}
|
||||
const minuteNum = Math.floor(seconds / SECONDS_A_MINUTE);
|
||||
const minute = fillZero(minuteNum);
|
||||
const second = fillZero(seconds - minuteNum * SECONDS_A_MINUTE);
|
||||
return `${minute}: ${second}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定整数范围内的随机整数
|
||||
* @param start - 开始范围
|
||||
* @param end - 结束范围
|
||||
*/
|
||||
export function getRandomInteger(end: number, start = 0) {
|
||||
const range = end - start;
|
||||
const random = Math.floor(Math.random() * range + start);
|
||||
return random;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
* 策略模式
|
||||
* @param actions 每一种可能执行的操作
|
||||
*/
|
||||
export function exeStrategyActions(actions: Common.StrategyAction[]) {
|
||||
actions.some(item => {
|
||||
const [flag, action] = item;
|
||||
if (flag) {
|
||||
action();
|
||||
}
|
||||
return flag;
|
||||
});
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
import { dataTypeLabels } from '@/constants';
|
||||
|
||||
function getDataTypeString<K extends TypeUtil.DataTypeStringKey>(value: unknown) {
|
||||
return Object.prototype.toString.call(value) as TypeUtil.DataTypeString<K>;
|
||||
}
|
||||
|
||||
export function isNumber<T extends number>(value: T | unknown): value is T {
|
||||
return getDataTypeString(value) === dataTypeLabels.number;
|
||||
}
|
||||
|
||||
export function isString<T extends string>(value: T | unknown): value is T {
|
||||
return getDataTypeString(value) === dataTypeLabels.string;
|
||||
}
|
||||
|
||||
export function isBoolean<T extends boolean>(value: T | unknown): value is T {
|
||||
return getDataTypeString(value) === dataTypeLabels.boolean;
|
||||
}
|
||||
|
||||
export function isNull<T extends null>(value: T | unknown): value is T {
|
||||
return getDataTypeString(value) === dataTypeLabels.null;
|
||||
}
|
||||
|
||||
export function isUndefined<T extends undefined>(value: T | unknown): value is T {
|
||||
return getDataTypeString(value) === dataTypeLabels.undefined;
|
||||
}
|
||||
|
||||
export function isSymbol<T extends symbol>(value: T | unknown): value is T {
|
||||
return getDataTypeString(value) === dataTypeLabels.symbol;
|
||||
}
|
||||
|
||||
export function isBigInt<T extends bigint>(value: T | unknown): value is T {
|
||||
return getDataTypeString(value) === dataTypeLabels.bigInt;
|
||||
}
|
||||
|
||||
export function isObject<T extends Record<string, any>>(value: T | unknown): value is T {
|
||||
return getDataTypeString(value) === dataTypeLabels.object;
|
||||
}
|
||||
|
||||
export function isArray<T extends any[]>(value: T | unknown): value is T {
|
||||
return getDataTypeString(value) === dataTypeLabels.array;
|
||||
}
|
||||
|
||||
export function isFunction<T extends (...args: any[]) => any | void>(value: T | unknown): value is T {
|
||||
return getDataTypeString(value) === dataTypeLabels.function;
|
||||
}
|
||||
|
||||
export function isDate<T extends Date>(value: T | unknown): value is T {
|
||||
return getDataTypeString(value) === dataTypeLabels.date;
|
||||
}
|
||||
|
||||
export function isRegExp<T extends RegExp>(value: T | unknown): value is T {
|
||||
return getDataTypeString(value) === dataTypeLabels.regExp;
|
||||
}
|
||||
|
||||
export function isPromise<T extends Promise<any>>(value: T | unknown): value is T {
|
||||
return getDataTypeString(value) === dataTypeLabels.promise;
|
||||
}
|
||||
|
||||
export function isSet<T extends Set<any>>(value: T | unknown): value is T {
|
||||
return getDataTypeString(value) === dataTypeLabels.set;
|
||||
}
|
||||
|
||||
export function isMap<T extends Map<any, any>>(value: T | unknown): value is T {
|
||||
return getDataTypeString(value) === dataTypeLabels.map;
|
||||
}
|
||||
|
||||
export function isFile<T extends File>(value: T | unknown): value is T {
|
||||
return getDataTypeString(value) === dataTypeLabels.file;
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import CryptoJS from 'crypto-js';
|
||||
|
||||
const CryptoSecret = '__CryptoJS_Secret__';
|
||||
|
||||
/**
|
||||
* 加密数据
|
||||
* @param data - 数据
|
||||
*/
|
||||
export function encrypt(data: any) {
|
||||
const newData = JSON.stringify(data);
|
||||
return CryptoJS.AES.encrypt(newData, CryptoSecret).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密数据
|
||||
* @param cipherText - 密文
|
||||
*/
|
||||
export function decrypt(cipherText: string) {
|
||||
const bytes = CryptoJS.AES.decrypt(cipherText, CryptoSecret);
|
||||
const originalText = bytes.toString(CryptoJS.enc.Utf8);
|
||||
if (originalText) {
|
||||
return JSON.parse(originalText);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './rule';
|
||||
@@ -1,78 +0,0 @@
|
||||
import type { Ref } from 'vue';
|
||||
import type { FormItemRule } from 'naive-ui';
|
||||
import { REGEXP_CODE_SIX, REGEXP_EMAIL, REGEXP_PHONE, REGEXP_PWD } from '@/config';
|
||||
|
||||
/** 创建自定义错误信息的必填表单规则 */
|
||||
export const createRequiredFormRule = (message = '不能为空'): FormItemRule => ({ required: true, message });
|
||||
|
||||
export const requiredFormRule = createRequiredFormRule();
|
||||
|
||||
/** 表单规则 */
|
||||
interface CustomFormRules {
|
||||
/** 手机号码 */
|
||||
phone: FormItemRule[];
|
||||
/** 密码 */
|
||||
pwd: FormItemRule[];
|
||||
/** 验证码 */
|
||||
code: FormItemRule[];
|
||||
/** 邮箱 */
|
||||
email: FormItemRule[];
|
||||
}
|
||||
|
||||
/** 表单规则 */
|
||||
export const formRules: CustomFormRules = {
|
||||
phone: [
|
||||
createRequiredFormRule('请输入手机号码'),
|
||||
{ pattern: REGEXP_PHONE, message: '手机号码格式错误', trigger: 'input' }
|
||||
],
|
||||
pwd: [
|
||||
createRequiredFormRule('请输入密码'),
|
||||
{ pattern: REGEXP_PWD, message: '密码为6-18位数字/字符/符号,至少2种组合', trigger: 'input' }
|
||||
],
|
||||
code: [
|
||||
createRequiredFormRule('请输入验证码'),
|
||||
{ pattern: REGEXP_CODE_SIX, message: '验证码格式错误', trigger: 'input' }
|
||||
],
|
||||
email: [{ pattern: REGEXP_EMAIL, message: '邮箱格式错误', trigger: 'blur' }]
|
||||
};
|
||||
|
||||
/** 是否为空字符串 */
|
||||
function isBlankString(str: string) {
|
||||
return str.trim() === '';
|
||||
}
|
||||
|
||||
/** 获取确认密码的表单规则 */
|
||||
export function getConfirmPwdRule(pwd: Ref<string>) {
|
||||
const confirmPwdRule: FormItemRule[] = [
|
||||
{ required: true, message: '请输入确认密码' },
|
||||
{
|
||||
validator: (rule, value) => {
|
||||
if (!isBlankString(value) && value !== pwd.value) {
|
||||
return Promise.reject(rule.message);
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
message: '输入的值与密码不一致',
|
||||
trigger: 'input'
|
||||
}
|
||||
];
|
||||
return confirmPwdRule;
|
||||
}
|
||||
|
||||
/** 获取图片验证码的表单规则 */
|
||||
export function getImgCodeRule(imgCode: Ref<string>) {
|
||||
const imgCodeRule: FormItemRule[] = [
|
||||
{ required: true, message: '请输入验证码' },
|
||||
{
|
||||
validator: (rule, value) => {
|
||||
if (!isBlankString(value) && value !== imgCode.value) {
|
||||
return Promise.reject(rule.message);
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
message: '验证码不正确',
|
||||
trigger: 'blur'
|
||||
}
|
||||
];
|
||||
return imgCodeRule;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
export * from './common';
|
||||
export * from './storage';
|
||||
export * from './service';
|
||||
export * from './router';
|
||||
export * from './form';
|
||||
@@ -1,25 +0,0 @@
|
||||
/**
|
||||
* 根据用户权限过滤路由
|
||||
* @param routes - 权限路由
|
||||
* @param permission - 权限
|
||||
*/
|
||||
export function filterAuthRoutesByUserPermission(routes: AuthRoute.Route[], permission: Auth.RoleType) {
|
||||
return routes.map(route => filterAuthRouteByUserPermission(route, permission)).flat(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户权限过滤单个路由
|
||||
* @param route - 单个权限路由
|
||||
* @param permission - 权限
|
||||
*/
|
||||
function filterAuthRouteByUserPermission(route: AuthRoute.Route, permission: Auth.RoleType): AuthRoute.Route[] {
|
||||
const filterRoute = { ...route };
|
||||
const hasPermission =
|
||||
!route.meta.permissions || permission === 'super' || route.meta.permissions.includes(permission);
|
||||
|
||||
if (filterRoute.children) {
|
||||
const filterChildren = filterRoute.children.map(item => filterAuthRouteByUserPermission(item, permission)).flat(1);
|
||||
Object.assign(filterRoute, { children: filterChildren });
|
||||
}
|
||||
return hasPermission ? [filterRoute] : [];
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
import { getTopLevelMenu } from './helpers';
|
||||
/**
|
||||
* 获取面包屑数据
|
||||
* @param activeKey - 当前页面路由的key
|
||||
* @param menus - 菜单数据
|
||||
* @param rootPath - 根路由路径
|
||||
*/
|
||||
export function getBreadcrumbByRouteKey(activeKey: string, menus: App.GlobalMenuOption[], rootPath: string) {
|
||||
const breadcrumbMenu = getBreadcrumbMenu(activeKey, menus);
|
||||
const breadcrumb = breadcrumbMenu.map(item => transformBreadcrumbMenuToBreadcrumb(item, rootPath));
|
||||
return breadcrumb;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据菜单数据获取面包屑格式的菜单
|
||||
* @param activeKey - 当前页面路由的key
|
||||
* @param menus - 菜单数据
|
||||
*/
|
||||
function getBreadcrumbMenu(activeKey: string, menus: App.GlobalMenuOption[]) {
|
||||
const breadcrumbMenu: App.GlobalMenuOption[] = [];
|
||||
const topLevelMenu = getTopLevelMenu(activeKey, menus);
|
||||
const options = topLevelMenu ? getBreadcrumbMenuItem(activeKey, topLevelMenu as App.GlobalMenuOption) : [];
|
||||
breadcrumbMenu.push(...options);
|
||||
return breadcrumbMenu;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据单个菜单数据获取面包屑格式的菜单
|
||||
* @param activeKey - 当前页面路由的key
|
||||
* @param menu - 单个菜单数据
|
||||
*/
|
||||
function getBreadcrumbMenuItem(activeKey: string, menu: App.GlobalMenuOption) {
|
||||
const breadcrumbMenu: App.GlobalMenuOption[] = [];
|
||||
if (activeKey === menu.routeName) {
|
||||
breadcrumbMenu.push(menu);
|
||||
}
|
||||
if (activeKey.includes(menu.routeName) && menu.children && menu.children.length) {
|
||||
breadcrumbMenu.push(menu);
|
||||
breadcrumbMenu.push(
|
||||
...menu.children.map(item => getBreadcrumbMenuItem(activeKey, item as App.GlobalMenuOption)).flat(1)
|
||||
);
|
||||
}
|
||||
|
||||
return breadcrumbMenu;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将面包屑格式的菜单数据转换成面包屑数据
|
||||
* @param menu - 单个菜单数据
|
||||
* @param rootPath - 根路由路径
|
||||
*/
|
||||
function transformBreadcrumbMenuToBreadcrumb(menu: App.GlobalMenuOption, rootPath: string) {
|
||||
const hasChildren = Boolean(menu.children && menu.children.length);
|
||||
const breadcrumb: App.GlobalBreadcrumb = {
|
||||
key: menu.routeName,
|
||||
label: menu.label as string,
|
||||
routeName: menu.routeName,
|
||||
disabled: menu.routePath === rootPath,
|
||||
hasChildren,
|
||||
i18nTitle: menu.i18nTitle
|
||||
};
|
||||
if (menu.icon) {
|
||||
breadcrumb.icon = menu.icon;
|
||||
}
|
||||
if (hasChildren) {
|
||||
breadcrumb.options = menu.children?.map(item =>
|
||||
transformBreadcrumbMenuToBreadcrumb(item as App.GlobalMenuOption, rootPath)
|
||||
) as NonNullable<App.GlobalBreadcrumb['options']>;
|
||||
}
|
||||
return breadcrumb;
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
/**
|
||||
* 获取缓存的路由对应组件的名称
|
||||
* @param routes - 转换后的vue路由
|
||||
*/
|
||||
export function getCacheRoutes(routes: RouteRecordRaw[]) {
|
||||
const cacheNames: string[] = [];
|
||||
routes.forEach(route => {
|
||||
// 只需要获取二级路由的缓存的组件名
|
||||
if (hasChildren(route)) {
|
||||
(route.children as RouteRecordRaw[]).forEach(item => {
|
||||
if (isKeepAlive(item)) {
|
||||
cacheNames.push(item.name as string);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return cacheNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* 路由是否缓存
|
||||
* @param route
|
||||
*/
|
||||
function isKeepAlive(route: RouteRecordRaw) {
|
||||
return Boolean(route?.meta?.keepAlive);
|
||||
}
|
||||
/**
|
||||
* 是否有二级路由
|
||||
* @param route
|
||||
*/
|
||||
function hasChildren(route: RouteRecordRaw) {
|
||||
return Boolean(route.children && route.children.length);
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
import type { RouteComponent } from 'vue-router';
|
||||
import { BasicLayout, BlankLayout } from '@/layouts';
|
||||
import { views } from '@/views';
|
||||
import { isFunction } from '../common';
|
||||
|
||||
type Lazy<T> = () => Promise<T>;
|
||||
|
||||
interface ModuleComponent {
|
||||
default: RouteComponent;
|
||||
}
|
||||
|
||||
type LayoutComponent = Record<UnionKey.LayoutComponentType, Lazy<ModuleComponent>>;
|
||||
|
||||
/**
|
||||
* 获取布局的vue文件(懒加载的方式)
|
||||
* @param layoutType - 布局类型
|
||||
*/
|
||||
export function getLayoutComponent(layoutType: UnionKey.LayoutComponentType) {
|
||||
const layoutComponent: LayoutComponent = {
|
||||
basic: BasicLayout,
|
||||
blank: BlankLayout
|
||||
};
|
||||
return layoutComponent[layoutType];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取页面导入的vue文件
|
||||
* @param routeKey - 路由key
|
||||
*/
|
||||
export function getViewComponent(routeKey: AuthRoute.LastDegreeRouteKey) {
|
||||
if (!views[routeKey]) {
|
||||
throw new Error(`路由“${routeKey}”没有对应的组件文件!`);
|
||||
}
|
||||
return setViewComponentName(views[routeKey], routeKey);
|
||||
}
|
||||
|
||||
/** 给页面组件设置名称 */
|
||||
function setViewComponentName(component: RouteComponent | Lazy<ModuleComponent>, name: string) {
|
||||
if (isAsyncComponent(component)) {
|
||||
return async () => {
|
||||
const result = await component();
|
||||
Object.assign(result.default, { name });
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
Object.assign(component, { name });
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
function isAsyncComponent(component: RouteComponent | Lazy<ModuleComponent>): component is Lazy<ModuleComponent> {
|
||||
return isFunction(component);
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
/**
|
||||
* 获取所有固定路由的名称集合
|
||||
* @param routes - 固定路由
|
||||
*/
|
||||
export function getConstantRouteNames(routes: AuthRoute.Route[]) {
|
||||
return routes.map(route => getConstantRouteName(route)).flat(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有固定路由的名称集合
|
||||
* @param route - 固定路由
|
||||
*/
|
||||
function getConstantRouteName(route: AuthRoute.Route) {
|
||||
const names = [route.name];
|
||||
if (route.children?.length) {
|
||||
names.push(...route.children!.map(item => getConstantRouteName(item)).flat(1));
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据路由名称查找顶级菜单
|
||||
* @param routeName - 当前页面路由的key
|
||||
* @param menus - 菜单数据
|
||||
*/
|
||||
export function getTopLevelMenu(routeName: string, menus: App.GlobalMenuOption[]): App.GlobalMenuOption | undefined {
|
||||
return menus.find(item => {
|
||||
if (item.routeName === routeName) return true;
|
||||
if (Array.isArray(item.children)) {
|
||||
return getTopLevelMenu(routeName, item.children);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
export * from './module';
|
||||
export * from './helpers';
|
||||
export * from './cache';
|
||||
export * from './auth';
|
||||
export * from './menu';
|
||||
export * from './breadcrumb';
|
||||
export * from './regexp';
|
||||
export * from './transform';
|
||||
@@ -1,122 +0,0 @@
|
||||
import { useIconRender } from '@/composables';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
/**
|
||||
* 将权限路由转换成菜单
|
||||
* @param routes - 路由
|
||||
*/
|
||||
export function transformAuthRouteToMenu(routes: AuthRoute.Route[]): App.GlobalMenuOption[] {
|
||||
const globalMenu: App.GlobalMenuOption[] = [];
|
||||
routes.forEach(route => {
|
||||
const { name, path, meta } = route;
|
||||
const routeName = name as string;
|
||||
let menuChildren: App.GlobalMenuOption[] | undefined;
|
||||
if (route.children && route.children.length > 0) {
|
||||
menuChildren = transformAuthRouteToMenu(route.children);
|
||||
}
|
||||
const menuItem: App.GlobalMenuOption = addPartialProps({
|
||||
menu: {
|
||||
key: routeName,
|
||||
label: meta.title,
|
||||
routeName,
|
||||
routePath: path,
|
||||
i18nTitle: meta.i18nTitle
|
||||
},
|
||||
icon: meta.icon,
|
||||
localIcon: meta.localIcon,
|
||||
children: menuChildren
|
||||
});
|
||||
|
||||
if (!hideInMenu(route)) {
|
||||
globalMenu.push(menuItem);
|
||||
}
|
||||
});
|
||||
|
||||
return globalMenu;
|
||||
}
|
||||
|
||||
/**
|
||||
* 翻译菜单
|
||||
* @param menus
|
||||
* @returns
|
||||
*/
|
||||
export function translateMenuLabel(menus: App.GlobalMenuOption[]): App.GlobalMenuOption[] {
|
||||
const globalMenu: App.GlobalMenuOption[] = [];
|
||||
menus.forEach(menu => {
|
||||
let menuChildren: App.GlobalMenuOption[] | undefined;
|
||||
if (menu.children && menu.children.length > 0) {
|
||||
menuChildren = translateMenuLabel(menu.children);
|
||||
}
|
||||
const menuItem: App.GlobalMenuOption = {
|
||||
...menu,
|
||||
children: menuChildren,
|
||||
label: menu.i18nTitle ? $t(menu.i18nTitle) : menu.label
|
||||
};
|
||||
globalMenu.push(menuItem);
|
||||
});
|
||||
return globalMenu;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前路由所在菜单数据的paths
|
||||
* @param activeKey - 当前路由的key
|
||||
* @param menus - 菜单数据
|
||||
*/
|
||||
export function getActiveKeyPathsOfMenus(activeKey: string, menus: App.GlobalMenuOption[]) {
|
||||
const keys: string[] = [];
|
||||
const lists: App.GlobalMenuOption[] = [];
|
||||
function traverse(list: App.GlobalMenuOption[], parent: App.GlobalMenuOption | null = null) {
|
||||
list.forEach((t: App.GlobalMenuOption) => {
|
||||
lists.push(t);
|
||||
if (parent) {
|
||||
t.parent = parent;
|
||||
}
|
||||
if (t.children) {
|
||||
traverse(t.children, t);
|
||||
}
|
||||
});
|
||||
}
|
||||
traverse(JSON.parse(JSON.stringify(menus)));
|
||||
lists.forEach((t: App.GlobalMenuOption) => {
|
||||
if (t.routeName === activeKey) {
|
||||
let temp = t;
|
||||
while (temp) {
|
||||
keys.push(temp.routeName);
|
||||
temp = temp.parent as App.GlobalMenuOption;
|
||||
}
|
||||
}
|
||||
});
|
||||
return keys;
|
||||
}
|
||||
|
||||
/** 路由不转换菜单 */
|
||||
function hideInMenu(route: AuthRoute.Route) {
|
||||
return Boolean(route.meta.hide);
|
||||
}
|
||||
|
||||
/** 给菜单添加可选属性 */
|
||||
function addPartialProps(config: {
|
||||
menu: App.GlobalMenuOption;
|
||||
icon?: string;
|
||||
localIcon?: string;
|
||||
children?: App.GlobalMenuOption[];
|
||||
}) {
|
||||
const { iconRender } = useIconRender();
|
||||
|
||||
const item = { ...config.menu };
|
||||
|
||||
const { icon, localIcon, children } = config;
|
||||
|
||||
if (localIcon) {
|
||||
Object.assign(item, { icon: iconRender({ localIcon }) });
|
||||
}
|
||||
|
||||
if (icon) {
|
||||
Object.assign(item, { icon: iconRender({ icon }) });
|
||||
}
|
||||
|
||||
if (children) {
|
||||
Object.assign(item, { children });
|
||||
}
|
||||
return item;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* 权限路由排序
|
||||
* @param routes - 权限路由
|
||||
*/
|
||||
export function sortRoutes(routes: AuthRoute.Route[]) {
|
||||
return routes
|
||||
.sort((next, pre) => Number(next.meta?.order) - Number(pre.meta?.order))
|
||||
.map(i => {
|
||||
if (i.children) sortRoutes(i.children);
|
||||
return i;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理全部导入的路由模块
|
||||
* @param modules - 路由模块
|
||||
*/
|
||||
export function handleModuleRoutes(modules: AuthRoute.RouteModule) {
|
||||
const routes: AuthRoute.Route[] = [];
|
||||
|
||||
Object.keys(modules).forEach(key => {
|
||||
const item = modules[key].default;
|
||||
if (item) {
|
||||
routes.push(item);
|
||||
} else {
|
||||
window.console.error(`路由模块解析出错: key = ${key}`);
|
||||
}
|
||||
});
|
||||
|
||||
return sortRoutes(routes);
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
/** 获取登录页面模块的动态路由的正则 */
|
||||
export function getLoginModuleRegExp() {
|
||||
const modules: UnionKey.LoginModule[] = ['pwd-login', 'code-login', 'register', 'reset-pwd', 'bind-wechat'];
|
||||
return modules.join('|');
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
import { getLayoutComponent, getViewComponent } from './component';
|
||||
|
||||
/**
|
||||
* 将权限路由转换成vue路由
|
||||
* @param routes - 权限路由
|
||||
* @description 所有多级路由都会被转换成二级路由
|
||||
*/
|
||||
export function transformAuthRouteToVueRoutes(routes: AuthRoute.Route[]) {
|
||||
return routes.map(route => transformAuthRouteToVueRoute(route)).flat(1);
|
||||
}
|
||||
|
||||
type ComponentAction = Record<AuthRoute.RouteComponentType, () => void>;
|
||||
|
||||
/**
|
||||
* 将单个权限路由转换成vue路由
|
||||
* @param item - 单个权限路由
|
||||
*/
|
||||
export function transformAuthRouteToVueRoute(item: AuthRoute.Route) {
|
||||
const resultRoute: RouteRecordRaw[] = [];
|
||||
|
||||
const itemRoute = { ...item } as RouteRecordRaw;
|
||||
|
||||
// 动态path
|
||||
if (hasDynamicPath(item)) {
|
||||
Object.assign(itemRoute, { path: item.meta.dynamicPath });
|
||||
}
|
||||
|
||||
// 外链路由
|
||||
if (hasHref(item)) {
|
||||
Object.assign(itemRoute, { component: getViewComponent('404') });
|
||||
}
|
||||
|
||||
// 路由组件
|
||||
if (hasComponent(item)) {
|
||||
const action: ComponentAction = {
|
||||
basic() {
|
||||
itemRoute.component = getLayoutComponent('basic');
|
||||
},
|
||||
blank() {
|
||||
itemRoute.component = getLayoutComponent('blank');
|
||||
},
|
||||
multi() {
|
||||
// 多级路由一定有子路由
|
||||
if (hasChildren(item)) {
|
||||
Object.assign(itemRoute, { meta: { ...itemRoute.meta, multi: true } });
|
||||
delete itemRoute.component;
|
||||
} else {
|
||||
window.console.error('多级路由缺少子路由: ', item);
|
||||
}
|
||||
},
|
||||
self() {
|
||||
itemRoute.component = getViewComponent(item.name as AuthRoute.LastDegreeRouteKey);
|
||||
}
|
||||
};
|
||||
try {
|
||||
if (item.component) {
|
||||
action[item.component]();
|
||||
} else {
|
||||
window.console.error('路由组件解析失败: ', item);
|
||||
}
|
||||
} catch {
|
||||
window.console.error('路由组件解析失败: ', item);
|
||||
}
|
||||
}
|
||||
|
||||
// 注意:单独路由没有children
|
||||
if (isSingleRoute(item)) {
|
||||
if (hasChildren(item)) {
|
||||
window.console.error('单独路由不应该有子路由: ', item);
|
||||
}
|
||||
|
||||
// 捕获无效路由的需特殊处理
|
||||
if (item.name === 'not-found') {
|
||||
itemRoute.children = [
|
||||
{
|
||||
path: '',
|
||||
name: item.name,
|
||||
component: getViewComponent('not-found')
|
||||
}
|
||||
];
|
||||
} else {
|
||||
const parentPath = `${itemRoute.path}-parent` as AuthRouteUtils.SingleRouteKey;
|
||||
|
||||
const layout = item.meta.singleLayout === 'basic' ? getLayoutComponent('basic') : getLayoutComponent('blank');
|
||||
|
||||
const parentRoute: RouteRecordRaw = {
|
||||
path: parentPath,
|
||||
component: layout,
|
||||
redirect: item.path,
|
||||
children: [itemRoute]
|
||||
};
|
||||
|
||||
return [parentRoute];
|
||||
}
|
||||
}
|
||||
|
||||
// 子路由
|
||||
if (hasChildren(item)) {
|
||||
const children = (item.children as AuthRoute.Route[]).map(child => transformAuthRouteToVueRoute(child)).flat();
|
||||
|
||||
// 找出第一个不为多级路由中间级的子路由路径作为重定向路径
|
||||
const redirectPath = (children.find(v => !v.meta?.multi)?.path || '/') as AuthRoute.RoutePath;
|
||||
|
||||
if (redirectPath === '/') {
|
||||
window.console.error('该多级路由没有有效的子路径', item);
|
||||
}
|
||||
|
||||
if (item.component === 'multi') {
|
||||
// 多级路由,将子路由提取出来变成同级
|
||||
resultRoute.push(...children);
|
||||
delete itemRoute.children;
|
||||
} else {
|
||||
itemRoute.children = children;
|
||||
}
|
||||
itemRoute.redirect = redirectPath;
|
||||
}
|
||||
|
||||
resultRoute.push(itemRoute);
|
||||
|
||||
return resultRoute;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将权限路由转换成搜索的菜单数据
|
||||
* @param routes - 权限路由
|
||||
* @param treeMap
|
||||
*/
|
||||
export function transformAuthRouteToSearchMenus(routes: AuthRoute.Route[], treeMap: AuthRoute.Route[] = []) {
|
||||
if (routes && routes.length === 0) return [];
|
||||
return routes.reduce((acc, cur) => {
|
||||
if (!cur.meta?.hide) {
|
||||
acc.push(cur);
|
||||
}
|
||||
if (cur.children && cur.children.length > 0) {
|
||||
transformAuthRouteToSearchMenus(cur.children, treeMap);
|
||||
}
|
||||
return acc;
|
||||
}, treeMap);
|
||||
}
|
||||
|
||||
/** 将路由名字转换成路由路径 */
|
||||
export function transformRouteNameToRoutePath(name: Exclude<AuthRoute.AllRouteKey, 'not-found'>): AuthRoute.RoutePath {
|
||||
const rootPath: AuthRoute.RoutePath = '/';
|
||||
if (name === 'root') return rootPath;
|
||||
|
||||
const splitMark = '_';
|
||||
const pathSplitMark = '/';
|
||||
const path = name.split(splitMark).join(pathSplitMark);
|
||||
|
||||
return (pathSplitMark + path) as AuthRoute.RoutePath;
|
||||
}
|
||||
|
||||
/** 将路由路径转换成路由名字 */
|
||||
export function transformRoutePathToRouteName<K extends AuthRoute.RoutePath>(path: K) {
|
||||
if (path === '/') return 'root';
|
||||
|
||||
const pathSplitMark = '/';
|
||||
const routeSplitMark = '_';
|
||||
|
||||
const name = path.split(pathSplitMark).slice(1).join(routeSplitMark) as AuthRoute.AllRouteKey;
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否有外链
|
||||
* @param item - 权限路由
|
||||
*/
|
||||
function hasHref(item: AuthRoute.Route) {
|
||||
return Boolean(item.meta.href);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否有动态路由path
|
||||
* @param item - 权限路由
|
||||
*/
|
||||
function hasDynamicPath(item: AuthRoute.Route) {
|
||||
return Boolean(item.meta.dynamicPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否有路由组件
|
||||
* @param item - 权限路由
|
||||
*/
|
||||
function hasComponent(item: AuthRoute.Route) {
|
||||
return Boolean(item.component);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否有子路由
|
||||
* @param item - 权限路由
|
||||
*/
|
||||
function hasChildren(item: AuthRoute.Route) {
|
||||
return Boolean(item.children && item.children.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是单层级路由
|
||||
* @param item - 权限路由
|
||||
*/
|
||||
function isSingleRoute(item: AuthRoute.Route) {
|
||||
return Boolean(item.meta.singleLayout);
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
import type { AxiosError, AxiosResponse } from 'axios';
|
||||
import {
|
||||
DEFAULT_REQUEST_ERROR_CODE,
|
||||
DEFAULT_REQUEST_ERROR_MSG,
|
||||
ERROR_STATUS,
|
||||
NETWORK_ERROR_CODE,
|
||||
NETWORK_ERROR_MSG,
|
||||
REQUEST_TIMEOUT_CODE,
|
||||
REQUEST_TIMEOUT_MSG
|
||||
} from '@/config';
|
||||
import { exeStrategyActions } from '../common';
|
||||
import { showErrorMsg } from './msg';
|
||||
|
||||
type ErrorStatus = keyof typeof ERROR_STATUS;
|
||||
|
||||
/**
|
||||
* 处理axios请求失败的错误
|
||||
* @param axiosError - 错误
|
||||
*/
|
||||
export function handleAxiosError(axiosError: AxiosError) {
|
||||
const error: Service.RequestError = {
|
||||
type: 'axios',
|
||||
code: DEFAULT_REQUEST_ERROR_CODE,
|
||||
msg: DEFAULT_REQUEST_ERROR_MSG
|
||||
};
|
||||
|
||||
const actions: Common.StrategyAction[] = [
|
||||
[
|
||||
// 网路错误
|
||||
!window.navigator.onLine || axiosError.message === 'Network Error',
|
||||
() => {
|
||||
Object.assign(error, { code: NETWORK_ERROR_CODE, msg: NETWORK_ERROR_MSG });
|
||||
}
|
||||
],
|
||||
[
|
||||
// 超时错误
|
||||
axiosError.code === REQUEST_TIMEOUT_CODE && axiosError.message.includes('timeout'),
|
||||
() => {
|
||||
Object.assign(error, { code: REQUEST_TIMEOUT_CODE, msg: REQUEST_TIMEOUT_MSG });
|
||||
}
|
||||
],
|
||||
[
|
||||
// 请求不成功的错误
|
||||
Boolean(axiosError.response),
|
||||
() => {
|
||||
const errorCode: ErrorStatus = (axiosError.response?.status as ErrorStatus) || 'DEFAULT';
|
||||
const msg = ERROR_STATUS[errorCode];
|
||||
Object.assign(error, { code: errorCode, msg });
|
||||
}
|
||||
]
|
||||
];
|
||||
|
||||
exeStrategyActions(actions);
|
||||
|
||||
showErrorMsg(error);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理请求成功后的错误
|
||||
* @param response - 请求的响应
|
||||
*/
|
||||
export function handleResponseError(response: AxiosResponse) {
|
||||
const error: Service.RequestError = {
|
||||
type: 'axios',
|
||||
code: DEFAULT_REQUEST_ERROR_CODE,
|
||||
msg: DEFAULT_REQUEST_ERROR_MSG
|
||||
};
|
||||
|
||||
if (!window.navigator.onLine) {
|
||||
// 网路错误
|
||||
Object.assign(error, { code: NETWORK_ERROR_CODE, msg: NETWORK_ERROR_MSG });
|
||||
} else {
|
||||
// 请求成功的状态码非200的错误
|
||||
const errorCode: ErrorStatus = response.status as ErrorStatus;
|
||||
const msg = ERROR_STATUS[errorCode] || DEFAULT_REQUEST_ERROR_MSG;
|
||||
Object.assign(error, { type: 'http', code: errorCode, msg });
|
||||
}
|
||||
|
||||
showErrorMsg(error);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理后端返回的错误(业务错误)
|
||||
* @param backendResult - 后端接口的响应数据
|
||||
*/
|
||||
export function handleBackendError(backendResult: Record<string, any>, config: Service.BackendResultConfig) {
|
||||
const { codeKey, msgKey } = config;
|
||||
const error: Service.RequestError = {
|
||||
type: 'backend',
|
||||
code: backendResult[codeKey],
|
||||
msg: backendResult[msgKey]
|
||||
};
|
||||
|
||||
showErrorMsg(error);
|
||||
|
||||
return error;
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/** 统一失败和成功的请求结果的数据类型 */
|
||||
export async function handleServiceResult<T = any>(error: Service.RequestError | null, data: any) {
|
||||
if (error) {
|
||||
const fail: Service.FailedResult = {
|
||||
error,
|
||||
data: null
|
||||
};
|
||||
return fail;
|
||||
}
|
||||
const success: Service.SuccessResult<T> = {
|
||||
error: null,
|
||||
data
|
||||
};
|
||||
return success;
|
||||
}
|
||||
|
||||
/** 请求结果的适配器:用于接收适配器函数和请求结果 */
|
||||
export function adapter<T extends Service.ServiceAdapter>(
|
||||
adapterFun: T,
|
||||
...args: Service.MultiRequestResult<Parameters<T>>
|
||||
): Service.RequestResult<ReturnType<T>> {
|
||||
let result: Service.RequestResult | undefined;
|
||||
|
||||
const hasError = args.some(item => {
|
||||
const flag = Boolean(item.error);
|
||||
if (flag) {
|
||||
result = {
|
||||
error: item.error,
|
||||
data: null
|
||||
};
|
||||
}
|
||||
return flag;
|
||||
});
|
||||
|
||||
if (!hasError) {
|
||||
const adapterFunArgs = args.map(item => item.data);
|
||||
result = {
|
||||
error: null,
|
||||
data: adapterFun(...adapterFunArgs)
|
||||
};
|
||||
}
|
||||
|
||||
return result!;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export * from './transform';
|
||||
export * from './error';
|
||||
export * from './handler';
|
||||
@@ -1,29 +0,0 @@
|
||||
import { ERROR_MSG_DURATION, NO_ERROR_MSG_CODE } from '@/config';
|
||||
|
||||
/** 错误消息栈,防止同一错误同时出现 */
|
||||
const errorMsgStack = new Map<string | number, string>([]);
|
||||
|
||||
function addErrorMsg(error: Service.RequestError) {
|
||||
errorMsgStack.set(error.code, error.msg);
|
||||
}
|
||||
function removeErrorMsg(error: Service.RequestError) {
|
||||
errorMsgStack.delete(error.code);
|
||||
}
|
||||
function hasErrorMsg(error: Service.RequestError) {
|
||||
return errorMsgStack.has(error.code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示错误信息
|
||||
* @param error
|
||||
*/
|
||||
export function showErrorMsg(error: Service.RequestError) {
|
||||
if (!error.msg || NO_ERROR_MSG_CODE.includes(error.code) || hasErrorMsg(error)) return;
|
||||
|
||||
addErrorMsg(error);
|
||||
window.console.warn(error.code, error.msg);
|
||||
window.$message?.error(error.msg, { duration: ERROR_MSG_DURATION });
|
||||
setTimeout(() => {
|
||||
removeErrorMsg(error);
|
||||
}, ERROR_MSG_DURATION);
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import { stringify } from 'qs';
|
||||
import FormData from 'form-data';
|
||||
import { isArray, isFile } from '../common';
|
||||
|
||||
/**
|
||||
* 请求数据的转换
|
||||
* @param requestData - 请求数据
|
||||
* @param contentType - 请求头的Content-Type
|
||||
*/
|
||||
export async function transformRequestData(requestData: any, contentType?: UnionKey.ContentType) {
|
||||
// application/json类型不处理
|
||||
let data = requestData;
|
||||
// form类型转换
|
||||
if (contentType === 'application/x-www-form-urlencoded') {
|
||||
data = stringify(requestData);
|
||||
}
|
||||
// form-data类型转换
|
||||
if (contentType === 'multipart/form-data') {
|
||||
data = await handleFormData(requestData);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async function handleFormData(data: Record<string, any>) {
|
||||
const formData = new FormData();
|
||||
const entries = Object.entries(data);
|
||||
|
||||
entries.forEach(async ([key, value]) => {
|
||||
const isFileType = isFile(value) || (isArray(value) && value.length && isFile(value[0]));
|
||||
|
||||
if (isFileType) {
|
||||
await transformFile(formData, key, value);
|
||||
} else {
|
||||
formData.append(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
return formData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 接口为上传文件的类型时数据转换
|
||||
* @param key - 文件的属性名
|
||||
* @param file - 单文件或多文件
|
||||
*/
|
||||
async function transformFile(formData: FormData, key: string, file: File[] | File) {
|
||||
if (isArray(file)) {
|
||||
// 多文件
|
||||
await Promise.all(
|
||||
(file as File[]).map(item => {
|
||||
formData.append(key, item);
|
||||
return true;
|
||||
})
|
||||
);
|
||||
} else {
|
||||
// 单文件
|
||||
formData.append(key, file);
|
||||
}
|
||||
}
|
||||
7
src/utils/storage.ts
Normal file
7
src/utils/storage.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { createStorage, createLocalforage } from '@sa/utils';
|
||||
|
||||
export const localStg = createStorage<StorageType.Local>('local');
|
||||
|
||||
export const sessionStg = createStorage<StorageType.Session>('session');
|
||||
|
||||
export const localforage = createLocalforage<StorageType.Local>('local');
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './local';
|
||||
export * from './session';
|
||||
@@ -1,57 +0,0 @@
|
||||
import { decrypt, encrypt } from '../crypto';
|
||||
interface StorageData<T> {
|
||||
value: T;
|
||||
expire: number | null;
|
||||
}
|
||||
|
||||
function createLocalStorage<T extends StorageInterface.Local = StorageInterface.Local>() {
|
||||
/** 默认缓存期限为7天 */
|
||||
const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7;
|
||||
|
||||
function set<K extends keyof T>(key: K, value: T[K], expire: number | null = DEFAULT_CACHE_TIME) {
|
||||
const storageData: StorageData<T[K]> = {
|
||||
value,
|
||||
expire: expire !== null ? new Date().getTime() + expire * 1000 : null
|
||||
};
|
||||
const json = encrypt(storageData);
|
||||
window.localStorage.setItem(key as string, json);
|
||||
}
|
||||
|
||||
function get<K extends keyof T>(key: K) {
|
||||
const json = window.localStorage.getItem(key as string);
|
||||
if (json) {
|
||||
let storageData: StorageData<T[K]> | null = null;
|
||||
try {
|
||||
storageData = decrypt(json);
|
||||
} catch {
|
||||
// 防止解析失败
|
||||
}
|
||||
if (storageData) {
|
||||
const { value, expire } = storageData;
|
||||
// 在有效期内直接返回
|
||||
if (expire === null || expire >= Date.now()) {
|
||||
return value as T[K];
|
||||
}
|
||||
}
|
||||
remove(key);
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function remove(key: keyof T) {
|
||||
window.localStorage.removeItem(key as string);
|
||||
}
|
||||
function clear() {
|
||||
window.localStorage.clear();
|
||||
}
|
||||
|
||||
return {
|
||||
set,
|
||||
get,
|
||||
remove,
|
||||
clear
|
||||
};
|
||||
}
|
||||
|
||||
export const localStg = createLocalStorage();
|
||||
@@ -1,35 +0,0 @@
|
||||
import { decrypt, encrypt } from '../crypto';
|
||||
|
||||
function createSessionStorage<T extends StorageInterface.Session = StorageInterface.Session>() {
|
||||
function set<K extends keyof T>(key: K, value: T[K]) {
|
||||
const json = encrypt(value);
|
||||
sessionStorage.setItem(key as string, json);
|
||||
}
|
||||
function get<K extends keyof T>(key: K) {
|
||||
const json = sessionStorage.getItem(key as string);
|
||||
let data: T[K] | null = null;
|
||||
if (json) {
|
||||
try {
|
||||
data = decrypt(json);
|
||||
} catch {
|
||||
// 防止解析失败
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
function remove(key: keyof T) {
|
||||
window.sessionStorage.removeItem(key as string);
|
||||
}
|
||||
function clear() {
|
||||
window.sessionStorage.clear();
|
||||
}
|
||||
|
||||
return {
|
||||
set,
|
||||
get,
|
||||
remove,
|
||||
clear
|
||||
};
|
||||
}
|
||||
|
||||
export const sessionStg = createSessionStorage();
|
||||
Reference in New Issue
Block a user