docs(@vben/docs): 添加 VCropper 图片裁剪组件文档 (#7904)
* docs(@vben/docs): 添加 VCropper 图片裁剪组件文档 - 新增中文文档 docs/src/components/common-ui/vben-cropper.md - 新增英文文档 docs/src/en/components/common-ui/vben-cropper.md - 新增基础用法示例 demos/vben-cropper/basic - 新增固定比例裁剪示例 demos/vben-cropper/aspect-ratio - 更新侧边栏配置添加 Cropper 入口 * fix: 更正跨域图片描述并修复 demo 内存泄漏 - 文档更正:网络图片导出裁剪结果需服务端 CORS 支持 - 修复 URL.createObjectURL 内存泄漏:添加 revokeObjectURL 释放
This commit is contained in:
102
docs/src/demos/vben-cropper/aspect-ratio/index.vue
Normal file
102
docs/src/demos/vben-cropper/aspect-ratio/index.vue
Normal file
@@ -0,0 +1,102 @@
|
||||
<script lang="ts" setup>
|
||||
import { onBeforeUnmount, ref } from 'vue';
|
||||
|
||||
import { VCropper } from '@vben/common-ui';
|
||||
|
||||
const cropperRef = ref<InstanceType<typeof VCropper>>();
|
||||
const aspectRatio = ref('1:1');
|
||||
const imageUrl = ref('https://picsum.photos/seed/cropper-ratio/800/600');
|
||||
const croppedImage = ref('');
|
||||
|
||||
const aspectOptions = [
|
||||
{ label: '1:1 (正方形)', value: '1:1' },
|
||||
{ label: '16:9 (宽屏)', value: '16:9' },
|
||||
{ label: '4:3 (标准)', value: '4:3' },
|
||||
{ label: '3:4 (竖版)', value: '3:4' },
|
||||
{ label: '3:2 (照片)', value: '3:2' },
|
||||
];
|
||||
|
||||
// 释放旧的 object URL 以避免内存泄漏
|
||||
const revokeCroppedImage = () => {
|
||||
if (croppedImage.value?.startsWith('blob:')) {
|
||||
URL.revokeObjectURL(croppedImage.value);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCrop = async () => {
|
||||
const blob = await cropperRef.value?.getCropImage('image/jpeg', 0.9, 'blob');
|
||||
if (blob instanceof Blob) {
|
||||
// 释放旧的 URL
|
||||
revokeCroppedImage();
|
||||
croppedImage.value = URL.createObjectURL(blob);
|
||||
}
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
// 释放 URL
|
||||
revokeCroppedImage();
|
||||
croppedImage.value = '';
|
||||
imageUrl.value = `https://picsum.photos/seed/cropper-${Date.now()}/800/600`;
|
||||
};
|
||||
|
||||
// 组件卸载时清理
|
||||
onBeforeUnmount(() => {
|
||||
revokeCroppedImage();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="mb-4">
|
||||
<label class="text-sm text-gray-500 mr-2">选择比例:</label>
|
||||
<select v-model="aspectRatio" class="px-3 py-1 border rounded text-sm">
|
||||
<option
|
||||
v-for="option in aspectOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<VCropper
|
||||
ref="cropperRef"
|
||||
:img="imageUrl"
|
||||
:width="500"
|
||||
:height="300"
|
||||
:aspect-ratio="aspectRatio"
|
||||
/>
|
||||
|
||||
<div class="mt-4 flex gap-2">
|
||||
<button
|
||||
class="px-4 py-2 bg-blue-500 rounded hover:bg-blue-600"
|
||||
@click="handleCrop"
|
||||
>
|
||||
裁剪图片
|
||||
</button>
|
||||
<button
|
||||
class="px-4 py-2 bg-gray-500 rounded hover:bg-gray-600"
|
||||
@click="handleReset"
|
||||
>
|
||||
重置
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="croppedImage" class="mt-4">
|
||||
<p class="text-sm text-gray-500 mb-2">
|
||||
裁剪结果 (比例: {{ aspectRatio }}):
|
||||
</p>
|
||||
<img :src="croppedImage" class="max-w-full rounded border" />
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<p class="text-sm text-gray-500">提示:</p>
|
||||
<ul class="mt-2 text-xs text-gray-400 list-disc pl-4">
|
||||
<li>设置固定比例后,裁剪框始终维持该比例</li>
|
||||
<li>切换比例会自动重新计算裁剪框大小</li>
|
||||
<li>比例格式为 "宽:高",如 "16:9"</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
70
docs/src/demos/vben-cropper/basic/index.vue
Normal file
70
docs/src/demos/vben-cropper/basic/index.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<script lang="ts" setup>
|
||||
import { onBeforeUnmount, ref } from 'vue';
|
||||
|
||||
import { VCropper } from '@vben/common-ui';
|
||||
|
||||
const cropperRef = ref<InstanceType<typeof VCropper>>();
|
||||
const imageUrl = ref('https://picsum.photos/seed/cropper-demo/800/600');
|
||||
const croppedImage = ref('');
|
||||
|
||||
// 释放旧的 object URL 以避免内存泄漏
|
||||
const revokeCroppedImage = () => {
|
||||
if (croppedImage.value?.startsWith('blob:')) {
|
||||
URL.revokeObjectURL(croppedImage.value);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCrop = async () => {
|
||||
const blob = await cropperRef.value?.getCropImage('image/jpeg', 0.9, 'blob');
|
||||
if (blob instanceof Blob) {
|
||||
// 释放旧的 URL
|
||||
revokeCroppedImage();
|
||||
croppedImage.value = URL.createObjectURL(blob);
|
||||
}
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
// 释放 URL
|
||||
revokeCroppedImage();
|
||||
croppedImage.value = '';
|
||||
// 重新加载图片以重置裁剪框
|
||||
imageUrl.value = `https://picsum.photos/seed/cropper-${Date.now()}/800/600`;
|
||||
};
|
||||
|
||||
// 组件卸载时清理
|
||||
onBeforeUnmount(() => {
|
||||
revokeCroppedImage();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<VCropper ref="cropperRef" :img="imageUrl" :width="500" :height="300" />
|
||||
<div class="mt-4 flex gap-2">
|
||||
<button
|
||||
class="px-4 py-2 bg-blue-500 rounded hover:bg-blue-600"
|
||||
@click="handleCrop"
|
||||
>
|
||||
裁剪图片
|
||||
</button>
|
||||
<button
|
||||
class="px-4 py-2 bg-gray-500 rounded hover:bg-gray-600"
|
||||
@click="handleReset"
|
||||
>
|
||||
重置
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="croppedImage" class="mt-4">
|
||||
<p class="text-sm text-gray-500 mb-2">裁剪结果:</p>
|
||||
<img :src="croppedImage" class="max-w-full rounded border" />
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<p class="text-sm text-gray-500">提示:</p>
|
||||
<ul class="mt-2 text-xs text-gray-400 list-disc pl-4">
|
||||
<li>拖拽裁剪框中心区域可移动裁剪位置</li>
|
||||
<li>拖拽四角或四边可调整裁剪框大小</li>
|
||||
<li>默认为自由比例,可调整为任意比例</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user