1. 新增会员权益配置控制器 Memberbenefit 和 Publish,拆分运营配置模块 2. 新增套餐表字段 free_view_quota、post_discount,支持按套餐配置免费看和发帖折扣 3. 优化前端页面,新增套餐编辑表单、区域选择器、发布配置交互 4. 新增调试支持,支持 mock openid、本地 IP 绕过权限,便于自动化测试 5. 新增数据库工具脚本,用于检查配置、修复数据、验证会员功能 6. 优化会员接口,返回套餐维度的权益信息,支持无限次免费看场景
319 行
13 KiB
PHP
319 行
13 KiB
PHP
<?php
|
||
|
||
namespace app\admin\command;
|
||
|
||
use think\Cache;
|
||
use think\console\Command;
|
||
use think\console\Input;
|
||
use think\console\Output;
|
||
use think\Db;
|
||
|
||
/**
|
||
* 同城业务后台菜单结构重建(幂等)
|
||
*
|
||
* 分类原则:按运营岗位职责划分,而不是按控制器目录。
|
||
* - 数据看板:业务总览
|
||
* - 信息管理:信息列表、信息分类、草稿、评价、举报(内容审核闭环)
|
||
* - 商家会员:商家、用户、消息
|
||
* - 交易财务:订单、资金流水、付费查看、佣金、提现
|
||
* - 运营推广:轮播、套餐、优惠券、分销关系
|
||
* - 权限配置:管理员、角色、菜单规则、操作日志
|
||
* - 系统设置:平台概况、系统配置(含小程序/发布/分销分组)、服务区域、附件、个人资料、插件
|
||
*
|
||
* 命名规范:菜单文字不超过 5 个汉字,尽量统一为 4 个字。
|
||
*
|
||
* 用法:
|
||
* php think fc:menu
|
||
*
|
||
* 说明:
|
||
* - 以 name 为唯一键 upsert,重复执行不会产生重复菜单;
|
||
* - 只调整「菜单/规则」的归属、标题、图标、排序、显隐,不改任何业务数据;
|
||
* - 清理 FastAdmin 演示菜单与已下线的公众号菜单;
|
||
* - 执行后清理 __menu__ 缓存,左侧菜单立即生效。
|
||
*/
|
||
class FcMenu extends Command
|
||
{
|
||
/** 一级菜单:name => [标题, 图标, 权重] */
|
||
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/publish' => ['fc/marketing', '运营配置', 'fa fa-sliders', 88],
|
||
'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/memberbenefit'];
|
||
|
||
/** 权限节点:name => [上级菜单, 标题] */
|
||
const NODES = [
|
||
'fc/dashboard/index' => ['fc/dashboard', '查看'],
|
||
'fc/publish/index' => ['fc/publish', '查看'],
|
||
'fc/plan/savebenefit' => ['fc/plan', '保存特权'],
|
||
'fc/distrelation/saveconfig' => ['fc/distrelation', '保存配置'],
|
||
'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);
|
||
}
|
||
}
|