浏览代码

完成农贸市场价格调整记录和每日价格趋势功能

- 新增价格调整记录功能:
  * 创建MexPriceAdjustment模型和相关数据表
  * 实现价格调整记录的自动记录功能
  * 添加价格调整记录的后台管理界面
  * 在价格配置编辑时自动记录调整历史

- 新增每日价格趋势功能:
  * 创建MexDailyPriceTrend模型和相关数据表
  * 实现每日价格趋势数据生成服务
  * 添加每日价格趋势的后台管理界面
  * 集成Dcat Admin图表功能显示价格趋势折线图

- 完善后台菜单:
  * 将价格调整记录和每日价格趋势添加到农贸市场管理菜单
  * 使用合适的图标和排序

- 修复相关问题:
  * 修复Repository基类引用问题
  * 修复枚举类型在后台显示的兼容性问题
  * 完善错误处理和数据验证
AI Assistant 6 月之前
父节点
当前提交
f2892a49f4

+ 146 - 0
AiWork/2025年06月/21日2330-农场作物软删除功能和事件日志后台管理.md

@@ -0,0 +1,146 @@
+# 农场作物软删除功能和事件日志后台管理
+
+**时间**: 2025年06月21日 23:30  
+**任务类型**: 功能开发和Bug修复  
+**状态**: ✅ 已完成
+
+## 任务概述
+
+本次任务完成了两个重要功能:
+1. 为农场作物模型实现软删除功能
+2. 修复作物事件日志后台管理界面缺失问题
+
+## 任务1:作物信息模型,软删除
+
+### 问题描述
+农场作物模型缺少软删除功能,铲除作物时直接物理删除,无法保留审计记录。
+
+### 解决方案
+
+#### 1. 数据库结构修改
+- 添加 `deleted_at` 字段支持软删除
+- 添加 `idx_deleted_at` 索引优化查询
+- 修改唯一索引策略,使用复合索引 `idx_land_id_deleted_at` 解决软删除与唯一约束冲突
+
+#### 2. 模型修改
+```php
+// app/Module/Farm/Models/FarmCrop.php
+use Illuminate\Database\Eloquent\SoftDeletes;
+
+class FarmCrop extends ModelCore
+{
+    use SoftDeletes;
+    
+    protected $casts = [
+        'deleted_at' => 'datetime',
+        // ...
+    ];
+}
+```
+
+#### 3. 逻辑层实现
+- `CropLogic::shovelCrop()` - 使用软删除替代物理删除
+- `CropLogic::forceDeleteCrop()` - 强制物理删除
+- `CropLogic::restoreCrop()` - 恢复软删除的作物
+- `CropLogic::getTrashedCropByLandId()` - 获取软删除作物
+
+#### 4. 服务层封装
+```php
+// app/Module/Farm/Services/CropService.php
+public static function shovelCrop(int $userId, int $landId): bool
+public static function forceDeleteCrop(int $userId, int $landId): bool
+public static function restoreCrop(int $userId, int $landId): bool
+public static function getTrashedCropByLandId(int $landId): ?FarmCropDto
+```
+
+#### 5. 测试验证
+- 创建单元测试 `tests/Feature/Farm/CropSoftDeleteTest.php`
+- 创建命令行测试工具 `app/Console/Commands/TestCropSoftDelete.php`
+- 验证软删除、强制删除、恢复功能正常工作
+
+### 功能特性
+- ✅ 软删除:铲除作物时保留数据用于审计
+- ✅ 强制删除:提供物理删除功能用于数据清理
+- ✅ 恢复功能:可以恢复误删的作物
+- ✅ 查询支持:支持查询软删除的作物记录
+- ✅ 索引优化:解决软删除与唯一索引的冲突问题
+
+## 任务2:作物事件日志,后台缺失
+
+### 问题描述
+作物事件日志的后台管理控制器已存在,但未添加到后台菜单中,管理员无法访问。
+
+### 解决方案
+
+#### 1. 添加后台菜单
+```sql
+INSERT INTO kku_admin_menu (parent_id, `order`, title, icon, uri, `show`, extension, created_at, updated_at) 
+VALUES (265, 86, '作物事件日志', 'fa-history', 'farm-crop-logs', 1, '', NOW(), NOW());
+```
+
+#### 2. 修复Dcat Admin兼容性问题
+```php
+// 修复Grid列display方法参数问题
+$grid->column('event_data', '事件数据')->display(function ($value) {
+    if (empty($value)) {
+        return '<span class="text-muted">无数据</span>';
+    }
+    
+    $jsonStr = json_encode($value, JSON_UNESCAPED_UNICODE);
+    if (strlen($jsonStr) > 100) {
+        $jsonStr = substr($jsonStr, 0, 100) . '...';
+    }
+    
+    return "<small class='text-info'>{$jsonStr}</small>";
+});
+```
+
+### 功能特性
+- ✅ 菜单导航:在农场管理下添加作物事件日志菜单
+- ✅ 统计信息:显示总事件数、今日事件数、各类型事件统计
+- ✅ 列表展示:显示ID、用户ID、作物ID、种子名称、土地ID、事件类型、生长阶段、事件数据、创建时间
+- ✅ 筛选功能:支持按用户ID、作物ID、事件类型、生长阶段、事件时间筛选
+- ✅ 详情查看:可查看每个事件的详细信息,包括格式化的JSON事件数据
+- ✅ 只读设计:禁用新增、编辑、删除功能,符合日志记录特性
+
+## 技术要点
+
+### 软删除索引策略
+解决了软删除与唯一索引冲突的关键问题:
+- 原索引:`UNIQUE KEY idx_land_id (land_id)` 
+- 新索引:`UNIQUE KEY idx_land_id_deleted_at (land_id, deleted_at)`
+
+这样软删除的记录不会与活跃记录产生唯一约束冲突。
+
+### Dcat Admin兼容性
+修复了Grid列display方法的参数传递问题,确保与Dcat Admin框架的兼容性。
+
+## 测试验证
+
+### 软删除功能测试
+```bash
+# 运行单元测试
+vendor/bin/phpunit tests/Feature/Farm/CropSoftDeleteTest.php
+
+# 运行命令行测试
+php artisan farm:test-soft-delete 39077 296 1
+```
+
+### 后台管理测试
+- ✅ 菜单正常显示和导航
+- ✅ 列表页面正常显示统计信息
+- ✅ 筛选功能正常工作
+- ✅ 分页功能正常
+- ✅ 详情页面正常显示
+
+## 代码提交
+
+**提交信息**: 实现农场作物软删除功能并修复作物事件日志后台管理  
+**提交哈希**: 21cc6c09  
+**文件变更**: 8个文件,新增881行,删除27行
+
+## 总结
+
+本次任务成功实现了农场作物的软删除功能,提供了完整的软删除、强制删除、恢复功能,并解决了软删除与数据库唯一约束的冲突问题。同时修复了作物事件日志后台管理界面缺失的问题,为管理员提供了完整的事件日志查看和分析功能。
+
+两个功能都经过了充分的测试验证,确保功能正常工作,代码质量良好。

+ 182 - 0
AiWork/2025年06月/22日1233-修复推广信息接口奖励数据显示问题.md

@@ -0,0 +1,182 @@
+# 修复推广信息接口奖励数据显示问题
+
+## 任务时间
+- 开始时间:2025-06-22 12:29
+- 完成时间:2025-06-22 12:33
+
+## 问题描述
+用户反馈推广信息接口响应中缺少 `today_reward` 和 `total_reward` 字段数据,日志显示这两个字段没有数据。
+
+## 问题分析
+
+### 初步分析
+1. **接口响应缺失字段**:推广信息接口返回的 `promotionInfo` 中没有 `today_reward` 和 `total_reward` 字段
+2. **用户确实有奖励**:通过查看 `user_logs` 表发现用户39077确实有URS推广奖励记录
+
+### 深入调查
+通过查看 `user_logs` 表发现用户39077有以下URS推广奖励:
+```sql
+-- 用户39077的URS推广奖励记录
+ID: 741001 - "urs_promotion_harvest获得萝卜 11" (2025-06-22)
+ID: 707198 - "urs_promotion_harvest获得辣椒 10" (2025-06-21) 
+ID: 707158 - "urs_promotion_harvest获得萝卜 6" (2025-06-21)
+```
+
+对应的 `item_transaction_logs` 记录:
+```sql
+ID: 11027 - 萝卜(item_id:2) 11个, source_type: 'urs_promotion_harvest'
+ID: 11011 - 辣椒(item_id:3) 10个, source_type: 'urs_promotion_harvest'  
+ID: 10991 - 萝卜(item_id:2) 6个, source_type: 'urs_promotion_harvest'
+```
+
+### 根本原因
+**查询逻辑错误**:`InfoHandler` 中的 `getRewardStatsFromLogs` 方法只查询了 `game_reward_logs` 表,但实际的URS推广奖励记录存储在 `item_transaction_logs` 表中。
+
+## 解决方案
+
+### 第一步:确保字段始终存在
+修改 `InfoHandler` 确保 `today_reward` 和 `total_reward` 字段始终存在:
+1. 修改主处理逻辑,使用空奖励对象替代null值
+2. 更新 `setEmptyResponse` 方法设置空奖励字段
+3. 添加 `createEmptyReward` 方法创建空奖励对象
+
+### 第二步:修复查询逻辑
+在 `getRewardStatsFromLogs` 方法中添加对 `ItemTransactionLog` 表的查询:
+1. 添加对 `item_transaction_logs` 表的查询逻辑
+2. 查询条件:`transaction_type = 1`(收入类型)且 `source_type` 为URS推广相关类型
+3. 修复类名错误:使用正确的 `App\Module\GameItems\Models\ItemTransactionLog`
+
+## 代码修改
+
+### 文件:`app/Module/AppGame/Handler/Promotion/InfoHandler.php`
+
+#### 1. 修改主处理逻辑
+```php
+// 设置收益数据 - 始终设置奖励字段,没有数据时使用空奖励对象
+$todayReward = $rewardStats['today_reward'] ?? $this->createEmptyReward();
+$totalReward = $rewardStats['total_reward'] ?? $this->createEmptyReward();
+$response->setTodayReward($todayReward);
+$response->setTotalReward($totalReward);
+```
+
+#### 2. 更新setEmptyResponse方法
+```php
+// 设置空的奖励对象
+$emptyReward = $this->createEmptyReward();
+$response->setTodayReward($emptyReward);
+$response->setTotalReward($emptyReward);
+```
+
+#### 3. 添加createEmptyReward方法
+```php
+private function createEmptyReward(): Reward
+{
+    $reward = new Reward();
+    $reward->setItems([]);
+    $reward->setCoins([]);
+    $reward->setGods([]);
+    $reward->setLands([]);
+    $reward->setPets([]);
+    $reward->setPetPowers([]);
+    $reward->setSkins([]);
+    return $reward;
+}
+```
+
+#### 4. 添加ItemTransactionLog查询
+```php
+// 1.1. 从物品交易日志表中统计URS推广奖励(补充查询)
+$itemQuery = \App\Module\GameItems\Models\ItemTransactionLog::where('user_id', $farmUserId)
+    ->where('transaction_type', 1) // 收入类型
+    ->whereIn('source_type', $ursPromotionSourceTypes);
+
+if ($startDate) {
+    $itemQuery->where('created_at', '>=', $startDate);
+}
+if ($endDate) {
+    $itemQuery->where('created_at', '<=', $endDate);
+}
+
+$itemLogs = $itemQuery->get();
+
+foreach ($itemLogs as $log) {
+    $stats['total_count']++;
+    
+    $itemId = $log->item_id;
+    $quantity = $log->quantity;
+    
+    if ($itemId > 0) {
+        // 累加相同物品的数量
+        if (isset($stats['items'][$itemId])) {
+            $stats['items'][$itemId] += $quantity;
+        } else {
+            $stats['items'][$itemId] = $quantity;
+        }
+    }
+    
+    // 按来源类型统计
+    $sourceType = $log->source_type;
+    if (!isset($stats['by_source_type'][$sourceType])) {
+        $stats['by_source_type'][$sourceType] = ['amount' => 0, 'count' => 0];
+    }
+    $stats['by_source_type'][$sourceType]['count']++;
+}
+```
+
+## 测试结果
+
+### 修复前
+```json
+{
+  "promotionInfo": {
+    "totalCount": "1",
+    "directCount": "1", 
+    "activeCount": "1",
+    "directActiveCount": "1"
+    // 缺少 todayReward 和 totalReward 字段
+  }
+}
+```
+
+### 修复后
+```json
+{
+  "promotionInfo": {
+    "totalCount": "1",
+    "directCount": "1",
+    "activeCount": "1", 
+    "directActiveCount": "1",
+    "todayReward": {
+      "items": [
+        {
+          "itemId": "2",
+          "quantity": "11"
+        }
+      ]
+    },
+    "totalReward": {
+      "items": [
+        {
+          "itemId": "2", 
+          "quantity": "17"
+        },
+        {
+          "itemId": "3",
+          "quantity": "10"
+        }
+      ]
+    }
+  }
+}
+```
+
+### 数据验证
+- **今日奖励**:萝卜11个(2025-06-22获得)
+- **总奖励**:萝卜17个(6+11)+ 辣椒10个(历史总计)
+
+## 提交记录
+- 第一次提交:修复字段缺失问题,确保始终返回奖励字段
+- 第二次提交:修复查询逻辑,正确从ItemTransactionLog表获取URS推广奖励数据
+
+## 总结
+通过分析用户日志发现了查询逻辑的根本问题,URS推广奖励实际存储在 `item_transaction_logs` 表而非 `game_reward_logs` 表。修复后接口能够正确显示用户的推广奖励数据,解决了前端显示问题。

