This commit is contained in:
30
plugins/addon/theme_configurator/README.md
Normal file
30
plugins/addon/theme_configurator/README.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Theme Configurator 插件
|
||||||
|
|
||||||
|
此插件演示如何通过后台插件的方式为 BlackFruit-UI 主题提供可配置能力,支持设置导航、页脚、站点信息、SEO、首页轮播以及右侧浮窗,并提供 `/console/v1/theme/config` 接口与前端联动。
|
||||||
|
|
||||||
|
## 功能
|
||||||
|
- 后台界面(`template/admin/index.php`)以 JSON 的形式集中维护所有主题参数;
|
||||||
|
- 接口 `GET/POST /{DIR_ADMIN}/v1/theme/config` 提供配置读取与保存;
|
||||||
|
- 前台接口 `GET /console/v1/theme/config` 输出与 `/console/v1/common` 相同结构的数据,BlackFruit-UI 可以直接接入;
|
||||||
|
- 插件安装时创建 `addon_theme_configurator` 表并写入默认配置。
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
```
|
||||||
|
plugins/addon/theme_configurator
|
||||||
|
├── ThemeConfigurator.php # 插件主文件
|
||||||
|
├── controller/
|
||||||
|
│ ├── ThemeController.php # 后台 API
|
||||||
|
│ └── clientarea/ThemeController.php # 前台 API
|
||||||
|
├── model/ThemeConfigModel.php # 主题配置模型
|
||||||
|
├── template/admin/index.php # 后台可视化页面
|
||||||
|
├── route.php # 自定义路由定义
|
||||||
|
├── sidebar*.php # 前后台导航
|
||||||
|
├── auth.php # 权限配置
|
||||||
|
└── lang/zh-cn.php # 多语言
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用步骤
|
||||||
|
1. 将目录复制到业务系统的 `public/plugins/addon` 下;
|
||||||
|
2. 在后台启用插件,安装脚本会自动创建 `addon_theme_configurator` 表;
|
||||||
|
3. 进入“插件 > 主题配置”页,按 JSON 结构维护导航、SEO、轮播、侧边栏等;
|
||||||
|
4. 前端 BlackFruit-UI 请求 `/console/v1/theme/config` 以获取运行时配置。若需要兼容现有 `/console/v1/common`,可在 Nginx 或网关层做转发。
|
||||||
80
plugins/addon/theme_configurator/ThemeConfigurator.php
Normal file
80
plugins/addon/theme_configurator/ThemeConfigurator.php
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
namespace addon\theme_configurator;
|
||||||
|
|
||||||
|
use addon\theme_configurator\model\ThemeConfigModel;
|
||||||
|
use think\facade\Db;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主题可配置项插件主要类
|
||||||
|
*/
|
||||||
|
class ThemeConfigurator
|
||||||
|
{
|
||||||
|
/** @var array 插件基础信息 */
|
||||||
|
public $info = [
|
||||||
|
'name' => 'ThemeConfigurator',
|
||||||
|
'title' => '主题可视化配置',
|
||||||
|
'description' => '通过插件管理 BlackFruit-UI 导航、页脚、SEO、轮播与侧边栏等主题参数',
|
||||||
|
'author' => 'yiqiu',
|
||||||
|
'version' => '1.0.0',
|
||||||
|
];
|
||||||
|
|
||||||
|
/** @var bool 标记不需要默认插件导航 */
|
||||||
|
public $noNav = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安装插件 - 初始化存储表以及默认配置
|
||||||
|
*/
|
||||||
|
public function install()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
Db::execute(
|
||||||
|
"CREATE TABLE IF NOT EXISTS `addon_theme_configurator`(
|
||||||
|
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
`config` longtext NOT NULL,
|
||||||
|
`update_time` int(11) DEFAULT 0,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"
|
||||||
|
);
|
||||||
|
} catch (\Throwable $throwable) {
|
||||||
|
return [
|
||||||
|
'status' => 400,
|
||||||
|
'msg' => $throwable->getMessage(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
(new ThemeConfigModel())->initConfig();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'status' => 200,
|
||||||
|
'msg' => lang_plugins('theme_configurator_install_success'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 卸载插件 - 移除存储表
|
||||||
|
*/
|
||||||
|
public function uninstall()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
Db::execute("DROP TABLE IF EXISTS `addon_theme_configurator`;");
|
||||||
|
} catch (\Throwable $throwable) {
|
||||||
|
return [
|
||||||
|
'status' => 400,
|
||||||
|
'msg' => $throwable->getMessage(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'status' => 200,
|
||||||
|
'msg' => lang_plugins('theme_configurator_uninstall_success'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 供其他业务调用,快速获取主题配置
|
||||||
|
*/
|
||||||
|
public function getThemeConfig()
|
||||||
|
{
|
||||||
|
return (new ThemeConfigModel())->getConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
15
plugins/addon/theme_configurator/auth.php
Normal file
15
plugins/addon/theme_configurator/auth.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'title' => 'auth_plugin_addon_theme_configurator',
|
||||||
|
'url' => 'theme_config',
|
||||||
|
'child' => [
|
||||||
|
[
|
||||||
|
'title' => 'auth_plugin_addon_theme_configurator_save',
|
||||||
|
'url' => '',
|
||||||
|
'auth_rule' => 'ThemeController::save',
|
||||||
|
'auth_rule_title' => 'auth_plugin_addon_theme_configurator_save',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
namespace addon\theme_configurator\controller;
|
||||||
|
|
||||||
|
use addon\theme_configurator\model\ThemeConfigModel;
|
||||||
|
use app\admin\controller\PluginAdminBaseController;
|
||||||
|
use think\App;
|
||||||
|
use think\Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 后台主题配置控制器
|
||||||
|
*/
|
||||||
|
class ThemeController extends PluginAdminBaseController
|
||||||
|
{
|
||||||
|
protected ThemeConfigModel $model;
|
||||||
|
|
||||||
|
public function __construct(App $app = null)
|
||||||
|
{
|
||||||
|
parent::__construct($app);
|
||||||
|
$this->model = new ThemeConfigModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取配置
|
||||||
|
*/
|
||||||
|
public function config(): Response
|
||||||
|
{
|
||||||
|
return json([
|
||||||
|
'status' => 200,
|
||||||
|
'msg' => lang_plugins('theme_configurator_success'),
|
||||||
|
'data' => $this->model->getConfig(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存配置
|
||||||
|
*/
|
||||||
|
public function save(): Response
|
||||||
|
{
|
||||||
|
$param = $this->request->param();
|
||||||
|
$payload = [
|
||||||
|
'seo' => $param['seo'] ?? [],
|
||||||
|
'header_nav' => $param['header_nav'] ?? [],
|
||||||
|
'footer_nav' => $param['footer_nav'] ?? [],
|
||||||
|
'site_config' => $param['site_config'] ?? [],
|
||||||
|
'friendly_link' => $param['friendly_link'] ?? [],
|
||||||
|
'banner' => $param['banner'] ?? [],
|
||||||
|
'side' => $param['side'] ?? [],
|
||||||
|
];
|
||||||
|
|
||||||
|
$config = $this->model->saveConfig($payload);
|
||||||
|
|
||||||
|
return json([
|
||||||
|
'status' => 200,
|
||||||
|
'msg' => lang_plugins('theme_configurator_save_success'),
|
||||||
|
'data' => $config,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
namespace addon\theme_configurator\controller\clientarea;
|
||||||
|
|
||||||
|
use addon\theme_configurator\model\ThemeConfigModel;
|
||||||
|
use app\home\controller\PluginBaseController;
|
||||||
|
use think\App;
|
||||||
|
use think\Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 前台主题配置读取控制器
|
||||||
|
*/
|
||||||
|
class ThemeController extends PluginBaseController
|
||||||
|
{
|
||||||
|
protected ThemeConfigModel $model;
|
||||||
|
|
||||||
|
public function __construct(App $app = null)
|
||||||
|
{
|
||||||
|
parent::__construct($app);
|
||||||
|
$this->model = new ThemeConfigModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回给 BlackFruit-UI 的 /console/v1/common 同结构数据
|
||||||
|
*/
|
||||||
|
public function config(): Response
|
||||||
|
{
|
||||||
|
$config = $this->model->getConfig();
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'enterprise_name' => $config['site_config']['enterprise_name'] ?? '',
|
||||||
|
'enterprise_telephone' => $config['site_config']['enterprise_telephone'] ?? '',
|
||||||
|
'enterprise_mailbox' => $config['site_config']['enterprise_mailbox'] ?? '',
|
||||||
|
'enterprise_qrcode' => $config['site_config']['enterprise_qrcode'] ?? '',
|
||||||
|
'official_website_logo' => $config['site_config']['official_website_logo'] ?? '',
|
||||||
|
'icp_info' => $config['site_config']['icp_info'] ?? '',
|
||||||
|
'icp_info_link' => $config['site_config']['icp_info_link'] ?? '',
|
||||||
|
'public_security_network_preparation' => $config['site_config']['public_security_network_preparation'] ?? '',
|
||||||
|
'public_security_network_preparation_link' => $config['site_config']['public_security_network_preparation_link'] ?? '',
|
||||||
|
'telecom_appreciation' => $config['site_config']['telecom_appreciation'] ?? '',
|
||||||
|
'copyright_info' => $config['site_config']['copyright_info'] ?? '',
|
||||||
|
'terms_service_url' => $config['site_config']['terms_service_url'] ?? '',
|
||||||
|
'terms_privacy_url' => $config['site_config']['terms_privacy_url'] ?? '',
|
||||||
|
'cloud_product_link' => $config['site_config']['cloud_product_link'] ?? '',
|
||||||
|
'dcim_product_link' => $config['site_config']['dcim_product_link'] ?? '',
|
||||||
|
'honor' => $config['site_config']['honor'] ?? [],
|
||||||
|
'partner' => $config['site_config']['partner'] ?? [],
|
||||||
|
'friendly_link' => $config['friendly_link'] ?? [],
|
||||||
|
'banner' => $config['banner'] ?? [],
|
||||||
|
'header_nav' => $config['header_nav'] ?? [],
|
||||||
|
'footer_nav' => $config['footer_nav'] ?? [],
|
||||||
|
'side_floating_window' => $config['side'] ?? [],
|
||||||
|
'feedback_type' => $config['site_config']['feedback_type'] ?? [],
|
||||||
|
];
|
||||||
|
|
||||||
|
return json([
|
||||||
|
'status' => 200,
|
||||||
|
'msg' => lang_plugins('theme_configurator_success'),
|
||||||
|
'data' => $data,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
10
plugins/addon/theme_configurator/lang/zh-cn.php
Normal file
10
plugins/addon/theme_configurator/lang/zh-cn.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
return [
|
||||||
|
'theme_configurator_success' => '请求成功',
|
||||||
|
'theme_configurator_save_success' => '主题配置保存成功',
|
||||||
|
'theme_configurator_install_success' => '主题配置插件安装成功',
|
||||||
|
'theme_configurator_uninstall_success' => '主题配置插件卸载成功',
|
||||||
|
'nav_plugin_addon_theme_configurator' => '主题配置',
|
||||||
|
'auth_plugin_addon_theme_configurator' => '主题控制',
|
||||||
|
'auth_plugin_addon_theme_configurator_save' => '保存主题配置',
|
||||||
|
];
|
||||||
93
plugins/addon/theme_configurator/model/ThemeConfigModel.php
Normal file
93
plugins/addon/theme_configurator/model/ThemeConfigModel.php
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
namespace addon\theme_configurator\model;
|
||||||
|
|
||||||
|
use think\facade\Db;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主题配置模型
|
||||||
|
*/
|
||||||
|
class ThemeConfigModel
|
||||||
|
{
|
||||||
|
protected $table = 'addon_theme_configurator';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取配置
|
||||||
|
*/
|
||||||
|
public function getConfig(): array
|
||||||
|
{
|
||||||
|
$row = Db::name($this->table)->order('id', 'asc')->find();
|
||||||
|
if (!$row) {
|
||||||
|
return $this->defaultConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
$config = json_decode($row['config'], true);
|
||||||
|
return is_array($config) ? $config : $this->defaultConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存配置
|
||||||
|
*/
|
||||||
|
public function saveConfig(array $config): array
|
||||||
|
{
|
||||||
|
$payload = [
|
||||||
|
'config' => json_encode($config, JSON_UNESCAPED_UNICODE),
|
||||||
|
'update_time' => time(),
|
||||||
|
];
|
||||||
|
|
||||||
|
$exists = Db::name($this->table)->order('id', 'asc')->find();
|
||||||
|
if ($exists) {
|
||||||
|
Db::name($this->table)->where('id', $exists['id'])->update($payload);
|
||||||
|
} else {
|
||||||
|
Db::name($this->table)->insert($payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安装时写入默认配置
|
||||||
|
*/
|
||||||
|
public function initConfig(): void
|
||||||
|
{
|
||||||
|
$exists = Db::name($this->table)->order('id', 'asc')->find();
|
||||||
|
if (!$exists) {
|
||||||
|
$this->saveConfig($this->defaultConfig());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认结构
|
||||||
|
*/
|
||||||
|
protected function defaultConfig(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'seo' => [
|
||||||
|
'title' => 'BlackFruit-UI',
|
||||||
|
'keywords' => '云服务器,主题云,BlackFruit',
|
||||||
|
'description' => 'BlackFruit-UI 默认站点说明',
|
||||||
|
],
|
||||||
|
'header_nav' => [],
|
||||||
|
'footer_nav' => [],
|
||||||
|
'site_config' => [
|
||||||
|
'enterprise_name' => '主题云',
|
||||||
|
'enterprise_telephone' => '400-000-0000',
|
||||||
|
'enterprise_mailbox' => 'support@example.com',
|
||||||
|
'enterprise_qrcode' => '/upload/qrcode.png',
|
||||||
|
'official_website_logo' => '/upload/logo.png',
|
||||||
|
'icp_info' => '京ICP备示例号',
|
||||||
|
'icp_info_link' => 'https://beian.miit.gov.cn/#/Integrated/index',
|
||||||
|
'public_security_network_preparation' => '京公网安备示例号',
|
||||||
|
'public_security_network_preparation_link' => 'https://beian.mps.gov.cn/#/query/webSearch',
|
||||||
|
'telecom_appreciation' => '增值电信业务经营许可证',
|
||||||
|
'copyright_info' => '© ' . date('Y') . ' 主题云',
|
||||||
|
'terms_service_url' => '/agreement/service.html',
|
||||||
|
'terms_privacy_url' => '/agreement/privacy.html',
|
||||||
|
'cloud_product_link' => '/cart/goods.htm?id=1',
|
||||||
|
'dcim_product_link' => '/cart/goods.htm?id=2',
|
||||||
|
],
|
||||||
|
'friendly_link' => [],
|
||||||
|
'banner' => [],
|
||||||
|
'side' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
30
plugins/addon/theme_configurator/route.php
Normal file
30
plugins/addon/theme_configurator/route.php
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
use think\facade\Route;
|
||||||
|
|
||||||
|
// 前台读取接口
|
||||||
|
Route::group('console/v1', function () {
|
||||||
|
Route::get('theme/config', "\\addon\\theme_configurator\\controller\\clientarea\\ThemeController@config")
|
||||||
|
->append([
|
||||||
|
'_plugin' => 'theme_configurator',
|
||||||
|
'_controller' => 'theme',
|
||||||
|
'_action' => 'config',
|
||||||
|
]);
|
||||||
|
})->middleware(\app\http\middleware\ParamFilter::class);
|
||||||
|
|
||||||
|
// 后台配置接口
|
||||||
|
Route::group(DIR_ADMIN . '/v1', function () {
|
||||||
|
Route::get('theme/config', "\\addon\\theme_configurator\\controller\\ThemeController@config")
|
||||||
|
->append([
|
||||||
|
'_plugin' => 'theme_configurator',
|
||||||
|
'_controller' => 'theme',
|
||||||
|
'_action' => 'config',
|
||||||
|
])
|
||||||
|
->middleware(\app\http\middleware\CheckAdmin::class);
|
||||||
|
Route::post('theme/config', "\\addon\\theme_configurator\\controller\\ThemeController@save")
|
||||||
|
->append([
|
||||||
|
'_plugin' => 'theme_configurator',
|
||||||
|
'_controller' => 'theme',
|
||||||
|
'_action' => 'save',
|
||||||
|
])
|
||||||
|
->middleware(\app\http\middleware\CheckAdmin::class);
|
||||||
|
})->middleware(\app\http\middleware\ParamFilter::class);
|
||||||
17
plugins/addon/theme_configurator/sidebar.php
Normal file
17
plugins/addon/theme_configurator/sidebar.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'name' => 'nav_plugin_addon_theme_configurator',
|
||||||
|
'url' => '',
|
||||||
|
'icon' => 'dashboard',
|
||||||
|
'in' => '',
|
||||||
|
'child' => [
|
||||||
|
[
|
||||||
|
'name' => 'nav_plugin_addon_theme_configurator',
|
||||||
|
'url' => 'index',
|
||||||
|
'in' => '',
|
||||||
|
'icon' => 'setting',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
9
plugins/addon/theme_configurator/sidebar_clientarea.php
Normal file
9
plugins/addon/theme_configurator/sidebar_clientarea.php
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'name' => 'nav_plugin_addon_theme_configurator',
|
||||||
|
'url' => 'theme_configurator',
|
||||||
|
'icon' => 'setting',
|
||||||
|
'child' => [],
|
||||||
|
],
|
||||||
|
];
|
||||||
82
plugins/addon/theme_configurator/template/admin/index.php
Normal file
82
plugins/addon/theme_configurator/template/admin/index.php
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-cn">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>主题配置</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
padding: 24px;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
|
||||||
|
background: #f5f6fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 24px;
|
||||||
|
box-shadow: 0 5px 16px rgba(0, 0, 0, 0.08);
|
||||||
|
max-width: 960px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 280px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #dfe3ec;
|
||||||
|
padding: 12px;
|
||||||
|
font-family: "JetBrains Mono", Consolas, monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
background: #0f172a;
|
||||||
|
color: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-top: 16px;
|
||||||
|
padding: 10px 24px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: #2563eb;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="card">
|
||||||
|
<h2>主题配置 JSON</h2>
|
||||||
|
<p>此页面演示如何通过插件快速调整 BlackFruit-UI 的导航、页脚、SEO、轮播、侧边栏以及企业信息。直接编辑下方 JSON 提交即可。</p>
|
||||||
|
<textarea id="themeConfig"></textarea>
|
||||||
|
<button id="saveBtn">保存配置</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const input = document.getElementById("themeConfig");
|
||||||
|
const request = (url, options = {}) =>
|
||||||
|
fetch(url, Object.assign({ headers: { "Content-Type": "application/json" } }, options))
|
||||||
|
.then((res) => res.json());
|
||||||
|
|
||||||
|
request("/<?= DIR_ADMIN ?>/v1/theme/config").then((res) => {
|
||||||
|
input.value = JSON.stringify(res.data, null, 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("saveBtn").addEventListener("click", () => {
|
||||||
|
try {
|
||||||
|
const payload = JSON.parse(input.value);
|
||||||
|
request("/<?= DIR_ADMIN ?>/v1/theme/config", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
}).then((res) => {
|
||||||
|
alert(res.msg || "保存成功");
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
alert("JSON 解析失败: " + err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
(function () {
|
||||||
|
const module_lang = {
|
||||||
|
"zh-cn": {
|
||||||
|
save: "保存配置",
|
||||||
|
},
|
||||||
|
"zh-hk": {
|
||||||
|
save: "保存配置",
|
||||||
|
},
|
||||||
|
"en-us": {
|
||||||
|
save: "Save",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const DEFAULT_LANG = localStorage.getItem("backLang") || "zh-cn";
|
||||||
|
window.theme_configurator_lang = module_lang[DEFAULT_LANG];
|
||||||
|
})();
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
(function () {
|
||||||
|
const module_lang = {
|
||||||
|
"zh-cn": {
|
||||||
|
success: "请求成功",
|
||||||
|
},
|
||||||
|
"zh-hk": {
|
||||||
|
success: "请求成功",
|
||||||
|
},
|
||||||
|
"en-us": {
|
||||||
|
success: "Success",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const DEFAULT_LANG = localStorage.getItem("lang") || "zh-cn";
|
||||||
|
window.theme_configurator_client_lang = module_lang[DEFAULT_LANG];
|
||||||
|
})();
|
||||||
408
plugins_dev.md
Normal file
408
plugins_dev.md
Normal file
@@ -0,0 +1,408 @@
|
|||||||
|
阅读须知:此文件是插件制作方法,和BlackFruit-UI项目是互相辅助的关系,BlackFruit-UI需要配置的地方都会在插件中体现,也就是一个业务管理系统的主题和后台插件控制主题的关系。所以以下内容的路径不是指的BlackFruit-UI的位置,是业务管理系统的,和本项目无关。
|
||||||
|
|
||||||
|
一、位置
|
||||||
|
|
||||||
|
插件放在\public\plugins\addon\目录下
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
二、目录结构
|
||||||
|
|
||||||
|
\plugins\addon\idcsmart_ticket
|
||||||
|
|
||||||
|
|--- controller(控制器目录,后台控制器直接放此目录下)
|
||||||
|
|
||||||
|
|--- clientarea(前台控制器放此目录下)
|
||||||
|
|
||||||
|
|--- lang(语言目录)
|
||||||
|
|
||||||
|
|--- zh-cn.php(中文语言文件)
|
||||||
|
|
||||||
|
|--- template(模板)
|
||||||
|
|
||||||
|
|--- admin(后台模板目录)
|
||||||
|
|
||||||
|
|--- clientarea(前台模板目录)
|
||||||
|
|
||||||
|
|--- IdcsmartTicket.php(插件主文件)
|
||||||
|
|
||||||
|
|--- route.php(自定义路由文件)
|
||||||
|
|
||||||
|
|--- sidebar.php(后台插件导航文件)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
三、开发流程
|
||||||
|
|
||||||
|
以工单插件为例:
|
||||||
|
|
||||||
|
1、创建工单插件目录\public\plugins\addon\idcsmart_ticket\;
|
||||||
|
|
||||||
|
①目录名以小写字母+下划线形式,必须以字母开头,如idcsmart_ticket;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
2、创建入口文件:IdcsmartTicket.php:
|
||||||
|
|
||||||
|
①命名空间namespace addon\idcsmart_ticket(控制器下命名空间对应相应目录即可,其它同理);
|
||||||
|
|
||||||
|
②以目录名大驼峰+.php,创建在插件根目录下,如idcsmart_ticket\IdcsmartTicket.php;
|
||||||
|
|
||||||
|
③文件中需要定义info属性,示例:
|
||||||
|
|
||||||
|
# 插件基本信息
|
||||||
|
public $info = array(
|
||||||
|
'name' => 'IdcsmartTicket', //插件英文名,作为插件唯一标识,改成你的插件英文就行了
|
||||||
|
'title' => '智简魔方工单插件', //插件名称
|
||||||
|
'description' => '智简魔方工单插件', //插件描述
|
||||||
|
'author' => 'idcsmart', //开发者
|
||||||
|
'version' => '1.0', // 版本号
|
||||||
|
);
|
||||||
|
|
||||||
|
④必须实现install()安装以及uninstall()卸载方法
|
||||||
|
|
||||||
|
⑤可实现钩子方法,如要实现订单创建后钩子after_order_create,则可在此文件中创建
|
||||||
|
|
||||||
|
public function afterOrderCreate($param)公共方法;
|
||||||
|
|
||||||
|
⑥系统会在订单创建后的位置放置⑤中的钩子,放置方式:
|
||||||
|
|
||||||
|
hook(‘after_order_create’,[‘id’=>$orderId]) ;
|
||||||
|
|
||||||
|
⑦如果定义变量noNav,表示不需要默认导航(具体参考7,插件后台导航)
|
||||||
|
|
||||||
|
public $noNav;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
3、创建插件后台控制器 idcsmart_ticket\controller\TicketController.php
|
||||||
|
|
||||||
|
①命名空间addon\idcsmart_ticket\controller;
|
||||||
|
|
||||||
|
②继承后台基类控制器PluginAdminBaseController,增加了权限控制,允许使用一些通用方法;
|
||||||
|
|
||||||
|
③get_admin_id()可获取后台管理员登录ID;
|
||||||
|
|
||||||
|
④实现接口,如public function ticketList(),系统默认访问此接口的url为:
|
||||||
|
|
||||||
|
网站地址/admin/addon?_plugin=idcsmart_ticket&_controller=ticket&_action=ticket_list,说明:系统默认地址默认登录,若需要免登录访问,需要自定义路由(见后面自定义路由);
|
||||||
|
|
||||||
|
⑤系统提供了生成④中默认访问地址的方法:
|
||||||
|
|
||||||
|
idcsmart_addon_url($url,$vars=[],$is_admin=false)
|
||||||
|
|
||||||
|
参数说明:
|
||||||
|
|
||||||
|
url: 格式:插件名://控制器名/方法,如 IdcsmartTicket://Ticket/ticketList
|
||||||
|
|
||||||
|
vars: 参数,默认为空数组
|
||||||
|
|
||||||
|
Is_admin: 是否后台,默认为false
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
4、创建插件前台控制器idcsmart_ticket\controller\clientarea\TicketController
|
||||||
|
|
||||||
|
.php
|
||||||
|
|
||||||
|
①命名空间addon\idcsmart_ticket\controller\clientarea;
|
||||||
|
|
||||||
|
②继承前台基类控制器PluginBaseController,允许使用一些通用方法;
|
||||||
|
|
||||||
|
③get_client_id()可获取前台客户登录ID;
|
||||||
|
|
||||||
|
④实现接口,如public function ticketList(),系统默认访问此接口的url为:
|
||||||
|
|
||||||
|
网站地址/console/addon?_plugin=idcsmart_ticket&_controller=ticket&_action=ticket_list或者:
|
||||||
|
|
||||||
|
网站地址/console/addon?_plugin=1&_controller=ticket&_action=ticket_list,1表示插件ID;
|
||||||
|
|
||||||
|
说明:系统默认地址默认登录,若需要免登录访问,需要自定义路由(见后面自定义路由);
|
||||||
|
|
||||||
|
⑤系统提供了生成④中默认访问地址的方法:
|
||||||
|
|
||||||
|
idcsmart_addon_url($url,$vars=[],$is_admin=false)
|
||||||
|
|
||||||
|
参数说明:
|
||||||
|
|
||||||
|
url: 格式:插件名://控制器名/方法,如 IdcsmartTicket://Ticket/ticketList
|
||||||
|
|
||||||
|
vars: 参数,默认为空数组
|
||||||
|
|
||||||
|
Is_admin: 是否后台,默认为false
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
5、插件提供两种路由:
|
||||||
|
|
||||||
|
①、系统默认url带参数,参考4
|
||||||
|
|
||||||
|
②、自定义路由,插件根目录下定义route.php,示例:
|
||||||
|
|
||||||
|
前台路由:
|
||||||
|
|
||||||
|
Route::group('console/v1',function (){
|
||||||
|
|
||||||
|
Route::get('ticket',"\\addon\\idcsmart_ticket\\controller\\clientarea\\TicketController@ticketList")->append(['_plugin'=>'idcsmart_ticket','_controller'=>'ticket','_action'=>'ticket_list']);# 带上默认参数,可以使用继承控制器app\admin\controller\PluginBaseController的一些通用方法,也可以不追加这些参数(_plugin插件名称C风格,_controller控制器名称C风格,_action方法名称C风格)
|
||||||
|
|
||||||
|
})->middleware(\app\http\middleware\ParamFilter::class)
|
||||||
|
|
||||||
|
->middleware(\app\http\middleware\CheckHome::class);#前台需要登录就使用此中间件
|
||||||
|
|
||||||
|
后台路由:
|
||||||
|
|
||||||
|
Route::group(DIR_ADMIN . '/v1',function (){
|
||||||
|
|
||||||
|
Route::get('ticket',"\\addon\idcsmart_ticket\controller\TicketController@ticketList")->append(['_plugin'=>'idcsmart_ticket','_controller'=>'ticket','_action'=>'ticket_list']);})->middleware(\app\http\middleware\ParamFilter::class)->
|
||||||
|
|
||||||
|
middleware(\app\http\middleware\CheckAdmin::class);#后台需要登录就使用此中间件
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
6、插件多语言
|
||||||
|
|
||||||
|
①创建语言文件lang/zh-cn.php,返回如下格式的数组:
|
||||||
|
|
||||||
|
return [
|
||||||
|
'success_message' => '请求成功',
|
||||||
|
'ticket_title_require' => '请输入工单标题',
|
||||||
|
|
||||||
|
'ticket_log_client_create_ticket' => '{client}新建工单:{ticket_id}',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
②使用lang_plugins('语言标识',$param=[])实现多语言,$param为语言文件中参数,格式['{client}'=>'wyh'],如:
|
||||||
|
|
||||||
|
lang_plugins('ticket_log_client_create_ticket',['{client}'=>'wyh','{ticket_id}'=>1]);
|
||||||
|
|
||||||
|
③前端多语言文件
|
||||||
|
|
||||||
|
前台语言文件:在template/clientarea/lang/目录下index.js文件;
|
||||||
|
|
||||||
|
后台语言文件:在template/admin/lang目录下
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
|
||||||
|
const module_lang = {
|
||||||
|
|
||||||
|
"zh-cn": {
|
||||||
|
|
||||||
|
add: "添加",
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
"zh-hk": {
|
||||||
|
|
||||||
|
add: "添加",
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
"en-us": {
|
||||||
|
|
||||||
|
add: "Add",
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_LANG = localStorage.getItem("backLang") || "zh-cn";
|
||||||
|
|
||||||
|
window.module_lang = module_lang[DEFAULT_LANG];
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
具体参考idcsmart_ticket工单插件
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
7、插件后台导航
|
||||||
|
|
||||||
|
①导航默认会创建在插件导航之下,默认链接:plugin/插件名/index.html,对应template/admin/index.php文件,同时需要在lang/下的语言文件创建如下格式的语言,nav_plugin_addon_插件名:
|
||||||
|
|
||||||
|
'nav_plugin_addon_idcsmart_ticket' => '工单',
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
②自定义导航,若是一级导航,会增加在插件导航之上,管理导航之下,在插件根目录下创建sidebar.php,示例:
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 自定义后台导航菜单(仅支持一二级),注意系统会在插件导航下默认创建url为"plugin/插件名称/index.html"的导航,因此需要在template/admin/目录下创建index.php文件作为插件入口
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
[ # 一级导航
|
||||||
|
'name' => 'nav_plugin_addon_ticket', # 导航名称,不要与系统冲突(参考idcsmart_nav表中name字段),同时需要在lang/目录下定义语言
|
||||||
|
'url' => '', # 为空表示一级导航,不需要链接
|
||||||
|
'icon' => 'tools', # 图标,获取图标:https://tdesign.tencent.com/vue/components/icon
|
||||||
|
'in' => '', # 一级导航,此值为空
|
||||||
|
'child' => [ # 二级导航
|
||||||
|
[
|
||||||
|
'name' => 'nav_plugin_addon_ticket_list', # 导航名称
|
||||||
|
'url' => 'ticket', # 链接格式,会自动加上.html
|
||||||
|
'in' => 'nav_user_management', # 可定义导航在某个一级导航之下,默认会放置在此一级导航最后的位置(获取方式:idcsmart_nav表中的parent_id==0的name字段)
|
||||||
|
'icon' => '', # 图标,获取图标:https://tdesign.tencent.com/vue/components/icon
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'nav_plugin_addon_ticket_internal_list', # 导航名称
|
||||||
|
'url' => 'ticket_internal', # 链接格式,会自动加上.html
|
||||||
|
'in' => '', # 可定义导航在某个一级导航之下,默认会放置在此一级导航最后的位置
|
||||||
|
'icon' => '', # 图标,获取图标:https://tdesign.tencent.com/vue/components/icon
|
||||||
|
],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
8、插件前台导航
|
||||||
|
|
||||||
|
①导航默认会创建在插件导航之下,默认链接:plugin/插件名/index.html;
|
||||||
|
|
||||||
|
②自定义导航,会增加在插件导航之上,管理导航之下,在插件根目录下创建sidebar_clientarea.php,示例:
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 自定义前台导航菜单(仅支持一二级)
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'name' => 'nav_plugin_addon_ticket', # 链接名称,同时需要在lang/目录下定义语言
|
||||||
|
'url' => '', # 链接格式,会自动加上.html
|
||||||
|
'icon' => '', # 图标
|
||||||
|
'child' => [ # 二级菜单
|
||||||
|
[
|
||||||
|
'name' => 'nav_plugin_addon_ticket_list',
|
||||||
|
'url' => 'ticket',
|
||||||
|
'icon' => '', # 图标
|
||||||
|
],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
9、插件后台权限管理
|
||||||
|
|
||||||
|
在插件根目录下创建auth.php,示例如下(注意定义语言):
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* 定义权限,系统会默认插入名称为插件名的一级权限,以下仅需定义二级/三级权限;(首先,要使用二级权限,插件后台控制器需要继承app\event\controller\PluginAdminBaseController基类控制器)
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'title' => 'auth_plugin_addon_ticket_list', # y用户工单
|
||||||
|
'url' => 'ticket',
|
||||||
|
'child' => [ # 操作权限
|
||||||
|
[
|
||||||
|
'title' => 'auth_plugin_addon_ticket_view', # 工单查看
|
||||||
|
'url' => '',
|
||||||
|
'auth_rule' => 'TicketController::ticketList', # 工单列表具体控制器方法
|
||||||
|
'auth_rule_title' => 'auth_rule_plugin_addon_ticket_list' # 具体权限名称
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'title' => 'auth_plugin_addon_ticket_receive',
|
||||||
|
'url' => '',
|
||||||
|
'auth_rule' => 'TicketController::receive',
|
||||||
|
'auth_rule_title' => 'auth_rule_plugin_addon_ticket_receive' # 具体权限名称
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'title' => 'auth_plugin_addon_ticket_resolved',
|
||||||
|
'url' => '',
|
||||||
|
'auth_rule' => 'TicketController::resolved',
|
||||||
|
'auth_rule_title' => 'auth_rule_plugin_addon_ticket_resolved' # 具体权限名称
|
||||||
|
],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'title' => 'auth_plugin_addon_ticket_detail', # 工单详情
|
||||||
|
'url' => 'ticket_detail',
|
||||||
|
'child' => [
|
||||||
|
[
|
||||||
|
'title' => 'auth_plugin_addon_ticket_view',
|
||||||
|
'url' => '',
|
||||||
|
'auth_rule' => 'TicketController::index',
|
||||||
|
'auth_rule_title' => 'auth_rule_plugin_addon_ticket_index' # 具体权限名称
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'title' => 'auth_plugin_addon_ticket_reply',
|
||||||
|
'url' => '',
|
||||||
|
'auth_rule' => 'TicketController::reply',
|
||||||
|
'auth_rule_title' => 'auth_rule_plugin_addon_ticket_reply' # 具体权限名称
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'title' => 'auth_plugin_addon_ticket_download',
|
||||||
|
'url' => '',
|
||||||
|
'auth_rule' => 'TicketController::download',
|
||||||
|
'auth_rule_title' => 'auth_rule_plugin_addon_ticket_download' # 具体权限名称
|
||||||
|
],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'title' => 'auth_plugin_addon_ticket_internal_list', # 内部工单
|
||||||
|
'url' => 'ticket_internal',
|
||||||
|
'child' => [
|
||||||
|
[
|
||||||
|
'title' => 'auth_plugin_addon_ticket_view',
|
||||||
|
'url' => '',
|
||||||
|
'auth_rule' => 'TicketInternalController::ticketInternalList',
|
||||||
|
'auth_rule_title' => 'auth_rule_plugin_addon_ticket_internal_list' # 具体权限名称
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'title' => 'auth_plugin_addon_ticket_view',
|
||||||
|
'url' => '',
|
||||||
|
'auth_rule' => 'TicketInternalController::index',
|
||||||
|
'auth_rule_title' => 'auth_rule_plugin_addon_ticket_internal_index' # 具体权限名称
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'title' => 'auth_plugin_addon_ticket_add',
|
||||||
|
'url' => '',
|
||||||
|
'auth_rule' => 'TicketInternalController::create',
|
||||||
|
'auth_rule_title' => 'auth_rule_plugin_addon_ticket_internal_create' # 具体权限名称
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'title' => 'auth_plugin_addon_ticket_receive',
|
||||||
|
'url' => '',
|
||||||
|
'auth_rule' => 'TicketInternalController::receive',
|
||||||
|
'auth_rule_title' => 'auth_rule_plugin_addon_ticket_internal_receive' # 具体权限名称
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'title' => 'auth_plugin_addon_ticket_resolved',
|
||||||
|
'url' => '',
|
||||||
|
'auth_rule' => 'TicketInternalController::resolved',
|
||||||
|
'auth_rule_title' => 'auth_rule_plugin_addon_ticket_internal_resolved' # 具体权限名称
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'title' => 'auth_plugin_addon_ticket_reply',
|
||||||
|
'url' => '',
|
||||||
|
'auth_rule' => 'TicketInternalController::reply',
|
||||||
|
'auth_rule_title' => 'auth_rule_plugin_addon_ticket_internal_reply' # 具体权限名称
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'title' => 'auth_plugin_addon_ticket_forward',
|
||||||
|
'url' => '',
|
||||||
|
'auth_rule' => 'TicketInternalController::forward',
|
||||||
|
'auth_rule_title' => 'auth_rule_plugin_addon_ticket_internal_forward' # 具体权限名称
|
||||||
|
],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
10、插件接口内部调用
|
||||||
|
|
||||||
|
使用plugin_api($addon,$controller,$action,$param=[])函数可内部调用插件API,需要开发者提供插件API开发文档,具体格式参考系统API文档
|
||||||
|
|
||||||
|
* @param string addon - 插件 require
|
||||||
|
|
||||||
|
* @param string controller - 控制器前缀 require
|
||||||
|
|
||||||
|
* @param string action - 方法 require
|
||||||
|
|
||||||
|
* @param array param - 传入的参数
|
||||||
Reference in New Issue
Block a user