621 lines
31 KiB
HTML
621 lines
31 KiB
HTML
<!DOCTYPE html>
|
|
<!--
|
|
社交信息流 · 布局样式复刻 Demo(无品牌标识版)
|
|
------------------------------------------------
|
|
- 版式/布局参考主流社交平台桌面端信息流(顶栏 + 三栏 + 信息流卡片)
|
|
- 不包含任何真实平台的商标 / Logo / 品牌元素,可放心二次修改
|
|
- 所有展示数据集中在 <script> 顶部的「数据区」,替换字段即可换数据
|
|
- 双击直接用浏览器打开,无任何外部依赖
|
|
-->
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>社交信息流 · 布局样式 Demo</title>
|
|
<style>
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
html { color-scheme: light; }
|
|
body {
|
|
background: #f0f2f5;
|
|
color: #050505;
|
|
font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue",
|
|
"PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
|
|
font-size: 15px;
|
|
}
|
|
button { font: inherit; border: 0; background: none; cursor: pointer; color: inherit; }
|
|
svg { display: block; }
|
|
|
|
/* ============ 顶栏 ============ */
|
|
.topbar {
|
|
position: fixed; inset: 0 0 auto 0; height: 56px; z-index: 100;
|
|
background: #fff; box-shadow: 0 1px 2px rgba(0,0,0,.1);
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
padding: 0 16px;
|
|
}
|
|
.tb-left, .tb-right { display: flex; align-items: center; gap: 8px; min-width: 280px; }
|
|
.tb-right { justify-content: flex-end; }
|
|
.logo-mark {
|
|
width: 40px; height: 40px; border-radius: 50%;
|
|
background: #1877f2; color: #fff; display: grid; place-items: center; flex: none;
|
|
}
|
|
.search-pill {
|
|
display: flex; align-items: center; gap: 8px;
|
|
background: #f0f2f5; border-radius: 20px; padding: 9px 14px;
|
|
width: 240px; color: #65676b;
|
|
}
|
|
.tb-center { display: flex; gap: 6px; }
|
|
.tb-tab {
|
|
width: 108px; height: 56px; border-radius: 8px;
|
|
display: grid; place-items: center; color: #65676b; position: relative;
|
|
}
|
|
.tb-tab:hover { background: #f2f2f2; }
|
|
.tb-tab.active { color: #1877f2; }
|
|
.tb-tab.active::after {
|
|
content: ""; position: absolute; left: 12px; right: 12px; bottom: 0;
|
|
height: 3px; border-radius: 3px 3px 0 0; background: #1877f2;
|
|
}
|
|
.icon-btn {
|
|
width: 40px; height: 40px; border-radius: 50%;
|
|
background: #e4e6eb; display: grid; place-items: center; position: relative;
|
|
}
|
|
.icon-btn:hover { background: #d8dadf; }
|
|
.notif-badge {
|
|
position: absolute; top: -3px; right: -3px;
|
|
background: #e41e3f; color: #fff; font-size: 11px; font-weight: 600;
|
|
line-height: 1; border-radius: 10px; padding: 3px 5px; border: 2px solid #fff;
|
|
}
|
|
.create-btn {
|
|
display: flex; align-items: center; gap: 6px;
|
|
background: #e4e6eb; border-radius: 8px; padding: 10px 14px; font-weight: 600;
|
|
}
|
|
.create-btn:hover { background: #d8dadf; }
|
|
|
|
/* ============ 三栏布局 ============ */
|
|
.layout {
|
|
display: flex; justify-content: center; gap: 16px;
|
|
max-width: 1240px; margin: 0 auto; padding: 72px 12px 32px;
|
|
}
|
|
.side {
|
|
width: 300px; flex: none;
|
|
position: sticky; top: 72px; align-self: flex-start;
|
|
max-height: calc(100vh - 80px); overflow-y: auto;
|
|
}
|
|
.feed { width: 590px; flex: none; }
|
|
@media (max-width: 1160px) { .side.right { display: none; } }
|
|
@media (max-width: 980px) { .side.left { display: none; } }
|
|
|
|
/* ============ 左侧导航 ============ */
|
|
.side-item {
|
|
display: flex; align-items: center; gap: 12px;
|
|
padding: 8px 12px; margin: 0 8px; border-radius: 8px;
|
|
font-size: 15px; font-weight: 500;
|
|
}
|
|
.side-item:hover { background: #e4e6eb; }
|
|
.side-item .si-ic { width: 36px; height: 36px; flex: none; color: #3a4a5c; display: grid; place-items: center; }
|
|
.side-sep { height: 1px; background: #e4e6eb; margin: 10px 16px; }
|
|
.side-title { color: #65676b; font-size: 17px; font-weight: 600; margin: 14px 16px 4px; }
|
|
.see-more { color: #65676b; }
|
|
|
|
/* ============ 快拍 ============ */
|
|
.stories { display: flex; gap: 8px; overflow-x: auto; padding: 4px 2px 12px; }
|
|
.story {
|
|
width: 112px; height: 200px; flex: none; border-radius: 12px;
|
|
position: relative; overflow: hidden; box-shadow: 0 1px 2px rgba(0,0,0,.15);
|
|
}
|
|
.story-create {
|
|
background: #fff; display: flex; flex-direction: column; justify-content: flex-end;
|
|
}
|
|
.story-create .sc-img { height: 148px; display: grid; place-items: center; color: #a8b3c4; }
|
|
.story-create .sc-label {
|
|
padding: 10px 8px 12px; text-align: center; font-size: 13px; font-weight: 600;
|
|
border-top: 1px solid #e4e6eb;
|
|
}
|
|
.story-create .sc-plus {
|
|
position: absolute; left: 50%; top: 124px; transform: translateX(-50%);
|
|
width: 36px; height: 36px; border-radius: 50%; background: #1877f2; color: #fff;
|
|
display: grid; place-items: center; border: 3px solid #fff;
|
|
}
|
|
.story-meta { position: absolute; left: 8px; right: 8px; bottom: 8px; color: #fff; }
|
|
.story-meta .sm-name {
|
|
font-size: 13px; font-weight: 600; text-shadow: 0 1px 2px rgba(0,0,0,.6);
|
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
}
|
|
.story-meta .avatar { border: 2px solid #fff; margin-bottom: 6px; }
|
|
|
|
/* ============ 卡片通用 ============ */
|
|
.card {
|
|
background: #fff; border-radius: 8px;
|
|
box-shadow: 0 1px 2px rgba(0,0,0,.1); margin-bottom: 16px;
|
|
}
|
|
.avatar {
|
|
width: 40px; height: 40px; border-radius: 50%; flex: none;
|
|
display: grid; place-items: center; color: #fff; font-weight: 600; font-size: 16px;
|
|
user-select: none; background: #1877f2;
|
|
}
|
|
.avatar.sm { width: 32px; height: 32px; font-size: 13px; }
|
|
|
|
/* ============ 发帖框 ============ */
|
|
.composer-top { display: flex; align-items: center; gap: 10px; padding: 12px 16px; }
|
|
.composer-input {
|
|
flex: 1; background: #f0f2f5; border-radius: 20px; padding: 10px 16px;
|
|
color: #65676b; font-size: 16px;
|
|
}
|
|
.composer-actions { display: flex; border-top: 1px solid #e4e6eb; margin: 2px 12px 0; }
|
|
.c-action {
|
|
flex: 1; display: flex; justify-content: center; align-items: center; gap: 8px;
|
|
padding: 10px; border-radius: 6px; font-weight: 600; color: #65676b;
|
|
}
|
|
.c-action:hover { background: #f2f2f2; }
|
|
.ic-video { color: #f3425f; } .ic-photo { color: #45bd62; } .ic-emoji { color: #f7b928; }
|
|
|
|
/* ============ 帖子 ============ */
|
|
.post-head { display: flex; align-items: center; gap: 10px; padding: 12px 16px 0; }
|
|
.ph-meta { flex: 1; min-width: 0; }
|
|
.ph-name { font-weight: 600; display: flex; align-items: center; gap: 4px; }
|
|
.ph-sub { color: #65676b; font-size: 13px; display: flex; align-items: center; gap: 4px; margin-top: 2px; }
|
|
.ph-more { color: #65676b; }
|
|
.post-text { padding: 12px 16px; line-height: 1.55; white-space: pre-wrap; }
|
|
.post-media { display: grid; gap: 2px; background: #fff; }
|
|
.post-media.n1 { grid-template-columns: 1fr; }
|
|
.post-media.n2 { grid-template-columns: 1fr 1fr; }
|
|
.post-media.n3 { grid-template-columns: 2fr 1fr; }
|
|
.post-media.n4 { grid-template-columns: 1fr 1fr; }
|
|
.tile { position: relative; overflow: hidden; aspect-ratio: 1.8; background: #dfe7f5; }
|
|
.post-media.n3 .tile:first-child { grid-row: span 2; aspect-ratio: auto; }
|
|
.tile img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
|
.img-ph {
|
|
position: absolute; inset: 0; display: grid; place-items: center;
|
|
color: rgba(255,255,255,.9);
|
|
}
|
|
.tile-more {
|
|
position: absolute; inset: 0; background: rgba(0,0,0,.45); color: #fff;
|
|
display: grid; place-items: center; font-size: 26px; font-weight: 600;
|
|
}
|
|
.link-card {
|
|
margin: 0 16px 12px; border: 1px solid #e4e6eb; border-radius: 8px;
|
|
overflow: hidden; display: flex;
|
|
}
|
|
.link-img { width: 140px; flex: none; min-height: 120px; position: relative; background: #dfe7f5; }
|
|
.link-img .img-ph { color: rgba(255,255,255,.9); }
|
|
.link-body { padding: 10px 14px; min-width: 0; }
|
|
.link-domain { color: #65676b; font-size: 12px; text-transform: uppercase; letter-spacing: .04em; }
|
|
.link-title { font-weight: 600; font-size: 15px; margin: 3px 0 4px; }
|
|
.link-desc { color: #65676b; font-size: 13px; line-height: 1.4; }
|
|
|
|
.stats { display: flex; align-items: center; gap: 6px; padding: 10px 16px 6px; color: #65676b; }
|
|
.stats .grow { flex: 1; }
|
|
.rx {
|
|
width: 20px; height: 20px; border-radius: 50%; background: #fff;
|
|
display: grid; place-items: center; box-shadow: 0 0 0 1px rgba(0,0,0,.05);
|
|
}
|
|
.post-actions { display: flex; border-top: 1px solid #e4e6eb; margin: 4px 12px 4px; }
|
|
.p-action {
|
|
flex: 1; display: flex; justify-content: center; align-items: center; gap: 8px;
|
|
padding: 9px; border-radius: 6px; font-weight: 600; color: #65676b;
|
|
}
|
|
.p-action:hover { background: #f2f2f2; }
|
|
.p-action.liked { color: #1877f2; }
|
|
|
|
.comments { padding: 2px 16px 12px; }
|
|
.comment { display: flex; gap: 8px; margin-top: 8px; }
|
|
.c-bubble { background: #f0f2f5; border-radius: 16px; padding: 8px 12px; min-width: 0; }
|
|
.c-name { font-weight: 600; font-size: 13px; }
|
|
.c-text { font-size: 15px; line-height: 1.45; }
|
|
.c-foot { padding-left: 40px; color: #65676b; font-size: 13px; font-weight: 600; display: flex; gap: 12px; margin-top: 2px; }
|
|
|
|
/* ============ 右侧栏 ============ */
|
|
.sponsored { display: flex; gap: 12px; padding: 8px 12px; margin: 0 8px; border-radius: 8px; }
|
|
.sponsored:hover { background: #e4e6eb; }
|
|
.sq {
|
|
width: 96px; height: 96px; flex: none; border-radius: 8px; overflow: hidden;
|
|
position: relative; background: #dfe7f5;
|
|
}
|
|
.sp-title { font-weight: 500; }
|
|
.sp-domain { color: #65676b; font-size: 13px; margin-top: 2px; }
|
|
.birthday {
|
|
display: flex; gap: 10px; padding: 4px 16px; font-size: 14px; line-height: 1.45;
|
|
}
|
|
.contact-row {
|
|
display: flex; align-items: center; gap: 10px;
|
|
padding: 6px 12px; margin: 0 8px; border-radius: 8px; font-weight: 500;
|
|
}
|
|
.contact-row:hover { background: #e4e6eb; }
|
|
.contact-av { position: relative; flex: none; }
|
|
.online-dot {
|
|
position: absolute; right: 0; bottom: 0; width: 10px; height: 10px;
|
|
border-radius: 50%; background: #31a24c; border: 2px solid #fff;
|
|
}
|
|
.contacts-head {
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
padding: 6px 16px 2px;
|
|
}
|
|
.contacts-head .ch-actions { display: flex; gap: 8px; color: #65676b; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<!-- ================= 顶栏 ================= -->
|
|
<header class="topbar">
|
|
<div class="tb-left">
|
|
<div class="logo-mark" title="示例 Logo(替换成你们自己的标识)">
|
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M21 11.5a8.5 8.5 0 0 1-8.5 8.5c-1.5 0-2.9-.4-4.1-1L3 20l1-5.4A8.5 8.5 0 1 1 21 11.5z"/>
|
|
</svg>
|
|
</div>
|
|
<div class="search-pill">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><line x1="16.5" y1="16.5" x2="21" y2="21"/></svg>
|
|
搜索
|
|
</div>
|
|
</div>
|
|
|
|
<nav class="tb-center">
|
|
<button class="tb-tab active" title="首页">
|
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 10.5 12 3l9 7.5"/><path d="M5 9.5V21h14V9.5"/></svg>
|
|
</button>
|
|
<button class="tb-tab" title="视频">
|
|
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="2.5" y="6" width="13" height="12" rx="2.5"/><path d="M15.5 10.5 21.5 7v10l-6-3.5"/></svg>
|
|
</button>
|
|
<button class="tb-tab" title="市场">
|
|
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 9.5 5.2 4h13.6L20 9.5"/><path d="M4 9.5V20h16V9.5"/><path d="M10 20v-5.5h4V20"/></svg>
|
|
</button>
|
|
<button class="tb-tab" title="群组">
|
|
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="8.5" r="3"/><path d="M3 20c0-3.2 2.7-5 6-5s6 1.8 6 5"/><circle cx="17" cy="9.5" r="2.3"/><path d="M16 15.3c2.9.2 5 1.8 5 4.7"/></svg>
|
|
</button>
|
|
<button class="tb-tab" title="游戏">
|
|
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="2.5" y="7.5" width="19" height="10" rx="4"/><path d="M7 10.5v4M5 12.5h4"/><circle cx="16" cy="11" r=".6" fill="currentColor"/><circle cx="18.5" cy="13.5" r=".6" fill="currentColor"/></svg>
|
|
</button>
|
|
</nav>
|
|
|
|
<div class="tb-right">
|
|
<button class="create-btn">
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><path d="M12 5v14M5 12h14"/></svg>
|
|
创建
|
|
</button>
|
|
<button class="icon-btn" title="菜单">
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><circle cx="5" cy="5" r="1.7"/><circle cx="12" cy="5" r="1.7"/><circle cx="19" cy="5" r="1.7"/><circle cx="5" cy="12" r="1.7"/><circle cx="12" cy="12" r="1.7"/><circle cx="19" cy="12" r="1.7"/><circle cx="5" cy="19" r="1.7"/><circle cx="12" cy="19" r="1.7"/><circle cx="19" cy="19" r="1.7"/></svg>
|
|
</button>
|
|
<button class="icon-btn" title="消息">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M21 11.5a8.5 8.5 0 0 1-8.5 8.5c-1.5 0-2.9-.4-4.1-1L3 20l1-5.4A8.5 8.5 0 1 1 21 11.5z"/><path d="M12.8 8 9.3 13h2.6l-.6 3.3 3.5-5.1h-2.6z"/></svg>
|
|
</button>
|
|
<button class="icon-btn" title="通知">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M18 16v-5a6 6 0 1 0-12 0v5l-1.6 1.8h15.2z"/><path d="M10 20.2a2.1 2.1 0 0 0 4 0"/></svg>
|
|
<span class="notif-badge">3</span>
|
|
</button>
|
|
<div class="avatar" style="background:#45bd62">我</div>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- ================= 三栏 ================= -->
|
|
<div class="layout">
|
|
|
|
<!-- ---- 左侧导航 ---- -->
|
|
<aside class="side left">
|
|
<div class="side-item">
|
|
<div class="si-ic"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="8.5" r="3"/><path d="M3 20c0-3.2 2.7-5 6-5s6 1.8 6 5"/><circle cx="17" cy="9.5" r="2.3"/><path d="M16 15.3c2.9.2 5 1.8 5 4.7"/></svg></div>
|
|
好友
|
|
</div>
|
|
<div class="side-item">
|
|
<div class="si-ic"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 7.5V12l3 2"/></svg></div>
|
|
回忆动态
|
|
</div>
|
|
<div class="side-item">
|
|
<div class="si-ic"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21l-7-5-7 5V5.5A2.5 2.5 0 0 1 7.5 3h9a2.5 2.5 0 0 1 2.5 2.5z"/></svg></div>
|
|
已保存
|
|
</div>
|
|
<div class="side-item">
|
|
<div class="si-ic"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="8.5" r="3"/><path d="M3 20c0-3.2 2.7-5 6-5s6 1.8 6 5"/><circle cx="17" cy="9.5" r="2.3"/><path d="M16 15.3c2.9.2 5 1.8 5 4.7"/></svg></div>
|
|
群组
|
|
</div>
|
|
<div class="side-item">
|
|
<div class="si-ic"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="2.5" y="6" width="13" height="12" rx="2.5"/><path d="M15.5 10.5 21.5 7v10l-6-3.5"/></svg></div>
|
|
视频
|
|
</div>
|
|
<div class="side-item">
|
|
<div class="si-ic"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 9.5 5.2 4h13.6L20 9.5"/><path d="M4 9.5V20h16V9.5"/><path d="M10 20v-5.5h4V20"/></svg></div>
|
|
市场
|
|
</div>
|
|
<div class="side-item">
|
|
<div class="si-ic"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M16 3v4M8 3v4M3 11h18"/></svg></div>
|
|
活动
|
|
</div>
|
|
|
|
<div class="side-sep"></div>
|
|
<div class="side-title">你的捷径</div>
|
|
<div class="side-item"><div class="avatar sm" style="background:#7c5cff">登</div>周末登山小队</div>
|
|
<div class="side-item"><div class="avatar sm" style="background:#12b5cb">摄</div>城市摄影交流</div>
|
|
<div class="side-item"><div class="avatar sm" style="background:#f3425f">书</div>读书会</div>
|
|
|
|
<div class="side-item see-more">
|
|
<div class="si-ic"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg></div>
|
|
查看更多
|
|
</div>
|
|
</aside>
|
|
|
|
<!-- ---- 中间信息流 ---- -->
|
|
<main class="feed">
|
|
<!-- 快拍 -->
|
|
<div class="stories" id="stories"></div>
|
|
|
|
<!-- 发帖框 -->
|
|
<div class="card">
|
|
<div class="composer-top">
|
|
<div class="avatar" style="background:#45bd62">我</div>
|
|
<div class="composer-input">有什么新鲜事?</div>
|
|
</div>
|
|
<div class="composer-actions">
|
|
<button class="c-action ic-video">
|
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="2.5" y="6" width="13" height="12" rx="2.5"/><path d="M15.5 10.5 21.5 7v10l-6-3.5"/></svg>
|
|
直播视频
|
|
</button>
|
|
<button class="c-action ic-photo">
|
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><circle cx="8.5" cy="10" r="1.5"/><path d="M21 15.5l-5-5-9 8"/></svg>
|
|
照片/视频
|
|
</button>
|
|
<button class="c-action ic-emoji">
|
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M8.5 14.5s1.3 1.8 3.5 1.8 3.5-1.8 3.5-1.8"/><circle cx="9" cy="9.5" r=".6" fill="currentColor"/><circle cx="15" cy="9.5" r=".6" fill="currentColor"/></svg>
|
|
感受/活动
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 帖子(由下方数据区渲染) -->
|
|
<div id="posts"></div>
|
|
</main>
|
|
|
|
<!-- ---- 右侧栏 ---- -->
|
|
<aside class="side right">
|
|
<div class="side-title" style="margin-top:4px">赞助内容</div>
|
|
<div id="sponsored"></div>
|
|
|
|
<div class="side-sep"></div>
|
|
<div class="birthday">
|
|
<span style="font-size:24px">🎂</span>
|
|
<div><b>小林</b> 和 <b>赵敏</b> 今天过生日</div>
|
|
</div>
|
|
|
|
<div class="side-sep"></div>
|
|
<div class="contacts-head">
|
|
<span style="color:#65676b;font-weight:600">联络人</span>
|
|
<span class="ch-actions">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><line x1="16.5" y1="16.5" x2="21" y2="21"/></svg>
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2.5" y="6" width="13" height="12" rx="2.5"/><path d="M15.5 10.5 21.5 7v10l-6-3.5"/></svg>
|
|
</span>
|
|
</div>
|
|
<div id="contacts"></div>
|
|
</aside>
|
|
</div>
|
|
|
|
<script>
|
|
/* ============================================================
|
|
数据区 —— 换数据只改这里
|
|
------------------------------------------------------------
|
|
FEED 帖子字段:
|
|
name 作者名 verified 是否显示认证标
|
|
time 发布时间文案 audience "公开" / "好友"
|
|
text 正文(\n 换行)
|
|
images 配图数组:"" = 灰色占位图;填图片 URL 则显示真实图片
|
|
数量 1-4 张,布局自动变化(1 大图 / 2 / 3 / 4 宫格)
|
|
link 可选链接卡片 { domain, title, desc }
|
|
likes 点赞数 shares 分享数
|
|
comments 评论数组 [{ name, text, time, likes }]
|
|
============================================================ */
|
|
const FEED = [
|
|
{
|
|
name: "张伟", time: "5 小时", audience: "公开",
|
|
text: "连续加班两周终于把项目收尾了,今天准点下班,\n路上顺手拍了一张晚霞。生活还是需要一点仪式感 🙂",
|
|
images: [""],
|
|
likes: 128, shares: 6,
|
|
comments: [
|
|
{ name: "赵敏", text: "恭喜收尾!这张晚霞拍得真好", time: "4 小时", likes: 12 },
|
|
{ name: "孙鹏", text: "下次组个饭局庆祝一下", time: "3 小时", likes: 3 },
|
|
],
|
|
},
|
|
{
|
|
name: "城市天气指南", verified: true, time: "3 小时", audience: "公开",
|
|
text: "本周天气提示:周三起冷空气过境,气温下降 6–8℃,早晚温差加大,提醒大家适当添衣。详细预报见评论区链接。",
|
|
images: ["", "", ""],
|
|
likes: 402, shares: 36,
|
|
comments: [
|
|
{ name: "吴倩", text: "收到,已经把薄羽绒翻出来了", time: "2 小时", likes: 8 },
|
|
],
|
|
},
|
|
{
|
|
name: "王强", time: "9 小时", audience: "好友",
|
|
text: "求推荐一条周末适合新手的徒步路线,最好单程 10 公里以内、交通方便的。打算这周就走,谢谢大家!",
|
|
images: [],
|
|
likes: 17, shares: 0,
|
|
comments: [
|
|
{ name: "陈晨", text: "西郊森林公园的环线不错,全程约 8 公里,坡度平缓", time: "8 小时", likes: 5 },
|
|
{ name: "周婷", text: "记得带够水,最近天气比较干", time: "7 小时", likes: 2 },
|
|
],
|
|
},
|
|
{
|
|
name: "陈晨", time: "1 天", audience: "公开",
|
|
text: "整理了一篇新手徒步装备清单,有需要的朋友自取~",
|
|
images: [],
|
|
link: { domain: "example-outdoor.com", title: "2026 秋季徒步装备清单(新手向)", desc: "从鞋服到导航设备,一份可以照着买的入门清单。" },
|
|
likes: 54, shares: 9,
|
|
comments: [],
|
|
},
|
|
];
|
|
|
|
const STORIES = ["赵敏", "孙鹏", "周婷", "吴倩", "林芳"];
|
|
|
|
const SPONSORED = [
|
|
{ title: "云端笔记 Pro", domain: "cloudnote.example.com" },
|
|
{ title: "户外装备精选", domain: "outdoor.example.com" },
|
|
];
|
|
|
|
const CONTACTS = ["刘洋", "赵敏", "孙鹏", "周婷", "吴倩", "郑凯", "林芳"];
|
|
|
|
/* ============================================================
|
|
以下为渲染逻辑,一般不用动
|
|
============================================================ */
|
|
const AV_COLORS = ["#1877f2", "#f3425f", "#45bd62", "#f7b928", "#7c5cff", "#12b5cb"];
|
|
const PH_GRADIENTS = [
|
|
"linear-gradient(135deg,#a8c5f0,#dfe7f5)",
|
|
"linear-gradient(135deg,#f8d3c5,#f7ebe4)",
|
|
"linear-gradient(135deg,#c9e5d3,#eaf5ee)",
|
|
"linear-gradient(135deg,#e2d8f7,#f0eaf9)",
|
|
"linear-gradient(135deg,#f6e3b0,#faf2df)",
|
|
];
|
|
const STORY_GRADIENTS = [
|
|
"linear-gradient(165deg,#5b8def,#8f6ee8)",
|
|
"linear-gradient(165deg,#f5a623,#ec5e8f)",
|
|
"linear-gradient(165deg,#2ec3a6,#3f8ee8)",
|
|
"linear-gradient(165deg,#8f6ee8,#5b8def)",
|
|
"linear-gradient(165deg,#ec5e8f,#f5a623)",
|
|
];
|
|
|
|
const esc = s => String(s).replace(/[&<>"]/g, c => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c]));
|
|
const colorOf = name => AV_COLORS[[...name].reduce((a, c) => a + c.charCodeAt(0), 0) % AV_COLORS.length];
|
|
const avatarHtml = (name, cls = "") =>
|
|
`<div class="avatar ${cls}" style="background:${colorOf(name)}">${esc(name.slice(0, 1))}</div>`;
|
|
|
|
const ICONS = {
|
|
like: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.3a2 2 0 0 0 2-1.7l1.4-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"/></svg>',
|
|
likeFilled: '<svg width="14" height="14" viewBox="0 0 24 24" fill="#1877f2" stroke="#1877f2" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.3a2 2 0 0 0 2-1.7l1.4-9a2 2 0 0 0-2-2.3z"/><path d="M7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"/></svg>',
|
|
heart: '<svg width="14" height="14" viewBox="0 0 24 24" fill="#f3425f"><path d="M20.8 4.6a5.5 5.5 0 0 0-7.8 0L12 5.7l-1-1.1a5.5 5.5 0 0 0-7.8 7.8l1 1L12 21.2l7.8-7.8 1-1a5.5 5.5 0 0 0 0-7.8z"/></svg>',
|
|
comment: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/></svg>',
|
|
share: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><path d="M8.59 13.51l6.83 3.98M15.41 6.51l-6.82 3.98"/></svg>',
|
|
globe: '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a13.5 13.5 0 0 1 0 18 13.5 13.5 0 0 1 0-18z"/></svg>',
|
|
friends: '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="9" cy="8.5" r="3"/><path d="M3 20c0-3.2 2.7-5 6-5s6 1.8 6 5"/><circle cx="17" cy="9.5" r="2.3"/></svg>',
|
|
dots: '<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><circle cx="5" cy="12" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="19" cy="12" r="1.8"/></svg>',
|
|
mountain: '<svg width="46" height="46" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><circle cx="8.5" cy="10" r="1.5"/><path d="M21 15.5l-5-5-9 8"/></svg>',
|
|
link: '<svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.5.5l3-3a5 5 0 0 0-7-7l-1.7 1.7"/><path d="M14 11a5 5 0 0 0-7.5-.5l-3 3a5 5 0 0 0 7 7l1.7-1.7"/></svg>',
|
|
check: '<svg width="14" height="14" viewBox="0 0 24 24" fill="#1877f2"><circle cx="12" cy="12" r="10"/><path d="M17.5 9.5 10.7 16 7 12.3l1.4-1.4 2.3 2.3 5.4-5.2z" fill="#fff"/></svg>',
|
|
plus: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"><path d="M12 5v14M5 12h14"/></svg>',
|
|
};
|
|
|
|
const audienceIcon = a => (a === "好友" ? ICONS.friends : ICONS.globe);
|
|
const isImg = v => /^(https?:|data:|\.?\/)/.test(v);
|
|
|
|
function tileHtml(v, i) {
|
|
const inner = isImg(v)
|
|
? `<img src="${esc(v)}" alt="">`
|
|
: `<div class="img-ph" style="background:${PH_GRADIENTS[i % PH_GRADIENTS.length]}">${ICONS.mountain}</div>`;
|
|
return `<div class="tile">${inner}</div>`;
|
|
}
|
|
|
|
function mediaHtml(images) {
|
|
if (!images || !images.length) return "";
|
|
const shown = images.slice(0, 4);
|
|
const cls = `n${Math.min(shown.length, 4)}`;
|
|
let html = shown.map((v, i) => tileHtml(v, i)).join("");
|
|
if (images.length > 4) {
|
|
html = html.replace(/<div class="tile">[\s\S]*<\/div>$/, m =>
|
|
m + `<div class="tile-more">+${images.length - 3}</div>`);
|
|
}
|
|
return `<div class="post-media ${cls}">${html}</div>`;
|
|
}
|
|
|
|
function linkCardHtml(link) {
|
|
if (!link) return "";
|
|
return `
|
|
<div class="link-card">
|
|
<div class="link-img"><div class="img-ph" style="background:linear-gradient(135deg,#a8c5f0,#dfe7f5)">${ICONS.link}</div></div>
|
|
<div class="link-body">
|
|
<div class="link-domain">${esc(link.domain)}</div>
|
|
<div class="link-title">${esc(link.title)}</div>
|
|
<div class="link-desc">${esc(link.desc || "")}</div>
|
|
</div>
|
|
</div>`;
|
|
}
|
|
|
|
function commentHtml(c) {
|
|
return `
|
|
<div class="comment">
|
|
${avatarHtml(c.name, "sm")}
|
|
<div style="min-width:0">
|
|
<div class="c-bubble">
|
|
<div class="c-name">${esc(c.name)}</div>
|
|
<div class="c-text">${esc(c.text)}</div>
|
|
</div>
|
|
<div class="c-foot">
|
|
<span>赞 ${c.likes}</span><span>回复</span><span>${esc(c.time)}</span>
|
|
</div>
|
|
</div>
|
|
</div>`;
|
|
}
|
|
|
|
function postHtml(p) {
|
|
const comments = (p.comments || []).map(commentHtml).join("");
|
|
const commentCount = p.comments.length;
|
|
return `
|
|
<article class="card post">
|
|
<div class="post-head">
|
|
${avatarHtml(p.name)}
|
|
<div class="ph-meta">
|
|
<div class="ph-name">${esc(p.name)}${p.verified ? ICONS.check : ""}</div>
|
|
<div class="ph-sub">${audienceIcon(p.audience)} ${esc(p.time)}</div>
|
|
</div>
|
|
<button class="ph-more">${ICONS.dots}</button>
|
|
</div>
|
|
${p.text ? `<div class="post-text">${esc(p.text)}</div>` : ""}
|
|
${mediaHtml(p.images)}
|
|
${linkCardHtml(p.link)}
|
|
<div class="stats">
|
|
<span class="rx">${ICONS.likeFilled}</span><span class="rx">${ICONS.heart}</span>
|
|
<span class="like-count" data-base="${p.likes}">${p.likes}</span>
|
|
<span class="grow"></span>
|
|
${commentCount ? `<span>${commentCount} 条评论</span>` : ""}
|
|
${p.shares ? `<span>· ${p.shares} 次分享</span>` : ""}
|
|
</div>
|
|
<div class="post-actions">
|
|
<button class="p-action act-like">${ICONS.like}<span>赞</span></button>
|
|
<button class="p-action">${ICONS.comment}<span>评论</span></button>
|
|
<button class="p-action">${ICONS.share}<span>分享</span></button>
|
|
</div>
|
|
${comments ? `<div class="comments">${comments}</div>` : ""}
|
|
</article>`;
|
|
}
|
|
|
|
function storiesHtml() {
|
|
const items = STORIES.map((n, i) => `
|
|
<div class="story" style="background:${STORY_GRADIENTS[i % STORY_GRADIENTS.length]}">
|
|
<div class="story-meta">
|
|
<div class="avatar sm" style="background:${colorOf(n)}">${esc(n.slice(0, 1))}</div>
|
|
<div class="sm-name">${esc(n)}</div>
|
|
</div>
|
|
</div>`).join("");
|
|
return `
|
|
<div class="story story-create">
|
|
<div class="sc-img">${ICONS.mountain}</div>
|
|
<div class="sc-plus">${ICONS.plus}</div>
|
|
<div class="sc-label">创建快拍</div>
|
|
</div>${items}`;
|
|
}
|
|
|
|
function render() {
|
|
document.getElementById("posts").innerHTML = FEED.map(postHtml).join("");
|
|
document.getElementById("stories").innerHTML = storiesHtml();
|
|
document.getElementById("sponsored").innerHTML = SPONSORED.map(s => `
|
|
<div class="sponsored">
|
|
<div class="sq"><div class="img-ph" style="background:linear-gradient(135deg,#c9d8ef,#e9eff8)">${ICONS.mountain}</div></div>
|
|
<div style="min-width:0">
|
|
<div class="sp-title">${esc(s.title)}</div>
|
|
<div class="sp-domain">${esc(s.domain)}</div>
|
|
</div>
|
|
</div>`).join("");
|
|
document.getElementById("contacts").innerHTML = CONTACTS.map(n => `
|
|
<div class="contact-row">
|
|
<div class="contact-av">${avatarHtml(n, "sm")}<span class="online-dot"></span></div>
|
|
${esc(n)}
|
|
</div>`).join("");
|
|
}
|
|
|
|
/* 点赞按钮可点击切换(纯前端演示) */
|
|
document.addEventListener("click", e => {
|
|
const btn = e.target.closest(".act-like");
|
|
if (!btn) return;
|
|
const count = btn.closest(".post").querySelector(".like-count");
|
|
const base = Number(count.dataset.base);
|
|
const liked = btn.classList.toggle("liked");
|
|
count.textContent = base + (liked ? 1 : 0);
|
|
});
|
|
|
|
render();
|
|
</script>
|
|
</body>
|
|
</html>
|