FarmUserSummaryController.php 41 KB

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