+ 240 - 0
app/Module/Mex/AdminControllers/MexDailyPriceTrendController.php

@@ -0,0 +1,240 @@
+<?php
+
+namespace App\Module\Mex\AdminControllers;
+
+use App\Module\Fund\Enums\FUND_CURRENCY_TYPE;
+use App\Module\Mex\AdminControllers\Helper\GridHelper;
+use App\Module\Mex\Metrics\PriceTrendChart;
+use App\Module\Mex\Repositories\MexDailyPriceTrendRepository;
+use App\Module\Mex\Service\MexDailyPriceTrendService;
+use Spatie\RouteAttributes\Attributes\Get;
+use Spatie\RouteAttributes\Attributes\Resource;
+use UCore\DcatAdmin\AdminController;
+use Dcat\Admin\Grid;
+use Dcat\Admin\Show;
+use Dcat\Admin\Layout\Content;
+
+/**
+ * 农贸市场每日价格趋势管理
+ *
+ * 路由:/admin/mex-daily-price-trends
+ */
+#[Resource('mex-daily-price-trends', names: 'dcat.admin.mex-daily-price-trends')]
+class MexDailyPriceTrendController extends AdminController
+{
+    /**
+     * 页面标题
+     */
+    protected $title = '农贸市场每日价格趋势';
+
+    /**
+     * 列表页面
+     */
+    public function index(Content $content)
+    {
+        return $content
+            ->title('农贸市场每日价格趋势')
+            ->description('价格趋势分析与数据管理')
+            ->row(function ($row) {
+                // 添加价格趋势图表
+                $chart = new PriceTrendChart();
+                $row->column(12, $chart);
+            })
+            ->body($this->grid());
+    }
+
+    /**
+     * 数据表格
+     */
+    protected function grid()
+    {
+        return Grid::make(new MexDailyPriceTrendRepository(['item']), function (Grid $grid) {
+            $helper = new GridHelper($grid, $this);
+            
+            $grid->column('id', 'ID')->sortable();
+            $grid->column('item_id', '商品ID')->link(function ($value) {
+                return admin_url("game-items/{$value}");
+            });
+            $grid->column('item.name', '商品名称');
+            $grid->column('currency_type', '币种')->display(function ($value) {
+                if (is_string($value)) {
+                    return FUND_CURRENCY_TYPE::from($value)->name;
+                }
+                return $value->name;
+            });
+            $grid->column('trade_date', '交易日期')->sortable();
+            
+            // 价格信息
+            $grid->column('open_price', '开盘价')->display(function ($value) {
+                return $value ? number_format($value, 5) : '-';
+            });
+            $grid->column('close_price', '收盘价')->display(function ($value) {
+                return $value ? number_format($value, 5) : '-';
+            });
+            $grid->column('high_price', '最高价')->display(function ($value) {
+                return $value ? number_format($value, 5) : '-';
+            });
+            $grid->column('low_price', '最低价')->display(function ($value) {
+                return $value ? number_format($value, 5) : '-';
+            });
+            
+            // 价格变化
+            $grid->column('price_change_percent', '涨跌幅')->display(function ($value) {
+                if ($value === null) return '-';
+                $color = $value > 0 ? 'success' : ($value < 0 ? 'danger' : 'secondary');
+                $symbol = $value > 0 ? '+' : '';
+                return "<span class='text-{$color}'>{$symbol}" . number_format($value, 2) . "%</span>";
+            });
+            
+            // 交易统计
+            $grid->column('total_volume', '成交量')->sortable();
+            $grid->column('total_amount', '成交额')->display(function ($value) {
+                return number_format($value, 5);
+            })->sortable();
+            $grid->column('transaction_count', '成交笔数');
+            
+            // 波动率
+            $grid->column('volatility', '波动率')->display(function ($value) {
+                if ($value === null) return '-';
+                $level = $this->volatility_level;
+                $color = match($level) {
+                    '低波动' => 'success',
+                    '中等波动' => 'warning',
+                    '高波动' => 'danger',
+                    '极高波动' => 'dark',
+                    default => 'secondary'
+                };
+                return "<span class='badge badge-{$color}'>{$level} (" . number_format($value, 2) . "%)</span>";
+            });
+            
+            $helper->columnUpdatedAt();
+
+            // 筛选器
+            $grid->filter(function (Grid\Filter $filter) {
+                $filter->equal('item_id', '商品ID');
+                $filter->equal('currency_type', '币种')->select([
+                    FUND_CURRENCY_TYPE::ZUANSHI->value => '钻石',
+                    FUND_CURRENCY_TYPE::JINBI->value => '金币',
+                ]);
+                $filter->between('trade_date', '交易日期')->date();
+                $filter->between('total_volume', '成交量范围');
+                $filter->between('total_amount', '成交额范围');
+                $filter->between('price_change_percent', '涨跌幅范围');
+            });
+
+            // 默认排序
+            $grid->model()->orderBy('trade_date', 'desc')->orderBy('item_id');
+
+            // 禁用新增、编辑、删除操作
+            $grid->disableCreateButton();
+            $grid->disableActions();
+            $grid->disableBatchActions();
+            
+            // 添加工具栏按钮
+            $grid->tools(function (Grid\Tools $tools) {
+                $tools->append('<a href="' . admin_url('mex-daily-price-trends/generate') . '" class="btn btn-primary btn-sm">
+                    <i class="fa fa-refresh"></i> 生成趋势数据
+                </a>');
+            });
+        });
+    }
+
+    /**
+     * 详情页面
+     */
+    protected function detail($id)
+    {
+        return Show::make($id, new MexDailyPriceTrendRepository(['item']), function (Show $show) {
+            $show->field('id', 'ID');
+            $show->field('item_id', '商品ID');
+            $show->field('item.name', '商品名称');
+            $show->field('currency_type', '币种')->as(function ($value) {
+                if (is_string($value)) {
+                    return FUND_CURRENCY_TYPE::from($value)->name;
+                }
+                return $value->name;
+            });
+            $show->field('trade_date', '交易日期');
+
+            $show->divider('价格信息');
+            
+            $show->field('open_price', '开盘价')->as(function ($value) {
+                return $value ? number_format($value, 5) : '-';
+            });
+            $show->field('close_price', '收盘价')->as(function ($value) {
+                return $value ? number_format($value, 5) : '-';
+            });
+            $show->field('high_price', '最高价')->as(function ($value) {
+                return $value ? number_format($value, 5) : '-';
+            });
+            $show->field('low_price', '最低价')->as(function ($value) {
+                return $value ? number_format($value, 5) : '-';
+            });
+            $show->field('avg_price', '平均价')->as(function ($value) {
+                return $value ? number_format($value, 5) : '-';
+            });
+
+            $show->divider('价格变化');
+            
+            $show->field('price_change', '价格变化')->as(function ($value) {
+                return $value ? number_format($value, 5) : '-';
+            });
+            $show->field('price_change_percent', '涨跌幅')->as(function ($value) {
+                return $value ? number_format($value, 2) . '%' : '-';
+            });
+            $show->field('volatility', '波动率')->as(function ($value) {
+                return $value ? number_format($value, 2) . '%' : '-';
+            });
+
+            $show->divider('交易统计');
+            
+            $show->field('total_volume', '总成交量');
+            $show->field('total_amount', '总成交额')->as(function ($value) {
+                return number_format($value, 5);
+            });
+            $show->field('transaction_count', '成交笔数');
+            $show->field('buy_volume', '买入量');
+            $show->field('sell_volume', '卖出量');
+            $show->field('buy_amount', '买入额')->as(function ($value) {
+                return number_format($value, 5);
+            });
+            $show->field('sell_amount', '卖出额')->as(function ($value) {
+                return number_format($value, 5);
+            });
+
+            $show->divider('管理员操作');
+            
+            $show->field('admin_inject_volume', '管理员注入量');
+            $show->field('admin_recycle_volume', '管理员回收量');
+            $show->field('admin_inject_amount', '管理员注入额')->as(function ($value) {
+                return number_format($value, 5);
+            });
+            $show->field('admin_recycle_amount', '管理员回收额')->as(function ($value) {
+                return number_format($value, 5);
+            });
+
+            $show->field('created_at', '创建时间');
+            $show->field('updated_at', '更新时间');
+
+            // 禁用编辑和删除按钮
+            $show->disableEditButton();
+            $show->disableDeleteButton();
+        });
+    }
+
+    /**
+     * 生成趋势数据
+     */
+    #[Get('mex-daily-price-trends/generate', name: 'admin.mex-daily-price-trends.generate')]
+    public function generate()
+    {
+        try {
+            // 调用服务生成今日价格趋势数据
+            $trends = MexDailyPriceTrendService::generateTodayTrends();
+
+            return redirect()->back()->with('success', "价格趋势数据生成成功!共生成 {$trends->count()} 条记录。");
+        } catch (\Exception $e) {
+            return redirect()->back()->with('error', '生成失败:' . $e->getMessage());
+        }
+    }
+}

+ 126 - 0
app/Module/Mex/AdminControllers/MexPriceAdjustmentController.php

@@ -0,0 +1,126 @@
+<?php
+
+namespace App\Module\Mex\AdminControllers;
+
+use App\Module\Mex\AdminControllers\Helper\GridHelper;
+use App\Module\Mex\Repositories\MexPriceAdjustmentRepository;
+use App\Module\Mex\Enums\PriceAdjustmentType;
+use Spatie\RouteAttributes\Attributes\Resource;
+use UCore\DcatAdmin\AdminController;
+use Dcat\Admin\Grid;
+use Dcat\Admin\Show;
+
+/**
+ * 农贸市场价格调整记录管理
+ *
+ * 路由:/admin/mex-price-adjustments
+ */
+#[Resource('mex-price-adjustments', names: 'dcat.admin.mex-price-adjustments')]
+class MexPriceAdjustmentController extends AdminController
+{
+    /**
+     * 页面标题
+     */
+    protected $title = '农贸市场价格调整记录';
+
+    /**
+     * 列表页面
+     */
+    protected function grid()
+    {
+        return Grid::make(new MexPriceAdjustmentRepository(['priceConfig', 'item']), function (Grid $grid) {
+            $helper = new GridHelper($grid, $this);
+            
+            $grid->column('id', 'ID')->sortable();
+            $grid->column('item_id', '商品ID')->link(function ($value) {
+                return admin_url("game-items/{$value}");
+            });
+            $grid->column('item.name', '商品名称');
+            $grid->column('admin_user_id', '操作管理员');
+            $grid->column('adjustment_type', '调整类型')->display(function ($value) {
+                return $value->getDescription();
+            })->label([
+                'MIN_PRICE' => 'primary',
+                'MAX_PRICE' => 'success',
+                'PROTECTION_THRESHOLD' => 'warning',
+                'STATUS' => 'info',
+                'BATCH' => 'danger',
+            ]);
+            
+            // 价格变化摘要
+            $grid->column('price_change_summary', '价格变化')->display(function () {
+                return $this->price_change_summary;
+            });
+            
+            $grid->column('adjustment_reason', '调整原因')->limit(50);
+            $helper->columnCreatedAt();
+
+            // 筛选器
+            $grid->filter(function (Grid\Filter $filter) {
+                $filter->equal('id', 'ID');
+                $filter->equal('item_id', '商品ID');
+                $filter->equal('admin_user_id', '操作管理员');
+                $filter->equal('adjustment_type', '调整类型')->select(PriceAdjustmentType::getOptions());
+                $filter->like('adjustment_reason', '调整原因');
+                $filter->between('created_at', '调整时间')->datetime();
+            });
+
+            // 默认排序
+            $grid->model()->orderBy('created_at', 'desc');
+
+            // 禁用新增、编辑、删除操作
+            $grid->disableCreateButton();
+            $grid->disableActions();
+            $grid->disableBatchActions();
+        });
+    }
+
+    /**
+     * 详情页面
+     */
+    protected function detail($id)
+    {
+        return Show::make($id, new MexPriceAdjustmentRepository(['priceConfig', 'item']), function (Show $show) {
+            $show->field('id', 'ID');
+            $show->field('price_config_id', '价格配置ID');
+            $show->field('item_id', '商品ID');
+            $show->field('item.name', '商品名称');
+            $show->field('admin_user_id', '操作管理员');
+            $show->field('adjustment_type', '调整类型')->as(function ($value) {
+                if (is_string($value)) {
+                    return PriceAdjustmentType::from($value)->getDescription();
+                }
+                return $value->getDescription();
+            });
+
+            $show->divider('价格调整详情');
+            
+            $show->field('old_min_price', '调整前最低价')->as(function ($value) {
+                return $value ? number_format($value, 5) : '-';
+            });
+            $show->field('new_min_price', '调整后最低价')->as(function ($value) {
+                return $value ? number_format($value, 5) : '-';
+            });
+            $show->field('old_max_price', '调整前最高价')->as(function ($value) {
+                return $value ? number_format($value, 5) : '-';
+            });
+            $show->field('new_max_price', '调整后最高价')->as(function ($value) {
+                return $value ? number_format($value, 5) : '-';
+            });
+            $show->field('old_protection_threshold', '调整前保护阈值');
+            $show->field('new_protection_threshold', '调整后保护阈值');
+            $show->field('old_is_enabled', '调整前启用状态')->using([0 => '禁用', 1 => '启用']);
+            $show->field('new_is_enabled', '调整后启用状态')->using([0 => '禁用', 1 => '启用']);
+
+            $show->divider('调整说明');
+            
+            $show->field('adjustment_reason', '调整原因');
+            $show->field('market_impact_note', '市场影响说明');
+            $show->field('created_at', '调整时间');
+
+            // 禁用编辑和删除按钮
+            $show->disableEditButton();
+            $show->disableDeleteButton();
+        });
+    }
+}

+ 71 - 1
app/Module/Mex/AdminControllers/MexPriceConfigController.php

@@ -5,11 +5,13 @@ namespace App\Module\Mex\AdminControllers;
 use App\Module\Mex\AdminControllers\Helper\GridHelper;
 use App\Module\Mex\Repositories\MexPriceConfigRepository;
 use App\Module\Mex\Models\MexPriceConfig;
+use App\Module\Mex\Service\MexPriceAdjustmentService;
 use Spatie\RouteAttributes\Attributes\Resource;
 use UCore\DcatAdmin\AdminController;
 use Dcat\Admin\Form;
 use Dcat\Admin\Grid;
 use Dcat\Admin\Show;
+use Dcat\Admin\Admin;
 
 /**
  * 农贸市场价格配置管理
@@ -136,7 +138,26 @@ class MexPriceConfigController extends AdminController
             $form->display('created_at', '创建时间');
             $form->display('updated_at', '更新时间');
 
-            // 表单验证
+            // 添加调整原因字段(仅在编辑时显示,不保存到数据库)
+            if (!$form->isCreating()) {
+                $form->textarea('adjustment_reason', '调整原因')
+                    ->placeholder('请说明本次价格调整的原因...')
+                    ->help('记录价格调整的原因,便于后续追踪')
+                    ->customFormat(function ($value) {
+                        // 不保存到数据库
+                        return null;
+                    });
+
+                $form->textarea('market_impact_note', '市场影响说明')
+                    ->placeholder('请说明本次调整对市场可能产生的影响...')
+                    ->help('可选:说明价格调整对市场的预期影响')
+                    ->customFormat(function ($value) {
+                        // 不保存到数据库
+                        return null;
+                    });
+            }
+
+            // 表单验证和保存处理
             $form->saving(function (Form $form) {
                 // 转换价格为数值类型进行比较
                 $minPrice = (float)$form->min_price;
@@ -167,6 +188,55 @@ class MexPriceConfigController extends AdminController
                         return $form->response()->error('该商品已存在价格配置');
                     }
                 }
+
+                // 保存调整原因和市场影响说明到变量中,然后移除字段
+                $adjustmentReason = $form->input('adjustment_reason');
+                $marketImpactNote = $form->input('market_impact_note');
+
+                // 移除不需要保存到数据库的字段
+                $form->deleteInput('adjustment_reason');
+                $form->deleteInput('market_impact_note');
+            });
+
+            // 保存前处理 - 获取旧配置数据
+            $oldConfig = null;
+            $adjustmentReason = null;
+            $marketImpactNote = null;
+
+            $form->saving(function (Form $form) use (&$oldConfig, &$adjustmentReason, &$marketImpactNote) {
+                if (!$form->isCreating()) {
+                    // 保存调整前的配置数据
+                    $oldConfig = MexPriceConfig::find($form->model()->getKey());
+                }
+            });
+
+            // 保存后处理 - 记录价格调整历史
+            $form->saved(function (Form $form, $result) use (&$oldConfig, &$adjustmentReason, &$marketImpactNote) {
+                if (!$form->isCreating() && $oldConfig) {
+                    // 重新获取调整后的配置
+                    $newConfig = MexPriceConfig::find($form->model()->getKey());
+
+                    // 获取当前管理员用户ID
+                    $adminUserId = Admin::user()->id ?? 1;
+
+                    // 记录价格调整历史
+                    try {
+                        MexPriceAdjustmentService::recordPriceConfigAdjustment(
+                            $oldConfig,
+                            $newConfig,
+                            $adminUserId,
+                            $adjustmentReason,
+                            $marketImpactNote
+                        );
+                    } catch (\Exception $e) {
+                        // 记录日志但不影响主流程
+                        \Log::error('记录价格调整历史失败', [
+                            'error' => $e->getMessage(),
+                            'config_id' => $newConfig ? $newConfig->id : 'unknown',
+                            'admin_user_id' => $adminUserId,
+                        ]);
+                    }
+                }
             });
         });
     }

+ 142 - 0
app/Module/Mex/Commands/GenerateDailyPriceTrendsCommand.php

@@ -0,0 +1,142 @@
+<?php
+
+namespace App\Module\Mex\Commands;
+
+use App\Module\Mex\Service\MexDailyPriceTrendService;
+use Carbon\Carbon;
+use Illuminate\Console\Command;
+
+/**
+ * 生成每日价格趋势数据命令
+ */
+class GenerateDailyPriceTrendsCommand extends Command
+{
+    /**
+     * 命令签名
+     */
+    protected $signature = 'mex:generate-daily-trends 
+                            {--date= : 指定日期 (Y-m-d 格式,默认为昨天)}
+                            {--start-date= : 开始日期 (Y-m-d 格式)}
+                            {--end-date= : 结束日期 (Y-m-d 格式)}
+                            {--item-id= : 指定商品ID}';
+
+    /**
+     * 命令描述
+     */
+    protected $description = '生成农贸市场每日价格趋势数据';
+
+    /**
+     * 执行命令
+     */
+    public function handle(): int
+    {
+        $this->info('开始生成每日价格趋势数据...');
+
+        try {
+            $date = $this->option('date');
+            $startDate = $this->option('start-date');
+            $endDate = $this->option('end-date');
+            $itemId = $this->option('item-id');
+
+            if ($startDate && $endDate) {
+                // 批量生成指定日期范围的趋势数据
+                $this->generateDateRangeTrends($startDate, $endDate, $itemId);
+            } elseif ($date) {
+                // 生成指定日期的趋势数据
+                $this->generateSingleDateTrends($date, $itemId);
+            } else {
+                // 默认生成昨天的趋势数据
+                $yesterday = Carbon::yesterday()->format('Y-m-d');
+                $this->generateSingleDateTrends($yesterday, $itemId);
+            }
+
+            $this->info('每日价格趋势数据生成完成!');
+            return 0;
+
+        } catch (\Exception $e) {
+            $this->error('生成每日价格趋势数据失败: ' . $e->getMessage());
+            $this->error('错误详情: ' . $e->getTraceAsString());
+            return 1;
+        }
+    }
+
+    /**
+     * 生成单个日期的趋势数据
+     */
+    private function generateSingleDateTrends(string $date, ?int $itemId = null): void
+    {
+        $this->info("正在生成 {$date} 的价格趋势数据...");
+
+        if ($itemId) {
+            $this->info("指定商品ID: {$itemId}");
+        }
+
+        $trends = MexDailyPriceTrendService::generateMultipleDaysTrends(
+            $date,
+            $date,
+            $itemId
+        );
+
+        $count = $trends->count();
+        $this->info("成功生成 {$count} 条价格趋势记录");
+
+        if ($count > 0) {
+            $this->table(
+                ['商品ID', '开盘价', '收盘价', '最高价', '最低价', '成交量', '成交额'],
+                $trends->map(function ($trend) {
+                    return [
+                        $trend->itemId,
+                        number_format($trend->openPrice, 5),
+                        number_format($trend->closePrice, 5),
+                        number_format($trend->highPrice, 5),
+                        number_format($trend->lowPrice, 5),
+                        $trend->totalVolume,
+                        number_format($trend->totalAmount, 5),
+                    ];
+                })->toArray()
+            );
+        }
+    }
+
+    /**
+     * 生成日期范围的趋势数据
+     */
+    private function generateDateRangeTrends(string $startDate, string $endDate, ?int $itemId = null): void
+    {
+        $this->info("正在生成 {$startDate} 到 {$endDate} 的价格趋势数据...");
+
+        if ($itemId) {
+            $this->info("指定商品ID: {$itemId}");
+        }
+
+        $start = Carbon::parse($startDate);
+        $end = Carbon::parse($endDate);
+        $totalDays = $start->diffInDays($end) + 1;
+
+        $this->info("总共需要处理 {$totalDays} 天的数据");
+
+        $progressBar = $this->output->createProgressBar($totalDays);
+        $progressBar->start();
+
+        $totalTrends = 0;
+        $current = $start->copy();
+
+        while ($current <= $end) {
+            $dateStr = $current->format('Y-m-d');
+            
+            $trends = MexDailyPriceTrendService::generateMultipleDaysTrends(
+                $dateStr,
+                $dateStr,
+                $itemId
+            );
+
+            $totalTrends += $trends->count();
+            $progressBar->advance();
+            $current->addDay();
+        }
+
+        $progressBar->finish();
+        $this->newLine();
+        $this->info("成功生成 {$totalTrends} 条价格趋势记录");
+    }
+}

+ 41 - 0
app/Module/Mex/Databases/GenerateSql/mex_daily_price_trends.sql

@@ -0,0 +1,41 @@
+-- ******************************************************************
+-- 农贸市场每日价格趋势表
+-- 执行时间: 2025年06月22日
+-- 说明: 记录每日商品价格统计数据,用于价格趋势分析和图表展示
+-- ******************************************************************
+
+CREATE TABLE `kku_mex_daily_price_trends` (
+  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '趋势记录ID,主键',
+  `item_id` int NOT NULL COMMENT '商品ID,关联物品表',
+  `currency_type` int NOT NULL DEFAULT 2 COMMENT '币种类型,关联FUND_CURRENCY_TYPE枚举,默认2为钻石',
+  `trade_date` date NOT NULL COMMENT '交易日期',
+  `open_price` decimal(15,5) DEFAULT NULL COMMENT '开盘价(当日第一笔成交价格)',
+  `close_price` decimal(15,5) DEFAULT NULL COMMENT '收盘价(当日最后一笔成交价格)',
+  `high_price` decimal(15,5) DEFAULT NULL COMMENT '最高价(当日最高成交价格)',
+  `low_price` decimal(15,5) DEFAULT NULL COMMENT '最低价(当日最低成交价格)',
+  `avg_price` decimal(15,5) DEFAULT NULL COMMENT '平均价(当日成交均价)',
+  `total_volume` int NOT NULL DEFAULT 0 COMMENT '成交量(当日总成交数量)',
+  `total_amount` decimal(20,5) NOT NULL DEFAULT '0.00000' COMMENT '成交额(当日总成交金额)',
+  `transaction_count` int NOT NULL DEFAULT 0 COMMENT '成交笔数(当日总成交次数)',
+  `buy_volume` int NOT NULL DEFAULT 0 COMMENT '买入量(用户买入总数量)',
+  `sell_volume` int NOT NULL DEFAULT 0 COMMENT '卖出量(用户卖出总数量)',
+  `buy_amount` decimal(20,5) NOT NULL DEFAULT '0.00000' COMMENT '买入额(用户买入总金额)',
+  `sell_amount` decimal(20,5) NOT NULL DEFAULT '0.00000' COMMENT '卖出额(用户卖出总金额)',
+  `admin_inject_volume` int NOT NULL DEFAULT 0 COMMENT '管理员注入量',
+  `admin_recycle_volume` int NOT NULL DEFAULT 0 COMMENT '管理员回收量',
+  `admin_inject_amount` decimal(20,5) NOT NULL DEFAULT '0.00000' COMMENT '管理员注入金额',
+  `admin_recycle_amount` decimal(20,5) NOT NULL DEFAULT '0.00000' COMMENT '管理员回收金额',
+  `price_change` decimal(15,5) DEFAULT NULL COMMENT '价格变化(相对前一日收盘价)',
+  `price_change_percent` decimal(8,4) DEFAULT NULL COMMENT '价格变化百分比',
+  `volatility` decimal(8,4) DEFAULT NULL COMMENT '波动率((最高价-最低价)/开盘价*100)',
+  `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  PRIMARY KEY (`id`) USING BTREE,
+  UNIQUE KEY `uk_item_currency_date` (`item_id`,`currency_type`,`trade_date`) USING BTREE COMMENT '商品币种日期唯一索引',
+  KEY `idx_item_id` (`item_id`) USING BTREE COMMENT '商品ID索引',
+  KEY `idx_currency_type` (`currency_type`) USING BTREE COMMENT '币种类型索引',
+  KEY `idx_trade_date` (`trade_date`) USING BTREE COMMENT '交易日期索引',
+  KEY `idx_total_volume` (`total_volume`) USING BTREE COMMENT '成交量索引',
+  KEY `idx_total_amount` (`total_amount`) USING BTREE COMMENT '成交额索引',
+  KEY `idx_created_at` (`created_at`) USING BTREE COMMENT '创建时间索引'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='农贸市场每日价格趋势表';

+ 31 - 0
app/Module/Mex/Databases/GenerateSql/mex_price_adjustments.sql

@@ -0,0 +1,31 @@
+-- ******************************************************************
+-- 农贸市场价格调整记录表
+-- 执行时间: 2025年06月22日
+-- 说明: 记录管理员对价格配置的调整历史,便于追踪价格变化
+-- ******************************************************************
+
+CREATE TABLE `kku_mex_price_adjustments` (
+  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '调整记录ID,主键',
+  `price_config_id` bigint unsigned NOT NULL COMMENT '价格配置ID,关联mex_price_configs表',
+  `item_id` int NOT NULL COMMENT '商品ID,关联物品表',
+  `admin_user_id` bigint NOT NULL COMMENT '操作管理员用户ID',
+  `adjustment_type` enum('MIN_PRICE','MAX_PRICE','PROTECTION_THRESHOLD','STATUS','BATCH') COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '调整类型:MIN_PRICE最低价,MAX_PRICE最高价,PROTECTION_THRESHOLD保护阈值,STATUS启用状态,BATCH批量调整',
+  `old_min_price` decimal(15,5) DEFAULT NULL COMMENT '调整前最低价',
+  `new_min_price` decimal(15,5) DEFAULT NULL COMMENT '调整后最低价',
+  `old_max_price` decimal(15,5) DEFAULT NULL COMMENT '调整前最高价',
+  `new_max_price` decimal(15,5) DEFAULT NULL COMMENT '调整后最高价',
+  `old_protection_threshold` int DEFAULT NULL COMMENT '调整前保护阈值',
+  `new_protection_threshold` int DEFAULT NULL COMMENT '调整后保护阈值',
+  `old_is_enabled` tinyint(1) DEFAULT NULL COMMENT '调整前启用状态',
+  `new_is_enabled` tinyint(1) DEFAULT NULL COMMENT '调整后启用状态',
+  `adjustment_reason` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '调整原因',
+  `market_impact_note` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '市场影响说明',
+  `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '调整时间',
+  PRIMARY KEY (`id`) USING BTREE,
+  KEY `idx_price_config_id` (`price_config_id`) USING BTREE COMMENT '价格配置ID索引',
+  KEY `idx_item_id` (`item_id`) USING BTREE COMMENT '商品ID索引',
+  KEY `idx_admin_user_id` (`admin_user_id`) USING BTREE COMMENT '管理员用户ID索引',
+  KEY `idx_adjustment_type` (`adjustment_type`) USING BTREE COMMENT '调整类型索引',
+  KEY `idx_created_at` (`created_at`) USING BTREE COMMENT '调整时间索引',
+  CONSTRAINT `fk_mex_price_adjustments_price_config_id` FOREIGN KEY (`price_config_id`) REFERENCES `kku_mex_price_configs` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='农贸市场价格调整记录表';

+ 78 - 0
app/Module/Mex/Dto/MexDailyPriceTrendDto.php

@@ -0,0 +1,78 @@
+<?php
+
+namespace App\Module\Mex\Dto;
+
+use App\Module\Fund\Enums\FUND_CURRENCY_TYPE;
+use App\Module\Mex\Models\MexDailyPriceTrend;
+use UCore\Dto\BaseDto;
+
+/**
+ * 农贸市场每日价格趋势DTO
+ */
+class MexDailyPriceTrendDto extends BaseDto
+{
+    public int $id;
+    public int $itemId;
+    public FUND_CURRENCY_TYPE $currencyType;
+    public string $tradeDate;
+    public ?float $openPrice;
+    public ?float $closePrice;
+    public ?float $highPrice;
+    public ?float $lowPrice;
+    public ?float $avgPrice;
+    public int $totalVolume;
+    public float $totalAmount;
+    public int $transactionCount;
+    public int $buyVolume;
+    public int $sellVolume;
+    public float $buyAmount;
+    public float $sellAmount;
+    public int $adminInjectVolume;
+    public int $adminRecycleVolume;
+    public float $adminInjectAmount;
+    public float $adminRecycleAmount;
+    public ?float $priceChange;
+    public ?float $priceChangePercent;
+    public ?float $volatility;
+    public string $createdAt;
+    public string $updatedAt;
+    public string $priceTrendDescription;
+    public string $volatilityLevel;
+
+    /**
+     * 从模型创建DTO
+     */
+    public static function fromModel(MexDailyPriceTrend $model): self
+    {
+        $dto = new self();
+        $dto->id = $model->id;
+        $dto->itemId = $model->item_id;
+        $dto->currencyType = $model->currency_type;
+        $dto->tradeDate = $model->trade_date->format('Y-m-d');
+        $dto->openPrice = $model->open_price;
+        $dto->closePrice = $model->close_price;
+        $dto->highPrice = $model->high_price;
+        $dto->lowPrice = $model->low_price;
+        $dto->avgPrice = $model->avg_price;
+        $dto->totalVolume = $model->total_volume;
+        $dto->totalAmount = $model->total_amount;
+        $dto->transactionCount = $model->transaction_count;
+        $dto->buyVolume = $model->buy_volume;
+        $dto->sellVolume = $model->sell_volume;
+        $dto->buyAmount = $model->buy_amount;
+        $dto->sellAmount = $model->sell_amount;
+        $dto->adminInjectVolume = $model->admin_inject_volume;
+        $dto->adminRecycleVolume = $model->admin_recycle_volume;
+        $dto->adminInjectAmount = $model->admin_inject_amount;
+        $dto->adminRecycleAmount = $model->admin_recycle_amount;
+        $dto->priceChange = $model->price_change;
+        $dto->priceChangePercent = $model->price_change_percent;
+        $dto->volatility = $model->volatility;
+        $dto->createdAt = $model->created_at->format('Y-m-d H:i:s');
+        $dto->updatedAt = $model->updated_at->format('Y-m-d H:i:s');
+        $dto->priceTrendDescription = $model->price_trend_description;
+        $dto->volatilityLevel = $model->volatility_level;
+        
+        return $dto;
+    }
+}

+ 58 - 0
app/Module/Mex/Dto/MexPriceAdjustmentDto.php

@@ -0,0 +1,58 @@
+<?php
+
+namespace App\Module\Mex\Dto;
+
+use App\Module\Mex\Enums\PriceAdjustmentType;
+use App\Module\Mex\Models\MexPriceAdjustment;
+use UCore\Dto\BaseDto;
+
+/**
+ * 农贸市场价格调整记录DTO
+ */
+class MexPriceAdjustmentDto extends BaseDto
+{
+    public int $id;
+    public int $priceConfigId;
+    public int $itemId;
+    public int $adminUserId;
+    public PriceAdjustmentType $adjustmentType;
+    public ?float $oldMinPrice;
+    public ?float $newMinPrice;
+    public ?float $oldMaxPrice;
+    public ?float $newMaxPrice;
+    public ?int $oldProtectionThreshold;
+    public ?int $newProtectionThreshold;
+    public ?bool $oldIsEnabled;
+    public ?bool $newIsEnabled;
+    public ?string $adjustmentReason;
+    public ?string $marketImpactNote;
+    public string $createdAt;
+    public string $priceChangeSummary;
+
+    /**
+     * 从模型创建DTO
+     */
+    public static function fromModel(MexPriceAdjustment $model): self
+    {
+        $dto = new self();
+        $dto->id = $model->id;
+        $dto->priceConfigId = $model->price_config_id;
+        $dto->itemId = $model->item_id;
+        $dto->adminUserId = $model->admin_user_id;
+        $dto->adjustmentType = $model->adjustment_type;
+        $dto->oldMinPrice = $model->old_min_price;
+        $dto->newMinPrice = $model->new_min_price;
+        $dto->oldMaxPrice = $model->old_max_price;
+        $dto->newMaxPrice = $model->new_max_price;
+        $dto->oldProtectionThreshold = $model->old_protection_threshold;
+        $dto->newProtectionThreshold = $model->new_protection_threshold;
+        $dto->oldIsEnabled = $model->old_is_enabled;
+        $dto->newIsEnabled = $model->new_is_enabled;
+        $dto->adjustmentReason = $model->adjustment_reason;
+        $dto->marketImpactNote = $model->market_impact_note;
+        $dto->createdAt = $model->created_at->format('Y-m-d H:i:s');
+        $dto->priceChangeSummary = $model->price_change_summary;
+        
+        return $dto;
+    }
+}

+ 43 - 0
app/Module/Mex/Enums/PriceAdjustmentType.php

@@ -0,0 +1,43 @@
+<?php
+
+namespace App\Module\Mex\Enums;
+
+/**
+ * 价格调整类型枚举
+ */
+enum PriceAdjustmentType: string
+{
+    case MIN_PRICE = 'MIN_PRICE';              // 最低价调整
+    case MAX_PRICE = 'MAX_PRICE';              // 最高价调整
+    case PROTECTION_THRESHOLD = 'PROTECTION_THRESHOLD'; // 保护阈值调整
+    case STATUS = 'STATUS';                    // 启用状态调整
+    case BATCH = 'BATCH';                      // 批量调整
+
+    /**
+     * 获取调整类型描述
+     */
+    public function getDescription(): string
+    {
+        return match ($this) {
+            self::MIN_PRICE => '最低价调整',
+            self::MAX_PRICE => '最高价调整',
+            self::PROTECTION_THRESHOLD => '保护阈值调整',
+            self::STATUS => '启用状态调整',
+            self::BATCH => '批量调整',
+        };
+    }
+
+    /**
+     * 获取所有调整类型选项
+     */
+    public static function getOptions(): array
+    {
+        return [
+            self::MIN_PRICE->value => self::MIN_PRICE->getDescription(),
+            self::MAX_PRICE->value => self::MAX_PRICE->getDescription(),
+            self::PROTECTION_THRESHOLD->value => self::PROTECTION_THRESHOLD->getDescription(),
+            self::STATUS->value => self::STATUS->getDescription(),
+            self::BATCH->value => self::BATCH->getDescription(),
+        ];
+    }
+}

+ 28 - 0
app/Module/Mex/Listeners/TransactionCreatedListener.php

@@ -3,6 +3,7 @@
 namespace App\Module\Mex\Listeners;
 
 use App\Module\Mex\Events\TransactionCreatedEvent;
+use App\Module\Mex\Service\MexDailyPriceTrendService;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Cache;
 
@@ -41,6 +42,9 @@ class TransactionCreatedListener
         // 更新价格趋势
         $this->updatePriceTrend($transaction);
 
+        // 更新每日价格趋势数据
+        $this->updateDailyPriceTrend($transaction);
+
         // 检查异常交易
         $this->checkAbnormalTransaction($transaction);
     }
@@ -118,6 +122,30 @@ class TransactionCreatedListener
         Cache::put($priceHistoryKey, $history, 3600);
     }
 
