feat: fix frontend

This commit is contained in:
2025-06-21 16:55:45 +08:00
parent b52a9befee
commit 7cf5070288
17 changed files with 3071 additions and 393 deletions

View File

@@ -0,0 +1,19 @@
2025-06-21 15:10:59 [main] INFO c.a.s.AgriculturalStockPlatformApplication - Starting AgriculturalStockPlatformApplication using Java 17.0.10 on WIN11 with PID 11416 (D:\VScodeProject\work_4\backend\target\classes started by shenjianZ in D:\VScodeProject\work_4\backend)
2025-06-21 15:10:59 [main] INFO c.a.s.AgriculturalStockPlatformApplication - No active profile set, falling back to 1 default profile: "default"
2025-06-21 15:11:00 [main] INFO o.a.catalina.core.StandardService - Starting service [Tomcat]
2025-06-21 15:11:00 [main] INFO o.a.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.63]
2025-06-21 15:11:00 [main] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext
2025-06-21 15:11:01 [main] INFO o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default]
2025-06-21 15:11:01 [main] INFO org.hibernate.Version - HHH000412: Hibernate ORM core version 5.6.9.Final
2025-06-21 15:11:01 [main] INFO o.h.annotations.common.Version - HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2025-06-21 15:11:01 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
2025-06-21 15:11:01 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
2025-06-21 15:11:01 [main] INFO org.hibernate.dialect.Dialect - HHH000400: Using dialect: org.hibernate.dialect.MySQL8Dialect
2025-06-21 15:11:02 [main] INFO o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2025-06-21 15:11:03 [main] WARN o.s.b.a.o.j.JpaBaseConfiguration$JpaWebConfiguration - spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2025-06-21 15:11:04 [main] WARN c.b.m.core.metadata.TableInfoHelper - Can not find table primary key in Class: "com.agricultural.stock.entity.TechnicalIndicator".
2025-06-21 15:11:04 [main] WARN c.b.m.c.injector.DefaultSqlInjector - class com.agricultural.stock.entity.TechnicalIndicator ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method.
2025-06-21 15:11:04 [main] INFO c.a.s.AgriculturalStockPlatformApplication - Started AgriculturalStockPlatformApplication in 5.394 seconds (JVM running for 5.767)
2025-06-21 15:11:08 [http-nio-8080-exec-1] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
2025-06-21 15:14:01 [SpringApplicationShutdownHook] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated...
2025-06-21 15:14:01 [SpringApplicationShutdownHook] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed.

View File

@@ -7,7 +7,9 @@ import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.servers.Server; import io.swagger.v3.oas.models.servers.Server;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springdoc.core.GroupedOpenApi;
import java.util.Arrays;
import java.util.List; import java.util.List;
/** /**
@@ -40,7 +42,7 @@ public class OpenApiConfig {
.license(new License() .license(new License()
.name("MIT License") .name("MIT License")
.url("https://opensource.org/licenses/MIT"))) .url("https://opensource.org/licenses/MIT")))
.servers(List.of( .servers(Arrays.asList(
new Server() new Server()
.url("http://localhost:8080") .url("http://localhost:8080")
.description("本地开发环境"), .description("本地开发环境"),
@@ -49,4 +51,28 @@ public class OpenApiConfig {
.description("生产环境") .description("生产环境")
)); ));
} }
@Bean
public GroupedOpenApi allApi() {
return GroupedOpenApi.builder()
.group("all-apis")
.pathsToMatch("/**")
.build();
}
@Bean
public GroupedOpenApi stockApi() {
return GroupedOpenApi.builder()
.group("stock-apis")
.pathsToMatch("/api/stock/**")
.build();
}
@Bean
public GroupedOpenApi marketApi() {
return GroupedOpenApi.builder()
.group("market-apis")
.pathsToMatch("/api/market/**")
.build();
}
} }

View File

@@ -167,6 +167,26 @@ public class StockController {
} }
} }
/**
* 获取股票详情
*/
@GetMapping("/detail/{stockCode}")
@Operation(summary = "获取股票详情")
public Result<StockData> getStockDetail(
@Parameter(description = "股票代码") @PathVariable String stockCode) {
try {
StockData stockDetail = stockService.getStockDetail(stockCode);
if (stockDetail != null) {
return Result.success(stockDetail);
} else {
return Result.error("未找到该股票的详情数据");
}
} catch (Exception e) {
log.error("获取股票{}详情失败", stockCode, e);
return Result.error("获取股票详情失败: " + e.getMessage());
}
}
/** /**
* 搜索股票 * 搜索股票
*/ */

View File

@@ -79,6 +79,14 @@ public interface StockService {
* @return 预测数据列表 * @return 预测数据列表
*/ */
List<StockData> getStockPrediction(String stockCode, Integer days); List<StockData> getStockPrediction(String stockCode, Integer days);
/**
* 获取股票详情
*
* @param stockCode 股票代码
* @return 股票详情
*/
StockData getStockDetail(String stockCode);
/** /**
* 搜索股票 * 搜索股票

View File

@@ -251,9 +251,174 @@ public class StockServiceImpl implements StockService {
@Override @Override
public List<StockData> getStockPrediction(String stockCode, Integer days) { public List<StockData> getStockPrediction(String stockCode, Integer days) {
// 这里应该调用预测模型,暂时返回空列表 try {
log.info("股票预测功能暂未实现,股票代码: {}, 预测天数: {}", stockCode, days); log.info("生成股票预测数据,股票代码: {}, 预测天数: {}", stockCode, days);
return new ArrayList<>();
// 获取历史数据用于预测
List<StockData> historyData = stockDataMapper.getStockHistoryData(stockCode, 30);
if (historyData.isEmpty()) {
log.warn("未找到股票{}的历史数据,无法生成预测", stockCode);
return new ArrayList<>();
}
// 获取最新数据作为预测基础
StockData latestData = historyData.get(0); // getStockHistoryData返回的是按时间降序排列
// 计算历史价格变化趋势
BigDecimal avgChange = calculateAverageChange(historyData);
BigDecimal volatility = calculateVolatility(historyData);
List<StockData> predictionList = new ArrayList<>();
// 生成预测数据
BigDecimal currentPrice = latestData.getClosePrice();
LocalDateTime currentDate = LocalDateTime.now();
for (int i = 1; i <= days; i++) {
StockData prediction = new StockData();
// 基本信息
prediction.setStockCode(stockCode);
prediction.setStockName(latestData.getStockName());
prediction.setTradeDate(currentDate.plusDays(i));
// 简单预测模型:基于历史平均变化和随机波动
BigDecimal randomFactor = BigDecimal.valueOf((Math.random() - 0.5) * 2 * volatility.doubleValue());
BigDecimal predictedChange = avgChange.add(randomFactor);
// 计算预测价格
BigDecimal predictedPrice = currentPrice.multiply(
BigDecimal.ONE.add(predictedChange.divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP))
);
prediction.setOpenPrice(currentPrice);
prediction.setHighPrice(predictedPrice.multiply(BigDecimal.valueOf(1.05)));
prediction.setLowPrice(predictedPrice.multiply(BigDecimal.valueOf(0.95)));
prediction.setClosePrice(predictedPrice);
// 计算涨跌幅
BigDecimal changeAmount = predictedPrice.subtract(currentPrice);
BigDecimal changePercent = changeAmount.divide(currentPrice, 4, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100));
prediction.setChangeAmount(changeAmount);
prediction.setChangePercent(changePercent);
// 预测成交量(基于历史平均值)
double avgVolume = historyData.stream()
.map(StockData::getVolume)
.filter(Objects::nonNull)
.mapToLong(Long::longValue)
.average()
.orElse(100000L);
prediction.setVolume(Math.round(avgVolume * (0.8 + Math.random() * 0.4)));
// 预测成交额
if (prediction.getVolume() != null) {
prediction.setTurnover(predictedPrice.multiply(BigDecimal.valueOf(prediction.getVolume())));
}
// 市值
if (latestData.getMarketCap() != null && latestData.getClosePrice() != null && latestData.getClosePrice().compareTo(BigDecimal.ZERO) != 0) {
prediction.setMarketCap(latestData.getMarketCap()
.multiply(predictedPrice)
.divide(latestData.getClosePrice(), 2, RoundingMode.HALF_UP));
}
predictionList.add(prediction);
currentPrice = predictedPrice; // 更新当前价格为下一天的基础
}
return predictionList;
} catch (Exception e) {
log.error("生成股票{}预测数据失败", stockCode, e);
return new ArrayList<>();
}
}
/**
* 计算历史价格平均变化率
*/
private BigDecimal calculateAverageChange(List<StockData> historyData) {
if (historyData.size() < 2) {
return BigDecimal.ZERO;
}
BigDecimal totalChange = BigDecimal.ZERO;
int count = 0;
// 数据是按时间降序排列的,所以要反过来计算
for (int i = historyData.size() - 1; i > 0; i--) {
StockData current = historyData.get(i - 1); // 更新的数据
StockData previous = historyData.get(i); // 更早的数据
if (current.getClosePrice() != null && previous.getClosePrice() != null && previous.getClosePrice().compareTo(BigDecimal.ZERO) != 0) {
BigDecimal change = current.getClosePrice().subtract(previous.getClosePrice())
.divide(previous.getClosePrice(), 4, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100));
totalChange = totalChange.add(change);
count++;
}
}
return count > 0 ? totalChange.divide(BigDecimal.valueOf(count), 4, RoundingMode.HALF_UP) : BigDecimal.ZERO;
}
/**
* 计算价格波动率
*/
private BigDecimal calculateVolatility(List<StockData> historyData) {
if (historyData.size() < 2) {
return BigDecimal.valueOf(2.0); // 默认波动率
}
List<BigDecimal> changes = new ArrayList<>();
// 数据是按时间降序排列的,所以要反过来计算
for (int i = historyData.size() - 1; i > 0; i--) {
StockData current = historyData.get(i - 1); // 更新的数据
StockData previous = historyData.get(i); // 更早的数据
if (current.getClosePrice() != null && previous.getClosePrice() != null && previous.getClosePrice().compareTo(BigDecimal.ZERO) != 0) {
BigDecimal change = current.getClosePrice().subtract(previous.getClosePrice())
.divide(previous.getClosePrice(), 4, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100));
changes.add(change);
}
}
if (changes.isEmpty()) {
return BigDecimal.valueOf(2.0);
}
// 计算标准差
BigDecimal mean = changes.stream()
.reduce(BigDecimal.ZERO, BigDecimal::add)
.divide(BigDecimal.valueOf(changes.size()), 4, RoundingMode.HALF_UP);
BigDecimal variance = changes.stream()
.map(change -> change.subtract(mean).pow(2))
.reduce(BigDecimal.ZERO, BigDecimal::add)
.divide(BigDecimal.valueOf(changes.size()), 4, RoundingMode.HALF_UP);
return BigDecimal.valueOf(Math.sqrt(variance.doubleValue()));
}
@Override
public StockData getStockDetail(String stockCode) {
try {
List<StockData> stockList = stockDataMapper.getLatestStockData();
return stockList.stream()
.filter(stock -> stock.getStockCode().equals(stockCode))
.findFirst()
.orElse(null);
} catch (Exception e) {
log.error("获取股票{}详情失败", stockCode, e);
return null;
}
} }
@Override @Override

View File

