This commit is contained in:
lijianyou
2025-09-30 15:00:32 +08:00
parent 1b262f06b8
commit b4e759cf69
202 changed files with 22035 additions and 0 deletions
+161
View File
@@ -0,0 +1,161 @@
import axios, { AxiosInstance, AxiosRequestConfig, Method as _Method, AxiosResponse } from 'axios';
import qs from 'qs';
import Cookie from 'js-cookie';
declare interface _AxiosExtend {
/**
* 发起请求
* @param url 请求地址
* @param method 请求方法
* @param params 请求参数
* @param config 请求配置
*/
request<T = any, R = AxiosResponse<T>>(
url: string,
method: Method,
params?: Record<string | number, any>,
config?: AxiosRequestConfig
): Promise<R>;
/**
* 设置token
* @param value token值
* @param expires 过期时间
* - 类型为 number 时,表示 expires 毫秒后 token 过期
* - 类型为 Date 时,表示在 expires 这个时间点 token 过期
* @param name token 名称,默认为当前 http 实例的 xsrfCookieName 属性值
*/
setAuthorization(value: string, expires: number | Date, name?: string): void;
/**
* 移出token
* @param name token 名称, 默认为当前 http 实例的 xsrfCookieName 属性值
*/
removeAuthorization(name?: string): void;
/**
* 校验 token 是否有效
* @param name 需要校验的 token 名称,默认为当前 http 实例的 xsrfCookieName 属性值
*/
checkAuthorization(name?: string): boolean;
}
export interface AxiosHttp extends Omit<AxiosInstance, 'request'>, _AxiosExtend { }
export type Method = _Method | 'POST_JSON' | 'post_json' | 'PUT_JSON' | 'put_json';
/**
* 转表单格式
* @param params
* @returns
*/
export function toFormData(params?: Record<string | number, any>) {
const formData = new FormData();
if (!params) {
return formData;
}
Object.entries(params).forEach(([key, value]) => {
if (Array.isArray(value)) {
value.forEach((val) => {
formData.append(key, val);
});
} else {
formData.set(key, value);
}
});
return formData;
}
function toUrlencoded(params?: Record<string | number, any>) {
const urlencoded = new URLSearchParams();
for (const key in params) {
if (params[key] !== undefined) {
urlencoded.append(key, params[key]);
}
}
return urlencoded;
}
/**
* 创建 axios http
* @param config
* @returns
*/
function createAxiosHttp(config: AxiosRequestConfig): AxiosHttp {
const _axios = axios.create(config);
// 添加响应拦截器处理401状态
_axios.interceptors.response.use(
// 成功响应直接返回
response => response,
// 错误响应处理
error => {
// 检查是否是401未授权错误
if (error.response && error.response.status === 401) {
// 调用removeAuthorization方法清除token
http.removeAuthorization();
// 这里可以添加额外的处理,比如跳转到登录页
// 示例: window.location.href = '/login';
}
return Promise.reject(error);
}
);
const http: AxiosHttp = {
..._axios,
request<T = any, R = AxiosResponse<T>>(
url: string,
method: Method,
params?: Record<string | number, any>,
config?: AxiosRequestConfig
): Promise<R> {
const _method = method.toUpperCase();
switch (_method) {
case 'GET':
return _axios.get(url, {
params,
paramsSerializer: (data) => {
return qs.stringify(data, { indices: false, skipNulls: true });
},
...config,
});
case 'POST':
return _axios.post(url, toUrlencoded(params), config);
case 'POST_JSON':
return _axios.post(url, params, config);
case 'PUT':
return _axios.put(url, toFormData(params), config);
case 'PUT_JSON':
return _axios.put(url, params, config);
case 'DELETE':
return _axios.delete(url, { data: toFormData(params), ...config });
case 'HEAD':
return _axios.head(url, { params, ...config });
case 'OPTIONS':
return _axios.options(url, { params, ...config });
case 'PATCH':
return _axios.patch(url, { params, ...config });
case 'PURGE':
case 'LINK':
case 'UNLINK':
const m = _method as _Method;
return _axios.request({ url, method: m, params, ..._axios.defaults });
default:
return _axios.request({ url, method: 'GET', params, ..._axios.defaults });
}
},
setAuthorization(token: string, expires: number | Date, name?: string): void {
Cookie.set(name ?? _axios.defaults.xsrfCookieName!, token, { expires });
},
removeAuthorization(name?: string): void {
Cookie.remove(name ?? _axios.defaults.xsrfCookieName!);
},
checkAuthorization(name?: string | undefined): boolean {
return Boolean(Cookie.get(name ?? _axios.defaults.xsrfCookieName!));
},
};
return http;
}
export default createAxiosHttp;
+51
View File
@@ -0,0 +1,51 @@
import fs from 'fs';
import path from 'path';
/**
* 读取环境变量文件
* @param path
*/
export function readEnvFile(filePath: string) {
try {
const data = fs.readFileSync(filePath, 'utf8');
return data
.split('\n')
.map((kv) => kv.split('='))
.filter(([k, v]) => k != undefined && v != undefined)
.map(([k, v]) => ({ [k]: v.replace(/\r\n/, '').replace(/\r/, '') }))
.reduce((p, c) => ({ ...p, ...c }));
} catch (err) {}
return {};
}
/**
* 获取环境变量
* @param dirname 环境变量文件目录
* @param command 命令 build | dev
* @param mode 模式 development | production | test | or any other custom mode
*/
export function getEnv(dirname: string, command?: string, mode?: string) {
const _environment = command === 'build' ? 'production' : 'development';
const environment = mode || _environment;
const baseEnv = readEnvFile(path.resolve(dirname, `./.env`));
const modeEnv = readEnvFile(path.resolve(dirname, `./.env.${environment}`));
return { ...baseEnv, ...modeEnv };
}
/**
* 获取文件 base64 编码内容
* @param file
* @returns
*/
export async function getBase64(file: Blob): Promise<string> {
const promise = new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.addEventListener('load', () => resolve(reader.result as string));
try {
reader.readAsDataURL(file);
} catch (e) {
reject(e);
}
});
return promise;
}
+38
View File
@@ -0,0 +1,38 @@
/**
* 格式化金额
* @param value
* @param fixed
* @returns
*/
export function formatMoney(value: number, fixed: number = 0) {
let unit: string = value < 10000 ? '' : value < 100000000 ? 'w' : '亿';
value =
value < 10000
? value
: value < 100000000
? value / 10000
: value / 100000000;
let format: string = value.toFixed(fixed);
const _val = format.split('.');
const _int = _val[0],
_dec = _val[1];
return `${_val}${unit}`;
}
/**
* 千位格式化
* @param value
* @param fixed
* @returns
*/
export function formatThousand(value: number, fixed: number = 0): string {
const _val: string[] = value.toFixed(fixed).split('.');
let [_int, _dec] = _val;
_dec = (parseInt(_dec) === 0 ? undefined : _dec) as string;
let numbers = [];
let format = '';
for (let i = _int.length; i >= 0; i -= 3) {
numbers.push(_int.substring(i - 3 < 0 ? 0 : i - 3, i));
}
return numbers.reverse().join(',') + ((_dec && `.${_dec}`) ?? '');
}
+8
View File
@@ -0,0 +1,8 @@
/**
* 初始化目标值为 undefined 的属性
* @param target 目标对象
* @param dft 默认值对象
*/
export function initUndefined<T extends Record<string, any>, K extends keyof T>(target: T, dft: Required<Pick<T, K>>) {
(Object.keys(dft) as K[]).forEach((key) => (target[key] = target[key] ?? dft[key]));
}
+103
View File
@@ -0,0 +1,103 @@
import { onMounted, ref, onBeforeUnmount } from 'vue';
/**
* 计算元素距离屏幕左上距离
* @param el
* @returns
*/
export function offsetScreen(el: HTMLElement) {
let { offsetLeft, offsetTop } = el;
if (el.offsetParent) {
const [left, top] = offsetScreen(el.offsetParent as HTMLElement);
offsetLeft += left;
offsetTop += top;
}
return [offsetLeft, offsetTop];
}
/**
* 全屏api
* @param target 需要全屏的元素
* @returns
*/
export function useFullScreen(target: HTMLElement | string) {
let _target: HTMLElement = undefined;
const name = typeof target === 'object' ? target.tagName : target;
onMounted(() => {
document.addEventListener('fullscreenchange', fullscreenListener);
document.addEventListener('webkitfullscreenchange', fullscreenListener);
document.addEventListener('mozfullscreenchange', fullscreenListener);
document.addEventListener('msfullscreenchange', fullscreenListener);
});
onBeforeUnmount(() => {
document.removeEventListener('fullscreenchange', fullscreenListener);
document.removeEventListener('webkitfullscreenchange', fullscreenListener);
document.removeEventListener('mozfullscreenchange', fullscreenListener);
document.removeEventListener('msfullscreenchange', fullscreenListener);
});
let state = false;
const isEnter = ref(state);
function fullscreenListener(e: Event) {
if (e.target !== _target) {
isEnter.value = false;
state = false;
} else {
state = !state;
isEnter.value = state;
}
}
/**
* 进入全屏
* @returns
*/
async function enterFullScreen() {
return new Promise((resolve, reject) => {
_target = typeof target === 'string' ? document.querySelector(target) : target;
if (_target) {
// @ts-ignore
const { requestFullscreen, webkitRequestFullScreen, mozRequestFullScreen, msRequestFullscreen } = _target;
const _requestFullscreen: () => void =
requestFullscreen ?? webkitRequestFullScreen ?? mozRequestFullScreen ?? msRequestFullscreen;
if (_requestFullscreen) {
const _result = _requestFullscreen.apply(_target) as any;
if (_result instanceof Promise) {
_result.then(() => resolve(null)).catch((e) => reject(e));
} else {
resolve(null);
}
} else {
reject(`sorry, your browser don't support fullscreen feature`);
}
} else {
reject(`can'nt find target element ${name} for fullscreen`);
}
});
}
async function exitFullscreen() {
return new Promise((resolve, reject) => {
// @ts-ignore
const { exitFullscreen, webkitCancelFullScreen, mozCancelFullScreen, msExitFullscreen } = document;
const _exitFullscreen: () => void =
exitFullscreen ?? webkitCancelFullScreen ?? mozCancelFullScreen ?? msExitFullscreen;
if (_exitFullscreen) {
const _result = _exitFullscreen.apply(document) as any;
if (_result instanceof Promise) {
_result.then(() => resolve(null)).catch((e) => reject(e));
} else {
isEnter.value = false;
resolve(null);
}
} else {
reject(`sorry, your browser don't support fullscreen feature`);
}
});
}
return { enterFullScreen, exitFullscreen, isEnter };
}
+49
View File
@@ -0,0 +1,49 @@
export const emailReg = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
export const domainReg = /^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\.)+[A-Za-z]{2,6}$/
export const portReg = /^([1-9]|[1-9]\d{1,3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$/
export const subDomainPrefix = /^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])$/
export const passReg = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,}$/
export const userNameReg = /^[a-zA-Z0-9_]{3,}$/
export function checkPass(str: string) {
return passReg.test(str)
}
export function checkUserName(str: string) {
return userNameReg.test(str)
}
export function checkEmail(str: string) {
return emailReg.test(str)
}
export function checkDomain(str: string) {
return domainReg.test(str)
}
export function checkPort(str: string) {
return portReg.test(str)
}
export function checksubDomainPrefix(str: string) {
if (!str || str === '')
return false;
const subs = str.split(';')
if (subs) {
let succ = true
subs.forEach(sub => {
if (sub && sub != '')
if (!subDomainPrefix.test(sub)) {
succ = false;
}
});
return succ
} else {
return false;
}
}
+21
View File
@@ -0,0 +1,21 @@
import { watch, computed, ref } from 'vue';
function useModelValue<T>(value: () => T | undefined, onChange: (val?: T) => void, defaultValue?: T) {
const _value = ref<T>();
_value.value = value() ?? defaultValue;
const sValue = computed({
get() {
return value() ?? _value.value;
},
set(val: T | undefined) {
_value.value = val;
onChange(val);
},
});
watch(value, () => {
_value.value = value();
});
return { value: sValue };
}
export default useModelValue;
+21
View File
@@ -0,0 +1,21 @@
import { useSettingStore } from '@/store';
import { onBeforeMount, onUnmounted, onActivated, onDeactivated } from 'vue';
export function useUnbounded() {
const { setContentClass } = useSettingStore();
const setUnbounded = () => {
setContentClass('unbounded');
setTimeout(() => window.dispatchEvent(new Event('resize')), 300);
};
const removeUnbounded = () => {
setContentClass('common');
setTimeout(() => window.dispatchEvent(new Event('resize')), 300);
};
onBeforeMount(setUnbounded);
onUnmounted(removeUnbounded);
onActivated(setUnbounded);
onDeactivated(removeUnbounded);
}