瀏覽代碼

✨ feat: 新增图片裁剪功能

WanGxC 1 年之前
父節點
當前提交
b76662a028

+ 1 - 0
.env.development

@@ -4,6 +4,7 @@ ENV = 'development'
 # 本地环境接口地址
 # VITE_API_URL = 'http://127.0.0.1:8000'
 VITE_API_URL = 'http://192.168.1.225/'
+# VITE_API_URL = 'https://ads.vzzon.com'
 
 # 是否启用按钮权限
 VITE_PM_ENABLED = true

File diff suppressed because it is too large
+ 1 - 10314
package-lock.json


+ 2 - 2
package.json

@@ -20,7 +20,7 @@
 		"autoprefixer": "^10.4.14",
 		"axios": "^1.2.1",
 		"countup.js": "^2.3.2",
-		"cropperjs": "^1.5.13",
+		"cropperjs": "^2.0.0-beta.4",
 		"dayjs": "^1.11.10",
 		"e-icon-picker": "^2.1.1",
 		"echarts": "^5.4.1",
@@ -48,7 +48,7 @@
 		"ts-md5": "^1.3.1",
 		"vue": "^3.2.45",
 		"vue-clipboard3": "^2.0.0",
-		"vue-cropper": "^1.0.8",
+		"vue-cropper": "^1.1.1",
 		"vue-grid-layout": "^3.0.0-beta1",
 		"vue-i18n": "^9.2.2",
 		"vue-router": "^4.1.6",

+ 1 - 1
src/views/adManage/sd/campaigns/CreateCampaigns/component/AdCampaign.vue

