Jelajahi Sumber

Merge remote-tracking branch 'origin/master'

zhujing 3 minggu lalu
induk
melakukan
c0613d1d66
51 mengubah file dengan 825 tambahan dan 108 penghapusan
  1. 38 25
      Jenkinsfile
  2. 24 2
      java/storlead-api/src/main/resources/application-test.yml
  3. 13 0
      java/storlead-knowledge/storlead-knowledge-api/src/main/java/com/storlead/knowledge/api/DocumentController.java
  4. 34 0
      java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/controller/CustomerAiAnalysisController.java
  5. 175 0
      java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/controller/MarketingCampaignSopDetailController.java
  6. 14 0
      java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/controller/MarketingController.java
  7. 3 0
      java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/dto/MarketingCampaignDTO.java
  8. 51 0
      java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/dto/MarketingCampaignSopDetailDTO.java
  9. 5 0
      java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/dto/SopDetailCreateDTO.java
  10. 5 0
      java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/dto/SopDetailUpdateDTO.java
  11. 4 0
      java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/entity/MarketingCampaignEntity.java
  12. 55 0
      java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/entity/MarketingCampaignSopDetailEntity.java
  13. 4 0
      java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/entity/SopDetailEntity.java
  14. 12 0
      java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/mapper/MarketingCampaignSopDetailMapper.java
  15. 1 0
      java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/service/MarketingCampaignEntityService.java
  16. 10 0
      java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/service/MarketingCampaignSopDetailService.java
  17. 69 6
      java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/service/impl/MarketingCampaignEntityServiceImpl.java
  18. 16 0
      java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/service/impl/MarketingCampaignSopDetailServiceImpl.java
  19. 1 1
      ui/smarttrade-platform/.env.production
  20. 4 0
      ui/smarttrade-platform/.env.test
  21. 3 1
      ui/smarttrade-platform/package.json
  22. 2 1
      ui/smarttrade-platform/src/api/layout/index.js
  23. 1 0
      ui/smarttrade-platform/src/api/marketing/index.js
  24. 57 5
      ui/smarttrade-platform/src/components/addMarketing.vue
  25. 54 26
      ui/smarttrade-platform/src/views/MarketingView.vue
  26. 7 7
      ui/sp-storlead-center/src/api/public.ts
  27. 10 2
      ui/sp-storlead-center/src/api/system/bulletin/types.ts
  28. 2 2
      ui/sp-storlead-center/src/api/system/dept/index.ts
  29. 2 0
      ui/sp-storlead-center/src/layout/components/Navbar.vue
  30. 14 1
      ui/sp-storlead-center/src/layout/components/notice/index.vue
  31. 3 2
      ui/sp-storlead-center/src/permission.ts
  32. 1 1
      ui/sp-storlead-center/src/settings.ts
  33. 1 1
      ui/sp-storlead-center/src/store/modules/app.ts
  34. 4 4
      ui/sp-storlead-center/src/store/modules/notice.ts
  35. 5 0
      ui/sp-storlead-center/src/store/modules/permission.ts
  36. 6 2
      ui/sp-storlead-center/src/utils/common.ts
  37. 1 1
      ui/sp-storlead-center/src/utils/request.ts
  38. 17 5
      ui/sp-storlead-center/src/views/system/bulletin/index.vue
  39. 11 0
      ui/sp-storlead-center/src/views/system/cueWordTemplate/index.vue
  40. 11 1
      ui/sp-storlead-center/src/views/system/currencySetting/index.vue
  41. 1 1
      ui/sp-storlead-center/src/views/system/dept/index.vue
  42. 9 2
      ui/sp-storlead-center/src/views/system/dict/index.vue
  43. 8 1
      ui/sp-storlead-center/src/views/system/menu/index.vue
  44. 9 1
      ui/sp-storlead-center/src/views/system/messageTemplate/index.vue
  45. 6 0
      ui/sp-storlead-center/src/views/system/post/index.vue
  46. 10 1
      ui/sp-storlead-center/src/views/system/role/index.vue
  47. 11 1
      ui/sp-storlead-center/src/views/system/ruleConfig/index.vue
  48. 9 1
      ui/sp-storlead-center/src/views/system/task/index.vue
  49. 9 1
      ui/sp-storlead-center/src/views/system/user/index.vue
  50. 2 2
      ui/sp-user-center/src/api/public.ts
  51. 1 1
      ui/sp-user-center/src/utils/request.ts

+ 38 - 25
Jenkinsfile

