行程:#47~#51 打包 —— 双轨状态、动态类型、动作按钮、去提示 (#51)
#47 编辑浮层居中、类型换行、去掉手动状态选择、按钮改胶囊 #48 计划/实际双轨:完成即转实际行程,取消≠删除,记真实开始/结束时间 #50 类型点击无反应(@Builder 值传参不建立依赖) #51 节点类型动态加载(服务端9种,原端上硬编码5种)+ 地址结构化回调 API 新增:GET /trips/<id>/node-types、PATCH /trips/<id>/nodes/action DB 新增:trip_nodes.actual_started_at / actual_ended_at
This commit is contained in:
@@ -4521,6 +4521,116 @@ switch ($r) {
|
||||
redirect('trips/node_edit', ['id' => $nodeId]);
|
||||
break;
|
||||
|
||||
/* 🔴 #47(2026-11-05):**行程状态切换**(网页端)。
|
||||
用户:「在计划行程里面点击开始执行,它的状态自然变成进行中…
|
||||
如果它没去,取消了,那就自然变成已取消。」
|
||||
⇒ 状态由动作驱动,不再由编辑表单手工选(表单那边已删掉「执行状态」)。
|
||||
|
||||
⚠️ 返回 **JSON**(不是 redirect):前端 `app.js` 拿到后**只重绘那一段**
|
||||
的动作条,不整页刷新 —— 否则用户刚点完就被弹回顶部。
|
||||
⚠️ 返回的 `html` 是该节点动作条的新 HTML,前端用 outerHTML 就地替换。 */
|
||||
/* 🔴 #48(2026-11-05):**行程动作**(开始 / 完成 / 取消)。
|
||||
用户:「计划行程右上角…一个是开始,一个是完成,一个是编辑,一个是删除。
|
||||
在删除前面还应该有一个按钮,这个按钮是取消。」
|
||||
⇒ 返回 JSON,前端据此决定是否重载(动作会改变分组)。 */
|
||||
case 'node_act':
|
||||
$familyId = (int)Auth::familyId();
|
||||
Auth::requireWrite();
|
||||
csrf_check();
|
||||
$nodeId = (int)($_POST['node_id'] ?? 0);
|
||||
$act = (string)($_POST['act'] ?? '');
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
if ($nodeId <= 0) {
|
||||
echo json_encode(['ok' => false, 'error' => '缺 node_id'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
/* 校验归属(nodes → events 查一遍) */
|
||||
$stA = Database::get()->prepare(
|
||||
'SELECT n.event_id FROM trip_nodes n JOIN events e ON e.id = n.event_id
|
||||
WHERE n.id = ? AND n.family_id = ? AND e.family_id = ? AND n.deleted_at IS NULL'
|
||||
);
|
||||
$stA->execute([$nodeId, $familyId, $familyId]);
|
||||
$evId = $stA->fetchColumn();
|
||||
if ($evId === false) {
|
||||
echo json_encode(['ok' => false, 'error' => '找不到这段行程'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
$resA = \App\Trip::nodeAction($familyId, (int)$evId, $nodeId, $act);
|
||||
if (empty($resA['ok'])) {
|
||||
echo json_encode(['ok' => false, 'error' => (string)($resA['error'] ?? '没成功')], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
echo json_encode(['ok' => true, 'stage' => $resA['stage'] ?? '', 'status' => $resA['status'] ?? ''], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
|
||||
/* 🔴 #48:**删除**(软删,与"取消"完全不同)。
|
||||
用户:「所谓的删除,指的是我直接把这个计划给删掉了。」
|
||||
⇒ `deleted_at` 置位,彻底不显示。 */
|
||||
case 'node_del':
|
||||
$familyId = (int)Auth::familyId();
|
||||
Auth::requireWrite();
|
||||
csrf_check();
|
||||
$nodeId = (int)($_POST['node_id'] ?? 0);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
if ($nodeId <= 0) {
|
||||
echo json_encode(['ok' => false, 'error' => '缺 node_id'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
$stD = Database::get()->prepare(
|
||||
'SELECT n.id FROM trip_nodes n JOIN events e ON e.id = n.event_id
|
||||
WHERE n.id = ? AND n.family_id = ? AND e.family_id = ? AND n.deleted_at IS NULL'
|
||||
);
|
||||
$stD->execute([$nodeId, $familyId, $familyId]);
|
||||
if ($stD->fetchColumn() === false) {
|
||||
echo json_encode(['ok' => false, 'error' => '找不到这段行程'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
$upD = Database::get()->prepare(
|
||||
'UPDATE trip_nodes SET deleted_at = NOW(), updated_at = NOW() WHERE id = ?'
|
||||
);
|
||||
$upD->execute([$nodeId]);
|
||||
echo json_encode(['ok' => true], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
|
||||
case 'node_status':
|
||||
$familyId = (int)Auth::familyId();
|
||||
Auth::requireWrite();
|
||||
csrf_check();
|
||||
$nodeId = (int)($_POST['node_id'] ?? 0);
|
||||
$newSt = (string)($_POST['status'] ?? '');
|
||||
if ($nodeId <= 0) {
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode(['ok' => false, 'error' => '缺 node_id'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
if (!array_key_exists($newSt, \App\Trip::NODE_STATUS)) {
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode(['ok' => false, 'error' => '状态值不对'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
/* 校验归属(顺着 nodes → events 查一遍,防越权) */
|
||||
$stN = Database::get()->prepare(
|
||||
'SELECT n.id FROM trip_nodes n JOIN events e ON e.id = n.event_id
|
||||
WHERE n.id = ? AND n.family_id = ? AND e.family_id = ? AND n.deleted_at IS NULL'
|
||||
);
|
||||
$stN->execute([$nodeId, $familyId, $familyId]);
|
||||
if ($stN->fetchColumn() === false) {
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode(['ok' => false, 'error' => '找不到这段行程'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
$upN = Database::get()->prepare(
|
||||
'UPDATE trip_nodes SET status = ?, updated_at = NOW() WHERE id = ?'
|
||||
);
|
||||
$upN->execute([$newSt, $nodeId]);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode([
|
||||
'ok' => true,
|
||||
'status' => $newSt,
|
||||
'status_word' => \App\Trip::NODE_STATUS[$newSt],
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
|
||||
case 'trips/expense_del':
|
||||
$familyId = (int)Auth::familyId();
|
||||
Auth::requireWrite();
|
||||
|
||||
Reference in New Issue
Block a user