FarmUserSummaryController.php 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360
  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.show1')]
  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 admin_redirect('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. /**
  287. * @var FarmCrop $crop
  288. */
  289. $crop = $land->crop;
  290. $cropInfo = '无';
  291. $plantTime = '';
  292. $growthStage = '';
  293. $stageStartTime = '';
  294. $stageEndTime = '';
  295. $fruitInfo = '无';
  296. $disasterInfo = '无';
  297. if ($crop) {
  298. $seedName = $crop->seed ? $crop->seed->name : "种子{$crop->seed_id}";
  299. $cropInfo = $seedName;
  300. $plantTime = $crop->plant_time;
  301. $growthStage = $this->getGrowthStageName($crop->growth_stage);
  302. $stageStartTime = $crop->stage_start_time;
  303. $stageEndTime = $this->formatRelativeTime($crop->stage_end_time);
  304. // 处理果实信息
  305. if ($crop->final_output_item_id) {
  306. $fruitInfo = $this->getFruitInfo($crop->final_output_item_id,$crop->final_output_amount);
  307. } else {
  308. // 如果还没有确定最终产出,显示种子的可能产出
  309. if ($crop->seed && $crop->seed->outputs) {
  310. $fruitInfo = $this->getSeedPossibleOutputs($crop->seed);
  311. }
  312. }
  313. // 处理灾害信息
  314. if (!empty($crop->disasters)) {
  315. $disasterInfo = $this->formatDisasterInfo($crop->disasters);
  316. }
  317. }
  318. $rows[] = [
  319. $land->id,
  320. $land->position,
  321. $landType,
  322. $status,
  323. $cropInfo,
  324. $plantTime,
  325. $growthStage,
  326. $stageStartTime,
  327. $stageEndTime,
  328. $fruitInfo,
  329. $disasterInfo,
  330. ];
  331. }
  332. $table = new Table($headers, $rows);
  333. $content = $statsContent . '<div class="mt-3">' . $table->render() . '</div>';
  334. $content .= <<<HTML
  335. <div class="row mt-2">
  336. <div class="col-md-12">
  337. <a href="javascript:void(0);" class="btn btn-sm btn-primary"
  338. onclick="window.open('/admin/farm-lands?user_id={$userId}', '_blank')">查看土地详情</a>
  339. <a href="javascript:void(0);" class="btn btn-sm btn-primary"
  340. onclick="window.open('/admin/farm-crops?user_id={$userId}', '_blank')">查看作物详情</a>
  341. </div>
  342. </div>
  343. HTML;
  344. return new Card('土地信息', $content);
  345. }
  346. /**
  347. * 获取生长阶段名称
  348. *
  349. * @param GROWTH_STAGE|int $stage 生长阶段枚举或整数值
  350. * @return string 生长阶段名称
  351. */
  352. protected function getGrowthStageName($stage)
  353. {
  354. // 如果传入的是整数,转换为枚举
  355. if (is_int($stage)) {
  356. try {
  357. $stage = GROWTH_STAGE::from($stage);
  358. } catch (\ValueError) {
  359. return "未知阶段{$stage}";
  360. }
  361. }
  362. // 如果是null,返回未知
  363. if ($stage === null) {
  364. return "未知阶段";
  365. }
  366. // 获取阶段名称
  367. try {
  368. $stageNames = GROWTH_STAGE::getAll();
  369. $stageValue = $stage->value;
  370. return $stageNames[$stageValue] ?? "阶段{$stageValue}";
  371. } catch (\Throwable) {
  372. return "错误阶段";
  373. }
  374. }
  375. /**
  376. * 格式化相对时间描述
  377. *
  378. * @param string|\Carbon\Carbon|null $dateTime 时间
  379. * @return string 相对时间描述
  380. */
  381. protected function formatRelativeTime($dateTime): string
  382. {
  383. if (!$dateTime) {
  384. return '无';
  385. }
  386. try {
  387. // 确保是Carbon实例
  388. if (is_string($dateTime)) {
  389. $dateTime = \Carbon\Carbon::parse($dateTime);
  390. }
  391. $now = now();
  392. // 如果时间已经过去
  393. if ($dateTime->isPast()) {
  394. return '<span class="text-danger">已过期</span>';
  395. }
  396. // 计算时间差
  397. $diffInSeconds = $now->diffInSeconds($dateTime);
  398. $diffInMinutes = $now->diffInMinutes($dateTime);
  399. $diffInHours = $now->diffInHours($dateTime);
  400. $diffInDays = $now->diffInDays($dateTime);
  401. // 根据时间差返回合适的描述
  402. if ($diffInSeconds < 60) {
  403. $seconds = round($diffInSeconds);
  404. return "<span class='text-warning'>{$seconds}秒后</span>";
  405. } elseif ($diffInMinutes < 60) {
  406. $minutes = round($diffInMinutes);
  407. return "<span class='text-info'>{$minutes}分钟后</span>";
  408. } elseif ($diffInHours < 24) {
  409. // 小时显示保留1位小数,但如果是整数则不显示小数
  410. $hours = round($diffInHours, 1);
  411. $hoursDisplay = $hours == intval($hours) ? intval($hours) : $hours;
  412. return "<span class='text-primary'>{$hoursDisplay}小时后</span>";
  413. } elseif ($diffInDays < 7) {
  414. $days = round($diffInDays);
  415. return "<span class='text-secondary'>{$days}天后</span>";
  416. } else {
  417. // 超过7天显示具体日期
  418. return $dateTime->format('Y-m-d H:i:s');
  419. }
  420. } catch (\Throwable $e) {
  421. return '<span class="text-muted">时间格式错误</span>';
  422. }
  423. }
  424. /**
  425. * 物品信息卡片
  426. *
  427. * @param int $userId 用户ID
  428. * @return Card
  429. */
  430. protected function itemInfoCard($userId)
  431. {
  432. // 获取用户的物品信息
  433. $items = ItemUser::with('item')
  434. ->where('user_id', $userId)
  435. ->orderBy('quantity', 'desc')
  436. ->limit(20)
  437. ->get();
  438. if ($items->isEmpty()) {
  439. return new Card('物品信息', new Alert('warning', '该用户没有物品信息'));
  440. }
  441. // 创建物品表格
  442. $headers = [ '物品ID', '物品名称', '数量', '物品类型', '过期时间' ];
  443. $rows = [];
  444. foreach ($items as $item) {
  445. $itemName = $item->item ? $item->item->name : "物品{$item->item_id}";
  446. $itemType = $item->item ? $this->getItemTypeName($item->item->type) : '';
  447. $rows[] = [
  448. $item->item_id,
  449. $itemName,
  450. $item->quantity,
  451. $itemType,
  452. $item->expire_at ?: '永久',
  453. ];
  454. }
  455. $table = new Table($headers, $rows);
  456. // 获取用户物品总数
  457. $totalCount = ItemUser::where('user_id', $userId)->count();
  458. $content = <<<HTML
  459. <div class="alert alert-info">
  460. 用户共有 {$totalCount} 种物品,下表显示数量最多的前20种物品
  461. </div>
  462. {$table->render()}
  463. <div class="row mt-2">
  464. <div class="col-md-12">
  465. <a href="javascript:void(0);" class="btn btn-sm btn-primary" onclick="window.open('/admin/game-items-user-items?user_id={$userId}', '_blank')">查看物品详情</a>
  466. </div>
  467. </div>
  468. HTML;
  469. return new Card('物品信息', $content);
  470. }
  471. /**
  472. * 获取物品类型名称
  473. *
  474. * @param ITEM_TYPE|int $type 物品类型枚举或整数值
  475. * @return string 物品类型名称
  476. */
  477. protected function getItemTypeName($type)
  478. {
  479. // 如果传入的是整数,转换为枚举
  480. if (is_int($type)) {
  481. try {
  482. $type = ITEM_TYPE::from($type);
  483. } catch (\ValueError) {
  484. return "未知类型{$type}";
  485. }
  486. }
  487. // 如果是null,返回未知
  488. if ($type === null) {
  489. return "未知类型";
  490. }
  491. // 获取类型名称
  492. try {
  493. $typeNames = ITEM_TYPE::getValueDescription();
  494. $typeValue = $type->value;
  495. return $typeNames[$typeValue] ?? "类型{$typeValue}";
  496. } catch (\Throwable) {
  497. return "错误类型";
  498. }
  499. }
  500. /**
  501. * 格式化灾害信息
  502. *
  503. * @param array $disasters 灾害数组
  504. * @return string 格式化后的灾害信息
  505. */
  506. protected function formatDisasterInfo(array $disasters): string
  507. {
  508. if (empty($disasters)) {
  509. return '无';
  510. }
  511. $result = [];
  512. foreach ($disasters as $disaster) {
  513. $type = $disaster['type'] ?? 0;
  514. $status = $disaster['status'] ?? 'active'; // 默认为活跃状态
  515. // 获取灾害类型名称
  516. $typeName = DISASTER_TYPE::getName($type);
  517. // 灾害状态
  518. $statusText = $status === 'active' ? '<span class="badge badge-danger">活跃</span>' : '<span class="badge badge-secondary">已处理</span>';
  519. // 灾害开始时间
  520. $startTime = isset($disaster['start_time']) ? date('Y-m-d H:i:s', $disaster['start_time']) : '未知';
  521. // 灾害结束时间(如果有)
  522. $endTime = '';
  523. if (isset($disaster['end_time']) && $disaster['end_time'] > 0) {
  524. $endTime = date('Y-m-d H:i:s', $disaster['end_time']);
  525. }
  526. // 减产比例 - 从DisasterService获取默认值
  527. $defaultPenalties = \App\Module\Farm\Services\DisasterService::getAllDisasters();
  528. $defaultPenalty = $defaultPenalties[$type] ?? 0.05;
  529. $penalty = isset($disaster['penalty']) ? ($disaster['penalty'] * 100) . '%' : ($defaultPenalty * 100) . '%';
  530. // 组合灾害信息
  531. $disasterInfo = "<div><strong>{$typeName}</strong>: {$statusText}</div>";
  532. $disasterInfo .= "<div>开始: {$startTime}</div>";
  533. if ($endTime) {
  534. $disasterInfo .= "<div>结束: {$endTime}</div>";
  535. }
  536. $disasterInfo .= "<div>减产: {$penalty}</div>";
  537. // 如果有额外的灾害信息,也显示出来
  538. if (isset($disaster['id'])) {
  539. $disasterInfo .= "<div>ID: {$disaster['id']}</div>";
  540. }
  541. $result[] = $disasterInfo;
  542. }
  543. return implode('<hr style="margin: 5px 0;">', $result);
  544. }
  545. /**
  546. * 代币信息卡片
  547. *
  548. * @param int $userId 用户ID
  549. * @return Card
  550. */
  551. protected function fundInfoCard($userId)
  552. {
  553. // 获取用户的代币信息
  554. $funds = FundModel::where('user_id', $userId)->get();
  555. if ($funds->isEmpty()) {
  556. return new Card('代币信息', new Alert('warning', '该用户没有代币信息'));
  557. }
  558. // 获取资金类型名称映射
  559. $fundNames = AccountService::getFundsDesc();
  560. // 创建代币表格
  561. $headers = [ '账户ID', '账户名称', '余额', '更新时间' ];
  562. $rows = [];
  563. foreach ($funds as $fund) {
  564. try {
  565. $fundIdValue = $fund->fund_id->value();
  566. $fundName = $fundNames[$fundIdValue] ?? "账户{$fundIdValue}";
  567. $balance = $fund->balance;
  568. $rows[] = [
  569. $fundIdValue,
  570. $fundName,
  571. $balance,
  572. $fund->update_time ? date('Y-m-d H:i:s', $fund->update_time) : '',
  573. ];
  574. } catch (\Throwable) {
  575. // 如果出现异常,添加一个错误行
  576. $rows[] = [
  577. $fund->id ?? '未知',
  578. '数据错误',
  579. $fund->balance ?? '未知',
  580. $fund->update_time ? date('Y-m-d H:i:s', $fund->update_time) : '',
  581. ];
  582. }
  583. }
  584. $table = new Table($headers, $rows);
  585. $content = <<<HTML
  586. {$table->render()}
  587. <div class="row mt-2">
  588. <div class="col-md-12">
  589. <a href="javascript:void(0);" class="btn btn-sm btn-primary" onclick="window.open('/admin/fund-accounts?user_id={$userId}', '_blank')">查看账户详情</a>
  590. </div>
  591. </div>
  592. HTML;
  593. return new Card('代币信息', $content);
  594. }
  595. /**
  596. * 宠物信息卡片
  597. *
  598. * @param int $userId 用户ID
  599. * @return Card
  600. */
  601. protected function petInfoCard($userId)
  602. {
  603. // 获取用户的宠物信息
  604. $pets = PetUser::where('user_id', $userId)
  605. ->orderBy('level', 'desc')
  606. ->orderBy('grade', 'desc')
  607. ->get();
  608. if ($pets->isEmpty()) {
  609. return new Card('宠物信息', new Alert('warning', '该用户没有宠物信息'));
  610. }
  611. // 宠物统计
  612. $totalPets = $pets->count();
  613. $gradeStats = $pets->groupBy('grade')->map->count();
  614. $statusStats = $pets->groupBy('status')->map->count();
  615. $levelStats = [
  616. 'max_level' => $pets->max('level'),
  617. 'avg_level' => round($pets->avg('level'), 1),
  618. 'total_exp' => $pets->sum('experience'),
  619. ];
  620. // 创建宠物表格
  621. $headers = ['宠物ID','宠物名称', '品阶', '等级', '经验', '体力', '状态', '创建时间'];
  622. $rows = [];
  623. $gradeNames = [
  624. 1 => '一品',
  625. 2 => '二品',
  626. 3 => '三品',
  627. 4 => '四品',
  628. ];
  629. $statusNames = [
  630. PetStatus::NONE->value => '未知',
  631. PetStatus::NORMAL->value => '正常',
  632. PetStatus::FIGHTING->value => '战斗中',
  633. PetStatus::DEAD->value => '死亡',
  634. PetStatus::FEEDING->value => '喂养中',
  635. PetStatus::TRAINING->value => '训练中',
  636. PetStatus::RESTING->value => '休息中',
  637. PetStatus::TRAVELING->value => '外出中',
  638. ];
  639. foreach ($pets as $pet) {
  640. try {
  641. $gradeName = $gradeNames[$pet->grade] ?? "品阶{$pet->grade}";
  642. $statusName = $statusNames[$pet->status->value] ?? "状态{$pet->status->value}";
  643. // 状态颜色
  644. $statusBadge = match($pet->status) {
  645. PetStatus::NORMAL => '<span class="badge badge-success">' . $statusName . '</span>',
  646. PetStatus::FIGHTING => '<span class="badge badge-warning">' . $statusName . '</span>',
  647. PetStatus::DEAD => '<span class="badge badge-danger">' . $statusName . '</span>',
  648. PetStatus::FEEDING, PetStatus::TRAINING => '<span class="badge badge-info">' . $statusName . '</span>',
  649. PetStatus::RESTING, PetStatus::TRAVELING => '<span class="badge badge-primary">' . $statusName . '</span>',
  650. default => '<span class="badge badge-secondary">' . $statusName . '</span>',
  651. };
  652. $rows[] = [
  653. $pet->id,
  654. $pet->name,
  655. $gradeName,
  656. $pet->level,
  657. $pet->experience,
  658. $pet->stamina,
  659. $statusBadge,
  660. $pet->created_at->format('Y-m-d H:i:s'),
  661. ];
  662. } catch (\Throwable) {
  663. // 如果出现异常,添加一个错误行
  664. $rows[] = [
  665. $pet->name ?? '未知',
  666. '数据错误',
  667. $pet->level ?? 0,
  668. $pet->experience ?? 0,
  669. $pet->stamina ?? 0,
  670. '<span class="badge badge-danger">数据错误</span>',
  671. $pet->created_at ? $pet->created_at->format('Y-m-d H:i:s') : '未知',
  672. ];
  673. }
  674. }
  675. $table = new Table($headers, $rows);
  676. // 统计信息
  677. $statsHtml = '<div class="row mb-3">';
  678. $statsHtml .= '<div class="col-md-3"><strong>宠物总数:</strong> ' . $totalPets . '</div>';
  679. $statsHtml .= '<div class="col-md-3"><strong>最高等级:</strong> ' . $levelStats['max_level'] . '</div>';
  680. $statsHtml .= '<div class="col-md-3"><strong>平均等级:</strong> ' . $levelStats['avg_level'] . '</div>';
  681. $statsHtml .= '<div class="col-md-3"><strong>总经验:</strong> ' . number_format($levelStats['total_exp']) . '</div>';
  682. $statsHtml .= '</div>';
  683. // 品阶统计
  684. if (!$gradeStats->isEmpty()) {
  685. $statsHtml .= '<div class="row mb-3"><div class="col-md-12"><strong>品阶分布:</strong> ';
  686. foreach ($gradeStats as $grade => $count) {
  687. $gradeName = $gradeNames[$grade] ?? "品阶{$grade}";
  688. $statsHtml .= "{$gradeName}: {$count}只 ";
  689. }
  690. $statsHtml .= '</div></div>';
  691. }
  692. // 状态统计
  693. if (!$statusStats->isEmpty()) {
  694. $statsHtml .= '<div class="row mb-3"><div class="col-md-12"><strong>状态分布:</strong> ';
  695. foreach ($statusStats as $status => $count) {
  696. $statusName = $statusNames[$status] ?? "状态{$status}";
  697. $statsHtml .= "{$statusName}: {$count}只 ";
  698. }
  699. $statsHtml .= '</div></div>';
  700. }
  701. $content = $statsHtml . $table->render();
  702. return new Card('宠物信息', $content);
  703. }
  704. /**
  705. * 宠物生活技能情况卡片
  706. *
  707. * @param int $userId 用户ID
  708. * @return Card
  709. */
  710. protected function petLifeSkillsCard($userId)
  711. {
  712. // 获取用户的宠物
  713. $pets = PetUser::where('user_id', $userId)->get();
  714. if ($pets->isEmpty()) {
  715. return new Card('宠物生活技能情况', new Alert('warning', '该用户没有宠物,无法使用生活技能'));
  716. }
  717. // 获取所有宠物的激活技能
  718. $petIds = $pets->pluck('id')->toArray();
  719. $activeSkills = PetActiveSkill::with(['pet', 'skill'])
  720. ->whereIn('pet_id', $petIds)
  721. ->orderBy('status', 'asc')
  722. ->orderBy('end_time', 'desc')
  723. ->get();
  724. // 统计信息
  725. $totalSkills = $activeSkills->count();
  726. $activeCount = $activeSkills->where('status', 'active')->count();
  727. $expiredCount = $activeSkills->where('status', 'expired')->count();
  728. $cancelledCount = $activeSkills->where('status', 'cancelled')->count();
  729. // 技能类型统计
  730. $skillTypeStats = $activeSkills->groupBy('skill_name')->map->count();
  731. // 创建统计信息
  732. $statsHtml = '<div class="row mb-3">';
  733. $statsHtml .= '<div class="col-md-3"><strong>总技能使用次数:</strong> ' . $totalSkills . '</div>';
  734. $statsHtml .= '<div class="col-md-3"><strong>当前激活:</strong> <span class="badge badge-success">' . $activeCount . '</span></div>';
  735. $statsHtml .= '<div class="col-md-3"><strong>已过期:</strong> <span class="badge badge-secondary">' . $expiredCount . '</span></div>';
  736. $statsHtml .= '<div class="col-md-3"><strong>已取消:</strong> <span class="badge badge-warning">' . $cancelledCount . '</span></div>';
  737. $statsHtml .= '</div>';
  738. // 技能类型统计
  739. if (!$skillTypeStats->isEmpty()) {
  740. $statsHtml .= '<div class="row mb-3"><div class="col-md-12"><strong>技能使用统计:</strong> ';
  741. foreach ($skillTypeStats as $skillName => $count) {
  742. $statsHtml .= "<span class='badge badge-info mr-2'>{$skillName}: {$count}次</span> ";
  743. }
  744. $statsHtml .= '</div></div>';
  745. }
  746. if ($activeSkills->isEmpty()) {
  747. $content = $statsHtml . '<div class="alert alert-info">该用户的宠物还没有使用过生活技能</div>';
  748. // 即使没有激活技能,也显示历史使用记录
  749. $content .= $this->getRecentSkillUsageSection($petIds);
  750. return new Card('宠物生活技能情况', $content);
  751. }
  752. // 创建技能详情表格
  753. $headers = ['宠物名称', '技能名称', '开始时间', '结束时间', '状态', '剩余时间', '技能配置'];
  754. $rows = [];
  755. foreach ($activeSkills as $activeSkill) {
  756. try {
  757. $petName = $activeSkill->pet ? $activeSkill->pet->name : "宠物{$activeSkill->pet_id}";
  758. $skillName = $activeSkill->skill_name;
  759. $startTime = $activeSkill->start_time->format('Y-m-d H:i:s');
  760. $endTime = $activeSkill->end_time->format('Y-m-d H:i:s');
  761. // 状态显示 - 实时检查过期状态
  762. $isReallyActive = $activeSkill->status === 'active' && $activeSkill->end_time > now();
  763. $statusBadge = match(true) {
  764. $activeSkill->status === 'cancelled' => '<span class="badge badge-warning">已取消</span>',
  765. $isReallyActive => '<span class="badge badge-success">生效中</span>',
  766. default => '<span class="badge badge-secondary">已过期</span>',
  767. };
  768. // 剩余时间 - 实时计算
  769. $remainingTime = '';
  770. if ($isReallyActive) {
  771. $remaining = $activeSkill->getRemainingSeconds();
  772. if ($remaining > 0) {
  773. $hours = floor($remaining / 3600);
  774. $minutes = floor(($remaining % 3600) / 60);
  775. $seconds = $remaining % 60;
  776. $remainingTime = sprintf('%02d:%02d:%02d', $hours, $minutes, $seconds);
  777. } else {
  778. $remainingTime = '<span class="text-danger">已过期</span>';
  779. }
  780. } else {
  781. $remainingTime = '-';
  782. }
  783. // 技能配置信息
  784. $configInfo = '';
  785. if (!empty($activeSkill->config)) {
  786. $config = $activeSkill->config;
  787. $configItems = [];
  788. // 显示关键配置信息
  789. if (isset($config['check_interval'])) {
  790. $configItems[] = "检查间隔: {$config['check_interval']}秒";
  791. }
  792. if (isset($config['preferred_seeds']) && !empty($config['preferred_seeds'])) {
  793. $seedCount = count($config['preferred_seeds']);
  794. $configItems[] = "优先种子: {$seedCount}种";
  795. }
  796. if (isset($config['protected_types']) && !empty($config['protected_types'])) {
  797. $protectedTypes = implode(', ', $config['protected_types']);
  798. $configItems[] = "防护类型: {$protectedTypes}";
  799. }
  800. if (isset($config['statistics']) && !empty($config['statistics'])) {
  801. $statsCount = count($config['statistics']);
  802. $configItems[] = "执行记录: {$statsCount}次";
  803. }
  804. $configInfo = implode('<br>', $configItems);
  805. }
  806. $rows[] = [
  807. $petName,
  808. $skillName,
  809. $startTime,
  810. $endTime,
  811. $statusBadge,
  812. $remainingTime,
  813. $configInfo ?: '-'
  814. ];
  815. } catch (\Throwable $e) {
  816. // 如果出现异常,添加一个错误行
  817. $rows[] = [
  818. $activeSkill->pet ? $activeSkill->pet->name : '未知',
  819. $activeSkill->skill_name ?? '未知',
  820. $activeSkill->start_time ? $activeSkill->start_time->format('Y-m-d H:i:s') : '未知',
  821. $activeSkill->end_time ? $activeSkill->end_time->format('Y-m-d H:i:s') : '未知',
  822. '<span class="badge badge-danger">数据错误</span>',
  823. '-',
  824. '数据解析错误'
  825. ];
  826. }
  827. }
  828. $table = new Table($headers, $rows);
  829. $content = $statsHtml . $table->render();
  830. // 添加最近10次技能使用记录
  831. $content .= $this->getRecentSkillUsageSection($petIds);
  832. // 添加说明信息
  833. $content .= '<div class="alert alert-info mt-3">';
  834. $content .= '<strong>说明:</strong><br>';
  835. $content .= '• <strong>自动收菜</strong>:每分钟自动收获成熟的作物<br>';
  836. $content .= '• <strong>自动播种</strong>:每分钟自动在空闲土地上播种<br>';
  837. $content .= '• <strong>灾害防护</strong>:每5分钟检查并自动清除作物灾害<br>';
  838. $content .= '• 所有自动操作仍需消耗相应的道具(种子、化肥等)';
  839. $content .= '</div>';
  840. return new Card('宠物生活技能情况', $content);
  841. }
  842. /**
  843. * 获取最近技能使用记录区域
  844. *
  845. * @param array $petIds 宠物ID数组
  846. * @return string HTML内容
  847. */
  848. protected function getRecentSkillUsageSection(array $petIds): string
  849. {
  850. if (empty($petIds)) {
  851. return '';
  852. }
  853. // 获取最近10次技能使用记录
  854. $recentLogs = PetSkillLog::with(['pet', 'skill'])
  855. ->whereIn('pet_id', $petIds)
  856. ->orderBy('used_at', 'desc')
  857. ->limit(10)
  858. ->get();
  859. if ($recentLogs->isEmpty()) {
  860. return '<div class="mt-4"><h5>最近技能使用记录</h5><div class="alert alert-info">暂无技能使用记录</div></div>';
  861. }
  862. // 创建技能使用记录表格
  863. $headers = ['使用时间', '宠物名称', '技能名称', '体力消耗', '技能效果', '状态'];
  864. $rows = [];
  865. foreach ($recentLogs as $log) {
  866. try {
  867. $petName = $log->pet ? $log->pet->name : "宠物{$log->pet_id}";
  868. $skillName = $log->skill ? $log->skill->skill_name : "技能{$log->skill_id}";
  869. // 安全处理时间格式
  870. $usedAt = '未知时间';
  871. if ($log->used_at) {
  872. try {
  873. if ($log->used_at instanceof \Carbon\Carbon) {
  874. $usedAt = $log->used_at->format('Y-m-d H:i:s');
  875. } else {
  876. // 尝试解析字符串时间
  877. $usedAt = date('Y-m-d H:i:s', strtotime($log->used_at));
  878. }
  879. } catch (\Exception $e) {
  880. $usedAt = (string) $log->used_at;
  881. }
  882. }
  883. $staminaCost = $log->skill ? $log->skill->stamina_cost : '-';
  884. // 解析技能效果结果
  885. $effectResult = '';
  886. $status = '<span class="badge badge-success">成功</span>';
  887. if (!empty($log->effect_result)) {
  888. try {
  889. $effectData = json_decode($log->effect_result, true);
  890. if (is_array($effectData)) {
  891. $effectItems = [];
  892. // 根据技能类型显示不同的效果信息
  893. if (isset($effectData['skill_type'])) {
  894. $effectItems[] = "类型: {$effectData['skill_type']}";
  895. }
  896. if (isset($effectData['duration'])) {
  897. $hours = round($effectData['duration'] / 3600, 1);
  898. $effectItems[] = "持续: {$hours}小时";
  899. }
  900. if (isset($effectData['end_time'])) {
  901. $endTime = date('H:i', strtotime($effectData['end_time']));
  902. $effectItems[] = "结束: {$endTime}";
  903. }
  904. if (isset($effectData['message'])) {
  905. $effectItems[] = $effectData['message'];
  906. }
  907. // 检查是否有错误
  908. if (isset($effectData['success']) && $effectData['success'] === false) {
  909. $status = '<span class="badge badge-danger">失败</span>';
  910. if (isset($effectData['message'])) {
  911. $effectItems = [$effectData['message']];
  912. }
  913. }
  914. $effectResult = implode('<br>', $effectItems);
  915. } else {
  916. $effectResult = $log->effect_result;
  917. }
  918. } catch (\Exception $e) {
  919. $effectResult = '数据解析错误';
  920. $status = '<span class="badge badge-warning">异常</span>';
  921. }
  922. } else {
  923. $effectResult = '无效果记录';
  924. }
  925. $rows[] = [
  926. $usedAt,
  927. $petName,
  928. $skillName,
  929. $staminaCost,
  930. $effectResult ?: '-',
  931. $status
  932. ];
  933. } catch (\Throwable $e) {
  934. // 如果出现异常,添加一个错误行
  935. $usedAtDisplay = '未知';
  936. if ($log->used_at) {
  937. try {
  938. if ($log->used_at instanceof \Carbon\Carbon) {
  939. $usedAtDisplay = $log->used_at->format('Y-m-d H:i:s');
  940. } else {
  941. $usedAtDisplay = (string) $log->used_at;
  942. }
  943. } catch (\Exception $e) {
  944. $usedAtDisplay = '时间格式错误';
  945. }
  946. }
  947. $rows[] = [
  948. $usedAtDisplay,
  949. $log->pet ? $log->pet->name : '未知',
  950. $log->skill ? $log->skill->skill_name : '未知',
  951. '-',
  952. '数据解析错误',
  953. '<span class="badge badge-danger">错误</span>'
  954. ];
  955. }
  956. }
  957. $table = new Table($headers, $rows);
  958. $html = '<div class="mt-4">';
  959. $html .= '<h5>最近技能使用记录 <small class="text-muted">(最近10次)</small></h5>';
  960. $html .= $table->render();
  961. $html .= '</div>';
  962. return $html;
  963. }
  964. /**
  965. * 神像buff信息卡片
  966. *
  967. * @param int $userId 用户ID
  968. * @return Card
  969. */
  970. protected function buffInfoCard($userId)
  971. {
  972. // 获取用户的神像buff信息
  973. $buffs = FarmGodBuff::where('user_id', $userId)
  974. ->orderBy('expire_time', 'desc')
  975. ->get();
  976. if ($buffs->isEmpty()) {
  977. return new Card('神像加持信息', new Alert('warning', '该用户没有神像加持信息'));
  978. }
  979. // 创建buff表格
  980. $headers = [ '加持类型', '过期时间', '状态' ];
  981. $rows = [];
  982. $buffTypeNames = [
  983. 1 => '丰收之神',
  984. 2 => '雨露之神',
  985. 3 => '屠草之神',
  986. 4 => '拭虫之神',
  987. ];
  988. foreach ($buffs as $buff) {
  989. try {
  990. $buffType = $buffTypeNames[$buff->buff_type] ?? "类型{$buff->buff_type}";
  991. $isActive = now()->lt($buff->expire_time);
  992. $status = $isActive ? '<span class="badge badge-success">生效中</span>' : '<span class="badge badge-secondary">已过期</span>';
  993. $rows[] = [
  994. $buffType,
  995. $buff->expire_time,
  996. $status,
  997. ];
  998. } catch (\Throwable) {
  999. // 如果出现异常,添加一个错误行
  1000. $rows[] = [
  1001. '数据错误',
  1002. $buff->expire_time ?? '未知',
  1003. '<span class="badge badge-danger">数据错误</span>',
  1004. ];
  1005. }
  1006. }
  1007. $table = new Table($headers, $rows);
  1008. $content = $table->render();
  1009. return new Card('神像加持信息', $content);
  1010. }
  1011. /**
  1012. * 用户列表网格
  1013. *
  1014. * @return Grid
  1015. */
  1016. protected function grid()
  1017. {
  1018. return Grid::make(User::with([ 'info', 'farmUser' ]), function (Grid $grid) {
  1019. $grid->column('id', 'ID')->sortable();
  1020. // 用户基本信息
  1021. $grid->column('username', '用户名');
  1022. $grid->column('info.nickname', '昵称');
  1023. // 农场信息
  1024. $grid->column('farmUser.house_level', '房屋等级')->sortable();
  1025. // 土地统计
  1026. $grid->column('id', '土地统计')->display(function ($userId) {
  1027. $lands = FarmLand::where('user_id', $userId)->get();
  1028. if ($lands->isEmpty()) {
  1029. return '<span class="text-muted">无土地</span>';
  1030. }
  1031. $landTypeStats = $lands->groupBy('land_type')->map->count();
  1032. $totalLands = $lands->count();
  1033. $html = "<div>总计: {$totalLands}块土地</div>";
  1034. $landTypeNames = [
  1035. 1 => '普通土地',
  1036. 2 => '红土地',
  1037. 3 => '黑土地',
  1038. 4 => '金土地',
  1039. 5 => '蓝土地',
  1040. 6 => '紫土地',
  1041. ];
  1042. foreach ($landTypeStats as $typeId => $count) {
  1043. $typeName = $landTypeNames[$typeId] ?? "类型{$typeId}";
  1044. $html .= "<div>{$typeName}: {$count}块</div>";
  1045. }
  1046. return $html;
  1047. });
  1048. // 作物统计
  1049. $grid->column('id', '作物统计')->display(function ($userId) {
  1050. $crops = FarmCrop::where('user_id', $userId)->count();
  1051. return $crops > 0 ? "{$crops}种作物" : '<span class="text-muted">无作物</span>';
  1052. });
  1053. // 物品统计
  1054. $grid->column('id', '物品统计')->display(function ($userId) {
  1055. $itemCount = ItemUser::where('user_id', $userId)->count();
  1056. return $itemCount > 0 ? "{$itemCount}种物品" : '<span class="text-muted">无物品</span>';
  1057. });
  1058. // 代币统计
  1059. $grid->column('id', '代币统计')->display(function ($userId) {
  1060. $fundCount = FundModel::where('user_id', $userId)->count();
  1061. return $fundCount > 0 ? "{$fundCount}个账户" : '<span class="text-muted">无账户</span>';
  1062. });
  1063. // 宠物统计
  1064. $grid->column('id', '宠物统计')->display(function ($userId) {
  1065. $pets = PetUser::where('user_id', $userId)->get();
  1066. if ($pets->isEmpty()) {
  1067. return '<span class="text-muted">无宠物</span>';
  1068. }
  1069. $totalPets = $pets->count();
  1070. $maxLevel = $pets->max('level');
  1071. $gradeStats = $pets->groupBy('grade')->map->count();
  1072. $html = "<div>总计: {$totalPets}只宠物</div>";
  1073. $html .= "<div>最高等级: {$maxLevel}</div>";
  1074. // 显示品阶分布
  1075. $gradeNames = [1 => '一品', 2 => '二品', 3 => '三品', 4 => '四品'];
  1076. foreach ($gradeStats as $grade => $count) {
  1077. $gradeName = $gradeNames[$grade] ?? "品阶{$grade}";
  1078. $html .= "<div>{$gradeName}: {$count}只</div>";
  1079. }
  1080. return $html;
  1081. });
  1082. $grid->column('created_at', '创建时间')->sortable();
  1083. // 添加查看详情操作
  1084. $grid->actions(function (Grid\Displayers\Actions $actions) {
  1085. // 禁用默认操作按钮
  1086. $actions->disableDelete();
  1087. $actions->disableEdit();
  1088. $actions->disableQuickEdit();
  1089. // 修改查看按钮,使其打开详情页
  1090. $actions->append('<a href="' . admin_url('farm-user-summary/' . $actions->getKey()) . '" class="btn btn-sm btn-primary">查看详情</a>');
  1091. });
  1092. // 禁用创建按钮
  1093. $grid->disableCreateButton();
  1094. // 禁用批量操作
  1095. $grid->disableBatchActions();
  1096. // 添加搜索
  1097. $grid->filter(function (Grid\Filter $filter) {
  1098. $filter->equal('id', '用户ID');
  1099. $filter->like('username', '用户名');
  1100. $filter->like('info.nickname', '昵称');
  1101. $filter->equal('farmUser.house_level', '房屋等级');
  1102. });
  1103. });
  1104. }
  1105. /**
  1106. * 获取果实信息
  1107. *
  1108. * @param int $itemId 物品ID
  1109. * @return string
  1110. */
  1111. protected function getFruitInfo($itemId, $number)
  1112. {
  1113. try {
  1114. // 查询物品信息
  1115. $item = \App\Module\GameItems\Models\Item::find($itemId);
  1116. if ($item) {
  1117. return "<span class='text-success'>
  1118. <strong>{$item->name}</strong>
  1119. 数量 : {$number}
  1120. </span><br><small class='text-muted'>ID: {$itemId}</small>";
  1121. } else {
  1122. return "<span class='text-warning'>物品ID: {$itemId} , 数量 {$number} </span><br><small class='text-muted'>物品不存在</small>";
  1123. }
  1124. } catch (\Exception $e) {
  1125. return "<span class='text-danger'>获取失败</span><br><small class='text-muted'>ID: {$itemId}</small>";
  1126. }
  1127. }
  1128. /**
  1129. * 获取种子可能的产出信息
  1130. *
  1131. * @param \App\Module\Farm\Models\FarmSeed $seed 种子对象
  1132. * @return string
  1133. */
  1134. protected function getSeedPossibleOutputs($seed)
  1135. {
  1136. try {
  1137. $outputs = $seed->outputs;
  1138. if ($outputs->isEmpty()) {
  1139. return '<span class="text-muted">无产出配置</span>';
  1140. }
  1141. $outputInfo = [];
  1142. foreach ($outputs as $output) {
  1143. try {
  1144. $item = \App\Module\GameItems\Models\Item::find($output->item_id);
  1145. $itemName = $item ? $item->name : "物品{$output->item_id}";
  1146. $probability = $output->probability ?? 0;
  1147. $outputInfo[] = "<span class='text-info'>{$itemName}</span> <small class='text-muted'>({$probability}%)</small>";
  1148. } catch (\Exception $e) {
  1149. $outputInfo[] = "<span class='text-warning'>物品{$output->item_id}</span>";
  1150. }
  1151. }
  1152. return '<small>可能产出:<br>' . implode('<br>', $outputInfo) . '</small>';
  1153. } catch (\Exception $e) {
  1154. return '<span class="text-muted">获取产出信息失败</span>';
  1155. }
  1156. }
  1157. }