Ver código fonte

feat:支持多文件上传

pudongliang 4 meses atrás
pai
commit
fea63920b5

+ 3 - 3
.qoder/rules/always_on_rule.md

@@ -9,12 +9,12 @@ trigger: always_on
 前端界面风格参考 portal/src/conversion/index.vue
 带上具体的方法上下文时,如果要进行更改,只改动该方法的行为,其他更改给提醒确认后进行
 如果创建新表的CRUD,请修改java.com.moka.gdtauto.BaseCodeGenerator的TABLE_NAMES后执行其main方法
-不要读取resources目录下的配置文件,需要给出提示
+不要读取resources目录下的配置文件,需要时给出确认提示
 不要读取target, node_modules, dist目录下的内容
-不要读取.gitignore中提到的目录与文件    
+不要读取.gitignore中提到的目录与文件
 
 
-CRUD代码参考
+# CRUD代码参考
 ## controller
 参考 com.moka.gdtauto.controller.OssFileController
 

+ 1 - 1
portal/dist/assets/accountGroup-CsV7PgBZ.js → portal/dist/assets/accountGroup-BzAGj6fn.js

@@ -1 +1 @@
-import{r as u}from"./request-94g2Q2zR.js";function c(t){return u({url:"/api/account-group/create",method:"post",data:t})}function e(t){return u({url:"/api/account-group/update",method:"post",data:t})}function n(t){return u({url:`/api/account-group/${t}`,method:"delete"})}function a(t=1,o=10){return u({url:"/api/account-group/list",method:"get",params:{pageNum:t,pageSize:o}})}function p(){return u({url:"/api/account-group/all",method:"get"})}function i(t){return u({url:`/api/account-group/${t}/accounts`,method:"get"})}export{i as a,p as b,c,n as d,a as g,e as u};
+import{r as u}from"./request-Dj2m30gK.js";function c(t){return u({url:"/api/account-group/create",method:"post",data:t})}function e(t){return u({url:"/api/account-group/update",method:"post",data:t})}function n(t){return u({url:`/api/account-group/${t}`,method:"delete"})}function a(t=1,o=10){return u({url:"/api/account-group/list",method:"get",params:{pageNum:t,pageSize:o}})}function p(){return u({url:"/api/account-group/all",method:"get"})}function i(t){return u({url:`/api/account-group/${t}/accounts`,method:"get"})}export{i as a,p as b,c,n as d,a as g,e as u};

+ 1 - 1
portal/dist/assets/adPlanConf-CfESW9H5.js → portal/dist/assets/adPlanConf-DA-fk5eY.js

@@ -1 +1 @@
-import{r as a}from"./request-94g2Q2zR.js";function r(n){return a({url:"/api/ad-plan-conf/reCreate",method:"post",data:n})}function o(n){return a({url:"/api/ad-plan-conf/generate-plan",method:"post",data:n})}function l(n){return a({url:`/api/ad-plan-conf/reGeneratePlan/${n}`,method:"get"})}function u(n){return a({url:`/api/ad-plan-conf/syncSystemStatus/${n}`,method:"get"})}function d(n){return a({url:"/api/ad-plan-conf/create",method:"post",data:n})}function p(n){return a({url:"/api/ad-plan-conf/update",method:"post",data:n})}function i(n){return a({url:`/api/ad-plan-conf/${n}`,method:"delete"})}function s(n=1,e=10){return a({url:"/api/ad-plan-conf/list",method:"get",params:{pageNum:n,pageSize:e}})}function f(n){return a({url:`/api/ad-plan-conf/${n}`,method:"get"})}function c(n){return a({url:"/api/ad-plan/list",method:"get",params:n})}function m(n){return a({url:`/api/ad-plan-creative/plan/${n}`,method:"get"})}export{o as a,f as b,d as c,i as d,c as e,l as f,s as g,m as h,r,u as s,p as u};
+import{r as a}from"./request-Dj2m30gK.js";function r(n){return a({url:"/api/ad-plan-conf/reCreate",method:"post",data:n})}function o(n){return a({url:"/api/ad-plan-conf/generate-plan",method:"post",data:n})}function l(n){return a({url:`/api/ad-plan-conf/reGeneratePlan/${n}`,method:"get"})}function u(n){return a({url:`/api/ad-plan-conf/syncSystemStatus/${n}`,method:"get"})}function d(n){return a({url:"/api/ad-plan-conf/create",method:"post",data:n})}function p(n){return a({url:"/api/ad-plan-conf/update",method:"post",data:n})}function i(n){return a({url:`/api/ad-plan-conf/${n}`,method:"delete"})}function s(n=1,e=10){return a({url:"/api/ad-plan-conf/list",method:"get",params:{pageNum:n,pageSize:e}})}function f(n){return a({url:`/api/ad-plan-conf/${n}`,method:"get"})}function c(n){return a({url:"/api/ad-plan/list",method:"get",params:n})}function m(n){return a({url:`/api/ad-plan-creative/plan/${n}`,method:"get"})}export{o as a,f as b,d as c,i as d,c as e,l as f,s as g,m as h,r,u as s,p as u};

