🍔 插件菜单

ThinkAdmin 提供完善的插件菜单配置机制,支持自动生成菜单数据,简化插件集成流程。

📚 基础概念

🤔 基本介绍

插件菜单是插件在后台显示的菜单项,用于展示插件的功能入口。

简单理解:就像插件的"导航栏",告诉用户这个插件有哪些功能,点击菜单可以访问对应的功能页面。

实际例子

// 插件菜单定义
public static function menu(): array
{
    return [
        [
            'name' => '账号管理',
            'subs' => [
                ['name' => '账号列表', 'node' => 'plugin-account/index'],
                ['name' => '账号配置', 'node' => 'plugin-account/config'],
            ],
        ],
    ];
}

🤔 使用优势

问题1:插件功能无法访问

不使用插件菜单时,用户无法找到插件的功能入口:

// ❌ 不使用插件菜单:用户不知道插件有哪些功能
// 需要手动输入 URL 访问:/admin/plugin-account/index
// 用户体验差,容易忘记功能入口

问题2:菜单管理困难

不使用插件菜单时,需要手动在后台添加菜单:

// ❌ 需要手动在后台添加菜单
// 系统管理 → 菜单管理 → 添加菜单
// 每个插件都要手动添加,工作量大

使用插件菜单的优势

// ✅ 使用插件菜单:自动生成菜单
// 1. 在 Service 类中定义菜单
// 2. 插件安装时自动写入菜单
// 3. 用户可以直接看到插件的功能入口

🎯 核心功能

功能1:自动生成菜单

插件安装时,自动生成菜单数据:

// 插件安装时,自动调用 menu() 方法
// 自动写入 system_menu 数据表
// 用户可以直接看到插件的菜单

功能2:权限控制

支持菜单项的权限控制:

// 菜单项关联权限节点
['name' => '账号列表', 'node' => 'plugin-account/index']
// 只有拥有该权限的用户才能看到菜单

功能3:动态生成

菜单由插件服务类动态生成:

// 使用 appCode 动态获取插件编码
$code = app(static::class)->appCode;
['node' => "{$code}/config/index"]
// 避免硬编码,提高灵活性

🚀 主要功能

  • 自动生成: 插件能够通过配置自动生成菜单数据
  • 标准定义: 提供标准的菜单定义方式
  • 无缝集成: 菜单数据自动集成到系统菜单体系
  • 权限控制: 支持菜单项的权限控制
  • 灵活配置: 支持自定义菜单显示方式和属性
  • 统一管理: 通过插件中心统一管理所有插件菜单

📋 菜单类型

ThinkAdmin 支持两种菜单类型,各有特点:

1. 全局菜单

写入 system_menu 数据表的菜单,显示在系统主菜单中。

特点

  • 存储方式: 写入 system_menu 数据表
  • 管理工具: 使用 Phinx 数据库迁移工具
  • 灵活管理: 可以方便地创建、修改或删除菜单结构
  • 数据持久: 菜单数据持久化存储

使用场景

  • 系统核心功能菜单
  • 需要持久化存储的菜单
  • 需要手动管理的菜单

2. 插件专属菜单

由插件服务注册类动态生成的菜单,显示在插件专属空间。

特点

  • 显示位置: 插件专属空间左侧菜单
  • 动态生成: 由插件服务注册类动态生成
  • 功能模块: 清晰反映插件的功能模块
  • 返回入口: 提供返回插件中心的入口

使用场景

  • 插件功能菜单
  • 需要动态生成的菜单
  • 插件专属功能入口

⚙️ 配置方式

Service 类配置

在 Service 类中实现 menu() 方法,返回菜单配置数组。

方法要求

  • 方法实现: 在 Service 类中实现 menu 方法
  • 配置返回: 返回包含菜单项信息的数组或对象
  • 结构描述: 描述插件的菜单结构、权限要求等
  • 自动调用: 插件安装时系统自动调用

菜单配置选项

支持多种菜单配置选项,满足不同需求。

配置选项

  • 显示方式: 自定义菜单项的显示方式
  • 权限控制: 设置菜单项的权限要求
  • 业务需求: 适应不同的应用场景和业务需求
  • 灵活扩展: 支持菜单配置的灵活扩展

菜单分为两种类型

为了提高插件的集成性和易用性,系统优化了全局菜单的写入方式,并改进了插件专属空间的菜单管理。

首先,系统实现了全局菜单的数据库写入功能。通过将菜单数据写入 system_menu 数据表,我们可以更加灵活地管理整个应用的菜单结构。为了实现这一功能,我们借助了强大的数据库迁移工具 Phinx。通过 Phinx 我们可以方便地创建、修改或删除数据表结构,确保菜单数据的正确存储和访问。 其次,系统改进了插件专属空间的菜单管理。当通过插件中心进入插件专属空间时,左侧将展示当前插件的专属菜单。这些菜单项将清晰地反映出插件的功能模块,并提供返回插件中心的入口。为了实现这一功能,我们要求开发者在插件服务注册类中定义菜单。通过这种方式,插件能够动态地生成和管理自己的菜单项,无需手动修改配置文件或数据表。

插件中心统一入口

在安装 插件中心 后,系统进一步提升了插件管理的便捷性和统一性。用户可以通过插件中心的统一入口,直接访问各个应用插件的独立管理菜单。 具体来说,每个应用插件的 Service 类中定义的 menu 方法,负责生成该插件的专属管理菜单。当插件中心被安装并启用后,它会自动扫描并集成所有已安装插件的管理菜单。这样,用户只需通过插件中心的统一入口,即可一站式地管理和操作所有插件。

这一优化不仅简化了插件管理的流程,还提高了管理的效率。用户无需在不同的地方查找和访问各个插件的管理界面,只需在插件中心即可完成所有操作。同时,由于菜单是由插件自身定义的,因此可以确保菜单的准确性和完整性,避免了因手动配置而可能产生的错误。

写入全局菜单数据

在数据库脚本的执行过程中,系统引入了自动写入菜单数据的机制。具体而言,通过调用应用插件 Service 中的 menu 方法,我们可以获取到插件的菜单配置信息。利用 think\admin\extend\PhinxExtend::write2menu() 方法,我们将这些配置信息自动写入到 system_menu 数据表中,从而实现了菜单的自动生成。

温馨提示: 当卸载应用插件时,并不会自动删除与之相关的菜单项。这可能导致在卸载插件后,系统中仍然残留有无效或不再需要的菜单项,需要管理员手动去系统菜单管理界面进行删除操作。

📝 菜单配置案例

Service 类菜单定义

插件的 Service 类必须继承 think\admin\Plugin 并实现静态方法 menu()

<?php
declare(strict_types=1);

namespace app\wechat;

use think\admin\Plugin;

/**
 * 组件注册服务
 * @class Service
 * @package app\wechat
 */
class Service extends Plugin
{
    /**
     * 定义插件名称
     * @var string
     */
    protected $appName = '微信管理';

    /**
     * 定义安装包名
     * @var string
     */
    protected $package = 'zoujingli/think-plugs-wechat';

    /**
     * 定义插件菜单(静态方法)
     * @return array
     */
    public static function menu(): array
    {
        $code = app(static::class)->appCode; // 获取插件编码
        return [
            [
                'name' => '微信管理',
                'subs' => [
                    ['name' => '微信接口配置', 'icon' => 'layui-icon layui-icon-set', 'node' => "{$code}/config/options"],
                    ['name' => '微信支付配置', 'icon' => 'layui-icon layui-icon-rmb', 'node' => "{$code}/config/payment"],
                ],
            ],
            [
                'name' => '微信定制',
                'subs' => [
                    ['name' => '微信粉丝管理', 'icon' => 'layui-icon layui-icon-username', 'node' => "{$code}/fans/index"],
                    ['name' => '微信图文管理', 'icon' => 'layui-icon layui-icon-template-1', 'node' => "{$code}/news/index"],
                    ['name' => '微信菜单配置', 'icon' => 'layui-icon layui-icon-cellphone', 'node' => "{$code}/menu/index"],
                    ['name' => '回复规则管理', 'icon' => 'layui-icon layui-icon-engine', 'node' => "{$code}/keys/index"],
                    ['name' => '关注自动回复', 'icon' => 'layui-icon layui-icon-release', 'node' => "{$code}/auto/index"],
                ],
            ],
        ];
    }
}

数据库迁移脚本

<?php
use think\migration\Migrator;
use think\migration\db\Column;
use think\admin\extend\PhinxExtend;

class InstallWechatData extends Migrator
{
    /**
     * 安装微信插件数据
     */
    public function up()
    {
        // 写入菜单数据
        PhinxExtend::write2menu([
            'name' => '微信管理',
            'subs' => [
                ['name' => '公众号配置', 'node' => 'plugin-wechat/config/index'],
                ['name' => '菜单管理', 'node' => 'plugin-wechat/menu/index'],
                ['name' => '素材管理', 'node' => 'plugin-wechat/material/index'],
                ['name' => '用户管理', 'node' => 'plugin-wechat/fans/index'],
                ['name' => '消息管理', 'node' => 'plugin-wechat/keys/index'],
            ]
        ]);
    }

    /**
     * 卸载微信插件数据
     */
    public function down()
    {
        // 删除菜单数据
        PhinxExtend::remove2menu('plugin-wechat');
    }
}

菜单配置参数说明

参数类型必填说明示例
namestring菜单名称'微信管理'
nodestring权限节点(子菜单必填)'wechat/config/index'"{$code}/config/index"
iconstring菜单图标(LayUI 图标类)'layui-icon layui-icon-set'
subsarray子菜单数组[['name' => '...', 'node' => '...']]
sortint排序权重100
statusint状态(1=启用,0=禁用)1

菜单结构说明

菜单支持两级结构:

  • 一级菜单:包含 namesubs(子菜单数组)
  • 二级菜单:包含 namenode(权限节点)和可选的 icon

注意事项:

  • menu() 方法必须是静态方法(public static function menu()
  • 使用 app(static::class)->appCode 动态获取插件编码,避免硬编码
  • 权限节点格式:{插件编码}/{控制器}/{方法},如:plugin-account/master/index

参考链接

最近更新:
Contributors: 邹景立, Anyon