2026-02-02 13:32:33 +08:00
|
|
|
|
import CryptoJS from 'crypto-js'
|
|
|
|
|
|
|
|
|
|
|
|
function getUniversalZoomLevel() {
|
|
|
|
|
|
// 现代浏览器方案
|
|
|
|
|
|
if (window.visualViewport) {
|
|
|
|
|
|
return window.visualViewport.scale;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 备用方案1
|
|
|
|
|
|
if (window.devicePixelRatio) {
|
|
|
|
|
|
return window.devicePixelRatio;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 备用方案2(不精确)
|
|
|
|
|
|
return window.outerWidth / window.innerWidth;
|
|
|
|
|
|
}
|
|
|
|
|
|
const getMousePosition = (e: any, bor: any) => {
|
|
|
|
|
|
// if(e?.stopPropagation)e.stopPropagation()
|
|
|
|
|
|
// if(e?.preventDefault)e.preventDefault();
|
|
|
|
|
|
let event: any
|
|
|
|
|
|
if (bor) {
|
|
|
|
|
|
const touch = e.changedTouches[0] as any;
|
|
|
|
|
|
event = {
|
|
|
|
|
|
offsetX: touch.clientX - e.target.getBoundingClientRect().left,
|
|
|
|
|
|
offsetY: touch.clientY - e.target.getBoundingClientRect().top,
|
|
|
|
|
|
clientX: touch.clientX,
|
|
|
|
|
|
clientY: touch.clientY,
|
|
|
|
|
|
screenX: touch.screenX,
|
|
|
|
|
|
screenY: touch.screenY,
|
|
|
|
|
|
target: e.target,
|
|
|
|
|
|
}
|
|
|
|
|
|
// if(dom){
|
|
|
|
|
|
// event.offsetX = touch.clientX - dom.getBoundingClientRect().left
|
|
|
|
|
|
// event.offsetY = touch.clientY - dom.getBoundingClientRect().top
|
|
|
|
|
|
// }
|
|
|
|
|
|
} else {
|
|
|
|
|
|
event = {
|
|
|
|
|
|
offsetX: e.offsetX,
|
|
|
|
|
|
offsetY: e.offsetY,
|
|
|
|
|
|
clientX: e.clientX,
|
|
|
|
|
|
clientY: e.clientY,
|
|
|
|
|
|
screenX: e.screenX,
|
|
|
|
|
|
screenY: e.screenY,
|
|
|
|
|
|
target: e.target,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return event
|
|
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成UUID v4
|
|
|
|
|
|
* @returns 返回一个标准的UUID v4字符串,格式:xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function generateUUID(): string {
|
|
|
|
|
|
// 优先使用现代浏览器的crypto.randomUUID()方法
|
|
|
|
|
|
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
|
|
|
|
|
return crypto.randomUUID()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 备用方案:手动生成UUID v4
|
|
|
|
|
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
|
|
|
|
|
const r = Math.random() * 16 | 0
|
|
|
|
|
|
const v = c === 'x' ? r : (r & 0x3 | 0x8)
|
|
|
|
|
|
return v.toString(16)
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export {
|
|
|
|
|
|
getUniversalZoomLevel,
|
|
|
|
|
|
getMousePosition,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** 时间格式化-自定义格式
|
|
|
|
|
|
* @param value 时间对象|时间戳|时间字符串
|
|
|
|
|
|
* @param format 格式化字符串,默认值为 'yyyy-MM-dd HH:mm:ss'
|
|
|
|
|
|
* @returns 格式化后的时间字符串
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function FormatDate(value: Date | number | string, format: string = 'yyyy-MM-dd HH:mm:ss') {
|
|
|
|
|
|
const date = new Date(value);
|
|
|
|
|
|
const yyyy = String(date.getFullYear());
|
|
|
|
|
|
const yy = String(date.getFullYear()).slice(-2);
|
|
|
|
|
|
const MM = String(date.getMonth() + 1).padStart(2, '0');
|
|
|
|
|
|
const M = String(date.getMonth() + 1);
|
|
|
|
|
|
const dd = String(date.getDate()).padStart(2, '0');
|
|
|
|
|
|
const d = String(date.getDate());
|
|
|
|
|
|
const HH = String(date.getHours()).padStart(2, '0');
|
|
|
|
|
|
const H = String(date.getHours());
|
|
|
|
|
|
const mm = String(date.getMinutes()).padStart(2, '0');
|
|
|
|
|
|
const m = String(date.getMinutes());
|
|
|
|
|
|
const ss = String(date.getSeconds()).padStart(2, '0');
|
|
|
|
|
|
const s = String(date.getSeconds());
|
|
|
|
|
|
const str = format.replaceAll('yyyy', yyyy)
|
|
|
|
|
|
.replaceAll('yy', yy)
|
|
|
|
|
|
.replaceAll('MM', MM)
|
|
|
|
|
|
.replaceAll('M', M)
|
|
|
|
|
|
.replaceAll('dd', dd)
|
|
|
|
|
|
.replaceAll('d', d)
|
|
|
|
|
|
.replaceAll('HH', HH)
|
|
|
|
|
|
.replaceAll('H', H)
|
|
|
|
|
|
.replaceAll('mm', mm)
|
|
|
|
|
|
.replaceAll('m', m)
|
|
|
|
|
|
.replaceAll('ss', ss)
|
|
|
|
|
|
.replaceAll('s', s);
|
|
|
|
|
|
return str;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-04 10:01:50 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 倒计时
|
|
|
|
|
|
* @param time 倒计时时间,单位秒
|
|
|
|
|
|
* @returns 倒计时字符串,格式为 mm:ss
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function CountDown(time: number) {
|
|
|
|
|
|
const mm = String(Math.floor(time / 60)).padStart(2, '0');
|
|
|
|
|
|
const ss = String(time % 60).padStart(2, '0');
|
|
|
|
|
|
return `${mm}:${ss}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-02-02 13:32:33 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 下载图片
|
|
|
|
|
|
* @param list 图片列表
|
|
|
|
|
|
* @param onProgress 下载进度回调
|
|
|
|
|
|
* @param onError 下载错误回调
|
|
|
|
|
|
* @param onSuccess 下载成功回调
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function DownloadImages(list: Array<{ url: string, name?: string }>, onProgress?: (count: number, total: number, item: any) => void, onError?: (count: number, total: number, item: any) => void, onSuccess?: (successCount: number, errCount: number) => void) {
|
|
|
|
|
|
const total = list.length;
|
|
|
|
|
|
let count = 0;
|
|
|
|
|
|
let successCount = 0;
|
|
|
|
|
|
let errCount = 0;
|
|
|
|
|
|
for (let i = 0; i < list.length; i++) {
|
|
|
|
|
|
await new Promise((resolve) => {
|
|
|
|
|
|
const xhr = new XMLHttpRequest();
|
|
|
|
|
|
xhr.open("GET", list[i].url);
|
|
|
|
|
|
xhr.responseType = "blob"
|
|
|
|
|
|
xhr.onload = function () {
|
|
|
|
|
|
count++;
|
|
|
|
|
|
if (this.status === 200) {
|
|
|
|
|
|
const blob = this.response;
|
|
|
|
|
|
const a = document.createElement('a');
|
|
|
|
|
|
a.href = URL.createObjectURL(blob);
|
|
|
|
|
|
a.download = list[i].name || list[i].url.split('/').pop().split('?').shift();
|
|
|
|
|
|
a.click();
|
|
|
|
|
|
successCount++;
|
|
|
|
|
|
typeof onProgress === "function" && onProgress(count, total, list[i]);
|
|
|
|
|
|
resolve(blob);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
errCount++;
|
|
|
|
|
|
typeof onError === "function" && onError(count, total, list[i]);
|
|
|
|
|
|
resolve(true);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
xhr.onerror = function () {
|
|
|
|
|
|
count++;
|
|
|
|
|
|
errCount++;
|
|
|
|
|
|
typeof onError === "function" && onError(count, total, list[i]);
|
|
|
|
|
|
resolve(true);
|
|
|
|
|
|
};
|
|
|
|
|
|
xhr.send();
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
typeof onSuccess === "function" && onSuccess(successCount, errCount);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* MD5加密密码
|
|
|
|
|
|
* @param password 原始密码
|
|
|
|
|
|
* @returns MD5加密后的密码
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function encryptPassword(password: string): string {
|
|
|
|
|
|
return CryptoJS.MD5(password).toString()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 图片分享到WhatsApp
|
|
|
|
|
|
* @param url 图片URL
|
|
|
|
|
|
* @returns 无
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function shareImageToWhatsapp (url: string){
|
|
|
|
|
|
// 把图片 URL 转为 Blob
|
|
|
|
|
|
const blob = await fetch(url).then((res) => res.blob())
|
|
|
|
|
|
|
|
|
|
|
|
// 创建文件对象
|
|
|
|
|
|
const file = new File([blob], 'image.jpg', { type: 'image/jpeg' })
|
|
|
|
|
|
|
|
|
|
|
|
// 判断浏览器是否支持文件分享
|
|
|
|
|
|
if (navigator.canShare && navigator.canShare({ files: [file] })) {
|
|
|
|
|
|
await navigator.share({
|
|
|
|
|
|
files: [file]
|
|
|
|
|
|
})
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 你可以附加一些自定义文本
|
|
|
|
|
|
const message = 'share image ' + url
|
|
|
|
|
|
|
|
|
|
|
|
// 构造WhatsApp链接
|
|
|
|
|
|
const whatsappLink = `https://api.whatsapp.com/send/?text=${encodeURIComponent(message)}`
|
|
|
|
|
|
window.open(whatsappLink, '_blank')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|