const isEmail = (email) => { // let reg = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,})$/ let reg = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/ let result = reg.test(email) return result } function getUniversalZoomLevel() { // 现代浏览器方案 if (window.visualViewport) { return window.visualViewport.scale } // 备用方案1 if (window.devicePixelRatio) { return window.devicePixelRatio } // 备用方案2(不精确) return window.outerWidth / window.innerWidth } const getUploadUrl = () => { let url = import.meta.env.VITE_APP_BASE_URL || '' // let url = "http://18.167.251.121:10086" return url } const getMinioUrl = (url) => { if (!isValidMinioUrl(url)) return '' if (isUrl(url)) { const { pathname } = new URL(url) const result = pathname.slice(1) return result } return '' } function isValidMinioUrl(url) { return url.includes('www.minio-api.aida.com.hk') // 关键特征检测 } function isUrl(str) { try { new URL(str) return true } catch (e) { return false } } const rgbaToHex = (rgba) => { // rgba转16进制 let hex = '#' rgba.forEach((i, index) => { if (index == 3) { hex += Math.round(i * 255).toString(16) } else { hex += Number(i).toString(16).padStart(2, '0') } }) return hex } function base64ToFile(urlData, name) { let arr = urlData.split(',') let mime = arr[0].match(/:(.*?);/)[1] let bstr = atob(arr[1]) let n = bstr.length let u8arr = new Uint8Array(n) while (n--) { u8arr[n] = bstr.charCodeAt(n) } return new File([u8arr], name, { type: mime }) } function dataURLtoBlob(dataurl) { //吧data url转为blob对象 var arr = dataurl.split(',') var mime = arr[0].match(/:(.*?);/)[1] var bstr = atob(arr[1]) var n = bstr.length var u8arr = new Uint8Array(n) while (n--) { u8arr[n] = bstr.charCodeAt(n) } return new Blob([u8arr], { type: mime }) } function blobToFile(blob, fileName) { //给blob文件设置名字和日期 blob.lastModifiedDate = new Date() blob.name = fileName return blob } //下载图片 function downloadIamge(imgsrc, name) { // 下载图片地址和图片名 var image = new Image() // 解决跨域 Canvas 污染问题 image.setAttribute('crossOrigin', 'anonymous') image.onload = function () { var canvas = document.createElement('canvas') canvas.width = image.width canvas.height = image.height var context = canvas.getContext('2d') context.drawImage(image, 0, 0, image.width, image.height) var url = canvas.toDataURL('image/png') // 得到图片的base64编码数据 var a = document.createElement('a') // 生成一个a元素 var event = new MouseEvent('click') // 创建一个单击事件 a.download = name || 'generate' // 设置图片名称 a.href = url // 将生成的URL设置为a.href属性 a.target = '_blank' a.dispatchEvent(event) // 触发a的单击事件 image.remove() } image.src = imgsrc } async function downloadVideoWithFetch(url, filename) { try { const response = await fetch(url) const blob = await response.blob() const blobUrl = URL.createObjectURL(blob) const a = document.createElement('a') a.href = blobUrl a.download = filename || 'video.mp4' document.body.appendChild(a) a.click() // 清理 setTimeout(() => { document.body.removeChild(a) URL.revokeObjectURL(blobUrl) }, 100) } catch (error) { console.error('下载失败:', error) } } function dataURLtoFile(dataurl, filename) { //吧url转为文件对象,指定文件名称 var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1], bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n) while (n--) { u8arr[n] = bstr.charCodeAt(n) } var blob = dataURLtoBlob(dataurl) return blobToFile(blob, filename) } const base64toFile = (dataurl, filename = 'file') => { //转换base64 let arr = dataurl.split(',') let mime = arr[0].match(/:(.*?);/)[1] let suffix = mime.split('/')[1] let bstr = atob(arr[1]) let n = bstr.length let u8arr = new Uint8Array(n) while (n--) { u8arr[n] = bstr.charCodeAt(n) } return new File([u8arr], `${filename}.${suffix}`, { type: mime }) } const UrlToFile = async (url, imageName) => { const response = await fetch(url) const blob = await response.blob() return new File([blob], imageName, { type: 'image/png' }) } function rgbToHsv([R, G, B]) { //根据rgb获取hsv R /= 255 G /= 255 B /= 255 const max = Math.max(R, G, B) const min = Math.min(R, G, B) const delta = max - min var H, S, V if (delta === 0) { H = 0 } else if (max === R) { H = ((G - B) / delta) % 6 } else if (max === G) { H = (B - R) / delta + 2 } else { // max === B H = (R - G) / delta + 4 } H = Math.round(H * 60) // 范围为 0-360 if (H < 0) { H = 360 + H } if (max === 0) { S = 0 } else { S = delta / max } S = Math.round(S * 100) // 范围为 0-100 V = Math.round(max * 100) // 范围为 0-100 return [H, S, V] } const formatTime = (timestamp, fmt) => { //吧时间戳转为YYYY-MM-DD hh:mm:ss格式 // date = new Date(), fmt = 'MM/dd/yyyy'; let date = new Date() date.setTime(timestamp * 1000) if (!fmt) { formatRule ? (fmt = formatRule) : (fmt = 'YYYY-MM-DD hh:mm:ss') } // console.log(formatRule) let o = { 'M+': date.getMonth() + 1, // 月份 'D+': date.getDate(), // 日 'h+': date.getHours(), // 小时 'm+': date.getMinutes(), // 分 's+': date.getSeconds(), // 秒 'q+': Math.floor((date.getMonth() + 3) / 3), // 季度 'S+': date.getMilliseconds(), // 毫秒 a: date.getHours() > 12 ? 'PM' : 'AM' // 上午还是下午 } if (/(Y+)/.test(fmt)) { fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)) } if (/(a)/.test(fmt) && o['h+'] > 12) { o['h+'] = o['h+'] - 12 } for (let k in o) { if (new RegExp('(' + k + ')').test(fmt)) { fmt = fmt.replace( RegExp.$1, RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length) ) } } return fmt } const isMoible = () => { //判断是否是移动端 let is_mobile = navigator.userAgent .toLowerCase() .match( /(ipad|ipod|iphone|android|coolpad|mmp|smartphone|midp|wap|xoom|symbian|j2me|blackberry|wince)/i ) != null // alert(navigator.userAgent.toLowerCase()) var isiPad = navigator.maxTouchPoints && navigator.maxTouchPoints > 1 // if (is_mobile) { // return true//判断是否在正则内 // } else if(window.matchMedia("(pointer:fine)").matches){ // return false//判断是否支持鼠标 // }else{ // isiPad//判断触摸点 // } if (is_mobile) { return true //判断是否在正则内 } else { return isiPad //判断触摸点 } } let setPubDate = (date, t) => { const timestamp = new Date(date) const now = new Date() // 计算时间差(以毫秒为单位) const differenceMs = now - timestamp const seconds = Math.floor(differenceMs / 1000) const minutes = Math.floor(seconds / 60) const hours = Math.floor(minutes / 60) const days = Math.floor(hours / 24) const weeks = Math.floor(days / 7) const months = Math.floor(days / 30) const years = Math.floor(days / 365) // 根据时间差的大小返回不同的描述 if (years > 0) { return `${years} ${t('newScaleImage.yearsAgo')}` } else if (months > 0) { return `${months} ${t('newScaleImage.monthsAgo')}` } else if (weeks > 0) { return `${weeks} ${t('newScaleImage.WeeksAgo')}` } else if (days > 0) { return `${t('newScaleImage.daysAgo')}` } else if (hours > 0) { return `${hours} ${t('newScaleImage.HoursAgo')}` } else if (minutes > 0) { return `${minutes} ${t('newScaleImage.minutesAgo')}` } else { return `${t('newScaleImage.minuteAgo')}` } } function getBrowserInfo() { //获取是什么浏览器 var agent = navigator.userAgent.toLowerCase() var userAgent = navigator.userAgent var regStr_ie = /msie [\d.]+;/gi var regStr_ff = /firefox\/[\d.]+/gi var regStr_chrome = /chrome\/[\d.]+/gi var regStr_saf = /safari\/[\d.]+/gi var isIE = userAgent.indexOf('compatible') > -1 && userAgent.indexOf('MSIE') > -1 //判断是否IE<11浏览器 var isEdge = userAgent.indexOf('Edge') > -1 && !isIE //判断是否IE的Edge浏览器 //IE if (agent.indexOf('msie') > 0) { return agent.match(regStr_ie) } //firefox if (agent.indexOf('firefox') > 0) { return agent.match(regStr_ff) } //Chrome if (agent.indexOf('chrome') > 0) { return agent.match(regStr_chrome) } //Safari if (agent.indexOf('safari') > 0 && agent.indexOf('chrome') < 0) { return agent.match(regStr_saf) } } async function murmur() { //生成唯一标识 ,暂时没有使用 return await new Promise((resolve, reject) => { Fingerprint2.get(function (components) { const values = components.map(function (component, index) { if (index === 0) { //把微信浏览器里UA的wifi或4G等网络替换成空,不然切换网络会ID不一样 return component.value.replace(/\bNetType\/\w+\b/, '') } return component.value }) // 生成最终id murmur let murmur = Fingerprint2.x64hash128(values.join(''), 31) resolve(murmur) }) }) } /** * @description: 计算canvas渐变起始坐标 * @param {number} canvas width * @param {number} canvas height * @param {number} angle 角度 * @return {*} */ function calculateGradientCoordinate(width, height, angle) { if (angle >= 360) angle = angle - 360 if (angle < 0) angle = angle + 360 angle = Math.round(angle) // 当渐变轴垂直于矩形水平边上的两种结果 if (angle === 0) { return { x0: Math.round(width / 2), y0: height, x1: Math.round(width / 2), y1: 0 } } if (angle === 180) { return { x0: Math.round(width / 2), y0: 0, x1: Math.round(width / 2), y1: height } } // 当渐变轴垂直于矩形垂直边上的两种结果 if (angle === 90) { return { x0: 0, y0: Math.round(height / 2), x1: width, y1: Math.round(height / 2) } } if (angle === 270) { return { x0: width, y0: Math.round(height / 2), x1: 0, y1: Math.round(height / 2) } } // 从矩形左下角至右上角的对角线的角度 const alpha = Math.round( (Math.asin(width / Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2))) * 180) / Math.PI ) // 当渐变轴分别于矩形的两条对角线重合情况下的四种结果 if (angle === alpha) { return { x0: 0, y0: height, x1: width, y1: 0 } } if (angle === 180 - alpha) { return { x0: 0, y0: 0, x1: width, y1: height } } if (angle === 180 + alpha) { return { x0: width, y0: 0, x1: 0, y1: height } } if (angle === 360 - alpha) { return { x0: width, y0: height, x1: 0, y1: 0 } } // 以矩形的中点为坐标原点,向上为Y轴正方向,向右为X轴正方向建立直角坐标系 let x0 = 0, y0 = 0, x1 = 0, y1 = 0 // 当渐变轴与矩形的交点落在水平线上 if ( angle < alpha || // 处于第一象限 (angle > 180 - alpha && angle < 180) || // 处于第二象限 (angle > 180 && angle < 180 + alpha) || // 处于第三象限 angle > 360 - alpha // 处于第四象限 ) { // 将角度乘以(PI/180)即可转换为弧度 const radian = (angle * Math.PI) / 180 // 当在第一或第四象限,y是height / 2,否则y是-height / 2 const y = angle < alpha || angle > 360 - alpha ? height / 2 : -height / 2 const x = Math.tan(radian) * y // 当在第一或第二象限,l是width / 2 - x,否则l是-width / 2 - x const l = angle < alpha || (angle > 180 - alpha && angle < 180) ? width / 2 - x : -width / 2 - x const n = Math.pow(Math.sin(radian), 2) * l x1 = x + n y1 = y + n / Math.tan(radian) x0 = -x1 y0 = -y1 } // 当渐变轴与矩形的交点落在垂直线上 if ( (angle > alpha && angle < 90) || // 处于第一象限 (angle > 90 && angle < 180 - alpha) || // 处于第二象限 (angle > 180 + alpha && angle < 270) || // 处于第三象限 (angle > 270 && angle < 360 - alpha) // 处于第四象限 ) { // 将角度乘以(PI/180)即可转换为弧度 const radian = ((90 - angle) * Math.PI) / 180 // 当在第一或第二象限,x是width / 2,否则x是-width / 2 const x = (angle > alpha && angle < 90) || (angle > 90 && angle < 180 - alpha) ? width / 2 : -width / 2 const y = Math.tan(radian) * x // 当在第一或第四象限,l是height / 2 - y,否则l是-height / 2 - y const l = (angle > alpha && angle < 90) || (angle > 270 && angle < 360 - alpha) ? height / 2 - y : -height / 2 - y const n = Math.pow(Math.sin(radian), 2) * l x1 = x + n / Math.tan(radian) y1 = y + n x0 = -x1 y0 = -y1 } // 坐标系更改为canvas标准,Y轴向下为正方向 x0 = Math.round(x0 + width / 2) y0 = Math.round(height / 2 - y0) x1 = Math.round(x1 + width / 2) y1 = Math.round(height / 2 - y1) return { x0, y0, x1, y1 } } const setGradual = (colorObj, colorWidth, colorHeight) => { return new Promise((resolve, reject) => { let width = colorWidth || 320 let height = colorHeight || 700 const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') canvas.width = width canvas.height = height let { x0, y0, x1, y1 } = calculateGradientCoordinate(width, height, colorObj.angle) const gradient = ctx.createLinearGradient(x0, y0, x1, y1) colorObj.gradientList.forEach((item) => { let left = item.left.split('%')[0] / 100 let rgba = `rgba(${item.rgba.r},${item.rgba.g},${item.rgba.b},${item.rgba.a})` gradient.addColorStop(left, rgba) // 起始颜色 }) ctx.fillStyle = gradient ctx.fillRect(0, 0, width, height) // let dataURL = canvas.toDataURL('image/jpg'); resolve(canvas.toDataURL('image/jpg')) }) } function segmentImage(markerImage, fullImage, size) { return new Promise((resolve, reject) => { const markerCanvas = document.createElement('canvas') const fullCanvas = document.createElement('canvas') const nullCanvas = document.createElement('canvas') const ctx1 = markerCanvas.getContext('2d') const ctx2 = fullCanvas.getContext('2d') const ctx3 = nullCanvas.getContext('2d') markerCanvas.width = size.width markerCanvas.height = size.height fullCanvas.height = size.height fullCanvas.width = size.width nullCanvas.height = size.height nullCanvas.width = size.width let targetFrontUrl = '' let targetBackUrl = '' const marker = new Image() const full = new Image() marker.crossOrigin = 'anonymous' full.crossOrigin = 'anonymous' marker.onload = () => { ctx1.drawImage(marker, 0, 0, size.width, size.height) full.onload = () => { ctx2.drawImage(full, 0, 0, size.width, size.height) segmentImageItem() } full.src = fullImage } marker.src = markerImage function segmentImageItem() { const markerData = ctx1.getImageData(0, 0, size.width, size.height) const fullData = ctx2.getImageData(0, 0, size.width, size.height) const color1 = { r: 255, g: 0, b: 0 } // 第一个颜色 const color2 = { r: 0, g: 255, b: 0 } // 第二个颜色 const threshold = 100 // 颜色匹配的容差 // const isColorMatch = (r, g, b, color) => // (Math.abs(r - color.r) < threshold) || (Math.abs(0 - color.r) < threshold) && // (Math.abs(g - color.g) < threshold) || (Math.abs(0 - color.g) < threshold) && // (Math.abs(b - color.b) < threshold) || (Math.abs(0 - color.b) < threshold) const isColorMatch = (r, g, b, color) => (color.r >= color.g && r >= g) || (color.r < color.g && r < g) // (Math.abs(b - color.b) < threshold || Math.abs(0 - color.b) < threshold) const output1 = ctx3.createImageData(size.width, size.height) const output2 = ctx3.createImageData(size.width, size.height) for (let i = 0; i < markerData.data.length; i += 4) { const r = markerData.data[i] const g = markerData.data[i + 1] const b = markerData.data[i + 2] let a = markerData.data[i + 3] a > 1 ? (a = 255) : 0 if (r >= g && a > 1) { // 将完整图像中对应的像素复制到第一个输出图像 output1.data[i] = fullData.data[i] output1.data[i + 1] = fullData.data[i + 1] output1.data[i + 2] = fullData.data[i + 2] output1.data[i + 3] = fullData.data[i + 3] // output1.data[i] = 158; // output1.data[i + 1] = 51; // output1.data[i + 2] = 0; // output1.data[i + 3] = 255; // 第二个图像的像素置为透明 output2.data[i] = 0 output2.data[i + 1] = 0 output2.data[i + 2] = 0 output2.data[i + 3] = 0 } else if (r < g && a > 1) { // 将完整图像中对应的像素复制到第二个输出图像 output2.data[i] = fullData.data[i] output2.data[i + 1] = fullData.data[i + 1] output2.data[i + 2] = fullData.data[i + 2] output2.data[i + 3] = fullData.data[i + 3] // output2.data[i] = 158; // output2.data[i + 1] = 51; // output2.data[i + 2] = 0; // output2.data[i + 3] = 255; // 第一个图像的像素置为透明 output1.data[i] = 0 output1.data[i + 1] = 0 output1.data[i + 2] = 0 output1.data[i + 3] = 0 } else { // 两个图像的像素都置为透明 output1.data[i] = 0 output1.data[i + 1] = 0 output1.data[i + 2] = 0 output1.data[i + 3] = 0 output2.data[i] = 0 output2.data[i + 1] = 0 output2.data[i + 2] = 0 output2.data[i + 3] = 0 } } const createImageURL = (imageData) => { const canvas = document.createElement('canvas') canvas.width = size.width canvas.height = size.height const ctx = canvas.getContext('2d') ctx.putImageData(imageData, 0, 0) let data = canvas.toDataURL('image/png') canvas.remove() return data } targetBackUrl = createImageURL(output2) targetFrontUrl = createImageURL(output1) resolve({ targetFrontUrl, targetBackUrl }) markerCanvas.remove() fullCanvas.remove() nullCanvas.remove() } }) } /** * 处理PNG图片:透明度转白色,其他颜色转透明 * @param {string} sketchImage - 原始图片 * @returns {Promise} 处理后的ase64 */ function sketchToMask(sketchImage) { return new Promise((resolve, reject) => { const img = new Image() img.crossOrigin = 'anonymous' img.onload = function () { try { const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') canvas.width = img.width canvas.height = img.height ctx.drawImage(img, 0, 0) const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height) const data = imageData.data for (let i = 0; i < data.length; i += 4) { const r = data[i] const g = data[i + 1] const b = data[i + 2] const a = data[i + 3] if (a > 0) { data[i] = 0 data[i + 1] = 0 data[i + 2] = 0 data[i + 3] = 0 } else { // 完全透明的像素 -> 纯白色 data[i] = 255 data[i + 1] = 255 data[i + 2] = 255 data[i + 3] = 255 } } ctx.putImageData(imageData, 0, 0) const base64 = canvas.toDataURL('image/png') resolve(base64) } catch (error) { reject(error) } } img.onerror = function () { reject(new Error('图片加载失败')) } img.src = sketchImage }) } export { isEmail, getUploadUrl, getUniversalZoomLevel, rgbaToHex, getMinioUrl, base64ToFile, dataURLtoFile, blobToFile, base64toFile, rgbToHsv, formatTime, dataURLtoBlob, isMoible, downloadIamge, downloadVideoWithFetch, getBrowserInfo, setPubDate, murmur, setGradual, calculateGradientCoordinate, segmentImage, UrlToFile, sketchToMask }