@@ -92,7 +92,7 @@ interface campaignRuleForm {
   budget: string
 }
 const campaignRuleForm = reactive<campaignRuleForm>({
-  campaignName: 'AiTestX2024_19',
+  campaignName: 'AiTestX2024_2_26_brandLogo',
   adMix: '',
   startDate: '',
   endDate: '',

+ 35 - 46
src/views/adManage/sd/campaigns/CreateCampaigns/component/Creativity.vue

@@ -11,7 +11,7 @@
           <el-scrollbar height="655px">
             <div class="demo-collapse">
               <el-collapse v-model="activeNames" style="border: none">
-                <el-collapse-item title="徽标" name="logo">
+                <el-collapse-item title="徽标" name="logo" v-loading="pictureLoading">
                   <div style="display: flex; margin-bottom: 5px">
                     <p style="font-weight: 700; color: #1d2129; margin-right: 8px">品牌标识</p>
                     <el-switch v-model="isOpenLogo" size="small" />
@@ -19,7 +19,6 @@
                   <el-upload
                     v-model:file-list="pictureFileList"
                     :on-change="changePicture"
-                    v-loading="pictureLoading"
                     :on-remove="handleRemovePicture"
                     action="#"
                     accept=".png, .jpg"
@@ -68,6 +67,7 @@
                   <el-dialog v-model="dialogVisible">
                     <img w-full :src="dialogImageUrl" alt="Preview Image" />
                   </el-dialog>
+                  <CropperImg />
                 </el-collapse-item>
                 <el-collapse-item title="标题" name="title">
                   <div style="display: flex">
@@ -134,34 +134,7 @@
                   <div style="display: flex; margin-bottom: 5px">
                     <p style="font-weight: 700; color: #1d2129; margin-right: 8px">628x628</p>
                   </div>
-                  <el-upload
-                    v-model:file-list="pictureFileList3"
-                    :on-change="changePicture3"
-                    v-loading="pictureLoading3"
-                    :on-remove="handleRemovePicture3"
-                    action="#"
-                    accept=".png, .jpeg, .gif"
-                    :limit="1"
-                    list-type="picture-card"
-                    :auto-upload="false">
-                    <el-icon><Plus /></el-icon>
-                    <template #file="{ file }">
-                      <div>
-                        <img class="el-upload-list__item-thumbnail" :src="file.url" alt="" />
-                        <span class="el-upload-list__item-actions">
-                          <span class="el-upload-list__item-preview" @click="handlePicturePreview3(file)">
-                            <el-icon><zoom-in /></el-icon>
-                          </span>
-                          <span v-if="!disabled" class="el-upload-list__item-delete" @click="handleRemovePicture3()">
-                            <el-icon><Delete /></el-icon>
-                          </span>
-                        </span>
-                      </div>
-                    </template>
-                  </el-upload>
-                  <el-dialog v-model="dialogVisible3">
-                    <img w-full :src="dialogImageUrl3" alt="Preview Image" />
-                  </el-dialog>
+                  
                 </el-collapse-item>
               </el-collapse>
             </div>
@@ -184,10 +157,12 @@ import { Delete, Plus, ZoomIn } from '@element-plus/icons-vue'
 import type { UploadFile } from 'element-plus'
 import { ElMessage } from 'element-plus'
 import { storeToRefs } from 'pinia'
-import { Ref, inject, onMounted, ref } from 'vue'
+import { Ref, inject, onMounted, onBeforeUnmount, ref } from 'vue'
 import { checkAsset, postCreative, uploadFile } from '../api/index'
 import { useShopInfo } from '/@/stores/shopInfo'
 import emitter from '/@/utils/emitter'
+import CropperImg from '../component/CropperImg.vue'
+
 
 const respAdGroupId = inject<Ref>('respAdGroupId')
 const shopInfo = useShopInfo()
@@ -203,16 +178,33 @@ const disabled = ref(false)
 const dialogVisible = ref(false)
 const dialogImageUrl = ref('')
 const centerDialogVisible = ref(false)
+const logoImg = ref('')
+let imgDimension = {
+  left: 0,
+  top: 0,
+  width: 0,
+  height: 0,
+}
+
+emitter.on('send-logo-leftTop', (value: any) => {
+  imgDimension.left = value.left
+  imgDimension.top = value.top
+  imgDimension.width = value.width
+  imgDimension.height = value.height
+})
 
 function changePicture(file: UploadFile) {
+  emitter.emit('img-src', file.url)
+  logoImg.value = file.url
   const reader = new FileReader()
   reader.readAsDataURL(file.raw)
   reader.onload = (imgInfo: any) => {
     const img = new Image()
     img.src = imgInfo.target.result
     img.onload = () => {
-      if (img.width == 600 && img.height == 100) {
-        handleUpload(file)
+      emitter.emit('img-data', img)
+      if (img.width >= 600 && img.height >= 100) {
+        // handleUpload(file)
       } else {
         pictureFileList.value = []
         ElMessage.error('不符合尺寸要求')
@@ -234,7 +226,6 @@ function handlePictureCardPreview(file: UploadFile) {
 const brandEntityId = ref('')
 let brandLogoAssetID = ''
 let brandLogoAssetVersion = ''
-let brandLogoCrop = {}
 const isOpenLogo = ref(false)
 
 async function handleUpload(file: UploadFile) {
@@ -255,16 +246,10 @@ async function handleUpload(file: UploadFile) {
     }
     const resp = await checkAsset(obj)
     brandLogoAssetID = resp.data.assetId
-    const { width, height } = resp.data.fileMetadata
-    brandLogoCrop = {
-      width,
-      height,
-      top: 0,
-      left: 0,
-    }
     if (resp.data.checkresult == 'success') {
       ElMessage({ message: '上传成功', type: 'success' })
     } else {
+      pictureFileList.value = []
       ElMessage.error('上传失败')
     }
   } catch (error) {
@@ -300,7 +285,7 @@ function changePicture2(file: UploadFile) {
     img.src = imgInfo.target.result
     img.onload = () => {
       if (img.width == 1200 && img.height == 628) {
-        handleUploadPicture(file)
+        handleUploadPicture2(file)
       } else {
         pictureFileList2.value = []
         ElMessage.error('不符合尺寸要求')
@@ -318,13 +303,13 @@ function handlePicturePreview(file: UploadFile) {
   dialogVisible2.value = true
 }
 
-async function handleUploadPicture(file: UploadFile) {
+async function handleUploadPicture2(file: UploadFile) {
   const formData = new FormData()
   formData.append('file', file.raw)
   formData.append('profile_id', profile.value.profile_id)
   formData.append('brandEntityId', brandEntityId.value)
   formData.append('assetType', 'IMAGE')
-  formData.append('assetSubTypeList', JSON.stringify(['LOGO']))
+  formData.append('assetSubTypeList', JSON.stringify(['LIFESTYLE_IMAGE', 'OTHER_IMAGE']))
   pictureLoading2.value = true
   try {
     const response = await uploadFile(formData)
@@ -427,7 +412,7 @@ async function createCreativity() {
     headline: titleInp.value,
     brandLogo_assetId: brandLogoAssetID,
     brandLogo_assetVersion: brandLogoAssetVersion,
-    brandLogo_croppingCoordinates: brandLogoCrop,
+    brandLogo_croppingCoordinates: imgDimension,
     // contentType: '',
     rectCustomImage_assetId: pictureAssetID2,
     rectCustomImage_assetVersion: pictureAssetVersion2,
@@ -436,7 +421,7 @@ async function createCreativity() {
   }
   try {
     const response = await postCreative(obj)
-    if (response.data.creative_state == 'success') {
+    if (response.data.result[0].code == 'SUCCESS') {
       ElMessage({ message: '创建成功', type: 'success' })
     } else {
       ElMessage.error('上传失败')
@@ -453,6 +438,10 @@ onMounted(() => {
     brandEntityId.value = value.brandEntityId[0].brandEntityId
   })
 })
+
+onBeforeUnmount(()=>{
+  emitter.all.clear()
+})
 </script>
 
 <style scoped>

+ 107 - 0
src/views/adManage/sd/campaigns/CreateCampaigns/component/CropperImg.vue

@@ -0,0 +1,107 @@
+<template>
+  <div ref="outContianer" style="width: 100%">
+    <div :style="{ width: containerDimensions.width + 'px', height: containerDimensions.height + 'px' }">
+      <vueCropper
+        ref="cropper"
+        :img="option.img"
+        :canScale="option.canScale"
+        :full="option.full"
+        :outputSize="option.size"
+        :centerBox="option.centerBox"
+        :autoCrop="option.autoCrop"
+        :autoCropWidth="option.autoCropWidth"
+        :autoCropHeight="option.autoCropHeight"
+        :fixedBox="option.fixedBox"
+        :canMove="option.canMove"
+        :mode="option.mode"
+        :info="option.info"
+        @cropMoving="debouncedCropMoving"></vueCropper>
+    </div>
+  </div>
+  <div style="padding-top: 8px;">
+    <el-text class="mx-1">预览:</el-text>
+    <img :src="previewImg" />
+  </div>
+</template>
+
+<script setup>
+import { ref, onBeforeUnmount } from 'vue'
+import 'vue-cropper/dist/index.css'
+import { VueCropper } from 'vue-cropper'
+import emitter from '/@/utils/emitter'
+import _ from 'lodash'
+
+const cropper = ref()
+const containerDimensions = ref({ width: 100, height: 100 })
+
+const option = ref({
+  img: '',
+  size: 1,
+  info: false,
+  autoCrop: true,
+  autoCropWidth: 600,
+  autoCropHeight: 100,
+  canScale: false,
+  full: false,
+  fixedBox: true,
+  centerBox: true,
+  canMove: false,
+  enlarge: 1,
+  mode: 'contain',
+  original: false,
+  outputType: 'png',
+})
+
+// 根据图片大小缩放设置容器大小以及裁剪框大小
+const outContianer = ref()
+let scale = 1
+
+emitter.on('img-src', (value) => {
+  option.value.img = value
+})
+
+emitter.on('img-data', (value) => {
+  option.value.autoCropWidth = 600
+  option.value.autoCropHeight = 100
+
+  if (!(value.width == 600 && value.height == 100)) {
+    scale = Math.min(outContianer.value.offsetWidth / value.width, 1) // 缩放图片宽度到最大不超过容器宽度
+  } else {
+    scale = 1
+  }
+
+  // 计算缩放后的图片尺寸
+  const scaledWidth = value.width * scale
+  const scaledHeight = value.height * scale
+
+  // 更新容器尺寸
+  containerDimensions.value.width = scaledWidth
+  containerDimensions.value.height = scaledHeight
+
+  // 更新裁剪框尺寸
+  option.value.autoCropWidth = option.value.autoCropWidth * scale
+  option.value.autoCropHeight = option.value.autoCropHeight * scale
+})
+
+// 移动裁剪框后获取坐标 以及 渲染裁剪框图片
+const previewImg = ref('')
+
+function cropMoving(data) {
+  const left = parseInt((data.axis.x1 / scale).toFixed(0))  // 结果保留整数
+  const top = parseInt((data.axis.y1 / scale).toFixed(0))
+
+  emitter.emit('send-logo-leftTop', { left: left, top: top, width: 600, height: 100})
+
+  cropper.value.getCropData((data) => {
+    // 获取base64数据图片
+    previewImg.value = data
+  })
+  
+}
+
+const debouncedCropMoving = _.debounce(cropMoving, 100)
+
+onBeforeUnmount(() => {
+  emitter.all.clear()
+})
+</script>

+ 2 - 1
src/views/adManage/sd/campaigns/CreateCampaigns/index.vue

@@ -1,9 +1,10 @@
 <template>
   <div class="page-container">
+    <Creativity></Creativity>
     <AdCampaign @send-campaign="getCampaign" @send-targetType="getTargetType"></AdCampaign>
     <AdGroup @send-groupId="getGroupId"></AdGroup>
     <component :is="currentComponent"></component>
-    <Creativity v-if="product.length !== 0"></Creativity>
+    <!-- <Creativity v-if="product.length !== 0"></Creativity> -->
   </div>
 </template>
 

Some files were not shown because too many files changed in this diff