index.vue 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. <script lang="ts" setup>
  2. import { onBeforeUnmount, ref } from 'vue';
  3. import { VCropper } from '@vben/common-ui';
  4. const cropperRef = ref<InstanceType<typeof VCropper>>();
  5. const aspectRatio = ref('1:1');
  6. const imageUrl = ref('https://picsum.photos/seed/cropper-ratio/800/600');
  7. const croppedImage = ref('');
  8. const aspectOptions = [
  9. { label: '1:1 (正方形)', value: '1:1' },
  10. { label: '16:9 (宽屏)', value: '16:9' },
  11. { label: '4:3 (标准)', value: '4:3' },
  12. { label: '3:4 (竖版)', value: '3:4' },
  13. { label: '3:2 (照片)', value: '3:2' },
  14. ];
  15. // 释放旧的 object URL 以避免内存泄漏
  16. const revokeCroppedImage = () => {
  17. if (croppedImage.value?.startsWith('blob:')) {
  18. URL.revokeObjectURL(croppedImage.value);
  19. }
  20. };
  21. const handleCrop = async () => {
  22. const blob = await cropperRef.value?.getCropImage('image/jpeg', 0.9, 'blob');
  23. if (blob instanceof Blob) {
  24. // 释放旧的 URL
  25. revokeCroppedImage();
  26. croppedImage.value = URL.createObjectURL(blob);
  27. }
  28. };
  29. const handleReset = () => {
  30. // 释放 URL
  31. revokeCroppedImage();
  32. croppedImage.value = '';
  33. imageUrl.value = `https://picsum.photos/seed/cropper-${Date.now()}/800/600`;
  34. };
  35. // 组件卸载时清理
  36. onBeforeUnmount(() => {
  37. revokeCroppedImage();
  38. });
  39. </script>
  40. <template>
  41. <div>
  42. <div class="mb-4">
  43. <label class="text-sm text-gray-500 mr-2">选择比例:</label>
  44. <select v-model="aspectRatio" class="px-3 py-1 border rounded text-sm">
  45. <option
  46. v-for="option in aspectOptions"
  47. :key="option.value"
  48. :value="option.value"
  49. >
  50. {{ option.label }}
  51. </option>
  52. </select>
  53. </div>
  54. <VCropper
  55. ref="cropperRef"
  56. :img="imageUrl"
  57. :width="500"
  58. :height="300"
  59. :aspect-ratio="aspectRatio"
  60. />
  61. <div class="mt-4 flex gap-2">
  62. <button
  63. class="px-4 py-2 bg-blue-500 rounded hover:bg-blue-600"
  64. @click="handleCrop"
  65. >
  66. 裁剪图片
  67. </button>
  68. <button
  69. class="px-4 py-2 bg-gray-500 rounded hover:bg-gray-600"
  70. @click="handleReset"
  71. >
  72. 重置
  73. </button>
  74. </div>
  75. <div v-if="croppedImage" class="mt-4">
  76. <p class="text-sm text-gray-500 mb-2">
  77. 裁剪结果 (比例: {{ aspectRatio }}):
  78. </p>
  79. <img :src="croppedImage" class="max-w-full rounded border" />
  80. </div>
  81. <div class="mt-4">
  82. <p class="text-sm text-gray-500">提示:</p>
  83. <ul class="mt-2 text-xs text-gray-400 list-disc pl-4">
  84. <li>设置固定比例后,裁剪框始终维持该比例</li>
  85. <li>切换比例会自动重新计算裁剪框大小</li>
  86. <li>比例格式为 "宽:高",如 "16:9"</li>
  87. </ul>
  88. </div>
  89. </div>
  90. </template>