+    /**
+     * 更新每日价格趋势数据
+     */
+    protected function updateDailyPriceTrend($transaction): void
+    {
+        try {
+            // 异步生成当日价格趋势数据
+            $today = date('Y-m-d');
+            MexDailyPriceTrendService::generateDailyTrend(
+                $today,
+                $transaction->item_id,
+                $transaction->currency_type
+            );
+        } catch (\Exception $e) {
+            // 记录错误但不影响主流程
+            Log::error('更新每日价格趋势失败', [
+                'error' => $e->getMessage(),
+                'transaction_id' => $transaction->id,
+                'item_id' => $transaction->item_id,
+                'currency_type' => $transaction->currency_type->value,
+            ]);
+        }
+    }
+
     /**
      * 检查异常交易
      */

+ 264 - 0
app/Module/Mex/Logic/MexDailyPriceTrendLogic.php

@@ -0,0 +1,264 @@
+<?php
+
+namespace App\Module\Mex\Logic;
+
+use App\Module\Fund\Enums\FUND_CURRENCY_TYPE;
+use App\Module\Mex\Dto\MexDailyPriceTrendDto;
+use App\Module\Mex\Models\MexDailyPriceTrend;
+use App\Module\Mex\Models\MexTransaction;
+use Carbon\Carbon;
+use Illuminate\Support\Collection;
+
+
+/**
+ * 农贸市场每日价格趋势逻辑层
+ */
+class MexDailyPriceTrendLogic
+{
+    /**
+     * 生成指定日期的价格趋势数据
+     */
+    public function generateDailyTrend(string $date, int $itemId, FUND_CURRENCY_TYPE $currencyType): ?MexDailyPriceTrendDto
+    {
+        $startDate = Carbon::parse($date)->startOfDay();
+        $endDate = Carbon::parse($date)->endOfDay();
+
+        // 获取当日所有成交记录
+        $transactions = MexTransaction::where('item_id', $itemId)
+            ->where('currency_type', $currencyType)
+            ->whereBetween('created_at', [$startDate, $endDate])
+            ->orderBy('created_at')
+            ->get();
+
+        if ($transactions->isEmpty()) {
+            return null;
+        }
+
+        // 计算价格统计
+        $priceStats = $this->calculatePriceStatistics($transactions);
+        
+        // 计算交易统计
+        $tradeStats = $this->calculateTradeStatistics($transactions);
+        
+        // 获取前一日收盘价用于计算价格变化
+        $previousClosePrice = $this->getPreviousClosePrice($date, $itemId, $currencyType);
+        
+        // 计算价格变化
+        $priceChange = $this->calculatePriceChange($priceStats['close_price'], $previousClosePrice);
+
+        // 创建或更新趋势记录
+        $trend = MexDailyPriceTrend::updateOrCreate(
+            [
+                'item_id' => $itemId,
+                'currency_type' => $currencyType,
+                'trade_date' => $date,
+            ],
+            array_merge($priceStats, $tradeStats, $priceChange)
+        );
+
+        return MexDailyPriceTrendDto::fromModel($trend);
+    }
+
+    /**
+     * 批量生成多日价格趋势数据
+     */
+    public function generateMultipleDaysTrends(
+        string $startDate,
+        string $endDate,
+        ?int $itemId = null,
+        ?FUND_CURRENCY_TYPE $currencyType = null
+    ): Collection {
+        $results = collect();
+        $current = Carbon::parse($startDate);
+        $end = Carbon::parse($endDate);
+
+        while ($current <= $end) {
+            $dateStr = $current->format('Y-m-d');
+            
+            if ($itemId && $currencyType) {
+                // 生成指定商品的趋势
+                $trend = $this->generateDailyTrend($dateStr, $itemId, $currencyType);
+                if ($trend) {
+                    $results->push($trend);
+                }
+            } else {
+                // 生成所有商品的趋势
+                $this->generateAllItemsTrendsForDate($dateStr, $results);
+            }
+            
+            $current->addDay();
+        }
+
+        return $results;
+    }
+
+    /**
+     * 获取商品的价格趋势历史
+     */
+    public function getItemPriceTrends(
+        int $itemId,
+        FUND_CURRENCY_TYPE $currencyType,
+        string $startDate,
+        string $endDate,
+        int $limit = 100
+    ): Collection {
+        $trends = MexDailyPriceTrend::where('item_id', $itemId)
+            ->where('currency_type', $currencyType)
+            ->whereBetween('trade_date', [$startDate, $endDate])
+            ->orderBy('trade_date', 'desc')
+            ->limit($limit)
+            ->get();
+
+        return $trends->map(fn($trend) => MexDailyPriceTrendDto::fromModel($trend));
+    }
+
+    /**
+     * 获取价格趋势统计信息
+     */
+    public function getTrendStatistics(
+        int $itemId,
+        FUND_CURRENCY_TYPE $currencyType,
+        string $startDate,
+        string $endDate
+    ): array {
+        $trends = MexDailyPriceTrend::where('item_id', $itemId)
+            ->where('currency_type', $currencyType)
+            ->whereBetween('trade_date', [$startDate, $endDate])
+            ->get();
+
+        if ($trends->isEmpty()) {
+            return [];
+        }
+
+        return [
+            'period_start' => $startDate,
+            'period_end' => $endDate,
+            'trading_days' => $trends->count(),
+            'total_volume' => $trends->sum('total_volume'),
+            'total_amount' => $trends->sum('total_amount'),
+            'avg_daily_volume' => $trends->avg('total_volume'),
+            'avg_daily_amount' => $trends->avg('total_amount'),
+            'highest_price' => $trends->max('high_price'),
+            'lowest_price' => $trends->min('low_price'),
+            'period_start_price' => $trends->sortBy('trade_date')->first()->open_price,
+            'period_end_price' => $trends->sortByDesc('trade_date')->first()->close_price,
+            'avg_volatility' => $trends->avg('volatility'),
+            'max_volatility' => $trends->max('volatility'),
+        ];
+    }
+
+    /**
+     * 计算价格统计
+     */
+    private function calculatePriceStatistics(Collection $transactions): array
+    {
+        $prices = $transactions->pluck('price');
+
+        // 计算加权平均价格
+        $totalAmount = $transactions->sum('total_amount');
+        $totalQuantity = $transactions->sum('quantity');
+        $avgPrice = $totalQuantity > 0 ? $totalAmount / $totalQuantity : 0;
+
+        $openPrice = $transactions->first()->price;
+        $closePrice = $transactions->last()->price;
+        $highPrice = $prices->max();
+        $lowPrice = $prices->min();
+
+        // 计算波动率
+        $volatility = $openPrice > 0 ? (($highPrice - $lowPrice) / $openPrice * 100) : 0;
+
+        return [
+            'open_price' => $openPrice,
+            'close_price' => $closePrice,
+            'high_price' => $highPrice,
+            'low_price' => $lowPrice,
+            'avg_price' => $avgPrice,
+            'volatility' => $volatility,
+        ];
+    }
+
+    /**
+     * 计算交易统计
+     */
+    private function calculateTradeStatistics(Collection $transactions): array
+    {
+        $totalVolume = $transactions->sum('quantity');
+        $totalAmount = $transactions->sum('total_amount');
+        $transactionCount = $transactions->count();
+
+        // 按交易类型分组统计
+        $userBuyTransactions = $transactions->where('transaction_type', 'USER_BUY');
+        $userSellTransactions = $transactions->where('transaction_type', 'USER_SELL');
+        $adminInjectTransactions = $transactions->where('transaction_type', 'ADMIN_INJECT');
+        $adminRecycleTransactions = $transactions->where('transaction_type', 'ADMIN_RECYCLE');
+
+        return [
+            'total_volume' => $totalVolume,
+            'total_amount' => $totalAmount,
+            'transaction_count' => $transactionCount,
+            'buy_volume' => $userBuyTransactions->sum('quantity'),
+            'sell_volume' => $userSellTransactions->sum('quantity'),
+            'buy_amount' => $userBuyTransactions->sum('total_amount'),
+            'sell_amount' => $userSellTransactions->sum('total_amount'),
+            'admin_inject_volume' => $adminInjectTransactions->sum('quantity'),
+            'admin_recycle_volume' => $adminRecycleTransactions->sum('quantity'),
+            'admin_inject_amount' => $adminInjectTransactions->sum('total_amount'),
+            'admin_recycle_amount' => $adminRecycleTransactions->sum('total_amount'),
+        ];
+    }
+
+    /**
+     * 获取前一日收盘价
+     */
+    private function getPreviousClosePrice(string $date, int $itemId, FUND_CURRENCY_TYPE $currencyType): ?float
+    {
+        $previousDate = Carbon::parse($date)->subDay()->format('Y-m-d');
+
+        $previousTrend = MexDailyPriceTrend::where('item_id', $itemId)
+            ->where('currency_type', $currencyType)
+            ->where('trade_date', $previousDate)
+            ->first();
+
+        return $previousTrend?->close_price;
+    }
+
+    /**
+     * 计算价格变化
+     */
+    private function calculatePriceChange(float $currentPrice, ?float $previousPrice): array
+    {
+        if ($previousPrice === null || $previousPrice == 0) {
+            return [
+                'price_change' => null,
+                'price_change_percent' => null,
+            ];
+        }
+
+        $priceChange = $currentPrice - $previousPrice;
+        $priceChangePercent = ($priceChange / $previousPrice) * 100;
+
+        return [
+            'price_change' => $priceChange,
+            'price_change_percent' => $priceChangePercent,
+        ];
+    }
+
+    /**
+     * 生成指定日期所有商品的趋势数据
+     */
+    private function generateAllItemsTrendsForDate(string $date, Collection &$results): void
+    {
+        // 获取当日有交易的所有商品和币种组合
+        $itemCurrencyPairs = MexTransaction::whereDate('created_at', $date)
+            ->select('item_id', 'currency_type')
+            ->distinct()
+            ->get();
+
+        foreach ($itemCurrencyPairs as $pair) {
+            $trend = $this->generateDailyTrend($date, $pair->item_id, $pair->currency_type);
+            if ($trend) {
+                $results->push($trend);
+            }
+        }
+    }
+}

