新建地点的活动下拉可即时新增,地点行内编辑支持批量改该地点全部到访的活动

站长反馈「已有地点编辑和新建里只有类别没有活动能选」。实测澄清:新建地点卡一直有活动下拉,
只是形态与类别/照片页不一致(旁边常显一个新建框、下拉里没有「+ 新增活动…」);真正完全没有
活动字段的是地点列表行的「编辑」。

- 新建地点卡:活动下拉补「+ 新增活动…」哨兵,旁边的新建框改成选中哨兵才展开;
  删掉原来那对 #activity-add/#new-activity-name 监听——三处活动下拉现在共用同一套
  `visit-act-*` 委派处理器,重复监听会建出两条活动。
- 地点行内编辑:加「活动(改该地点全部到访)」下拉,默认「(不修改)」不预选当前值
  (否则每次改坐标保存都会重写历史到访的类型),并标出当前类型构成;选了真实活动时
  提交前 confirm 一次。
- 服务端(places 没有活动列,活动挂在到访上):editPlaceUpdate 解析 activity_id,空值=不修改,
  与地点行同一事务内 UPDATE visits 的 activity_id/is_home/is_work,flash 报改了几条;
  哨兵值/不存在的 id/别的数据集的活动一律拒。导出无需改动(ZVISIT.ZACTIVITY_ 本就是管理列)。
- 回归 internal/web/place_activity_test.go:批量改(含别的地点不受影响)、空值不修改、越权被拒、
  模板契约、以及导出包里那批到访的 ZACTIVITY_ 必须换成新活动的 rond 编号。
