import { APP_NAME, APP_VERSION, APP_NAME_TEXT, ENABLE_UPGRADE_VIP, } from "@/config/index"; import {LOGIN_ID_STORAGE_KEY } from '@/xiaolanbenlib/constant' import Taro, { FileSystemManager } from "@tarojs/taro"; import { error } from "console"; import { useRef, useCallback, useEffect } from "react"; const appTokenKey = `${APP_NAME}+${APP_VERSION}token`; export const getToken = () => { return Taro.getStorageSync(appTokenKey); }; export const setToken = (value: string) => { Taro.setStorageSync(appTokenKey, value); }; export const removeToken = () => { Taro.setStorageSync(appTokenKey, ""); }; export const getSuffix = (filePath: string) => { const suffix = filePath.split(".").pop() ?? ""; return suffix; }; const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; // Use a lookup table to find the index. const lookup = typeof Uint8Array === "undefined" ? [] : new Uint8Array(256); for (let i = 0; i < chars.length; i++) { lookup[chars.charCodeAt(i)] = i; } export const encode = (arraybuffer: ArrayBuffer): string => { let bytes = new Uint8Array(arraybuffer), i, len = bytes.length, base64 = ""; for (i = 0; i < len; i += 3) { base64 += chars[bytes[i] >> 2]; base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)]; base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)]; base64 += chars[bytes[i + 2] & 63]; } if (len % 3 === 2) { base64 = base64.substring(0, base64.length - 1) + "="; } else if (len % 3 === 1) { base64 = base64.substring(0, base64.length - 2) + "=="; } return base64; }; export const decode = (base64: string): ArrayBuffer => { let bufferLength = base64.length * 0.75, len = base64.length, i, p = 0, encoded1, encoded2, encoded3, encoded4; if (base64[base64.length - 1] === "=") { bufferLength--; if (base64[base64.length - 2] === "=") { bufferLength--; } } const arraybuffer = new ArrayBuffer(bufferLength), bytes = new Uint8Array(arraybuffer); for (i = 0; i < len; i += 4) { encoded1 = lookup[base64.charCodeAt(i)]; encoded2 = lookup[base64.charCodeAt(i + 1)]; encoded3 = lookup[base64.charCodeAt(i + 2)]; encoded4 = lookup[base64.charCodeAt(i + 3)]; bytes[p++] = (encoded1 << 2) | (encoded2 >> 4); bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2); bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63); } return arraybuffer; }; export function throttle(func: Function, delay: number): Function { let timer: NodeJS.Timeout | null = null; return function (...args: any[]) { if (!timer) { func.apply(this, args); timer = setTimeout(() => { timer = null; }, delay); } }; } export function debounce(func: Function, delay: number): Function { let timer: NodeJS.Timeout | null = null; return function (...args: any[]) { if (timer) { clearTimeout(timer); } timer = setTimeout(() => { func.apply(this, args); timer = null; }, delay); }; } /** * 自定义Hook实现debounce功能 * @param value 要延迟的值 * @param delay 延迟时间(毫秒) * @returns 经过debounce处理后的值 */ export function useDebounce(fn: () => void, delay: number) { const timer = useRef>(null); return () => { timer.current && clearTimeout(timer.current); timer.current = setTimeout(fn, delay); }; } // 修改 useDebounce 使其支持任意参数 export function useDebounceWithParams any>( fn: T, delay: number ): T { const timerRef = useRef(null); return useCallback( (...args: Parameters) => { if (timerRef.current) { clearTimeout(timerRef.current); } timerRef.current = setTimeout(() => { fn(...args); }, delay); }, [fn, delay] ) as T; } export const formatDate = (date: Date): string => { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, "0"); const day = String(date.getDate()).padStart(2, "0"); return `${year}-${month}-${day}`; }; export const formatDateFull = (date: Date): string => { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, "0"); // 月份从 0 开始,所以加 1 const day = String(date.getDate()).padStart(2, "0"); const hours = String(date.getHours()).padStart(2, "0"); const minutes = String(date.getMinutes()).padStart(2, "0"); return `${year}-${month}-${day} ${hours}:${minutes}`; }; export const formatSeconds = (seconds: number): string => { // 计算分钟数 const minutes = Math.floor(seconds / 60); // 计算剩余的秒数 const remainingSeconds = seconds % 60; // 确保分钟和秒都以两位数的形式显示 const formattedMinutes = String(minutes).padStart(2, "0"); const formattedSeconds = String(remainingSeconds).padStart(2, "0"); // 返回格式化的字符串 return `${formattedMinutes}:${formattedSeconds}`; }; // 是否是中文字符 export function isChineseChatacter(str: string): boolean { const code = str.charCodeAt(0); if ( (code >= 0x4e00 && code <= 0x9fff) || (code >= 0x3400 && code <= 0x4dbf) || (code >= 0x20000 && code <= 0x2a6df) || (code >= 0x2a700 && code <= 0x2b73f) || (code >= 0x2b740 && code <= 0x2b81f) || (code >= 0x2b820 && code <= 0x2ceaf) || (code >= 0xf900 && code <= 0xfaff) || (code >= 0x2f800 && code <= 0x2fa1f) ) { return true; } return false; } export function countCharacters(str: string): number { let count = 0; const arr = Array.from(str); for (let i = 0, j = arr.length; i < j; i++) { // 判断是否为中文字符 if (isChineseChatacter(arr[i])) { count += 1; } else { // 英文字符每两个算一个 count += 0.5; } } return Math.ceil(count); } export const getStrByMaxLength = (str: string, length: number) => { const arr = Array.from(str); let result = ""; let l = 0; for (let i = 0, j = arr.length; i < j; i++) { const s = arr[i]; if (isChineseChatacter(s)) { l += 1; } else { l += 0.5; } result += s; if (Math.ceil(l) >= length) { return result; } } return result; }; export const navToWebView = (url: string) => { Taro.navigateTo({ url: `/pages/webview/index?url=${encodeURIComponent(url)}`, }); }; export const getCanvasTempPath = ( canvas: any, canvasId: string ): Promise => { return new Promise((resolve, reject) => { Taro.canvasToTempFilePath( { x: 0, y: 0, fileType: "jpg", quality: 0.8, canvas: canvas, canvasId: canvasId, success: (res) => { console.log(res, res.tempFilePath); resolve(res.tempFilePath); // wx.showToast({ // title: 'canvas转图成功' + res.tempFilePath, // duration: 3000 // }) }, fail: (res) => { console.log(res); Taro.showToast({ title: "canvas转图片失败" + JSON.stringify(res), icon: "none", duration: 3000, }); reject(""); }, }, this ); }); }; export const saveMediaFile = async (tmpPath: string, isVideo: boolean = false): Promise => { return new Promise((resove) => { const params = { filePath: tmpPath, success() { console.log("save success"); resove(true); }, fail(error: any) { console.log("save fail", error); resove(false); }, }; isVideo ? Taro.saveVideoToPhotosAlbum(params) : Taro.saveImageToPhotosAlbum(params); }); }; export const getCloneVoiceIdentifier = (number: number): string => { const formattedNumber = number.toString().padStart(2, "0"); return `克隆声音${formattedNumber}`; }; export const navToVip = () => { if (ENABLE_UPGRADE_VIP) { Taro.navigateTo({ url: "/pages/vip/index" }); return; } Taro.showModal({ content: "此功能暂不可用", showCancel: false, }); }; export const getTempFileContent = ( tmpPath: string, encoding?: keyof FileSystemManager.Encoding ): Promise => { return new Promise((resolve, reject) => { const fs = Taro.getFileSystemManager(); fs.readFile({ filePath: tmpPath, encoding: encoding, success: (res) => { const fileContent = res.data as T; // console.log( // "%c [ fileContent ]: ", // "color: #bf2c9f; background: pink; font-size: 13px;", // fileContent // ); resolve(fileContent); }, fail: (err) => { console.error("读取文件失败", err); reject(err); }, }); }); }; export const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); export function generateRandomId(): string { const timestamp = Date.now().toString(36); // 当前时间戳转换为 base-36 字符串 const randomNum = Math.random().toString(36).substring(2, 8); // 生成随机数并转换为 base-36 字符串 return `${timestamp}-${randomNum}`; // 组合时间戳和随机数 } /** * 生成符合 RFC 4122 版本 4 的 UUID * @returns 格式为 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' 的 UUID 字符串 */ export function generateUUID(): string { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { const r = (Math.random() * 16) | 0; const v = c === 'x' ? r : (r & 0x3) | 0x8; return v.toString(16); }); } export const pickNonEmpty = (obj: T): Partial => { return Object.fromEntries( Object.entries(obj).filter(([_, value]) => value !== "" && value != null ) ) as Partial; }; export const getLoginId = () => { return Taro.getStorageSync(LOGIN_ID_STORAGE_KEY) // 打开小程序时创建 login_uuid } export const isSuccess = (status: number)=> { return status >= 200 && status < 300 } /** * 文件大小单位类型 */ export type FileSizeUnit = 'bit' | 'B' | 'KB' | 'MB' | 'GB' | 'TB'; /** * 文件大小转换配置 */ export interface FileSizeOptions { /** 保留小数位数,默认2位 */ precision?: number; /** 是否使用二进制单位(1024进制),默认为true */ binary?: boolean; /** 强制转换为指定单位,不自动选择 */ unit?: FileSizeUnit; /** 是否添加单位后缀,默认为true */ withUnit?: boolean; } /** * 文件大小转换工具 * @param size 文件大小(bit) * @param options 转换配置 * @returns 转换后的文件大小字符串或数字 */ export function convertFileSize(size: number, options: FileSizeOptions = {}): string | number { const { precision = 2, binary = true, unit, withUnit = true } = options; // 检查输入是否为有效数字 if (isNaN(size) || !isFinite(size)) { return withUnit ? 'Invalid size' : NaN; } // 处理零值 if (size === 0) { return withUnit ? `0${unit || 'B'}` : 0; } // 计算转换基数 const base = binary ? 1024 : 1000; // 定义单位列表 const units: FileSizeUnit[] = ['bit', 'B', 'KB', 'MB', 'GB', 'TB']; // 如果指定了单位,直接转换 if (unit) { const targetIndex = units.indexOf(unit); // 特殊处理bit到B的转换(1字节=8比特) if (unit === 'B') { size = size / 8; } else if (unit === 'bit') { // 如果目标是bit,直接返回原始值 return withUnit ? `${size} bit` : size; } // 计算从B到目标单位的转换 if (targetIndex > 1) { size = size / 8 / Math.pow(base, targetIndex - 1); } const result = Number(size.toFixed(precision)); return withUnit ? `${result} ${unit}` : result; } // 特殊处理bit和B if (size < 8) { return withUnit ? `${size} bit` : size; } if (size < base * 8) { const result = Number((size / 8).toFixed(precision)); return withUnit ? `${result} B` : result; } // 自动选择合适的单位 let unitIndex = 2; // 从KB开始 let convertedSize = size / 8; // 先转换为字节 while (convertedSize >= base && unitIndex < units.length - 1) { convertedSize /= base; unitIndex++; } const result = Number(convertedSize.toFixed(precision)); return withUnit ? `${result} ${units[unitIndex]}` : result; } // export const pxToRpx = (px: number) => { const systemInfo = Taro.getSystemInfoSync() return (750 / systemInfo.windowWidth) * px } /** * 为图片URL添加阿里云OSS处理参数 * 如果URL中不包含?x-oss-process=,则添加?x-oss-process=image/quality,Q_60/format,jpg * 如果URL中已包含其他查询参数,则追加&x-oss-process=image/quality,Q_60/format,jpg * @param url 原始图片URL * @returns 处理后的图片URL */ export function addOssProcessLowQualityParam(url: string): string { // 检查URL是否有效 if (!url || typeof url !== 'string') { return url; } // 检查是否已经包含OSS处理参数 if (url.includes('x-oss-process=')) { return url; } // 分离URL和查询参数 const [baseUrl, existingQuery] = url.split('?'); // 构建新的查询参数 const ossParam = 'x-oss-process=image/quality,Q_60/format,jpg'; const newQuery = existingQuery ? `${existingQuery}&${ossParam}` // 已有查询参数,追加OSS参数 : ossParam; // 没有查询参数,直接使用OSS参数 // 返回处理后的URL return `${baseUrl}?${newQuery}`; } // 使用示例 // const exampleUrl1 = "https://example.com/image.jpg"; // const exampleUrl2 = "https://example.com/image.png?width=200&height=200"; // const exampleUrl3 = "https://example.com/image.webp?x-oss-process=image/resize,w_300"; // console.log(addOssProcessParam(exampleUrl1)); // // 输出: https://example.com/image.jpg?x-oss-process=image/quality,Q_60/format,jpg // console.log(addOssProcessParam(exampleUrl2)); // // 输出: https://example.com/image.png?width=200&height=200&x-oss-process=image/quality,Q_60/format,jpg // console.log(addOssProcessParam(exampleUrl3)); // // 输出: https://example.com/image.webp?x-oss-process=image/resize,w_300 (保持不变,因为已包含OSS参数) export function restrictedPage() { // 方式一:不定义分享相关函数,默认就是禁止分享 // 方式二:显式返回不允许分享(两种分享方式都要处理) useEffect(() => { // 禁止单页分享 Taro.showShareMenu({ withShareTicket: false, menus: [] // 不提供任何分享菜单 }) // 禁止分享到朋友圈 Taro.hideShareMenu({ menus: ['shareAppMessage', 'shareTimeline'] }) }, []) }