+ 165 - 0
app/Module/Mex/Logic/MexPriceAdjustmentLogic.php

@@ -0,0 +1,165 @@
+<?php
+
+namespace App\Module\Mex\Logic;
+
+use App\Module\Mex\Dto\MexPriceAdjustmentDto;
+use App\Module\Mex\Enums\PriceAdjustmentType;
+use App\Module\Mex\Models\MexPriceAdjustment;
+use App\Module\Mex\Models\MexPriceConfig;
+use Illuminate\Support\Collection;
+
+/**
+ * 农贸市场价格调整记录逻辑层
+ */
+class MexPriceAdjustmentLogic
+{
+    /**
+     * 记录价格配置调整
+     */
+    public function recordPriceConfigAdjustment(
+        MexPriceConfig $oldConfig,
+        MexPriceConfig $newConfig,
+        int $adminUserId,
+        ?string $adjustmentReason = null,
+        ?string $marketImpactNote = null
+    ): MexPriceAdjustmentDto {
+        // 确定调整类型
+        $adjustmentType = $this->determineAdjustmentType($oldConfig, $newConfig);
+        
+        $adjustment = MexPriceAdjustment::create([
+            'price_config_id' => $newConfig->id,
+            'item_id' => $newConfig->item_id,
+            'admin_user_id' => $adminUserId,
+            'adjustment_type' => $adjustmentType,
+            'old_min_price' => $oldConfig->min_price,
+            'new_min_price' => $newConfig->min_price,
+            'old_max_price' => $oldConfig->max_price,
+            'new_max_price' => $newConfig->max_price,
+            'old_protection_threshold' => $oldConfig->protection_threshold,
+            'new_protection_threshold' => $newConfig->protection_threshold,
+            'old_is_enabled' => $oldConfig->is_enabled,
+            'new_is_enabled' => $newConfig->is_enabled,
+            'adjustment_reason' => $adjustmentReason,
+            'market_impact_note' => $marketImpactNote,
+        ]);
+
+        return MexPriceAdjustmentDto::fromModel($adjustment);
+    }
+
+    /**
+     * 获取商品的价格调整历史
+     */
+    public function getItemAdjustmentHistory(int $itemId, int $limit = 50): Collection
+    {
+        $adjustments = MexPriceAdjustment::where('item_id', $itemId)
+            ->with(['priceConfig', 'item'])
+            ->orderBy('created_at', 'desc')
+            ->limit($limit)
+            ->get();
+
+        return $adjustments->map(fn($adjustment) => MexPriceAdjustmentDto::fromModel($adjustment));
+    }
+
+    /**
+     * 获取管理员的调整操作记录
+     */
+    public function getAdminAdjustmentHistory(int $adminUserId, int $limit = 50): Collection
+    {
+        $adjustments = MexPriceAdjustment::where('admin_user_id', $adminUserId)
+            ->with(['priceConfig', 'item'])
+            ->orderBy('created_at', 'desc')
+            ->limit($limit)
+            ->get();
+
+        return $adjustments->map(fn($adjustment) => MexPriceAdjustmentDto::fromModel($adjustment));
+    }
+
+    /**
+     * 获取指定时间范围内的调整记录
+     */
+    public function getAdjustmentsByDateRange(
+        string $startDate,
+        string $endDate,
+        ?int $itemId = null,
+        ?int $adminUserId = null
+    ): Collection {
+        $query = MexPriceAdjustment::whereBetween('created_at', [$startDate, $endDate])
+            ->with(['priceConfig', 'item']);
+
+        if ($itemId) {
+            $query->where('item_id', $itemId);
+        }
+
+        if ($adminUserId) {
+            $query->where('admin_user_id', $adminUserId);
+        }
+
+        $adjustments = $query->orderBy('created_at', 'desc')->get();
+
+        return $adjustments->map(fn($adjustment) => MexPriceAdjustmentDto::fromModel($adjustment));
+    }
+
+    /**
+     * 获取调整统计信息
+     */
+    public function getAdjustmentStatistics(string $startDate, string $endDate): array
+    {
+        $adjustments = MexPriceAdjustment::whereBetween('created_at', [$startDate, $endDate])->get();
+
+        $stats = [
+            'total_adjustments' => $adjustments->count(),
+            'by_type' => [],
+            'by_admin' => [],
+            'by_item' => [],
+        ];
+
+        // 按调整类型统计
+        $stats['by_type'] = $adjustments->groupBy('adjustment_type')
+            ->map(fn($group) => $group->count())
+            ->toArray();
+
+        // 按管理员统计
+        $stats['by_admin'] = $adjustments->groupBy('admin_user_id')
+            ->map(fn($group) => $group->count())
+            ->toArray();
+
+        // 按商品统计
+        $stats['by_item'] = $adjustments->groupBy('item_id')
+            ->map(fn($group) => $group->count())
+            ->toArray();
+
+        return $stats;
+    }
+
+    /**
+     * 确定调整类型
+     */
+    private function determineAdjustmentType(MexPriceConfig $oldConfig, MexPriceConfig $newConfig): PriceAdjustmentType
+    {
+        $changes = [];
+        
+        if ($oldConfig->min_price != $newConfig->min_price) {
+            $changes[] = PriceAdjustmentType::MIN_PRICE;
+        }
+        
+        if ($oldConfig->max_price != $newConfig->max_price) {
+            $changes[] = PriceAdjustmentType::MAX_PRICE;
+        }
+        
+        if ($oldConfig->protection_threshold != $newConfig->protection_threshold) {
+            $changes[] = PriceAdjustmentType::PROTECTION_THRESHOLD;
+        }
+        
+        if ($oldConfig->is_enabled != $newConfig->is_enabled) {
+            $changes[] = PriceAdjustmentType::STATUS;
+        }
+
+        // 如果有多个变化,返回批量调整
+        if (count($changes) > 1) {
+            return PriceAdjustmentType::BATCH;
+        }
+
+        // 如果只有一个变化,返回对应的类型
+        return $changes[0] ?? PriceAdjustmentType::BATCH;
+    }
+}

