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); }); }); }