行程:#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:
2026-11-05 12:40:57 +08:00
parent a1140437a1
commit f58a128816
9 changed files with 1361 additions and 46 deletions
+151
View File
@@ -2645,3 +2645,154 @@ window.fbHomeTaskDrop = function (row, wasStatus) {
}
return true;
};
/* ============================================================
#47(2026-11-05)行程状态动作按钮:点一下 → PATCH → 就地重绘
------------------------------------------------------------
用户要求状态由**动作**驱动,不再由编辑表单手工选。
服务端接口:PATCH /api/v1/trips/<tripId>/nodes/status {node_id, status}
⚠️ 但网页端走的是表单式站点,不是 API 客户端 —— 用一个轻量的
POST 到同源端点(下面用 fetch 打站点自己的 node_status 动作)。
============================================================ */
(function () {
function bindNodeStatus() {
var btns = document.querySelectorAll('[data-node-status-set]');
for (var i = 0; i < btns.length; i++) {
var b = btns[i];
if (b.getAttribute('data-bound') === '1') { continue; }
b.setAttribute('data-bound', '1');
b.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
var self = e.currentTarget;
if (self.getAttribute('data-busy') === '1') { return; }
self.setAttribute('data-busy', '1');
var nodeId = self.getAttribute('data-node');
var status = self.getAttribute('data-node-status-set');
var box = self.closest('[data-node-status]');
var tripId = document.body.getAttribute('data-trip-id') || '';
var fd = new FormData();
fd.append('node_id', nodeId);
fd.append('status', status);
if (tripId) { fd.append('trip_id', tripId); }
fd.append('csrf', (window.FB_CSRF || ''));
fetch('index.php?action=node_status', {
method: 'POST',
body: fd,
headers: { 'X-Requested-With': 'fetch' }
}).then(function (r) { return r.json(); }).then(function (res) {
self.removeAttribute('data-busy');
if (!res || !res.ok) {
alert((res && res.error) ? res.error : '状态没改成,请重试。');
return;
}
/* 就地刷新这一段的动作条(不整页重载,避免丢失滚动位置) */
if (box && res.html) {
box.outerHTML = res.html;
bindNodeStatus();
} else {
location.reload();
}
}).catch(function () {
self.removeAttribute('data-busy');
alert('网络出错,状态没改成。');
});
});
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', bindNodeStatus);
} else {
bindNodeStatus();
}
window.fbBindNodeStatus = bindNodeStatus;
})();
/* ============================================================
#48(2026-11-05)行程动作:开始 / 完成 / 取消 / 删除
------------------------------------------------------------
用户:「计划行程右上角就不是两个按钮了,而是至少四个按钮:
一个是开始,一个是完成,一个是编辑,一个是删除。
在删除前面还应该有一个按钮,这个按钮是取消。」
⚠️ 完成 = 转成实际行程(stage: plan → actual),之后不再有状态。
⚠️ 取消 ≠ 删除:取消保留数据(stage=skipped),删除才软删。
============================================================ */
(function () {
function bindNodeActs() {
/* ---- 状态动作(开始 / 完成 / 取消)---- */
var acts = document.querySelectorAll('[data-node-act]');
for (var i = 0; i < acts.length; i++) {
var b = acts[i];
if (b.getAttribute('data-bound') === '1') { continue; }
b.setAttribute('data-bound', '1');
b.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
var self = e.currentTarget;
if (self.getAttribute('data-busy') === '1') { return; }
var act = self.getAttribute('data-node-act');
var nodeId = self.getAttribute('data-node');
/* 取消是不可逆的语义(虽然数据还在),先确认一下 */
if (act === 'cancel' && !confirm('标记为不去了?数据会保留在「未成行」里。')) {
return;
}
self.setAttribute('data-busy', '1');
var fd = new FormData();
fd.append('node_id', nodeId);
fd.append('act', act);
fd.append('csrf', (window.FB_CSRF || ''));
fetch('index.php?action=node_act', {
method: 'POST', body: fd,
headers: { 'X-Requested-With': 'fetch' }
}).then(function (r) { return r.json(); }).then(function (res) {
self.removeAttribute('data-busy');
if (!res || !res.ok) {
alert((res && res.error) ? res.error : '没成功,请重试。');
return;
}
/* 动作会改变分组(计划→实际/未成行),必须整页重载 */
location.reload();
}).catch(function () {
self.removeAttribute('data-busy');
alert('网络出错,没成功。');
});
});
}
/* ---- 删除(软删)---- */
var dels = document.querySelectorAll('[data-node-del]');
for (var k = 0; k < dels.length; k++) {
var d = dels[k];
if (d.getAttribute('data-bound') === '1') { continue; }
d.setAttribute('data-bound', '1');
d.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
var self = e.currentTarget;
if (!confirm('彻底删掉这段行程?')) { return; }
var fd = new FormData();
fd.append('node_id', self.getAttribute('data-node-del'));
fd.append('csrf', (window.FB_CSRF || ''));
fetch('index.php?action=node_del', {
method: 'POST', body: fd,
headers: { 'X-Requested-With': 'fetch' }
}).then(function (r) { return r.json(); }).then(function (res) {
if (!res || !res.ok) {
alert((res && res.error) ? res.error : '删不掉,请重试。');
return;
}
location.reload();
}).catch(function () { alert('网络出错。'); });
});
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', bindNodeActs);
} else {
bindNodeActs();
}
window.fbBindNodeActs = bindNodeActs;
})();