Procházet zdrojové kódy

Merge remote-tracking branch 'origin/master'

YPZ před 1 hodinou
rodič
revize
81eb0a9e98

+ 2 - 0
java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/properties/UrlChainDefinitionPorperties.java

@@ -73,6 +73,8 @@ public class UrlChainDefinitionPorperties {
 			"/sys/menu/update_menu_icon",
 			"/mail/file/images",
 			"/mail/file/cache_image_file",
+			"/mkt/email/file/images",
+			"/mkt/email/file/cache_image_file",
 			"/mail/import_mail_eml",
 			"/mail/import_mail_eml_zip",
 			"/sys/test/receive_mail_test",

+ 77 - 0
java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/mkt/MktAttachmentApiController.java

@@ -0,0 +1,77 @@
+package com.storlead.mail.controller.mkt;
+
+import com.storlead.framework.common.result.Result;
+import com.storlead.mail.webclient.client.MktMailFileRemoteClient;
+import com.storlead.mail.webclient.support.MailRemoteResponseWriter;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 营销邮件附件(路径前缀 /mkt/email/file,能力对齐 CRM {@code MaiAttachmentApiController})。
+ */
+@RestController
+@RequestMapping("/mkt/email/file")
+@Api(tags = "营销邮件: 附件")
+@Log4j2
+public class MktAttachmentApiController {
+
+    @Resource
+    private MktMailFileRemoteClient mktMailFileRemoteClient;
+
+    @PostMapping("/cache_mail_file")
+    @ApiOperation("缓存营销邮件临时附件(scene=MKT)")
+    public Result<?> cacheMailFile(@RequestParam("files") List<MultipartFile> files,
+                                   @RequestParam("accountId") Long accountId) {
+        return mktMailFileRemoteClient.cacheMailFile(files, accountId);
+    }
+
+    @PostMapping("/cache_image_file")
+    @ApiOperation("缓存营销邮件正文图片")
+    public Map<String, Object> cacheImageFile(@RequestParam("upload") MultipartFile file,
+                                              @RequestParam(value = "accountId", required = false) Long accountId) {
+        return mktMailFileRemoteClient.cacheImageFile(file, accountId);
+    }
+
+    @PostMapping("/download_mail_file")
+    @ApiOperation("下载营销临时附件")
+    public void downLoadMailFile(Long fileId, HttpServletResponse response) {
+        MailRemoteResponseWriter.write(response, mktMailFileRemoteClient.downloadTempFile(fileId));
+    }
+
+    @GetMapping("/images/{imageName}")
+    public ResponseEntity<byte[]> getImage(@PathVariable String imageName) {
+        return MailRemoteResponseWriter.image(mktMailFileRemoteClient.getImage(imageName));
+    }
+
+    @PostMapping("/list")
+    @ApiOperation("按邮件查询正式附件(mkt_mail_attachment)")
+    public Result<?> listAttachments(@RequestParam Long mailId) {
+        return mktMailFileRemoteClient.listAttachments(mailId);
+    }
+
+    @PostMapping("/download")
+    @ApiOperation("下载营销正式附件")
+    public void downloadFormalFile(@RequestParam Long fileId, HttpServletResponse response) {
+        MailRemoteResponseWriter.write(response, mktMailFileRemoteClient.downloadFormalFile(fileId));
+    }
+
+    @PostMapping("/download_temp")
+    @ApiOperation("下载营销临时附件")
+    public void downloadTempFile(@RequestParam Long fileId, HttpServletResponse response) {
+        MailRemoteResponseWriter.write(response, mktMailFileRemoteClient.downloadTempFile(fileId));
+    }
+}

+ 0 - 28
java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/mkt/MktMailApiController.java

@@ -5,7 +5,6 @@ import com.storlead.mail.webclient.client.MktMailRemoteClient;
 import com.storlead.mail.webclient.dto.MktEditMailContentRemoteDTO;
 import com.storlead.mail.webclient.dto.MktMailQueryRemoteDTO;
 import com.storlead.mail.webclient.dto.MktSendMailRemoteDTO;
-import com.storlead.mail.webclient.support.MailRemoteResponseWriter;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.extern.log4j.Log4j2;
@@ -18,11 +17,9 @@ import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.multipart.MultipartFile;
 
 import javax.annotation.Resource;
 import javax.servlet.http.HttpServletResponse;
-import java.util.List;
 
 /**
  * 营销邮件主流程(路径对齐邮件服务 {@code MktMailApiController},转发独立邮件服务 /mkt/**)。
@@ -36,31 +33,6 @@ public class MktMailApiController {
     @Resource
     private MktMailRemoteClient mktMailRemoteClient;
 
-    @PostMapping("/file/cache_mail_file")
-    @ApiOperation("缓存营销邮件临时附件(scene=MKT)")
-    public Result<?> cacheMailFile(@RequestParam("files") List<MultipartFile> files,
-                                   @RequestParam("accountId") Long accountId) {
-        return mktMailRemoteClient.cacheMailFile(files, accountId);
-    }
-
-    @PostMapping("/file/list")
-    @ApiOperation("按邮件查询正式附件(mkt_mail_attachment)")
-    public Result<?> listAttachments(@RequestParam Long mailId) {
-        return mktMailRemoteClient.listAttachments(mailId);
-    }
-
-    @PostMapping("/file/download")
-    @ApiOperation("下载营销正式附件")
-    public void downloadFormalFile(@RequestParam Long fileId, HttpServletResponse response) {
-        MailRemoteResponseWriter.write(response, mktMailRemoteClient.downloadFormalFile(fileId));
-    }
-
-    @PostMapping("/file/download_temp")
-    @ApiOperation("下载营销临时附件")
-    public void downloadTempFile(@RequestParam Long fileId, HttpServletResponse response) {
-        MailRemoteResponseWriter.write(response, mktMailRemoteClient.downloadTempFile(fileId));
-    }
-
     @PostMapping("/receive_mail")
     @ApiOperation("接收营销邮件(异步:INBOX+Sent Items,落 mkt_mail_message 并用 reply_msg_id 绑定原信)")
     public Result<?> receiveMail(@RequestParam("accountId") Long accountId) {

+ 1 - 0
java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/webclient/MailRemoteClientFacade.java

@@ -12,6 +12,7 @@ import lombok.RequiredArgsConstructor;
 public class MailRemoteClientFacade {
 
     private final MktMailRemoteClient mktMail;
+    private final MktMailFileRemoteClient mktMailFile;
     private final MktAccountRemoteClient mktAccount;
     private final CrmMailRemoteClient crmMail;
     private final CrmAccountRemoteClient crmAccount;

+ 54 - 0
java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/webclient/client/MktMailFileRemoteClient.java

@@ -0,0 +1,54 @@
+package com.storlead.mail.webclient.client;
+
+import com.storlead.framework.common.result.Result;
+import com.storlead.mail.webclient.constant.MailRemoteApiConstants;
+import com.storlead.mail.webclient.support.MailRemoteHttpSupport;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 营销邮件附件远程客户端,对齐 B 服务 {@code /mkt/file/**}。
+ */
+@RequiredArgsConstructor
+public class MktMailFileRemoteClient {
+
+    private final MailRemoteHttpSupport http;
+
+    public Result<?> cacheMailFile(List<MultipartFile> files, Long accountId) {
+        Map<String, String> extra = new HashMap<>();
+        if (accountId != null) {
+            extra.put("accountId", String.valueOf(accountId));
+        }
+        return http.postFiles(MailRemoteApiConstants.MKT_FILE_CACHE, "files", files, extra);
+    }
+
+    /** B 服务该接口直接返回 Map(非 Result 包装)。 */
+    public Map<String, Object> cacheImageFile(MultipartFile file, Long accountId) {
+        Map<String, String> extra = null;
+        if (accountId != null) {
+            extra = new HashMap<>();
+            extra.put("accountId", String.valueOf(accountId));
+        }
+        return http.postFileForMap(MailRemoteApiConstants.MKT_FILE_CACHE_IMAGE, "upload", file, extra);
+    }
+
+    public byte[] downloadTempFile(Long fileId) {
+        return http.postForBytes(MailRemoteApiConstants.MKT_FILE_DOWNLOAD_TEMP, http.formOf("fileId", fileId));
+    }
+
+    public byte[] getImage(String imageName) {
+        return http.getForBytes(MailRemoteApiConstants.MKT_FILE_IMAGES_PREFIX + imageName, null);
+    }
+
+    public Result<?> listAttachments(Long mailId) {
+        return http.postForm(MailRemoteApiConstants.MKT_FILE_LIST, http.formOf("mailId", mailId));
+    }
+
+    public byte[] downloadFormalFile(Long fileId) {
+        return http.postForBytes(MailRemoteApiConstants.MKT_FILE_DOWNLOAD, http.formOf("fileId", fileId));
+    }
+}

+ 0 - 21
java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/webclient/client/MktMailRemoteClient.java

@@ -9,7 +9,6 @@ import com.storlead.mail.webclient.dto.MktMailStatusSyncQueryRemoteDTO;
 import com.storlead.mail.webclient.dto.MktSendMailRemoteDTO;
 import com.storlead.mail.webclient.support.MailRemoteHttpSupport;
 import lombok.RequiredArgsConstructor;
-import org.springframework.web.multipart.MultipartFile;
 
 import java.util.HashMap;
 import java.util.List;
@@ -23,26 +22,6 @@ public class MktMailRemoteClient {
 
     private final MailRemoteHttpSupport http;
 
-    public Result<?> cacheMailFile(List<MultipartFile> files, Long accountId) {
-        Map<String, String> extra = new HashMap<>();
-        if (accountId != null) {
-            extra.put("accountId", String.valueOf(accountId));
-        }
-        return http.postFiles(MailRemoteApiConstants.MKT_FILE_CACHE, "files", files, extra);
-    }
-
-    public Result<?> listAttachments(Long mailId) {
-        return http.postForm(MailRemoteApiConstants.MKT_FILE_LIST, http.formOf("mailId", mailId));
-    }
-
-    public byte[] downloadFormalFile(Long fileId) {
-        return http.postForBytes(MailRemoteApiConstants.MKT_FILE_DOWNLOAD, http.formOf("fileId", fileId));
-    }
-
-    public byte[] downloadTempFile(Long fileId) {
-        return http.postForBytes(MailRemoteApiConstants.MKT_FILE_DOWNLOAD_TEMP, http.formOf("fileId", fileId));
-    }
-
     public Result<?> receiveMail(Long accountId) {
         return http.postForm(MailRemoteApiConstants.MKT_RECEIVE_MAIL, http.formOf("accountId", accountId));
     }

+ 8 - 1
java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/webclient/config/MailWebClientAutoConfiguration.java

@@ -60,6 +60,12 @@ public class MailWebClientAutoConfiguration {
         return new MktMailRemoteClient(http);
     }
 
+    @Bean
+    @ConditionalOnMissingBean
+    public MktMailFileRemoteClient mktMailFileRemoteClient(MailRemoteHttpSupport http) {
+        return new MktMailFileRemoteClient(http);
+    }
+
     @Bean
     @ConditionalOnMissingBean
     public MktAccountRemoteClient mktAccountRemoteClient(MailRemoteHttpSupport http) {
@@ -135,6 +141,7 @@ public class MailWebClientAutoConfiguration {
     @Bean
     @ConditionalOnMissingBean
     public MailRemoteClientFacade mailRemoteClientFacade(MktMailRemoteClient mktMail,
+                                                         MktMailFileRemoteClient mktMailFile,
                                                          MktAccountRemoteClient mktAccount,
                                                          CrmMailRemoteClient crmMail,
                                                          CrmAccountRemoteClient crmAccount,
@@ -148,7 +155,7 @@ public class MailWebClientAutoConfiguration {
                                                          CrmThumbnailRemoteClient crmThumbnail,
                                                          MailCommonRemoteClient commonRemote) {
         return new MailRemoteClientFacade(
-                mktMail, mktAccount, crmMail, crmAccount, crmMailFile,
+                mktMail, mktMailFile, mktAccount, crmMail, crmAccount, crmMailFile,
                 crmMailTemplates, crmMailBlacklist, crmUserEmailFolder,
                 crmEmailFolderRule, crmMailboxAutoReply, crmMailSignature, crmThumbnail,commonRemote);
     }

+ 8 - 0
java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/webclient/constant/MailRemoteApiConstants.java

@@ -16,6 +16,9 @@ public final class MailRemoteApiConstants {
     /** 缓存营销邮件临时附件(scene=MKT),multipart:files + accountId */
     public static final String MKT_FILE_CACHE = "/mkt/file/cache_mail_file";
 
+    /** 缓存营销邮件正文图片,multipart:upload;响应为 Map(非 Result) */
+    public static final String MKT_FILE_CACHE_IMAGE = "/mail/file/cache_image_file";
+
     /** 按邮件查询正式附件列表(mkt_mail_attachment),参数:mailId */
     public static final String MKT_FILE_LIST = "/mkt/file/list";
 
@@ -25,6 +28,11 @@ public final class MailRemoteApiConstants {
     /** 下载营销临时附件,参数:fileId,返回二进制流 */
     public static final String MKT_FILE_DOWNLOAD_TEMP = "/mkt/file/download_temp";
 
+    /**
+     * 获取营销邮件缓存图片。完整路径:{@value #MKT_FILE_IMAGES_PREFIX}{imageName}
+     */
+    public static final String MKT_FILE_IMAGES_PREFIX = "/mkt/file/images/";
+
     /** 接收营销邮件(异步拉取 INBOX+Sent),参数:accountId */
     public static final String MKT_RECEIVE_MAIL = "/mkt/receive_mail";
 

+ 2 - 2
ui/smarttrade-platform/public/ckeditor/config.js

@@ -10,8 +10,8 @@ CKEDITOR.editorConfig = function( config ) {
 	config.autoParagraph = false;
 	config.removeDialogTabs = 'image:advanced;image:Link';
 	config.extraPlugins = 'uploadimage,editorplaceholder';
-	config.filebrowserImageUploadUrl = '/router/rest/mail/file/cache_image_file';
-	config.uploadUrl = '/router/rest/mail/file/cache_image_file';
+	config.filebrowserImageUploadUrl = '/router/rest/mkt/email/file/cache_image_file';
+	config.uploadUrl = '/router/rest/mkt/email/file/cache_image_file';
 	config.versionCheck = false;
 	
 	config.toolbar_Mini = [

+ 4 - 0
ui/smarttrade-platform/src/components/addMarketing.vue

@@ -1054,6 +1054,10 @@
     }
 
     const addCustomSopStep = () => {
+        if(customSopData.value.details.length >= 6) {
+            ElMessage.error('最多只能添加6个步骤')
+            return
+        }
         const lastDay = customSopData.value.details[customSopData.value.details.length - 1]?.days || 0
         customSopData.value.details.push({
             name: '',

+ 1 - 1
ui/smarttrade-platform/src/components/marketing/customerDetail.vue

@@ -28,7 +28,7 @@
                                 <el-option label="暂时搁置" value="66" />
                             </el-select>
                         </el-form-item>
-                        <el-form-item label="" prop="regionValue">
+                        <el-form-item label="" prop="country">
                             <el-cascader style="width: 100%" placeholder="区域名称" :options="region"
                                     v-model="regionValue" clearable filterable @change="getRegion" />
                         </el-form-item>

+ 6 - 2
ui/smarttrade-platform/src/views/CustomersView.vue

@@ -123,7 +123,7 @@
     /> -->
 
     <!-- Customer Detail Modal -->
-    <div v-if="showDetailModal && selectedCustomer" class="modal-overlay" @click.self="showDetailModal = false">
+    <div v-if="showDetailModal && selectedCustomer" class="modal-overlay" @click.self="closeDetail()">
       <div class="modal customer-detail-modal">
         <div class="modal-header">
           <div class="modal-title">
@@ -243,7 +243,7 @@
           </div> -->
         </div>
         <div class="modal-footer">
-          <button class="btn btn-secondary" @click="showDetailModal = false">关闭</button>
+          <button class="btn btn-secondary" @click="closeDetail()">关闭</button>
           <!-- <button class="btn btn-secondary"><i class="fas fa-edit"></i> 编辑</button> -->
           <button class="btn btn-primary" @click="goOtherProject(selectedCustomer)">
             <i class="fas fa-rocket"></i> 客户详情
@@ -387,6 +387,10 @@ const goOtherProject = (data) => {
   window.open(jumpUrl, '_blank')
   // 当前窗口打开:window.location.href = jumpUrl
 }
+const closeDetail = ()=>{
+  showDetailModal.value = false
+  aiContentLoading.value=false
+}
 // const customers = ref([
 //   { 
 //     id: 1, flag: '🇸🇦', name: 'Riyadh Tech Distribution', status: 'prospect', contact: 'Ahmed Al-Rashid', 

+ 36 - 20
ui/smarttrade-platform/src/views/KnowledgeView.vue

@@ -406,8 +406,8 @@
     </div>
 
     <!-- 添加知识弹窗 -->
-    <div v-if="showAddModal" class="modal-overlay" style="z-index: 1002;" @click.self="showAddModal = false">
-      <div class="modal add-modal">
+    <div v-if="showAddModal" class="modal-overlay" style="z-index: 1002;" @click.self="showAddModal = false" >
+      <div class="modal add-modal" v-loading='dialogVisible' element-loading-background="rgba(0, 0, 0, 0.8)">
         <div class="modal-header">
           <div class="modal-title"><i class="fas fa-plus"></i> {{chunkType=='add'?'添加':'编辑'}}知识</div>
           <button class="modal-close" @click="showAddModal = false"><i class="fas fa-times"></i></button>
@@ -706,6 +706,7 @@ const toastMessage = ref('')
 const toastType = ref('success')
 const chunkType = ref('add')
 const knowledgeModelLoading = ref(false)
+const dialogVisible = ref(false)
 
 
 
@@ -852,30 +853,44 @@ const openCategory = (category) => {
   showToastMessage(`正在加载 ${category.name} 分类...`, 'success')
   getCategoryList(category.id)
 }
-const getCategoryList  = (id,type) => {
+const getCategoryList  = async (id,type) => {
+  const params = {...documentsParams.value}
   if(!type){
     categoryId.value = id?id:''
-    documentsParams.value.dataset_id = id?id:''
-    documentsParams.value.keyword = searchQuery.value
-    documentsParams.value.pageSize = 10
+    params.dataset_id = id?id:''
+    params.keyword = searchQuery.value
+    params.pageSize = 10
   }else{
-    documentsParams.value.dataset_id = id?id:''
-    documentsParams.value.pageSize = 9999
+    params.dataset_id = id?id:''
+    params.pageIndex = 1
+    params.pageSize = 9999
   }
-  getAllDocumnets(documentsParams.value).then(res => {
-    if(type=='add'){ //添加知识获取分类
-      documentList.value = res.result.data
-    }else{
-      knowledgeList.value = res.result.data
-      
-      tabs.value[1].count = categoryTotal.value = res.result.total
-    }
-  })
+  if(chunkType.value == 'edit'){
+    dialogVisible.value = true
+  }
+  try {
+    await getAllDocumnets(params).then(res => {
+      if(type=='add'){ //添加知识获取分类
+        documentList.value = res.result.data
+        if(chunkType.value == 'edit'){
+          dialogVisible.value = false
+
+        }
+      }else{
+        knowledgeList.value = res.result.data
+        
+        tabs.value[1].count = categoryTotal.value = res.result.total
+      }
+    })
+  } catch (err) {
+    ElMessage.error(err?.message || '稍后再试!')
+  }
+  
 }
 getCategoryList()
 const categoryPageCurrentChange = (page) => {
-    documentsParams.value.pageIndex = page
-    getCategoryList(categoryId.value)
+  documentsParams.value.pageIndex = page
+  getCategoryList(categoryId.value)
 }
 
 // 删除知识文档
@@ -1083,11 +1098,12 @@ const showKnowledgeDetail = (item) => {
 
 const editKnowledge = (item) => {
   chunkType.value = 'edit'
+  dialogVisible.value = true
+  getCategoryList(item.dataset_id,'add')
   newKnowledge.value.content = item.content
   newKnowledge.value.answer=item.answer
   newKnowledge.value.dataset_id=item.dataset_id
   newKnowledge.value.segment_id=item.id
-  getCategoryList(item.dataset_id,'add')
   newKnowledge.value.document_id=item.document_id
   showAddModal.value = true
 }