This commit is contained in:
@@ -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,
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
17
js/about.js
17
js/about.js
@@ -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,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
18
js/index.js
18
js/index.js
@@ -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,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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` 表并写入默认配置。
|
||||
|
||||
## 目录
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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' => [],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user