Files
nl-admin-api/app/Service/MenuService.php
2026-08-10 15:51:00 +08:00

234 lines
6.8 KiB
PHP
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Service;
use App\BaseApp\BaseService;
use App\Models\MenuModel;
use Exception;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
/**
* 菜单服务:列表树、搜索(含子菜单)、下拉
*/
class MenuService extends BaseService
{
public function __construct()
{
parent::__construct();
$this->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);
}
}