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];
|
||||
})();
|
||||
Reference in New Issue
Block a user