+ 179 - 0
app/Module/Mex/Metrics/PriceTrendChart.php

@@ -0,0 +1,179 @@
+<?php
+
+namespace App\Module\Mex\Metrics;
+
+use App\Module\Fund\Enums\FUND_CURRENCY_TYPE;
+use App\Module\Mex\Models\MexDailyPriceTrend;
+use Dcat\Admin\Widgets\Metrics\Line;
+use Illuminate\Http\Request;
+
+/**
+ * 农贸市场价格趋势图表
+ */
+class PriceTrendChart extends Line
+{
+    /**
+     * 初始化卡片内容
+     *
+     * @return void
+     */
+    protected function init()
+    {
+        parent::init();
+
+        $this->title('价格趋势');
+        $this->height(400);
+        $this->chartHeight(300);
+        
+        // 设置下拉选项
+        $this->dropdown([
+            '7' => '最近 7 天',
+            '14' => '最近 14 天',
+            '30' => '最近 30 天',
+            '90' => '最近 90 天',
+        ]);
+    }
+
+    /**
+     * 处理请求
+     *
+     * @param Request $request
+     * @return mixed|void
+     */
+    public function handle(Request $request)
+    {
+        $days = (int) $request->get('option', 7);
+        $itemId = $request->get('item_id');
+        $currencyType = $request->get('currency_type', FUND_CURRENCY_TYPE::ZUANSHI->value);
+        
+        $data = $this->getPriceTrendData($days, $itemId, $currencyType);
+        
+        // 卡片内容 - 显示最新价格
+        $latestPrice = $data['latest_price'] ?? 0;
+        $priceChange = $data['price_change'] ?? 0;
+        $changePercent = $data['change_percent'] ?? 0;
+        
+        $changeText = $priceChange >= 0 ? "+{$changePercent}%" : "{$changePercent}%";
+        $this->withContent(number_format($latestPrice, 5), $changeText);
+        
+        // 图表数据
+        $this->withChart($data['prices'], $data['dates']);
+    }
+
+    /**
+     * 获取价格趋势数据
+     *
+     * @param int $days
+     * @param int|null $itemId
+     * @param int $currencyType
+     * @return array
+     */
+    protected function getPriceTrendData(int $days, ?int $itemId = null, int $currencyType = 2): array
+    {
+        $startDate = now()->subDays($days - 1)->toDateString();
+        $endDate = now()->toDateString();
+        
+        $query = MexDailyPriceTrend::whereBetween('trade_date', [$startDate, $endDate])
+            ->where('currency_type', $currencyType)
+            ->orderBy('trade_date');
+            
+        if ($itemId) {
+            $query->where('item_id', $itemId);
+        }
+        
+        $trends = $query->get();
+        
+        $prices = [];
+        $dates = [];
+        $latestPrice = 0;
+        $firstPrice = 0;
+        
+        foreach ($trends as $trend) {
+            $prices[] = (float) $trend->close_price;
+            $dates[] = $trend->trade_date;
+            
+            if (!$firstPrice) {
+                $firstPrice = (float) $trend->close_price;
+            }
+            $latestPrice = (float) $trend->close_price;
+        }
+        
+        // 计算价格变化
+        $priceChange = $latestPrice - $firstPrice;
+        $changePercent = $firstPrice > 0 ? round(($priceChange / $firstPrice) * 100, 2) : 0;
+        
+        return [
+            'prices' => $prices,
+            'dates' => $dates,
+            'latest_price' => $latestPrice,
+            'price_change' => $priceChange,
+            'change_percent' => $changePercent,
+        ];
+    }
+
+    /**
+     * 设置图表数据
+     *
+     * @param array $data
+     * @param array $categories
+     * @return $this
+     */
+    public function withChart(array $data, array $categories)
+    {
+        return $this->chart([
+            'chart' => [
+                'type' => 'line',
+                'toolbar' => [
+                    'show' => true,
+                ],
+                'sparkline' => [
+                    'enabled' => false,
+                ],
+            ],
+            'series' => [
+                [
+                    'name' => '收盘价',
+                    'data' => $data,
+                ],
+            ],
+            'dataLabels' => [
+                'enabled' => true,
+            ],
+            'xaxis' => [
+                'type' => 'datetime',
+                'categories' => $categories,
+                'labels' => [
+                    'show' => true,
+                    'format' => 'MM-dd',
+                ],
+                'axisBorder' => [
+                    'show' => true,
+                ],
+            ],
+            'yaxis' => [
+                'labels' => [
+                    'show' => true,
+                    'formatter' => 'function(value) { return value.toFixed(5); }',
+                ],
+            ],
+            'tooltip' => [
+                'x' => [
+                    'show' => true,
+                    'format' => 'yyyy-MM-dd',
+                ],
+                'y' => [
+                    'formatter' => 'function(value) { return value.toFixed(5); }',
+                ],
+            ],
+            'stroke' => [
+                'width' => 3,
+                'curve' => 'smooth',
+            ],
+            'fill' => [
+                'opacity' => 0.1,
+                'type' => 'solid',
+            ],
+            'colors' => ['#1f77b4'],
+        ]);
+    }
+}

