Files
cashier-web/src/utils/index.ts
2026-01-12 10:25:03 +08:00

317 lines
9.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
}