修改插件UI
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
yiqiu
2025-11-21 12:42:18 +08:00
parent 8622f2b7b2
commit 5834ab9a74
10 changed files with 403 additions and 70 deletions

View File

@@ -108,15 +108,27 @@ $(function () {
};
// 获取通用配置信息
const getCommentInfo = () => {
const handleSuccess = function (res) {
sessionStorage.commentData = JSON.stringify(res.data);
setCommData();
};
$.ajax({
url: "/console/v1/theme/config",
method: "get",
headers: {
Authorization: "Bearer" + " " + localStorage.jwt,
},
success: handleSuccess,
error: function () {
// 若插件接口不存在或报错,回落到原有 /console/v1/common
$.ajax({
url: "/console/v1/common",
method: "get",
headers: {
Authorization: "Bearer" + " " + localStorage.jwt,
},
success: function (res) {
sessionStorage.commentData = JSON.stringify(res.data);
setCommData();
success: handleSuccess,
});
},
});
};

View File

@@ -22,15 +22,26 @@ $(function () {
// 获取通用配置信息
function getCommentInfo() {
const handleSuccess = function (res) {
sessionStorage.commentData = JSON.stringify(res.data);
setIndexData();
};
$.ajax({
url: "/console/v1/theme/config",
method: "get",
headers: {
Authorization: "Bearer" + " " + localStorage.jwt,
},
success: handleSuccess,
error: function () {
$.ajax({
url: "/console/v1/common",
method: "get",
headers: {
Authorization: "Bearer" + " " + localStorage.jwt,
},
success: function (res) {
sessionStorage.commentData = JSON.stringify(res.data);
setIndexData();
success: handleSuccess,
});
},
});
}

View File

@@ -67,15 +67,26 @@ $(function () {
}
// 获取通用配置信息
function getCommentInfo() {
const handleSuccess = function (res) {
sessionStorage.commentData = JSON.stringify(res.data);
setIndexData();
};
$.ajax({
url: "/console/v1/theme/config",
method: "get",
headers: {
Authorization: "Bearer" + " " + localStorage.jwt,
},
success: handleSuccess,
error: function () {
$.ajax({
url: "/console/v1/common",
method: "get",
headers: {
Authorization: "Bearer" + " " + localStorage.jwt,
},
success: function (res) {
sessionStorage.commentData = JSON.stringify(res.data);
setIndexData();
success: handleSuccess,
});
},
});
}

View File

@@ -130,15 +130,27 @@ $(function () {
}
// 获取通用配置信息
function getCommentInfo() {
const handleSuccess = function (res) {
sessionStorage.commentData = JSON.stringify(res.data);
setIndexData();
};
$.ajax({
url: "/console/v1/theme/config",
method: "get",
headers: {
Authorization: "Bearer" + " " + localStorage.jwt,
},
success: handleSuccess,
error: function () {
// 插件未启用时回落到原有 /console/v1/common
$.ajax({
url: "/console/v1/common",
method: "get",
headers: {
Authorization: "Bearer" + " " + localStorage.jwt,
},
success: function (res) {
sessionStorage.commentData = JSON.stringify(res.data);
setIndexData();
success: handleSuccess,
});
},
});
}

View File

@@ -1,11 +1,18 @@
# Theme Configurator 插件
此插件演示如何通过后台插件的方式为 BlackFruit-UI 主题提供可配置能力支持设置导航、页脚、站点信息、SEO、首页轮播以及右侧浮窗并提供 `/console/v1/theme/config` 接口与前端联动。
此插件演示如何通过后台插件的方式为 BlackFruit-UI 主题提供可配置能力支持设置导航、页脚、站点信息、SEO、首页轮播、友情链接、荣誉/合作伙伴、反馈类型以及右侧浮窗,并提供 `/console/v1/theme/config` 接口与前端联动。
## 功能
- 后台界面(`template/admin/index.html` JSON 的形式集中维护所有主题参数
- 后台界面(`template/admin/index.html`通过表单 + JSON 的方式维护主题参数
- SEO、站点基础信息企业名称、电话、备案、协议链接、产品链接等
- 首页轮播 Banner
- 友情链接friendly_link
- 企业荣誉honor、合作伙伴/成功案例partner
- 反馈类型feedback_type
- 右侧浮窗side / side_floating_window
- 复杂导航结构header_nav/footer_nav可在“高级配置 (JSON)”中维护。
- 接口 `GET/POST /{DIR_ADMIN}/v1/theme/config` 提供配置读取与保存;
- 前台接口 `GET /console/v1/theme/config` 输出与 `/console/v1/common` 同结构的数据BlackFruit-UI 可以直接接入;
- 前台接口 `GET /console/v1/theme/config` 输出与 `/console/v1/common` 同结构的数据BlackFruit-UI 可以直接接入;
- 插件安装时创建 `addon_theme_configurator` 表并写入默认配置。
## 目录

View File

@@ -45,6 +45,10 @@ class ThemeController extends PluginAdminBaseController
'friendly_link' => $param['friendly_link'] ?? [],
'banner' => $param['banner'] ?? [],
'side' => $param['side'] ?? [],
// 额外配置:荣誉、合作伙伴、反馈类型
'feedback_type' => $param['feedback_type'] ?? [],
'honor' => $param['honor'] ?? [],
'partner' => $param['partner'] ?? [],
];
$config = $this->model->saveConfig($payload);

View File

@@ -42,14 +42,15 @@ class ThemeController extends PluginBaseController
'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'] ?? [],
// 以下字段需与 /console/v1/common 保持一致的扁平结构
'honor' => $config['honor'] ?? ($config['site_config']['honor'] ?? []),
'partner' => $config['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'] ?? [],
'feedback_type' => $config['feedback_type'] ?? ($config['site_config']['feedback_type'] ?? []),
];
return json([

View File

@@ -80,8 +80,10 @@ class ThemeConfigModel
'keywords' => '云服务器,主题云,BlackFruit',
'description' => 'BlackFruit-UI 默认站点说明',
],
// 顶部/底部导航
'header_nav' => [],
'footer_nav' => [],
// 站点基础信息
'site_config' => [
'enterprise_name' => '主题云',
'enterprise_telephone' => '400-000-0000',
@@ -99,9 +101,14 @@ class ThemeConfigModel
'cloud_product_link' => '/cart/goods.htm?id=1',
'dcim_product_link' => '/cart/goods.htm?id=2',
],
// 友情链接、轮播、侧边浮窗等
'friendly_link' => [],
'banner' => [],
'side' => [],
// 用于 /console/v1/common 的扩展字段
'feedback_type' => [],
'honor' => [],
'partner' => [],
];
}
}

View File

@@ -61,6 +61,26 @@
v-model="fullConfig.site_config.public_security_network_preparation_link"
placeholder="https://beian.mps.gov.cn"></t-input>
</div>
<div class="form-item">
<label>电信增值许可证</label>
<t-input v-model="fullConfig.site_config.telecom_appreciation" placeholder="增值电信业务经营许可证"></t-input>
</div>
<div class="form-item">
<label>用户协议链接</label>
<t-input v-model="fullConfig.site_config.terms_service_url" placeholder="/agreement/service.html"></t-input>
</div>
<div class="form-item">
<label>隐私政策链接</label>
<t-input v-model="fullConfig.site_config.terms_privacy_url" placeholder="/agreement/privacy.html"></t-input>
</div>
<div class="form-item">
<label>云产品购买链接</label>
<t-input v-model="fullConfig.site_config.cloud_product_link" placeholder="/cart/goods.htm?id=1"></t-input>
</div>
<div class="form-item">
<label>物理机/DCIM 链接</label>
<t-input v-model="fullConfig.site_config.dcim_product_link" placeholder="/cart/goods.htm?id=2"></t-input>
</div>
<div class="form-item form-item--full">
<label>版权信息</label>
<t-input v-model="fullConfig.site_config.copyright_info" placeholder="© 2024 主题云"></t-input>
@@ -115,9 +135,146 @@
<t-button theme="primary" variant="outline" @click="addBanner">新增轮播</t-button>
</t-card>
<t-card class="theme-card" title="友情链接" bordered>
<div v-if="!fullConfig.friendly_link.length" class="empty-tip">还没有友情链接,点击下方按钮添加。</div>
<div class="config-item" v-for="(item, index) in fullConfig.friendly_link" :key="index">
<div class="config-item__header">
<h4>链接 {{ index + 1 }}</h4>
<t-button size="small" theme="danger" variant="outline" @click="removeFriendlyLink(index)">
删除
</t-button>
</div>
<div class="form-grid">
<div class="form-item">
<label>名称</label>
<t-input v-model="item.name" placeholder="合作伙伴名称"></t-input>
</div>
<div class="form-item form-item--full">
<label>链接地址</label>
<t-input v-model="item.url" placeholder="https://example.com"></t-input>
</div>
</div>
</div>
<t-button theme="primary" variant="outline" @click="addFriendlyLink">新增友情链接</t-button>
</t-card>
<t-card class="theme-card" title="荣誉与合作伙伴" bordered>
<h4 class="sub-title">企业荣誉</h4>
<div v-if="!fullConfig.honor.length" class="empty-tip">用于首页“荣誉资质”模块honor</div>
<div class="config-item" v-for="(item, index) in fullConfig.honor" :key="'honor-' + index">
<div class="config-item__header">
<h4>荣誉 {{ index + 1 }}</h4>
<t-button size="small" theme="danger" variant="outline" @click="removeHonor(index)">
删除
</t-button>
</div>
<div class="form-grid">
<div class="form-item">
<label>名称</label>
<t-input v-model="item.name" placeholder="高新技术企业"></t-input>
</div>
<div class="form-item form-item--full">
<label>图片地址</label>
<t-input v-model="item.img" placeholder="/upload/honor.png"></t-input>
</div>
</div>
</div>
<t-button theme="primary" variant="outline" @click="addHonor">新增荣誉</t-button>
<h4 class="sub-title mt-10">合作伙伴/成功案例</h4>
<div v-if="!fullConfig.partner.length" class="empty-tip">用于首页“典型案例/合作伙伴”模块partner</div>
<div class="config-item" v-for="(item, index) in fullConfig.partner" :key="'partner-' + index">
<div class="config-item__header">
<h4>伙伴 {{ index + 1 }}</h4>
<t-button size="small" theme="danger" variant="outline" @click="removePartner(index)">
删除
</t-button>
</div>
<div class="form-grid">
<div class="form-item">
<label>名称</label>
<t-input v-model="item.name" placeholder="合作伙伴/客户名称"></t-input>
</div>
<div class="form-item">
<label>图片地址</label>
<t-input v-model="item.img" placeholder="/upload/partner.png"></t-input>
</div>
<div class="form-item form-item--full">
<label>描述</label>
<t-textarea v-model="item.description" :autosize="{ minRows: 2, maxRows: 3 }" placeholder="一句话介绍该案例"></t-textarea>
</div>
</div>
</div>
<t-button theme="primary" variant="outline" @click="addPartner">新增合作伙伴</t-button>
</t-card>
<t-card class="theme-card" title="反馈类型" bordered>
<p class="theme-tip">
对应 <code>/console/v1/feedback</code> 的类型选项ID 需与后端保持一致,仅建议修改名称与描述。
</p>
<div v-if="!fullConfig.feedback_type.length" class="empty-tip">还没有反馈类型,点击下方按钮添加。</div>
<div class="config-item" v-for="(item, index) in fullConfig.feedback_type" :key="'feedback-' + index">
<div class="config-item__header">
<h4>类型 {{ index + 1 }}</h4>
<t-button size="small" theme="danger" variant="outline" @click="removeFeedbackType(index)">
删除
</t-button>
</div>
<div class="form-grid">
<div class="form-item">
<label>类型 ID</label>
<t-input v-model="item.id" placeholder="1"></t-input>
</div>
<div class="form-item">
<label>名称</label>
<t-input v-model="item.name" placeholder="产品建议"></t-input>
</div>
<div class="form-item form-item--full">
<label>描述</label>
<t-input v-model="item.description" placeholder="用于产品体验、功能建议等"></t-input>
</div>
</div>
</div>
<t-button theme="primary" variant="outline" @click="addFeedbackType">新增反馈类型</t-button>
</t-card>
<t-card class="theme-card" title="侧边浮窗" bordered>
<p class="theme-tip">
对应前台右侧悬浮工具条(电话咨询/在线客服/提交工单等),结构与模板中的 <code>side_floating_window</code> 一致。
</p>
<div v-if="!fullConfig.side.length" class="empty-tip">还没有侧边浮窗,点击下方按钮添加。</div>
<div class="config-item" v-for="(item, index) in fullConfig.side" :key="'side-' + index">
<div class="config-item__header">
<h4>浮窗 {{ index + 1 }}</h4>
<t-button size="small" theme="danger" variant="outline" @click="removeSide(index)">
删除
</t-button>
</div>
<div class="form-grid">
<div class="form-item">
<label>名称</label>
<t-input v-model="item.name" placeholder="电话咨询"></t-input>
</div>
<div class="form-item">
<label>图标地址</label>
<t-input v-model="item.icon" placeholder="/upload/side-phone.png"></t-input>
</div>
<div class="form-item form-item--full">
<label>内容(支持 HTML</label>
<t-textarea
v-model="item.content"
:autosize="{ minRows: 2, maxRows: 4 }"
placeholder="<p>7x24 小时不间断服务</p>"
></t-textarea>
</div>
</div>
</div>
<t-button theme="primary" variant="outline" @click="addSide">新增浮窗</t-button>
</t-card>
<t-card class="theme-card" title="高级配置 (JSON)" bordered>
<p class="theme-tip">
用于暂未在 UI 中开放的配置项(如导航结构、友情链接等)。可先点击“同步当前配置”再做修改
用于暂未在 UI 中开放的配置项(如复杂导航结构 header_nav/footer_nav 等)。如非必要,建议优先使用上方表单编辑
</p>
<t-textarea v-model="advancedText" :autosize="{ minRows: 14 }" class="theme-textarea"></t-textarea>
<div class="mt-10">
@@ -168,6 +325,10 @@
public_security_network_preparation_link: "",
telecom_appreciation: "",
copyright_info: "",
terms_service_url: "",
terms_privacy_url: "",
cloud_product_link: "",
dcim_product_link: "",
},
banner: [],
header_nav: [],
@@ -175,6 +336,8 @@
friendly_link: [],
side: [],
feedback_type: [],
honor: [],
partner: [],
});
new Vue({
@@ -219,7 +382,21 @@
: Array.isArray(data.side_floating_window)
? data.side_floating_window
: [],
feedback_type: Array.isArray(data.feedback_type) ? data.feedback_type : [],
feedback_type: Array.isArray(data.feedback_type)
? data.feedback_type
: Array.isArray(data.site_config && data.site_config.feedback_type)
? data.site_config.feedback_type
: [],
honor: Array.isArray(data.honor)
? data.honor
: Array.isArray(data.site_config && data.site_config.honor)
? data.site_config.honor
: [],
partner: Array.isArray(data.partner)
? data.partner
: Array.isArray(data.site_config && data.site_config.partner)
? data.site_config.partner
: [],
};
},
loadConfig() {
@@ -269,6 +446,69 @@
removeBanner(index) {
this.bannerList.splice(index, 1);
},
addFriendlyLink() {
if (!Array.isArray(this.fullConfig.friendly_link)) {
this.fullConfig.friendly_link = [];
}
this.fullConfig.friendly_link.push({
name: "",
url: "",
});
},
removeFriendlyLink(index) {
this.fullConfig.friendly_link.splice(index, 1);
},
addHonor() {
if (!Array.isArray(this.fullConfig.honor)) {
this.fullConfig.honor = [];
}
this.fullConfig.honor.push({
name: "",
img: "",
});
},
removeHonor(index) {
this.fullConfig.honor.splice(index, 1);
},
addPartner() {
if (!Array.isArray(this.fullConfig.partner)) {
this.fullConfig.partner = [];
}
this.fullConfig.partner.push({
name: "",
img: "",
description: "",
});
},
removePartner(index) {
this.fullConfig.partner.splice(index, 1);
},
addFeedbackType() {
if (!Array.isArray(this.fullConfig.feedback_type)) {
this.fullConfig.feedback_type = [];
}
this.fullConfig.feedback_type.push({
id: "",
name: "",
description: "",
});
},
removeFeedbackType(index) {
this.fullConfig.feedback_type.splice(index, 1);
},
addSide() {
if (!Array.isArray(this.fullConfig.side)) {
this.fullConfig.side = [];
}
this.fullConfig.side.push({
name: "",
icon: "",
content: "",
});
},
removeSide(index) {
this.fullConfig.side.splice(index, 1);
},
saveConfig() {
this.saving = true;
const payload = {

View File

@@ -54,6 +54,34 @@
font-size: 16px;
}
/* 通用列表块,复用轮播样式 */
.config-item {
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 16px;
margin-bottom: 16px;
background: #f9fafb;
}
.config-item__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.config-item__header h4 {
margin: 0;
font-size: 16px;
}
.sub-title {
margin: 0 0 8px;
font-size: 14px;
font-weight: 600;
color: #4b5563;
}
.empty-tip {
padding: 16px;
border: 1px dashed #cbd5f5;