model = MenuModel::class; $this->selectField = [ 'id', 'pid', 'title', 'icon', 'name', 'affix_tab', 'path', 'component', 'redirect', 'keep_alive', 'hide_in_menu', 'badge', 'badge_type', 'badge_variants', 'iframe_src', 'sort', 'query', 'created_at' ]; $this->queryField = [ 'title' => 'like', 'pid' => '=' ]; $this->optionField = ['id', 'title']; $this->orderBy = [ 'name' => 'sort', 'sort' => 'asc' ]; } /** * 获取菜单列表(扁平树,前端 transform) * 有 title 搜索时:命中节点 + 祖先 + 子孙,保证子菜单可搜到并完整展示路径 * 无搜索时:仍按根菜单分页,再挂两级子节点 * * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface */ public function list() { $title = trim((string) request()->get('title', '')); if ($title !== '') { return $this->listByTitleSearch($title); } $this->where[] = ['pid', '=', 0]; $list = $this->getPageList(); $ids = array_column($list['items'], 'id'); if (empty($ids)) { return $list; } $items = $this->model::where('deleted_at', 0)->whereIn('pid', $ids)->orderBy('sort')->get($this->selectField); $childrenIds = []; foreach ($items as $v) { $list['items'][] = $v; $childrenIds[] = $v['id']; } if (!empty($childrenIds)) { $items = $this->model::where('deleted_at', 0)->whereIn('pid', $childrenIds)->orderBy('sort')->get($this->selectField); foreach ($items as $v) { $list['items'][] = $v; } } return $list; } /** * 按标题模糊搜索:返回命中项及其祖先、子孙,供树表正确拼装 * 为什么要带祖先:仅返回子节点时 pid 找不到父级,树会丢节点或挂到根外 */ private function listByTitleSearch(string $title): array { $all = $this->model::where('deleted_at', 0) ->orderBy('sort') ->get($this->selectField) ->keyBy('id'); if ($all->isEmpty()) { return $this->emptyPageList(); } $matchedIds = []; foreach ($all as $id => $row) { $rowTitle = (string) ($row['title'] ?? ''); if ($rowTitle !== '' && mb_stripos($rowTitle, $title) !== false) { $matchedIds[] = (int) $id; } } if (empty($matchedIds)) { return $this->emptyPageList(); } // 子节点按 pid 分组,便于向下展开子孙 $childrenMap = []; foreach ($all as $id => $row) { $pid = (int) ($row['pid'] ?? 0); $childrenMap[$pid][] = (int) $id; } $needIds = []; foreach ($matchedIds as $mid) { // 向上:自身 + 祖先 $cur = $mid; while ($cur > 0 && $all->has($cur) && !isset($needIds[$cur])) { $needIds[$cur] = true; $cur = (int) ($all[$cur]['pid'] ?? 0); } // 向下:命中节点的全部子孙(搜父级时仍能看到子树) $stack = [$mid]; while (!empty($stack)) { $nodeId = array_pop($stack); foreach ($childrenMap[$nodeId] ?? [] as $cid) { if (!isset($needIds[$cid])) { $needIds[$cid] = true; $stack[] = $cid; } } } } $items = []; foreach ($all as $id => $row) { if (isset($needIds[(int) $id])) { $items[] = $row->toArray(); } } return [ 'items' => $items, 'page' => 1, 'size' => count($items), 'total' => count($items), ]; } /** * 空分页结构(与 getPageList 对齐) */ private function emptyPageList(): array { return [ 'items' => [], 'page' => 1, 'size' => 0, 'total' => 0, ]; } /** * 获取树形结构的菜单下拉 * @param bool $isSelect * @return array */ public function getTreeOption(bool $isSelect = false): array { $this->optionField = ['id', 'title', 'icon', 'pid']; $list = $this->model::get($this->optionField); $result = $this->utils->tree($list); if ($isSelect == 'true') { array_unshift($result, [ 'id' => 0, 'title' => '根目录', 'pid' => 0, 'icon' => '' ]); } return $result; } /** * 获取下拉列表 * @return mixed * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface * @throws Exception */ public function option(): mixed { $option = $this->getOption(); if (request()->get('is_select', false)) { $result = [ [ 'id' => 0, 'title' => '根目录' ] ]; foreach ($option as $item) { $result[] = [ 'id' => $item['id'], 'title' => $item['title'] ]; } return $result; } return $option; } /** * 获取数据详情 * @param $id * @return mixed * @throws Exception */ public function detail($id): mixed { return $this->getDetail($id); } /** * 新增数据 * @param $params * @return mixed */ public function create($params): mixed { $params['badge_type'] = !empty($params['badge_type']) ? $params['badge_type']: 0; $params['badge_variants'] = !empty($params['badge_variants']) ? $params['badge_variants']: 0; $params['created_at'] = time(); return $this->insert($params); } /** * 编辑数据 * @param $id * @param $params * @return mixed */ public function update($id, $params): mixed { $params['badge_type'] = !empty($params['badge_type']) ? $params['badge_type']: 0; $params['badge_variants'] = !empty($params['badge_variants']) ? $params['badge_variants']: 0; $params['updated_at'] = time(); return $this->save($id, $params); } /** * 删除数据 * @param $ids * @return mixed */ public function delete($ids): mixed { return $this->del($ids); } }