Files
gloabl_award_front/src/utils/util.ts
2026-02-06 16:07:16 +08:00

704 lines
20 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}