+ 1 - 1
portal/dist/assets/auth-PPO_Q_Ws.js → portal/dist/assets/auth-DAVaQXGm.js

@@ -1 +1 @@
-import{r as t}from"./request-94g2Q2zR.js";function i(){return t({url:"/api/gdt/auth/authorize-url",method:"get"})}function n(r){return t({url:"/api/gdt/auth/refresh",method:"post",params:r})}function u(){return t({url:"/api/gdt/auth/list",method:"get"})}function a(){return u()}function o(){return t({url:"/api/gdt/auth/expiring",method:"get"})}export{o as a,a as b,i as g,u as l,n as r};
+import{r as t}from"./request-Dj2m30gK.js";function i(){return t({url:"/api/gdt/auth/authorize-url",method:"get"})}function n(r){return t({url:"/api/gdt/auth/refresh",method:"post",params:r})}function u(){return t({url:"/api/gdt/auth/list",method:"get"})}function a(){return u()}function o(){return t({url:"/api/gdt/auth/expiring",method:"get"})}export{o as a,a as b,i as g,u as l,n as r};

+ 1 - 1
portal/dist/assets/conversion-DYiN6umm.js → portal/dist/assets/conversion-BOLK9Y_d.js

@@ -1 +1 @@
-import{r as t}from"./request-94g2Q2zR.js";function e(n,o){return t({url:"/api/gdt/conversion/sync",method:"post",params:{orgAccountId:n,accountId:o}})}function s(n=1,o=20){return t({url:"/api/gdt/conversion/list",method:"get",params:{page:n,pageSize:o}})}function i(n){return t({url:"/api/gdt/conversion/detail",method:"get",params:{conversionId:n}})}function a(){return t({url:"/api/gdt/conversion/all",method:"get"})}export{s as a,i as b,a as g,e as s};
+import{r as t}from"./request-Dj2m30gK.js";function e(n,o){return t({url:"/api/gdt/conversion/sync",method:"post",params:{orgAccountId:n,accountId:o}})}function s(n=1,o=20){return t({url:"/api/gdt/conversion/list",method:"get",params:{page:n,pageSize:o}})}function i(n){return t({url:"/api/gdt/conversion/detail",method:"get",params:{conversionId:n}})}function a(){return t({url:"/api/gdt/conversion/all",method:"get"})}export{s as a,i as b,a as g,e as s};

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
portal/dist/assets/detail-3ik5T0l5.js


+ 1 - 1
portal/dist/assets/drama-k1LdRq3L.js → portal/dist/assets/drama-ZEX6Zd-a.js

