import { t } from "@/utils/i18n"; // 过滤非数字 const getNumbersOnly = (text: string) => { return text?.replace(/[^0-9]/g, ""); }; // 判断字符串是否全是数字 const isNumber = (value: string) => { return !Number.isNaN(Number(value)); }; const isWeakNumber = (value: any) => { if (typeof value === "number") { return true; } if (value && typeof value === "string") { return isNumber(value); } return false; }; // 校验手机号是否合法 function isValidPhone(phone: string) { const reg = /^1[3456789]\d{9}$/; return reg.test(phone); } // 校验邮箱地址是否合法 function isValidEmail(email: string) { const reg = /^[\w-]+(\.[\w-]+)*@([\w-]+\.)+[a-zA-Z]{2,}$/; return reg.test(email); } // 校验数据库表名是否合法 // 1. 表名必须以字母开头,后面可以跟字母、数字或下划线。 function validateTableName(tableName: string) { const reg = /^[a-zA-Z_][a-zA-Z0-9_]*$/; return reg.test(tableName); } // 检测字符串是否为有效的JSON格式 function isValidJSON(str: string): boolean { if (!str || typeof str !== "string") { return false; } // 去除首尾空白字符 const trimmedStr = str.trim(); // 检查基本结构:对象必须以 { 开头, 以} 结尾; if (!/^{/.test(trimmedStr) || !/}$/.test(trimmedStr)) { return false; } try { JSON.parse(trimmedStr); return true; } catch (error) { return false; } } // 检测字符串是否为有效的JSON格式,并返回解析结果 function parseJSON( str: string, ): { isValid: boolean; data?: T; error?: string } { if (!str || typeof str !== "string") { return { isValid: false, error: t("Mobile.Common.invalidInputString") }; } try { const data = JSON.parse(str) as T; return { isValid: true, data }; } catch (error) { return { isValid: false, error: error instanceof Error ? error.message : t("Mobile.Common.invalidJsonFormat"), }; } } // 校验登录密码 function validatePassword(password: string) { return password?.length >= 6; // const reg = /^(?=.*\d)(?=.*[a-zA-Z])[\da-zA-Z~!@#$%^&*]{8,16}$/; // return reg.test(password); } // 获取url地址search中的参数 const getURLParams = () => { // #ifdef H5 const searchParams = window.location.search.substring(1).split("&"); // 为了解决元素隐式具有 "any" 类型的问题,将 params 的类型定义为 Record // 这样就可以使用 string 类型的 key 来索引 params 对象 const params: Record = {}; for (const param of searchParams) { const [key, value] = param.split("="); params[key] = value; } return params; // #endif // #ifndef H5 // 非H5平台从页面参数中获取 const pages = getCurrentPages(); const currentPage = pages[pages.length - 1]; const params: Record = {}; if (currentPage?.options) { for (const key in currentPage.options) { params[key] = currentPage.options[key] || ""; } } return params; // #endif }; // 给页面head添加base标签: const addBaseTarget = () => { // #ifdef H5 if (!document.head.querySelector("base")) { const base = document.createElement("base"); base.target = "_blank"; document.head.append(base); } // #endif // #ifndef H5 // 非H5平台无需处理 // #endif }; // 判断对象是否为空 const isEmptyObject = (obj: Record) => { return obj && typeof obj === "object" && Object.keys(obj).length === 0; }; // 格式化时间 function formatTimeAgo(targetTime: string) { if (!targetTime) { return ""; } const now = new Date().getTime(); // 当前时间戳,单位为毫秒 const target = new Date(targetTime).getTime(); const diff = now - target; // 时间差(毫秒) const diffSeconds = Math.floor(diff / 1000); // 转换为秒 const diffMinutes = Math.floor(diffSeconds / 60); // 转换为分钟 const diffHours = Math.floor(diffMinutes / 60); // 转换为小时 const diffDays = Math.floor(diffHours / 24); // 转换为天 if (diffDays > 365 * 2) { return t("Mobile.Time.yearsAgo", { count: Math.floor(diffDays / 365), }); } else if (diffDays > 365) { return t("Mobile.Time.lastYear"); } else if (diffDays > 30) { const currentDate = new Date(); const inputDate = new Date(targetTime); // 计算月份差 let monthsDifference = (currentDate.getFullYear() - inputDate.getFullYear()) * 12; monthsDifference += currentDate.getMonth() - inputDate.getMonth(); // 如果当前日期的日小于输入日期的日,月份差减 1 if (currentDate.getDate() < inputDate.getDate()) { monthsDifference--; } if (monthsDifference >= 1) { return t("Mobile.Time.monthsAgo", { count: monthsDifference, }); } return null; } else if (diffDays > 6) { return t("Mobile.Time.daysAgo", { count: diffDays, }); } else if (diffDays > 2) { let date = new Date(targetTime); let month = date.getMonth() + 1; let day = date.getDate() < 10 ? `0${date.getDate()}` : date.getDate(); return `${month}-${day}`; } else if (diffDays === 2) { return t("Mobile.Time.dayBeforeYesterday"); } else if (diffDays === 1) { return t("Mobile.Time.yesterday"); } else if (diffHours > 1) { return t("Mobile.Time.hoursAgo", { count: diffHours, }); } else if (diffMinutes > 0) { return t("Mobile.Time.minutesAgo", { count: diffMinutes, }); } else { return t("Mobile.Time.justNow"); } } // html自定义转义 function encodeHTML(str: string) { return str .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } // 检查第一个数组每个元素是否都存在于第二个数组中。 const arraysContainSameItems = (arr1: string[], arr2: string[]) => { // 使用 Set 去重 const set1 = new Set(arr1); const set2 = new Set(arr2); // 检查 set1 中的每个元素是否都在 set2 中 for (let item of set1) { if (!set2.has(item)) { return false; } } return true; }; // 向上查找元素 const findParentElement = (element: HTMLElement, className: string) => { // #ifdef H5 let currentElement = element; while (currentElement.parentElement) { if (currentElement.parentElement.classList.contains(className)) { return currentElement.parentElement; } currentElement = currentElement.parentElement; } return null; // #endif // #ifndef H5 // 非H5平台不支持DOM操作,返回null return null; // #endif }; const findClassElement = (currentElement: HTMLElement, className: string) => { // #ifdef H5 if (currentElement.classList.contains(className)) { return currentElement; } return findParentElement(currentElement, className); // #endif // #ifndef H5 // 非H5平台不支持DOM操作,返回null return null; // #endif }; const noop = () => {}; /** * 生成唯一ID(同步方法,适用于所有平台) * 格式:时间戳(13位) + 随机数(7位) = 20位字符串 * @returns 唯一ID字符串 */ function generateUniqueId(): string { // 时间戳(毫秒) const timestamp = Date.now().toString(36); // 随机数部分 - 使用多个随机源提高唯一性 const random1 = Math.random().toString(36).substring(2, 9); const random2 = Math.random().toString(36).substring(2, 5); // 组合成唯一ID return `${timestamp}${random1}${random2}`; } /** * 生成类似 UUID 格式的ID(同步方法) * 格式:xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx * @returns UUID格式的字符串 */ function generateUUID(): string { const timestamp = Date.now(); const random = () => Math.floor(Math.random() * 16).toString(16); // 使用时间戳和随机数组合 let uuid = ""; for (let i = 0; i < 32; i++) { if (i === 8 || i === 12 || i === 16 || i === 20) { uuid += "-"; } if (i === 12) { uuid += "4"; // UUID version 4 } else if (i === 16) { uuid += ((Math.random() * 4) | 8).toString(16); // UUID variant } else { // 混合时间戳和随机数 const useTime = i < 8; uuid += useTime ? ((timestamp >> ((7 - i) * 4)) & 0xf).toString(16) : random(); } } return uuid; } /** * 替换路径模板中的动态变量 * @param template - 路径模板,例如 /view/detail/{id} * @param params - 参数对象,例如 { id: 123 } * @returns 替换后的路径,例如 /view/detail/123 */ const fillPathParams = ( template: string, params: Record, ): string => { return template.replace(/{(\w+)}/g, (_, key) => { if (params[key] === undefined) { throw new Error( t("Mobile.Common.missingPathParam", { key, }), ); } return String(params[key]); }); }; /** * 检查路径模板中的变量是否在 data 中存在且值有效 * @param template 路径模板,例如 /user/{id}/{name} * @param data 参数对象,例如 { id: 1, name: 'Tom' } * @returns 是否全部存在且有效 */ const checkPathParams = ( template: string, data: Record, ): boolean => { const keys = [...template.matchAll(/{(\w+)}/g)].map((m) => m[1]); return ( keys.length === 0 || keys.every( (k) => data[k] !== undefined && data[k] !== null && data[k] !== "", ) ); }; /** * 将对象转换为 URL 查询字符串 * 用于替代 URLSearchParams(微信小程序不支持) * @param params 参数对象 * @returns URL 查询字符串,例如 "key1=value1&key2=value2" */ const objectToQueryString = ( params: Record | null | undefined, ): string => { if (!params || typeof params !== "object") { return ""; } const queryParts: string[] = []; for (const key in params) { if (params.hasOwnProperty(key)) { const value = params[key]; // 跳过 undefined 和 null 值 if (value === undefined || value === null || value === "") { continue; } // 对键和值进行 URL 编码 const encodedKey = encodeURIComponent(key); const encodedValue = encodeURIComponent(String(value)); queryParts.push(`${encodedKey}=${encodedValue}`); } } return queryParts.join("&"); }; /** * 从 URL 中移除指定 query 参数(支持多个、支持 hash、支持不存在的参数) * @param url * @param keys * @returns */ const removeQueryCompat = (url: string, keys: string | string[]): string => { const arr = Array.isArray(keys) ? keys : [keys]; const [base, hash = ""] = url.split("#"); const [path, query = ""] = base.split("?"); if (!query) return url; // 没 query 无需处理 const params = query.split("&").filter((item) => { const [key] = item.split("="); return !arr.includes(key); }); const newUrl = params.length ? `${path}?${params.join("&")}` : path; return hash ? `${newUrl}#${hash}` : newUrl; }; /** * 登录后跳转页面,如果redirectUrl存在,则跳转到redirectUrl,否则跳转到/pages/index/index * @param redirectUrl * @returns */ const redirectTo = (redirectUrl: string) => { if (!redirectUrl) { uni.reLaunch({ url: "/pages/index/index" }); return; } // #ifdef H5 || WEB location.replace(redirectUrl); // #endif // #ifdef MP-WEIXIN // 先解码URL,避免路径被错误拼接(如 %2Fpages%2Findex 会被拼接到当前路径后) const decodedUrl = decodeURIComponent(redirectUrl); uni.reLaunch({ url: decodedUrl }); // 清空全局变量 globalThis.appConfig.redirectUrl = null; // #endif }; /** * 获取当前页面的路径 */ const getCurrentPagePath = () => { // #ifdef H5 || WEB return window.location.href; // #endif // #ifdef MP-WEIXIN return getCurrentPages()[getCurrentPages().length - 1]?.route; // #endif }; /** * 获取当前页面的参数 */ const getCurrentPageParams = () => { // #ifdef H5 || WEB // 从 hash 中提取查询参数(适配 hash 路由模式) const hash = window.location.hash; const questionMarkIndex = hash.indexOf("?"); if (questionMarkIndex === -1) { return {}; } const queryString = hash.substring(questionMarkIndex + 1); const params: Record = {}; queryString.split("&").forEach((param) => { const [key, value] = param.split("="); if (key) { params[key] = value ? decodeURIComponent(value) : ""; } }); return params; // #endif // #ifdef MP-WEIXIN return getCurrentPages()[getCurrentPages().length - 1]?.options; // #endif }; /** * 获取当前页面的完整路径 * @param fullPath 是否包含参数 * @returns 当前页面的完整路径 */ const getCurrentPageFullPath = (fullPath: boolean = true) => { const path = getCurrentPagePath(); const params = objectToQueryString(getCurrentPageParams()); return fullPath && params ? `${path}?${params}` : path; }; /** * 跳转至上一页,如果页面栈中有多个页面,正常返回,如果页面栈只有一个页面(浏览器刷新后),使用浏览器历史记录返回 如果没有历史记录,跳转到首页 */ const jumpNavigateBack = () => { // 获取当前页面栈 const pages = getCurrentPages(); // 如果页面栈中有多个页面,正常返回 if (pages.length > 1) { uni.navigateBack({ delta: 1, }); } else { // 如果页面栈只有一个页面(浏览器刷新后),使用浏览器历史记录返回 如果没有历史记录,跳转到首页 uni.switchTab({ url: "/pages/index/index", }); } }; // 判断 url 地址是否是 http 或者 https 开头 const isHttpUrl = (url: string) => { return url.startsWith("http://") || url.startsWith("https://"); }; export { addBaseTarget, arraysContainSameItems, encodeHTML, findClassElement, findParentElement, formatTimeAgo, generateUniqueId, generateUUID, getNumbersOnly, getURLParams, isEmptyObject, isNumber, isValidEmail, isValidJSON, isValidPhone, isWeakNumber, noop, parseJSON, validatePassword, validateTableName, fillPathParams, checkPathParams, objectToQueryString, removeQueryCompat, redirectTo, getCurrentPagePath, getCurrentPageParams, getCurrentPageFullPath, jumpNavigateBack, isHttpUrl, };