This commit is contained in:
@@ -2,29 +2,134 @@
|
|||||||
|
|
||||||
<div id="theme-config-app" class="template" v-cloak>
|
<div id="theme-config-app" class="template" v-cloak>
|
||||||
<com-config>
|
<com-config>
|
||||||
<t-card class="theme-card">
|
<t-card class="theme-card" title="SEO 设置" bordered>
|
||||||
<div class="theme-card__header">
|
<div class="form-grid">
|
||||||
<div>
|
<div class="form-item">
|
||||||
<h3>主题配置</h3>
|
<label>站点标题</label>
|
||||||
<p class="theme-desc">
|
<t-input v-model="fullConfig.seo.title" placeholder="首页标题"></t-input>
|
||||||
在此集中维护 BlackFruit-UI 的导航、页脚、SEO、企业信息、轮播和侧边浮窗等配置。
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="form-item">
|
||||||
<t-button variant="outline" @click="loadConfig" :loading="loading">
|
<label>关键词</label>
|
||||||
刷新
|
<t-input v-model="fullConfig.seo.keywords" placeholder="关键词,逗号分隔"></t-input>
|
||||||
</t-button>
|
</div>
|
||||||
<t-button theme="primary" class="ml-10" @click="saveConfig" :loading="saving">
|
<div class="form-item form-item--full">
|
||||||
保存
|
<label>描述</label>
|
||||||
</t-button>
|
<t-textarea v-model="fullConfig.seo.description" :autosize="{ minRows: 2, maxRows: 4 }"
|
||||||
|
placeholder="站点描述"></t-textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<t-textarea v-model="configText" :autosize="{ minRows: 18, maxRows: 22 }" class="theme-textarea"
|
|
||||||
:placeholder="langPlaceholder"></t-textarea>
|
|
||||||
<p class="theme-tip">
|
|
||||||
JSON 结构与 `/console/v1/common` 返回值保持一致,保存后即可供前台调用。
|
|
||||||
</p>
|
|
||||||
</t-card>
|
</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.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 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="高级配置 (JSON)" bordered>
|
||||||
|
<p class="theme-tip">
|
||||||
|
用于暂未在 UI 中开放的配置项(如导航结构、友情链接等)。可先点击“同步当前配置”再做修改。
|
||||||
|
</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>
|
||||||
</com-config>
|
</com-config>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -35,20 +140,88 @@
|
|||||||
const adminPath = location.pathname.split("/")[1];
|
const adminPath = location.pathname.split("/")[1];
|
||||||
const base = `${host}/${adminPath}/v1/theme/config`;
|
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: "",
|
||||||
|
},
|
||||||
|
banner: [],
|
||||||
|
header_nav: [],
|
||||||
|
footer_nav: [],
|
||||||
|
friendly_link: [],
|
||||||
|
side: [],
|
||||||
|
feedback_type: [],
|
||||||
|
});
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
el: "#theme-config-app",
|
el: "#theme-config-app",
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
configText: "",
|
|
||||||
loading: false,
|
loading: false,
|
||||||
saving: false,
|
saving: false,
|
||||||
langPlaceholder: "请严格按照 JSON 格式填写主题配置...",
|
fullConfig: createDefaultConfig(),
|
||||||
|
advancedText: "",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
computed: {
|
||||||
this.loadConfig();
|
bannerList() {
|
||||||
|
if (!Array.isArray(this.fullConfig.banner)) {
|
||||||
|
this.fullConfig.banner = [];
|
||||||
|
}
|
||||||
|
return this.fullConfig.banner;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
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 : [],
|
||||||
|
};
|
||||||
|
},
|
||||||
loadConfig() {
|
loadConfig() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
axios
|
axios
|
||||||
@@ -59,23 +232,49 @@
|
|||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
const data = (res.data && res.data.data) || {};
|
const data = (res.data && res.data.data) || {};
|
||||||
this.configText = JSON.stringify(data, null, 2);
|
this.fullConfig = this.normalizeConfig(data);
|
||||||
|
this.syncJson();
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
saveConfig() {
|
syncJson() {
|
||||||
if (!this.configText.trim()) {
|
this.advancedText = JSON.stringify(this.fullConfig, null, 2);
|
||||||
return this.$message.warning("内容不能为空");
|
},
|
||||||
|
applyAdvanced() {
|
||||||
|
if (!this.advancedText.trim()) {
|
||||||
|
return showMessage(this, "warning", "JSON 内容为空");
|
||||||
}
|
}
|
||||||
let payload;
|
|
||||||
try {
|
try {
|
||||||
payload = JSON.parse(this.configText);
|
const parsed = JSON.parse(this.advancedText);
|
||||||
|
this.fullConfig = this.normalizeConfig(parsed);
|
||||||
|
showMessage(this, "success", "JSON 已应用");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return this.$message.error("JSON 解析失败:" + err.message);
|
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);
|
||||||
|
},
|
||||||
|
saveConfig() {
|
||||||
this.saving = true;
|
this.saving = true;
|
||||||
|
const payload = {
|
||||||
|
...this.fullConfig,
|
||||||
|
side_floating_window: this.fullConfig.side || [],
|
||||||
|
};
|
||||||
axios
|
axios
|
||||||
.post(base, payload, {
|
.post(base, payload, {
|
||||||
headers: {
|
headers: {
|
||||||
@@ -83,14 +282,17 @@
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
this.$message.success(res.data.msg || "保存成功");
|
showMessage(this, "success", (res.data && res.data.msg) || "保存成功");
|
||||||
this.configText = JSON.stringify(res.data.data, null, 2);
|
this.loadConfig();
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.saving = false;
|
this.saving = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.loadConfig();
|
||||||
|
},
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,22 +1,26 @@
|
|||||||
.theme-card {
|
.theme-card {
|
||||||
margin: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-card__header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-card__header h3 {
|
.form-grid {
|
||||||
margin: 0;
|
display: grid;
|
||||||
font-size: 20px;
|
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||||||
|
grid-gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-desc {
|
.form-item {
|
||||||
margin: 4px 0 0;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item--full {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item label {
|
||||||
|
font-size: 13px;
|
||||||
color: #6b7280;
|
color: #6b7280;
|
||||||
|
margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-textarea textarea {
|
.theme-textarea textarea {
|
||||||
@@ -24,11 +28,51 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.theme-tip {
|
.theme-tip {
|
||||||
margin-top: 12px;
|
margin-top: 0;
|
||||||
color: #9ca3af;
|
margin-bottom: 12px;
|
||||||
|
color: #94a3b8;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.banner-item {
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
background: #f9fafb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner-item__header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner-item__header h4 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-tip {
|
||||||
|
padding: 16px;
|
||||||
|
border: 1px dashed #cbd5f5;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #6b7280;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-bar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-10 {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.ml-10 {
|
.ml-10 {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user