fix: 文件上传压缩功能修复。

This commit is contained in:
tianyongbao
2026-07-08 10:10:38 +08:00
parent 6aaf564800
commit aeb1e39920
5 changed files with 445 additions and 261 deletions

View File

@@ -62,172 +62,12 @@
"navigationStyle": "custom"
}
},
{
"path": "pages/health/statistic/index",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/mine",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom"
}
},
{
"path": "pages/health/doctorRecord/list",
"style": {
"navigationBarTitleText": "就医记录"
}
}
,
{
"path": "pages/health/doctorRecord/addEdit",
"style": {
"navigationBarTitleText": "就医记录"
}
},
{
"path": "pages/health/doctorRecord/costList",
"style": {
"navigationBarTitleText": "费用明细"
}
},
{
"path": "pages/health/doctorRecord/costAddEdit",
"style": {
"navigationBarTitleText": "费用明细"
}
},
{
"path": "pages/health/healthRecord/list",
"style": {
"navigationBarTitleText": "健康档案"
}
} ,
{
"path": "pages/health/healthRecord/addEdit",
"style": {
"navigationBarTitleText": "健康档案"
}
},
{
"path": "pages/health/healthRecord/details",
"style": {
"navigationBarTitleText": "档案统计"
}
},
{
"path": "pages/health/marRecord/list",
"style": {
"navigationBarTitleText": "用药记录"
}
}
,
{
"path": "pages/health/marRecord/addEdit",
"style": {
"navigationBarTitleText": "用药记录"
}
},
{
"path": "pages/health/medicineBasic/list",
"style": {
"navigationBarTitleText": "药品基础信息"
}
}
,
{
"path": "pages/health/medicineBasic/details",
"style": {
"navigationBarTitleText": "药品基础信息详情"
}
}
,
{
"path": "pages/health/medicineBasic/addEdit",
"style": {
"navigationBarTitleText": "药品基础信息"
}
},
{
"path": "pages/health/medicineStockIn/list",
"style": {
"navigationBarTitleText": "药品入库清单"
}
}
,
{
"path": "pages/health/medicineStockIn/details",
"style": {
"navigationBarTitleText": "药品入库清单详情"
}
}
,
{
"path": "pages/health/medicineStockIn/addEdit",
"style": {
"navigationBarTitleText": "药品入库清单"
}
}, {
"path": "pages/health/medicineStock/list",
"style": {
"navigationBarTitleText": "药品实时库存"
}
}
,
{
"path": "pages/health/temperatureRecord/list",
"style": {
"navigationBarTitleText": "体温记录"
}
}
,
{
"path": "pages/health/temperatureRecord/addEdit",
"style": {
"navigationBarTitleText": "体温记录"
}
} ,
{
"path": "pages/health/processRecord/list",
"style": {
"navigationBarTitleText": "档案过程记录"
}
}
,
{
"path": "pages/health/processRecord/addEdit",
"style": {
"navigationBarTitleText": "过程记录"
}
},
{
"path": "pages/health/heightWeightRecord/list",
"style": {
"navigationBarTitleText": "身高体重记录"
}
}
,
{
"path": "pages/health/milkPowderRecord/addEdit",
"style": {
"navigationBarTitleText": "吃奶记录"
}
},
{
"path": "pages/health/milkPowderRecord/list",
"style": {
"navigationBarTitleText": "吃奶记录"
}
}
,
{
"path": "pages/health/heightWeightRecord/addEdit",
"style": {
"navigationBarTitleText": "身高体重记录"
}
},
{
"path": "pages/health/person/list",
@@ -256,81 +96,42 @@
}
},
{
"path": "pages/health/statistic/doctorStatistic/index",
"path": "pages/health/temperatureRecord/list",
"style": {
"navigationBarTitleText": "就医统计"
"navigationBarTitleText": "体温记录"
}
}
,
{
"path": "pages/health/temperatureRecord/addEdit",
"style": {
"navigationBarTitleText": "体温记录"
}
} ,
{
"path": "pages/health/statistic/healthStatistic/index",
"path": "pages/health/heightWeightRecord/list",
"style": {
"navigationBarTitleText": "档案统计"
"navigationBarTitleText": "身高体重记录"
}
}
,
{
"path": "pages/health/milkPowderRecord/addEdit",
"style": {
"navigationBarTitleText": "吃奶记录"
}
},
{
"path": "pages/health/statistic/healthScreen/index",
"path": "pages/health/milkPowderRecord/list",
"style": {
"navigationBarTitleText": "健康总览态势"
"navigationBarTitleText": "吃奶记录"
}
},
{
"path": "pages/health/statistic/recordScreen/index",
"style": {
"navigationBarTitleText": "健康档案态势"
}
} ,
,
{
"path": "pages/health/statistic/marStatistic/index",
"path": "pages/health/heightWeightRecord/addEdit",
"style": {
"navigationBarTitleText": "用药统计"
}
} ,
{
"path": "pages/health/statistic/temperatureStatistic/index",
"style": {
"navigationBarTitleText": "体温统计"
}
} ,
{
"path": "pages/health/statistic/milkPowderStatistic/index",
"style": {
"navigationBarTitleText": "吃奶量统计"
}
},
{
"path": "pages/health/medicationPlan/list",
"style": {
"navigationBarTitleText": "用药计划"
}
},
{
"path": "pages/health/medicationPlan/addEdit",
"style": {
"navigationBarTitleText": "用药计划"
}
},
{
"path": "pages/health/chronicDisease/list",
"style": {
"navigationBarTitleText": "慢性疾病档案"
}
},
{
"path": "pages/health/chronicDisease/addEdit",
"style": {
"navigationBarTitleText": "慢性疾病档案"
}
},
{
"path": "pages/health/chronicDisease/details",
"style": {
"navigationBarTitleText": "慢性疾病详情"
}
},
{
"path": "pages/health/medicationTask/list",
"style": {
"navigationBarTitleText": "服药任务"
"navigationBarTitleText": "身高体重记录"
}
},
{
@@ -393,9 +194,212 @@
}
}
]
},
{
"root": "pages/health/statistic",
"name": "statistic",
"pages": [
{
"path": "index",
"style": { "navigationBarTitleText": "统计分析" }
},
{
"path": "doctorStatistic/index",
"style": { "navigationBarTitleText": "就医统计" }
},
{
"path": "healthStatistic/index",
"style": { "navigationBarTitleText": "档案统计" }
},
{
"path": "healthScreen/index",
"style": { "navigationBarTitleText": "健康总览态势" }
},
{
"path": "recordScreen/index",
"style": { "navigationBarTitleText": "健康档案态势" }
},
{
"path": "marStatistic/index",
"style": { "navigationBarTitleText": "用药统计" }
},
{
"path": "temperatureStatistic/index",
"style": { "navigationBarTitleText": "体温统计" }
},
{
"path": "milkPowderStatistic/index",
"style": { "navigationBarTitleText": "吃奶量统计" }
}
]
},
{
"root": "pages/health/medicineBasic",
"name": "medicineBasic",
"pages": [
{
"path": "list",
"style": { "navigationBarTitleText": "药品基础信息" }
},
{
"path": "details",
"style": { "navigationBarTitleText": "药品基础信息详情" }
},
{
"path": "addEdit",
"style": { "navigationBarTitleText": "药品基础信息" }
}
]
},
{
"root": "pages/health/medicineStockIn",
"name": "medicineStockIn",
"pages": [
{
"path": "list",
"style": { "navigationBarTitleText": "药品入库清单" }
},
{
"path": "details",
"style": { "navigationBarTitleText": "药品入库清单详情" }
},
{
"path": "addEdit",
"style": { "navigationBarTitleText": "药品入库清单" }
}
]
},
{
"root": "pages/health/medicineStock",
"name": "medicineStock",
"pages": [
{
"path": "list",
"style": { "navigationBarTitleText": "药品实时库存" }
}
]
},
{
"root": "pages/health/medicationPlan",
"name": "medicationPlan",
"pages": [
{
"path": "list",
"style": { "navigationBarTitleText": "用药计划" }
},
{
"path": "addEdit",
"style": { "navigationBarTitleText": "用药计划" }
}
]
},
{
"root": "pages/health/medicationTask",
"name": "medicationTask",
"pages": [
{
"path": "list",
"style": { "navigationBarTitleText": "服药任务" }
}
]
},
{
"root": "pages/health/marRecord",
"name": "marRecord",
"pages": [
{
"path": "list",
"style": { "navigationBarTitleText": "用药记录" }
},
{
"path": "addEdit",
"style": { "navigationBarTitleText": "用药记录" }
}
]
},
{
"root": "pages/health/doctorRecord",
"name": "doctorRecord",
"pages": [
{
"path": "list",
"style": { "navigationBarTitleText": "就医记录" }
},
{
"path": "addEdit",
"style": { "navigationBarTitleText": "就医记录" }
},
{
"path": "costList",
"style": { "navigationBarTitleText": "费用明细" }
},
{
"path": "costAddEdit",
"style": { "navigationBarTitleText": "费用明细" }
}
]
},
{
"root": "pages/health/healthRecord",
"name": "healthRecord",
"pages": [
{
"path": "list",
"style": { "navigationBarTitleText": "健康档案" }
},
{
"path": "addEdit",
"style": { "navigationBarTitleText": "健康档案" }
},
{
"path": "details",
"style": { "navigationBarTitleText": "档案统计" }
}
]
},
{
"root": "pages/health/processRecord",
"name": "processRecord",
"pages": [
{
"path": "list",
"style": { "navigationBarTitleText": "档案过程记录" }
},
{
"path": "addEdit",
"style": { "navigationBarTitleText": "过程记录" }
}
]
},
{
"root": "pages/health/chronicDisease",
"name": "chronicDisease",
"pages": [
{
"path": "list",
"style": { "navigationBarTitleText": "慢性疾病档案" }
},
{
"path": "addEdit",
"style": { "navigationBarTitleText": "慢性疾病档案" }
},
{
"path": "details",
"style": { "navigationBarTitleText": "慢性疾病详情" }
}
]
}
],
"preloadRule": {
"pages/health/index": {
"network": "all",
"packages": ["medicineBasic", "medicineStockIn", "medicineStock", "medicationPlan", "medicationTask", "marRecord", "doctorRecord", "healthRecord", "processRecord", "chronicDisease"]
},
"pages/health/statistic/index": {
"network": "all",
"packages": ["statistic"]
}
},
"tabBar": {
"color": "#000000",
"selectedColor": "#000000",

View File

@@ -125,6 +125,7 @@
<script setup>
import { getActivity, addActivity, updateActivity, uploadFile } from '@/api/health/activity'
import config from '@/config'
import { compressImage } from '@/utils/imageCompress'
const { proxy } = getCurrentInstance()
import { getDicts } from '@/api/system/dict/data.js'
import dayjs from 'dayjs'
@@ -400,30 +401,32 @@ function chooseImage() {
})
}
function uploadImages(paths, index) {
async function uploadImages(paths, index) {
if (index >= paths.length) {
uploading.value = false
return
}
uploading.value = true
// H5 端 mask: true 会让页面无法点击,一旦上传出问题会导致用户被“锁住”
// 这里改为 mask: false允许用户取消会提示正在上传
uni.showLoading({ title: `处理中 ${index + 1}/${paths.length}`, mask: false })
try {
// 上传前先压缩(超过 500KB 才压,避免小图多走一道)
const compressedPath = await compressImage(paths[index])
uni.showLoading({ title: `上传中 ${index + 1}/${paths.length}`, mask: false })
uploadFile({
filePath: paths[index],
const res = await uploadFile({
filePath: compressedPath,
name: 'file'
}).then(res => {
})
const url = (res.data && res.data.url) || res.fileName || res.url
if (url) {
attachmentList.value.push(normalizeAttachmentUrl(url))
}
uni.hideLoading()
uploadImages(paths, index + 1)
}).catch(() => {
} catch (e) {
uni.hideLoading()
uploading.value = false
proxy.$refs['uToast'].show({ message: `${index + 1} 张上传失败`, type: 'error' })
})
}
}
function deleteImage(index) {

View File

@@ -114,6 +114,7 @@
<script setup>
import { getDoctorRecord, addDoctorRecord, updateDoctorRecord, uploadFile } from '@/api/health/doctorRecord'
import config from '@/config'
import { compressImage } from '@/utils/imageCompress'
import { listPerson } from '@/api/health/person'
import { listHealthRecord } from '@/api/health/healthRecord'
const { proxy } = getCurrentInstance()
@@ -457,30 +458,32 @@ function chooseImage() {
}
// 递归上传图片
function uploadImages(paths, index) {
async function uploadImages(paths, index) {
if (index >= paths.length) {
uploading.value = false
return
}
uploading.value = true
// H5 端 mask: true 会让页面无法点击,一旦上传出问题会导致用户被“锁住”
// 这里改为 mask: false允许用户取消会提示正在上传
uni.showLoading({ title: `处理中 ${index + 1}/${paths.length}`, mask: false })
try {
// 上传前先压缩(超过 500KB 才压,避免小图多走一道)
const compressedPath = await compressImage(paths[index])
uni.showLoading({ title: `上传中 ${index + 1}/${paths.length}`, mask: false })
uploadFile({
filePath: paths[index],
const res = await uploadFile({
filePath: compressedPath,
name: 'file'
}).then(res => {
})
const url = (res.data && res.data.url) || res.fileName || res.url
if (url) {
attachmentList.value.push(normalizeAttachmentUrl(url))
}
uni.hideLoading()
uploadImages(paths, index + 1)
}).catch(() => {
} catch (e) {
uni.hideLoading()
uploading.value = false
proxy.$refs['uToast'].show({ message: `${index + 1} 张上传失败`, type: 'error' })
})
}
}
// 删除图片

View File

@@ -177,6 +177,7 @@
<script setup>
import {getMedicineBasic, addMedicineBasic, updateMedicineBasic, uploadFile } from '@/api/health/medicineBasic'
import config from '@/config'
import { compressImage } from '@/utils/imageCompress'
const { proxy } = getCurrentInstance()
import { getDicts } from '@/api/system/dict/data.js'
import dayjs from 'dayjs'
@@ -617,19 +618,21 @@ function chooseImage() {
}
// 递归上传图片
function uploadImages(paths, index) {
async function uploadImages(paths, index) {
if (index >= paths.length) {
uploading.value = false
return
}
uploading.value = true
// H5 端 mask: true 会让页面无法点击,一旦上传出问题会导致用户被“锁住”
// 这里改为 mask: false允许用户取消会提示正在上传
uni.showLoading({ title: `处理中 ${index + 1}/${paths.length}`, mask: false })
try {
// 上传前先压缩(超过 500KB 才压,避免小图多走一道)
const compressedPath = await compressImage(paths[index])
uni.showLoading({ title: `上传中 ${index + 1}/${paths.length}`, mask: false })
uploadFile({
filePath: paths[index],
const res = await uploadFile({
filePath: compressedPath,
name: 'file'
}).then(res => {
})
// 打印返回数据,便于调试
console.log('==== upload response ====', JSON.stringify(res))
// 兼容多种返回格式
@@ -649,11 +652,11 @@ function uploadImages(paths, index) {
}
uni.hideLoading()
uploadImages(paths, index + 1)
}).catch(() => {
} catch (e) {
uni.hideLoading()
uploading.value = false
proxy.$refs['uToast'].show({ message: `${index + 1} 张上传失败`, type: 'error' })
})
}
}
// 删除图片

171
src/utils/imageCompress.ts Normal file
View File

@@ -0,0 +1,171 @@
/**
* 跨端图片压缩工具
*
* 用途:上传附件前压缩图片,避免大图(特别是手机原图 4MB+)导致上传卡死
*
* - H5用 Canvas 重绘 + toDataURL('image/jpeg', quality)
* - 小程序/APP用 uni.compressImage APIquality + compressedWidth
*
* 策略:
* - 原图小于 500KB 不压(避免无谓耗时)
* - 最大宽度限制 1280px横屏照片或按比例缩放
* - JPEG 质量 0.75(体积约 1/5 ~ 1/10肉眼几乎无差
* - 透明 PNG 也压成 JPEG牺牲透明通道换取体积如需透明可改 quality 调用
*
* 用法:
* import { compressImage } from '@/utils/imageCompress'
* const compressed = await compressImage(filePath)
* uploadFile({ filePath: compressed, ... })
*/
/** 不压缩的体积阈值字节800KB小图直接跳过避免无谓耗时 */
const SKIP_COMPRESS_SIZE = 800 * 1024
/** 最大边长超过按比例缩放保留宽高比。1920px 兼顾清晰度与体积 */
const MAX_SIDE = 1920
/** JPEG 压缩质量 0~1。0.85 可保证医疗单据/药品说明书文字清晰 */
const JPEG_QUALITY = 0.85
/**
* H5 端压缩Canvas 重绘 + toBlob
*/
function compressH5(filePath: string): Promise<string> {
return new Promise((resolve, reject) => {
const img = new Image()
img.crossOrigin = 'anonymous'
img.onload = () => {
try {
// 计算缩放后的尺寸
let { width, height } = img
const longSide = Math.max(width, height)
if (longSide > MAX_SIDE) {
const scale = MAX_SIDE / longSide
width = Math.round(width * scale)
height = Math.round(height * scale)
}
// 创建 canvas 重绘
const canvas = document.createElement('canvas')
canvas.width = width
canvas.height = height
const ctx = canvas.getContext('2d')
if (!ctx) {
resolve(filePath) // 极端情况降级到原图
return
}
// 白底(防止 PNG 透明通道转 JPEG 后变黑)
ctx.fillStyle = '#ffffff'
ctx.fillRect(0, 0, width, height)
ctx.drawImage(img, 0, 0, width, height)
// 转成 Blob 返回 ObjectURL避免 base64 体积膨胀)
canvas.toBlob(
(blob) => {
if (!blob) {
resolve(filePath)
return
}
// 释放旧的 ObjectURL如果是 blob: URL
if (filePath.startsWith('blob:')) {
try { URL.revokeObjectURL(filePath) } catch (e) { /* ignore */ }
}
resolve(URL.createObjectURL(blob))
},
'image/jpeg',
JPEG_QUALITY
)
} catch (err) {
console.warn('[imageCompress] H5 压缩失败,降级原图', err)
resolve(filePath)
}
}
img.onerror = (err) => {
console.warn('[imageCompress] H5 图片加载失败,降级原图', err)
resolve(filePath)
}
img.src = filePath
})
}
/**
* 小程序/APP 端压缩uni.compressImage
*/
function compressMP(filePath: string): Promise<string> {
return new Promise((resolve) => {
uni.compressImage({
src: filePath,
quality: Math.round(JPEG_QUALITY * 100), // 75
compressedWidth: MAX_SIDE,
success: (res) => resolve(res.tempFilePath),
fail: (err) => {
console.warn('[imageCompress] uni.compressImage 失败,降级原图', err)
resolve(filePath) // 失败降级
}
})
})
}
/**
* 获取文件大小(字节)。
* - H5: blob: / data: URL 用 fetch 解析
* - 小程序/APP: uni.getFileInfo
*/
function getFileSize(filePath: string): Promise<number> {
return new Promise((resolve) => {
// #ifdef H5
try {
if (filePath.startsWith('blob:') || filePath.startsWith('data:')) {
fetch(filePath)
.then(r => r.blob())
.then(b => resolve(b.size))
.catch(() => resolve(0))
return
}
} catch (e) { /* fallthrough */ }
resolve(0) // 无法获取则不压缩(保守策略)
return
// #endif
// #ifndef H5
uni.getFileInfo({
filePath,
success: (res) => resolve(res.size),
fail: () => resolve(0)
})
// #endif
})
}
/**
* 压缩图片入口(跨端)
* @param filePath 图片临时路径
* @returns 压缩后的图片路径(如失败则返回原路径)
*/
export async function compressImage(filePath: string): Promise<string> {
if (!filePath) return filePath
try {
// 检查体积,小图直接跳过
const size = await getFileSize(filePath)
if (size > 0 && size < SKIP_COMPRESS_SIZE) {
return filePath
}
// #ifdef H5
return await compressH5(filePath)
// #endif
// #ifndef H5
return await compressMP(filePath)
// #endif
} catch (err) {
console.warn('[imageCompress] 压缩过程异常,降级原图', err)
return filePath
}
}
/**
* 批量压缩图片(串行执行,避免一次性占用过多内存)
* @param filePaths 图片路径数组
* @returns 压缩后的路径数组(与入参顺序一致)
*/
export async function compressImages(filePaths: string[]): Promise<string[]> {
const result: string[] = []
for (const p of filePaths) {
result.push(await compressImage(p))
}
return result
}