677 lines
8.6 KiB
Vue
677 lines
8.6 KiB
Vue
<template>
|
||
|
||
<u-popup v-model="visible" mode="center" width="85%" border-radius="16" :mask-close-able="false">
|
||
|
||
<view class="popup-wrap">
|
||
|
||
<view class="popup-title">图片较大</view>
|
||
|
||
<view v-if="sizeText" class="size-text">{{ sizeText }}</view>
|
||
|
||
|
||
|
||
<view v-if="status === 'compressing'" class="compressing-panel">
|
||
|
||
<image
|
||
|
||
v-if="originalPath"
|
||
|
||
:src="originalPath"
|
||
|
||
mode="aspectFit"
|
||
|
||
class="preview-image single"
|
||
|
||
@click="previewImage(originalPath)"
|
||
|
||
/>
|
||
|
||
<text v-if="originalPath" class="preview-hint">点击预览</text>
|
||
|
||
<view class="compressing-status">
|
||
|
||
<u-loading mode="circle" />
|
||
|
||
<text class="progress-text">{{ progressText }}</text>
|
||
|
||
</view>
|
||
|
||
</view>
|
||
|
||
|
||
|
||
<view v-else-if="status === 'ready'" class="compare-panel">
|
||
|
||
<view class="preview-column">
|
||
|
||
<text class="preview-label">原图</text>
|
||
|
||
<image
|
||
|
||
:src="originalPath"
|
||
|
||
mode="aspectFit"
|
||
|
||
class="preview-image"
|
||
|
||
@click="previewImage(originalPath)"
|
||
|
||
/>
|
||
|
||
<text class="preview-size">{{ formatFileSize(originalSize) }}</text>
|
||
|
||
<text class="preview-hint">点击预览</text>
|
||
|
||
</view>
|
||
|
||
<view class="preview-column">
|
||
|
||
<text class="preview-label">压缩后</text>
|
||
|
||
<image
|
||
|
||
:src="compressedPath"
|
||
|
||
mode="aspectFit"
|
||
|
||
class="preview-image"
|
||
|
||
@click="previewImage(compressedPath)"
|
||
|
||
/>
|
||
|
||
<text class="preview-size">{{ formatFileSize(compressedSize) }}</text>
|
||
|
||
<text class="preview-hint">点击预览</text>
|
||
|
||
</view>
|
||
|
||
</view>
|
||
|
||
|
||
|
||
<view v-else-if="status === 'gifSkipped'" class="gif-panel">
|
||
|
||
<image
|
||
|
||
v-if="originalPath"
|
||
|
||
:src="originalPath"
|
||
|
||
mode="aspectFit"
|
||
|
||
class="preview-image single"
|
||
|
||
@click="previewImage(originalPath)"
|
||
|
||
/>
|
||
|
||
<text v-if="originalPath" class="preview-hint">点击预览</text>
|
||
|
||
<text class="hint-text">GIF 不支持压缩,将上传原图。</text>
|
||
|
||
</view>
|
||
|
||
|
||
|
||
<view v-else class="error-panel">
|
||
|
||
<image
|
||
|
||
v-if="originalPath"
|
||
|
||
:src="originalPath"
|
||
|
||
mode="aspectFit"
|
||
|
||
class="preview-image single"
|
||
|
||
@click="previewImage(originalPath)"
|
||
|
||
/>
|
||
|
||
<text v-if="originalPath" class="preview-hint">点击预览</text>
|
||
|
||
<text class="error-text">{{ errorMessage }}</text>
|
||
|
||
</view>
|
||
|
||
|
||
|
||
<view class="footer-actions">
|
||
|
||
<view class="btn btn-default" @click="finish('cancel')">取消</view>
|
||
|
||
<view
|
||
|
||
v-if="status === 'ready' || status === 'error'"
|
||
|
||
class="btn btn-default"
|
||
|
||
@click="finish('original')"
|
||
|
||
>
|
||
|
||
{{ originalButtonLabel }}
|
||
|
||
</view>
|
||
|
||
<view
|
||
|
||
v-if="status === 'ready'"
|
||
|
||
class="btn btn-primary"
|
||
|
||
@click="finish('compressed')"
|
||
|
||
>
|
||
|
||
{{ compressedButtonLabel }}
|
||
|
||
</view>
|
||
|
||
<view
|
||
|
||
v-if="status === 'gifSkipped'"
|
||
|
||
class="btn btn-primary"
|
||
|
||
@click="finish('original')"
|
||
|
||
>
|
||
|
||
{{ originalButtonLabel }}
|
||
|
||
</view>
|
||
|
||
</view>
|
||
|
||
</view>
|
||
|
||
</u-popup>
|
||
|
||
</template>
|
||
|
||
|
||
|
||
<script>
|
||
|
||
import {
|
||
|
||
formatFileSize,
|
||
|
||
formatSavingsPercent,
|
||
|
||
isGifPath,
|
||
|
||
tryCompressImage,
|
||
|
||
} from '@/utils/image-compress.js';
|
||
|
||
|
||
|
||
export default {
|
||
|
||
name: 'ImageCompressPopup',
|
||
|
||
data() {
|
||
|
||
return {
|
||
|
||
visible: false,
|
||
|
||
status: 'compressing',
|
||
|
||
progressText: '正在压缩,请稍候…',
|
||
|
||
errorMessage: '',
|
||
|
||
originalPath: '',
|
||
|
||
compressedPath: '',
|
||
|
||
originalSize: 0,
|
||
|
||
compressedSize: 0,
|
||
|
||
skipped: false,
|
||
|
||
resolver: null,
|
||
|
||
};
|
||
|
||
},
|
||
|
||
computed: {
|
||
|
||
sizeText() {
|
||
|
||
if (!this.originalSize) return '';
|
||
|
||
const originalText = formatFileSize(this.originalSize);
|
||
|
||
if (this.status === 'gifSkipped') {
|
||
|
||
return `原图 ${originalText}`;
|
||
|
||
}
|
||
|
||
if (this.compressedSize && this.status === 'ready') {
|
||
|
||
const savings = formatSavingsPercent(this.originalSize, this.compressedSize);
|
||
|
||
return `原图 ${originalText} → 压缩后 ${formatFileSize(this.compressedSize)}${savings}`;
|
||
|
||
}
|
||
|
||
if (this.compressedSize) {
|
||
|
||
return `原图 ${originalText} → 压缩后 ${formatFileSize(this.compressedSize)}`;
|
||
|
||
}
|
||
|
||
return `原图 ${originalText}`;
|
||
|
||
},
|
||
|
||
originalButtonLabel() {
|
||
|
||
if (!this.originalSize) return '使用原图';
|
||
|
||
return `使用原图 (${formatFileSize(this.originalSize)})`;
|
||
|
||
},
|
||
|
||
compressedButtonLabel() {
|
||
|
||
if (!this.compressedSize) return '使用压缩图';
|
||
|
||
return `使用压缩图 (${formatFileSize(this.compressedSize)})`;
|
||
|
||
},
|
||
|
||
},
|
||
|
||
methods: {
|
||
|
||
formatFileSize,
|
||
|
||
previewImage(current) {
|
||
|
||
if (!this.originalPath) return;
|
||
|
||
const urls =
|
||
|
||
this.status === 'ready' && this.compressedPath
|
||
|
||
? [this.originalPath, this.compressedPath]
|
||
|
||
: [this.originalPath];
|
||
|
||
uni.previewImage({
|
||
|
||
urls,
|
||
|
||
current,
|
||
|
||
});
|
||
|
||
},
|
||
|
||
open({ path, size }) {
|
||
|
||
return new Promise((resolve) => {
|
||
|
||
this.resolver = resolve;
|
||
|
||
this.originalPath = path;
|
||
|
||
this.originalSize = size;
|
||
|
||
this.compressedPath = '';
|
||
|
||
this.compressedSize = 0;
|
||
|
||
this.skipped = false;
|
||
|
||
this.errorMessage = '';
|
||
|
||
this.progressText = '正在压缩,请稍候…';
|
||
|
||
this.visible = true;
|
||
|
||
this.startCompress(path, size);
|
||
|
||
});
|
||
|
||
},
|
||
|
||
async startCompress(path, size) {
|
||
|
||
if (isGifPath(path)) {
|
||
|
||
this.skipped = true;
|
||
|
||
this.status = 'gifSkipped';
|
||
|
||
return;
|
||
|
||
}
|
||
|
||
|
||
|
||
this.status = 'compressing';
|
||
|
||
this.progressText = '正在压缩(质量 80%)…';
|
||
|
||
|
||
|
||
try {
|
||
|
||
const compressed = await tryCompressImage(path, [80, 60, 40], (quality) => {
|
||
|
||
this.progressText = `正在压缩(质量 ${quality}%)…`;
|
||
|
||
});
|
||
|
||
this.compressedPath = compressed.path;
|
||
|
||
this.compressedSize = compressed.size;
|
||
|
||
this.status = 'ready';
|
||
|
||
this.progressText = '';
|
||
|
||
} catch (error) {
|
||
|
||
this.status = 'error';
|
||
|
||
this.errorMessage = '图片压缩失败,请使用原图或取消';
|
||
|
||
this.progressText = '';
|
||
|
||
}
|
||
|
||
},
|
||
|
||
finish(choice) {
|
||
|
||
const result = { choice, path: null, size: 0, usedCompress: false };
|
||
|
||
|
||
|
||
if (choice === 'cancel') {
|
||
|
||
this.visible = false;
|
||
|
||
this.resolver && this.resolver(null);
|
||
|
||
this.resolver = null;
|
||
|
||
return;
|
||
|
||
}
|
||
|
||
|
||
|
||
if (choice === 'compressed' && this.status === 'ready') {
|
||
|
||
result.path = this.compressedPath;
|
||
|
||
result.size = this.compressedSize;
|
||
|
||
result.usedCompress = true;
|
||
|
||
} else {
|
||
|
||
result.path = this.originalPath;
|
||
|
||
result.size = this.originalSize;
|
||
|
||
result.usedCompress = false;
|
||
|
||
}
|
||
|
||
|
||
|
||
this.visible = false;
|
||
|
||
this.resolver && this.resolver(result);
|
||
|
||
this.resolver = null;
|
||
|
||
},
|
||
|
||
},
|
||
|
||
};
|
||
|
||
</script>
|
||
|
||
|
||
|
||
<style lang="scss" scoped>
|
||
|
||
.popup-wrap {
|
||
|
||
padding: 32rpx;
|
||
|
||
}
|
||
|
||
|
||
|
||
.popup-title {
|
||
|
||
font-size: 32rpx;
|
||
|
||
font-weight: 600;
|
||
|
||
text-align: center;
|
||
|
||
margin-bottom: 16rpx;
|
||
|
||
}
|
||
|
||
|
||
|
||
.size-text {
|
||
|
||
font-size: 26rpx;
|
||
|
||
color: #666;
|
||
|
||
text-align: center;
|
||
|
||
margin-bottom: 24rpx;
|
||
|
||
}
|
||
|
||
|
||
|
||
.preview-image {
|
||
|
||
width: 100%;
|
||
|
||
height: 280rpx;
|
||
|
||
background: #fafafa;
|
||
|
||
border-radius: 12rpx;
|
||
|
||
}
|
||
|
||
|
||
|
||
.preview-image.single {
|
||
|
||
width: 100%;
|
||
|
||
}
|
||
|
||
|
||
|
||
.compare-panel {
|
||
|
||
display: flex;
|
||
|
||
gap: 16rpx;
|
||
|
||
}
|
||
|
||
|
||
|
||
.preview-column {
|
||
|
||
flex: 1;
|
||
|
||
display: flex;
|
||
|
||
flex-direction: column;
|
||
|
||
align-items: center;
|
||
|
||
}
|
||
|
||
|
||
|
||
.preview-label {
|
||
|
||
font-size: 26rpx;
|
||
|
||
font-weight: 500;
|
||
|
||
margin-bottom: 12rpx;
|
||
|
||
}
|
||
|
||
|
||
|
||
.preview-size {
|
||
|
||
margin-top: 8rpx;
|
||
|
||
font-size: 22rpx;
|
||
|
||
color: #999;
|
||
|
||
}
|
||
|
||
|
||
|
||
.preview-hint {
|
||
|
||
margin-top: 4rpx;
|
||
|
||
font-size: 22rpx;
|
||
|
||
color: #bbb;
|
||
|
||
}
|
||
|
||
|
||
|
||
.compressing-panel,
|
||
|
||
.gif-panel,
|
||
|
||
.error-panel {
|
||
|
||
display: flex;
|
||
|
||
flex-direction: column;
|
||
|
||
align-items: center;
|
||
|
||
gap: 24rpx;
|
||
|
||
}
|
||
|
||
|
||
|
||
.compressing-status {
|
||
|
||
display: flex;
|
||
|
||
align-items: center;
|
||
|
||
gap: 16rpx;
|
||
|
||
}
|
||
|
||
|
||
|
||
.progress-text,
|
||
|
||
.hint-text {
|
||
|
||
font-size: 26rpx;
|
||
|
||
color: #666;
|
||
|
||
}
|
||
|
||
|
||
|
||
.error-text {
|
||
|
||
font-size: 26rpx;
|
||
|
||
color: #fa3534;
|
||
|
||
}
|
||
|
||
|
||
|
||
.footer-actions {
|
||
|
||
display: flex;
|
||
|
||
justify-content: flex-end;
|
||
|
||
flex-wrap: wrap;
|
||
|
||
gap: 16rpx;
|
||
|
||
margin-top: 32rpx;
|
||
|
||
}
|
||
|
||
|
||
|
||
.btn {
|
||
|
||
min-width: 160rpx;
|
||
|
||
padding: 16rpx 24rpx;
|
||
|
||
border-radius: 8rpx;
|
||
|
||
font-size: 26rpx;
|
||
|
||
text-align: center;
|
||
|
||
}
|
||
|
||
|
||
|
||
.btn-default {
|
||
|
||
background: #f5f5f5;
|
||
|
||
color: #333;
|
||
|
||
}
|
||
|
||
|
||
|
||
.btn-primary {
|
||
|
||
background: #2979ff;
|
||
|
||
color: #fff;
|
||
|
||
}
|
||
|
||
</style>
|
||
|