FarmUserSummaryController.php 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354
  1. <?php
  2. namespace App\Module\Game\AdminControllers;
  3. use App\Module\Farm\Enums\DISASTER_TYPE;
  4. use App\Module\Farm\Enums\GROWTH_STAGE;
  5. use App\Module\Farm\Models\FarmCrop;
  6. use App\Module\Farm\Models\FarmGodBuff;
  7. use App\Module\Farm\Models\FarmHouseConfig;
  8. use App\Module\Farm\Models\FarmLand;
  9. use App\Module\Farm\Models\FarmUser;
  10. use App\Module\Farm\Services\DisasterService;
  11. use App\Module\Fund\Models\FundModel;
  12. use App\Module\Fund\Services\AccountService;
  13. use App\Module\GameItems\Enums\ITEM_TYPE;
  14. use App\Module\GameItems\Models\ItemUser;
  15. use App\Module\Pet\Models\PetUser;
  16. use App\Module\Pet\Models\PetActiveSkill;
  17. use App\Module\Pet\Models\PetSkillLog;
  18. use App\Module\Pet\Enums\PetStatus;
  19. use App\Module\User\Models\User;
  20. use Dcat\Admin\Grid;
  21. use Dcat\Admin\Layout\Content;
  22. use Dcat\Admin\Layout\Row;
  23. use Dcat\Admin\Widgets\Card;
  24. use Dcat\Admin\Widgets\Table;
  25. use Dcat\Admin\Widgets\Alert;
  26. use Spatie\RouteAttributes\Attributes\Get;
  27. use Spatie\RouteAttributes\Attributes\Resource;
  28. use UCore\DcatAdmin\AdminController;
  29. /**
  30. * 农场用户信息汇总控制器
  31. *
  32. * 用于展示用户的农场信息汇总,包括房屋、土地、作物、物品和代币信息
  33. */
  34. #[Resource('farm-user-summary', names: 'dcat.admin.farm-user-summary')]
  35. class FarmUserSummaryController extends AdminController
  36. {
  37. /**
  38. * 页面标题
  39. *
  40. * @var string
  41. */
  42. protected $title = '农场用户信息汇总';
  43. /**
  44. * 用户列表页面
  45. *
  46. * @param Content $content
  47. * @return Content
  48. */
  49. public function index(Content $content)
  50. {
  51. return $content
  52. ->title($this->title)
  53. ->description('查看用户的农场信息汇总')
  54. ->body($this->grid());
  55. }
  56. /**
  57. * 查看指定用户的农场信息汇总
  58. *
  59. * @param int $userId 用户ID
  60. * @param Content $content
  61. * @return Content
  62. */
  63. #[Get('farm-user-summary/{userId}', name: 'dcat.admin.farm-user-summary.show')]
  64. public function show($userId, Content $content)
  65. {
  66. // 记录请求信息
  67. \Illuminate\Support\Facades\Log::info('访问农场用户信息汇总', [
  68. 'user_id' => $userId,
  69. 'referer' => request()->header('referer'),
  70. 'user_agent' => request()->header('user-agent'),
  71. 'ip' => request()->ip(),
  72. 'url' => request()->fullUrl(),
  73. ]);
  74. // 查找用户
  75. $user = User::find($userId);
  76. if (!$user) {
  77. admin_error('错误', "用户 {$userId} 不存在");
  78. \Illuminate\Support\Facades\Log::warning('访问不存在的用户', [ 'user_id' => $userId ]);
  79. return redirect()->route('dcat.admin.farm-user-summary');
  80. }
  81. // 检查是否存在农场用户记录
  82. $farmUser = FarmUser::where('user_id', $userId)->first();
  83. if (!$farmUser) {
  84. admin_warning('提示', "用户 {$userId} 没有农场信息");
  85. \Illuminate\Support\Facades\Log::info('用户没有农场信息', [ 'user_id' => $userId ]);
  86. // 不重定向,继续显示用户信息,只是提示没有农场信息
  87. }
  88. return $content
  89. ->title($this->title)
  90. ->description("用户 {$user->username}(ID: {$user->id})的农场信息汇总")
  91. ->body(function (Row $row) use ($user) {
  92. // 第一行:用户基本信息和房屋信息
  93. $row->column(6, $this->userInfoCard($user));
  94. $row->column(6, $this->houseInfoCard($user->id));
  95. // 第二行:土地信息和作物信息
  96. $row->column(12, $this->landInfoCard($user->id));
  97. // 第三行:物品信息
  98. $row->column(12, $this->itemInfoCard($user->id));
  99. // 第四行:代币信息
  100. $row->column(12, $this->fundInfoCard($user->id));
  101. // 第五行:宠物信息
  102. $row->column(12, $this->petInfoCard($user->id));
  103. // 第六行:宠物生活技能情况
  104. $row->column(12, $this->petLifeSkillsCard($user->id));
  105. // 第七行:神像buff信息
  106. $row->column(12, $this->buffInfoCard($user->id));
  107. });
  108. }
  109. /**
  110. * 用户基本信息卡片
  111. *
  112. * @param User $user 用户对象
  113. * @return Card
  114. */
  115. protected function userInfoCard(User $user)
  116. {
  117. $userInfo = $user->info;
  118. $avatar = $userInfo ? $userInfo->avatar : '';
  119. $nickname = $userInfo ? $userInfo->nickname : '';
  120. $content = <<<HTML
  121. <div class="row">
  122. <div class="col-md-4">
  123. <img src="/saaa/{$avatar}" class="img-fluid rounded" style="max-width: 100px;" >
  124. </div>
  125. <div class="col-md-8">
  126. <p><strong>用户ID:</strong>{$user->id}</p>
  127. <p><strong>用户名:</strong>{$user->username}</p>
  128. <p><strong>昵称:</strong>{$nickname}</p>
  129. <p><strong>注册时间:</strong>{$user->created_at}</p>
  130. </div>
  131. </div>
  132. HTML;
  133. return new Card('用户基本信息', $content);
  134. }
  135. /**
  136. * 房屋信息卡片
  137. *
  138. * @param int $userId 用户ID
  139. * @return Card
  140. */
  141. protected function houseInfoCard($userId)
  142. {
  143. // 获取用户的农场信息
  144. $farmUser = FarmUser::where('user_id', $userId)->first();
  145. if (!$farmUser) {
  146. return new Card('房屋信息', new Alert('warning', '该用户没有农场信息'));
  147. }
  148. // 获取房屋配置信息
  149. $houseConfig = FarmHouseConfig::where('level', $farmUser->house_level)->first();
  150. if (!$houseConfig) {
  151. return new Card('房屋信息', new Alert('warning', "找不到房屋等级 {$farmUser->house_level} 的配置信息"));
  152. }
  153. $content = <<<HTML
  154. <div class="row">
  155. <div class="col-md-12">
  156. <p><strong>房屋等级:</strong>{$farmUser->house_level}</p>
  157. <p><strong>最后升级时间:</strong>{$farmUser->last_upgrade_time}</p>
  158. <p><strong>产出加成:</strong>{$houseConfig->output_bonus}</p>
  159. <p><strong>特殊土地上限:</strong>{$houseConfig->special_land_limit}</p>
  160. <p><strong>可用土地数量:</strong>{$houseConfig->available_lands}</p>
  161. HTML;
  162. // 如果有降级天数,显示降级信息
  163. if ($houseConfig->downgrade_days) {
  164. $content .= "<p><strong>降级天数:</strong>{$houseConfig->downgrade_days}</p>";
  165. }
  166. $content .= <<<HTML
  167. </div>
  168. </div>
  169. <div class="row mt-2">
  170. <div class="col-md-12">
  171. <a href="javascript:void(0);" class="btn btn-sm btn-primary" onclick="window.open('/admin/farm-house-configs', '_blank')">查看房屋配置</a>
  172. </div>
  173. </div>
  174. HTML;
  175. return new Card('房屋信息', $content);
  176. }
  177. /**
  178. * 土地信息卡片
  179. *
  180. * @param int $userId 用户ID
  181. * @return Card
  182. */
  183. protected function landInfoCard($userId)
  184. {
  185. // 获取用户的土地信息
  186. /**
  187. * @var FarmLand $land
  188. */
  189. $lands = FarmLand::with([ 'landType', 'crop.seed' ])
  190. ->where('user_id', $userId)
  191. ->get();
  192. if ($lands->isEmpty()) {
  193. return new Card('土地信息', new Alert('warning', '该用户没有土地信息'));
  194. }
  195. // 土地类型统计
  196. $landTypeStats = $lands->groupBy('land_type')->map->count();
  197. // 土地状态统计
  198. $landStatusStats = $lands->groupBy('status')->map->count();
  199. // 灾害统计
  200. $disasterStats = [
  201. 1 => 0, // 干旱
  202. 2 => 0, // 虫害
  203. 3 => 0, // 杂草
  204. ];
  205. // 统计活跃的灾害
  206. foreach ($lands as $land) {
  207. if ($land->crop && !empty($land->crop->disasters)) {
  208. foreach ($land->crop->disasters as $disaster) {
  209. if (($disaster['status'] ?? '') === 'active') {
  210. $type = $disaster['type'] ?? 0;
  211. if (isset($disasterStats[$type])) {
  212. $disasterStats[$type]++;
  213. }
  214. }
  215. }
  216. }
  217. }
  218. // 创建土地类型、状态和灾害的统计表格
  219. $statsContent = '<div class="row">';
  220. // 土地类型统计
  221. $statsContent .= '<div class="col-md-4">';
  222. $statsContent .= '<h5>土地类型统计</h5>';
  223. $statsContent .= '<table class="table table-sm table-bordered">';
  224. $statsContent .= '<thead><tr><th>土地类型</th><th>数量</th></tr></thead>';
  225. $statsContent .= '<tbody>';
  226. $landTypeNames = [
  227. 1 => '普通土地',
  228. 2 => '红土地',
  229. 3 => '黑土地',
  230. 4 => '金土地',
  231. 5 => '蓝土地',
  232. 6 => '紫土地',
  233. ];
  234. foreach ($landTypeStats as $typeId => $count) {
  235. $typeName = $landTypeNames[$typeId] ?? "类型{$typeId}";
  236. $statsContent .= "<tr><td>{$typeName}</td><td>{$count}</td></tr>";
  237. }
  238. $statsContent .= '</tbody></table></div>';
  239. // 土地状态统计
  240. $statsContent .= '<div class="col-md-4">';
  241. $statsContent .= '<h5>土地状态统计</h5>';
  242. $statsContent .= '<table class="table table-sm table-bordered">';
  243. $statsContent .= '<thead><tr><th>土地状态</th><th>数量</th></tr></thead>';
  244. $statsContent .= '<tbody>';
  245. $landStatusNames = [
  246. 0 => '空闲',
  247. 1 => '种植中',
  248. 2 => '灾害',
  249. 3 => '可收获',
  250. 4 => '枯萎',
  251. ];
  252. foreach ($landStatusStats as $statusId => $count) {
  253. $statusName = $landStatusNames[$statusId] ?? "状态{$statusId}";
  254. $statsContent .= "<tr><td>{$statusName}</td><td>{$count}</td></tr>";
  255. }
  256. $statsContent .= '</tbody></table></div>';
  257. // 灾害统计
  258. $statsContent .= '<div class="col-md-4">';
  259. $statsContent .= '<h5>灾害统计</h5>';
  260. $statsContent .= '<table class="table table-sm table-bordered">';
  261. $statsContent .= '<thead><tr><th>灾害类型</th><th>数量</th><th>减产比例</th></tr></thead>';
  262. $statsContent .= '<tbody>';
  263. $disasterNames = DISASTER_TYPE::getAll();
  264. $totalDisasters = 0;
  265. $d = DisasterService::getAllDisasters();
  266. foreach ($disasterStats as $typeId => $count) {
  267. $typeName = $disasterNames[$typeId] ?? "未知灾害{$typeId}";
  268. $penalty = $d[$typeId];
  269. $totalDisasters += $count;
  270. $statsContent .= "<tr><td>{$typeName}</td><td>{$count}</td><td>{$penalty}</td></tr>";
  271. }
  272. // 添加总计行
  273. $totalPenalty = $totalDisasters > 0 ? ($totalDisasters * 5) . '%' : '0%';
  274. $statsContent .= "<tr class='table-info'><td><strong>总计</strong></td><td>{$totalDisasters}</td><td>{$totalPenalty}</td></tr>";
  275. $statsContent .= '</tbody></table></div>';
  276. $statsContent .= '</div>';
  277. // 创建土地详情表格
  278. $headers = [
  279. 'ID', '位置', '土地类型', '状态', '种植作物', '种植时间', '生长阶段', '本阶段开始时间', '本阶段结束时间(剩余)',
  280. '果实信息', '灾害情况'
  281. ];
  282. $rows = [];
  283. foreach ($lands as $land) {
  284. $landType = $land->landType ? $land->landType->name : "类型{$land->land_type}";
  285. $status = $landStatusNames[$land->status] ?? "状态{$land->status}";
  286. $crop = $land->crop;
  287. $cropInfo = '无';
  288. $plantTime = '';
  289. $growthStage = '';
  290. $stageStartTime = '';
  291. $stageEndTime = '';
  292. $fruitInfo = '无';
  293. $disasterInfo = '无';
  294. if ($crop) {
  295. $seedName = $crop->seed ? $crop->seed->name : "种子{$crop->seed_id}";
  296. $cropInfo = $seedName;
  297. $plantTime = $crop->plant_time;
  298. $growthStage = $this->getGrowthStageName($crop->growth_stage);
  299. $stageStartTime = $crop->stage_start_time;
  300. $stageEndTime = $this->formatRelativeTime($crop->stage_end_time);
  301. // 处理果实信息
  302. if ($crop->final_output_item_id) {
  303. $fruitInfo = $this->getFruitInfo($crop->final_output_item_id);
  304. } else {
  305. // 如果还没有确定最终产出,显示种子的可能产出
  306. if ($crop->seed && $crop->seed->outputs) {
  307. $fruitInfo = $this->getSeedPossibleOutputs($crop->seed);
  308. }
  309. }
  310. // 处理灾害信息
  311. if (!empty($crop->disasters)) {
  312. $disasterInfo = $this->formatDisasterInfo($crop->disasters);
  313. }
  314. }
  315. $rows[] = [
  316. $land->id,
  317. $land->position,
  318. $landType,
  319. $status,
  320. $cropInfo,
  321. $plantTime,
  322. $growthStage,
  323. $stageStartTime,
  324. $stageEndTime,
  325. $fruitInfo,
  326. $disasterInfo,
  327. ];
  328. }
  329. $table = new Table($headers, $rows);
  330. $content = $statsContent . '<div class="mt-3">' . $table->render() . '</div>';
  331. $content .= <<<HTML
  332. <div class="row mt-2">
  333. <div class="col-md-12">
  334. <a href="javascript:void(0);" class="btn btn-sm btn-primary"
  335. onclick="window.open('/admin/farm-lands?user_id={$userId}', '_blank')">查看土地详情</a>
  336. <a href="javascript:void(0);" class="btn btn-sm btn-primary"
  337. onclick="window.open('/admin/farm-crops?user_id={$userId}', '_blank')">查看作物详情</a>
  338. </div>
  339. </div>
  340. HTML;
  341. return new Card('土地信息', $content);
  342. }
  343. /**
  344. * 获取生长阶段名称
  345. *
  346. * @param GROWTH_STAGE|int $stage 生长阶段枚举或整数值
  347. * @return string 生长阶段名称
  348. */
  349. protected function getGrowthStageName($stage)
  350. {
  351. // 如果传入的是整数,转换为枚举
  352. if (is_int($stage)) {
  353. try {
  354. $stage = GROWTH_STAGE::from($stage);
  355. } catch (\ValueError) {
  356. return "未知阶段{$stage}";
  357. }
  358. }
  359. // 如果是null,返回未知
  360. if ($stage === null) {
  361. return "未知阶段";
  362. }
  363. // 获取阶段名称
  364. try {
  365. $stageNames = GROWTH_STAGE::getAll();
  366. $stageValue = $stage->value;
  367. return $stageNames[$stageValue] ?? "阶段{$stageValue}";
  368. } catch (\Throwable) {
  369. return "错误阶段";
  370. }
  371. }
  372. /**
  373. * 格式化相对时间描述
  374. *
  375. * @param string|\Carbon\Carbon|null $dateTime 时间
  376. * @return string 相对时间描述
  377. */
  378. protected function formatRelativeTime($dateTime): string
  379. {
  380. if (!$dateTime) {
  381. return '无';
  382. }
  383. try {
  384. // 确保是Carbon实例
  385. if (is_string($dateTime)) {
  386. $dateTime = \Carbon\Carbon::parse($dateTime);
  387. }
  388. $now = now();
  389. // 如果时间已经过去
  390. if ($dateTime->isPast()) {
  391. return '<span class="text-danger">已过期</span>';
  392. }
  393. // 计算时间差
  394. $diffInSeconds = $now->diffInSeconds($dateTime);
  395. $diffInMinutes = $now->diffInMinutes($dateTime);
  396. $diffInHours = $now->diffInHours($dateTime);
  397. $diffInDays = $now->diffInDays($dateTime);
  398. // 根据时间差返回合适的描述
  399. if ($diffInSeconds < 60) {
  400. $seconds = round($diffInSeconds);
  401. return "<span class='text-warning'>{$seconds}秒后</span>";
  402. } elseif ($diffInMinutes < 60) {
  403. $minutes = round($diffInMinutes);
  404. return "<span class='text-info'>{$minutes}分钟后</span>";
  405. } elseif ($diffInHours < 24) {
  406. // 小时显示保留1位小数,但如果是整数则不显示小数
  407. $hours = round($diffInHours, 1);
  408. $hoursDisplay = $hours == intval($hours) ? intval($hours) : $hours;
  409. return "<span class='text-primary'>{$hoursDisplay}小时后</span>";
  410. } elseif ($diffInDays < 7) {
  411. $days = round($diffInDays);
  412. return "<span class='text-secondary'>{$days}天后</span>";
  413. } else {
  414. // 超过7天显示具体日期
  415. return $dateTime->format('Y-m-d H:i:s');
  416. }
  417. } catch (\Throwable $e) {
  418. return '<span class="text-muted">时间格式错误</span>';
  419. }
  420. }
  421. /**
  422. * 物品信息卡片
  423. *
  424. * @param int $userId 用户ID
  425. * @return Card
  426. */
  427. protected function itemInfoCard($userId)
  428. {
  429. // 获取用户的物品信息
  430. $items = ItemUser::with('item')
  431. ->where('user_id', $userId)
  432. ->orderBy('quantity', 'desc')
  433. ->limit(20)
  434. ->get();
  435. if ($items->isEmpty()) {
  436. return new Card('物品信息', new Alert('warning', '该用户没有物品信息'));
  437. }
  438. // 创建物品表格
  439. $headers = [ '物品ID', '物品名称', '数量', '物品类型', '过期时间' ];
  440. $rows = [];
  441. foreach ($items as $item) {
  442. $itemName = $item->item ? $item->item->name : "物品{$item->item_id}";
  443. $itemType = $item->item ? $this->getItemTypeName($item->item->type) : '';
  444. $rows[] = [
  445. $item->item_id,
  446. $itemName,
  447. $item->quantity,
  448. $itemType,
  449. $item->expire_at ?: '永久',
  450. ];
  451. }
  452. $table = new Table($headers, $rows);
  453. // 获取用户物品总数
  454. $totalCount = ItemUser::where('user_id', $userId)->count();
  455. $content = <<<HTML
  456. <div class="alert alert-info">
  457. 用户共有 {$totalCount} 种物品,下表显示数量最多的前20种物品
  458. </div>
  459. {$table->render()}
  460. <div class="row mt-2">
  461. <div class="col-md-12">
  462. <a href="javascript:void(0);" class="btn btn-sm btn-primary" onclick="window.open('/admin/game-items-user-items?user_id={$userId}', '_blank')">查看物品详情</a>
  463. </div>
  464. </div>
  465. HTML;
  466. return new Card('物品信息', $content);
  467. }
  468. /**
  469. * 获取物品类型名称
  470. *
  471. * @param ITEM_TYPE|int $type 物品类型枚举或整数值
  472. * @return string 物品类型名称
  473. */
  474. protected function getItemTypeName($type)
  475. {
  476. // 如果传入的是整数,转换为枚举
  477. if (is_int($type)) {
  478. try {
  479. $type = ITEM_TYPE::from($type);
  480. } catch (\ValueError) {
  481. return "未知类型{$type}";
  482. }
  483. }
  484. // 如果是null,返回未知
  485. if ($type === null) {
  486. return "未知类型";
  487. }
  488. // 获取类型名称
  489. try {
  490. $typeNames = ITEM_TYPE::getValueDescription();
  491. $typeValue = $type->value;
  492. return $typeNames[$typeValue] ?? "类型{$typeValue}";
  493. } catch (\Throwable) {
  494. return "错误类型";
  495. }
  496. }
  497. /**
  498. * 格式化灾害信息
  499. *
  500. * @param array $disasters 灾害数组
  501. * @return string 格式化后的灾害信息
  502. */
  503. protected function formatDisasterInfo(array $disasters): string
  504. {
  505. if (empty($disasters)) {
  506. return '无';
  507. }
  508. $result = [];
  509. foreach ($disasters as $disaster) {
  510. $type = $disaster['type'] ?? 0;
  511. $status = $disaster['status'] ?? 'active'; // 默认为活跃状态
  512. // 获取灾害类型名称
  513. $typeName = DISASTER_TYPE::getName($type);
  514. // 灾害状态
  515. $statusText = $status === 'active' ? '<span class="badge badge-danger">活跃</span>' : '<span class="badge badge-secondary">已处理</span>';
  516. // 灾害开始时间
  517. $startTime = isset($disaster['start_time']) ? date('Y-m-d H:i:s', $disaster['start_time']) : '未知';
  518. // 灾害结束时间(如果有)
  519. $endTime = '';
  520. if (isset($disaster['end_time']) && $disaster['end_time'] > 0) {
  521. $endTime = date('Y-m-d H:i:s', $disaster['end_time']);
  522. }
  523. // 减产比例 - 从DisasterService获取默认值
  524. $defaultPenalties = \App\Module\Farm\Services\DisasterService::getAllDisasters();
  525. $defaultPenalty = $defaultPenalties[$type] ?? 0.05;
  526. $penalty = isset($disaster['penalty']) ? ($disaster['penalty'] * 100) . '%' : ($defaultPenalty * 100) . '%';
  527. // 组合灾害信息
  528. $disasterInfo = "<div><strong>{$typeName}</strong>: {$statusText}</div>";
  529. $disasterInfo .= "<div>开始: {$startTime}</div>";
  530. if ($endTime) {
  531. $disasterInfo .= "<div>结束: {$endTime}</div>";
  532. }
  533. $disasterInfo .= "<div>减产: {$penalty}</div>";
  534. // 如果有额外的灾害信息,也显示出来
  535. if (isset($disaster['id'])) {
  536. $disasterInfo .= "<div>ID: {$disaster['id']}</div>";
  537. }
  538. $result[] = $disasterInfo;
  539. }
  540. return implode('<hr style="margin: 5px 0;">', $result);
  541. }
  542. /**
  543. * 代币信息卡片
  544. *
  545. * @param int $userId 用户ID
  546. * @return Card
  547. */
  548. protected function fundInfoCard($userId)
  549. {
  550. // 获取用户的代币信息
  551. $funds = FundModel::where('user_id', $userId)->get();
  552. if ($funds->isEmpty()) {
  553. return new Card('代币信息', new Alert('warning', '该用户没有代币信息'));
  554. }
  555. // 获取资金类型名称映射
  556. $fundNames = AccountService::getFundsDesc();
  557. // 创建代币表格
  558. $headers = [ '账户ID', '账户名称', '余额', '更新时间' ];
  559. $rows = [];
  560. foreach ($funds as $fund) {
  561. try {
  562. $fundIdValue = $fund->fund_id->value();
  563. $fundName = $fundNames[$fundIdValue] ?? "账户{$fundIdValue}";
  564. $balance = $fund->balance;
  565. $rows[] = [
  566. $fundIdValue,
  567. $fundName,
  568. $balance,
  569. $fund->update_time ? date('Y-m-d H:i:s', $fund->update_time) : '',
  570. ];
  571. } catch (\Throwable) {
  572. // 如果出现异常,添加一个错误行
  573. $rows[] = [
  574. $fund->id ?? '未知',
  575. '数据错误',
  576. $fund->balance ?? '未知',
  577. $fund->update_time ? date('Y-m-d H:i:s', $fund->update_time) : '',
  578. ];
  579. }
  580. }
  581. $table = new Table($headers, $rows);
  582. $content = <<<HTML
  583. {$table->render()}
  584. <div class="row mt-2">
  585. <div class="col-md-12">
  586. <a href="javascript:void(0);" class="btn btn-sm btn-primary" onclick="window.open('/admin/fund-accounts?user_id={$userId}', '_blank')">查看账户详情</a>
  587. </div>
  588. </div>
  589. HTML;
  590. return new Card('代币信息', $content);
  591. }
  592. /**
  593. * 宠物信息卡片
  594. *
  595. * @param int $userId 用户ID
  596. * @return Card
  597. */
  598. protected function petInfoCard($userId)
  599. {
  600. // 获取用户的宠物信息
  601. $pets = PetUser::where('user_id', $userId)
  602. ->orderBy('level', 'desc')
  603. ->orderBy('grade', 'desc')
  604. ->get();
  605. if ($pets->isEmpty()) {
  606. return new Card('宠物信息', new Alert('warning', '该用户没有宠物信息'));
  607. }
  608. // 宠物统计
  609. $totalPets = $pets->count();
  610. $gradeStats = $pets->groupBy('grade')->map->count();
  611. $statusStats = $pets->groupBy('status')->map->count();
  612. $levelStats = [
  613. 'max_level' => $pets->max('level'),
  614. 'avg_level' => round($pets->avg('level'), 1),
  615. 'total_exp' => $pets->sum('experience'),
  616. ];
  617. // 创建宠物表格
  618. $headers = ['宠物ID','宠物名称', '品阶', '等级', '经验', '体力', '状态', '创建时间'];
  619. $rows = [];
  620. $gradeNames = [
  621. 1 => '一品',
  622. 2 => '二品',
  623. 3 => '三品',
  624. 4 => '四品',
  625. ];
  626. $statusNames = [
  627. PetStatus::NONE->value => '未知',
  628. PetStatus::NORMAL->value => '正常',
  629. PetStatus::FIGHTING->value => '战斗中',
  630. PetStatus::DEAD->value => '死亡',
  631. PetStatus::FEEDING->value => '喂养中',
  632. PetStatus::TRAINING->value => '训练中',
  633. PetStatus::RESTING->value => '休息中',
  634. PetStatus::TRAVELING->value => '外出中',
  635. ];
  636. foreach ($pets as $pet) {
  637. try {
  638. $gradeName = $gradeNames[$pet->grade] ?? "品阶{$pet->grade}";
  639. $statusName = $statusNames[$pet->status->value] ?? "状态{$pet->status->value}";
  640. // 状态颜色
  641. $statusBadge = match($pet->status) {
  642. PetStatus::NORMAL => '<span class="badge badge-success">' . $statusName . '</span>',
  643. PetStatus::FIGHTING => '<span class="badge badge-warning">' . $statusName . '</span>',
  644. PetStatus::DEAD => '<span class="badge badge-danger">' . $statusName . '</span>',
  645. PetStatus::FEEDING, PetStatus::TRAINING => '<span class="badge badge-info">' . $statusName . '</span>',
  646. PetStatus::RESTING, PetStatus::TRAVELING => '<span class="badge badge-primary">' . $statusName . '</span>',
  647. default => '<span class="badge badge-secondary">' . $statusName . '</span>',
  648. };
  649. $rows[] = [
  650. $pet->id,
  651. $pet->name,
  652. $gradeName,
  653. $pet->level,
  654. $pet->experience,
  655. $pet->stamina,
  656. $statusBadge,
  657. $pet->created_at->format('Y-m-d H:i:s'),
  658. ];
  659. } catch (\Throwable) {
  660. // 如果出现异常,添加一个错误行
  661. $rows[] = [
  662. $pet->name ?? '未知',
  663. '数据错误',
  664. $pet->level ?? 0,
  665. $pet->experience ?? 0,
  666. $pet->stamina ?? 0,
  667. '<span class="badge badge-danger">数据错误</span>',
  668. $pet->created_at ? $pet->created_at->format('Y-m-d H:i:s') : '未知',
  669. ];
  670. }
  671. }
  672. $table = new Table($headers, $rows);
  673. // 统计信息
  674. $statsHtml = '<div class="row mb-3">';
  675. $statsHtml .= '<div class="col-md-3"><strong>宠物总数:</strong> ' . $totalPets . '</div>';
  676. $statsHtml .= '<div class="col-md-3"><strong>最高等级:</strong> ' . $levelStats['max_level'] . '</div>';
  677. $statsHtml .= '<div class="col-md-3"><strong>平均等级:</strong> ' . $levelStats['avg_level'] . '</div>';
  678. $statsHtml .= '<div class="col-md-3"><strong>总经验:</strong> ' . number_format($levelStats['total_exp']) . '</div>';
  679. $statsHtml .= '</div>';
  680. // 品阶统计
  681. if (!$gradeStats->isEmpty()) {
  682. $statsHtml .= '<div class="row mb-3"><div class="col-md-12"><strong>品阶分布:</strong> ';
  683. foreach ($gradeStats as $grade => $count) {
  684. $gradeName = $gradeNames[$grade] ?? "品阶{$grade}";
  685. $statsHtml .= "{$gradeName}: {$count}只 ";
  686. }
  687. $statsHtml .= '</div></div>';
  688. }
  689. // 状态统计
  690. if (!$statusStats->isEmpty()) {
  691. $statsHtml .= '<div class="row mb-3"><div class="col-md-12"><strong>状态分布:</strong> ';
  692. foreach ($statusStats as $status => $count) {
  693. $statusName = $statusNames[$status] ?? "状态{$status}";
  694. $statsHtml .= "{$statusName}: {$count}只 ";
  695. }
  696. $statsHtml .= '</div></div>';
  697. }
  698. $content = $statsHtml . $table->render();
  699. return new Card('宠物信息', $content);
  700. }
  701. /**
  702. * 宠物生活技能情况卡片
  703. *
  704. * @param int $userId 用户ID
  705. * @return Card
  706. */
  707. protected function petLifeSkillsCard($userId)
  708. {
  709. // 获取用户的宠物
  710. $pets = PetUser::where('user_id', $userId)->get();
  711. if ($pets->isEmpty()) {
  712. return new Card('宠物生活技能情况', new Alert('warning', '该用户没有宠物,无法使用生活技能'));
  713. }
  714. // 获取所有宠物的激活技能
  715. $petIds = $pets->pluck('id')->toArray();
  716. $activeSkills = PetActiveSkill::with(['pet', 'skill'])
  717. ->whereIn('pet_id', $petIds)
  718. ->orderBy('status', 'asc')
  719. ->orderBy('end_time', 'desc')
  720. ->get();
  721. // 统计信息
  722. $totalSkills = $activeSkills->count();
  723. $activeCount = $activeSkills->where('status', 'active')->count();
  724. $expiredCount = $activeSkills->where('status', 'expired')->count();
  725. $cancelledCount = $activeSkills->where('status', 'cancelled')->count();
  726. // 技能类型统计
  727. $skillTypeStats = $activeSkills->groupBy('skill_name')->map->count();
  728. // 创建统计信息
  729. $statsHtml = '<div class="row mb-3">';
  730. $statsHtml .= '<div class="col-md-3"><strong>总技能使用次数:</strong> ' . $totalSkills . '</div>';
  731. $statsHtml .= '<div class="col-md-3"><strong>当前激活:</strong> <span class="badge badge-success">' . $activeCount . '</span></div>';
  732. $statsHtml .= '<div class="col-md-3"><strong>已过期:</strong> <span class="badge badge-secondary">' . $expiredCount . '</span></div>';
  733. $statsHtml .= '<div class="col-md-3"><strong>已取消:</strong> <span class="badge badge-warning">' . $cancelledCount . '</span></div>';
  734. $statsHtml .= '</div>';
  735. // 技能类型统计
  736. if (!$skillTypeStats->isEmpty()) {
  737. $statsHtml .= '<div class="row mb-3"><div class="col-md-12"><strong>技能使用统计:</strong> ';
  738. foreach ($skillTypeStats as $skillName => $count) {
  739. $statsHtml .= "<span class='badge badge-info mr-2'>{$skillName}: {$count}次</span> ";
  740. }
  741. $statsHtml .= '</div></div>';
  742. }
  743. if ($activeSkills->isEmpty()) {
  744. $content = $statsHtml . '<div class="alert alert-info">该用户的宠物还没有使用过生活技能</div>';
  745. // 即使没有激活技能,也显示历史使用记录
  746. $content .= $this->getRecentSkillUsageSection($petIds);
  747. return new Card('宠物生活技能情况', $content);
  748. }
  749. // 创建技能详情表格
  750. $headers = ['宠物名称', '技能名称', '开始时间', '结束时间', '状态', '剩余时间', '技能配置'];
  751. $rows = [];
  752. foreach ($activeSkills as $activeSkill) {
  753. try {
  754. $petName = $activeSkill->pet ? $activeSkill->pet->name : "宠物{$activeSkill->pet_id}";
  755. $skillName = $activeSkill->skill_name;
  756. $startTime = $activeSkill->start_time->format('Y-m-d H:i:s');
  757. $endTime = $activeSkill->end_time->format('Y-m-d H:i:s');
  758. // 状态显示 - 实时检查过期状态
  759. $isReallyActive = $activeSkill->status === 'active' && $activeSkill->end_time > now();
  760. $statusBadge = match(true) {
  761. $activeSkill->status === 'cancelled' => '<span class="badge badge-warning">已取消</span>',
  762. $isReallyActive => '<span class="badge badge-success">生效中</span>',
  763. default => '<span class="badge badge-secondary">已过期</span>',
  764. };
  765. // 剩余时间 - 实时计算
  766. $remainingTime = '';
  767. if ($isReallyActive) {
  768. $remaining = $activeSkill->getRemainingSeconds();
  769. if ($remaining > 0) {
  770. $hours = floor($remaining / 3600);
  771. $minutes = floor(($remaining % 3600) / 60);
  772. $seconds = $remaining % 60;
  773. $remainingTime = sprintf('%02d:%02d:%02d', $hours, $minutes, $seconds);
  774. } else {
  775. $remainingTime = '<span class="text-danger">已过期</span>';
  776. }
  777. } else {
  778. $remainingTime = '-';
  779. }
  780. // 技能配置信息
  781. $configInfo = '';
  782. if (!empty($activeSkill->config)) {
  783. $config = $activeSkill->config;
  784. $configItems = [];
  785. // 显示关键配置信息
  786. if (isset($config['check_interval'])) {
  787. $configItems[] = "检查间隔: {$config['check_interval']}秒";
  788. }
  789. if (isset($config['preferred_seeds']) && !empty($config['preferred_seeds'])) {
  790. $seedCount = count($config['preferred_seeds']);
  791. $configItems[] = "优先种子: {$seedCount}种";
  792. }
  793. if (isset($config['protected_types']) && !empty($config['protected_types'])) {
  794. $protectedTypes = implode(', ', $config['protected_types']);
  795. $configItems[] = "防护类型: {$protectedTypes}";
  796. }
  797. if (isset($config['statistics']) && !empty($config['statistics'])) {
  798. $statsCount = count($config['statistics']);
  799. $configItems[] = "执行记录: {$statsCount}次";
  800. }
  801. $configInfo = implode('<br>', $configItems);
  802. }
  803. $rows[] = [
  804. $petName,
  805. $skillName,
  806. $startTime,
  807. $endTime,
  808. $statusBadge,
  809. $remainingTime,
  810. $configInfo ?: '-'
  811. ];
  812. } catch (\Throwable $e) {
  813. // 如果出现异常,添加一个错误行
  814. $rows[] = [
  815. $activeSkill->pet ? $activeSkill->pet->name : '未知',
  816. $activeSkill->skill_name ?? '未知',
  817. $activeSkill->start_time ? $activeSkill->start_time->format('Y-m-d H:i:s') : '未知',
  818. $activeSkill->end_time ? $activeSkill->end_time->format('Y-m-d H:i:s') : '未知',
  819. '<span class="badge badge-danger">数据错误</span>',
  820. '-',
  821. '数据解析错误'
  822. ];
  823. }
  824. }
  825. $table = new Table($headers, $rows);
  826. $content = $statsHtml . $table->render();
  827. // 添加最近10次技能使用记录
  828. $content .= $this->getRecentSkillUsageSection($petIds);
  829. // 添加说明信息
  830. $content .= '<div class="alert alert-info mt-3">';
  831. $content .= '<strong>说明:</strong><br>';
  832. $content .= '• <strong>自动收菜</strong>:每分钟自动收获成熟的作物<br>';
  833. $content .= '• <strong>自动播种</strong>:每分钟自动在空闲土地上播种<br>';
  834. $content .= '• <strong>灾害防护</strong>:每5分钟检查并自动清除作物灾害<br>';
  835. $content .= '• 所有自动操作仍需消耗相应的道具(种子、化肥等)';
  836. $content .= '</div>';
  837. return new Card('宠物生活技能情况', $content);
  838. }
  839. /**
  840. * 获取最近技能使用记录区域
  841. *
  842. * @param array $petIds 宠物ID数组
  843. * @return string HTML内容
  844. */
  845. protected function getRecentSkillUsageSection(array $petIds): string
  846. {
  847. if (empty($petIds)) {
  848. return '';
  849. }
  850. // 获取最近10次技能使用记录
  851. $recentLogs = PetSkillLog::with(['pet', 'skill'])
  852. ->whereIn('pet_id', $petIds)
  853. ->orderBy('used_at', 'desc')
  854. ->limit(10)
  855. ->get();
  856. if ($recentLogs->isEmpty()) {
  857. return '<div class="mt-4"><h5>最近技能使用记录</h5><div class="alert alert-info">暂无技能使用记录</div></div>';
  858. }
  859. // 创建技能使用记录表格
  860. $headers = ['使用时间', '宠物名称', '技能名称', '体力消耗', '技能效果', '状态'];
  861. $rows = [];
  862. foreach ($recentLogs as $log) {
  863. try {
  864. $petName = $log->pet ? $log->pet->name : "宠物{$log->pet_id}";
  865. $skillName = $log->skill ? $log->skill->skill_name : "技能{$log->skill_id}";
  866. // 安全处理时间格式
  867. $usedAt = '未知时间';
  868. if ($log->used_at) {
  869. try {
  870. if ($log->used_at instanceof \Carbon\Carbon) {
  871. $usedAt = $log->used_at->format('Y-m-d H:i:s');
  872. } else {
  873. // 尝试解析字符串时间
  874. $usedAt = date('Y-m-d H:i:s', strtotime($log->used_at));
  875. }
  876. } catch (\Exception $e) {
  877. $usedAt = (string) $log->used_at;
  878. }
  879. }
  880. $staminaCost = $log->skill ? $log->skill->stamina_cost : '-';
  881. // 解析技能效果结果
  882. $effectResult = '';
  883. $status = '<span class="badge badge-success">成功</span>';
  884. if (!empty($log->effect_result)) {
  885. try {
  886. $effectData = json_decode($log->effect_result, true);
  887. if (is_array($effectData)) {
  888. $effectItems = [];
  889. // 根据技能类型显示不同的效果信息
  890. if (isset($effectData['skill_type'])) {
  891. $effectItems[] = "类型: {$effectData['skill_type']}";
  892. }
  893. if (isset($effectData['duration'])) {
  894. $hours = round($effectData['duration'] / 3600, 1);
  895. $effectItems[] = "持续: {$hours}小时";
  896. }
  897. if (isset($effectData['end_time'])) {
  898. $endTime = date('H:i', strtotime($effectData['end_time']));
  899. $effectItems[] = "结束: {$endTime}";
  900. }
  901. if (isset($effectData['message'])) {
  902. $effectItems[] = $effectData['message'];
  903. }
  904. // 检查是否有错误
  905. if (isset($effectData['success']) && $effectData['success'] === false) {
  906. $status = '<span class="badge badge-danger">失败</span>';
  907. if (isset($effectData['message'])) {
  908. $effectItems = [$effectData['message']];
  909. }
  910. }
  911. $effectResult = implode('<br>', $effectItems);
  912. } else {
  913. $effectResult = $log->effect_result;
  914. }
  915. } catch (\Exception $e) {
  916. $effectResult = '数据解析错误';
  917. $status = '<span class="badge badge-warning">异常</span>';
  918. }
  919. } else {
  920. $effectResult = '无效果记录';
  921. }
  922. $rows[] = [
  923. $usedAt,
  924. $petName,
  925. $skillName,
  926. $staminaCost,
  927. $effectResult ?: '-',
  928. $status
  929. ];
  930. } catch (\Throwable $e) {
  931. // 如果出现异常,添加一个错误行
  932. $usedAtDisplay = '未知';
  933. if ($log->used_at) {
  934. try {
  935. if ($log->used_at instanceof \Carbon\Carbon) {
  936. $usedAtDisplay = $log->used_at->format('Y-m-d H:i:s');
  937. } else {
  938. $usedAtDisplay = (string) $log->used_at;
  939. }
  940. } catch (\Exception $e) {
  941. $usedAtDisplay = '时间格式错误';
  942. }
  943. }
  944. $rows[] = [
  945. $usedAtDisplay,
  946. $log->pet ? $log->pet->name : '未知',
  947. $log->skill ? $log->skill->skill_name : '未知',
  948. '-',
  949. '数据解析错误',
  950. '<span class="badge badge-danger">错误</span>'
  951. ];
  952. }
  953. }
  954. $table = new Table($headers, $rows);
  955. $html = '<div class="mt-4">';
  956. $html .= '<h5>最近技能使用记录 <small class="text-muted">(最近10次)</small></h5>';
  957. $html .= $table->render();
  958. $html .= '</div>';
  959. return $html;
  960. }
  961. /**
  962. * 神像buff信息卡片
  963. *
  964. * @param int $userId 用户ID
  965. * @return Card
  966. */
  967. protected function buffInfoCard($userId)
  968. {
  969. // 获取用户的神像buff信息
  970. $buffs = FarmGodBuff::where('user_id', $userId)
  971. ->orderBy('expire_time', 'desc')
  972. ->get();
  973. if ($buffs->isEmpty()) {
  974. return new Card('神像加持信息', new Alert('warning', '该用户没有神像加持信息'));
  975. }
  976. // 创建buff表格
  977. $headers = [ '加持类型', '过期时间', '状态' ];
  978. $rows = [];
  979. $buffTypeNames = [
  980. 1 => '丰收之神',
  981. 2 => '雨露之神',
  982. 3 => '屠草之神',
  983. 4 => '拭虫之神',
  984. ];
  985. foreach ($buffs as $buff) {
  986. try {
  987. $buffType = $buffTypeNames[$buff->buff_type] ?? "类型{$buff->buff_type}";
  988. $isActive = now()->lt($buff->expire_time);
  989. $status = $isActive ? '<span class="badge badge-success">生效中</span>' : '<span class="badge badge-secondary">已过期</span>';
  990. $rows[] = [
  991. $buffType,
  992. $buff->expire_time,
  993. $status,
  994. ];
  995. } catch (\Throwable) {
  996. // 如果出现异常,添加一个错误行
  997. $rows[] = [
  998. '数据错误',
  999. $buff->expire_time ?? '未知',
  1000. '<span class="badge badge-danger">数据错误</span>',
  1001. ];
  1002. }
  1003. }
  1004. $table = new Table($headers, $rows);
  1005. $content = $table->render();
  1006. return new Card('神像加持信息', $content);
  1007. }
  1008. /**
  1009. * 用户列表网格
  1010. *
  1011. * @return Grid
  1012. */
  1013. protected function grid()
  1014. {
  1015. return Grid::make(User::with([ 'info', 'farmUser' ]), function (Grid $grid) {
  1016. $grid->column('id', 'ID')->sortable();
  1017. // 用户基本信息
  1018. $grid->column('username', '用户名');
  1019. $grid->column('info.nickname', '昵称');
  1020. // 农场信息
  1021. $grid->column('farmUser.house_level', '房屋等级')->sortable();
  1022. // 土地统计
  1023. $grid->column('id', '土地统计')->display(function ($userId) {
  1024. $lands = FarmLand::where('user_id', $userId)->get();
  1025. if ($lands->isEmpty()) {
  1026. return '<span class="text-muted">无土地</span>';
  1027. }
  1028. $landTypeStats = $lands->groupBy('land_type')->map->count();
  1029. $totalLands = $lands->count();
  1030. $html = "<div>总计: {$totalLands}块土地</div>";
  1031. $landTypeNames = [
  1032. 1 => '普通土地',
  1033. 2 => '红土地',
  1034. 3 => '黑土地',
  1035. 4 => '金土地',
  1036. 5 => '蓝土地',
  1037. 6 => '紫土地',
  1038. ];
  1039. foreach ($landTypeStats as $typeId => $count) {
  1040. $typeName = $landTypeNames[$typeId] ?? "类型{$typeId}";
  1041. $html .= "<div>{$typeName}: {$count}块</div>";
  1042. }
  1043. return $html;
  1044. });
  1045. // 作物统计
  1046. $grid->column('id', '作物统计')->display(function ($userId) {
  1047. $crops = FarmCrop::where('user_id', $userId)->count();
  1048. return $crops > 0 ? "{$crops}种作物" : '<span class="text-muted">无作物</span>';
  1049. });
  1050. // 物品统计
  1051. $grid->column('id', '物品统计')->display(function ($userId) {
  1052. $itemCount = ItemUser::where('user_id', $userId)->count();
  1053. return $itemCount > 0 ? "{$itemCount}种物品" : '<span class="text-muted">无物品</span>';
  1054. });
  1055. // 代币统计
  1056. $grid->column('id', '代币统计')->display(function ($userId) {
  1057. $fundCount = FundModel::where('user_id', $userId)->count();
  1058. return $fundCount > 0 ? "{$fundCount}个账户" : '<span class="text-muted">无账户</span>';
  1059. });
  1060. // 宠物统计
  1061. $grid->column('id', '宠物统计')->display(function ($userId) {
  1062. $pets = PetUser::where('user_id', $userId)->get();
  1063. if ($pets->isEmpty()) {
  1064. return '<span class="text-muted">无宠物</span>';
  1065. }
  1066. $totalPets = $pets->count();
  1067. $maxLevel = $pets->max('level');
  1068. $gradeStats = $pets->groupBy('grade')->map->count();
  1069. $html = "<div>总计: {$totalPets}只宠物</div>";
  1070. $html .= "<div>最高等级: {$maxLevel}</div>";
  1071. // 显示品阶分布
  1072. $gradeNames = [1 => '一品', 2 => '二品', 3 => '三品', 4 => '四品'];
  1073. foreach ($gradeStats as $grade => $count) {
  1074. $gradeName = $gradeNames[$grade] ?? "品阶{$grade}";
  1075. $html .= "<div>{$gradeName}: {$count}只</div>";
  1076. }
  1077. return $html;
  1078. });
  1079. $grid->column('created_at', '创建时间')->sortable();
  1080. // 添加查看详情操作
  1081. $grid->actions(function (Grid\Displayers\Actions $actions) {
  1082. // 禁用默认操作按钮
  1083. $actions->disableDelete();
  1084. $actions->disableEdit();
  1085. $actions->disableQuickEdit();
  1086. // 修改查看按钮,使其打开详情页
  1087. $actions->append('<a href="' . admin_url('farm-user-summary/' . $actions->getKey()) . '" class="btn btn-sm btn-primary">查看详情</a>');
  1088. });
  1089. // 禁用创建按钮
  1090. $grid->disableCreateButton();
  1091. // 禁用批量操作
  1092. $grid->disableBatchActions();
  1093. // 添加搜索
  1094. $grid->filter(function (Grid\Filter $filter) {
  1095. $filter->equal('id', '用户ID');
  1096. $filter->like('username', '用户名');
  1097. $filter->like('info.nickname', '昵称');
  1098. $filter->equal('farmUser.house_level', '房屋等级');
  1099. });
  1100. });
  1101. }
  1102. /**
  1103. * 获取果实信息
  1104. *
  1105. * @param int $itemId 物品ID
  1106. * @return string
  1107. */
  1108. protected function getFruitInfo($itemId)
  1109. {
  1110. try {
  1111. // 查询物品信息
  1112. $item = \App\Module\GameItems\Models\Item::find($itemId);
  1113. if ($item) {
  1114. return "<span class='text-success'><strong>{$item->name}</strong></span><br><small class='text-muted'>ID: {$itemId}</small>";
  1115. } else {
  1116. return "<span class='text-warning'>物品ID: {$itemId}</span><br><small class='text-muted'>物品不存在</small>";
  1117. }
  1118. } catch (\Exception $e) {
  1119. return "<span class='text-danger'>获取失败</span><br><small class='text-muted'>ID: {$itemId}</small>";
  1120. }
  1121. }
  1122. /**
  1123. * 获取种子可能的产出信息
  1124. *
  1125. * @param \App\Module\Farm\Models\FarmSeed $seed 种子对象
  1126. * @return string
  1127. */
  1128. protected function getSeedPossibleOutputs($seed)
  1129. {
  1130. try {
  1131. $outputs = $seed->outputs;
  1132. if ($outputs->isEmpty()) {
  1133. return '<span class="text-muted">无产出配置</span>';
  1134. }
  1135. $outputInfo = [];
  1136. foreach ($outputs as $output) {
  1137. try {
  1138. $item = \App\Module\GameItems\Models\Item::find($output->item_id);
  1139. $itemName = $item ? $item->name : "物品{$output->item_id}";
  1140. $probability = $output->probability ?? 0;
  1141. $outputInfo[] = "<span class='text-info'>{$itemName}</span> <small class='text-muted'>({$probability}%)</small>";
  1142. } catch (\Exception $e) {
  1143. $outputInfo[] = "<span class='text-warning'>物品{$output->item_id}</span>";
  1144. }
  1145. }
  1146. return '<small>可能产出:<br>' . implode('<br>', $outputInfo) . '</small>';
  1147. } catch (\Exception $e) {
  1148. return '<span class="text-muted">获取产出信息失败</span>';
  1149. }
  1150. }
  1151. }