1.4.8-增加标签预览

This commit is contained in:
RachelOS
2025-12-23 10:04:32 +08:00
parent f51956734f
commit 729af552e0
12 changed files with 212 additions and 51 deletions
+36
View File
@@ -437,6 +437,42 @@ class WXArticleFetcher:
except Exception as e:
print_error(f"修复图片失败: {str(e)}")
return content
def get_image_url(self,url:str)->str:
base_url=cfg.get("server.base_url","")
return f"{base_url}/static/res/logo/{url}"
def get_description(self,content:str,length:int=200)->str:
soup = BeautifulSoup(content, 'html.parser')
# 找到内容
js_content_div = soup
if js_content_div is None:
return ""
content = js_content_div.get_text().strip().strip("\n").replace("\n"," ").replace("\r"," ")
return content[:length]+"..." if len(content)>length else content
def proxy_images(self,content:str)->str:
try:
soup = BeautifulSoup(content, 'html.parser')
# 找到内容
js_content_div = soup
# 移除style属性中的visibility: hidden;
if js_content_div is None:
return ""
js_content_div.attrs.pop('style', None)
# 找到所有的img标签
img_tags = js_content_div.find_all('img')
# 遍历每个img标签并修改属性,设置宽度为1080p
for img_tag in img_tags:
if 'src' in img_tag.attrs:
img_tag['src'] = self.get_image_url(img_tag['src'])
if 'style' in img_tag.attrs:
style = img_tag['style']
# 使用正则表达式替换width属性
style = re.sub(r'width\s*:\s*\d+\s*px', 'width: 100%', style)
img_tag['style'] = style
return js_content_div.prettify()
except Exception as e:
print_error(f"Proxy图片失败: {str(e)}")
return content
def clean_article_content(self,html_content: str):
from tools.html import htmltools
+5 -7
View File
@@ -8,7 +8,9 @@
<!-- 公众号信息 -->
<div class="mp-info">
<div style="width: 48px; height: 48px; background: #e9ecef; border-radius: 4px; display: flex; align-items: center; justify-content: center;">
📱
{% if article.mp_cover %}
<img src="{{article.mp_cover}}" alt="{{article.title}}" width="50px"/>
{% endif %}
</div>
<div style="flex: 1;">
<div style="font-weight: 500; font-size: 1.1rem;">{{article.mp_name}}</div>
@@ -26,11 +28,7 @@
</div>
</div>
{% if article.pic_url %}
<div style="text-align: center; margin-bottom: 1rem;">
<img src="{{article.pic_url}}" alt="{{article.title}}" style="max-width: 100%; max-height: 400px; object-fit: cover; border-radius: 8px;">
</div>
{% endif %}
{% if article.description %}
<div style="background: #f8f9fa; padding: 1rem; border-radius: 4px; border-left: 4px solid #667eea; margin-bottom: 1rem;">
@@ -40,7 +38,7 @@
</div>
<!-- 文章内容 -->
<div class="article-content">
<div style="text-align: center; padding: 2rem;">
<div style="text-align: center;">
<p class="text-muted mt-2"> {{article.content}}</p>
<p style="margin-top: 1rem;">
<a href="{{article.url}}" target="_blank" class="btn" rel="noopener noreferrer">
-4
View File
@@ -3,10 +3,6 @@
{% include "includes/articles_styles.html" %}
<div class="mb-4">
<h1>📄 文章列表</h1>
<p class="text-muted mt-2">浏览微信公众号文章,支持多维度筛选和搜索</p>
</div>
<!-- 搜索表单 -->
<div class="search-form">
+3 -6
View File
@@ -1,15 +1,12 @@
{%include "includes/header.html" %}
{% block title %}WeRSS - 标签首页{% endblock %}
<div class="mb-4">
<h1>📚 标签分类</h1>
<p class="text-muted mt-2">按标签浏览微信公众号文章,发现感兴趣的内容</p>
</div>
{% if tags %}
<div class="grid grid-3">
{% for tag in tags %}
<div class="card">
<div style="text-align: center; margin-bottom: 1rem;">
<img src="{{tag.cover}}" alt="{{tag.name}}" onerror="this.src='/static/logo.svg'" style="width: 80px; height: 80px; object-fit: cover; border-radius: 8px;">
</div>
<h3>{{tag.name}}</h3>
<p class="text-muted">{{tag.intro or "暂无简介"}}</p>
<div style="margin: 1rem 0;">
@@ -3,4 +3,17 @@
.article-content { background: white; padding: 2rem; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); margin-bottom: 2rem; line-height: 1.8; }
.mp-info { display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem; padding: 1rem; background: #f8f9fa; border-radius: 4px; }
.article-meta { display: flex; gap: 2rem; align-items: center; margin-top: 1rem; padding-top: 1rem; border-top: 1px solid #e9ecef; font-size: 0.9rem; color: #6c757d; }
img{max-width:1000px !important;
border-width: 0 !important;
height: auto !important;
}
@media (max-width: 768px) {
.article-header { padding: 1.5rem 1rem; margin-bottom: 1.5rem; }
.article-content { padding: 1.5rem 1rem; margin-bottom: 1.5rem; }
.mp-info { flex-direction: column; text-align: center; gap: 0.75rem; padding: 0.75rem; }
.article-meta { flex-direction: column; gap: 0.5rem; text-align: center; font-size: 0.8rem; }
.article-meta div { display: flex; flex-direction: column; gap: 0.25rem; }
img{max-width:100% !important;}
}
</style>
+18 -3
View File
@@ -7,9 +7,24 @@
.search-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 1rem; }
.form-group { display: flex; flex-direction: column; }
.form-group label { font-weight: 500; margin-bottom: 0.25rem; font-size: 0.9rem; }
.form-control { padding: 0.5rem; border: 1px solid #ced4da; border-radius: 4px; font-size: 0.9rem; }
.search-buttons { display: flex; gap: 0.5rem; justify-content: flex-end; }
.form-control { padding: 0.75rem; border: 1px solid #ced4da; border-radius: 4px; font-size: 1rem; transition: border-color 0.3s; }
.form-control:focus { outline: none; border-color: #667eea; box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.1); }
.search-buttons { display: flex; gap: 0.5rem; justify-content: flex-end; flex-wrap: wrap; }
@media (max-width: 768px) {
.search-form { padding: 1rem; }
.search-row { grid-template-columns: 1fr; gap: 0.75rem; }
.form-control { padding: 0.75rem; font-size: 16px; /* 防止iOS缩放 */ }
.search-buttons { justify-content: center; }
.btn { flex: 1; min-width: 120px; text-align: center; }
}
.article-item { padding: 1rem 0; border-bottom: 1px solid #e9ecef; }
.article-item:last-child { border-bottom: none; }
.article-meta { display: flex; gap: 1rem; font-size: 0.8rem; color: #6c757d; margin-top: 0.5rem; }
.article-meta { display: flex; gap: 1rem; font-size: 0.8rem; color: #6c757d; margin-top: 0.5rem; flex-wrap: wrap; }
@media (max-width: 768px) {
.article-item { padding: 0.75rem 0; }
.article-meta { gap: 0.75rem; font-size: 0.75rem; }
.article-meta span { line-height: 1.4; }
}
</style>
+16
View File
@@ -7,6 +7,22 @@
<p class="text-muted mt-2">高效订阅您感兴趣的微信公众号内容</p>
</div>
</footer>
<script>
function toggleMenu() {
const nav = document.getElementById('nav-menu');
nav.classList.toggle('active');
}
// 点击外部关闭菜单
document.addEventListener('click', function(event) {
const nav = document.getElementById('nav-menu');
const toggle = document.querySelector('.mobile-menu-toggle');
if (!nav.contains(event.target) && !toggle.contains(event.target)) {
nav.classList.remove('active');
}
});
</script>
{% block extra_js %}{% endblock %}
</body>
</html>
+45 -9
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}WeRSS{% endblock %}</title>
<title>{% block title %}微信公众号订阅助手{% endblock %}</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; line-height: 1.6; color: #333; background-color: #f8f9fa; }
@@ -11,23 +11,54 @@
header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 1rem 0; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
.header-content { display: flex; justify-content: space-between; align-items: center; }
.logo { font-size: 1.5rem; font-weight: bold; text-decoration: none; color: white; }
nav ul { list-style: none; display: flex; gap: 2rem; }
nav a { color: white; text-decoration: none; font-weight: 500; }
nav ul { list-style: none; display: flex; gap: 1rem; }
nav a { color: white; text-decoration: none; font-weight: 500; font-size: 0.9rem; }
/* 移动端汉堡菜单 */
.mobile-menu-toggle { display: none; background: none; border: none; color: white; font-size: 1.5rem; cursor: pointer; }
@media (max-width: 768px) {
.container { padding: 0 15px; }
.header-content { flex-wrap: wrap; }
.mobile-menu-toggle { display: block; }
nav { display: none; width: 100%; order: 3; }
nav.active { display: block; }
nav ul { flex-direction: column; gap: 0.5rem; padding-top: 1rem; }
.logo { font-size: 1.2rem; }
}
main { min-height: calc(100vh - 200px); padding: 2rem 0; }
footer { background: #343a40; color: white; text-align: center; padding: 2rem 0; margin-top: 3rem; }
.card { background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); padding: 1.5rem; margin-bottom: 1.5rem; }
.grid { display: grid; gap: 1.5rem; }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
.btn { display: inline-block; padding: 0.5rem 1rem; background: #667eea; color: white; text-decoration: none; border-radius: 4px; }
.btn:hover { background: #5a6fd8; }
.btn { display: inline-block; padding: 0.4rem 0.8rem; background: #667eea; color: white; text-decoration: none; border-radius: 4px; border: none; cursor: pointer; transition: all 0.3s; font-size: 0.8rem; }
.btn:hover { background: #5a6fd8; transform: translateY(-1px); box-shadow: 0 4px 8px rgba(0,0,0,0.1); }
.btn-secondary { background: #6c757d; }
.btn:hover { opacity: 0.9; }
.btn-secondary:hover { background: #5a6268; }
.text-center { text-align: center; }
.text-muted { color: #6c757d; }
.mt-2 { margin-top: 0.5rem; }
.mb-2 { margin-bottom: 0.5rem; }
.mb-4 { margin-bottom: 1rem; }
/* 移动端汉堡菜单 */
.mobile-menu-toggle { display: none; background: none; border: none; color: white; font-size: 1.5rem; cursor: pointer; }
@media (max-width: 768px) {
.container { padding: 0 15px; }
.header-content { flex-wrap: wrap; }
.mobile-menu-toggle { display: block; }
nav { display: none; width: 100%; order: 3; }
nav.active { display: block; }
nav ul { flex-direction: column; gap: 0.5rem; padding-top: 1rem; }
.logo { font-size: 1.2rem; }
main { padding: 1rem 0; }
.card { padding: 1rem; margin-bottom: 1rem; }
.btn { padding: 0.4rem 0.7rem; font-size: 0.8rem; }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; }
.grid-2 { grid-template-columns: 1fr; }
}
</style>
{% block extra_head %}{% endblock %}
</head>
@@ -35,11 +66,16 @@
<header>
<div class="container">
<div class="header-content">
<a href="/views/home" class="logo">📰 WeRSS</a>
<nav>
<a href="/views/home" class="logo">
微信公众号订阅助手
</a>
<button class="mobile-menu-toggle" onclick="toggleMenu()"></button>
<nav id="nav-menu">
<ul>
<li><a href="/views/home">首页</a></li>
<li><a href="/views/articles">文章列表</a></li>
<li><a href="/views/articles">文章</a></li>
<li><a href="https://github.com/rachelos/we-mp-rss" target="_blank" rel="noopener noreferrer">Github</a></li>
<li><a href="https://gitee.com/rachel_os/we-mp-rss" target="_blank" rel="noopener noreferrer">Gitee</a></li>
</ul>
</nav>
</div>
+43 -12
View File
@@ -1,15 +1,46 @@
{% if total_pages > 1 %}
<div class="text-center" style="display: flex; justify-content: center; align-items: center; gap: 1rem;">
{% if has_prev %}
<a href="{{base_url}}?page={{=current_page - 1}}&limit={{limit}}" class="btn btn-secondary">« 上一页</a>
{% endif %}
<span style="color: #6c757d;">
第 {{current_page}} 页,共 {{total_pages}} 页 (总计 {{total_items}} {{item_name}})
</span>
{% if has_next %}
<a href="{{base_url}}?page={{=current_page + 1}}&limit={{limit}}" class="btn btn-secondary">下一页 »</a>
{% endif %}
<div class="pagination-container">
<div class="pagination-wrapper">
{% if has_prev %}
<a href="{{base_url}}?page={{=current_page - 1}}&limit={{limit}}" class="btn btn-secondary">« 上一页</a>
{% endif %}
<div class="pagination-info">
<span>第 {{current_page}} 页</span>
<span class="pagination-total">共 {{total_pages}} 页</span>
<span class="pagination-items">{{total_items}} {{item_name}}</span>
</div>
{% if has_next %}
<a href="{{base_url}}?page={{=current_page + 1}}&limit={{limit}}" class="btn btn-secondary">下一页 »</a>
{% endif %}
</div>
</div>
<style>
.pagination-container { margin: 2rem 0; }
.pagination-wrapper {
display: flex;
justify-content: center;
align-items: center;
gap: 1rem;
flex-wrap: wrap;
padding: 0 1rem;
}
.pagination-info {
flex-direction: column;
align-items: center;
gap: 0.25rem;
color: #6c757d;
font-size: 0.9rem;
}
.pagination-total { font-size: 0.8rem; }
.pagination-items { font-size: 0.8rem; }
@media (max-width: 768px) {
.pagination-wrapper { gap: 0.75rem; padding: 0 0.5rem; }
.pagination-info { font-size: 0.8rem; display: none; }
.btn { flex: 1; min-width: 100px; text-align: center; font-size: 0.9rem; }
}
</style>
{% endif %}
+14 -1
View File
@@ -5,8 +5,16 @@
.tag-header { display: flex; align-items: start; gap: 2rem; margin-bottom: 2rem; }
.tag-cover { width: 120px; height: 120px; border-radius: 8px; object-fit: cover; }
.tag-info h1 { margin-bottom: 0.5rem; color: #333; }
.tag-stats { display: flex; gap: 1rem; margin-top: 1rem; }
.tag-stats { display: flex; gap: 1rem; margin-top: 1rem; flex-wrap: wrap; }
.stat-item { background: #e9ecef; padding: 0.5rem 1rem; border-radius: 20px; font-size: 0.9rem; }
@media (max-width: 768px) {
.tag-header { flex-direction: column; gap: 1rem; text-align: center; }
.tag-cover { width: 100px; height: 100px; margin: 0 auto; }
.tag-info { text-align: center; }
.tag-stats { justify-content: center; }
.stat-item { font-size: 0.8rem; padding: 0.4rem 0.8rem; }
}
.article-item { border-bottom: 1px solid #e9ecef; padding-bottom: 1rem; margin-bottom: 1rem; }
.article-item:last-child { border-bottom: none; }
.article-title { font-size: 1.1rem; font-weight: 600; margin-bottom: 0.5rem; }
@@ -16,4 +24,9 @@
.mp-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 1rem; margin-top: 1rem; }
.mp-item { display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem; background: #f8f9fa; border-radius: 4px; }
.mp-avatar { width: 32px; height: 32px; border-radius: 50%; object-fit: cover; }
@media (max-width: 768px) {
.mp-grid { grid-template-columns: 1fr; gap: 0.75rem; }
.mp-item { padding: 0.75rem; }
}
</style>
+18 -8
View File
@@ -3,6 +3,7 @@ from fastapi.responses import HTMLResponse
from typing import Optional
import os
from datetime import datetime
import re
from core.db import DB
from core.models.article import Article
@@ -11,6 +12,13 @@ from core.models.tags import Tags
from apis.base import format_search_kw
from core.lax.template_parser import TemplateParser
from views.config import base
from driver.wxarticle import Web
def process_content_images(content: str) -> str:
"""处理文章内容中的图片链接,添加前缀"""
if not content:
return content
return Web.proxy_images(content)
# 创建路由器
router = APIRouter(tags=["文章"])
@@ -81,11 +89,13 @@ async def articles_view(
# 处理文章数据
article_list = []
for article, feed in articles:
processed_content = process_content_images(article.content or "")
content_display = processed_content[:200] + "..." if processed_content and len(processed_content) > 200 else processed_content
article_data = {
"id": article.id,
"title": article.title,
"description": article.description,
"pic_url": article.pic_url,
"pic_url": Web.get_image_url(article.pic_url),
"url": article.url,
"publish_time": datetime.fromtimestamp(article.publish_time).strftime('%Y-%m-%d %H:%M') if article.publish_time else "",
"created_at": article.created_at.strftime('%Y-%m-%d %H:%M') if article.created_at else "",
@@ -93,7 +103,7 @@ async def articles_view(
"mp_id": article.mp_id,
"mp_cover": feed.mp_cover if feed else "",
"is_read": bool(article.is_read),
"content": article.content[:200] + "..." if article.content and len(article.content) > 200 else (article.content or "")
"content": content_display
}
article_list.append(article_data)
@@ -226,8 +236,8 @@ async def article_detail_view(
rel_data = {
"id": rel_article.id,
"title": rel_article.title,
"description": rel_article.description,
"pic_url": rel_article.pic_url,
"description": rel_article.description or Web.get_description(rel_article.content),
"pic_url": Web.get_image_url(rel_article.pic_url),
"publish_time": datetime.fromtimestamp(rel_article.publish_time).strftime('%Y-%m-%d %H:%M') if rel_article.publish_time else ""
}
related_list.append(rel_data)
@@ -236,15 +246,15 @@ async def article_detail_view(
article_data = {
"id": article.id,
"title": article.title,
"description": article.description,
"pic_url": article.pic_url,
"description": article.description or Web.get_description(article.content),
"pic_url": Web.get_image_url(rel_article.pic_url),
"url": article.url,
"publish_time": datetime.fromtimestamp(article.publish_time).strftime('%Y-%m-%d %H:%M') if article.publish_time else "",
"created_at": article.created_at.strftime('%Y-%m-%d %H:%M') if article.created_at else "",
"content": article.content or "",
"content": process_content_images(article.content or ""),
"mp_name": feed.mp_name if feed else "未知公众号",
"mp_id": article.mp_id,
"mp_cover": feed.mp_cover if feed else "",
"mp_cover": Web.get_image_url(feed.mp_cover) if feed else "",
"mp_intro": feed.mp_intro if feed else ""
}
+1 -1
View File
@@ -60,7 +60,7 @@ async def home_view(
tag_data = {
"id": tag.id,
"name": tag.name,
"cover": tag.cover,
"cover": Web.get_image_url(tag.cover) if tag.cover else "",
"intro": tag.intro,
"mp_count": mp_count,
"article_count": article_count,