@@ -4,7 +4,7 @@ properties([
                  choiceType: 'PT_SINGLE_SELECT',
                  description: '选择要部署 API or UI',
                  name: 'modulePrefix',
-                 randomName: 'choice-parameter-5631314439613978',
+                 randomName: 'choice-parameter-56313144329613978',
                  script: [
                          $class: 'GroovyScript',
                          fallbackScript: [
@@ -25,7 +25,7 @@ properties([
                  choiceType: 'PT_SINGLE_SELECT',
                  description: '选择要部署的项目',
                  name: 'module',
-                 randomName: 'choice-parameter-5631314456178619',
+                 randomName: 'choice-parameter-56313144562178619',
                  referencedParameters: 'modulePrefix',
                  script: [
                          $class: 'GroovyScript',
@@ -39,13 +39,13 @@ properties([
                                  classpath: [],
                                  sandbox: true,
                                  script:
-                                         ''' if (modulePrefix.equals("java")){
-                                return["storlead-api"]
-                            }
-                            else if(modulePrefix.equals("ui")){
-                                return["smarttrade-platform","sp-user-center"]
-                            }
-                        '''
+                                    ''' if (modulePrefix.equals("java")){
+                                            return["storlead-api"]
+                                        }
+                                        else if(modulePrefix.equals("ui")){
+                                            return["smarttrade-platform","su-user-center"]
+                                        }
+                                    '''
                          ]
                  ]
                 ]
@@ -111,13 +111,13 @@ pipeline {
                                 if (params.profile == "test") {
                                     SWARM_INIT_REPLICAS_NUM = 1
                                     SERVER_HOST = "test1.storlead.com"
-                                    API_PORT = 10060
-                                    API_REMOTE_DEBUG_PORT = 10061
+                                    API_PORT = 10070
+                                    API_REMOTE_DEBUG_PORT = 10071
                                     SERVER_HOST_NAME =  "test1"
                                 } else if (params.profile == "prod") {
                                     SERVER_HOST = "110.41.82.21"
-                                    API_PORT = 10060
-                                    API_REMOTE_DEBUG_PORT = 10061
+                                    API_PORT = 10070
+                                    API_REMOTE_DEBUG_PORT = 10071
                                     SERVER_HOST_NAME =  "node1"
                                 }
                             }
@@ -177,11 +177,11 @@ pipeline {
                             remote.user = usernameHW
                             remote.password = passwordHW
                         }
-//                         if(params.module == 'sp-user-mobile-center') {
-                           sh """cd ${params.modulePrefix}/${params.module} && npm install  && npm run build:${params.profile}"""
-                        } else {
+//                         if (params.module == 'sp-user-mobile-center') {
                            sh """cd ${params.modulePrefix}/${params.module} && npm install  && npm run ${params.profile}"""
-                        }
+//                         } else {
+//                            sh """cd ${params.modulePrefix}/${params.module} && npm install  && npm run ${params.profile}"""
+//                         }
 
 //                          sh """cd ${params.modulePrefix}/${params.module}  && npm run ${params.profile}"""
 
@@ -208,7 +208,8 @@ pipeline {
             }
             agent {
                 docker {
-                    image 'maven:3-amazoncorretto-11'
+                    // 与 Temurin 17 一致(含 Maven);若本地无此镜像会自动拉取
+                    image 'maven:3-eclipse-temurin-17'
                     //让docker 使用 host 宿主机的 m2仓库 使用root用户来运行 以让指定的~/.m2/config/setting.xml 阿里加速下载maven 依赖生效
                     args '-v /app:/app:z -v $HOME/.m2:/root/.m2:z -u root'
                     reuseNode true
@@ -216,7 +217,8 @@ pipeline {
             }
             steps {
                 echo "编译 API 代码"
-                sh "mvn clean package -U -am -pl ${params.module} -P${params.profile} -Dmaven.test.skip=true"
+                // 根 reactor 模块路径为 java/storlead-api,-pl 用 :artifactId 按构件名选择
+                sh "mvn clean package -U -am -pl :${params.module} -P${params.profile} -Dmaven.test.skip=true"
             }
         }
 
@@ -230,14 +232,15 @@ pipeline {
                 script {
                     docker.withRegistry(DOCKER_LOGIN_REGISTRY, DOCKER_REGISTRY) {
 
-                        BASE_IMAGE = 'registry.cn-shenzhen.aliyuncs.com/storlead/openjdk:11'
-                        sh "docker pull ${BASE_IMAGE}"
+                        // 使用 Jenkins 节点已有的 eclipse-temurin:17
+                        BASE_IMAGE = 'eclipse-temurin:17'
+                        sh "docker pull ${BASE_IMAGE} || true"
 
                         sh """
                           echo '
                           FROM ${BASE_IMAGE}
 
-                          COPY ${params.module}/target/${params.module}.jar app.jar
+                          COPY java/${params.module}/target/${params.module}-${VERSION}.jar app.jar
                           EXPOSE ${API_PORT}
                           EXPOSE ${API_REMOTE_DEBUG_PORT}
                           HEALTHCHECK --interval=1m --timeout=10s CMD curl -f http://localhost:${API_PORT}/${API_NAMESPACE_RESTFUL}/actuator
@@ -298,9 +301,17 @@ pipeline {
         placement:
           constraints:  
             - 'node.hostname == ${SERVER_HOST_NAME}'
+
+        resources:
+          limits:
+            memory: 2G
+          reservations:
+            memory: 512M
+
         update_config:
           parallelism: 1
           delay: 1m
+
         restart_policy:
           condition: on-failure
           # 最多连续重试3次如果仍然失败则放弃重试
@@ -314,14 +325,15 @@ pipeline {
       environment:
         - "TZ=Asia/Shanghai"
         - "SPRING_PROFILES_ACTIVE=${params.profile}"
+        - "JAVA_OPTS=-Xms512m -Xmx1024m"
       volumes:
         - /app:/app
         - /mnt/vdb/storlead:/mnt/vdb/storlead
       ports:
         - "${API_PORT}:${API_PORT}"
         - "${API_REMOTE_DEBUG_PORT}:${API_REMOTE_DEBUG_PORT}"
-      networks:
-        - vonedao_net
+    networks:
+        - ${ARTIFACTID}
       extra_hosts:
         - "www.storlead.com:172.18.194.161"
         - "node1:110.41.82.21"
@@ -330,7 +342,8 @@ pipeline {
         - "test1.storlead.com:172.18.194.168"
         - "test2.storlead.com:172.18.194.163"
   networks:
-    vonedao_net:
+    ${ARTIFACTID}:
+       driver: overlay
        external: true
                           ' > ${params.module}-${params.profile}-stack.yml
                          """

+ 24 - 2
java/storlead-api/src/main/resources/application-test.yml

@@ -1,5 +1,5 @@
 server:
-  port: 10020
+  port: 10070
   tomcat:
     max-swallow-size: -1
     basedir: /app/sp/smarttrade/temp/${spring.profiles.active}
@@ -89,7 +89,7 @@ spring:
         connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
       datasource:
         master:
-          url: jdbc:mysql://mysql.test.storlead.com:39091/sp_smart_trade_test?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+          url: jdbc:mysql://mysql.test.storlead.com:39091/sp_saas_platform_test?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
           username: root
           password: rCgRgLjH99Xvg5BN
           driver-class-name: com.mysql.jdbc.Driver
@@ -113,6 +113,16 @@ spring:
           url: jdbc:mysql://mysql.test.storlead.com:39091/storlead_test?useSSL=false&autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&noDatetimeStringSync=true&serverTimezone=Asia/Shanghai
           username: root
           password: rCgRgLjH99Xvg5BN
+        otr:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://mysql.test.storlead.com:39091/storlead_test?useSSL=false&autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&noDatetimeStringSync=true&serverTimezone=Asia/Shanghai
+          username: root
+          password: rCgRgLjH99Xvg5BN
+        oa:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://139.159.206.64:65369/storlead_ecology_prod?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+          username: storlead_ecology
+          password: 3raNoDvbo7jqbwtedQGQ
 
 #mybatis plus 设置
 mybatis-plus:
@@ -162,6 +172,8 @@ logging:
               Jackson2ObjectMapperBuilder: off # 禁止okhttp4 打印警告日志 For Jackson Kotlin classes support please add "com.fasterxml.jackson.module:jackson-module-kotlin" to the classpath
     com:
       storlead: debug
+
+environment: test
 dify:
   base-url: http://192.168.1.77/v1/
   dataset-api-key: dataset-qfhXMNcjsRSwN5CK6aoWx3hl
@@ -172,3 +184,13 @@ dify:
     mail: app-4-qfhXMNcjsRSwN5CK6aoWx3hl #邮件
 
 domainname: https://test1.storlead.com
+
+
+storlead:
+  es:
+    enabled: false
+    host: 39.108.252.62
+    port: 9200
+    scheme: http
+    username: elastic
+    password: storlead123456

+ 13 - 0
java/storlead-knowledge/storlead-knowledge-api/src/main/java/com/storlead/knowledge/api/DocumentController.java

@@ -145,6 +145,19 @@ public class DocumentController {
         );
     }
 
+    @GetMapping("{dataset_id}/documents/{document_id}/download")
+    @ApiOperation("下载文档")
+    public Result<Object> downloadDocument (
+            @PathVariable String dataset_id,
+            @PathVariable String document_id
+    ) {
+        String url = difyProperties.getBaseUrl() + "datasets/"+dataset_id + "/documents/"+ document_id+"/download";
+        return httpService.get(
+                url,
+                null,
+                "Bearer "+difyProperties.getDatasetApiKey(),
+                new TypeReference<>() {});
+    }
 
 
     @GetMapping("getAllDocumnets")

+ 34 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/controller/CustomerAiAnalysisController.java

@@ -3,6 +3,7 @@ package com.storlead.trade.controller;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.framework.common.constant.CommonConstant;
 import com.storlead.framework.common.result.Result;
 import com.storlead.trade.dto.CustomerAnalysisResultDTO;
 import com.storlead.trade.dto.CustomerSingleAnalysisRequestDTO;
@@ -19,7 +20,11 @@ import org.springframework.util.ObjectUtils;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
+import java.time.LocalDateTime;
+import java.time.temporal.WeekFields;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 
 @RestController
@@ -141,4 +146,33 @@ public class CustomerAiAnalysisController {
         }
         return Result.result(statistics);
     }
+
+    @GetMapping("/customer-group/stats")
+    @ApiOperation("目标客户群统计数据")
+    public Result<Object> customerGroupStats() {
+        Map<String, Object> result = new HashMap<>();
+        
+        // 全部潜客 - 查询总数(排除已删除)
+        LambdaQueryWrapper<CustomerAnalysisResultEntity> totalWrapper = new LambdaQueryWrapper<>();
+        totalWrapper.eq(CustomerAnalysisResultEntity::getIsDelete, CommonConstant.DEL_FLAG_0);
+        long totalCustomers = customerAnalysisResultEntityService.count(totalWrapper);
+        result.put("totalCustomers", totalCustomers);
+        
+        // 本周新增 - 查询本周创建的数据(排除已删除)
+        LocalDateTime now = LocalDateTime.now();
+        LocalDateTime weekStart = now.with(WeekFields.of(Locale.getDefault()).dayOfWeek(), 1).toLocalDate().atStartOfDay();
+        LambdaQueryWrapper<CustomerAnalysisResultEntity> weekWrapper = new LambdaQueryWrapper<>();
+        weekWrapper.eq(CustomerAnalysisResultEntity::getIsDelete, CommonConstant.DEL_FLAG_0)
+                   .ge(CustomerAnalysisResultEntity::getCreateTime, weekStart);
+        long weeklyNew = customerAnalysisResultEntityService.count(weekWrapper);
+        result.put("weeklyNew", weeklyNew);
+        
+        // 高评分客户 - 默认值
+        result.put("highScoreCustomers", 124);
+        
+        // 沉默客户 - 默认值
+        result.put("silentCustomers", 89);
+        
+        return Result.result(result);
+    }
 }

+ 175 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/controller/MarketingCampaignSopDetailController.java

@@ -0,0 +1,175 @@
+package com.storlead.trade.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.framework.common.result.Result;
+import com.storlead.trade.dto.MarketingCampaignSopDetailDTO;
+import com.storlead.trade.entity.MarketingCampaignSopDetailEntity;
+import com.storlead.trade.service.MarketingCampaignSopDetailService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.BeanUtils;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.ObjectUtils;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 营销活动SOP明细 Controller
+ */
+@RestController
+@RequestMapping("/marketing/campaign/sop-detail")
+@Api(tags = "营销: 营销活动SOP详情明细")
+public class MarketingCampaignSopDetailController {
+
+    @Resource
+    private MarketingCampaignSopDetailService marketingCampaignSopDetailService;
+
+    /**
+     * 根据id查询详情
+     */
+    @GetMapping("/get/{id}")
+    @ApiOperation(value = "根据id查询营销活动SOP明细详情", notes = "根据主键id查询详情")
+    public Result<Object> getById(@PathVariable Long id) {
+        MarketingCampaignSopDetailEntity entity = marketingCampaignSopDetailService.getById(id);
+        if (entity == null) {
+            return Result.error("数据不存在");
+        }
+        return Result.result(entity);
+    }
+
+    /**
+     * 分页查询
+     */
+    @PostMapping("/list")
+    @ApiOperation(value = "分页查询营销活动SOP明细", notes = "支持按营销活动id筛选")
+    public Result<Object> list(@RequestBody MarketingCampaignSopDetailDTO dto) {
+        Page<MarketingCampaignSopDetailEntity> page = new Page<>(dto.getPageIndex(), dto.getPageSize());
+        LambdaQueryWrapper<MarketingCampaignSopDetailEntity> wrapper = new LambdaQueryWrapper<>();
+        // 按营销活动id筛选
+        if (!ObjectUtils.isEmpty(dto.getMarketingCampaignId())) {
+            wrapper.eq(MarketingCampaignSopDetailEntity::getMarketingCampaignId, dto.getMarketingCampaignId());
+        }
+        // 按SOP明细id筛选
+        if (!ObjectUtils.isEmpty(dto.getSopDetailId())) {
+            wrapper.eq(MarketingCampaignSopDetailEntity::getSopDetailId, dto.getSopDetailId());
+        }
+        // 按步骤序号筛选
+        if (!ObjectUtils.isEmpty(dto.getStep())) {
+            wrapper.eq(MarketingCampaignSopDetailEntity::getStep, dto.getStep());
+        }
+        // 按触达渠道筛选
+        if (!ObjectUtils.isEmpty(dto.getType())) {
+            wrapper.eq(MarketingCampaignSopDetailEntity::getType, dto.getType());
+        }
+        // 按创建时间倒序
+        wrapper.orderByDesc(MarketingCampaignSopDetailEntity::getCreateTime);
+        // 分页查询
+        IPage<MarketingCampaignSopDetailEntity> iPage = marketingCampaignSopDetailService.page(page, wrapper);
+        return Result.result(iPage);
+    }
+
+    /**
+     * 新增
+     */
+    @PostMapping("/add")
+    @ApiOperation(value = "新增营销活动SOP明细", notes = "支持单条新增")
+    @Transactional(rollbackFor = Throwable.class)
+    public Result<?> add(@RequestBody MarketingCampaignSopDetailDTO dto) {
+        if (ObjectUtils.isEmpty(dto.getMarketingCampaignId())) {
+            return Result.error("营销活动id不能为空");
+        }
+        if (ObjectUtils.isEmpty(dto.getSopDetailId())) {
+            return Result.error("SOP明细id不能为空");
+        }
+        MarketingCampaignSopDetailEntity entity = new MarketingCampaignSopDetailEntity();
+        BeanUtils.copyProperties(dto, entity);
+        boolean isOk = marketingCampaignSopDetailService.save(entity);
+        if (!isOk) {
+            return Result.error("添加失败");
+        }
+        return Result.result(entity);
+    }
+
+    /**
+     * 批量新增
+     */
+    @PostMapping("/add/batch")
+    @ApiOperation(value = "批量新增营销活动SOP明细", notes = "支持批量新增")
+    @Transactional(rollbackFor = Throwable.class)
+    public Result<?> addBatch(@RequestBody List<MarketingCampaignSopDetailDTO> dtoList) {
+        if (CollectionUtils.isEmpty(dtoList)) {
+            return Result.error("数据不能为空");
+        }
+        List<MarketingCampaignSopDetailEntity> entityList = new ArrayList<>();
+        for (MarketingCampaignSopDetailDTO dto : dtoList) {
+            MarketingCampaignSopDetailEntity entity = new MarketingCampaignSopDetailEntity();
+            BeanUtils.copyProperties(dto, entity);
+            entityList.add(entity);
+        }
+        boolean isOk = marketingCampaignSopDetailService.saveBatch(entityList);
+        if (!isOk) {
+            return Result.error("批量添加失败");
+        }
+        return Result.ok();
+    }
+
+    /**
+     * 编辑
+     */
+    @PostMapping("/edit")
+    @ApiOperation(value = "编辑营销活动SOP明细", notes = "编辑营销活动SOP明细")
+    @Transactional(rollbackFor = Throwable.class)
+    public Result<?> edit(@RequestBody MarketingCampaignSopDetailDTO dto) {
+        if (ObjectUtils.isEmpty(dto.getId())) {
+            return Result.error("id不能为空");
+        }
+        MarketingCampaignSopDetailEntity entity = new MarketingCampaignSopDetailEntity();
+        BeanUtils.copyProperties(dto, entity);
+        boolean isOk = marketingCampaignSopDetailService.updateById(entity);
+        if (!isOk) {
+            return Result.error("修改失败");
+        }
+        return Result.result(entity);
+    }
+
+    /**
+     * 删除
+     */
+    @PostMapping("/delete")
+    @ApiOperation(value = "删除营销活动SOP明细", notes = "支持单条id或批量idList删除")
+    @Transactional(rollbackFor = Throwable.class)
+    public Result<?> delete(@RequestBody MarketingCampaignSopDetailDTO dto) {
+        boolean isOk = false;
+        Long id = dto.getId();
+        if (!ObjectUtils.isEmpty(id)) {
+            isOk = marketingCampaignSopDetailService.removeById(id);
+        }
+        List<Long> idList = dto.getIdList();
+        if (!CollectionUtils.isEmpty(idList)) {
+            isOk = marketingCampaignSopDetailService.removeByIds(idList);
+        }
+        if (!isOk) {
+            return Result.error("删除失败");
+        }
+        return Result.ok();
+    }
+
+    /**
+     * 根据营销活动id查询SOP明细列表
+     */
+    @GetMapping("/campaign/{marketingCampaignId}")
+    @ApiOperation(value = "根据营销活动id查询SOP明细列表", notes = "查询该营销活动关联的所有SOP明细")
+    public Result<Object> getByCampaignId(@PathVariable Long marketingCampaignId) {
+        LambdaQueryWrapper<MarketingCampaignSopDetailEntity> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(MarketingCampaignSopDetailEntity::getMarketingCampaignId, marketingCampaignId);
+        wrapper.orderByAsc(MarketingCampaignSopDetailEntity::getStep);
+        List<MarketingCampaignSopDetailEntity> list = marketingCampaignSopDetailService.list(wrapper);
+        return Result.result(list);
+    }
+}

+ 14 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/controller/MarketingController.java

@@ -6,6 +6,7 @@ import com.storlead.framework.common.result.Result;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
 import org.springframework.util.ObjectUtils;
@@ -66,4 +67,17 @@ public class MarketingController {
         return Result.ok();
     }
 
+    @GetMapping("/dashboard/stats")
+    @ApiOperation("营销: 仪表盘统计数据")
+    public Result<Object> dashboardStats() {
+        return Result.result(new java.util.HashMap<String, Object>() {{
+            put("monthlySent", "2.8K");
+            put("avgOpenRate", "45%");
+            put("clickRate", "12%");
+            put("replyRate", "8%");
+            put("protectedCustomers", 156);
+            put("pendingReview", 23);
+        }});
+    }
+
 }

+ 3 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/dto/MarketingCampaignDTO.java

@@ -116,6 +116,9 @@ public class MarketingCampaignDTO extends Page {
     @TableField("sop_id")
     private Integer sopId;
 
+    @ApiModelProperty(value = "SOP序列详情idList")
+    private List<Long> sopDetailIdList;
+
     @ApiModelProperty(value = "未打开邮件跟进规则(1是0否)")
     @TableField("follow_up_rules_not_opened")
     private Integer followUpRulesNotOpened;

+ 51 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/dto/MarketingCampaignSopDetailDTO.java

@@ -0,0 +1,51 @@
+package com.storlead.trade.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 营销活动SOP明细 DTO
+ */
+@Data
+@ApiModel(value = "MarketingCampaignSopDetailDTO", description = "营销活动SOP明细请求")
+public class MarketingCampaignSopDetailDTO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "主键id(编辑时必填)")
+    private Long id;
+
+    @ApiModelProperty(value = "主键id(编辑时必填)")
+    private List<Long> idList;
+
+    @ApiModelProperty(value = "营销活动id")
+    private Long marketingCampaignId;
+
+    @ApiModelProperty(value = "SOP明细id")
+    private Long sopDetailId;
+
+    @ApiModelProperty(value = "步骤序号")
+    private Integer step;
+
+    @ApiModelProperty(value = "步骤名称")
+    private String name;
+
+    @ApiModelProperty(value = "邮件内容模板")
+    private String emailContent;
+
+    @ApiModelProperty(value = "间隔天数")
+    private String days;
+
+    @ApiModelProperty(value = "触达渠道")
+    private String type;
+
+    @ApiModelProperty(value = "页码", required = true)
+    private Integer pageIndex = 1;
+
+    @ApiModelProperty(value = "每页数量", required = true)
+    private Integer pageSize = 10;
+}

+ 5 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/dto/SopDetailCreateDTO.java

@@ -1,5 +1,6 @@
 package com.storlead.trade.dto;
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
@@ -21,6 +22,10 @@ public class SopDetailCreateDTO implements Serializable {
     @ApiModelProperty(value = "步骤名称", required = true)
     private String name;
 
+    @ApiModelProperty(value = "邮件内容模板")
+    @TableField("email_content")
+    private String emailContent;
+
     @ApiModelProperty(value = "间隔天数")
     private String days;
 

+ 5 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/dto/SopDetailUpdateDTO.java

@@ -1,5 +1,6 @@
 package com.storlead.trade.dto;
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
@@ -21,6 +22,10 @@ public class SopDetailUpdateDTO implements Serializable {
 
     @ApiModelProperty(value = "步骤名称")
     private String name;
+    
+    @ApiModelProperty(value = "邮件内容模板")
+    @TableField("email_content")
+    private String emailContent;
 
     @ApiModelProperty(value = "间隔天数")
     private String days;

+ 4 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/entity/MarketingCampaignEntity.java

@@ -114,6 +114,10 @@ public class MarketingCampaignEntity extends SysBaseField {
     @TableField("sop_id")
     private Integer sopId;
 
+    @ApiModelProperty(value = "sop详情id(逗号分隔)")
+    @TableField("sop_detail_ids")
+    private String sopDetailIds;
+
     @ApiModelProperty(value = "未打开邮件跟进规则(1是0否)")
     @TableField("follow_up_rules_not_opened")
     private Integer followUpRulesNotOpened;

+ 55 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/entity/MarketingCampaignSopDetailEntity.java

@@ -0,0 +1,55 @@
+package com.storlead.trade.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.storlead.framework.mybatis.entity.SysBaseField;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 营销活动SOP明细关联表 Entity
+ */
+@Data
+@TableName("marketing_campaign_sop_detail")
+@ApiModel(value = "MarketingCampaignSopDetailEntity", description = "营销活动SOP明细关联")
+public class MarketingCampaignSopDetailEntity extends SysBaseField implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "主键id")
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    @ApiModelProperty(value = "营销活动id")
+    @TableField("marketing_campaign_id")
+    private Long marketingCampaignId;
+
+    @ApiModelProperty(value = "SOP明细id")
+    @TableField("sop_detail_id")
+    private Long sopDetailId;
+
+    @ApiModelProperty(value = "步骤序号")
+    @TableField("step")
+    private Integer step;
+
+    @ApiModelProperty(value = "步骤名称")
+    @TableField("name")
+    private String name;
+
+    @ApiModelProperty(value = "邮件内容模板")
+    @TableField("email_content")
+    private String emailContent;
+
+    @ApiModelProperty(value = "间隔天数")
+    @TableField("days")
+    private String days;
+
+    @ApiModelProperty(value = "触达渠道")
+    @TableField("type")
+    private String type;
+}

+ 4 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/entity/SopDetailEntity.java

@@ -28,6 +28,10 @@ public class SopDetailEntity extends SysBaseField {
     @TableField("name")
     private String name;
 
+    @ApiModelProperty(value = "邮件内容模板")
+    @TableField("email_content")
+    private String emailContent;
+
     @ApiModelProperty(value = "间隔天数")
     @TableField("days")
     private String days;

+ 12 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/mapper/MarketingCampaignSopDetailMapper.java

@@ -0,0 +1,12 @@
+package com.storlead.trade.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.storlead.trade.entity.MarketingCampaignSopDetailEntity;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 营销活动SOP明细 Mapper接口
+ */
+@Mapper
+public interface MarketingCampaignSopDetailMapper extends BaseMapper<MarketingCampaignSopDetailEntity> {
+}

+ 1 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/service/MarketingCampaignEntityService.java

@@ -12,4 +12,5 @@ public interface MarketingCampaignEntityService extends MyBaseService<MarketingC
     Result<Object> addMarket(MarketingCampaignDTO marketingCampaignDTO);
 
     Result<Object> editMarket(MarketingCampaignDTO marketingCampaignDTO);
+    
 }

+ 10 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/service/MarketingCampaignSopDetailService.java

@@ -0,0 +1,10 @@
+package com.storlead.trade.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.storlead.trade.entity.MarketingCampaignSopDetailEntity;
+
+/**
+ * 营销活动SOP明细 Service接口
+ */
+public interface MarketingCampaignSopDetailService extends IService<MarketingCampaignSopDetailEntity> {
+}

+ 69 - 6
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/service/impl/MarketingCampaignEntityServiceImpl.java

@@ -5,14 +5,12 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.framework.common.util.StringUtil;
+import com.storlead.trade.controller.MarketingCampaignSopDetailController;
 import com.storlead.trade.dto.MarketingCampaignDTO;
-import com.storlead.trade.entity.CustomerAnalysisResultEntity;
-import com.storlead.trade.entity.CustomerBaseEntity;
-import com.storlead.trade.entity.MarketingCampaignEntity;
+import com.storlead.trade.entity.*;
 import com.storlead.trade.mapper.MarketingCampaignEntityMapper;
-import com.storlead.trade.service.CustomerAnalysisResultEntityService;
-import com.storlead.trade.service.CustomerBaseEntityService;
-import com.storlead.trade.service.MarketingCampaignEntityService;
+import com.storlead.trade.service.*;
 import com.storlead.trade.vo.MarketingCampaignVO;
 import com.storlead.framework.common.constant.CommonConstant;
 import com.storlead.framework.common.result.Result;
@@ -20,6 +18,7 @@ import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
 import org.springframework.beans.BeanUtils;
 import org.springframework.stereotype.Service;
 import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
 
 
 import javax.annotation.Resource;
@@ -36,6 +35,10 @@ public class MarketingCampaignEntityServiceImpl
     private CustomerBaseEntityService customerBaseEntityService;
     @Resource
     private CustomerAnalysisResultEntityService customerAnalysisResultEntityService;
+    @Resource
+    private SopDetailService sopDetailService;
+    @Resource
+    private MarketingCampaignSopDetailService marketingCampaignSopDetailService;
     @Override
     public Result<Object> getList(MarketingCampaignDTO marketingCampaignDTO) {
         IPage<MarketingCampaignVO> pageVO = new Page<>(marketingCampaignDTO.getPageIndex(), marketingCampaignDTO.getPageSize());
@@ -112,6 +115,36 @@ public class MarketingCampaignEntityServiceImpl
                 return Result.error("添加失败");
             }
         }
+        //添加关联sopDetailIdList,并调取ai生成邮件内容
+        List<Long> sopDetailIdList = marketingCampaignDTO.getSopDetailIdList();
+        if (!CollectionUtils.isEmpty(sopDetailIdList)) {
+            //将sopDetailIdList转为 逗号分隔的字符串
+            String sopDetailIdListString = StringUtil.longListToString(sopDetailIdList);
+            //判断是否和原有的相同
+            if (!StringUtils.isEmpty(marketingCampaignEntity.getSopDetailIds()) && !marketingCampaignEntity.getSopDetailIds().equals(sopDetailIdListString)) {
+
+                marketingCampaignEntity.setSopDetailIds(sopDetailIdListString);
+                this.updateById(marketingCampaignEntity);
+                //删除原有关联sopDetail
+                LambdaUpdateWrapper<MarketingCampaignSopDetailEntity> MarketingCampaignSopDetailUpdateWrapper = new LambdaUpdateWrapper<>();
+                MarketingCampaignSopDetailUpdateWrapper.eq(MarketingCampaignSopDetailEntity::getMarketingCampaignId, marketingCampaignEntity.getId());
+                MarketingCampaignSopDetailUpdateWrapper.set(MarketingCampaignSopDetailEntity::getIsDelete, 1);
+                marketingCampaignSopDetailService.update(MarketingCampaignSopDetailUpdateWrapper);
+
+                List<SopDetailEntity> sopDetailEntityList = sopDetailService.listByIds(sopDetailIdList).stream().toList();
+                List<MarketingCampaignSopDetailEntity> marketingCampaignSopDetailEntityList = new ArrayList<>();
+                for (SopDetailEntity sopDetailEntity : sopDetailEntityList) {
+                    MarketingCampaignSopDetailEntity marketingCampaignSopDetailEntity = new MarketingCampaignSopDetailEntity();
+                    marketingCampaignSopDetailEntity.setMarketingCampaignId(marketingCampaignEntity.getId());
+                    marketingCampaignSopDetailEntity.setSopDetailId(sopDetailEntity.getId());
+                    //预留补充ai生成的邮件内容模板
+//                    marketingCampaignSopDetailEntity.setEmailContent();
+                    marketingCampaignSopDetailEntityList.add(marketingCampaignSopDetailEntity);
+                }
+                boolean saveOk = marketingCampaignSopDetailService.saveBatch(marketingCampaignSopDetailEntityList);
+            }
+
+        }
         return Result.ok();
     }
 
@@ -141,6 +174,36 @@ public class MarketingCampaignEntityServiceImpl
                 return Result.error("修改失败");
             }
         }
+        //添加关联sopDetailIdList,并调取ai生成邮件内容
+        List<Long> sopDetailIdList = marketingCampaignDTO.getSopDetailIdList();
+        if (!CollectionUtils.isEmpty(sopDetailIdList)) {
+            //将sopDetailIdList转为 逗号分隔的字符串
+            String sopDetailIdListString = StringUtil.longListToString(sopDetailIdList);
+            //判断是否和原有的相同
+            if (!StringUtils.isEmpty(marketingCampaignEntity.getSopDetailIds()) && !marketingCampaignEntity.getSopDetailIds().equals(sopDetailIdListString)) {
+
+                marketingCampaignEntity.setSopDetailIds(sopDetailIdListString);
+                this.updateById(marketingCampaignEntity);
+                //删除原有关联sopDetail
+                LambdaUpdateWrapper<MarketingCampaignSopDetailEntity> MarketingCampaignSopDetailUpdateWrapper = new LambdaUpdateWrapper<>();
+                MarketingCampaignSopDetailUpdateWrapper.eq(MarketingCampaignSopDetailEntity::getMarketingCampaignId, marketingCampaignEntity.getId());
+                MarketingCampaignSopDetailUpdateWrapper.set(MarketingCampaignSopDetailEntity::getIsDelete, 1);
+                marketingCampaignSopDetailService.update(MarketingCampaignSopDetailUpdateWrapper);
+
+                List<SopDetailEntity> sopDetailEntityList = sopDetailService.listByIds(sopDetailIdList).stream().toList();
+                List<MarketingCampaignSopDetailEntity> marketingCampaignSopDetailEntityList = new ArrayList<>();
+                for (SopDetailEntity sopDetailEntity : sopDetailEntityList) {
+                    MarketingCampaignSopDetailEntity marketingCampaignSopDetailEntity = new MarketingCampaignSopDetailEntity();
+                    marketingCampaignSopDetailEntity.setMarketingCampaignId(marketingCampaignEntity.getId());
+                    marketingCampaignSopDetailEntity.setSopDetailId(sopDetailEntity.getId());
+                    //预留补充ai生成的邮件内容模板
+//                    marketingCampaignSopDetailEntity.setEmailContent();
+                    marketingCampaignSopDetailEntityList.add(marketingCampaignSopDetailEntity);
+                }
+                boolean saveOk = marketingCampaignSopDetailService.saveBatch(marketingCampaignSopDetailEntityList);
+            }
+
+        }
         return Result.ok();
     }
 }

