Переглянути джерело

feat(admin): 优化土地详情页面显示

- 在土地信息卡片中添加相对时间描述
- 新增 formatRelativeTime 方法用于格式化时间
- 调整阶段结束时间的显示方式,增加剩余时间提示
notfff 7 місяців тому
батько
коміт
c524fbe08c

+ 1 - 1
app/Module/AppGame/Handler/Land/WateringHandler.php

@@ -15,7 +15,7 @@ use UCore\Exception\LogicException;
 /**
  * 处理浇水操作请求
  * Watering
- * 
+ *
  */
 class WateringHandler extends BaseHandler
 {

+ 51 - 2
app/Module/Game/AdminControllers/FarmUserSummaryController.php

@@ -314,7 +314,7 @@ class FarmUserSummaryController extends AdminController
 
         // 创建土地详情表格
         $headers = [
-            'ID', '位置', '土地类型', '状态', '种植作物', '种植时间', '生长阶段', '本阶段开始时间', '本阶段结束时间',
+            'ID', '位置', '土地类型', '状态', '种植作物', '种植时间', '生长阶段', '本阶段开始时间', '本阶段结束时间(剩余)',
             '灾害情况'
         ];
         $rows    = [];
@@ -337,7 +337,7 @@ class FarmUserSummaryController extends AdminController
                 $plantTime      = $crop->plant_time;
                 $growthStage    = $this->getGrowthStageName($crop->growth_stage);
                 $stageStartTime = $crop->stage_start_time;
-                $stageEndTime   = $crop->stage_end_time;
+                $stageEndTime   = $this->formatRelativeTime($crop->stage_end_time);
 
                 // 处理灾害信息
                 if (!empty($crop->disasters)) {
@@ -411,6 +411,55 @@ class FarmUserSummaryController extends AdminController
         }
     }
 
