[标题, 图标, 权重] */ const GROUPS = [ 'console' => ['数据看板', 'fa fa-dashboard', 100], 'fc/content' => ['信息管理', 'fa fa-file-text-o', 90], 'fc/user' => ['商家会员', 'fa fa-users', 80], 'fc/trade' => ['交易财务', 'fa fa-shopping-cart', 70], 'fc/marketing' => ['运营推广', 'fa fa-share-alt', 60], 'fc/auth' => ['权限配置', 'fa fa-group', 50], 'fc/system' => ['系统设置', 'fa fa-cogs', 40], ]; /** 功能菜单:name => [一级菜单, 标题, 图标, 权重] */ const ITEMS = [ // 数据看板 'fc/dashboard' => ['console', '业务总览', 'fa fa-dashboard', 98], // 信息管理:信息 + 分类 + 审核 'fc/postaudit' => ['fc/content', '信息审核', 'fa fa-check-square-o', 96], 'fc/post' => ['fc/content', '信息列表', 'fa fa-file-text', 95], 'fc/category' => ['fc/content', '分类管理', 'fa fa-list', 90], 'fc/draft' => ['fc/content', '草稿管理', 'fa fa-folder-o', 85], 'fc/comment' => ['fc/content', '评价管理', 'fa fa-star', 80], 'fc/report' => ['fc/content', '举报处理', 'fa fa-flag', 75], // 商家会员 'fc/merchant' => ['fc/user', '商家管理', 'fa fa-building', 95], 'fc/userext' => ['fc/user', '用户管理', 'fa fa-user-circle', 90], 'fc/message' => ['fc/user', '消息管理', 'fa fa-bell', 85], // 交易财务 'fc/order' => ['fc/trade', '订单管理', 'fa fa-file-text-o', 95], 'fc/financeflow' => ['fc/trade', '资金流水', 'fa fa-money', 90], 'fc/payview' => ['fc/trade', '付费查看', 'fa fa-eye', 85], 'fc/commission' => ['fc/trade', '佣金明细', 'fa fa-cny', 80], 'fc/withdraw' => ['fc/trade', '提现审核', 'fa fa-credit-card', 75], // 运营推广 'fc/banner' => ['fc/marketing', '轮播管理', 'fa fa-image', 95], 'fc/plan' => ['fc/marketing', '会员套餐', 'fa fa-diamond', 90], 'fc/coupon' => ['fc/marketing', '优惠券', 'fa fa-ticket', 85], 'fc/distrelation' => ['fc/marketing', '分销关系', 'fa fa-sitemap', 80], // 权限配置 'auth/admin' => ['fc/auth', '管理员', 'fa fa-user', 95], 'auth/group' => ['fc/auth', '角色管理', 'fa fa-group', 90], 'auth/rule' => ['fc/auth', '菜单规则', 'fa fa-bars', 85], 'auth/adminlog' => ['fc/auth', '操作日志', 'fa fa-list-alt', 80], // 系统设置 'general/config' => ['fc/system', '系统配置', 'fa fa-cog', 99], 'dashboard' => ['fc/system', '平台概况', 'fa fa-dashboard', 95], 'fc/area' => ['fc/system', '服务区域', 'fa fa-map-o', 90], 'general/attachment' => ['fc/system', '附件管理', 'fa fa-file-image-o', 80], 'general/profile' => ['fc/system', '个人资料', 'fa fa-user', 70], 'addon' => ['fc/system', '插件管理', 'fa fa-rocket', 60], ]; /** 已并入系统配置页、不再单独显示的历史菜单 */ const RETIRED = ['fc/setting', 'fc/publish/index']; /** 权限节点:name => [上级菜单, 标题] */ const NODES = [ 'fc/dashboard/index' => ['fc/dashboard', '查看'], 'fc/post/audit' => ['fc/post', '审核'], 'fc/post/top' => ['fc/post', '置顶'], 'fc/post/offline' => ['fc/post', '下架'], 'fc/post/online' => ['fc/post', '上架'], 'fc/coupon/index' => ['fc/coupon', '查看'], 'fc/coupon/add' => ['fc/coupon', '添加'], 'fc/coupon/edit' => ['fc/coupon', '编辑'], 'fc/coupon/del' => ['fc/coupon', '删除'], 'fc/coupon/multi' => ['fc/coupon', '批量更新'], ]; /** 需要整体下线的 FastAdmin 内置菜单(公众号模块 / 框架会员模块) */ const OFFLINE = [ 'wechat' => '公众号模块与本项目小程序无关,下线', 'user' => '框架会员模块与「商家会员 → 用户管理」重复,下线', ]; /** 下线菜单统一改为 4 字中文,避免规则列表里出现英文/超长标题 */ const OFFLINE_TITLES = [ 'user' => '会员管理', 'user/user' => '会员管理', 'user/group' => '会员分组', 'user/rule' => '会员规则', 'wechat' => '微信管理', 'wechat/autoreply' => '自动回复', 'wechat/response' => '资源管理', 'wechat/config' => '配置管理', 'wechat/menu' => '菜单管理', ]; /** 需要清理的演示菜单 */ const DEMO = ['category']; protected function configure() { $this->setName('fc:menu') ->setDescription('重建同城业务后台菜单结构(幂等)'); } protected function execute(Input $input, Output $output) { self::apply($output); } /** * 应用目标菜单树 * * @param Output|null $output * @return void */ public static function apply(Output $output = null) { $now = time(); $table = Db::name('auth_rule'); // 0) 旧分组更名:分销管理 → 运营推广,避免按 name 定位时新建一个空分组 if (!$table->where('name', 'fc/marketing')->find()) { $old = $table->where('name', 'fc/dist')->find(); if ($old) { $table->where('id', $old['id'])->update([ 'name' => 'fc/marketing', 'updatetime' => $now, ]); } } // 1) 一级菜单 $groupIds = []; foreach (self::GROUPS as $name => $item) { $groupIds[$name] = self::put($table, $name, [ 'type' => 'menu', 'pid' => 0, 'title' => $item[0], 'icon' => $item[1], 'ismenu' => 1, 'weigh' => $item[2], ], $now); } // 2) 功能菜单 $itemIds = []; foreach (self::ITEMS as $name => $item) { $parent = $groupIds[$item[0]] ?? ($groupIds['fc/system'] ?? 0); $itemIds[$name] = self::put($table, $name, [ 'type' => 'file', 'pid' => $parent, 'title' => $item[1], 'icon' => $item[2], 'ismenu' => 1, 'weigh' => $item[3], ], $now); } // 3) 权限节点(只补缺,不改业务已用的标题) $nodeCount = 0; foreach (self::NODES as $name => $node) { $parent = $itemIds[$node[0]] ?? ($groupIds[$node[0]] ?? 0); if (!$parent) { continue; } self::put($table, $name, [ 'type' => 'file', 'pid' => $parent, 'title' => $node[1], 'icon' => 'fa fa-circle-o', 'ismenu' => 0, 'weigh' => 0, ], $now); $nodeCount++; } // 4) 菜单页说明(build_heading 读取 auth_rule.remark,而不是控制器注释) $table->where('name', 'auth/rule')->update([ 'remark' => '左侧菜单与权限节点都在这里维护;修改后左侧导航自动刷新,删除菜单会连同其子级一起删除', ]); // 5) 演示菜单清理 $removed = 0; foreach (self::DEMO as $name) { $row = $table->where('name', $name)->find(); if (!$row) { continue; } $ids = array_merge([(int)$row['id']], $table->where('pid', $row['id'])->column('id')); $removed += $table->where('id', 'in', $ids)->delete(); } // 6) 下线框架/公众号菜单(父级隐藏后,子级不会再出现在侧边栏) $offline = 0; foreach (array_keys(self::OFFLINE) as $name) { $row = $table->where('name', $name)->find(); if (!$row) { continue; } $table->where('id', $row['id'])->update(['status' => 'hidden', 'updatetime' => $now]); $table->where('pid', $row['id'])->update(['status' => 'hidden', 'updatetime' => $now]); $offline++; } foreach (self::OFFLINE_TITLES as $name => $title) { $table->where('name', $name)->update(['title' => $title]); } // 6.1) 已并入系统配置页的历史菜单,不再单独显示 foreach (self::RETIRED as $name) { $table->where('name', $name)->update(['status' => 'hidden', 'updatetime' => $now]); } // 7) 清理已搬空的历史分组(同城业务 / 基础数据 / 常规管理 / 权限管理) $emptyRemoved = 0; foreach (['fc', 'fc/base', 'general', 'auth'] as $name) { $row = $table->where('name', $name)->find(); if ($row && !$table->where('pid', $row['id'])->count()) { $table->where('id', $row['id'])->delete(); $emptyRemoved++; } } // 8) 菜单缓存 try { Cache::rm('__menu__'); } catch (\Exception $e) { // 缓存清理失败不影响菜单数据,过期后会自动重建 } if ($output) { // 菜单命名规范:不超过 5 个汉字 $tooLong = []; foreach (self::GROUPS as $group) { if (mb_strlen($group[0], 'UTF-8') > 5) { $tooLong[] = $group[0]; } } foreach (self::ITEMS as $item) { if (mb_strlen($item[1], 'UTF-8') > 5) { $tooLong[] = $item[1]; } } $output->writeln(' - 一级菜单 ' . count(self::GROUPS) . ' / 功能菜单 ' . count(self::ITEMS)); $output->writeln(' - 权限节点 ' . $nodeCount . ' 个;清理演示菜单 ' . $removed . ' 条;下线框架菜单 ' . $offline . ' 组;移除空分组 ' . $emptyRemoved . ' 个'); if ($tooLong) { $output->writeln(' - 警告:以下菜单超过 5 个汉字:' . implode('、', $tooLong)); } $output->writeln(' - 已清理后台菜单缓存 __menu__'); } } /** * 按 name 写入一条规则:存在则更新,不存在则插入 * * @param \think\db\Query $table * @param string $name * @param array $data * @param int $now * @return int */ protected static function put($table, $name, array $data, $now) { $data = array_merge([ 'type' => 'file', 'pid' => 0, 'title' => $name, 'icon' => 'fa fa-circle-o', 'url' => '', 'ismenu' => 1, 'menutype' => 'addtabs', 'weigh' => 0, 'status' => 'normal', ], $data); $data['updatetime'] = $now; $row = $table->where('name', $name)->find(); if ($row) { $table->where('id', $row['id'])->update($data); return (int)$row['id']; } $data['name'] = $name; $data['createtime'] = $now; return (int)$table->insertGetId($data); } }