更新显示架构
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "默认模板",
|
||||
"description": "标准企业官网模板,适用于各类企业展示",
|
||||
"version": "1.0.0",
|
||||
"author": "System",
|
||||
"fields": {
|
||||
"site_name": "网站名称",
|
||||
"banner": "轮播图列表",
|
||||
"news": "新闻列表",
|
||||
"solutions": "解决方案",
|
||||
"partners": "合作伙伴"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title data-field="site_name">企业官网</title>
|
||||
<link rel="stylesheet" href="./styles/main.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- 头部 -->
|
||||
<header class="header">
|
||||
<div class="container">
|
||||
<h1 data-field="site_name">企业官网</h1>
|
||||
<nav class="nav">
|
||||
<a href="/">首页</a>
|
||||
<a href="/news">新闻资讯</a>
|
||||
<a href="/solutions">解决方案</a>
|
||||
<a href="/contact">联系我们</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 轮播图 -->
|
||||
<section class="banner">
|
||||
<div class="banner-slides">
|
||||
<div class="slide active" data-field="banner">
|
||||
<img src="./images/banner1.jpg" alt="Banner 1">
|
||||
<div class="banner-content">
|
||||
<h2>欢迎来到我们的网站</h2>
|
||||
<p>专业的企业数字化解决方案提供商</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 新闻资讯 -->
|
||||
<section class="news section">
|
||||
<div class="container">
|
||||
<h2 class="section-title">新闻资讯</h2>
|
||||
<div class="news-list" data-field="news">
|
||||
<div class="news-item">
|
||||
<h3>公司动态</h3>
|
||||
<p>这里是新闻内容...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 解决方案 -->
|
||||
<section class="solutions section">
|
||||
<div class="container">
|
||||
<h2 class="section-title">解决方案</h2>
|
||||
<div class="solutions-grid" data-field="solutions">
|
||||
<div class="solution-card">
|
||||
<h3>解决方案一</h3>
|
||||
<p>详细描述...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 合作伙伴 -->
|
||||
<section class="partners section">
|
||||
<div class="container">
|
||||
<h2 class="section-title">合作伙伴</h2>
|
||||
<div class="partners-grid" data-field="partners">
|
||||
<div class="partner-logo">Partner Logo</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 底部 -->
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<p>© 2024 <span data-field="site_name">企业官网</span>. All rights reserved.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- 数据注入脚本 -->
|
||||
<script src="./js/theme-loader.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,213 @@
|
||||
/**
|
||||
* 模板数据注入脚本
|
||||
* 功能:监听 postMessage 事件,当收到 SET_SITE_DATA 时,
|
||||
* 自动寻找带有 data-field 属性的 HTML 标签,替换为对应的数据
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// 日志开关
|
||||
const DEBUG = false;
|
||||
|
||||
function log(...args) {
|
||||
if (DEBUG) {
|
||||
console.log('[ThemeLoader]', ...args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 替换元素内容或属性
|
||||
* @param {HTMLElement} element - 目标元素
|
||||
* @param {string} field - 字段名
|
||||
* @param {any} value - 数据值
|
||||
*/
|
||||
function applyDataToElement(element, field, value) {
|
||||
if (value === undefined || value === null) {
|
||||
log(`字段 ${field} 值为空,跳过`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果是数组或对象,尝试解析
|
||||
if (typeof value === 'string') {
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
if (parsed) {
|
||||
value = parsed;
|
||||
}
|
||||
} catch (e) {
|
||||
// 保持原值
|
||||
}
|
||||
}
|
||||
|
||||
// 处理数组类型(用于轮播图、列表等)
|
||||
if (Array.isArray(value)) {
|
||||
handleArrayField(element, field, value);
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理对象类型
|
||||
if (typeof value === 'object') {
|
||||
handleObjectField(element, field, value);
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理基础类型(字符串、数字)
|
||||
// 1. 如果是 input/textarea/select,设置为 value
|
||||
const tagName = element.tagName.toLowerCase();
|
||||
if (tagName === 'input' || tagName === 'textarea' || tagName === 'select') {
|
||||
element.value = value;
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 如果有 src 属性,设置为 src(图片等)
|
||||
if (element.hasAttribute('src') && !element.hasAttribute('data-keep-src')) {
|
||||
// 检查是否是占位符图片
|
||||
const currentSrc = element.getAttribute('src');
|
||||
if (!currentSrc || currentSrc.indexOf('placeholder') > -1) {
|
||||
element.src = value;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 否则设置为 innerText
|
||||
element.innerText = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理数组类型的字段(如轮播图、列表)
|
||||
*/
|
||||
function handleArrayField(container, field, dataList) {
|
||||
// 查找模板元素(带有 data-template 属性的元素)
|
||||
const templateElement = container.querySelector('[data-template]');
|
||||
if (!templateElement) {
|
||||
log(`字段 ${field} 未找到模板元素`);
|
||||
return;
|
||||
}
|
||||
|
||||
const template = templateElement.cloneNode(true);
|
||||
template.removeAttribute('data-template');
|
||||
template.style.display = '';
|
||||
|
||||
// 清空容器
|
||||
container.innerHTML = '';
|
||||
|
||||
// 渲染每个数据项
|
||||
dataList.forEach((item, index) => {
|
||||
const itemElement = template.cloneNode(true);
|
||||
applyDataToObject(itemElement, item, index);
|
||||
container.appendChild(itemElement);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理对象类型的字段
|
||||
*/
|
||||
function handleObjectField(element, field, data) {
|
||||
// 递归处理对象属性
|
||||
applyDataToObject(element, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据应用到元素及其子元素
|
||||
*/
|
||||
function applyDataToObject(element, data, index = 0) {
|
||||
// 处理 data-field 属性
|
||||
const fieldElements = element.querySelectorAll('[data-field]');
|
||||
fieldElements.forEach(el => {
|
||||
const field = el.getAttribute('data-field');
|
||||
// 支持点号分隔的路径,如 "banner.0.image"
|
||||
const value = getNestedValue(data, field);
|
||||
if (value !== undefined) {
|
||||
applyDataToElement(el, field, value);
|
||||
}
|
||||
});
|
||||
|
||||
// 也处理元素本身的 data-field
|
||||
if (element.hasAttribute('data-field')) {
|
||||
const field = element.getAttribute('data-field');
|
||||
const value = getNestedValue(data, field);
|
||||
if (value !== undefined) {
|
||||
applyDataToElement(element, field, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取嵌套属性值
|
||||
* @param {object} obj - 数据对象
|
||||
* @param {string} path - 属性路径,如 "banner.0.image"
|
||||
* @returns {any}
|
||||
*/
|
||||
function getNestedValue(obj, path) {
|
||||
if (!path) return obj;
|
||||
|
||||
const keys = path.split('.');
|
||||
let value = obj;
|
||||
|
||||
for (const key of keys) {
|
||||
if (value === null || value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
value = value[key];
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化数据注入
|
||||
*/
|
||||
function init() {
|
||||
log('ThemeLoader 初始化');
|
||||
|
||||
// 监听来自父窗口的消息
|
||||
window.addEventListener('message', function(event) {
|
||||
log('收到消息:', event.data);
|
||||
|
||||
// 验证消息类型
|
||||
if (!event.data || event.data.type !== 'SET_SITE_DATA') {
|
||||
return;
|
||||
}
|
||||
|
||||
const siteData = event.data.data;
|
||||
if (!siteData) {
|
||||
log('未收到有效数据');
|
||||
return;
|
||||
}
|
||||
|
||||
log('开始注入数据:', siteData);
|
||||
|
||||
// 查找所有带有 data-field 属性的元素
|
||||
const elements = document.querySelectorAll('[data-field]');
|
||||
|
||||
elements.forEach(element => {
|
||||
const field = element.getAttribute('data-field');
|
||||
const value = siteData[field];
|
||||
|
||||
if (value !== undefined) {
|
||||
applyDataToElement(element, field, value);
|
||||
}
|
||||
});
|
||||
|
||||
// 触发自定义事件,通知数据已加载
|
||||
window.dispatchEvent(new CustomEvent('themeDataLoaded', {
|
||||
detail: siteData
|
||||
}));
|
||||
|
||||
log('数据注入完成');
|
||||
});
|
||||
|
||||
// 发送就绪消息给父窗口
|
||||
window.parent.postMessage({
|
||||
type: 'THEME_READY'
|
||||
}, '*');
|
||||
}
|
||||
|
||||
// DOM 加载完成后初始化
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,153 @@
|
||||
/* 基础样式 */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: 60px 0;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
text-align: center;
|
||||
font-size: 32px;
|
||||
margin-bottom: 40px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 头部 */
|
||||
.header {
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.header .container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 70px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 24px;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.nav a {
|
||||
margin-left: 30px;
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.nav a:hover {
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
/* 轮播图 */
|
||||
.banner {
|
||||
margin-top: 70px;
|
||||
height: 500px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.banner-slides .slide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.banner-slides .slide.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.banner-slides .slide img {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.banner-content {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.banner-content h2 {
|
||||
font-size: 48px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.banner-content p {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
/* 新闻 */
|
||||
.news-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.news-item {
|
||||
background: #f5f5f5;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* 解决方案 */
|
||||
.solutions-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.solution-card {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 40px 20px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 合作伙伴 */
|
||||
.partners-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.partner-logo {
|
||||
background: #f5f5f5;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* 底部 */
|
||||
.footer {
|
||||
background: #333;
|
||||
color: #fff;
|
||||
padding: 30px 0;
|
||||
text-align: center;
|
||||
}
|
||||
Reference in New Issue
Block a user