+    /**
+     * 格式化相对时间描述
+     *
+     * @param string|\Carbon\Carbon|null $dateTime 时间
+     * @return string 相对时间描述
+     */
+    protected function formatRelativeTime($dateTime): string
+    {
+        if (!$dateTime) {
+            return '无';
+        }
+
+        try {
+            // 确保是Carbon实例
+            if (is_string($dateTime)) {
+                $dateTime = \Carbon\Carbon::parse($dateTime);
+            }
+
+            $now = now();
+
+            // 如果时间已经过去
+            if ($dateTime->isPast()) {
+                return '<span class="text-danger">已过期</span>';
+            }
+
+            // 计算时间差
+            $diffInSeconds = $now->diffInSeconds($dateTime);
+            $diffInMinutes = $now->diffInMinutes($dateTime);
+            $diffInHours = $now->diffInHours($dateTime);
+            $diffInDays = $now->diffInDays($dateTime);
+
+            // 根据时间差返回合适的描述
+            if ($diffInSeconds < 60) {
+                return "<span class='text-warning'>{$diffInSeconds}秒后</span>";
+            } elseif ($diffInMinutes < 60) {
+                return "<span class='text-info'>{$diffInMinutes}分钟后</span>";
+            } elseif ($diffInHours < 24) {
+                return "<span class='text-primary'>{$diffInHours}小时后</span>";
+            } elseif ($diffInDays < 7) {
+                return "<span class='text-secondary'>{$diffInDays}天后</span>";
+            } else {
+                // 超过7天显示具体日期
+                return $dateTime->format('Y-m-d H:i:s');
+            }
+        } catch (\Throwable $e) {
+            return '<span class="text-muted">时间格式错误</span>';
+        }
+    }
+
     /**
      * 物品信息卡片
      *

+ 174 - 0
docs/农场用户信息汇总页面相对时间显示优化说明.md

@@ -0,0 +1,174 @@
+# 农场用户信息汇总页面相对时间显示优化说明
+
+## 优化内容
+
+将农场用户信息汇总页面中的"本阶段结束时间"从绝对时间显示改为相对时间描述(多久之后),提升用户体验和信息的直观性。
+
+## 修改详情
+
+### 1. 新增相对时间格式化方法
+
+**文件**: `app/Module/Game/AdminControllers/FarmUserSummaryController.php`
+
+新增 `formatRelativeTime()` 方法,用于将绝对时间转换为相对时间描述:
+
+```php
+/**
+ * 格式化相对时间描述
+ *
+ * @param string|\Carbon\Carbon|null $dateTime 时间
+ * @return string 相对时间描述
+ */
+protected function formatRelativeTime($dateTime): string
+{
+    if (!$dateTime) {
+        return '无';
+    }
+
+    try {
+        // 确保是Carbon实例
+        if (is_string($dateTime)) {
+            $dateTime = \Carbon\Carbon::parse($dateTime);
+        }
+
+        $now = now();
+        
+        // 如果时间已经过去
+        if ($dateTime->isPast()) {
+            return '<span class="text-danger">已过期</span>';
+        }
+
+        // 计算时间差并返回合适的描述
+        $diffInSeconds = $now->diffInSeconds($dateTime);
+        $diffInMinutes = $now->diffInMinutes($dateTime);
+        $diffInHours = $now->diffInHours($dateTime);
+        $diffInDays = $now->diffInDays($dateTime);
+
+        if ($diffInSeconds < 60) {
+            return "<span class='text-warning'>{$diffInSeconds}秒后</span>";
+        } elseif ($diffInMinutes < 60) {
+            return "<span class='text-info'>{$diffInMinutes}分钟后</span>";
+        } elseif ($diffInHours < 24) {
+            return "<span class='text-primary'>{$diffInHours}小时后</span>";
+        } elseif ($diffInDays < 7) {
+            return "<span class='text-secondary'>{$diffInDays}天后</span>";
+        } else {
+            // 超过7天显示具体日期
+            return $dateTime->format('Y-m-d H:i:s');
+        }
+    } catch (\Throwable $e) {
+        return '<span class="text-muted">时间格式错误</span>';
+    }
+}
+```
+
+### 2. 修改时间显示逻辑
+
+将原来直接显示 `$crop->stage_end_time` 的地方改为使用相对时间格式:
+
+```php
+// 修改前
+$stageEndTime = $crop->stage_end_time;
+
+// 修改后
+$stageEndTime = $this->formatRelativeTime($crop->stage_end_time);
+```
+
+### 3. 更新表格列标题
+
+将表格列标题从"本阶段结束时间"更新为"本阶段结束时间(剩余)",更清楚地表示这是相对时间:
+
+```php
+$headers = [
+    'ID', '位置', '土地类型', '状态', '种植作物', '种植时间', '生长阶段', 
+    '本阶段开始时间', '本阶段结束时间(剩余)', '灾害情况'
+];
+```
+
+## 功能特性
+
+### 时间显示规则
+
+1. **空值处理**: 如果时间为空或null,显示"无"
+2. **已过期时间**: 显示红色的"已过期"标识
+3. **秒级精度**: 小于1分钟时显示"X秒后"(黄色)
+4. **分钟级精度**: 1-59分钟时显示"X分钟后"(蓝色)
+5. **小时级精度**: 1-23小时时显示"X小时后"(主色调)
+6. **天级精度**: 1-6天时显示"X天后"(灰色)
+7. **长期时间**: 超过7天时显示具体日期时间
+
+### 颜色编码
+
+- 🟡 **黄色 (text-warning)**: 秒级时间,紧急程度高
+- 🔵 **蓝色 (text-info)**: 分钟级时间,需要关注
+- 🔷 **主色调 (text-primary)**: 小时级时间,正常状态
+- ⚫ **灰色 (text-secondary)**: 天级时间,长期计划
+- 🔴 **红色 (text-danger)**: 已过期,需要处理
+- 🔘 **静音色 (text-muted)**: 错误状态
+
+## 用户体验提升
+
+### 修改前
+- 显示绝对时间:`2025-05-24 15:30:00`
+- 用户需要自己计算剩余时间
+- 不够直观,需要心算
+
+### 修改后
+- 显示相对时间:`2小时后`
+- 一目了然的剩余时间
+- 颜色编码提供视觉提示
+- 自动处理过期状态
+
+## 测试验证
+
+创建了单元测试 `tests/Unit/FarmUserSummaryRelativeTimeTest.php` 来验证:
+
+1. 空值和null值的处理
+2. 已过期时间的显示
+3. 不同时间单位的正确显示
+4. 边界情况的处理
+5. 无效时间格式的错误处理
+6. 字符串时间输入的支持
+
+## 示例效果
+
+| 剩余时间 | 显示效果 | 颜色 |
+|---------|---------|------|
+| 30秒 | `30秒后` | 黄色 |
+| 5分钟 | `5分钟后` | 蓝色 |
+| 2小时 | `2小时后` | 主色调 |
+| 3天 | `3天后` | 灰色 |
+| 10天 | `2025-05-24 15:30:00` | 默认 |
+| 已过期 | `已过期` | 红色 |
+| 无数据 | `无` | 默认 |
+
+## 兼容性说明
+
+- 支持 Carbon 实例和字符串时间输入
+- 自动处理时区转换
+- 包含完整的异常处理
+- 向后兼容原有的时间格式
+
+## 后续优化建议
+
+1. **实时更新**: 考虑添加JavaScript实现页面时间的实时倒计时
+2. **国际化**: 支持多语言的时间描述
+3. **精度配置**: 允许管理员配置时间显示的精度级别
+4. **批量操作**: 为即将到期的作物提供批量操作功能
+5. **提醒功能**: 为临近结束的阶段添加提醒机制
+
+## 相关文件
+
+### 修改的文件
+- `app/Module/Game/AdminControllers/FarmUserSummaryController.php`
+
+### 新增的文件
+- `tests/Unit/FarmUserSummaryRelativeTimeTest.php`
+- `docs/农场用户信息汇总页面相对时间显示优化说明.md`
+
+## 注意事项
+
+1. **性能考虑**: 相对时间计算在页面加载时进行,不会影响数据库查询性能
+2. **缓存策略**: 页面数据建议适当缓存,避免频繁计算
+3. **时区处理**: 确保服务器时区设置正确,避免时间计算偏差
+4. **浏览器兼容**: HTML和CSS样式在主流浏览器中均有良好支持

+ 194 - 0
tests/Unit/FarmUserSummaryRelativeTimeTest.php

@@ -0,0 +1,194 @@
+<?php
+
+namespace Tests\Unit;
+
+use App\Module\Game\AdminControllers\FarmUserSummaryController;
+use Carbon\Carbon;
+use PHPUnit\Framework\TestCase;
+use ReflectionClass;
+
+/**
+ * 农场用户信息汇总相对时间测试
+ */
+class FarmUserSummaryRelativeTimeTest extends TestCase
+{
+    /**
+     * 获取控制器实例并访问受保护的方法
+     */
+    private function getController()
+    {
+        $controller = new FarmUserSummaryController();
+        $reflection = new ReflectionClass($controller);
+        $method = $reflection->getMethod('formatRelativeTime');
+        $method->setAccessible(true);
+        
+        return [$controller, $method];
+    }
+
+    /**
+     * 测试空值处理
+     */
+    public function testFormatRelativeTimeWithNull()
+    {
+        [$controller, $method] = $this->getController();
+        
+        $result = $method->invoke($controller, null);
+        $this->assertEquals('无', $result);
+        
+        $result = $method->invoke($controller, '');
+        $this->assertEquals('无', $result);
+    }
+
+    /**
+     * 测试已过期时间
+     */
+    public function testFormatRelativeTimeWithPastTime()
+    {
+        [$controller, $method] = $this->getController();
+        
+        $pastTime = Carbon::now()->subHours(1);
+        $result = $method->invoke($controller, $pastTime);
+        
+        $this->assertStringContains('已过期', $result);
+        $this->assertStringContains('text-danger', $result);
+    }
+
+    /**
+     * 测试秒级时间差
+     */
+    public function testFormatRelativeTimeWithSeconds()
+    {
+        [$controller, $method] = $this->getController();
+        
+        $futureTime = Carbon::now()->addSeconds(30);
+        $result = $method->invoke($controller, $futureTime);
+        
+        $this->assertStringContains('秒后', $result);
+        $this->assertStringContains('text-warning', $result);
+    }
+
+    /**
+     * 测试分钟级时间差
+     */
+    public function testFormatRelativeTimeWithMinutes()
+    {
+        [$controller, $method] = $this->getController();
+        
+        $futureTime = Carbon::now()->addMinutes(30);
+        $result = $method->invoke($controller, $futureTime);
+        
+        $this->assertStringContains('分钟后', $result);
+        $this->assertStringContains('text-info', $result);
+    }
+
+    /**
+     * 测试小时级时间差
+     */
+    public function testFormatRelativeTimeWithHours()
+    {
+        [$controller, $method] = $this->getController();
+        
+        $futureTime = Carbon::now()->addHours(5);
+        $result = $method->invoke($controller, $futureTime);
+        
+        $this->assertStringContains('小时后', $result);
+        $this->assertStringContains('text-primary', $result);
+    }
+
+    /**
+     * 测试天级时间差
+     */
+    public function testFormatRelativeTimeWithDays()
+    {
+        [$controller, $method] = $this->getController();
+        
+        $futureTime = Carbon::now()->addDays(3);
+        $result = $method->invoke($controller, $futureTime);
+        
+        $this->assertStringContains('天后', $result);
+        $this->assertStringContains('text-secondary', $result);
+    }
+
+    /**
+     * 测试超过7天的时间差
+     */
+    public function testFormatRelativeTimeWithWeeks()
+    {
+        [$controller, $method] = $this->getController();
+        
+        $futureTime = Carbon::now()->addDays(10);
+        $result = $method->invoke($controller, $futureTime);
+        
+        // 超过7天应该显示具体日期
+        $this->assertStringContains($futureTime->format('Y-m-d H:i:s'), $result);
+    }
+
+    /**
+     * 测试字符串时间输入
+     */
+    public function testFormatRelativeTimeWithStringInput()
+    {
+        [$controller, $method] = $this->getController();
+        
+        $futureTimeString = Carbon::now()->addHours(2)->toDateTimeString();
+        $result = $method->invoke($controller, $futureTimeString);
+        
+        $this->assertStringContains('小时后', $result);
+        $this->assertStringContains('text-primary', $result);
+    }
+
+    /**
+     * 测试无效时间格式
+     */
+    public function testFormatRelativeTimeWithInvalidFormat()
+    {
+        [$controller, $method] = $this->getController();
+        
+        $result = $method->invoke($controller, 'invalid-date-format');
+        
+        $this->assertStringContains('时间格式错误', $result);
+        $this->assertStringContains('text-muted', $result);
+    }
+
+    /**
+     * 测试边界情况 - 正好1分钟
+     */
+    public function testFormatRelativeTimeExactlyOneMinute()
+    {
+        [$controller, $method] = $this->getController();
+        
+        $futureTime = Carbon::now()->addMinutes(1);
+        $result = $method->invoke($controller, $futureTime);
+        
+        $this->assertStringContains('分钟后', $result);
+        $this->assertStringContains('text-info', $result);
+    }
+
+    /**
+     * 测试边界情况 - 正好1小时
+     */
+    public function testFormatRelativeTimeExactlyOneHour()
+    {
+        [$controller, $method] = $this->getController();
+        
+        $futureTime = Carbon::now()->addHours(1);
+        $result = $method->invoke($controller, $futureTime);
+        
+        $this->assertStringContains('小时后', $result);
+        $this->assertStringContains('text-primary', $result);
+    }
+
+    /**
+     * 测试边界情况 - 正好1天
+     */
+    public function testFormatRelativeTimeExactlyOneDay()
+    {
+        [$controller, $method] = $this->getController();
+        
+        $futureTime = Carbon::now()->addDays(1);
+        $result = $method->invoke($controller, $futureTime);
+        
+        $this->assertStringContains('天后', $result);
+        $this->assertStringContains('text-secondary', $result);
+    }
+}