Files
zjpb.net/templates/detail_new.html
Jowe e71230ca96 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>
2026-01-10 18:04:42 +08:00

1368 lines
37 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends 'base_new.html' %}
{% block title %}{{ site.name }} - ZJPB AI工具导航{% endblock %}
{% block extra_head %}
<!-- SEO Meta Tags (v2.4新增) -->
<meta name="description" content="{{ site.short_desc or (site.description[:150] if site.description else '') }}">
<meta name="keywords" content="{{ site.name }},{% for tag in site.tags %}{{ tag.name }},{% endfor %}AI工具,自己品吧,ZJPB">
<link rel="canonical" href="{{ request.url }}">
<!-- Open Graph (v2.4新增) -->
<meta property="og:type" content="website">
<meta property="og:title" content="{{ site.name }}">
<meta property="og:description" content="{{ site.short_desc or (site.description[:150] if site.description else '') }}">
<meta property="og:url" content="{{ request.url }}">
{% if site.logo %}<meta property="og:image" content="{{ request.url_root.rstrip('/') }}{{ site.logo }}">{% endif %}
<!-- Schema.org 结构化数据 (v2.4新增) -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "{{ site.name }}",
"url": "{{ site.url }}",
{% if site.logo %}"image": "{{ request.url_root.rstrip('/') }}{{ site.logo }}",{% endif %}
"description": "{{ site.short_desc or site.description or site.name }}",
{% if site.features %}"featureList": {{ site.features.split('\n') | tojson }},{% endif %}
"applicationCategory": "WebApplication",
"operatingSystem": "Any",
{% if site.tags %}"keywords": "{{ site.tags | map(attribute='name') | join(', ') }}",{% endif %}
"offers": {
"@type": "Offer",
"price": "0",
"priceCurrency": "CNY"
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.5",
"ratingCount": "{{ site.view_count or 1 }}"
}
}
</script>
<!-- BreadcrumbList 结构化数据 (v2.4新增) -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "首页",
"item": "{{ request.url_root.rstrip('/') }}"
}{% if site.tags and site.tags|length > 0 %},
{
"@type": "ListItem",
"position": 2,
"name": "{{ site.tags[0].name }}",
"item": "{{ request.url_root.rstrip('/') }}/?tag={{ site.tags[0].slug }}"
}{% endif %},
{
"@type": "ListItem",
"position": {% if site.tags and site.tags|length > 0 %}3{% else %}2{% endif %},
"name": "{{ site.name }}",
"item": "{{ request.url }}"
}
]
}
</script>
{% endblock %}
{% block extra_css %}
<style>
/* v2.4新增: 面包屑导航样式 */
.breadcrumb {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 0;
font-size: 14px;
color: var(--text-secondary);
flex-wrap: nowrap;
white-space: nowrap;
overflow-x: auto;
overflow-y: hidden;
}
.breadcrumb-item {
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 {
color: var(--primary-blue);
}
.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;
}
/* 返回链接 */
.back-link {
display: inline-flex;
align-items: center;
gap: 6px;
color: var(--text-secondary);
text-decoration: none;
font-size: 24px;
font-weight: 500;
transition: color 0.2s;
}
.back-link:hover {
color: var(--text-primary);
}
/* 产品头部区域 */
.product-header-wrapper {
display: flex;
gap: 32px;
margin-bottom: 32px;
align-items: flex-start;
position: relative;
}
.product-main-section {
flex: 1;
background: var(--bg-white);
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
padding: 32px;
}
.product-header {
display: flex;
gap: 20px;
margin-bottom: 20px;
}
.product-logo-large {
width: 88px;
height: 88px;
border-radius: var(--radius-lg);
object-fit: cover;
flex-shrink: 0;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.product-main-info {
flex: 1;
}
.product-main-info h1 {
font-size: 32px;
font-weight: 700;
margin-bottom: 8px;
line-height: 1.2;
}
.product-link {
display: inline-flex;
align-items: center;
gap: 4px;
color: var(--primary-blue);
text-decoration: none;
font-size: 14px;
margin-bottom: 16px;
}
.product-link:hover {
text-decoration: underline;
}
.product-meta {
display: flex;
gap: 20px;
margin-bottom: 16px;
flex-wrap: wrap;
}
.meta-item {
display: flex;
align-items: center;
gap: 6px;
color: var(--text-secondary);
font-size: 14px;
}
.product-tags-list {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.product-tag {
padding: 6px 12px;
background: #f1f5f9;
color: #64748b;
border-radius: 6px;
font-size: 13px;
font-weight: 500;
text-decoration: none;
transition: all 0.2s;
}
.product-tag:hover {
background: rgba(14, 165, 233, 0.1);
color: var(--primary-blue);
}
.product-tag.free {
background: rgba(139, 92, 246, 0.1);
color: #8b5cf6;
}
/* Try It Now卡片 - 独立定位 */
.try-now-card {
position: sticky;
top: 88px;
width: 300px;
background: var(--bg-white);
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
padding: 24px;
flex-shrink: 0;
}
.try-now-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.try-now-header h3 {
font-size: 16px;
font-weight: 600;
margin: 0;
}
.status-badge {
padding: 4px 10px;
background: rgba(34, 197, 94, 0.1);
color: #059669;
border-radius: 4px;
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.visit-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
width: 100%;
padding: 12px;
background: var(--primary-blue);
color: white;
border: none;
border-radius: var(--radius-md);
text-decoration: none;
font-weight: 600;
font-size: 14px;
transition: all 0.2s;
text-transform: uppercase;
letter-spacing: 0.3px;
}
.visit-btn:hover {
background: var(--primary-dark);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(14, 165, 233, 0.3);
}
.visit-hint {
text-align: center;
margin-top: 12px;
font-size: 12px;
color: var(--text-muted);
}
/* 主内容布局 */
.content-layout {
display: grid;
grid-template-columns: 1fr 300px;
gap: 32px;
margin-bottom: 48px;
}
.main-column {
min-width: 0;
}
.sidebar-column {
position: sticky;
top: 88px;
align-self: flex-start;
}
/* 内容块 */
.content-block {
background: var(--bg-white);
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
padding: 32px;
margin-bottom: 24px;
}
.content-block h2 {
display: flex;
align-items: center;
gap: 10px;
font-size: 20px;
font-weight: 700;
margin-bottom: 20px;
}
.content-block p {
color: var(--text-secondary);
line-height: 1.7;
margin-bottom: 16px;
}
.content-block ul,
.content-block ol {
margin-left: 20px;
margin-bottom: 16px;
}
.content-block li {
color: var(--text-secondary);
line-height: 1.7;
margin-bottom: 8px;
}
/* 新闻卡片 */
.news-item {
padding: 20px;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
margin-bottom: 16px;
transition: all 0.2s;
}
.news-item:hover {
border-color: var(--primary-blue);
box-shadow: var(--shadow-md);
}
.news-item:last-child {
margin-bottom: 0;
}
.news-badge {
display: inline-block;
padding: 4px 10px;
background: rgba(139, 92, 246, 0.1);
color: #8b5cf6;
border-radius: 4px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 12px;
}
.news-item h4 {
font-size: 16px;
font-weight: 600;
margin-bottom: 8px;
color: var(--text-primary);
}
.news-item p {
font-size: 14px;
color: var(--text-secondary);
margin-bottom: 8px;
line-height: 1.6;
}
.news-date {
font-size: 12px;
color: var(--text-muted);
}
/* 推荐卡片 */
.recommendations-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
}
.recommendation-card {
padding: 20px;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
text-decoration: none;
color: var(--text-primary);
transition: all 0.2s;
display: flex;
gap: 12px;
position: relative;
}
.recommendation-card:hover {
border-color: var(--primary-blue);
box-shadow: var(--shadow-md);
transform: translateY(-2px);
}
.recommendation-card .arrow-icon {
position: absolute;
top: 20px;
right: 20px;
color: #cbd5e1;
font-size: 20px;
transition: all 0.2s;
}
.recommendation-card:hover .arrow-icon {
color: var(--primary-blue);
transform: translate(2px, -2px);
}
.rec-logo {
width: 48px;
height: 48px;
border-radius: var(--radius-md);
object-fit: cover;
flex-shrink: 0;
}
.rec-info {
flex: 1;
padding-right: 24px;
}
.rec-info h4 {
font-size: 14px;
font-weight: 600;
margin-bottom: 4px;
}
.rec-info p {
font-size: 12px;
color: var(--text-secondary);
margin-bottom: 8px;
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.rec-tags {
display: flex;
gap: 4px;
flex-wrap: wrap;
}
.rec-tag {
padding: 2px 8px;
background: #f1f5f9;
color: #64748b;
border-radius: 4px;
font-size: 11px;
font-weight: 500;
}
/* Markdown内容样式 */
.markdown-content {
color: var(--text-secondary);
line-height: 1.8;
}
.markdown-content h1,
.markdown-content h2,
.markdown-content h3 {
color: var(--text-primary);
font-weight: 600;
margin-top: 24px;
margin-bottom: 16px;
line-height: 1.4;
}
.markdown-content h1 {
font-size: 24px;
}
.markdown-content h2 {
font-size: 20px;
}
.markdown-content h3 {
font-size: 18px;
}
.markdown-content p {
margin-bottom: 16px;
line-height: 1.8;
}
.markdown-content ul,
.markdown-content ol {
margin: 16px 0;
padding-left: 24px;
}
.markdown-content ul li {
list-style: none;
position: relative;
padding-left: 20px;
margin-bottom: 12px;
line-height: 1.8;
}
.markdown-content ul li:before {
content: "▸";
position: absolute;
left: 0;
color: var(--primary-blue);
font-weight: bold;
}
.markdown-content ol li {
margin-bottom: 12px;
line-height: 1.8;
padding-left: 8px;
}
.markdown-content code {
background: #f1f5f9;
color: #e11d48;
padding: 2px 6px;
border-radius: 4px;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 0.9em;
}
.markdown-content pre {
background: #1e293b;
color: #e2e8f0;
padding: 16px;
border-radius: 8px;
overflow-x: auto;
margin: 16px 0;
}
.markdown-content pre code {
background: transparent;
color: inherit;
padding: 0;
border-radius: 0;
}
.markdown-content strong {
font-weight: 600;
color: var(--text-primary);
}
.markdown-content em {
font-style: italic;
}
.markdown-content a {
color: var(--primary-blue);
text-decoration: none;
border-bottom: 1px solid transparent;
transition: border-color 0.2s;
}
.markdown-content a:hover {
border-bottom-color: var(--primary-blue);
}
.markdown-content blockquote {
border-left: 4px solid var(--primary-blue);
padding-left: 16px;
margin: 16px 0;
color: var(--text-secondary);
font-style: italic;
}
.markdown-content hr {
border: none;
border-top: 1px solid var(--border-color);
margin: 24px 0;
}
/* 响应式 */
@media (max-width: 968px) {
.product-header-wrapper {
flex-direction: column;
}
.try-now-card {
width: 100%;
position: static;
}
.content-layout {
grid-template-columns: 1fr;
}
.sidebar-column {
position: static;
}
.recommendations-grid {
grid-template-columns: 1fr;
}
}
</style>
{% endblock %}
{% block content %}
<div class="main-content">
<!-- 顶部空白 -->
<div style="height: 20px;"></div>
<!-- v2.4新增: 面包屑导航 -->
<nav class="breadcrumb" aria-label="breadcrumb">
<div class="breadcrumb-item">
<a href="/">首页</a>
</div>
<span class="breadcrumb-separator"></span>
{% if site.tags and site.tags|length > 0 %}
<div class="breadcrumb-item">
<a href="/?tag={{ site.tags[0].slug }}">{{ site.tags[0].name }}</a>
</div>
<span class="breadcrumb-separator"></span>
{% endif %}
<div class="breadcrumb-item active">
{{ site.name }}
</div>
</nav>
<div style="height: 10px;"></div>
<!-- 返回链接 -->
<a href="/" class="back-link">
<span></span>
返回首页
</a>
<!-- 底部空白 -->
<div style="height: 20px;"></div>
<!-- 产品头部区域 -->
<div class="product-header-wrapper">
<!-- 左侧主内容 -->
<div class="product-main-section">
<div class="product-header">
<!-- Logo -->
{% if site.logo %}
<img src="{{ site.logo }}" alt="{{ site.name }}" class="product-logo-large">
{% else %}
<div class="product-logo-large" style="background: linear-gradient(135deg, #0ea5e9 0%, #8b5cf6 100%);"></div>
{% endif %}
<!-- 产品信息 -->
<div class="product-main-info">
<h1>{{ site.name }}</h1>
<a href="{{ site.url }}" target="_blank" class="product-link">
{{ site.url }}
<span></span>
</a>
<div class="product-meta">
<div class="meta-item">
<span>👁</span>
<span>{{ site.view_count | default(0) }} 次浏览</span>
</div>
<div class="meta-item">
<span>📅</span>
<span>添加于 {{ site.created_at.strftime('%Y年%m月%d日') }}</span>
</div>
</div>
<div class="product-tags-list">
{% for tag in site.tags %}
<a href="/?tag={{ tag.slug }}" class="product-tag">{{ tag.name }}</a>
{% endfor %}
<span class="product-tag free">免费试用</span>
</div>
</div>
</div>
</div>
<!-- Try It Now卡片 -->
<div class="try-now-card">
<div class="try-now-header">
<h3>立即访问</h3>
<span class="status-badge">在线</span>
</div>
<a href="{{ site.url }}" target="_blank" class="visit-btn">
访问网站
<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>
<!-- 内容布局 -->
<div class="content-layout">
<!-- 主列 -->
<div class="main-column">
<!-- Product Overview -->
<div class="content-block">
<h2>
<span></span>
产品概述
</h2>
<div class="markdown-content">{{ site.description | auto_link(site.id) | markdown | safe }}</div>
</div>
<!-- Detailed Description -->
{% if site.features %}
<div class="content-block">
<h2>
<span>📋</span>
主要功能
</h2>
<div class="markdown-content">{{ site.features | auto_link(site.id) | markdown | safe }}</div>
</div>
{% endif %}
<!-- Related News -->
{% if news_list %}
<div class="content-block">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h2 style="margin: 0;">
<span>📰</span>
相关新闻
</h2>
<button id="refreshNewsBtn" class="refresh-news-btn" onclick="refreshNews('{{ site.code }}')">
<span class="refresh-icon"></span> 获取最新资讯
</button>
</div>
<div id="newsContainer">
{% for news in news_list %}
<div class="news-item">
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 12px;">
<span class="news-badge">{{ news.news_type }}</span>
{% if news.source_name %}
<div style="display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-muted);">
{% if news.source_icon %}
<img src="{{ news.source_icon }}" alt="{{ news.source_name }}" style="width: 16px; height: 16px; border-radius: 2px;">
{% endif %}
<span>{{ news.source_name }}</span>
</div>
{% endif %}
</div>
<h4>
{% if news.url %}
<a href="{{ news.url }}" target="_blank" rel="noopener noreferrer" style="color: var(--text-primary); text-decoration: none;">
{{ news.title }}
</a>
{% else %}
{{ news.title }}
{% endif %}
</h4>
{% if news.content %}
<p>{{ news.content[:200] }}{% if news.content|length > 200 %}...{% endif %}</p>
{% endif %}
<div style="display: flex; justify-content: space-between; align-items: center;">
<div class="news-date">
{% if news.published_at %}
{{ news.published_at.strftime('%Y年%m月%d日') }}
{% else %}
未知日期
{% endif %}
</div>
{% if news.url %}
<a href="{{ news.url }}" target="_blank" rel="noopener noreferrer" style="font-size: 12px; color: var(--primary-blue); text-decoration: none;">
阅读全文 ↗
</a>
{% endif %}
</div>
</div>
{% endfor %}
</div><!-- End newsContainer -->
</div>
{% endif %}
</div>
<!-- 侧边栏 -->
<div class="sidebar-column">
<!-- Similar Recommendations -->
{% if recommended_sites %}
<div class="content-block">
<h2>
<span></span>
相似推荐
</h2>
{% for rec_site in recommended_sites %}
<a href="/site/{{ rec_site.code }}" class="recommendation-card" style="display: flex; gap: 12px; padding: 16px; border: 1px solid var(--border-color); border-radius: 12px; margin-bottom: 12px; text-decoration: none; transition: all 0.2s;">
{% if rec_site.logo %}
<img src="{{ rec_site.logo }}" alt="{{ rec_site.name }}" style="width: 48px; height: 48px; border-radius: 8px; flex-shrink: 0;">
{% else %}
<div style="width: 48px; height: 48px; border-radius: 8px; background: linear-gradient(135deg, #0ea5e9 0%, #8b5cf6 100%); flex-shrink: 0;"></div>
{% endif %}
<div style="flex: 1; min-width: 0;">
<h4 style="font-size: 14px; font-weight: 600; margin: 0 0 4px 0; color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">{{ rec_site.name }}</h4>
<p style="font-size: 12px; color: var(--text-secondary); margin: 0; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;">{{ rec_site.short_desc or rec_site.description }}</p>
</div>
</a>
{% endfor %}
</div>
{% endif %}
</div>
</div>
</div>
<style>
.refresh-news-btn {
padding: 8px 16px;
background: linear-gradient(135deg, var(--primary-blue) 0%, var(--primary-dark) 100%);
color: white;
border: none;
border-radius: 8px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s;
display: flex;
align-items: center;
gap: 6px;
}
.refresh-news-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(14, 165, 233, 0.3);
}
.refresh-news-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.refresh-news-btn.loading .refresh-icon {
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.news-status {
padding: 12px;
margin: 16px 0;
border-radius: 8px;
font-size: 14px;
display: none;
}
.news-status.success {
background: rgba(34, 197, 94, 0.1);
border: 1px solid rgba(34, 197, 94, 0.3);
color: #22c55e;
}
.news-status.error {
background: rgba(239, 68, 68, 0.1);
border: 1px solid rgba(239, 68, 68, 0.3);
color: #ef4444;
}
</style>
<script>
function refreshNews(siteCode) {
const btn = document.getElementById('refreshNewsBtn');
const newsContainer = document.getElementById('newsContainer');
if (!btn || !newsContainer) return;
// 禁用按钮,显示加载状态
btn.disabled = true;
btn.classList.add('loading');
btn.innerHTML = '<span class="refresh-icon">↻</span> 正在获取...';
// 显示加载提示
const loadingMsg = document.createElement('div');
loadingMsg.className = 'news-status';
loadingMsg.style.display = 'block';
loadingMsg.textContent = '正在获取最新资讯,请稍候...';
newsContainer.insertAdjacentElement('beforebegin', loadingMsg);
// 调用刷新API
fetch(`/api/refresh-site-news/${siteCode}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
// 刷新成功,重新加载页面
loadingMsg.className = 'news-status success';
loadingMsg.textContent = `✓ 成功获取 ${data.saved_count || 0} 条新资讯,页面即将刷新...`;
setTimeout(() => {
window.location.reload();
}, 1500);
} else {
// 刷新失败
loadingMsg.className = 'news-status error';
loadingMsg.textContent = `${data.message || '获取失败,请稍后重试'}`;
btn.disabled = false;
btn.classList.remove('loading');
btn.innerHTML = '<span class="refresh-icon">↻</span> 获取最新资讯';
// 3秒后隐藏错误消息
setTimeout(() => {
loadingMsg.style.display = 'none';
}, 3000);
}
})
.catch(error => {
console.error('Error:', error);
loadingMsg.className = 'news-status error';
loadingMsg.textContent = '✗ 网络请求失败,请检查网络连接';
btn.disabled = false;
btn.classList.remove('loading');
btn.innerHTML = '<span class="refresh-icon">↻</span> 获取最新资讯';
// 3秒后隐藏错误消息
setTimeout(() => {
loadingMsg.style.display = 'none';
}, 3000);
});
}
</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 %}