你要改的那个「海滩」原来根本不在数据里——它是 Go 里硬编码的 map,所以改不动。
更糟的是这样的 map 有**两张**,而且对不上:
MKPOICategoryFoodMarket 菜市场(编辑页) / 食品市场(补全页、GPX)
MKPOICategoryCafe 咖啡馆 / 咖啡厅
MKPOICategoryFitnessCenter 健身房 / 健身中心
外加 ATM / Atm 大小写各写一份、一条永远匹配不上的 MKPOICategoryMarina2
现在合成一张内置表(42 条,冲突处取编辑页那套),并在它上面加了一层站点覆盖:
- 新表 category_labels(value, label),站点级、不挂 dataset——值本身才是稳定键
- 端点 POST /admin/edit/category/label;传空 label = 删掉覆盖行、恢复内置默认名
- 类别台账那行改成两个带标签的输入框:
导出值 [MKPOICategoryBeach] ← 会跟着导出写回手机
显示名 [ ] ← 只影响站内显示,placeholder 写着默认值
是 MKPOICategory 枚举但内置表里没有的,会提示「填一个中文名」,这就是你要的补全入口
**显示名绝不写进 places.poi_category**——中文跟着写进去就把导出数据污染了,
专门加了 TestCategoryLabelNeverTouchesValue 钉这条。显示名会贯穿到所有中文名的地方:
编辑页/照片页/补全页的下拉、补全页「在用」标签、访客地点页、GPX 的 desc。
连带删掉两个绑在旧表上的模板函数 catName / poiname:模板里算不出显示名(要读站点覆盖),
改成在 Go 侧算好塞进数据(FillupRow.CatLabel、PlaceData.CategoryLabel)。
252 lines
12 KiB
HTML
252 lines
12 KiB
HTML
{{define "content"}}
|
||
<div class="wrap">
|
||
<div class="page-head">
|
||
<div>
|
||
<h1>地点补全</h1>
|
||
<div class="sub">
|
||
共 <strong>{{num .Total}}</strong> 个地点缺活动或类别,其中 <strong>{{num .WithHint}}</strong> 个能给出建议。
|
||
<strong>只填空</strong>——已经有活动或类别的一律不动。
|
||
</div>
|
||
</div>
|
||
<div class="row"><a class="btn" href="/admin">← 返回后台</a></div>
|
||
</div>
|
||
|
||
{{if .Flash}}<div class="alert ok">{{.Flash}}</div>{{end}}
|
||
{{if .Err}}<div class="alert err">{{.Err}}</div>{{end}}
|
||
|
||
{{if .Rows}}
|
||
<div class="card">
|
||
<h2>待补全({{num .Total}}){{if gt .PageCount 1}}<span class="muted" style="font-size:13px;font-weight:400"> 第 {{.PageNo}} / {{.PageCount}} 页</span>{{end}}</h2>
|
||
<div class="card-sub">
|
||
<strong>默认都是「不动」</strong>,你改过的才会写进去。
|
||
活动可以直接打字(会提示已有的类型),也可以点「采纳本页建议」一键填上;
|
||
类别从下拉里挑,都不合适就选「其它」自己写一个(真机也存中文类别,写中文同样认得)。<br>
|
||
活动按<strong>地点</strong>补:一次把它名下所有没设活动的到访一起填上,不会只改一条。
|
||
右侧「高德参考」是高德在这个坐标上找到的 POI 与类型,拿不准时作参考。
|
||
</div>
|
||
|
||
{{if gt .PageCount 1}}
|
||
<div class="pager">
|
||
{{if gt .PageNo 1}}<a href="{{.PageLink (sub .PageNo 1)}}">上一页</a>{{end}}
|
||
<span class="cur">第 {{.PageNo}} / {{.PageCount}} 页</span>
|
||
{{if lt .PageNo .PageCount}}<a href="{{.PageLink (add .PageNo 1)}}">下一页</a>{{end}}
|
||
</div>
|
||
{{end}}
|
||
|
||
<form method="post" action="/admin/fillup/apply" id="fillup-form">
|
||
<input type="hidden" name="page" value="{{.PageNo}}">
|
||
<div class="tw">
|
||
<table class="tb tb-stack">
|
||
<thead>
|
||
<tr>
|
||
<th>地点</th><th>城市</th><th class="num">待设到访</th><th>缺什么</th>
|
||
<th>活动</th><th>类别</th><th>建议来源</th><th>高德参考</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{{range $row := .Rows}}
|
||
<tr>
|
||
<td>{{if $row.Name}}{{$row.Name}}{{else}}<span class="muted">(未命名)</span>{{end}}</td>
|
||
<td data-label="城市">{{if $row.City}}{{$row.City}}{{else}}<span class="muted">—</span>{{end}}</td>
|
||
<td class="num" data-label="待设到访">{{if $row.NoAct}}{{$row.NoAct}}{{else}}<span class="muted">—</span>{{end}}</td>
|
||
<td data-label="缺什么">{{$row.Missing}}</td>
|
||
<td data-label="活动">
|
||
{{if $row.NoAct}}
|
||
{{$ahint := $row.ActHint}}
|
||
<select name="act_{{$row.PlaceID}}" class="fill-sel" data-hint="{{$ahint}}" style="min-width:10em">
|
||
<option value="">(不动)</option>
|
||
{{if $ahint}}<option value="{{$ahint}}">建议:{{$ahint}}</option>{{end}}
|
||
{{range $.Activities}}{{if ne .Name $ahint}}<option value="{{.Name}}">{{.Name}}</option>{{end}}{{end}}
|
||
<option value="__custom__">其它(自己写)…</option>
|
||
</select>
|
||
<input type="text" name="act_{{$row.PlaceID}}__custom" placeholder="例如 爬山"
|
||
style="display:none;margin-top:4px;min-width:10em">
|
||
{{else}}<span class="muted">—</span>{{end}}
|
||
</td>
|
||
<td data-label="类别">
|
||
{{if $row.NoCat}}
|
||
{{$hint := $row.CatHint}}
|
||
<select name="cat_{{$row.PlaceID}}" class="fill-sel" data-hint="{{$hint}}" style="min-width:10em">
|
||
<option value="">(不动)</option>
|
||
{{if $hint}}<option value="{{$hint}}">建议:{{$row.CatLabel}}</option>{{end}}
|
||
{{range $.Categories}}{{if ne .Value $hint}}<option value="{{.Value}}">{{.Label}}</option>{{end}}{{end}}
|
||
<option value="__custom__">其它(自己写)…</option>
|
||
</select>
|
||
<input type="text" name="cat_{{$row.PlaceID}}__custom" placeholder="例如 美食街"
|
||
style="display:none;margin-top:4px;min-width:10em">
|
||
{{else}}<span class="muted">—</span>{{end}}
|
||
</td>
|
||
<td data-label="建议来源">{{if $row.Source}}{{$row.Source}}{{else}}<span class="muted">—</span>{{end}}</td>
|
||
<td data-label="高德参考">
|
||
{{if $row.AmapType}}
|
||
<span style="font-size:12.5px">{{if $row.AmapPoi}}{{$row.AmapPoi}}<br>{{end}}<span class="muted">{{$row.AmapType}}</span></span>
|
||
{{else}}<span class="muted">—</span>{{end}}
|
||
</td>
|
||
</tr>
|
||
{{end}}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div class="row" style="margin-top:12px">
|
||
<button class="btn primary" type="submit">执行补全</button>
|
||
<button class="btn" type="button" id="pick-hints">采纳本页建议</button>
|
||
<button class="btn" type="submit" formaction="/admin/fillup/amap" id="amap-btn"
|
||
title="用高德按坐标搜一遍,给还没建议的地点补上参考(每批 40 个)">用高德分析剩下的</button>
|
||
</div>
|
||
</form>
|
||
|
||
{{if gt .PageCount 1}}
|
||
<div class="pager" style="margin-top:12px">
|
||
{{if gt .PageNo 1}}<a href="{{.PageLink (sub .PageNo 1)}}">上一页</a>{{end}}
|
||
<span class="cur">第 {{.PageNo}} / {{.PageCount}} 页</span>
|
||
{{if lt .PageNo .PageCount}}<a href="{{.PageLink (add .PageNo 1)}}">下一页</a>{{end}}
|
||
</div>
|
||
{{end}}
|
||
</div>
|
||
{{else}}
|
||
<div class="card">
|
||
<div class="alert ok">所有地点的活动和类别都齐了,没什么要补的。</div>
|
||
</div>
|
||
{{end}}
|
||
</div>
|
||
|
||
<script>
|
||
(function () {
|
||
var KEY = 'rond-fillup-draft';
|
||
var NEW = 'rond-fillup-newopts';
|
||
|
||
// 活动和类别是同一套下拉:选了「其它(自己写)」就露出旁边的文本框
|
||
document.querySelectorAll('select.fill-sel').forEach(function (sel) {
|
||
var box = document.querySelector('input[name="' + sel.name + '__custom"]');
|
||
if (!box) return;
|
||
var sync = function () {
|
||
var custom = sel.value === '__custom__';
|
||
box.style.display = custom ? '' : 'none';
|
||
if (custom) box.focus();
|
||
};
|
||
sel.addEventListener('change', sync);
|
||
sync();
|
||
});
|
||
|
||
// 下拉的候选是渲染时定好的,手工新填的名字进不了候选,后面遇到同类地点还得重打一遍。
|
||
// 所以把填过的值记下来补进同类下拉:本页实时生效,翻页/点「用高德分析」也留得住,
|
||
// 提交成功后清掉(那之后它们已经在真正的候选里了)。
|
||
var fromOther = {act: [], cat: []}; // 从别处(别的页/上次)带来的
|
||
var fromPage = {act: [], cat: []}; // 本页文本框里当前填着的
|
||
try {
|
||
var saved = JSON.parse(sessionStorage.getItem(NEW) || '{}');
|
||
fromOther.act = saved.act || [];
|
||
fromOther.cat = saved.cat || [];
|
||
} catch (e) { /* 坏数据就当没有 */ }
|
||
|
||
var paint = function (prefix) {
|
||
var vals = fromOther[prefix].concat(fromPage[prefix].filter(function (v) {
|
||
return fromOther[prefix].indexOf(v) < 0;
|
||
}));
|
||
document.querySelectorAll('select[name^="' + prefix + '_"]').forEach(function (sel) {
|
||
// 重画不能动用户已经选好的东西:这一格选的要是「新填」里的值,先留住它
|
||
var keep = sel.value;
|
||
if (keep && vals.indexOf(keep) < 0 && [].some.call(sel.options, function (o) {
|
||
return o.value === keep && o.getAttribute('data-local');
|
||
})) vals.push(keep);
|
||
[].forEach.call(sel.querySelectorAll('option[data-local]'), function (o) { o.remove(); });
|
||
// 插在「(不动)」/建议之后、真正的候选之前,免得沉到列表最底下去找
|
||
var anchor = null;
|
||
for (var i = 0; i < sel.options.length; i++) {
|
||
var o = sel.options[i];
|
||
if (o.getAttribute('data-local') || !o.value || o.value === sel.dataset.hint) continue;
|
||
anchor = o;
|
||
break;
|
||
}
|
||
vals.forEach(function (v) {
|
||
if ([].some.call(sel.options, function (o) { return o.value === v; })) return;
|
||
var o = document.createElement('option');
|
||
o.value = v;
|
||
o.textContent = '新填:' + v;
|
||
o.setAttribute('data-local', '1');
|
||
sel.insertBefore(o, anchor);
|
||
});
|
||
// 选项重插之后浏览器不会自己选回来,得手工还回去
|
||
if (keep && sel.value !== keep) sel.value = keep;
|
||
});
|
||
};
|
||
|
||
var collect = function (prefix) {
|
||
fromPage[prefix] = [];
|
||
document.querySelectorAll('input[name^="' + prefix + '_"][name$="__custom"]').forEach(function (box) {
|
||
var v = box.value.trim();
|
||
if (v && fromPage[prefix].indexOf(v) < 0) fromPage[prefix].push(v);
|
||
});
|
||
paint(prefix);
|
||
try {
|
||
sessionStorage.setItem(NEW, JSON.stringify({
|
||
act: fromOther.act.concat(fromPage.act),
|
||
cat: fromOther.cat.concat(fromPage.cat)
|
||
}));
|
||
} catch (e) { /* 隐私模式就算了 */ }
|
||
};
|
||
|
||
// 文本框里打字就同步,不用等失焦或提交。直接挂在框上,不靠事件冒泡——
|
||
// 省得以后有人在这行 DOM 上动手脚(或程序化派发)时静默失效。
|
||
document.querySelectorAll('input[name$="__custom"]').forEach(function (box) {
|
||
box.addEventListener('input', function () { collect(box.name.split('_')[0]); });
|
||
});
|
||
|
||
// 采纳本页建议:把建议项选上(活动和类别都是下拉)
|
||
var pick = document.getElementById('pick-hints');
|
||
if (pick) {
|
||
pick.addEventListener('click', function () {
|
||
var n = 0;
|
||
document.querySelectorAll('select.fill-sel').forEach(function (sel) {
|
||
if (sel.value || !sel.dataset.hint) return;
|
||
sel.value = sel.dataset.hint;
|
||
n++;
|
||
});
|
||
pick.textContent = n ? '已填入 ' + n + ' 条建议' : '本页没有可填的建议';
|
||
});
|
||
}
|
||
|
||
// 离开本页(翻页 / 「用高德分析」)之前把填过的存起来、回来接着填——
|
||
// 否则挑了半天,点一下就全没了。按 name 合并,所以翻页也留得住。
|
||
var save = function () {
|
||
var draft = {};
|
||
try { draft = JSON.parse(sessionStorage.getItem(KEY) || '{}'); } catch (e) { draft = {}; }
|
||
document.querySelectorAll('select.fill-sel, input[name$="__custom"]').forEach(function (el) {
|
||
if (el.value) draft[el.name] = el.value;
|
||
else delete draft[el.name]; // 本页明确清空的就是不要了,别让旧值顶上来
|
||
});
|
||
try { sessionStorage.setItem(KEY, JSON.stringify(draft)); } catch (e) { /* 隐私模式就算了 */ }
|
||
};
|
||
var amap = document.getElementById('amap-btn');
|
||
if (amap) amap.addEventListener('click', save);
|
||
document.querySelectorAll('.pager a').forEach(function (a) { a.addEventListener('click', save); });
|
||
|
||
// 先把「新填」的候选补上再恢复草稿:草稿里选中的可能就是新填的值,
|
||
// 选项不在跟前的话 select.value 是设不进去的。
|
||
paint('act');
|
||
paint('cat');
|
||
try {
|
||
var draft = JSON.parse(sessionStorage.getItem(KEY) || '{}');
|
||
Object.keys(draft).forEach(function (name) {
|
||
var el = document.querySelector('[name="' + name + '"]');
|
||
if (!el) return;
|
||
el.value = draft[name];
|
||
if (el.tagName === 'SELECT') el.dispatchEvent(new Event('change'));
|
||
});
|
||
} catch (e) { /* 坏数据就当没有 */ }
|
||
// 恢复出来的文本框(`set value` 不触发 input)也要算进「新填」
|
||
collect('act');
|
||
collect('cat');
|
||
|
||
// 提交成功后草稿就没用了,清掉,免得下次进来还带着旧选择
|
||
var form = document.getElementById('fillup-form');
|
||
if (form) form.addEventListener('submit', function () {
|
||
try {
|
||
sessionStorage.removeItem(KEY);
|
||
sessionStorage.removeItem(NEW);
|
||
} catch (e) {}
|
||
});
|
||
})();
|
||
</script>
|
||
{{end}}
|