新增配置插件
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
yiqiu
2025-11-20 23:37:31 +08:00
parent 3e345e9e22
commit 8a1fa3fb8b
14 changed files with 923 additions and 0 deletions

View 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 或网关层做转发。

View 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();
}
}

View 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',
],
],
],
];

View File

@@ -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,
]);
}
}

View File

@@ -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,
]);
}
}

View 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' => '保存主题配置',
];

View 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' => [],
];
}
}

View 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);

View 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',
],
],
],
];

View File

@@ -0,0 +1,9 @@
<?php
return [
[
'name' => 'nav_plugin_addon_theme_configurator',
'url' => 'theme_configurator',
'icon' => 'setting',
'child' => [],
],
];

View 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>

View File

@@ -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];
})();

View File

@@ -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];
})();