MexDailyPriceTrendController.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. <?php
  2. namespace App\Module\Mex\AdminControllers;
  3. use App\Module\Fund\Enums\FUND_CURRENCY_TYPE;
  4. use App\Module\Mex\AdminControllers\Helper\GridHelper;
  5. use App\Module\Mex\Metrics\PriceTrendChart;
  6. use App\Module\Mex\Models\MexDailyPriceTrend;
  7. use App\Module\Mex\Repositories\MexDailyPriceTrendRepository;
  8. use App\Module\Mex\Service\MexDailyPriceTrendService;
  9. use Spatie\RouteAttributes\Attributes\Get;
  10. use Spatie\RouteAttributes\Attributes\Resource;
  11. use UCore\DcatAdmin\AdminController;
  12. use Dcat\Admin\Grid;
  13. use Dcat\Admin\Show;
  14. use Dcat\Admin\Layout\Content;
  15. /**
  16. * 农贸市场每日价格趋势管理
  17. *
  18. * 路由:/admin/mex-daily-price-trends
  19. */
  20. #[Resource('mex-daily-price-trends', names: 'dcat.admin.mex-daily-price-trends')]
  21. class MexDailyPriceTrendController extends AdminController
  22. {
  23. /**
  24. * 页面标题
  25. */
  26. protected $title = '农贸市场每日价格趋势';
  27. /**
  28. * 列表页面
  29. */
  30. public function index(Content $content)
  31. {
  32. return $content
  33. ->title('农贸市场每日价格趋势')
  34. ->description('价格趋势分析与数据管理')
  35. ->row(function ($row) {
  36. // 添加价格趋势统计卡片
  37. $row->column(4, $this->createPriceStatsCard());
  38. $row->column(4, $this->createVolumeStatsCard());
  39. $row->column(4, $this->createTrendStatsCard());
  40. })
  41. ->body($this->grid());
  42. }
  43. /**
  44. * 数据表格
  45. */
  46. protected function grid()
  47. {
  48. return Grid::make(new MexDailyPriceTrendRepository(['item']), function (Grid $grid) {
  49. $helper = new GridHelper($grid, $this);
  50. $grid->column('id', 'ID')->sortable();
  51. $grid->column('item_id', '商品ID')->link(function ($value) {
  52. return admin_url("game-items/{$value}");
  53. });
  54. $grid->column('item.name', '商品名称');
  55. $grid->column('currency_type', '币种')->display(function ($value) {
  56. if (is_string($value)) {
  57. return FUND_CURRENCY_TYPE::from($value)->name;
  58. }
  59. return $value->name;
  60. });
  61. $grid->column('trade_date', '交易日期')->sortable();
  62. // 价格信息
  63. $grid->column('open_price', '开盘价')->display(function ($value) {
  64. return $value ? number_format($value, 5) : '-';
  65. });
  66. $grid->column('close_price', '收盘价')->display(function ($value) {
  67. return $value ? number_format($value, 5) : '-';
  68. });
  69. $grid->column('high_price', '最高价')->display(function ($value) {
  70. return $value ? number_format($value, 5) : '-';
  71. });
  72. $grid->column('low_price', '最低价')->display(function ($value) {
  73. return $value ? number_format($value, 5) : '-';
  74. });
  75. // 价格变化
  76. $grid->column('price_change_percent', '涨跌幅')->display(function ($value) {
  77. if ($value === null) return '-';
  78. $color = $value > 0 ? 'success' : ($value < 0 ? 'danger' : 'secondary');
  79. $symbol = $value > 0 ? '+' : '';
  80. return "<span class='text-{$color}'>{$symbol}" . number_format($value, 2) . "%</span>";
  81. });
  82. // 交易统计
  83. $grid->column('total_volume', '成交量')->sortable();
  84. $grid->column('total_amount', '成交额')->display(function ($value) {
  85. return number_format($value, 5);
  86. })->sortable();
  87. $grid->column('transaction_count', '成交笔数');
  88. // 波动率
  89. $grid->column('volatility', '波动率')->display(function ($value) {
  90. if ($value === null) return '-';
  91. $level = $this->volatility_level;
  92. $color = match($level) {
  93. '低波动' => 'success',
  94. '中等波动' => 'warning',
  95. '高波动' => 'danger',
  96. '极高波动' => 'dark',
  97. default => 'secondary'
  98. };
  99. return "<span class='badge badge-{$color}'>{$level} (" . number_format($value, 2) . "%)</span>";
  100. });
  101. $helper->columnUpdatedAt();
  102. // 筛选器
  103. $grid->filter(function (Grid\Filter $filter) {
  104. $filter->equal('item_id', '商品ID');
  105. $filter->equal('currency_type', '币种')->select([
  106. FUND_CURRENCY_TYPE::ZUANSHI->value => '钻石',
  107. FUND_CURRENCY_TYPE::JINBI->value => '金币',
  108. ]);
  109. $filter->between('trade_date', '交易日期')->date();
  110. $filter->between('total_volume', '成交量范围');
  111. $filter->between('total_amount', '成交额范围');
  112. $filter->between('price_change_percent', '涨跌幅范围');
  113. });
  114. // 默认排序
  115. $grid->model()->orderBy('trade_date', 'desc')->orderBy('item_id');
  116. // 禁用新增、编辑、删除操作
  117. $grid->disableCreateButton();
  118. $grid->disableActions();
  119. $grid->disableBatchActions();
  120. // 添加工具栏按钮
  121. $grid->tools(function (Grid\Tools $tools) {
  122. $tools->append('<a href="' . admin_url('mex-daily-price-trends/generate') . '" class="btn btn-primary btn-sm">
  123. <i class="fa fa-refresh"></i> 生成趋势数据
  124. </a>');
  125. });
  126. });
  127. }
  128. /**
  129. * 详情页面
  130. */
  131. protected function detail($id)
  132. {
  133. return Show::make($id, new MexDailyPriceTrendRepository(['item']), function (Show $show) {
  134. $show->field('id', 'ID');
  135. $show->field('item_id', '商品ID');
  136. $show->field('item.name', '商品名称');
  137. $show->field('currency_type', '币种')->as(function ($value) {
  138. if (is_string($value)) {
  139. return FUND_CURRENCY_TYPE::from($value)->name;
  140. }
  141. return $value->name;
  142. });
  143. $show->field('trade_date', '交易日期');
  144. $show->divider('价格信息');
  145. $show->field('open_price', '开盘价')->as(function ($value) {
  146. return $value ? number_format($value, 5) : '-';
  147. });
  148. $show->field('close_price', '收盘价')->as(function ($value) {
  149. return $value ? number_format($value, 5) : '-';
  150. });
  151. $show->field('high_price', '最高价')->as(function ($value) {
  152. return $value ? number_format($value, 5) : '-';
  153. });
  154. $show->field('low_price', '最低价')->as(function ($value) {
  155. return $value ? number_format($value, 5) : '-';
  156. });
  157. $show->field('avg_price', '平均价')->as(function ($value) {
  158. return $value ? number_format($value, 5) : '-';
  159. });
  160. $show->divider('价格变化');
  161. $show->field('price_change', '价格变化')->as(function ($value) {
  162. return $value ? number_format($value, 5) : '-';
  163. });
  164. $show->field('price_change_percent', '涨跌幅')->as(function ($value) {
  165. return $value ? number_format($value, 2) . '%' : '-';
  166. });
  167. $show->field('volatility', '波动率')->as(function ($value) {
  168. return $value ? number_format($value, 2) . '%' : '-';
  169. });
  170. $show->divider('交易统计');
  171. $show->field('total_volume', '总成交量');
  172. $show->field('total_amount', '总成交额')->as(function ($value) {
  173. return number_format($value, 5);
  174. });
  175. $show->field('transaction_count', '成交笔数');
  176. $show->field('buy_volume', '买入量');
  177. $show->field('sell_volume', '卖出量');
  178. $show->field('buy_amount', '买入额')->as(function ($value) {
  179. return number_format($value, 5);
  180. });
  181. $show->field('sell_amount', '卖出额')->as(function ($value) {
  182. return number_format($value, 5);
  183. });
  184. $show->divider('管理员操作');
  185. $show->field('admin_inject_volume', '管理员注入量');
  186. $show->field('admin_recycle_volume', '管理员回收量');
  187. $show->field('admin_inject_amount', '管理员注入额')->as(function ($value) {
  188. return number_format($value, 5);
  189. });
  190. $show->field('admin_recycle_amount', '管理员回收额')->as(function ($value) {
  191. return number_format($value, 5);
  192. });
  193. $show->field('created_at', '创建时间');
  194. $show->field('updated_at', '更新时间');
  195. // 禁用编辑和删除按钮
  196. $show->disableEditButton();
  197. $show->disableDeleteButton();
  198. });
  199. }
  200. /**
  201. * 生成趋势数据
  202. */
  203. #[Get('mex-daily-price-trends/generate', name: 'admin.mex-daily-price-trends.generate')]
  204. public function generate()
  205. {
  206. try {
  207. // 调用服务生成今日价格趋势数据
  208. $trends = MexDailyPriceTrendService::generateTodayTrends();
  209. return redirect()->back()->with('success', "价格趋势数据生成成功!共生成 {$trends->count()} 条记录。");
  210. } catch (\Exception $e) {
  211. return redirect()->back()->with('error', '生成失败:' . $e->getMessage());
  212. }
  213. }
  214. /**
  215. * 创建价格统计卡片
  216. */
  217. protected function createPriceStatsCard()
  218. {
  219. // 获取最近7天的价格统计
  220. $trends = MexDailyPriceTrend::where('trade_date', '>=', now()->subDays(7)->toDateString())
  221. ->selectRaw('AVG(close_price) as avg_price, MAX(high_price) as max_price, MIN(low_price) as min_price')
  222. ->first();
  223. $avgPrice = $trends->avg_price ?? 0;
  224. $maxPrice = $trends->max_price ?? 0;
  225. $minPrice = $trends->min_price ?? 0;
  226. return '<div class="info-box bg-info">
  227. <span class="info-box-icon"><i class="fa fa-chart-line"></i></span>
  228. <div class="info-box-content">
  229. <span class="info-box-text">价格统计</span>
  230. <span class="info-box-number">' . number_format($avgPrice, 5) . '</span>
  231. <div class="progress">
  232. <div class="progress-bar" style="width: 70%"></div>
  233. </div>
  234. <span class="progress-description">
  235. 最高: ' . number_format($maxPrice, 5) . ' | 最低: ' . number_format($minPrice, 5) . '
  236. </span>
  237. </div>
  238. </div>';
  239. }
  240. /**
  241. * 创建成交量统计卡片
  242. */
  243. protected function createVolumeStatsCard()
  244. {
  245. // 获取最近7天的成交量统计
  246. $totalVolume = MexDailyPriceTrend::where('trade_date', '>=', now()->subDays(7)->toDateString())
  247. ->sum('total_volume');
  248. $todayVolume = MexDailyPriceTrend::where('trade_date', now()->toDateString())
  249. ->sum('total_volume');
  250. return '<div class="info-box bg-success">
  251. <span class="info-box-icon"><i class="fa fa-chart-bar"></i></span>
  252. <div class="info-box-content">
  253. <span class="info-box-text">成交量统计</span>
  254. <span class="info-box-number">' . number_format($totalVolume) . '</span>
  255. <div class="progress">
  256. <div class="progress-bar" style="width: 50%"></div>
  257. </div>
  258. <span class="progress-description">
  259. 今日: ' . number_format($todayVolume) . '
  260. </span>
  261. </div>
  262. </div>';
  263. }
  264. /**
  265. * 创建趋势统计卡片
  266. */
  267. protected function createTrendStatsCard()
  268. {
  269. // 获取活跃商品数量和数据天数
  270. $activeItems = MexDailyPriceTrend::distinct('item_id')->count();
  271. $dataDays = MexDailyPriceTrend::distinct('trade_date')->count();
  272. return '<div class="info-box bg-warning">
  273. <span class="info-box-icon"><i class="fa fa-trending-up"></i></span>
  274. <div class="info-box-content">
  275. <span class="info-box-text">趋势统计</span>
  276. <span class="info-box-number">' . $activeItems . ' 个</span>
  277. <div class="progress">
  278. <div class="progress-bar" style="width: 80%"></div>
  279. </div>
  280. <span class="progress-description">
  281. 数据天数: ' . $dataDays . ' 天
  282. </span>
  283. </div>
  284. </div>';
  285. }
  286. }