Explorar el Código

Merge remote-tracking branch 'origin/master'

1811872455@163.com hace 1 semana
padre
commit
41b04145dc
Se han modificado 24 ficheros con 1669 adiciones y 385 borrados
  1. 1 0
      java/storlead-api/pom.xml
  2. 1 1
      java/storlead-knowledge/storlead-knowledge-api/src/main/java/com/storlead/knowledge/api/ChunkController.java
  3. 1 1
      java/storlead-knowledge/storlead-knowledge-api/src/main/java/com/storlead/knowledge/api/DocumentController.java
  4. 1 1
      java/storlead-knowledge/storlead-knowledge-api/src/main/java/com/storlead/knowledge/api/KnowledgeController.java
  5. 5 0
      java/storlead-knowledge/storlead-knowledge-biz/pom.xml
  6. 1 1
      java/storlead-knowledge/storlead-knowledge-core/src/main/java/com/storlead/knowledge/pojo/dto/QueryPageDTO.java
  7. 10 2
      java/storlead-sasa/storlead-sales/src/main/java/com/storlead/sales/service/impl/customer/CustomerServiceImpl.java
  8. 2 2
      java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/controller/SopController.java
  9. 0 1
      java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/dto/SopCreateDTO.java
  10. 13 0
      java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/service/impl/SopServiceImpl.java
  11. 0 41
      java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/vo/MarketingCampaignVO.java
  12. 172 0
      ui/smarttrade-platform/package-lock.json
  13. 4 3
      ui/smarttrade-platform/package.json
  14. 1 1
      ui/smarttrade-platform/src/api/customer/index.js
  15. 7 0
      ui/smarttrade-platform/src/api/knowledge/index.js
  16. 3 1
      ui/smarttrade-platform/src/api/marketing/index.js
  17. 10 0
      ui/smarttrade-platform/src/assets/styles/marketing.css
  18. 360 161
      ui/smarttrade-platform/src/components/addMarketing.vue
  19. 4 0
      ui/smarttrade-platform/src/main.js
  20. 825 0
      ui/smarttrade-platform/src/utils/region.ts
  21. 33 0
      ui/smarttrade-platform/src/utils/time.js
  22. 122 107
      ui/smarttrade-platform/src/views/CustomersView.vue
  23. 91 59
      ui/smarttrade-platform/src/views/KnowledgeView.vue
  24. 2 3
      ui/smarttrade-platform/src/views/MarketingView.vue

+ 1 - 0
java/storlead-api/pom.xml

@@ -95,6 +95,7 @@
                 <artifactId>spring-boot-maven-plugin</artifactId>
                 <configuration>
                     <mainClass>com.storlead.StorleadTradeApplication</mainClass>
+                    <jvmArguments>--add-opens java.base/java.lang=ALL-UNNAMED</jvmArguments>
                 </configuration>
             </plugin>
         </plugins>

+ 1 - 1
java/storlead-knowledge/storlead-knowledge-api/src/main/java/com/storlead/knowledge/api/ChunkController.java