@@ -1 +1 @@
-import{r as t}from"./request-94g2Q2zR.js";function n(a){return t({url:"/api/gdt/asset/drama/sync",method:"post",params:{accountId:a}})}function m(a=1,s=20,r=""){return t({url:"/api/gdt/asset/drama/list",method:"get",params:{dramaName:r,page:a,pageSize:s}})}function d(a){return t({url:"/api/gdt/asset/drama/myDramaAssets",method:"get",params:{dramaName:a}})}function o(){return t({url:"/api/gdt/asset/drama/sync/conf/myConf",method:"get"})}function u(a){return t({url:"/api/gdt/asset/drama/sync/conf/add",method:"post",data:a})}function i(a){return t({url:"/api/gdt/asset/drama/sync/conf/delete",method:"post",params:{id:a}})}export{o as a,u as b,i as d,m as g,d as m,n as s};
+import{r as t}from"./request-Dj2m30gK.js";function n(a){return t({url:"/api/gdt/asset/drama/sync",method:"post",params:{accountId:a}})}function m(a=1,s=20,r=""){return t({url:"/api/gdt/asset/drama/list",method:"get",params:{dramaName:r,page:a,pageSize:s}})}function d(a){return t({url:"/api/gdt/asset/drama/myDramaAssets",method:"get",params:{dramaName:a}})}function o(){return t({url:"/api/gdt/asset/drama/sync/conf/myConf",method:"get"})}function u(a){return t({url:"/api/gdt/asset/drama/sync/conf/add",method:"post",data:a})}function i(a){return t({url:"/api/gdt/asset/drama/sync/conf/delete",method:"post",params:{id:a}})}export{o as a,u as b,i as d,m as g,d as m,n as s};

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
portal/dist/assets/index-B4B_YKUb.js


Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
portal/dist/assets/index-BE2ZoE-o.js


Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 1
portal/dist/assets/index-CKvrzot2.js


Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
portal/dist/assets/index-ChRpP0We.js


+ 0 - 1
portal/dist/assets/index-D2u6GVqk.css