+ 16 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/service/impl/MarketingCampaignSopDetailServiceImpl.java

@@ -0,0 +1,16 @@
+package com.storlead.trade.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.storlead.trade.entity.MarketingCampaignSopDetailEntity;
+import com.storlead.trade.mapper.MarketingCampaignSopDetailMapper;
+import com.storlead.trade.service.MarketingCampaignSopDetailService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 营销活动SOP明细 Service实现
+ */
+@Service
+public class MarketingCampaignSopDetailServiceImpl
+        extends ServiceImpl<MarketingCampaignSopDetailMapper, MarketingCampaignSopDetailEntity>
+        implements MarketingCampaignSopDetailService {
+}

+ 1 - 1
ui/smarttrade-platform/.env.production

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

+ 4 - 0
ui/smarttrade-platform/.env.test

@@ -0,0 +1,4 @@
+# 测试环境后台接口地址
+VITE_API_BASE_URL=/
+
+VITE_API_APP_ID=7

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

@@ -4,7 +4,9 @@
   "private": true,
   "type": "module",
   "scripts": {
-    "dev": "vite",
+    "dev": "vite serve --mode development",
+    "test": "vite build --mode test",
+    "prod": "vite build --mode production",
     "build": "vite build",
     "preview": "vite preview"
   },

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

@@ -9,4 +9,5 @@ export const setMessageConfig = (data) => post('/router/rest/sys/message/config/
 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)
-export const getLoginData = (data) => post('/router/rest/sys/login/log/pageList', data)
+export const getLoginData = (data) => post('/router/rest/sys/login/log/pageList', data)
+export const getListTree = (data) => post('/router/rest/sys/user/pagelist', data)

+ 1 - 0
ui/smarttrade-platform/src/api/marketing/index.js

@@ -7,3 +7,4 @@ export const editSop = (id, data = {}) => put(`/router/rest/api/sop/${id}`, data
 export const getSopById = (id) => get(`/router/rest/api/sop/${id}`)
 export const addMarketing = (data = {}) => post('/router/rest/marketing/add', data)
 export const editMarketing = (data = {}) => post('/router/rest/marketing/edit', data)
+export const marketingAnalysis = () => get('/router/rest/marketing/dashboard/stats')

+ 57 - 5
ui/smarttrade-platform/src/components/addMarketing.vue

@@ -210,31 +210,62 @@
                                     v-model="regionValue" clearable filterable @change="getRegion" />
                             </div>
                             <div class="form-group">
-                                <label class="form-label">行业</label>
+                                <!-- <label class="form-label">行业</label>
                                 <el-select v-model="newCampaign.filters.industry" clearable placeholder="全部行业">
                                     <el-option label="数据中心" value="datacenter" />
                                     <el-option label="消费电子" value="consumer" />
                                     <el-option label="电子分销" value="distributor" />
                                     <el-option label="零售" value="retail" />
                                     <el-option label="制造业" value="manufacturing" />
+                                </el-select> -->
+                                <label class="form-label">客户状态</label>
+                                <el-select v-model="customerParams.customerStatusDictValue" clearable placeholder="客户状态">
+                                    <el-option label="暂无需求" value="1" />
+                                    <el-option label="潜在客户" value="2" />
+                                    <el-option label="意向客户" value="3" />
+                                    <el-option label="忠诚客户" value="4" />
+                                    <el-option label="了解产品" value="11" />
+                                    <el-option label="正在跟进" value="22" />
+                                    <el-option label="寄样试用" value="33" />
+                                    <el-option label="意向购买" value="44" />
+                                    <el-option label="准备付款" value="55" />
+                                    <el-option label="暂时搁置" value="66" />
                                 </el-select>
                             </div>
                         </div>
                         <div class="filter-row">
                             <div class="form-group">
-                                <label class="form-label">意向分数</label>
+                                <!-- <label class="form-label">意向分数</label>
                                 <el-select v-model="newCampaign.filters.scoreRange" clearable placeholder="不限">
                                     <el-option label="80-100分" value="high" />
                                     <el-option label="60-79分" value="medium" />
                                     <el-option label="40-59分" value="low" />
+                                </el-select> -->
+                                <label class="form-label">客户类型</label>
+                                <el-select v-model="customerParams.customerTypeDictValue" clearable placeholder="客户类型">
+                                    <el-option label="供应厂商" value="4" />
+                                    <el-option label="代理合作" value="5" />
+                                    <el-option label="普通客户" value="1" />
+                                    <el-option label="友商客户" value="3" />
+                                    <el-option label="定制客户" value="2" />
+                                    <el-option label="内部客户" value="6" />
+                                    <el-option label="客户保护" value="10" />
+                                    <el-option label="协助项目" value="11" />
                                 </el-select>
                             </div>
                             <div class="form-group">
-                                <label class="form-label">客户规模</label>
+                                <!-- <label class="form-label">客户规模</label>
                                 <el-select v-model="newCampaign.filters.companySize" clearable placeholder="不限">
                                     <el-option label="大型企业(500+)" value="large" />
                                     <el-option label="中型企业(100-500)" value="medium" />
                                     <el-option label="小微企业(<100)" value="small" />
+                                </el-select> -->
+                                <label class="form-label">客户等级</label>
+                                <el-select v-model="customerParams.customerLevelDictValue" clearable placeholder="客户等级">
+                                    <el-option label="A级" value="1" />
+                                    <el-option label="B级" value="2" />
+                                    <el-option label="C级" value="3" />
+                                    <el-option label="D级" value="4" />
                                 </el-select>
                             </div>
                         </div>
@@ -884,6 +915,7 @@
     import PreviewCustomerModal from './PreviewCustomerModal.vue'
     import { getSopList, addMarketing, editMarketing, addSop, editSop } from '@/api/marketing'
     import { getCustomerList } from '@/api/customer'
+    import { getListTree } from '@/api/layout'
     import { getUserEmails } from '@/api/emails'
     import { region } from '@/utils/region';
 
@@ -1154,10 +1186,19 @@
             region: '', industry: '', scoreRange: '', stage: '', companySize: '', purchaseCycle: '',
             keyword: '', selectedTags: []
         }
