Files
BlackFruit-UI/plugins/addon/theme_configurator/template/admin/index.html
yiqiu 74a008fe98
All checks were successful
continuous-integration/drone/push Build is passing
移植所有功能
2025-11-21 15:47:29 +08:00

542 lines
21 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<link rel="stylesheet" href="/plugins/addon/theme_configurator/template/admin/theme.css" />
<div id="theme-config-app" class="template" v-cloak>
<t-card class="theme-card" title="SEO 设置" bordered>
<div class="form-grid">
<div class="form-item">
<label>站点标题</label>
<t-input v-model="fullConfig.seo.title" placeholder="首页标题"></t-input>
</div>
<div class="form-item">
<label>关键词</label>
<t-input v-model="fullConfig.seo.keywords" placeholder="关键词,逗号分隔"></t-input>
</div>
<div class="form-item form-item--full">
<label>描述</label>
<t-textarea v-model="fullConfig.seo.description" :autosize="{ minRows: 2, maxRows: 4 }"
placeholder="站点描述"></t-textarea>
</div>
</div>
</t-card>
<t-card class="theme-card" title="企业信息" bordered>
<div class="form-grid">
<div class="form-item">
<label>企业名称</label>
<t-input v-model="fullConfig.site_config.enterprise_name" placeholder="主题云"></t-input>
</div>
<div class="form-item">
<label>联系电话</label>
<t-input v-model="fullConfig.site_config.enterprise_telephone" placeholder="400-000-0000"></t-input>
</div>
<div class="form-item">
<label>联系邮箱</label>
<t-input v-model="fullConfig.site_config.enterprise_mailbox" placeholder="support@example.com"></t-input>
</div>
<div class="form-item">
<label>Logo 地址</label>
<t-input v-model="fullConfig.site_config.official_website_logo" placeholder="/upload/logo.png"></t-input>
</div>
<div class="form-item">
<label>在线客服链接</label>
<t-input v-model="fullConfig.site_config.online_customer_service_link" placeholder="http://www.test.com"></t-input>
</div>
<div class="form-item">
<label>二维码地址</label>
<t-input v-model="fullConfig.site_config.enterprise_qrcode" placeholder="/upload/qrcode.png"></t-input>
</div>
<div class="form-item">
<label>ICP 号</label>
<t-input v-model="fullConfig.site_config.icp_info" placeholder="京ICP备XXXX号"></t-input>
</div>
<div class="form-item">
<label>ICP 链接</label>
<t-input v-model="fullConfig.site_config.icp_info_link" placeholder="https://beian.miit.gov.cn"></t-input>
</div>
<div class="form-item">
<label>公安备案号</label>
<t-input v-model="fullConfig.site_config.public_security_network_preparation"
placeholder="京公网安备XXXX号"></t-input>
</div>
<div class="form-item">
<label>公安备案链接</label>
<t-input
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>
</div>
</div>
</t-card>
<t-card class="theme-card" title="首页轮播" bordered>
<div v-if="!bannerList.length" class="empty-tip">还没有轮播图,点击下方按钮添加。</div>
<div class="banner-item" v-for="(banner, index) in bannerList" :key="index">
<div class="banner-item__header">
<h4>轮播 {{ index + 1 }}</h4>
<t-button size="small" theme="danger" variant="outline" @click="removeBanner(index)">
删除
</t-button>
</div>
<div class="form-grid">
<div class="form-item">
<label>标题</label>
<t-input v-model="banner.title" placeholder="如:弹性算力"></t-input>
</div>
<div class="form-item">
<label>描述</label>
<t-input v-model="banner.description" placeholder="一句宣传语"></t-input>
</div>
<div class="form-item form-item--full">
<label>图片地址</label>
<t-input v-model="banner.img" placeholder="/upload/banner.png"></t-input>
</div>
<div class="form-item">
<label>跳转链接</label>
<t-input v-model="banner.url" placeholder="/cloud.html"></t-input>
</div>
<div class="form-item">
<label>新窗口打开</label>
<t-switch size="large" v-model="banner.blank"></t-switch>
</div>
<div class="form-item">
<label>按钮文字</label>
<t-input v-model="banner.button_text" placeholder="立即查看"></t-input>
</div>
<div class="form-item">
<label>按钮链接</label>
<t-input v-model="banner.button_link" placeholder="/cloud.html"></t-input>
</div>
<div class="form-item">
<label>按钮新窗口</label>
<t-switch size="large" v-model="banner.button_blank"></t-switch>
</div>
</div>
</div>
<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 中开放的配置项(如复杂导航结构 header_nav/footer_nav 等)。如非必要,建议优先使用上方表单编辑。
</p>
<t-textarea v-model="advancedText" :autosize="{ minRows: 14 }" class="theme-textarea"></t-textarea>
<div class="mt-10">
<t-button variant="outline" @click="syncJson">同步当前配置</t-button>
<t-button class="ml-10" @click="applyAdvanced">应用 JSON</t-button>
</div>
</t-card>
<div class="action-bar">
<t-button variant="outline" @click="loadConfig" :loading="loading">重新加载</t-button>
<t-button theme="primary" class="ml-10" @click="saveConfig" :loading="saving">保存全部配置</t-button>
</div>
</div>
<script src="/plugins/addon/theme_configurator/template/admin/lang/index.js"></script>
<script src="/plugins/addon/theme_configurator/template/admin/js/axios.min.js"></script>
<script>
(function () {
const host = location.origin;
const adminPath = location.pathname.split("/")[1];
const base = `${host}/${adminPath}/v1/theme/config`;
const showMessage = (vm, type, text) => {
if (vm.$message && typeof vm.$message[type] === "function") {
vm.$message[type](text);
} else if (window.MessagePlugin && typeof window.MessagePlugin[type] === "function") {
window.MessagePlugin[type](text);
} else {
alert(text);
}
};
const createDefaultConfig = () => ({
seo: {
title: "",
keywords: "",
description: "",
},
site_config: {
enterprise_name: "",
enterprise_telephone: "",
enterprise_mailbox: "",
enterprise_qrcode: "",
official_website_logo: "",
icp_info: "",
icp_info_link: "",
public_security_network_preparation: "",
public_security_network_preparation_link: "",
telecom_appreciation: "",
copyright_info: "",
terms_service_url: "",
terms_privacy_url: "",
cloud_product_link: "",
dcim_product_link: "",
},
banner: [],
header_nav: [],
footer_nav: [],
friendly_link: [],
side: [],
feedback_type: [],
honor: [],
partner: [],
});
new Vue({
el: "#theme-config-app",
data() {
return {
loading: false,
saving: false,
fullConfig: createDefaultConfig(),
advancedText: "",
};
},
computed: {
bannerList() {
if (!Array.isArray(this.fullConfig.banner)) {
this.fullConfig.banner = [];
}
return this.fullConfig.banner;
},
},
methods: {
normalizeConfig(data = {}) {
const defaults = createDefaultConfig();
return {
...defaults,
...data,
seo: {
...defaults.seo,
...(data.seo || {}),
},
site_config: {
...defaults.site_config,
...(data.site_config || {}),
},
banner: Array.isArray(data.banner) ? data.banner : [],
header_nav: Array.isArray(data.header_nav) ? data.header_nav : [],
footer_nav: Array.isArray(data.footer_nav) ? data.footer_nav : [],
friendly_link: Array.isArray(data.friendly_link) ? data.friendly_link : [],
side:
Array.isArray(data.side)
? data.side
: Array.isArray(data.side_floating_window)
? data.side_floating_window
: [],
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() {
this.loading = true;
axios
.get(base, {
headers: {
Authorization: "Bearer " + localStorage.getItem("backJwt"),
},
})
.then((res) => {
const data = (res.data && res.data.data) || {};
this.fullConfig = this.normalizeConfig(data);
this.syncJson();
})
.finally(() => {
this.loading = false;
});
},
syncJson() {
this.advancedText = JSON.stringify(this.fullConfig, null, 2);
},
applyAdvanced() {
if (!this.advancedText.trim()) {
return showMessage(this, "warning", "JSON 内容为空");
}
try {
const parsed = JSON.parse(this.advancedText);
this.fullConfig = this.normalizeConfig(parsed);
showMessage(this, "success", "JSON 已应用");
} catch (err) {
showMessage(this, "error", "JSON 解析失败:" + err.message);
}
},
addBanner() {
this.bannerList.push({
title: "",
description: "",
img: "",
url: "",
blank: false,
button_text: "",
button_link: "",
button_blank: false,
});
},
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 = {
...this.fullConfig,
side_floating_window: this.fullConfig.side || [],
};
axios
.post(base, payload, {
headers: {
Authorization: "Bearer " + localStorage.getItem("backJwt"),
},
})
.then((res) => {
showMessage(this, "success", (res.data && res.data.msg) || "保存成功");
this.loadConfig();
})
.finally(() => {
this.saving = false;
});
},
},
mounted() {
this.loadConfig();
},
});
})();
</script>