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:
198
app.py
198
app.py
@@ -239,6 +239,204 @@ def create_app(config_name='default'):
|
||||
|
||||
return render_template('detail_new.html', site=site, news_list=news_list, recommended_sites=recommended_sites)
|
||||
|
||||
# ========== 社媒营销路由 (v2.5新增) ==========
|
||||
@app.route('/api/generate-social-share', methods=['POST'])
|
||||
def generate_social_share():
|
||||
"""一键生成社媒分享文案(前台可访问)"""
|
||||
try:
|
||||
data = request.get_json() or {}
|
||||
site_code = (data.get('site_code') or '').strip()
|
||||
platforms = data.get('platforms') or []
|
||||
|
||||
if not site_code:
|
||||
return jsonify({'success': False, 'message': '请提供网站编码'}), 400
|
||||
|
||||
site = Site.query.filter_by(code=site_code, is_active=True).first()
|
||||
if not site:
|
||||
return jsonify({'success': False, 'message': '网站不存在或未启用'}), 404
|
||||
|
||||
# 允许的平台列表(前端也会限制,这里再做一次兜底)
|
||||
allowed_platforms = {
|
||||
'universal',
|
||||
'xiaohongshu',
|
||||
'douyin',
|
||||
'bilibili',
|
||||
'wechat',
|
||||
'moments',
|
||||
'x',
|
||||
'linkedin'
|
||||
}
|
||||
|
||||
if not isinstance(platforms, list):
|
||||
platforms = []
|
||||
platforms = [p for p in platforms if isinstance(p, str)]
|
||||
platforms = [p.strip().lower() for p in platforms if p.strip()]
|
||||
platforms = [p for p in platforms if p in allowed_platforms]
|
||||
|
||||
# 默认生成通用模板
|
||||
if not platforms:
|
||||
platforms = ['universal']
|
||||
|
||||
# 组织站点信息
|
||||
site_info = {
|
||||
'name': site.name,
|
||||
'url': site.url,
|
||||
'short_desc': site.short_desc or '',
|
||||
'description': (site.description or '')[:1200],
|
||||
'features': (site.features or '')[:1200],
|
||||
'tags': [t.name for t in (site.tags or [])]
|
||||
}
|
||||
|
||||
# 尝试使用PromptTemplate + DeepSeek生成
|
||||
from utils.tag_generator import TagGenerator
|
||||
generator = TagGenerator()
|
||||
prompt = PromptTemplate.query.filter_by(key='social_share', is_active=True).first()
|
||||
|
||||
generated = {}
|
||||
|
||||
if prompt and generator.client:
|
||||
# 构造提示词变量
|
||||
tags_text = ','.join(site_info['tags'])
|
||||
user_prompt = prompt.user_prompt_template.format(
|
||||
name=site_info['name'],
|
||||
url=site_info['url'],
|
||||
short_desc=site_info['short_desc'],
|
||||
description=site_info['description'],
|
||||
features=site_info['features'],
|
||||
tags=tags_text,
|
||||
platforms=','.join(platforms)
|
||||
)
|
||||
|
||||
try:
|
||||
resp = generator.client.chat.completions.create(
|
||||
model='deepseek-chat',
|
||||
messages=[
|
||||
{'role': 'system', 'content': prompt.system_prompt},
|
||||
{'role': 'user', 'content': user_prompt}
|
||||
],
|
||||
temperature=0.7,
|
||||
max_tokens=1200
|
||||
)
|
||||
text = (resp.choices[0].message.content or '').strip()
|
||||
# 约定:模型返回JSON字符串;若不是JSON则回退到纯文本放到universal
|
||||
import json as _json
|
||||
try:
|
||||
parsed = _json.loads(text)
|
||||
if isinstance(parsed, dict):
|
||||
for k, v in parsed.items():
|
||||
if isinstance(k, str) and isinstance(v, str) and k in allowed_platforms:
|
||||
generated[k] = v.strip()
|
||||
except Exception:
|
||||
generated['universal'] = text
|
||||
except Exception as e:
|
||||
# AI失败不影响整体,回退模板
|
||||
print(f"社媒文案AI生成失败:{str(e)}")
|
||||
|
||||
# 回退:规则模板(根据各平台字数限制优化)
|
||||
def build_fallback(p):
|
||||
name = site_info['name']
|
||||
url = site_info['url']
|
||||
short_desc = site_info['short_desc'] or ''
|
||||
full_desc = site_info['description'] or ''
|
||||
features = site_info['features'] or ''
|
||||
tags = site_info['tags'][:6]
|
||||
tags_line = ' ' + ' '.join([f"#{t}" for t in tags]) if tags else ''
|
||||
|
||||
# X (Twitter) - 280字符限制,简洁为主
|
||||
if p == 'x':
|
||||
desc = (short_desc or full_desc)[:100]
|
||||
base = f"🔥 {name}\n\n{desc}\n\n🔗 {url}{tags_line}"
|
||||
return base[:280]
|
||||
|
||||
# LinkedIn - 3000字限制,专业风格
|
||||
if p == 'linkedin':
|
||||
desc = (full_desc or short_desc)[:500]
|
||||
content = f"💡 推荐一个实用工具:{name}\n\n📝 简介:\n{desc}\n\n"
|
||||
if features:
|
||||
features_list = features.split('\n')[:3]
|
||||
content += f"✨ 主要功能:\n" + '\n'.join([f"• {f.strip()}" for f in features_list if f.strip()][:3]) + "\n\n"
|
||||
content += f"🔗 官网:{url}\n\n"
|
||||
if tags:
|
||||
content += f"📌 适用场景:{', '.join(tags)}"
|
||||
return content[:3000]
|
||||
|
||||
# 小红书 - 2000字限制,图文风格
|
||||
if p == 'xiaohongshu':
|
||||
desc = (short_desc or full_desc)[:300]
|
||||
content = f"✨ {name} | 我最近在用的宝藏工具\n\n"
|
||||
content += f"📌 一句话介绍:\n{desc}\n\n"
|
||||
if features:
|
||||
features_list = features.split('\n')[:5]
|
||||
content += f"🎯 核心功能:\n" + '\n'.join([f"✓ {f.strip()}" for f in features_list if f.strip()][:5]) + "\n\n"
|
||||
content += f"🔗 体验入口:{url}\n\n{tags_line}"
|
||||
return content[:2000]
|
||||
|
||||
# 抖音 - 2000字限制,短视频文案风格
|
||||
if p == 'douyin':
|
||||
desc = (short_desc or full_desc)[:200]
|
||||
content = f"🔥 发现一个超好用的工具:{name}\n\n"
|
||||
content += f"💡 {desc}\n\n"
|
||||
if features:
|
||||
features_list = features.split('\n')[:3]
|
||||
content += "✨ 亮点功能:\n" + '\n'.join([f"👉 {f.strip()}" for f in features_list if f.strip()][:3]) + "\n\n"
|
||||
content += f"🔗 想试试戳:{url}\n\n{tags_line}"
|
||||
return content[:2000]
|
||||
|
||||
# B站 - 20000字限制,专栏风格
|
||||
if p == 'bilibili':
|
||||
desc = (full_desc or short_desc)[:800]
|
||||
content = f"【分享】{name} - 值得一试的优质工具\n\n"
|
||||
content += f"## 📖 工具简介\n{desc}\n\n"
|
||||
if features:
|
||||
content += f"## ✨ 主要功能\n{features}\n\n"
|
||||
content += f"## 🔗 体验地址\n{url}\n\n"
|
||||
if tags:
|
||||
content += f"## 🏷️ 相关标签\n{' / '.join(tags)}"
|
||||
return content[:20000]
|
||||
|
||||
# 微信公众号 - 基本无限制,正式风格
|
||||
if p == 'wechat':
|
||||
desc = (full_desc or short_desc)[:600]
|
||||
content = f"今天给大家分享一个实用工具:{name}\n\n"
|
||||
content += f"📝 工具介绍\n{desc}\n\n"
|
||||
if features:
|
||||
content += f"✨ 核心功能\n{features}\n\n"
|
||||
content += f"🔗 官方网站\n{url}\n\n"
|
||||
if tags:
|
||||
content += f"如果你也在关注「{' · '.join(tags)}」相关的工具,不妨收藏一下这个实用的选择。"
|
||||
return content
|
||||
|
||||
# 朋友圈 - 简短为主
|
||||
if p == 'moments':
|
||||
desc = (short_desc or full_desc)[:80]
|
||||
return f"💡 {name}\n{desc}\n🔗 {url}{tags_line}".strip()
|
||||
|
||||
# 通用模板
|
||||
desc = (full_desc or short_desc)[:300]
|
||||
return f"{name}\n\n{desc}\n\n{url}{tags_line}".strip()
|
||||
|
||||
results = {}
|
||||
for p in platforms:
|
||||
results[p] = generated.get(p) or build_fallback(p)
|
||||
|
||||
# 统一补充一个通用版本
|
||||
if 'universal' not in results:
|
||||
results['universal'] = generated.get('universal') or build_fallback('universal')
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'site': {
|
||||
'code': site.code,
|
||||
'name': site.name,
|
||||
'url': site.url
|
||||
},
|
||||
'platforms': platforms,
|
||||
'content': results
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'message': f'生成失败: {str(e)}'}), 500
|
||||
|
||||
# ========== 后台登录路由 ==========
|
||||
@app.route('/admin/login', methods=['GET', 'POST'])
|
||||
def admin_login():
|
||||
|
||||
Reference in New Issue
Block a user