utils.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. /**
  2. * 跳转到指定页面
  3. * @param {String} url
  4. * @param {Boolean} isNeedLogin
  5. * @param {Object} options
  6. * @returns
  7. */
  8. export const navigateTo = async (url, options = {}) => {
  9. // console.log('navigateTo = ' , url)
  10. if (!url) {
  11. return;
  12. }
  13. try {
  14. await uni.navigateTo({
  15. url,
  16. ...options,
  17. });
  18. // console.log('res = ' , res)
  19. // if (!res) {
  20. // uni.reLaunch({
  21. // url,
  22. // })
  23. // }
  24. } catch (error) {
  25. console.log("error = ", error);
  26. uni.reLaunch({
  27. url,
  28. });
  29. //TODO handle the exception
  30. }
  31. };
  32. /**
  33. * 关闭当前页面跳转到指定页面
  34. * @param {String} url
  35. * @param {Boolean} isNeedLogin
  36. * @returns
  37. */
  38. export const redirectTo = async (url) => {
  39. if (!url) {
  40. return;
  41. }
  42. const res = await uni
  43. .redirectTo({
  44. url,
  45. })
  46. .catch(console.log);
  47. if (!res) {
  48. uni.reLaunch({
  49. url,
  50. });
  51. }
  52. };
  53. /**
  54. * 弹窗展示成功信息
  55. * @param {String} title 超过 7 个汉字会截断
  56. * @param {Number} millisecond
  57. */
  58. export const successToast = (title, millisecond = 1500) => {
  59. uni.showToast({
  60. title,
  61. });
  62. return new Promise((resolve) => {
  63. setTimeout(() => {
  64. resolve();
  65. }, millisecond);
  66. });
  67. };
  68. /**
  69. * 弹窗展示错误信息
  70. * @param {String} title
  71. * @param {Number} millisecond
  72. */
  73. export const errorToast = (title, millisecond = 1500) => {
  74. uni.showToast({
  75. title,
  76. icon: "none",
  77. });
  78. return new Promise((resolve) => {
  79. setTimeout(() => {
  80. resolve();
  81. }, millisecond);
  82. });
  83. };
  84. /**
  85. * 弹窗确认操作
  86. * @param {String} content
  87. * @param {String} title
  88. * @param {Object} options
  89. */
  90. export const confirmModal = (content, title = "提示", options) =>
  91. new Promise((resolve, reject) => {
  92. uni.showModal({
  93. ...options,
  94. title,
  95. content,
  96. success: (modal) => {
  97. resolve(modal.confirm);
  98. },
  99. fail: (err) => {
  100. reject(err);
  101. },
  102. });
  103. });
  104. // 日期格式化
  105. export const parseTime = (time, pattern) => {
  106. if (!time) {
  107. return null;
  108. }
  109. const format = pattern || "{y}-{m}-{d} {h}:{i}:{s}";
  110. let date;
  111. if (typeof time === "object") {
  112. date = time;
  113. } else {
  114. if (typeof time === "string" && /^[0-9]+$/.test(time)) {
  115. time = parseInt(time);
  116. } else if (typeof time === "string") {
  117. time = time
  118. .replace(new RegExp(/-/gm), "/")
  119. .replace("T", " ")
  120. .replace(new RegExp(/\.[\d]{3}/gm), "");
  121. }
  122. if (typeof time === "number" && time.toString().length === 10) {
  123. time = time * 1000;
  124. }
  125. date = new Date(time);
  126. }
  127. const formatObj = {
  128. y: date.getFullYear(),
  129. m: date.getMonth() + 1,
  130. d: date.getDate(),
  131. h: date.getHours(),
  132. i: date.getMinutes(),
  133. s: date.getSeconds(),
  134. a: date.getDay(),
  135. };
  136. return format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
  137. let value = formatObj[key];
  138. // Note: getDay() returns 0 on Sunday
  139. if (key === "a") {
  140. return ["日", "一", "二", "三", "四", "五", "六"][value];
  141. }
  142. if (result.length > 0 && value < 10) {
  143. value = "0" + value;
  144. }
  145. return value || 0;
  146. });
  147. };
  148. // 处理 微信小程序 图片 视频 表格自适应
  149. // 处理后的内容(优化图片显示)
  150. export const processedContent = (value) => {
  151. if (!value) return "";
  152. // 处理富文本内容,优化图片自适应
  153. let content = value;
  154. // 为图片添加样式,确保自适应
  155. content = content.replace(/<img([^>]*)>/gi, function (match, attrs) {
  156. // 移除原有的 width 和 height 属性
  157. attrs = attrs.replace(/\s*(width|height)\s*=\s*["'][^"']*["']/gi, "");
  158. // 添加自适应样式
  159. return `<img${attrs} style="max-width: 100%; width: auto; height: auto; display: block; object-fit: contain;" />`;
  160. });
  161. // 为视频添加自适应样式
  162. content = content.replace(/<video([^>]*)>/gi, function (match, attrs) {
  163. attrs = attrs.replace(/\s*(width|height)\s*=\s*["'][^"']*["']/gi, "");
  164. return `<video${attrs} style="max-width: 100%; width: auto; height: auto; display: block; margin: 16rpx auto;" />`;
  165. });
  166. // 处理表格自适应
  167. content = content.replace(
  168. /<table([^>]*)>/gi,
  169. '<table$1 style="width: 100%; border-collapse: collapse; font-size: 24rpx; overflow-x: auto; display: block; white-space: nowrap;" />'
  170. );
  171. return content;
  172. };
  173. /**
  174. * 取富文本内容的第一段文本
  175. * */
  176. export const getFirstParagraph = (htmlString) => {
  177. if (!htmlString || typeof htmlString !== 'string') return '';
  178. // 匹配第一个 <p> 标签(兼容 <p>、<p class="..."> 等)
  179. const pRegex = /<p[^>]*>(.*?)<\/p>/is;
  180. const match = htmlString.match(pRegex);
  181. if (!match || !match[1]) return '';
  182. // 提取到的内容,去除所有标签,保留纯文本
  183. const innerHTML = match[1];
  184. const text = innerHTML.replace(/<[^>]+>/g, '').trim(); // 去除所有标签
  185. return text;
  186. }
  187. // 获取标题
  188. export const getLabel = (val, list) => {
  189. for (let item of list) {
  190. if (item.value === val) {
  191. return item.label || item.text;
  192. }
  193. }
  194. }
  195. /***
  196. * 文件下载功能,自动判断环境,h5环境直接打开文件,小程序环境下载到本地
  197. * @param {String} url
  198. * @returns {Promise}
  199. * @description 下载文件到本地,并打开文件
  200. * */
  201. export const downloadFile = (url) => {
  202. if (!url) {
  203. uni.showToast({ title: "文件地址为空", icon: "none" });
  204. return Promise.reject(new Error("url is required"));
  205. }
  206. const cleanUrl = String(url).split("#")[0].split("?")[0];
  207. const fileNameFromUrl = cleanUrl.split("/").pop() || "";
  208. const ext = (fileNameFromUrl.split(".").pop() || "").toLowerCase();
  209. const isDoc = ["pdf", "docx", "doc", "ppt", "pptx", "xls", "xlsx"].includes(ext);
  210. const isImage = ["png", "jpeg", "jpg"].includes(ext);
  211. const fallbackName = ext ? `download.${ext}` : "download";
  212. const downloadName = fileNameFromUrl || fallbackName;
  213. uni.showLoading({ title: "文件处理中...", mask: true });
  214. // #ifdef H5
  215. return (async () => {
  216. try {
  217. if (isImage) {
  218. window.open(url, "_blank");
  219. return;
  220. }
  221. const response = await fetch(url);
  222. if (!response.ok) throw new Error(`download failed: ${response.status}`);
  223. const blob = await response.blob();
  224. const blobUrl = window.URL.createObjectURL(blob);
  225. const a = document.createElement("a");
  226. a.href = blobUrl;
  227. a.download = downloadName;
  228. a.style.display = "none";
  229. document.body.appendChild(a);
  230. a.click();
  231. document.body.removeChild(a);
  232. window.URL.revokeObjectURL(blobUrl);
  233. uni.showToast({ title: "开始下载", icon: "none" });
  234. } catch (e) {
  235. try {
  236. window.open(url, "_blank");
  237. } catch (_) {}
  238. uni.showToast({ title: "已在新窗口打开", icon: "none" });
  239. } finally {
  240. uni.hideLoading();
  241. }
  242. })();
  243. // #endif
  244. // #ifndef H5
  245. return new Promise((resolve, reject) => {
  246. uni.downloadFile({
  247. url,
  248. success: (res) => {
  249. if (res.statusCode !== 200 || !res.tempFilePath) {
  250. uni.hideLoading();
  251. uni.showToast({ title: "下载失败", icon: "none" });
  252. reject(res);
  253. return;
  254. }
  255. const tempFilePath = res.tempFilePath;
  256. if (isImage) {
  257. uni.previewImage({ urls: [tempFilePath] });
  258. if (!uni.saveImageToPhotosAlbum) {
  259. uni.hideLoading();
  260. resolve({ tempFilePath });
  261. return;
  262. }
  263. uni.saveImageToPhotosAlbum({
  264. filePath: tempFilePath,
  265. success: () => {
  266. uni.showToast({ title: "已保存到相册", icon: "none" });
  267. resolve({ tempFilePath });
  268. },
  269. fail: (err) => {
  270. const errMsg = (err && err.errMsg) || "";
  271. if (/auth|permission|authorize|denied/i.test(errMsg)) {
  272. uni.showToast({ title: "请授权保存到相册", icon: "none" });
  273. } else {
  274. uni.showToast({ title: "保存相册失败", icon: "none" });
  275. }
  276. resolve({ tempFilePath, err });
  277. },
  278. complete: () => {
  279. uni.hideLoading();
  280. },
  281. });
  282. return;
  283. }
  284. if (isDoc && uni.openDocument) {
  285. uni.saveFile({
  286. tempFilePath,
  287. success: (saveRes) => {
  288. uni.openDocument({
  289. filePath: saveRes.savedFilePath,
  290. showMenu: true,
  291. success: () => {
  292. resolve({ savedFilePath: saveRes.savedFilePath });
  293. },
  294. fail: (err) => {
  295. uni.hideLoading();
  296. uni.showToast({ title: "文件打开失败", icon: "none" });
  297. reject(err);
  298. },
  299. complete: () => {
  300. uni.hideLoading();
  301. },
  302. });
  303. },
  304. fail: (err) => {
  305. uni.hideLoading();
  306. uni.showToast({ title: "文件保存失败", icon: "none" });
  307. reject(err);
  308. },
  309. });
  310. return;
  311. }
  312. if (uni.saveFile) {
  313. uni.saveFile({
  314. tempFilePath,
  315. success: (saveRes) => {
  316. uni.hideLoading();
  317. uni.showToast({ title: "下载完成", icon: "none" });
  318. resolve({ savedFilePath: saveRes.savedFilePath });
  319. },
  320. fail: () => {
  321. uni.hideLoading();
  322. uni.showToast({ title: "下载完成", icon: "none" });
  323. resolve({ tempFilePath });
  324. },
  325. });
  326. return;
  327. }
  328. uni.hideLoading();
  329. uni.showToast({ title: "下载完成", icon: "none" });
  330. resolve({ tempFilePath });
  331. },
  332. fail: (err) => {
  333. uni.hideLoading();
  334. uni.showToast({ title: "下载失败", icon: "none" });
  335. reject(err);
  336. },
  337. });
  338. });
  339. // #endif
  340. };
  341. /**
  342. * 保留指定位数小数
  343. * @param {string|number} value - 要处理的值
  344. * @param {number} n - 保留的小数位数,默认为2位
  345. * @returns {string} 处理后的值
  346. */
  347. export const retainDecimals = (value, n = 2) => {
  348. // 确保 n 是正整数
  349. const decimalPlaces = Math.max(0, Math.floor(n));
  350. // 转换为字符串处理
  351. const stringValue = String(value);
  352. // 移除非数字和小数点的字符
  353. let cleanValue = stringValue.replace(/[^\d.]/g, "");
  354. // 处理多个小数点的情况,保留第一个小数点
  355. const firstDotIndex = cleanValue.indexOf('.');
  356. if (firstDotIndex !== -1) {
  357. // 保留第一个小数点前的部分和第一个小数点
  358. const beforeDot = cleanValue.substring(0, firstDotIndex + 1);
  359. // 移除第一个小数点后所有的小数点,只保留数字
  360. const afterDot = cleanValue.substring(firstDotIndex + 1).replace(/\./g, '');
  361. // 根据参数 n 限制小数点后的位数
  362. const limitedAfterDot = afterDot.substring(0, decimalPlaces);
  363. // 如果小数点后没有数字,去除小数点
  364. if (limitedAfterDot === '') {
  365. cleanValue = beforeDot.substring(0, firstDotIndex);
  366. } else {
  367. cleanValue = beforeDot + limitedAfterDot;
  368. }
  369. }
  370. return cleanValue;
  371. };
  372. /**
  373. * 移除监听
  374. *
  375. * */
  376. export const onOffLine = (eventName = 'popupChange', callback) => {
  377. uni.$off(eventName, callback)
  378. };