Fan 12 часов назад
Родитель
Сommit
bed638f62c

+ 2 - 1
ui/smarttrade-platform/src/api/emails/index.js

@@ -3,4 +3,5 @@ import { get, post, put, remove, upload } from '@/utils/request'
 export const getMailsPort = (mailAccount) => post('/router/rest/mail/common/mail_port_config', { mailAccount })
 export const emailsTest = (data) => post('/router/rest/mkt/email/account/test_connect', data)
 export const addEmailsSetting = (data) => post('/router/rest/mkt/email/account/save', data)
-export const getUserEmails = () => post('/router/rest/mkt/email/account/personal_mail_account')
+export const getUserEmails = () => post('/router/rest/mkt/email/account/personal_mail_account')
+export const getAccountTotal = (data) => post('/router/rest/marketEmails/account-total', data)

+ 23 - 7
ui/smarttrade-platform/src/components/email/email.vue

@@ -18,14 +18,14 @@
             <div class="overview-card">
               <div class="overview-icon"><i class="fas fa-paper-plane"></i></div>
               <div class="overview-info">
-                <div class="overview-value">{{ emailAccounts.reduce((sum, a) => sum + a.sendToday, 0) }}</div>
-                <div class="overview-label">今日发送</div>
+                <div class="overview-value">{{ total.sendTotal }}</div>
+                <div class="overview-label">总发送</div>
               </div>
             </div>
             <div class="overview-card">
               <div class="overview-icon"><i class="fas fa-reply"></i></div>
               <div class="overview-info">
-                <div class="overview-value">{{ Math.round(emailAccounts.reduce((sum, a) => sum + a.replyRate, 0) / emailAccounts.length) }}%</div>
+                <div class="overview-value">{{ Math.round(total.replyRate) }}%</div>
                 <div class="overview-label">平均回复率</div>
               </div>
             </div>
@@ -51,9 +51,9 @@
                 <div class="account-email">{{ account.emailAddress }}</div>
                 <div class="account-name">销售邮箱</div>
                 <div class="account-stats">
-                  <span><i class="fas fa-paper-plane"></i> 总发送: 45</span>
-                  <span><i class="fas fa-reply"></i> 回复率: 32%</span>
-                  <span><i class="fas fa-sync"></i>  2026-03-24 09:00</span>
+                  <span><i class="fas fa-paper-plane"></i> 总发送: {{ account.accountTotal.sendCount }}</span>
+                  <span><i class="fas fa-reply"></i> 回复率: {{ account.accountTotal.replyRate * 100 }}%</span>
+                  <span v-if="account.accountTotal.lastSendTime"><i class="fas fa-sync"></i>  {{ account.accountTotal.lastSendTime }}</span>
                 </div>
               </div>
               <!-- <div class="account-auth">
@@ -255,7 +255,7 @@
 <script setup>
     import { ref } from 'vue'
     import AddEmailModal from './addEmail.vue'
-    import { getMailsPort } from '@/api/emails'
+    import { getMailsPort, getAccountTotal } from '@/api/emails'
 
     const showEmailModal = ref(false)
     // 企业邮箱列表
@@ -265,9 +265,25 @@
     ])
 
     const emailList = ref([])
+    const total = ref({
+      sendTotal: 0,
+      replyRate: 0
+    })
     const open = (list) => {
         showEmailModal.value = true
         emailList.value = list || []
+        let params = {
+          outboxIds: emailList.value.map(item => item.id).join(',')
+        }
+        if(emailList.value.length != 0) {
+          getAccountTotal(params).then(res => {
+            emailList.value.map(item => {
+              item.accountTotal = res.result.find(acc => acc.outboxId === item.id) || {}
+            })
+            total.value.sendTotal = emailList.value.reduce((sum, a) => sum + (a.accountTotal.sendCount || 0), 0)
+            total.value.replyRate = emailList.value.reduce((sum, a) => sum + (a.accountTotal.replyRate * 100 || 0), 0) / emailList.value.length
+          })
+        }
     }
 
     const tabTyps = ref('email')

+ 10 - 8
ui/smarttrade-platform/src/components/marketing/aiGenerator.vue

@@ -309,7 +309,8 @@
             let subject = ''
             let waitingForSubject = false
             let readingSubject = false