@@ -1 +0,0 @@
-.btn-primary[data-v-fe12f8a1]{background:linear-gradient(135deg,#667eea,#764ba2);border:none;color:#fff}.btn-primary[data-v-fe12f8a1]:hover{background:linear-gradient(135deg,#764ba2,#667eea)}.btn-secondary[data-v-fe12f8a1]{border-color:#e5e7eb;color:#6b7280}.btn-secondary[data-v-fe12f8a1]:hover{background-color:#f9fafb}[data-v-fe12f8a1] .el-upload-dragger{border:2px dashed #d1d5db;border-radius:8px;background-color:#f9fafb;transition:all .3s}[data-v-fe12f8a1] .el-upload-dragger:hover{border-color:#667eea;background-color:#f3f4f6}

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
portal/dist/assets/index-DAdhKd4k.js


Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
portal/dist/assets/index-DE36DMkY.js


Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
portal/dist/assets/index-DEWRnCl6.js


Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
portal/dist/assets/index-Dnorhycp.js


+ 1 - 0
portal/dist/assets/index-DuXhWDX2.css

@@ -0,0 +1 @@
+.btn-primary[data-v-d6211b6e]{background:linear-gradient(135deg,#667eea,#764ba2);border:none;color:#fff}.btn-primary[data-v-d6211b6e]:hover{background:linear-gradient(135deg,#764ba2,#667eea)}.btn-secondary[data-v-d6211b6e]{border-color:#e5e7eb;color:#6b7280}.btn-secondary[data-v-d6211b6e]:hover{background-color:#f9fafb}[data-v-d6211b6e] .el-upload-dragger{border:2px dashed #d1d5db;border-radius:8px;background-color:#f9fafb;transition:all .3s}[data-v-d6211b6e] .el-upload-dragger:hover{border-color:#667eea;background-color:#f3f4f6}

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
portal/dist/assets/index-IyrB9eOV.js


Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
portal/dist/assets/index-N7t_qe4j.js


Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
portal/dist/assets/index-_szYEjDS.js


Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
portal/dist/assets/index-faECyidg.js


Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
portal/dist/assets/index-laslM0VB.js


+ 1 - 1
portal/dist/assets/ossFile-B9lWIgeT.js → portal/dist/assets/ossFile-BGmv9sY9.js

@@ -1 +1 @@
-import{r as t}from"./request-94g2Q2zR.js";function i(e){return t({timeout:3e5,url:"/api/oss-file/upload",method:"post",data:e,headers:{"Content-Type":"multipart/form-data"}})}function o(e){return t({url:"/api/oss-file/list",method:"get",params:e})}function r(e){return t({url:`/api/oss-file/${e}`,method:"delete"})}function l(e){return t({url:"/api/oss-file/batch",method:"delete",params:{fileIds:e}})}export{l as b,r as d,o as g,i as u};
+import{r as t}from"./request-Dj2m30gK.js";function i(e){return t({timeout:3e5,url:"/api/oss-file/upload",method:"post",data:e,headers:{"Content-Type":"multipart/form-data"}})}function o(e){return t({url:"/api/oss-file/list",method:"get",params:e})}function r(e){return t({url:`/api/oss-file/${e}`,method:"delete"})}function l(e){return t({url:"/api/oss-file/batch",method:"delete",params:{fileIds:e}})}export{l as b,r as d,o as g,i as u};

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
portal/dist/assets/request-Dj2m30gK.js


+ 1 - 1
portal/dist/assets/user-DCZ_sCvl.js → portal/dist/assets/user-DofYv8ML.js

@@ -1 +1 @@
-import{r as e}from"./request-94g2Q2zR.js";function s(){return e({url:"/api/user/list",method:"get"})}function u(r){return e({url:"/api/user/info",method:"get",params:r})}function n(r){return e({url:"/api/user/user-token-url",method:"get",params:r})}function o(r){return e({url:"/api/user/login",method:"post",params:r})}function a(r){return e({url:"/api/user/change-password",method:"post",params:r})}export{u as a,s as b,a as c,n as g,o as l};
+import{r as e}from"./request-Dj2m30gK.js";function s(){return e({url:"/api/user/list",method:"get"})}function u(r){return e({url:"/api/user/info",method:"get",params:r})}function n(r){return e({url:"/api/user/user-token-url",method:"get",params:r})}function o(r){return e({url:"/api/user/login",method:"post",params:r})}function a(r){return e({url:"/api/user/change-password",method:"post",params:r})}export{u as a,s as b,a as c,n as g,o as l};

+ 1 - 1
portal/dist/index.html

@@ -5,7 +5,7 @@
     <link rel="icon" type="image/svg+xml" href="/vite.svg" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title>腾讯广告自动化管理后台</title>
-    <script type="module" crossorigin src="/assets/index-HWWOxLkr.js"></script>
+    <script type="module" crossorigin src="/assets/index-CKvrzot2.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-CdDwsIxv.css">
   </head>
   <body>

+ 81 - 37
portal/src/views/oss-file/index.vue

@@ -31,8 +31,11 @@
           <el-upload
             ref="uploadRef"
             :auto-upload="false"
-            :limit="1"
+            :limit="20"
+            multiple
+            :accept="fileAccept"
             :on-change="handleFileChange"
+            :on-remove="handleFileRemove"
             :on-exceed="handleExceed"
             :file-list="uploadFileList"
             drag>
@@ -42,7 +45,7 @@
             </div>
             <template #tip>
               <div class="el-upload__tip">
-                支持各种文件格式,建议文件大小不超过100MB
+                {{ uploadForm.fileType === 'image' ? '支持 jpg/png/gif/webp 格式图片' : '支持 mp4/mov/avi 格式视频' }},最多20个文件,单个文件建议不超过100MB
               </div>
             </template>
           </el-upload>
@@ -199,11 +202,21 @@
 </template>
 
 <script setup>
-import { ref, reactive, onMounted } from 'vue'
+import { ref, reactive, computed, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { UploadFilled } from '@element-plus/icons-vue'
 import { uploadFile, getFileList, deleteFile, batchDeleteFiles } from '@/api/ossFile'
 
+// 上传表单
+const uploadFormRef = ref(null)
+const uploadRef = ref(null)
+const uploadForm = reactive({
+  businessType: 1,
+  fileType: 'image',
+  folder: 'images/',
+  files: []
+})
+
 const handleBusinessTypeChange = (value) => {
   uploadForm.businessType = value
   // 品牌形象只能选图片
@@ -211,6 +224,8 @@ const handleBusinessTypeChange = (value) => {
     uploadForm.fileType = 'image'
     uploadForm.folder = 'images/'
   }
+  // 切换业务类型时清空已选文件
+  clearSelectedFiles()
 }
 
 const handleFileTypeChange = (value) => {
@@ -220,16 +235,16 @@ const handleFileTypeChange = (value) => {
   } else if (value === 'video') {
     uploadForm.folder = 'videos/'
   }
+  // 切换文件类型时清空已选文件
+  clearSelectedFiles()
+}
+
+// 清空已选文件
+const clearSelectedFiles = () => {
+  uploadRef.value?.clearFiles()
+  uploadFileList.value = []
+  uploadForm.files = []
 }
-// 上传表单
-const uploadFormRef = ref(null)
-const uploadRef = ref(null)
-const uploadForm = reactive({
-  businessType: 1,
-  fileType: 'image',
-  folder: 'images/',
-  file: null
-})
 
 const uploadRules = {
   businessType: [{ required: true, message: '请选择业务分类', trigger: 'change' }],
@@ -240,6 +255,16 @@ const uploadRules = {
 const uploadFileList = ref([])
 const uploading = ref(false)
 
+// 根据文件类型计算接受的文件格式
+const fileAccept = computed(() => {
+  if (uploadForm.fileType === 'image') {
+    return 'image/jpeg,image/png,image/gif,image/webp'
+  } else if (uploadForm.fileType === 'video') {
+    return 'video/mp4,video/quicktime,video/x-msvideo'
+  }
+  return ''
+})
+
 // 查询表单
 const queryForm = reactive({
   businessType: null,
@@ -264,13 +289,17 @@ const previewVisible = ref(false)
 const previewUrl = ref('')
 
 // 文件选择
-const handleFileChange = (file) => {
-  uploadForm.file = file.raw
-  uploadFileList.value = [file]
+const handleFileChange = (file, fileList) => {
+  uploadForm.files = fileList.map(f => f.raw)
+}
+
+// 文件移除
+const handleFileRemove = (file, fileList) => {
+  uploadForm.files = fileList.map(f => f.raw)
 }
 
 const handleExceed = () => {
-  ElMessage.warning('只能上传一个文件')
+  ElMessage.warning('最多只能上传20个文件')
 }
 
 // 提交上传
@@ -280,32 +309,47 @@ const submitUpload = async () => {
   await uploadFormRef.value.validate(async (valid) => {
     if (!valid) return
     
-    if (!uploadForm.file) {
+    if (!uploadForm.files || uploadForm.files.length === 0) {
       ElMessage.warning('请选择文件')
       return
     }
 
     uploading.value = true
-    const formData = new FormData()
-    formData.append('file', uploadForm.file)
-    formData.append('folder', uploadForm.folder)
-    formData.append('fileType', uploadForm.fileType)
-    formData.append('businessType', uploadForm.businessType)
-
-    try {
-      const res = await uploadFile(formData)
-      if (res.success) {
-        ElMessage.success('文件上传成功')
-        resetForm()
-        loadFileList()
-      } else {
-        ElMessage.error(res.message || '上传失败')
+    let successCount = 0
+    let failCount = 0
+
+    for (const file of uploadForm.files) {
+      const formData = new FormData()
+      formData.append('file', file)
+      formData.append('folder', uploadForm.folder)
+      formData.append('fileType', uploadForm.fileType)
+      formData.append('businessType', uploadForm.businessType)
+
+      try {
+        const res = await uploadFile(formData)
+        if (res.success) {
+          successCount++
+        } else {
+          failCount++
+        }
+      } catch (error) {
+        console.error('上传失败:', error)
+        failCount++
       }
-    } catch (error) {
-      console.error('上传失败:', error)
-      ElMessage.error('上传失败: ' + (error.message || '未知错误'))
-    } finally {
-      uploading.value = false
+    }
+
+    uploading.value = false
+
+    if (successCount > 0 && failCount === 0) {
+      ElMessage.success(`${successCount}个文件上传成功`)
+      resetForm()
+      loadFileList()
+    } else if (successCount > 0 && failCount > 0) {
+      ElMessage.warning(`上传完成:${successCount}个成功,${failCount}个失败`)
+      resetForm()
+      loadFileList()
+    } else {
+      ElMessage.error('所有文件上传失败')
     }
   })
 }
@@ -315,7 +359,7 @@ const resetForm = () => {
   uploadFormRef.value?.resetFields()
   uploadRef.value?.clearFiles()
   uploadFileList.value = []
-  uploadForm.file = null
+  uploadForm.files = []
 }
 
 // 加载文件列表

+ 2 - 2
portal/vite.config.js

@@ -14,8 +14,8 @@ export default defineConfig({
     port: 5173,
     proxy: {
       '/api': {
-        target: 'http://localhost:8080',
-        // target: 'https://ad-auto.mokamrp.com/api',
+        //target: 'http://localhost:8080',
+        target: 'https://ad-auto.mokamrp.com/api',
         changeOrigin: true,
         rewrite: (path) => path.replace(/^\/api/, '')
       }

+ 4 - 0
src/main/resources/application.yml

@@ -2,6 +2,10 @@ spring:
   application:
     name: gdt-auto
     version: v1.0.0
+  servlet:
+    multipart:
+      max-file-size: 100MB
+      max-request-size: 100MB
   profiles:
     active: test
   

+ 26 - 0
src/test/java/com/moka/gdtauto/service/CreateAdGroupTest.java

@@ -0,0 +1,26 @@
+package com.moka.gdtauto.service;
+
+import com.moka.gdtauto.GdtAutoApplication;
+import com.moka.gdtauto.entity.AdPlan;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ActiveProfiles;
+
+import java.util.List;
+
+@SpringBootTest(classes = GdtAutoApplication.class)
+@ActiveProfiles("test")
+public class CreateAdGroupTest {
+    
+    @Autowired
+    private AdPlanConfService adPlanConfService;
+    @Autowired
+    private AdPlanService adPlanService;
+    
+    @Test
+    public void createAdGroup() {
+        AdPlan  adPlan = adPlanService.getById(126);
+        adPlanConfService.asyncTencentInterface(List.of(adPlan));
+    }
+}

+ 11 - 0
src/test/java/com/moka/gdtauto/util/SampleTest.java

@@ -1,5 +1,8 @@
 package com.moka.gdtauto.util;
 
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+
 import org.junit.jupiter.api.Test;
 import org.springframework.util.DigestUtils;
 import com.alibaba.fastjson.JSON;
@@ -15,6 +18,14 @@ public class SampleTest {
     }
 
     @Test
+    public void test2() {
+        BigDecimal bigDecimal = new BigDecimal("100.00");
+        System.out.println(bigDecimal);
+        double value = bigDecimal.setScale(2, RoundingMode.HALF_UP).doubleValue();
+        System.out.println(value);
+    }
+
+    @Test
     public void Test() {
         WriteTargetingSetting targetingSetting = TargetConfUtil.getTargetingSetting(1);
         System.out.println(JSON.toJSONString(targetingSetting, true));

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff