فهرست منبع

Merge branch 'master' of https://gogs.storlead.com/storlead/storlead-sasa-platform

183207892172 3 هفته پیش
والد
کامیت
edf1f72c6e

+ 7 - 7
Jenkinsfile

@@ -40,10 +40,10 @@ properties([
                                  sandbox: true,
                                  script:
                                          ''' if (modulePrefix.equals("java")){
-                                return["storlead-centre-api"]
+                                return["storlead-api"]
                             }
                             else if(modulePrefix.equals("ui")){
-                                return["sp-user-center"]
+                                return["smarttrade-platform","sp-user-center"]
                             }
                         '''
                          ]
@@ -133,9 +133,9 @@ pipeline {
 
                         echo "处理MODULE_NAMESPACE_RESTFUL"
                         //把 module 名 从 sp-xxx 变成 xxx 也就是 项目 的 namespace
-                        if (API_NAMESPACE_RESTFUL == "storlead-centre-api") {
-                            API_NAMESPACE_RESTFUL = "api"
-                        }
+//                         if (API_NAMESPACE_RESTFUL == "storlead-centre-api") {
+//                             API_NAMESPACE_RESTFUL = "api"
+//                         }
                         echo "处理镜像推送"
                     }
                 }
@@ -177,7 +177,7 @@ pipeline {
                             remote.user = usernameHW
                             remote.password = passwordHW
                         }