+        customerParams.value = {
+            blurry: '',
+            country: '',
+            customerStatusDictValue: '',
+            customerTypeDictValue: '',
+            customerLevelDictValue: ''
+        }
+        getCustomerData()
     }
     const removeTag = (tag) => {
         const idx = newCampaign.value.filters.selectedTags.indexOf(tag)
         if (idx > -1) newCampaign.value.filters.selectedTags.splice(idx, 1)
+        customerList.value = customerList.value.filter(c => !c.customerName.includes(tag))
     }
     const applySuggestion = (type) => {
         if (type === 'name') {
@@ -1282,6 +1323,13 @@
         getSop(data?.sopId || '')
         customerList.value = []
         getEmails()
+        getTree()
+    }
+
+    const getTree = () => {
+        getListTree({isIncludeResign: false, reviewTag: 1, pageIndex: 1, pageSize: 10000 }).then(res => {
+            console.log('res :>> ', res);
+        })
     }
 
     const regionValue = ref([])
@@ -1289,13 +1337,18 @@
         customerParams.value.country = value ? value[1] : ''
     }
     const customerParams = ref({
-        country: ''
+        blurry: '',
+        country: '',
+        customerStatusDictValue: '',
+        customerTypeDictValue: '',
+        customerLevelDictValue: ''
     })
     const customerList = ref([])
     const sourceData = ref({})
     const getCustomerData = () => {
         getCustomerList({ pageIndex: 1, pageSize: 10000, ...customerParams.value }).then(res => {
             customerList.value = res.result.records || []
+            newCampaign.value.filters.selectedTags = customerList.value.map(item => item.customerName)
             sourceData.value = {}
             customerList.value.map(item => {
                 if(!item.customerSourceName) return
@@ -1305,7 +1358,6 @@
                     sourceData.value[item.customerSourceName] = 1
                 }
             })
-            console.log('sourceData.value :>> ', sourceData.value);
         })
     }
 

+ 54 - 26
ui/smarttrade-platform/src/views/MarketingView.vue

@@ -23,42 +23,42 @@
       <div class="stat-card-lg">
         <div class="stat-icon-lg blue"><i class="fas fa-paper-plane"></i></div>
         <div class="stat-content-lg">
-          <h3>{{ formatNumber(marketingStats.totalSent) }}</h3>
+          <h3>{{ statsData?.monthlySent }}</h3>
           <p>本月发送</p>
         </div>
       </div>
       <div class="stat-card-lg">
         <div class="stat-icon-lg green"><i class="fas fa-envelope-open"></i></div>
         <div class="stat-content-lg">
-          <h3>{{ marketingStats.openRate }}%</h3>
+          <h3>{{ statsData?.avgOpenRate }}</h3>
           <p>平均打开率</p>
         </div>
       </div>
       <div class="stat-card-lg">
         <div class="stat-icon-lg purple"><i class="fas fa-mouse-pointer"></i></div>
         <div class="stat-content-lg">
-          <h3>{{ marketingStats.clickRate }}%</h3>
+          <h3>{{ statsData?.clickRate }}</h3>
           <p>点击率</p>
         </div>
       </div>
       <div class="stat-card-lg">
         <div class="stat-icon-lg orange"><i class="fas fa-reply"></i></div>
         <div class="stat-content-lg">
-          <h3>{{ marketingStats.replyRate }}%</h3>
+          <h3>{{ statsData?.replyRate }}</h3>
           <p>回复率</p>
         </div>
       </div>
       <div class="stat-card-lg">
         <div class="stat-icon-lg success"><i class="fas fa-shield-alt"></i></div>
         <div class="stat-content-lg">
-          <h3>{{ marketingStats.protectedCustomers }}</h3>
+          <h3>{{ statsData?.protectedCustomers }}</h3>
           <p>受保护客户</p>
         </div>
       </div>
       <div class="stat-card-lg">
         <div class="stat-icon-lg warning"><i class="fas fa-exclamation-circle"></i></div>
         <div class="stat-content-lg">
-          <h3>{{ marketingStats.pendingReview }}</h3>
+          <h3>{{ statsData?.pendingReview }}</h3>
           <p>待审核</p>
         </div>
       </div>
@@ -430,29 +430,28 @@
               <h4>输入要求</h4>
               <div class="form-group">
                 <label class="form-label">邮件类型</label>
-                <select class="form-select" v-model="aiGenerator.type">
-                  <option value="introduction">首次介绍信</option>
-                  <option value="followup">跟进邮件</option>
-                  <option value="product">产品推荐</option>
-                  <option value="offer">报价邮件</option>
-                </select>
+                <el-select v-model="aiGenerator.type">
+                  <el-option value="introduction" label="首次介绍信"></el-option>
+                  <el-option value="followup" label="跟进邮件"></el-option>
+                  <el-option value="product" label="产品推荐"></el-option>
+                  <el-option value="offer" label="报价邮件"></el-option>
+                </el-select>
               </div>
               <div class="form-group">
                 <label class="form-label">产品/服务</label>
