AiStatistics.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712
  1. <?php
  2. namespace app\admin\controller;
  3. use app\common\util\AppMsg;
  4. use think\Db;
  5. use think\Exception;
  6. use think\exception\DbException;
  7. class AiStatistics extends Auth
  8. {
  9. static $types = [
  10. 1 =>'报修',
  11. 2 =>'保洁',
  12. 3 =>'运送',
  13. 4 =>'安保',
  14. ];
  15. static $colorMap = [
  16. 1 => '#0FB36A',
  17. 2 => '#168BFF',
  18. 3 => '#F79B10',
  19. 4 => '#6F3DFF',
  20. ];
  21. public function statisticsData()
  22. {
  23. $today = date('Ymd');
  24. $records = Db::name('ai_send_success')
  25. ->where('org_id', $this->orgId)
  26. ->field('mode')
  27. ->group('mode')
  28. ->column('COUNT(*)', 'mode');
  29. $done = [
  30. 'total' => array_sum($records),
  31. 'bx' => $records[1] ?? 0,
  32. 'bj' => $records[2] ?? 0,
  33. 'ys' => $records[3] ?? 0,
  34. 'ab' => $records[4] ?? 0
  35. ];
  36. $orders = Db::name('orders')
  37. ->where('org_id',$this->orgId)
  38. ->where('del',0)
  39. ->where('create_yyyymmdd',$today)
  40. ->where('order_mode',1)
  41. ->whereNotExists(function ($query) {
  42. $query->table('ai_send_fail')
  43. ->whereColumn('ai_send_fail.order_id', 'orders.id');
  44. })
  45. ->field('work_type_mode')
  46. ->group('work_type_mode')
  47. ->column('COUNT(*)', 'work_type_mode');
  48. $undo = [
  49. 'total' => array_sum($orders),
  50. 'bx' => $orders[1] ?? 0,
  51. 'bj' => $orders[2] ?? 0,
  52. 'ys' => $orders[3] ?? 0,
  53. 'ab' => $orders[4] ?? 0,
  54. ];
  55. $data = [
  56. 'done'=>$done,
  57. 'undo'=>$undo,
  58. ];
  59. return json($data);
  60. }
  61. public function chartData()
  62. {
  63. $type = input('type', 'day', 'trim');
  64. $today = date('Ymd');
  65. // 根据类型设置查询条件
  66. switch ($type) {
  67. case 'month':
  68. $startDate = date('Ymd', strtotime('first day of this month'));
  69. $endDate = $today;
  70. $dateFormat = 'Ymd';
  71. $displayFormat = 'm-d';
  72. $dateRange = $this->getDateRange($startDate, $endDate, $dateFormat);
  73. break;
  74. case 'year':
  75. $startDate = date('Ymd', strtotime('first day of January'));
  76. $endDate = $today;
  77. $displayFormat = 'Y-m';
  78. $dateRange = $this->getMonthRange($startDate, $endDate);
  79. break;
  80. case 'day':
  81. default:
  82. $startDate = date('Ymd', strtotime('-7 days'));
  83. $endDate = $today;
  84. $dateFormat = 'Ymd';
  85. $displayFormat = 'm-d';
  86. $dateRange = $this->getDateRange($startDate, $endDate, $dateFormat);
  87. break;
  88. }
  89. $echart = Db::name('todo')
  90. ->where('org_id', $this->orgId)
  91. ->where('del', 0)
  92. ->where('create_yyyymmdd', '>=', $startDate)
  93. ->where('create_yyyymmdd', '<=', $endDate)
  94. ->field("create_yyyymmdd, SUM(create_user_id = -1) as ai, SUM(create_user_id != -1) as user")
  95. ->group('create_yyyymmdd')
  96. ->order('create_yyyymmdd')
  97. ->select();
  98. $statsByDate = [];
  99. foreach ($echart as $item) {
  100. if ($type == 'year') {
  101. $key = substr($item['create_yyyymmdd'], 0, 6);
  102. } else {
  103. $key = $item['create_yyyymmdd'];
  104. }
  105. if (!isset($statsByDate[$key])) {
  106. $statsByDate[$key] = ['ai' => 0, 'user' => 0];
  107. }
  108. $statsByDate[$key]['ai'] += (int)$item['ai'];
  109. $statsByDate[$key]['user'] += (int)$item['user'];
  110. }
  111. $aiData = [];
  112. $userData = [];
  113. $dates = [];
  114. foreach ($dateRange as $date) {
  115. $key = $type == 'year' ? substr($date, 0, 6) : $date;
  116. $aiData[] = isset($statsByDate[$key]) ? (int)$statsByDate[$key]['ai'] : 0;
  117. $userData[] = isset($statsByDate[$key]) ? (int)$statsByDate[$key]['user'] : 0;
  118. if ($type == 'year') {
  119. $dates[] = date($displayFormat, strtotime($key . '01'));
  120. } else {
  121. $dates[] = date($displayFormat, strtotime($date));
  122. }
  123. }
  124. $data = [
  125. 'ai' => $aiData,
  126. 'user' => $userData,
  127. 'date' => $dates,
  128. 'type' => $type
  129. ];
  130. return json($data);
  131. }
  132. public function countData()
  133. {
  134. $dispatched = Db::name('ai_send_success')
  135. ->where('org_id', $this->orgId)
  136. ->whereTime('create_time', 'today')
  137. ->count();
  138. $error = Db::name('ai_send_fail')
  139. ->where('org_id', $this->orgId)
  140. ->where('status', 0)
  141. ->whereTime('create_time', 'today')
  142. ->count();
  143. $order = Db::name('orders')
  144. ->where('org_id',$this->orgId)
  145. ->where('order_mode',1)
  146. ->where('del',0)
  147. ->whereIn('work_type_mode',[1,2,3,4])
  148. ->where('create_yyyymmdd',date('Ymd'))
  149. ->whereNotExists(function ($query) {
  150. $query->table('ai_send_fail')
  151. ->whereColumn('ai_send_fail.order_id', 'orders.id');
  152. })
  153. ->count();
  154. $data = [
  155. 'today' => $dispatched,
  156. 'error' => $error,
  157. 'wait' => $order
  158. ];
  159. return json($data);
  160. }
  161. public function dispatchedData()
  162. {
  163. $size = input('size',10,'intval');
  164. $page = input('page',1,'intval');
  165. $mode= input('mode',0,'intval');
  166. $query = Db::name('ai_send_success')
  167. ->alias('a')
  168. ->join('orders o','a.order_id = o.id')
  169. ->where('a.org_id', $this->orgId)
  170. ->whereTime('a.create_time', 'today')
  171. ->when($mode > 0,function ($query) use($mode){
  172. $query->where('a.mode', $mode);
  173. })
  174. ->field('a.user_id,a.todo_id,a.mode,a.create_time,o.sn,o.user_id as uid,o.id');
  175. $todoList = $query
  176. ->order('a.create_time desc')
  177. ->limit(($page -1) * $size,$size)
  178. ->select();
  179. $total = $query->removeOption('limit')->count();
  180. $userIds = array_column($todoList,'uid');
  181. $toUserIds = array_column($todoList,'user_id');
  182. $allIds = array_unique(array_merge($toUserIds,$userIds));
  183. $users = Db::name('user')->whereIn('id',$allIds)->column('real_name','id');
  184. foreach ($todoList as &$v){
  185. $v['user'] = $users[$v['uid']]??'';
  186. $v['toUser'] = $users[$v['user_id']]??'';
  187. $v['type'] = self::$types[$v['mode']]??'';
  188. }
  189. unset($v);
  190. return json(['data'=>$todoList,'total'=>$total]);
  191. }
  192. public function unDispatchedData()
  193. {
  194. $size = input('size',10,'intval');
  195. $page = input('page',1,'intval');
  196. $type = input('type','','trim');
  197. if ($type == 'wait'){
  198. $query = Db::name('orders')
  199. ->where('org_id',$this->orgId)
  200. ->where('order_mode',1)
  201. ->where('del',0)
  202. ->whereIn('work_type_mode',[1,2,3,4])
  203. ->where('create_yyyymmdd',date('Ymd'))
  204. ->whereNotExists(function ($q) {
  205. $q->table('ai_send_fail')
  206. ->whereColumn('ai_send_fail.order_id', 'orders.id');
  207. })
  208. ->field('id,sn,work_type_mode,user_id');
  209. }else{
  210. $query = Db::name('ai_send_fail')
  211. ->alias('a')
  212. ->join('orders o','a.order_id = o.id')
  213. ->where('a.org_id', $this->orgId)
  214. ->where('a.status', 0)
  215. ->whereTime('a.create_time', 'today')
  216. ->field('o.id,o.sn,o.work_type_mode,o.user_id');
  217. }
  218. $orderList = $query
  219. ->limit(($page -1) * $size,$size)
  220. ->select();
  221. $total = $query->removeOption('limit')->count();
  222. $users = Db::name('user') ->whereIn('id',array_unique(array_column($orderList,'user_id'))) ->column('real_name','id');
  223. $i = 1;
  224. foreach ($orderList as &$v2){
  225. $v2['user'] = $users[$v2['user_id']]??'';
  226. $v2['type'] = self::$types[$v2['work_type_mode']]??'';
  227. $v2['flag'] = 0;
  228. if ($i <= 20){
  229. $v2['flag'] = 1;
  230. $i ++;
  231. }
  232. }
  233. unset($v2);
  234. return json(['data'=>$orderList,'total'=>$total]);
  235. }
  236. public function staffData()
  237. {
  238. $result = $this->getBatchModeStats([1,2,3,4], $this->orgId);
  239. $list = $result['list'];
  240. $summary = $result['summary'];
  241. $chartData = [];
  242. foreach ($list as $modeId => $data) {
  243. $chartData[] = [
  244. 'value' => $data['active'],
  245. 'total' => $data['total'],
  246. 'name' => $data['mode_name'],
  247. 'itemStyle' => [
  248. 'color' => self::$colorMap[$modeId] ?? '#999'
  249. ]
  250. ];
  251. }
  252. $data = [
  253. 'bxInfo' => $list[1],
  254. 'bjInfo' => $list[2],
  255. 'ysInfo' => $list[3],
  256. 'abInfo' => $list[4],
  257. 'staffData' =>$chartData,
  258. 'staffSummary' =>$summary
  259. ];
  260. return json($data);
  261. }
  262. public function orderData()
  263. {
  264. $type = input('type', 'day', 'trim');
  265. $today = date('Ymd');
  266. // 根据类型设置查询条件
  267. switch ($type) {
  268. case 'month':
  269. $startDate = date('Ymd', strtotime('first day of this month'));
  270. $endDate = $today;
  271. break;
  272. case 'year':
  273. $startDate = date('Ymd', strtotime('first day of January'));
  274. $endDate = $today;
  275. break;
  276. case 'day':
  277. default:
  278. $startDate = date('Ymd', strtotime('-7 days'));
  279. $endDate = $today;
  280. break;
  281. }
  282. $modeMap = self::$types;
  283. $modeIds = array_keys($modeMap);
  284. // 查询数据,按mode、来源分组统计总数
  285. $echart = Db::name('todo')
  286. ->where('org_id', $this->orgId)
  287. ->where('del', 0)
  288. ->where('create_yyyymmdd', '>=', $startDate)
  289. ->where('create_yyyymmdd', '<=', $endDate)
  290. ->whereIn('work_type_mode', $modeIds)
  291. ->field("work_type_mode, SUM(create_user_id = -1) as ai, SUM(create_user_id != -1) as user")
  292. ->group('work_type_mode')
  293. ->order('work_type_mode')
  294. ->select();
  295. // 将查询结果转为以mode为key的数组
  296. $statsByMode = [];
  297. foreach ($echart as $item) {
  298. $statsByMode[$item['work_type_mode']] = [
  299. 'ai' => (int)$item['ai'],
  300. 'user' => (int)$item['user']
  301. ];
  302. }
  303. // 按mode顺序构建一维数组
  304. $aiData = [];
  305. $userData = [];
  306. $modeNames = [];
  307. foreach ($modeMap as $modeId => $modeName) {
  308. $aiData[] = isset($statsByMode[$modeId]) ? $statsByMode[$modeId]['ai'] : 0;
  309. $userData[] = isset($statsByMode[$modeId]) ? $statsByMode[$modeId]['user'] : 0;
  310. $modeNames[] = $modeName;
  311. }
  312. $data = [
  313. 'ai' => $aiData,
  314. 'user' => $userData,
  315. 'modes' => $modeNames,
  316. ];
  317. return json($data);
  318. }
  319. public function userData()
  320. {
  321. $size = input('size',7,'intval');
  322. $page = input('page',1,'intval');
  323. $start = ($page - 1) * $size;
  324. $mode= input('mode',0,'intval');
  325. $roleData = $mode > 0 ? $this->getRoleIdsByModeIds([$mode],$this->orgId) : $this->getRoleIdsByModeIds([1,2,3,4],$this->orgId);
  326. $map[] = ['u.del','=',0];
  327. $map[] = ['u.enable','=',1];
  328. $map[] = ['u.type','=',0];
  329. $map[] = ['ur.roles_id','in',$roleData['all_role_ids']];
  330. $query = Db::name('user')
  331. ->field('u.id,u.real_name,u.work,ur.roles_id')
  332. ->alias('u')
  333. ->join('user_roles ur','u.id=ur.user_id')
  334. ->where($map);
  335. $lists = $query->select();
  336. $total = count($lists);
  337. $roleIds = array_unique(array_column($lists, 'roles_id'));
  338. $roleNames = Db::name('roles')
  339. ->where('id', 'in', $roleIds)
  340. ->column('name', 'id');
  341. $userIds = array_column($lists, 'id');
  342. $todoCounts = Db::name('todo')
  343. ->field("CONCAT(to_user_id, '_', todo_mode) as mode_key, COUNT(*) as count")
  344. ->where('del', 0)
  345. ->whereIn('to_user_id', $userIds)
  346. ->where('todo_mode', 'in', [1, 2])
  347. ->group('to_user_id, todo_mode')
  348. ->select();
  349. $todoCountMap = [];
  350. foreach ($todoCounts as $item) {
  351. $todoCountMap[$item['mode_key']] = (int)$item['count'];
  352. }
  353. $avgTimes = Db::name('todo')
  354. ->field('to_user_id, AVG(xy_time) as receive, AVG(wc_time) as finish')
  355. ->where('del', 0)
  356. ->whereIn('to_user_id', $userIds)
  357. // ->where('todo_mode', 1)
  358. ->group('to_user_id')
  359. ->select();
  360. $avgTimeMap = [];
  361. foreach ($avgTimes as $item) {
  362. $avgTimeMap[$item['to_user_id']] = $item;
  363. }
  364. foreach ($lists as &$list) {
  365. $list['role_name'] = $roleNames[$list['roles_id']] ?? '未知角色';
  366. $list['work'] = (int)$list['work'];
  367. $list['doing'] = $todoCountMap[$list['id'] . '_2'] ?? 0;
  368. $list['wait'] = $todoCountMap[$list['id'] . '_1'] ?? 0;
  369. $avgData = $avgTimeMap[$list['id']] ?? null;
  370. $list['receive'] = $avgData ? $this->formatDuration($avgData['receive']) : '--';
  371. $list['finish'] = $avgData ? $this->formatDuration($avgData['finish']) : '--';
  372. }
  373. unset($list);
  374. usort($lists, function($a, $b) {
  375. if ($a['doing'] != $b['doing']) {
  376. return $b['doing'] - $a['doing'];
  377. }
  378. return $b['wait'] - $a['wait'];
  379. });
  380. $lists = array_slice($lists, $start, $size);
  381. return json(['data'=>$lists,'total'=>$total]);
  382. }
  383. /**
  384. * 获取日期范围(按天)
  385. */
  386. private function getDateRange($startDate, $endDate, $format = 'Ymd')
  387. {
  388. $dates = [];
  389. $current = strtotime($startDate);
  390. $end = strtotime($endDate);
  391. while ($current <= $end) {
  392. $dates[] = date($format, $current);
  393. $current = strtotime('+1 day', $current);
  394. }
  395. return $dates;
  396. }
  397. /**
  398. * 获取月份范围(按月)
  399. */
  400. private function getMonthRange($startDate, $endDate)
  401. {
  402. $dates = [];
  403. $current = strtotime($startDate);
  404. $end = strtotime($endDate);
  405. // 调整到月初
  406. $current = strtotime(date('Y-m-01', $current));
  407. while ($current <= $end) {
  408. $dates[] = date('Ymd', $current);
  409. $current = strtotime('+1 month', $current);
  410. }
  411. return $dates;
  412. }
  413. /**
  414. * 批量统计多个工作类型模式的用户数量(包含汇总)
  415. * @param array $modeIds 工作类型模式ID列表
  416. * @param int $orgId 组织ID
  417. * @return array
  418. */
  419. public function getBatchModeStats($modeIds, $orgId)
  420. {
  421. if (empty($modeIds)) {
  422. return [
  423. 'list' => [],
  424. 'summary' => [
  425. 'total' => 0,
  426. 'active' => 0,
  427. 'offline' => 0,
  428. 'active_rate' => 0,
  429. ]
  430. ];
  431. }
  432. // 1. 获取模式名称映射
  433. $modeNames = [];
  434. $modeConfigs = Db::name('work_type_mode')
  435. ->where('id', 'in', $modeIds)
  436. ->field('id, name')
  437. ->select();
  438. foreach ($modeConfigs as $config) {
  439. $modeNames[$config['id']] = $config['name'];
  440. }
  441. // 2. 获取所有角色ID和模式角色映射(使用优化后的方法)
  442. $roleData = $this->getRoleIdsByModeIds($modeIds, $orgId);
  443. $allRoleIds = $roleData['all_role_ids'];
  444. $modeRolesMap = $roleData['mode_roles_map'];
  445. // 如果没有角色,返回空数据
  446. $emptyResult = [
  447. 'list' => [],
  448. 'summary' => [
  449. 'total' => 0,
  450. 'active' => 0,
  451. 'offline' => 0,
  452. 'active_rate' => 0,
  453. ]
  454. ];
  455. if (empty($allRoleIds)) {
  456. foreach ($modeIds as $modeId) {
  457. $emptyResult['list'][$modeId] = [
  458. 'mode_id' => $modeId,
  459. 'mode_name' => $modeNames[$modeId] ?? '未知模式',
  460. 'total' => 0,
  461. 'active' => 0,
  462. 'offline' => 0,
  463. 'active_rate' => 0,
  464. ];
  465. }
  466. return $emptyResult;
  467. }
  468. // 3. 一次性查询所有用户
  469. $users = Db::name('user_roles')
  470. ->alias('ur')
  471. ->join('user u', 'u.id = ur.user_id')
  472. ->where('u.del', '=', 0)
  473. ->where('u.enable', '=', 1)
  474. ->where('ur.roles_id', 'in', $allRoleIds)
  475. ->field('u.id, u.work, ur.roles_id')
  476. ->select();
  477. // 4. 构建角色用户映射
  478. $roleUserMap = [];
  479. foreach ($users as $user) {
  480. $roleId = $user['roles_id'];
  481. $userId = $user['id'];
  482. $work = $user['work'];
  483. if (!isset($roleUserMap[$roleId])) {
  484. $roleUserMap[$roleId] = [
  485. 'total' => [],
  486. 'active' => [],
  487. ];
  488. }
  489. $roleUserMap[$roleId]['total'][$userId] = true;
  490. if ($work == 1) {
  491. $roleUserMap[$roleId]['active'][$userId] = true;
  492. }
  493. }
  494. // 5. 计算每个模式的统计数据(直接使用 $modeRolesMap)
  495. $result = [];
  496. foreach ($modeIds as $modeId) {
  497. $roleIds = $modeRolesMap[$modeId] ?? [];
  498. $totalUsers = [];
  499. $activeUsers = [];
  500. foreach ($roleIds as $roleId) {
  501. if (isset($roleUserMap[$roleId])) {
  502. $totalUsers += $roleUserMap[$roleId]['total'];
  503. $activeUsers += $roleUserMap[$roleId]['active'];
  504. }
  505. }
  506. $total = count($totalUsers);
  507. $active = count($activeUsers);
  508. $result[$modeId] = [
  509. 'mode_id' => $modeId,
  510. 'mode_name' => $modeNames[$modeId] ?? '未知模式',
  511. 'total' => $total,
  512. 'active' => $active,
  513. 'offline' => $total - $active,
  514. 'active_rate' => $total > 0 ? round(($active / $total) * 100, 2) : 0,
  515. ];
  516. }
  517. // 6. 计算汇总数据
  518. $summaryTotal = count($users);
  519. $summaryActive = count(array_filter($users, function($user) {
  520. return $user['work'] == 1;
  521. }));
  522. return [
  523. 'list' => $result,
  524. 'summary' => [
  525. 'total' => $summaryTotal,
  526. 'active' => $summaryActive,
  527. 'offline' => $summaryTotal - $summaryActive,
  528. 'active_rate' => $summaryTotal > 0 ? round(($summaryActive / $summaryTotal) * 100, 2) : 0,
  529. ]
  530. ];
  531. }
  532. /**
  533. * 根据模式ID列表获取所有关联的角色ID(包含每个模式的角色映射)
  534. * @param array $modeIds 模式ID列表
  535. * @param int $orgId 组织ID
  536. * @return array 返回 [
  537. * 'all_role_ids' => 所有角色ID(已去重),
  538. * 'mode_roles_map' => [模式ID => 角色ID列表]
  539. * ]
  540. */
  541. public function getRoleIdsByModeIds($modeIds, $orgId)
  542. {
  543. if (empty($modeIds)) {
  544. return [
  545. 'all_role_ids' => [],
  546. 'mode_roles_map' => []
  547. ];
  548. }
  549. $allRoleIds = [];
  550. $modeRolesMap = [];
  551. // 1. 获取模式配置
  552. $modeConfigs = Db::name('work_type_mode')
  553. ->where('id', 'in', $modeIds)
  554. ->field('id, roles')
  555. ->select();
  556. // 2. 获取组织配置
  557. $orgConfigs = Db::name('work_type_mode_org')
  558. ->where('org_id', $orgId)
  559. ->where('value', 'in', $modeIds)
  560. ->select();
  561. $orgConfigMap = [];
  562. foreach ($orgConfigs as $config) {
  563. $orgConfigMap[$config['value']] = $config;
  564. }
  565. // 3. 遍历模式配置,收集所有角色ID
  566. foreach ($modeConfigs as $config) {
  567. $modeId = $config['id'];
  568. // 确定角色ID列表(优先使用组织配置)
  569. $roleIdsStr = '';
  570. if (isset($orgConfigMap[$modeId])) {
  571. $orgConfig = $orgConfigMap[$modeId];
  572. $roleIdsStr = !empty($orgConfig['org_roles'])
  573. ? $orgConfig['org_roles']
  574. : ($orgConfig['roles'] ?? '');
  575. } else {
  576. $roleIdsStr = $config['roles'] ?? '';
  577. }
  578. // 解析角色ID
  579. $roleIds = $roleIdsStr ? array_filter(array_map('trim', explode(',', $roleIdsStr))) : [];
  580. if (empty($roleIds)) {
  581. $modeRolesMap[$modeId] = [];
  582. continue;
  583. }
  584. // 获取每个角色的所有子角色
  585. $allChildrenIds = [];
  586. foreach ($roleIds as $roleId) {
  587. $children = model('Roles')->getChildrenIds($roleId, $orgId) ?? [];
  588. $allChildrenIds = array_merge($allChildrenIds, $children, [$roleId]);
  589. }
  590. $allChildrenIds = array_unique($allChildrenIds);
  591. $modeRolesMap[$modeId] = $allChildrenIds;
  592. $allRoleIds = array_merge($allRoleIds, $allChildrenIds);
  593. }
  594. // 去重返回
  595. return [
  596. 'all_role_ids' => array_unique($allRoleIds),
  597. 'mode_roles_map' => $modeRolesMap
  598. ];
  599. }
  600. /**
  601. * 格式化时长(秒数转为 x时x分x秒 格式)
  602. * @param float|int|null $seconds 秒数
  603. * @return string 格式化后的时长,如 "1h2min30s" 或 "--"
  604. */
  605. private function formatDuration($seconds)
  606. {
  607. if (empty($seconds) || $seconds <= 0) {
  608. return '--';
  609. }
  610. $seconds = (int)round($seconds);
  611. $hours = floor($seconds / 3600);
  612. $minutes = floor(($seconds % 3600) / 60);
  613. $remainingSeconds = $seconds % 60;
  614. $parts = [];
  615. if ($hours > 0) {
  616. $parts[] = $hours . 'h';
  617. }
  618. if ($minutes > 0) {
  619. $parts[] = $minutes . 'min';
  620. }
  621. if ($remainingSeconds > 0 || empty($parts)) {
  622. $parts[] = $remainingSeconds . 's';
  623. }
  624. return implode('', $parts);
  625. }
  626. }