FarmUserSummaryController.php 47 KB

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