+ 137 - 0
app/Module/Mex/Models/MexDailyPriceTrend.php

@@ -0,0 +1,137 @@
+<?php
+
+namespace App\Module\Mex\Models;
+
+use App\Module\Fund\Enums\FUND_CURRENCY_TYPE;
+use App\Module\GameItems\Models\Item;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
+use UCore\ModelCore;
+
+/**
+ * 农贸市场每日价格趋势模型
+ *
+ * field start 
+ * @property  int  $id  趋势记录ID,主键
+ * @property  int  $item_id  商品ID,关联物品表
+ * @property  \App\Module\Fund\Enums\FUND_CURRENCY_TYPE  $currency_type  币种类型,关联FUND_CURRENCY_TYPE枚举,默认2为钻石
+ * @property  string  $trade_date  交易日期
+ * @property  float  $open_price  开盘价(当日第一笔成交价格)
+ * @property  float  $close_price  收盘价(当日最后一笔成交价格)
+ * @property  float  $high_price  最高价(当日最高成交价格)
+ * @property  float  $low_price  最低价(当日最低成交价格)
+ * @property  float  $avg_price  平均价(当日成交均价)
+ * @property  int  $total_volume  成交量(当日总成交数量)
+ * @property  float  $total_amount  成交额(当日总成交金额)
+ * @property  int  $transaction_count  成交笔数(当日总成交次数)
+ * @property  int  $buy_volume  买入量(用户买入总数量)
+ * @property  int  $sell_volume  卖出量(用户卖出总数量)
+ * @property  float  $buy_amount  买入额(用户买入总金额)
+ * @property  float  $sell_amount  卖出额(用户卖出总金额)
+ * @property  int  $admin_inject_volume  管理员注入量
+ * @property  int  $admin_recycle_volume  管理员回收量
+ * @property  float  $admin_inject_amount  管理员注入金额
+ * @property  float  $admin_recycle_amount  管理员回收金额
+ * @property  float  $price_change  价格变化(相对前一日收盘价)
+ * @property  float  $price_change_percent  价格变化百分比
+ * @property  float  $volatility  波动率((最高价-最低价)/开盘价*100)
+ * @property  \Carbon\Carbon  $created_at  创建时间
+ * @property  \Carbon\Carbon  $updated_at  更新时间
+ * field end
+ */
+class MexDailyPriceTrend extends ModelCore
+{
+    protected $table = 'mex_daily_price_trends';
+
+    protected $fillable = [
+        'item_id',
+        'currency_type',
+        'trade_date',
+        'open_price',
+        'close_price',
+        'high_price',
+        'low_price',
+        'avg_price',
+        'total_volume',
+        'total_amount',
+        'transaction_count',
+        'buy_volume',
+        'sell_volume',
+        'buy_amount',
+        'sell_amount',
+        'admin_inject_volume',
+        'admin_recycle_volume',
+        'admin_inject_amount',
+        'admin_recycle_amount',
+        'price_change',
+        'price_change_percent',
+        'volatility',
+    ];
+
+    protected $casts = [
+        'item_id' => 'integer',
+        'currency_type' => FUND_CURRENCY_TYPE::class,
+        'trade_date' => 'date',
+        'open_price' => 'decimal:5',
+        'close_price' => 'decimal:5',
+        'high_price' => 'decimal:5',
+        'low_price' => 'decimal:5',
+        'avg_price' => 'decimal:5',
+        'total_volume' => 'integer',
+        'total_amount' => 'decimal:5',
+        'transaction_count' => 'integer',
+        'buy_volume' => 'integer',
+        'sell_volume' => 'integer',
+        'buy_amount' => 'decimal:5',
+        'sell_amount' => 'decimal:5',
+        'admin_inject_volume' => 'integer',
+        'admin_recycle_volume' => 'integer',
+        'admin_inject_amount' => 'decimal:5',
+        'admin_recycle_amount' => 'decimal:5',
+        'price_change' => 'decimal:5',
+        'price_change_percent' => 'decimal:4',
+        'volatility' => 'decimal:4',
+    ];
+
+    /**
+     * 获取关联的商品信息
+     */
+    public function item(): BelongsTo
+    {
+        return $this->belongsTo(Item::class, 'item_id');
+    }
+
+    /**
+     * 获取价格变化趋势描述
+     */
+    public function getPriceTrendDescriptionAttribute(): string
+    {
+        if ($this->price_change_percent === null) {
+            return '无变化';
+        }
+
+        $percent = abs($this->price_change_percent);
+        $direction = $this->price_change_percent > 0 ? '上涨' : '下跌';
+        
+        return "{$direction} {$percent}%";
+    }
+
+    /**
+     * 获取波动率等级
+     */
+    public function getVolatilityLevelAttribute(): string
+    {
+        if ($this->volatility === null) {
+            return '无数据';
+        }
+
+        if ($this->volatility < 5) {
+            return '低波动';
+        } elseif ($this->volatility < 15) {
+            return '中等波动';
+        } elseif ($this->volatility < 30) {
+            return '高波动';
+        } else {
+            return '极高波动';
+        }
+    }
+}