-                <select class="form-select" v-model="aiGenerator.product">
-                  <option value="ssd">固态硬盘 SSD</option>
-                  <option value="hdd">机械硬盘 HDD</option>
-                  <option value="memory">内存条</option>
-                  <option value="storage">存储方案</option>
-                </select>
+                <el-select v-model="aiGenerator.product">
+                  <el-option value="ssd" label="固态硬盘 SSD"></el-option>
+                  <el-option value="hdd" label="机械硬盘 HDD"></el-option>
+                  <el-option value="memory" label="内存条"></el-option>
+                  <el-option value="storage" label="存储方案"></el-option>
+                </el-select>
               </div>
               <div class="form-group">
                 <label class="form-label">核心卖点</label>
-                <div class="checkbox-grid">
-                  <label v-for="point in sellingPoints" :key="point.id" class="checkbox-item">
-                    <input type="checkbox" v-model="aiGenerator.selectedPoints" :value="point.id">
-                    <span>{{ point.label }}</span>
-                  </label>
+                <div>
+                  <el-checkbox-group v-model="aiGenerator.selectedPoints">
+                    <el-checkbox :label="point.label" :value="point.id" v-for="point in sellingPoints" />
+                  </el-checkbox-group>
                 </div>
               </div>
               <div class="form-group">
@@ -473,8 +472,7 @@
               </div>
               <div class="form-group">
                 <label class="form-label">额外要求</label>
-                <textarea class="form-textarea" v-model="aiGenerator.additionalRequirements" rows="2"
-                          placeholder="例如:强调价格优势、提及促销信息"></textarea>
+                <el-input type="textarea" v-model="aiGenerator.additionalRequirements" :rows="2" placeholder="例如:强调价格优势、提及促销信息"></el-input>
               </div>
               <button class="btn btn-primary btn-block" @click="generateEmail" :disabled="isGenerating">
                 <i class="fas fa-magic" :class="{ 'fa-spin': isGenerating }"></i>
@@ -1256,7 +1254,7 @@
 <script setup>
 import { ref, computed, onMounted } from 'vue'
 import AddMarketing from '../components/addMarketing.vue'
-import { getMarketingList, getSopById } from '../api/marketing'
+import { getMarketingList, getSopById, marketingAnalysis } from '../api/marketing'
 
 const filterTab = ref('all')
 
@@ -1291,6 +1289,7 @@ const openCreateModal = () => {
 const marketingList = ref([])
 onMounted(() => {
   getData()
+  getStatsData()
 })
 
 const getData = () => {
@@ -1308,6 +1307,13 @@ const getData = () => {
   })
 }
 
+const statsData = ref()
+const getStatsData = () => {
+  marketingAnalysis().then(res => {
+    statsData.value = res.result
+  })
+}
+
 
 // History filters
 const historySearch = ref('')
