mrr.sj.front/utils/service.js

347 lines
11 KiB
JavaScript
Raw Normal View History

2026-03-24 11:45:13 +08:00
export class ServiceManager {
constructor() {
this.availableSlots = {}
this.serviceDurations = {}
this.blockedTimes = {}
}
// 设置工作日工作时间
setWorkingHours(day, startTime, endTime) {
if (!this.availableSlots[day]) {
this.availableSlots[day] = []
}
this.availableSlots[day].push([startTime, endTime])
}
// 添加服务
addService(serviceName, durationMinutes) {
this.serviceDurations[serviceName] = durationMinutes
}
// 添加不可预约时间
addBlockedTime(day, startTime, endTime) {
if (!this.blockedTimes[day]) {
this.blockedTimes[day] = []
}
this.blockedTimes[day].push([startTime, endTime])
}
// 获取可用时间段
getAvailableSlots(day, serviceName, existingAppointments = []) {
const duration = this.serviceDurations[serviceName]
if (!duration) throw new Error('服务不存在')
const available = this.availableSlots[day] || []
const blocked = this.blockedTimes[day] || []
const allUnavailable = [...existingAppointments, ...blocked]
// 合并重叠的不可用时间段
allUnavailable.sort((a, b) => this.compareTime(a[0], b[0]))
const mergedUnavailable = []
for (const [start, end] of allUnavailable) {
if (mergedUnavailable.length === 0) {
mergedUnavailable.push([start, end])
} else {
const [lastStart, lastEnd] = mergedUnavailable[mergedUnavailable.length - 1]
if (this.compareTime(start, lastEnd) <= 0) {
const newEnd = this.compareTime(end, lastEnd) > 0 ? end : lastEnd
mergedUnavailable[mergedUnavailable.length - 1][1] = newEnd
} else {
mergedUnavailable.push([start, end])
}
}
}
// 计算最终可用时间段
const finalAvailable = []
for (const [workStart, workEnd] of available) {
let currentStart = workStart
for (const [busyStart, busyEnd] of mergedUnavailable) {
if (this.compareTime(busyEnd, currentStart) <= 0) continue
if (this.compareTime(busyStart, workEnd) >= 0) break
if (this.compareTime(currentStart, busyStart) < 0) {
const slotEnd = this.compareTime(busyStart, workEnd) < 0 ? busyStart : workEnd
finalAvailable.push([currentStart, slotEnd])
}
currentStart = this.compareTime(busyEnd, workEnd) < 0 ? busyEnd : workEnd
}
if (this.compareTime(currentStart, workEnd) < 0) {
finalAvailable.push([currentStart, workEnd])
}
}
// 返回足够时长的时段
return finalAvailable.filter(([start, end]) => {
return this.timeDiffInMinutes(start, end) >= duration
})
}
// 时间比较方法
compareTime(time1, time2) {
const [h1, m1] = time1.split(':').map(Number)
const [h2, m2] = time2.split(':').map(Number)
if (h1 !== h2) return h1 - h2
return m1 - m2
}
// 计算时间差(分钟)
timeDiffInMinutes(start, end) {
const [h1, m1] = start.split(':').map(Number)
const [h2, m2] = end.split(':').map(Number)
return (h2 * 60 + m2) - (h1 * 60 + m1)
}
}
//oss压缩图片
export function handleImgSize(url, maxSizekb = 20, minRatioo = 0.01) {
return new Promise((resolve, reject) => {
// 1. 处理URL移除已有的OSS处理参数避免重复叠加
const [originalUrl, existingParams] = url.split('?');
const baseUrl = originalUrl; // 原始图片地址(无处理参数)
// 2. 下载原始图片,获取真实体积
uni.downloadFile({
url: baseUrl,
success: (downloadRes) => {
const tempFilePath = downloadRes.tempFilePath;
if (!tempFilePath) {
const error = new Error("下载失败,未获取到临时路径");
console.error(error);
reject(error);
return;
}
// 3. 读取原始图片体积
uni.getFileInfo({
filePath: tempFilePath,
success: (fileInfoRes) => {
const fileSizeByte = fileInfoRes.size;
const fileSizeKB = fileSizeByte / 1024;
console.log(
`原始体积:${fileSizeKB.toFixed(2)} KB | 限制体积:${maxSizekb} KB`
);
// 4. 若体积已达标直接返回原URL保留原有参数
if (fileSizeKB <= maxSizekb) {
console.log("体积达标,无需压缩");
const finalUrl = existingParams ?
`${baseUrl}?${existingParams}` : baseUrl;
resolve(finalUrl);
return;
}
// 5. 核心修正:根据体积反推合理的尺寸压缩比例
// 原理:体积 ≈ (尺寸比例k)² × 原体积 → k ≈ √(目标体积/原体积)
const targetRatio = maxSizekb / fileSizeKB; // 目标体积占原体积的比例
let sizeRatio = Math.sqrt(targetRatio); // 推导的尺寸压缩比例k
// 6. 限制尺寸比例下限(避免压缩过小导致模糊,可根据需求调整)
const minSizeRatio = minRatioo; // 最小缩放到原尺寸的30%p_30
if (sizeRatio < minSizeRatio) {
sizeRatio = minSizeRatio;
console.warn(`压缩比例过低,强制限制为${minSizeRatio}(避免模糊)`);
}
// 7. 转换为OSS的p参数百分比取整
const percentage = Math.round(sizeRatio * 100);
console.log(`计算压缩参数:尺寸缩放到${percentage}%p_${percentage}`);
// 8. 拼接最终URL保留原有参数叠加压缩参数
const ossParam = `x-oss-process=image/resize,p_${percentage}`;
const finalUrl = existingParams ?
`${baseUrl}?${existingParams}&${ossParam}` // 已有参数时用&拼接
:
`${baseUrl}?${ossParam}`; // 无参数时用?拼接
resolve(finalUrl);
},
fail: (err) => {
console.error("获取文件信息失败:", err);
reject(err);
}
});
},
fail: (err) => {
console.error("下载图片失败:", err);
reject(err);
}
});
});
}
//uniappy压缩图片
// export function handleImgSizeUni(url, maxSizekb = 128, minRatioo = 0.01) {
// return new Promise((resolve, reject) => {
// uni.getSavedFileInfo({
// filePath: url, //仅做示例用,非真正的文件路径
// success: function(fileInfoRes) {
// console.log(11111111,fileInfoRes)
// const fileSizeByte = fileInfoRes.size;
// const fileSizeKB = fileSizeByte / 1024;
// console.log(`原始体积:${fileSizeKB.toFixed(2)} KB`);
// if (fileSizeKB <= maxSizekb) {
// console.log("体积达标,无需压缩");
// resolve(url);
// return;
// }
// // 5. 核心修正:根据体积反推合理的尺寸压缩比例
// // 原理:体积 ≈ (尺寸比例k)² × 原体积 → k ≈ √(目标体积/原体积)
// const targetRatio = maxSizekb / fileSizeKB; // 目标体积占原体积的比例
// let sizeRatio = Math.sqrt(targetRatio); // 推导的尺寸压缩比例k
// // 6. 限制尺寸比例下限(避免压缩过小导致模糊,可根据需求调整)
// const minSizeRatio = minRatioo; // 最小缩放到原尺寸的30%p_30
// if (sizeRatio < minSizeRatio) {
// sizeRatio = minSizeRatio;
// console.warn(`压缩比例过低,强制限制为${minSizeRatio}(避免模糊)`);
// }
// const percentage = (Math.floor(sizeRatio * 10000) / 100) + '%';
// console.log("percentage",percentage);
// uni.compressImage({
// src: url,
// quality: 74,
// width: percentage,
// height: percentage,
// success: res2 => {
// console.log(res2.tempFilePath)
// uni.getSavedFileInfo({
// filePath: res2.tempFilePath, //仅做示例用,非真正的文件路径
// success: function (res3) {
// const fileSizeByte2 = res3.size;
// const fileSizeKB2 = fileSizeByte2 / 1024;
// console.log(`现在体积:${fileSizeKB2.toFixed(2)} KB`);
// }
// });
// resolve(res2.tempFilePath);
// },
// fail: (err) => {
// console.error("图片压缩失败:", err);
// reject(err);
// }
// })
// },
// fail: (err) => {
// console.error("获取文件信息失败:", err);
// reject(err);
// }
// });
// });
// }
export function handleImgSizeUni(url, maxSizekb = 30, minWidth = 20, minHeight = 20) {
console.log(url)
return new Promise((resolve, reject) => {
// 1. 获取图片基本信息(大小、宽高)
Promise.all([
// 获取文件大小
new Promise((res, rej) => {
uni.getFileInfo({
filePath: url,
success: res,
fail: rej
});
}),
// 获取图片宽高
new Promise((res, rej) => {
uni.getImageInfo({
src: url,
success: res,
fail: rej
});
})
]).then(([fileInfo, imgInfo]) => {
console.log(`imgInfo`,imgInfo);
const fileSizeKB = fileInfo.size / 1024;
console.log(`原始体积:${fileSizeKB.toFixed(2)} KB原始尺寸${imgInfo.width}x${imgInfo.height}`);
// 2. 体积达标直接返回
if (fileSizeKB <= maxSizekb) {
console.log("体积达标,无需压缩");
resolve(url);
return;
}
// 3. 压缩参数初始化(优先质量调整)
let quality = 70; // 初始高质量
let { width: originW, height: originH } = imgInfo;
let scale = 1; // 尺寸缩放比例1为不缩放
// 4. 循环调整参数直到体积达标最多尝试5次避免无限循环
const maxAttempts = 10;
let attempts = 0;
let tempFilePath ="";
const compressLoop = () => {
attempts++;
if (attempts > maxAttempts) {
console.warn("达到最大尝试次数,返回当前压缩结果",tempFilePath);
resolve(tempFilePath);
return;
}
// 计算当前缩放后的尺寸(保持比例)
const currentW = Math.max(originW * scale, minWidth); // 不小于最小宽度
const currentH = Math.max(originH * scale, minHeight); // 不小于最小高度
// 执行压缩
uni.compressImage({
src: url,
quality, // 质量参数0-100越高越清晰
width: currentW, // 具体像素值,避免百分比误差
height: currentH,
success: (res) => {
// 检查压缩后体积
uni.getFileInfo({
filePath: res.tempFilePath,
success: (compressedFile) => {
const compressedKB = compressedFile.size / 1024;
console.log(`${attempts}次压缩:质量=${quality},尺寸=${currentW}x${currentH},体积=${compressedKB.toFixed(2)} KB`);
tempFilePath = res.tempFilePath
if (compressedKB <= maxSizekb) {
// 体积达标,返回结果
resolve(res.tempFilePath);
} else {
// 体积不达标,调整参数继续尝试
if (quality > 20) {
// 优先降质量每次降10
quality -= 15;
} else {
// 质量较低时再缩小尺寸每次缩放到80%
scale *= 0.8;
}
compressLoop();
}
},
fail: (err) => {
console.error("获取压缩后文件信息失败:", err);
reject(err);
}
});
},
fail: (err) => {
console.error("图片压缩失败:", err);
reject(err);
}
});
};
// 开始压缩循环
compressLoop();
}).catch((err) => {
console.error("获取图片信息失败:", err);
reject(err);
});
});
}