行程:#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:
@@ -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;
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user