test ferst

This commit is contained in:
jze9
2026-05-13 19:05:07 +05:00
commit 0ac1c8a862
33 changed files with 3702 additions and 0 deletions

240
web/views/news_feed.py Normal file
View File

@@ -0,0 +1,240 @@
import flet as ft
import designer as d
import api_client as api
class NewsFeedView:
def __init__(self, page: ft.Page):
self.page = page
self._articles: list = []
self._offset = 0
self._limit = 20
self._has_more = True
self._loading = False
self._category: str | None = None
self._categories: list = []
self._list_col = ft.Column(spacing=16)
self._loader = ft.Container(
content=d.loader(36),
alignment=ft.alignment.center,
padding=20,
visible=True,
)
self._load_more_btn = ft.ElevatedButton(
"Загрузить ещё",
icon=ft.icons.EXPAND_MORE,
on_click=lambda e: self.page.run_task(self._load_more),
visible=False,
style=ft.ButtonStyle(
bgcolor=d.SURFACE,
color=d.PRIMARY,
shape=ft.RoundedRectangleBorder(radius=8),
),
)
self._cat_row = ft.Row(scroll=ft.ScrollMode.AUTO, spacing=8)
def build_page_view(self) -> ft.View:
header = ft.Container(
content=ft.Row(
[
ft.Row([
ft.Icon(ft.icons.NEWSPAPER, color=ft.colors.WHITE, size=28),
ft.Text("Новости", size=20, weight=ft.FontWeight.BOLD, color=ft.colors.WHITE),
], spacing=8),
ft.Row([
ft.TextButton(
"Войти",
icon=ft.icons.ADMIN_PANEL_SETTINGS_OUTLINED,
style=ft.ButtonStyle(color=ft.colors.WHITE70),
on_click=lambda e: self.page.go("/admin/login"),
)
]),
],
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
),
bgcolor=d.ADMIN_SIDEBAR,
padding=ft.padding.symmetric(horizontal=24, vertical=14),
)
cat_section = ft.Container(
content=ft.Column([
ft.Container(content=self._cat_row, padding=ft.padding.symmetric(horizontal=24, vertical=8)),
]),
bgcolor=d.SURFACE,
border=ft.Border(bottom=ft.BorderSide(1, d.DIVIDER)),
)
main_content = ft.Container(
content=ft.Column(
[
self._list_col,
self._loader,
ft.Container(
content=self._load_more_btn,
alignment=ft.alignment.center,
padding=16,
),
],
spacing=0,
),
padding=ft.padding.symmetric(horizontal=24, vertical=20),
alignment=ft.alignment.top_center,
)
return ft.View(
route="/",
bgcolor=d.BACKGROUND,
controls=[
ft.Column(
[
header,
cat_section,
ft.Container(
content=main_content,
alignment=ft.alignment.top_center,
expand=True,
),
],
spacing=0,
expand=True,
scroll=ft.ScrollMode.AUTO,
)
],
)
async def load(self):
self._categories = await api.get_categories()
self._render_categories()
await self._load_articles()
async def _load_more(self):
if self._loading or not self._has_more:
return
self._loading = True
self._load_more_btn.disabled = True
self.page.update()
self._offset += self._limit
await self._load_articles()
self._loading = False
self._load_more_btn.disabled = False
self.page.update()
async def _load_articles(self):
self._loader.visible = True
self.page.update()
data = await api.get_news(offset=self._offset, limit=self._limit, category=self._category)
self._loader.visible = False
if not data:
self.page.update()
return
items = data.get("items", [])
self._articles.extend(items)
self._has_more = data.get("has_more", False)
for a in items:
self._list_col.controls.append(self._build_card(a))
self._load_more_btn.visible = self._has_more
self.page.update()
def _render_categories(self):
self._cat_row.controls = [
self._cat_chip(None, "Все")
] + [
self._cat_chip(c["slug"], c["name"]) for c in self._categories
]
self.page.update()
def _cat_chip(self, slug: str | None, label: str) -> ft.Chip:
is_active = self._category == slug
return ft.Chip(
label=ft.Text(label, size=13),
bgcolor=d.PRIMARY if is_active else d.SURFACE,
label_style=ft.TextStyle(color=ft.colors.WHITE if is_active else d.TEXT_PRIMARY),
on_click=lambda e, s=slug: self.page.run_task(self._filter_category, s),
padding=ft.padding.symmetric(horizontal=12, vertical=6),
side=ft.BorderSide(1, d.PRIMARY if is_active else d.DIVIDER),
)
async def _filter_category(self, slug: str | None):
self._category = slug
self._articles = []
self._offset = 0
self._has_more = True
self._list_col.controls.clear()
self._render_categories()
await self._load_articles()
def _build_card(self, article: dict) -> ft.Container:
pub_date = ""
if article.get("published_at"):
pub_date = article["published_at"][:10]
cat = article.get("category")
cat_chip = ft.Container(
content=ft.Text(cat["name"], size=11, color=ft.colors.WHITE),
bgcolor=d.PRIMARY,
border_radius=4,
padding=ft.padding.symmetric(horizontal=8, vertical=3),
visible=bool(cat),
) if cat else ft.Container(visible=False)
cover = ft.Image(
src=article["cover_url"],
width=260,
height=160,
fit=ft.ImageFit.COVER,
border_radius=ft.BorderRadius(0, 8, 8, 0),
) if article.get("cover_url") else ft.Container(visible=False)
text_part = ft.Column(
[
ft.Row([cat_chip, ft.Text(pub_date, size=11, color=d.TEXT_SECONDARY)], spacing=8),
ft.Text(
article["title"],
size=18,
weight=ft.FontWeight.BOLD,
color=d.TEXT_PRIMARY,
max_lines=3,
overflow=ft.TextOverflow.ELLIPSIS,
),
ft.Text(
article.get("excerpt", ""),
size=13,
color=d.TEXT_SECONDARY,
max_lines=3,
overflow=ft.TextOverflow.ELLIPSIS,
),
ft.Row(
[
ft.Row([
ft.Icon(ft.icons.REMOVE_RED_EYE_OUTLINED, size=14, color=d.TEXT_SECONDARY),
ft.Text(str(article.get("view_count", 0)), size=12, color=d.TEXT_SECONDARY),
], spacing=4),
ft.TextButton(
"Читать →",
style=ft.ButtonStyle(color=d.PRIMARY),
on_click=lambda e, s=article["slug"]: self.page.go(f"/news/{s}"),
),
],
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
),
],
spacing=8,
expand=True,
)
return ft.Container(
content=ft.Row([ft.Container(content=text_part, expand=True, padding=20), cover], spacing=0),
bgcolor=d.SURFACE,
border_radius=12,
border=ft.border.all(1, d.DIVIDER),
shadow=ft.BoxShadow(blur_radius=6, color=ft.colors.with_opacity(0.05, ft.colors.BLACK)),
clip_behavior=ft.ClipBehavior.HARD_EDGE,
on_click=lambda e, s=article["slug"]: self.page.go(f"/news/{s}"),
ink=True,
)