@@ -32,7 +32,7 @@ public class ChunkController {
         String url = difyProperties.getBaseUrl()  + "datasets/"+ dataset_id +"/documents/"+document_id +"/segments";
         return httpService.get(
                 url,
-                Map.of("page", page.getPageIndex(), "size", page.getPageSize(), "keyword", page.getKeyword()),
+                page.getKeyword()==null?Map.of("page", page.getPageIndex(), "size", page.getPageSize()):Map.of("page", page.getPageIndex(), "size", page.getPageSize(), "keyword", page.getKeyword()),
                 "Bearer "+difyProperties.getDatasetApiKey(),
                 new TypeReference<>() {}
         );

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

@@ -35,7 +35,7 @@ public class DocumentController {
         String url = difyProperties.getBaseUrl() +"datasets/"+ dataset_id + "/documents";
         return httpService.get(
                 url,
-                Map.of("page", page.getPageIndex(), "size", page.getPageSize(), "keyword", page.getKeyword()),
+                page.getKeyword()==null?Map.of("page", page.getPageIndex(), "size", page.getPageSize()):Map.of("page", page.getPageIndex(), "size", page.getPageSize(), "keyword", page.getKeyword()),
                 "Bearer "+difyProperties.getDatasetApiKey(),
                 new TypeReference<>() {}
         );

+ 1 - 1
java/storlead-knowledge/storlead-knowledge-api/src/main/java/com/storlead/knowledge/api/KnowledgeController.java

@@ -32,7 +32,7 @@ public class KnowledgeController {
         String url = difyProperties.getBaseUrl() + "datasets";
         return httpService.get(
                 url,
-                Map.of("page", page.getPageIndex(), "size", page.getPageSize(), "keyword", page.getKeyword()),
+                page.getKeyword()==null?Map.of("page", page.getPageIndex(), "size", page.getPageSize()):Map.of("page", page.getPageIndex(), "size", page.getPageSize(), "keyword", page.getKeyword()),
                 "Bearer "+difyProperties.getDatasetApiKey(),
                 new TypeReference<>() {}
         );

+ 5 - 0
java/storlead-knowledge/storlead-knowledge-biz/pom.xml

@@ -29,6 +29,11 @@
             <groupId>com.storlead.boot</groupId>
             <artifactId>storlead-common</artifactId>
         </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>

+ 1 - 1
java/storlead-knowledge/storlead-knowledge-core/src/main/java/com/storlead/knowledge/pojo/dto/QueryPageDTO.java

@@ -10,7 +10,7 @@ import lombok.EqualsAndHashCode;
 @EqualsAndHashCode(callSuper = true)
 @Data
 public class QueryPageDTO extends PageDTO {
-    private String keyword;
+    private String keyword ;
     private String query;
     private String status;
 }

+ 10 - 2
java/storlead-sasa/storlead-sales/src/main/java/com/storlead/sales/service/impl/customer/CustomerServiceImpl.java

@@ -2660,8 +2660,16 @@ public class CustomerServiceImpl extends MyBaseServiceImpl<CustomerMapper, Custo
         }
 //        List<CustomerListVO> customerListVO = new ArrayList<>();
         //复制list给VO类
-        DefaultMapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
-        List<CustomerListVO> customerListVO = mapperFactory.getMapperFacade().mapAsList(page.getRecords(),CustomerListVO.class);
+//        DefaultMapperFactory mapperFactory = new DefaultMapperFactory.Builder()
+//                .useBuiltinConverters(false)  // 禁用内置转换器(包括 CloneableConverter)
+//                .build();
+//        List<CustomerListVO> customerListVO = mapperFactory.getMapperFacade().mapAsList(page.getRecords(),CustomerListVO.class);
+        List<CustomerListVO> customerListVO = new ArrayList<>();
+        for (CustomerEntity entity : page.getRecords()) {
+            CustomerListVO vo = new CustomerListVO();
+            BeanUtils.copyProperties(entity, vo);
+            customerListVO.add(vo);
+        }
         //获取企业详细信息
         List<Long> customerIdList = customerListVO.stream().map(CustomerListVO::getId).collect(Collectors.toList());
         Map<Long, CustomerCompanyEntity> customerCompanyMap =  customerCompanyService.customerIdListToCompanyMap(customerIdList);

+ 2 - 2
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/controller/SopController.java

@@ -38,7 +38,7 @@ public class SopController {
      * @param dto SOP创建请求
      * @return SOP ID
      */
-    @PostMapping
+    @PostMapping("/create")
     @ApiOperation(value = "创建SOP", notes = "创建SOP主表及步骤明细")
     public Result<Object> createSop(@Valid @RequestBody SopCreateDTO dto) {
         Result<Object> result = sopService.createSop(dto.getName(), dto.getDetails());
@@ -71,7 +71,7 @@ public class SopController {
      * @RequestBody SopResponseDTO dto
      * @return SOP列表
      */
-    @GetMapping
+    @PostMapping("/list")
     @ApiOperation(value = "分页查询SOP列表", notes = "支持按名称模糊查询")
     public Result<Object> listSop(@RequestBody SopResponseDTO dto) {
         try {

+ 0 - 1
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/dto/SopCreateDTO.java

@@ -20,6 +20,5 @@ public class SopCreateDTO {
     private String name;
 
     @ApiModelProperty(value = "SOP步骤明细", required = true)
-    @NotEmpty(message = "SOP步骤不能为空")
     private List<SopDetailCreateDTO> details;
 }

+ 13 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/service/impl/SopServiceImpl.java

@@ -21,6 +21,7 @@ import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
 /**
@@ -157,6 +158,18 @@ public class SopServiceImpl extends ServiceImpl<SopMapper, SopEntity> implements
         }).collect(Collectors.toList());
         responsePage.setRecords(records);
 
+        List<Long> sopIds = records.stream().map(SopResponseDTO::getId).collect(Collectors.toList());
+        List<SopDetailEntity> details = sopDetailService.list(new LambdaQueryWrapper<SopDetailEntity>()
+                .in(SopDetailEntity::getSopId, sopIds)
+                .eq(SopDetailEntity::getIsDelete, 0));
+        List<SopDetailResponseDTO> detailResponses = details.stream().map(detail -> {
+            SopDetailResponseDTO detailDto = new SopDetailResponseDTO();
+            BeanUtils.copyProperties(detail, detailDto);
+            return detailDto;
+        }).collect(Collectors.toList());
+        Map<Long, List<SopDetailResponseDTO>> detailMap = detailResponses.stream().collect(Collectors.groupingBy(SopDetailResponseDTO::getSopId));
+        records.forEach(sopDetailDto -> sopDetailDto.setDetails(detailMap.get(sopDetailDto.getId())));
+
         return responsePage;
     }
 }

+ 0 - 41
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/vo/MarketingCampaignVO.java

@@ -2,7 +2,6 @@ package com.storlead.trade.vo;
 
 import com.alibaba.fastjson.annotation.JSONField;
 import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.storlead.trade.entity.CustomerAnalysisResultEntity;
@@ -23,169 +22,129 @@ public class MarketingCampaignVO extends SysBaseField {
     private Long id;
 
     @ApiModelProperty(value = "活动名称")
-    @TableField("name")
     private String name;
 
     @ApiModelProperty(value = "活动类型(1潜客开发、2客户培养、3客户唤醒、4产品推广、5节日营销、6展会跟进)")
-    @TableField("type")
     private Integer type;
 
     @ApiModelProperty(value = "优先级(1紧急、2高优先、3中等、4低优先)")
-    @TableField("priority")
     private Integer priority;
 
     @ApiModelProperty(value = "开始日期")
     @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd")
     @DateTimeFormat(pattern="yyyy-MM-dd")
     @JSONField(format ="yyyy-MM-dd")
-    @TableField("begin_data")
     private LocalDate beginData;
 
     @ApiModelProperty(value = "结束日期")
     @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd")
     @DateTimeFormat(pattern="yyyy-MM-dd")
     @JSONField(format ="yyyy-MM-dd")
-    @TableField("end_date")
     private LocalDate endDate;
 
     @ApiModelProperty(value = "负责人id")
-    @TableField("supervisor")
     private Long supervisor;
 
     @ApiModelProperty(value = "关联商机")
-    @TableField("link_to_opportunity")
     private Long linkToOpportunity;
 
     @ApiModelProperty(value = "活动描述")
-    @TableField("description")
     private String description;
 
     @ApiModelProperty(value = "发送量")
-    @TableField("send_volume")
     private Integer sendVolume;
 
     @ApiModelProperty(value = "打开率")
-    @TableField("open rate")
     private Double openRate;
 
     @ApiModelProperty(value = "回复率")
-    @TableField("reply_rate")
     private Double replyRate;
 
     @ApiModelProperty(value = "商机数")
-    @TableField("opportunity_count")
     private Integer opportunityCount;
 
     @ApiModelProperty(value = "渠道配置")
-    @TableField("channel_configuration")
     private Integer channelConfiguration;
 
     @ApiModelProperty(value = "发送邮箱")
-    @TableField("email_address")
     private String emailAddress;
 
     @ApiModelProperty(value = "发送时间")
-    @TableField("email_sent_time")
     private String emailSentTime;
 
     @ApiModelProperty(value = "邮件模板")
-    @TableField("email_template")
     private String emailTemplate;
 
     @ApiModelProperty(value = "每批发送量")
-    @TableField("email_batch_size")
     private Integer emailBatchSize;
 
     @ApiModelProperty(value = "邮件主题")
-    @TableField("email_subject")
     private String emailSubject;
 
     @ApiModelProperty(value = "是否追踪邮件打开(1是0否)")
-    @TableField("track_config_open")
     private Integer trackConfigOpen;
 
     @ApiModelProperty(value = "是否追踪链接点击(1是0否)")
-    @TableField("track_config_click")
     private Integer trackConfigClick;
 
     @ApiModelProperty(value = "是否追踪回复(1是0否)")
-    @TableField("track_config_reply")
     private Integer trackConfigReply;
 
     @ApiModelProperty(value = "SOP序列id")
-    @TableField("sop_id")
     private Integer sopId;
 
     @ApiModelProperty(value = "未打开邮件跟进规则(1是0否)")
-    @TableField("follow_up_rules_not_opened")
     private Integer followUpRulesNotOpened;
 
     @ApiModelProperty(value = "未打开邮件跟进天数")
-    @TableField("follow_up_rules_not_opened_days")
     private String followUpRulesNotOpenedDays;
 
     @ApiModelProperty(value = "未回复邮件跟进规则(1是0否)")
-    @TableField("follow_up_rules_not_replied")
     private Integer followUpRulesNotReplied;
 
     @ApiModelProperty(value = "未回复邮件跟进天数")
-    @TableField("follow_up_rules_not_replied_days")
     private String followUpRulesNotRepliedDays;
 
     @ApiModelProperty(value = "未点击链接跟进规则(1是0否)")
-    @TableField("follow_up_rules_link_not_clicked")
     private Integer followUpRulesLinkNotClicked;
 
     @ApiModelProperty(value = "未点击链接跟进天数")
-    @TableField("follow_up_rules_link_not_clicked_days")
     private String followUpRulesLinkNotClickedDays;
 
     @ApiModelProperty(value = "客户回复后自动停止(1是0否)")
-    @TableField("follow_up_rules_replied")
     private Integer followUpRulesReplied;
 
     @ApiModelProperty(value = "客户预约后停止(1是0否)")
-    @TableField("follow_up_rules_appointment_booked")
     private Integer followUpRulesAppointmentBooked;
 
     @ApiModelProperty(value = "A/B测试(1是0否)")
-    @TableField("a_b_testing")
     private Integer aBTesting;
 
     @ApiModelProperty(value = "智能调度类型")
-    @TableField("smart_scheduling_type")
     private String smartSchedulingType;
 
     @ApiModelProperty(value = "智能调度开始日期")
-    @TableField("smart_scheduling_start_date")
     private LocalDate smartSchedulingStartDate;
 
     @ApiModelProperty(value = "智能调度开始时间")
-    @TableField("smart_scheduling_start_time")
     private String smartSchedulingStartTime;
 
     @ApiModelProperty(value = "是否已确认内容合规")
-    @TableField("has_content_compliant")
     private Integer hasContentCompliant;
 
     @ApiModelProperty(value = "是否已确认发送时间合理")
-    @TableField("has_schedule_approved")
     private Integer hasScheduleApproved;
 
     @ApiModelProperty(value = "是否符合GDPR")
-    @TableField("has_gdpr_compliant")
     private Integer hasGdprCompliant;
 
     @ApiModelProperty(value = "是否包含退订链接")
-    @TableField("has_unsubscribe_link")
     private Integer hasUnsubscribeLink;
 
     @ApiModelProperty(value = "进度百分比")
-    @TableField("progress_percent")
     private Double progressPercent;
 
     @ApiModelProperty(value = "状态 0未开始 1运行中 2暂停 3完成")
-    @TableField("status")
     private Integer status;
 
     @ApiModelProperty(value = "关联客户id")

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

@@ -10,6 +10,7 @@
       "dependencies": {
         "@fortawesome/fontawesome-free": "^6.5.1",
         "axios": "^1.6.7",
+        "element-plus": "^2.14.2",
         "pinia": "^2.1.7",
         "vue": "^3.4.21",
         "vue-router": "^4.3.0"
@@ -65,6 +66,22 @@
         "node": ">=6.9.0"
       }
     },
+    "node_modules/@ctrl/tinycolor": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz",
+      "integrity": "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==",
+      "engines": {
+        "node": ">=14"
+      }
+    },
+    "node_modules/@element-plus/icons-vue": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz",
+      "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==",
+      "peerDependencies": {
+        "vue": "^3.2.0"
+      }
+    },
     "node_modules/@esbuild/aix-ppc64": {
       "version": "0.21.5",
       "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
@@ -456,6 +473,28 @@
         "node": ">=12"
       }
     },
+    "node_modules/@floating-ui/core": {
+      "version": "1.7.5",
+      "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz",
+      "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==",
+      "dependencies": {
+        "@floating-ui/utils": "^0.2.11"
+      }
+    },
+    "node_modules/@floating-ui/dom": {
+      "version": "1.7.6",
+      "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz",
+      "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==",
+      "dependencies": {
+        "@floating-ui/core": "^1.7.5",
+        "@floating-ui/utils": "^0.2.11"
+      }
+    },
+    "node_modules/@floating-ui/utils": {
+      "version": "0.2.11",
+      "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz",
+      "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="
+    },
     "node_modules/@fortawesome/fontawesome-free": {
       "version": "6.7.2",
       "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.7.2.tgz",
@@ -471,6 +510,16 @@
       "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
       "license": "MIT"
     },
+    "node_modules/@popperjs/core": {
+      "name": "@sxzz/popperjs-es",
+      "version": "2.11.8",
+      "resolved": "https://registry.npmjs.org/@sxzz/popperjs-es/-/popperjs-es-2.11.8.tgz",
+      "integrity": "sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/popperjs"
+      }
+    },
     "node_modules/@rollup/rollup-android-arm-eabi": {
       "version": "4.60.1",
       "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz",
@@ -828,6 +877,24 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/@types/lodash": {
+      "version": "4.17.24",
+      "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz",
+      "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ=="
+    },
+    "node_modules/@types/lodash-es": {
+      "version": "4.17.12",
+      "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz",
+      "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
+      "dependencies": {
+        "@types/lodash": "*"
+      }
+    },
+    "node_modules/@types/web-bluetooth": {
+      "version": "0.0.21",
+      "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
+      "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="
+    },
     "node_modules/@vitejs/plugin-vue": {
       "version": "5.2.4",
       "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
@@ -948,6 +1015,41 @@
       "integrity": "sha512-ksNyrmRQzWJJ8n3cRDuSF7zNNontuJg1YHnmWRJd2AMu8Ij2bqwiiri2lH5rHtYPZjj4STkNcgcmiQqlOjiYGg==",
       "license": "MIT"
     },
+    "node_modules/@vueuse/core": {
+      "version": "14.3.0",
+      "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.3.0.tgz",
+      "integrity": "sha512-aHfz47g0ZhMtTVHmIzMVpJy8ePhhOy68GY5bv110+5DVtZ+W7BsOx+m61UNQqfrWyPztIHIanWa3E2tib3NFIw==",
+      "dependencies": {
+        "@types/web-bluetooth": "^0.0.21",
+        "@vueuse/metadata": "14.3.0",
+        "@vueuse/shared": "14.3.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "vue": "^3.5.0"
+      }
+    },
+    "node_modules/@vueuse/metadata": {
+      "version": "14.3.0",
+      "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.3.0.tgz",
+      "integrity": "sha512-BwxmbAzwAVF50+MW57GXOUEV61nFBGnlBvrTqj49PqWJu3uw7hdu72ztXeZ33RdZtDY6kO+bfCAE1PCn88Tktw==",
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@vueuse/shared": {
+      "version": "14.3.0",
+      "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.3.0.tgz",
+      "integrity": "sha512-bZpge9eSXwa4ToSiqJ7j6KRwhAsneMFoSz3LMWKQDkqimm3D/tbFlrklrs/IOqC8tEcYmXQZJ6N0UrjhBirVCg==",
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "vue": "^3.5.0"
+      }
+    },
     "node_modules/agent-base": {
       "version": "6.0.2",
       "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
@@ -960,6 +1062,11 @@
         "node": ">= 6.0.0"
       }
     },
+    "node_modules/async-validator": {
+      "version": "4.2.5",
+      "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz",
+      "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg=="
+    },
     "node_modules/asynckit": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -1009,6 +1116,11 @@
       "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
       "license": "MIT"
     },
+    "node_modules/dayjs": {
+      "version": "1.11.21",
+      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz",
+      "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA=="
+    },
     "node_modules/debug": {
       "version": "4.4.3",
       "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@@ -1049,6 +1161,31 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/element-plus": {
+      "version": "2.14.2",
+      "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.14.2.tgz",
+      "integrity": "sha512-eNH9uP3wQoNqieEIHXiNvIVv+zO5sZDU0CAZq5b0zqSN06DD0/V9xIq1R/qm3rw5k3nBTM1JvpxhCfRbaFLzDQ==",
+      "dependencies": {
+        "@ctrl/tinycolor": "^4.2.0",
+        "@element-plus/icons-vue": "^2.3.2",
+        "@floating-ui/dom": "^1.7.6",
+        "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.8",
+        "@types/lodash": "^4.17.24",
+        "@types/lodash-es": "^4.17.12",
+        "@vueuse/core": "14.3.0",
+        "async-validator": "^4.2.5",
+        "dayjs": "^1.11.20",
+        "lodash": "^4.18.1",
+        "lodash-es": "^4.18.1",
+        "lodash-unified": "^1.0.3",
+        "memoize-one": "^6.0.0",
+        "normalize-wheel-es": "^1.2.0",
+        "vue-component-type-helpers": "^3.3.3"
+      },
+      "peerDependencies": {
+        "vue": "^3.3.7"
+      }
+    },
     "node_modules/entities": {
       "version": "7.0.1",
       "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
@@ -1312,6 +1449,26 @@
         "node": ">= 6"
       }
     },
+    "node_modules/lodash": {
+      "version": "4.18.1",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
+      "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="
+    },
+    "node_modules/lodash-es": {
+      "version": "4.18.1",
+      "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz",
+      "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A=="
+    },
+    "node_modules/lodash-unified": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/lodash-unified/-/lodash-unified-1.0.3.tgz",
+      "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==",
+      "peerDependencies": {
+        "@types/lodash-es": "*",
+        "lodash": "*",
+        "lodash-es": "*"
+      }
+    },
     "node_modules/magic-string": {
       "version": "0.30.21",
       "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
@@ -1330,6 +1487,11 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/memoize-one": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
+      "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
+    },
     "node_modules/mime-db": {
       "version": "1.52.0",
       "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -1375,6 +1537,11 @@
         "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
       }
     },
+    "node_modules/normalize-wheel-es": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
+      "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw=="
+    },
     "node_modules/picocolors": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -1575,6 +1742,11 @@
         }
       }
     },
+    "node_modules/vue-component-type-helpers": {
+      "version": "3.3.6",
+      "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-3.3.6.tgz",
+      "integrity": "sha512-FkljacAwJ9BUoSUdpFe3VDy0sGigNlTH9+2zcXUWmZOjN8swiCkl3t48wOJun0OsUd2cEIda1l04tsxMiKIIrQ=="
+    },
     "node_modules/vue-demi": {
       "version": "0.14.10",
       "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",

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

@@ -9,11 +9,12 @@
     "preview": "vite preview"
   },
   "dependencies": {
+    "@fortawesome/fontawesome-free": "^6.5.1",
     "axios": "^1.6.7",
-    "vue": "^3.4.21",
-    "vue-router": "^4.3.0",
+    "element-plus": "^2.14.2",
     "pinia": "^2.1.7",
-    "@fortawesome/fontawesome-free": "^6.5.1"
+    "vue": "^3.4.21",
+    "vue-router": "^4.3.0"
   },
   "devDependencies": {
     "@vitejs/plugin-vue": "^5.0.4",

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

@@ -1,3 +1,3 @@
 import { get, post, put, remove, upload } from '@/utils/request'
 
-export const getCustomerList = (params = {}) => post('/router/rest/customer/list', params)
+export const getCustomerList = (data = {}) => post('/router/rest/customer/list', data)

+ 7 - 0
ui/smarttrade-platform/src/api/knowledge/index.js

@@ -0,0 +1,7 @@
+import { get, post, put, remove, upload } from '@/utils/request'
+
+export const knowledgeDatasets = (params = {}) => get('/router/rest/knowledge/base/datasets', params)
+export const knowledgeDocuments = (params = {}) => {
+    const { dataset_id, ...query } = params
+    return get(`/router/rest/knowledge/document/${dataset_id}/documents`, params)
+}

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

@@ -1,3 +1,5 @@
 import { get, post, put, remove, upload } from '@/utils/request'
 
-export const getMarketingList = (params = {}) => post('/router/rest/marketing/list', params)
+export const getMarketingList = (data = {}) => post('/router/rest/marketing/list', data)
+export const getSopList = (data = {}) => post('/router/rest/api/sop', data)
+export const addMarketing = (data = {}) => post('/router/rest/marketing/add', data)

+ 10 - 0
ui/smarttrade-platform/src/assets/styles/marketing.css

@@ -750,6 +750,16 @@
 .confirm-section h5 { font-size: 13px; margin-bottom: 12px; display: flex; align-items: center; gap: 6px; }
 .confirm-item { position: relative; padding-left: 28px; }
 .confirm-item .checkmark { position: absolute; left: 0; width: 18px; height: 18px; border: 2px solid var(--border); border-radius: 4px; }
+.confirm-item input {
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 18px;
+  height: 18px;
+  margin: 0;
+  opacity: 0;
+  cursor: pointer;
+}
 .confirm-item input:checked + .checkmark { background: var(--success); border-color: var(--success); }
 .confirm-item input:checked + .checkmark::after { content: '✓'; position: absolute; color: white; font-size: 12px; top: 50%; left: 50%; transform: translate(-50%, -50%); }
 

+ 360 - 161
ui/smarttrade-platform/src/components/addMarketing.vue

@@ -44,8 +44,12 @@
                     <h4><i class="fas fa-info-circle"></i> 活动基本信息</h4>
                     <div class="form-group">
                         <label class="form-label">活动名称 <span class="required">*</span></label>
-                        <input type="text" class="form-input" v-model="newCampaign.name"
-                            placeholder="例如:2026春季中东客户开发活动" />
+                        <el-input
+                            v-model="newCampaign.name"
+                            maxlength="30"
+                            show-word-limit
+                            placeholder="例如:2026春季中东客户开发活动"
+                        />
                         <div class="form-hint" v-if="!newCampaign.name">
                             建议格式:时间+区域+目标+活动类型
                         </div>
@@ -53,70 +57,83 @@
                     <div class="form-row">
                         <div class="form-group">
                             <label class="form-label">活动类型</label>
-                            <select class="form-select" v-model="newCampaign.type">
-                                <option value="prospect">潜客开发</option>
-                                <option value="nurture">客户培育</option>
-                                <option value="reactivate">客户唤醒</option>
-                                <option value="promote">产品推广</option>
-                                <option value="seasonal">节日营销</option>
-                                <option value="exhibition">展会跟进</option>
-                            </select>
+                            <el-select v-model="newCampaign.type" placeholder="请选择活动类型">
+                                <el-option label="潜客开发" value="潜客开发" />
+                                <el-option label="客户培育" value="客户培育" />
+                                <el-option label="客户唤醒" value="客户唤醒" />
+                                <el-option label="产品推广" value="产品推广" />
+                                <el-option label="节日营销" value="节日营销" />
+                                <el-option label="展会跟进" value="展会跟进" />
+                            </el-select>
                         </div>
                         <div class="form-group">
                             <label class="form-label">优先级</label>
-                            <select class="form-select" v-model="newCampaign.priority">
-                                <option value="urgent">紧急</option>
-                                <option value="high">高优先</option>
-                                <option value="medium">中等</option>
-                                <option value="low">低优先</option>
-                            </select>
+                            <el-select v-model="newCampaign.priority" placeholder="请选择优先级">
+                                <el-option label="高" value="高" />
+                                <el-option label="中" value="中" />
+                                <el-option label="低" value="低" />
+                            </el-select>
                         </div>
                     </div>
                     <div class="form-row">
                         <div class="form-group">
                             <label class="form-label">开始日期</label>
-                            <input type="date" class="form-input" v-model="newCampaign.startDate" />
+                            <el-date-picker
+                                v-model="newCampaign.startDate"
+                                type="datetime"
+                                format="YYYY-MM-DD HH:mm"
+                                value-format="YYYY-MM-DD HH:mm:ss"
+                                placeholder="选择开始时间"
+                            />
                         </div>
                         <div class="form-group">
                             <label class="form-label">结束日期</label>
-                            <input type="date" class="form-input" v-model="newCampaign.endDate" />
+                            <el-date-picker
+                                v-model="newCampaign.endDate"
+                                type="datetime"
+                                format="YYYY-MM-DD HH:mm"
+                                value-format="YYYY-MM-DD HH:mm:ss"
+                                placeholder="选择结束时间"
+                            />
                         </div>
                     </div>
                     <div class="form-row">
                         <div class="form-group">
                             <label class="form-label">负责人</label>
-                            <select class="form-select" v-model="newCampaign.owner">
-                                <option value="">选择负责人</option>
-                                <option value="sales1">张经理</option>
-                                <option value="sales2">李主管</option>
-                                <option value="sales3">王小明</option>
-                            </select>
+                            <el-select v-model="newCampaign.owner" multiple collapse-tags placeholder="选择负责人">
+                                <el-option label="张经理" value="sales1" />
+                                <el-option label="李主管" value="sales2" />
+                                <el-option label="王小明" value="sales3" />
+                            </el-select>
                         </div>
                     </div>
                     <div class="form-group">
                         <label class="form-label">活动描述</label>
-                        <textarea class="form-textarea" v-model="newCampaign.description" rows="3"
-                            placeholder="描述本次营销活动的目标和策略..."></textarea>
+                        <el-input
+                            v-model="newCampaign.description"
+                            type="textarea"
+                            :rows="3"
+                            maxlength="150"
+                            show-word-limit
+                            placeholder="描述本次营销活动的目标和策略..."
+                        />
                     </div>
                     <div class="form-group">
                         <label class="form-label">活动目标</label>
                         <div class="goal-inputs">
                             <div class="goal-item">
                                 <i class="fas fa-envelope"></i>
-                                <input type="number" class="goal-input" v-model="newCampaign.goals.sent"
-                                    placeholder="0" />
+                                <el-input-number v-model="newCampaign.goals.sent" :controls="false" :min="0" />
                                 <span>发送量</span>
                             </div>
                             <div class="goal-item">
                                 <i class="fas fa-envelope-open"></i>
-                                <input type="number" class="goal-input" v-model="newCampaign.goals.openRate"
-                                    placeholder="0" />
+                                <el-input-number v-model="newCampaign.goals.openRate" :controls="false" :min="0" :max="100" />
                                 <span>% 打开率</span>
                             </div>
                             <div class="goal-item">
                                 <i class="fas fa-reply"></i>
-                                <input type="number" class="goal-input" v-model="newCampaign.goals.replyRate"
-                                    placeholder="0" />
+                                <el-input-number v-model="newCampaign.goals.replyRate" :controls="false" :min="0" :max="100" />
                                 <span>% 回复率</span>
                             </div>
                         </div>
@@ -188,54 +205,49 @@
                         <div class="filter-row">
                             <div class="form-group">
                                 <label class="form-label">地区</label>
-                                <select class="form-select" v-model="newCampaign.filters.region">
-                                    <option value="">全部地区</option>
-                                    <option value="middle-east">中东</option>
-                                    <option value="europe">欧洲</option>
-                                    <option value="north-america">北美</option>
-                                    <option value="southeast-asia">东南亚</option>
-                                    <option value="africa">非洲</option>
-                                    <option value="south-america">南美</option>
-                                </select>
+                                <el-select v-model="newCampaign.filters.region" clearable placeholder="全部地区">
+                                    <el-option label="中东" value="middle-east" />
+                                    <el-option label="欧洲" value="europe" />
+                                    <el-option label="北美" value="north-america" />
+                                    <el-option label="东南亚" value="southeast-asia" />
+                                    <el-option label="非洲" value="africa" />
+                                    <el-option label="南美" value="south-america" />
+                                </el-select>
                             </div>
                             <div class="form-group">
                                 <label class="form-label">行业</label>
-                                <select class="form-select" v-model="newCampaign.filters.industry">
-                                    <option value="">全部行业</option>
-                                    <option value="datacenter">数据中心</option>
-                                    <option value="consumer">消费电子</option>
-                                    <option value="distributor">电子分销</option>
-                                    <option value="retail">零售</option>
-                                    <option value="manufacturing">制造业</option>
-                                </select>
+                                <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>
                             </div>
                         </div>
                         <div class="filter-row">
                             <div class="form-group">
                                 <label class="form-label">意向分数</label>
-                                <select class="form-select" v-model="newCampaign.filters.scoreRange">
-                                    <option value="">不限</option>
-                                    <option value="high">80-100分</option>
-                                    <option value="medium">60-79分</option>
-                                    <option value="low">40-59分</option>
-                                </select>
+                                <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>
                             </div>
                             <div class="form-group">
                                 <label class="form-label">客户规模</label>
-                                <select class="form-select" v-model="newCampaign.filters.companySize">
-                                    <option value="">不限</option>
-                                    <option value="large">大型企业(500+)</option>
-                                    <option value="medium">中型企业(100-500)</option>
-                                    <option value="small">小微企业(<100)</option>
-                                </select>
+                                <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>
                             </div>
                         </div>
                         <!-- 自定义关键词搜索 -->
                         <div class="form-group">
                             <label class="form-label">关键词搜索</label>
                             <div class="keyword-search">
-                                <input type="text" class="form-input" v-model="newCampaign.filters.keyword"
-                                    placeholder="搜索公司名、联系人、产品关键词..." />
+                                <el-input v-model="newCampaign.filters.keyword" placeholder="搜索公司名、联系人、产品关键词..." clearable />
                                 <button class="btn btn-secondary">
                                     <i class="fas fa-search"></i>
                                 </button>
@@ -331,47 +343,44 @@
                         <div class="form-row">
                             <div class="form-group">
                                 <label class="form-label">发件邮箱</label>
-                                <select class="form-select" v-model="newCampaign.emailSender">
-                                    <option>sales@company.com</option>
-                                    <option>marketing@company.com</option>
-                                    <option>support@company.com</option>
-                                </select>
+                                <el-select v-model="newCampaign.emailSender" placeholder="请选择发件邮箱">
+                                    <el-option label="sales@company.com" value="sales@company.com" />
+                                    <el-option label="marketing@company.com" value="marketing@company.com" />
+                                    <el-option label="support@company.com" value="support@company.com" />
+                                </el-select>
                             </div>
                             <div class="form-group">
                                 <label class="form-label">发送时间</label>
-                                <select class="form-select" v-model="newCampaign.emailSendTime">
-                                    <option value="optimal">AI推荐最优时间</option>
-                                    <option value="morning">上午 9:00-11:00</option>
-                                    <option value="afternoon">下午 14:00-16:00</option>
-                                    <option value="evening">晚上 19:00-21:00</option>
-                                </select>
+                                <el-select v-model="newCampaign.emailSendTime" placeholder="请选择发送时间">
+                                    <el-option label="AI推荐最优时间" value="optimal" />
+                                    <el-option label="上午 9:00-11:00" value="morning" />
+                                    <el-option label="下午 14:00-16:00" value="afternoon" />
+                                    <el-option label="晚上 19:00-21:00" value="evening" />
+                                </el-select>
                             </div>
                         </div>
                         <div class="form-row">
                             <div class="form-group">
                                 <label class="form-label">生成方式</label>
-                                <select class="form-select" v-model="newCampaign.emailTemplate">
-                                    <option value="default">默认模板</option>
-                                    <option value="professional">专业商务</option>
-                                    <option value="friendly">友好亲切</option>
-                                    <option value="minimal">极简风格</option>
-                                </select>
+                                <el-select v-model="newCampaign.emailTemplate" placeholder="请选择生成方式">
+                                    <el-option label="AI自主" value="AI自主" />
+                                    <el-option label="人工协同" value="人工协同" />
+                                </el-select>
                             </div>
                             <div class="form-group">
                                 <label class="form-label">每批发送量</label>
-                                <select class="form-select" v-model="newCampaign.batchSize">
-                                    <option value="50">50封/批</option>
-                                    <option value="100">100封/批</option>
-                                    <option value="200">200封/批</option>
-                                    <option value="500">500封/批</option>
-                                </select>
+                                <el-select v-model="newCampaign.batchSize" placeholder="请选择每批发送量">
+                                    <el-option label="50封/批" value="50" />
+                                    <el-option label="100封/批" value="100" />
+                                    <el-option label="200封/批" value="200" />
+                                    <el-option label="500封/批" value="500" />
+                                </el-select>
                             </div>
                         </div>
                         <div class="form-group">
                             <label class="form-label">邮件主题 <span class="required">*</span></label>
                             <div class="subject-input-group">
-                                <input type="text" class="form-input" v-model="newCampaign.emailSubject"
-                                    placeholder="输入邮件主题" />
+                                <el-input style="flex: 1" v-model="newCampaign.emailSubject" placeholder="输入邮件主题" />
                                 <button class="btn btn-primary" @click="generateSubjectAI">
                                     <i class="fas fa-magic"></i> AI优化
                                 </button>
@@ -386,8 +395,7 @@
                         </div>
                         <div class="form-group">
                             <label class="form-label">邮件预览文本</label>
-                            <input type="text" class="form-input" v-model="newCampaign.emailPreviewText"
-                                placeholder="邮件列表中显示的预览文字(可选)" />
+                            <el-input v-model="newCampaign.emailPreviewText" placeholder="邮件列表中显示的预览文字(可选)" />
                         </div>
                     </div>
 
@@ -397,24 +405,23 @@
                         <div class="form-row">
                             <div class="form-group">
                                 <label class="form-label">发送账号</label>
-                                <select class="form-select" v-model="newCampaign.whatsappAccount">
-                                    <option>业务账号1</option>
-                                    <option>业务账号2</option>
-                                </select>
+                                <el-select v-model="newCampaign.whatsappAccount" placeholder="请选择发送账号">
+                                    <el-option label="业务账号1" value="业务账号1" />
+                                    <el-option label="业务账号2" value="业务账号2" />
+                                </el-select>
                             </div>
                             <div class="form-group">
                                 <label class="form-label">发送间隔</label>
-                                <select class="form-select" v-model="newCampaign.whatsappInterval">
-                                    <option value="30">30秒/条</option>
-                                    <option value="60">1分钟/条</option>
-                                    <option value="120">2分钟/条</option>
-                                </select>
+                                <el-select v-model="newCampaign.whatsappInterval" placeholder="请选择发送间隔">
+                                    <el-option label="30秒/条" value="30" />
+                                    <el-option label="1分钟/条" value="60" />
+                                    <el-option label="2分钟/条" value="120" />
+                                </el-select>
                             </div>
                         </div>
                         <div class="form-group">
                             <label class="form-label">消息模板</label>
-                            <textarea class="form-textarea" v-model="newCampaign.whatsappTemplate" rows="3"
-                                placeholder="输入 WhatsApp 消息模板..."></textarea>
+                            <el-input v-model="newCampaign.whatsappTemplate" type="textarea" :rows="3" placeholder="输入 WhatsApp 消息模板..." />
                         </div>
                     </div>
 
@@ -424,19 +431,19 @@
                         <div class="form-row">
                             <div class="form-group">
                                 <label class="form-label">操作类型</label>
-                                <select class="form-select" v-model="newCampaign.linkedinAction">
-                                    <option value="message">发送消息</option>
-                                    <option value="connect">发送连接请求</option>
-                                    <option value="inmail">InMail</option>
-                                </select>
+                                <el-select v-model="newCampaign.linkedinAction" placeholder="请选择操作类型">
+                                    <el-option label="发送消息" value="message" />
+                                    <el-option label="发送连接请求" value="connect" />
+                                    <el-option label="InMail" value="inmail" />
+                                </el-select>
                             </div>
                             <div class="form-group">
                                 <label class="form-label">每日上限</label>
-                                <select class="form-select" v-model="newCampaign.linkedinLimit">
-                                    <option value="20">20次/天</option>
-                                    <option value="30">30次/天</option>
-                                    <option value="50">50次/天</option>
-                                </select>
+                                <el-select v-model="newCampaign.linkedinLimit" placeholder="请选择每日上限">
+                                    <el-option label="20次/天" value="20" />
+                                    <el-option label="30次/天" value="30" />
+                                    <el-option label="50次/天" value="50" />
+                                </el-select>
                             </div>
                         </div>
                     </div>
@@ -501,12 +508,12 @@
                                 <div class="rule-desc">3天后发送提醒邮件,更换主题行</div>
                             </div>
                             <div class="rule-config" v-if="newCampaign.rules.noOpenFollowup">
-                                <select class="mini-select" v-model="newCampaign.rules.noOpenFollowupDays">
-                                    <option value="2">2天后</option>
-                                    <option value="3">3天后</option>
-                                    <option value="5">5天后</option>
-                                    <option value="7">7天后</option>
-                                </select>
+                                <el-select class="mini-select" v-model="newCampaign.rules.noOpenFollowupDays" placeholder="请选择">
+                                    <el-option label="2天后" value="2" />
+                                    <el-option label="3天后" value="3" />
+                                    <el-option label="5天后" value="5" />
+                                    <el-option label="7天后" value="7" />
+                                </el-select>
                             </div>
                         </div>
                         <div class="rule-item">
@@ -519,11 +526,11 @@
                                 <div class="rule-desc">7天后跟进</div>
                             </div>
                             <div class="rule-config" v-if="newCampaign.rules.noReplyFollowup">
-                                <select class="mini-select" v-model="newCampaign.rules.noReplyFollowupDays">
-                                    <option value="5">5天后</option>
-                                    <option value="7">7天后</option>
-                                    <option value="10">10天后</option>
-                                </select>
+                                <el-select class="mini-select" v-model="newCampaign.rules.noReplyFollowupDays" placeholder="请选择">
+                                    <el-option label="5天后" value="5" />
+                                    <el-option label="7天后" value="7" />
+                                    <el-option label="10天后" value="10" />
+                                </el-select>
                             </div>
                         </div>
                         <div class="rule-item">
@@ -551,29 +558,29 @@
                             <div class="form-row">
                                 <div class="form-group">
                                     <label class="form-label">测试变量</label>
-                                    <select class="form-select" v-model="newCampaign.abTest.variable">
-                                        <option value="subject">邮件主题</option>
-                                        <option value="content">邮件内容</option>
-                                        <option value="sendTime">发送时间</option>
-                                        <option value="fromName">发件人名称</option>
-                                    </select>
+                                    <el-select v-model="newCampaign.abTest.variable" placeholder="请选择测试变量">
+                                        <el-option label="邮件主题" value="subject" />
+                                        <el-option label="邮件内容" value="content" />
+                                        <el-option label="发送时间" value="sendTime" />
+                                        <el-option label="发件人名称" value="fromName" />
+                                    </el-select>
                                 </div>
                                 <div class="form-group">
                                     <label class="form-label">测试比例</label>
-                                    <select class="form-select" v-model="newCampaign.abTest.ratio">
-                                        <option value="50">50% : 50%</option>
-                                        <option value="30">30% : 70%</option>
-                                        <option value="20">20% : 80%</option>
-                                    </select>
+                                    <el-select v-model="newCampaign.abTest.ratio" placeholder="请选择测试比例">
+                                        <el-option label="50% : 50%" value="50" />
+                                        <el-option label="30% : 70%" value="30" />
+                                        <el-option label="20% : 80%" value="20" />
+                                    </el-select>
                                 </div>
                             </div>
                             <div class="form-group">
                                 <label class="form-label">胜出条件</label>
-                                <select class="form-select" v-model="newCampaign.abTest.winnerCriteria">
-                                    <option value="openRate">打开率最高</option>
-                                    <option value="clickRate">点击率最高</option>
-                                    <option value="replyRate">回复率最高</option>
-                                </select>
+                                <el-select v-model="newCampaign.abTest.winnerCriteria" placeholder="请选择胜出条件">
+                                    <el-option label="打开率最高" value="openRate" />
+                                    <el-option label="点击率最高" value="clickRate" />
+                                    <el-option label="回复率最高" value="replyRate" />
+                                </el-select>
                             </div>
                             <div class="ab-test-preview">
                                 <div class="ab-variant">
@@ -584,8 +591,7 @@
                                 </div>
                                 <div class="ab-variant">
                                     <span class="variant-label">B 版本</span>
-                                    <input type="text" class="variant-input" v-model="newCampaign.abTest.variantB"
-                                        placeholder="输入测试主题..." />
+                                    <el-input v-model="newCampaign.abTest.variantB" class="variant-input" placeholder="输入测试主题..." />
                                 </div>
                             </div>
                         </div>
@@ -628,11 +634,11 @@
                             <div class="form-row">
                                 <div class="form-group">
                                     <label class="form-label">开始日期</label>
-                                    <input type="date" class="form-input" v-model="newCampaign.scheduledDate" />
+                                    <el-date-picker v-model="newCampaign.scheduledDate" type="date" format="YYYY-MM-DD" value-format="YYYY-MM-DD" placeholder="选择日期" />
                                 </div>
                                 <div class="form-group">
                                     <label class="form-label">开始时间</label>
-                                    <input type="time" class="form-input" v-model="newCampaign.scheduledTime" />
+                                    <el-time-picker v-model="newCampaign.scheduledTime" format="HH:mm" value-format="HH:mm" placeholder="选择时间" />
                                 </div>
                             </div>
                         </div>
@@ -797,7 +803,7 @@
                 <button v-if="currentStep < 5" class="btn btn-primary" @click="currentStep++">
                     下一步 <i class="fas fa-arrow-right"></i>
                 </button>
-                <button v-else class="btn btn-primary" :disabled="!canCreate" @click="createCampaign">
+                <button v-else class="btn btn-primary" :disabled="!canCreate || isSubmitting" @click="createCampaign">
                     <i class="fas fa-rocket"></i> 创建活动
                 </button>
             </div>
@@ -815,7 +821,7 @@
                 <!-- SOP 名称 -->
                 <div class="form-group">
                     <label class="form-label">序列名称 <span class="required">*</span></label>
-                    <input type="text" class="form-input" v-model="customSopData.name" placeholder="例如:我的自定义开发序列">
+                    <el-input v-model="customSopData.name" placeholder="例如:我的自定义开发序列" />
                 </div>
 
                 <!-- 步骤列表 -->
@@ -834,25 +840,23 @@
                                 <div class="step-row">
                                     <div class="form-group">
                                         <label class="form-label">步骤名称</label>
-                                        <input type="text" class="form-input" v-model="step.title"
-                                            placeholder="例如:发送产品介绍">
+                                        <el-input v-model="step.title" placeholder="例如:发送产品介绍" />
                                     </div>
                                     <div class="form-group">
                                         <label class="form-label">间隔天数</label>
-                                        <input type="number" class="form-input" v-model="step.day" min="1" max="30">
+                                        <el-input-number v-model="step.day" :controls="false" :min="1" :max="30" />
                                     </div>
                                 </div>
                                 <div class="step-row">
                                     <div class="form-group">
                                         <label class="form-label">触达渠道</label>
-                                        <select class="form-select" v-model="step.channel"
-                                            @change="updateChannelIcon(index)">
-                                            <option value="email">邮件</option>
-                                            <option value="whatsapp">WhatsApp</option>
-                                            <option value="linkedin">LinkedIn</option>
-                                            <option value="phone">电话</option>
-                                            <option value="wechat">微信</option>
-                                        </select>
+                                        <el-select v-model="step.channel" placeholder="请选择渠道" @change="updateChannelIcon(index)">
+                                            <el-option label="邮件" value="email" />
+                                            <el-option label="WhatsApp" value="whatsapp" />
+                                            <el-option label="LinkedIn" value="linkedin" />
+                                            <el-option label="电话" value="phone" />
+                                            <el-option label="微信" value="wechat" />
+                                        </el-select>
                                     </div>
                                     <div class="channel-preview">
                                         <div class="preview-badge" :class="step.channel">
@@ -913,12 +917,20 @@
 <script setup>
     import { ref, computed } from 'vue'
     import PreviewCustomerModal from './PreviewCustomerModal.vue'
+    import { getSopList, addMarketing } from '@/api/marketing'
 
     const showCreateModal = ref(false)
     const currentStep = ref(1)
+    const toast = (message, type = 'success') => {
+        if (typeof window !== 'undefined' && typeof window.toast === 'function') {
+            window.toast(message, type)
+            return
+        }
+        console[type === 'success' ? 'log' : 'warn'](`[toast] ${message}`)
+    }
     const newCampaign = ref({
-        name: '', type: 'prospect', priority: 'medium', description: '',
-        startDate: '', endDate: '', owner: '', relatedOpportunity: '',
+        name: '', type: '潜客开发', priority: '中', description: '',
+        startDate: '', endDate: '', owner: [], relatedOpportunity: '',
         segment: 'all', aiRecommend: 'high-value',
         filters: {
             region: '', industry: '', scoreRange: '', stage: '', companySize: '', purchaseCycle: '',
@@ -927,7 +939,7 @@
         goals: { sent: 500, openRate: 40, replyRate: 10, opportunities: 15 },
         channels: ['email'],
         emailSender: 'sales@company.com', emailSendTime: 'optimal',
-        emailTemplate: 'default', batchSize: '100',
+        emailTemplate: 'AI自主', batchSize: '100',
         emailSubject: '', emailPreviewText: '',
         trackOpen: true, trackClick: true, trackReply: true,
         whatsappAccount: '业务账号1', whatsappInterval: '60', whatsappTemplate: '',
@@ -1017,9 +1029,91 @@
     const getChannelIconById = (id) => ({ email: 'fas fa-envelope', whatsapp: 'fab fa-whatsapp', linkedin: 'fab fa-linkedin', wechat: 'fab fa-weixin' }[id] || 'fas fa-comment')
     const hasWarnings = computed(() => selectedCustomerCount.value > 500 || !newCampaign.value.emailSubject || newCampaign.value.channels.length === 0)
     const confirmChecks = ref({ content: false, schedule: false, compliance: false, unsubscribe: true })
+    const isSubmitting = ref(false)
     const canCreate = computed(() => confirmChecks.value.content && confirmChecks.value.schedule && confirmChecks.value.compliance)
 
-    const createCampaign = () => { closeCreateModal(); toast('营销活动已创建', 'success') }
+    const goToStep = (step) => { currentStep.value = step }
+
+    const createCampaign = async () => {
+        if (!newCampaign.value.name) {
+            currentStep.value = 1
+            toast('请填写活动名称', 'error')
+            return
+        }
+        if (newCampaign.value.channels.includes('email') && !newCampaign.value.emailSubject) {
+            currentStep.value = 3
+            toast('请设置邮件主题', 'error')
+            return
+        }
+        if (!canCreate.value) {
+            toast('请完成确认事项后创建活动', 'error')
+            return
+        }
+        isSubmitting.value = true
+        try {
+            const typeMap = { prospect: 1, nurture: 2, reactivate: 3, promote: 4, seasonal: 5, exhibition: 6 }
+            const priorityMap = { urgent: 1, high: 2, medium: 3, low: 4 }
+            const ownerMap = { sales1: 1, sales2: 2, sales3: 3 }
+            const selectedOwners = Array.isArray(newCampaign.value.owner)
+                ? newCampaign.value.owner.filter(Boolean)
+                : [newCampaign.value.owner].filter(Boolean)
+            const sopIdMap = { standard: 1, aggressive: 2, nurture: 3, reactivate: 4, custom: 0 }
+            const channelConfig = newCampaign.value.channels.reduce((acc, channel) => {
+                if (channel === 'email') return acc + 1
+                if (channel === 'whatsapp') return acc + 2
+                if (channel === 'linkedin') return acc + 4
+                return acc
+            }, 0)
+            const payload = {
+                name: newCampaign.value.name,
+                type: typeMap[newCampaign.value.type] || 1,
+                priority: priorityMap[newCampaign.value.priority] || 3,
+                description: newCampaign.value.description,
+                beginData: newCampaign.value.startDate || null,
+                endDate: newCampaign.value.endDate || null,
+                supervisor: selectedOwners.length ? ownerMap[selectedOwners[0]] || null : null,
+                linkToOpportunity: newCampaign.value.relatedOpportunity || null,
+                sendVolume: Number(newCampaign.value.goals.sent) || null,
+                openRate: Number(newCampaign.value.goals.openRate) || null,
+                replyRate: Number(newCampaign.value.goals.replyRate) || null,
+                opportunityCount: Number(newCampaign.value.goals.opportunities) || null,
+                channelConfiguration: channelConfig,
+                emailAddress: newCampaign.value.emailSender,
+                emailSentTime: newCampaign.value.emailSendTime,
+                emailTemplate: newCampaign.value.emailTemplate,
+                emailBatchSize: Number(newCampaign.value.batchSize) || null,
+                emailSubject: newCampaign.value.emailSubject,
+                trackConfigOpen: newCampaign.value.trackOpen ? 1 : 0,
+                trackConfigClick: newCampaign.value.trackClick ? 1 : 0,
+                trackConfigReply: newCampaign.value.trackReply ? 1 : 0,
+                sopId: sopIdMap[newCampaign.value.sopTemplate] || 0,
+                followUpRulesNotOpened: newCampaign.value.rules.noOpenFollowup ? 1 : 0,
+                followUpRulesNotOpenedDays: newCampaign.value.rules.noOpenFollowupDays,
+                followUpRulesNotReplied: newCampaign.value.rules.noReplyFollowup ? 1 : 0,
+                followUpRulesNotRepliedDays: newCampaign.value.rules.noReplyFollowupDays,
+                followUpRulesLinkNotClicked: newCampaign.value.rules.noClickFollowup ? 1 : 0,
+                followUpRulesLinkNotClickedDays: newCampaign.value.rules.noClickFollowupDays || null,
+                followUpRulesReplied: newCampaign.value.rules.stopOnReply ? 1 : 0,
+                followUpRulesAppointmentBooked: newCampaign.value.rules.stopOnBooking ? 1 : 0,
+                aBTesting: newCampaign.value.abTest.enabled ? 1 : 0,
+                smartSchedulingType: newCampaign.value.scheduleMode,
+                smartSchedulingStartDate: newCampaign.value.scheduledDate || null,
+                smartSchedulingStartTime: newCampaign.value.scheduledTime || null,
+                hasContentCompliant: confirmChecks.value.content ? 1 : 0,
+                hasScheduleApproved: confirmChecks.value.schedule ? 1 : 0,
+                hasGdprCompliant: confirmChecks.value.compliance ? 1 : 0,
+                hasUnsubscribeLink: confirmChecks.value.unsubscribe ? 1 : 0
+            }
+            console.log(payload)
+            await addMarketing(payload)
+            toast('营销活动已创建', 'success')
+            closeCreateModal()
+        } catch (error) {
+            toast(error?.message || '创建营销活动失败', 'error')
+        } finally {
+            isSubmitting.value = false
+        }
+    }
 
     const selectSubject = (subj) => { newCampaign.value.emailSubject = subj; showSubjectSuggestions.value = false }
     const toggleChannel = (id) => {
@@ -1136,7 +1230,17 @@
         toast('自定义SOP已保存', 'success')
     }
 
-    const open = () => { currentStep.value = 1; showCreateModal.value = true }
+    const getSop = () => {
+        getSopList({ pageIndex: 1, pageSize: 100 }).then(res => {
+            console.log('res :>> ', res);
+        })
+    }
+
+    const open = () => {
+        currentStep.value = 1;
+        showCreateModal.value = true;
+        getSop()
+    }
     const closeCreateModal = () => { showCreateModal.value = false; currentStep.value = 1 }
 
     defineExpose({
@@ -1146,5 +1250,100 @@
 </script>
 
 <style scoped>
-@import '../assets/styles/marketing.css'
+@import '../assets/styles/marketing.css';
+
+.create-modal :deep(.el-input__wrapper),
+.create-modal :deep(.el-textarea__inner),
+.create-modal :deep(.el-select .el-select__wrapper),
+.create-modal :deep(.el-input-number__wrapper),
+.create-modal :deep(.el-date-editor.el-input__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;
+}
+
+.create-modal :deep(.el-date-editor) {
+    width: 100%;
+    min-width: 0;
+}
+
+.create-modal :deep(.el-input__wrapper.is-focus),
+.create-modal :deep(.el-select .el-select__wrapper.is-focus),
+.create-modal :deep(.el-input-number__wrapper.is-focus),
+.create-modal :deep(.el-date-editor.el-input__wrapper.is-focus) {
+    background: var(--bg-card);
+    border-color: var(--primary);
+    box-shadow: 0 0 0 1px var(--primary) inset;
+}
+
+.create-modal :deep(.el-input__inner),
+.create-modal :deep(.el-textarea__inner),
+.create-modal :deep(.el-input-number__inner),
+.create-modal :deep(.el-date-editor .el-input__inner) {
+    background: transparent;
+    color: var(--text-primary);
+}
+
+.create-modal :deep(.el-input__inner::placeholder),
+.create-modal :deep(.el-textarea__inner::placeholder),
+.create-modal :deep(.el-date-editor .el-input__inner::placeholder) {
+    color: var(--text-muted);
+}
+
+.create-modal :deep(.el-select-dropdown),
+.create-modal :deep(.el-picker-panel) {
+    background: var(--bg-card);
+    border: 1px solid var(--border);
+    color: var(--text-primary);
+}
+
+.create-modal :deep(.el-select-dropdown__item.hover),
+.create-modal :deep(.el-select-dropdown__item:hover) {
+    background: rgba(99, 102, 241, 0.12);
+    color: var(--primary);
+}
+
+.create-modal :deep(.el-select-dropdown__item.is-selected) {
+    background: rgba(99, 102, 241, 0.2);
+    color: var(--primary);
+    font-weight: 600;
+}
+
+.create-modal :deep(.el-date-table td.current:not(.disabled) .el-date-table-cell__text),
+.create-modal :deep(.el-date-table td.today .el-date-table-cell__text) {
+    background: var(--primary);
+    color: white;
+}
+
+.create-modal :deep(.el-input__count),
+.create-modal :deep(.el-input__count-inner) {
+    background: transparent;
+    color: var(--text-muted);
+}
+
+.create-modal :deep(.el-input-number) {
+    width: 130px;
+}
+
+.create-modal :deep(.el-input-number__increase),
+.create-modal :deep(.el-input-number__decrease) {
+    background: var(--bg-hover);
+    color: var(--text-primary);
+    border-color: var(--border);
+}
+
+.create-modal :deep(.el-input-number__increase:hover),
+.create-modal :deep(.el-input-number__decrease:hover) {
+    background: rgba(99, 102, 241, 0.12);
+    color: var(--primary);
+}
+
+.create-modal :deep(.el-select__selection .el-select__placeholder:not(.is-transparent)) {
+    color: var(--text-primary);
+}
 </style>

+ 4 - 0
ui/smarttrade-platform/src/main.js

@@ -1,5 +1,8 @@
 import { createApp } from 'vue'
 import { createPinia } from 'pinia'
+import ElementPlus from 'element-plus'
+import zhCn from 'element-plus/es/locale/lang/zh-cn'
+import 'element-plus/dist/index.css'
 import App from './App.vue'
 import router from './router'
 import './assets/styles/main.css'
@@ -8,5 +11,6 @@ const app = createApp(App)
 
 app.use(createPinia())
 app.use(router)
+app.use(ElementPlus, { locale: zhCn })
 
 app.mount('#app')

+ 825 - 0
ui/smarttrade-platform/src/utils/region.ts

@@ -0,0 +1,825 @@
+
+const region =  [
+      {
+        value: "亚洲",
+        label: "亚洲",
+        children: [
+          {
+            value: "中国",
+            label: "中国",
+          },
+          {
+            value: "蒙古",
+            label: "蒙古",
+          },
+          {
+            value: "朝鲜",
+            label: "朝鲜",
+          },
+          {
+            value: "韩国",
+            label: "韩国",
+          },
+          {
+            value: "日本",
+            label: "日本",
+          },
+          {
+            value: "菲律宾",
+            label: "菲律宾",
+          },
+          {
+            value: "越南",
+            label: "越南",
+          },
+          {
+            value: "老挝",
+            label: "老挝",
+          },
+          {
+            value: "柬埔寨",
+            label: "柬埔寨",
+          },
+          {
+            value: "缅甸",
+            label: "缅甸",
+          },
+          {
+            value: "泰国",
+            label: "泰国",
+          },
+          {
+            value: "马来西亚",
+            label: "马来西亚",
+          },
+          {
+            value: "文莱",
+            label: "文莱",
+          },
+          {
+            value: "新加坡",
+            label: "新加坡",
+          },
+          {
+            value: "印度尼西亚",
+            label: "印度尼西亚",
+          },
+          {
+            value: "东帝汶",
+            label: "东帝汶",
+          },
+          {
+            value: "尼泊尔",
+            label: "尼泊尔",
+          },
+          {
+            value: "不丹",
+            label: "不丹",
+          },
+          {
+            value: "孟加拉国",
+            label: "孟加拉国",
+          },
+          {
+            value: "印度",
+            label: "印度",
+          },
+          {
+            value: "巴基斯坦",
+            label: "巴基斯坦",
+          },
+          {
+            value: "斯里兰卡",
+            label: "斯里兰卡",
+          },
+          {
+            value: "马尔代夫",
+            label: "马尔代夫",
+          },
+          {
+            value: "哈萨克斯坦",
+            label: "哈萨克斯坦",
+          },
+          {
+            value: "吉尔吉斯斯坦",
+            label: "吉尔吉斯斯坦",
+          },
+          {
+            value: "塔吉克斯坦",
+            label: "塔吉克斯坦",
+          },
+          {
+            value: "乌兹别克斯坦",
+            label: "乌兹别克斯坦",
+          },
+          {
+            value: "土库曼斯坦",
+            label: "土库曼斯坦",
+          },
+          {
+            value: "阿富汗",
+            label: "阿富汗",
+          },
+          {
+            value: "伊拉克",
+            label: "伊拉克",
+          },
+          {
+            value: "伊朗",
+            label: "伊朗",
+          },
+   
+          {
+            value: "叙利亚",
+            label: "叙利亚",
+          },
+          {
+            value: "约旦",
+            label: "约旦",
+          },
+          {
+            value: "黎巴嫩",
+            label: "黎巴嫩",
+          },
+          {
+            value: "以色列",
+            label: "以色列",
+          },
+          {
+            value: "巴勒斯坦",
+            label: "巴勒斯坦",
+          },
+          {
+            value: "沙特阿拉伯",
+            label: "沙特阿拉伯",
+          },
+          {
+            value: "巴林",
+            label: "巴林",
+          },
+          {
+            value: "卡塔尔",
+            label: "卡塔尔",
+          },
+   
+          {
+            value: "科威特",
+            label: "科威特",
+          },
+          {
+            value: "阿拉伯联合酋长国",
+            label: "阿拉伯联合酋长国",
+          },
+          {
+            value: "阿曼",
+            label: "阿曼",
+          },
+          {
+            value: "也门",
+            label: "也门",
+          },
+          {
+            value: "格鲁吉亚",
+            label: "格鲁吉亚",
+          },
+          {
+            value: "亚美尼亚",
+            label: "亚美尼亚",
+          },
+          {
+            value: "阿塞拜疆",
+            label: "阿塞拜疆",
+          },
+          {
+            value: "土耳其",
+            label: "土耳其",
+          },
+          {
+            value: "塞浦路斯",
+            label: "塞浦路斯",
+          },
+        ],
+      },
+      {
+        value: "欧洲",
+        label: "欧洲",
+        children: [
+          {
+            value: "芬兰",
+            label: "芬兰",
+          },
+          {
+            value: "瑞典",
+            label: "瑞典",
+          },
+          {
+            value: "挪威",
+            label: "挪威",
+          },
+          {
+            value: "冰岛",
+            label: "冰岛",
+          },
+          {
+            value: "爱沙尼亚",
+            label: "爱沙尼亚",
+          },
+          {
+            value: "拉脱维亚",
+            label: "拉脱维亚",
+          },
+          {
+            value: "立陶宛",
+            label: "立陶宛",
+          },
+          {
+            value: "摩尔多瓦",
+            label: "摩尔多瓦",
+          },
+          {
+            value: "白俄罗斯",
+            label: "白俄罗斯",
+          },
+          {
+            value: "俄罗斯",
+            label: "俄罗斯",
+          },
+          {
+            value: "乌克兰",
+            label: "乌克兰",
+          },
+          {
+            value: "波兰",
+            label: "波兰",
+          },
+          {
+            value: "捷克共和国",
+            label: "捷克共和国",
+          },
+          {
+            value: "斯洛伐克",
+            label: "斯洛伐克",
+          },
+          {
+            value: "匈牙利",
+            label: "匈牙利",
+          },
+          {
+            value: "德国",
+            label: "德国",
+          },
+          {
+            value: "奥地利",
+            label: "奥地利",
+          },
+          {
+            value: "瑞士",
+            label: "瑞士",
+          },
+          {
+            value: "列支敦士登",
+            label: "列支敦士登",
+          },
+          {
+            value: "英国",
+            label: "英国",
+          },
+          {
+            value: "爱尔兰",
+            label: "爱尔兰",
+          },
+          {
+            value: "荷兰",
+            label: "荷兰",
+          },
+          {
+            value: "比利时",
+            label: "比利时",
+          },
+          {
+            value: "卢森堡",
+            label: "卢森堡",
+          },
+          {
+            value: "法国",
+            label: "法国",
+          },
+          {
+            value: "摩纳哥",
+            label: "摩纳哥",
+          },
+          {
+            value: "罗马尼亚",
+            label: "罗马尼亚",
+          },
+          {
+            value: "保加利亚",
+            label: "保加利亚",
+          },
+          {
+            value: "塞尔维亚",
+            label: "塞尔维亚",
+          },
+          {
+            value: "马其顿",
+            label: "马其顿",
+          },
+          {
+            value: "斯洛文尼亚",
+            label: "斯洛文尼亚",
+          },
+          {
+            value: "克罗地亚",
+            label: "克罗地亚",
+          },
+          {
+            value: "黑山共和国",
+            label: "黑山共和国",
+          },
+          {
+            value: "波斯尼亚和黑塞哥维那",
+            label: "波斯尼亚和黑塞哥维那",
+          },
+          {
+            value: "阿尔巴尼亚",
+            label: "阿尔巴尼亚",
+          },
+          {
+            value: "希腊",
+            label: "希腊",
+          },
+          {
+            value: "意大利",
+            label: "意大利",
+          },
+          {
+            value: "马耳他",
+            label: "马耳他",
+          },
+          {
+            value: "丹麦",
+            label: "丹麦",
+          },
+          {
+            value: "梵蒂冈",
+            label: "梵蒂冈",
+          },
+          {
+            value: "圣马力诺",
+            label: "圣马力诺",
+          },
+          {
+            value: "西班牙",
+            label: "西班牙",
+          },
+          {
+            value: "葡萄牙",
+            label: "葡萄牙",
+          },
+          {
+            value: "安道尔共和国",
+            label: "安道尔共和国",
+          },
+        ],
+      },
+      {
+        value: "非洲",
+        label: "非洲",
+        children: [
+          {
+            value: "埃及",
+            label: "埃及",
+          },
+          {
+            value: "利比亚",
+            label: "利比亚",
+          },
+          {
+            value: "突尼斯",
+            label: "突尼斯",
+          },
+          {
+            value: "阿尔及利亚",
+            label: "阿尔及利亚",
+          },
+          {
+            value: "摩洛哥",
+            label: "摩洛哥",
+          },
+          {
+            value: "苏丹",
+            label: "苏丹",
+          },
+          {
+            value: "南苏丹",
+            label: "南苏丹",
+          },
+          {
+            value: "埃塞俄比亚",
+            label: "埃塞俄比亚",
+          },
+          {
+            value: "厄立特里亚",
+            label: "厄立特里亚",
+          },
+          {
+            value: "索马里",
+            label: "索马里",
+          },
+          {
+            value: "吉布提",
+            label: "吉布提",
+          },
+          {
+            value: "肯尼亚",
+            label: "肯尼亚",
+          },
+          {
+            value: "坦桑尼亚",
+            label: "坦桑尼亚",
+          },
+          {
+            value: "乌干达",
+            label: "乌干达",
+          },
+          {
+            value: "卢旺达",
+            label: "卢旺达",
+          },
+          {
+            value: "布隆迪",
+            label: "布隆迪",
+          },
+          {
+            value: "塞舌尔",
+            label: "塞舌尔",
+          },
+          {
+            value: "乍得共和国",
+            label: "乍得共和国",
+          },
+          {
+            value: "中非共和国",
+            label: "中非共和国",
+          },
+          {
+            value: "喀麦隆",
+            label: "喀麦隆",
+          },
+          {
+            value: "赤道几内亚",
+            label: "赤道几内亚",
+          },
+          {
+            value: "加蓬",
+            label: "加蓬",
+          },
+          {
+            value: "刚果共和国",
+            label: "刚果共和国",
+          },
+          {
+            value: "刚果民主共和国",
+            label: "刚果民主共和国",
+          },
+          {
+            value: "圣多美和普林西比",
+            label: "圣多美和普林西比",
+          },
+          {
+            value: "毛里塔尼亚",
+            label: "毛里塔尼亚",
+          },
+          {
+            value: "塞内加尔",
+            label: "塞内加尔",
+          },
+          {
+            value: "冈比亚",
+            label: "冈比亚",
+          },
+          {
+            value: "马里共和国",
+            label: "马里共和国",
+          },
+          {
+            value: "布基纳法索",
+            label: "布基纳法索",
+          },
+          {
+            value: "几内亚",
+            label: "几内亚",
+          },
+          {
+            value: "几内亚比绍",
+            label: "几内亚比绍",
+          },
+          {
+            value: "佛得角",
+            label: "佛得角",
+          },
+          {
+            value: "塞拉利昂",
+            label: "塞拉利昂",
+          },
+          {
+            value: "利比里亚",
+            label: "利比里亚",
+          },
+          {
+            value: "科特迪瓦",
+            label: "科特迪瓦",
+          },
+          {
+            value: "加纳",
+            label: "加纳",
+          },
+          {
+            value: "多哥",
+            label: "多哥",
+          },
+          {
+            value: "贝宁共和国",
+            label: "贝宁共和国",
+          },
+          {
+            value: "尼日尔",
+            label: "尼日尔",
+          },
+          {
+            value: "尼日利亚",
+            label: "尼日利亚",
+          },
+          {
+            value: "赞比亚",
+            label: "赞比亚",
+          },
+          {
+            value: "安哥拉",
+            label: "安哥拉",
+          },
+          {
+            value: "津巴布韦",
+            label: "津巴布韦",
+          },
+          {
+            value: "马拉维",
+            label: "马拉维",
+          },
+          {
+            value: "莫桑比克",
+            label: "莫桑比克",
+          },
+          {
+            value: "博茨瓦纳",
+            label: "博茨瓦纳",
+          },
+          {
+            value: "纳米比亚",
+            label: "纳米比亚",
+          },
+          {
+            value: "南非",
+            label: "南非",
+          },
+          {
+            value: "斯威士兰",
+            label: "斯威士兰",
+          },
+          {
+            value: "莱索托",
+            label: "莱索托",
+          },
+          {
+            value: "马达加斯加",
+            label: "马达加斯加",
+          },
+          {
+            value: "科摩罗联盟",
+            label: "科摩罗联盟",
+          },
+          {
+            value: "毛里求斯",
+            label: "毛里求斯",
+          },
+        ],
+      },
+      {
+        value: "北美洲",
+        label: "北美洲",
+        children: [
+          {
+            value: "加拿大",
+            label: "加拿大",
+          },
+          {
+            value: "美国",
+            label: "美国",
+          },
+          {
+            value: "墨西哥",
+            label: "墨西哥",
+          },
+          {
+            value: "危地马拉",
+            label: "危地马拉",
+          },
+          {
+            value: "伯利兹",
+            label: "伯利兹",
+          },
+          {
+            value: "萨尔瓦多",
+            label: "萨尔瓦多",
+          },
+          {
+            value: "洪都拉斯",
+            label: "洪都拉斯",
+          },
+          {
+            value: "尼加拉瓜",
+            label: "尼加拉瓜",
+          },
+          {
+            value: "哥斯达黎加",
+            label: "哥斯达黎加",
+          },
+          {
+            value: "巴拿马",
+            label: "巴拿马",
+          },
+          {
+            value: "巴哈马群岛",
+            label: "巴哈马群岛",
+          },
+          {
+            value: "古巴",
+            label: "古巴",
+          },
+          {
+            value: "牙买加",
+            label: "牙买加",
+          },
+          {
+            value: "海地",
+            label: "海地",
+          },
+          {
+            value: "多米尼加共和国",
+            label: "多米尼加共和国",
+          },
+          {
+            value: "安提瓜和巴布达",
+            label: "安提瓜和巴布达",
+          },
+          {
+            value: "圣基茨和尼维斯",
+            label: "圣基茨和尼维斯",
+          },
+          {
+            value: "多米尼克",
+            label: "多米尼克",
+          },
+          {
+            value: "圣卢西亚",
+            label: "圣卢西亚",
+          },
+          {
+            value: "圣文森特和格林纳丁斯",
+            label: "圣文森特和格林纳丁斯",
+          },
+          {
+            value: "格林纳达",
+            label: "格林纳达",
+          },
+          {
+            value: "巴巴多斯",
+            label: "巴巴多斯",
+          },
+          {
+            value: "特立尼达和多巴哥",
+            label: "特立尼达和多巴哥",
+          },
+        ],
+      },
+      {
+        value: "南美洲",
+        label: "南美洲",
+        children: [
+          {
+            value: "哥伦比亚",
+            label: "哥伦比亚",
+          },
+          {
+            value: "委内瑞拉",
+            label: "委内瑞拉",
+          },
+          {
+            value: "圭亚那",
+            label: "圭亚那",
+          },
+          {
+            value: "苏里南",
+            label: "苏里南",
+          },
+          {
+            value: "厄瓜多尔",
+            label: "厄瓜多尔",
+          },
+          {
+            value: "秘鲁",
+            label: "秘鲁",
+          },
+          {
+            value: "巴西",
+            label: "巴西",
+          },
+          {
+            value: "智利",
+            label: "智利",
+          },
+          {
+            value: "阿根廷",
+            label: "阿根廷",
+          },
+          {
+            value: "乌拉圭",
+            label: "乌拉圭",
+          },
+          {
+            value: "巴拉圭",
+            label: "巴拉圭",
+          },
+        ],
+      },
+      {
+        value: "大洋洲",
+        label: "大洋洲",
+        children: [
+          {
+            value: "澳大利亚",
+            label: "澳大利亚",
+          },
+          {
+            value: "新西兰",
+            label: "新西兰",
+          },
+          {
+            value: "帕劳",
+            label: "帕劳",
+          },
+          {
+            value: "密克罗尼西亚联邦",
+            label: "密克罗尼西亚联邦",
+          },
+          {
+            value: "马绍尔群岛",
+            label: "马绍尔群岛",
+          },
+          {
+            value: "基里巴斯",
+            label: "基里巴斯",
+          },
+          {
+            value: "瑙鲁",
+            label: "瑙鲁",
+          },
+          {
+            value: "巴布亚新几内亚",
+            label: "巴布亚新几内亚",
+          },
+          {
+            value: "所罗门群岛",
+            label: "所罗门群岛",
+          },
+          {
+            value: "瓦努阿图",
+            label: "瓦努阿图",
+          },
+          {
+            value: "斐济",
+            label: "斐济",
+          },
+          {
+            value: "图瓦卢",
+            label: "图瓦卢",
+          },
+          {
+            value: "萨摩亚",
+            label: "萨摩亚",
+          },
+          {
+            value: "汤加",
+            label: "汤加",
+          },
+          {
+            value: "库克群岛",
+            label: "库克群岛",
+          },
+        ],
+      },
+    ]
+
+export {
+    region
+}

+ 33 - 0
ui/smarttrade-platform/src/utils/time.js

@@ -0,0 +1,33 @@
+/**
+ * 秒级时间戳 转 多久前/多久后
+ * @param {number} timestamp 秒时间戳
+ * @returns string
+ */
+export function formatTimeAgo(timestamp) {
+  const now = Math.floor(Date.now() / 1000) // 当前秒级时间戳
+  const diff = timestamp - now // 差值,正数=未来,负数=过去
+
+  const absDiff = Math.abs(diff)
+  const minute = 60
+  const hour = minute * 60
+  const day = hour * 24
+  const month = day * 30
+  const year = day * 365
+
+  let text = ''
+  if (absDiff < minute) {
+    text = `${absDiff}秒`
+  } else if (absDiff < hour) {
+    text = `${Math.floor(absDiff / minute)}分钟`
+  } else if (absDiff < day) {
+    text = `${Math.floor(absDiff / hour)}小时`
+  } else if (absDiff < month) {
+    text = `${Math.floor(absDiff / day)}天`
+  } else if (absDiff < year) {
+    text = `${Math.floor(absDiff / month)}个月`
+  } else {
+    text = `${Math.floor(absDiff / year)}年`
+  }
+
+  return diff > 0 ? `${text}后` : `${text}前`
+}

+ 122 - 107
ui/smarttrade-platform/src/views/CustomersView.vue

@@ -36,16 +36,11 @@
     <div style="display: flex; gap: 16px; margin-bottom: 20px;">
       <div class="search-box" style="flex: 1;">
         <i class="fas fa-search search-icon"></i>
-        <input type="text" v-model="searchQuery" class="search-input" placeholder="搜索公司名称、联系人、邮箱...">
+        <input type="text" v-model="searchQuery" class="search-input" placeholder="搜索公司名称、联系人、邮箱..." @input="getCustomer">
       </div>
       <div style="display: flex; gap: 8px;">
-        <select v-model="regionFilter" class="form-select" style="width: auto; padding: 8px 16px;">
-          <option value="">全部地区</option>
-          <option value="中东">中东</option>
-          <option value="欧洲">欧洲</option>
-          <option value="北美">北美</option>
-          <option value="东南亚">东南亚</option>
-        </select>
+        <el-cascader class="form-select" style="width: auto; padding: 0;" placeholder="区域名称" :options="region"
+          v-model="regionValue" clearable filterable @change="getRegion" />
         <select v-model="industryFilter" class="form-select" style="width: auto; padding: 8px 16px;">
           <option value="">全部行业</option>
           <option value="固态硬盘">固态硬盘</option>
@@ -81,24 +76,25 @@
         <div class="customer-flag">{{ customer.flag }}</div>
         <div class="customer-info">
           <div class="customer-header">
-            <span class="customer-name">{{ customer.name }}</span>
-            <span class="customer-status" :style="{ background: getStatusBg(customer.status), color: getStatusColor(customer.status) }">
+            <span class="customer-name">{{ customer.customerName }}</span>
+            <!-- 客户类型标识 -->
+            <!-- <span class="customer-status" :style="{ background: getStatusBg(customer.status), color: getStatusColor(customer.status) }">
               {{ getStatusLabel(customer.status) }}
-            </span>
+            </span> -->
           </div>
           <div class="customer-contact">
-            <i class="fas fa-user"></i> {{ customer.contact }} · {{ customer.role }}
+            <i class="fas fa-user"></i> {{ customer.liaison.name }}<span v-if="customer.liaison.departmentPosition"> · </span>{{ customer.liaison.departmentPosition }}
           </div>
           <div class="customer-meta">
-            <span><i class="fas fa-map-marker-alt"></i> {{ customer.location }}</span>
-            <span><i class="fas fa-industry"></i> {{ customer.industry }}</span>
-            <span><i class="fas fa-dollar-sign"></i> {{ customer.annualPurchase }}</span>
+            <span><i class="fas fa-map-marker-alt"></i> {{ customer.continent }} · {{ customer.country }}</span>
+            <span><i class="fas fa-industry"></i> {{ customer.customerCompany.industry }}</span>
+            <!-- <span><i class="fas fa-dollar-sign"></i> {{ customer.annualPurchase }}</span> -->
           </div>
         </div>
-        <div class="customer-score">
+        <!-- <div class="customer-score">
           <div class="score-circle" :class="getScoreClass(customer.score)">{{ customer.score }}</div>
           <div class="score-label">意向分</div>
-        </div>
+        </div> -->
         <div class="customer-actions" @click.stop>
           <button class="btn btn-secondary btn-sm" @click="showCustomerDetail(customer)"><i class="fas fa-eye"></i>更多详情</button>
         </div>
@@ -127,7 +123,7 @@
         </div>
         <div class="modal-body">
           <!-- Quick Stats -->
-          <div class="detail-stats">
+          <!-- <div class="detail-stats">
             <div class="detail-stat-card">
               <div class="detail-stat-value" :class="getScoreClass(selectedCustomer.score)">{{ selectedCustomer.score }}</div>
               <div class="detail-stat-label">意向分</div>
@@ -144,7 +140,7 @@
               <div class="detail-stat-value">{{ selectedCustomer.lastContact }}</div>
               <div class="detail-stat-label">最后联系</div>
             </div>
-          </div>
+          </div> -->
 
           <!-- Contact Info -->
           <div class="detail-section">
@@ -154,29 +150,29 @@
                 <i class="fas fa-user"></i>
                 <div class="contact-content">
                   <div class="contact-label">主要联系人</div>
-                  <div class="contact-value">{{ selectedCustomer.contact }}</div>
-                  <div class="contact-sub">{{ selectedCustomer.role }}</div>
+                  <div class="contact-value">{{ selectedCustomer.liaison?.name }}</div>
+                  <div class="contact-sub">{{ selectedCustomer.liaison?.departmentPosition }}</div>
                 </div>
               </div>
               <div class="contact-item">
                 <i class="fas fa-envelope"></i>
                 <div class="contact-content">
                   <div class="contact-label">邮箱</div>
-                  <div class="contact-value">{{ selectedCustomer.email }}</div>
+                  <div class="contact-value">{{ selectedCustomer.liaison?.email }}</div>
                 </div>
               </div>
               <div class="contact-item">
                 <i class="fas fa-phone"></i>
                 <div class="contact-content">
                   <div class="contact-label">电话</div>
-                  <div class="contact-value">{{ selectedCustomer.phone }}</div>
+                  <div class="contact-value">{{ selectedCustomer.liaison?.telephone }}</div>
                 </div>
               </div>
               <div class="contact-item">
                 <i class="fas fa-globe"></i>
                 <div class="contact-content">
                   <div class="contact-label">官网</div>
-                  <div class="contact-value link">{{ selectedCustomer.website }}</div>
+                  <div class="contact-value link">{{ selectedCustomer.customerCompany?.website }}</div>
                 </div>
               </div>
             </div>
@@ -185,7 +181,7 @@
           <!-- AI Insights -->
           <div class="detail-section">
             <h4><i class="fas fa-robot"></i> AI 洞察</h4>
-            <div class="ai-insight-cards">
+            <div class="ai-insight-cards" v-if="selectedCustomer.aiInsights">
               <div class="ai-insight-card" v-for="insight in selectedCustomer.aiInsights" :key="insight.type">
                 <div class="insight-icon" :class="insight.level">
                   <i :class="insight.icon"></i>
@@ -201,7 +197,7 @@
           <!-- Interaction Timeline -->
           <div class="detail-section">
             <h4><i class="fas fa-history"></i> 互动记录</h4>
-            <div class="timeline">
+            <div class="timeline" v-if="selectedCustomer.interactions">
               <div class="timeline-item" v-for="record in selectedCustomer.interactions" :key="record.id">
                 <div class="timeline-dot" :class="record.type"></div>
                 <div class="timeline-content">
@@ -231,7 +227,7 @@
         </div>
         <div class="modal-footer">
           <button class="btn btn-secondary" @click="showDetailModal = false">关闭</button>
-          <button class="btn btn-secondary"><i class="fas fa-edit"></i> 编辑</button>
+          <!-- <button class="btn btn-secondary"><i class="fas fa-edit"></i> 编辑</button> -->
           <button class="btn btn-primary" @click="">
             <i class="fas fa-rocket"></i> 客户详情
           </button>
@@ -252,6 +248,7 @@
 <script setup>
 import { ref, computed } from 'vue'
 import { getCustomerList } from '../api/customer'
+import {region} from '@/utils/region';
 // 引入添加客户组件
 import AddCustomerModal from '../components/addCustomer.vue'
 
@@ -266,89 +263,106 @@ const showToast = ref(false)
 const toastMessage = ref('')
 const toastType = ref('success')
 
-const customerStats = {
-  total: 1247,
-  prospect: 812,
-  intent: 224,
-  deal: 156,
-  silent: 55
-}
-
-const tabs = [
-  { key: 'all', label: '全部客户', count: 1247 },
-  { key: 'prospect', label: '潜在客户', count: 812 },
-  { key: 'intent', label: '意向客户', count: 224 },
-  { key: 'deal', label: '成交客户', count: 156 },
-  { key: 'silent', label: '沉默客户', count: 55 }
-]
-
-getCustomerList().then(res => {
-  console.log('res :>》》》> ', res);
+const customerStats = ref({
+  total: 0,
+  prospect: 0,
+  intent: 0,
+  deal: 0,
+  silent: 0
 })
 
-const customers = ref([
-  { 
-    id: 1, flag: '🇸🇦', name: 'Riyadh Tech Distribution', status: 'prospect', contact: 'Ahmed Al-Rashid', 
-    role: '采购总监', location: '沙特·利雅得', industry: '固态硬盘进口商', annualPurchase: '年采购$2M+', score: 92,
-    email: 'ahmed@riyahtech.sa', phone: '+966 50 123 4567', website: 'www.riyahtech.sa',
-    totalDeals: 3, totalAmount: 4560000, lastContact: '3天前',
-    aiInsights: [
-      { type: 'purchase', level: 'high', icon: 'fas fa-shopping-cart', title: '采购意愿强烈', description: '近30天内访问官网5次,下载产品手册,建议重点跟进' },
-      { type: 'timing', level: 'medium', icon: 'fas fa-clock', title: '最佳联系时间', description: '根据历史数据分析,周二上午10点联系成功率最高' },
-      { type: 'decision', level: 'high', icon: 'fas fa-user-tie', title: '决策人已识别', description: 'Ahmed Al-Rashid 为采购决策人,已验证邮箱有效性' }
-    ],
-    interactions: [
-      { id: 1, type: 'email', action: '发送报价单', detail: 'SSD采购报价已发送,等待回复', time: '3天前' },
-      { id: 2, type: 'visit', action: '官网访问', detail: '浏览了企业级SSD产品页面,停留8分钟', time: '5天前' },
-      { id: 3, type: 'call', action: '电话沟通', detail: '确认了采购需求,数量约5000片', time: '1周前' },
-      { id: 4, type: 'email', action: '首次联系', detail: '通过LinkedIn找到联系人,发送介绍邮件', time: '2周前' }
-    ],
-    opportunities: [
-      { id: 1, name: 'SSD批量采购', amount: 850000, stage: '谈判中', statusClass: 'negotiation', statusLabel: '跟进中' }
-    ]
-  },
-  { 
-    id: 2, flag: '🇦🇪', name: 'Dubai Storage Solutions', status: 'prospect', contact: 'Mohammed Khan', 
-    role: 'CEO', location: '阿联酋·迪拜', industry: '电子分销商', annualPurchase: '年采购$1.5M', score: 88,
-    email: 'mohammed@dubaistorage.ae', phone: '+971 50 987 6543', website: 'www.dubaistorage.ae',
-    totalDeals: 2, totalAmount: 2800000, lastContact: '1天前',
-    aiInsights: [
-      { type: 'purchase', level: 'medium', icon: 'fas fa-chart-line', title: '采购潜力', description: '公司规模扩张中,预计明年需求增长30%' }
-    ],
-    interactions: [
-      { id: 1, type: 'meeting', action: '视频会议', detail: '讨论Q2采购计划,对NVMe SSD感兴趣', time: '1天前' }
-    ],
-    opportunities: []
-  },
-  { 
-    id: 3, flag: '🇰🇼', name: 'Kuwait Data Systems', status: 'intent', contact: 'Faisal Al-Mutairi', 
-    role: 'IT总监', location: '科威特', industry: '数据中心集成商', annualPurchase: '年采购$2.5M', score: 85,
-    email: 'faisal@kwdata.kw', phone: '+965 99 888 777', website: 'www.kwdata.kw',
-    totalDeals: 5, totalAmount: 6800000, lastContact: '今天',
-    aiInsights: [
-      { type: 'timing', level: 'high', icon: 'fas fa-fire', title: '紧急需求', description: '数据中心扩容项目急需采购,建议立即跟进' }
-    ],
-    interactions: [
-      { id: 1, type: 'email', action: '回复询价', detail: '确认了技术规格,要求提供样品', time: '今天' }
-    ],
-    opportunities: [
-      { id: 1, name: '数据中心扩容项目', amount: 1200000, stage: '方案报价', statusClass: 'proposal', statusLabel: '待报价' }
-    ]
-  },
-  { 
-    id: 4, flag: '🇩🇪', name: 'Berlin Tech GmbH', status: 'deal', contact: 'Hans Mueller', 
-    role: '采购经理', location: '德国·柏林', industry: '企业级存储', annualPurchase: '年采购$3M+', score: 95,
-    email: 'hans@berlintech.de', phone: '+49 30 12345678', website: 'www.berlintech.de',
-    totalDeals: 8, totalAmount: 12500000, lastContact: '2天前',
-    aiInsights: [],
-    interactions: [
-      { id: 1, type: 'order', action: '订单确认', detail: 'PO-2026-0315 已确认,金额¥680,000', time: '2天前' }
-    ],
-    opportunities: [
-      { id: 1, name: 'NVMe SSD批量采购', amount: 680000, stage: '已成交', statusClass: 'won', statusLabel: '已成交' }
-    ]
-  }
+const tabs = ref([
+  { key: 'all', label: '全部客户', count: 0 },
+  // { key: 'prospect', label: '潜在客户', count: 812 },
+  // { key: 'intent', label: '意向客户', count: 224 },
+  // { key: 'deal', label: '成交客户', count: 156 },
+  // { key: 'silent', label: '沉默客户', count: 55 }
 ])
+const customers = ref([])
+const params = ref({
+  pageIndex: 1, 
+  pageSize: 10,
+  blurry:null,
+  country:null,
+})
+const getCustomer = () => {
+    params.value.blurry = searchQuery.value
+    getCustomerList(params.value).then(res => {
+    customerStats.value.total = tabs.value[0].count = res.result.total
+    customers.value = res.result.records
+  })
+}
+getCustomer()
+
+const regionValue = ref([])
+const getRegion = (value) => {
+    params.value.country = value?value[1]:''
+    getCustomer()
+}
+
+// const customers = ref([
+//   { 
+//     id: 1, flag: '🇸🇦', name: 'Riyadh Tech Distribution', status: 'prospect', contact: 'Ahmed Al-Rashid', 
+//     role: '采购总监', location: '沙特·利雅得', industry: '固态硬盘进口商', annualPurchase: '年采购$2M+', score: 92,
+//     email: 'ahmed@riyahtech.sa', phone: '+966 50 123 4567', website: 'www.riyahtech.sa',
+//     totalDeals: 3, totalAmount: 4560000, lastContact: '3天前',
+//     aiInsights: [
+//       { type: 'purchase', level: 'high', icon: 'fas fa-shopping-cart', title: '采购意愿强烈', description: '近30天内访问官网5次,下载产品手册,建议重点跟进' },
+//       { type: 'timing', level: 'medium', icon: 'fas fa-clock', title: '最佳联系时间', description: '根据历史数据分析,周二上午10点联系成功率最高' },
+//       { type: 'decision', level: 'high', icon: 'fas fa-user-tie', title: '决策人已识别', description: 'Ahmed Al-Rashid 为采购决策人,已验证邮箱有效性' }
+//     ],
+//     interactions: [
+//       { id: 1, type: 'email', action: '发送报价单', detail: 'SSD采购报价已发送,等待回复', time: '3天前' },
+//       { id: 2, type: 'visit', action: '官网访问', detail: '浏览了企业级SSD产品页面,停留8分钟', time: '5天前' },
+//       { id: 3, type: 'call', action: '电话沟通', detail: '确认了采购需求,数量约5000片', time: '1周前' },
+//       { id: 4, type: 'email', action: '首次联系', detail: '通过LinkedIn找到联系人,发送介绍邮件', time: '2周前' }
+//     ],
+//     opportunities: [
+//       { id: 1, name: 'SSD批量采购', amount: 850000, stage: '谈判中', statusClass: 'negotiation', statusLabel: '跟进中' }
+//     ]
+//   },
+//   { 
+//     id: 2, flag: '🇦🇪', name: 'Dubai Storage Solutions', status: 'prospect', contact: 'Mohammed Khan', 
+//     role: 'CEO', location: '阿联酋·迪拜', industry: '电子分销商', annualPurchase: '年采购$1.5M', score: 88,
+//     email: 'mohammed@dubaistorage.ae', phone: '+971 50 987 6543', website: 'www.dubaistorage.ae',
+//     totalDeals: 2, totalAmount: 2800000, lastContact: '1天前',
+//     aiInsights: [
+//       { type: 'purchase', level: 'medium', icon: 'fas fa-chart-line', title: '采购潜力', description: '公司规模扩张中,预计明年需求增长30%' }
+//     ],
+//     interactions: [
+//       { id: 1, type: 'meeting', action: '视频会议', detail: '讨论Q2采购计划,对NVMe SSD感兴趣', time: '1天前' }
+//     ],
+//     opportunities: []
+//   },
+//   { 
+//     id: 3, flag: '🇰🇼', name: 'Kuwait Data Systems', status: 'intent', contact: 'Faisal Al-Mutairi', 
+//     role: 'IT总监', location: '科威特', industry: '数据中心集成商', annualPurchase: '年采购$2.5M', score: 85,
+//     email: 'faisal@kwdata.kw', phone: '+965 99 888 777', website: 'www.kwdata.kw',
+//     totalDeals: 5, totalAmount: 6800000, lastContact: '今天',
+//     aiInsights: [
+//       { type: 'timing', level: 'high', icon: 'fas fa-fire', title: '紧急需求', description: '数据中心扩容项目急需采购,建议立即跟进' }
+//     ],
+//     interactions: [
+//       { id: 1, type: 'email', action: '回复询价', detail: '确认了技术规格,要求提供样品', time: '今天' }
+//     ],
+//     opportunities: [
+//       { id: 1, name: '数据中心扩容项目', amount: 1200000, stage: '方案报价', statusClass: 'proposal', statusLabel: '待报价' }
+//     ]
+//   },
+//   { 
+//     id: 4, flag: '🇩🇪', name: 'Berlin Tech GmbH', status: 'deal', contact: 'Hans Mueller', 
+//     role: '采购经理', location: '德国·柏林', industry: '企业级存储', annualPurchase: '年采购$3M+', score: 95,
+//     email: 'hans@berlintech.de', phone: '+49 30 12345678', website: 'www.berlintech.de',
+//     totalDeals: 8, totalAmount: 12500000, lastContact: '2天前',
+//     aiInsights: [],
+//     interactions: [
+//       { id: 1, type: 'order', action: '订单确认', detail: 'PO-2026-0315 已确认,金额¥680,000', time: '2天前' }
+//     ],
+//     opportunities: [
+//       { id: 1, name: 'NVMe SSD批量采购', amount: 680000, stage: '已成交', statusClass: 'won', statusLabel: '已成交' }
+//     ]
+//   }
+// ])
 
 const filteredCustomers = computed(() => {
   let result = customers.value
@@ -501,6 +515,7 @@ const handleAddCustomer = (customerData) => {
 .search-input { width: 100%; padding: 10px 14px 10px 40px; background: var(--bg-card); border: 1px solid var(--border); border-radius: 10px; color: var(--text-primary); font-size: 13px; }
 .search-input:focus { outline: none; border-color: var(--primary); }
 .form-select { background: var(--bg-card); border: 1px solid var(--border); color: var(--text-primary); }
+.form-select :deep(.el-input__wrapper){ background: var(--bg-card);}
 .customer-card { display: flex; align-items: center; gap: 16px; padding: 16px; background: var(--bg-card); border-radius: 12px; margin-bottom: 12px; border: 1px solid var(--border); cursor: pointer; transition: all 0.2s; }
 .customer-card:hover { border-color: var(--primary); }
 .customer-flag { font-size: 32px; }

+ 91 - 59
ui/smarttrade-platform/src/views/KnowledgeView.vue

@@ -125,23 +125,23 @@
       </div>
     </div>
 
-    <!-- Categories Tab -->
+    <!-- 知识分类 Tab -->
     <div v-if="activeTab === 'categories'" class="section">
       <div class="category-grid">
-        <div v-for="category in filteredCategories" :key="category.id"
+        <div v-for="category in categories" :key="category.id"
              class="category-card"
              @click="openCategory(category)">
-          <div class="category-header">
+          <!-- <div class="category-header">
             <div class="category-icon" :style="{ background: category.iconBg }">{{ category.icon }}</div>
             <div class="category-badge" v-if="category.trending">
               <i class="fas fa-fire"></i> 热门
             </div>
-          </div>
+          </div> -->
           <div class="category-name">{{ category.name }}</div>
           <div class="category-desc">{{ category.description }}</div>
           <div class="category-stats">
             <div class="category-stat">
-              <span class="stat-value">{{ category.count }}</span>
+              <span class="stat-value">{{ category.document_count }}</span>
               <span class="stat-label">知识点</span>
             </div>
             <div class="category-stat">
@@ -154,31 +154,31 @@
             </div>
           </div>
           <div class="category-footer">
-            <span class="update-time">更新于 {{ category.updatedAt }}</span>
+            <span class="update-time">更新于 {{ category.timeip }}</span>
             <i class="fas fa-chevron-right"></i>
           </div>
         </div>
       </div>
     </div>
 
-    <!-- All Knowledge Tab -->
+    <!-- 全部知识 Tab -->
     <div v-if="activeTab === 'all'" class="section">
       <div class="knowledge-list">
-        <div v-for="item in filteredKnowledge" :key="item.id"
+        <div v-for="item in knowledgeList" :key="item.id"
              class="knowledge-card"
              @click="showKnowledgeDetail(item)">
-          <div class="knowledge-icon" :style="{ background: item.iconBg }">{{ item.icon }}</div>
+          <!-- <div class="knowledge-icon" :style="{ background: item.iconBg }">{{ item.icon }}</div> -->
           <div class="knowledge-content">
             <div class="knowledge-header">
-              <div class="knowledge-question">{{ item.question }}</div>
-              <span class="knowledge-category" :style="{ background: item.iconBg }">{{ item.categoryName }}</span>
+              <div class="knowledge-question">{{ item.name }}</div>
+              <!-- <span class="knowledge-category" :style="{ background: item.iconBg }">{{ item.categoryName }}</span> -->
             </div>
-            <div class="knowledge-answer">{{ item.answer }}</div>
+            <!-- <div class="knowledge-answer">{{ item.answer }}</div>
             <div class="knowledge-meta">
               <span><i class="fas fa-eye"></i> {{ item.views }}次查看</span>
               <span><i class="fas fa-thumbs-up"></i> {{ item.likes }}次好评</span>
               <span><i class="fas fa-clock"></i> {{ item.time }}</span>
-            </div>
+            </div> -->
           </div>
           <div class="knowledge-actions" @click.stop>
             <button class="btn btn-secondary btn-sm" @click="editKnowledge(item)">
@@ -192,7 +192,7 @@
       </div>
     </div>
 
-    <!-- Q&A Records Tab -->
+    <!-- 问答记录 Tab -->
     <div v-if="activeTab === 'qa'" class="section">
       <div class="qa-section">
         <div class="qa-header">
@@ -241,7 +241,7 @@
       </div>
     </div>
 
-    <!-- Training Tab -->
+    <!-- AI训练 Tab -->
     <div v-if="activeTab === 'training'" class="section">
       <div class="training-section">
         <div class="training-header">
@@ -283,7 +283,7 @@
       </div>
     </div>
 
-    <!-- Settings Tab -->
+    <!-- 设置 Tab -->
     <div v-if="activeTab === 'settings'" class="section">
       <div class="settings-section">
         <div class="settings-card">
@@ -441,7 +441,7 @@
             </div>
             <div class="detail-stat">
               <i class="fas fa-clock"></i>
-              <span>更新于 {{ selectedKnowledge.time }}</span>
+              <span>更新于 {{ selectedKnowledge.timeip }}</span>
             </div>
           </div>
         </div>
@@ -499,6 +499,8 @@
 
 <script setup>
 import { ref, computed } from 'vue'
+import { knowledgeDatasets,knowledgeDocuments } from '../api/knowledge'
+import { formatTimeAgo } from '@/utils/time'
 
 const showAddModal = ref(false)
 const showDetailModal = ref(false)
@@ -529,13 +531,13 @@ const syncStatus = {
   lastSync: '2天前'
 }
 
-const tabs = [
-  { key: 'categories', label: '知识分类', count: 12, icon: 'fas fa-folder' },
-  { key: 'all', label: '全部知识', count: 1247, icon: 'fas fa-book' },
-  { key: 'qa', label: '问答记录', count: 356, icon: 'fas fa-comments' },
-  { key: 'training', label: 'AI 训练', count: 3, icon: 'fas fa-dumbbell' },
+const tabs = ref([
+  { key: 'categories', label: '知识分类', count: 0, icon: 'fas fa-folder' },
+  { key: 'all', label: '全部知识', count: 0, icon: 'fas fa-book' },
+  { key: 'qa', label: '问答记录', count: 0, icon: 'fas fa-comments' },
+  { key: 'training', label: 'AI 训练', count: 0, icon: 'fas fa-dumbbell' },
   { key: 'settings', label: '设置', count: 0, icon: 'fas fa-cog' }
-]
+])
 
 const newKnowledge = ref({
   question: '',
@@ -545,25 +547,25 @@ const newKnowledge = ref({
   relatedQuestions: ''
 })
 
-const categories = ref([
-  { id: 1, icon: '📦', iconBg: 'rgba(99, 102, 241, 0.2)', name: '产品知识', description: '产品规格、型号、功能特性', count: 234, queries: 1256, accuracy: 96, trending: true, updatedAt: '2小时前' },
-  { id: 2, icon: '💰', iconBg: 'rgba(16, 185, 129, 0.2)', name: '价格政策', description: '价格体系、折扣规则、付款条件', count: 156, queries: 892, accuracy: 98, trending: false, updatedAt: '昨天' },
-  { id: 3, icon: '🚚', iconBg: 'rgba(245, 158, 11, 0.2)', name: '物流信息', description: '交期、运费、物流方式', count: 89, queries: 567, accuracy: 92, trending: false, updatedAt: '3天前' },
-  { id: 4, icon: '📋', iconBg: 'rgba(139, 92, 246, 0.2)', name: '商务条款', description: '合同条款、质保政策、售后服务', count: 178, queries: 445, accuracy: 94, trending: false, updatedAt: '昨天' },
-  { id: 5, icon: '🏭', iconBg: 'rgba(6, 182, 212, 0.2)', name: '行业知识', description: '行业趋势、技术标准、认证信息', count: 245, queries: 334, accuracy: 88, trending: true, updatedAt: '1周前' },
-  { id: 6, icon: '🌍', iconBg: 'rgba(236, 72, 153, 0.2)', name: '市场信息', description: '市场行情、竞争对手、客户案例', count: 167, queries: 289, accuracy: 85, trending: false, updatedAt: '5天前' },
-  { id: 7, icon: '🔧', iconBg: 'rgba(59, 130, 246, 0.2)', name: '技术参数', description: '技术规格、性能指标、兼容性', count: 98, queries: 456, accuracy: 97, trending: false, updatedAt: '2天前' },
-  { id: 8, icon: '❓', iconBg: 'rgba(148, 163, 184, 0.2)', name: '常见问题', description: '客户高频问题及标准回答', count: 80, queries: 1234, accuracy: 95, trending: true, updatedAt: '今天' }
-])
-
-const knowledgeList = ref([
-  { id: 1, icon: '📦', iconBg: 'rgba(99, 102, 241, 0.2)', categoryName: '产品知识', question: 'MOQ最小起订量是多少?', answer: '标准产品MOQ为50件,定制产品MOQ为100件,具体视产品类型而定。', extendedAnswer: '对于常规库存产品,我们接受最低50件起订。定制产品由于需要单独开模和生产准备,最低起订量为100件。大客户可享受更灵活的起订量政策,请联系销售经理协商。', tags: ['MOQ', '起订量', '订单'], relatedQuestions: ['可以降低起订量吗?', '首单有优惠吗?'], views: 1234, likes: 89, time: '2小时前' },
-  { id: 2, icon: '🚚', iconBg: 'rgba(245, 158, 11, 0.2)', categoryName: '物流信息', question: '交期一般是多久?', answer: '常规产品15-20天,定制产品30-45天,加急订单可缩短至7天。', extendedAnswer: '标准产品通常有库存,交期15-20个工作日。定制产品需要根据复杂程度评估,一般为30-45天。如有紧急需求,我们提供加急服务,最快可7天交货,但需收取10-20%的加急费用。', tags: ['交期', '发货', '物流'], relatedQuestions: ['可以加急吗?', '运费谁承担?'], views: 987, likes: 67, time: '昨天' },
-  { id: 3, icon: '💰', iconBg: 'rgba(16, 185, 129, 0.2)', categoryName: '价格政策', question: '付款方式有哪些?', answer: '支持T/T、L/C、PayPal等多种付款方式,新客户首单建议30%预付款。', extendedAnswer: '我们接受电汇(T/T)、信用证(L/C)、PayPal、西联汇款等付款方式。对于老客户可提供月结30天账期。新客户首单建议预付30%,发货前付清尾款。大额订单可协商分期付款。', tags: ['付款', 'T/T', '信用证'], relatedQuestions: ['可以月结吗?', '支持什么货币?'], views: 876, likes: 54, time: '2天前' },
-  { id: 4, icon: '🔧', iconBg: 'rgba(59, 130, 246, 0.2)', categoryName: '技术参数', question: 'SSD的使用寿命是多久?', answer: '企业级SSD设计寿命为5-10年,写入寿命(TBW)根据容量不同。', extendedAnswer: '我们的企业级SSD采用高耐久性NAND闪存,设计寿命5-10年。1TB型号TBW为600TB,2TB型号为1200TB。在正常工作负载下,可稳定运行超过200万小时(MTBF)。', tags: ['SSD', '寿命', 'TBW'], relatedQuestions: ['如何查看剩余寿命?', '保修期多久?'], views: 654, likes: 45, time: '3天前' },
-  { id: 5, icon: '📋', iconBg: 'rgba(139, 92, 246, 0.2)', categoryName: '商务条款', question: '质保期是多久?', answer: '标准产品提供3年质保,企业级产品提供5年质保。', extendedAnswer: '消费级产品提供3年有限质保,企业级产品提供5年质保服务。质保期内非人为损坏可免费更换。我们提供全球联保服务,无论在哪购买均可享受本地化售后服务。', tags: ['质保', '售后', '保修'], relatedQuestions: ['质保包括哪些?', '如何申请保修?'], views: 543, likes: 38, time: '4天前' },
-  { id: 6, icon: '🏭', iconBg: 'rgba(6, 182, 212, 0.2)', categoryName: '行业知识', question: 'NVMe和SATA有什么区别?', answer: 'NVMe采用PCIe通道,速度更快延迟更低,适合高性能应用。', extendedAnswer: 'NVMe SSD通过PCIe总线直接与CPU通信,理论带宽可达32GB/s,延迟极低。SATA SSD受限于SATA接口,最大速度约600MB/s。对于需要高IOPS的应用场景,NVMe是更好的选择。', tags: ['NVMe', 'SATA', '技术'], relatedQuestions: ['如何选择接口?', '兼容性如何?'], views: 432, likes: 34, time: '5天前' }
-])
+// const categories = ref([
+//   { id: 1, icon: '📦', iconBg: 'rgba(99, 102, 241, 0.2)', name: '产品知识', description: '产品规格、型号、功能特性', count: 234, queries: 1256, accuracy: 96, trending: true, updatedAt: '2小时前' },
+//   { id: 2, icon: '💰', iconBg: 'rgba(16, 185, 129, 0.2)', name: '价格政策', description: '价格体系、折扣规则、付款条件', count: 156, queries: 892, accuracy: 98, trending: false, updatedAt: '昨天' },
+//   { id: 3, icon: '🚚', iconBg: 'rgba(245, 158, 11, 0.2)', name: '物流信息', description: '交期、运费、物流方式', count: 89, queries: 567, accuracy: 92, trending: false, updatedAt: '3天前' },
+//   { id: 4, icon: '📋', iconBg: 'rgba(139, 92, 246, 0.2)', name: '商务条款', description: '合同条款、质保政策、售后服务', count: 178, queries: 445, accuracy: 94, trending: false, updatedAt: '昨天' },
+//   { id: 5, icon: '🏭', iconBg: 'rgba(6, 182, 212, 0.2)', name: '行业知识', description: '行业趋势、技术标准、认证信息', count: 245, queries: 334, accuracy: 88, trending: true, updatedAt: '1周前' },
+//   { id: 6, icon: '🌍', iconBg: 'rgba(236, 72, 153, 0.2)', name: '市场信息', description: '市场行情、竞争对手、客户案例', count: 167, queries: 289, accuracy: 85, trending: false, updatedAt: '5天前' },
+//   { id: 7, icon: '🔧', iconBg: 'rgba(59, 130, 246, 0.2)', name: '技术参数', description: '技术规格、性能指标、兼容性', count: 98, queries: 456, accuracy: 97, trending: false, updatedAt: '2天前' },
+//   { id: 8, icon: '❓', iconBg: 'rgba(148, 163, 184, 0.2)', name: '常见问题', description: '客户高频问题及标准回答', count: 80, queries: 1234, accuracy: 95, trending: true, updatedAt: '今天' }
+// ])
+
+// const knowledgeList = ref([
+//   { id: 1, icon: '📦', iconBg: 'rgba(99, 102, 241, 0.2)', categoryName: '产品知识', question: 'MOQ最小起订量是多少?', answer: '标准产品MOQ为50件,定制产品MOQ为100件,具体视产品类型而定。', extendedAnswer: '对于常规库存产品,我们接受最低50件起订。定制产品由于需要单独开模和生产准备,最低起订量为100件。大客户可享受更灵活的起订量政策,请联系销售经理协商。', tags: ['MOQ', '起订量', '订单'], relatedQuestions: ['可以降低起订量吗?', '首单有优惠吗?'], views: 1234, likes: 89, time: '2小时前' },
+//   { id: 2, icon: '🚚', iconBg: 'rgba(245, 158, 11, 0.2)', categoryName: '物流信息', question: '交期一般是多久?', answer: '常规产品15-20天,定制产品30-45天,加急订单可缩短至7天。', extendedAnswer: '标准产品通常有库存,交期15-20个工作日。定制产品需要根据复杂程度评估,一般为30-45天。如有紧急需求,我们提供加急服务,最快可7天交货,但需收取10-20%的加急费用。', tags: ['交期', '发货', '物流'], relatedQuestions: ['可以加急吗?', '运费谁承担?'], views: 987, likes: 67, time: '昨天' },
+//   { id: 3, icon: '💰', iconBg: 'rgba(16, 185, 129, 0.2)', categoryName: '价格政策', question: '付款方式有哪些?', answer: '支持T/T、L/C、PayPal等多种付款方式,新客户首单建议30%预付款。', extendedAnswer: '我们接受电汇(T/T)、信用证(L/C)、PayPal、西联汇款等付款方式。对于老客户可提供月结30天账期。新客户首单建议预付30%,发货前付清尾款。大额订单可协商分期付款。', tags: ['付款', 'T/T', '信用证'], relatedQuestions: ['可以月结吗?', '支持什么货币?'], views: 876, likes: 54, time: '2天前' },
+//   { id: 4, icon: '🔧', iconBg: 'rgba(59, 130, 246, 0.2)', categoryName: '技术参数', question: 'SSD的使用寿命是多久?', answer: '企业级SSD设计寿命为5-10年,写入寿命(TBW)根据容量不同。', extendedAnswer: '我们的企业级SSD采用高耐久性NAND闪存,设计寿命5-10年。1TB型号TBW为600TB,2TB型号为1200TB。在正常工作负载下,可稳定运行超过200万小时(MTBF)。', tags: ['SSD', '寿命', 'TBW'], relatedQuestions: ['如何查看剩余寿命?', '保修期多久?'], views: 654, likes: 45, time: '3天前' },
+//   { id: 5, icon: '📋', iconBg: 'rgba(139, 92, 246, 0.2)', categoryName: '商务条款', question: '质保期是多久?', answer: '标准产品提供3年质保,企业级产品提供5年质保。', extendedAnswer: '消费级产品提供3年有限质保,企业级产品提供5年质保服务。质保期内非人为损坏可免费更换。我们提供全球联保服务,无论在哪购买均可享受本地化售后服务。', tags: ['质保', '售后', '保修'], relatedQuestions: ['质保包括哪些?', '如何申请保修?'], views: 543, likes: 38, time: '4天前' },
+//   { id: 6, icon: '🏭', iconBg: 'rgba(6, 182, 212, 0.2)', categoryName: '行业知识', question: 'NVMe和SATA有什么区别?', answer: 'NVMe采用PCIe通道,速度更快延迟更低,适合高性能应用。', extendedAnswer: 'NVMe SSD通过PCIe总线直接与CPU通信,理论带宽可达32GB/s,延迟极低。SATA SSD受限于SATA接口,最大速度约600MB/s。对于需要高IOPS的应用场景,NVMe是更好的选择。', tags: ['NVMe', 'SATA', '技术'], relatedQuestions: ['如何选择接口?', '兼容性如何?'], views: 432, likes: 34, time: '5天前' }
+// ])
 
 const qaRecords = ref([
   { id: 1, sourceType: 'customer', sourceLabel: '客户问答', question: '你们的NVMe SSD支持哪些接口?', answer: '我们提供M.2 2280、M.2 2242和U.2三种接口规格,支持PCIe 4.0 x4总线,向下兼容PCIe 3.0。', time: '10分钟前', rating: 'positive' },
@@ -579,19 +581,53 @@ const trainingTasks = ref([
   { id: 3, name: '客户问答优化', description: '基于历史问答记录优化回复准确性', status: 'paused', progress: 35, documents: 234, learned: 82 }
 ])
 
-const filteredCategories = computed(() => {
-  if (!searchQuery.value) return categories.value
-  return categories.value.filter(c => c.name.includes(searchQuery.value) || c.description.includes(searchQuery.value))
-})
+const categories = ref([])
+// 获取知识分类
+const getKnowledgeDatasets = () => {
+    knowledgeDatasets({ pageIndex: 1, pageSize: 10 }).then(res => {
+    categories.value = res.result.data
+    res.result.data.forEach(el => {
+      el.timeip = formatTimeAgo(el.updated_at)
+    });
+    tabs.value[0].count = res.result.total
+    console.log('res :>> ', res);
+  })
+}
+getKnowledgeDatasets()
 
-const filteredKnowledge = computed(() => {
-  if (!searchQuery.value) return knowledgeList.value
-  return knowledgeList.value.filter(k => 
-    k.question.includes(searchQuery.value) || 
-    k.answer.includes(searchQuery.value) ||
-    k.tags.some(t => t.includes(searchQuery.value))
-  )
-})
+const knowledgeList = ref([])
+// 获取知识分类下的知识
+const documentsParams = ref({})
+const openCategory = (category) => {
+  activeTab.value = 'all'
+  searchQuery.value = category.name
+  showToastMessage(`正在加载 ${category.name} 分类...`, 'success')
+  documentsParams.value = {
+    pageIndex:1,
+    pageSize:10,
+    dataset_id:category.id
+  }
+  knowledgeDocuments(documentsParams.value).then(res => {
+    console.log(res.result.data)
+    knowledgeList.value = res.result.data
+  })
+}
+
+
+
+// const filteredCategories = computed(() => {
+//   if (!searchQuery.value) return categories.value
+//   return categories.value.filter(c => c.name.includes(searchQuery.value) || c.description.includes(searchQuery.value))
+// })
+
+// const filteredKnowledge = computed(() => {
+//   if (!searchQuery.value) return knowledgeList.value
+//   return knowledgeList.value.filter(k => 
+//     k.question.includes(searchQuery.value) || 
+//     k.answer.includes(searchQuery.value) ||
+//     k.tags.some(t => t.includes(searchQuery.value))
+//   )
+// })
 
 const filteredQA = computed(() => {
   let result = qaRecords.value
@@ -608,11 +644,7 @@ const formatNumber = (num) => num.toLocaleString()
 
 const getTaskStatusLabel = (status) => ({ running: '训练中', completed: '已完成', paused: '已暂停' }[status] || status)
 
-const openCategory = (category) => {
-  activeTab.value = 'all'
-  searchQuery.value = category.name
-  showToastMessage(`正在加载 ${category.name} 分类...`, 'success')
-}
+
 
 const showKnowledgeDetail = (item) => {
   selectedKnowledge.value = item

+ 2 - 3
ui/smarttrade-platform/src/views/MarketingView.vue

@@ -1309,7 +1309,7 @@ const openCreateModal = () => {
   addMarketingRef.value.open()
 }
 
-getMarketingList().then(res => {
+getMarketingList({ pageIndex: 1, pageSize: 10 }).then(res => {
   console.log('res :>> ', res);
 })
 
@@ -1485,12 +1485,11 @@ const formatNumber = (n) => n >= 1000 ? (n / 1000).toFixed(1) + 'K' : n.toLocale
 const getStatusLabel = (s) => ({ active: '进行中', paused: '已暂停', completed: '已完成' }[s] || s)
 const getChannelIcon = (c) => ({ '邮件': 'fas fa-envelope', 'WhatsApp': 'fab fa-whatsapp', 'LinkedIn': 'fab fa-linkedin' }[c] || 'fas fa-comment')
 
-const getPriorityLabel = (priority) => ({ urgent: '紧急', high: '高优先', medium: '中', low: '低优先' }[priority] || priority)
+const getPriorityLabel = (priority) => ({ high: '高', medium: '中', low: '低' }[priority] || priority)
 const getRegionLabel = (region) => ({ 'middle-east': '中东', 'europe': '欧洲', 'north-america': '北美', 'southeast-asia': '东南亚', 'africa': '非洲', 'south-america': '南美' }[region] || '全部地区')
 const getIndustryLabel = (industry) => ({ 'datacenter': '数据中心', 'consumer': '消费电子', 'distributor': '电子分销', 'retail': '零售', 'manufacturing': '制造业' }[industry] || '全部行业')
 const getChannelName = (id) => ({ email: '邮件营销', whatsapp: 'WhatsApp', linkedin: 'LinkedIn', wechat: '微信企业号' }[id] || id)
 
-const goToStep = (step) => { currentStep.value = step }
 const generateSubjectAI = () => {
   showSubjectSuggestions.value = !showSubjectSuggestions.value
   if (showSubjectSuggestions.value) toast('已生成推荐主题', 'success')