feat: v2.5 社媒分享功能 + 面包屑优化

新增功能:
- 社媒分享:一键生成多平台分享文案
  - 支持8个平台:通用、小红书、抖音、B站、微信公众号、朋友圈、X (Twitter)、LinkedIn
  - 智能生成适配各平台特点的文案(字数限制、风格、内容丰富度)
  - 集成DeepSeek AI自动生成 + 规则模板回退
  - "分享"按钮支持直接跳转到平台发布后台
  - X (Twitter)支持预填充文案
  - 自动复制文案到剪贴板

优化改进:
- 面包屑导航:修复折行问题,确保始终单行显示
  - 添加 white-space: nowrap 和 flex-shrink: 0
  - 长标题自动省略显示
- 社媒分享按钮文案:从"一键生成社媒分享"改为"分享"
- 更新Twitter为X,使用x.com域名

技术实现:
- 前端:detail_new.html 新增分享弹窗和交互逻辑
- 后端:app.py 新增 /api/generate-social-share 接口
- 支持按平台定制文案内容和格式

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Jowe
2026-01-10 18:04:42 +08:00
parent 8011e5bd4a
commit e71230ca96
2 changed files with 612 additions and 2 deletions

View File

@@ -80,19 +80,25 @@
padding: 12px 0;
font-size: 14px;
color: var(--text-secondary);
flex-wrap: wrap;
flex-wrap: nowrap;
white-space: nowrap;
overflow-x: auto;
overflow-y: hidden;
}
.breadcrumb-item {
display: flex;
display: inline-flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
white-space: nowrap;
}
.breadcrumb-item a {
color: var(--text-secondary);
text-decoration: none;
transition: color 0.2s;
white-space: nowrap;
}
.breadcrumb-item a:hover {
@@ -102,11 +108,16 @@
.breadcrumb-item.active {
color: var(--text-primary);
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 300px;
}
.breadcrumb-separator {
color: var(--text-muted);
user-select: none;
flex-shrink: 0;
}
/* 返回链接 */
@@ -715,8 +726,41 @@
访问网站
<span></span>
</a>
<!-- v2.5新增:社媒分享入口 -->
<button type="button" class="share-btn" onclick="openShareModal()">
分享
<span></span>
</button>
<p class="visit-hint">在新标签页打开 • {{ site.url.split('/')[2] if site.url else '' }}</p>
</div>
<!-- v2.5新增:社媒分享弹窗 -->
<div id="shareModal" class="share-modal" style="display:none;">
<div class="share-modal-backdrop" onclick="closeShareModal()"></div>
<div class="share-modal-card" role="dialog" aria-modal="true" aria-label="社媒分享文案">
<div class="share-modal-header">
<div>
<div class="share-modal-title">社媒分享文案</div>
<div class="share-modal-subtitle">{{ site.name }} · 一键生成/一键复制</div>
</div>
<button type="button" class="share-modal-close" onclick="closeShareModal()">×</button>
</div>
<div class="share-platform-tabs" id="shareTabs"></div>
<div class="share-content">
<textarea id="shareText" class="share-textarea" rows="10" readonly placeholder="点击生成后显示内容..."></textarea>
<div class="share-actions">
<button type="button" id="shareGenerateBtn" class="share-action-primary" onclick="generateShare()">生成文案</button>
<button type="button" class="share-action-share" onclick="shareToplatform()">分享</button>
<button type="button" class="share-action-secondary" onclick="copyShare()">复制</button>
</div>
<div id="shareStatus" class="share-status" style="display:none;"></div>
</div>
</div>
</div>
</div>
<!-- 内容布局 -->
@@ -952,4 +996,372 @@ function refreshNews(siteCode) {
}
</script>
<style>
.share-btn {
margin-top: 12px;
width: 100%;
padding: 12px;
border-radius: var(--radius-md);
border: 1px solid rgba(14, 165, 233, 0.35);
background: rgba(14, 165, 233, 0.08);
color: var(--primary-dark);
font-weight: 700;
font-size: 14px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
transition: all 0.2s;
}
.share-btn:hover {
background: rgba(14, 165, 233, 0.12);
transform: translateY(-1px);
}
.share-modal {
position: fixed;
inset: 0;
z-index: 2000;
}
.share-modal-backdrop {
position: absolute;
inset: 0;
background: rgba(15, 23, 42, 0.55);
}
.share-modal-card {
position: relative;
width: min(720px, calc(100vw - 32px));
margin: 72px auto 0;
background: var(--bg-white);
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
overflow: hidden;
}
.share-modal-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
padding: 18px 20px;
border-bottom: 1px solid var(--border-color);
background: linear-gradient(135deg, rgba(14, 165, 233, 0.08) 0%, rgba(139, 92, 246, 0.08) 100%);
}
.share-modal-title {
font-size: 16px;
font-weight: 800;
color: var(--text-primary);
}
.share-modal-subtitle {
font-size: 12px;
color: var(--text-secondary);
margin-top: 2px;
}
.share-modal-close {
width: 32px;
height: 32px;
border-radius: 8px;
border: 1px solid var(--border-color);
background: var(--bg-white);
cursor: pointer;
font-size: 18px;
line-height: 1;
color: var(--text-secondary);
}
.share-platform-tabs {
display: flex;
gap: 8px;
padding: 14px 20px 0;
flex-wrap: wrap;
}
.share-tab {
padding: 8px 12px;
border-radius: 999px;
border: 1px solid var(--border-color);
background: #f8fafc;
color: var(--text-secondary);
font-size: 13px;
cursor: pointer;
transition: all 0.15s;
}
.share-tab.active {
background: rgba(14, 165, 233, 0.12);
border-color: rgba(14, 165, 233, 0.35);
color: var(--primary-dark);
font-weight: 700;
}
.share-content {
padding: 14px 20px 20px;
}
.share-textarea {
width: 100%;
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 12px;
font-size: 14px;
line-height: 1.6;
resize: vertical;
min-height: 180px;
background: var(--bg-page);
}
.share-actions {
display: flex;
gap: 10px;
justify-content: flex-end;
margin-top: 12px;
}
.share-action-primary,
.share-action-share,
.share-action-secondary {
padding: 10px 14px;
border-radius: 10px;
border: 1px solid var(--border-color);
cursor: pointer;
font-weight: 700;
transition: all 0.2s;
}
.share-action-primary {
background: var(--primary-blue);
border-color: rgba(14, 165, 233, 0.6);
color: white;
}
.share-action-primary:disabled {
opacity: 0.65;
cursor: not-allowed;
}
.share-action-share {
background: linear-gradient(135deg, #8b5cf6 0%, #ec4899 100%);
border-color: rgba(139, 92, 246, 0.6);
color: white;
}
.share-action-share:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
}
.share-action-secondary {
background: var(--bg-white);
color: var(--text-primary);
}
.share-status {
margin-top: 10px;
font-size: 13px;
color: var(--text-secondary);
}
.share-status.success { color: #16a34a; }
.share-status.error { color: #dc2626; }
@media (max-width: 520px) {
.share-modal-card { margin-top: 56px; }
.share-actions { flex-direction: column; }
.share-action-primary,
.share-action-share,
.share-action-secondary { width: 100%; }
}
</style>
<script>
const SHARE_PLATFORMS = [
{ key: 'universal', label: '通用', publishUrl: null },
{ key: 'xiaohongshu', label: '小红书', publishUrl: 'https://creator.xiaohongshu.com/publish/publish' },
{ key: 'douyin', label: '抖音', publishUrl: 'https://creator.douyin.com/creator-micro/content/publish' },
{ key: 'bilibili', label: 'B站', publishUrl: 'https://member.bilibili.com/platform/upload/text/edit' },
{ key: 'wechat', label: '公众号', publishUrl: 'https://mp.weixin.qq.com' },
{ key: 'moments', label: '朋友圈', publishUrl: null },
{ key: 'x', label: 'X (Twitter)', publishUrl: 'https://x.com/intent/tweet' },
{ key: 'linkedin', label: 'LinkedIn', publishUrl: 'https://www.linkedin.com/feed/' },
];
let shareActivePlatform = 'universal';
let shareContentCache = {};
function openShareModal() {
const modal = document.getElementById('shareModal');
if (!modal) return;
modal.style.display = 'block';
renderShareTabs();
setSharePlatform(shareActivePlatform);
document.addEventListener('keydown', onShareEsc);
}
function closeShareModal() {
const modal = document.getElementById('shareModal');
if (!modal) return;
modal.style.display = 'none';
document.removeEventListener('keydown', onShareEsc);
}
function onShareEsc(e) {
if (e.key === 'Escape') closeShareModal();
}
function renderShareTabs() {
const tabs = document.getElementById('shareTabs');
if (!tabs) return;
tabs.innerHTML = '';
SHARE_PLATFORMS.forEach(p => {
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'share-tab' + (p.key === shareActivePlatform ? ' active' : '');
btn.textContent = p.label;
btn.onclick = () => setSharePlatform(p.key);
tabs.appendChild(btn);
});
}
function setSharePlatform(platformKey) {
shareActivePlatform = platformKey;
renderShareTabs();
const textarea = document.getElementById('shareText');
if (!textarea) return;
textarea.value = shareContentCache[platformKey] || '';
const status = document.getElementById('shareStatus');
if (status) status.style.display = 'none';
}
function setShareStatus(type, message) {
const el = document.getElementById('shareStatus');
if (!el) return;
el.className = 'share-status' + (type ? ' ' + type : '');
el.textContent = message;
el.style.display = 'block';
}
function generateShare() {
const btn = document.getElementById('shareGenerateBtn');
if (btn) {
btn.disabled = true;
btn.textContent = '生成中...';
}
setShareStatus('', '正在生成文案,请稍候...');
const payload = {
site_code: '{{ site.code }}',
platforms: SHARE_PLATFORMS.map(p => p.key)
};
fetch('/api/generate-social-share', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
})
.then(r => r.json())
.then(data => {
if (!data || !data.success) {
throw new Error((data && data.message) ? data.message : '生成失败');
}
shareContentCache = data.content || {};
setSharePlatform(shareActivePlatform);
setShareStatus('success', '已生成,可直接复制使用。');
})
.catch(err => {
setShareStatus('error', err.message || '网络请求失败');
})
.finally(() => {
if (btn) {
btn.disabled = false;
btn.textContent = '生成文案';
}
});
}
function copyShare() {
const textarea = document.getElementById('shareText');
if (!textarea) return;
const text = textarea.value || '';
if (!text.trim()) {
setShareStatus('error', '没有可复制的内容,请先生成。');
return;
}
const doFallback = () => {
textarea.focus();
textarea.select();
try {
document.execCommand('copy');
setShareStatus('success', '已复制到剪贴板');
} catch (e) {
setShareStatus('error', '复制失败,请手动选择复制');
}
};
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text)
.then(() => setShareStatus('success', '已复制到剪贴板'))
.catch(() => doFallback());
} else {
doFallback();
}
}
function shareToplatform() {
const textarea = document.getElementById('shareText');
if (!textarea) return;
const text = textarea.value || '';
if (!text.trim()) {
setShareStatus('error', '请先生成文案后再分享');
return;
}
// 查找当前平台配置
const platform = SHARE_PLATFORMS.find(p => p.key === shareActivePlatform);
if (!platform) {
setShareStatus('error', '未知平台');
return;
}
// 如果平台没有发布URL提示用户
if (!platform.publishUrl) {
setShareStatus('error', `${platform.label}暂不支持直接跳转,请手动复制后前往平台发布`);
return;
}
// 根据平台处理跳转
let shareUrl = platform.publishUrl;
// X (Twitter) 支持预填充文本
if (platform.key === 'x') {
const encodedText = encodeURIComponent(text);
shareUrl = `${platform.publishUrl}?text=${encodedText}`;
}
// 打开发布页面
window.open(shareUrl, '_blank');
setShareStatus('success', `正在跳转到${platform.label}发布页面...`);
// 自动复制文案到剪贴板
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).catch(() => {});
}
}
</script>
{% endblock %}