index.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. import {
  2. APP_NAME,
  3. APP_VERSION,
  4. APP_NAME_TEXT,
  5. ENABLE_UPGRADE_VIP,
  6. } from "@/config/index";
  7. import {LOGIN_ID_STORAGE_KEY } from '@/xiaolanbenlib/constant'
  8. import Taro, { FileSystemManager } from "@tarojs/taro";
  9. import { error } from "console";
  10. import { useRef, useCallback, useEffect } from "react";
  11. const appTokenKey = `${APP_NAME}+${APP_VERSION}token`;
  12. export const getToken = () => {
  13. return Taro.getStorageSync(appTokenKey);
  14. };
  15. export const setToken = (value: string) => {
  16. Taro.setStorageSync(appTokenKey, value);
  17. };
  18. export const removeToken = () => {
  19. Taro.setStorageSync(appTokenKey, "");
  20. };
  21. export const getSuffix = (filePath: string) => {
  22. const suffix = filePath.split(".").pop() ?? "";
  23. return suffix;
  24. };
  25. const chars =
  26. "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  27. // Use a lookup table to find the index.
  28. const lookup = typeof Uint8Array === "undefined" ? [] : new Uint8Array(256);
  29. for (let i = 0; i < chars.length; i++) {
  30. lookup[chars.charCodeAt(i)] = i;
  31. }
  32. export const encode = (arraybuffer: ArrayBuffer): string => {
  33. let bytes = new Uint8Array(arraybuffer),
  34. i,
  35. len = bytes.length,
  36. base64 = "";
  37. for (i = 0; i < len; i += 3) {
  38. base64 += chars[bytes[i] >> 2];
  39. base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];
  40. base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
  41. base64 += chars[bytes[i + 2] & 63];
  42. }
  43. if (len % 3 === 2) {
  44. base64 = base64.substring(0, base64.length - 1) + "=";
  45. } else if (len % 3 === 1) {
  46. base64 = base64.substring(0, base64.length - 2) + "==";
  47. }
  48. return base64;
  49. };
  50. export const decode = (base64: string): ArrayBuffer => {
  51. let bufferLength = base64.length * 0.75,
  52. len = base64.length,
  53. i,
  54. p = 0,
  55. encoded1,
  56. encoded2,
  57. encoded3,
  58. encoded4;
  59. if (base64[base64.length - 1] === "=") {
  60. bufferLength--;
  61. if (base64[base64.length - 2] === "=") {
  62. bufferLength--;
  63. }
  64. }
  65. const arraybuffer = new ArrayBuffer(bufferLength),
  66. bytes = new Uint8Array(arraybuffer);
  67. for (i = 0; i < len; i += 4) {
  68. encoded1 = lookup[base64.charCodeAt(i)];
  69. encoded2 = lookup[base64.charCodeAt(i + 1)];
  70. encoded3 = lookup[base64.charCodeAt(i + 2)];
  71. encoded4 = lookup[base64.charCodeAt(i + 3)];
  72. bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
  73. bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
  74. bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
  75. }
  76. return arraybuffer;
  77. };
  78. export function throttle(func: Function, delay: number): Function {
  79. let timer: NodeJS.Timeout | null = null;
  80. return function (...args: any[]) {
  81. if (!timer) {
  82. func.apply(this, args);
  83. timer = setTimeout(() => {
  84. timer = null;
  85. }, delay);
  86. }
  87. };
  88. }
  89. export function debounce(func: Function, delay: number): Function {
  90. let timer: NodeJS.Timeout | null = null;
  91. return function (...args: any[]) {
  92. if (timer) {
  93. clearTimeout(timer);
  94. }
  95. timer = setTimeout(() => {
  96. func.apply(this, args);
  97. timer = null;
  98. }, delay);
  99. };
  100. }
  101. /**
  102. * 自定义Hook实现debounce功能
  103. * @param value 要延迟的值
  104. * @param delay 延迟时间(毫秒)
  105. * @returns 经过debounce处理后的值
  106. */
  107. export function useDebounce(fn: () => void, delay: number) {
  108. const timer = useRef<null | ReturnType<typeof setTimeout>>(null);
  109. return () => {
  110. timer.current && clearTimeout(timer.current);
  111. timer.current = setTimeout(fn, delay);
  112. };
  113. }
  114. // 修改 useDebounce 使其支持任意参数
  115. export function useDebounceWithParams<T extends (...args: any[]) => any>(
  116. fn: T,
  117. delay: number
  118. ): T {
  119. const timerRef = useRef<NodeJS.Timeout | null>(null);
  120. return useCallback(
  121. (...args: Parameters<T>) => {
  122. if (timerRef.current) {
  123. clearTimeout(timerRef.current);
  124. }
  125. timerRef.current = setTimeout(() => {
  126. fn(...args);
  127. }, delay);
  128. },
  129. [fn, delay]
  130. ) as T;
  131. }
  132. export const formatDate = (date: Date): string => {
  133. const year = date.getFullYear();
  134. const month = String(date.getMonth() + 1).padStart(2, "0");
  135. const day = String(date.getDate()).padStart(2, "0");
  136. return `${year}-${month}-${day}`;
  137. };
  138. export const formatDateFull = (date: Date): string => {
  139. const year = date.getFullYear();
  140. const month = String(date.getMonth() + 1).padStart(2, "0"); // 月份从 0 开始,所以加 1
  141. const day = String(date.getDate()).padStart(2, "0");
  142. const hours = String(date.getHours()).padStart(2, "0");
  143. const minutes = String(date.getMinutes()).padStart(2, "0");
  144. return `${year}-${month}-${day} ${hours}:${minutes}`;
  145. };
  146. export const formatSeconds = (seconds: number): string => {
  147. // 计算分钟数
  148. const minutes = Math.floor(seconds / 60);
  149. // 计算剩余的秒数
  150. const remainingSeconds = seconds % 60;
  151. // 确保分钟和秒都以两位数的形式显示
  152. const formattedMinutes = String(minutes).padStart(2, "0");
  153. const formattedSeconds = String(remainingSeconds).padStart(2, "0");
  154. // 返回格式化的字符串
  155. return `${formattedMinutes}:${formattedSeconds}`;
  156. };
  157. // 是否是中文字符
  158. export function isChineseChatacter(str: string): boolean {
  159. const code = str.charCodeAt(0);
  160. if (
  161. (code >= 0x4e00 && code <= 0x9fff) ||
  162. (code >= 0x3400 && code <= 0x4dbf) ||
  163. (code >= 0x20000 && code <= 0x2a6df) ||
  164. (code >= 0x2a700 && code <= 0x2b73f) ||
  165. (code >= 0x2b740 && code <= 0x2b81f) ||
  166. (code >= 0x2b820 && code <= 0x2ceaf) ||
  167. (code >= 0xf900 && code <= 0xfaff) ||
  168. (code >= 0x2f800 && code <= 0x2fa1f)
  169. ) {
  170. return true;
  171. }
  172. return false;
  173. }
  174. export function countCharacters(str: string): number {
  175. let count = 0;
  176. const arr = Array.from(str);
  177. for (let i = 0, j = arr.length; i < j; i++) {
  178. // 判断是否为中文字符
  179. if (isChineseChatacter(arr[i])) {
  180. count += 1;
  181. } else {
  182. // 英文字符每两个算一个
  183. count += 0.5;
  184. }
  185. }
  186. return Math.ceil(count);
  187. }
  188. export const getStrByMaxLength = (str: string, length: number) => {
  189. const arr = Array.from(str);
  190. let result = "";
  191. let l = 0;
  192. for (let i = 0, j = arr.length; i < j; i++) {
  193. const s = arr[i];
  194. if (isChineseChatacter(s)) {
  195. l += 1;
  196. } else {
  197. l += 0.5;
  198. }
  199. result += s;
  200. if (Math.ceil(l) >= length) {
  201. return result;
  202. }
  203. }
  204. return result;
  205. };
  206. export const navToWebView = (url: string) => {
  207. Taro.navigateTo({
  208. url: `/pages/webview/index?url=${encodeURIComponent(url)}`,
  209. });
  210. };
  211. export const getCanvasTempPath = (
  212. canvas: any,
  213. canvasId: string
  214. ): Promise<string> => {
  215. return new Promise((resolve, reject) => {
  216. Taro.canvasToTempFilePath(
  217. {
  218. x: 0,
  219. y: 0,
  220. fileType: "jpg",
  221. quality: 0.8,
  222. canvas: canvas,
  223. canvasId: canvasId,
  224. success: (res) => {
  225. console.log(res, res.tempFilePath);
  226. resolve(res.tempFilePath);
  227. // wx.showToast({
  228. // title: 'canvas转图成功' + res.tempFilePath,
  229. // duration: 3000
  230. // })
  231. },
  232. fail: (res) => {
  233. console.log(res);
  234. Taro.showToast({
  235. title: "canvas转图片失败" + JSON.stringify(res),
  236. icon: "none",
  237. duration: 3000,
  238. });
  239. reject("");
  240. },
  241. },
  242. this
  243. );
  244. });
  245. };
  246. export const saveMediaFile = async (tmpPath: string, isVideo: boolean = false): Promise<boolean> => {
  247. return new Promise((resove) => {
  248. const params = {
  249. filePath: tmpPath,
  250. success() {
  251. console.log("save success");
  252. resove(true);
  253. },
  254. fail(error: any) {
  255. console.log("save fail", error);
  256. resove(false);
  257. },
  258. };
  259. isVideo ? Taro.saveVideoToPhotosAlbum(params) : Taro.saveImageToPhotosAlbum(params);
  260. });
  261. };
  262. export const getCloneVoiceIdentifier = (number: number): string => {
  263. const formattedNumber = number.toString().padStart(2, "0");
  264. return `克隆声音${formattedNumber}`;
  265. };
  266. export const navToVip = () => {
  267. if (ENABLE_UPGRADE_VIP) {
  268. Taro.navigateTo({ url: "/pages/vip/index" });
  269. return;
  270. }
  271. Taro.showModal({
  272. content: "此功能暂不可用",
  273. showCancel: false,
  274. });
  275. };
  276. export const getTempFileContent = <T>(
  277. tmpPath: string,
  278. encoding?: keyof FileSystemManager.Encoding
  279. ): Promise<T> => {
  280. return new Promise((resolve, reject) => {
  281. const fs = Taro.getFileSystemManager();
  282. fs.readFile({
  283. filePath: tmpPath,
  284. encoding: encoding,
  285. success: (res) => {
  286. const fileContent = res.data as T;
  287. // console.log(
  288. // "%c [ fileContent ]: ",
  289. // "color: #bf2c9f; background: pink; font-size: 13px;",
  290. // fileContent
  291. // );
  292. resolve(fileContent);
  293. },
  294. fail: (err) => {
  295. console.error("读取文件失败", err);
  296. reject(err);
  297. },
  298. });
  299. });
  300. };
  301. export const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
  302. export function generateRandomId(): string {
  303. const timestamp = Date.now().toString(36); // 当前时间戳转换为 base-36 字符串
  304. const randomNum = Math.random().toString(36).substring(2, 8); // 生成随机数并转换为 base-36 字符串
  305. return `${timestamp}-${randomNum}`; // 组合时间戳和随机数
  306. }
  307. /**
  308. * 生成符合 RFC 4122 版本 4 的 UUID
  309. * @returns 格式为 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' 的 UUID 字符串
  310. */
  311. export function generateUUID(): string {
  312. return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
  313. const r = (Math.random() * 16) | 0;
  314. const v = c === 'x' ? r : (r & 0x3) | 0x8;
  315. return v.toString(16);
  316. });
  317. }
  318. export const pickNonEmpty = <T extends object>(obj: T): Partial<T> => {
  319. return Object.fromEntries(
  320. Object.entries(obj).filter(([_, value]) =>
  321. value !== "" && value != null
  322. )
  323. ) as Partial<T>;
  324. };
  325. export const getLoginId = () => {
  326. return Taro.getStorageSync(LOGIN_ID_STORAGE_KEY) // 打开小程序时创建 login_uuid
  327. }
  328. export const isSuccess = (status: number)=> {
  329. return status >= 200 && status < 300
  330. }
  331. /**
  332. * 文件大小单位类型
  333. */
  334. export type FileSizeUnit = 'bit' | 'B' | 'KB' | 'MB' | 'GB' | 'TB';
  335. /**
  336. * 文件大小转换配置
  337. */
  338. export interface FileSizeOptions {
  339. /** 保留小数位数,默认2位 */
  340. precision?: number;
  341. /** 是否使用二进制单位(1024进制),默认为true */
  342. binary?: boolean;
  343. /** 强制转换为指定单位,不自动选择 */
  344. unit?: FileSizeUnit;
  345. /** 是否添加单位后缀,默认为true */
  346. withUnit?: boolean;
  347. }
  348. /**
  349. * 文件大小转换工具
  350. * @param size 文件大小(bit)
  351. * @param options 转换配置
  352. * @returns 转换后的文件大小字符串或数字
  353. */
  354. export function convertFileSize(size: number, options: FileSizeOptions = {}): string | number {
  355. const {
  356. precision = 2,
  357. binary = true,
  358. unit,
  359. withUnit = true
  360. } = options;
  361. // 检查输入是否为有效数字
  362. if (isNaN(size) || !isFinite(size)) {
  363. return withUnit ? 'Invalid size' : NaN;
  364. }
  365. // 处理零值
  366. if (size === 0) {
  367. return withUnit ? `0${unit || 'B'}` : 0;
  368. }
  369. // 计算转换基数
  370. const base = binary ? 1024 : 1000;
  371. // 定义单位列表
  372. const units: FileSizeUnit[] = ['bit', 'B', 'KB', 'MB', 'GB', 'TB'];
  373. // 如果指定了单位,直接转换
  374. if (unit) {
  375. const targetIndex = units.indexOf(unit);
  376. // 特殊处理bit到B的转换(1字节=8比特)
  377. if (unit === 'B') {
  378. size = size / 8;
  379. } else if (unit === 'bit') {
  380. // 如果目标是bit,直接返回原始值
  381. return withUnit ? `${size} bit` : size;
  382. }
  383. // 计算从B到目标单位的转换
  384. if (targetIndex > 1) {
  385. size = size / 8 / Math.pow(base, targetIndex - 1);
  386. }
  387. const result = Number(size.toFixed(precision));
  388. return withUnit ? `${result} ${unit}` : result;
  389. }
  390. // 特殊处理bit和B
  391. if (size < 8) {
  392. return withUnit ? `${size} bit` : size;
  393. }
  394. if (size < base * 8) {
  395. const result = Number((size / 8).toFixed(precision));
  396. return withUnit ? `${result} B` : result;
  397. }
  398. // 自动选择合适的单位
  399. let unitIndex = 2; // 从KB开始
  400. let convertedSize = size / 8; // 先转换为字节
  401. while (convertedSize >= base && unitIndex < units.length - 1) {
  402. convertedSize /= base;
  403. unitIndex++;
  404. }
  405. const result = Number(convertedSize.toFixed(precision));
  406. return withUnit ? `${result} ${units[unitIndex]}` : result;
  407. }
  408. //
  409. export const pxToRpx = (px: number) => {
  410. const systemInfo = Taro.getSystemInfoSync()
  411. return (750 / systemInfo.windowWidth) * px
  412. }
  413. /**
  414. * 为图片URL添加阿里云OSS处理参数
  415. * 如果URL中不包含?x-oss-process=,则添加?x-oss-process=image/quality,Q_60/format,jpg
  416. * 如果URL中已包含其他查询参数,则追加&x-oss-process=image/quality,Q_60/format,jpg
  417. * @param url 原始图片URL
  418. * @returns 处理后的图片URL
  419. */
  420. export function addOssProcessLowQualityParam(url: string): string {
  421. // 检查URL是否有效
  422. if (!url || typeof url !== 'string') {
  423. return url;
  424. }
  425. // 检查是否已经包含OSS处理参数
  426. if (url.includes('x-oss-process=')) {
  427. return url;
  428. }
  429. // 分离URL和查询参数
  430. const [baseUrl, existingQuery] = url.split('?');
  431. // 构建新的查询参数
  432. const ossParam = 'x-oss-process=image/quality,Q_60/format,jpg';
  433. const newQuery = existingQuery
  434. ? `${existingQuery}&${ossParam}` // 已有查询参数,追加OSS参数
  435. : ossParam; // 没有查询参数,直接使用OSS参数
  436. // 返回处理后的URL
  437. return `${baseUrl}?${newQuery}`;
  438. }
  439. // 使用示例
  440. // const exampleUrl1 = "https://example.com/image.jpg";
  441. // const exampleUrl2 = "https://example.com/image.png?width=200&height=200";
  442. // const exampleUrl3 = "https://example.com/image.webp?x-oss-process=image/resize,w_300";
  443. // console.log(addOssProcessParam(exampleUrl1));
  444. // // 输出: https://example.com/image.jpg?x-oss-process=image/quality,Q_60/format,jpg
  445. // console.log(addOssProcessParam(exampleUrl2));
  446. // // 输出: https://example.com/image.png?width=200&height=200&x-oss-process=image/quality,Q_60/format,jpg
  447. // console.log(addOssProcessParam(exampleUrl3));
  448. // // 输出: https://example.com/image.webp?x-oss-process=image/resize,w_300 (保持不变,因为已包含OSS参数)
  449. export function restrictedPage() {
  450. // 方式一:不定义分享相关函数,默认就是禁止分享
  451. // 方式二:显式返回不允许分享(两种分享方式都要处理)
  452. useEffect(() => {
  453. // 禁止单页分享
  454. Taro.showShareMenu({
  455. withShareTicket: false,
  456. menus: [] // 不提供任何分享菜单
  457. })
  458. // 禁止分享到朋友圈
  459. Taro.hideShareMenu({
  460. menus: ['shareAppMessage', 'shareTimeline']
  461. })
  462. }, [])
  463. }