refactor: 清理废弃模块并重构管理端与全端代码

- 移除所有废弃的旧首页配置模块,包括destinationHero、demand相关、HomeExperience、vehicleService等表结构与代码,新增数据库迁移删除遗留表
- 重构管理端后台布局为标准RuoYi风格,新增Sidebar、Navbar、SettingsDrawer等组件,替换旧的过渡菜单实现
- 调整移动端响应断点为768px,更新布局相关测试用例,新增布局组件测试
- 删除OperationsTools发布/重置页面,移除/tools菜单入口,清理对应的API调用与测试代码
- 重构玩法模块编辑器:替换旧的图片手动输入框为图片上传组件,移除冗余的取消按钮
- 新增线索查询offset参数支持,完善查询参数处理逻辑
- 优化rbac菜单构建逻辑,新增可见性控制参数
- 修正管家编辑器提示文案,优化系统资源编辑器表单初始化逻辑
- 清理小程序端废弃的体验推荐组件与相关测试代码
- 更新文档与种子脚本,匹配新的API契约与使用说明
This commit is contained in:
duanshuwen committed 2026-08-26 23:24:28 +08:00
1 parent f6c5ad589f
commit fbf2c772a8
63 files changed
+2272 -1991

No files matched your search

+4 -175
View File
@@ -7,7 +7,7 @@ from pydantic import ValidationError
from app.auth import create_customer_token, hash_password, require_admin
from app.database import get_db
from app.main import create_app
from app.models import AdminUser, Customer, DemandFeatureCard, DemandForm, DemandHero, DestinationHero, HeroSlide, Lead, MediaAsset, VehicleOption
from app.models import AdminUser, Customer, HeroSlide, Lead, MediaAsset, VehicleOption
from app.routers import admin as admin_router
from app.routers import public as public_router
from app.routers.shared import site_config
@@ -113,62 +113,6 @@ def make_vehicle_option(**overrides):
)
def make_destination_hero(**overrides):
return DestinationHero(
id=overrides.get("id", "destination-hero-test"),
title=overrides.get("title", "目的地页主视觉"),
kicker=overrides.get("kicker", "贵州小包团目的地"),
image=overrides.get("image", "/assets/destination-hero.jpg"),
sortOrder=overrides.get("sortOrder", 0),
isActive=overrides.get("isActive", True),
createdAt=datetime(2026, 1, 1),
updatedAt=datetime(2026, 1, 2),
)
def make_demand_hero(**overrides):
return DemandHero(
id=overrides.get("id", "demand-hero-test"),
title=overrides.get("title", "告诉我们日期、人数和想法"),
kicker=overrides.get("kicker", "3步定制"),
description=overrides.get("description", "管家会按同行人、预算和体力强度,重新组合酒店、用车和景点节奏。"),
steps=overrides.get("steps", ["提交需求", "管家沟通", "确认方案"]),
sortOrder=overrides.get("sortOrder", 0),
isActive=overrides.get("isActive", True),
createdAt=datetime(2026, 1, 1),
updatedAt=datetime(2026, 1, 2),
)
def make_demand_feature_card(**overrides):
return DemandFeatureCard(
id=overrides.get("id", "demand-feature-card-test"),
title=overrides.get("title", "动线"),
description=overrides.get("description", "按天数顺路排"),
sortOrder=overrides.get("sortOrder", 0),
isActive=overrides.get("isActive", True),
createdAt=datetime(2026, 1, 1),
updatedAt=datetime(2026, 1, 2),
)
def make_demand_form(**overrides):
return DemandForm(
id=overrides.get("id", "demand-form-test"),
destinationLabel=overrides.get("destinationLabel", "目的地/玩法"),
destinationPlaceholder=overrides.get("destinationPlaceholder", "例如:贵州、黄果树、西江苗寨"),
phoneLabel=overrides.get("phoneLabel", "联系方式"),
phonePlaceholder=overrides.get("phonePlaceholder", "手机号 / 微信号"),
noteLabel=overrides.get("noteLabel", "补充说明"),
notePlaceholder=overrides.get("notePlaceholder", "出行日期、人数、酒店偏好、预算范围"),
submitLabel=overrides.get("submitLabel", "提交出行需求"),
chips=overrides.get("chips", ["贵州", "黄果树", "荔波小七孔", "西江苗寨", "梵净山", "万峰林"]),
isActive=overrides.get("isActive", True),
createdAt=datetime(2026, 1, 1),
updatedAt=datetime(2026, 1, 2),
)
def make_admin_user():
return AdminUser(
id="admin-test",
@@ -349,43 +293,10 @@ def test_admin_login_returns_token_and_user():
("module", "payload", "expected"),
[
("heroSlides", {"title": "新轮播", "image": None, "targetType": None}, {"title": "新轮播", "image": None}),
("destinationHero", {"title": "目的地页主视觉", "kicker": "贵州小包团目的地", "image": None}, {"title": "目的地页主视觉", "kicker": "贵州小包团目的地", "image": None}),
("vehicleOptions", {"title": "5座舒适用车", "description": "用车文案", "image": None}, {"title": "5座舒适用车", "description": "用车文案", "image": None}),
(
"demandHero",
{"title": "需求页主视觉", "kicker": "3步定制", "description": "说明", "steps": ["提交", "沟通", "确认"]},
{"title": "需求页主视觉", "kicker": "3步定制", "description": "说明", "steps": ["提交", "沟通", "确认"]},
),
(
"demandFeatureCards",
{"title": "动线", "description": "按天数顺路排"},
{"title": "动线", "description": "按天数顺路排"},
),
(
"demandForm",
{"submitLabel": "提交出行需求", "destinationLabel": "目的地/玩法", "chips": [" 贵州 ", "", "黄果树"]},
{"submitLabel": "提交出行需求", "destinationLabel": "目的地/玩法", "chips": ["贵州", "黄果树"]},
),
(
"vehicleService",
{
"introTitle": "旅游专属用车",
"intro": "提交需求后由管家联系确认。",
"serviceSections": [{"title": "包车", "description": "行程灵活"}],
"advantages": ["车型匹配", "服务省心"],
"processSteps": [{"title": "提交需求", "description": "填写日期和地点"}],
},
{
"introTitle": "旅游专属用车",
"intro": "提交需求后由管家联系确认。",
"serviceSections": [{"title": "包车", "description": "行程灵活"}],
"advantages": ["车型匹配", "服务省心"],
"processSteps": [{"title": "提交需求", "description": "填写日期和地点"}],
},
),
],
)
def test_site_config_create_modules_defaults_fields_and_audits(module, payload, expected):
def test_site_config_create_supported_modules_defaults_fields_and_audits(module, payload, expected):
fake_db = FakeDb(scalar_values=[4])
app = authenticated_app(fake_db)
@@ -399,10 +310,7 @@ def test_site_config_create_modules_defaults_fields_and_audits(module, payload,
for key, value in expected.items():
assert body[key] == value
assert body["isActive"] is True
if module in {"demandForm", "vehicleService"}:
assert "sortOrder" not in body
else:
assert body["sortOrder"] == 5
assert body["sortOrder"] == 5
assert fake_db.committed
assert fake_db.added[-1].action == "create"
@@ -465,70 +373,6 @@ def test_admin_site_config_hero_slides_use_dedicated_contract_without_targets():
assert "targetValue" not in hero
def test_admin_site_config_includes_destination_page_modules():
fake_db = FakeDb(scalar_results=[[], [], [], [], [], [], [], [], [], [], []])
app = authenticated_app(fake_db)
try:
response = TestClient(app).get("/api/admin/site-config")
finally:
app.dependency_overrides.clear()
assert response.status_code == 200
body = response.json()["data"]
assert "destinations" not in body
assert "ctaBanners" not in body
assert "campaigns" not in body
assert "hotelGroups" not in body
assert "map" not in body
assert "themes" not in body
assert body["destinationHero"] == []
assert "destinationRegions" not in body
def test_admin_site_config_includes_demand_page_modules():
fake_db = FakeDb(scalar_results=[[], [], [], [], [], [], [], [], [], [], [], [], [], [], []])
app = authenticated_app(fake_db)
try:
response = TestClient(app).get("/api/admin/site-config")
finally:
app.dependency_overrides.clear()
assert response.status_code == 200
body = response.json()["data"]
assert body["demandHero"] == []
assert body["demandFeatureCards"] == []
assert body["demandForm"] == []
def test_public_site_config_includes_destination_page_modules():
destination_hero = make_destination_hero()
fake_db = FakeDb(scalar_results=[[], [destination_hero], [], [], [], [], [], [], [], [], [], []])
result = site_config(fake_db, active_only=True)
assert "destinations" not in result
assert "ctaBanners" not in result
assert "campaigns" not in result
assert "hotelGroups" not in result
assert "map" not in result
assert "themes" not in result
assert result["destinationHero"] == [
{
"id": "destination-hero-test",
"title": "目的地页主视觉",
"kicker": "贵州小包团目的地",
"image": "/assets/destination-hero.jpg",
"sortOrder": 0,
"isActive": True,
"createdAt": "2026-01-01T00:00:00",
"updatedAt": "2026-01-02T00:00:00",
}
]
assert "destinationRegions" not in result
def test_site_config_create_hero_slide_ignores_target_fields_and_returns_dedicated_contract():
fake_db = FakeDb(scalar_values=[0])
app = authenticated_app(fake_db)
@@ -558,21 +402,6 @@ def test_site_config_create_hero_slide_ignores_target_fields_and_returns_dedicat
assert created.targetValue is None
def test_site_config_create_demand_form_rejects_duplicate_singleton():
fake_db = FakeDb(scalar_results=[[make_demand_form()]])
app = authenticated_app(fake_db)
try:
response = TestClient(app).post("/api/admin/site-config/demandForm", json={"submitLabel": "提交"})
finally:
app.dependency_overrides.clear()
assert response.status_code == 409
assert response.json()["errorCode"] == "MODULE_CONFIG_SINGLETON_EXISTS"
assert not fake_db.added
assert not fake_db.committed
def test_site_config_patch_hero_slide_ignores_target_fields_and_returns_dedicated_contract():
hero_slide = make_hero_slide(targetType="search", targetValue="search-test")
fake_db = FakeDb(get_result=hero_slide)
@@ -734,7 +563,7 @@ def test_admin_leads_endpoint_accepts_source_keyword_and_created_range_filters()
assert response.json() == {
"code": 200,
"msg": "success",
"data": {"items": []},
"data": {"items": [], "total": 0, "offset": 0, "take": 50},
}
stmt = str(fake_db.scalar_statements[0])
assert '"Lead".status' in stmt