feat: add products/cases page models and forms lead-capture app

- apps/products: ProductIndexPage/ProductPage
- apps/cases: CaseStudyIndexPage/CaseStudyPage
- apps/forms: FormDefinition/FormDefinitionField/Lead + submit API
- core/blocks: CaseStudyBlock, FormBlock (with API representation)
- register new apps, custom leads API route, migrations
This commit is contained in:
2026-08-06 16:04:21 +08:00
parent 7f51a31150
commit 12f7caa158
25 changed files with 618 additions and 0 deletions
+113
View File
@@ -0,0 +1,113 @@
"""
线索表单:FormDefinition 是可复用的表单定义 Snippet,通过 core.blocks.FormBlock
嵌入任意 StreamField 页面;提交结果保存为 Lead。
详见 documents/设计方案分析与完善版.md §2.6FormBlock)与 B2B 官网核心转化路径。
"""
from django.db import models
from modelcluster.fields import ParentalKey
from modelcluster.models import ClusterableModel
from wagtail.admin.panels import FieldPanel, InlinePanel
from wagtail.models import Orderable
from wagtail.snippets.models import register_snippet
FIELD_TYPE_CHOICES = [
("text", "单行文本"),
("textarea", "多行文本"),
("email", "邮箱"),
("tel", "电话"),
("select", "下拉选择"),
]
@register_snippet
class FormDefinition(ClusterableModel):
"""可复用的线索表单定义。"""
name = models.CharField(max_length=100, verbose_name="表单名称")
submit_button_text = models.CharField(
max_length=50, default="提交", verbose_name="提交按钮文案"
)
success_message = models.CharField(
max_length=200,
default="提交成功,我们会尽快与您联系。",
verbose_name="提交成功提示",
)
notification_email = models.EmailField(blank=True, verbose_name="通知邮箱")
panels = [
FieldPanel("name"),
FieldPanel("submit_button_text"),
FieldPanel("success_message"),
FieldPanel("notification_email"),
InlinePanel("fields", label="表单字段"),
]
class Meta:
verbose_name = "线索表单"
verbose_name_plural = "线索表单"
def __str__(self):
return self.name
class FormDefinitionField(Orderable):
"""FormDefinition 的子字段,编辑器在后台可自由增删排序。"""
form = ParentalKey(
FormDefinition, on_delete=models.CASCADE, related_name="fields"
)
label = models.CharField(max_length=100, verbose_name="字段标签")
field_key = models.SlugField(
max_length=50,
verbose_name="字段标识",
help_text="提交数据中使用的 key,例如 name / email / phone",
)
field_type = models.CharField(
max_length=20, choices=FIELD_TYPE_CHOICES, default="text", verbose_name="字段类型"
)
required = models.BooleanField(default=True, verbose_name="是否必填")
choices = models.CharField(
max_length=255,
blank=True,
verbose_name="可选项",
help_text="逗号分隔,仅“下拉选择”类型使用",
)
panels = [
FieldPanel("label"),
FieldPanel("field_key"),
FieldPanel("field_type"),
FieldPanel("required"),
FieldPanel("choices"),
]
@register_snippet
class Lead(models.Model):
"""表单提交产生的线索记录,供销售/市场团队在后台查看。"""
form = models.ForeignKey(
FormDefinition,
on_delete=models.SET_NULL,
null=True,
related_name="leads",
verbose_name="来源表单",
)
data = models.JSONField(default=dict, verbose_name="提交数据")
source_url = models.URLField(blank=True, verbose_name="来源页面")
created_at = models.DateTimeField(auto_now_add=True, verbose_name="提交时间")
panels = [
FieldPanel("form"),
FieldPanel("data"),
FieldPanel("source_url"),
]
class Meta:
verbose_name = "线索"
verbose_name_plural = "线索"
ordering = ["-created_at"]
def __str__(self):
form_name = self.form.name if self.form_id else "(表单已删除)"
return f"{form_name} - {self.created_at:%Y-%m-%d %H:%M}"