""" 可复用的 StreamField Block 库。 参见 documents/设计方案分析与完善版.md §2.6 组件清单。 """ from django.utils.translation import gettext_lazy as _ from wagtail import blocks from wagtail.images.blocks import ImageChooserBlock from wagtail.snippets.blocks import SnippetChooserBlock from .serializers import serialize_image class CTAButtonBlock(blocks.StructBlock): text = blocks.CharBlock(max_length=50, label=_("按钮文案")) link = blocks.URLBlock(label=_("按钮链接")) class Meta: icon = "link" label = _("按钮") class HeroBlock(blocks.StructBlock): title = blocks.CharBlock(max_length=120, label=_("标题")) subtitle = blocks.TextBlock(required=False, label=_("副标题")) background_image = ImageChooserBlock(required=False, label=_("背景图")) buttons = blocks.ListBlock(CTAButtonBlock(), label=_("按钮组")) class Meta: icon = "image" label = _("首屏 Hero") template = "core/blocks/hero_block.html" class StatItemBlock(blocks.StructBlock): value = blocks.CharBlock(max_length=20, label=_("数值")) label = blocks.CharBlock(max_length=50, label=_("说明文字")) class StatsBlock(blocks.StructBlock): items = blocks.ListBlock(StatItemBlock(), label=_("数据项")) class Meta: icon = "site" label = _("数据展示") template = "core/blocks/stats_block.html" class FeatureBlock(blocks.StructBlock): icon = blocks.CharBlock(max_length=50, required=False, label=_("图标")) title = blocks.CharBlock(max_length=100, label=_("标题")) description = blocks.TextBlock(label=_("描述")) class Meta: icon = "pick" label = _("能力/优势") template = "core/blocks/feature_block.html" class FeatureGridBlock(blocks.StructBlock): heading = blocks.CharBlock(max_length=100, required=False, label=_("模块标题")) items = blocks.ListBlock(FeatureBlock(), label=_("能力列表")) class Meta: icon = "grip" label = _("能力网格") class FAQItemBlock(blocks.StructBlock): question = blocks.CharBlock(max_length=200, label=_("问题")) answer = blocks.RichTextBlock(label=_("回答")) class FAQBlock(blocks.StructBlock): items = blocks.ListBlock(FAQItemBlock(), label=_("问答列表")) class Meta: icon = "help" label = _("常见问题") template = "core/blocks/faq_block.html" class CTABlock(blocks.StructBlock): heading = blocks.CharBlock(max_length=120, label=_("标题")) description = blocks.TextBlock(required=False, label=_("描述")) button = CTAButtonBlock(label=_("按钮")) class Meta: icon = "plus" label = _("行动号召 CTA") template = "core/blocks/cta_block.html" class LogoCloudBlock(blocks.StructBlock): heading = blocks.CharBlock(max_length=100, required=False, label=_("标题")) logos = blocks.ListBlock(ImageChooserBlock(), label=_("Logo 列表")) class Meta: icon = "image" label = _("客户 Logo 墙") template = "core/blocks/logo_cloud_block.html" class CaseStudyBlock(blocks.StructBlock): case_page = blocks.PageChooserBlock( page_type="cases.CaseStudyPage", label=_("案例页面") ) summary = blocks.CharBlock(max_length=250, required=False, label=_("摘要覆盖")) class Meta: icon = "doc-full" label = _("客户案例卡片") class FormBlock(blocks.StructBlock): """嵌入可复用的线索表单(apps.forms.FormDefinition Snippet)。""" form = SnippetChooserBlock("forms.FormDefinition", label=_("选择表单")) class Meta: icon = "form" label = _("线索表单") def get_api_representation(self, value, context=None): """ 默认 SnippetChooserBlock 只会序列化为主键,前端渲染动态表单需要完整字段定义, 因此这里手动展开 FormDefinition 及其子字段(详见 apps/forms/models.py)。 """ form_def = value.get("form") if form_def is None: return {"form": None} return { "form": { "id": form_def.pk, "name": form_def.name, "submit_button_text": form_def.submit_button_text, "success_message": form_def.success_message, "fields": [ { "label": field.label, "field_key": field.field_key, "field_type": field.field_type, "required": field.required, "choices": [ choice.strip() for choice in field.choices.split(",") if choice.strip() ], } for field in form_def.fields.all() ], } } class ProductCardItemBlock(blocks.StructBlock): title = blocks.CharBlock(max_length=100, label=_("标题")) description = blocks.TextBlock(required=False, label=_("描述")) image = ImageChooserBlock(required=False, label=_("图片")) link = blocks.URLBlock(required=False, label=_("链接")) class ProductCardBlock(blocks.StructBlock): heading = blocks.CharBlock(max_length=100, required=False, label=_("模块标题")) items = blocks.ListBlock(ProductCardItemBlock(), label=_("产品列表")) class Meta: icon = "grip" label = _("产品矩阵") class PricingPlanBlock(blocks.StructBlock): name = blocks.CharBlock(max_length=50, label=_("方案名称")) price = blocks.CharBlock(max_length=50, label=_("价格")) features = blocks.ListBlock( blocks.CharBlock(max_length=100), label=_("功能列表") ) highlighted = blocks.BooleanBlock( required=False, default=False, label=_("突出显示") ) button = CTAButtonBlock(required=False, label=_("按钮")) class PricingBlock(blocks.StructBlock): heading = blocks.CharBlock(max_length=100, required=False, label=_("模块标题")) plans = blocks.ListBlock(PricingPlanBlock(), label=_("定价方案")) class Meta: icon = "tag" label = _("定价") class TimelineItemBlock(blocks.StructBlock): year = blocks.CharBlock(max_length=20, label=_("年份/时间")) event = blocks.TextBlock(label=_("事件描述")) class TimelineBlock(blocks.StructBlock): heading = blocks.CharBlock(max_length=100, required=False, label=_("模块标题")) items = blocks.ListBlock(TimelineItemBlock(), label=_("时间线条目")) class Meta: icon = "date" label = _("发展历程") class TeamMemberChooserBlock(SnippetChooserBlock): """选择 apps.core.TeamMember Snippet,展开完整字段供前端渲染(而非仅主键)。""" def __init__(self, **kwargs): super().__init__("core.TeamMember", **kwargs) def get_api_representation(self, value, context=None): if value is None: return None return { "id": value.pk, "name": value.name, "role": value.role, "bio": value.bio, "photo": serialize_image(value.photo), } class TeamBlock(blocks.StructBlock): heading = blocks.CharBlock(max_length=100, required=False, label=_("模块标题")) members = blocks.ListBlock(TeamMemberChooserBlock(), label=_("团队成员")) class Meta: icon = "group" label = _("团队展示") class TechStackItemBlock(blocks.StructBlock): icon = blocks.CharBlock(max_length=50, required=False, label=_("图标")) label = blocks.CharBlock(max_length=50, label=_("名称")) class TechStackBlock(blocks.StructBlock): heading = blocks.CharBlock(max_length=100, required=False, label=_("模块标题")) items = blocks.ListBlock(TechStackItemBlock(), label=_("技术项")) class Meta: icon = "cogs" label = _("技术架构") class VideoBlock(blocks.StructBlock): heading = blocks.CharBlock(max_length=100, required=False, label=_("标题")) video_url = blocks.URLBlock( label=_("视频地址"), help_text=_( "建议使用自建 OSS/COS 存储的 mp4 直链,或腾讯视频/哔哩哔哩等国内平台的" "嵌入地址,避免使用 YouTube/Vimeo(详见 §2.15 国内可访问性要求)" ), ) poster = ImageChooserBlock(required=False, label=_("封面图")) class Meta: icon = "media" label = _("视频") COMMON_BLOCKS = [ ("hero", HeroBlock()), ("stats", StatsBlock()), ("feature_grid", FeatureGridBlock()), ("faq", FAQBlock()), ("cta", CTABlock()), ("logo_cloud", LogoCloudBlock()), ("case_study", CaseStudyBlock()), ("form", FormBlock()), ("product_card", ProductCardBlock()), ("pricing", PricingBlock()), ("timeline", TimelineBlock()), ("team", TeamBlock()), ("tech_stack", TechStackBlock()), ("video", VideoBlock()), ("richtext", blocks.RichTextBlock(label=_("富文本"))), ]