修复Vue Admin布局中RouterView未绑定唯一key的问题,解决RBAC系统资源页面切换后内容不刷新的ISSUE-001故障,新增routeViewKey工具函数区分路由实例 完善路由配置,新增admin空路径路由,修复路由命名空路径告警 新增媒体管理、运维发布/重置页面,补全全站路由注册与动态路由过滤逻辑 新增玩法、线索、管家、系统资源管理的全套工具函数、业务组件及单元测试用例 修复后端app/seed.py中媒体资源重复检测未考虑会话内pending资源的bug,新增对应测试验证修复逻辑 修正alembic迁移版本0015的依赖关系,删除废弃的旧迁移文件 优化后端系统接口的字典序列化逻辑,简化重复代码 添加QA验收报告与相关测试截图,完善项目测试覆盖
161 lines
5.0 KiB
Python
161 lines
5.0 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
from sqlalchemy import delete, select
|
|
from sqlalchemy.orm import Session
|
|
from .auth import hash_password
|
|
from .content import DEMAND_FEATURE_CARDS, DEMAND_FORM, DEMAND_HERO, DESTINATION_HERO, HERO_SLIDES, VEHICLE_OPTIONS, VEHICLE_SERVICE
|
|
from .database import Base, SessionLocal, engine
|
|
from .models import (
|
|
AdminUser,
|
|
DemandFeatureCard,
|
|
DemandForm,
|
|
DemandHero,
|
|
DestinationHero,
|
|
HeroSlide,
|
|
MediaAsset,
|
|
SiteVersion,
|
|
VehicleOption,
|
|
VehicleServiceConfig,
|
|
utc_now,
|
|
)
|
|
def create_media(db: Session, url: str | None, group: str, name: str | None = None) -> None:
|
|
if not url:
|
|
return
|
|
media = db.scalar(select(MediaAsset).where(MediaAsset.url == url))
|
|
if media is None:
|
|
media = next((item for item in db.new if isinstance(item, MediaAsset) and item.url == url), None)
|
|
if media:
|
|
media.group = group
|
|
media.name = name
|
|
else:
|
|
db.add(MediaAsset(url=url, group=group, name=name))
|
|
|
|
|
|
def reset_guizhou_content(db: Session) -> dict:
|
|
for model in [SiteVersion, DemandForm, DemandFeatureCard, DemandHero, VehicleOption, VehicleServiceConfig, DestinationHero, HeroSlide, MediaAsset]:
|
|
db.execute(delete(model))
|
|
db.flush()
|
|
|
|
for index, slide in enumerate(HERO_SLIDES):
|
|
create_media(db, slide["image"], "hero", slide["title"])
|
|
db.add(
|
|
HeroSlide(
|
|
title=slide["title"],
|
|
kicker=slide["kicker"],
|
|
actionLabel=slide["action"],
|
|
image=slide["image"],
|
|
sortOrder=index,
|
|
)
|
|
)
|
|
|
|
for index, item in enumerate(DESTINATION_HERO):
|
|
create_media(db, item.get("image"), "destination-hero", item["title"])
|
|
db.add(
|
|
DestinationHero(
|
|
title=item["title"],
|
|
kicker=item.get("kicker"),
|
|
image=item.get("image"),
|
|
sortOrder=index,
|
|
)
|
|
)
|
|
|
|
for index, option in enumerate(VEHICLE_OPTIONS):
|
|
create_media(db, option["image"], "vehicle-option", option["title"])
|
|
db.add(
|
|
VehicleOption(
|
|
title=option["title"],
|
|
description=option.get("description"),
|
|
image=option.get("image"),
|
|
sortOrder=index,
|
|
)
|
|
)
|
|
|
|
db.add(VehicleServiceConfig(**VEHICLE_SERVICE))
|
|
|
|
for index, item in enumerate(DEMAND_HERO):
|
|
db.add(
|
|
DemandHero(
|
|
title=item["title"],
|
|
kicker=item.get("kicker"),
|
|
description=item.get("description"),
|
|
steps=item.get("steps", []),
|
|
sortOrder=index,
|
|
)
|
|
)
|
|
|
|
for index, item in enumerate(DEMAND_FEATURE_CARDS):
|
|
db.add(
|
|
DemandFeatureCard(
|
|
title=item["title"],
|
|
description=item.get("description"),
|
|
sortOrder=index,
|
|
)
|
|
)
|
|
|
|
db.add(DemandForm(**DEMAND_FORM))
|
|
|
|
snapshot = SiteVersion(
|
|
title="guizhou-content-reset",
|
|
status="published",
|
|
publishedAt=utc_now(),
|
|
snapshot={
|
|
"heroSlides": len(HERO_SLIDES),
|
|
"destinationHero": len(DESTINATION_HERO),
|
|
"vehicleOptions": len(VEHICLE_OPTIONS),
|
|
"vehicleService": 1,
|
|
"demandHero": len(DEMAND_HERO),
|
|
"demandFeatureCards": len(DEMAND_FEATURE_CARDS),
|
|
"demandForm": 1,
|
|
},
|
|
)
|
|
db.add(snapshot)
|
|
db.flush()
|
|
return {
|
|
"heroSlides": len(HERO_SLIDES),
|
|
"destinationHero": len(DESTINATION_HERO),
|
|
"vehicleOptions": len(VEHICLE_OPTIONS),
|
|
"vehicleService": 1,
|
|
"demandHero": len(DEMAND_HERO),
|
|
"demandFeatureCards": len(DEMAND_FEATURE_CARDS),
|
|
"demandForm": 1,
|
|
"siteVersionId": snapshot.id,
|
|
}
|
|
|
|
|
|
def seed_database(reset: bool) -> dict:
|
|
Base.metadata.create_all(bind=engine)
|
|
with SessionLocal() as db:
|
|
user = db.scalar(select(AdminUser).where(AdminUser.email == "admin@example.com"))
|
|
if user:
|
|
user.passwordHash = hash_password("ChangeMe123!")
|
|
user.isActive = True
|
|
user.name = "后台管理员"
|
|
user.role = "super_admin"
|
|
else:
|
|
db.add(
|
|
AdminUser(
|
|
email="admin@example.com",
|
|
name="后台管理员",
|
|
passwordHash=hash_password("ChangeMe123!"),
|
|
role="super_admin",
|
|
)
|
|
)
|
|
result = reset_guizhou_content(db) if reset else {"reset": False}
|
|
db.commit()
|
|
return result
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description="Seed WonderQ Admin database")
|
|
parser.add_argument("--no-reset", action="store_true", help="Only ensure admin user exists")
|
|
args = parser.parse_args()
|
|
result = seed_database(reset=not args.no_reset)
|
|
print(f"Seed complete. Admin login: admin@example.com / ChangeMe123!")
|
|
print(json.dumps(result, ensure_ascii=False))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|