feat: 行程活动拖拽排序 + 编辑表单(含高德 POI 搜索)

- 安装 @dnd-kit/core/sortable/utilities/modifiers
- BlindboxPlan: 同天内拖拽排序(PointerSensor + TouchSensor,限垂直轴)
- BlindboxPlan: 点击编辑弹出 sheet modal,支持改时间(type=time)、时长、活动名
- BlindboxPlan: POI 字段改为高德 inputtips 搜索下拉,选中自动填入名称/地址/坐标
- BlindboxPlan: 搜索传入房间坐标 location 参数,结果按距离排序
- BlindboxPlan: 跨天移动通过 select 立即生效
- plan route: 新增 update_plan action,支持 PATCH 保存修改后的 days
- page.tsx: 新增 handlePlanDaysChange,乐观更新本地 state + 失败时回滚
This commit is contained in:
2026-03-02 12:06:50 +08:00
parent df2e373beb
commit 4e6a3e007c
5 changed files with 502 additions and 55 deletions
+23
View File
@@ -606,6 +606,27 @@ export default function BlindboxRoomPage() {
router.refresh();
}, [router]);
const handlePlanDaysChange = useCallback(async (newDays: WeekendPlanData[]) => {
if (!planId || !profile) return;
const prevDays = planDays;
setPlanDays(newDays);
if (planAccepted) {
setActiveContract((prev) => prev ? { ...prev, days: newDays } : prev);
}
try {
const res = await fetch("/api/blindbox/plan", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ planId, userId: profile.id, action: "update_plan", days: newDays }),
});
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error || "保存失败");
} catch (e) {
setPlanDays(prevDays);
if (planAccepted) setActiveContract((prev) => prev ? { ...prev, days: prevDays } : prev);
toast.show(e instanceof Error ? e.message : "保存失败");
}
}, [planId, profile, planDays, planAccepted, toast]);
/** Non-creator: leave room (remove membership). Creator: delete room (after confirm). */
const handleLeaveOrDelete = async () => {
if (!confirmLeave) {
@@ -1090,6 +1111,8 @@ export default function BlindboxRoomPage() {
days={planDays}
accepted={planAccepted}
regenerating={generating}
onDaysChange={handlePlanDaysChange}
location={room.lng != null && room.lat != null ? `${room.lng},${room.lat}` : undefined}
onAccept={async () => {
setPlanAccepted(true);
fireConfetti();