feat: 支持按目标大小压缩图片
- 新增 target_size_bytes 参数,支持直接指定压缩后的目标大小(字节) - 实现自动缩放算法:当仅调整质量无法达到目标时,自动缩小图片尺寸 - 前端新增压缩模式切换:百分比模式 / 目标大小模式 - 支持 KB/MB 单位选择 - 优化二分搜索算法,提高目标大小的精准度 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -20,8 +20,13 @@ interface UploadItem {
|
||||
|
||||
const auth = useAuthStore()
|
||||
|
||||
type CompressionMode = 'percent' | 'size'
|
||||
|
||||
const options = reactive({
|
||||
mode: 'percent' as CompressionMode, // 压缩模式:百分比 / 目标大小
|
||||
compressionRate: 60,
|
||||
targetSize: '' as string, // 目标大小数值
|
||||
targetUnit: 'KB' as 'KB' | 'MB', // 目标大小单位
|
||||
maxWidth: '' as string,
|
||||
maxHeight: '' as string,
|
||||
})
|
||||
@@ -105,18 +110,33 @@ function toInt(v: string): number | undefined {
|
||||
return Math.floor(n)
|
||||
}
|
||||
|
||||
function getTargetSizeBytes(): number | undefined {
|
||||
if (options.mode !== 'size') return undefined
|
||||
const size = Number(options.targetSize)
|
||||
if (!Number.isFinite(size) || size <= 0) return undefined
|
||||
return options.targetUnit === 'MB' ? size * 1024 * 1024 : size * 1024
|
||||
}
|
||||
|
||||
async function runOne(item: UploadItem) {
|
||||
item.status = 'compressing'
|
||||
item.error = undefined
|
||||
|
||||
try {
|
||||
const compressOptions = options.mode === 'percent'
|
||||
? {
|
||||
compression_rate: options.compressionRate,
|
||||
max_width: toInt(options.maxWidth),
|
||||
max_height: toInt(options.maxHeight),
|
||||
}
|
||||
: {
|
||||
target_size_bytes: getTargetSizeBytes(),
|
||||
max_width: toInt(options.maxWidth),
|
||||
max_height: toInt(options.maxHeight),
|
||||
}
|
||||
|
||||
const result = await compressFile(
|
||||
item.file,
|
||||
{
|
||||
compression_rate: options.compressionRate,
|
||||
max_width: toInt(options.maxWidth),
|
||||
max_height: toInt(options.maxHeight),
|
||||
},
|
||||
compressOptions,
|
||||
auth.token,
|
||||
)
|
||||
|
||||
@@ -460,7 +480,31 @@ async function resendVerification() {
|
||||
<div class="text-sm font-medium text-slate-900">压缩参数</div>
|
||||
|
||||
<div class="mt-4 grid grid-cols-1 gap-4">
|
||||
<label class="space-y-1">
|
||||
<!-- 压缩模式切换 -->
|
||||
<div class="space-y-2">
|
||||
<div class="text-xs font-medium text-slate-600">压缩模式</div>
|
||||
<div class="flex rounded-lg border border-slate-200 p-1">
|
||||
<button
|
||||
type="button"
|
||||
class="flex-1 rounded-md px-3 py-1.5 text-xs font-medium transition"
|
||||
:class="options.mode === 'percent' ? 'bg-indigo-600 text-white' : 'text-slate-600 hover:bg-slate-100'"
|
||||
@click="options.mode = 'percent'"
|
||||
>
|
||||
按百分比
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="flex-1 rounded-md px-3 py-1.5 text-xs font-medium transition"
|
||||
:class="options.mode === 'size' ? 'bg-indigo-600 text-white' : 'text-slate-600 hover:bg-slate-100'"
|
||||
@click="options.mode = 'size'"
|
||||
>
|
||||
按目标大小
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 百分比模式 -->
|
||||
<label v-if="options.mode === 'percent'" class="space-y-1">
|
||||
<div class="flex items-center justify-between text-xs font-medium text-slate-600">
|
||||
<span>压缩率</span>
|
||||
<span class="text-slate-500">{{ options.compressionRate }}%</span>
|
||||
@@ -476,6 +520,28 @@ async function resendVerification() {
|
||||
<div class="text-xs text-slate-500">数值越小压缩越强,目标为压缩后体积占原图比例(100% 为不压缩)。</div>
|
||||
</label>
|
||||
|
||||
<!-- 目标大小模式 -->
|
||||
<div v-else class="space-y-1">
|
||||
<div class="text-xs font-medium text-slate-600">目标大小</div>
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
v-model="options.targetSize"
|
||||
type="number"
|
||||
min="1"
|
||||
placeholder="例如 50"
|
||||
class="flex-1 rounded-md border border-slate-200 bg-white px-3 py-2 text-sm text-slate-800"
|
||||
/>
|
||||
<select
|
||||
v-model="options.targetUnit"
|
||||
class="rounded-md border border-slate-200 bg-white px-3 py-2 text-sm text-slate-800"
|
||||
>
|
||||
<option value="KB">KB</option>
|
||||
<option value="MB">MB</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="text-xs text-slate-500">直接指定压缩后的目标大小,系统会自动调整质量以逼近目标(仅 JPEG/WebP/AVIF 支持)。</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<label class="space-y-1">
|
||||
<div class="text-xs font-medium text-slate-600">最大宽度(px)</div>
|
||||
|
||||
@@ -84,6 +84,7 @@ export interface CompressResponse {
|
||||
export interface CompressOptions {
|
||||
level?: CompressionLevel
|
||||
compression_rate?: number
|
||||
target_size_bytes?: number // 新增:直接指定目标大小(字节)
|
||||
output_format?: OutputFormat
|
||||
max_width?: number
|
||||
max_height?: number
|
||||
@@ -96,6 +97,7 @@ export async function compressFile(file: File, options: CompressOptions, token?:
|
||||
|
||||
if (options.level) form.append('level', options.level)
|
||||
if (options.compression_rate) form.append('compression_rate', String(options.compression_rate))
|
||||
if (options.target_size_bytes) form.append('target_size_bytes', String(options.target_size_bytes))
|
||||
if (options.output_format) form.append('output_format', options.output_format)
|
||||
if (options.max_width) form.append('max_width', String(options.max_width))
|
||||
if (options.max_height) form.append('max_height', String(options.max_height))
|
||||
|
||||
Reference in New Issue
Block a user