新增ai-agent项目

This commit is contained in:
启航老齐 2026-03-07 11:20:39 +08:00
parent e929a0dee8
commit e907e6003a
15 changed files with 1869 additions and 135 deletions

38
api/ai-agent/.gitignore vendored Normal file
View File

@ -0,0 +1,38 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

7
api/ai-agent/Dockerfile Normal file
View File

@ -0,0 +1,7 @@
FROM openjdk:17-jdk-slim
WORKDIR /app
COPY ./target/erp-api-2.12.0.jar erp-api.jar
CMD ["java", "-Duser.timezone=Asia/Shanghai", "-jar", "erp-api.jar"]

154
api/ai-agent/pom.xml Normal file
View File

@ -0,0 +1,154 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- <parent>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-parent</artifactId>-->
<!-- <version>3.0.2</version>-->
<!-- <relativePath/>-->
<!-- </parent>-->
<parent>
<groupId>cn.qihangerp.api</groupId>
<artifactId>api</artifactId>
<version>2.12.0</version>
</parent>
<artifactId>ai-agent</artifactId>
<packaging>jar</packaging>
<version>0.1.0</version>
<name>ai-agent</name>
<url>http://maven.apache.org</url>
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter</artifactId>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-web</artifactId>-->
<!-- <exclusions>-->
<!-- <exclusion>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-tomcat</artifactId>-->
<!-- </exclusion>-->
<!-- </exclusions>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-undertow</artifactId>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter</artifactId>-->
<!-- <version>4.0.0</version>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-loadbalancer</artifactId>-->
<!-- <version>4.0.0</version>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>4.0.0</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.kafka</groupId>-->
<!-- <artifactId>spring-kafka</artifactId>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>com.alibaba.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>-->
<!-- </dependency>-->
<!--SpringCloud Alibaba nacos 服务发现依赖-->
<dependency>
<groupId>cn.qihangerp.core</groupId>
<artifactId>security</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>cn.qihangerp.module</groupId>-->
<!-- <artifactId>goods</artifactId>-->
<!-- <version>1.0</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>cn.qihangerp.module</groupId>-->
<!-- <artifactId>order</artifactId>-->
<!-- <version>1.0</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>cn.qihangerp.service</groupId>-->
<!-- <artifactId>stock</artifactId>-->
<!-- <version>2.0.0</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>cn.qihangerp.services</groupId>-->
<!-- <artifactId>goods-api</artifactId>-->
<!-- <version>1.0.6</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>cn.qihangerp.service</groupId>-->
<!-- <artifactId>order</artifactId>-->
<!-- <version>2.0.0</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>cn.qihangerp.services</groupId>-->
<!-- <artifactId>order-api</artifactId>-->
<!-- <version>1.0.6</version>-->
<!-- </dependency>-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.5</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.5</version>
</dependency>
<dependency>
<groupId>cn.qihangerp.service</groupId>
<artifactId>service</artifactId>
<version>2.0.0</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.projectlombok</groupId>-->
<!-- <artifactId>lombok</artifactId>-->
<!-- <version>1.18.30</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,31 @@
package cn.qihangerp.erp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.client.RestTemplate;
//@EnableDiscoveryClient
//@MapperScan("cn.qihangerp.oms.mapper")
@EnableFeignClients(basePackages = "cn.qihangerp.erp")
@EnableDiscoveryClient
@ComponentScan(basePackages={"cn.qihangerp"})
@SpringBootApplication
public class AiAgent {
public static void main( String[] args )
{
System.out.println( "Hello ai-agent!" );
SpringApplication.run(AiAgent.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

View File

@ -0,0 +1,21 @@
package cn.qihangerp.erp.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration
@MapperScan({"cn.qihangerp.mapper","cn.qihangerp.module.mapper","cn.qihangerp.mapper"})
public class MybatisPlusConfig {
@Primary
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); //注意使用哪种数据库
return interceptor;
}
}

View File

@ -0,0 +1,39 @@
package cn.qihangerp.erp.controller;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@AllArgsConstructor
@RestController
public class HomeController {
// @Resource
// private EchoService echoService;
@Autowired
private RestTemplate restTemplate;
@Autowired
private RedisTemplate<String,String> redisTemplate;
@GetMapping("/")
public String home(){
return "{'code':0,'msg':'oms-api请通过api访问'}";
}
@GetMapping(value = "/echo-rest")
public String rest() {
return restTemplate.getForObject("http://tao-oms/test/na", String.class);
}
// @GetMapping(value = "/echo-feign")
// public String feign() {
// String token = "Bearer eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjdkOTBmN2EzLWUwNWQtNDkxNy04NjIzLTU1OGRhNGY3NjE3NiJ9._Oukm9b0P1WvcOywLdhs6_BOt_6mRSF41Q6f4fBm_DGUkPR86Qg1tqyRTM5ouTR2Xz46IRuRAVez8Wcl3NIlwg";
//
// return echoService.echo(token);
// }
}

View File

@ -0,0 +1,174 @@
package cn.qihangerp.erp.controller;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Slf4j
@RestController
@RequestMapping("/sse")
public class SseController {
private static final Map<String, SseEmitter> emitters = new ConcurrentHashMap<>();
private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
@GetMapping(value = "/connect", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter connect(@RequestParam String clientId) {
SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
emitters.put(clientId, emitter);
// 设置超时处理
emitter.onTimeout(() -> emitters.remove(clientId));
emitter.onCompletion(() -> emitters.remove(clientId));
// 发送连接成功消息
try {
emitter.send(SseEmitter.event()
.name("connected")
.data("连接成功"));
} catch (IOException e) {
emitters.remove(clientId);
}
// 定期发送心跳
executorService.scheduleAtFixedRate(() -> {
try {
if (emitters.containsKey(clientId)) {
emitters.get(clientId).send(SseEmitter.event()
.name("heartbeat")
.data("ping"));
}
} catch (IOException e) {
emitters.remove(clientId);
}
}, 30, 30, TimeUnit.SECONDS);
return emitter;
}
@GetMapping("/send")
public String sendMessage(@RequestParam String clientId, @RequestParam String message) {
log.info("=============来新消息了!");
SseEmitter emitter = emitters.get(clientId);
if (emitter != null) {
try {
// 调用opencode接口获取回复
String response = callOpencodeApi(message);
emitter.send(SseEmitter.event()
.name("message")
.data(response));
return "消息发送成功";
} catch (Exception e) {
emitters.remove(clientId);
return "消息发送失败";
}
}
return "客户端不存在";
}
private String callOpencodeApi(String message) throws Exception {
// 创建HTTP客户端
HttpClient client = HttpClient.newHttpClient();
// 1. 创建新会话
HttpRequest createSessionRequest = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:14967/session"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{}"))
.build();
HttpResponse<String> createSessionResponse = client.send(createSessionRequest, HttpResponse.BodyHandlers.ofString());
String sessionId = parseSessionId(createSessionResponse.body());
// 2. 构建消息请求体
JSONObject requestBody = new JSONObject();
JSONArray parts = new JSONArray();
JSONObject part = new JSONObject();
part.put("type", "text");
part.put("text", message);
parts.add(part);
requestBody.put("parts", parts);
// 3. 向会话发送消息
HttpRequest sendMessageRequest = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:14967/session/" + sessionId + "/message"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(requestBody.toJSONString()))
.build();
// 发送请求并获取响应
HttpResponse<String> response = client.send(sendMessageRequest, HttpResponse.BodyHandlers.ofString());
// 解析响应提取AI回复
return parseAIResponse(response.body());
}
private String parseSessionId(String responseBody) {
// 简单解析JSON提取sessionId
// 实际项目中建议使用JSON库
int idIndex = responseBody.indexOf("\"id\":\"");
if (idIndex != -1) {
int start = idIndex + 6;
int end = responseBody.indexOf("\"", start);
if (end != -1) {
return responseBody.substring(start, end);
}
}
return "";
}
private String parseAIResponse(String responseBody) {
log.info("=================AI回复==========");
log.info(responseBody);
try {
// 解析响应提取AI回复
JSONObject jsonObject = JSONObject.parseObject(responseBody);
if (jsonObject.containsKey("info")) {
JSONArray parts = jsonObject.getJSONArray("parts");
for (int i = 0; i < parts.size(); i++) {
JSONObject part = parts.getJSONObject(i);
if (part.containsKey("text")) {
return part.getString("text");
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return "无法获取AI回复";
}
@GetMapping("/disconnect")
public String disconnect(@RequestParam String clientId) {
SseEmitter emitter = emitters.remove(clientId);
if (emitter != null) {
emitter.complete();
return "断开连接成功";
}
return "客户端不存在";
}
@GetMapping("/status")
public String getStatus() {
return "当前连接数: " + emitters.size();
}
}

View File

@ -0,0 +1,12 @@
//package cn.qihangerp.oms.feign;
//
//import org.springframework.cloud.openfeign.FeignClient;
//import org.springframework.web.bind.annotation.GetMapping;
//import org.springframework.web.bind.annotation.RequestHeader;
//
//
//@FeignClient(name = "open-api")
//public interface EchoService {
// @GetMapping(value = "/test/na")
// String echo(@RequestHeader(name = "Authorization",required = true) String Token);
//}

View File

@ -0,0 +1,66 @@
package cn.qihangerp.erp.feign;
import com.alibaba.fastjson2.JSONObject;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "oms-api")
public interface OpenApiService {
@GetMapping(value = "/dou/order/get_detail")
JSONObject getDouOrderDetail(@RequestParam String orderId);
@GetMapping(value = "/dou/refund/get_detail")
JSONObject getDouRefundDetail(@RequestParam String id);
/**
* 抖店发货
* @param Token
* @return
*/
// @GetMapping(value = "/dou/ship/order_ship")
// JSONObject shipDouOrder(@RequestHeader(name = "Authorization",required = true) String Token, @RequestBody DouOrderShipBo bo);
// @GetMapping(value = "/dou/ship/order_ship_multi_pack")
// JSONObject shipDouOrderMultiPack(@RequestHeader(name = "Authorization",required = true) String Token, @RequestBody DouOrderShipMultiPackBo bo);
@GetMapping(value = "/jd/order/get_detail")
JSONObject getJdOrderDetail(@RequestParam Long orderId,@RequestParam Integer vc);
@GetMapping(value = "/jd/refund/get_detail")
JSONObject getJdRefundDetail(@RequestParam Long refundId,@RequestParam Integer vc);
@GetMapping(value = "/pdd/order/get_detail")
JSONObject getPddOrderDetail(@RequestParam String sn);
@GetMapping(value = "/pdd/refund/get_detail")
JSONObject getPddRefundDetail(@RequestParam Long id);
/**
* 淘宝发货
* @param Token
* @return
*/
// @GetMapping(value = "/tao/ship/order_ship")
// JSONObject shipTaoOrder(@RequestHeader(name = "Authorization",required = true) String Token, @RequestBody TaoOrderShipBo bo);
@GetMapping(value = "/tao/order/get_detail")
JSONObject getTaoOrderDetail(@RequestParam String tid);
@GetMapping(value = "/tao/refund/get_detail")
JSONObject getTaoRefundDetail(@RequestParam Long refundId);
@GetMapping(value = "/wei/order/get_detail")
JSONObject getWeiOrderDetail(@RequestParam String orderId);
@GetMapping(value = "/wei/refund/get_detail")
JSONObject getWeiRefundDetail(@RequestParam String afterSaleOrderId);
/**
* 微信小店发货
* @param Token
* @return
*/
// @GetMapping(value = "/wei/ship/order_ship")
// JSONObject shipWeiOrder(@RequestHeader(name = "Authorization",required = true) String Token, @RequestBody WeiOrderShipBo bo);
}

View File

@ -0,0 +1,365 @@
//package cn.qihangerp.erp.serviceImpl;
//
//import com.fasterxml.jackson.databind.ObjectMapper;
//import jakarta.annotation.PostConstruct;
//import okhttp3.*;
//import org.slf4j.Logger;
//import org.slf4j.LoggerFactory;
//import org.springframework.beans.factory.annotation.Value;
//import org.springframework.stereotype.Service;
//import java.io.IOException;
//import java.util.*;
//import java.util.concurrent.TimeUnit;
//
//@Service
//public class DeepSeekService {
//
// private static final Logger log = LoggerFactory.getLogger(DeepSeekService.class);
// private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
//
// @Value("${deepseek.api.key}")
// private String apiKey;
//
// @Value("${deepseek.api.endpoint:https://api.deepseek.com/v1/chat/completions}")
// private String apiEndpoint;
//
// @Value("${deepseek.api.model:deepseek-chat}")
// private String model;
//
// private OkHttpClient okHttpClient;
// private final ObjectMapper objectMapper;
//
// // 缓存最近一次成功的分析结果
// private Map<String, Object> cachedAnalysis = new HashMap<>();
//
// public DeepSeekService(ObjectMapper objectMapper) {
// this.objectMapper = objectMapper;
// }
//
// @PostConstruct
// public void init() {
// // 配置具有重试和连接池功能的OkHttpClient
// this.okHttpClient = new OkHttpClient.Builder()
// .connectTimeout(15, TimeUnit.SECONDS) // 连接超时
// .readTimeout(30, TimeUnit.SECONDS) // 读取超时
// .writeTimeout(15, TimeUnit.SECONDS) // 写入超时
// .connectionPool(new ConnectionPool(5, 5, TimeUnit.MINUTES)) // 连接池
// .addInterceptor(new RetryInterceptor(3)) // 自定义重试拦截器
// .addInterceptor(new LoggingInterceptor()) // 日志拦截器
// .build();
// }
//
// /**
// * 调用DeepSeek API - 带有Spring Retry重试机制
// */
//// @Retryable(
//// value = {IOException.class, RuntimeException.class},
//// maxAttempts = 3,
//// backoff = @Backoff(delay = 1000, multiplier = 2, maxDelay = 10000)
//// )
// public Map<String, Object> analyzeData(Map<String, Object> formattedData, String analysisType) {
// String cacheKey = generateCacheKey(formattedData, analysisType);
//
// try {
// // 1. 构建请求体
// String requestBody = buildRequestBody(formattedData, analysisType);
// RequestBody body = RequestBody.create(requestBody, JSON);
//
// // 2. 构建请求
// Request request = new Request.Builder()
// .url(apiEndpoint)
// .header("Authorization", "Bearer " + apiKey)
// .header("Content-Type", "application/json")
// .post(body)
// .build();
//
// // 3. 执行请求并处理响应
// try (Response response = okHttpClient.newCall(request).execute()) {
// if (!response.isSuccessful()) {
// handleErrorResponse(response, cacheKey);
// }
//
// String responseBody = response.body().string();
// Map<String, Object> result = parseResponse(responseBody, analysisType);
//
// // 缓存成功的结果
// cacheSuccessfulResult(cacheKey, result);
// return result;
// }
//
// } catch (Exception e) {
// log.error("调用DeepSeek API失败尝试使用缓存或降级方案", e);
// return getFallbackAnalysis(cacheKey, analysisType);
// }
// }
//
// /**
// * 为补货建议优化的专用方法
// */
// public Map<String, Object> generateReplenishmentSuggestions(Map<String, Object> inventoryData) {
// try {
// String prompt = buildReplenishmentPrompt(inventoryData);
//
// Map<String, Object> requestBody = Map.of(
// "model", model,
// "messages", List.of(
// Map.of("role", "system", "content",
// "你是一个经验丰富的电商库存管理专家,擅长制定补货策略。"),
// Map.of("role", "user", "content", prompt)
// ),
// "temperature", 0.2,
// "max_tokens", 1500,
// "response_format", Map.of("type", "json_object")
// );
//
// String jsonBody = objectMapper.writeValueAsString(requestBody);
//
// Request request = new Request.Builder()
// .url(apiEndpoint)
// .header("Authorization", "Bearer " + apiKey)
// .post(RequestBody.create(jsonBody, JSON))
// .build();
//
// // 设置更短的超时时间因为补货建议需要快速响应
// OkHttpClient quickClient = okHttpClient.newBuilder()
// .readTimeout(15, TimeUnit.SECONDS)
// .build();
//
// try (Response response = quickClient.newCall(request).execute()) {
// if (response.isSuccessful()) {
// String responseBody = response.body().string();
// return parseReplenishmentResponse(responseBody);
// } else {
// // 如果API失败使用本地算法生成补货建议
// return generateLocalReplenishmentSuggestions(inventoryData);
// }
// }
//
// } catch (Exception e) {
// log.warn("AI补货建议失败使用本地算法", e);
// return generateLocalReplenishmentSuggestions(inventoryData);
// }
// }
//
// /**
// * 本地补货算法 - 服务降级方案
// */
// private Map<String, Object> generateLocalReplenishmentSuggestions(Map<String, Object> inventoryData) {
// List<Map<String, Object>> products = (List<Map<String, Object>>) inventoryData.get("data");
// List<Map<String, Object>> suggestions = new ArrayList<>();
// int totalQuantity = 0;
// double estimatedCost = 0.0;
//
// for (Map<String, Object> product : products) {
// String status = (String) product.get("inventoryStatus");
//
// // 只处理需要补货的产品
// if ("HEALTHY".equals(status) || "OVERSTOCK".equals(status)) {
// continue;
// }
//
// int currentStock = (int) product.getOrDefault("currentStock", 0);
// int safetyStock = (int) product.getOrDefault("safetyStock", 100);
// double avgDailySales = ((Integer) product.getOrDefault("avgDailySales", 10)).doubleValue();
// double coverDays = (double) product.getOrDefault("coverDays", 0.0);
//
// Map<String, Object> suggestion = new HashMap<>();
// suggestion.put("product_id", product.get("productId"));
// suggestion.put("product_name", product.get("productName"));
//
// // 本地补货逻辑
// int suggestedQty;
// String urgency;
//
// if (currentStock <= 0) {
// suggestedQty = (int) (avgDailySales * 30);
// urgency = "紧急";
// } else if (coverDays < 3) {
// suggestedQty = (int) (avgDailySales * 15 - currentStock);
// urgency = "";
// } else if (currentStock < safetyStock) {
// suggestedQty = safetyStock * 2 - currentStock;
// urgency = "";
// } else {
// suggestedQty = (int) (avgDailySales * 7);
// urgency = "";
// }
//
// suggestedQty = Math.max(suggestedQty, 10);
// totalQuantity += suggestedQty;
//
// suggestion.put("suggested_quantity", suggestedQty);
// suggestion.put("urgency", urgency);
// suggestion.put("reason", "本地算法计算");
// suggestion.put("expected_cover_days", suggestedQty / Math.max(avgDailySales, 1));
//
// suggestions.add(suggestion);
// }
//
// return Map.of(
// "success", true,
// "source", "local_algorithm",
// "replenishment_list", suggestions,
// "total_replenishment_quantity", totalQuantity,
// "analysis_summary", "基于本地规则生成的补货建议",
// "recommendations", List.of(
// "建议优先处理标记为'紧急'的商品",
// "此为本地降级方案AI分析恢复后将提供更精确建议"
// )
// );
// }
//
// /**
// * 自定义重试拦截器
// */
// private static class RetryInterceptor implements Interceptor {
// private final int maxRetries;
//
// public RetryInterceptor(int maxRetries) {
// this.maxRetries = maxRetries;
// }
//
// @Override
// public Response intercept(Chain chain) throws IOException {
// Request request = chain.request();
// Response response = null;
// IOException exception = null;
//
// // 重试逻辑
// for (int retryCount = 0; retryCount <= maxRetries; retryCount++) {
// try {
// response = chain.proceed(request);
//
// // 只有服务器错误(5xx)或特定客户端错误才重试
// if (response.isSuccessful() ||
// (response.code() != 503 && response.code() != 429 && response.code() != 408)) {
// return response;
// }
//
// log.warn("API请求失败状态码: {}, 重试: {}/{}",
// response.code(), retryCount, maxRetries);
//
// // 关闭响应体
// response.close();
//
// } catch (IOException e) {
// exception = e;
// log.warn("网络异常,重试: {}/{}", retryCount, maxRetries, e);
// }
//
// // 如果不是最后一次重试等待一段时间
// if (retryCount < maxRetries) {
// try {
// // 指数退避1s, 2s, 4s...
// long waitTime = (long) Math.pow(2, retryCount) * 1000;
// Thread.sleep(waitTime);
// } catch (InterruptedException e) {
// Thread.currentThread().interrupt();
// throw new IOException("重试被中断", e);
// }
// }
// }
//
// if (exception != null) {
// throw exception;
// }
//
// return response;
// }
// }
//
// /**
// * 日志拦截器
// */
// private static class LoggingInterceptor implements Interceptor {
// @Override
// public Response intercept(Chain chain) throws IOException {
// Request request = chain.request();
// long startTime = System.currentTimeMillis();
//
// log.debug("发送请求: {} {}", request.method(), request.url());
//
// Response response;
// try {
// response = chain.proceed(request);
// } catch (IOException e) {
// long duration = System.currentTimeMillis() - startTime;
// log.error("请求失败: {} {} - 耗时: {}ms",
// request.method(), request.url(), duration, e);
// throw e;
// }
//
// long duration = System.currentTimeMillis() - startTime;
// log.info("收到响应: {} {} - 状态: {} - 耗时: {}ms",
// request.method(), request.url(), response.code(), duration);
//
// return response;
// }
// }
//
// /**
// * 错误处理
// */
// private void handleErrorResponse(Response response, String cacheKey) throws IOException {
// int code = response.code();
// String errorBody = response.body() != null ? response.body().string() : "无错误详情";
//
// log.error("DeepSeek API错误响应: 状态码={}, 错误信息={}", code, errorBody);
//
// // 根据错误类型处理
// if (code == 401) {
// throw new RuntimeException("API密钥无效或已过期");
// } else if (code == 429) {
// throw new RuntimeException("请求频率超限,请稍后重试");
// } else if (code == 503) {
// // 服务不可用尝试使用缓存
// if (cachedAnalysis.containsKey(cacheKey)) {
// log.info("服务不可用,使用缓存结果");
// throw new ServiceUnavailableException("服务不可用,已返回缓存结果");
// }
// throw new RuntimeException("DeepSeek服务暂时不可用请稍后重试");
// } else {
// throw new RuntimeException(String.format("API请求失败: %d - %s", code, errorBody));
// }
// }
//
// /**
// * 服务降级获取缓存或基础分析
// */
// private Map<String, Object> getFallbackAnalysis(String cacheKey, String analysisType) {
// // 1. 首先尝试缓存
// if (cachedAnalysis.containsKey(cacheKey)) {
// log.info("使用缓存的AI分析结果");
// Map<String, Object> cached = (Map<String, Object>) cachedAnalysis.get(cacheKey);
// cached.put("source", "cached");
// return cached;
// }
//
// // 2. 返回基础分析模板
// log.info("返回基础分析模板");
// return Map.of(
// "success", false,
// "source", "fallback_template",
// "message", "AI分析服务暂时不可用",
// "basic_analysis", Map.of(
// "suggestion", "建议检查库存水平,重点关注缺货商品",
// "generated_at", new Date()
// ),
// "recommendations", List.of(
// "1. 优先处理库存为0的商品",
// "2. 检查日销量高但库存低的商品",
// "3. AI服务恢复后重新获取详细分析"
// )
// );
// }
//
// // 其他辅助方法buildRequestBody, parseResponse等保持原有逻辑
// // ...
//}
//
//// 自定义异常类
//class ServiceUnavailableException extends RuntimeException {
// public ServiceUnavailableException(String message) {
// super(message);
// }
//}

View File

@ -0,0 +1,273 @@
package cn.qihangerp.erp.serviceImpl;
import com.fasterxml.jackson.annotation.JsonProperty;
import okhttp3.*;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
public class InventorySalesAnalyzer {
// 配置你的 DeepSeek API 信息
private static final String API_KEY = "sk-e1f3aecc45e44eca9451d5a659a4bc91";
private static final String API_URL = "https://api.deepseek.com/v1/chat/completions";
private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
private static final OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
.readTimeout(60, java.util.concurrent.TimeUnit.SECONDS)
.build();
private static final ObjectMapper objectMapper = new ObjectMapper();
// 你的数据
private static final String INVENTORY_JSON = "[{\"id\":1,\"goods_title\":\"雷士照明led吸顶灯灯芯替换圆形灯板节能灯芯冷光高显护眼健康\",\"sku_name\":\"白光12W\",\"stock_num\":12},{\"id\":2,\"goods_title\":\"雷士照明led吸顶灯灯芯替换圆形灯板节能灯芯冷光高显护眼健康\",\"sku_name\":\"白光18W\",\"stock_num\":12},{\"id\":3,\"goods_title\":\"雷士照明led吸顶灯灯芯替换圆形灯板节能灯芯冷光高显护眼健康\",\"sku_name\":\"白光24W\",\"stock_num\":12},{\"id\":4,\"goods_title\":\"雷士照明led吸顶灯灯芯替换圆形灯板节能灯芯冷光高显护眼健康\",\"sku_name\":\"双色36W\",\"stock_num\":12}]";
private static final String SALES_JSON = "[{\"order_num\":\"1\",\"sku_id\":1,\"count\":1,\"item_amount\":29.32,\"order_time\":\"2025-05-24 23:19:51\"},{\"order_num\":\"1\",\"sku_id\":3,\"count\":1,\"item_amount\":29.32,\"order_time\":\"2025-05-24 23:19:51\"},{\"order_num\":\"1\",\"sku_id\":1,\"count\":1,\"item_amount\":29.32,\"order_time\":\"2025-05-24 23:19:51\"},{\"order_num\":\"1\",\"sku_id\":2,\"count\":1,\"item_amount\":29.32,\"order_time\":\"2025-05-24 23:19:51\"},{\"order_num\":\"1\",\"sku_id\":4,\"count\":1,\"item_amount\":29.32,\"order_time\":\"2025-05-24 23:19:51\"},{\"order_num\":\"1\",\"sku_id\":1,\"count\":1,\"item_amount\":29.32,\"order_time\":\"2025-05-24 23:19:51\"},{\"order_num\":\"1\",\"sku_id\":1,\"count\":1,\"item_amount\":29.32,\"order_time\":\"2025-05-24 23:19:51\"},{\"order_num\":\"1\",\"sku_id\":3,\"count\":1,\"item_amount\":29.32,\"order_time\":\"2025-05-24 23:19:51\"},{\"order_num\":\"1\",\"sku_id\":1,\"count\":1,\"item_amount\":29.32,\"order_time\":\"2025-05-24 23:19:51\"},{\"order_num\":\"1\",\"sku_id\":1,\"count\":1,\"item_amount\":29.32,\"order_time\":\"2025-05-24 23:19:51\"},{\"order_num\":\"1\",\"sku_id\":2,\"count\":1,\"item_amount\":29.32,\"order_time\":\"2025-05-24 23:19:51\"}]";
public static void main(String[] args) {
try {
System.out.println("开始分析库存与销售数据...\n");
// 1. 解析数据
List<InventoryItem> inventoryList = parseInventoryData();
List<SalesOrder> salesList = parseSalesData();
// 2. 分析数据并生成报告
String analysisResult = analyzeInventoryAndSales(inventoryList, salesList);
System.out.println("=== AI 分析报告 ===\n");
System.out.println(analysisResult);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 核心分析方法
*/
public static String analyzeInventoryAndSales(List<InventoryItem> inventory,
List<SalesOrder> sales) throws IOException {
// 1. 数据预处理 SKU ID 关联库存和销售数据
Map<Integer, SkuAnalysis> analysisMap = new HashMap<>();
// 初始化库存数据
for (InventoryItem item : inventory) {
SkuAnalysis analysis = new SkuAnalysis();
analysis.id = item.id;
analysis.goodsTitle = item.goodsTitle;
analysis.skuName = item.skuName;
analysis.stockNum = item.stockNum;
analysisMap.put(item.id, analysis);
}
// 统计销售数据
for (SalesOrder order : sales) {
if (analysisMap.containsKey(order.skuId)) {
SkuAnalysis analysis = analysisMap.get(order.skuId);
analysis.totalSales += order.count;
analysis.totalRevenue += order.itemAmount;
analysis.orderCount++;
// 记录销售时间用于趋势分析
analysis.salesTimes.add(order.orderTime);
}
}
// 2. 计算关键指标
for (SkuAnalysis analysis : analysisMap.values()) {
// 计算日均销量假设数据是最近30天的
analysis.dailyAvgSales = analysis.totalSales / 30.0;
// 计算可售天数
if (analysis.dailyAvgSales > 0) {
analysis.daysOfSupply = analysis.stockNum / analysis.dailyAvgSales;
} else {
analysis.daysOfSupply = 999; // 无销售
}
// 判断库存状态
analysis.stockStatus = determineStockStatus(analysis.stockNum, analysis.dailyAvgSales);
}
// 3. 构建 AI 分析提示词
String prompt = buildAnalysisPrompt(analysisMap);
// 4. 调用 DeepSeek API
return callDeepSeekAPI(prompt);
}
/**
* 构建 AI 分析提示词
*/
private static String buildAnalysisPrompt(Map<Integer, SkuAnalysis> analysisMap) {
StringBuilder prompt = new StringBuilder();
prompt.append("你是一名专业的电商库存管理专家。请分析以下 LED 灯具产品的库存与销售数据,并提供专业的分析报告和建议:\n\n");
prompt.append("=== 数据概览 ===\n");
prompt.append("产品名称:雷士照明 LED 吸顶灯灯芯\n");
prompt.append("分析时间:").append(new Date()).append("\n\n");
prompt.append("=== 详细数据 ===\n");
prompt.append(String.format("%-8s %-12s %-8s %-8s %-12s %-10s %-15s\n",
"SKU ID", "规格", "库存量", "总销量", "总销售额", "可售天数", "库存状态"));
prompt.append("-".repeat(80)).append("\n");
for (SkuAnalysis analysis : analysisMap.values()) {
prompt.append(String.format("%-8d %-12s %-8d %-8d %-12.2f %-10.1f %-15s\n",
analysis.id,
analysis.skuName,
analysis.stockNum,
analysis.totalSales,
analysis.totalRevenue,
analysis.daysOfSupply,
analysis.stockStatus
));
}
prompt.append("\n=== 分析要求 ===\n");
prompt.append("请基于以上数据,提供以下分析:\n");
prompt.append("1. **库存健康度分析**评估每个SKU的库存状况识别缺货风险\n");
prompt.append("2. **销售表现分析**:分析各规格产品的销售情况,找出畅销款和滞销款\n");
prompt.append("3. **补货建议**\n");
prompt.append(" - 哪些SKU需要立即补货建议补货数量\n");
prompt.append(" - 哪些SKU库存过多建议如何清理\n");
prompt.append(" - 建议的安全库存水平\n");
prompt.append("4. **运营建议**:基于销售模式,给出采购、促销或产品组合建议\n\n");
prompt.append("请以专业报告格式回复,包含具体数据和理由。");
return prompt.toString();
}
/**
* 调用 DeepSeek API
*/
private static String callDeepSeekAPI(String prompt) throws IOException {
// 构建请求体
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("model", "deepseek-chat");
requestBody.put("messages", Arrays.asList(
Map.of("role", "user", "content", prompt)
));
requestBody.put("temperature", 0.3); // 降低随机性使分析更稳定
requestBody.put("max_tokens", 2000);
String jsonBody = objectMapper.writeValueAsString(requestBody);
// 创建请求
Request request = new Request.Builder()
.url(API_URL)
.header("Authorization", "Bearer " + API_KEY)
.header("Content-Type", "application/json")
.post(RequestBody.create(jsonBody, JSON))
.build();
// 发送请求带重试机制
for (int attempt = 0; attempt < 3; attempt++) {
try (Response response = client.newCall(request).execute()) {
if (response.isSuccessful()) {
String responseBody = response.body().string();
return extractContentFromResponse(responseBody);
} else if (response.code() == 429 || response.code() >= 500) {
// 频率限制或服务器错误等待后重试
System.out.println("请求失败,状态码: " + response.code() + ",等待重试...");
Thread.sleep(2000 * (attempt + 1));
continue;
} else {
throw new IOException("API请求失败: " + response.code() + " - " + response.message());
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IOException("请求被中断", e);
}
}
throw new IOException("API请求失败已重试3次");
}
/**
* API 响应中提取内容
*/
private static String extractContentFromResponse(String responseBody) throws IOException {
Map<String, Object> responseMap = objectMapper.readValue(responseBody,
new TypeReference<Map<String, Object>>() {});
List<Map<String, Object>> choices = (List<Map<String, Object>>) responseMap.get("choices");
if (choices != null && !choices.isEmpty()) {
Map<String, Object> choice = choices.get(0);
Map<String, Object> message = (Map<String, Object>) choice.get("message");
return (String) message.get("content");
}
return "未获取到有效回复";
}
/**
* 判断库存状态
*/
private static String determineStockStatus(int stock, double dailySales) {
if (stock == 0) return "缺货";
if (dailySales == 0) return "滞销";
double daysOfSupply = stock / dailySales;
if (daysOfSupply < 7) return "急需补货";
if (daysOfSupply < 14) return "需要补货";
if (daysOfSupply < 30) return "库存正常";
if (daysOfSupply < 60) return "库存偏高";
return "库存积压";
}
// 数据解析方法
private static List<InventoryItem> parseInventoryData() throws IOException {
return objectMapper.readValue(INVENTORY_JSON,
new TypeReference<List<InventoryItem>>() {});
}
private static List<SalesOrder> parseSalesData() throws IOException {
return objectMapper.readValue(SALES_JSON,
new TypeReference<List<SalesOrder>>() {});
}
// 数据类定义
static class InventoryItem {
public int id;
@JsonProperty("goods_title")
public String goodsTitle;
@JsonProperty("sku_name")
public String skuName;
@JsonProperty("stock_num")
public int stockNum;
}
static class SalesOrder {
@JsonProperty("order_num")
public String orderNum;
@JsonProperty("sku_id")
public int skuId;
public int count;
@JsonProperty("item_amount")
public double itemAmount;
@JsonProperty("order_time")
public String orderTime;
}
static class SkuAnalysis {
public int id;
public String goodsTitle;
public String skuName;
public int stockNum;
public int totalSales = 0;
public double totalRevenue = 0.0;
public int orderCount = 0;
public double dailyAvgSales = 0.0;
public double daysOfSupply = 0.0;
public String stockStatus = "未知";
public List<String> salesTimes = new ArrayList<>();
}
}

View File

@ -0,0 +1,101 @@
server:
port: 8084
spring:
application:
name: ai-agent
cloud:
loadbalancer:
nacos:
enabled: true
nacos:
# serverAddr: 127.0.0.1:8848
discovery:
server-addr: 127.0.0.1:8848
# username: nacos
# password: nacos
data:
# redis 配置
redis:
# 地址
# host: 8.130.98.215
host: 127.0.0.1
# 端口默认为6379
port: 6379
# 数据库索引
database: 0
# 密码
# password: 123321
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
datasource:
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/qihang-erp?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: Andy_123
hikari:
maximum-pool-size: 10
min-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
# kafka:
# bootstrap-servers: localhost:9092
# producer:
# batch-size: 16384 #批量大小
# acks: -1 #应答级别:多少个分区副本备份完成时向生产者发送ack确认(可选0、1、all/-1)
# retries: 10 # 消息发送重试次数
# # transaction-id-prefix: tx_1 #事务id前缀
# buffer-memory: 33554432
# key-serializer: org.apache.kafka.common.serialization.StringSerializer
# value-serializer: org.apache.kafka.common.serialization.StringSerializer
# properties:
# linger:
# ms: 2000 #提交延迟
# # partitioner: #指定分区器
# # class: com.example.kafkademo.config.CustomizePartitioner
# consumer:
# group-id: testGroup #默认的消费组ID
# enable-auto-commit: true #是否自动提交offset
# auto-commit-interval: 2000 #提交offset延时
# # 当kafka中没有初始offset或offset超出范围时将自动重置offset
# # earliest:重置为分区中最小的offset;
# # latest:重置为分区中最新的offset(消费分区中新产生的数据);
# # none:只要有一个分区不存在已提交的offset,就抛出异常;
# auto-offset-reset: latest
# max-poll-records: 500 #单次拉取消息的最大条数
# key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
# value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
# properties:
# session:
# timeout:
# ms: 120000 # 消费会话超时时间(超过这个时间 consumer 没有发送心跳,就会触发 rebalance 操作)
# request:
# timeout:
# ms: 18000 # 消费请求的超时时间
mybatis-plus:
mapper-locations: classpath*:mapper/**/*Mapper.xml
type-aliases-package: cn.qihangerp.oms.domain;cn.qihangerp.module.domain;cn.qihangerp.security.entity;
# configuration:
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开启sql日志
deepseek:
api:
key:
endpoint: https://api.deepseek.com/chat/completions
model: deepseek-chat

View File

@ -37,10 +37,10 @@ spring:
filters:
- StripPrefix=2
- id: open_api_route
uri: lb://open-api
- id: ai_agent_route
uri: lb://ai-agent
predicates:
- Path=/api/open-api/**
- Path=/api/ai-agent/**
filters:
- StripPrefix=2

View File

@ -21,6 +21,7 @@
<url>http://maven.apache.org</url>
<modules>
<module>gateway</module>
<module>ai-agent</module>
<module>erp-api</module>
<module>sys-api</module>
<module>oms-api</module>

View File

@ -1,164 +1,616 @@
<template>
<div class="dashboard-editor-container">
<panel-group :chart-data="report" @handleSetLineChartData="handleSetLineChartData" />
<el-row style="background:#fff;padding:16px 16px 0;margin-bottom:32px;">
<line-chart :chart-data="lineChartData" />
</el-row>
<!-- <el-row>-->
<!-- <el-table-->
<!-- :data="tableData"-->
<!-- style="width: 100%">-->
<!-- <el-table-column-->
<!-- prop="date"-->
<!-- label="日期"-->
<!-- width="180">-->
<!-- </el-table-column>-->
<!-- <el-table-column-->
<!-- prop="name"-->
<!-- label="姓名"-->
<!-- width="180">-->
<!-- </el-table-column>-->
<!-- <el-table-column-->
<!-- prop="address"-->
<!-- label="地址">-->
<!-- </el-table-column>-->
<!-- </el-table>-->
<!-- </el-row>-->
<el-row :gutter="32">
<!-- <el-col :xs="24" :sm="24" :lg="8">-->
<!-- <div class="chart-wrapper">-->
<!-- <raddar-chart />-->
<!-- </div>-->
<!-- </el-col>-->
<el-col :xs="24" :sm="24" :lg="24">
<div class="chart-wrapper">
<pie-chart :chart-data="skuTopData" />
<div class="chat-home-container">
<!-- 左侧聊天区域 -->
<div class="chat-container">
<div class="chat-header">
<div class="header-left">
<i class="el-icon-chat-dot-round"></i>
<span class="title">工作助手</span>
</div>
</el-col>
<!-- <el-col :xs="24" :sm="24" :lg="12">-->
<!-- <div class="chart-wrapper">-->
<!-- <bar-chart />-->
<!-- </div>-->
<!-- </el-col>-->
</el-row>
<div class="header-right">
<el-tag type="success" size="mini">在线</el-tag>
</div>
</div>
<div class="chat-messages" ref="messageContainer">
<div
v-for="(message, index) in messages"
:key="index"
:class="['message-item', message.isMe ? 'message-right' : 'message-left']"
>
<div class="message-avatar">
<el-avatar :size="32" :src="message.avatar">
{{ message.isMe ? '我' : '助' }}
</el-avatar>
</div>
<div class="message-content">
<div class="message-text">{{ message.content }}</div>
<div class="message-time">{{ message.time }}</div>
</div>
</div>
</div>
<div class="chat-input">
<el-input
type="textarea"
:rows="3"
placeholder="请输入消息..."
v-model="inputMessage"
@keyup.enter.native="sendMessage"
></el-input>
<div class="input-actions">
<el-button type="primary" size="small" @click="sendMessage" :disabled="!inputMessage.trim()">
发送
</el-button>
</div>
</div>
</div>
<!-- 右侧导航面板 -->
<div class="nav-panel">
<div class="nav-header">
<i class="el-icon-menu"></i>
<span class="title">快捷导航</span>
</div>
<!-- 快捷功能区 -->
<div class="quick-actions">
<div class="section-title">
<i class="el-icon-lightning"></i>
快捷操作
</div>
<div class="action-grid">
<div
v-for="action in quickActions"
:key="action.id"
class="action-item"
@click="sendQuickMessage(action)"
>
<div class="action-icon">
<i :class="action.icon"></i>
</div>
<div class="action-text">{{ action.title }}</div>
</div>
</div>
</div>
<!-- 常用功能区 -->
<div class="common-functions">
<div class="section-title">
<i class="el-icon-star-on"></i>
常用功能
</div>
<div class="function-list">
<div
v-for="func in commonFunctions"
:key="func.id"
class="function-item"
@click="navigateTo(func.path)"
>
<i :class="func.icon"></i>
<span>{{ func.title }}</span>
</div>
</div>
</div>
<!-- 系统统计 -->
<div class="system-stats">
<div class="section-title">
<i class="el-icon-data-analysis"></i>
系统概览
</div>
<div class="stats-grid">
<div class="stat-item">
<div class="stat-value">{{ stats.waitShip }}</div>
<div class="stat-label">待发货</div>
</div>
<div class="stat-item">
<div class="stat-value">{{ stats.orderCount }}</div>
<div class="stat-label">今日订单</div>
</div>
<div class="stat-item">
<div class="stat-value">¥{{ stats.salesVolume }}</div>
<div class="stat-label">今日销售额</div>
</div>
<div class="stat-item">
<div class="stat-value">{{ stats.shopCount }}</div>
<div class="stat-label">店铺数</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import PanelGroup from './dashboard/PanelGroup'
import LineChart from './dashboard/LineChart'
import RaddarChart from './dashboard/RaddarChart'
import PieChart from './dashboard/PieChart'
import BarChart from './dashboard/BarChart'
import { todayDaily,salesDaily,salesTopSku } from "@/api/report/report";
const lineChartData = {
// newVisitis: {
// expectedData: [100, 120, 161, 134, 105, 160, 165],
// actualData: [120, 82, 91, 154, 162, 140, 145]
// },
// messages: {
// expectedData: [200, 192, 120, 144, 160, 130, 140],
// actualData: [180, 160, 151, 106, 145, 150, 130]
// },
// purchases: {
// expectedData: [80, 100, 121, 104, 105, 90, 100],
// actualData: [120, 90, 100, 138, 142, 130, 130]
// },
// shoppings: {
// expectedData: [130, 140, 141, 142, 145, 150, 160],
// actualData: [120, 82, 91, 154, 162, 140, 130]
// }
salesDaily: {
date:['09-15','09-16','09-17','09-18','09-19','09-20','09-21'],
salesVolume: [130, 140, 141, 142, 145, 150, 160],
salesOrder: [120, 82, 91, 154, 162, 140, 130]
}
}
import { todayDaily } from "@/api/report/report";
import { getToken } from "@/utils/auth";
export default {
name: 'Index',
components: {
PanelGroup,
LineChart,
RaddarChart,
PieChart,
BarChart
},
data() {
return {
lineChartData: {
date:[],
salesVolume:[],
salesOrder:[]
inputMessage: '',
messages: [
{
content: '您好!我是您的工作助手,有什么可以帮助您的吗?',
time: this.formatTime(new Date()),
isMe: false,
avatar: ''
}
],
quickActions: [
{ id: 1, title: '查看待发货', icon: 'el-icon-truck', message: '请帮我查看今天有多少待发货订单' },
{ id: 2, title: '同步订单', icon: 'el-icon-refresh', message: '请同步所有店铺的订单数据' },
{ id: 3, title: '库存预警', icon: 'el-icon-warning', message: '检查一下哪些商品库存不足' },
{ id: 4, title: '今日统计', icon: 'el-icon-data-line', message: '请告诉我今天的销售统计数据' },
{ id: 5, title: '商品管理', icon: 'el-icon-goods', message: '我想管理商品信息' },
{ id: 6, title: '售后处理', icon: 'el-icon-service', message: '查看需要处理的售后订单' }
],
commonFunctions: [
{ id: 1, title: '订单管理', icon: 'el-icon-s-order', path: '/order' },
{ id: 2, title: '商品管理', icon: 'el-icon-goods', path: '/goods' },
{ id: 3, title: '库存管理', icon: 'el-icon-box', path: '/stock' },
{ id: 4, title: '发货管理', icon: 'el-icon-truck', path: '/shipping' },
{ id: 5, title: '售后管理', icon: 'el-icon-service', path: '/refund' },
{ id: 6, title: '店铺管理', icon: 'el-icon-shop', path: '/shop' },
{ id: 7, title: '采购管理', icon: 'el-icon-shopping-cart-full', path: '/purchase' },
{ id: 8, title: '系统设置', icon: 'el-icon-setting', path: '/system' }
],
stats: {
waitShip: 0,
salesVolume: 0,
orderCount: 0,
shopCount: 0
},
skuTopData:{
data:[
// { value: 320, name: 'AAA' },
// { value: 240, name: 'BBB' },
// { value: 149, name: 'CC' },
// { value: 100, name: 'DD' },
// { value: 59, name: 'DEEE' }
]
},
report:{
waitShip:0,
salesVolume:5989.98,
orderCount:302,
shopCount:8
}
sse: null,
clientId: '',
isSseConnected: false,
isLoading: false
}
},
mounted() {
//
todayDaily().then(resp=>{
this.report = resp.data
})
salesDaily().then(resp=>{
this.lineChartData.date=[]
this.lineChartData.salesVolume =[]
this.lineChartData.salesOrder =[]
resp.data.forEach(x=>{
this.lineChartData.date.push(x.date)
this.lineChartData.salesVolume.push(x.amount)
this.lineChartData.salesOrder.push(x.count)
})
})
salesTopSku().then(resp=>{
console.log("=====",resp.data)
this.skuTopData.data= resp.data
})
this.loadSystemStats();
this.initSse();
},
beforeDestroy() {
this.closeSse();
},
methods: {
handleSetLineChartData(type) {
// this.lineChartData = lineChartData[type]
initSse() {
// ID
this.clientId = 'client_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
// token
const token = getToken();
// SSEtoken
this.sse = new EventSource(`${process.env.VUE_APP_BASE_API}/api/ai-agent/sse/connect?clientId=${this.clientId}&token=${token}`);
//
this.sse.addEventListener('connected', (event) => {
console.log('SSE连接成功:', event.data);
this.isSseConnected = true;
});
//
this.sse.addEventListener('message', (event) => {
console.log('收到SSE消息:', event.data);
//
if (this.isLoading) {
this.messages = this.messages.filter(msg => !msg.isLoading);
this.isLoading = false;
}
//
this.messages.push({
content: event.data,
time: this.formatTime(new Date()),
isMe: false,
avatar: ''
});
this.scrollToBottom();
});
//
this.sse.addEventListener('heartbeat', (event) => {
console.log('收到心跳:', event.data);
});
//
this.sse.onerror = (error) => {
console.error('SSE连接错误:', error);
this.isSseConnected = false;
//
setTimeout(() => {
this.initSse();
}, 5000);
};
},
closeSse() {
if (this.sse) {
this.sse.close();
this.sse = null;
}
},
sendMessage() {
if (!this.inputMessage.trim()) return;
//
this.messages.push({
content: this.inputMessage,
time: this.formatTime(new Date()),
isMe: true,
avatar: ''
});
// loading
this.isLoading = true;
this.messages.push({
content: '正在思考...',
time: this.formatTime(new Date()),
isMe: false,
avatar: '',
isLoading: true
});
// token
const token = getToken();
// SSE
if (this.isSseConnected) {
// 使fetch
fetch(`${process.env.VUE_APP_BASE_API}/api/ai-agent/sse/send?clientId=${this.clientId}&message=${encodeURIComponent(this.inputMessage)}&token=${token}`)
.then(response => response.text())
.then(data => {
console.log('消息发送结果:', data);
})
.catch(error => {
console.error('消息发送失败:', error);
// 使
this.generateReply(this.inputMessage);
this.isLoading = false;
});
} else {
// SSE使
this.generateReply(this.inputMessage);
this.isLoading = false;
}
this.inputMessage = '';
this.scrollToBottom();
},
sendQuickMessage(action) {
this.inputMessage = action.message;
this.sendMessage();
},
generateReply(userMessage) {
let reply = '';
if (userMessage.includes('待发货')) {
reply = `您有 ${this.stats.waitShip} 个待发货订单,建议尽快处理。`;
} else if (userMessage.includes('订单')) {
reply = `今日订单总数:${this.stats.orderCount} 单,销售额:¥${this.stats.salesVolume}`;
} else if (userMessage.includes('库存')) {
reply = '正在为您检查库存情况,请稍等...';
} else if (userMessage.includes('统计')) {
reply = `今日数据:订单${this.stats.orderCount}单,销售额¥${this.stats.salesVolume},待发货${this.stats.waitShip}`;
} else {
reply = '我理解您的需求,正在为您处理...';
}
this.messages.push({
content: reply,
time: this.formatTime(new Date()),
isMe: false,
avatar: ''
});
this.scrollToBottom();
},
navigateTo(path) {
this.$router.push(path);
},
loadSystemStats() {
todayDaily().then(resp => {
this.stats = resp.data;
}).catch(error => {
console.error('加载统计失败:', error);
});
},
formatTime(date) {
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
return `${hours}:${minutes}`;
},
scrollToBottom() {
this.$nextTick(() => {
const container = this.$refs.messageContainer;
if (container) {
container.scrollTop = container.scrollHeight;
}
});
}
}
}
</script>
<style lang="scss" scoped>
.dashboard-editor-container {
padding: 32px;
background-color: rgb(240, 242, 245);
position: relative;
.chat-home-container {
display: flex;
height: calc(100vh - 84px);
background-color: #f5f7fa;
padding: 20px;
gap: 20px;
}
.chart-wrapper {
background: #fff;
padding: 16px 16px 0;
margin-bottom: 32px;
//
.chat-container {
flex: 1;
display: flex;
flex-direction: column;
background: white;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.chat-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid #eee;
background: #fafafa;
.header-left {
display: flex;
align-items: center;
gap: 8px;
.el-icon-chat-dot-round {
font-size: 20px;
color: #409eff;
}
.title {
font-size: 16px;
font-weight: 600;
color: #333;
}
}
}
@media (max-width:1024px) {
.chart-wrapper {
padding: 8px;
.chat-messages {
flex: 1;
padding: 20px;
overflow-y: auto;
background: #fafafa;
.message-item {
display: flex;
margin-bottom: 20px;
max-width: 80%;
&.message-left {
align-self: flex-start;
}
&.message-right {
align-self: flex-end;
flex-direction: row-reverse;
.message-content {
background: #409eff;
color: white;
.message-time {
color: rgba(255, 255, 255, 0.8);
}
}
}
}
.message-avatar {
margin: 0 12px;
}
.message-content {
background: white;
padding: 12px 16px;
border-radius: 18px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
.message-text {
font-size: 14px;
line-height: 1.5;
margin-bottom: 4px;
}
.message-time {
font-size: 12px;
color: #999;
text-align: right;
}
}
}
.chat-input {
padding: 20px;
border-top: 1px solid #eee;
background: white;
.input-actions {
margin-top: 12px;
text-align: right;
}
}
//
.nav-panel {
width: 320px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
overflow-y: auto;
}
.nav-header {
display: flex;
align-items: center;
gap: 8px;
padding: 16px 20px;
border-bottom: 1px solid #eee;
background: #fafafa;
.el-icon-menu {
font-size: 18px;
color: #409eff;
}
.title {
font-size: 16px;
font-weight: 600;
color: #333;
}
}
.section-title {
display: flex;
align-items: center;
gap: 8px;
padding: 16px 20px 8px;
font-size: 14px;
font-weight: 600;
color: #666;
border-bottom: 1px solid #f0f0f0;
margin-bottom: 16px;
}
//
.quick-actions {
.action-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
padding: 0 20px 20px;
}
.action-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 16px;
border: 1px solid #eee;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s;
&:hover {
border-color: #409eff;
background: #f0f8ff;
transform: translateY(-2px);
}
.action-icon {
font-size: 24px;
color: #409eff;
margin-bottom: 8px;
}
.action-text {
font-size: 13px;
color: #666;
}
}
}
//
.common-functions {
.function-list {
padding: 0 20px 20px;
}
.function-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s;
margin-bottom: 8px;
&:hover {
background: #f0f8ff;
color: #409eff;
}
i {
font-size: 16px;
width: 20px;
}
span {
font-size: 14px;
}
}
}
//
.system-stats {
.stats-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
padding: 0 20px 20px;
}
.stat-item {
text-align: center;
padding: 16px;
background: #f8f9fa;
border-radius: 8px;
.stat-value {
font-size: 20px;
font-weight: 600;
color: #409eff;
margin-bottom: 4px;
}
.stat-label {
font-size: 12px;
color: #999;
}
}
}
//
.chat-messages::-webkit-scrollbar,
.nav-panel::-webkit-scrollbar {
width: 6px;
}
.chat-messages::-webkit-scrollbar-track,
.nav-panel::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
.chat-messages::-webkit-scrollbar-thumb,
.nav-panel::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
}
.chat-messages::-webkit-scrollbar-thumb:hover,
.nav-panel::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
</style>