CutImageDialog.vue 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. <template>
  2. <a-modal
  3. v-model:open="visible"
  4. width="100%"
  5. :footer="false"
  6. :closable="false"
  7. :maskClosable="false"
  8. wrapClassName="cut-image-dialog full-modal"
  9. >
  10. <div ref="imgContainRef" class="cut-image">
  11. <div v-if="visible" class="cut-image-body" :style="imageStyle">
  12. <img
  13. ref="imgRef"
  14. :src="getFileUrl(sheetUrl)"
  15. alt="原图"
  16. @load="initImageSize"
  17. />
  18. <element-resize
  19. v-if="selection.w"
  20. v-model="selection"
  21. class="element-resize-act"
  22. :active="['r', 'rb', 'b', 'lb', 'l', 'lt', 't', 'rt']"
  23. >
  24. <div class="image-selection" :style="selectionStyle"></div>
  25. </element-resize>
  26. </div>
  27. <div class="cut-image-action">
  28. <div class="cut-close" @click="close"><CloseOutlined /></div>
  29. <div class="cut-save" @click="confirm"><SaveOutlined /></div>
  30. </div>
  31. </div>
  32. </a-modal>
  33. </template>
  34. <script setup lang="ts">
  35. import { computed, ref, watch } from "vue";
  36. import { SaveOutlined, CloseOutlined } from "@ant-design/icons-vue";
  37. import useModal from "@/hooks/useModal";
  38. import { getFileUrl, objAssign, getBoxImageSize } from "@/utils/tool";
  39. import ElementResize from "@/components/ElementResize/index.vue";
  40. defineOptions({
  41. name: "CutImageDialog",
  42. });
  43. /* modal */
  44. const { visible, open, close } = useModal();
  45. defineExpose({ open, close });
  46. const props = defineProps<{
  47. sheetUrl: string;
  48. sliceSelection?: AreaSize;
  49. }>();
  50. const emit = defineEmits(["confirm"]);
  51. const initSelection = {
  52. w: 0,
  53. h: 0,
  54. x: 0,
  55. y: 0,
  56. };
  57. const originImgRef = ref();
  58. const curCroppper = ref();
  59. const showCanvas = ref(false);
  60. const selection = ref({
  61. ...initSelection,
  62. });
  63. const selectionStyle = computed(() => {
  64. return {
  65. width: `${selection.value.w}px`,
  66. height: `${selection.value.h}px`,
  67. top: `${selection.value.y}px`,
  68. left: `${selection.value.x}px`,
  69. };
  70. });
  71. const imageSize = ref({
  72. width: 0,
  73. height: 0,
  74. left: 0,
  75. top: 0,
  76. });
  77. const imageStyle = computed(() => {
  78. return {
  79. width: `${imageSize.value.width}px`,
  80. height: `${imageSize.value.height}px`,
  81. top: `${imageSize.value.top}px`,
  82. left: `${imageSize.value.left}px`,
  83. };
  84. });
  85. const imgContainRef = ref();
  86. const imgRef = ref();
  87. function initImageSize() {
  88. const imgDom = imgRef.value as HTMLImageElement;
  89. const elDom = imgContainRef.value as HTMLDivElement;
  90. const imgSize = getBoxImageSize({
  91. box: {
  92. width: elDom.clientWidth,
  93. height: elDom.clientHeight,
  94. },
  95. img: {
  96. width: imgDom.naturalWidth,
  97. height: imgDom.naturalHeight,
  98. },
  99. rotate: 0,
  100. });
  101. imageSize.value = objAssign(imageSize.value, imgSize);
  102. if (!props.sliceSelection) return;
  103. const rate = imgDom.naturalWidth / imageSize.value.width;
  104. selection.value = {
  105. x: (props.sliceSelection.x * imgDom.naturalWidth) / rate,
  106. y: (props.sliceSelection.y * imgDom.naturalHeight) / rate,
  107. w: (props.sliceSelection.w * imgDom.naturalWidth) / rate,
  108. h: (props.sliceSelection.h * imgDom.naturalHeight) / rate,
  109. };
  110. }
  111. async function confirm() {
  112. const imgDom = imgRef.value as HTMLImageElement;
  113. const rate = imageSize.value.width / imgDom.naturalWidth;
  114. const selectionArea: AreaSize = {
  115. x: selection.value.x / rate,
  116. y: selection.value.y / rate,
  117. w: selection.value.w / rate,
  118. h: selection.value.h / rate,
  119. };
  120. const file = await getSliceImage(imgDom, selectionArea).catch((e) => {
  121. console.error(e);
  122. });
  123. if (!file) return;
  124. emit("confirm", file);
  125. close();
  126. }
  127. function getSliceImage(
  128. imgDom: HTMLImageElement,
  129. area: AreaSize
  130. ): Promise<File> {
  131. return new Promise((resolve, reject) => {
  132. const canvas = document.createElement("canvas");
  133. const ctx = canvas.getContext("2d");
  134. if (!ctx) return reject(new Error("不支持canvas"));
  135. canvas.width = area.w;
  136. canvas.height = area.h;
  137. ctx.drawImage(
  138. imgDom,
  139. area.x,
  140. area.y,
  141. area.w,
  142. area.h,
  143. 0,
  144. 0,
  145. canvas.width,
  146. canvas.height
  147. );
  148. canvas.toBlob((blob) => {
  149. if (blob) {
  150. resolve(new File([blob], "slice.png", { type: "image/png" }));
  151. } else {
  152. reject(new Error("构建文件失败"));
  153. }
  154. });
  155. });
  156. }
  157. // init
  158. watch(
  159. () => visible.value,
  160. (val) => {
  161. if (!val) {
  162. selection.value = {
  163. ...initSelection,
  164. };
  165. }
  166. },
  167. {
  168. immediate: true,
  169. }
  170. );
  171. </script>