feat: add 18 independent industry demo templates
This commit is contained in:
+50
-1
@@ -6,8 +6,10 @@ apps.core 全局 Snippet 只读接口。挂载在 /api/v1/custom/core/
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.throttling import ScopedRateThrottle
|
||||
from rest_framework.views import APIView
|
||||
from django.http import Http404
|
||||
|
||||
from .models import NavigationMenu, Partner, SiteSettings, TeamMember, Testimonial
|
||||
from .industry_templates import INDUSTRIES, demo_path, demo_root
|
||||
from .models import NavigationMenu, Partner, SimpleContentPage, SiteSettings, TeamMember, Testimonial
|
||||
from .serializers import (
|
||||
NavigationMenuSerializer,
|
||||
PartnerSerializer,
|
||||
@@ -65,3 +67,50 @@ class SiteSettingsView(APIView):
|
||||
def get(self, request, *args, **kwargs):
|
||||
settings_obj = SiteSettings.for_request(request)
|
||||
return Response(SiteSettingsSerializer(settings_obj).data)
|
||||
|
||||
|
||||
class IndustryDemoView(APIView):
|
||||
throttle_classes = [ScopedRateThrottle]
|
||||
throttle_scope = "public"
|
||||
|
||||
def get(self, request, industry=None, *args, **kwargs):
|
||||
if industry is None:
|
||||
return Response({"items": [
|
||||
{"slug": slug, "label": spec["label"], "company": spec["company"],
|
||||
"description": spec["description"], "url": demo_path(slug)}
|
||||
for slug, spec in INDUSTRIES.items()
|
||||
if (root := demo_root(slug)) is not None and root.live
|
||||
]})
|
||||
if industry not in INDUSTRIES:
|
||||
raise Http404
|
||||
root = demo_root(industry)
|
||||
if root is None or not root.live or not type(root).objects.public().filter(pk=root.pk).exists():
|
||||
raise Http404
|
||||
path = request.query_params.get("path", "").strip("/")
|
||||
page = root
|
||||
if path:
|
||||
for segment in path.split("/"):
|
||||
if not segment or segment in (".", ".."):
|
||||
raise Http404
|
||||
child = page.get_children().live().public().filter(slug=segment).first()
|
||||
if child is None:
|
||||
raise Http404
|
||||
page = child.specific
|
||||
if not page.get_view_restrictions().exists() and not root.get_view_restrictions().exists():
|
||||
body = getattr(page, "body", None)
|
||||
blocks = body.stream_block.get_api_representation(body, context={"request": request}) if hasattr(body, "stream_block") else []
|
||||
children = [
|
||||
{"title": child.title, "url": demo_path(industry, f"{path}/{child.slug}")}
|
||||
for child in page.get_children().live().public()
|
||||
]
|
||||
navigation = [{"title": "首页", "url": demo_path(industry)}] + [
|
||||
{"title": child.title, "url": demo_path(industry, child.slug)}
|
||||
for child in root.get_children().live().public()
|
||||
if child.slug != "privacy-policy"
|
||||
]
|
||||
return Response({"industry": industry, "label": INDUSTRIES[industry]["label"],
|
||||
"company": root.title, "title": page.title, "blocks": blocks,
|
||||
"html": str(body) if isinstance(page, SimpleContentPage) else "",
|
||||
"intro": getattr(page, "intro", "") or getattr(page, "summary", ""),
|
||||
"children": children, "navigation": navigation})
|
||||
raise Http404
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
"""独立行业演示内容树,不修改默认站点或已有页面。"""
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import transaction
|
||||
from django.utils import timezone
|
||||
from django.utils.html import format_html
|
||||
from wagtail.models import Page
|
||||
|
||||
from apps.blog.models import BlogIndexPage, BlogPage
|
||||
from apps.cases.models import CaseStudyIndexPage, CaseStudyPage
|
||||
from apps.forms.models import FormDefinition
|
||||
from apps.home.models import HomePage
|
||||
from apps.products.models import ProductIndexPage, ProductPage
|
||||
from apps.solutions.models import SolutionIndexPage, SolutionPage
|
||||
from .management.commands.seed_contact_page import CONTACT_FORM_FIELDS
|
||||
from .models import ContactPage, SimpleContentPage
|
||||
from .site_setup import _ensure_page
|
||||
|
||||
INDUSTRIES = {
|
||||
"software": {
|
||||
"label": "软件服务", "company": "云启软件(虚构演示)",
|
||||
"headline": "让软件贴合业务,让团队专注增长",
|
||||
"description": "从需求分析、应用开发到系统集成,展示软件服务企业的完整交付能力。",
|
||||
"products": [("content-platform", "企业内容平台", "统一管理品牌内容、产品资料与咨询入口。"),
|
||||
("custom-development", "定制开发服务", "围绕企业业务流程开发 Web 应用与接口。")],
|
||||
"solution": ("digital-workplace", "数字化协同方案", "梳理现有流程,连接应用和数据,分阶段开展数字化建设。"),
|
||||
"case": "内容平台建设案例", "article": "软件项目如何定义验收标准",
|
||||
"features": [("需求共创", "通过访谈与原型明确业务边界。"), ("迭代交付", "以可验证成果推进开发。"), ("运维支持", "提供上线培训与持续维护。")],
|
||||
"steps": ["需求分析", "原型验证", "开发测试", "上线维护"],
|
||||
},
|
||||
"manufacturing": {
|
||||
"label": "制造业", "company": "精工制造(虚构演示)",
|
||||
"headline": "从图纸到交付,呈现可靠的制造流程",
|
||||
"description": "面向采购与工程团队,展示加工能力、产品规格与质量管理流程。",
|
||||
"products": [("precision-parts", "精密零部件", "按技术图纸评估材料、尺寸公差与加工工艺。"),
|
||||
("automation-equipment", "自动化工装设备", "围绕生产工序设计工装与自动化辅助设备。")],
|
||||
"solution": ("flexible-production", "柔性生产方案", "评估批量、工艺与检验要求,制定打样和批次交付计划。"),
|
||||
"case": "生产工装改造案例", "article": "采购询价前需要准备哪些资料",
|
||||
"features": [("工艺评审", "结合图纸与应用场景评估可制造性。"), ("质量检验", "记录检验要求与批次追溯信息。"), ("交付协同", "确认打样、包装和交期安排。")],
|
||||
"steps": ["图纸评审", "样件确认", "生产检验", "批次交付"],
|
||||
},
|
||||
"consulting": {
|
||||
"label": "管理咨询", "company": "知行咨询(虚构演示)",
|
||||
"headline": "从问题诊断到行动方案,让改善有章可循",
|
||||
"description": "展示咨询服务范围、工作方法和交付物,帮助企业判断合作是否适合。",
|
||||
"products": [("strategy-advisory", "战略规划咨询", "梳理目标、市场假设与资源约束,形成行动路线。"),
|
||||
("process-advisory", "流程优化咨询", "识别协作瓶颈,设计职责和流程改进方案。")],
|
||||
"solution": ("organization-growth", "组织成长方案", "围绕组织目标开展访谈、流程诊断与实施辅导。"),
|
||||
"case": "组织流程诊断案例", "article": "如何准备一次有效的咨询需求访谈",
|
||||
"features": [("业务诊断", "用访谈与资料分析形成问题清单。"), ("方案共创", "与团队确认改进优先级及实施路径。"), ("落地辅导", "跟进执行反馈,调整行动计划。")],
|
||||
"steps": ["需求访谈", "诊断分析", "方案研讨", "实施辅导"],
|
||||
},
|
||||
"education": {
|
||||
"label": "教育培训", "company": "启学教育(虚构演示)",
|
||||
"headline": "围绕学习目标,呈现清晰的课程与教学安排",
|
||||
"description": "展示成人学习课程、企业培训及教学服务流程,不承诺考试通过、升学或就业结果。",
|
||||
"products": [("skills-courses", "成人技能课程", "展示课程目标、适用基础、教学形式与学习任务。"),
|
||||
("corporate-training", "企业团队培训", "根据岗位需求规划课程主题、练习与反馈安排。")],
|
||||
"solution": ("learning-program", "团队学习方案", "梳理学习需求、安排课程与练习,以过程反馈改进学习计划。"),
|
||||
"case": "团队课程组织案例", "article": "选择培训课程前如何核对课程信息",
|
||||
"features": [("目标梳理", "明确适用人群与课程边界。"), ("教学安排", "展示课程结构与学习任务。"), ("学习反馈", "记录参与反馈,不作成绩或证书保证。")],
|
||||
"steps": ["需求沟通", "课程规划", "学习实施", "反馈复盘"],
|
||||
},
|
||||
"healthcare": {
|
||||
"label": "医疗健康", "company": "安禾健康(虚构演示)",
|
||||
"headline": "清晰展示健康服务信息与预约流程",
|
||||
"description": "展示机构服务说明、预约指引与健康信息管理流程;不提供诊断、治疗建议或疗效承诺。",
|
||||
"products": [("appointment-guidance", "预约服务指引", "介绍服务时间、预约流程及到访前的信息确认方式。"),
|
||||
("health-information", "健康信息服务", "展示公开信息栏目与服务说明,不提供个体化医疗建议。")],
|
||||
"solution": ("service-coordination", "服务协同流程", "明确预约确认、信息指引和服务反馈环节,不收集病历或患者资料。"),
|
||||
"case": "预约流程优化案例(非患者案例)", "article": "浏览健康服务网站时需要核对哪些信息",
|
||||
"features": [("信息透明", "说明服务范围,真实资质需正式上线前核验。"), ("预约指引", "提供流程说明,不代替专业医疗咨询。"), ("隐私保护", "演示不收集病历、诊断或其他健康敏感信息。")],
|
||||
"steps": ["服务了解", "流程确认", "信息指引", "服务反馈"],
|
||||
},
|
||||
"dining": {
|
||||
"label": "餐饮", "company": "禾味餐饮(虚构演示)",
|
||||
"headline": "从菜单到到店体验,展示餐饮品牌的日常服务",
|
||||
"description": "展示菜单结构、门店服务及活动餐饮流程,食材、过敏原、价格和营业信息需正式上线前核实。",
|
||||
"products": [("seasonal-menu", "季节菜单", "展示菜品分类与用餐场景,实际供应、价格和过敏原信息需另行确认。"),
|
||||
("event-catering", "活动餐饮服务", "围绕人数、场地与用餐安排沟通餐饮服务范围。")],
|
||||
"solution": ("group-dining", "团体用餐方案", "确认人数、饮食需求与服务时间,安排菜单沟通和现场服务。"),
|
||||
"case": "团体用餐组织案例", "article": "预约团体用餐前需要确认哪些事项",
|
||||
"features": [("菜单展示", "清晰介绍品类与供应说明。"), ("到店指引", "确认营业时间与预订规则。"), ("服务沟通", "核对人数、饮食限制与服务安排。")],
|
||||
"steps": ["需求确认", "菜单沟通", "安排确认", "用餐反馈"],
|
||||
},
|
||||
"construction": {
|
||||
"label": "建筑工程", "company": "筑行工程(虚构演示)",
|
||||
"headline": "从项目需求到交付节点,清晰展示工程服务流程",
|
||||
"description": "展示工程策划、施工协同与交付管理;真实资质、安全要求和承包范围需正式合作前核验。",
|
||||
"products": [("project-planning", "工程项目策划", "梳理项目目标、场地条件与阶段任务,不替代专业设计及审批。"),
|
||||
("construction-coordination", "施工协同服务", "展示进度沟通、资料记录与现场协作流程,不提供施工操作指导。")],
|
||||
"solution": ("project-delivery", "工程交付协同方案", "确认职责、阶段节点与验收资料,实际标准和责任以合同及适用规定为准。"),
|
||||
"case": "工程节点协同案例", "article": "工程项目沟通前需要准备哪些资料",
|
||||
"features": [("范围确认", "明确服务边界与参与方职责。"), ("节点管理", "展示进度计划及变更沟通机制。"), ("资料交接", "按约定整理验收和交付资料。")],
|
||||
"steps": ["需求梳理", "范围确认", "实施协同", "交付核对"],
|
||||
},
|
||||
"real-estate": {
|
||||
"label": "房地产", "company": "栖城置业(虚构演示)",
|
||||
"headline": "清晰呈现空间信息与看房咨询流程",
|
||||
"description": "展示住宅与商业空间栏目及咨询流程;全部空间均为虚构示例,不构成真实房源、报价或投资建议。",
|
||||
"products": [("residential-spaces", "住宅空间展示", "介绍空间布局与使用场景,面积、价格、产权及销售许可须核验。"),
|
||||
("commercial-spaces", "商业空间展示", "展示办公与经营空间信息结构,实际用途、可用状态和租售条件须另行确认。")],
|
||||
"solution": ("viewing-consultation", "看房咨询流程", "梳理空间需求、资料核对与到访沟通,不提供在线交易、贷款审批或收益承诺。"),
|
||||
"case": "空间资料整理案例(非真实成交)", "article": "浏览房源信息时需要核对哪些事项",
|
||||
"features": [("信息展示", "区分示例信息与经核验的实际房源。"), ("需求沟通", "了解使用场景与到访安排。"), ("资料核对", "正式上线前核实权属、许可及费用说明。")],
|
||||
"steps": ["需求了解", "资料核对", "到访沟通", "反馈整理"],
|
||||
},
|
||||
"home-renovation": {
|
||||
"label": "家居装修", "company": "和居设计(虚构演示)",
|
||||
"headline": "从空间需求到方案沟通,展示家居装修服务",
|
||||
"description": "展示空间设计、材料沟通和装修协同流程;实际报价、材料性能、施工许可及质量标准需核实。",
|
||||
"products": [("interior-design", "室内空间设计", "围绕生活习惯与空间条件沟通布局和风格,不替代结构安全评估。"),
|
||||
("material-selection", "材料选配服务", "展示材料清单与选配流程,环保、防火及其他性能以合规检测资料为准。")],
|
||||
"solution": ("renovation-coordination", "家居装修协同方案", "明确设计确认、预算范围、阶段沟通与验收安排,变更及质保条件按合同确认。"),
|
||||
"case": "居住空间方案沟通案例", "article": "装修需求沟通前如何整理空间与预算信息",
|
||||
"features": [("空间沟通", "明确使用需求与现有条件。"), ("清单确认", "核对材料、预算和服务边界。"), ("阶段反馈", "记录变更与验收事项,不作零污染承诺。")],
|
||||
"steps": ["需求访谈", "方案沟通", "清单确认", "阶段验收"],
|
||||
},
|
||||
"agri-food": {
|
||||
"label": "农业食品", "company": "禾源农食(虚构演示)",
|
||||
"headline": "从产地信息到供货沟通,展示农业食品服务",
|
||||
"description": "展示农产品、食品供应与批次信息流程;产地、生产许可、检测、储运和过敏原信息须正式上线前核验。",
|
||||
"products": [("farm-produce", "农产品展示", "介绍品类、规格及供应季节,不宣称未经核验的有机认证或品质等级。"),
|
||||
("food-supply", "食品供应服务", "展示包装规格与供货沟通流程,保质期、配料、过敏原及储存条件以合规标签为准。")],
|
||||
"solution": ("supply-traceability", "供货信息协同方案", "梳理批次资料、供货安排与交接记录,不替代食品安全检测或真实追溯系统。"),
|
||||
"case": "批次资料协同案例", "article": "食品采购沟通前需要核对哪些信息",
|
||||
"features": [("品类说明", "明确规格与实际供应范围。"), ("资料核对", "核验许可、标签及检测资料。"), ("交接沟通", "确认批次和储运要求,不提供食品处理操作指导。")],
|
||||
"steps": ["需求沟通", "资料核对", "供货确认", "交接反馈"],
|
||||
},
|
||||
"energy-environment": {
|
||||
"label": "能源环保", "company": "清衡能源(虚构演示)",
|
||||
"headline": "清晰展示能源管理与环保服务的实施边界",
|
||||
"description": "展示能源信息梳理与环保项目协同;资质、监测数据、节能减排效果及合规要求须核验,不作收益或达标保证。",
|
||||
"products": [("energy-management", "能源管理服务", "展示用能资料整理与管理流程,不提供电气安装操作或未经验证的节能数据。"),
|
||||
("environmental-coordination", "环保项目协同", "梳理项目资料、监测要求与参与方职责,不替代法定检测、审批或污染处理操作规范。")],
|
||||
"solution": ("resource-management", "资源管理协同方案", "确认数据范围、评估依据与阶段反馈,实际成效需由合规方法及可核验数据支持。"),
|
||||
"case": "用能资料整理案例(非减排业绩)", "article": "能源环保项目沟通前需要准备哪些资料",
|
||||
"features": [("边界明确", "说明服务范围及专业责任。"), ("数据核验", "区分示例数据与实际监测结果。"), ("阶段协同", "记录资料、评估和交接事项,不作零排放承诺。")],
|
||||
"steps": ["需求梳理", "资料核对", "方案沟通", "阶段反馈"],
|
||||
},
|
||||
"auto-services": {
|
||||
"label": "汽车服务", "company": "行安汽车(虚构演示)",
|
||||
"headline": "从服务了解到账单确认,展示汽车服务流程",
|
||||
"description": "展示车辆保养、到店检查与服务沟通;维修资质、适用车型、配件、费用及质保条件须核实。",
|
||||
"products": [("maintenance-services", "车辆保养服务", "介绍保养项目与预约流程,实际周期和项目以车辆手册及专业检查为准。"),
|
||||
("inspection-consultation", "到店检查咨询", "说明车辆情况沟通与检查安排,不提供远程故障诊断或维修操作指导。")],
|
||||
"solution": ("service-appointment", "到店服务协同方案", "确认服务需求、到店时间、报价授权与交付记录,不替代专业检查或车辆安全检验。"),
|
||||
"case": "到店服务沟通案例", "article": "预约车辆服务前需要确认哪些事项",
|
||||
"features": [("需求了解", "梳理车型与服务诉求,演示不收集车牌或车架号。"), ("费用确认", "施工前核对服务范围和报价授权。"), ("交付说明", "记录完成项目与质保边界,不作绝对安全保证。")],
|
||||
"steps": ["需求沟通", "到店确认", "报价授权", "交付反馈"],
|
||||
},
|
||||
"culture-media": {
|
||||
"label": "文化传媒", "company": "映川传媒(虚构演示)",
|
||||
"headline": "从创意沟通到内容交付,展示文化传媒服务",
|
||||
"description": "展示品牌内容策划、活动传播与制作协同;作品、肖像、音乐及素材授权须核验,不承诺传播量或营销效果。",
|
||||
"products": [("content-planning", "品牌内容策划", "梳理受众、主题与内容形式,示例不使用真实客户作品或未经授权的素材。"),
|
||||
("event-communication", "活动传播服务", "展示活动内容与传播安排,实际许可、场地、执行范围和发布渠道须确认。")],
|
||||
"solution": ("creative-delivery", "创意制作协同方案", "确认创意范围、素材授权、审核节点与交付规格,版权归属及修改次数以合同为准。"),
|
||||
"case": "内容审核协同案例(非真实客户作品)", "article": "内容制作前如何整理需求与授权资料",
|
||||
"features": [("需求策划", "明确受众、主题及交付形式。"), ("授权核对", "核验素材使用范围与肖像许可。"), ("审核交付", "记录版本反馈与发布授权,不作流量保证。")],
|
||||
"steps": ["需求沟通", "创意确认", "审核协同", "内容交付"],
|
||||
},
|
||||
"sports-fitness": {
|
||||
"label": "体育健身", "company": "跃动健身(虚构演示)",
|
||||
"headline": "清晰展示运动课程与到店体验流程",
|
||||
"description": "展示运动课程、场馆服务与报名沟通;教练资质、适用人群、费用及场馆条件须核验,不承诺减重或身体改善效果。",
|
||||
"products": [("fitness-classes", "运动课程展示", "介绍课程形式、参与基础与安排,不提供个体训练处方、康复或医疗建议。"),
|
||||
("venue-services", "场馆体验服务", "展示开放时间、预约流程与使用规则,实际设施和安全要求须到店确认。")],
|
||||
"solution": ("activity-enrollment", "运动参与协同方案", "梳理课程需求、报名规则与反馈流程,不替代专业健康评估,演示不收集健康敏感信息。"),
|
||||
"case": "课程报名组织案例(非效果证明)", "article": "报名运动课程前需要确认哪些事项",
|
||||
"features": [("课程说明", "明确参与要求与课程边界。"), ("预约沟通", "核对时间、费用及退改规则。"), ("参与反馈", "记录服务反馈,不作成绩或健身效果保证。")],
|
||||
"steps": ["课程了解", "规则确认", "预约沟通", "服务反馈"],
|
||||
},
|
||||
"pet-services": {
|
||||
"label": "宠物服务", "company": "伴爪宠物(虚构演示)",
|
||||
"headline": "从服务了解到账单确认,展示宠物生活服务",
|
||||
"description": "展示宠物美容、寄养与到店沟通;经营资质、服务条件及费用须核实,不提供动物诊疗、用药或行为治疗建议。",
|
||||
"products": [("pet-grooming", "宠物美容服务", "介绍服务项目与预约要求,适用情况及操作由合格服务人员现场评估。"),
|
||||
("pet-boarding", "宠物寄养服务", "展示环境说明、入住规则与交接安排,健康及防疫要求由实际机构确认。")],
|
||||
"solution": ("pet-care-coordination", "宠物服务交接方案", "明确服务范围、授权事项、交接记录与紧急联系流程;异常情况应联系合格兽医,不作绝对安全保证。"),
|
||||
"case": "寄养交接沟通案例", "article": "预约宠物服务前需要核对哪些事项",
|
||||
"features": [("服务说明", "区分生活服务与专业动物诊疗。"), ("条件确认", "核对入住规则、费用及服务边界。"), ("交接记录", "记录授权和反馈,不在演示中提交真实联系方式或宠物医疗资料。")],
|
||||
"steps": ["需求沟通", "条件确认", "服务交接", "反馈核对"],
|
||||
},
|
||||
"legal-services": {
|
||||
"label": "法律服务", "company": "明衡法律(虚构演示)",
|
||||
"headline": "清晰展示法律服务范围与委托沟通流程",
|
||||
"description": "展示企业法律服务、合同事务与委托流程;机构和人员执业资质须核验,不构成个案法律意见或胜诉承诺。",
|
||||
"products": [("business-legal", "企业法律服务", "介绍服务范围与沟通方式,具体意见应由具备资质的专业人员结合事实提供。"),
|
||||
("contract-services", "合同事务服务", "展示合同事务沟通与审核流程,演示不接收合同原件、案件证据或保密材料。")],
|
||||
"solution": ("engagement-coordination", "法律委托协同方案", "说明需求沟通、利益冲突核查、委托确认及交付安排;浏览或提交演示表单不建立委托关系。"),
|
||||
"case": "委托流程整理案例(非真实案件)", "article": "选择法律服务前需要确认哪些事项",
|
||||
"features": [("范围说明", "明确服务边界与执业资质核验要求。"), ("委托确认", "核对费用、利益冲突和保密约定。"), ("交付沟通", "说明交付形式,不作结果或办理时限保证。")],
|
||||
"steps": ["需求沟通", "冲突核查", "委托确认", "交付反馈"],
|
||||
},
|
||||
"finance-tax": {
|
||||
"label": "财税服务", "company": "清账财税(虚构演示)",
|
||||
"headline": "从服务范围到资料交接,展示财税协同流程",
|
||||
"description": "展示账务协同、税务事务与交接流程;服务资质、适用政策及费用须核验,不提供个别税务建议或节税保证。",
|
||||
"products": [("accounting-coordination", "账务协同服务", "介绍账务资料整理与交接流程,演示不接收发票、银行账户或真实财务凭证。"),
|
||||
("tax-affairs", "税务事务服务", "展示事项沟通与资料核对安排,具体处理须依据现行规定及企业实际情况由专业人员确认。")],
|
||||
"solution": ("finance-handover", "财税资料交接方案", "明确服务范围、责任分工与交接记录,不替代法定申报、审计或专业税务判断。"),
|
||||
"case": "财税交接流程案例(非节税业绩)", "article": "委托财税服务前需要核对哪些事项",
|
||||
"features": [("范围确认", "核实服务资质与职责边界。"), ("资料保护", "正式业务通过经授权的安全渠道交接。"), ("进度沟通", "记录事项和反馈,不作退税或零风险承诺。")],
|
||||
"steps": ["需求梳理", "范围确认", "交接核对", "事项反馈"],
|
||||
},
|
||||
"human-resources": {
|
||||
"label": "人力资源", "company": "同程人才(虚构演示)",
|
||||
"headline": "清晰呈现企业人才服务与招聘协同流程",
|
||||
"description": "展示企业招聘协同、岗位信息与人事服务流程;许可、岗位真实性及服务条件须核验,不承诺录用、薪酬或招聘结果。",
|
||||
"products": [("recruitment-coordination", "招聘协同服务", "介绍企业岗位需求与招聘沟通流程,所有岗位均为虚构示例,不开放真实简历投递。"),
|
||||
("hr-process-services", "人事流程服务", "展示入职资料及流程协同范围,不接收身份证、薪资记录或员工个人资料,不提供个案劳动法律意见。")],
|
||||
"solution": ("talent-service", "人才服务协同方案", "确认岗位职责、合法合理的任职要求、服务分工与反馈机制,不替代真实招聘系统或劳动合同。"),
|
||||
"case": "岗位需求沟通案例(非真实招聘成果)", "article": "委托人力资源服务前需要确认哪些事项",
|
||||
"features": [("岗位核对", "明确真实岗位信息与任职要求。"), ("授权保护", "正式业务按授权及适用规定处理个人信息。"), ("协同反馈", "记录流程进度,不作就业或人才到岗保证。")],
|
||||
"steps": ["需求访谈", "岗位核对", "协同确认", "反馈复盘"],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def demo_root(industry):
|
||||
if industry not in INDUSTRIES:
|
||||
raise ValidationError("未知行业模板。")
|
||||
return HomePage.objects.child_of(Page.get_first_root_node()).filter(slug=f"demo-{industry}").first()
|
||||
|
||||
|
||||
def demo_path(industry, path=""):
|
||||
return f"/demo/{industry}/" + path.strip("/")
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
def seed_industry_templates():
|
||||
root = Page.objects.select_for_update().get(pk=Page.get_first_root_node().pk)
|
||||
created = []
|
||||
for industry, spec in INDUSTRIES.items():
|
||||
def ensure(parent, model, slug, title, **fields):
|
||||
return _ensure_page(parent, model, slug, title, created, **fields)
|
||||
|
||||
def cta():
|
||||
return ("cta", {"heading": "讨论您的项目需求", "description": "仅用于演示,请勿提交真实个人或商业敏感信息。",
|
||||
"button": {"text": "体验咨询表单", "link": demo_path(industry, "contact")}})
|
||||
|
||||
home = ensure(root, HomePage, f"demo-{industry}", spec["company"], body=[
|
||||
("hero", {"title": spec["headline"], "subtitle": spec["description"], "background_image": None,
|
||||
"buttons": [{"text": "了解产品与服务", "link": demo_path(industry, "products")},
|
||||
{"text": "项目咨询", "link": demo_path(industry, "contact")}]}),
|
||||
("feature_grid", {"heading": "核心能力", "items": [
|
||||
{"icon": str(i + 1), "title": title, "description": description}
|
||||
for i, (title, description) in enumerate(spec["features"])]}),
|
||||
("product_card", {"heading": "产品与服务", "items": [
|
||||
{"title": title, "description": description, "image": None, "link": demo_path(industry, f"products/{slug}")}
|
||||
for slug, title, description in spec["products"]]}),
|
||||
("faq", {"items": [{"question": "这些企业和案例是真实的吗?", "answer": "<p>不是。所有企业、案例及能力描述均为虚构模板演示,不代表真实业绩、资质或承诺。</p>"}]}), cta(),
|
||||
])
|
||||
products = ensure(home, ProductIndexPage, "products", "产品与服务", intro=spec["description"])
|
||||
for slug, title, description in spec["products"]:
|
||||
ensure(products, ProductPage, slug, title, summary=description, body=[
|
||||
("richtext", str(format_html("<h2>{}</h2><p>{}</p><p>示例内容:实际规格、范围与交付条件需另行确认。</p>", title, description))), cta()])
|
||||
solutions = ensure(home, SolutionIndexPage, "solutions", "解决方案", intro="以业务场景为起点,明确实施范围。")
|
||||
slug, title, description = spec["solution"]
|
||||
ensure(solutions, SolutionPage, slug, title, industry=spec["label"], summary=description, body=[
|
||||
("richtext", str(format_html("<h2>{}</h2><p>{}</p>", title, description))),
|
||||
("feature_grid", {"heading": "实施步骤", "items": [
|
||||
{"icon": str(i + 1), "title": step, "description": "与客户确认本阶段成果和下一步安排。"}
|
||||
for i, step in enumerate(spec["steps"])]}), cta()])
|
||||
cases = ensure(home, CaseStudyIndexPage, "cases", "示例案例", intro="案例均为虚构,不代表实际客户或业绩。")
|
||||
ensure(cases, CaseStudyPage, "example-project", f"{spec['case']}(虚构)", client_name="虚构示例客户",
|
||||
industry=spec["label"], published_at=timezone.now(), summary="展示项目背景、实施过程与验收方式,不代表真实成果。",
|
||||
body=[("richtext", "<h2>项目背景(虚构)</h2><p>示例客户希望改善现有业务流程。</p><h2>实施过程</h2><p>确认需求、评审方案、分阶段验收。</p><h2>模板提示</h2><p>请替换为获得客户授权的真实案例,不使用虚构数字或资质。</p>"), cta()])
|
||||
blog = ensure(home, BlogIndexPage, "blog", "行业洞察", intro="分享方法与实践,以下为模板示例。")
|
||||
ensure(blog, BlogPage, "project-checklist", spec["article"], published_at=timezone.now(), intro=spec["description"],
|
||||
body=[("richtext", "<h2>准备清单</h2><ul><li>明确业务目标与需求范围。</li><li>整理现有资料与约束。</li><li>确认交付物及验收方式。</li><li>约定沟通渠道与责任人。</li></ul><p>本文为演示材料,不构成专业建议。</p>")])
|
||||
ensure(home, SimpleContentPage, "about", "关于我们", body=str(format_html(
|
||||
"<h2>{}</h2><p>{}</p><p>此企业为虚构演示,未声明真实团队、客户、资质或业绩。</p>", spec["company"], spec["description"])))
|
||||
ensure(home, SimpleContentPage, "privacy-policy", "演示隐私说明", body="<p>演示表单会在本地测试数据库保存提交内容,不发送外部通知。请勿提交真实个人或商业敏感信息。正式上线前需制定真实隐私政策。</p>")
|
||||
if not home.get_children().filter(slug="contact").exists():
|
||||
form = FormDefinition.objects.create(name=f"{spec['label']}演示咨询", notification_email="",
|
||||
success_message="演示提交已记录,不会发送外部通知。")
|
||||
for order, field in enumerate(CONTACT_FORM_FIELDS):
|
||||
form.fields.create(sort_order=order, **field)
|
||||
form.save()
|
||||
ensure(home, ContactPage, "contact", "联系咨询", body=[
|
||||
("richtext", "<h2>演示咨询表单</h2><p>请使用虚构资料测试,不要提交真实个人信息。</p>"), ("form", {"form": form})])
|
||||
elif not isinstance(home.get_children().get(slug="contact").specific, ContactPage):
|
||||
raise ValidationError("演示联系页面类型冲突。")
|
||||
return created
|
||||
@@ -0,0 +1,15 @@
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
from apps.core.industry_templates import seed_industry_templates
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "创建已配置行业的独立演示模板;只补齐缺失内容,不修改默认站点。"
|
||||
|
||||
def handle(self, *args, **options):
|
||||
try:
|
||||
created = seed_industry_templates()
|
||||
except ValidationError as error:
|
||||
raise CommandError(";".join(error.messages)) from error
|
||||
self.stdout.write(self.style.SUCCESS(f"行业演示模板已就绪,新增 {len(created)} 个页面。"))
|
||||
@@ -0,0 +1,96 @@
|
||||
import pytest
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.test import Client
|
||||
from wagtail.models import Page, Site
|
||||
|
||||
from apps.forms.models import FormDefinition
|
||||
from .industry_templates import INDUSTRIES, demo_root, seed_industry_templates
|
||||
from .models import NavigationMenu
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_industry_templates_are_independent_and_non_overwriting():
|
||||
site = Site.objects.get(is_default_site=True)
|
||||
original_root = site.root_page_id
|
||||
original_pages = list(Page.objects.values_list("pk", "title", "latest_revision_id"))
|
||||
original_menus = list(NavigationMenu.objects.values_list("pk", "name"))
|
||||
created = seed_industry_templates()
|
||||
assert len(created) == 13 * len(INDUSTRIES)
|
||||
assert len(set(demo_root(slug).pk for slug in INDUSTRIES)) == len(INDUSTRIES)
|
||||
for slug in INDUSTRIES:
|
||||
assert demo_root(slug).get_descendants().count() == 12
|
||||
home = demo_root("manufacturing")
|
||||
home.title = "人工编辑制造演示"
|
||||
home.save()
|
||||
revision = home.save_revision()
|
||||
assert seed_industry_templates() == []
|
||||
home.refresh_from_db()
|
||||
assert home.title == "人工编辑制造演示"
|
||||
assert home.latest_revision_id == revision.pk
|
||||
site.refresh_from_db()
|
||||
assert site.root_page_id == original_root
|
||||
assert list(Page.objects.filter(pk__in=[p[0] for p in original_pages]).values_list("pk", "title", "latest_revision_id")) == original_pages
|
||||
assert list(NavigationMenu.objects.values_list("pk", "name")) == original_menus
|
||||
assert FormDefinition.objects.filter(name__contains="演示咨询").count() == len(INDUSTRIES)
|
||||
assert not FormDefinition.objects.filter(name__contains="演示咨询").exclude(notification_email="").exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_industry_api_is_scoped_and_serializes_blocks():
|
||||
seed_industry_templates()
|
||||
client = Client()
|
||||
listing = client.get("/api/v1/custom/core/demos/").json()
|
||||
assert len(listing["items"]) == len(INDUSTRIES)
|
||||
for slug, spec in INDUSTRIES.items():
|
||||
result = client.get(f"/api/v1/custom/core/demos/{slug}/").json()
|
||||
assert result["company"] == spec["company"]
|
||||
assert result["blocks"][0]["type"] == "hero"
|
||||
assert all(link["url"].startswith(f"/demo/{slug}/") for link in result["navigation"])
|
||||
product_slug = spec["products"][0][0]
|
||||
detail = client.get(f"/api/v1/custom/core/demos/{slug}/", {"path": f"products/{product_slug}"})
|
||||
assert detail.status_code == 200
|
||||
assert detail.json()["title"] == spec["products"][0][1]
|
||||
contact = client.get(f"/api/v1/custom/core/demos/{slug}/", {"path": "contact"}).json()
|
||||
form = next(block for block in contact["blocks"] if block["type"] == "form")["value"]["form"]
|
||||
assert len(form["fields"]) == 5
|
||||
assert client.get("/api/v1/custom/core/demos/unknown/").status_code == 404
|
||||
for path in ["../demo-software", "products/content-platform", "products//missing", "missing"]:
|
||||
assert client.get("/api/v1/custom/core/demos/manufacturing/", {"path": path}).status_code == 404
|
||||
product = demo_root("software").get_children().get(slug="products").get_children().first().specific
|
||||
product.unpublish()
|
||||
assert client.get("/api/v1/custom/core/demos/software/", {"path": f"products/{product.slug}"}).status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_industry_initialization_rolls_back_on_conflict():
|
||||
root = Page.get_first_root_node()
|
||||
root.add_child(instance=Page(title="冲突页面", slug="demo-manufacturing"))
|
||||
before = Page.objects.count()
|
||||
with pytest.raises(ValidationError):
|
||||
seed_industry_templates()
|
||||
assert Page.objects.count() == before
|
||||
assert demo_root("software") is None
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize("existing_slugs", [
|
||||
("software", "manufacturing", "consulting"),
|
||||
("software", "manufacturing", "consulting", "education", "healthcare", "dining"),
|
||||
("software", "manufacturing", "consulting", "education", "healthcare", "dining",
|
||||
"construction", "real-estate", "home-renovation"),
|
||||
("software", "manufacturing", "consulting", "education", "healthcare", "dining",
|
||||
"construction", "real-estate", "home-renovation", "agri-food", "energy-environment", "auto-services"),
|
||||
("software", "manufacturing", "consulting", "education", "healthcare", "dining",
|
||||
"construction", "real-estate", "home-renovation", "agri-food", "energy-environment", "auto-services",
|
||||
"culture-media", "sports-fitness", "pet-services"),
|
||||
])
|
||||
def test_adding_new_industries_preserves_existing_demos(monkeypatch, existing_slugs):
|
||||
existing = {slug: spec for slug, spec in INDUSTRIES.items() if slug in existing_slugs}
|
||||
with monkeypatch.context() as patch:
|
||||
patch.setattr("apps.core.industry_templates.INDUSTRIES", existing)
|
||||
seed_industry_templates()
|
||||
before = list(Page.objects.order_by("pk").values_list("pk", "title", "latest_revision_id"))
|
||||
assert len(seed_industry_templates()) == 13 * (len(INDUSTRIES) - len(existing))
|
||||
assert list(Page.objects.filter(pk__in=[row[0] for row in before]).order_by("pk").values_list("pk", "title", "latest_revision_id")) == before
|
||||
for slug in INDUSTRIES.keys() - existing.keys():
|
||||
assert demo_root(slug).get_descendants().count() == 12
|
||||
@@ -1,6 +1,7 @@
|
||||
from django.urls import path
|
||||
|
||||
from .api import (
|
||||
IndustryDemoView,
|
||||
NavigationMenuView,
|
||||
PartnerListView,
|
||||
SiteSettingsView,
|
||||
@@ -9,6 +10,8 @@ from .api import (
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
path("demos/", IndustryDemoView.as_view(), name="industry-demo-list"),
|
||||
path("demos/<slug:industry>/", IndustryDemoView.as_view(), name="industry-demo"),
|
||||
path("team/", TeamMemberListView.as_view(), name="team-list"),
|
||||
path("testimonials/", TestimonialListView.as_view(), name="testimonial-list"),
|
||||
path("partners/", PartnerListView.as_view(), name="partner-list"),
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
"""本机 Docker 行业演示 HTTP 验收,不改变默认官网。"""
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
if os.environ.get("DJANGO_SETTINGS_MODULE") != "wagtailcms.settings.docker_local":
|
||||
raise SystemExit("仅允许在 docker_local 隔离环境执行。")
|
||||
|
||||
import django
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
django.setup()
|
||||
|
||||
from wagtail.models import Page, Site
|
||||
from apps.core.industry_templates import INDUSTRIES, seed_industry_templates
|
||||
from apps.core.models import NavigationMenu, SiteSettings
|
||||
from apps.forms.models import Lead
|
||||
|
||||
|
||||
def require(condition, message):
|
||||
if not condition:
|
||||
raise AssertionError(message)
|
||||
|
||||
|
||||
def main():
|
||||
session = requests.Session()
|
||||
backend = "http://localhost:8000/api/v1/custom/core/demos/"
|
||||
frontend = "http://frontend:3000"
|
||||
site = Site.objects.get(is_default_site=True)
|
||||
original_root = site.root_page_id
|
||||
settings = list(SiteSettings.objects.values())
|
||||
menus = list(NavigationMenu.objects.values())
|
||||
page_state = list(Page.objects.order_by("pk").values_list("pk", "title", "live", "latest_revision_id"))
|
||||
require(seed_industry_templates() == [], "重复初始化仍新增页面")
|
||||
require(page_state == list(Page.objects.order_by("pk").values_list("pk", "title", "live", "latest_revision_id")), "初始化改变页面或修订")
|
||||
site.refresh_from_db()
|
||||
require(site.root_page_id == original_root, "默认官网根页面发生变化")
|
||||
require(list(SiteSettings.objects.values()) == settings, "默认企业配置发生变化")
|
||||
require(list(NavigationMenu.objects.values()) == menus, "原有导航被修改")
|
||||
print("PASS: 初始化幂等、页面修订及默认官网配置保留")
|
||||
listing = session.get(backend, timeout=30)
|
||||
listing.raise_for_status()
|
||||
require(len(listing.json()["items"]) == len(INDUSTRIES), "演示模板未全部初始化")
|
||||
response = session.get(f"{frontend}/demo", timeout=30)
|
||||
require(response.status_code == 200, "演示入口不可用")
|
||||
for industry, spec in INDUSTRIES.items():
|
||||
base = f"/demo/{industry}"
|
||||
home = session.get(f"{backend}{industry}/", timeout=30)
|
||||
home.raise_for_status()
|
||||
data = home.json()
|
||||
require(data["company"] == spec["company"], "行业企业资料串用")
|
||||
paths = ["", "products", "solutions", "cases", "blog", "about", "contact", "privacy-policy",
|
||||
*[f"products/{product[0]}" for product in spec["products"]],
|
||||
f"solutions/{spec['solution'][0]}", "cases/example-project", "blog/project-checklist"]
|
||||
for path in paths:
|
||||
time.sleep(3)
|
||||
response = session.get(f"{frontend}{base}/{path}", timeout=30)
|
||||
require(response.status_code == 200, f"页面失败:{industry}/{path} HTTP {response.status_code}")
|
||||
soup = BeautifulSoup(response.text, "html.parser")
|
||||
require(spec["company"] in soup.header.get_text(), f"页头企业串用:{industry}/{path}")
|
||||
nav = soup.find("nav", {"aria-label": "行业导航"})
|
||||
require(nav is not None and all(a["href"].rstrip("/") == base or a["href"].startswith(base + "/") for a in nav.find_all("a")), "行业导航越界")
|
||||
require("虚构" in response.text, "缺少演示标注")
|
||||
require(soup.find("meta", {"name": "robots"}) is not None, "缺少演示搜索限制")
|
||||
contact = session.get(f"{backend}{industry}/", params={"path": "contact"}, timeout=30).json()
|
||||
form = next(block["value"]["form"] for block in contact["blocks"] if block["type"] == "form")
|
||||
require(len(form["fields"]) == 5, "表单字段未持久化")
|
||||
marker = f"industry-smoke-{uuid.uuid4().hex}"
|
||||
payload = {
|
||||
"form_id": form["id"], "data": {"name": "虚构测试", "company": spec["company"], "phone": "010-12345678", "email": "demo@example.test", "message": marker},
|
||||
"source_url": f"http://localhost:13000{base}/contact", "website": "",
|
||||
}
|
||||
for attempt in range(3):
|
||||
response = session.post("http://localhost:8000/api/v1/custom/leads/", json=payload, timeout=30)
|
||||
if response.status_code != 429 or attempt == 2:
|
||||
break
|
||||
wait = min(120, max(1, int(response.headers.get("Retry-After", "60"))))
|
||||
print(f"表单限流:遵守 Retry-After 等待 {wait} 秒")
|
||||
time.sleep(wait)
|
||||
require(response.status_code == 201, f"行业表单提交失败:{response.status_code}")
|
||||
require(Lead.objects.filter(form_id=form["id"], data__message=marker).exists(), "行业线索未入库")
|
||||
require(not Lead.objects.get(data__message=marker).form.notification_email, "演示表单配置了外部邮箱")
|
||||
print(f"PASS: {spec['label']} 13 页面、独立导航、演示标注及表单入库")
|
||||
for path in ["/demo/unknown", "/demo/software/missing", "/demo/manufacturing/products/content-platform"]:
|
||||
require(session.get(frontend + path, timeout=30).status_code == 404, f"未知或跨行业路径未拒绝:{path}")
|
||||
print("ALL INDUSTRY HTTP SMOKE CHECKS PASSED")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,89 @@
|
||||
# 行业官网模板演示
|
||||
|
||||
## 本机地址
|
||||
|
||||
- 模板入口:http://localhost:13000/demo
|
||||
- 软件服务:http://localhost:13000/demo/software
|
||||
- 制造业:http://localhost:13000/demo/manufacturing
|
||||
- 管理咨询:http://localhost:13000/demo/consulting
|
||||
- 教育培训:http://localhost:13000/demo/education
|
||||
- 医疗健康:http://localhost:13000/demo/healthcare
|
||||
- 餐饮:http://localhost:13000/demo/dining
|
||||
- 建筑工程:http://localhost:13000/demo/construction
|
||||
- 房地产:http://localhost:13000/demo/real-estate
|
||||
- 家居装修:http://localhost:13000/demo/home-renovation
|
||||
- 农业食品:http://localhost:13000/demo/agri-food
|
||||
- 能源环保:http://localhost:13000/demo/energy-environment
|
||||
- 汽车服务:http://localhost:13000/demo/auto-services
|
||||
- 文化传媒:http://localhost:13000/demo/culture-media
|
||||
- 体育健身:http://localhost:13000/demo/sports-fitness
|
||||
- 宠物服务:http://localhost:13000/demo/pet-services
|
||||
- 法律服务:http://localhost:13000/demo/legal-services
|
||||
- 财税服务:http://localhost:13000/demo/finance-tax
|
||||
- 人力资源:http://localhost:13000/demo/human-resources
|
||||
|
||||
顶部“切换行业”导航切换独立 URL,各行业独立维护内容,不会重置原官网或其他行业内容。
|
||||
|
||||
## 初始化与部署
|
||||
|
||||
在仓库根目录执行:
|
||||
|
||||
1. `docker.exe compose -f docker-compose.local.yml up -d --build --wait`
|
||||
2. `docker.exe compose -f docker-compose.local.yml exec -T backend python manage.py seed_industry_templates`
|
||||
|
||||
若 Windows 系统中的同名 docker 程序无输出,使用 Docker Desktop 的完整程序路径 `C:\Program Files\Docker\Docker\resources\bin\docker.exe`。
|
||||
|
||||
初始化是显式操作,不随普通启动自动创建演示内容。重复执行仅创建缺失页面,不覆盖已有编辑、不重新发布已有草稿、不修改默认 Site 或原有全局导航。页面类型冲突时整体事务回滚。
|
||||
|
||||
本机验证时后端新增代码通过 Compose cp 部署到现有容器,并重启本项目后端;前端镜像已重新构建。若重新创建后端容器,应按上述命令重建后端镜像,确保新代码被打包。复制到容器中的文件不是镜像持久变更。
|
||||
|
||||
## 每行业内容
|
||||
|
||||
每套 13 页面:首页、产品列表与两个产品详情、方案列表与详情、案例列表与详情、文章列表与详情、关于、联系、演示隐私说明。
|
||||
|
||||
- 软件服务:企业内容平台、定制开发、数字化协同。
|
||||
- 制造业:精密零部件、自动化工装、柔性生产。
|
||||
- 管理咨询:战略规划、流程优化、组织成长。
|
||||
- 教育培训:成人技能课程、企业团队培训、团队学习方案;不承诺考试通过、升学或就业结果。
|
||||
- 医疗健康:预约服务指引、公开健康信息、服务协同流程;不提供个体诊断、治疗建议或疗效承诺,案例为流程示例而非患者案例。
|
||||
- 餐饮:季节菜单、活动餐饮、团体用餐;实际供应、价格、过敏原和营业信息须正式上线前核实。
|
||||
- 建筑工程:工程策划、施工协同、工程交付;真实资质、安全要求、承包范围及验收标准须核验,模板不提供施工操作指导。
|
||||
- 房地产:住宅与商业空间展示、看房咨询流程;空间均为虚构,不构成真实房源或投资建议,价格、产权、用途及许可须核验。
|
||||
- 家居装修:室内设计、材料选配、装修协同;报价、施工许可及材料性能须核实,不替代结构安全评估,不作零污染承诺。
|
||||
- 农业食品:农产品展示、食品供应、供货信息协同;许可、标签、过敏原、检测及储运资料须核验,不宣称未经核验的有机认证。
|
||||
- 能源环保:能源管理、环保项目协同、资源管理流程;资质、监测数据及节能减排效果须核验,不作收益、达标或零排放保证。
|
||||
- 汽车服务:车辆保养、到店检查咨询、服务协同;维修资质、配件、费用及质保条件须核实,不提供远程故障诊断或维修操作指导。
|
||||
|
||||
- 文化传媒:品牌内容策划、活动传播、创意制作协同;素材、音乐、肖像及作品授权须核验,不承诺传播量或营销效果。
|
||||
- 体育健身:运动课程、场馆体验、运动参与协同;教练资质、参与要求、费用及退改规则须核验,不提供个体训练处方或医疗建议,不承诺健身效果。
|
||||
- 宠物服务:宠物美容、寄养、服务交接;经营资质、服务条件及费用须核实,不提供诊疗、用药或行为治疗建议。
|
||||
|
||||
- 法律服务:企业法律服务、合同事务、委托协同;执业资质须核验,不构成个案意见或胜诉承诺,演示表单不建立委托关系,也不接收案件材料。
|
||||
- 财税服务:账务协同、税务事务、资料交接;资质与适用政策须核验,不提供个别税务建议或节税保证,不接收财务凭证和银行账户。
|
||||
- 人力资源:招聘协同、人事流程、人才服务;岗位均为虚构,不开放真实简历投递,不承诺录用或薪酬,不接收身份证及员工资料。
|
||||
|
||||
十八行业共 234 页面。已有十五行业环境再次执行初始化命令,仅补齐新增三行业的 39 页面;已有十二、九、六、三行业环境分别补齐 78、117、156、195 页面,均保留原有页面及修订。
|
||||
|
||||
所有企业和案例均为虚构,不代表客户、资质、业绩或承诺。演示页带 noindex 标记,不作为正式官网上线材料。请勿提交真实个人或商业敏感信息。
|
||||
|
||||
## 后台编辑
|
||||
|
||||
在 Wagtail 页面管理中找到根级 `demo-software`、`demo-manufacturing`、`demo-consulting`、`demo-education`、`demo-healthcare`、`demo-dining`、`demo-construction`、`demo-real-estate`、`demo-home-renovation`、`demo-agri-food`、`demo-energy-environment`、`demo-auto-services` 十二个首页内容树。编辑区块后发布,即可在对应演示 URL 查看。演示详情接口只返回发布的公开内容;草稿及受限内容不能通过演示详情接口读取。
|
||||
|
||||
其余六行业根级内容树为 `demo-culture-media`、`demo-sports-fitness`、`demo-pet-services`、`demo-legal-services`、`demo-finance-tax`、`demo-human-resources`,与上述十二棵树一起独立维护。
|
||||
|
||||
行业页头使用独立首页标题,导航从该行业内容树生成,不使用原官网全局菜单。产品、方案、案例及文章列表按各自内容树生成。当前十八行业共享布局与区块组件,使用不同强调色和独立内容,并非十八套完全独立的视觉设计系统。
|
||||
|
||||
每行业有独立演示咨询表单,初始通知邮箱为空,提交会存储在本地线索数据库。不要配置真实外部通知邮箱。停止服务不会删除数据;只有确认不需要本地测试数据时才删除卷。
|
||||
|
||||
医疗健康演示仅供网站内容与流程展示,不作为真实诊疗或预约系统;不要在通用咨询表单中提交病历、诊断或其他健康敏感信息。
|
||||
|
||||
## 验证
|
||||
|
||||
- 后端:`docker.exe compose -f docker-compose.local.yml exec -T backend python -m pytest --ds=wagtailcms.settings.docker_local --create-db -q`
|
||||
- 行业 HTTP:`docker.exe compose -f docker-compose.local.yml exec -T backend python docker/smoke_industries.py`
|
||||
- 前端镜像构建阶段执行 Node 测试与 Next.js 生产构建。
|
||||
|
||||
已完成原生与 Docker PostgreSQL 后端 96 项测试、前端 18 项测试及生产构建。升级回归覆盖从三、六、九、十二、十五行业升级到十八行业且保留已有页面及修订。HTTP 验收覆盖十八行业 234 页面、导航隔离、演示标注、表单字段与入库、初始化幂等性、默认站点与配置保留,以及未知或跨行业路径 404。后端仍有 12 项既有警告。
|
||||
|
||||
验收未覆盖真实浏览器视觉布局、移动端交互和后台预览自动化。演示 HTTP 脚本每次完整运行会创建十八条虚构测试线索,中途失败时已创建线索也会保留。巡检按每页至少 3 秒节流,表单遇到 429 时遵守 Retry-After,未放宽服务端限流策略。
|
||||
Reference in New Issue
Block a user