@@ -1777,4 +1783,26 @@ const editCampaign = (campaign) => {
 
 <style scoped>
  @import '../assets/styles/marketing.css';
+
+.ai-modal :deep(.el-textarea__inner),
+.ai-modal :deep(.el-select .el-select__wrapper) {
+    background: var(--bg-dark);
+    border: 1px solid var(--border);
+    box-shadow: none;
+    border-radius: 8px;
+    color: var(--text-primary);
+    width: 100%;
+    box-sizing: border-box;
+    min-width: 0;
+}
+.ai-modal :deep(.el-select__selection .el-select__placeholder:not(.is-transparent)) {
+    color: var(--text-primary);
+}
+.ai-modal :deep(.el-checkbox__input.is-checked .el-checkbox__inner) {
+    background: var(--primary);
+    border-color: var(--primary);
+}
+.ai-modal :deep(.el-checkbox__label) {
+    color: var(--text-primary);
+}
 </style>

+ 7 - 7
ui/sp-storlead-center/src/api/public.ts

@@ -174,27 +174,27 @@ export const listMessage = (data: any) => {
 
 /**
  * 获取类型以及未读数量
- * @param data
+ * @param appId 应用ID
  * @returns {*}
  */
-export const getMessageType = () => {
+export const getMessageType = (appId?: string | number) => {
     return request({
-        url: '/sys/inside/message/getNoReadCount',
+        url: '/sys/message/getNoReadCount',
         method: 'post',
-        data: {}
+        data: { appId }
     });
 };
 
 /**
  * 获取公告未读数量
- * @param data
+ * @param appId 应用ID
  * @returns {*}
  */
-export const getBulletinUnreadCount = () => {
+export const getBulletinUnreadCount = (appId?: string | number) => {
     return request({
         url: '/bulletin/unread-count',
         method: 'get',
-        params: {}
+        params: { appId }
     });
 };
 

+ 10 - 2
ui/sp-storlead-center/src/api/system/bulletin/types.ts

@@ -128,12 +128,16 @@ export interface BulletinForm extends BaseEntity {
    * 显示顺序
    */
   sort?: number;
-  columnValue?: string|number;
+    columnValue?: string|number;
   noticeUserIdList?: string[]|number[];
   isTalk?: number;
   remindMode?: string;
   fileIds?: string[];
   files?: string;
+  /**
+   * 所属应用id
+   */
+  appId?: string | number;
 
 }
 
@@ -198,11 +202,15 @@ export interface BulletinQuery extends PageQuery {
    * 日期范围参数
    */
   params?: any;
-  blurry?: string;
+    blurry?: string;
   startDate?: string;
   endDate?: string;
   columnValue?: string|number;
   createByIds?: string[]|number[];
+  /**
+   * 所属应用id
+   */
+  appId?: string | number;
 }
 
 

+ 2 - 2
ui/sp-storlead-center/src/api/system/dept/index.ts

@@ -4,11 +4,11 @@ import { DeptForm, DeptQuery, DeptVO } from './types';
 
 
 // 查询部门列表
-export const listDeptList = (types: number[], menuId: string | undefined, enable?: number) => {
+export const listDeptList = (types: number[], menuId: string | undefined, enable?: number, appId?: string | number) => {
   return request({
     url: '/sys/dept/orgTree',
     method: 'post',
-    data: { orgTypes: types, reviewTag: 1, scopeMenuId: menuId, enable }
+    data: { orgTypes: types, reviewTag: 1, scopeMenuId: menuId, enable, appId }
   });
 };
 // 查询部门列表

+ 2 - 0
ui/sp-storlead-center/src/layout/components/Navbar.vue

@@ -148,6 +148,8 @@ const getFirstLeafPath = (routes: any[], parentPath = ''): string => {
 // 切换应用系统:更新激活应用并跳转第一个可用路由
 const switchApp = (app: any) => {
     permissionStore.setActiveApp(app.appId);
+    // 切换应用后立即刷新消息未读数
+    useNoticeStore().getMessageCount(app.appId);
     const targetPath = getFirstLeafPath(app.children);
     if (targetPath) {
         router.push(targetPath).catch(() => {});

+ 14 - 1
ui/sp-storlead-center/src/layout/components/notice/index.vue

@@ -70,6 +70,9 @@
     import { listMessage, getMessageType, updateRead, updateReadAll, clearRead } from '@/api/public'
     import setMsgTools from './SetMsgTools.vue'
     import moment from "moment";
+    import { usePermissionStore } from '@/store/modules/permission';
+
+    const permissionStore = usePermissionStore();
 
     const typeData = ref([])
     const setMsgToolsRef = ref()
@@ -81,6 +84,7 @@
         pageIndex: 1,
         pageSize: 10,
         messageType: 0,
+        appId: undefined as string | number | undefined,
     })
 
     const noticeData = ref([])
@@ -88,7 +92,7 @@
 
     const router = useRouter();
     const getType = () => {
-        getMessageType().then(res => {
+        getMessageType(permissionStore.activeAppId).then(res => {
             if(res.success) {
                 typeData.value = res.result
                 search.value.messageType = Number(router.currentRoute.value.query.messageType) as number
@@ -189,7 +193,16 @@
         }
     });
 
+    // 监听应用切换,刷新消息类型和列表
+    watch(() => permissionStore.activeAppId, (newAppId) => {
+        if (newAppId) {
+            search.value.appId = newAppId;
+            getType();
+        }
+    });
+
     onMounted(() => {
+        search.value.appId = permissionStore.activeAppId;
         getType()
     })
 </script>

+ 3 - 2
ui/sp-storlead-center/src/permission.ts

@@ -92,11 +92,12 @@ router.beforeEach(async (to, from, next) => {
                     });
                     // 邮箱页面不需要循环调未读数接口
                     if(to.path.indexOf('/email') == -1) {
+                        const currentAppId = usePermissionStore().activeAppId;
                         useNoticeStore().getCount() // 邮箱未读
-                        useNoticeStore().getMessageCount() // 消息未读
+                        useNoticeStore().getMessageCount(currentAppId) // 消息未读
                         setInterval(() => {
                             useNoticeStore().getCount()
-                            useNoticeStore().getMessageCount()
+                            useNoticeStore().getMessageCount(usePermissionStore().activeAppId)
                         }, 1000 * 60)
                     }
                     next({...to, replace: true}); // hack方法 确保addRoutes已完成

+ 1 - 1
ui/sp-storlead-center/src/settings.ts

@@ -9,7 +9,7 @@ const setting: DefaultSettings = {
   /**
    * 侧边栏主题 深色主题theme-dark,浅色主题theme-light
    */
-  sideTheme: 'theme-dark',
+  sideTheme: 'theme-light',
   /**
    * 是否系统布局配置
    */

+ 1 - 1
ui/sp-storlead-center/src/store/modules/app.ts

@@ -4,7 +4,7 @@ import enUS from 'element-plus/es/locale/lang/en';
 export const useAppStore = defineStore('app', () => {
   const sidebarStatus = useStorage('sidebarStatus', '1');
   const sidebar = reactive({
-    opened: sidebarStatus.value ? !!+sidebarStatus.value : true,
+    opened: true,
     withoutAnimation: false,
     hide: false,
 

+ 4 - 4
ui/sp-storlead-center/src/store/modules/notice.ts

@@ -44,14 +44,14 @@ export const useNoticeStore = defineStore('notice', () => {
     })
   }
 
-  // 获取消息未读数
-  const getMessageCount = async() => {
-    await getMessageType().then(res => {
+    // 获取消息未读数
+  const getMessageCount = async(appId?: string | number) => {
+    await getMessageType(appId).then(res => {
       let countList = (res.result || []).map(item => item.stateNumber)
       state.messageNoReadCount = eval(countList.join("+"))
       state.messageData = res.result || []
     })
-    await getBulletinUnreadCount().then(res => {
+    await getBulletinUnreadCount(appId).then(res => {
       state.bulletinNoReadCount = res.result.count
     })
   }

+ 5 - 0
ui/sp-storlead-center/src/store/modules/permission.ts

@@ -123,6 +123,11 @@ export const usePermissionStore = defineStore('permission', () => {
      */
     const filterAsyncRouter = (asyncRouterMap: RouteOption[], lastRouter?: RouteOption, type = false): RouteOption[] => {
         return asyncRouterMap.filter((route) => {
+            // 统一 hidden 字段类型:后端可能返回 Integer 0/1 或 String "0"/"1"
+            // JS 中 "0" 是 truthy,会导致可见路由被错误当成隐藏路由
+            const rawHidden = (route as any).hidden;
+            (route as any).hidden = rawHidden === true || rawHidden === 1 || rawHidden === '1';
+
             if (type && route.children) {
                 route.children = filterChildren(route.children, undefined);
             }

+ 6 - 2
ui/sp-storlead-center/src/utils/common.ts

@@ -185,9 +185,13 @@ export const handleTree = <T>(data: any[], id?: string, parentId?: string, child
       tree.push(d);
     }
   }
+  const visited = new Set<any>();
   const adaptToChildrenList = (o: any) => {
-    if (childrenListMap[o[config.id]] !== null) {
-      o[config.childrenList] = childrenListMap[o[config.id]];
+    const id = o[config.id];
+    if (visited.has(id)) return;
+    visited.add(id);
+    if (childrenListMap[id] != null) {
+      o[config.childrenList] = childrenListMap[id];
     }
     if (o[config.childrenList]) {
       for (const c of o[config.childrenList]) {

+ 1 - 1
ui/sp-storlead-center/src/utils/request.ts

@@ -182,7 +182,7 @@ service.interceptors.response.use(
     } else if (message.includes('Request failed with status code')) {
       message = '系统接口' + message.substr(message.length - 3) + '异常';
     }
-    let msgUrls  = ['/sys/inside/message/getNoReadCount', '/mail/get_new_mail', '/mail/receive_mail']
+    let msgUrls  = ['/sys/message/getNoReadCount', '/mail/get_new_mail', '/mail/receive_mail']
     if(msgUrls.includes(error?.config?.url)) {
       return false
     }else {

+ 17 - 5
ui/sp-storlead-center/src/views/system/bulletin/index.vue

@@ -1,6 +1,7 @@
 <template>
     <el-card shadow="never" class="list-card">
         <template #header>
+            <AppTabs v-model="currentAppId" @change="handleAppChange" />
             <div class="search" v-show="showSearch">
                 <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
                     <el-form-item  prop="noticeUserIds">
@@ -39,7 +40,7 @@
             </div>
         </template>
 
-        <Table ref="bulletinTable"
+        <Table v-if="currentAppId" ref="bulletinTable"
                :tableParams="tableParams"
                :btnRole="leftBtn"
                @handleAdd="handleAdd"
@@ -119,6 +120,14 @@
     import Table from '@/components/Table/index.vue'
     import { tableTypes } from '@/components/Table/types'
     import {useDictCache} from '@/hooks/web/useDict'
+    import AppTabs from '@/components/AppTabs/index.vue'
+
+    const currentAppId = ref('')
+    const handleAppChange = () => {
+        ;(tableParams.queryParams as any).appId = currentAppId.value
+        bulletinTable.value?.getData()
+    }
+
     const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 
 
@@ -179,7 +188,7 @@
             },
         ],
         keyId: 'id',
-        queryParams: {
+                queryParams: {
             deadlineDate: undefined,
             title: undefined,
             content: undefined,
@@ -191,13 +200,14 @@
             ownerBy: undefined,
             formerOwnerBy: undefined,
             sort: undefined,
+            appId: undefined,
         },
         listFn: listBulletin,
         delFn: delBulletin,
     })
 
 
-    const initFormData: BulletinForm = {
+        const initFormData: BulletinForm = {
         id: undefined,
         deadlineDate: undefined,
         title: undefined,
@@ -210,7 +220,8 @@
         ownerBy: undefined,
         formerOwnerBy: undefined,
         sort: undefined,
-        noticeUserIdList:[]
+        noticeUserIdList: [],
+        appId: undefined,
     }
     const data = reactive<PageData<BulletinForm, BulletinQuery>>({
         form: {...initFormData},
@@ -278,9 +289,10 @@
         multiple.value = !selection.length;
     }
 
-    /** 新增按钮操作 */
+        /** 新增按钮操作 */
     const handleAdd = () => {
         reset();
+        form.value.appId = currentAppId.value;
         dialog.visible = true;
         dialog.title = "添加公告主";
     }

+ 11 - 0
ui/sp-storlead-center/src/views/system/cueWordTemplate/index.vue

@@ -1,6 +1,10 @@
 <template>
 	<el-card shadow="never" class="list-card">
+		<template #header>
+			<AppTabs v-model="currentAppId" @change="handleAppChange" />
+		</template>
 		<Table
+            v-if="currentAppId"
             ref="cueWordTemplateTable"
             :tableParams="tableParams"
             :btnRole="roleBtn"
@@ -21,6 +25,13 @@ import auth from '@/plugins/auth'
 import Table from '@/components/Table/index.vue'
 import { tableTypes } from '@/components/Table/types'
 import addCueWordTemplate from './add.vue'
+import AppTabs from '@/components/AppTabs/index.vue'
+
+const currentAppId = ref('')
+const handleAppChange = () => {
+	;(tableParams.queryParams as any).appId = currentAppId.value
+	cueWordTemplateTable.value?.getData()
+}
 
 const roleBtn = ref([
     { type: 'add', hasPermi: 'system:cueWordTemplate:add', },

+ 11 - 1
ui/sp-storlead-center/src/views/system/currencySetting/index.vue

@@ -1,6 +1,9 @@
 <template>
     <el-card shadow="never" class="list-card">
-        <Table ref="currencySettingTable"
+        <template #header>
+            <AppTabs v-model="currentAppId" @change="handleAppChange" />
+        </template>
+        <Table v-if="currentAppId" ref="currencySettingTable"
                :tableParams="tableParams"
                :btnRole="leftBtn"
                @handleAdd="handleAdd"
@@ -49,6 +52,13 @@ import {CurrencySettingVO, CurrencySettingQuery, CurrencySettingForm} from '@/ap
 import auth from '@/plugins/auth'
 import Table from '@/components/Table/index.vue'
 import {tableTypes} from '@/components/Table/types'
+import AppTabs from '@/components/AppTabs/index.vue'
+
+const currentAppId = ref('')
+const handleAppChange = () => {
+    ;(tableParams.queryParams as any).appId = currentAppId.value
+    currencySettingTable.value?.getData()
+}
 
 const {proxy} = getCurrentInstance() as ComponentInternalInstance;
 

+ 1 - 1
ui/sp-storlead-center/src/views/system/dept/index.vue

@@ -169,7 +169,7 @@ const { form, rules } = toRefs<PageData<DeptForm, DeptQuery>>(data)
 const getList = async () => {
     loading.value = true;
     // queryParams.value = [10, 20, 30]
-    const res = await listDeptList([10, 20, 30], undefined);
+    const res = await listDeptList([10, 20, 30], undefined, undefined, undefined);
     // const data = proxy?.handleTree<DeptVO>(res.data, "deptId")
     // if (data) {
     //   deptList.value = res

+ 9 - 2
ui/sp-storlead-center/src/views/system/dict/index.vue

@@ -1,8 +1,8 @@
 <template>
-
     <el-card shadow="never" class="list-card">
 
         <template #header>
+            <AppTabs v-model="currentAppId" @change="handleAppChange" />
             <el-tabs v-model="queryParams.dictType"  @tab-change="changeTab">
                 <el-tab-pane name="" label="全部" ></el-tab-pane>
                 <el-tab-pane name="1" label="业务字典" ></el-tab-pane>
@@ -25,7 +25,7 @@
             </div>
         </template>
 
-        <Table ref="dictTable"
+        <Table v-if="currentAppId" ref="dictTable"
                :tableParams="tableParams"
                :btnRole="leftBtn"
                @handleAdd="handleAdd"
@@ -79,6 +79,13 @@ import auth from '@/plugins/auth'
 import Table from '@/components/Table/index.vue'
 import indexDetail from './indexDetail.vue'
 import { tableTypes } from '@/components/Table/types'
+import AppTabs from '@/components/AppTabs/index.vue'
+
+const currentAppId = ref('')
+const handleAppChange = () => {
+    ;(tableParams.queryParams as any).appId = currentAppId.value
+    dictTable.value?.getData()
+}
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const dictDetailref = ref()

+ 8 - 1
ui/sp-storlead-center/src/views/system/menu/index.vue

@@ -1,5 +1,6 @@
 <template>
     <div class="p-2" style="width: 100%;overflow-y: scroll">
+        <AppTabs v-model="currentAppId" @change="handleAppChange" />
         <!--        <transition :enter-active-class="proxy?.animate.searchAnimate.enter"-->
         <!--                    :leave-active-class="proxy?.animate.searchAnimate.leave">-->
         <div class="mb-[10px]">
@@ -249,6 +250,13 @@
 import {addMenu, delMenu, getMenu, listMenu, allMenuList, updateMenu, listAllMenu} from '@/api/system/menu';
 import {MenuForm, MenuQuery, MenuVO, MenuFormEntity, MenuVOEntity} from '@/api/system/menu/types';
 import {MenuTypeEnum} from '@/enums/MenuTypeEnum';
+import AppTabs from '@/components/AppTabs/index.vue'
+
+const currentAppId = ref('')
+const handleAppChange = () => {
+    ;(queryParams as any).appId = currentAppId.value
+    getList()
+}
 
 interface MenuOptionsType {
     id?: number;
@@ -448,6 +456,5 @@ const handleDelete = async (row: MenuVO) => {
 }
 
 onMounted(() => {
-    getList();
 });
 </script>

+ 9 - 1
ui/sp-storlead-center/src/views/system/messageTemplate/index.vue

@@ -1,6 +1,7 @@
 <template>
 	<el-card shadow="never" class="list-card">
 		<template #header>
+			<AppTabs v-model="currentAppId" @change="handleAppChange" />
 			<div class="search">
 				<el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="90px">
 					<el-form-item label="输入搜索:" prop="blurry">
@@ -17,7 +18,7 @@
 			</div>
 		</template>
 
-		<Table ref="messageTemplateTable" :tableParams="tableParams" :btnRole="[]" :right-btn="[]">
+		<Table v-if="currentAppId" ref="messageTemplateTable" :tableParams="tableParams" :btnRole="[]" :right-btn="[]">
 		</Table>
 	</el-card>
 
@@ -32,6 +33,13 @@ import Table from '@/components/Table/index.vue'
 import { tableTypes } from '@/components/Table/types'
 import { useDictCache } from '@/hooks/web/useDict'
 import MessageDetail from './detail.vue'
+import AppTabs from '@/components/AppTabs/index.vue'
+
+const currentAppId = ref('')
+const handleAppChange = () => {
+	;(tableParams.queryParams as any).appId = currentAppId.value
+	messageTemplateTable.value?.getData()
+}
 
 const { queryDictDetailByCodes } = useDictCache(['query_work_data_group'])
 queryDictDetailByCodes()

+ 6 - 0
ui/sp-storlead-center/src/views/system/post/index.vue

@@ -1,5 +1,6 @@
 <template>
   <div class="p-2">
+    <AppTabs v-model="currentAppId" @change="handleAppChange" />
 <!--    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">-->
 <!--      <div class="mb-[10px]" v-show="showSearch">-->
 <!--        <el-card shadow="hover">-->
@@ -107,6 +108,11 @@
 </template>
 
 <script setup name="Post" lang="ts">
+import AppTabs from '@/components/AppTabs/index.vue'
+
+const currentAppId = ref('')
+const handleAppChange = () => {}
+
 // import { listPost, addPost, delPost, getPost, updatePost } from "@/api/system/post";
 // import { PostForm, PostQuery, PostVO } from "@/api/system/post/types";
 //

+ 10 - 1
ui/sp-storlead-center/src/views/system/role/index.vue

@@ -1,6 +1,7 @@
 <template>
     <el-card shadow="never" class="list-card">
         <template #header>
+            <AppTabs v-model="currentAppId" @change="handleAppChange" />
             <div class="search" v-show="showSearch">
                 <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
                     <el-form-item prop="name">
@@ -21,7 +22,7 @@
             </div>
         </template>
 
-        <Table ref="roleTable"
+        <Table v-if="currentAppId" ref="roleTable"
                :tableParams="tableParams"
                :btnRole="leftBtn"
                @handleAdd="handleAdd"
@@ -151,6 +152,14 @@ import {Check, Close} from '@element-plus/icons-vue'
 import {MenuFormEntity, MenuQuery, MenuTreeOption, RoleMenuTree, DeptTreeOption} from '@/api/system/menu/types';
 import {DeptQuery} from "@/api/system/dept/types";
 import MobileAuth from './MobileAuth.vue'
+import AppTabs from '@/components/AppTabs/index.vue'
+
+const currentAppId = ref('')
+const handleAppChange = () => {
+    ;(tableParams.queryParams as any).appId = currentAppId.value
+    roleTable.value?.getData()
+}
+
 const roleList = ref<RoleVO[]>([]);
 const buttonLoading = ref(false);
 const buttonDataLoading = ref(false);

+ 11 - 1
ui/sp-storlead-center/src/views/system/ruleConfig/index.vue

@@ -1,5 +1,8 @@
 <template>
     <el-card shadow="never" class="list-card">
+        <template #header>
+            <AppTabs v-model="currentAppId" @change="handleAppChange" />
+        </template>
         <!-- <template #header>
             <div class="search" v-show="showSearch">
                 <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
@@ -14,7 +17,7 @@
             </div>
         </template> -->
 
-        <Table ref="ruleConfigTable"
+        <Table v-if="currentAppId" ref="ruleConfigTable"
                :tableParams="tableParams"
                :rightBtn="[]"
                @handleEdit="handleUpdate"
@@ -262,6 +265,13 @@ import auth from '@/plugins/auth'
 import Table from '@/components/Table/index.vue'
 import { tableTypes } from '@/components/Table/types'
 import { useDictCache } from '@/hooks/web/useDict'
+import AppTabs from '@/components/AppTabs/index.vue'
+
+const currentAppId = ref('')
+const handleAppChange = () => {
+    ;(tableParams.queryParams as any).appId = currentAppId.value
+    ruleConfigTable.value?.getData()
+}
 
 const { queryDictDetailByCodes } = useDictCache(['customer_type'])
 queryDictDetailByCodes()

+ 9 - 1
ui/sp-storlead-center/src/views/system/task/index.vue

@@ -2,6 +2,7 @@
     <el-card shadow="never" class="list-card">
 
         <template #header>
+            <AppTabs v-model="currentAppId" @change="handleAppChange" />
 
             <div class="search" v-show="showSearch">
                 <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
@@ -19,7 +20,7 @@
             </div>
         </template>
 
-        <Table ref="taskTableRef" :tableParams="tableParams" :btnRole="leftBtn" @handleAdd="handleAddTask"
+        <Table v-if="currentAppId" ref="taskTableRef" :tableParams="tableParams" :btnRole="leftBtn" @handleAdd="handleAddTask"
             @handleEdit="handleUpdate" @selectionChange="handleSelectionChange">
 
 
@@ -61,6 +62,13 @@ import { addTask, editTask, deleteTask, pauseTask, resumeTask, triggerTask, task
 import { getToday, getYesterday, getCurrentWeek, getMonthLastDay, addDateTime, subDateTime, getDayInfo, getWeekDay, dateFormate } from '@/utils/datetime';
 import { tableTypes } from '@/components/Table/types'
 import { TaskInfo, TaskInfoQuery } from '@/api/system/task/types';
+import AppTabs from '@/components/AppTabs/index.vue'
+
+const currentAppId = ref('')
+const handleAppChange = () => {
+    ;(tableParams.queryParams as any).appId = currentAppId.value
+    taskTableRef.value?.getData()
+}
 
 const querys = reactive({
     showLoading: false,

+ 9 - 1
ui/sp-storlead-center/src/views/system/user/index.vue

@@ -1,6 +1,7 @@
 <template>
     <el-card shadow="never" class="list-card">
         <template #header>
+            <AppTabs v-model="currentAppId" @change="handleAppChange" />
             <div class="search" v-show="showSearch">
                 <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
                     <el-form-item prop="mobile">
@@ -16,7 +17,7 @@
             </div>
         </template>
 
-        <MyTable ref="userTable" :tableParams="tableParams" :btnRole="leftBtn" :rightBtn="[]"
+        <MyTable v-if="currentAppId" ref="userTable" :tableParams="tableParams" :btnRole="leftBtn" :rightBtn="[]"
             @selectionChange="handleSelectionChange">
             <template #left>
                 <el-button v-hasPermi="[`system:user:syncOa`]" :loading="syncOaLoading" @click="syncOa">同步用户</el-button>
@@ -81,6 +82,13 @@ import { resignTransfer, ResignTransferTypes } from '@/api/customer';
 
 import auth from '@/plugins/auth'
 import {completelyDelEmails} from "@/api/emails";
+import AppTabs from '@/components/AppTabs/index.vue'
+
+const currentAppId = ref('')
+const handleAppChange = () => {
+    ;(tableParams.queryParams as any).appId = currentAppId.value
+    userTable.value?.getData()
+}
 
 const {proxy} = getCurrentInstance() as ComponentInternalInstance;
 

+ 2 - 2
ui/sp-user-center/src/api/public.ts

@@ -166,7 +166,7 @@ export const listExport = (query: any) => {
  */
 export const listMessage = (data: any) => {
     return request({
-        url: '/sys/inside/message/pageList',
+        url: '/sys/message/pageList',
         method: 'post',
         data
     });
@@ -179,7 +179,7 @@ export const listMessage = (data: any) => {
  */
 export const getMessageType = () => {
     return request({
-        url: '/sys/inside/message/getNoReadCount',
+        url: '/sys/message/getNoReadCount',
         method: 'post',
         data: {}
     });

+ 1 - 1
ui/sp-user-center/src/utils/request.ts

@@ -248,7 +248,7 @@ service.interceptors.response.use(
     } else if (message.includes('Request failed with status code')) {
       message = '系统接口' + message.substr(message.length - 3) + '异常';
     }
-    let msgUrls  = ['/sys/inside/message/getNoReadCount', '/mail/get_new_mail', '/mail/receive_mail']
+    let msgUrls  = ['/sys/message/getNoReadCount', '/mail/get_new_mail', '/mail/receive_mail']
     if(msgUrls.includes(error?.config?.url)) {
       return false
     }else {