+ 114 - 0
app/Module/Mex/Models/MexPriceAdjustment.php

@@ -0,0 +1,114 @@
+<?php
+
+namespace App\Module\Mex\Models;
+
+use App\Module\GameItems\Models\Item;
+use App\Module\Mex\Enums\PriceAdjustmentType;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
+use UCore\ModelCore;
+
+/**
+ * 农贸市场价格调整记录模型
+ *
+ * field start 
+ * @property  int  $id  调整记录ID,主键
+ * @property  int  $price_config_id  价格配置ID,关联mex_price_configs表
+ * @property  int  $item_id  商品ID,关联物品表
+ * @property  int  $admin_user_id  操作管理员用户ID
+ * @property  \App\Module\Mex\Enums\PriceAdjustmentType  $adjustment_type  调整类型:MIN_PRICE最低价,MAX_PRICE最高价,PROTECTION_THRESHOLD保护阈值,STATUS启用状态,BATCH批量调整
+ * @property  float  $old_min_price  调整前最低价
+ * @property  float  $new_min_price  调整后最低价
+ * @property  float  $old_max_price  调整前最高价
+ * @property  float  $new_max_price  调整后最高价
+ * @property  int  $old_protection_threshold  调整前保护阈值
+ * @property  int  $new_protection_threshold  调整后保护阈值
+ * @property  bool  $old_is_enabled  调整前启用状态
+ * @property  bool  $new_is_enabled  调整后启用状态
+ * @property  string  $adjustment_reason  调整原因
+ * @property  string  $market_impact_note  市场影响说明
+ * @property  \Carbon\Carbon  $created_at  调整时间
+ * field end
+ */
+class MexPriceAdjustment extends ModelCore
+{
+    protected $table = 'mex_price_adjustments';
+
+    // 禁用updated_at字段的自动管理,因为表中只有created_at字段
+    public const UPDATED_AT = null;
+
+    protected $fillable = [
+        'price_config_id',
+        'item_id',
+        'admin_user_id',
+        'adjustment_type',
+        'old_min_price',
+        'new_min_price',
+        'old_max_price',
+        'new_max_price',
+        'old_protection_threshold',
+        'new_protection_threshold',
+        'old_is_enabled',
+        'new_is_enabled',
+        'adjustment_reason',
+        'market_impact_note',
+    ];
+
+    protected $casts = [
+        'price_config_id' => 'integer',
+        'item_id' => 'integer',
+        'admin_user_id' => 'integer',
+        'adjustment_type' => PriceAdjustmentType::class,
+        'old_min_price' => 'decimal:5',
+        'new_min_price' => 'decimal:5',
+        'old_max_price' => 'decimal:5',
+        'new_max_price' => 'decimal:5',
+        'old_protection_threshold' => 'integer',
+        'new_protection_threshold' => 'integer',
+        'old_is_enabled' => 'boolean',
+        'new_is_enabled' => 'boolean',
+    ];
+
+    /**
+     * 获取关联的价格配置
+     */
+    public function priceConfig(): BelongsTo
+    {
+        return $this->belongsTo(MexPriceConfig::class, 'price_config_id');
+    }
+
+    /**
+     * 获取关联的商品信息
+     */
+    public function item(): BelongsTo
+    {
+        return $this->belongsTo(Item::class, 'item_id');
+    }
+
+    /**
+     * 获取价格变化摘要
+     */
+    public function getPriceChangeSummaryAttribute(): string
+    {
+        $changes = [];
+        
+        if ($this->old_min_price !== $this->new_min_price) {
+            $changes[] = "最低价: {$this->old_min_price} → {$this->new_min_price}";
+        }
+        
+        if ($this->old_max_price !== $this->new_max_price) {
+            $changes[] = "最高价: {$this->old_max_price} → {$this->new_max_price}";
+        }
+        
+        if ($this->old_protection_threshold !== $this->new_protection_threshold) {
+            $changes[] = "保护阈值: {$this->old_protection_threshold} → {$this->new_protection_threshold}";
+        }
+        
+        if ($this->old_is_enabled !== $this->new_is_enabled) {
+            $oldStatus = $this->old_is_enabled ? '启用' : '禁用';
+            $newStatus = $this->new_is_enabled ? '启用' : '禁用';
+            $changes[] = "状态: {$oldStatus} → {$newStatus}";
+        }
+        
+        return implode('; ', $changes);
+    }
+}

