Prechádzať zdrojové kódy

feat: 新增ImageCropping

kailong321200875 1 rok pred
rodič
commit
b0a43a70e6

+ 1 - 0
package.json

@@ -35,6 +35,7 @@
     "@zxcvbn-ts/core": "^3.0.4",
     "animate.css": "^4.1.1",
     "axios": "^1.6.0",
+    "cropperjs": "^1.6.1",
     "dayjs": "^1.11.10",
     "driver.js": "^1.3.0",
     "echarts": "^5.4.3",

+ 69 - 55
src/components/ImageCropping/src/ImageCropping.vue

@@ -1,76 +1,90 @@
 <script setup lang="ts">
 import { useDesign } from '@/hooks/web/useDesign'
-import { propTypes } from '@/utils/propTypes'
-import { CSSProperties, computed } from 'vue'
+import { nextTick, unref, ref, watch, onBeforeUnmount, onMounted } from 'vue'
+import Cropper from 'cropperjs'
+import 'cropperjs/dist/cropper.min.css'
+
+// interface CropperOptions extends /* @vue-ignore */ Cropper.Options {
+//   imageUrl: string
+// }
 
 const { getPrefixCls } = useDesign()
 
 const prefixCls = getPrefixCls('image-cropping')
 
-const bgIcon =
-  'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC'
-
 const props = defineProps({
-  imageUrl: propTypes.string,
-  boxWidth: propTypes.oneOfType([propTypes.number, propTypes.string]).def('100%'),
-  boxHeight: propTypes.oneOfType([propTypes.number, propTypes.string]).def('100%'),
-  dragWidth: propTypes.oneOfType([propTypes.number, propTypes.string]).def(200),
-  dragHeight: propTypes.oneOfType([propTypes.number, propTypes.string]).def(200),
-  cropWidth: propTypes.oneOfType([propTypes.number, propTypes.string]).def(200),
-  cropHeight: propTypes.oneOfType([propTypes.number, propTypes.string]).def(200)
+  imageUrl: {
+    type: String,
+    default: '',
+    required: true
+  },
+  cropBoxWidth: {
+    type: Number,
+    default: 200
+  },
+  cropBoxHeight: {
+    type: Number,
+    default: 200
+  }
 })
 
-const boxStyles = computed((): CSSProperties => {
-  return {
-    width: (typeof props.boxWidth === 'number' ? `${props.boxWidth}px` : props.boxWidth) ?? '100%',
-    height:
-      (typeof props.boxHeight === 'number' ? `${props.boxHeight}px` : props.boxHeight) ?? '100%',
-    position: 'relative',
-    backgroundImage: `url(${bgIcon})`
-  }
+const imgRef = ref<HTMLImageElement>()
+const cropperRef = ref<Cropper>()
+const intiCropper = () => {
+  if (!unref(imgRef)) return
+  const imgEl = unref(imgRef)!
+  cropperRef.value = new Cropper(imgEl, {
+    aspectRatio: 1,
+    viewMode: 1,
+    dragMode: 'move',
+    cropBoxResizable: false,
+    cropBoxMovable: false,
+    toggleDragModeOnDblclick: false,
+    checkCrossOrigin: false,
+    ready() {
+      const containerData = unref(cropperRef)?.getContainerData()
+      unref(cropperRef)?.setCropBoxData({
+        width: props.cropBoxWidth,
+        height: props.cropBoxHeight,
+        left: (containerData?.width || 0) / 2 - 100,
+        top: (containerData?.height || 0) / 2 - 100
+      })
+    }
+  })
+}
+
+onMounted(() => {
+  intiCropper()
 })
 
-const dragStyles = computed((): CSSProperties => {
-  return {
-    width: (typeof props.dragWidth === 'number' ? `${props.dragWidth}px` : props.dragWidth) ?? 200,
-    height:
-      (typeof props.dragHeight === 'number' ? `${props.dragHeight}px` : props.dragHeight) ?? 200,
-    position: 'absolute',
-    top: '50%',
-    left: '50%',
-    transform: 'translate(-50%, -50%)',
-    zIndex: 1,
-    boxShadow: '0 0 0 1px var(--el-color-primary),0 0 0 10000px rgba(0,0,0,.5)',
-    cursor: 'move'
+watch(
+  () => props.imageUrl,
+  async (url) => {
+    await nextTick()
+    if (url) {
+      unref(cropperRef)?.replace(url)
+    }
   }
+)
+
+onBeforeUnmount(() => {
+  unref(cropperRef)?.destroy()
 })
 
-const cropStyles = computed((): CSSProperties => {
-  return {
-    width: (typeof props.cropWidth === 'number' ? `${props.cropWidth}px` : props.cropWidth) ?? 300,
-    height:
-      (typeof props.cropHeight === 'number' ? `${props.cropHeight}px` : props.cropHeight) ?? 300,
-    position: 'absolute',
-    top: '50%',
-    left: '80px',
-    transform: 'translate(0, -50%)',
-    overflow: 'hidden',
-    borderRadius: '50%',
-    border: '1px solid var(--el-border-color)'
-  }
+defineExpose({
+  cropperExpose: () => unref(cropperRef)
 })
 </script>
 
 <template>
-  <div :class="prefixCls" class="flex">
-    <div class="flex-1">
-      <div :style="boxStyles">
-        <img :src="imageUrl" class="w-full absolute top-[50%] left-[50%]" alt="" srcset="" />
-        <div :style="dragStyles"> </div>
-      </div>
-    </div>
-    <div class="relative w-full">
-      <div :style="cropStyles"></div>
-    </div>
+  <div :class="prefixCls" class="flex justify-center items-center">
+    <img
+      ref="imgRef"
+      :src="imageUrl"
+      class="block max-w-full"
+      crossorigin="anonymous"
+      alt=""
+      srcset=""
+    />
   </div>
 </template>

+ 14 - 3
src/views/Components/ImageCropping.vue

@@ -1,14 +1,25 @@
 <script setup lang="ts">
 import { ContentWrap } from '@/components/ContentWrap'
 import { ImageCropping } from '@/components/ImageCropping'
+import { ref, unref } from 'vue'
+import { ElButton, ElInput } from 'element-plus'
+
+const cropperExpose = ref<InstanceType<typeof ImageCropping>>()
+
+const base64 = ref('')
+
+const getBase64 = () => {
+  base64.value = unref(cropperExpose)?.cropperExpose()?.getCroppedCanvas()?.toDataURL() ?? ''
+}
 </script>
 
 <template>
   <ContentWrap title="图片裁剪">
+    <ElButton type="primary" class="mb-20px" @click="getBase64">裁剪</ElButton>
+    <ElInput v-model="base64" class="mb-20px" type="textarea" />
     <ImageCropping
-      :box-width="350"
-      :box-height="380"
-      image-url="https://images6.alphacoders.com/657/thumbbig-657194.webp"
+      ref="cropperExpose"
+      image-url="https://hips.hearstapps.com/hmg-prod/images/%E5%AE%8B%E6%99%BA%E5%AD%9D-1597774015.jpg?crop=0.500xw:1.00xh;0.500xw,0&resize=640:*"
     />
   </ContentWrap>
 </template>

+ 2 - 1
vite.config.ts

@@ -152,7 +152,8 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
         'vue-json-pretty',
         '@zxcvbn-ts/core',
         'dayjs',
-        'mockjs'
+        'mockjs',
+        'cropperjs'
       ]
     }
   }