@@ -71,7 +71,9 @@ springdoc:
path: /swagger-ui path: /swagger-ui
enabled: true enabled: true
config-url: /v3/api-docs/swagger-config config-url: /v3/api-docs/swagger-config
urls-primary-name: default disable-swagger-default-url: true
url: /v3/api-docs
urls-primary-name: all-apis
packages-to-scan: com.agricultural.stock.controller packages-to-scan: com.agricultural.stock.controller
paths-to-match: /api/** paths-to-match: /api/**

View File

@@ -355,3 +355,284 @@
2025-06-18 08:54:04,912 - INFO - 数据采集完成,共处理 67 只股票 2025-06-18 08:54:04,912 - INFO - 数据采集完成,共处理 67 只股票
2025-06-18 08:54:30,695 - INFO - 收到停止信号,正在关闭... 2025-06-18 08:54:30,695 - INFO - 收到停止信号,正在关闭...
2025-06-18 08:54:30,696 - INFO - 股票数据采集器已停止 2025-06-18 08:54:30,696 - INFO - 股票数据采集器已停止
2025-06-21 15:36:01,487 - INFO - 数据库连接成功
2025-06-21 15:36:01,488 - INFO - 股票数据采集器启动
2025-06-21 15:36:01,488 - INFO - 定时任务已启动,每 5 分钟采集一次
2025-06-21 15:36:01,488 - INFO - 开始执行股票数据采集...
2025-06-21 15:36:01,721 - INFO - 成功获取股票 sz300189 数据: 神农种业
2025-06-21 15:36:02,357 - INFO - 成功获取股票 sz000713 数据: 丰乐种业
2025-06-21 15:36:03,005 - INFO - 成功获取股票 sh600313 数据: 农发种业
2025-06-21 15:36:03,631 - INFO - 成功获取股票 bj837403 数据: 康农种业
2025-06-21 15:36:04,272 - INFO - 成功获取股票 sz200505 数据: 京粮B
2025-06-21 15:36:04,876 - INFO - 成功获取股票 sz300268 数据: *ST佳沃
2025-06-21 15:36:05,501 - INFO - 成功获取股票 sz000930 数据: 中粮科技
2025-06-21 15:36:06,140 - INFO - 成功获取股票 sz002299 数据: 圣农发展
2025-06-21 15:36:06,756 - INFO - 成功获取股票 sh600371 数据: 万向德农
2025-06-21 15:36:07,372 - INFO - 成功获取股票 sh600598 数据: 北大荒
2025-06-21 15:36:08,017 - INFO - 成功获取股票 sh603609 数据: 禾丰股份
2025-06-21 15:36:08,628 - INFO - 成功获取股票 bj831087 数据: 秋乐种业
2025-06-21 15:36:09,256 - INFO - 成功获取股票 sh603363 数据: 傲农生物
2025-06-21 15:36:09,902 - INFO - 成功获取股票 sh603336 数据: 宏辉果蔬
2025-06-21 15:36:10,522 - INFO - 成功获取股票 sh600354 数据: 敦煌种业
2025-06-21 15:36:11,151 - INFO - 成功获取股票 sz002385 数据: 大北农
2025-06-21 15:36:11,778 - INFO - 成功获取股票 sz000048 数据: 京基智农
2025-06-21 15:36:12,405 - INFO - 成功获取股票 sh600251 数据: 冠农股份
2025-06-21 15:36:13,051 - INFO - 成功获取股票 sz002321 数据: 华英农业
2025-06-21 15:36:13,691 - INFO - 成功获取股票 sz000505 数据: 京粮控股
2025-06-21 15:36:14,339 - INFO - 成功获取股票 sz001366 数据: 播恩集团
2025-06-21 15:36:14,982 - INFO - 成功获取股票 sz002772 数据: 众兴菌业
2025-06-21 15:36:15,609 - INFO - 成功获取股票 sz002041 数据: 登海种业
2025-06-21 15:36:16,230 - INFO - 成功获取股票 sh600127 数据: 金健米业
2025-06-21 15:36:16,868 - INFO - 成功获取股票 sz002548 数据: 金新农
2025-06-21 15:36:17,535 - INFO - 成功获取股票 sh605296 数据: 神农集团
2025-06-21 15:36:18,165 - INFO - 成功获取股票 sh600359 数据: 新农开发
2025-06-21 15:36:18,800 - INFO - 成功获取股票 sh601952 数据: 苏垦农发
2025-06-21 15:36:19,420 - INFO - 成功获取股票 sh600975 数据: 新五丰
2025-06-21 15:36:20,048 - INFO - 成功获取股票 sz300505 数据: 川金诺
2025-06-21 15:36:20,656 - INFO - 成功获取股票 sh600141 数据: XD兴发集
2025-06-21 15:36:21,293 - INFO - 成功获取股票 sz000893 数据: 亚钾国际
2025-06-21 15:36:21,929 - INFO - 成功获取股票 sh600691 数据: 阳煤化工
2025-06-21 15:36:22,552 - INFO - 成功获取股票 sh600470 数据: 六国化工
2025-06-21 15:36:23,163 - INFO - 成功获取股票 sh000912 数据: 300消费
2025-06-21 15:36:23,785 - INFO - 成功获取股票 sz000408 数据: 藏格矿业
2025-06-21 15:36:24,410 - INFO - 成功获取股票 sz000902 数据: 新洋丰
2025-06-21 15:36:25,040 - INFO - 成功获取股票 sz002539 数据: 云图控股
2025-06-21 15:36:25,662 - INFO - 成功获取股票 sz002599 数据: 盛通股份
2025-06-21 15:36:26,277 - INFO - 成功获取股票 sz002545 数据: 东方铁塔
2025-06-21 15:36:26,919 - INFO - 成功获取股票 sz300387 数据: 富邦科技
2025-06-21 15:36:27,548 - INFO - 成功获取股票 sz002274 数据: 华昌化工
2025-06-21 15:36:28,226 - INFO - 成功获取股票 sz002470 数据: 金正大
2025-06-21 15:36:28,895 - INFO - 成功获取股票 sz002538 数据: 司尔特
2025-06-21 15:36:29,536 - INFO - 成功获取股票 sz000731 数据: 四川美丰
2025-06-21 15:36:30,195 - INFO - 成功获取股票 sh600078 数据: 澄星股份
2025-06-21 15:36:30,841 - INFO - 成功获取股票 sh600227 数据: 赤天化
2025-06-21 15:36:31,481 - INFO - 成功获取股票 sh603395 数据: 红四方
2025-06-21 15:36:32,103 - INFO - 成功获取股票 sz002588 数据: 史丹利
2025-06-21 15:36:32,718 - INFO - 成功获取股票 sz000422 数据: 湖北宜化
2025-06-21 15:36:33,345 - INFO - 成功获取股票 sz002556 数据: 辉隆股份
2025-06-21 15:36:33,987 - INFO - 成功获取股票 sz002312 数据: 川发龙蟒
2025-06-21 15:36:34,620 - INFO - 成功获取股票 sz002170 数据: 芭田股份
2025-06-21 15:36:35,398 - INFO - 成功获取股票 sz000792 数据: 盐湖股份
2025-06-21 15:36:36,082 - INFO - 成功获取股票 sh600096 数据: 云天化
2025-06-21 15:36:36,825 - INFO - 成功获取股票 sz001231 数据: 农心科技
2025-06-21 15:36:37,477 - INFO - 成功获取股票 sz002731 数据: 萃华珠宝
2025-06-21 15:36:38,171 - INFO - 成功获取股票 sz200553 数据: 安道麦B
2025-06-21 15:36:38,800 - INFO - 成功获取股票 sh603810 数据: 丰山集团
2025-06-21 15:36:39,426 - INFO - 成功获取股票 sh603970 数据: 中农立华
2025-06-21 15:36:40,052 - INFO - 成功获取股票 sz301035 数据: 润丰股份
2025-06-21 15:36:40,695 - INFO - 成功获取股票 sz002868 数据: *ST绿康
2025-06-21 15:36:41,349 - INFO - 成功获取股票 sz003042 数据: 中农联合
2025-06-21 15:36:42,004 - INFO - 成功获取股票 sz002391 数据: 长青股份
2025-06-21 15:36:42,614 - INFO - 成功获取股票 bj870866 数据: 绿亨科技
2025-06-21 15:36:43,269 - INFO - 成功获取股票 sz301665 数据: 泰禾股份
2025-06-21 15:36:43,934 - INFO - 成功获取股票 sh600486 数据: 扬农化工
2025-06-21 15:36:44,435 - INFO - 本次采集完成,共获取 67 只股票数据
2025-06-21 15:36:44,441 - INFO - 插入股票 sz300189 数据
2025-06-21 15:36:44,444 - INFO - 插入股票 sz000713 数据
2025-06-21 15:36:44,445 - INFO - 插入股票 sh600313 数据
2025-06-21 15:36:44,447 - INFO - 插入股票 bj837403 数据
2025-06-21 15:36:44,449 - INFO - 插入股票 sz200505 数据
2025-06-21 15:36:44,450 - INFO - 插入股票 sz300268 数据
2025-06-21 15:36:44,452 - INFO - 插入股票 sz000930 数据
2025-06-21 15:36:44,454 - INFO - 插入股票 sz002299 数据
2025-06-21 15:36:44,455 - INFO - 插入股票 sh600371 数据
2025-06-21 15:36:44,458 - INFO - 插入股票 sh600598 数据
2025-06-21 15:36:44,460 - INFO - 插入股票 sh603609 数据
2025-06-21 15:36:44,462 - INFO - 插入股票 bj831087 数据
2025-06-21 15:36:44,463 - INFO - 插入股票 sh603363 数据
2025-06-21 15:36:44,465 - INFO - 插入股票 sh603336 数据
2025-06-21 15:36:44,467 - INFO - 插入股票 sh600354 数据
2025-06-21 15:36:44,468 - INFO - 插入股票 sz002385 数据
2025-06-21 15:36:44,470 - INFO - 插入股票 sz000048 数据
2025-06-21 15:36:44,471 - INFO - 插入股票 sh600251 数据
2025-06-21 15:36:44,473 - INFO - 插入股票 sz002321 数据
2025-06-21 15:36:44,475 - INFO - 插入股票 sz000505 数据
2025-06-21 15:36:44,477 - INFO - 插入股票 sz001366 数据
2025-06-21 15:36:44,478 - INFO - 插入股票 sz002772 数据
2025-06-21 15:36:44,479 - INFO - 插入股票 sz002041 数据
2025-06-21 15:36:44,481 - INFO - 插入股票 sh600127 数据
2025-06-21 15:36:44,482 - INFO - 插入股票 sz002548 数据
2025-06-21 15:36:44,484 - INFO - 插入股票 sh605296 数据
2025-06-21 15:36:44,485 - INFO - 插入股票 sh600359 数据
2025-06-21 15:36:44,487 - INFO - 插入股票 sh601952 数据
2025-06-21 15:36:44,489 - INFO - 插入股票 sh600975 数据
2025-06-21 15:36:44,492 - INFO - 插入股票 sz300505 数据
2025-06-21 15:36:44,493 - INFO - 插入股票 sh600141 数据
2025-06-21 15:36:44,495 - INFO - 插入股票 sz000893 数据
2025-06-21 15:36:44,497 - INFO - 插入股票 sh600691 数据
2025-06-21 15:36:44,498 - INFO - 插入股票 sh600470 数据
2025-06-21 15:36:44,500 - INFO - 插入股票 sh000912 数据
2025-06-21 15:36:44,501 - INFO - 插入股票 sz000408 数据
2025-06-21 15:36:44,503 - INFO - 插入股票 sz000902 数据
2025-06-21 15:36:44,505 - INFO - 插入股票 sz002539 数据
2025-06-21 15:36:44,508 - INFO - 插入股票 sz002599 数据
2025-06-21 15:36:44,510 - INFO - 插入股票 sz002545 数据
2025-06-21 15:36:44,511 - INFO - 插入股票 sz300387 数据
2025-06-21 15:36:44,512 - INFO - 插入股票 sz002274 数据
2025-06-21 15:36:44,514 - INFO - 插入股票 sz002470 数据
2025-06-21 15:36:44,515 - INFO - 插入股票 sz002538 数据
2025-06-21 15:36:44,516 - INFO - 插入股票 sz000731 数据
2025-06-21 15:36:44,518 - INFO - 插入股票 sh600078 数据
2025-06-21 15:36:44,519 - INFO - 插入股票 sh600227 数据
2025-06-21 15:36:44,521 - INFO - 插入股票 sh603395 数据
2025-06-21 15:36:44,525 - INFO - 插入股票 sz002588 数据
2025-06-21 15:36:44,527 - INFO - 插入股票 sz000422 数据
2025-06-21 15:36:44,528 - INFO - 插入股票 sz002556 数据
2025-06-21 15:36:44,529 - INFO - 插入股票 sz002312 数据
2025-06-21 15:36:44,531 - INFO - 插入股票 sz002170 数据
2025-06-21 15:36:44,532 - INFO - 插入股票 sz000792 数据
2025-06-21 15:36:44,533 - INFO - 插入股票 sh600096 数据
2025-06-21 15:36:44,535 - INFO - 插入股票 sz001231 数据
2025-06-21 15:36:44,536 - INFO - 插入股票 sz002731 数据
2025-06-21 15:36:44,538 - INFO - 插入股票 sz200553 数据
2025-06-21 15:36:44,539 - INFO - 插入股票 sh603810 数据
2025-06-21 15:36:44,542 - INFO - 插入股票 sh603970 数据
2025-06-21 15:36:44,543 - INFO - 插入股票 sz301035 数据
2025-06-21 15:36:44,544 - INFO - 插入股票 sz002868 数据
2025-06-21 15:36:44,546 - INFO - 插入股票 sz003042 数据
2025-06-21 15:36:44,548 - INFO - 插入股票 sz002391 数据
2025-06-21 15:36:44,549 - INFO - 插入股票 bj870866 数据
2025-06-21 15:36:44,551 - INFO - 插入股票 sz301665 数据
2025-06-21 15:36:44,552 - INFO - 插入股票 sh600486 数据
2025-06-21 15:36:44,560 - INFO - 成功处理 67 条股票数据到数据库
2025-06-21 15:36:44,561 - INFO - 数据采集完成,共处理 67 只股票
2025-06-21 15:41:01,678 - INFO - 开始执行股票数据采集...
2025-06-21 15:41:01,809 - INFO - 成功获取股票 sz300189 数据: 神农种业
2025-06-21 15:41:02,450 - INFO - 成功获取股票 sz000713 数据: 丰乐种业
2025-06-21 15:41:03,074 - INFO - 成功获取股票 sh600313 数据: 农发种业
2025-06-21 15:41:03,715 - INFO - 成功获取股票 bj837403 数据: 康农种业
2025-06-21 15:41:04,319 - INFO - 成功获取股票 sz200505 数据: 京粮B
2025-06-21 15:41:04,974 - INFO - 成功获取股票 sz300268 数据: *ST佳沃
2025-06-21 15:41:05,643 - INFO - 成功获取股票 sz000930 数据: 中粮科技
2025-06-21 15:41:06,253 - INFO - 成功获取股票 sz002299 数据: 圣农发展
2025-06-21 15:41:06,890 - INFO - 成功获取股票 sh600371 数据: 万向德农
2025-06-21 15:41:07,544 - INFO - 成功获取股票 sh600598 数据: 北大荒
2025-06-21 15:41:08,146 - INFO - 成功获取股票 sh603609 数据: 禾丰股份
2025-06-21 15:41:08,770 - INFO - 成功获取股票 bj831087 数据: 秋乐种业
2025-06-21 15:41:09,463 - INFO - 成功获取股票 sh603363 数据: 傲农生物
2025-06-21 15:41:10,093 - INFO - 成功获取股票 sh603336 数据: 宏辉果蔬
2025-06-21 15:41:10,706 - INFO - 成功获取股票 sh600354 数据: 敦煌种业
2025-06-21 15:41:11,321 - INFO - 成功获取股票 sz002385 数据: 大北农
2025-06-21 15:41:11,942 - INFO - 成功获取股票 sz000048 数据: 京基智农
2025-06-21 15:41:12,568 - INFO - 成功获取股票 sh600251 数据: 冠农股份
2025-06-21 15:41:13,209 - INFO - 成功获取股票 sz002321 数据: 华英农业
2025-06-21 15:41:13,856 - INFO - 成功获取股票 sz000505 数据: 京粮控股
2025-06-21 15:41:14,498 - INFO - 成功获取股票 sz001366 数据: 播恩集团
2025-06-21 15:41:15,142 - INFO - 成功获取股票 sz002772 数据: 众兴菌业
2025-06-21 15:41:15,772 - INFO - 成功获取股票 sz002041 数据: 登海种业
2025-06-21 15:41:16,397 - INFO - 成功获取股票 sh600127 数据: 金健米业
2025-06-21 15:41:17,021 - INFO - 成功获取股票 sz002548 数据: 金新农
2025-06-21 15:41:17,690 - INFO - 成功获取股票 sh605296 数据: 神农集团
2025-06-21 15:41:18,303 - INFO - 成功获取股票 sh600359 数据: 新农开发
2025-06-21 15:41:18,982 - INFO - 成功获取股票 sh601952 数据: 苏垦农发
2025-06-21 15:41:19,628 - INFO - 成功获取股票 sh600975 数据: 新五丰
2025-06-21 15:41:20,376 - INFO - 成功获取股票 sz300505 数据: 川金诺
2025-06-21 15:41:21,043 - INFO - 成功获取股票 sh600141 数据: XD兴发集
2025-06-21 15:41:21,717 - INFO - 成功获取股票 sz000893 数据: 亚钾国际
2025-06-21 15:41:22,348 - INFO - 成功获取股票 sh600691 数据: 阳煤化工
2025-06-21 15:41:22,966 - INFO - 成功获取股票 sh600470 数据: 六国化工
2025-06-21 15:41:23,560 - INFO - 成功获取股票 sh000912 数据: 300消费
2025-06-21 15:41:24,163 - INFO - 成功获取股票 sz000408 数据: 藏格矿业
2025-06-21 15:41:24,808 - INFO - 成功获取股票 sz000902 数据: 新洋丰
2025-06-21 15:41:25,419 - INFO - 成功获取股票 sz002539 数据: 云图控股
2025-06-21 15:41:26,020 - INFO - 成功获取股票 sz002599 数据: 盛通股份
2025-06-21 15:41:26,659 - INFO - 成功获取股票 sz002545 数据: 东方铁塔
2025-06-21 15:41:27,268 - INFO - 成功获取股票 sz300387 数据: 富邦科技
2025-06-21 15:41:27,895 - INFO - 成功获取股票 sz002274 数据: 华昌化工
2025-06-21 15:41:28,586 - INFO - 成功获取股票 sz002470 数据: 金正大
2025-06-21 15:41:29,220 - INFO - 成功获取股票 sz002538 数据: 司尔特
2025-06-21 15:41:29,827 - INFO - 成功获取股票 sz000731 数据: 四川美丰
2025-06-21 15:41:30,450 - INFO - 成功获取股票 sh600078 数据: 澄星股份
2025-06-21 15:41:31,072 - INFO - 成功获取股票 sh600227 数据: 赤天化
2025-06-21 15:41:31,702 - INFO - 成功获取股票 sh603395 数据: 红四方
2025-06-21 15:41:32,314 - INFO - 成功获取股票 sz002588 数据: 史丹利
2025-06-21 15:41:32,932 - INFO - 成功获取股票 sz000422 数据: 湖北宜化
2025-06-21 15:41:33,617 - INFO - 成功获取股票 sz002556 数据: 辉隆股份
2025-06-21 15:41:34,222 - INFO - 成功获取股票 sz002312 数据: 川发龙蟒
2025-06-21 15:41:34,849 - INFO - 成功获取股票 sz002170 数据: 芭田股份
2025-06-21 15:41:35,524 - INFO - 成功获取股票 sz000792 数据: 盐湖股份
2025-06-21 15:41:36,171 - INFO - 成功获取股票 sh600096 数据: 云天化
2025-06-21 15:41:36,830 - INFO - 成功获取股票 sz001231 数据: 农心科技
2025-06-21 15:41:37,517 - INFO - 成功获取股票 sz002731 数据: 萃华珠宝
2025-06-21 15:41:38,138 - INFO - 成功获取股票 sz200553 数据: 安道麦B
2025-06-21 15:41:38,815 - INFO - 成功获取股票 sh603810 数据: 丰山集团
2025-06-21 15:41:39,439 - INFO - 成功获取股票 sh603970 数据: 中农立华
2025-06-21 15:41:40,102 - INFO - 成功获取股票 sz301035 数据: 润丰股份
2025-06-21 15:41:40,710 - INFO - 成功获取股票 sz002868 数据: *ST绿康
2025-06-21 15:41:41,316 - INFO - 成功获取股票 sz003042 数据: 中农联合
2025-06-21 15:41:41,943 - INFO - 成功获取股票 sz002391 数据: 长青股份
2025-06-21 15:41:42,554 - INFO - 成功获取股票 bj870866 数据: 绿亨科技
2025-06-21 15:41:43,174 - INFO - 成功获取股票 sz301665 数据: 泰禾股份
2025-06-21 15:41:43,774 - INFO - 成功获取股票 sh600486 数据: 扬农化工
2025-06-21 15:41:44,275 - INFO - 本次采集完成,共获取 67 只股票数据
2025-06-21 15:41:44,279 - INFO - 更新股票 sz300189 数据
2025-06-21 15:41:44,282 - INFO - 更新股票 sz000713 数据
2025-06-21 15:41:44,284 - INFO - 更新股票 sh600313 数据
2025-06-21 15:41:44,286 - INFO - 更新股票 bj837403 数据
2025-06-21 15:41:44,289 - INFO - 更新股票 sz200505 数据
2025-06-21 15:41:44,291 - INFO - 更新股票 sz300268 数据
2025-06-21 15:41:44,293 - INFO - 更新股票 sz000930 数据
2025-06-21 15:41:44,295 - INFO - 更新股票 sz002299 数据
2025-06-21 15:41:44,298 - INFO - 更新股票 sh600371 数据
2025-06-21 15:41:44,300 - INFO - 更新股票 sh600598 数据
2025-06-21 15:41:44,302 - INFO - 更新股票 sh603609 数据
2025-06-21 15:41:44,306 - INFO - 更新股票 bj831087 数据
2025-06-21 15:41:44,308 - INFO - 更新股票 sh603363 数据
2025-06-21 15:41:44,310 - INFO - 更新股票 sh603336 数据
2025-06-21 15:41:44,313 - INFO - 更新股票 sh600354 数据
2025-06-21 15:41:44,315 - INFO - 更新股票 sz002385 数据
2025-06-21 15:41:44,318 - INFO - 更新股票 sz000048 数据
2025-06-21 15:41:44,321 - INFO - 更新股票 sh600251 数据
2025-06-21 15:41:44,323 - INFO - 更新股票 sz002321 数据
2025-06-21 15:41:44,324 - INFO - 更新股票 sz000505 数据
2025-06-21 15:41:44,326 - INFO - 更新股票 sz001366 数据
2025-06-21 15:41:44,330 - INFO - 更新股票 sz002772 数据
2025-06-21 15:41:44,332 - INFO - 更新股票 sz002041 数据
2025-06-21 15:41:44,333 - INFO - 更新股票 sh600127 数据
2025-06-21 15:41:44,336 - INFO - 更新股票 sz002548 数据
2025-06-21 15:41:44,337 - INFO - 更新股票 sh605296 数据
2025-06-21 15:41:44,339 - INFO - 更新股票 sh600359 数据
2025-06-21 15:41:44,341 - INFO - 更新股票 sh601952 数据
2025-06-21 15:41:44,343 - INFO - 更新股票 sh600975 数据
2025-06-21 15:41:44,346 - INFO - 更新股票 sz300505 数据
2025-06-21 15:41:44,348 - INFO - 更新股票 sh600141 数据
2025-06-21 15:41:44,351 - INFO - 更新股票 sz000893 数据
2025-06-21 15:41:44,353 - INFO - 更新股票 sh600691 数据
2025-06-21 15:41:44,355 - INFO - 更新股票 sh600470 数据
2025-06-21 15:41:44,357 - INFO - 更新股票 sh000912 数据
2025-06-21 15:41:44,359 - INFO - 更新股票 sz000408 数据
2025-06-21 15:41:44,362 - INFO - 更新股票 sz000902 数据
2025-06-21 15:41:44,364 - INFO - 更新股票 sz002539 数据
2025-06-21 15:41:44,366 - INFO - 更新股票 sz002599 数据
2025-06-21 15:41:44,369 - INFO - 更新股票 sz002545 数据
2025-06-21 15:41:44,371 - INFO - 更新股票 sz300387 数据
2025-06-21 15:41:44,373 - INFO - 更新股票 sz002274 数据
2025-06-21 15:41:44,375 - INFO - 更新股票 sz002470 数据
2025-06-21 15:41:44,377 - INFO - 更新股票 sz002538 数据
2025-06-21 15:41:44,379 - INFO - 更新股票 sz000731 数据
2025-06-21 15:41:44,382 - INFO - 更新股票 sh600078 数据
2025-06-21 15:41:44,385 - INFO - 更新股票 sh600227 数据
2025-06-21 15:41:44,388 - INFO - 更新股票 sh603395 数据
2025-06-21 15:41:44,390 - INFO - 更新股票 sz002588 数据
2025-06-21 15:41:44,393 - INFO - 更新股票 sz000422 数据
2025-06-21 15:41:44,396 - INFO - 更新股票 sz002556 数据
2025-06-21 15:41:44,399 - INFO - 更新股票 sz002312 数据
2025-06-21 15:41:44,401 - INFO - 更新股票 sz002170 数据
2025-06-21 15:41:44,404 - INFO - 更新股票 sz000792 数据
2025-06-21 15:41:44,406 - INFO - 更新股票 sh600096 数据
2025-06-21 15:41:44,408 - INFO - 更新股票 sz001231 数据
2025-06-21 15:41:44,410 - INFO - 更新股票 sz002731 数据
2025-06-21 15:41:44,413 - INFO - 更新股票 sz200553 数据
2025-06-21 15:41:44,415 - INFO - 更新股票 sh603810 数据
2025-06-21 15:41:44,418 - INFO - 更新股票 sh603970 数据
2025-06-21 15:41:44,420 - INFO - 更新股票 sz301035 数据
2025-06-21 15:41:44,421 - INFO - 更新股票 sz002868 数据
2025-06-21 15:41:44,423 - INFO - 更新股票 sz003042 数据
2025-06-21 15:41:44,424 - INFO - 更新股票 sz002391 数据
2025-06-21 15:41:44,426 - INFO - 更新股票 bj870866 数据
2025-06-21 15:41:44,429 - INFO - 更新股票 sz301665 数据
2025-06-21 15:41:44,432 - INFO - 更新股票 sh600486 数据
2025-06-21 15:41:44,438 - INFO - 成功处理 67 条股票数据到数据库
2025-06-21 15:41:44,439 - INFO - 数据采集完成,共处理 67 只股票
2025-06-21 15:43:59,960 - INFO - 收到停止信号,正在关闭...
2025-06-21 15:43:59,961 - INFO - 股票数据采集器已停止

View File

@@ -34,14 +34,9 @@
</el-menu-item> </el-menu-item>
<el-menu-item index="/market-analysis"> <el-menu-item index="/market-analysis">
<el-icon><Pie /></el-icon> <el-icon><DataBoard /></el-icon>
<span>市场分析</span> <span>市场分析</span>
</el-menu-item> </el-menu-item>
<el-menu-item index="/health">
<el-icon><Monitor /></el-icon>
<span>系统监控</span>
</el-menu-item>
</el-menu> </el-menu>
</div> </div>

View File

@@ -77,6 +77,14 @@ export function getStockPrediction(stockCode, days = 7) {
}) })
} }
// 获取股票详情
export function getStockDetail(stockCode) {
return request({
url: `/api/stock/detail/${stockCode}`,
method: 'get'
})
}
// 搜索股票 // 搜索股票
export function searchStocks(keyword) { export function searchStocks(keyword) {
return request({ return request({

View File

@@ -1,10 +1,12 @@
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory } from 'vue-router'
import Dashboard from '@/views/Dashboard.vue' import Dashboard from '@/views/Dashboard.vue'
import HealthCheck from '@/views/HealthCheck.vue'
import Rankings from '@/views/Rankings.vue' import Rankings from '@/views/Rankings.vue'
import StockSearch from '@/views/StockSearch.vue' import StockSearch from '@/views/StockSearch.vue'
import StockDetail from '@/views/StockDetail.vue' import StockDetail from '@/views/StockDetail.vue'
import MarketAnalysis from '@/views/MarketAnalysis.vue' import MarketAnalysis from '@/views/MarketAnalysis.vue'
import StockDetailView from '@/views/StockDetailView.vue'
import StockTrendView from '@/views/StockTrendView.vue'
import StockPredictionView from '@/views/StockPredictionView.vue'
const routes = [ const routes = [
{ {
@@ -31,29 +33,29 @@ const routes = [
component: StockDetail, component: StockDetail,
meta: { title: '股票详情' } meta: { title: '股票详情' }
}, },
{
path: '/stock/:stockCode/detail',
name: 'StockDetailView',
component: StockDetailView,
meta: { title: '股票详情分析' }
},
{ {
path: '/stock/:stockCode/trend', path: '/stock/:stockCode/trend',
name: 'StockTrend', name: 'StockTrendView',
component: StockDetail, component: StockTrendView,
meta: { title: '股票趋势' } meta: { title: '股票趋势分析' }
}, },
{ {
path: '/stock/:stockCode/prediction', path: '/stock/:stockCode/prediction',
name: 'StockPrediction', name: 'StockPredictionView',
component: StockDetail, component: StockPredictionView,
meta: { title: '股票预测' } meta: { title: '股票预测分析' }
}, },
{ {
path: '/market-analysis', path: '/market-analysis',
name: 'MarketAnalysis', name: 'MarketAnalysis',
component: MarketAnalysis, component: MarketAnalysis,
meta: { title: '市场分析' } meta: { title: '市场分析' }
},
{
path: '/health',
name: 'HealthCheck',
component: HealthCheck,
meta: { title: '健康检查' }
} }
] ]

View File

@@ -94,7 +94,7 @@
<!-- 快速导航 --> <!-- 快速导航 -->
<el-row :gutter="20" class="quick-nav-section"> <el-row :gutter="20" class="quick-nav-section">
<el-col :span="6"> <el-col :span="8">
<el-card class="nav-card" @click="navigateTo('/search')"> <el-card class="nav-card" @click="navigateTo('/search')">
<div class="nav-content"> <div class="nav-content">
<div class="nav-icon search"> <div class="nav-icon search">
@@ -107,7 +107,7 @@
</div> </div>
</el-card> </el-card>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="8">
<el-card class="nav-card" @click="navigateTo('/rankings')"> <el-card class="nav-card" @click="navigateTo('/rankings')">
<div class="nav-content"> <div class="nav-content">
<div class="nav-icon rankings"> <div class="nav-icon rankings">
@@ -120,11 +120,11 @@
</div> </div>
</el-card> </el-card>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="8">
<el-card class="nav-card" @click="navigateTo('/market-analysis')"> <el-card class="nav-card" @click="navigateTo('/market-analysis')">
<div class="nav-content"> <div class="nav-content">
<div class="nav-icon analysis"> <div class="nav-icon analysis">
<el-icon><Pie /></el-icon> <el-icon><DataBoard /></el-icon>
</div> </div>
<div class="nav-text"> <div class="nav-text">
<h3>市场分析</h3> <h3>市场分析</h3>
@@ -133,32 +133,15 @@
</div> </div>
</el-card> </el-card>
</el-col> </el-col>
<el-col :span="6">
<el-card class="nav-card" @click="navigateTo('/health')">
<div class="nav-content">
<div class="nav-icon health">
<el-icon><Monitor /></el-icon>
</div>
<div class="nav-text">
<h3>系统监控</h3>
<p>系统健康状态检查</p>
</div>
</div>
</el-card>
</el-col>
</el-row> </el-row>
<!-- 数据表格 --> <!-- 市场分析数据表格 -->
<el-row> <el-row>
<el-col :span="24"> <el-col :span="24">
<el-card class="table-card"> <el-card class="table-card">
<template #header> <template #header>
<div class="card-header"> <div class="card-header">
<span>最新市场分析数据</span> <span>最新市场分析数据</span>
<el-button type="success" size="small" @click="runSparkAnalysis">
<el-icon><Lightning /></el-icon>
运行Spark分析
</el-button>
</div> </div>
</template> </template>
<el-table :data="recentData" style="width: 100%"> <el-table :data="recentData" style="width: 100%">
@@ -203,7 +186,7 @@
import { ref, onMounted, nextTick } from 'vue' import { ref, onMounted, nextTick } from 'vue'
import * as echarts from 'echarts' import * as echarts from 'echarts'
import { getLatestMarketAnalysis, getRecentMarketAnalysis } from '@/api/market' import { getLatestMarketAnalysis, getRecentMarketAnalysis } from '@/api/market'
import { getMarketAnalysis, getRealtimeStockData } from '@/api/stock' import { getMarketAnalysis } from '@/api/stock'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
@@ -215,6 +198,7 @@ export default {
const recentData = ref([]) const recentData = ref([])
const loading = ref(false) const loading = ref(false)
// 加载最新市场数据 // 加载最新市场数据
const loadLatestData = async () => { const loadLatestData = async () => {
try { try {
@@ -245,6 +229,8 @@ export default {
} }
} }
// 初始化饼图 // 初始化饼图
const initPieChart = async () => { const initPieChart = async () => {
await nextTick() await nextTick()
@@ -336,6 +322,8 @@ export default {
return (num / 10000).toFixed(1) return (num / 10000).toFixed(1)
} }
// 刷新数据 // 刷新数据
const refreshData = () => { const refreshData = () => {
loadLatestData() loadLatestData()
@@ -347,19 +335,13 @@ export default {
loadRecentData() loadRecentData()
} }
// 模拟运行Spark分析
const runSparkAnalysis = () => {
ElMessage.success('Spark分析任务已提交请稍候查看结果')
setTimeout(() => {
refreshData()
}, 2000)
}
// 导航到指定页面 // 导航到指定页面
const navigateTo = (path) => { const navigateTo = (path) => {
router.push(path) router.push(path)
} }
onMounted(() => { onMounted(() => {
loadLatestData() loadLatestData()
loadRecentData() loadRecentData()
@@ -371,7 +353,6 @@ export default {
loading, loading,
refreshData, refreshData,
loadTrendData, loadTrendData,
runSparkAnalysis,
formatNumber, formatNumber,
navigateTo navigateTo
} }

View File

@@ -1,108 +0,0 @@
<template>
<div class="health-check">
<el-card>
<template #header>
<div class="card-header">
<span>系统健康检查</span>
<el-tag type="success">运行正常</el-tag>
</div>
</template>
<el-descriptions :column="2" border>
<el-descriptions-item label="前端状态">
<el-tag type="success">正常运行</el-tag>
</el-descriptions-item>
<el-descriptions-item label="端口">3000</el-descriptions-item>
<el-descriptions-item label="Vue版本">{{ vueVersion }}</el-descriptions-item>
<el-descriptions-item label="Element Plus">已加载</el-descriptions-item>
<el-descriptions-item label="路由">{{ routerReady ? '已配置' : '未配置' }}</el-descriptions-item>
<el-descriptions-item label="状态管理">{{ storeReady ? '已配置' : '未配置' }}</el-descriptions-item>
</el-descriptions>
<div style="margin-top: 20px;">
<el-button type="primary" @click="testApi">测试API连接</el-button>
<el-button type="success" @click="testStyles">测试样式变量</el-button>
</div>
<div v-if="apiStatus" style="margin-top: 15px;">
<el-alert :title="apiStatus.title" :type="apiStatus.type" show-icon />
</div>
</el-card>
</div>
</template>
<script>
import { ref, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useStore } from 'vuex'
import { ElMessage } from 'element-plus'
import { version } from 'vue'
export default {
name: 'HealthCheck',
setup() {
const router = useRouter()
const route = useRoute()
const store = useStore()
const apiStatus = ref(null)
const vueVersion = ref(version)
const routerReady = ref(!!router)
const storeReady = ref(!!store)
const testApi = async () => {
try {
const response = await fetch('/api/health')
if (response.ok) {
apiStatus.value = {
title: 'API连接成功',
type: 'success'
}
} else {
apiStatus.value = {
title: 'API连接失败',
type: 'error'
}
}
} catch (error) {
apiStatus.value = {
title: `API连接错误: ${error.message}`,
type: 'warning'
}
}
}
const testStyles = () => {
ElMessage.success('SCSS变量加载正常')
}
onMounted(() => {
console.log('健康检查页面已加载')
})
return {
vueVersion,
routerReady,
storeReady,
apiStatus,
testApi,
testStyles
}
}
}
</script>
<style lang="scss" scoped>
@import "@/styles/variables.scss";
.health-check {
padding: 20px;
background-color: $bg-color-page;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>

View File

@@ -28,7 +28,7 @@
</div> </div>
<div class="price-change" :class="{ 'positive': stockInfo.changePercent >= 0, 'negative': stockInfo.changePercent < 0 }"> <div class="price-change" :class="{ 'positive': stockInfo.changePercent >= 0, 'negative': stockInfo.changePercent < 0 }">
{{ stockInfo.changePercent >= 0 ? '+' : '' }}{{ stockInfo.changePercent?.toFixed(2) }}% {{ stockInfo.changePercent >= 0 ? '+' : '' }}{{ stockInfo.changePercent?.toFixed(2) }}%
({{ stockInfo.changePercent >= 0 ? '+' : '' }}{{ stockInfo.changeAmount?.toFixed(2) }}) ({{ stockInfo.changeAmount?.toFixed(2) }})
</div> </div>
</div> </div>
<div class="stock-metrics"> <div class="stock-metrics">
@@ -195,7 +195,7 @@
<script> <script>
import { ref, onMounted, nextTick } from 'vue' import { ref, onMounted, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { getStockHistory, getStockTrend, getStockPrediction, searchStocks } from '@/api/stock' import { getStockHistory, getStockTrend, getStockPrediction, searchStocks, getStockDetail } from '@/api/stock'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import * as echarts from 'echarts' import * as echarts from 'echarts'
@@ -222,12 +222,41 @@ export default {
// 加载股票基本信息 // 加载股票基本信息
const loadStockInfo = async () => { const loadStockInfo = async () => {
try { try {
const response = await searchStocks(stockCode.value) // 优先使用详情API
if (response.data && response.data.length > 0) { const detailResponse = await getStockDetail(stockCode.value)
stockInfo.value = response.data[0] if (detailResponse.data) {
stockInfo.value = {
stockCode: detailResponse.data.stockCode,
stockName: detailResponse.data.stockName,
currentPrice: detailResponse.data.closePrice,
changePercent: detailResponse.data.changePercent,
changeAmount: detailResponse.data.changeAmount,
volume: detailResponse.data.volume,
marketCap: detailResponse.data.marketCap,
highPrice: detailResponse.data.highPrice,
lowPrice: detailResponse.data.lowPrice
}
} else {
// 如果详情API无数据尝试搜索API
const searchResponse = await searchStocks(stockCode.value)
if (searchResponse.data && searchResponse.data.length > 0) {
const stockData = searchResponse.data[0]
stockInfo.value = {
stockCode: stockData.stockCode,
stockName: stockData.stockName,
currentPrice: stockData.closePrice,
changePercent: stockData.changePercent,
changeAmount: stockData.changeAmount,
volume: stockData.volume,
marketCap: stockData.marketCap,
highPrice: stockData.highPrice,
lowPrice: stockData.lowPrice
}
}
} }
} catch (error) { } catch (error) {
console.error('加载股票信息失败:', error) console.error('加载股票信息失败:', error)
ElMessage.error('加载股票信息失败')
} }
} }
@@ -296,52 +325,117 @@ export default {
// 初始化价格图表 // 初始化价格图表
const initPriceChart = async () => { const initPriceChart = async () => {
const chartDom = document.getElementById('price-chart') const chartDom = document.getElementById('price-chart')
if (!chartDom || historyData.value.length === 0) return if (!chartDom) return
const myChart = echarts.init(chartDom) const myChart = echarts.init(chartDom)
const dates = historyData.value.map(item => item.timestamp?.split('T')[0] || item.date)
const prices = historyData.value.map(item => item.currentPrice) if (historyData.value.length === 0) {
// 如果没有数据,显示空状态
const option = {
title: {
text: '暂无历史数据',
left: 'center',
top: 'center',
textStyle: {
color: '#999',
fontSize: 16
}
}
}
myChart.setOption(option)
return
}
// 处理日期格式
const dates = historyData.value.map(item => {
if (item.tradeDate) {
const date = new Date(item.tradeDate)
return date.toISOString().split('T')[0]
}
return item.timestamp?.split('T')[0] || item.date || '未知日期'
})
// 处理价格数据
const prices = historyData.value.map(item => {
return item.closePrice || item.currentPrice || 0
})
const option = { const option = {
title: { title: {
text: '价格走势', text: '历史价格走势',
left: 'center' left: 'left',
textStyle: {
fontSize: 16,
color: '#303133'
}
}, },
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
formatter: (params) => { formatter: (params) => {
const dataIndex = params[0].dataIndex const dataIndex = params[0].dataIndex
const data = historyData.value[dataIndex] const data = historyData.value[dataIndex]
const price = data.closePrice || data.currentPrice || 0
const changePercent = data.changePercent || 0
const volume = data.volume || 0
return ` return `
<div> <div style="padding: 10px;">
<p>日期: ${dates[dataIndex]}</p> <p><strong>日期:</strong> ${dates[dataIndex]}</p>
<p>价格: ¥${data.currentPrice?.toFixed(2)}</p> <p><strong>收盘价:</strong> ¥${price.toFixed(2)}</p>
<p>涨跌幅: ${data.changePercent?.toFixed(2)}%</p> <p><strong>涨跌幅:</strong> ${changePercent.toFixed(2)}%</p>
<p>成交量: ${formatVolume(data.volume)}</p> <p><strong>成交量:</strong> ${formatVolume(volume)}</p>
</div> </div>
` `
} }
}, },
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: '15%',
containLabel: true
},
xAxis: { xAxis: {
type: 'category', type: 'category',
data: dates data: dates,
axisLine: {
lineStyle: {
color: '#8392A5'
}
}
}, },
yAxis: { yAxis: {
type: 'value', type: 'value',
name: '价格(¥)', name: '价格(¥)',
scale: true scale: true,
axisLine: {
lineStyle: {
color: '#8392A5'
}
},
splitLine: {
show: true,
lineStyle: {
color: '#E6E8EB'
}
}
}, },
series: [ series: [
{ {
data: prices, data: prices,
type: 'line', type: 'line',
smooth: true, smooth: true,
symbol: 'circle',
symbolSize: 4,
lineStyle: { lineStyle: {
color: '#409EFF', color: '#409EFF',
width: 2 width: 2
}, },
areaStyle: { areaStyle: {
color: 'rgba(64, 158, 255, 0.1)' color: 'rgba(64, 158, 255, 0.1)'
},
itemStyle: {
color: '#409EFF'
} }
} }
] ]
@@ -352,30 +446,89 @@ export default {
// 初始化趋势图表 // 初始化趋势图表
const initTrendChart = async () => { const initTrendChart = async () => {
const chartDom = document.getElementById('trend-chart') const chartDom = document.getElementById('trend-chart')
if (!chartDom || !trendData.value.priceHistory) return if (!chartDom) return
const myChart = echarts.init(chartDom) const myChart = echarts.init(chartDom)
if (!trendData.value || !trendData.value.priceHistory || trendData.value.priceHistory.length === 0) {
// 如果没有趋势数据,显示空状态
const option = {
title: {
text: '暂无趋势数据',
left: 'center',
top: 'center',
textStyle: {
color: '#999',
fontSize: 14
}
}
}
myChart.setOption(option)
return
}
const data = trendData.value.priceHistory || [] const data = trendData.value.priceHistory || []
const dates = data.map(item => item.date) const dates = data.map(item => {
const prices = data.map(item => item.price) if (item.date) return item.date
const ma5 = data.map(item => item.ma5) if (item.tradeDate) {
const ma20 = data.map(item => item.ma20) const date = new Date(item.tradeDate)
return date.toISOString().split('T')[0]
}
return '未知日期'
})
const prices = data.map(item => item.price || item.closePrice || 0)
const ma5 = data.map(item => item.ma5 || null)
const ma20 = data.map(item => item.ma20 || null)
const option = { const option = {
tooltip: { tooltip: {
trigger: 'axis' trigger: 'axis',
formatter: (params) => {
let result = `<div style="padding: 10px;"><strong>${params[0].axisValue}</strong><br/>`
params.forEach(param => {
if (param.value !== null) {
result += `<span style="color: ${param.color};">●</span> ${param.seriesName}: ¥${param.value.toFixed(2)}<br/>`
}
})
result += '</div>'
return result
}
}, },
legend: { legend: {
data: ['股价', 'MA5', 'MA20'] data: ['股价', 'MA5', 'MA20'],
top: 10
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: '15%',
containLabel: true
}, },
xAxis: { xAxis: {
type: 'category', type: 'category',
data: dates data: dates,
axisLine: {
lineStyle: {
color: '#8392A5'
}
}
}, },
yAxis: { yAxis: {
type: 'value', type: 'value',
name: '价格(¥)', name: '价格(¥)',
scale: true scale: true,
axisLine: {
lineStyle: {
color: '#8392A5'
}
},
splitLine: {
show: true,
lineStyle: {
color: '#E6E8EB'
}
}
}, },
series: [ series: [
{ {
@@ -383,21 +536,28 @@ export default {
data: prices, data: prices,
type: 'line', type: 'line',
smooth: true, smooth: true,
lineStyle: { color: '#409EFF' } symbol: 'circle',
symbolSize: 4,
lineStyle: { color: '#409EFF', width: 2 },
itemStyle: { color: '#409EFF' }
}, },
{ {
name: 'MA5', name: 'MA5',
data: ma5, data: ma5,
type: 'line', type: 'line',
smooth: true, smooth: true,
lineStyle: { color: '#67C23A' } symbol: 'none',
lineStyle: { color: '#67C23A', width: 1 },
itemStyle: { color: '#67C23A' }
}, },
{ {
name: 'MA20', name: 'MA20',
data: ma20, data: ma20,
type: 'line', type: 'line',
smooth: true, smooth: true,
lineStyle: { color: '#E6A23C' } symbol: 'none',
lineStyle: { color: '#E6A23C', width: 1 },
itemStyle: { color: '#E6A23C' }
} }
] ]
} }
@@ -407,17 +567,47 @@ export default {
// 初始化预测图表 // 初始化预测图表
const initPredictionChart = async () => { const initPredictionChart = async () => {
const chartDom = document.getElementById('prediction-chart') const chartDom = document.getElementById('prediction-chart')
if (!chartDom || predictionData.value.length === 0) return if (!chartDom) return
const myChart = echarts.init(chartDom) const myChart = echarts.init(chartDom)
if (predictionData.value.length === 0) {
// 如果没有预测数据,显示空状态
const option = {
title: {
text: '暂无预测数据,请点击"生成预测"按钮',
left: 'center',
top: 'center',
textStyle: {
color: '#999',
fontSize: 14
}
}
}
myChart.setOption(option)
return
}
// 历史数据最近10天 // 历史数据最近10天
const historyDates = historyData.value.slice(-10).map(item => item.timestamp?.split('T')[0] || item.date) const historySlice = historyData.value.slice(-10)
const historyPrices = historyData.value.slice(-10).map(item => item.currentPrice) const historyDates = historySlice.map(item => {
if (item.tradeDate) {
const date = new Date(item.tradeDate)
return date.toISOString().split('T')[0]
}
return item.timestamp?.split('T')[0] || item.date || '历史'
})
const historyPrices = historySlice.map(item => item.closePrice || item.currentPrice || 0)
// 预测数据 // 预测数据
const predictionDates = predictionData.value.map(item => item.timestamp?.split('T')[0] || item.date) const predictionDates = predictionData.value.map(item => {
const predictionPrices = predictionData.value.map(item => item.currentPrice) if (item.tradeDate) {
const date = new Date(item.tradeDate)
return date.toISOString().split('T')[0]
}
return item.timestamp?.split('T')[0] || item.date || '预测'
})
const predictionPrices = predictionData.value.map(item => item.closePrice || item.currentPrice || 0)
const allDates = [...historyDates, ...predictionDates] const allDates = [...historyDates, ...predictionDates]
const historySeries = [...historyPrices, ...new Array(predictionDates.length).fill(null)] const historySeries = [...historyPrices, ...new Array(predictionDates.length).fill(null)]
@@ -425,23 +615,61 @@ export default {
const option = { const option = {
title: { title: {
text: '股价预测', text: '股价预测分析',
left: 'center' left: 'left',
textStyle: {
fontSize: 16,
color: '#303133'
}
}, },
tooltip: { tooltip: {
trigger: 'axis' trigger: 'axis',
formatter: (params) => {
let result = `<div style="padding: 10px;"><strong>${params[0].axisValue}</strong><br/>`
params.forEach(param => {
if (param.value !== null) {
result += `<span style="color: ${param.color};">●</span> ${param.seriesName}: ¥${param.value.toFixed(2)}<br/>`
}
})
result += '</div>'
return result
}
}, },
legend: { legend: {
data: ['历史价格', '预测价格'] data: ['历史价格', '预测价格'],
top: 30
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: '20%',
containLabel: true
}, },
xAxis: { xAxis: {
type: 'category', type: 'category',
data: allDates data: allDates,
axisLine: {
lineStyle: {
color: '#8392A5'
}
}
}, },
yAxis: { yAxis: {
type: 'value', type: 'value',
name: '价格(¥)', name: '价格(¥)',
scale: true scale: true,
axisLine: {
lineStyle: {
color: '#8392A5'
}
},
splitLine: {
show: true,
lineStyle: {
color: '#E6E8EB'
}
}
}, },
series: [ series: [
{ {
@@ -449,19 +677,26 @@ export default {
data: historySeries, data: historySeries,
type: 'line', type: 'line',
smooth: true, smooth: true,
lineStyle: { color: '#409EFF' }, symbol: 'circle',
areaStyle: { color: 'rgba(64, 158, 255, 0.1)' } symbolSize: 4,
lineStyle: { color: '#409EFF', width: 2 },
areaStyle: { color: 'rgba(64, 158, 255, 0.1)' },
itemStyle: { color: '#409EFF' }
}, },
{ {
name: '预测价格', name: '预测价格',
data: predictionSeries, data: predictionSeries,
type: 'line', type: 'line',
smooth: true, smooth: true,
symbol: 'diamond',
symbolSize: 5,
lineStyle: { lineStyle: {
color: '#F56C6C', color: '#F56C6C',
type: 'dashed' type: 'dashed',
width: 2
}, },
areaStyle: { color: 'rgba(245, 108, 108, 0.1)' } areaStyle: { color: 'rgba(245, 108, 108, 0.1)' },
itemStyle: { color: '#F56C6C' }
} }
] ]
} }
@@ -502,24 +737,32 @@ export default {
// 预测统计 // 预测统计
const getPredictionMax = () => { const getPredictionMax = () => {
if (predictionData.value.length === 0) return '0.00' if (predictionData.value.length === 0) return '0.00'
return Math.max(...predictionData.value.map(item => item.currentPrice)).toFixed(2) const prices = predictionData.value.map(item => item.closePrice || item.currentPrice || 0)
return Math.max(...prices).toFixed(2)
} }
const getPredictionMin = () => { const getPredictionMin = () => {
if (predictionData.value.length === 0) return '0.00' if (predictionData.value.length === 0) return '0.00'
return Math.min(...predictionData.value.map(item => item.currentPrice)).toFixed(2) const prices = predictionData.value.map(item => item.closePrice || item.currentPrice || 0)
return Math.min(...prices).toFixed(2)
} }
const getPredictionAvg = () => { const getPredictionAvg = () => {
if (predictionData.value.length === 0) return '0.00' if (predictionData.value.length === 0) return '0.00'
const avg = predictionData.value.reduce((sum, item) => sum + item.currentPrice, 0) / predictionData.value.length const prices = predictionData.value.map(item => item.closePrice || item.currentPrice || 0)
const avg = prices.reduce((sum, price) => sum + price, 0) / prices.length
return avg.toFixed(2) return avg.toFixed(2)
} }
const getPredictionReturn = () => { const getPredictionReturn = () => {
if (predictionData.value.length === 0 || !stockInfo.value.currentPrice) return 0 if (predictionData.value.length === 0 || !stockInfo.value.currentPrice) return 0
const lastPrediction = predictionData.value[predictionData.value.length - 1] const lastPrediction = predictionData.value[predictionData.value.length - 1]
const returnRate = ((lastPrediction.currentPrice - stockInfo.value.currentPrice) / stockInfo.value.currentPrice) * 100 const lastPrice = lastPrediction.closePrice || lastPrediction.currentPrice || 0
const currentPrice = stockInfo.value.currentPrice || 0
if (currentPrice === 0) return 0
const returnRate = ((lastPrice - currentPrice) / currentPrice) * 100
return returnRate.toFixed(2) return returnRate.toFixed(2)
} }

View File

@@ -0,0 +1,606 @@
<template>
<div class="stock-detail-view">
<!-- 页面标题 -->
<div class="page-header">
<div class="stock-info">
<h1>{{ stockInfo.stockName || stockCode }}</h1>
<p class="stock-code">{{ stockCode }}</p>
</div>
<div class="action-buttons">
<el-button @click="goBack">
<el-icon><ArrowLeft /></el-icon>
返回
</el-button>
<el-button type="primary" @click="refreshData" :loading="loading">
<el-icon><Refresh /></el-icon>
刷新
</el-button>
</div>
</div>
<!-- 股票基本信息 -->
<el-row :gutter="20" class="stock-overview">
<el-col :span="8">
<el-card class="price-card">
<div class="price-section">
<div class="current-price">
¥{{ stockInfo.closePrice?.toFixed(2) || '0.00' }}
</div>
<div class="price-change" :class="getPriceChangeClass()">
<el-icon v-if="stockInfo.changePercent > 0"><CaretTop /></el-icon>
<el-icon v-else-if="stockInfo.changePercent < 0"><CaretBottom /></el-icon>
<el-icon v-else><Minus /></el-icon>
{{ formatChangePercent(stockInfo.changePercent) }}%
({{ formatChangeAmount(stockInfo.changeAmount) }})
</div>
</div>
</el-card>
</el-col>
<el-col :span="16">
<el-card class="metrics-card">
<el-row :gutter="20">
<el-col :span="6">
<div class="metric-item">
<div class="metric-label">开盘价</div>
<div class="metric-value">¥{{ stockInfo.openPrice?.toFixed(2) || '--' }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="metric-item">
<div class="metric-label">最高价</div>
<div class="metric-value">¥{{ stockInfo.highPrice?.toFixed(2) || '--' }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="metric-item">
<div class="metric-label">最低价</div>
<div class="metric-value">¥{{ stockInfo.lowPrice?.toFixed(2) || '--' }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="metric-item">
<div class="metric-label">昨收价</div>
<div class="metric-value">¥{{ stockInfo.preClosePrice?.toFixed(2) || '--' }}</div>
</div>
</el-col>
</el-row>
<el-row :gutter="20" style="margin-top: 20px;">
<el-col :span="6">
<div class="metric-item">
<div class="metric-label">成交量</div>
<div class="metric-value">{{ formatVolume(stockInfo.volume) }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="metric-item">
<div class="metric-label">成交额</div>
<div class="metric-value">{{ formatTurnover(stockInfo.turnover) }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="metric-item">
<div class="metric-label">市值</div>
<div class="metric-value">{{ formatMarketCap(stockInfo.marketCap) }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="metric-item">
<div class="metric-label">换手率</div>
<div class="metric-value">{{ stockInfo.turnoverRate?.toFixed(2) || '--' }}%</div>
</div>
</el-col>
</el-row>
</el-card>
</el-col>
</el-row>
<!-- 分时图 -->
<el-row :gutter="20" class="chart-section">
<el-col :span="24">
<el-card class="chart-card">
<template #header>
<div class="card-header">
<span>实时分时图</span>
<div class="chart-controls">
<el-button-group size="small">
<el-button :type="chartType === 'minute' ? 'primary' : ''" @click="changeChartType('minute')">
分时
</el-button>
<el-button :type="chartType === 'kline' ? 'primary' : ''" @click="changeChartType('kline')">
K线
</el-button>
</el-button-group>
</div>
</div>
</template>
<div id="detail-chart" style="height: 500px;" v-loading="chartLoading"></div>
</el-card>
</el-col>
</el-row>
<!-- 详细信息表格 -->
<el-row>
<el-col :span="24">
<el-card class="info-card">
<template #header>
<span>详细信息</span>
</template>
<el-descriptions :column="3" border>
<el-descriptions-item label="股票代码">{{ stockInfo.stockCode }}</el-descriptions-item>
<el-descriptions-item label="股票名称">{{ stockInfo.stockName }}</el-descriptions-item>
<el-descriptions-item label="交易日期">{{ formatDate(stockInfo.tradeDate) }}</el-descriptions-item>
<el-descriptions-item label="开盘价">¥{{ stockInfo.openPrice?.toFixed(2) || '--' }}</el-descriptions-item>
<el-descriptions-item label="收盘价">¥{{ stockInfo.closePrice?.toFixed(2) || '--' }}</el-descriptions-item>
<el-descriptions-item label="最高价">¥{{ stockInfo.highPrice?.toFixed(2) || '--' }}</el-descriptions-item>
<el-descriptions-item label="最低价">¥{{ stockInfo.lowPrice?.toFixed(2) || '--' }}</el-descriptions-item>
<el-descriptions-item label="昨收价">¥{{ stockInfo.preClosePrice?.toFixed(2) || '--' }}</el-descriptions-item>
<el-descriptions-item label="涨跌额">{{ formatChangeAmount(stockInfo.changeAmount) }}</el-descriptions-item>
<el-descriptions-item label="涨跌幅">{{ formatChangePercent(stockInfo.changePercent) }}%</el-descriptions-item>
<el-descriptions-item label="成交量">{{ formatVolume(stockInfo.volume) }}</el-descriptions-item>
<el-descriptions-item label="成交额">{{ formatTurnover(stockInfo.turnover) }}</el-descriptions-item>
<el-descriptions-item label="总市值">{{ formatMarketCap(stockInfo.marketCap) }}</el-descriptions-item>
<el-descriptions-item label="流通市值">{{ formatMarketCap(stockInfo.circulationMarketCap) }}</el-descriptions-item>
<el-descriptions-item label="换手率">{{ stockInfo.turnoverRate?.toFixed(2) || '--' }}%</el-descriptions-item>
</el-descriptions>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import { ref, onMounted, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { getStockDetail } from '@/api/stock'
import { ElMessage } from 'element-plus'
import * as echarts from 'echarts'
export default {
name: 'StockDetailView',
setup() {
const route = useRoute()
const router = useRouter()
const stockCode = ref(route.params.stockCode)
const stockInfo = ref({})
const loading = ref(false)
const chartLoading = ref(false)
const chartType = ref('minute')
// 加载股票详情
const loadStockDetail = async () => {
try {
loading.value = true
const response = await getStockDetail(stockCode.value)
if (response.code === 200 && response.data) {
stockInfo.value = response.data
await nextTick()
initChart()
} else {
ElMessage.error(response.message || '获取股票详情失败')
}
} catch (error) {
console.error('获取股票详情失败:', error)
ElMessage.error('获取股票详情失败')
} finally {
loading.value = false
}
}
// 初始化图表
const initChart = async () => {
await nextTick()
const chartDom = document.getElementById('detail-chart')
if (!chartDom) return
const myChart = echarts.init(chartDom)
if (chartType.value === 'minute') {
initMinuteChart(myChart)
} else {
initKLineChart(myChart)
}
}
// 初始化分时图
const initMinuteChart = (chart) => {
const option = {
title: {
text: `${stockInfo.value.stockName}(${stockInfo.value.stockCode})`,
left: 'left'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: generateTimeData(),
axisLine: {
lineStyle: {
color: '#8392A5'
}
}
},
yAxis: {
type: 'value',
scale: true,
axisLine: {
lineStyle: {
color: '#8392A5'
}
},
splitLine: {
show: true,
lineStyle: {
color: '#E6E8EB'
}
}
},
series: [
{
name: '价格',
type: 'line',
data: generatePriceData(),
smooth: true,
lineStyle: {
color: stockInfo.value.changePercent >= 0 ? '#f56c6c' : '#67c23a'
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0,
color: stockInfo.value.changePercent >= 0 ? 'rgba(245, 108, 108, 0.3)' : 'rgba(103, 194, 58, 0.3)'
}, {
offset: 1,
color: stockInfo.value.changePercent >= 0 ? 'rgba(245, 108, 108, 0.1)' : 'rgba(103, 194, 58, 0.1)'
}]
}
}
}
]
}
chart.setOption(option)
}
// 初始化K线图
const initKLineChart = (chart) => {
const option = {
title: {
text: `${stockInfo.value.stockName}(${stockInfo.value.stockCode}) K线图`,
left: 'left'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
}
},
grid: {
left: '3%',
right: '4%',
bottom: '15%',
containLabel: true
},
xAxis: {
type: 'category',
data: generateDateData(),
axisLine: {
lineStyle: {
color: '#8392A5'
}
}
},
yAxis: {
type: 'value',
scale: true,
axisLine: {
lineStyle: {
color: '#8392A5'
}
}
},
series: [
{
name: 'K线',
type: 'candlestick',
data: generateKLineData(),
itemStyle: {
color: '#f56c6c',
color0: '#67c23a',
borderColor: '#f56c6c',
borderColor0: '#67c23a'
}
}
]
}
chart.setOption(option)
}
// 生成模拟时间数据
const generateTimeData = () => {
const times = []
for (let i = 9; i <= 15; i++) {
for (let j = 0; j < 60; j += 5) {
if (i === 9 && j < 30) continue
if (i === 15 && j > 0) break
if (i === 11 && j >= 30) continue
if (i === 12) continue
if (i === 13 && j === 0) continue
times.push(`${i.toString().padStart(2, '0')}:${j.toString().padStart(2, '0')}`)
}
}
return times
}
// 生成模拟价格数据
const generatePriceData = () => {
const basePrice = stockInfo.value.closePrice || 10
const data = []
const times = generateTimeData()
for (let i = 0; i < times.length; i++) {
const fluctuation = (Math.random() - 0.5) * 0.1
const price = basePrice * (1 + fluctuation)
data.push(price.toFixed(2))
}
return data
}
// 生成模拟日期数据
const generateDateData = () => {
const dates = []
const today = new Date()
for (let i = 29; i >= 0; i--) {
const date = new Date(today)
date.setDate(date.getDate() - i)
dates.push(date.toISOString().split('T')[0])
}
return dates
}
// 生成模拟K线数据
const generateKLineData = () => {
const basePrice = stockInfo.value.closePrice || 10
const data = []
let currentPrice = basePrice
for (let i = 0; i < 30; i++) {
const change = (Math.random() - 0.5) * 0.2
const open = currentPrice
const close = currentPrice * (1 + change)
const high = Math.max(open, close) * (1 + Math.random() * 0.05)
const low = Math.min(open, close) * (1 - Math.random() * 0.05)
data.push([open.toFixed(2), close.toFixed(2), low.toFixed(2), high.toFixed(2)])
currentPrice = close
}
return data
}
// 切换图表类型
const changeChartType = (type) => {
chartType.value = type
initChart()
}
// 刷新数据
const refreshData = () => {
loadStockDetail()
}
// 返回
const goBack = () => {
router.go(-1)
}
// 格式化函数
const getPriceChangeClass = () => {
if (!stockInfo.value.changePercent) return ''
return stockInfo.value.changePercent > 0 ? 'positive' : stockInfo.value.changePercent < 0 ? 'negative' : ''
}
const formatChangePercent = (value) => {
if (!value) return '0.00'
return (value > 0 ? '+' : '') + value.toFixed(2)
}
const formatChangeAmount = (value) => {
if (!value) return '0.00'
return (value > 0 ? '+' : '') + value.toFixed(2)
}
const formatVolume = (volume) => {
if (!volume) return '0'
if (volume >= 100000000) {
return (volume / 100000000).toFixed(2) + '亿'
} else if (volume >= 10000) {
return (volume / 10000).toFixed(2) + '万'
}
return volume.toString()
}
const formatTurnover = (turnover) => {
if (!turnover) return '0'
if (turnover >= 100000000) {
return (turnover / 100000000).toFixed(2) + '亿元'
} else if (turnover >= 10000) {
return (turnover / 10000).toFixed(2) + '万元'
}
return turnover.toFixed(2) + '元'
}
const formatMarketCap = (marketCap) => {
if (!marketCap) return '0'
if (marketCap >= 100000000) {
return (marketCap / 100000000).toFixed(2) + '亿元'
} else if (marketCap >= 10000) {
return (marketCap / 10000).toFixed(2) + '万元'
}
return marketCap.toFixed(2) + '元'
}
const formatDate = (date) => {
if (!date) return '--'
return new Date(date).toLocaleDateString()
}
onMounted(() => {
loadStockDetail()
})
return {
stockCode,
stockInfo,
loading,
chartLoading,
chartType,
loadStockDetail,
changeChartType,
refreshData,
goBack,
getPriceChangeClass,
formatChangePercent,
formatChangeAmount,
formatVolume,
formatTurnover,
formatMarketCap,
formatDate
}
}
}
</script>
<style lang="scss" scoped>
.stock-detail-view {
padding: 20px;
background-color: #f5f7fa;
min-height: 100vh;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
.stock-info {
h1 {
margin: 0;
color: #303133;
font-size: 24px;
}
.stock-code {
margin: 5px 0 0 0;
color: #909399;
font-size: 14px;
}
}
.action-buttons {
display: flex;
gap: 10px;
}
}
.stock-overview {
margin-bottom: 20px;
}
.price-card {
.price-section {
text-align: center;
padding: 20px;
.current-price {
font-size: 36px;
font-weight: bold;
color: #303133;
margin-bottom: 10px;
}
.price-change {
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
gap: 5px;
&.positive {
color: #f56c6c;
}
&.negative {
color: #67c23a;
}
}
}
}
.metrics-card {
.metric-item {
text-align: center;
.metric-label {
font-size: 12px;
color: #909399;
margin-bottom: 5px;
}
.metric-value {
font-size: 16px;
font-weight: 500;
color: #303133;
}
}
}
.chart-section {
margin-bottom: 20px;
}
.chart-card, .info-card {
border: none;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
span {
font-weight: 600;
color: #303133;
}
}
.chart-controls {
display: flex;
align-items: center;
gap: 10px;
}
:deep(.el-descriptions__label) {
font-weight: 600;
}
:deep(.el-descriptions__content) {
color: #303133;
}
</style>

View File

@@ -0,0 +1,795 @@
<template>
<div class="stock-prediction-view">
<!-- 页面标题 -->
<div class="page-header">
<div class="stock-info">
<h1>{{ stockCode }} - 股票预测分析</h1>
<p class="stock-code">预测模型基于历史数据和技术指标</p>
</div>
<div class="action-buttons">
<el-button @click="goBack">
<el-icon><ArrowLeft /></el-icon>
返回
</el-button>
<el-button type="primary" @click="generatePrediction" :loading="loading">
<el-icon><Refresh /></el-icon>
生成预测
</el-button>
</div>
</div>
<!-- 预测配置 -->
<el-row :gutter="20" class="prediction-config">
<el-col :span="24">
<el-card class="config-card">
<template #header>
<span>预测配置</span>
</template>
<el-row :gutter="20">
<el-col :span="6">
<div class="config-item">
<label>预测天数:</label>
<el-select v-model="predictionDays" @change="generatePrediction" style="width: 100%;">
<el-option label="3天" :value="3" />
<el-option label="7天" :value="7" />
<el-option label="15天" :value="15" />
<el-option label="30天" :value="30" />
</el-select>
</div>
</el-col>
<el-col :span="6">
<div class="config-item">
<label>预测模型:</label>
<el-select v-model="predictionModel" style="width: 100%;">
<el-option label="线性回归" value="linear" />
<el-option label="移动平均" value="ma" />
<el-option label="ARIMA" value="arima" />
<el-option label="神经网络" value="nn" />
</el-select>
</div>
</el-col>
<el-col :span="6">
<div class="config-item">
<label>置信区间:</label>
<el-select v-model="confidenceLevel" style="width: 100%;">
<el-option label="90%" value="0.9" />
<el-option label="95%" value="0.95" />
<el-option label="99%" value="0.99" />
</el-select>
</div>
</el-col>
<el-col :span="6">
<div class="config-item">
<label>历史数据:</label>
<el-select v-model="historyDays" style="width: 100%;">
<el-option label="30天" :value="30" />
<el-option label="60天" :value="60" />
<el-option label="90天" :value="90" />
<el-option label="180天" :value="180" />
</el-select>
</div>
</el-col>
</el-row>
</el-card>
</el-col>
</el-row>
<!-- 预测结果概览 -->
<el-row :gutter="20" class="prediction-overview" v-if="predictionData.length > 0">
<el-col :span="6">
<el-card class="prediction-card">
<div class="prediction-info">
<div class="prediction-value positive">¥{{ getPredictionMax() }}</div>
<div class="prediction-subtitle">预测最高价</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="prediction-card">
<div class="prediction-info">
<div class="prediction-value negative">¥{{ getPredictionMin() }}</div>
<div class="prediction-subtitle">预测最低价</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="prediction-card">
<div class="prediction-info">
<div class="prediction-value">¥{{ getPredictionAvg() }}</div>
<div class="prediction-subtitle">平均预测价</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="prediction-card">
<div class="prediction-info">
<div class="prediction-value" :class="{ 'positive': getPredictionReturn() > 0, 'negative': getPredictionReturn() < 0 }">
{{ formatChangePercent(getPredictionReturn()) }}%
</div>
<div class="prediction-subtitle">预期收益率</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 预测图表 -->
<el-row :gutter="20" class="chart-section">
<el-col :span="16">
<el-card class="chart-card">
<template #header>
<div class="card-header">
<span>价格预测图</span>
<div class="chart-controls">
<el-switch
v-model="showConfidenceInterval"
@change="updateChart"
active-text="显示置信区间"
inactive-text="隐藏置信区间"
/>
</div>
</div>
</template>
<div id="prediction-chart" style="height: 500px;" v-loading="chartLoading"></div>
</el-card>
</el-col>
<el-col :span="8">
<el-card class="analysis-card">
<template #header>
<span>预测分析</span>
</template>
<div class="analysis-content">
<div class="analysis-item">
<div class="analysis-label">预测准确度</div>
<div class="analysis-value">
<el-progress :percentage="predictionAccuracy" :color="getAccuracyColor()" />
<span class="accuracy-text">{{ predictionAccuracy }}%</span>
</div>
</div>
<div class="analysis-item">
<div class="analysis-label">风险等级</div>
<div class="analysis-value">
<el-tag :type="getRiskTagType()">{{ getRiskLevel() }}</el-tag>
</div>
</div>
<div class="analysis-item">
<div class="analysis-label">投资建议</div>
<div class="analysis-value">
<el-tag :type="getRecommendationTagType()">{{ getRecommendation() }}</el-tag>
</div>
</div>
<div class="analysis-item">
<div class="analysis-label">支撑位</div>
<div class="analysis-value">¥{{ getSupportLevel() }}</div>
</div>
<div class="analysis-item">
<div class="analysis-label">阻力位</div>
<div class="analysis-value">¥{{ getResistanceLevel() }}</div>
</div>
<div class="analysis-item">
<div class="analysis-label">波动率</div>
<div class="analysis-value">{{ getVolatility() }}%</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 预测数据表格 -->
<el-row>
<el-col :span="24">
<el-card class="table-card">
<template #header>
<span>预测数据详情</span>
</template>
<el-table :data="predictionTableData" style="width: 100%" v-loading="loading">
<el-table-column prop="date" label="预测日期" width="120">
<template #default="scope">
{{ formatDate(scope.row.date) }}
</template>
</el-table-column>
<el-table-column prop="predictedPrice" label="预测价格" width="120">
<template #default="scope">
¥{{ scope.row.predictedPrice?.toFixed(2) }}
</template>
</el-table-column>
<el-table-column prop="lowerBound" label="下限价格" width="120">
<template #default="scope">
¥{{ scope.row.lowerBound?.toFixed(2) }}
</template>
</el-table-column>
<el-table-column prop="upperBound" label="上限价格" width="120">
<template #default="scope">
¥{{ scope.row.upperBound?.toFixed(2) }}
</template>
</el-table-column>
<el-table-column prop="changePercent" label="预测涨跌幅" width="120">
<template #default="scope">
<span :class="{ 'positive': scope.row.changePercent > 0, 'negative': scope.row.changePercent < 0 }">
{{ formatChangePercent(scope.row.changePercent) }}%
</span>
</template>
</el-table-column>
<el-table-column prop="confidence" label="置信度" width="100">
<template #default="scope">
{{ (scope.row.confidence * 100).toFixed(1) }}%
</template>
</el-table-column>
<el-table-column prop="riskLevel" label="风险等级" width="100">
<template #default="scope">
<el-tag :type="getRiskTagTypeByLevel(scope.row.riskLevel)" size="small">
{{ scope.row.riskLevel }}
</el-tag>
</template>
</el-table-column>
</el-table>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import { ref, onMounted, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { getStockPrediction, getStockDetail } from '@/api/stock'
import { ElMessage } from 'element-plus'
import * as echarts from 'echarts'
export default {
name: 'StockPredictionView',
setup() {
const route = useRoute()
const router = useRouter()
const stockCode = ref(route.params.stockCode)
const predictionData = ref([])
const predictionTableData = ref([])
const loading = ref(false)
const chartLoading = ref(false)
// 配置参数
const predictionDays = ref(7)
const predictionModel = ref('linear')
const confidenceLevel = ref('0.95')
const historyDays = ref(60)
const showConfidenceInterval = ref(true)
// 预测分析结果
const predictionAccuracy = ref(85)
const currentPrice = ref(0)
// 生成预测数据
const generatePrediction = async () => {
try {
loading.value = true
chartLoading.value = true
// 获取当前价格
const detailResponse = await getStockDetail(stockCode.value)
if (detailResponse.code === 200 && detailResponse.data) {
currentPrice.value = detailResponse.data.closePrice || 10
}
// 获取预测数据这里使用模拟数据实际应该调用预测API
const response = await getStockPrediction(stockCode.value, predictionDays.value)
// 生成模拟预测数据
const mockPredictionData = generateMockPredictionData()
predictionData.value = mockPredictionData
predictionTableData.value = generateTableData(mockPredictionData)
await nextTick()
initPredictionChart()
ElMessage.success('预测数据生成成功')
} catch (error) {
console.error('生成预测数据失败:', error)
ElMessage.error('生成预测数据失败')
} finally {
loading.value = false
chartLoading.value = false
}
}
// 生成模拟预测数据
const generateMockPredictionData = () => {
const data = []
let basePrice = currentPrice.value
const today = new Date()
for (let i = 1; i <= predictionDays.value; i++) {
const date = new Date(today)
date.setDate(date.getDate() + i)
// 模拟价格波动
const trend = (Math.random() - 0.5) * 0.1 // -5% 到 +5%
const noise = (Math.random() - 0.5) * 0.05 // 噪声
const predictedPrice = basePrice * (1 + trend + noise)
// 置信区间
const volatility = 0.05
const lowerBound = predictedPrice * (1 - volatility)
const upperBound = predictedPrice * (1 + volatility)
data.push({
date: date,
predictedPrice: predictedPrice,
lowerBound: lowerBound,
upperBound: upperBound,
changePercent: ((predictedPrice - basePrice) / basePrice) * 100,
confidence: parseFloat(confidenceLevel.value),
riskLevel: getRiskLevelByVolatility(Math.abs(trend + noise) * 100)
})
basePrice = predictedPrice
}
return data
}
// 生成表格数据
const generateTableData = (data) => {
return data.map(item => ({
...item,
date: item.date.toISOString().split('T')[0]
}))
}
// 初始化预测图表
const initPredictionChart = () => {
const chartDom = document.getElementById('prediction-chart')
if (!chartDom) return
const myChart = echarts.init(chartDom)
// 历史数据(模拟)
const historyDates = []
const historyPrices = []
const today = new Date()
for (let i = 30; i >= 1; i--) {
const date = new Date(today)
date.setDate(date.getDate() - i)
historyDates.push(date.toISOString().split('T')[0])
const fluctuation = (Math.random() - 0.5) * 0.1
const price = currentPrice.value * (1 + fluctuation)
historyPrices.push(price.toFixed(2))
}
// 预测数据
const predictionDates = predictionData.value.map(item => item.date.toISOString().split('T')[0])
const predictionPrices = predictionData.value.map(item => item.predictedPrice.toFixed(2))
const lowerBounds = predictionData.value.map(item => item.lowerBound.toFixed(2))
const upperBounds = predictionData.value.map(item => item.upperBound.toFixed(2))
const allDates = [...historyDates, ...predictionDates]
const allPrices = [...historyPrices, ...predictionPrices]
const series = [
{
name: '历史价格',
type: 'line',
data: historyPrices.map((price, index) => [historyDates[index], price]),
lineStyle: {
color: '#409EFF',
width: 2
},
symbol: 'circle',
symbolSize: 4
},
{
name: '预测价格',
type: 'line',
data: predictionPrices.map((price, index) => [predictionDates[index], price]),
lineStyle: {
color: '#F56C6C',
width: 2,
type: 'dashed'
},
symbol: 'diamond',
symbolSize: 6
}
]
if (showConfidenceInterval.value) {
series.push({
name: '置信区间',
type: 'line',
data: lowerBounds.map((price, index) => [predictionDates[index], price]),
lineStyle: {
color: '#E6A23C',
width: 1,
opacity: 0.6
},
symbol: 'none',
areaStyle: {
color: 'rgba(230, 162, 60, 0.2)'
},
stack: 'confidence'
})
series.push({
name: '',
type: 'line',
data: upperBounds.map((price, index) => [predictionDates[index], price]),
lineStyle: {
color: '#E6A23C',
width: 1,
opacity: 0.6
},
symbol: 'none',
areaStyle: {
color: 'rgba(230, 162, 60, 0.2)'
},
stack: 'confidence'
})
}
const option = {
title: {
text: `${stockCode.value} 股价预测分析`,
left: 'left'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
}
},
legend: {
data: showConfidenceInterval.value ? ['历史价格', '预测价格', '置信区间'] : ['历史价格', '预测价格'],
top: 30
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: '15%',
containLabel: true
},
xAxis: {
type: 'time',
axisLine: {
lineStyle: {
color: '#8392A5'
}
}
},
yAxis: {
type: 'value',
scale: true,
axisLine: {
lineStyle: {
color: '#8392A5'
}
},
splitLine: {
show: true,
lineStyle: {
color: '#E6E8EB'
}
}
},
series: series
}
myChart.setOption(option)
}
// 更新图表
const updateChart = () => {
initPredictionChart()
}
// 返回
const goBack = () => {
router.go(-1)
}
// 计算预测统计
const getPredictionMax = () => {
if (predictionData.value.length === 0) return '0.00'
const max = Math.max(...predictionData.value.map(item => item.predictedPrice))
return max.toFixed(2)
}
const getPredictionMin = () => {
if (predictionData.value.length === 0) return '0.00'
const min = Math.min(...predictionData.value.map(item => item.predictedPrice))
return min.toFixed(2)
}
const getPredictionAvg = () => {
if (predictionData.value.length === 0) return '0.00'
const sum = predictionData.value.reduce((acc, item) => acc + item.predictedPrice, 0)
return (sum / predictionData.value.length).toFixed(2)
}
const getPredictionReturn = () => {
if (predictionData.value.length === 0) return 0
const lastPrice = predictionData.value[predictionData.value.length - 1].predictedPrice
return ((lastPrice - currentPrice.value) / currentPrice.value * 100)
}
// 分析函数
const getAccuracyColor = () => {
if (predictionAccuracy.value >= 80) return '#67c23a'
if (predictionAccuracy.value >= 60) return '#e6a23c'
return '#f56c6c'
}
const getRiskLevel = () => {
const returnRate = Math.abs(getPredictionReturn())
if (returnRate > 10) return '高风险'
if (returnRate > 5) return '中风险'
return '低风险'
}
const getRiskTagType = () => {
const level = getRiskLevel()
if (level === '高风险') return 'danger'
if (level === '中风险') return 'warning'
return 'success'
}
const getRiskLevelByVolatility = (volatility) => {
if (volatility > 5) return '高风险'
if (volatility > 2) return '中风险'
return '低风险'
}
const getRiskTagTypeByLevel = (level) => {
if (level === '高风险') return 'danger'
if (level === '中风险') return 'warning'
return 'success'
}
const getRecommendation = () => {
const returnRate = getPredictionReturn()
if (returnRate > 5) return '买入'
if (returnRate > 0) return '持有'
if (returnRate > -5) return '观望'
return '卖出'
}
const getRecommendationTagType = () => {
const recommendation = getRecommendation()
if (recommendation === '买入') return 'success'
if (recommendation === '持有') return 'primary'
if (recommendation === '观望') return 'warning'
return 'danger'
}
const getSupportLevel = () => {
return (currentPrice.value * 0.95).toFixed(2)
}
const getResistanceLevel = () => {
return (currentPrice.value * 1.05).toFixed(2)
}
const getVolatility = () => {
if (predictionData.value.length === 0) return '0.00'
const prices = predictionData.value.map(item => item.predictedPrice)
const avg = prices.reduce((sum, price) => sum + price, 0) / prices.length
const variance = prices.reduce((sum, price) => sum + Math.pow(price - avg, 2), 0) / prices.length
const volatility = Math.sqrt(variance) / avg * 100
return volatility.toFixed(2)
}
// 格式化函数
const formatChangePercent = (value) => {
if (!value) return '0.00'
return (value > 0 ? '+' : '') + value.toFixed(2)
}
const formatDate = (date) => {
if (!date) return '--'
return new Date(date).toLocaleDateString()
}
onMounted(() => {
generatePrediction()
})
return {
stockCode,
predictionData,
predictionTableData,
loading,
chartLoading,
predictionDays,
predictionModel,
confidenceLevel,
historyDays,
showConfidenceInterval,
predictionAccuracy,
generatePrediction,
updateChart,
goBack,
getPredictionMax,
getPredictionMin,
getPredictionAvg,
getPredictionReturn,
getAccuracyColor,
getRiskLevel,
getRiskTagType,
getRiskTagTypeByLevel,
getRecommendation,
getRecommendationTagType,
getSupportLevel,
getResistanceLevel,
getVolatility,
formatChangePercent,
formatDate
}
}
}
</script>
<style lang="scss" scoped>
.stock-prediction-view {
padding: 20px;
background-color: #f5f7fa;
min-height: 100vh;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
.stock-info {
h1 {
margin: 0;
color: #303133;
font-size: 24px;
}
.stock-code {
margin: 5px 0 0 0;
color: #909399;
font-size: 14px;
}
}
.action-buttons {
display: flex;
gap: 10px;
}
}
.prediction-config {
margin-bottom: 20px;
}
.config-card {
border: none;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
.config-item {
label {
display: block;
margin-bottom: 8px;
font-size: 14px;
color: #606266;
font-weight: 500;
}
}
}
.prediction-overview {
margin-bottom: 20px;
}
.prediction-card {
border: none;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
.prediction-info {
text-align: center;
padding: 20px;
.prediction-value {
font-size: 24px;
font-weight: bold;
color: #303133;
margin-bottom: 8px;
&.positive {
color: #f56c6c;
}
&.negative {
color: #67c23a;
}
}
.prediction-subtitle {
font-size: 14px;
color: #909399;
}
}
}
.chart-section {
margin-bottom: 20px;
}
.chart-card, .analysis-card, .table-card {
border: none;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
span {
font-weight: 600;
color: #303133;
}
}
.chart-controls {
display: flex;
align-items: center;
gap: 10px;
}
.analysis-content {
.analysis-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 0;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.analysis-label {
font-size: 14px;
color: #606266;
font-weight: 500;
}
.analysis-value {
display: flex;
align-items: center;
gap: 10px;
.accuracy-text {
font-size: 14px;
color: #303133;
font-weight: 500;
}
}
}
}
.positive {
color: #f56c6c;
}
.negative {
color: #67c23a;
}
:deep(.el-table) {
.positive {
color: #f56c6c;
}
.negative {
color: #67c23a;
}
}
</style>

View File

@@ -0,0 +1,658 @@
<template>
<div class="stock-trend-view">
<!-- 页面标题 -->
<div class="page-header">
<div class="stock-info">
<h1>{{ trendData.stockName || stockCode }} - 趋势分析</h1>
<p class="stock-code">{{ stockCode }}</p>
</div>
<div class="action-buttons">
<el-button @click="goBack">
<el-icon><ArrowLeft /></el-icon>
返回
</el-button>
<el-button type="primary" @click="refreshData" :loading="loading">
<el-icon><Refresh /></el-icon>
刷新数据
</el-button>
</div>
</div>
<!-- 趋势概览 -->
<el-row :gutter="20" class="trend-overview">
<el-col :span="6">
<el-card class="trend-card">
<div class="trend-info">
<div class="trend-icon" :class="getTrendClass()">
<el-icon v-if="trendData.trendDirection === 'UP'"><CaretTop /></el-icon>
<el-icon v-else-if="trendData.trendDirection === 'DOWN'"><CaretBottom /></el-icon>
<el-icon v-else><Minus /></el-icon>
</div>
<div class="trend-text">
<div class="trend-title">{{ getTrendText() }}</div>
<div class="trend-subtitle">趋势方向</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="trend-card">
<div class="trend-info">
<div class="trend-value">{{ trendData.trendStrength?.toFixed(2) || '0.00' }}%</div>
<div class="trend-subtitle">趋势强度</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="trend-card">
<div class="trend-info">
<div class="trend-value">¥{{ trendData.currentPrice?.toFixed(2) || '0.00' }}</div>
<div class="trend-subtitle">当前价格</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="trend-card">
<div class="trend-info">
<div class="trend-value" :class="{ 'positive': trendData.totalChangePercent > 0, 'negative': trendData.totalChangePercent < 0 }">
{{ formatChangePercent(trendData.totalChangePercent) }}%
</div>
<div class="trend-subtitle">总涨跌幅</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 趋势图表 -->
<el-row :gutter="20" class="chart-section">
<el-col :span="16">
<el-card class="chart-card">
<template #header>
<div class="card-header">
<span>价格趋势图</span>
<div class="chart-controls">
<el-select v-model="days" @change="loadTrendData" size="small" style="width: 120px;">
<el-option label="7天" :value="7" />
<el-option label="15天" :value="15" />
<el-option label="30天" :value="30" />
<el-option label="60天" :value="60" />
<el-option label="90天" :value="90" />
</el-select>
</div>
</div>
</template>
<div id="trend-chart" style="height: 500px;" v-loading="chartLoading"></div>
</el-card>
</el-col>
<el-col :span="8">
<el-card class="stats-card">
<template #header>
<span>统计信息</span>
</template>
<div class="stats-content">
<div class="stat-item">
<div class="stat-label">最高价</div>
<div class="stat-value">¥{{ trendData.highestPrice?.toFixed(2) || '--' }}</div>
</div>
<div class="stat-item">
<div class="stat-label">最低价</div>
<div class="stat-value">¥{{ trendData.lowestPrice?.toFixed(2) || '--' }}</div>
</div>
<div class="stat-item">
<div class="stat-label">平均价</div>
<div class="stat-value">¥{{ trendData.averagePrice?.toFixed(2) || '--' }}</div>
</div>
<div class="stat-item">
<div class="stat-label">平均涨跌幅</div>
<div class="stat-value" :class="{ 'positive': trendData.avgChangePercent > 0, 'negative': trendData.avgChangePercent < 0 }">
{{ formatChangePercent(trendData.avgChangePercent) }}%
</div>
</div>
<div class="stat-item">
<div class="stat-label">总成交量</div>
<div class="stat-value">{{ formatVolume(trendData.totalVolume) }}</div>
</div>
<div class="stat-item">
<div class="stat-label">平均成交量</div>
<div class="stat-value">{{ formatVolume(trendData.avgVolume) }}</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 成交量图表 -->
<el-row class="volume-section">
<el-col :span="24">
<el-card class="chart-card">
<template #header>
<span>成交量分析</span>
</template>
<div id="volume-chart" style="height: 300px;" v-loading="chartLoading"></div>
</el-card>
</el-col>
</el-row>
<!-- 价格历史数据表格 -->
<el-row>
<el-col :span="24">
<el-card class="table-card">
<template #header>
<span>历史价格数据</span>
</template>
<el-table :data="trendData.priceHistory || []" style="width: 100%" max-height="400">
<el-table-column prop="tradeDate" label="交易日期" width="120">
<template #default="scope">
{{ formatDate(scope.row.tradeDate) }}
</template>
</el-table-column>
<el-table-column prop="openPrice" label="开盘价" width="100">
<template #default="scope">
¥{{ scope.row.openPrice?.toFixed(2) }}
</template>
</el-table-column>
<el-table-column prop="closePrice" label="收盘价" width="100">
<template #default="scope">
¥{{ scope.row.closePrice?.toFixed(2) }}
</template>
</el-table-column>
<el-table-column prop="highPrice" label="最高价" width="100">
<template #default="scope">
¥{{ scope.row.highPrice?.toFixed(2) }}
</template>
</el-table-column>
<el-table-column prop="lowPrice" label="最低价" width="100">
<template #default="scope">
¥{{ scope.row.lowPrice?.toFixed(2) }}
</template>
</el-table-column>
<el-table-column prop="changePercent" label="涨跌幅" width="100">
<template #default="scope">
<span :class="{ 'positive': scope.row.changePercent > 0, 'negative': scope.row.changePercent < 0 }">
{{ formatChangePercent(scope.row.changePercent) }}%
</span>
</template>
</el-table-column>
<el-table-column prop="volume" label="成交量" width="120">
<template #default="scope">
{{ formatVolume(scope.row.volume) }}
</template>
</el-table-column>
</el-table>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import { ref, onMounted, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { getStockTrend } from '@/api/stock'
import { ElMessage } from 'element-plus'
import * as echarts from 'echarts'
export default {
name: 'StockTrendView',
setup() {
const route = useRoute()
const router = useRouter()
const stockCode = ref(route.params.stockCode)
const trendData = ref({})
const loading = ref(false)
const chartLoading = ref(false)
const days = ref(30)
// 加载趋势数据
const loadTrendData = async () => {
try {
loading.value = true
chartLoading.value = true
const response = await getStockTrend(stockCode.value, days.value)
if (response.code === 200 && response.data) {
trendData.value = response.data
await nextTick()
initCharts()
} else {
ElMessage.error(response.message || '获取趋势数据失败')
}
} catch (error) {
console.error('获取趋势数据失败:', error)
ElMessage.error('获取趋势数据失败')
} finally {
loading.value = false
chartLoading.value = false
}
}
// 初始化图表
const initCharts = async () => {
await nextTick()
initTrendChart()
initVolumeChart()
}
// 初始化趋势图表
const initTrendChart = () => {
const chartDom = document.getElementById('trend-chart')
if (!chartDom) return
const myChart = echarts.init(chartDom)
const priceHistory = trendData.value.priceHistory || []
const dates = priceHistory.map(item => formatDate(item.tradeDate))
const closePrices = priceHistory.map(item => item.closePrice)
const openPrices = priceHistory.map(item => item.openPrice)
const highPrices = priceHistory.map(item => item.highPrice)
const lowPrices = priceHistory.map(item => item.lowPrice)
const option = {
title: {
text: `${trendData.value.stockName}(${stockCode.value}) 价格趋势`,
left: 'left'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
}
},
legend: {
data: ['收盘价', '开盘价', '最高价', '最低价'],
top: 30
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: '15%',
containLabel: true
},
xAxis: {
type: 'category',
data: dates,
axisLine: {
lineStyle: {
color: '#8392A5'
}
}
},
yAxis: {
type: 'value',
scale: true,
axisLine: {
lineStyle: {
color: '#8392A5'
}
},
splitLine: {
show: true,
lineStyle: {
color: '#E6E8EB'
}
}
},
series: [
{
name: '收盘价',
type: 'line',
data: closePrices,
smooth: true,
lineStyle: {
color: '#409EFF',
width: 2
},
symbol: 'circle',
symbolSize: 4
},
{
name: '开盘价',
type: 'line',
data: openPrices,
smooth: true,
lineStyle: {
color: '#67C23A',
width: 1
},
symbol: 'none'
},
{
name: '最高价',
type: 'line',
data: highPrices,
smooth: true,
lineStyle: {
color: '#F56C6C',
width: 1,
type: 'dashed'
},
symbol: 'none'
},
{
name: '最低价',
type: 'line',
data: lowPrices,
smooth: true,
lineStyle: {
color: '#E6A23C',
width: 1,
type: 'dashed'
},
symbol: 'none'
}
]
}
myChart.setOption(option)
}
// 初始化成交量图表
const initVolumeChart = () => {
const chartDom = document.getElementById('volume-chart')
if (!chartDom) return
const myChart = echarts.init(chartDom)
const priceHistory = trendData.value.priceHistory || []
const dates = priceHistory.map(item => formatDate(item.tradeDate))
const volumes = priceHistory.map(item => item.volume)
const option = {
title: {
text: '成交量趋势',
left: 'left'
},
tooltip: {
trigger: 'axis',
formatter: function(params) {
return `${params[0].name}<br/>成交量: ${formatVolume(params[0].value)}`
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: '15%',
containLabel: true
},
xAxis: {
type: 'category',
data: dates,
axisLine: {
lineStyle: {
color: '#8392A5'
}
}
},
yAxis: {
type: 'value',
axisLine: {
lineStyle: {
color: '#8392A5'
}
},
splitLine: {
show: true,
lineStyle: {
color: '#E6E8EB'
}
}
},
series: [
{
name: '成交量',
type: 'bar',
data: volumes,
itemStyle: {
color: '#409EFF'
}
}
]
}
myChart.setOption(option)
}
// 刷新数据
const refreshData = () => {
loadTrendData()
}
// 返回
const goBack = () => {
router.go(-1)
}
// 获取趋势类名
const getTrendClass = () => {
const direction = trendData.value.trendDirection
if (direction === 'UP') return 'trend-up'
if (direction === 'DOWN') return 'trend-down'
return 'trend-flat'
}
// 获取趋势文本
const getTrendText = () => {
const direction = trendData.value.trendDirection
if (direction === 'UP') return '上涨趋势'
if (direction === 'DOWN') return '下跌趋势'
return '震荡趋势'
}
// 格式化函数
const formatChangePercent = (value) => {
if (!value) return '0.00'
return (value > 0 ? '+' : '') + value.toFixed(2)
}
const formatVolume = (volume) => {
if (!volume) return '0'
if (volume >= 100000000) {
return (volume / 100000000).toFixed(2) + '亿'
} else if (volume >= 10000) {
return (volume / 10000).toFixed(2) + '万'
}
return volume.toString()
}
const formatDate = (date) => {
if (!date) return '--'
return new Date(date).toLocaleDateString()
}
onMounted(() => {
loadTrendData()
})
return {
stockCode,
trendData,
loading,
chartLoading,
days,
loadTrendData,
refreshData,
goBack,
getTrendClass,
getTrendText,
formatChangePercent,
formatVolume,
formatDate
}
}
}
</script>
<style lang="scss" scoped>
.stock-trend-view {
padding: 20px;
background-color: #f5f7fa;
min-height: 100vh;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
.stock-info {
h1 {
margin: 0;
color: #303133;
font-size: 24px;
}
.stock-code {
margin: 5px 0 0 0;
color: #909399;
font-size: 14px;
}
}
.action-buttons {
display: flex;
gap: 10px;
}
}
.trend-overview {
margin-bottom: 20px;
}
.trend-card {
border: none;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
.trend-info {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
padding: 20px;
text-align: center;
.trend-icon {
font-size: 36px;
margin-bottom: 10px;
&.trend-up {
color: #f56c6c;
}
&.trend-down {
color: #67c23a;
}
&.trend-flat {
color: #909399;
}
}
.trend-value {
font-size: 24px;
font-weight: bold;
color: #303133;
margin-bottom: 5px;
&.positive {
color: #f56c6c;
}
&.negative {
color: #67c23a;
}
}
.trend-title {
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 5px;
}
.trend-subtitle {
font-size: 12px;
color: #909399;
}
}
}
.chart-section, .volume-section {
margin-bottom: 20px;
}
.chart-card, .stats-card, .table-card {
border: none;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
span {
font-weight: 600;
color: #303133;
}
}
.chart-controls {
display: flex;
align-items: center;
gap: 10px;
}
.stats-content {
.stat-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 0;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.stat-label {
font-size: 14px;
color: #909399;
}
.stat-value {
font-size: 16px;
font-weight: 500;
color: #303133;
&.positive {
color: #f56c6c;
}
&.negative {
color: #67c23a;
}
}
}
}
.positive {
color: #f56c6c;
}
.negative {
color: #67c23a;
}
:deep(.el-table) {
.positive {
color: #f56c6c;
}
.negative {
color: #67c23a;
}
}
</style>

View File

@@ -1,20 +1,39 @@
2025-06-04 20:18:02 [main] INFO c.a.s.AgriculturalStockPlatformApplication - Starting AgriculturalStockPlatformApplication using Java 1.8.0_202 on WIN11 with PID 10364 (D:\VScodeProject\work_4\backend\target\classes started by shenjianZ in D:\VScodeProject\work_4) 2025-06-21 15:06:48 [main] INFO c.a.s.AgriculturalStockPlatformApplication - Starting AgriculturalStockPlatformApplication using Java 1.8.0_202 on WIN11 with PID 4824 (D:\VScodeProject\work_4\backend\target\classes started by shenjianZ in D:\VScodeProject\work_4)
2025-06-04 20:18:02 [main] INFO c.a.s.AgriculturalStockPlatformApplication - No active profile set, falling back to 1 default profile: "default" 2025-06-21 15:06:48 [main] INFO c.a.s.AgriculturalStockPlatformApplication - No active profile set, falling back to 1 default profile: "default"
2025-06-04 20:18:05 [main] INFO o.a.catalina.core.StandardService - Starting service [Tomcat] 2025-06-21 15:06:51 [main] INFO o.a.catalina.core.StandardService - Starting service [Tomcat]
2025-06-04 20:18:05 [main] INFO o.a.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.63] 2025-06-21 15:06:51 [main] INFO o.a.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.63]
2025-06-04 20:18:05 [main] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext 2025-06-21 15:06:52 [main] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext
2025-06-04 20:18:06 [main] INFO o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default] 2025-06-21 15:06:52 [main] INFO o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default]
2025-06-04 20:18:06 [main] INFO org.hibernate.Version - HHH000412: Hibernate ORM core version 5.6.9.Final 2025-06-21 15:06:52 [main] INFO org.hibernate.Version - HHH000412: Hibernate ORM core version 5.6.9.Final
2025-06-04 20:18:06 [main] INFO o.h.annotations.common.Version - HCANN000001: Hibernate Commons Annotations {5.1.2.Final} 2025-06-21 15:06:52 [main] INFO o.h.annotations.common.Version - HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2025-06-04 20:18:06 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting... 2025-06-21 15:06:53 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
2025-06-04 20:18:06 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed. 2025-06-21 15:06:53 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
2025-06-04 20:18:06 [main] INFO org.hibernate.dialect.Dialect - HHH000400: Using dialect: org.hibernate.dialect.MySQL8Dialect 2025-06-21 15:06:53 [main] INFO org.hibernate.dialect.Dialect - HHH000400: Using dialect: org.hibernate.dialect.MySQL8Dialect
2025-06-04 20:18:07 [main] INFO o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform] 2025-06-21 15:06:53 [main] INFO o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2025-06-04 20:18:08 [main] WARN o.s.b.w.s.c.AnnotationConfigServletWebServerApplicationContext - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'stockController' defined in file [D:\VScodeProject\work_4\backend\target\classes\com\agricultural\stock\controller\StockController.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.agricultural.stock.service.StockService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {} 2025-06-21 15:06:54 [main] WARN o.s.b.a.o.j.JpaBaseConfiguration$JpaWebConfiguration - spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2025-06-04 20:18:08 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated... 2025-06-21 15:06:55 [main] WARN c.b.m.core.metadata.TableInfoHelper - Can not find table primary key in Class: "com.agricultural.stock.entity.TechnicalIndicator".
2025-06-04 20:18:08 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed. 2025-06-21 15:06:55 [main] WARN c.b.m.c.injector.DefaultSqlInjector - class com.agricultural.stock.entity.TechnicalIndicator ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method.
2025-06-04 20:18:08 [main] INFO o.a.catalina.core.StandardService - Stopping service [Tomcat] 2025-06-21 15:06:55 [main] INFO c.a.s.AgriculturalStockPlatformApplication - Started AgriculturalStockPlatformApplication in 7.716 seconds (JVM running for 8.752)
2025-06-04 20:18:08 [main] ERROR o.s.b.d.LoggingFailureAnalysisReporter - 2025-06-21 15:07:01 [http-nio-8080-exec-1] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
2025-06-21 15:10:41 [SpringApplicationShutdownHook] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated...
2025-06-21 15:10:41 [SpringApplicationShutdownHook] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed.
2025-06-21 15:14:09 [main] INFO c.a.s.AgriculturalStockPlatformApplication - Starting AgriculturalStockPlatformApplication using Java 1.8.0_202 on WIN11 with PID 26436 (D:\VScodeProject\work_4\backend\target\classes started by shenjianZ in D:\VScodeProject\work_4)
2025-06-21 15:14:09 [main] INFO c.a.s.AgriculturalStockPlatformApplication - No active profile set, falling back to 1 default profile: "default"
2025-06-21 15:14:10 [main] INFO o.a.catalina.core.StandardService - Starting service [Tomcat]
2025-06-21 15:14:10 [main] INFO o.a.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.63]
2025-06-21 15:14:11 [main] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext
2025-06-21 15:14:11 [main] INFO o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default]
2025-06-21 15:14:11 [main] INFO org.hibernate.Version - HHH000412: Hibernate ORM core version 5.6.9.Final
2025-06-21 15:14:11 [main] INFO o.h.annotations.common.Version - HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2025-06-21 15:14:11 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
2025-06-21 15:14:11 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
2025-06-21 15:14:11 [main] INFO org.hibernate.dialect.Dialect - HHH000400: Using dialect: org.hibernate.dialect.MySQL8Dialect
2025-06-21 15:14:12 [main] INFO o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2025-06-21 15:14:12 [main] WARN o.s.b.w.s.c.AnnotationConfigServletWebServerApplicationContext - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration': Unsatisfied dependency expressed through method 'setConfigurers' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'swaggerWebMvcConfigurer' defined in class path resource [org/springdoc/webmvc/ui/SwaggerConfig.class]: Unsatisfied dependency expressed through method 'swaggerWebMvcConfigurer' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springdoc.core.SwaggerUiConfigParameters': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.springdoc.core.SwaggerUiConfigProperties' available: expected single matching bean but found 2: swaggerUiConfigProperties,org.springdoc.core.SwaggerUiConfigProperties
2025-06-21 15:14:12 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated...
2025-06-21 15:14:12 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed.
2025-06-21 15:14:12 [main] INFO o.a.catalina.core.StandardService - Stopping service [Tomcat]
2025-06-21 15:14:12 [main] ERROR o.s.b.d.LoggingFailureAnalysisReporter -
*************************** ***************************
APPLICATION FAILED TO START APPLICATION FAILED TO START
@@ -22,172 +41,130 @@ APPLICATION FAILED TO START
Description: Description:
Parameter 0 of constructor in com.agricultural.stock.controller.StockController required a bean of type 'com.agricultural.stock.service.StockService' that could not be found. Parameter 0 of constructor in org.springdoc.core.SwaggerUiConfigParameters required a single bean, but 2 were found:
- swaggerUiConfigProperties: defined by method 'swaggerUiConfigProperties' in class path resource [com/agricultural/stock/config/OpenApiConfig.class]
- org.springdoc.core.SwaggerUiConfigProperties: defined in null
Action: Action:
Consider defining a bean of type 'com.agricultural.stock.service.StockService' in your configuration. Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
2025-06-04 20:29:56 [main] INFO c.a.s.AgriculturalStockPlatformApplication - Starting AgriculturalStockPlatformApplication using Java 1.8.0_202 on WIN11 with PID 22200 (D:\VScodeProject\work_4\backend\target\classes started by shenjianZ in D:\VScodeProject\work_4) 2025-06-21 15:15:02 [main] INFO c.a.s.AgriculturalStockPlatformApplication - Starting AgriculturalStockPlatformApplication using Java 1.8.0_202 on WIN11 with PID 10800 (D:\VScodeProject\work_4\backend\target\classes started by shenjianZ in D:\VScodeProject\work_4)
2025-06-04 20:29:56 [main] INFO c.a.s.AgriculturalStockPlatformApplication - No active profile set, falling back to 1 default profile: "default" 2025-06-21 15:15:02 [main] INFO c.a.s.AgriculturalStockPlatformApplication - No active profile set, falling back to 1 default profile: "default"
2025-06-04 20:29:58 [main] INFO o.a.catalina.core.StandardService - Starting service [Tomcat] 2025-06-21 15:15:04 [main] INFO o.a.catalina.core.StandardService - Starting service [Tomcat]
2025-06-04 20:29:58 [main] INFO o.a.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.63] 2025-06-21 15:15:04 [main] INFO o.a.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.63]
2025-06-04 20:29:59 [main] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext 2025-06-21 15:15:04 [main] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext
2025-06-04 20:29:59 [main] INFO o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default] 2025-06-21 15:15:04 [main] INFO o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default]
2025-06-04 20:29:59 [main] INFO org.hibernate.Version - HHH000412: Hibernate ORM core version 5.6.9.Final 2025-06-21 15:15:04 [main] INFO org.hibernate.Version - HHH000412: Hibernate ORM core version 5.6.9.Final
2025-06-04 20:29:59 [main] INFO o.h.annotations.common.Version - HCANN000001: Hibernate Commons Annotations {5.1.2.Final} 2025-06-21 15:15:05 [main] INFO o.h.annotations.common.Version - HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2025-06-04 20:29:59 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting... 2025-06-21 15:15:05 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
2025-06-04 20:29:59 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed. 2025-06-21 15:15:05 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
2025-06-04 20:29:59 [main] INFO org.hibernate.dialect.Dialect - HHH000400: Using dialect: org.hibernate.dialect.MySQL8Dialect 2025-06-21 15:15:05 [main] INFO org.hibernate.dialect.Dialect - HHH000400: Using dialect: org.hibernate.dialect.MySQL8Dialect
2025-06-04 20:30:00 [main] INFO o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform] 2025-06-21 15:15:06 [main] INFO o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2025-06-04 20:30:01 [main] WARN o.s.b.a.o.j.JpaBaseConfiguration$JpaWebConfiguration - spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning 2025-06-21 15:15:06 [main] WARN o.s.b.a.o.j.JpaBaseConfiguration$JpaWebConfiguration - spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2025-06-04 20:30:02 [main] WARN c.b.m.core.metadata.TableInfoHelper - Can not find table primary key in Class: "com.agricultural.stock.entity.TechnicalIndicator". 2025-06-21 15:15:07 [main] WARN c.b.m.core.metadata.TableInfoHelper - Can not find table primary key in Class: "com.agricultural.stock.entity.TechnicalIndicator".
2025-06-04 20:30:02 [main] WARN c.b.m.c.injector.DefaultSqlInjector - class com.agricultural.stock.entity.TechnicalIndicator ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method. 2025-06-21 15:15:07 [main] WARN c.b.m.c.injector.DefaultSqlInjector - class com.agricultural.stock.entity.TechnicalIndicator ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method.
2025-06-04 20:30:02 [main] WARN o.s.b.w.s.c.AnnotationConfigServletWebServerApplicationContext - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException 2025-06-21 15:15:08 [main] INFO c.a.s.AgriculturalStockPlatformApplication - Started AgriculturalStockPlatformApplication in 5.923 seconds (JVM running for 7.223)
2025-06-04 20:30:02 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated... 2025-06-21 15:15:11 [http-nio-8080-exec-2] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
2025-06-04 20:30:02 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed. 2025-06-21 15:15:12 [http-nio-8080-exec-8] INFO o.s.api.AbstractOpenApiResource - Init duration for springdoc-openapi is: 522 ms
2025-06-04 20:30:02 [main] INFO o.a.catalina.core.StandardService - Stopping service [Tomcat] 2025-06-21 15:21:55 [http-nio-8080-exec-1] INFO c.a.s.service.impl.StockServiceImpl - 股票预测功能暂未实现,股票代码: sh600371, 预测天数: 7
2025-06-04 20:30:02 [main] ERROR o.s.boot.SpringApplication - Application run failed 2025-06-21 15:40:09 [http-nio-8080-exec-9] INFO c.a.s.service.impl.StockServiceImpl - 股票预测功能暂未实现,股票代码: sz300189, 预测天数: 7
org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException 2025-06-21 15:40:20 [http-nio-8080-exec-1] INFO c.a.s.service.impl.StockServiceImpl - 股票预测功能暂未实现,股票代码: sz300189, 预测天数: 7
at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:181) 2025-06-21 15:40:27 [http-nio-8080-exec-9] INFO c.a.s.service.impl.StockServiceImpl - 股票预测功能暂未实现,股票代码: sz300189, 预测天数: 7
at org.springframework.context.support.DefaultLifecycleProcessor.access$200(DefaultLifecycleProcessor.java:54) 2025-06-21 15:40:49 [http-nio-8080-exec-1] INFO c.a.s.service.impl.StockServiceImpl - 股票预测功能暂未实现,股票代码: sh603336, 预测天数: 7
at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:356) 2025-06-21 15:42:33 [http-nio-8080-exec-4] INFO c.a.s.service.impl.StockServiceImpl - 股票预测功能暂未实现,股票代码: sz300505, 预测天数: 7
at java.lang.Iterable.forEach(Iterable.java:75) 2025-06-21 15:46:12 [http-nio-8080-exec-6] INFO c.a.s.service.impl.StockServiceImpl - 股票预测功能暂未实现,股票代码: sz300189, 预测天数: 7
at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:155) 2025-06-21 15:46:18 [http-nio-8080-exec-1] INFO c.a.s.service.impl.StockServiceImpl - 股票预测功能暂未实现,股票代码: sz300189, 预测天数: 7
at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:123) 2025-06-21 15:46:47 [http-nio-8080-exec-2] INFO c.a.s.service.impl.StockServiceImpl - 股票预测功能暂未实现,股票代码: sz301035, 预测天数: 7
at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:935) 2025-06-21 16:06:00 [http-nio-8080-exec-2] INFO c.a.s.service.impl.StockServiceImpl - 股票预测功能暂未实现,股票代码: sz300189, 预测天数: 7
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:586) 2025-06-21 16:07:10 [http-nio-8080-exec-7] INFO c.a.s.service.impl.StockServiceImpl - 股票预测功能暂未实现,股票代码: sz300189, 预测天数: 7
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:147) 2025-06-21 16:07:18 [http-nio-8080-exec-1] INFO c.a.s.service.impl.StockServiceImpl - 股票预测功能暂未实现,股票代码: sz300189, 预测天数: 7
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:734) 2025-06-21 16:07:46 [SpringApplicationShutdownHook] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated...
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408) 2025-06-21 16:07:46 [SpringApplicationShutdownHook] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed.
at org.springframework.boot.SpringApplication.run(SpringApplication.java:308) 2025-06-21 16:07:58 [main] INFO c.a.s.AgriculturalStockPlatformApplication - Starting AgriculturalStockPlatformApplication using Java 1.8.0_202 on WIN11 with PID 28420 (D:\VScodeProject\work_4\backend\target\classes started by shenjianZ in D:\VScodeProject\work_4)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) 2025-06-21 16:07:58 [main] INFO c.a.s.AgriculturalStockPlatformApplication - No active profile set, falling back to 1 default profile: "default"
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295) 2025-06-21 16:08:00 [main] INFO o.a.catalina.core.StandardService - Starting service [Tomcat]
at com.agricultural.stock.AgriculturalStockPlatformApplication.main(AgriculturalStockPlatformApplication.java:27) 2025-06-21 16:08:00 [main] INFO o.a.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.63]
Caused by: java.lang.NullPointerException: null 2025-06-21 16:08:00 [main] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext
at springfox.documentation.spring.web.WebMvcPatternsRequestConditionWrapper.getPatterns(WebMvcPatternsRequestConditionWrapper.java:56) 2025-06-21 16:08:00 [main] INFO o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default]
at springfox.documentation.RequestHandler.sortedPaths(RequestHandler.java:113) 2025-06-21 16:08:00 [main] INFO org.hibernate.Version - HHH000412: Hibernate ORM core version 5.6.9.Final
at springfox.documentation.spi.service.contexts.Orderings.lambda$byPatternsCondition$3(Orderings.java:89) 2025-06-21 16:08:01 [main] INFO o.h.annotations.common.Version - HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
at java.util.Comparator.lambda$comparing$77a9974f$1(Comparator.java:469) 2025-06-21 16:08:01 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
at java.util.TimSort.countRunAndMakeAscending(TimSort.java:355) 2025-06-21 16:08:01 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
at java.util.TimSort.sort(TimSort.java:220) 2025-06-21 16:08:01 [main] INFO org.hibernate.dialect.Dialect - HHH000400: Using dialect: org.hibernate.dialect.MySQL8Dialect
at java.util.Arrays.sort(Arrays.java:1512) 2025-06-21 16:08:01 [main] INFO o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
at java.util.ArrayList.sort(ArrayList.java:1462) 2025-06-21 16:08:02 [main] WARN o.s.b.a.o.j.JpaBaseConfiguration$JpaWebConfiguration - spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
at java.util.stream.SortedOps$RefSortingSink.end(SortedOps.java:387) 2025-06-21 16:08:03 [main] WARN c.b.m.core.metadata.TableInfoHelper - Can not find table primary key in Class: "com.agricultural.stock.entity.TechnicalIndicator".
at java.util.stream.Sink$ChainedReference.end(Sink.java:258) 2025-06-21 16:08:03 [main] WARN c.b.m.c.injector.DefaultSqlInjector - class com.agricultural.stock.entity.TechnicalIndicator ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method.
at java.util.stream.Sink$ChainedReference.end(Sink.java:258) 2025-06-21 16:08:03 [main] INFO c.a.s.AgriculturalStockPlatformApplication - Started AgriculturalStockPlatformApplication in 5.411 seconds (JVM running for 6.284)
at java.util.stream.Sink$ChainedReference.end(Sink.java:258) 2025-06-21 16:08:10 [http-nio-8080-exec-6] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
at java.util.stream.Sink$ChainedReference.end(Sink.java:258) 2025-06-21 16:08:13 [http-nio-8080-exec-10] INFO c.a.s.service.impl.StockServiceImpl - 股票预测功能暂未实现,股票代码: sz300189, 预测天数: 7
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482) 2025-06-21 16:08:17 [http-nio-8080-exec-7] INFO c.a.s.service.impl.StockServiceImpl - 股票预测功能暂未实现,股票代码: sz300189, 预测天数: 7
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) 2025-06-21 16:08:23 [http-nio-8080-exec-8] INFO c.a.s.service.impl.StockServiceImpl - 股票预测功能暂未实现,股票代码: sz300189, 预测天数: 7
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708) 2025-06-21 16:08:46 [http-nio-8080-exec-6] INFO c.a.s.service.impl.StockServiceImpl - 股票预测功能暂未实现,股票代码: sz300189, 预测天数: 7
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) 2025-06-21 16:09:31 [http-nio-8080-exec-8] INFO c.a.s.service.impl.StockServiceImpl - 股票预测功能暂未实现,股票代码: sz000713, 预测天数: 7
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499) 2025-06-21 16:15:43 [http-nio-8080-exec-1] INFO c.a.s.service.impl.StockServiceImpl - 股票预测功能暂未实现,股票代码: sz300189, 预测天数: 7
at springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider.requestHandlers(WebMvcRequestHandlerProvider.java:81) 2025-06-21 16:15:57 [http-nio-8080-exec-10] INFO c.a.s.service.impl.StockServiceImpl - 股票预测功能暂未实现,股票代码: sz300189, 预测天数: 7
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) 2025-06-21 16:19:16 [http-nio-8080-exec-5] INFO c.a.s.service.impl.StockServiceImpl - 股票预测功能暂未实现,股票代码: sz300189, 预测天数: 7
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1382) 2025-06-21 16:19:37 [http-nio-8080-exec-10] INFO c.a.s.service.impl.StockServiceImpl - 股票预测功能暂未实现,股票代码: sz300189, 预测天数: 7
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481) 2025-06-21 16:19:44 [http-nio-8080-exec-5] INFO c.a.s.service.impl.StockServiceImpl - 股票预测功能暂未实现,股票代码: sz300189, 预测天数: 7
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) 2025-06-21 16:19:48 [http-nio-8080-exec-4] INFO c.a.s.service.impl.StockServiceImpl - 股票预测功能暂未实现,股票代码: sz300189, 预测天数: 7
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708) 2025-06-21 16:19:58 [http-nio-8080-exec-8] INFO c.a.s.service.impl.StockServiceImpl - 股票预测功能暂未实现,股票代码: sz300189, 预测天数: 7
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) 2025-06-21 16:24:16 [SpringApplicationShutdownHook] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated...
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499) 2025-06-21 16:24:16 [SpringApplicationShutdownHook] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed.
at springfox.documentation.spring.web.plugins.AbstractDocumentationPluginsBootstrapper.withDefaults(AbstractDocumentationPluginsBootstrapper.java:107) 2025-06-21 16:24:23 [main] INFO c.a.s.AgriculturalStockPlatformApplication - Starting AgriculturalStockPlatformApplication using Java 1.8.0_202 on WIN11 with PID 15804 (D:\VScodeProject\work_4\backend\target\classes started by shenjianZ in D:\VScodeProject\work_4)
at springfox.documentation.spring.web.plugins.AbstractDocumentationPluginsBootstrapper.buildContext(AbstractDocumentationPluginsBootstrapper.java:91) 2025-06-21 16:24:23 [main] INFO c.a.s.AgriculturalStockPlatformApplication - No active profile set, falling back to 1 default profile: "default"
at springfox.documentation.spring.web.plugins.AbstractDocumentationPluginsBootstrapper.bootstrapDocumentationPlugins(AbstractDocumentationPluginsBootstrapper.java:82) 2025-06-21 16:24:24 [main] INFO o.a.catalina.core.StandardService - Starting service [Tomcat]
at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper.start(DocumentationPluginsBootstrapper.java:100) 2025-06-21 16:24:24 [main] INFO o.a.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.63]
at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:178) 2025-06-21 16:24:25 [main] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext
... 14 common frames omitted 2025-06-21 16:24:25 [main] INFO o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default]
2025-06-04 20:30:56 [main] INFO c.a.s.AgriculturalStockPlatformApplication - Starting AgriculturalStockPlatformApplication using Java 1.8.0_202 on WIN11 with PID 19328 (D:\VScodeProject\work_4\backend\target\classes started by shenjianZ in D:\VScodeProject\work_4) 2025-06-21 16:24:25 [main] INFO org.hibernate.Version - HHH000412: Hibernate ORM core version 5.6.9.Final
2025-06-04 20:30:56 [main] INFO c.a.s.AgriculturalStockPlatformApplication - No active profile set, falling back to 1 default profile: "default" 2025-06-21 16:24:25 [main] INFO o.h.annotations.common.Version - HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2025-06-04 20:30:59 [main] INFO o.a.catalina.core.StandardService - Starting service [Tomcat] 2025-06-21 16:24:25 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
2025-06-04 20:30:59 [main] INFO o.a.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.63] 2025-06-21 16:24:25 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
2025-06-04 20:30:59 [main] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext 2025-06-21 16:24:25 [main] INFO org.hibernate.dialect.Dialect - HHH000400: Using dialect: org.hibernate.dialect.MySQL8Dialect
2025-06-04 20:31:00 [main] INFO o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default] 2025-06-21 16:24:26 [main] INFO o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2025-06-04 20:31:00 [main] INFO org.hibernate.Version - HHH000412: Hibernate ORM core version 5.6.9.Final 2025-06-21 16:24:27 [main] WARN o.s.b.a.o.j.JpaBaseConfiguration$JpaWebConfiguration - spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2025-06-04 20:31:00 [main] INFO o.h.annotations.common.Version - HCANN000001: Hibernate Commons Annotations {5.1.2.Final} 2025-06-21 16:24:28 [main] WARN c.b.m.core.metadata.TableInfoHelper - Can not find table primary key in Class: "com.agricultural.stock.entity.TechnicalIndicator".
2025-06-04 20:31:00 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting... 2025-06-21 16:24:28 [main] WARN c.b.m.c.injector.DefaultSqlInjector - class com.agricultural.stock.entity.TechnicalIndicator ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method.
2025-06-04 20:31:00 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed. 2025-06-21 16:24:28 [main] INFO c.a.s.AgriculturalStockPlatformApplication - Started AgriculturalStockPlatformApplication in 5.62 seconds (JVM running for 6.522)
2025-06-04 20:31:01 [main] INFO org.hibernate.dialect.Dialect - HHH000400: Using dialect: org.hibernate.dialect.MySQL8Dialect 2025-06-21 16:24:32 [http-nio-8080-exec-1] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
2025-06-04 20:31:01 [main] INFO o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform] 2025-06-21 16:24:32 [http-nio-8080-exec-1] INFO c.a.s.service.impl.StockServiceImpl - 生成股票预测数据,股票代码: sz300189, 预测天数: 7
2025-06-04 20:31:01 [main] WARN o.s.b.w.s.c.AnnotationConfigServletWebServerApplicationContext - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'marketAnalysisController' defined in file [D:\VScodeProject\work_4\backend\target\classes\com\agricultural\stock\controller\MarketAnalysisController.class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.agricultural.stock.controller.MarketAnalysisController]: Constructor threw exception; nested exception is java.lang.Error: Unresolved compilation problems: 2025-06-21 16:24:44 [http-nio-8080-exec-5] INFO c.a.s.service.impl.StockServiceImpl - 生成股票预测数据,股票代码: sz300189, 预测天数: 7
Api cannot be resolved to a type 2025-06-21 16:24:48 [http-nio-8080-exec-8] INFO c.a.s.service.impl.StockServiceImpl - 生成股票预测数据,股票代码: sz300189, 预测天数: 7
ApiOperation cannot be resolved to a type 2025-06-21 16:26:47 [http-nio-8080-exec-6] INFO c.a.s.service.impl.StockServiceImpl - 生成股票预测数据,股票代码: sz300189, 预测天数: 7
ApiOperation cannot be resolved to a type 2025-06-21 16:27:14 [http-nio-8080-exec-10] INFO c.a.s.service.impl.StockServiceImpl - 生成股票预测数据,股票代码: sz300189, 预测天数: 7
ApiOperation cannot be resolved to a type 2025-06-21 16:27:49 [http-nio-8080-exec-4] INFO c.a.s.service.impl.StockServiceImpl - 生成股票预测数据,股票代码: sz300189, 预测天数: 7
2025-06-21 16:28:27 [http-nio-8080-exec-9] INFO c.a.s.service.impl.StockServiceImpl - 生成股票预测数据,股票代码: sz300189, 预测天数: 7
2025-06-04 20:31:01 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated... 2025-06-21 16:28:49 [http-nio-8080-exec-2] INFO c.a.s.service.impl.StockServiceImpl - 生成股票预测数据,股票代码: sz300189, 预测天数: 7
2025-06-04 20:31:01 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed. 2025-06-21 16:30:39 [http-nio-8080-exec-7] INFO c.a.s.service.impl.StockServiceImpl - 生成股票预测数据,股票代码: sz300189, 预测天数: 7
2025-06-04 20:31:01 [main] INFO o.a.catalina.core.StandardService - Stopping service [Tomcat] 2025-06-21 16:31:25 [SpringApplicationShutdownHook] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated...
2025-06-04 20:31:01 [main] ERROR o.s.boot.SpringApplication - Application run failed 2025-06-21 16:31:25 [SpringApplicationShutdownHook] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed.
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'marketAnalysisController' defined in file [D:\VScodeProject\work_4\backend\target\classes\com\agricultural\stock\controller\MarketAnalysisController.class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.agricultural.stock.controller.MarketAnalysisController]: Constructor threw exception; nested exception is java.lang.Error: Unresolved compilation problems: 2025-06-21 16:31:32 [main] INFO c.a.s.AgriculturalStockPlatformApplication - Starting AgriculturalStockPlatformApplication using Java 1.8.0_202 on WIN11 with PID 10584 (D:\VScodeProject\work_4\backend\target\classes started by shenjianZ in D:\VScodeProject\work_4)
Api cannot be resolved to a type 2025-06-21 16:31:32 [main] INFO c.a.s.AgriculturalStockPlatformApplication - No active profile set, falling back to 1 default profile: "default"
ApiOperation cannot be resolved to a type 2025-06-21 16:31:33 [main] INFO o.a.catalina.core.StandardService - Starting service [Tomcat]
ApiOperation cannot be resolved to a type 2025-06-21 16:31:33 [main] INFO o.a.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.63]
ApiOperation cannot be resolved to a type 2025-06-21 16:31:34 [main] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext
2025-06-21 16:31:34 [main] INFO o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1334) 2025-06-21 16:31:34 [main] INFO org.hibernate.Version - HHH000412: Hibernate ORM core version 5.6.9.Final
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1232) 2025-06-21 16:31:34 [main] INFO o.h.annotations.common.Version - HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582) 2025-06-21 16:31:34 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) 2025-06-21 16:31:34 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) 2025-06-21 16:31:34 [main] INFO org.hibernate.dialect.Dialect - HHH000400: Using dialect: org.hibernate.dialect.MySQL8Dialect
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) 2025-06-21 16:31:35 [main] INFO o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) 2025-06-21 16:31:36 [main] WARN o.s.b.a.o.j.JpaBaseConfiguration$JpaWebConfiguration - spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) 2025-06-21 16:31:37 [main] WARN c.b.m.core.metadata.TableInfoHelper - Can not find table primary key in Class: "com.agricultural.stock.entity.TechnicalIndicator".
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:953) 2025-06-21 16:31:37 [main] WARN c.b.m.c.injector.DefaultSqlInjector - class com.agricultural.stock.entity.TechnicalIndicator ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method.
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918) 2025-06-21 16:31:37 [main] INFO c.a.s.AgriculturalStockPlatformApplication - Started AgriculturalStockPlatformApplication in 5.772 seconds (JVM running for 6.518)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) 2025-06-21 16:31:49 [http-nio-8080-exec-1] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:147) 2025-06-21 16:31:50 [http-nio-8080-exec-4] INFO c.a.s.service.impl.StockServiceImpl - 生成股票预测数据,股票代码: sz300189, 预测天数: 7
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:734) 2025-06-21 16:32:05 [http-nio-8080-exec-2] INFO c.a.s.service.impl.StockServiceImpl - 生成股票预测数据,股票代码: sz300189, 预测天数: 7
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408) 2025-06-21 16:32:10 [http-nio-8080-exec-6] INFO c.a.s.service.impl.StockServiceImpl - 生成股票预测数据,股票代码: sz000713, 预测天数: 7
at org.springframework.boot.SpringApplication.run(SpringApplication.java:308) 2025-06-21 16:32:27 [http-nio-8080-exec-2] INFO c.a.s.service.impl.StockServiceImpl - 生成股票预测数据,股票代码: sz200505, 预测天数: 7
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) 2025-06-21 16:33:16 [http-nio-8080-exec-5] INFO c.a.s.service.impl.StockServiceImpl - 生成股票预测数据,股票代码: sz002539, 预测天数: 7
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295) 2025-06-21 16:33:24 [http-nio-8080-exec-1] INFO c.a.s.service.impl.StockServiceImpl - 生成股票预测数据,股票代码: sz300505, 预测天数: 7
at com.agricultural.stock.AgriculturalStockPlatformApplication.main(AgriculturalStockPlatformApplication.java:27) 2025-06-21 16:33:32 [http-nio-8080-exec-7] INFO c.a.s.service.impl.StockServiceImpl - 生成股票预测数据,股票代码: sz300505, 预测天数: 7
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.agricultural.stock.controller.MarketAnalysisController]: Constructor threw exception; nested exception is java.lang.Error: Unresolved compilation problems: 2025-06-21 16:33:41 [http-nio-8080-exec-4] INFO c.a.s.service.impl.StockServiceImpl - 生成股票预测数据,股票代码: sh000912, 预测天数: 7
Api cannot be resolved to a type 2025-06-21 16:37:10 [SpringApplicationShutdownHook] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated...
ApiOperation cannot be resolved to a type 2025-06-21 16:37:10 [SpringApplicationShutdownHook] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed.
ApiOperation cannot be resolved to a type
ApiOperation cannot be resolved to a type
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:224)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:87)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1326)
... 17 common frames omitted
Caused by: java.lang.Error: Unresolved compilation problems:
Api cannot be resolved to a type
ApiOperation cannot be resolved to a type
ApiOperation cannot be resolved to a type
ApiOperation cannot be resolved to a type
at com.agricultural.stock.controller.MarketAnalysisController.<init>(MarketAnalysisController.java:22)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:211)
... 19 common frames omitted
2025-06-04 20:33:00 [main] INFO c.a.s.AgriculturalStockPlatformApplication - Starting AgriculturalStockPlatformApplication using Java 1.8.0_202 on WIN11 with PID 11952 (D:\VScodeProject\work_4\backend\target\classes started by shenjianZ in D:\VScodeProject\work_4)
2025-06-04 20:33:00 [main] INFO c.a.s.AgriculturalStockPlatformApplication - No active profile set, falling back to 1 default profile: "default"
2025-06-04 20:33:01 [main] INFO o.a.catalina.core.StandardService - Starting service [Tomcat]
2025-06-04 20:33:01 [main] INFO o.a.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.63]
2025-06-04 20:33:02 [main] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext
2025-06-04 20:33:02 [main] INFO o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default]
2025-06-04 20:33:02 [main] INFO org.hibernate.Version - HHH000412: Hibernate ORM core version 5.6.9.Final
2025-06-04 20:33:02 [main] INFO o.h.annotations.common.Version - HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2025-06-04 20:33:02 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
2025-06-04 20:33:02 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
2025-06-04 20:33:02 [main] INFO org.hibernate.dialect.Dialect - HHH000400: Using dialect: org.hibernate.dialect.MySQL8Dialect
2025-06-04 20:33:03 [main] INFO o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2025-06-04 20:33:03 [main] WARN o.s.b.a.o.j.JpaBaseConfiguration$JpaWebConfiguration - spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2025-06-04 20:33:04 [main] WARN c.b.m.core.metadata.TableInfoHelper - Can not find table primary key in Class: "com.agricultural.stock.entity.TechnicalIndicator".
2025-06-04 20:33:04 [main] WARN c.b.m.c.injector.DefaultSqlInjector - class com.agricultural.stock.entity.TechnicalIndicator ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method.
2025-06-04 20:33:04 [main] INFO c.a.s.AgriculturalStockPlatformApplication - Started AgriculturalStockPlatformApplication in 5.049 seconds (JVM running for 5.764)
2025-06-04 20:33:11 [http-nio-8080-exec-1] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
2025-06-04 20:35:00 [http-nio-8080-exec-7] INFO o.s.api.AbstractOpenApiResource - Init duration for springdoc-openapi is: 534 ms
2025-06-04 21:09:37 [http-nio-8080-exec-10] INFO c.a.s.service.impl.StockServiceImpl - 股票预测功能暂未实现,股票代码: sz300630, 预测天数: 7
2025-06-04 21:09:52 [http-nio-8080-exec-9] INFO c.a.s.service.impl.StockServiceImpl - 股票预测功能暂未实现,股票代码: sz300630, 预测天数: 7
2025-06-04 21:09:56 [http-nio-8080-exec-10] INFO c.a.s.service.impl.StockServiceImpl - 股票预测功能暂未实现,股票代码: sz300630, 预测天数: 7
2025-06-04 21:10:00 [http-nio-8080-exec-9] INFO c.a.s.service.impl.StockServiceImpl - 股票预测功能暂未实现,股票代码: sz300630, 预测天数: 7
2025-06-04 21:13:44 [SpringApplicationShutdownHook] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated...
2025-06-04 21:13:44 [SpringApplicationShutdownHook] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed.