This commit is contained in:
oneao committed 2026-03-23 17:29:20 +08:00
1 parent 62550b8182
commit 3e172a1d53
26 files changed
+4820 -413

No files matched your search

@@ -33,3 +33,77 @@ export function diffObject(newData, oldData, primaryKeys = [], excludeKeys = [])
return { isChanged, result };
}
/* 比对并分类数据变更
* @param {Array} currentData 变更后的数据数组
* @param {Array} originalData 原始数据数组
* @param {String} primaryKey 主键字段名
* @param {Array} ignoreFields 过滤字段(可传可不传)
* @returns {Object} 包含分类结果的对象 { insertdata: [], updatedata: [], deletedata: [], hasChanges: Boolean }
*/
export function compareAndClassifyData(currentData, originalData, primaryKey) {
const result = {
insertdata: [],
updatedata: [],
deletedata: [],
hasChanges: false, // 默认无变动
};
// 1. 找出新增的数据
result.insertdata = currentData.filter(
(row) =>
!row[primaryKey] ||
!originalData.some((originalRow) => originalRow[primaryKey] === row[primaryKey]),
);
// 2. 找出修改的数据
result.updatedata = currentData.reduce((acc, row) => {
const rowId = row[primaryKey];
if (!rowId) return acc;
const originalRow = originalData.find((r) => r[primaryKey] === rowId);
if (!originalRow) return acc;
const changedFields = {};
Object.keys(row).forEach((key) => {
if (key !== primaryKey && !key.startsWith("v_")) {
const oldVal = originalRow[key];
const newVal = row[key];
const normalize = (v) => (v === null || v === undefined ? "" : v);
if (normalize(newVal) !== normalize(oldVal)) {
// ✅ 避免 undefined 被丢弃
changedFields[key] = newVal ?? "";
}
}
});
// Object.keys(row).forEach((key) => {
// if (key !== primaryKey && !isEqual(row[key], originalRow[key])) {
// if (!key.startsWith('v_')) {
// changedFields[key] = row[key]
// }
// }
// })
if (Object.keys(changedFields).length > 0) {
acc.push({
[primaryKey]: rowId,
...changedFields,
});
}
return acc;
}, []);
// 3. 找出删除的数据
result.deletedata = originalData.filter(
(originalRow) => !currentData.some((row) => row[primaryKey] === originalRow[primaryKey]),
);
// 4. 是否有任何变动
result.hasChanges =
result.insertdata.length > 0 || result.updatedata.length > 0 || result.deletedata.length > 0;
return result;
}
@@ -0,0 +1,366 @@
import { getAliyunKeyApi } from "@/request/api";
/**
* 选择并压缩图片
*/
export function uploadImage(options = {}) {
const { quality = 0.9, maxSize = 512000 } = options
return new Promise((resolve, reject) => {
if (typeof document === 'undefined') {
reject(new Error('Require browser environment'))
return
}
const input = document.createElement('input')
input.type = 'file'
input.accept = 'image/*'
input.style.display = 'none'
input.onchange = async (e) => {
const file = e.target.files?.[0]
if (!file) {
reject(new Error('No file selected'))
return
}
try {
if (!file.type.startsWith('image/')) {
throw new Error('Invalid image file')
}
// 创建一个临时URL来加载图片
const url = URL.createObjectURL(file)
// 调用 compressImage 进行压缩
const compressResult = await compressImage(url, quality, maxSize)
// 释放临时URL
URL.revokeObjectURL(url)
resolve({
blob: compressResult.blob,
url: compressResult.url,
fileName: file.name,
fileExt: getFileExtension(file.name),
fileSize: file.size,
})
}
catch (err) {
reject(err)
}
finally {
document.body.removeChild(input)
}
}
document.body.appendChild(input)
input.click()
})
}
/**
* 选择视频
*/
export function uploadVideo() {
return new Promise((resolve, reject) => {
if (typeof document === 'undefined') {
reject(new Error('Require browser environment'))
return
}
const input = document.createElement('input')
input.type = 'file'
input.accept = 'video/*'
input.style.display = 'none'
input.onchange = (e) => {
const file = e.target.files?.[0]
if (!file) {
reject(new Error('No file selected'))
return
}
if (!file.type.startsWith('video/')) {
reject(new Error('Invalid video file'))
return
}
resolve({
blob: file,
url: URL.createObjectURL(file),
fileName: file.name,
fileExt: getFileExtension(file.name),
fileSize: file.size,
})
document.body.removeChild(input)
}
document.body.appendChild(input)
input.click()
})
}
// 压缩图片
export async function compressImage(url, quality = 0.9, maxSize = 512000) {
return new Promise((resolve, reject) => {
const img = new Image()
img.src = url
img.onload = async () => {
try {
// 获取原图大小
const imgSize = await fetch(url)
.then(res => res.blob())
.then(blob => blob.size)
if (imgSize <= maxSize) {
return resolve({ url, size: imgSize, blob: await fetch(url).then(res => res.blob()) })
}
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
if (!ctx)
return reject(new Error('无法获取canvas上下文'))
let targetWidth = img.width
let targetHeight = img.height
// 最大边长限制
const maxEdge = 2000
if (targetWidth > maxEdge || targetHeight > maxEdge) {
if (targetWidth > targetHeight) {
targetHeight = (maxEdge * targetHeight) / targetWidth
targetWidth = maxEdge
}
else {
targetWidth = (maxEdge * targetWidth) / targetHeight
targetHeight = maxEdge
}
}
canvas.width = targetWidth
canvas.height = targetHeight
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.drawImage(img, 0, 0, targetWidth, targetHeight)
const getBlob = q =>
new Promise((resolve, reject) => {
canvas.toBlob(
blob => (blob ? resolve(blob) : reject(new Error('Blob转换失败'))),
'image/jpeg',
q,
)
})
let blob = await getBlob(quality)
// 调整质量直到接近最大文件大小
let minQ = 0.5
let maxQ = quality
let attempts = 0
while (blob.size > maxSize && maxQ - minQ > 0.01 && attempts < 5) {
const midQ = (minQ + maxQ) / 2
blob = await getBlob(midQ)
if (blob.size > maxSize) {
maxQ = midQ
}
else {
minQ = midQ
}
attempts++
}
resolve({ blob, size: blob.size, url: URL.createObjectURL(blob) })
}
catch (err) {
reject(err)
}
}
img.onerror = () => reject(new Error('图片加载失败'))
})
}
export function formatFileSize(size) {
if (size < 1024)
return `${size} B`
else if (size < 1024 * 1024)
return `${(size / 1024).toFixed(2)} KB`
else if (size < 1024 * 1024 * 1024)
return `${(size / (1024 * 1024)).toFixed(2)} MB`
else return `${(size / (1024 * 1024 * 1024)).toFixed(2)} GB`
}
// 获取文件扩展名并进行分类
export function getFileExtension(fileName) {
// 获取文件扩展名
const fileExt = fileName.includes('.')
? fileName.slice(fileName.lastIndexOf('.') + 1).toLowerCase() // 获取扩展名并转为小写
: ''
// 如果是office类文件,将其归类到 "office"
if (['docx', 'docm', 'xls', 'xlsx'].includes(fileExt)) {
return 'office'
}
// 返回扩展名
return fileExt
}
// 上传到阿里云
// blob fileName fileSize xh moduleid father cateid
export function uploadFile(blob, fileName, fileExt, fileSize, xh, moduleid, father, cateid) {
return new Promise((resolve, reject) => {
getAliyunKeyApi()
.then(async (res) => {
try {
const aliyunKey = res.data
const formData = new FormData()
let uniqueName = `${Date.now()}_${Math.random()
.toString(36)
.slice(2)}`
if (fileExt) {
uniqueName += `.${fileExt}`
}
formData.append('key', `${aliyunKey.dir}${uniqueName}`)
formData.append('OSSAccessKeyId', aliyunKey.accessid)
formData.append('policy', aliyunKey.policy)
formData.append('Signature', aliyunKey.signature)
formData.append('file', blob, uniqueName)
const xhr = new XMLHttpRequest()
xhr.open('POST', aliyunKey.host, true)
xhr.onload = async () => {
if (xhr.status === 200 || xhr.status === 204) {
const allFilePath = `${aliyunKey.host}/${aliyunKey.dir}${uniqueName}`
const insertRes = await insertFileApi({
father,
mx_moduleid: moduleid,
mx_xh: xh,
mx_filename: fileName,
mx_mapfilename: allFilePath,
mx_filesize: fileSize,
mx_fileext: fileExt,
mx_cate_id: cateid || '',
})
if (insertRes.success) {
resolve({
father,
mx_moduleid: moduleid,
mx_xh: xh,
mx_filename: fileName,
mx_mapfilename: allFilePath,
mx_filesize: fileSize,
mx_fileext: fileExt,
mx_cate_id: cateid || '',
})
}
else {
reject(new Error(insertRes.data.message))
}
}
else {
ElMessage.error('上传失败,请稍后再试')
reject(new Error(`OSS 上传失败: ${xhr.responseText}`))
}
}
xhr.onerror = () => {
ElMessage.error('上传失败,请稍后再试')
reject(new Error('OSS 上传异常'))
}
xhr.send(formData)
}
catch (err) {
ElMessage.error('上传失败,请稍后再试')
reject(err)
}
})
.catch(reject)
})
}
export function getAliyunFileBlob(url) {
return new Promise((resolve, reject) => {
// 创建一个独立的异步函数来处理 fetch 请求
async function fetchFile() {
try {
const response = await fetch(url, { mode: 'cors' })
// 检查响应状态
if (!response.ok) {
ElMessage.error(`下载失败:${response.status}`)
reject(new Error(`下载失败:${response.status}`))
return
}
// 获取文件内容
const blob = await response.blob()
// 返回blob
resolve(blob)
}
catch (error) {
// 捕获网络请求失败或其他错误
ElMessage.error('下载文件时发生错误')
reject(error)
}
}
// 调用异步函数
fetchFile()
})
}
export function downloadZip(
files,
fileName = '文件合集',
callback,
) {
const zip = new JSZip()
const addedFiles = new Set() // 用来存储已添加的文件名
let result = files.map((item) => {
let promise = getAliyunFileBlob(item.mx_mapfilename).then((res) => {
// 处理文件名重复的情况
let uniqueFileName = getUniqueFileName(item.mx_filename, addedFiles)
zip.file(uniqueFileName, res, { binary: true })
addedFiles.add(uniqueFileName) // 添加文件名到已添加的集合中
})
return promise
})
Promise.all(result).then(() => {
zip.generateAsync({ type: 'blob' }).then((res) => {
callback && callback()
saveAs(res, `${fileName}.zip`)
})
}).catch(() => {
callback && callback()
})
}
// 处理文件名重复的函数
function getUniqueFileName(fileName, addedFiles) {
let uniqueFileName = fileName
let counter = 1
// 如果文件名已经存在,则在文件名后加上 (1), (2), ...
while (addedFiles.has(uniqueFileName)) {
const fileNameWithoutExtension = uniqueFileName.replace(/\.[^/.]+$/, '')
const extension = uniqueFileName.slice(uniqueFileName.lastIndexOf('.'))
uniqueFileName = `${fileNameWithoutExtension} (${counter})${extension}`
counter++
}
return uniqueFileName
}