+ 19 - 0
app/Module/Mex/Repositories/MexDailyPriceTrendRepository.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace App\Module\Mex\Repositories;
+
+use App\Module\Mex\Models\MexDailyPriceTrend;
+use UCore\DcatAdmin\Repository\EloquentRepository;
+
+/**
+ * 农贸市场每日价格趋势仓库
+ */
+class MexDailyPriceTrendRepository extends EloquentRepository
+{
+    /**
+     * 关联的模型类
+     *
+     * @var string
+     */
+    protected $eloquentClass = MexDailyPriceTrend::class;
+}

+ 19 - 0
app/Module/Mex/Repositories/MexPriceAdjustmentRepository.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace App\Module\Mex\Repositories;
+
+use App\Module\Mex\Models\MexPriceAdjustment;
+use UCore\DcatAdmin\Repository\EloquentRepository;
+
+/**
+ * 农贸市场价格调整记录仓库
+ */
+class MexPriceAdjustmentRepository extends EloquentRepository
+{
+    /**
+     * 关联的模型类
+     *
+     * @var string
+     */
+    protected $eloquentClass = MexPriceAdjustment::class;
+}

+ 83 - 0
app/Module/Mex/Service/MexDailyPriceTrendService.php

@@ -0,0 +1,83 @@
+<?php
+
+namespace App\Module\Mex\Service;
+
+use App\Module\Fund\Enums\FUND_CURRENCY_TYPE;
+use App\Module\Mex\Dto\MexDailyPriceTrendDto;
+use App\Module\Mex\Logic\MexDailyPriceTrendLogic;
+use Illuminate\Support\Collection;
+
+/**
+ * 农贸市场每日价格趋势服务层
+ */
+class MexDailyPriceTrendService
+{
+    /**
+     * 生成指定日期的价格趋势数据
+     */
+    public static function generateDailyTrend(string $date, int $itemId, FUND_CURRENCY_TYPE $currencyType): ?MexDailyPriceTrendDto
+    {
+        $logic = new MexDailyPriceTrendLogic();
+        return $logic->generateDailyTrend($date, $itemId, $currencyType);
+    }
+
+    /**
+     * 批量生成多日价格趋势数据
+     */
+    public static function generateMultipleDaysTrends(
+        string $startDate,
+        string $endDate,
+        ?int $itemId = null,
+        ?FUND_CURRENCY_TYPE $currencyType = null
+    ): Collection {
+        $logic = new MexDailyPriceTrendLogic();
+        return $logic->generateMultipleDaysTrends($startDate, $endDate, $itemId, $currencyType);
+    }
+
+    /**
+     * 获取商品的价格趋势历史
+     */
+    public static function getItemPriceTrends(
+        int $itemId,
+        FUND_CURRENCY_TYPE $currencyType,
+        string $startDate,
+        string $endDate,
+        int $limit = 100
+    ): Collection {
+        $logic = new MexDailyPriceTrendLogic();
+        return $logic->getItemPriceTrends($itemId, $currencyType, $startDate, $endDate, $limit);
+    }
+
+    /**
+     * 获取价格趋势统计信息
+     */
+    public static function getTrendStatistics(
+        int $itemId,
+        FUND_CURRENCY_TYPE $currencyType,
+        string $startDate,
+        string $endDate
+    ): array {
+        $logic = new MexDailyPriceTrendLogic();
+        return $logic->getTrendStatistics($itemId, $currencyType, $startDate, $endDate);
+    }
+
+    /**
+     * 生成今日所有商品的价格趋势
+     */
+    public static function generateTodayTrends(): Collection
+    {
+        $today = date('Y-m-d');
+        $logic = new MexDailyPriceTrendLogic();
+        return $logic->generateMultipleDaysTrends($today, $today);
+    }
+
+    /**
+     * 生成昨日所有商品的价格趋势
+     */
+    public static function generateYesterdayTrends(): Collection
+    {
+        $yesterday = date('Y-m-d', strtotime('-1 day'));
+        $logic = new MexDailyPriceTrendLogic();
+        return $logic->generateMultipleDaysTrends($yesterday, $yesterday);
+    }
+}

+ 74 - 0
app/Module/Mex/Service/MexPriceAdjustmentService.php

@@ -0,0 +1,74 @@
+<?php
+
+namespace App\Module\Mex\Service;
+
+use App\Module\Mex\Dto\MexPriceAdjustmentDto;
+use App\Module\Mex\Logic\MexPriceAdjustmentLogic;
+use App\Module\Mex\Models\MexPriceConfig;
+use Illuminate\Support\Collection;
+
+/**
+ * 农贸市场价格调整记录服务层
+ */
+class MexPriceAdjustmentService
+{
+    /**
+     * 记录价格配置调整
+     */
+    public static function recordPriceConfigAdjustment(
+        MexPriceConfig $oldConfig,
+        MexPriceConfig $newConfig,
+        int $adminUserId,
+        ?string $adjustmentReason = null,
+        ?string $marketImpactNote = null
+    ): MexPriceAdjustmentDto {
+        $logic = new MexPriceAdjustmentLogic();
+        return $logic->recordPriceConfigAdjustment(
+            $oldConfig,
+            $newConfig,
+            $adminUserId,
+            $adjustmentReason,
+            $marketImpactNote
+        );
+    }
+
+    /**
+     * 获取商品的价格调整历史
+     */
+    public static function getItemAdjustmentHistory(int $itemId, int $limit = 50): Collection
+    {
+        $logic = new MexPriceAdjustmentLogic();
+        return $logic->getItemAdjustmentHistory($itemId, $limit);
+    }
+
+    /**
+     * 获取管理员的调整操作记录
+     */
+    public static function getAdminAdjustmentHistory(int $adminUserId, int $limit = 50): Collection
+    {
+        $logic = new MexPriceAdjustmentLogic();
+        return $logic->getAdminAdjustmentHistory($adminUserId, $limit);
+    }
+
+    /**
+     * 获取指定时间范围内的调整记录
+     */
+    public static function getAdjustmentsByDateRange(
+        string $startDate,
+        string $endDate,
+        ?int $itemId = null,
+        ?int $adminUserId = null
+    ): Collection {
+        $logic = new MexPriceAdjustmentLogic();
+        return $logic->getAdjustmentsByDateRange($startDate, $endDate, $itemId, $adminUserId);
+    }
+
+    /**
+     * 获取调整统计信息
+     */
+    public static function getAdjustmentStatistics(string $startDate, string $endDate): array
+    {
+        $logic = new MexPriceAdjustmentLogic();
+        return $logic->getAdjustmentStatistics($startDate, $endDate);
+    }
+}