- 顺手:TestMovementCleanExport 建出 ROND_CLEAN_OUT 的父目录,别再把「目录不存在」报成导出失败。
This commit is contained in:
tamakyi
2026-09-18 21:23:13 +08:00
parent 96e76318ab
commit 86e420bd4e
5 changed files with 339 additions and 33 deletions
+36 -23
View File
@@ -98,15 +98,17 @@
<a href="javascript:void(0)" id="activity-delete" class="danger">删除</a>
</span>
</span>
<select name="activity_id" id="activity-select">
{{/* 与照片导入页、到访行内编辑同一套:下拉里带「+ 新增活动…」,选中后就地展开新建框 */}}
<select name="activity_id" id="activity-select" class="activity-id-inline">
<option value="">未指定</option>
{{range .Activities}}<option value="{{.ID}}">{{.Name}}</option>{{end}}
<option value="__new__">+ 新增活动…</option>
</select>
</label>
<label class="f" style="flex:1 1 170px"><span>新建活动</span>
<label class="f visit-act-new" style="flex:1 1 170px;display:none"><span>新建活动</span>
<div class="input-btn">
<input type="text" id="new-activity-name" placeholder="名称">
<button type="button" class="btn" id="activity-add">添加</button>
<input type="text" class="visit-act-name" placeholder="名称">
<button type="button" class="btn visit-act-add">添加</button>
</div>
</label>
<label class="f" style="flex:1 1 140px"><span>备注</span><input type="text" name="remark" placeholder="可选"></label>
@@ -161,7 +163,7 @@
<td style="white-space:nowrap">
<details class="inline-edit"{{if and $.OpenSrcPK (eq .SrcPK $.OpenSrcPK)}} open{{end}}>
<summary class="btn sm">编辑</summary>
<form method="post" action="/admin/edit/place/update" style="margin-top:8px;min-width:280px">
<form method="post" action="/admin/edit/place/update" data-visit-count="{{.VisitCount}}" style="margin-top:8px;min-width:280px">
<input type="hidden" name="src_pk" value="{{.SrcPK}}">
<label class="f"><span>名称<b class="req">*</b></span><input type="text" name="name" value="{{.Name}}" required></label>
<label class="f"><span>类别</span>
@@ -173,6 +175,22 @@
</select>
</label>
<label class="f cat-custom" style="display:none"><span>自定义类别</span><input type="text" name="category_custom" placeholder="如 公园"></label>
{{/* 改的是该地点名下**全部到访**的类型,所以默认「(不修改)」、
不预选当前值:否则每次改个坐标保存,都会把历史到访重写一遍 */}}
<label class="f"><span>活动<span class="muted">(改该地点全部到访)</span></span>
<select name="activity_id" class="activity-id-inline">
<option value="">(不修改)</option>
{{range $.Activities}}<option value="{{.ID}}">{{.Name}}</option>{{end}}
<option value="__new__">+ 新增活动…</option>
</select>
</label>
<label class="f visit-act-new" style="display:none"><span>新建活动</span>
<div class="input-btn">
<input type="text" class="visit-act-name" placeholder="名称">
<button type="button" class="btn visit-act-add">添加</button>
</div>
</label>
{{if .ActivityText}}<div class="card-sub" style="margin:0 0 8px">该地点到访当前类型:<span class="mono">{{.ActivityText}}</span></div>{{end}}
<div class="row">
<label class="f" style="flex:1 1 120px"><span>纬度<b class="req">*</b></span><input type="text" name="lat" value="{{printf "%.6f" .Lat}}" required></label>
<label class="f" style="flex:1 1 120px"><span>经度<b class="req">*</b></span><input type="text" name="lon" value="{{printf "%.6f" .Lon}}" required></label>
@@ -939,23 +957,11 @@
}).catch(function (e) { alert('创建活动失败:' + e); });
}
var actAdd = document.getElementById('activity-add');
if (actAdd) {
var newActInput = document.getElementById('new-activity-name');
var addActivityTop = function () {
var sel = document.getElementById('activity-select');
createActivity(newActInput.value, function (id) {
if (sel) sel.value = id;
newActInput.value = '';
});
};
actAdd.addEventListener('click', addActivityTop);
newActInput.addEventListener('keydown', function (e) {
if (e.key === 'Enter') { e.preventDefault(); addActivityTop(); }
});
}
// 顶部「新建活动」不再单独接事件:它和到访/地点行内编辑共用下面那套
// `visit-act-*` 委派处理器(同一个表单里只有一个新建框,重复监听会建出两条)。
// ---- 到访行内编辑里的「+ 新增活动…」:就地展开新建框,建完选中并收起 ----
// ---- 下拉里的「+ 新增活动…」:就地展开新建框,建完选中并收起 ----
// 顶部新建地点卡、到访行内编辑、地点行内编辑三处共用(靠 form 里的类名找同伴元素)
function inlineActParts(el) {
var form = el.closest ? el.closest('form') : null;
if (!form) return null;
@@ -993,16 +999,23 @@
inlineActCreate(t);
});
// 提交守卫:还停在「+ 新增活动…」上就拦下来。否则 activity_id 会带着 "__new__" 提交,
// resolveActivity 解析不出整数就当成「无」,这条到访的类型会被静默清掉。
// resolveActivity 解析不出整数就当成「无」,那条记录的类型会被静默清掉。
document.addEventListener('submit', function (e) {
var f = e.target;
if (!f || !f.querySelector) return;
var sel = f.querySelector('select.activity-id-inline');
if (sel && sel.value === '__new__') {
e.preventDefault();
alert('类型选了「+ 新增活动…」但还没点「添加」:填个名字再点添加,或改回「(无)」。');
alert('活动选了「+ 新增活动…」但还没点「添加」:填个名字再点添加,或把这一项改回默认值。');
sel.focus();
return;
}
// 地点行内编辑的「活动」是批量改该地点**全部**到访,动的是历史记录,落库前再问一次
if (!f.getAttribute || f.getAttribute('action') !== '/admin/edit/place/update') return;
if (!sel || !sel.value) return;
var n = f.getAttribute('data-visit-count') || '';
if (!window.confirm('把该地点' + (n ? '全部 ' + n + ' 条' : '全部') + '到访的活动都改成「' +
sel.options[sel.selectedIndex].textContent + '」?原有记录会被一起改掉。')) e.preventDefault();
}, true);
// ---------- 活动改名 / 删除:作用于下拉里当前选中的那一个 ----------