-                        if(params.module == 'sp-user-mobile-center') {
+//                         if(params.module == 'sp-user-mobile-center') {
                            sh """cd ${params.modulePrefix}/${params.module} && npm install  && npm run build:${params.profile}"""
                         } else {
                            sh """cd ${params.modulePrefix}/${params.module} && npm install  && npm run ${params.profile}"""
@@ -190,7 +190,7 @@ pipeline {
                         sshCommand remote: remote, command: "rm -rf /app/sp/ui/app-centre/${params.profile}/${params.module}/dist/*"
 
 
-                        echo "/app/sp/ui/sales/${params.profile}/${params.module}/*"
+                        echo "/app/sp/ui/app-centre/${params.profile}/${params.module}/*"
 
                         sshPut remote: remote, from: "${params.modulePrefix}/${params.module}/dist/", into: "/app/sp/ui/app-centre/${params.profile}/${params.module}/"
 

+ 2 - 0
ui/smarttrade-platform/.env.development

@@ -1,2 +1,4 @@
 # 开发环境后台接口地址
 VITE_API_BASE_URL=http://localhost:10010/
+
+VITE_API_APP_ID=7

+ 2 - 0
ui/smarttrade-platform/.env.production

@@ -1,2 +1,4 @@
 # 生产环境后台接口地址
 VITE_API_BASE_URL=/api
+
+VITE_API_APP_ID=7

+ 9 - 0
ui/smarttrade-platform/package-lock.json

@@ -11,6 +11,7 @@
         "@fortawesome/fontawesome-free": "^6.5.1",
         "axios": "^1.6.7",
         "element-plus": "^2.14.2",
+        "moment": "^2.30.1",
         "pinia": "^2.1.7",
         "vue": "^3.4.21",
         "vue-router": "^4.3.0"
@@ -1513,6 +1514,14 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/moment": {
+      "version": "2.30.1",
+      "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
+      "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
+      "engines": {
+        "node": "*"
+      }
+    },
     "node_modules/ms": {
       "version": "2.1.3",
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",

+ 1 - 0
ui/smarttrade-platform/package.json

@@ -12,6 +12,7 @@
     "@fortawesome/fontawesome-free": "^6.5.1",
     "axios": "^1.6.7",
     "element-plus": "^2.14.2",
+    "moment": "^2.30.1",
     "pinia": "^2.1.7",
     "vue": "^3.4.21",
     "vue-router": "^4.3.0"

+ 12 - 3
ui/smarttrade-platform/src/api/knowledge/index.js

@@ -23,7 +23,6 @@ export const getAllChunks = (params = {}) => get('/router/rest/knowledge/chunk/g
 
 export const getAllDocumnets = (params = {}) => {
     const query = { ...params }
-    // 判断dataset_id无有效值,直接删除属性
     if ([undefined, null, ''].includes(query.dataset_id)) {
         delete query.dataset_id
     }
@@ -32,10 +31,20 @@ export const getAllDocumnets = (params = {}) => {
 
 export const delDocuments = (params = {}) => {
     const { dataset_id, document_id, ...query } = params
-    return remove(`/router/rest/knowledge/document/${dataset_id}/document/${document_id}`, params)
+    return remove(`/router/rest/knowledge/document/${dataset_id}/documents/${document_id}`, params)
+}
+
+export const createChunk = (data = {}) => {
+    const { dataset_id,document_id, ...query } = data
+    return post(`/router/rest/knowledge/chunk/${dataset_id}/${document_id}`, data)
 }
 
 export const delChunk = (params = {}) => {
     const { dataset_id, document_id,segment_id, ...query } = params
-    return  remove(`/router/rest/knowledge/chunk//${dataset_id}/${document_id}/${segment_id}`, params)
+    return  remove(`/router/rest/knowledge/chunk/${dataset_id}/${document_id}/${segment_id}`, params)
+}
+
+export const editChunk = (data = {}) => {
+    const { dataset_id,document_id,segment_id, ...query } = data
+    return post(`/router/rest/knowledge/chunk/${dataset_id}/${document_id}/${segment_id}`, data)
 }

+ 6 - 1
ui/smarttrade-platform/src/api/layout/index.js

@@ -1,6 +1,11 @@
 import { get, post, put, remove, upload } from '@/utils/request'
 
-export const getNoReadCount = () => post('/router/rest/sys/message/getNoReadCountByApp', {appId: 7})
+export const getNoReadCount = () => post('/router/rest/sys/message/getNoReadCount', {appId: import.meta.env.VITE_API_APP_ID})
+export const getMessageList = (data) => post('/router/rest/sys/message/pageList', data)
+export const updateRead = (data) => post('/router/rest/sys/message/read', data)
+export const updateReadAll = (data) => post('/router/rest/sys/message/readAll', data)
+export const listPageBuUserSet = (data) => post('/router/rest/sys/message/config/listPage', data)
+export const setMessageConfig = (data) => post('/router/rest/sys/message/config/setMessageConfig', data)
 export const getUserInfo = () => post('/router/rest/sys/auth/getCurrentUserInfo')
 export const updateUserPwd = (data) => post('/router/rest/sys/auth/modifyPass', data)
 export const updateRemark = (data) => post('/router/rest/sys/auth/updateRemark', data)

+ 13 - 3
ui/smarttrade-platform/src/components/layout/Sidebar.vue

@@ -330,6 +330,7 @@
 import { ref, onMounted } from 'vue'
 import { useRoute, useRouter } from 'vue-router'
 import { getUserInfo, updateUserPwd, getLoginData, updateRemark } from '@/api/layout'
+import { ElMessageBox } from 'element-plus'
 
 const route = useRoute()
 const router = useRouter()
@@ -380,9 +381,18 @@ const toggleTheme = () => {
 }
 
 const handleLogout = () => {
-  showUserMenu.value = false
-  // Handle logout
-  console.log('Logout')
+  ElMessageBox.confirm(
+    '确定要退出登录吗?',
+    '提示',
+    {
+      confirmButtonText: '确认',
+      cancelButtonText: '取消',
+      type: 'warning',
+    }
+  ).then(() => {
+    localStorage.clear()
+    router.push({ path: '/login' })
+  })
 }
 
 const pwdForm = ref({

+ 162 - 245
ui/smarttrade-platform/src/components/layout/TopBar.vue

@@ -28,7 +28,7 @@
       <!-- Notifications -->
       <div class="action-btn notification-btn" @click="toggleNotifications" :class="{ active: showNotifications }">
         <i class="fas fa-bell"></i>
-        <span v-if="unreadCount > 0" class="notification-badge">{{ unreadCount > 99 ? '99+' : unreadCount }}</span>
+        <span v-if="noReadCount > 0" class="notification-badge">{{ noReadCount > 99 ? '99+' : noReadCount }}</span>
       </div>
 
       <!-- Help -->
@@ -45,11 +45,11 @@
           <div class="panel-title">
             <i class="fas fa-bell"></i>
             消息中心
-            <span class="unread-count">{{ unreadCount }}条未读</span>
+            <span class="unread-count" v-if="noReadCount > 0">{{ noReadCount }}条未读</span>
           </div>
           <div class="panel-actions">
             <button class="action-text" @click="markAllAsRead">全部已读</button>
-            <button class="action-text" @click="showSettings = true">
+            <button class="action-text" @click="openSettings">
               <i class="fas fa-cog"></i>
             </button>
           </div>
@@ -59,14 +59,13 @@
         <div class="panel-tabs">
           <div 
             v-for="tab in notificationTabs" 
-            :key="tab.key"
+            :key="tab.messageType"
             class="panel-tab"
-            :class="{ active: activeTab === tab.key }"
-            @click="activeTab = tab.key"
+            :class="{ active: activeTab === tab.messageType }"
+            @click="activeTab = tab.messageType"
           >
-            <i :class="tab.icon"></i>
-            {{ tab.label }}
-            <span v-if="tab.count > 0" class="tab-count">{{ tab.count }}</span>
+            {{ tab.messageTypeName }}
+            <span v-if="tab.stateNumber > 0" class="tab-count">{{ tab.stateNumber > 99 ? '99+' : tab.stateNumber }}</span>
           </div>
         </div>
 
@@ -77,30 +76,23 @@
               v-for="notification in filteredNotifications" 
               :key="notification.id"
               class="notification-item"
-              :class="{ unread: !notification.read, [notification.type]: true }"
+              :class="{ unread: !notification.isRead }"
               @click="handleNotificationClick(notification)"
             >
-              <div class="notification-icon" :style="{ background: getIconBg(notification.type), color: getIconColor(notification.type) }">
-                <i :class="notification.icon"></i>
+              <div class="notification-icon" :style="{ background: getIconBg('reply'), color: getIconColor('reply') }">
+                <i class="fas fa-reply"></i>
               </div>
               <div class="notification-content">
                 <div class="notification-title">{{ notification.title }}</div>
-                <div class="notification-desc">{{ notification.description }}</div>
+                <div class="notification-desc">{{ notification.content }}</div>
                 <div class="notification-meta">
                   <span class="notification-time">{{ notification.time }}</span>
-                  <span v-if="notification.source" class="notification-source">
-                    <i :class="notification.sourceIcon"></i>
-                    {{ notification.source }}
-                  </span>
                 </div>
               </div>
               <div class="notification-actions">
-                <button v-if="!notification.read" class="mark-read-btn" @click.stop="markAsRead(notification.id)">
+                <button v-if="!notification.isRead" class="mark-read-btn" @click.stop="markAsRead(notification)">
                   <i class="fas fa-check"></i>
                 </button>
-                <button class="more-btn" @click.stop="showNotificationMenu($event, notification)">
-                  <i class="fas fa-ellipsis-v"></i>
-                </button>
               </div>
             </div>
           </template>
@@ -114,9 +106,7 @@
 
         <!-- Panel Footer -->
         <div class="panel-footer">
-          <button class="btn btn-secondary btn-sm" @click="viewAllNotifications">
-            <i class="fas fa-list"></i> 查看全部消息
-          </button>
+          <div></div>
           <button class="btn btn-primary btn-sm" @click="showNotifications = false">
             关闭
           </button>
@@ -224,8 +214,8 @@
         <div class="modal notification-detail-modal">
           <div class="modal-header">
             <div class="modal-title">
-              <div class="title-icon" :style="{ background: getIconBg(selectedNotification.type), color: getIconColor(selectedNotification.type) }">
-                <i :class="selectedNotification.icon"></i>
+              <div class="title-icon" :style="{ background: getIconBg('reply'), color: getIconColor('reply') }">
+                <i class="fas fa-reply"></i>
               </div>
               <span>{{ selectedNotification.title }}</span>
             </div>
@@ -238,7 +228,7 @@
               <i class="fas fa-clock"></i>
               {{ selectedNotification.time }}
             </div>
-            <div class="detail-content" v-html="selectedNotification.detail"></div>
+            <div class="detail-content" v-html="selectedNotification.content"></div>
             
             <!-- Quick Actions -->
             <div v-if="selectedNotification.actions" class="detail-actions">
@@ -580,27 +570,38 @@
   <AddMarketing ref="addMarketingRef" />
 
   <div v-if="showSettings" class="modal-overlay" @click.self="showSettings = false">
-    <div class="modal create-modal">
+    <div class="modal create-modal" style="max-width: 800px;">
       <div class="modal-header">
           <div class="modal-title"><i class="fas fa-bell"></i> 通知设置</div>
           <button class="modal-close" @click="showSettings = false"><i class="fas fa-times"></i></button>
       </div>
       <div class="modal-body">
         <div class="settings-list">
-          <div class="settings-item" style="padding-top: 0;">
-            <div class="item-icon" style="background: rgba(99, 102, 241, 0.2); color: var(--primary);">
+          <div class="settings-item" v-for="item in settingsData">
+            <!-- <div class="item-icon" style="background: rgba(99, 102, 241, 0.2); color: var(--primary);">
               <i class="fas fa-envelope"></i>
-            </div>
+            </div> -->
             <div class="item-content">
-              <div class="item-title">邮件通知</div>
-              <div class="item-desc">重要通知发送到邮箱</div>
+              <div class="item-title">{{ item.templateServiceName }}</div>
+              <div class="item-desc">{{ item.eventName }}</div>
             </div>
             <div class="item-action">
-              <el-switch v-model="settings.emailNotify" style="--el-switch-on-color: #13ce66;" />
+              <div>
+                <span>企微消息:</span>
+                <el-switch v-model="item.wecom" @change="changeSetting(item, 'wecom')" />
+              </div>
+              <div>
+                <span>站内信:</span>
+                <el-switch v-model="item.site" @change="changeSetting(item, 'site')" />
+              </div>
+              <div>
+                <span>邮件:</span>
+                <el-switch v-model="item.mail" @change="changeSetting(item, 'mail')" />
+              </div>
             </div>
           </div>
 
-          <div class="settings-item">
+          <!-- <div class="settings-item">
             <div class="item-icon" style="background: rgba(16, 185, 129, 0.2); color: var(--success);">
               <i class="fas fa-reply"></i>
             </div>
@@ -609,9 +610,20 @@
               <div class="item-desc">客户回复邮件、消息时即时提醒</div>
             </div>
             <div class="item-action">
-              <el-switch v-model="settings.replyNotify" style="--el-switch-on-color: #13ce66;" />
+              <div>
+                <span>企微消息:</span>
+                <el-switch v-model="settings.replyNotify" />
+              </div>
+              <div>
+                <span>站内信:</span>
+                <el-switch v-model="settings.replyNotify" />
+              </div>
+              <div>
+                <span>邮件:</span>
+                <el-switch v-model="settings.replyNotify" />
+              </div>
             </div>
-          </div>
+          </div> -->
         </div>
       </div>
     </div>
@@ -622,7 +634,8 @@
 import { ref, computed, onMounted } from 'vue'
 import { useRoute } from 'vue-router'
 import AddMarketing from '../addMarketing.vue'
-import { getNoReadCount } from '@/api/layout'
+import { getNoReadCount, getMessageList, updateRead, updateReadAll, listPageBuUserSet } from '@/api/layout'
+import moment from 'moment'
 
 const route = useRoute()
 const showQuickActions = ref(false)
@@ -770,9 +783,32 @@ const generateReport = () => {
   alert('报告生成中,完成后将发送至您的邮箱...')
 }
 
+// Notification data
+const notifications = ref([])
 const toggleNotifications = () => {
   showNotifications.value = !showNotifications.value
-  if (showNotifications.value) showHelp.value = false
+  if (showNotifications.value){
+    showHelp.value = false
+    let params = {
+      pageIndex: 1,
+      pageSize: 10000,
+      appId: import.meta.env.VITE_API_APP_ID
+    }
+    getMessageList(params).then(res => {
+      notifications.value = (res.result.records || []).map(item => {
+          // 判断创建时间是不是今天
+          let date = moment(item.createTime).format("YYYY-MM-DD HH:mm:ss")
+          if(date.slice(0, 10) == moment().format("YYYY-MM-DD")){
+              item.time = "今天 " + date.slice(11, 16)
+          }else{
+              item.time = moment(item.createTime).format("MM月DD日 HH:mm")
+          }
+          return item
+      })
+    }).catch(err => {
+      console.error('获取消息列表失败:', err)
+    })
+  }
 }
 
 const toggleHelp = () => {
@@ -780,196 +816,8 @@ const toggleHelp = () => {
   if (showHelp.value) showNotifications.value = false
 }
 
-// Notification data
-const notifications = ref([
-  // 客户回复
-  {
-    id: 1,
-    type: 'reply',
-    icon: 'fas fa-reply',
-    title: 'Riyadh Tech 回复了邮件',
-    description: '对方对您的产品报价表示感兴趣,希望进一步了解交期和付款方式',
-    detail: '<p>客户 Ahmed Al-Rashid 回复了您的报价邮件:</p><blockquote style="border-left:3px solid var(--primary);padding-left:12px;margin:12px 0;color:var(--text-secondary);">您好,我们对贵公司的SSD产品很感兴趣。请问MOQ是多少?交期大概多久?付款方式有哪些?</blockquote><p>建议尽快回复客户,可以提供详细的报价单和公司介绍。</p>',
-    time: '5分钟前',
-    source: '邮件',
-    sourceIcon: 'fas fa-envelope',
-    read: false,
-    actions: [
-      { label: '立即回复', icon: 'fas fa-reply', primary: true },
-      { label: '查看邮件', icon: 'fas fa-envelope-open' },
-    ]
-  },
-  {
-    id: 2,
-    type: 'reply',
-    icon: 'fab fa-whatsapp',
-    title: 'Dubai Storage WhatsApp 已读',
-    description: '您发送的消息已被查看,对方正在输入中...',
-    detail: '<p>客户 Mohammed Khan 已读您的 WhatsApp 消息,显示对方正在输入回复。</p><p>消息内容:关于企业级SSD采购的询价</p>',
-    time: '15分钟前',
-    source: 'WhatsApp',
-    sourceIcon: 'fab fa-whatsapp',
-    read: false
-  },
-  {
-    id: 3,
-    type: 'reply',
-    icon: 'fab fa-linkedin',
-    title: 'LinkedIn 好友请求被接受',
-    description: 'Kuwait Data Systems 的 CIO Faisal 接受了您的好友请求',
-    detail: '<p>Faisal Al-Mutairi (CTO @ Kuwait Data Systems) 接受了您的 LinkedIn 连接请求。</p><p>建议:发送个性化感谢消息,介绍公司和产品。</p>',
-    time: '1小时前',
-    source: 'LinkedIn',
-    sourceIcon: 'fab fa-linkedin',
-    read: true
-  },
-  
-  // 风险预警
-  {
-    id: 4,
-    type: 'risk',
-    icon: 'fas fa-exclamation-triangle',
-    title: '⚠️ 高风险预警:客户信用下调',
-    description: 'ABC Corp 信用评级从 A 下调至 B,建议提高预付款比例',
-    detail: '<p><strong>风险等级:高</strong></p><p>客户 ABC Corp 的信用评级发生变动:</p><ul><li>原评级:A</li><li>新评级:B</li><li>原因:近期财务状况恶化</li></ul><p><strong>建议措施:</strong></p><ul><li>提高预付款比例至 50%</li><li>缩短付款周期</li><li>关注后续订单风险</li></ul>',
-    time: '30分钟前',
-    source: '风控系统',
-    sourceIcon: 'fas fa-shield-alt',
-    read: false,
-    actions: [
-      { label: '查看详情', icon: 'fas fa-info-circle', primary: true },
-      { label: '调整付款条件', icon: 'fas fa-edit' }
-    ]
-  },
-  {
-    id: 5,
-    type: 'risk',
-    icon: 'fas fa-gavel',
-    title: '欧盟能效新规即将生效',
-    description: '2026年7月生效,涉及SSD产品能效认证要求',
-    detail: '<p><strong>法规更新提醒</strong></p><p>欧盟委员会发布新的能效认证要求,将于2026年7月1日正式生效。</p><p><strong>影响范围:</strong></p><ul><li>所有出口欧盟的SSD产品</li><li>需要提供能效测试报告</li><li>认证周期约30天</li></ul><p><strong>建议:</strong>提前准备相关认证材料。</p>',
-    time: '2小时前',
-    source: '市场情报',
-    sourceIcon: 'fas fa-globe',
-    read: false
-  },
-  
-  // AI情报
-  {
-    id: 6,
-    type: 'ai',
-    icon: 'fas fa-robot',
-    title: '🤖 AI发现新商机',
-    description: '识别到新加坡 Digital Hub 近期频繁访问官网定价页面',
-    detail: '<p><strong>AI商机分析报告</strong></p><p>客户行为分析:</p><ul><li>过去7天访问定价页面 12 次</li><li>查看企业级SSD产品详情 8 次</li><li>下载产品手册 2 次</li></ul><p><strong>AI建议:</strong>该客户意向度高,建议尽快主动联系。</p>',
-    time: '45分钟前',
-    source: 'AI大脑',
-    sourceIcon: 'fas fa-brain',
-    read: false,
-    actions: [
-      { label: '立即联系', icon: 'fas fa-phone', primary: true },
-      { label: '发送报价', icon: 'fas fa-file-invoice' }
-    ]
-  },
-  {
-    id: 7,
-    type: 'ai',
-    icon: 'fas fa-lightbulb',
-    title: '💡 AI策略建议',
-    description: '基于近期数据分析,建议调整中东地区邮件发送时间',
-    detail: '<p><strong>策略优化建议</strong></p><p>AI分析了过去30天的邮件数据,发现:</p><ul><li>中东客户最佳回复时段:晚上8-10点</li><li>当前发送时段:上午10点</li><li>预计调整后回复率提升:52%</li></ul><p>是否应用此策略?</p>',
-    time: '3小时前',
-    source: 'AI策略中心',
-    sourceIcon: 'fas fa-lightbulb',
-    read: true,
-    actions: [
-      { label: '应用策略', icon: 'fas fa-check', primary: true },
-      { label: '查看详情', icon: 'fas fa-chart-bar' }
-    ]
-  },
-  
-  // 营销报告
-  {
-    id: 8,
-    type: 'marketing',
-    icon: 'fas fa-chart-line',
-    title: '📊 周度营销报告已生成',
-    description: '本周发送邮件 1,247 封,打开率 42%,回复率 18%',
-    detail: '<p><strong>本周营销数据汇总</strong></p><table style="width:100%;border-collapse:collapse;margin:12px 0;"><tr style="border-bottom:1px solid var(--border)"><td style="padding:8px 0;">邮件发送</td><td style="text-align:right;font-weight:600;">1,247 封</td></tr><tr style="border-bottom:1px solid var(--border)"><td style="padding:8px 0;">打开率</td><td style="text-align:right;font-weight:600;color:var(--success)">42% (+8%)</td></tr><tr style="border-bottom:1px solid var(--border)"><td style="padding:8px 0;">回复率</td><td style="text-align:right;font-weight:600;color:var(--success)">18% (+5%)</td></tr><tr><td style="padding:8px 0;">新增商机</td><td style="text-align:right;font-weight:600;">23 个</td></tr></table><p>详细报告已发送至您的邮箱。</p>',
-    time: '今天 09:00',
-    source: '自动报告',
-    sourceIcon: 'fas fa-file-alt',
-    read: true,
-    actions: [
-      { label: '查看完整报告', icon: 'fas fa-file-pdf', primary: true }
-    ]
-  },
-  {
-    id: 9,
-    type: 'marketing',
-    icon: 'fas fa-check-circle',
-    title: '✅ 营销活动完成',
-    description: '中东潜客开发活动已完成,共触达 156 位客户',
-    detail: '<p><strong>活动执行报告</strong></p><p>活动名称:中东潜客开发</p><p>执行时间:2026-03-12 ~ 2026-03-19</p><p><strong>执行结果:</strong></p><ul><li>目标客户:156 位</li><li>邮件送达:152 封 (97%)</li><li>邮件打开:68 封 (45%)</li><li>客户回复:12 封 (8%)</li><li>意向客户:5 位</li></ul>',
-    time: '昨天 18:30',
-    source: '营销系统',
-    sourceIcon: 'fas fa-paper-plane',
-    read: true
-  },
-  
-  // 寻客通知
-  {
-    id: 10,
-    type: 'prospect',
-    icon: 'fas fa-user-plus',
-    title: '🎯 发现 12 个高匹配潜客',
-    description: '基于您的历史成交客户,AI 推荐 12 个相似潜客',
-    detail: '<p><strong>AI寻客报告</strong></p><p>寻客条件:中东地区 + SSD进口商</p><p><strong>推荐潜客 TOP 3:</strong></p><ol><li>Riyadh Tech Distribution (92分)</li><li>Dubai Storage Solutions (88分)</li><li>Saudi Digital Hub (76分)</li></ol><p>点击查看完整列表。</p>',
-    time: '今天 14:30',
-    source: '自动寻客',
-    sourceIcon: 'fas fa-crosshairs',
-    read: false,
-    actions: [
-      { label: '查看潜客列表', icon: 'fas fa-list', primary: true },
-      { label: '发送营销邮件', icon: 'fas fa-paper-plane' }
-    ]
-  },
-  {
-    id: 11,
-    type: 'prospect',
-    icon: 'fas fa-search',
-    title: '寻客任务更新',
-    description: '欧洲企业级存储寻客任务进度 75%,已发现 45 家目标公司',
-    detail: '<p><strong>任务进度更新</strong></p><p>任务名称:欧洲企业级存储寻客</p><p>当前进度:75%</p><p><strong>已发现:</strong></p><ul><li>目标公司:45 家</li><li>关键人信息:28 位</li><li>高匹配度:12 家</li></ul>',
-    time: '今天 11:20',
-    source: '寻客任务',
-    sourceIcon: 'fas fa-tasks',
-    read: true
-  },
-  
-  // 系统通知
-  {
-    id: 12,
-    type: 'system',
-    icon: 'fas fa-cog',
-    title: '系统更新通知',
-    description: '领存智贸 AI 已更新至 v2.3,新增策略自进化功能',
-    detail: '<p><strong>版本更新 v2.3</strong></p><p><strong>新增功能:</strong></p><ul><li>AI策略自进化 - 自动测试优化营销策略</li><li>智能跟进提醒 - 基于客户行为预测最佳跟进时机</li><li>知识库问答优化 - 支持更多行业知识</li></ul><p><strong>优化:</strong></p><ul><li>邮件生成速度提升 30%</li><li>潜客匹配准确率提升 15%</li></ul>',
-    time: '昨天 10:00',
-    source: '系统',
-    sourceIcon: 'fas fa-server',
-    read: true
-  }
-])
-
 // Computed
-const notificationTabs = computed(() => [
-  { key: 'all', label: '全部', icon: 'fas fa-inbox', count: notifications.value.filter(n => !n.read).length },
-  { key: 'reply', label: '客户回复', icon: 'fas fa-reply', count: notifications.value.filter(n => n.type === 'reply' && !n.read).length },
-  { key: 'marketing', label: '营销报告', icon: 'fas fa-chart-line', count: notifications.value.filter(n => n.type === 'marketing' && !n.read).length }
-])
-
-const unreadCount = computed(() => notifications.value.filter(n => !n.read).length)
+const notificationTabs = ref([])
 
 const filteredNotifications = computed(() => {
   if (activeTab.value === 'all') return notifications.value
@@ -1002,30 +850,81 @@ const getIconColor = (type) => {
 }
 
 const handleNotificationClick = (notification) => {
-  if (!notification.read) {
-    notification.read = true
-  }
+  setRead(notification)
   selectedNotification.value = notification
+  // if(notification.callbackUrl){
+  //     window.open(item.callbackUrl, '_blank')
+  // }
 }
 
-const markAsRead = (id) => {
-  const notification = notifications.value.find(n => n.id === id)
-  if (notification) {
-    notification.read = true
-  }
+const markAsRead = (notification) => {
+  setRead(notification)
 }
 
-const markAllAsRead = () => {
-  notifications.value.forEach(n => n.read = true)
+const setRead = (item) => {
+  if(!item.isRead){
+      updateRead({ messageLogIds: [item.id], appId: import.meta.env.VITE_API_APP_ID }).then(res => {
+        item.isRead = 1
+        getCount()
+        // item.stateNumber --
+      }).catch(err => {
+        ElMessage.error(err.msg);
+      })
+  }
 }
 
-const viewAllNotifications = () => {
-  showNotifications.value = false
-  // Navigate to notifications page
+const markAllAsRead = () => {
+  if(notifications.value.length == 0){
+      ElMessage.error('列表暂无数据');
+      return
+  }
+  updateReadAll({ appId: import.meta.env.VITE_API_APP_ID }).then(res => {
+      if(res.success){
+          ElMessage.success('操作成功');
+          notifications.value.map(item => item.isRead = 1)
+          getCount()
+      }else{
+          ElMessage.error(res.msg);
+      }
+  })
 }
 
-const showNotificationMenu = (event, notification) => {
-  // Show context menu
+const settingsData = ref([])
+const openSettings = () => {
+  listPageBuUserSet({ pageIndex: 1, pageSize: 9999, appId: import.meta.env.VITE_API_APP_ID }).then(res => {
+    settingsData.value = res.result.records.map(item => {
+      item.wecom = handleData('wecom', item)
+      item.site = handleData('site', item)
+      item.mail = handleData('mail', item)
+      return item
+    })
+  })
+  showSettings.value = true
+}
+const handleData = (type, column) => {
+  let enabled = true
+	let templateData = null
+	if(column.userDetails && column.userDetails.length > 0) {
+		templateData = column.userDetails.find(item => item.templateEventId == column.id && item.templateDetailType == type)
+      //找到对应的配置就按照对应的配置显示
+      if(templateData) {
+          enabled = templateData?.enabled
+      }
+  }
+  return enabled
+}
+const changeSetting = (item, type) => {
+  let params = {
+    appId: import.meta.env.VITE_API_APP_ID,
+    templateDetailType: type,
+    templateEventId: item.id,
+    enabled: item[type]
+  }
+  setMessageConfig(params).then(res => {
+    ElMessage.success((item[type] ? '启用' : '禁用') + '成功')
+  }).catch(err => {
+    ElMessage.error(err.msg)
+  })
 }
 
 const handleAction = (action) => {
@@ -1106,10 +1005,19 @@ const showPrivacy = () => {
   console.log('Show privacy')
 }
 
-onMounted(() => {
+const noReadCount = ref(0)
+const getCount = () => {
   getNoReadCount().then(res => {
-    console.log('Unread notifications count:', res)
+    let countList = (res.result || []).map(item => item.stateNumber)
+    noReadCount.value = eval(countList.join("+"))
+    notificationTabs.value = [
+      { messageType: 'all', messageTypeName: '全部', stateNumber: noReadCount.value },
+      ...res.result
+    ]
   })
+}
+onMounted(() => {
+  getCount()
 })
 </script>
 
@@ -1701,6 +1609,7 @@ onMounted(() => {
   position: sticky;
   top: 0;
   z-index: 10;
+  margin-bottom: 0;
 }
 
 .panel-title {
@@ -2364,6 +2273,14 @@ onMounted(() => {
   cursor: pointer;
 }
 
+.settings-item:first-child{
+  padding-top: 0;
+}
+
+.settings-item .el-switch{
+  --el-switch-on-color: #13ce66;
+}
+
 .item-icon {
   width: 40px;
   height: 40px;

+ 83 - 43
ui/smarttrade-platform/src/views/KnowledgeView.vue

@@ -6,9 +6,9 @@
         <p class="page-desc">AI 驱动的企业知识管理,智能问答与知识沉淀</p>
       </div>
       <div class="header-actions">
-        <button class="btn btn-secondary" @click="showImportModal = true">
+        <!-- <button class="btn btn-secondary" @click="showImportModal = true">
           <i class="fas fa-file-import"></i> 批量导入
-        </button>
+        </button> -->
         <button class="btn btn-primary" @click="addKnowledgeDocument()">
           <i class="fas fa-plus"></i> 添加知识
         </button>
@@ -187,7 +187,7 @@
             </div> -->
           </div>
           <div class="knowledge-actions" @click.stop>
-            <button class="btn btn-secondary btn-sm" @click="editKnowledge(item)">
+            <button class="btn btn-secondary btn-sm" @click="editDocument(item)">
               <i class="fas fa-edit"></i>
             </button>
             <el-popconfirm title="确认删除?" @confirm="deleteKnowledge(item)">
@@ -401,27 +401,34 @@
     </div>
 
     <!-- 添加知识弹窗 -->
-    <div v-if="showAddModal" class="modal-overlay" @click.self="showAddModal = false">
+    <div v-if="showAddModal" class="modal-overlay" style="z-index: 1002;" @click.self="showAddModal = false">
       <div class="modal add-modal">
         <div class="modal-header">
-          <div class="modal-title"><i class="fas fa-plus"></i> 添加知识</div>
+          <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>
         </div>
         <div class="modal-body">
           <div class="form-group">
             <label class="form-label">问题/关键词 <span class="required">*</span></label>
-            <input type="text" class="form-input" v-model="newKnowledge.name" placeholder="例如:MOQ最小起订量是多少?">
+            <input type="text" class="form-input" v-model="newKnowledge.content" placeholder="例如:MOQ最小起订量是多少?">
           </div>
           <div class="form-group">
             <label class="form-label">答案/内容 <span class="required">*</span></label>
-            <textarea class="form-textarea" v-model="newKnowledge.text" rows="4" placeholder="输入详细答案..."></textarea>
+            <textarea class="form-textarea" v-model="newKnowledge.answer" rows="4" placeholder="输入详细答案..."></textarea>
           </div>
           <div class="form-group">
             <label class="form-label">知识分类 <span class="required">*</span></label>
-            <select class="form-select" v-model="newKnowledge.dataset_id">
+            <select class="form-select" v-model="newKnowledge.dataset_id" @change='getDocument' :disabled='chunkType=="edit"'>
               <option value="">选择分类</option>
               <option v-for="cat in categories" :key="cat.id" :value="cat.id">{{ cat.name }}</option>
             </select>
+          </div>
+          <div class="form-group">
+            <label class="form-label">知识文档 <span class="required">*</span></label>
+            <select class="form-select" v-model="newKnowledge.document_id" :disabled='chunkType=="edit"'>
+              <option value="">选择文档</option>
+              <option v-for="cat in documentList" :key="cat.id" :value="cat.id">{{ cat.name }}</option>
+            </select>
           </div>
             <!-- <div class="form-group">
               <label class="form-label">关键词标签</label>
@@ -434,7 +441,7 @@
         </div>
         <div class="modal-footer">
           <button class="btn btn-secondary" @click="showAddModal = false">取消</button>
-          <button class="btn btn-primary" @click="addKnowledge"><i class="fas fa-plus"></i> 添加</button>
+          <button class="btn btn-primary" @click="addKnowledge"><i class="fas fa-plus"></i> {{chunkType=='add'?'添加':'确认'}}</button>
         </div>
       </div>
     </div>
@@ -497,15 +504,15 @@
       </div>
     </div>
 
-    <!-- Knowledge Detail Modal -->
+    <!-- 知识块详情弹窗 -->
     <div v-if="showDetailModal && selectedKnowledge" class="modal-overlay" @click.self="showDetailModal = false">
       <div class="modal detail-modal">
         <div class="modal-header">
           <div class="modal-title">
-            <span class="detail-icon" :style="{ background: selectedKnowledge.iconBg }">{{ selectedKnowledge.icon }}</span>
+            <!-- <span class="detail-icon" :style="{ background: selectedKnowledge.iconBg }">{{ selectedKnowledge.icon }}</span> -->
             <div>
-              <h3>{{ selectedKnowledge.question }}</h3>
-              <p>{{ selectedKnowledge.categoryName }}</p>
+              <h3>{{ selectedKnowledge.sign_content }}</h3>
+              <!-- <p>{{ selectedKnowledge.categoryName }}</p> -->
             </div>
           </div>
           <button class="modal-close" @click="showDetailModal = false"><i class="fas fa-times"></i></button>
@@ -515,22 +522,22 @@
             <div class="detail-label"><i class="fas fa-lightbulb"></i> 答案内容</div>
             <div class="detail-content">{{ selectedKnowledge.answer }}</div>
           </div>
-          <div class="detail-section" v-if="selectedKnowledge.extendedAnswer">
+          <!-- <div class="detail-section" v-if="selectedKnowledge.extendedAnswer">
             <div class="detail-label"><i class="fas fa-file-alt"></i> 扩展说明</div>
             <div class="detail-content extended">{{ selectedKnowledge.extendedAnswer }}</div>
-          </div>
-          <div class="detail-section">
+          </div> -->
+          <!-- <div class="detail-section">
             <div class="detail-label"><i class="fas fa-tags"></i> 关键词标签</div>
             <div class="detail-tags">
               <span v-for="tag in selectedKnowledge.tags" :key="tag" class="tag">{{ tag }}</span>
             </div>
-          </div>
-          <div class="detail-section" v-if="selectedKnowledge.relatedQuestions && selectedKnowledge.relatedQuestions.length">
+          </div> -->
+          <!-- <div class="detail-section" v-if="selectedKnowledge.relatedQuestions && selectedKnowledge.relatedQuestions.length">
             <div class="detail-label"><i class="fas fa-link"></i> 相关问题</div>
             <div class="related-questions">
               <div v-for="(q, idx) in selectedKnowledge.relatedQuestions" :key="idx" class="related-item">{{ q }}</div>
             </div>
-          </div>
+          </div> -->
           <div class="detail-stats-row">
             <div class="detail-stat">
               <i class="fas fa-eye"></i>
@@ -660,7 +667,7 @@
 
 <script setup>
 import { ref, computed } from 'vue'
-import { knowledgeDatasets,knowledgeDocuments,createKnowledge,chatMessages,baseTraining,creatTraining,baseStatistics,getAllChunks,getAllDocumnets,delDocuments,delChunk } from '../api/knowledge'
+import { knowledgeDatasets,knowledgeDocuments,createKnowledge,createChunk,editChunk,chatMessages,baseTraining,creatTraining,baseStatistics,getAllChunks,getAllDocumnets,delDocuments,delChunk } from '../api/knowledge'
 import { formatTimeAgo } from '@/utils/time'
 import { ElMessage } from 'element-plus'
 
@@ -680,6 +687,7 @@ const confidenceThreshold = ref(70)
 const showToast = ref(false)
 const toastMessage = ref('')
 const toastType = ref('success')
+const chunkType = ref('add')
 
 
 const syncStatus = {
@@ -699,11 +707,9 @@ const tabs = ref([
 
 const newKnowledge = ref({
   dataset_id: '',
-  text: '',
-  name: '',
-  indexing_technique: "high_quality",
-  doc_form: "qa_model",
-  doc_language:"Chinese Simplified"
+  document_id: '',
+  content: '',
+  answer: '',
 })
 
 // const categories = ref([
@@ -812,6 +818,7 @@ const changeCategory = () => {
 }
 
 const knowledgeList = ref([])
+const documentList = ref([])
 // 获取知识列表
 const categoryId = ref()
 const categoryTotal = ref()
@@ -826,14 +833,23 @@ const openCategory = (category) => {
   showToastMessage(`正在加载 ${category.name} 分类...`, 'success')
   getCategoryList(category.id)
 }
-const getCategoryList  = (id) => {
-  categoryId.value = id?id:''
-  documentsParams.value.dataset_id = id?id:''
-  documentsParams.value.keyword = searchQuery.value
+const getCategoryList  = (id,type) => {
+  if(!type){
+    categoryId.value = id?id:''
+    documentsParams.value.dataset_id = id?id:''
+    documentsParams.value.keyword = searchQuery.value
+    documentsParams.value.pageSize = 10
+  }else{
+    documentsParams.value.pageSize = 9999
+  }
   getAllDocumnets(documentsParams.value).then(res => {
     console.log(res)
-    knowledgeList.value = res.result.data
-    tabs.value[1].count = categoryTotal.value = res.result.total
+    if(type=='add'){ //添加知识获取分类
+      documentList.value = res.result.data
+    }else{
+      knowledgeList.value = res.result.data
+      tabs.value[1].count = categoryTotal.value = res.result.total
+    }
   })
 }
 getCategoryList()
@@ -1052,29 +1068,53 @@ const formatNumber = (num) => num.toLocaleString()
 
 
 const editKnowledge = (item) => {
-  showDetailModal.value = false
-  showToastMessage('正在编辑知识...', 'success')
+  chunkType.value = 'edit'
+  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
+}
+const editDocument = (item) => {
+  
 }
 
-
+const getDocument = () =>{
+  getCategoryList(newKnowledge.value.dataset_id,'add')
+}
 
 const addKnowledgeDocument = ()=> {
-  newKnowledge.value.name = ''
-  newKnowledge.value.text=''
+  newKnowledge.value.content = ''
+  newKnowledge.value.answer=''
   newKnowledge.value.dataset_id=''
+  newKnowledge.value.document_id=''
+  newKnowledge.value.segment_id=''
+  chunkType.value = 'add'
   showAddModal.value = true
 }
 const addKnowledge = () => {
-  if(!newKnowledge.value.name||!newKnowledge.value.text||!newKnowledge.value.dataset_id){
+  if(!newKnowledge.value.answer||!newKnowledge.value.content||!newKnowledge.value.dataset_id||!newKnowledge.value.document_id){
     ElMessage.error('请填入必填项')
     return false
   }
-  createKnowledge(newKnowledge.value).then(res => {
-    if(res.success){
-      howToastMessage('知识添加成功,AI 已学习', 'success')
-      showAddModal.value = false
-    }
-  })
+  if(chunkType.value =='add'){
+    createChunk(newKnowledge.value).then(res => {
+      if(res.success){
+        howToastMessage('知识添加成功,AI 已学习', 'success')
+        showAddModal.value = false
+      }
+    })
+  }else{
+    editChunk(newKnowledge.value).then(res => {
+      if(res.success){
+        howToastMessage('知识编辑成功,AI 已学习', 'success')
+        showAddModal.value = false
+      }
+    })
+  }
+  
 }
 
 const optimizeAnswer = (item) => {