317 lines
9.4 KiB
TypeScript
317 lines
9.4 KiB
TypeScript
import { BigNumber } from "bignumber.js";
|
||
/**
|
||
* Check if an element has a class
|
||
* @param {HTMLElement} ele
|
||
* @param {string} cls
|
||
* @returns {boolean}
|
||
*/
|
||
export function hasClass(ele: HTMLElement, cls: string) {
|
||
return !!ele.className.match(new RegExp("(\\s|^)" + cls + "(\\s|$)"));
|
||
}
|
||
|
||
/**
|
||
* Add class to element
|
||
* @param {HTMLElement} ele
|
||
* @param {string} cls
|
||
*/
|
||
export function addClass(ele: HTMLElement, cls: string) {
|
||
if (!hasClass(ele, cls)) ele.className += " " + cls;
|
||
}
|
||
|
||
/**
|
||
* Remove class from element
|
||
* @param {HTMLElement} ele
|
||
* @param {string} cls
|
||
*/
|
||
export function removeClass(ele: HTMLElement, cls: string) {
|
||
if (hasClass(ele, cls)) {
|
||
const reg = new RegExp("(\\s|^)" + cls + "(\\s|$)");
|
||
ele.className = ele.className.replace(reg, " ");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 判断是否是外部链接
|
||
*
|
||
* @param {string} path
|
||
* @returns {Boolean}
|
||
*/
|
||
export function isExternal(path: string) {
|
||
const isExternal = /^(https?:|http?:|mailto:|tel:)/.test(path);
|
||
return isExternal;
|
||
}
|
||
|
||
/**
|
||
* 格式化增长率,保留两位小数 ,并且去掉末尾的0 取绝对值
|
||
*
|
||
* @param growthRate
|
||
* @returns
|
||
*/
|
||
export function formatGrowthRate(growthRate: number) {
|
||
if (growthRate === 0) {
|
||
return "-";
|
||
}
|
||
|
||
const formattedRate = Math.abs(growthRate * 100)
|
||
.toFixed(2)
|
||
.replace(/\.?0+$/, "");
|
||
return formattedRate + "%";
|
||
}
|
||
/**
|
||
* Parse the time to string
|
||
* @param {(Object|string|number)} time
|
||
* @param {string} cFormat
|
||
* @returns {string}
|
||
*/
|
||
export function parseTime(time: string | number | Date | null, cFormat: string | undefined) {
|
||
if (arguments.length === 0) {
|
||
return null;
|
||
}
|
||
const format = cFormat || "{y}-{m}-{d} {h}:{i}:{s}";
|
||
let date;
|
||
if (typeof time === "undefined" || time === null || time === "null") {
|
||
return "";
|
||
} else if (typeof time === "object") {
|
||
date = time;
|
||
} else {
|
||
if (typeof time === "string" && /^[0-9]+$/.test(time)) {
|
||
time = parseInt(time);
|
||
}
|
||
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: keyof typeof formatObj): string => {
|
||
let value = formatObj[key];
|
||
// Note: getDay() returns 0 on Sunday
|
||
if (key === "a") {
|
||
return ["日", "一", "二", "三", "四", "五", "六"][value];
|
||
}
|
||
if (result.length > 0 && value < 10) {
|
||
value = Number("0" + value.toString());
|
||
}
|
||
return value.toString() || "0";
|
||
});
|
||
return time_str;
|
||
}
|
||
|
||
// 下载文件
|
||
export function downloadFile(obj: BlobPart, name: string, suffix: string, useUnix = true) {
|
||
const url = window.URL.createObjectURL(new Blob([obj]));
|
||
const link = document.createElement("a");
|
||
link.style.display = "none";
|
||
link.href = url;
|
||
const newFilename = useUnix ? (parseTime(new Date(), undefined) + "-") : '' + name.trim()
|
||
const fileName = newFilename + "." + suffix;
|
||
link.setAttribute("download", fileName);
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
document.body.removeChild(link);
|
||
}
|
||
|
||
/**
|
||
* 判断主店同步是否启用
|
||
*/
|
||
export function isSyncStatus() {
|
||
let userInfo = ref(JSON.parse(localStorage.getItem('userInfo') || '{}'))
|
||
if (userInfo.value.isHeadShop == 0 && userInfo.value.isEnableProdSync == 1 && userInfo.value.isEnableVipSync == 1 && userInfo.value.isEnableConsSync == 1) {
|
||
return true
|
||
} else {
|
||
return false
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 判断是否有某权限
|
||
*/
|
||
export function hasPermission(params: any) {
|
||
let $PermissionObj = JSON.parse(localStorage.getItem("permission") || '[]')
|
||
const obj = $PermissionObj.find((v: any) => v == params || v == params)
|
||
if (obj) {
|
||
return obj
|
||
}
|
||
return false
|
||
}
|
||
|
||
/**
|
||
* 过滤输入,只允许数字和最多两位小数
|
||
* @param {string} value - 输入框当前值
|
||
* @param {boolean} isIntegerOnly - 是否只允许正整数(无小数点),开启时最小值为1
|
||
* @returns {string} 过滤后的合法值
|
||
*/
|
||
export function filterNumberInput(value, isIntegerOnly = false) {
|
||
// 第一步就过滤所有非数字和非小数点的字符(包括字母)
|
||
let filtered = value.replace(/[^\d.]/g, "");
|
||
|
||
// 整数模式处理
|
||
if (isIntegerOnly !== false) {
|
||
// 移除所有小数点
|
||
filtered = filtered.replace(/\./g, "");
|
||
|
||
// 处理前导零
|
||
filtered = filtered.replace(/^0+(\d)/, "$1") || filtered;
|
||
|
||
// 空值处理(允许临时删除)
|
||
if (filtered === "") {
|
||
return "";
|
||
}
|
||
|
||
// 最小值限制
|
||
if (filtered === isIntegerOnly || parseInt(filtered, 10) < isIntegerOnly) {
|
||
return isIntegerOnly;
|
||
}
|
||
|
||
return filtered;
|
||
}
|
||
|
||
// 小数模式处理
|
||
const parts = filtered.split(".");
|
||
if (parts.length > 1) {
|
||
filtered = parts[0] + "." + (parts[1].substring(0, 2) || "");
|
||
}
|
||
|
||
// 处理前导零
|
||
if (filtered.startsWith("0") && filtered.length > 1 && !filtered.startsWith("0.")) {
|
||
filtered = filtered.replace(/^0+(\d)/, "$1");
|
||
}
|
||
|
||
return filtered;
|
||
}
|
||
|
||
/**
|
||
* 将时分秒字符串转换为完整日期格式
|
||
* @param {string} timeStr - 时分秒字符串,格式需为 HH:mm:ss(如 '00:53:00')
|
||
* @param {Object} options - 可选配置项
|
||
* @param {string} [options.customDate] - 自定义日期,格式为 YYYY-MM-DD(默认使用当前日期)
|
||
* @param {string} [options.format='YYYY-MM-DD HH:mm:ss'] - 输出的日期格式
|
||
* @returns {string|null} 转换后的日期字符串,失败时返回 null
|
||
*/
|
||
export function convertTimeToDate(timeStr, options = {}) {
|
||
// 解构配置项,设置默认值
|
||
const { customDate, format = "YYYY-MM-DD HH:mm:ss" } = options;
|
||
|
||
// 1. 校验时分秒格式(必须为 HH:mm:ss,允许数字1-2位)
|
||
const timeRegex = /^\d{1,2}:\d{1,2}:\d{1,2}$/;
|
||
if (!timeRegex.test(timeStr)) {
|
||
console.error("时分秒格式错误,请使用 HH:mm:ss 格式(如 00:53:00)");
|
||
return null;
|
||
}
|
||
|
||
// 2. 确定日期部分(自定义日期或当前日期)
|
||
let datePart;
|
||
if (customDate) {
|
||
// 校验自定义日期格式
|
||
if (!dayjs(customDate, "YYYY-MM-DD", true).isValid()) {
|
||
console.error("自定义日期格式错误,请使用 YYYY-MM-DD 格式(如 2024-05-20)");
|
||
return null;
|
||
}
|
||
datePart = customDate;
|
||
} else {
|
||
// 使用当前日期(格式:YYYY-MM-DD)
|
||
datePart = dayjs().format("YYYY-MM-DD");
|
||
}
|
||
|
||
// 3. 组合日期和时分秒,生成完整日期对象
|
||
const fullDateTime = `${datePart} ${timeStr}`;
|
||
const dateObj = dayjs(fullDateTime);
|
||
|
||
// 4. 校验完整日期是否有效(如避免 2024-02-30 这种无效日期)
|
||
if (!dateObj.isValid()) {
|
||
console.error("生成的日期无效,请检查日期或时分秒是否合理");
|
||
return null;
|
||
}
|
||
|
||
// 5. 按指定格式返回日期字符串
|
||
return dateObj.format(format);
|
||
}
|
||
|
||
/**
|
||
* 乘法计算并格式化结果
|
||
* @param {string|number} num1 - 第一个乘数
|
||
* @param {string|number} num2 - 第二个乘数
|
||
* @returns {string} 保留两位小数的结果(不四舍五入,补零)
|
||
*/
|
||
export const multiplyAndFormat = (num1: any, num2: any = 1): string => {
|
||
try {
|
||
// 转换为BigNumber(使用字符串构造避免精度问题)
|
||
const bigNum1 = new BigNumber(num1.toString());
|
||
const bigNum2 = new BigNumber(num2.toString());
|
||
|
||
// 1. 乘法计算
|
||
const product = bigNum1.multipliedBy(bigNum2);
|
||
|
||
// 2. 截断到两位小数(不四舍五入)
|
||
const truncated = product.decimalPlaces(2, BigNumber.ROUND_DOWN);
|
||
|
||
// 3. 格式化保留两位小数(补零)
|
||
return truncated.toFixed(2);
|
||
} catch (error) {
|
||
console.error('计算错误:', error);
|
||
return '0.00'; // 出错时返回默认值
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 判断目标值是否包含指定字符串
|
||
* @param {string|number|array} target - 目标值(字符串/数字/数组)
|
||
* @param {string} searchStr - 要查找的子字符串
|
||
* @param {object} [options] - 可选配置
|
||
* @param {boolean} [options.ignoreCase=false] - 是否忽略大小写
|
||
* @param {boolean} [options.allowEmpty=false] - 当 searchStr 为空时,是否返回 true(默认 false)
|
||
* @returns {boolean} 是否包含指定字符串
|
||
*/
|
||
export function includesString(target, searchStr, options = {}) {
|
||
// 解构配置,设置默认值
|
||
const { ignoreCase = false, allowEmpty = false } = options;
|
||
|
||
// 1. 处理 searchStr 为空的情况
|
||
if (searchStr === '' || searchStr === null || searchStr === undefined) {
|
||
return allowEmpty;
|
||
}
|
||
|
||
// 2. 统一将目标值转为字符串(兼容数字/数组等)
|
||
let targetStr = '';
|
||
if (typeof target === 'string') {
|
||
targetStr = target;
|
||
} else if (typeof target === 'number') {
|
||
targetStr = String(target);
|
||
} else if (Array.isArray(target)) {
|
||
// 数组:拼接为字符串(也可改为 "数组中某一项包含",根据需求调整)
|
||
targetStr = target.join(',');
|
||
} else {
|
||
// 其他类型(对象/布尔等):转为字符串或返回 false
|
||
targetStr = String(target);
|
||
}
|
||
|
||
// 3. 处理大小写忽略
|
||
const processedTarget = ignoreCase ? targetStr.toLowerCase() : targetStr;
|
||
const processedSearch = ignoreCase ? searchStr.toLowerCase() : searchStr;
|
||
|
||
// 4. 执行包含判断
|
||
return processedTarget.includes(processedSearch);
|
||
}
|
||
|
||
/**
|
||
* 校验手机号码(中国大陆)
|
||
* - 支持 11 位手机号,号段 13x-19x
|
||
* @param {string} phone
|
||
* @returns {boolean}
|
||
*/
|
||
export function isValidMobile(phone: string): boolean {
|
||
if (!phone && phone !== 0) return false;
|
||
const s = String(phone).trim();
|
||
// 中国大陆手机号正则:以1开头,第二位3-9,后面9位数字,总共11位
|
||
const mobileRegex = /^1[3-9]\d{9}$/;
|
||
return mobileRegex.test(s);
|
||
}
|