-            while (true) {
+            let streamFinished = false
+            while (!streamFinished) {
                 const { done, value } = await reader.read();
                 if (done) break;
 
@@ -328,7 +329,8 @@
                     if (trimmedLine === 'data: [DONE]') {
                         console.log('流传输结束');
                         doneData += '</p>'
-                        return;
+                        streamFinished = true
+                        break
                     }
 
                     if (trimmedLine.startsWith('data:')) {
@@ -361,14 +363,14 @@
                                 subject += data.slice(0, newlineIndex)
                                 readingSubject = false
                                 const contentAfterSubject = data.slice(newlineIndex + 1)
-                                if (contentAfterSubject.trim()) {
-                                    doneData += contentAfterSubject
+                                if (contentAfterSubject) {
+                                    doneData += contentAfterSubject.replace(/\\n|\r\n?|\n/g, '</p><p>')
                                 } else {
                                     doneData += '</p><p>'
                                 }
-                            } else if(trimmedData){
-                                doneData += data
-                            }else{
+                            } else if (data) {
+                                doneData += data.replace(/\\n|\r\n?|\n/g, '</p><p>')
+                            } else {
                                 doneData += '</p><p>'
                             }
                         } catch (e) {
@@ -377,7 +379,7 @@
                     }
                 }
             }
-            emailContent.value = doneData.replace('<p></p>', '')
+            emailContent.value = doneData.replace(/<p>\s*<\/p>/g, '')
             emailSubject.value = subject.replace(': ', '')
             isGenerating.value = false
             ElMessage.success('生成成功')

+ 68 - 1
ui/smarttrade-platform/src/views/emails/edit.vue

@@ -104,13 +104,80 @@
         form.value.content = val
     }
 
+    const removeEmptyParagraphs = (html) => {
+        const container = document.createElement('div')
+        container.innerHTML = html
+        container.querySelectorAll('p').forEach(paragraph => {
+            if (!paragraph.textContent.trim() && !paragraph.querySelector('img, video, iframe, table')) {
+                paragraph.remove()
+            }
+        })
+        container.querySelectorAll('br').forEach(lineBreak => {
+            let nextSibling = lineBreak.nextSibling
+            while (nextSibling?.nodeType === Node.TEXT_NODE && !nextSibling.textContent.trim()) {
+                const whitespaceNode = nextSibling
+                nextSibling = nextSibling.nextSibling
+                whitespaceNode.remove()
+            }
+            while (nextSibling?.nodeName === 'BR') {
+                const duplicateLineBreak = nextSibling
+                nextSibling = nextSibling.nextSibling
+                duplicateLineBreak.remove()
+            }
+        })
+        return container.innerHTML
+    }
+
+    const formatEditorContent = (content) => {
+        const normalizedContent = String(content || '').replace(/\r\n?/g, '\n')
+        if (!normalizedContent) {
+            return normalizedContent
+        }
+
+        const lineBreakPattern = /\\n|\n/g
+        if (/<\/?[a-z][^>]*>/i.test(normalizedContent)) {
+            const container = document.createElement('div')
+            container.innerHTML = normalizedContent
+            const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT)
+            const textNodes = []
+
+            while (walker.nextNode()) {
+                textNodes.push(walker.currentNode)
+            }
+
+            textNodes.forEach(textNode => {
+                if (!textNode.nodeValue.trim()) return
+
+                const lines = textNode.nodeValue.split(lineBreakPattern).filter(line => line.trim())
+                if (lines.length === 1) return
+
+                const fragment = document.createDocumentFragment()
+                lines.forEach((line, index) => {
+                    if (line) fragment.append(document.createTextNode(line))
+                    if (index < lines.length - 1) fragment.append(document.createElement('br'))
+                })
+                textNode.replaceWith(fragment)
+            })
+
+            return removeEmptyParagraphs(container.innerHTML)
+        }
+
+        const container = document.createElement('div')
+        container.textContent = normalizedContent
+        const formattedContent = container.innerHTML
+            .split(lineBreakPattern)
+            .map(line => `<p>${line || '&nbsp;'}</p>`)
+            .join('')
+        return removeEmptyParagraphs(formattedContent)
+    }
+
     const router = useRouter();
     let queryData = router.currentRoute.value.query
     const getData = () => {
         getEmailById({ id: queryData.id }).then(res => {
             form.value.recipient = res.result.recipient
             form.value.subject = res.result.subject
-            form.value.content = res.result.content
+            form.value.content = formatEditorContent(res.result.content)
         })
     }