feat: 新增主题配置管理页面,支持企业信息、侧边浮窗、反馈类型、SEO和首页轮播设置,并更新了样式。
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -1,10 +1,86 @@
|
||||
<link rel="stylesheet" href="/plugins/addon/theme_configurator/template/admin/theme.css" />
|
||||
|
||||
<div id="theme-config-app" class="template" v-cloak>
|
||||
<t-tabs v-model="activeTab" placement="top">
|
||||
<t-tab-panel value="basic" label="基础信息">
|
||||
<t-card class="theme-card" title="企业信息" bordered>
|
||||
<div class="form-grid">
|
||||
<div id="theme-config-app" class="admin-container" v-cloak>
|
||||
<!-- 顶部工具栏 -->
|
||||
<header class="admin-header">
|
||||
<div class="admin-header__left">
|
||||
<div class="admin-logo">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M2 17L12 22L22 17" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M2 12L12 17L22 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<span>黑果云模板控制器</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-header__right">
|
||||
<button class="btn btn-primary btn-lg" @click="saveConfig" :disabled="saving">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" v-if="!saving">
|
||||
<path d="M13.5 2.5H2.5V13.5H13.5V2.5Z" stroke="currentColor" stroke-width="1.5"/>
|
||||
<path d="M10.5 2.5V6.5H5.5V2.5" stroke="currentColor" stroke-width="1.5"/>
|
||||
<path d="M5.5 9.5H10.5V13.5H5.5V9.5Z" stroke="currentColor" stroke-width="1.5"/>
|
||||
</svg>
|
||||
<span v-if="saving">保存中...</span>
|
||||
<span v-else>保存全部配置</span>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 主体布局 -->
|
||||
<div class="admin-layout">
|
||||
<!-- 侧边栏导航 -->
|
||||
<aside class="admin-sidebar">
|
||||
<nav class="sidebar-nav">
|
||||
<a href="#basic" class="nav-item" :class="{active: activeSection === 'basic'}" @click="activeSection = 'basic'">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" stroke="currentColor" stroke-width="2"/>
|
||||
<line x1="3" y1="9" x2="21" y2="9" stroke="currentColor" stroke-width="2"/>
|
||||
<line x1="9" y1="21" x2="9" y2="9" stroke="currentColor" stroke-width="2"/>
|
||||
</svg>
|
||||
<span>基础配置</span>
|
||||
</a>
|
||||
<a href="#seo" class="nav-item" :class="{active: activeSection === 'seo'}" @click="activeSection = 'seo'">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
|
||||
<circle cx="11" cy="11" r="8" stroke="currentColor" stroke-width="2"/>
|
||||
<path d="M21 21L16.65 16.65" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
<span>SEO设置</span>
|
||||
</a>
|
||||
<a href="#home" class="nav-item" :class="{active: activeSection === 'home'}" @click="activeSection = 'home'">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M3 9L12 2L21 9V20C21 20.5304 20.7893 21.0391 20.4142 21.4142C20.0391 21.7893 19.5304 22 19 22H5C4.46957 22 3.96086 21.7893 3.58579 21.4142C3.21071 21.0391 3 20.5304 3 20V9Z" stroke="currentColor" stroke-width="2"/>
|
||||
</svg>
|
||||
<span>首页内容</span>
|
||||
</a>
|
||||
<a href="#nav" class="nav-item" :class="{active: activeSection === 'nav'}" @click="activeSection = 'nav'">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
|
||||
<line x1="3" y1="12" x2="21" y2="12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="3" y1="6" x2="21" y2="6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="3" y1="18" x2="21" y2="18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
<span>导航配置</span>
|
||||
</a>
|
||||
<a href="#advanced" class="nav-item" :class="{active: activeSection === 'advanced'}" @click="activeSection = 'advanced'">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
|
||||
<circle cx="12" cy="12" r="3" stroke="currentColor" stroke-width="2"/>
|
||||
<path d="M12 1V3M12 21V23M4.22 4.22L5.64 5.64M18.36 18.36L19.78 19.78M1 12H3M21 12H23M4.22 19.78L5.64 18.36M18.36 5.64L19.78 4.22" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
<span>高级设置</span>
|
||||
</a>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<main class="admin-main">
|
||||
|
||||
|
||||
<section id="basic" class="config-section" :class="{active: activeSection === 'basic'}">
|
||||
<div class="section-card">
|
||||
<div class="section-header">
|
||||
<h2>企业信息</h2>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<div class="form-fields">
|
||||
<div class="form-item">
|
||||
<label>企业名称</label>
|
||||
<t-input v-model="fullConfig.site_config.enterprise_name" placeholder="主题云"></t-input>
|
||||
@@ -17,9 +93,9 @@
|
||||
<label>联系邮箱</label>
|
||||
<t-input v-model="fullConfig.site_config.enterprise_mailbox" placeholder="support@example.com"></t-input>
|
||||
</div>
|
||||
<div class="form-item form-item--full">
|
||||
<div class="form-item form-item">
|
||||
<label>Logo 地址</label>
|
||||
<div class="upload-row">
|
||||
<div class="upload-control">
|
||||
<t-input v-model="fullConfig.site_config.official_website_logo" placeholder="/upload/logo.png"></t-input>
|
||||
<t-upload theme="custom" :action="uploadUrl" :headers="uploadHeaders"
|
||||
:format-response="uploadFormatResponse" :show-upload-progress="false" :max="1"
|
||||
@@ -30,9 +106,9 @@
|
||||
</t-upload>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item form-item--full">
|
||||
<div class="form-item form-item">
|
||||
<label>二维码地址</label>
|
||||
<div class="upload-row">
|
||||
<div class="upload-control">
|
||||
<t-input v-model="fullConfig.site_config.enterprise_qrcode" placeholder="/upload/qrcode.png"></t-input>
|
||||
<t-upload theme="custom" :action="uploadUrl" :headers="uploadHeaders"
|
||||
:format-response="uploadFormatResponse" :show-upload-progress="false" :max="1"
|
||||
@@ -86,19 +162,23 @@
|
||||
<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">
|
||||
<div class="form-item form-item">
|
||||
<label>版权信息</label>
|
||||
<t-input v-model="fullConfig.site_config.copyright_info" placeholder="© 2025 主题云"></t-input>
|
||||
</div>
|
||||
</div>
|
||||
</t-card>
|
||||
</div>
|
||||
|
||||
<t-card class="theme-card" title="侧边浮窗" bordered>
|
||||
<p class="theme-tip">
|
||||
<div class="section-card">
|
||||
<div class="section-header">
|
||||
<h2>侧边浮窗</h2>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<p class="alert alert-info">
|
||||
对应前台右侧悬浮工具条(电话咨询/在线客服/提交工单等),结构与模板中的
|
||||
<code>side_floating_window</code> 一致。
|
||||
</p>
|
||||
<div v-if="!fullConfig.side.length" class="empty-tip">
|
||||
<div v-if="!fullConfig.side.length" class="empty-state">
|
||||
还没有侧边浮窗,点击下方按钮添加。
|
||||
</div>
|
||||
<div class="config-item" v-for="(item, index) in fullConfig.side" :key="'side-' + index">
|
||||
@@ -108,14 +188,14 @@
|
||||
删除
|
||||
</t-button>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-fields">
|
||||
<div class="form-item">
|
||||
<label>名称</label>
|
||||
<t-input v-model="item.name" placeholder="电话咨询"></t-input>
|
||||
</div>
|
||||
<div class="form-item form-item--full">
|
||||
<div class="form-item form-item">
|
||||
<label>图标地址</label>
|
||||
<div class="upload-row">
|
||||
<div class="upload-control">
|
||||
<t-input v-model="item.icon" placeholder="/upload/side-phone.png"></t-input>
|
||||
<t-upload theme="custom" :action="uploadUrl" :headers="uploadHeaders"
|
||||
:format-response="uploadFormatResponse" :show-upload-progress="false" :max="1"
|
||||
@@ -126,7 +206,7 @@
|
||||
</t-upload>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item form-item--full">
|
||||
<div class="form-item form-item">
|
||||
<label>内容(支持 HTML)</label>
|
||||
<t-textarea v-model="item.content" :autosize="{ minRows: 2, maxRows: 4 }"
|
||||
placeholder="<p>7x24 小时不间断服务</p>"></t-textarea>
|
||||
@@ -134,13 +214,17 @@
|
||||
</div>
|
||||
</div>
|
||||
<t-button theme="primary" variant="outline" @click="addSide">新增浮窗</t-button>
|
||||
</t-card>
|
||||
</div>
|
||||
|
||||
<t-card class="theme-card" title="反馈类型" bordered>
|
||||
<p class="theme-tip">
|
||||
<div class="section-card">
|
||||
<div class="section-header">
|
||||
<h2>反馈类型</h2>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<p class="alert alert-info">
|
||||
对应 <code>/console/v1/feedback</code> 的类型选项,ID 需与后端保持一致,仅建议修改名称与描述。
|
||||
</p>
|
||||
<div v-if="!fullConfig.feedback_type.length" class="empty-tip">
|
||||
<div v-if="!fullConfig.feedback_type.length" class="empty-state">
|
||||
还没有反馈类型,点击下方按钮添加。
|
||||
</div>
|
||||
<div class="config-item" v-for="(item, index) in fullConfig.feedback_type" :key="'feedback-' + index">
|
||||
@@ -150,7 +234,7 @@
|
||||
删除
|
||||
</t-button>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-fields">
|
||||
<div class="form-item">
|
||||
<label>类型 ID</label>
|
||||
<t-input v-model="item.id" placeholder="1"></t-input>
|
||||
@@ -159,19 +243,23 @@
|
||||
<label>名称</label>
|
||||
<t-input v-model="item.name" placeholder="产品建议"></t-input>
|
||||
</div>
|
||||
<div class="form-item form-item--full">
|
||||
<div class="form-item form-item">
|
||||
<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-tab-panel>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<t-tab-panel value="seo" label="SEO 管理">
|
||||
<t-card class="theme-card" title="SEO 设置" bordered>
|
||||
<div class="form-grid">
|
||||
<section id="seo" class="config-section" :class="{active: activeSection === 'seo'}">
|
||||
<div class="section-card">
|
||||
<div class="section-header">
|
||||
<h2>SEO 设置</h2>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<div class="form-fields">
|
||||
<div class="form-item">
|
||||
<label>站点标题</label>
|
||||
<t-input v-model="fullConfig.seo.title" placeholder="首页标题"></t-input>
|
||||
@@ -180,18 +268,22 @@
|
||||
<label>关键词</label>
|
||||
<t-input v-model="fullConfig.seo.keywords" placeholder="关键词,逗号分隔"></t-input>
|
||||
</div>
|
||||
<div class="form-item form-item--full">
|
||||
<div class="form-item form-item">
|
||||
<label>描述</label>
|
||||
<t-textarea v-model="fullConfig.seo.description" :autosize="{ minRows: 2, maxRows: 4 }"
|
||||
placeholder="站点描述"></t-textarea>
|
||||
</div>
|
||||
</div>
|
||||
</t-card>
|
||||
</t-tab-panel>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<t-tab-panel value="home" label="首页内容">
|
||||
<t-card class="theme-card" title="首页轮播文案" bordered>
|
||||
<div v-if="!bannerList.length" class="empty-tip">
|
||||
<section id="home" class="config-section" :class="{active: activeSection === 'home'}">
|
||||
<div class="section-card">
|
||||
<div class="section-header">
|
||||
<h2>首页轮播文案</h2>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<div v-if="!bannerList.length" class="empty-state">
|
||||
还没有轮播文案,点击下方按钮添加。
|
||||
</div>
|
||||
<div class="banner-item" v-for="(banner, index) in bannerList" :key="index">
|
||||
@@ -201,14 +293,14 @@
|
||||
删除
|
||||
</t-button>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-fields">
|
||||
<div class="form-item">
|
||||
<label>标题</label>
|
||||
<t-input v-model="banner.title" placeholder="如:弹性算力"></t-input>
|
||||
</div>
|
||||
<div class="form-item form-item--full">
|
||||
<div class="form-item form-item">
|
||||
<label>图片地址</label>
|
||||
<div class="upload-row">
|
||||
<div class="upload-control">
|
||||
<t-input v-model="banner.img" placeholder="/upload/banner-1.png"></t-input>
|
||||
<t-upload theme="custom" :action="uploadUrl" :headers="uploadHeaders"
|
||||
:format-response="uploadFormatResponse" :show-upload-progress="false" :max="1"
|
||||
@@ -223,7 +315,7 @@
|
||||
<label>描述</label>
|
||||
<t-input v-model="banner.description" placeholder="一句宣传语"></t-input>
|
||||
</div>
|
||||
<div class="form-item form-item--full">
|
||||
<div class="form-item form-item">
|
||||
<label>标签</label>
|
||||
<t-input v-model="banner.tags" placeholder="如:高速,低价,安全(多个标签用逗号分隔)"></t-input>
|
||||
</div>
|
||||
@@ -231,7 +323,7 @@
|
||||
<label>跳转链接</label>
|
||||
<t-input v-model="banner.url" placeholder="/cloud.html"></t-input>
|
||||
</div>
|
||||
<div class="form-item form-item--switch">
|
||||
<div class="form-item form-switch">
|
||||
<label><span class="switch-label-icon">🔗</span>新窗口</label>
|
||||
<t-switch size="large" v-model="banner.blank"></t-switch>
|
||||
</div>
|
||||
@@ -243,17 +335,21 @@
|
||||
<label>按钮链接</label>
|
||||
<t-input v-model="banner.button_link" placeholder="/cloud.html"></t-input>
|
||||
</div>
|
||||
<div class="form-item form-item--switch">
|
||||
<div class="form-item form-switch">
|
||||
<label><span class="switch-label-icon">🔗</span>按钮新窗口</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>
|
||||
</div>
|
||||
|
||||
<t-card class="theme-card" title="企业资质与荣誉" bordered>
|
||||
<div v-if="!fullConfig.honor.length" class="empty-tip">
|
||||
<div class="section-card">
|
||||
<div class="section-header">
|
||||
<h2>企业资质与荣誉</h2>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<div v-if="!fullConfig.honor.length" class="empty-state">
|
||||
用于首页"荣誉资质"模块(honor)。
|
||||
</div>
|
||||
<div class="config-item" v-for="(item, index) in fullConfig.honor" :key="'honor-' + index">
|
||||
@@ -263,14 +359,14 @@
|
||||
删除
|
||||
</t-button>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-fields">
|
||||
<div class="form-item">
|
||||
<label>名称</label>
|
||||
<t-input v-model="item.name" placeholder="高新技术企业"></t-input>
|
||||
</div>
|
||||
<div class="form-item form-item--full">
|
||||
<div class="form-item form-item">
|
||||
<label>图片地址</label>
|
||||
<div class="upload-row">
|
||||
<div class="upload-control">
|
||||
<t-input v-model="item.img" placeholder="/upload/honor.png"></t-input>
|
||||
<t-upload theme="custom" :action="uploadUrl" :headers="uploadHeaders"
|
||||
:format-response="uploadFormatResponse" :show-upload-progress="false" :max="1"
|
||||
@@ -284,24 +380,28 @@
|
||||
</div>
|
||||
</div>
|
||||
<t-button theme="primary" variant="outline" @click="addHonor">新增荣誉</t-button>
|
||||
</t-card>
|
||||
</div>
|
||||
|
||||
</t-tab-panel>
|
||||
</section>
|
||||
|
||||
<t-tab-panel value="nav" label="导航配置">
|
||||
<t-card class="theme-card" title="顶部导航(header_nav)" bordered>
|
||||
<p class="theme-tip">
|
||||
<section id="nav" class="config-section" :class="{active: activeSection === 'nav'}">
|
||||
<div class="section-card">
|
||||
<div class="section-header">
|
||||
<h2>顶部导航(header_nav)</h2>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<p class="alert alert-info">
|
||||
控制站点顶部导航栏及其下拉菜单结构。第一个导航项用作 Logo 点击跳转地址。
|
||||
</p>
|
||||
<div class="form-grid">
|
||||
<div class="form-item form-item--full">
|
||||
<div class="form-fields">
|
||||
<div class="form-item form-item">
|
||||
<label>Logo 点击跳转地址</label>
|
||||
<t-input v-model="homeNav.file_address" placeholder="index.html 或 /index.html"></t-input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="sub-title mt-10">一级导航</h4>
|
||||
<div v-if="headerNavList.length <= 1" class="empty-tip">
|
||||
<div v-if="headerNavList.length <= 1" class="empty-state">
|
||||
还没有自定义导航,请点击下方按钮新增。
|
||||
</div>
|
||||
<div class="config-item" v-for="(item, index) in headerNavList" :key="'nav-' + index" v-if="index > 0">
|
||||
@@ -311,7 +411,7 @@
|
||||
删除
|
||||
</t-button>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-fields">
|
||||
<div class="form-item">
|
||||
<label>名称</label>
|
||||
<t-input v-model="item.name" placeholder="如:产品"></t-input>
|
||||
@@ -320,14 +420,14 @@
|
||||
<label>点击链接(可选)</label>
|
||||
<t-input v-model="item.file_address" placeholder="/cloud.html"></t-input>
|
||||
</div>
|
||||
<div class="form-item form-item--switch">
|
||||
<div class="form-item form-switch">
|
||||
<label><span class="switch-label-icon">🔗</span>新窗口</label>
|
||||
<t-switch v-model="item.blank"></t-switch>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="sub-title mt-10">下拉子菜单</h4>
|
||||
<div v-if="!getHeaderChildren(item).length" class="empty-tip">
|
||||
<div v-if="!getHeaderChildren(item).length" class="empty-state">
|
||||
还没有子菜单,点击下方按钮新增。
|
||||
</div>
|
||||
<div class="config-item" v-for="(child, cIndex) in getHeaderChildren(item)"
|
||||
@@ -338,7 +438,7 @@
|
||||
删除
|
||||
</t-button>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-fields">
|
||||
<div class="form-item">
|
||||
<label>名称</label>
|
||||
<t-input v-model="child.name" placeholder="如:云服务器"></t-input>
|
||||
@@ -347,13 +447,13 @@
|
||||
<label>链接地址</label>
|
||||
<t-input v-model="child.file_address" placeholder="/cloud.html"></t-input>
|
||||
</div>
|
||||
<div class="form-item form-item--switch">
|
||||
<div class="form-item form-switch">
|
||||
<label><span class="switch-label-icon">🔗</span>新窗口</label>
|
||||
<t-switch v-model="child.blank"></t-switch>
|
||||
</div>
|
||||
<div class="form-item form-item--full">
|
||||
<div class="form-item form-item">
|
||||
<label>图标地址</label>
|
||||
<div class="upload-row">
|
||||
<div class="upload-control">
|
||||
<t-input v-model="child.icon" placeholder="/upload/nav-icon.png"></t-input>
|
||||
<t-upload theme="custom" :action="uploadUrl" :headers="uploadHeaders"
|
||||
:format-response="uploadFormatResponse" :show-upload-progress="false" :max="1" @success="
|
||||
@@ -365,7 +465,7 @@
|
||||
</t-upload>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item form-item--full">
|
||||
<div class="form-item form-item">
|
||||
<label>描述</label>
|
||||
<t-textarea v-model="child.description" :autosize="{ minRows: 2, maxRows: 3 }"
|
||||
placeholder="如:高可用的弹性计算服务"></t-textarea>
|
||||
@@ -379,16 +479,20 @@
|
||||
<t-button class="mt-10" theme="primary" variant="outline" @click="addHeaderNav">
|
||||
新增一级导航
|
||||
</t-button>
|
||||
</t-card>
|
||||
</t-tab-panel>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<t-tab-panel value="footer" label="底部栏目">
|
||||
<t-card class="theme-card" title="底部栏目(footer_nav)" bordered>
|
||||
<p class="theme-tip">
|
||||
<section id="footer" class="config-section" :class="{active: activeSection === 'footer'}">
|
||||
<div class="section-card">
|
||||
<div class="section-header">
|
||||
<h2>底部栏目(footer_nav)</h2>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<p class="alert alert-info">
|
||||
控制首页底部多列链接(如【热门产品】【客户支持】等),结构与模板中的
|
||||
<code>footer_nav</code> 一致。
|
||||
</p>
|
||||
<div v-if="!footerNavList.length" class="empty-tip">
|
||||
<div v-if="!footerNavList.length" class="empty-state">
|
||||
还没有底部栏目,点击下方按钮新增。
|
||||
</div>
|
||||
<div class="config-item" v-for="(group, index) in footerNavList" :key="'footer-' + index">
|
||||
@@ -398,14 +502,14 @@
|
||||
删除
|
||||
</t-button>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-fields">
|
||||
<div class="form-item">
|
||||
<label>栏目标题</label>
|
||||
<t-input v-model="group.name" placeholder="如:热门产品"></t-input>
|
||||
</div>
|
||||
</div>
|
||||
<h4 class="sub-title mt-10">栏目链接</h4>
|
||||
<div v-if="!getFooterChildren(group).length" class="empty-tip">
|
||||
<div v-if="!getFooterChildren(group).length" class="empty-state">
|
||||
还没有链接,点击下方按钮新增。
|
||||
</div>
|
||||
<div class="config-item" v-for="(link, cIndex) in getFooterChildren(group)"
|
||||
@@ -416,7 +520,7 @@
|
||||
删除
|
||||
</t-button>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-fields">
|
||||
<div class="form-item">
|
||||
<label>名称</label>
|
||||
<t-input v-model="link.name" placeholder="如:云服务器"></t-input>
|
||||
@@ -425,7 +529,7 @@
|
||||
<label>链接地址</label>
|
||||
<t-input v-model="link.url" placeholder="/cloud.html"></t-input>
|
||||
</div>
|
||||
<div class="form-item form-item--switch">
|
||||
<div class="form-item form-switch">
|
||||
<label><span class="switch-label-icon">🔗</span>新窗口</label>
|
||||
<t-switch v-model="link.blank"></t-switch>
|
||||
</div>
|
||||
@@ -438,10 +542,14 @@
|
||||
<t-button class="mt-10" theme="primary" variant="outline" @click="addFooterNav">
|
||||
新增栏目
|
||||
</t-button>
|
||||
</t-card>
|
||||
</div>
|
||||
|
||||
<t-card class="theme-card" title="友情链接" bordered>
|
||||
<div v-if="!fullConfig.friendly_link.length" class="empty-tip">
|
||||
<div class="section-card">
|
||||
<div class="section-header">
|
||||
<h2>友情链接</h2>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<div v-if="!fullConfig.friendly_link.length" class="empty-state">
|
||||
还没有友情链接,点击下方按钮添加。
|
||||
</div>
|
||||
<div class="config-item" v-for="(item, index) in fullConfig.friendly_link" :key="index">
|
||||
@@ -451,33 +559,37 @@
|
||||
删除
|
||||
</t-button>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-fields">
|
||||
<div class="form-item">
|
||||
<label>名称</label>
|
||||
<t-input v-model="item.name" placeholder="合作伙伴名称"></t-input>
|
||||
</div>
|
||||
<div class="form-item form-item--full">
|
||||
<div class="form-item form-item">
|
||||
<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-tab-panel>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<t-tab-panel v-if="showAdvanced" value="advanced" label="高级设置(JSON)">
|
||||
<t-card class="theme-card" title="高级配置 (JSON)" bordered>
|
||||
<p class="theme-tip">
|
||||
<div class="section-card">
|
||||
<div class="section-header">
|
||||
<h2>高级配置 (JSON)</h2>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<p class="alert alert-info">
|
||||
用于暂未在 UI 中开放的配置项(如复杂导航结构 header_nav/footer_nav 等)。如非必要,建议优先使用上方表单编辑。
|
||||
</p>
|
||||
<t-textarea v-model="advancedText" :autosize="{ minRows: 14 }" class="theme-textarea"></t-textarea>
|
||||
<t-textarea v-model="advancedText" :autosize="{ minRows: 14 }" class="json-editor"></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>
|
||||
</t-tab-panel>
|
||||
</div>
|
||||
</section>
|
||||
</t-tabs>
|
||||
|
||||
<div class="action-bar">
|
||||
@@ -491,8 +603,16 @@
|
||||
</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 src="/plugins/addon/theme_configurator/template/admin/lang/index.js"> </main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</script>
|
||||
<script src="/plugins/addon/theme_configurator/template/admin/js/axios.min.js"> </main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</script>
|
||||
<script>
|
||||
(function () {
|
||||
const host = location.origin;
|
||||
@@ -552,7 +672,7 @@
|
||||
fullConfig: createDefaultConfig(),
|
||||
advancedText: "",
|
||||
showAdvanced: false,
|
||||
activeTab: "basic",
|
||||
activeSection: "basic",
|
||||
uploadUrl: `${host}/${adminPath}/v1/upload`,
|
||||
uploadHeaders: {
|
||||
Authorization: "Bearer " + localStorage.getItem("backJwt"),
|
||||
@@ -911,4 +1031,8 @@
|
||||
},
|
||||
});
|
||||
})();
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,914 @@
|
||||
<link rel="stylesheet" href="/plugins/addon/theme_configurator/template/admin/theme.css" />
|
||||
|
||||
<div id="theme-config-app" class="template" v-cloak>
|
||||
<t-tabs v-model="activeTab" placement="top">
|
||||
<t-tab-panel value="basic" label="基础信息">
|
||||
<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 form-item--full">
|
||||
<label>Logo 地址</label>
|
||||
<div class="upload-row">
|
||||
<t-input v-model="fullConfig.site_config.official_website_logo" placeholder="/upload/logo.png"></t-input>
|
||||
<t-upload theme="custom" :action="uploadUrl" :headers="uploadHeaders"
|
||||
:format-response="uploadFormatResponse" :show-upload-progress="false" :max="1"
|
||||
@success="(ctx) => handleUpload(['site_config', 'official_website_logo'], ctx)">
|
||||
<t-button size="small" class="ml-10">
|
||||
<t-icon name="upload" size="small" /> 上传
|
||||
</t-button>
|
||||
</t-upload>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item form-item--full">
|
||||
<label>二维码地址</label>
|
||||
<div class="upload-row">
|
||||
<t-input v-model="fullConfig.site_config.enterprise_qrcode" placeholder="/upload/qrcode.png"></t-input>
|
||||
<t-upload theme="custom" :action="uploadUrl" :headers="uploadHeaders"
|
||||
:format-response="uploadFormatResponse" :show-upload-progress="false" :max="1"
|
||||
@success="(ctx) => handleUpload(['site_config', 'enterprise_qrcode'], ctx)">
|
||||
<t-button size="small" class="ml-10">
|
||||
<t-icon name="upload" size="small" /> 上传
|
||||
</t-button>
|
||||
</t-upload>
|
||||
</div>
|
||||
</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>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="© 2025 主题云"></t-input>
|
||||
</div>
|
||||
</div>
|
||||
</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 form-item--full">
|
||||
<label>图标地址</label>
|
||||
<div class="upload-row">
|
||||
<t-input v-model="item.icon" placeholder="/upload/side-phone.png"></t-input>
|
||||
<t-upload theme="custom" :action="uploadUrl" :headers="uploadHeaders"
|
||||
:format-response="uploadFormatResponse" :show-upload-progress="false" :max="1"
|
||||
@success="(ctx) => handleUpload(['side', index, 'icon'], ctx)">
|
||||
<t-button size="small" class="ml-10">
|
||||
<t-icon name="upload" size="small" /> 上传
|
||||
</t-button>
|
||||
</t-upload>
|
||||
</div>
|
||||
</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="反馈类型" 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-tab-panel>
|
||||
|
||||
<t-tab-panel value="seo" label="SEO 管理">
|
||||
<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-tab-panel>
|
||||
|
||||
<t-tab-panel value="home" label="首页内容">
|
||||
<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 form-item--full">
|
||||
<label>图片地址</label>
|
||||
<div class="upload-row">
|
||||
<t-input v-model="banner.img" placeholder="/upload/banner-1.png"></t-input>
|
||||
<t-upload theme="custom" :action="uploadUrl" :headers="uploadHeaders"
|
||||
:format-response="uploadFormatResponse" :show-upload-progress="false" :max="1"
|
||||
@success="(ctx) => handleUpload(['banner', index, 'img'], ctx)">
|
||||
<t-button size="small" class="ml-10">
|
||||
<t-icon name="upload" size="small" /> 上传
|
||||
</t-button>
|
||||
</t-upload>
|
||||
</div>
|
||||
</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.tags" placeholder="如:高速,低价,安全(多个标签用逗号分隔)"></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 form-item--switch">
|
||||
<label><span class="switch-label-icon">🔗</span>新窗口</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 form-item--switch">
|
||||
<label><span class="switch-label-icon">🔗</span>按钮新窗口</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.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>
|
||||
<div class="upload-row">
|
||||
<t-input v-model="item.img" placeholder="/upload/honor.png"></t-input>
|
||||
<t-upload theme="custom" :action="uploadUrl" :headers="uploadHeaders"
|
||||
:format-response="uploadFormatResponse" :show-upload-progress="false" :max="1"
|
||||
@success="(ctx) => handleUpload(['honor', index, 'img'], ctx)">
|
||||
<t-button size="small" class="ml-10">
|
||||
<t-icon name="upload" size="small" /> 上传
|
||||
</t-button>
|
||||
</t-upload>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<t-button theme="primary" variant="outline" @click="addHonor">新增荣誉</t-button>
|
||||
</t-card>
|
||||
|
||||
</t-tab-panel>
|
||||
|
||||
<t-tab-panel value="nav" label="导航配置">
|
||||
<t-card class="theme-card" title="顶部导航(header_nav)" bordered>
|
||||
<p class="theme-tip">
|
||||
控制站点顶部导航栏及其下拉菜单结构。第一个导航项用作 Logo 点击跳转地址。
|
||||
</p>
|
||||
<div class="form-grid">
|
||||
<div class="form-item form-item--full">
|
||||
<label>Logo 点击跳转地址</label>
|
||||
<t-input v-model="homeNav.file_address" placeholder="index.html 或 /index.html"></t-input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="sub-title mt-10">一级导航</h4>
|
||||
<div v-if="headerNavList.length <= 1" class="empty-tip">
|
||||
还没有自定义导航,请点击下方按钮新增。
|
||||
</div>
|
||||
<div class="config-item" v-for="(item, index) in headerNavList" :key="'nav-' + index" v-if="index > 0">
|
||||
<div class="config-item__header">
|
||||
<h4>导航 {{ index }}</h4>
|
||||
<t-button size="small" theme="danger" variant="outline" @click="removeHeaderNav(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.file_address" placeholder="/cloud.html"></t-input>
|
||||
</div>
|
||||
<div class="form-item form-item--switch">
|
||||
<label><span class="switch-label-icon">🔗</span>新窗口</label>
|
||||
<t-switch v-model="item.blank"></t-switch>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="sub-title mt-10">下拉子菜单</h4>
|
||||
<div v-if="!getHeaderChildren(item).length" class="empty-tip">
|
||||
还没有子菜单,点击下方按钮新增。
|
||||
</div>
|
||||
<div class="config-item" v-for="(child, cIndex) in getHeaderChildren(item)"
|
||||
:key="'nav-' + index + '-child-' + cIndex">
|
||||
<div class="config-item__header">
|
||||
<h4>子菜单 {{ cIndex + 1 }}</h4>
|
||||
<t-button size="small" theme="danger" variant="outline" @click="removeHeaderNavChild(index, cIndex)">
|
||||
删除
|
||||
</t-button>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-item">
|
||||
<label>名称</label>
|
||||
<t-input v-model="child.name" placeholder="如:云服务器"></t-input>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label>链接地址</label>
|
||||
<t-input v-model="child.file_address" placeholder="/cloud.html"></t-input>
|
||||
</div>
|
||||
<div class="form-item form-item--switch">
|
||||
<label><span class="switch-label-icon">🔗</span>新窗口</label>
|
||||
<t-switch v-model="child.blank"></t-switch>
|
||||
</div>
|
||||
<div class="form-item form-item--full">
|
||||
<label>图标地址</label>
|
||||
<div class="upload-row">
|
||||
<t-input v-model="child.icon" placeholder="/upload/nav-icon.png"></t-input>
|
||||
<t-upload theme="custom" :action="uploadUrl" :headers="uploadHeaders"
|
||||
:format-response="uploadFormatResponse" :show-upload-progress="false" :max="1" @success="
|
||||
(ctx) => handleUpload(['header_nav', index, 'children', cIndex, 'icon'], ctx)
|
||||
">
|
||||
<t-button size="small" class="ml-10">
|
||||
<t-icon name="upload" size="small" /> 上传
|
||||
</t-button>
|
||||
</t-upload>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item form-item--full">
|
||||
<label>描述</label>
|
||||
<t-textarea v-model="child.description" :autosize="{ minRows: 2, maxRows: 3 }"
|
||||
placeholder="如:高可用的弹性计算服务"></t-textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<t-button class="mt-10" theme="primary" size="small" variant="outline" @click="addHeaderNavChild(index)">
|
||||
新增子菜单
|
||||
</t-button>
|
||||
</div>
|
||||
<t-button class="mt-10" theme="primary" variant="outline" @click="addHeaderNav">
|
||||
新增一级导航
|
||||
</t-button>
|
||||
</t-card>
|
||||
</t-tab-panel>
|
||||
|
||||
<t-tab-panel value="footer" label="底部栏目">
|
||||
<t-card class="theme-card" title="底部栏目(footer_nav)" bordered>
|
||||
<p class="theme-tip">
|
||||
控制首页底部多列链接(如【热门产品】【客户支持】等),结构与模板中的
|
||||
<code>footer_nav</code> 一致。
|
||||
</p>
|
||||
<div v-if="!footerNavList.length" class="empty-tip">
|
||||
还没有底部栏目,点击下方按钮新增。
|
||||
</div>
|
||||
<div class="config-item" v-for="(group, index) in footerNavList" :key="'footer-' + index">
|
||||
<div class="config-item__header">
|
||||
<h4>栏目 {{ index + 1 }}</h4>
|
||||
<t-button size="small" theme="danger" variant="outline" @click="removeFooterNav(index)">
|
||||
删除
|
||||
</t-button>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-item">
|
||||
<label>栏目标题</label>
|
||||
<t-input v-model="group.name" placeholder="如:热门产品"></t-input>
|
||||
</div>
|
||||
</div>
|
||||
<h4 class="sub-title mt-10">栏目链接</h4>
|
||||
<div v-if="!getFooterChildren(group).length" class="empty-tip">
|
||||
还没有链接,点击下方按钮新增。
|
||||
</div>
|
||||
<div class="config-item" v-for="(link, cIndex) in getFooterChildren(group)"
|
||||
:key="'footer-' + index + '-child-' + cIndex">
|
||||
<div class="config-item__header">
|
||||
<h4>链接 {{ cIndex + 1 }}</h4>
|
||||
<t-button size="small" theme="danger" variant="outline" @click="removeFooterNavChild(index, cIndex)">
|
||||
删除
|
||||
</t-button>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-item">
|
||||
<label>名称</label>
|
||||
<t-input v-model="link.name" placeholder="如:云服务器"></t-input>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label>链接地址</label>
|
||||
<t-input v-model="link.url" placeholder="/cloud.html"></t-input>
|
||||
</div>
|
||||
<div class="form-item form-item--switch">
|
||||
<label><span class="switch-label-icon">🔗</span>新窗口</label>
|
||||
<t-switch v-model="link.blank"></t-switch>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<t-button class="mt-10" theme="primary" size="small" variant="outline" @click="addFooterNavChild(index)">
|
||||
新增链接
|
||||
</t-button>
|
||||
</div>
|
||||
<t-button class="mt-10" theme="primary" variant="outline" @click="addFooterNav">
|
||||
新增栏目
|
||||
</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-tab-panel>
|
||||
|
||||
<t-tab-panel v-if="showAdvanced" value="advanced" label="高级设置(JSON)">
|
||||
<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>
|
||||
</t-tab-panel>
|
||||
</t-tabs>
|
||||
|
||||
<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>
|
||||
<t-button class="ml-10" variant="outline" @click="toggleAdvanced">
|
||||
{{ showAdvanced ? "隐藏高级 JSON" : "显示高级 JSON" }}
|
||||
</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: "",
|
||||
online_customer_service_link: "",
|
||||
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: "",
|
||||
showAdvanced: false,
|
||||
activeTab: "basic",
|
||||
uploadUrl: `${host}/${adminPath}/v1/upload`,
|
||||
uploadHeaders: {
|
||||
Authorization: "Bearer " + localStorage.getItem("backJwt"),
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
bannerList() {
|
||||
if (!Array.isArray(this.fullConfig.banner)) {
|
||||
this.fullConfig.banner = [];
|
||||
}
|
||||
return this.fullConfig.banner;
|
||||
},
|
||||
headerNavList() {
|
||||
if (!Array.isArray(this.fullConfig.header_nav)) {
|
||||
this.fullConfig.header_nav = [];
|
||||
}
|
||||
if (this.fullConfig.header_nav.length === 0) {
|
||||
this.fullConfig.header_nav.push({
|
||||
name: "首页",
|
||||
file_address: "index.html",
|
||||
blank: false,
|
||||
children: [],
|
||||
});
|
||||
}
|
||||
return this.fullConfig.header_nav;
|
||||
},
|
||||
homeNav() {
|
||||
return this.headerNavList[0];
|
||||
},
|
||||
footerNavList() {
|
||||
if (!Array.isArray(this.fullConfig.footer_nav)) {
|
||||
this.fullConfig.footer_nav = [];
|
||||
}
|
||||
return this.fullConfig.footer_nav;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
normalizeConfig(data = {}) {
|
||||
const defaults = createDefaultConfig();
|
||||
const merged = {
|
||||
...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.length
|
||||
? 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
|
||||
: [],
|
||||
};
|
||||
|
||||
if (!Array.isArray(merged.header_nav) || merged.header_nav.length === 0) {
|
||||
merged.header_nav = [
|
||||
{
|
||||
name: "首页",
|
||||
file_address: "index.html",
|
||||
blank: false,
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return merged;
|
||||
},
|
||||
uploadFormatResponse(res) {
|
||||
if (!res || res.status !== 200) {
|
||||
return { error: "上传失败" };
|
||||
}
|
||||
return res;
|
||||
},
|
||||
handleUpload(path, ctx) {
|
||||
try {
|
||||
const resp = ctx && ctx.file && ctx.file.response;
|
||||
const data = resp && resp.data ? resp.data : resp;
|
||||
if (!data) {
|
||||
return showMessage(this, "error", "上传失败:未获取到响应数据");
|
||||
}
|
||||
// 优先使用后端返回的 image_url,其次 url,最后才是 save_name
|
||||
let url = data.image_url || data.url || data.save_name || "";
|
||||
if (!url) {
|
||||
return showMessage(this, "error", "上传失败:未获取到文件地址");
|
||||
}
|
||||
|
||||
// 相对路径前面补一个 /,保持站点根路径
|
||||
if (!/^https?:\/\//i.test(url) && url.charAt(0) !== "/") {
|
||||
url = "/" + url;
|
||||
}
|
||||
|
||||
this.setConfigByPath(path, url);
|
||||
} catch (e) {
|
||||
showMessage(this, "error", "上传处理异常:" + e.message);
|
||||
}
|
||||
},
|
||||
setConfigByPath(path, value) {
|
||||
if (!Array.isArray(path) || !path.length) return;
|
||||
let target = this.fullConfig;
|
||||
for (let i = 0; i < path.length - 1; i++) {
|
||||
const key = path[i];
|
||||
if (typeof key === "number") {
|
||||
if (!Array.isArray(target)) return;
|
||||
target = target[key];
|
||||
} else {
|
||||
if (!target[key]) {
|
||||
this.$set(target, key, {});
|
||||
}
|
||||
target = target[key];
|
||||
}
|
||||
if (!target) return;
|
||||
}
|
||||
const lastKey = path[path.length - 1];
|
||||
if (typeof lastKey === "number") {
|
||||
if (!Array.isArray(target)) return;
|
||||
this.$set(target, lastKey, value);
|
||||
} else {
|
||||
this.$set(target, lastKey, value);
|
||||
}
|
||||
},
|
||||
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: "",
|
||||
img: "",
|
||||
description: "",
|
||||
tags: "",
|
||||
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);
|
||||
},
|
||||
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);
|
||||
},
|
||||
getHeaderChildren(item) {
|
||||
if (!item) return [];
|
||||
if (!Array.isArray(item.children)) {
|
||||
this.$set(item, "children", []);
|
||||
}
|
||||
return item.children;
|
||||
},
|
||||
addHeaderNav() {
|
||||
if (!Array.isArray(this.fullConfig.header_nav)) {
|
||||
this.fullConfig.header_nav = [];
|
||||
}
|
||||
this.fullConfig.header_nav.push({
|
||||
name: "",
|
||||
file_address: "",
|
||||
blank: false,
|
||||
children: [],
|
||||
});
|
||||
},
|
||||
removeHeaderNav(index) {
|
||||
if (index === 0) {
|
||||
return;
|
||||
}
|
||||
this.headerNavList.splice(index, 1);
|
||||
},
|
||||
addHeaderNavChild(index) {
|
||||
const item = this.headerNavList[index];
|
||||
if (!item) return;
|
||||
if (!Array.isArray(item.children)) {
|
||||
this.$set(item, "children", []);
|
||||
}
|
||||
item.children.push({
|
||||
name: "",
|
||||
file_address: "",
|
||||
blank: false,
|
||||
icon: "",
|
||||
description: "",
|
||||
});
|
||||
},
|
||||
removeHeaderNavChild(index, childIndex) {
|
||||
const item = this.headerNavList[index];
|
||||
if (!item || !Array.isArray(item.children)) return;
|
||||
item.children.splice(childIndex, 1);
|
||||
},
|
||||
getFooterChildren(group) {
|
||||
if (!group) return [];
|
||||
if (!Array.isArray(group.children)) {
|
||||
this.$set(group, "children", []);
|
||||
}
|
||||
return group.children;
|
||||
},
|
||||
addFooterNav() {
|
||||
if (!Array.isArray(this.fullConfig.footer_nav)) {
|
||||
this.fullConfig.footer_nav = [];
|
||||
}
|
||||
this.fullConfig.footer_nav.push({
|
||||
name: "",
|
||||
children: [],
|
||||
});
|
||||
},
|
||||
removeFooterNav(index) {
|
||||
this.footerNavList.splice(index, 1);
|
||||
},
|
||||
addFooterNavChild(index) {
|
||||
const group = this.footerNavList[index];
|
||||
if (!group) return;
|
||||
if (!Array.isArray(group.children)) {
|
||||
this.$set(group, "children", []);
|
||||
}
|
||||
group.children.push({
|
||||
name: "",
|
||||
url: "",
|
||||
blank: false,
|
||||
});
|
||||
},
|
||||
removeFooterNavChild(index, childIndex) {
|
||||
const group = this.footerNavList[index];
|
||||
if (!group || !Array.isArray(group.children)) return;
|
||||
group.children.splice(childIndex, 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;
|
||||
});
|
||||
},
|
||||
toggleAdvanced() {
|
||||
this.showAdvanced = !this.showAdvanced;
|
||||
if (this.showAdvanced) {
|
||||
this.syncJson();
|
||||
this.activeTab = "advanced";
|
||||
} else if (this.activeTab === "advanced") {
|
||||
this.activeTab = "basic";
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.loadConfig();
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
914
plugins/addon/theme_configurator/template/admin/index_new.html
Normal file
914
plugins/addon/theme_configurator/template/admin/index_new.html
Normal file
@@ -0,0 +1,914 @@
|
||||
<link rel="stylesheet" href="/plugins/addon/theme_configurator/template/admin/theme.css" />
|
||||
|
||||
<div id="theme-config-app" class="admin-container" v-cloak>
|
||||
|
||||
<section id="basic" class="config-section" :class="{active: activeSection === 'basic'}">
|
||||
<div class="section-card" data-title="企业信息">
|
||||
<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 form-item--full">
|
||||
<label>Logo 地址</label>
|
||||
<div class="upload-row">
|
||||
<t-input v-model="fullConfig.site_config.official_website_logo" placeholder="/upload/logo.png"></t-input>
|
||||
<t-upload theme="custom" :action="uploadUrl" :headers="uploadHeaders"
|
||||
:format-response="uploadFormatResponse" :show-upload-progress="false" :max="1"
|
||||
@success="(ctx) => handleUpload(['site_config', 'official_website_logo'], ctx)">
|
||||
<t-button size="small" class="ml-10">
|
||||
<t-icon name="upload" size="small" /> 上传
|
||||
</t-button>
|
||||
</t-upload>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item form-item--full">
|
||||
<label>二维码地址</label>
|
||||
<div class="upload-row">
|
||||
<t-input v-model="fullConfig.site_config.enterprise_qrcode" placeholder="/upload/qrcode.png"></t-input>
|
||||
<t-upload theme="custom" :action="uploadUrl" :headers="uploadHeaders"
|
||||
:format-response="uploadFormatResponse" :show-upload-progress="false" :max="1"
|
||||
@success="(ctx) => handleUpload(['site_config', 'enterprise_qrcode'], ctx)">
|
||||
<t-button size="small" class="ml-10">
|
||||
<t-icon name="upload" size="small" /> 上传
|
||||
</t-button>
|
||||
</t-upload>
|
||||
</div>
|
||||
</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>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="© 2025 主题云"></t-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-card" data-title="侧边浮窗">
|
||||
<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 form-item--full">
|
||||
<label>图标地址</label>
|
||||
<div class="upload-row">
|
||||
<t-input v-model="item.icon" placeholder="/upload/side-phone.png"></t-input>
|
||||
<t-upload theme="custom" :action="uploadUrl" :headers="uploadHeaders"
|
||||
:format-response="uploadFormatResponse" :show-upload-progress="false" :max="1"
|
||||
@success="(ctx) => handleUpload(['side', index, 'icon'], ctx)">
|
||||
<t-button size="small" class="ml-10">
|
||||
<t-icon name="upload" size="small" /> 上传
|
||||
</t-button>
|
||||
</t-upload>
|
||||
</div>
|
||||
</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>
|
||||
</div>
|
||||
|
||||
<div class="section-card" data-title="反馈类型">
|
||||
<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>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="seo" class="config-section" :class="{active: activeSection === 'seo'}">
|
||||
<div class="section-card" data-title="SEO 设置">
|
||||
<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>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="home" class="config-section" :class="{active: activeSection === 'home'}">
|
||||
<div class="section-card" data-title="首页轮播文案">
|
||||
<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 form-item--full">
|
||||
<label>图片地址</label>
|
||||
<div class="upload-row">
|
||||
<t-input v-model="banner.img" placeholder="/upload/banner-1.png"></t-input>
|
||||
<t-upload theme="custom" :action="uploadUrl" :headers="uploadHeaders"
|
||||
:format-response="uploadFormatResponse" :show-upload-progress="false" :max="1"
|
||||
@success="(ctx) => handleUpload(['banner', index, 'img'], ctx)">
|
||||
<t-button size="small" class="ml-10">
|
||||
<t-icon name="upload" size="small" /> 上传
|
||||
</t-button>
|
||||
</t-upload>
|
||||
</div>
|
||||
</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.tags" placeholder="如:高速,低价,安全(多个标签用逗号分隔)"></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 form-item--switch">
|
||||
<label><span class="switch-label-icon">🔗</span>新窗口</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 form-item--switch">
|
||||
<label><span class="switch-label-icon">🔗</span>按钮新窗口</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>
|
||||
</div>
|
||||
|
||||
<div class="section-card" data-title="企业资质与荣誉">
|
||||
<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>
|
||||
<div class="upload-row">
|
||||
<t-input v-model="item.img" placeholder="/upload/honor.png"></t-input>
|
||||
<t-upload theme="custom" :action="uploadUrl" :headers="uploadHeaders"
|
||||
:format-response="uploadFormatResponse" :show-upload-progress="false" :max="1"
|
||||
@success="(ctx) => handleUpload(['honor', index, 'img'], ctx)">
|
||||
<t-button size="small" class="ml-10">
|
||||
<t-icon name="upload" size="small" /> 上传
|
||||
</t-button>
|
||||
</t-upload>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<t-button theme="primary" variant="outline" @click="addHonor">新增荣誉</t-button>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<section id="nav" class="config-section" :class="{active: activeSection === 'nav'}">
|
||||
<div class="section-card" data-title="顶部导航(header_nav)">
|
||||
<p class="theme-tip">
|
||||
控制站点顶部导航栏及其下拉菜单结构。第一个导航项用作 Logo 点击跳转地址。
|
||||
</p>
|
||||
<div class="form-grid">
|
||||
<div class="form-item form-item--full">
|
||||
<label>Logo 点击跳转地址</label>
|
||||
<t-input v-model="homeNav.file_address" placeholder="index.html 或 /index.html"></t-input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="sub-title mt-10">一级导航</h4>
|
||||
<div v-if="headerNavList.length <= 1" class="empty-tip">
|
||||
还没有自定义导航,请点击下方按钮新增。
|
||||
</div>
|
||||
<div class="config-item" v-for="(item, index) in headerNavList" :key="'nav-' + index" v-if="index > 0">
|
||||
<div class="config-item__header">
|
||||
<h4>导航 {{ index }}</h4>
|
||||
<t-button size="small" theme="danger" variant="outline" @click="removeHeaderNav(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.file_address" placeholder="/cloud.html"></t-input>
|
||||
</div>
|
||||
<div class="form-item form-item--switch">
|
||||
<label><span class="switch-label-icon">🔗</span>新窗口</label>
|
||||
<t-switch v-model="item.blank"></t-switch>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="sub-title mt-10">下拉子菜单</h4>
|
||||
<div v-if="!getHeaderChildren(item).length" class="empty-tip">
|
||||
还没有子菜单,点击下方按钮新增。
|
||||
</div>
|
||||
<div class="config-item" v-for="(child, cIndex) in getHeaderChildren(item)"
|
||||
:key="'nav-' + index + '-child-' + cIndex">
|
||||
<div class="config-item__header">
|
||||
<h4>子菜单 {{ cIndex + 1 }}</h4>
|
||||
<t-button size="small" theme="danger" variant="outline" @click="removeHeaderNavChild(index, cIndex)">
|
||||
删除
|
||||
</t-button>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-item">
|
||||
<label>名称</label>
|
||||
<t-input v-model="child.name" placeholder="如:云服务器"></t-input>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label>链接地址</label>
|
||||
<t-input v-model="child.file_address" placeholder="/cloud.html"></t-input>
|
||||
</div>
|
||||
<div class="form-item form-item--switch">
|
||||
<label><span class="switch-label-icon">🔗</span>新窗口</label>
|
||||
<t-switch v-model="child.blank"></t-switch>
|
||||
</div>
|
||||
<div class="form-item form-item--full">
|
||||
<label>图标地址</label>
|
||||
<div class="upload-row">
|
||||
<t-input v-model="child.icon" placeholder="/upload/nav-icon.png"></t-input>
|
||||
<t-upload theme="custom" :action="uploadUrl" :headers="uploadHeaders"
|
||||
:format-response="uploadFormatResponse" :show-upload-progress="false" :max="1" @success="
|
||||
(ctx) => handleUpload(['header_nav', index, 'children', cIndex, 'icon'], ctx)
|
||||
">
|
||||
<t-button size="small" class="ml-10">
|
||||
<t-icon name="upload" size="small" /> 上传
|
||||
</t-button>
|
||||
</t-upload>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item form-item--full">
|
||||
<label>描述</label>
|
||||
<t-textarea v-model="child.description" :autosize="{ minRows: 2, maxRows: 3 }"
|
||||
placeholder="如:高可用的弹性计算服务"></t-textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<t-button class="mt-10" theme="primary" size="small" variant="outline" @click="addHeaderNavChild(index)">
|
||||
新增子菜单
|
||||
</t-button>
|
||||
</div>
|
||||
<t-button class="mt-10" theme="primary" variant="outline" @click="addHeaderNav">
|
||||
新增一级导航
|
||||
</t-button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="footer" class="config-section" :class="{active: activeSection === 'footer'}">
|
||||
<div class="section-card" data-title="底部栏目(footer_nav)">
|
||||
<p class="theme-tip">
|
||||
控制首页底部多列链接(如【热门产品】【客户支持】等),结构与模板中的
|
||||
<code>footer_nav</code> 一致。
|
||||
</p>
|
||||
<div v-if="!footerNavList.length" class="empty-tip">
|
||||
还没有底部栏目,点击下方按钮新增。
|
||||
</div>
|
||||
<div class="config-item" v-for="(group, index) in footerNavList" :key="'footer-' + index">
|
||||
<div class="config-item__header">
|
||||
<h4>栏目 {{ index + 1 }}</h4>
|
||||
<t-button size="small" theme="danger" variant="outline" @click="removeFooterNav(index)">
|
||||
删除
|
||||
</t-button>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-item">
|
||||
<label>栏目标题</label>
|
||||
<t-input v-model="group.name" placeholder="如:热门产品"></t-input>
|
||||
</div>
|
||||
</div>
|
||||
<h4 class="sub-title mt-10">栏目链接</h4>
|
||||
<div v-if="!getFooterChildren(group).length" class="empty-tip">
|
||||
还没有链接,点击下方按钮新增。
|
||||
</div>
|
||||
<div class="config-item" v-for="(link, cIndex) in getFooterChildren(group)"
|
||||
:key="'footer-' + index + '-child-' + cIndex">
|
||||
<div class="config-item__header">
|
||||
<h4>链接 {{ cIndex + 1 }}</h4>
|
||||
<t-button size="small" theme="danger" variant="outline" @click="removeFooterNavChild(index, cIndex)">
|
||||
删除
|
||||
</t-button>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-item">
|
||||
<label>名称</label>
|
||||
<t-input v-model="link.name" placeholder="如:云服务器"></t-input>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label>链接地址</label>
|
||||
<t-input v-model="link.url" placeholder="/cloud.html"></t-input>
|
||||
</div>
|
||||
<div class="form-item form-item--switch">
|
||||
<label><span class="switch-label-icon">🔗</span>新窗口</label>
|
||||
<t-switch v-model="link.blank"></t-switch>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<t-button class="mt-10" theme="primary" size="small" variant="outline" @click="addFooterNavChild(index)">
|
||||
新增链接
|
||||
</t-button>
|
||||
</div>
|
||||
<t-button class="mt-10" theme="primary" variant="outline" @click="addFooterNav">
|
||||
新增栏目
|
||||
</t-button>
|
||||
</div>
|
||||
|
||||
<div class="section-card" data-title="友情链接">
|
||||
<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>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<t-tab-panel v-if="showAdvanced" value="advanced" label="高级设置(JSON)">
|
||||
<div class="section-card" data-title="高级配置 (JSON)">
|
||||
<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>
|
||||
</div>
|
||||
</section>
|
||||
</t-tabs>
|
||||
|
||||
<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>
|
||||
<t-button class="ml-10" variant="outline" @click="toggleAdvanced">
|
||||
{{ showAdvanced ? "隐藏高级 JSON" : "显示高级 JSON" }}
|
||||
</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: "",
|
||||
online_customer_service_link: "",
|
||||
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: "",
|
||||
showAdvanced: false,
|
||||
activeTab: "basic",
|
||||
uploadUrl: `${host}/${adminPath}/v1/upload`,
|
||||
uploadHeaders: {
|
||||
Authorization: "Bearer " + localStorage.getItem("backJwt"),
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
bannerList() {
|
||||
if (!Array.isArray(this.fullConfig.banner)) {
|
||||
this.fullConfig.banner = [];
|
||||
}
|
||||
return this.fullConfig.banner;
|
||||
},
|
||||
headerNavList() {
|
||||
if (!Array.isArray(this.fullConfig.header_nav)) {
|
||||
this.fullConfig.header_nav = [];
|
||||
}
|
||||
if (this.fullConfig.header_nav.length === 0) {
|
||||
this.fullConfig.header_nav.push({
|
||||
name: "首页",
|
||||
file_address: "index.html",
|
||||
blank: false,
|
||||
children: [],
|
||||
});
|
||||
}
|
||||
return this.fullConfig.header_nav;
|
||||
},
|
||||
homeNav() {
|
||||
return this.headerNavList[0];
|
||||
},
|
||||
footerNavList() {
|
||||
if (!Array.isArray(this.fullConfig.footer_nav)) {
|
||||
this.fullConfig.footer_nav = [];
|
||||
}
|
||||
return this.fullConfig.footer_nav;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
normalizeConfig(data = {}) {
|
||||
const defaults = createDefaultConfig();
|
||||
const merged = {
|
||||
...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.length
|
||||
? 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
|
||||
: [],
|
||||
};
|
||||
|
||||
if (!Array.isArray(merged.header_nav) || merged.header_nav.length === 0) {
|
||||
merged.header_nav = [
|
||||
{
|
||||
name: "首页",
|
||||
file_address: "index.html",
|
||||
blank: false,
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return merged;
|
||||
},
|
||||
uploadFormatResponse(res) {
|
||||
if (!res || res.status !== 200) {
|
||||
return { error: "上传失败" };
|
||||
}
|
||||
return res;
|
||||
},
|
||||
handleUpload(path, ctx) {
|
||||
try {
|
||||
const resp = ctx && ctx.file && ctx.file.response;
|
||||
const data = resp && resp.data ? resp.data : resp;
|
||||
if (!data) {
|
||||
return showMessage(this, "error", "上传失败:未获取到响应数据");
|
||||
}
|
||||
// 优先使用后端返回的 image_url,其次 url,最后才是 save_name
|
||||
let url = data.image_url || data.url || data.save_name || "";
|
||||
if (!url) {
|
||||
return showMessage(this, "error", "上传失败:未获取到文件地址");
|
||||
}
|
||||
|
||||
// 相对路径前面补一个 /,保持站点根路径
|
||||
if (!/^https?:\/\//i.test(url) && url.charAt(0) !== "/") {
|
||||
url = "/" + url;
|
||||
}
|
||||
|
||||
this.setConfigByPath(path, url);
|
||||
} catch (e) {
|
||||
showMessage(this, "error", "上传处理异常:" + e.message);
|
||||
}
|
||||
},
|
||||
setConfigByPath(path, value) {
|
||||
if (!Array.isArray(path) || !path.length) return;
|
||||
let target = this.fullConfig;
|
||||
for (let i = 0; i < path.length - 1; i++) {
|
||||
const key = path[i];
|
||||
if (typeof key === "number") {
|
||||
if (!Array.isArray(target)) return;
|
||||
target = target[key];
|
||||
} else {
|
||||
if (!target[key]) {
|
||||
this.$set(target, key, {});
|
||||
}
|
||||
target = target[key];
|
||||
}
|
||||
if (!target) return;
|
||||
}
|
||||
const lastKey = path[path.length - 1];
|
||||
if (typeof lastKey === "number") {
|
||||
if (!Array.isArray(target)) return;
|
||||
this.$set(target, lastKey, value);
|
||||
} else {
|
||||
this.$set(target, lastKey, value);
|
||||
}
|
||||
},
|
||||
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: "",
|
||||
img: "",
|
||||
description: "",
|
||||
tags: "",
|
||||
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);
|
||||
},
|
||||
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);
|
||||
},
|
||||
getHeaderChildren(item) {
|
||||
if (!item) return [];
|
||||
if (!Array.isArray(item.children)) {
|
||||
this.$set(item, "children", []);
|
||||
}
|
||||
return item.children;
|
||||
},
|
||||
addHeaderNav() {
|
||||
if (!Array.isArray(this.fullConfig.header_nav)) {
|
||||
this.fullConfig.header_nav = [];
|
||||
}
|
||||
this.fullConfig.header_nav.push({
|
||||
name: "",
|
||||
file_address: "",
|
||||
blank: false,
|
||||
children: [],
|
||||
});
|
||||
},
|
||||
removeHeaderNav(index) {
|
||||
if (index === 0) {
|
||||
return;
|
||||
}
|
||||
this.headerNavList.splice(index, 1);
|
||||
},
|
||||
addHeaderNavChild(index) {
|
||||
const item = this.headerNavList[index];
|
||||
if (!item) return;
|
||||
if (!Array.isArray(item.children)) {
|
||||
this.$set(item, "children", []);
|
||||
}
|
||||
item.children.push({
|
||||
name: "",
|
||||
file_address: "",
|
||||
blank: false,
|
||||
icon: "",
|
||||
description: "",
|
||||
});
|
||||
},
|
||||
removeHeaderNavChild(index, childIndex) {
|
||||
const item = this.headerNavList[index];
|
||||
if (!item || !Array.isArray(item.children)) return;
|
||||
item.children.splice(childIndex, 1);
|
||||
},
|
||||
getFooterChildren(group) {
|
||||
if (!group) return [];
|
||||
if (!Array.isArray(group.children)) {
|
||||
this.$set(group, "children", []);
|
||||
}
|
||||
return group.children;
|
||||
},
|
||||
addFooterNav() {
|
||||
if (!Array.isArray(this.fullConfig.footer_nav)) {
|
||||
this.fullConfig.footer_nav = [];
|
||||
}
|
||||
this.fullConfig.footer_nav.push({
|
||||
name: "",
|
||||
children: [],
|
||||
});
|
||||
},
|
||||
removeFooterNav(index) {
|
||||
this.footerNavList.splice(index, 1);
|
||||
},
|
||||
addFooterNavChild(index) {
|
||||
const group = this.footerNavList[index];
|
||||
if (!group) return;
|
||||
if (!Array.isArray(group.children)) {
|
||||
this.$set(group, "children", []);
|
||||
}
|
||||
group.children.push({
|
||||
name: "",
|
||||
url: "",
|
||||
blank: false,
|
||||
});
|
||||
},
|
||||
removeFooterNavChild(index, childIndex) {
|
||||
const group = this.footerNavList[index];
|
||||
if (!group || !Array.isArray(group.children)) return;
|
||||
group.children.splice(childIndex, 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;
|
||||
});
|
||||
},
|
||||
toggleAdvanced() {
|
||||
this.showAdvanced = !this.showAdvanced;
|
||||
if (this.showAdvanced) {
|
||||
this.syncJson();
|
||||
this.activeTab = "advanced";
|
||||
} else if (this.activeTab === "advanced") {
|
||||
this.activeTab = "basic";
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.loadConfig();
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@@ -0,0 +1,255 @@
|
||||
<link rel="stylesheet" href="/plugins/addon/theme_configurator/template/admin/theme.css" />
|
||||
|
||||
<div id="theme-config-app" class="admin-container" v-cloak>
|
||||
<!-- 顶部工具栏 -->
|
||||
<header class="admin-header">
|
||||
<div class="admin-header__left">
|
||||
<div class="admin-logo">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
<path d="M2 17L12 22L22 17" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
<path d="M2 12L12 17L22 12" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
</svg>
|
||||
<span>黑果云模板控制器</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-header__right">
|
||||
<button class="btn btn-primary btn-lg" @click="saveConfig" :disabled="saving">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" v-if="!saving">
|
||||
<path d="M13.5 2.5H2.5V13.5H13.5V2.5Z" stroke="currentColor" stroke-width="1.5" />
|
||||
<path d="M10.5 2.5V6.5H5.5V2.5" stroke="currentColor" stroke-width="1.5" />
|
||||
<path d="M5.5 9.5H10.5V13.5H5.5V9.5Z" stroke="currentColor" stroke-width="1.5" />
|
||||
</svg>
|
||||
<span v-if="saving">保存中...</span>
|
||||
<span v-else>保存全部配置</span>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 主体布局 -->
|
||||
<div class="admin-layout">
|
||||
<!-- 侧边栏导航 -->
|
||||
<aside class="admin-sidebar">
|
||||
<nav class="sidebar-nav">
|
||||
<a href="#basic" class="nav-item" :class="{active: activeSection === 'basic'}"
|
||||
@click="activeSection = 'basic'">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" stroke="currentColor" stroke-width="2" />
|
||||
<line x1="3" y1="9" x2="21" y2="9" stroke="currentColor" stroke-width="2" />
|
||||
<line x1="9" y1="21" x2="9" y2="9" stroke="currentColor" stroke-width="2" />
|
||||
</svg>
|
||||
<span>基础配置</span>
|
||||
</a>
|
||||
<a href="#seo" class="nav-item" :class="{active: activeSection === 'seo'}"
|
||||
@click="activeSection = 'seo'">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
|
||||
<circle cx="11" cy="11" r="8" stroke="currentColor" stroke-width="2" />
|
||||
<path d="M21 21L16.65 16.65" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
|
||||
</svg>
|
||||
<span>SEO设置</span>
|
||||
</a>
|
||||
<a href="#home" class="nav-item" :class="{active: activeSection === 'home'}"
|
||||
@click="activeSection = 'home'">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
|
||||
<path
|
||||
d="M3 9L12 2L21 9V20C21 20.5304 20.7893 21.0391 20.4142 21.4142C20.0391 21.7893 19.5304 22 19 22H5C4.46957 22 3.96086 21.7893 3.58579 21.4142C3.21071 21.0391 3 20.5304 3 20V9Z"
|
||||
stroke="currentColor" stroke-width="2" />
|
||||
</svg>
|
||||
<span>首页内容</span>
|
||||
</a>
|
||||
<a href="#nav" class="nav-item" :class="{active: activeSection === 'nav'}"
|
||||
@click="activeSection = 'nav'">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
|
||||
<line x1="3" y1="12" x2="21" y2="12" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" />
|
||||
<line x1="3" y1="6" x2="21" y2="6" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" />
|
||||
<line x1="3" y1="18" x2="21" y2="18" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" />
|
||||
</svg>
|
||||
<span>导航配置</span>
|
||||
</a>
|
||||
<a href="#advanced" class="nav-item" :class="{active: activeSection === 'advanced'}"
|
||||
@click="activeSection = 'advanced'">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
|
||||
<circle cx="12" cy="12" r="3" stroke="currentColor" stroke-width="2" />
|
||||
<path
|
||||
d="M12 1V3M12 21V23M4.22 4.22L5.64 5.64M18.36 18.36L19.78 19.78M1 12H3M21 12H23M4.22 19.78L5.64 18.36M18.36 5.64L19.78 4.22"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" />
|
||||
</svg>
|
||||
<span>高级设置</span>
|
||||
</a>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<main class="admin-main">
|
||||
<!-- 基础配置区块 -->
|
||||
<section id="basic" class="config-section" v-show="activeSection === 'basic'">
|
||||
<!-- 企业信息 -->
|
||||
<div class="section-card">
|
||||
<div class="section-header">
|
||||
<h2>企业信息</h2>
|
||||
<p class="section-desc">配置企业基础联系信息</p>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<div class="form-group">
|
||||
<label>企业名称</label>
|
||||
<t-input v-model="fullConfig.site_config.enterprise_name" placeholder="主题云"></t-input>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>联系电话</label>
|
||||
<t-input v-model="fullConfig.site_config.enterprise_telephone"
|
||||
placeholder="400-000-0000"></t-input>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>联系邮箱</label>
|
||||
<t-input v-model="fullConfig.site_config.enterprise_mailbox"
|
||||
placeholder="support@example.com"></t-input>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>在线客服链接</label>
|
||||
<t-input v-model="fullConfig.site_config.online_customer_service_link"
|
||||
placeholder="http://www.test.com"></t-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Logo与图片 -->
|
||||
<div class="section-card">
|
||||
<div class="section-header">
|
||||
<h2>Logo与图片</h2>
|
||||
<p class="section-desc">上传网站Logo和二维码</p>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<div class="form-group">
|
||||
<label>网站Logo</label>
|
||||
<div class="upload-control">
|
||||
<t-input v-model="fullConfig.site_config.official_website_logo"
|
||||
placeholder="/upload/logo.png"></t-input>
|
||||
<t-upload theme="custom" :action="uploadUrl" :headers="uploadHeaders"
|
||||
:format-response="uploadFormatResponse" :show-upload-progress="false" :max="1"
|
||||
@success="(ctx) => handleUpload(['site_config', 'official_website_logo'], ctx)">
|
||||
<button class="btn btn-secondary">
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="none">
|
||||
<path
|
||||
d="M14 10V12.6667C14 13.0203 13.8595 13.3594 13.6095 13.6095C13.3594 13.8595 13.0203 14 12.6667 14H3.33333C2.97971 14 2.64057 13.8595 2.39052 13.6095C2.14048 13.3594 2 13.0203 2 12.6667V10"
|
||||
stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
|
||||
<path d="M11.3333 5.33333L8 2L4.66667 5.33333" stroke="currentColor"
|
||||
stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M8 2V10" stroke="currentColor" stroke-width="1.5"
|
||||
stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
选择文件
|
||||
</button>
|
||||
</t-upload>
|
||||
</div>
|
||||
<div class="form-hint">建议尺寸: 200×60 像素, PNG/SVG格式</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>企业二维码</label>
|
||||
<div class="upload-control">
|
||||
<t-input v-model="fullConfig.site_config.enterprise_qrcode"
|
||||
placeholder="/upload/qrcode.png"></t-input>
|
||||
<t-upload theme="custom" :action="uploadUrl" :headers="uploadHeaders"
|
||||
:format-response="uploadFormatResponse" :show-upload-progress="false" :max="1"
|
||||
@success="(ctx) => handleUpload(['site_config', 'enterprise_qrcode'], ctx)">
|
||||
<button class="btn btn-secondary">
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="none">
|
||||
<path
|
||||
d="M14 10V12.6667C14 13.0203 13.8595 13.3594 13.6095 13.6095C13.3594 13.8595 13.0203 14 12.6667 14H3.33333C2.97971 14 2.64057 13.8595 2.39052 13.6095C2.14048 13.3594 2 13.0203 2 12.6667V10"
|
||||
stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
|
||||
<path d="M11.3333 5.33333L8 2L4.66667 5.33333" stroke="currentColor"
|
||||
stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M8 2V10" stroke="currentColor" stroke-width="1.5"
|
||||
stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
选择文件
|
||||
</button>
|
||||
</t-upload>
|
||||
</div>
|
||||
<div class="form-hint">建议尺寸: 200×200 像素, PNG格式</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 备案信息 -->
|
||||
<div class="section-card">
|
||||
<div class="section-header">
|
||||
<h2>备案信息</h2>
|
||||
<p class="section-desc">网站备案和许可证信息</p>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<div class="form-group">
|
||||
<label>ICP备案号</label>
|
||||
<t-input v-model="fullConfig.site_config.icp_info" placeholder="京ICP备XXXX号"></t-input>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<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-group">
|
||||
<label>公安备案号</label>
|
||||
<t-input v-model="fullConfig.site_config.public_security_network_preparation"
|
||||
placeholder="京公网安备XXXX号"></t-input>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<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-group">
|
||||
<label>增值电信许可证</label>
|
||||
<t-input v-model="fullConfig.site_config.telecom_appreciation"
|
||||
placeholder="增值电信业务经营许可证"></t-input>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>版权信息</label>
|
||||
<t-input v-model="fullConfig.site_config.copyright_info" placeholder="© 2025 主题云"></t-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 链接配置 -->
|
||||
<div class="section-card">
|
||||
<div class="section-header">
|
||||
<h2>页面链接</h2>
|
||||
<p class="section-desc">配置各类页面跳转链接</p>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<div class="form-group">
|
||||
<label>用户协议链接</label>
|
||||
<t-input v-model="fullConfig.site_config.terms_service_url"
|
||||
placeholder="/agreement/service.html"></t-input>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>隐私政策链接</label>
|
||||
<t-input v-model="fullConfig.site_config.terms_privacy_url"
|
||||
placeholder="/agreement/privacy.html"></t-input>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>云产品购买链接</label>
|
||||
<t-input v-model="fullConfig.site_config.cloud_product_link"
|
||||
placeholder="/cart/goods.htm?id=1"></t-input>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>物理机/DCIM链接</label>
|
||||
<t-input v-model="fullConfig.site_config.dcim_product_link"
|
||||
placeholder="/cart/goods.htm?id=2"></t-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
由于文件太长,我需要分多次发送。让我先完成这一部分,然后继续下一部分。
|
||||
|
||||
这个版本采用:
|
||||
1. ⭐ 侧边栏导航
|
||||
2. ⭐ 卡片式内容
|
||||
3. ⭐ 单列表单
|
||||
4. ⭐ 现代化按钮
|
||||
|
||||
是否继续创建剩余部分 (SEO设置、首页内容、导航配置等)?
|
||||
@@ -1,87 +1,229 @@
|
||||
/* ============================================
|
||||
黑果云模板控制器 - 清爽专业风格
|
||||
设计理念:简洁、可靠、商务化
|
||||
黑果云模板控制器 - 现代管理后台
|
||||
设计风格: 侧边栏 + 卡片式 + Ant Design配色
|
||||
============================================ */
|
||||
|
||||
/* CSS 变量定义 - 统一配色方案 */
|
||||
:root {
|
||||
/* 主色调 - 专业蓝 */
|
||||
--primary-color: #2563eb;
|
||||
--primary-hover: #1d4ed8;
|
||||
--primary-light: #eff6ff;
|
||||
/* Ant Design 配色 */
|
||||
--primary: #1890ff;
|
||||
--primary-hover: #40a9ff;
|
||||
--primary-active: #096dd9;
|
||||
--primary-light: #e6f7ff;
|
||||
|
||||
/* 背景色 - 清爽白灰 */
|
||||
--bg-page: #f8fafc;
|
||||
--bg-card: #ffffff;
|
||||
--bg-section: #f1f5f9;
|
||||
--bg-input: #ffffff;
|
||||
/* 背景色 */
|
||||
--bg-body: #f0f2f5;
|
||||
--bg-container: #ffffff;
|
||||
--bg-sidebar: #001529;
|
||||
--bg-header: #ffffff;
|
||||
|
||||
/* 文字色 - 清晰层级 */
|
||||
--text-primary: #0f172a;
|
||||
--text-secondary: #64748b;
|
||||
--text-tertiary: #94a3b8;
|
||||
--text-disabled: #cbd5e1;
|
||||
/* 文字色 */
|
||||
--text-primary: #262626;
|
||||
--text-secondary: #595959;
|
||||
--text-tertiary: #8c8c8c;
|
||||
--text-light: rgba(255, 255, 255, 0.85);
|
||||
|
||||
/* 边框色 */
|
||||
--border-light: #e2e8f0;
|
||||
--border-normal: #cbd5e1;
|
||||
--border-dark: #94a3b8;
|
||||
|
||||
/* 功能色 */
|
||||
--success: #10b981;
|
||||
--warning: #f59e0b;
|
||||
--danger: #ef4444;
|
||||
--border: #d9d9d9;
|
||||
--border-light: #e8e8e8;
|
||||
--border-lighter: #f0f0f0;
|
||||
|
||||
/* 间距 */
|
||||
--spacing-xs: 8px;
|
||||
--spacing-sm: 12px;
|
||||
--spacing-sm: 8px;
|
||||
--spacing-md: 16px;
|
||||
--spacing-lg: 24px;
|
||||
--spacing-xl: 32px;
|
||||
|
||||
/* 圆角 */
|
||||
--radius-sm: 6px;
|
||||
--radius-md: 8px;
|
||||
--radius-lg: 12px;
|
||||
/* 阴影 */
|
||||
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
/* 全局容器 */
|
||||
.template {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: var(--spacing-lg);
|
||||
background: var(--bg-page);
|
||||
}
|
||||
/* ============================================
|
||||
全局样式
|
||||
============================================ */
|
||||
|
||||
/* 隐藏加载时的闪烁 */
|
||||
[v-cloak] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
卡片样式
|
||||
============================================ */
|
||||
|
||||
.theme-card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: var(--radius-md);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
overflow: hidden;
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-body);
|
||||
}
|
||||
|
||||
/* 卡片标题 */
|
||||
.theme-card .t-card__title {
|
||||
/* ============================================
|
||||
主容器
|
||||
============================================ */
|
||||
|
||||
.admin-container {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
顶部工具栏
|
||||
============================================ */
|
||||
|
||||
.admin-header {
|
||||
height: 60px;
|
||||
background: var(--bg-header);
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 var(--spacing-lg);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.admin-header__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.admin-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
padding: 20px var(--spacing-lg);
|
||||
border-bottom: 1px solid var(--bg-section);
|
||||
background: var(--bg-card);
|
||||
}
|
||||
|
||||
/* 卡片内容 */
|
||||
.theme-card .t-card__body {
|
||||
.admin-logo svg {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.admin-header__right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
主体布局
|
||||
============================================ */
|
||||
|
||||
.admin-layout {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
侧边栏
|
||||
============================================ */
|
||||
|
||||
.admin-sidebar {
|
||||
width: 220px;
|
||||
background: var(--bg-sidebar);
|
||||
overflow-y: auto;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
padding: var(--spacing-md) 0;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px var(--spacing-lg);
|
||||
color: var(--text-light);
|
||||
text-decoration: none;
|
||||
transition: all 0.3s;
|
||||
cursor: pointer;
|
||||
border-left: 3px solid transparent;
|
||||
}
|
||||
|
||||
.nav-item svg {
|
||||
flex-shrink: 0;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.nav-item:hover {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.nav-item:hover svg {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
background: var(--primary);
|
||||
border-left-color: #ffffff;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.nav-item.active svg {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
主内容区
|
||||
============================================ */
|
||||
|
||||
.admin-main {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.config-section {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.config-section.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
区块卡片
|
||||
============================================ */
|
||||
|
||||
.section-card {
|
||||
background: var(--bg-container);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid var(--border-lighter);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
padding: 20px var(--spacing-lg);
|
||||
border-bottom: 1px solid var(--border-lighter);
|
||||
}
|
||||
|
||||
.section-header h2 {
|
||||
margin: 0 0 4px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.section-header h2 svg {
|
||||
color: var(--primary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.section-desc {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.section-body {
|
||||
padding: var(--spacing-lg);
|
||||
}
|
||||
|
||||
@@ -89,386 +231,397 @@
|
||||
表单样式
|
||||
============================================ */
|
||||
|
||||
/* 表单网格布局 */
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: var(--spacing-lg) 20px;
|
||||
padding: 4px 0;
|
||||
.form-group {
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
/* 表单项 */
|
||||
.form-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.form-group:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form-item--full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
/* 标签样式 */
|
||||
.form-item label {
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
line-height: 1.5;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* Switch 开关布局 */
|
||||
.form-item--switch {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
background: var(--bg-section);
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--border-light);
|
||||
.form-group label .required {
|
||||
color: #ff4d4f;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.form-item--switch label {
|
||||
margin-bottom: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
输入框样式
|
||||
============================================ */
|
||||
|
||||
.t-input,
|
||||
.t-textarea {
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--border-light);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.t-input:focus-within,
|
||||
.t-textarea:focus-within {
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 3px var(--primary-light);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
提示文本
|
||||
============================================ */
|
||||
|
||||
.theme-tip {
|
||||
margin: 0 0 var(--spacing-md);
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
background: var(--primary-light);
|
||||
border-left: 3px solid var(--primary-color);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--text-secondary);
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.theme-tip code {
|
||||
padding: 2px var(--spacing-xs);
|
||||
background: rgba(37, 99, 235, 0.1);
|
||||
.form-control {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
color: var(--primary-color);
|
||||
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s;
|
||||
background: var(--bg-container);
|
||||
}
|
||||
|
||||
.form-control:hover {
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 2px var(--primary-light);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.form-hint {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
JSON 编辑器
|
||||
上传控件
|
||||
============================================ */
|
||||
|
||||
.theme-textarea textarea {
|
||||
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||
.upload-control {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.upload-control .t-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
按钮样式
|
||||
============================================ */
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
padding: 0 16px;
|
||||
height: 36px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
border-radius: 4px;
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn svg {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 主按钮 */
|
||||
.btn-primary {
|
||||
background: var(--primary);
|
||||
color: #ffffff;
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
background: var(--primary-hover);
|
||||
border-color: var(--primary-hover);
|
||||
}
|
||||
|
||||
.btn-primary:active:not(:disabled) {
|
||||
background: var(--primary-active);
|
||||
border-color: var(--primary-active);
|
||||
}
|
||||
|
||||
/* 次要按钮 */
|
||||
.btn-secondary {
|
||||
background: var(--bg-container);
|
||||
color: var(--text-primary);
|
||||
border-color: var(--border);
|
||||
}
|
||||
|
||||
.btn-secondary:hover:not(:disabled) {
|
||||
color: var(--primary);
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
/* 危险按钮 */
|
||||
.btn-danger {
|
||||
background: var(--bg-container);
|
||||
color: #ff4d4f;
|
||||
border-color: var(--border);
|
||||
}
|
||||
|
||||
.btn-danger:hover:not(:disabled) {
|
||||
color: #ffffff;
|
||||
background: #ff4d4f;
|
||||
border-color: #ff4d4f;
|
||||
}
|
||||
|
||||
/* 大按钮 */
|
||||
.btn-lg {
|
||||
height: 40px;
|
||||
padding: 0 24px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
/* 小按钮 */
|
||||
.btn-sm {
|
||||
height: 28px;
|
||||
padding: 0 12px;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
padding: var(--spacing-md) !important;
|
||||
background: #1e293b;
|
||||
color: #e2e8f0;
|
||||
border: 1px solid #334155;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.theme-textarea textarea:focus {
|
||||
background: #1e293b;
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15);
|
||||
/* 图标按钮 */
|
||||
.btn-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.btn-icon:hover {
|
||||
background: var(--primary-light);
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.btn-icon-danger:hover {
|
||||
background: #fff1f0;
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
配置项卡片 (轮播、荣誉等)
|
||||
配置项列表
|
||||
============================================ */
|
||||
|
||||
.banner-item,
|
||||
.config-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
.config-item {
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--spacing-lg);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
background: var(--bg-card);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 配置项头部 */
|
||||
.banner-item__header,
|
||||
.config-item__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--spacing-md);
|
||||
padding-bottom: var(--spacing-sm);
|
||||
border-bottom: 1px solid var(--bg-section);
|
||||
padding: 12px var(--spacing-md);
|
||||
background: #fafafa;
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
}
|
||||
|
||||
.banner-item__header h4,
|
||||
.config-item__header h4 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
.config-item__title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
子标题
|
||||
============================================ */
|
||||
.config-item__actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
margin: var(--spacing-xl) 0 var(--spacing-md);
|
||||
padding-left: var(--spacing-sm);
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
border-left: 3px solid var(--primary-color);
|
||||
.config-item__body {
|
||||
padding: var(--spacing-md);
|
||||
}
|
||||
|
||||
/* 添加按钮 */
|
||||
.btn-add-item {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
border: 2px dashed var(--border);
|
||||
background: #fafafa;
|
||||
border-radius: 6px;
|
||||
color: var(--text-tertiary);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.btn-add-item:hover {
|
||||
border-color: var(--primary);
|
||||
color: var(--primary);
|
||||
background: var(--primary-light);
|
||||
}
|
||||
|
||||
.btn-add-item svg {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
空状态
|
||||
============================================ */
|
||||
|
||||
.empty-tip {
|
||||
padding: var(--spacing-xl) var(--spacing-lg);
|
||||
border: 2px dashed var(--border-normal);
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--text-tertiary);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
background: var(--bg-section);
|
||||
.empty-state {
|
||||
padding: 60px 20px;
|
||||
text-align: center;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.empty-state svg {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
opacity: 0.3;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
提示框
|
||||
============================================ */
|
||||
|
||||
.alert {
|
||||
padding: 12px 16px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: var(--spacing-md);
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
上传行
|
||||
============================================ */
|
||||
|
||||
.upload-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.upload-row .t-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.upload-row .t-button {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
底部操作栏
|
||||
============================================ */
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
margin-top: var(--spacing-xl);
|
||||
padding: var(--spacing-md) var(--spacing-lg);
|
||||
background: var(--bg-card);
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--border-light);
|
||||
position: sticky;
|
||||
bottom: 20px;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
/* 按钮样式 */
|
||||
.action-bar .t-button {
|
||||
min-width: 120px;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.action-bar .t-button--primary {
|
||||
background: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.action-bar .t-button--primary:hover {
|
||||
background: var(--primary-hover);
|
||||
border-color: var(--primary-hover);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Tab 标签页
|
||||
============================================ */
|
||||
|
||||
.t-tabs__nav {
|
||||
background: var(--bg-card);
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
padding: 0;
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.t-tab {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.t-tab:hover {
|
||||
color: var(--primary-color);
|
||||
.alert-info {
|
||||
background: var(--primary-light);
|
||||
border-left: 3px solid var(--primary);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.t-tab.t-is-active {
|
||||
color: var(--primary-color) !important;
|
||||
background: var(--primary-light) !important;
|
||||
border-bottom: 2px solid var(--primary-color) !important;
|
||||
.alert code {
|
||||
padding: 2px 6px;
|
||||
background: rgba(24, 144, 255, 0.1);
|
||||
border-radius: 3px;
|
||||
color: var(--primary);
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
工具类
|
||||
Switch开关
|
||||
============================================ */
|
||||
|
||||
.mt-10 {
|
||||
margin-top: var(--spacing-md);
|
||||
}
|
||||
|
||||
.ml-10 {
|
||||
margin-left: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.mb-10 {
|
||||
.form-switch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px var(--spacing-md);
|
||||
background: #fafafa;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--border-light);
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.form-switch label {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
JSON编辑器
|
||||
============================================ */
|
||||
|
||||
.json-editor textarea {
|
||||
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
padding: 16px !important;
|
||||
background: #1e1e1e;
|
||||
color: #d4d4d4;
|
||||
border: 1px solid #3c3c3c;
|
||||
border-radius: 4px;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.json-editor textarea:focus {
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.15);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
响应式
|
||||
============================================ */
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.admin-sidebar {
|
||||
width: 180px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.template {
|
||||
padding: var(--spacing-sm);
|
||||
.admin-sidebar {
|
||||
position: fixed;
|
||||
left: -220px;
|
||||
top: 60px;
|
||||
bottom: 0;
|
||||
z-index: 99;
|
||||
transition: left 0.3s;
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--spacing-md);
|
||||
.admin-sidebar.is-open {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.banner-item,
|
||||
.config-item {
|
||||
.admin-main {
|
||||
padding: var(--spacing-md);
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-sm);
|
||||
position: relative;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.action-bar .t-button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-item--switch {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: var(--spacing-sm);
|
||||
.section-body {
|
||||
padding: var(--spacing-md);
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
滚动条美化 (保持简洁)
|
||||
TDesign组件覆盖
|
||||
============================================ */
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
.t-input,
|
||||
.t-textarea,
|
||||
.t-select {
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--bg-section);
|
||||
.t-input:hover,
|
||||
.t-textarea:hover {
|
||||
border-color: var(--primary) !important;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--border-normal);
|
||||
border-radius: 4px;
|
||||
.t-input:focus-within,
|
||||
.t-textarea:focus-within {
|
||||
border-color: var(--primary) !important;
|
||||
box-shadow: 0 0 0 2px var(--primary-light) !important;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--border-dark);
|
||||
.t-button--primary {
|
||||
background: var(--primary) !important;
|
||||
border-color: var(--primary) !important;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
按钮状态
|
||||
============================================ */
|
||||
|
||||
.t-button--loading {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.t-button--danger:hover {
|
||||
background: #dc2626;
|
||||
border-color: #dc2626;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
表单验证提示
|
||||
============================================ */
|
||||
|
||||
.form-error {
|
||||
color: var(--danger);
|
||||
font-size: 13px;
|
||||
margin-top: var(--spacing-xs);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
成功提示
|
||||
============================================ */
|
||||
|
||||
.success-indicator {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
padding: 6px var(--spacing-sm);
|
||||
background: var(--success);
|
||||
color: white;
|
||||
border-radius: 20px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
其他优化
|
||||
============================================ */
|
||||
|
||||
/* 移除emoji标记,使用纯文本 */
|
||||
.form-item label::before,
|
||||
.banner-item__header h4::before,
|
||||
.config-item__header h4::before,
|
||||
.empty-tip::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Switch 图标简化 */
|
||||
.switch-label-icon {
|
||||
display: none;
|
||||
.t-button--primary:hover {
|
||||
background: var(--primary-hover) !important;
|
||||
border-color: var(--primary-hover) !important;
|
||||
}
|
||||
Reference in New Issue
Block a user