Initial commit: hulai admin API backend
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
@@ -0,0 +1,28 @@
|
||||
from app.models.user import AdminUser
|
||||
from app.models.brand import Brand, BrandTrustStat, BrandDifferentiator
|
||||
from app.models.product import ProductConfig, ProductVersion, ProductVersionHighlight, SummerCamp, SummerCampPrinciple, SummerCampActivity, SummerCampItinerary, SummerCampFaq, SelectionGuide
|
||||
from app.models.faq import FaqCategory, FaqQuestion
|
||||
from app.models.review import ReviewSummary, Review
|
||||
from app.models.about import AboutStory, AboutSubsidiary, AboutCertification, AboutTrademark, AboutCopyright, AboutGuarantee, AboutXiaohongshu, AboutTeam, AboutCulture, AboutCultureValue
|
||||
from app.models.contact import ContactChannel, ContactConfig
|
||||
from app.models.seo import SeoPage
|
||||
from app.models.navigation import NavHeader, NavFooterGroup, NavFooterLink
|
||||
from app.models.version import VersionConfig, VersionUpgrade, VersionHighlight, VersionCompare, VersionTimeline, VersionTimelineChange, VersionPhilosophy
|
||||
from app.models.guide import GuideConfig, GuideSection
|
||||
from app.models.site_image import SiteImage
|
||||
from app.models.export_log import ExportLog
|
||||
from app.models.seasonal_product import SeasonalProductConfig, SeasonalProductVersion, SeasonalProductHighlight, SeasonalProductTimeline
|
||||
from app.models.winter_camp import WinterCampConfig, WinterCampHotel, WinterCampItinerary, WinterCampFaq
|
||||
from app.models.destination import DestinationConfig, DestinationItem, DestinationDimension, DestinationHonestItem
|
||||
from app.models.blog import Blog
|
||||
from app.models.story import Story
|
||||
from app.models.news import News
|
||||
from app.models.pricing import PricingItem
|
||||
from app.models.qualification import Qualification
|
||||
from app.models.partner import Partner
|
||||
from app.models.calendar import CalendarMonth
|
||||
from app.models.destination_detail import DestinationDetail
|
||||
from app.models.gallery import GalleryItem
|
||||
from app.models.courses import CoursesConfig
|
||||
from app.models.selector import SelectorConfig
|
||||
from app.models.customize_submission import CustomizeSubmission
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
from __future__ import annotations
|
||||
from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime, ForeignKey, JSON
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class AboutStory(Base):
|
||||
__tablename__ = "about_story"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
title = Column(String(200))
|
||||
content = Column(Text)
|
||||
founding_moment = Column(String(500))
|
||||
totem_description = Column(Text)
|
||||
totem_tagline = Column(String(500))
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
|
||||
|
||||
class AboutSubsidiary(Base):
|
||||
__tablename__ = "about_subsidiaries"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String(255), nullable=False)
|
||||
role = Column(Text)
|
||||
established = Column(String(20))
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
|
||||
class AboutCertification(Base):
|
||||
__tablename__ = "about_certifications"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
title = Column(String(255), nullable=False)
|
||||
detail = Column(String(500))
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
|
||||
class AboutTrademark(Base):
|
||||
__tablename__ = "about_trademark"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String(100))
|
||||
scope = Column(String(100))
|
||||
holder = Column(String(255))
|
||||
description = Column(Text)
|
||||
|
||||
|
||||
class AboutCopyright(Base):
|
||||
__tablename__ = "about_copyrights"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String(200), nullable=False)
|
||||
reg_no = Column(String(100))
|
||||
category = Column(String(100))
|
||||
holder = Column(String(255))
|
||||
date = Column(String(20))
|
||||
description = Column(Text)
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
|
||||
class AboutGuarantee(Base):
|
||||
__tablename__ = "about_guarantees"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
title = Column(String(255), nullable=False)
|
||||
detail = Column(Text)
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
|
||||
class AboutXiaohongshu(Base):
|
||||
__tablename__ = "about_xiaohongshu"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
account = Column(String(200))
|
||||
verified = Column(Boolean, default=False)
|
||||
verified_type = Column(String(200))
|
||||
followers = Column(String(50))
|
||||
likes = Column(String(50))
|
||||
awards = Column(JSON)
|
||||
tagline = Column(String(500))
|
||||
tags = Column(JSON)
|
||||
description = Column(Text)
|
||||
|
||||
|
||||
class AboutTeam(Base):
|
||||
__tablename__ = "about_team"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
summary = Column(Text)
|
||||
|
||||
|
||||
class AboutCulture(Base):
|
||||
__tablename__ = "about_culture"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
core = Column(String(200))
|
||||
transparency = Column(Text)
|
||||
trust = Column(String(500))
|
||||
grief_award_name = Column(String(100))
|
||||
grief_award_description = Column(Text)
|
||||
|
||||
values = relationship("AboutCultureValue", back_populates="culture", cascade="all, delete-orphan", order_by="AboutCultureValue.sort_order")
|
||||
|
||||
|
||||
class AboutCultureValue(Base):
|
||||
__tablename__ = "about_culture_values"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
culture_id = Column(Integer, ForeignKey("about_culture.id", ondelete="CASCADE"), nullable=False)
|
||||
name = Column(String(50), nullable=False)
|
||||
expression = Column(Text)
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
culture = relationship("AboutCulture", back_populates="values")
|
||||
|
||||
|
||||
class AboutFounderDetail(Base):
|
||||
"""单个创始人详细介绍(刘涛)"""
|
||||
__tablename__ = "about_founder_detail"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String(100))
|
||||
title = Column(String(200))
|
||||
brand_founded = Column(Integer)
|
||||
years_in_hulunbuir = Column(String(50))
|
||||
background = Column(Text)
|
||||
expertise = Column(JSON) # list of strings
|
||||
media_presence = Column(JSON) # list of strings
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
|
||||
|
||||
class AboutFounder(Base):
|
||||
"""三位创始人简短介绍(列表)"""
|
||||
__tablename__ = "about_founders"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String(100), nullable=False)
|
||||
title = Column(String(200))
|
||||
story = Column(Text)
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
|
||||
class AboutMilestone(Base):
|
||||
"""品牌成长纪事"""
|
||||
__tablename__ = "about_milestones"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
year = Column(Integer, nullable=False)
|
||||
event = Column(Text, nullable=False)
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
|
||||
class AboutServicePrinciple(Base):
|
||||
"""服务原则(列表字符串)"""
|
||||
__tablename__ = "about_service_principles"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
text = Column(Text, nullable=False)
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
|
||||
class AboutDifferentiation(Base):
|
||||
"""差异化竞争力"""
|
||||
__tablename__ = "about_differentiation"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String(100), nullable=False)
|
||||
detail = Column(Text)
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
|
||||
class AboutServiceJourney(Base):
|
||||
"""服务旅程配置(标题+子标题)"""
|
||||
__tablename__ = "about_service_journey"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
title = Column(String(200))
|
||||
subtitle = Column(String(500))
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
|
||||
moments = relationship("AboutServiceMoment", back_populates="journey", cascade="all, delete-orphan", order_by="AboutServiceMoment.sort_order")
|
||||
|
||||
|
||||
class AboutServiceMoment(Base):
|
||||
"""服务触点(8个时刻)"""
|
||||
__tablename__ = "about_service_moments"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
journey_id = Column(Integer, ForeignKey("about_service_journey.id", ondelete="CASCADE"), nullable=False)
|
||||
step = Column(Integer, nullable=False)
|
||||
name = Column(String(100), nullable=False)
|
||||
detail = Column(Text)
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
journey = relationship("AboutServiceJourney", back_populates="moments")
|
||||
|
||||
|
||||
class AboutStats(Base):
|
||||
"""品牌统计数据(单行JSON)"""
|
||||
__tablename__ = "about_stats"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
data = Column(JSON) # flat key-value dict
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
@@ -0,0 +1,24 @@
|
||||
from __future__ import annotations
|
||||
from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime, JSON
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class Blog(Base):
|
||||
__tablename__ = "blog_posts"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
title = Column(String(200), nullable=False)
|
||||
slug = Column(String(200), unique=True, nullable=False)
|
||||
cover_image = Column(String(500))
|
||||
summary = Column(Text)
|
||||
content = Column(Text)
|
||||
author = Column(String(100))
|
||||
category = Column(String(100))
|
||||
tags = Column(JSON)
|
||||
published_at = Column(String(50))
|
||||
is_visible = Column(Boolean, default=True)
|
||||
sort_order = Column(Integer, default=0)
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
@@ -0,0 +1,53 @@
|
||||
from __future__ import annotations
|
||||
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, JSON
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class Brand(Base):
|
||||
__tablename__ = "brand"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String(100), nullable=False)
|
||||
full_name = Column(String(255))
|
||||
domain = Column(String(100))
|
||||
url = Column(String(255))
|
||||
slogan_emotional = Column(String(255))
|
||||
slogan_functional = Column(String(255))
|
||||
icp_entity = Column(String(255))
|
||||
icp = Column(String(100))
|
||||
icp_url = Column(String(255))
|
||||
e_contract = Column(String(255))
|
||||
cta_buttons = Column(JSON) # {primary, secondary, pricing, customize, contact}
|
||||
conversion_path = Column(JSON) # list of {step, label, to}
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
|
||||
trust_stats = relationship("BrandTrustStat", back_populates="brand", cascade="all, delete-orphan", order_by="BrandTrustStat.sort_order")
|
||||
differentiators = relationship("BrandDifferentiator", back_populates="brand", cascade="all, delete-orphan", order_by="BrandDifferentiator.sort_order")
|
||||
|
||||
|
||||
class BrandTrustStat(Base):
|
||||
__tablename__ = "brand_trust_stats"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
brand_id = Column(Integer, ForeignKey("brand.id", ondelete="CASCADE"), nullable=False)
|
||||
value = Column(String(50), nullable=False)
|
||||
unit = Column(String(20))
|
||||
label = Column(String(100), nullable=False)
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
brand = relationship("Brand", back_populates="trust_stats")
|
||||
|
||||
|
||||
class BrandDifferentiator(Base):
|
||||
__tablename__ = "brand_differentiators"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
brand_id = Column(Integer, ForeignKey("brand.id", ondelete="CASCADE"), nullable=False)
|
||||
title = Column(String(100), nullable=False)
|
||||
description = Column(Text)
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
brand = relationship("Brand", back_populates="differentiators")
|
||||
@@ -0,0 +1,21 @@
|
||||
from __future__ import annotations
|
||||
from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime, JSON, UniqueConstraint
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class CalendarMonth(Base):
|
||||
__tablename__ = "calendar_months"
|
||||
__table_args__ = (UniqueConstraint("year", "month", name="uq_calendar_year_month"),)
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
year = Column(Integer, nullable=False)
|
||||
month = Column(Integer, nullable=False) # 1-12
|
||||
title = Column(String(100))
|
||||
description = Column(Text)
|
||||
weather = Column(String(200))
|
||||
highlights = Column(JSON) # list of strings
|
||||
events = Column(JSON) # list of {date, title, description}
|
||||
is_available = Column(Boolean, default=True)
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
@@ -0,0 +1,24 @@
|
||||
from __future__ import annotations
|
||||
from sqlalchemy import Column, Integer, String, Text, Boolean
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class ContactChannel(Base):
|
||||
__tablename__ = "contact_channels"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
type = Column(String(50), nullable=False)
|
||||
label = Column(String(100), nullable=False)
|
||||
value = Column(String(500))
|
||||
qr_image = Column(String(500))
|
||||
is_primary = Column(Boolean, default=False)
|
||||
description = Column(Text)
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
|
||||
class ContactConfig(Base):
|
||||
__tablename__ = "contact_config"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
security_notice = Column(Text)
|
||||
@@ -0,0 +1,15 @@
|
||||
from __future__ import annotations
|
||||
from sqlalchemy import Column, Integer, JSON, DateTime
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class CoursesConfig(Base):
|
||||
__tablename__ = "courses_config"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
modules = Column(JSON) # list of course modules
|
||||
age_groups = Column(JSON) # list of age group configs
|
||||
faqs = Column(JSON) # list of {question, answer}
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
@@ -0,0 +1,19 @@
|
||||
from __future__ import annotations
|
||||
from sqlalchemy import Column, Integer, JSON, DateTime
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class CustomizeConfig(Base):
|
||||
"""定制表单页面配置(选项列表、流程步骤、联系方式等)"""
|
||||
__tablename__ = "customize_config"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
trust_stats = Column(JSON) # [{value, unit, label}]
|
||||
durations = Column(JSON) # [{value, label}]
|
||||
activities = Column(JSON) # [{value, label}]
|
||||
budgets = Column(JSON) # [{value, label}]
|
||||
process = Column(JSON) # [{title, desc}]
|
||||
contact = Column(JSON) # {wechat, tip}
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
@@ -0,0 +1,24 @@
|
||||
from __future__ import annotations
|
||||
from sqlalchemy import Column, Integer, String, Text, DateTime, JSON, Enum
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class CustomizeSubmission(Base):
|
||||
__tablename__ = "customize_submissions"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String(100))
|
||||
phone = Column(String(50))
|
||||
wechat = Column(String(100))
|
||||
adults = Column(Integer, default=0)
|
||||
children = Column(Integer, default=0)
|
||||
travel_dates = Column(String(200))
|
||||
budget = Column(String(100))
|
||||
interests = Column(JSON) # list of interest tags
|
||||
notes = Column(Text)
|
||||
source = Column(String(100)) # 来源页面
|
||||
status = Column(Enum("pending", "processed"), default="pending")
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
@@ -0,0 +1,50 @@
|
||||
"""Destination comparison (目的地对比) data model."""
|
||||
from __future__ import annotations
|
||||
from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime, JSON
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class DestinationConfig(Base):
|
||||
__tablename__ = "destination_config"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
title = Column(String(200))
|
||||
subtitle = Column(String(200))
|
||||
intro = Column(Text)
|
||||
closing_title = Column(String(200))
|
||||
closing_text = Column(Text)
|
||||
data_sources = Column(Text)
|
||||
honest_title = Column(String(200))
|
||||
honest_subtitle = Column(String(200))
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
|
||||
|
||||
class DestinationItem(Base):
|
||||
__tablename__ = "destination_items"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
dest_id = Column(String(50), nullable=False)
|
||||
name = Column(String(100), nullable=False)
|
||||
tag = Column(String(200))
|
||||
highlight = Column(Boolean, default=False)
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
|
||||
class DestinationDimension(Base):
|
||||
__tablename__ = "destination_dimensions"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
label = Column(String(100), nullable=False)
|
||||
icon = Column(String(10))
|
||||
values = Column(JSON) # list of 3 strings, one per destination
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
|
||||
class DestinationHonestItem(Base):
|
||||
__tablename__ = "destination_honest_items"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
text = Column(Text, nullable=False)
|
||||
sort_order = Column(Integer, default=0)
|
||||
@@ -0,0 +1,26 @@
|
||||
from __future__ import annotations
|
||||
from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime, JSON
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class DestinationDetail(Base):
|
||||
__tablename__ = "destination_details"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String(200), nullable=False)
|
||||
slug = Column(String(200), unique=True, nullable=False)
|
||||
subtitle = Column(String(500))
|
||||
cover_image = Column(String(500))
|
||||
description = Column(Text)
|
||||
location = Column(String(200))
|
||||
best_season = Column(String(200))
|
||||
duration = Column(String(100))
|
||||
highlights = Column(JSON) # list of {icon, title, description}
|
||||
gallery = Column(JSON) # list of image URLs
|
||||
tags = Column(JSON) # list of strings
|
||||
is_visible = Column(Boolean, default=True)
|
||||
sort_order = Column(Integer, default=0)
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
@@ -0,0 +1,16 @@
|
||||
from __future__ import annotations
|
||||
from sqlalchemy import Column, Integer, String, Text, DateTime, JSON, Enum
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class ExportLog(Base):
|
||||
__tablename__ = "export_log"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
exported_by = Column(String(50))
|
||||
modules = Column(JSON)
|
||||
status = Column(Enum("success", "failed"), default="success")
|
||||
message = Column(Text)
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
@@ -0,0 +1,30 @@
|
||||
from __future__ import annotations
|
||||
from sqlalchemy import Column, Integer, String, Text, ForeignKey, JSON
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class FaqCategory(Base):
|
||||
__tablename__ = "faq_categories"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
category_id = Column(String(50), unique=True, nullable=False)
|
||||
name = Column(String(100), nullable=False)
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
questions = relationship("FaqQuestion", back_populates="category", cascade="all, delete-orphan", order_by="FaqQuestion.sort_order")
|
||||
|
||||
|
||||
class FaqQuestion(Base):
|
||||
__tablename__ = "faq_questions"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
category_id = Column(Integer, ForeignKey("faq_categories.id", ondelete="CASCADE"), nullable=False)
|
||||
question_id = Column(String(50), unique=True, nullable=False)
|
||||
question = Column(Text, nullable=False)
|
||||
answer = Column(Text, nullable=False)
|
||||
related_links = Column(JSON)
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
category = relationship("FaqCategory", back_populates="questions")
|
||||
@@ -0,0 +1,21 @@
|
||||
from __future__ import annotations
|
||||
from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime, JSON
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class GalleryItem(Base):
|
||||
__tablename__ = "gallery_items"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
title = Column(String(200))
|
||||
image = Column(String(500), nullable=False)
|
||||
photographer = Column(String(100))
|
||||
location = Column(String(200))
|
||||
description = Column(Text)
|
||||
tags = Column(JSON) # list of strings
|
||||
is_visible = Column(Boolean, default=True)
|
||||
sort_order = Column(Integer, default=0)
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
@@ -0,0 +1,34 @@
|
||||
from __future__ import annotations
|
||||
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, JSON
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class GuideConfig(Base):
|
||||
__tablename__ = "guide_config"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
page_intro = Column(Text)
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
|
||||
sections = relationship("GuideSection", back_populates="config", cascade="all, delete-orphan", order_by="GuideSection.sort_order")
|
||||
|
||||
|
||||
class GuideSection(Base):
|
||||
"""Each section has unique nested structure, stored as JSON columns."""
|
||||
__tablename__ = "guide_sections"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
config_id = Column(Integer, ForeignKey("guide_config.id", ondelete="CASCADE"), nullable=False)
|
||||
section_id = Column(String(50), unique=True, nullable=False)
|
||||
title = Column(String(200))
|
||||
subtitle = Column(String(500))
|
||||
icon = Column(String(10))
|
||||
content = Column(Text)
|
||||
# Flexible JSON columns for varied section structures
|
||||
data = Column(JSON)
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
config = relationship("GuideConfig", back_populates="sections")
|
||||
@@ -0,0 +1,37 @@
|
||||
from __future__ import annotations
|
||||
from sqlalchemy import Column, Integer, String, ForeignKey, JSON
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class NavHeader(Base):
|
||||
__tablename__ = "nav_header"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
text = Column(String(100), nullable=False)
|
||||
to_path = Column(String(255), nullable=False)
|
||||
children = Column(JSON) # [{text, to, season}, ...] for dropdown menus
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
|
||||
class NavFooterGroup(Base):
|
||||
__tablename__ = "nav_footer_groups"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
title = Column(String(100), nullable=False)
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
links = relationship("NavFooterLink", back_populates="group", cascade="all, delete-orphan", order_by="NavFooterLink.sort_order")
|
||||
|
||||
|
||||
class NavFooterLink(Base):
|
||||
__tablename__ = "nav_footer_links"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
group_id = Column(Integer, ForeignKey("nav_footer_groups.id", ondelete="CASCADE"), nullable=False)
|
||||
text = Column(String(100), nullable=False)
|
||||
to_path = Column(String(255), nullable=False)
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
group = relationship("NavFooterGroup", back_populates="links")
|
||||
@@ -0,0 +1,22 @@
|
||||
from __future__ import annotations
|
||||
from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class News(Base):
|
||||
__tablename__ = "news"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
title = Column(String(200), nullable=False)
|
||||
cover_image = Column(String(500))
|
||||
summary = Column(Text)
|
||||
content = Column(Text)
|
||||
source = Column(String(100))
|
||||
source_url = Column(String(500))
|
||||
published_at = Column(String(50))
|
||||
is_visible = Column(Boolean, default=True)
|
||||
sort_order = Column(Integer, default=0)
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
@@ -0,0 +1,18 @@
|
||||
from __future__ import annotations
|
||||
from sqlalchemy import Column, Integer, String, Text, DateTime
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class Partner(Base):
|
||||
__tablename__ = "partners"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String(200), nullable=False)
|
||||
logo = Column(String(500))
|
||||
website = Column(String(500))
|
||||
description = Column(Text)
|
||||
category = Column(String(100))
|
||||
sort_order = Column(Integer, default=0)
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
@@ -0,0 +1,22 @@
|
||||
from __future__ import annotations
|
||||
from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime, JSON
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class PricingItem(Base):
|
||||
__tablename__ = "pricing_items"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
product_name = Column(String(200), nullable=False)
|
||||
product_slug = Column(String(100))
|
||||
price_from = Column(String(50))
|
||||
price_unit = Column(String(50))
|
||||
price_label = Column(String(100))
|
||||
description = Column(Text)
|
||||
features = Column(JSON)
|
||||
notes = Column(JSON)
|
||||
is_visible = Column(Boolean, default=True)
|
||||
sort_order = Column(Integer, default=0)
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
@@ -0,0 +1,125 @@
|
||||
from __future__ import annotations
|
||||
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, JSON
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class ProductConfig(Base):
|
||||
__tablename__ = "product_config"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
narrative = Column(Text)
|
||||
pricing_philosophy = Column(Text)
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
|
||||
versions = relationship("ProductVersion", back_populates="config", cascade="all, delete-orphan", order_by="ProductVersion.sort_order")
|
||||
summer_camp = relationship("SummerCamp", back_populates="config", uselist=False, cascade="all, delete-orphan")
|
||||
selection_guide = relationship("SelectionGuide", back_populates="config", uselist=False, cascade="all, delete-orphan")
|
||||
|
||||
|
||||
class ProductVersion(Base):
|
||||
__tablename__ = "product_versions"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
config_id = Column(Integer, ForeignKey("product_config.id", ondelete="CASCADE"), nullable=False)
|
||||
version_id = Column(String(50), unique=True, nullable=False)
|
||||
name = Column(String(200), nullable=False)
|
||||
days = Column(Integer, nullable=False)
|
||||
nights = Column(Integer, nullable=False)
|
||||
audience = Column(String(255))
|
||||
description = Column(Text)
|
||||
tag = Column(String(50))
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
config = relationship("ProductConfig", back_populates="versions")
|
||||
highlights = relationship("ProductVersionHighlight", back_populates="version", cascade="all, delete-orphan", order_by="ProductVersionHighlight.sort_order")
|
||||
|
||||
|
||||
class ProductVersionHighlight(Base):
|
||||
__tablename__ = "product_version_highlights"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
version_id = Column(Integer, ForeignKey("product_versions.id", ondelete="CASCADE"), nullable=False)
|
||||
text = Column(Text, nullable=False)
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
version = relationship("ProductVersion", back_populates="highlights")
|
||||
|
||||
|
||||
class SummerCamp(Base):
|
||||
__tablename__ = "summer_camp"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
config_id = Column(Integer, ForeignKey("product_config.id", ondelete="CASCADE"), nullable=False)
|
||||
name = Column(String(200))
|
||||
positioning = Column(Text)
|
||||
days = Column(Integer)
|
||||
nights = Column(Integer)
|
||||
sessions_json = Column(JSON)
|
||||
difference_from_v9 = Column(Text)
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
|
||||
config = relationship("ProductConfig", back_populates="summer_camp")
|
||||
principles = relationship("SummerCampPrinciple", back_populates="camp", cascade="all, delete-orphan", order_by="SummerCampPrinciple.sort_order")
|
||||
activities = relationship("SummerCampActivity", back_populates="camp", cascade="all, delete-orphan", order_by="SummerCampActivity.sort_order")
|
||||
itinerary = relationship("SummerCampItinerary", back_populates="camp", cascade="all, delete-orphan", order_by="SummerCampItinerary.sort_order")
|
||||
faq = relationship("SummerCampFaq", back_populates="camp", cascade="all, delete-orphan", order_by="SummerCampFaq.sort_order")
|
||||
|
||||
|
||||
class SummerCampPrinciple(Base):
|
||||
__tablename__ = "summer_camp_principles"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
camp_id = Column(Integer, ForeignKey("summer_camp.id", ondelete="CASCADE"), nullable=False)
|
||||
text = Column(String(255), nullable=False)
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
camp = relationship("SummerCamp", back_populates="principles")
|
||||
|
||||
|
||||
class SummerCampActivity(Base):
|
||||
__tablename__ = "summer_camp_activities"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
camp_id = Column(Integer, ForeignKey("summer_camp.id", ondelete="CASCADE"), nullable=False)
|
||||
text = Column(Text, nullable=False)
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
camp = relationship("SummerCamp", back_populates="activities")
|
||||
|
||||
|
||||
class SummerCampItinerary(Base):
|
||||
__tablename__ = "summer_camp_itinerary"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
camp_id = Column(Integer, ForeignKey("summer_camp.id", ondelete="CASCADE"), nullable=False)
|
||||
text = Column(Text, nullable=False)
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
camp = relationship("SummerCamp", back_populates="itinerary")
|
||||
|
||||
|
||||
class SummerCampFaq(Base):
|
||||
__tablename__ = "summer_camp_faq"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
camp_id = Column(Integer, ForeignKey("summer_camp.id", ondelete="CASCADE"), nullable=False)
|
||||
question = Column(Text, nullable=False)
|
||||
answer = Column(Text, nullable=False)
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
camp = relationship("SummerCamp", back_populates="faq")
|
||||
|
||||
|
||||
class SelectionGuide(Base):
|
||||
__tablename__ = "selection_guide"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
config_id = Column(Integer, ForeignKey("product_config.id", ondelete="CASCADE"), nullable=False)
|
||||
by_vacation_length = Column(JSON)
|
||||
by_child_age = Column(JSON)
|
||||
by_preference = Column(JSON)
|
||||
|
||||
config = relationship("ProductConfig", back_populates="selection_guide")
|
||||
@@ -0,0 +1,19 @@
|
||||
from __future__ import annotations
|
||||
from sqlalchemy import Column, Integer, String, Text, DateTime
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class Qualification(Base):
|
||||
__tablename__ = "qualifications"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
title = Column(String(200), nullable=False)
|
||||
issuer = Column(String(200))
|
||||
year = Column(String(20))
|
||||
image = Column(String(500))
|
||||
description = Column(Text)
|
||||
category = Column(String(100))
|
||||
sort_order = Column(Integer, default=0)
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
@@ -0,0 +1,32 @@
|
||||
from __future__ import annotations
|
||||
from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime, JSON
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class ReviewSummary(Base):
|
||||
__tablename__ = "review_summary"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
total_count = Column(Integer, default=0)
|
||||
approval_rate = Column(String(10))
|
||||
keywords = Column(JSON)
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
|
||||
|
||||
class Review(Base):
|
||||
__tablename__ = "reviews"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
nickname = Column(String(100), nullable=False)
|
||||
travel_date = Column(String(50))
|
||||
product_version = Column(String(200))
|
||||
screenshot = Column(String(500))
|
||||
content = Column(Text, nullable=False)
|
||||
scenes = Column(JSON)
|
||||
concerns = Column(JSON)
|
||||
sort_order = Column(Integer, default=0)
|
||||
is_visible = Column(Boolean, default=True)
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
@@ -0,0 +1,66 @@
|
||||
"""
|
||||
Seasonal products: autumn (游牧的森林) and winter (嗨冰雪).
|
||||
Both share the same table structure, distinguished by `season` field.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from sqlalchemy import Column, Integer, String, Text, DateTime, JSON
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class SeasonalProductConfig(Base):
|
||||
__tablename__ = "seasonal_product_config"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
season = Column(String(20), unique=True, nullable=False) # 'autumn' or 'winter'
|
||||
narrative = Column(Text)
|
||||
style = Column(Text)
|
||||
brand_name = Column(String(100))
|
||||
version_label = Column(String(100))
|
||||
season_label = Column(String(100))
|
||||
selection_guide = Column(JSON) # {byVacationLength: [...]}
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
|
||||
|
||||
class SeasonalProductVersion(Base):
|
||||
__tablename__ = "seasonal_product_versions"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
season = Column(String(20), nullable=False) # 'autumn' or 'winter'
|
||||
version_id = Column(String(50), unique=True, nullable=False)
|
||||
name = Column(String(200), nullable=False)
|
||||
days = Column(Integer, nullable=False)
|
||||
nights = Column(Integer, nullable=False)
|
||||
tag = Column(String(50))
|
||||
line = Column(String(50))
|
||||
route = Column(String(200))
|
||||
audience = Column(String(255))
|
||||
description = Column(Text)
|
||||
highlights = Column(JSON) # list of strings
|
||||
itinerary = Column(JSON) # list of strings
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
|
||||
class SeasonalProductHighlight(Base):
|
||||
__tablename__ = "seasonal_product_highlights"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
season = Column(String(20), nullable=False)
|
||||
category = Column(String(20), nullable=False) # 'shared', 'south', 'north'
|
||||
title = Column(String(200), nullable=False)
|
||||
description = Column(Text)
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
|
||||
class SeasonalProductTimeline(Base):
|
||||
__tablename__ = "seasonal_product_timeline"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
season = Column(String(20), nullable=False)
|
||||
version = Column(String(50))
|
||||
date = Column(String(50))
|
||||
title = Column(String(200))
|
||||
changes = Column(JSON) # list of {type, text}
|
||||
reason = Column(Text)
|
||||
sort_order = Column(Integer, default=0)
|
||||
@@ -0,0 +1,14 @@
|
||||
from __future__ import annotations
|
||||
from sqlalchemy import Column, Integer, JSON, DateTime
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class SelectorConfig(Base):
|
||||
__tablename__ = "selector_config"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
questions = Column(JSON) # list of {id, text, options: [{value, label}]}
|
||||
rules = Column(JSON) # list of {conditions, recommendation}
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
@@ -0,0 +1,19 @@
|
||||
from __future__ import annotations
|
||||
from sqlalchemy import Column, Integer, String, Text, DateTime
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class SeoPage(Base):
|
||||
__tablename__ = "seo_pages"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
page_key = Column(String(50), unique=True, nullable=False)
|
||||
title = Column(String(500))
|
||||
description = Column(Text)
|
||||
h1 = Column(String(500))
|
||||
keywords = Column(Text)
|
||||
og_image = Column(String(500))
|
||||
tldr = Column(Text)
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
@@ -0,0 +1,17 @@
|
||||
from __future__ import annotations
|
||||
from sqlalchemy import Column, Integer, String, Text
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class SiteImage(Base):
|
||||
"""网站图片配置 - 每张图片一条记录,用 group + key 标识"""
|
||||
__tablename__ = "site_images"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
group_name = Column(String(50), nullable=False) # logo, hero, mascot, team, etc.
|
||||
image_key = Column(String(50), nullable=False) # main, background, front, etc.
|
||||
image_path = Column(String(500), nullable=False) # /images/logo.png
|
||||
label = Column(String(100)) # 显示名称:品牌Logo
|
||||
size_hint = Column(String(100)) # 建议尺寸:200x200px
|
||||
description = Column(Text) # 说明
|
||||
@@ -0,0 +1,24 @@
|
||||
from __future__ import annotations
|
||||
from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime, JSON
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class Story(Base):
|
||||
__tablename__ = "stories"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
title = Column(String(200), nullable=False)
|
||||
cover_image = Column(String(500))
|
||||
summary = Column(Text)
|
||||
content = Column(Text)
|
||||
customer_name = Column(String(100))
|
||||
avatar = Column(String(500))
|
||||
travel_date = Column(String(50))
|
||||
product_name = Column(String(200))
|
||||
scenes = Column(JSON)
|
||||
is_visible = Column(Boolean, default=True)
|
||||
sort_order = Column(Integer, default=0)
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
@@ -0,0 +1,18 @@
|
||||
from __future__ import annotations
|
||||
from sqlalchemy import Column, Integer, String, Boolean, DateTime, Enum
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class AdminUser(Base):
|
||||
__tablename__ = "admin_users"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
username = Column(String(50), unique=True, nullable=False)
|
||||
password_hash = Column(String(255), nullable=False)
|
||||
display_name = Column(String(100))
|
||||
role = Column(Enum("admin", "editor"), default="editor")
|
||||
is_active = Column(Boolean, default=True)
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
@@ -0,0 +1,100 @@
|
||||
from __future__ import annotations
|
||||
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, JSON, Enum
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class VersionConfig(Base):
|
||||
__tablename__ = "version_config"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
stats_iterations = Column(Integer)
|
||||
stats_years = Column(Integer)
|
||||
stats_guests = Column(String(50))
|
||||
quote_text = Column(Text)
|
||||
quote_author = Column(String(200))
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
|
||||
upgrades = relationship("VersionUpgrade", back_populates="config", cascade="all, delete-orphan", order_by="VersionUpgrade.sort_order")
|
||||
highlights = relationship("VersionHighlight", back_populates="config", cascade="all, delete-orphan", order_by="VersionHighlight.sort_order")
|
||||
compare = relationship("VersionCompare", back_populates="config", uselist=False, cascade="all, delete-orphan")
|
||||
timeline = relationship("VersionTimeline", back_populates="config", cascade="all, delete-orphan", order_by="VersionTimeline.sort_order")
|
||||
philosophy = relationship("VersionPhilosophy", back_populates="config", cascade="all, delete-orphan", order_by="VersionPhilosophy.sort_order")
|
||||
|
||||
|
||||
class VersionUpgrade(Base):
|
||||
__tablename__ = "version_upgrades"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
config_id = Column(Integer, ForeignKey("version_config.id", ondelete="CASCADE"), nullable=False)
|
||||
tag = Column(String(50))
|
||||
name = Column(String(200))
|
||||
description = Column(Text)
|
||||
reason = Column(Text)
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
config = relationship("VersionConfig", back_populates="upgrades")
|
||||
|
||||
|
||||
class VersionHighlight(Base):
|
||||
__tablename__ = "version_highlights"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
config_id = Column(Integer, ForeignKey("version_config.id", ondelete="CASCADE"), nullable=False)
|
||||
label = Column(String(50))
|
||||
text = Column(Text)
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
config = relationship("VersionConfig", back_populates="highlights")
|
||||
|
||||
|
||||
class VersionCompare(Base):
|
||||
__tablename__ = "version_compare"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
config_id = Column(Integer, ForeignKey("version_config.id", ondelete="CASCADE"), nullable=False)
|
||||
headers = Column(JSON)
|
||||
rows = Column(JSON)
|
||||
|
||||
config = relationship("VersionConfig", back_populates="compare")
|
||||
|
||||
|
||||
class VersionTimeline(Base):
|
||||
__tablename__ = "version_timeline"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
config_id = Column(Integer, ForeignKey("version_config.id", ondelete="CASCADE"), nullable=False)
|
||||
version = Column(String(20), nullable=False)
|
||||
date = Column(String(20))
|
||||
title = Column(String(200))
|
||||
reason = Column(Text)
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
config = relationship("VersionConfig", back_populates="timeline")
|
||||
changes = relationship("VersionTimelineChange", back_populates="timeline", cascade="all, delete-orphan", order_by="VersionTimelineChange.sort_order")
|
||||
|
||||
|
||||
class VersionTimelineChange(Base):
|
||||
__tablename__ = "version_timeline_changes"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
timeline_id = Column(Integer, ForeignKey("version_timeline.id", ondelete="CASCADE"), nullable=False)
|
||||
type = Column(Enum("add", "update", "remove"), nullable=False)
|
||||
text = Column(Text, nullable=False)
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
timeline = relationship("VersionTimeline", back_populates="changes")
|
||||
|
||||
|
||||
class VersionPhilosophy(Base):
|
||||
__tablename__ = "version_philosophy"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
config_id = Column(Integer, ForeignKey("version_config.id", ondelete="CASCADE"), nullable=False)
|
||||
label = Column(String(50))
|
||||
text = Column(Text)
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
config = relationship("VersionConfig", back_populates="philosophy")
|
||||
@@ -0,0 +1,62 @@
|
||||
"""Winter camp (小蒙马冬季亲子营) data model."""
|
||||
from __future__ import annotations
|
||||
from sqlalchemy import Column, Integer, String, Text, DateTime, JSON
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class WinterCampConfig(Base):
|
||||
__tablename__ = "winter_camp_config"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String(200))
|
||||
positioning = Column(Text)
|
||||
days = Column(Integer)
|
||||
nights = Column(Integer)
|
||||
max_families = Column(Integer)
|
||||
total_sessions = Column(Integer)
|
||||
age_range = Column(String(50))
|
||||
deposit = Column(Integer)
|
||||
season = Column(String(100))
|
||||
route = Column(String(200))
|
||||
why_hulunbuir = Column(Text)
|
||||
closing_note = Column(Text)
|
||||
photographer = Column(JSON) # {name, description, specialDay2}
|
||||
winter_clothing = Column(JSON) # {upper, lower, shoes, accessories}
|
||||
camp_advantages = Column(JSON) # list of strings
|
||||
service_config = Column(JSON) # list of strings
|
||||
camp_essentials = Column(JSON) # list of strings
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
|
||||
|
||||
class WinterCampHotel(Base):
|
||||
__tablename__ = "winter_camp_hotels"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String(200), nullable=False)
|
||||
star = Column(String(50))
|
||||
nights = Column(String(50))
|
||||
description = Column(Text)
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
|
||||
class WinterCampItinerary(Base):
|
||||
__tablename__ = "winter_camp_itinerary"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
day = Column(Integer, nullable=False)
|
||||
title = Column(String(200))
|
||||
summary = Column(Text)
|
||||
highlights = Column(JSON) # list of strings
|
||||
hotel = Column(String(200))
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
|
||||
class WinterCampFaq(Base):
|
||||
__tablename__ = "winter_camp_faq"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
question = Column(Text, nullable=False)
|
||||
answer = Column(Text, nullable=False)
|
||||
sort_order = Column(Integer, default=0)
|
||||
在新工单中引用
屏蔽一个用户