Files
WonderQ-Project/WonderQ-Admin/alembic/versions/0016_concierge_advisors.py
T
duanshuwen 7ba92f3c5d feat: 新增客片案例与团队共创模块,重构玩法与配置
- 新增客片案例全流程功能,包含前台页面、管理端配置、后端API、数据库迁移与文档
- 新增团队共创详情页面与对应接口
- 重构玩法模块:将静态playData替换为动态API获取,拆分类型定义到playTypes.ts
- 优化vite构建配置与环境变量处理,调整详情页操作栏文案
- 更新全量相关文档与配图,新增客片案例、团队共创API文档
- 新增测试用例,完善数据归一化逻辑
- 调整路由配置与环境变量示例文件
2026-08-19 20:23:04 +08:00

119 lines
4.2 KiB
Python

"""Create the concierge advisor domain.
Revision ID: 0016_concierge_advisors
Revises: 0015_wanfa
"""
from datetime import datetime
from alembic import context, op
import sqlalchemy as sa
from sqlalchemy import inspect
revision = "0016_concierge_advisors"
down_revision = "0015_wanfa"
branch_labels = None
depends_on = None
SEED_ADVISORS = (
{
"id": "advisor-amanda",
"avatar": "https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&fm=jpg&q=80&w=240&h=240",
"name": "Amanda",
"role": "SENIOR TRAVEL ADVISOR",
"details": [
{"icon": "calendar", "label": "服务经验:8年"},
{"icon": "navigate", "label": "擅长领域:瀑降 / 探洞"},
],
"qrImage": "https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=WonderQ%20Concierge",
"isActive": True,
"sortOrder": 0,
},
{
"id": "advisor-mia",
"avatar": "https://images.unsplash.com/photo-1531123897727-8f129e1688ce?auto=format&fit=crop&fm=jpg&q=80&w=240&h=240",
"name": "Mia",
"role": "LIFESTYLE TRAVEL ADVISOR",
"details": [
{"icon": "calendar", "label": "服务经验:6年"},
{"icon": "navigate", "label": "擅长领域:溯溪 / 人文探索"},
],
"qrImage": "https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=WonderQ%20Concierge",
"isActive": True,
"sortOrder": 1,
},
{
"id": "advisor-leo",
"avatar": "https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=crop&fm=jpg&q=80&w=240&h=240",
"name": "Leo",
"role": "WILD TRAVEL ADVISOR",
"details": [
{"icon": "calendar", "label": "服务经验:9年"},
{"icon": "navigate", "label": "擅长领域:洞穴探险 / 山地徒步"},
],
"qrImage": "https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=WonderQ%20Concierge",
"isActive": True,
"sortOrder": 2,
},
)
def _seed_advisors() -> None:
table = sa.table(
"ConciergeAdvisor",
sa.column("id", sa.String()),
sa.column("avatar", sa.String()),
sa.column("name", sa.String()),
sa.column("role", sa.String()),
sa.column("details", sa.JSON()),
sa.column("qrImage", sa.String()),
sa.column("isActive", sa.Boolean()),
sa.column("sortOrder", sa.Integer()),
sa.column("createdAt", sa.DateTime()),
sa.column("updatedAt", sa.DateTime()),
)
timestamp = datetime(2026, 1, 1)
op.bulk_insert(
table,
[{**advisor, "createdAt": timestamp, "updatedAt": timestamp} for advisor in SEED_ADVISORS],
)
def upgrade() -> None:
bind = op.get_bind()
existing_tables = set(inspect(bind).get_table_names())
if "ConciergeAdvisor" not in existing_tables:
op.create_table(
"ConciergeAdvisor",
sa.Column("id", sa.String(), nullable=False),
sa.Column("avatar", sa.String(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.Column("role", sa.String(), nullable=False),
sa.Column("details", sa.JSON(), nullable=False),
sa.Column("qrImage", sa.String(), nullable=False),
sa.Column("isActive", sa.Boolean(), nullable=False, server_default=sa.true()),
sa.Column("sortOrder", sa.Integer(), nullable=False, server_default="0"),
sa.Column("createdAt", sa.DateTime(), nullable=False),
sa.Column("updatedAt", sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint("id"),
)
op.create_index("ix_ConciergeAdvisor_isActive", "ConciergeAdvisor", ["isActive"])
op.create_index("ix_ConciergeAdvisor_sortOrder", "ConciergeAdvisor", ["sortOrder"])
if context.is_offline_mode():
_seed_advisors()
return
advisor_table = sa.table("ConciergeAdvisor", sa.column("id", sa.String()))
if bind.execute(sa.select(advisor_table.c.id).limit(1)).first() is None:
_seed_advisors()
def downgrade() -> None:
bind = op.get_bind()
existing_tables = set(inspect(bind).get_table_names())
if "ConciergeAdvisor" in existing_tables:
op.drop_table("ConciergeAdvisor")