347 lines
11 KiB
JavaScript
347 lines
11 KiB
JavaScript
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);
|
||
});
|
||
});
|
||
} |