- 地图脚本:localStorage 读写走 lsGet/lsSet 兜底(隐私模式下裸访问会打断整段脚本); preview() 在 /api/map-config 还没回来时不丢点(previewQueue 在 if (live) 之前排队); 异步回包一律发序号作废,取消后回来的包不再画 - photo-exif.js:EXIF 时间全 0 不再变成 1899-11-30 写进到访时间;读 count 前先按版本卡宽度 - 数据编辑页:三个筛选表单都把其余条件用隐藏域原样带上,提交不再把别的列表的筛选清掉 - 补全页:sessionStorage 的读写一律包 try(脏数据/隐私模式都会抛) - 窄屏:删掉被后面同特异性规则盖掉的死内边距;宽表堆叠类名成对、判据扩到所有 tb 表 - 无障碍:图片删除按钮补 aria-label,访客画廊图片补 alt - report.html 补上漏掉的 </tr>,fog-mask 的宽表带 tb-stack 本批不碰数据库与导出路径:往返包 823/323/8/30/791/557/2246 全等、 坐标偏差 0.000000000、时间偏差 0s、迷雾 8701 块一致。
463 lines
21 KiB
HTML
463 lines
21 KiB
HTML
{{define "content"}}
|
||
<div class="wrap">
|
||
<div class="page-head">
|
||
<div>
|
||
<h1>{{if .Place.Name}}{{.Place.Name}}{{else}}未命名地点{{end}}</h1>
|
||
<div class="sub">
|
||
{{if .Place.City}}{{.Place.City}}{{end}}
|
||
{{if .Place.Province}} · {{.Place.Province}}{{end}}
|
||
{{if .Place.District}} · {{.Place.District}}{{end}}
|
||
{{if .CategoryLabel}} · {{.CategoryLabel}}{{end}}
|
||
· <span class="mono">{{f1 .Place.Lat}}, {{f1 .Place.Lon}}</span>
|
||
</div>
|
||
</div>
|
||
<div class="row">
|
||
{{/* 有 back 就按它回去:从筛选过的地点库/时间线/地图点进来时,这里要回到**那份筛选结果** */}}
|
||
<a class="btn" href="{{if .Back}}{{.Back}}{{else}}/places{{end}}">← {{if .BackLabel}}{{.BackLabel}}{{else}}返回地点库{{end}}</a>
|
||
<a class="btn" href="{{.MapLink .Place.Name}}">在地图中查看</a>
|
||
{{if .IsAdmin}}<a class="btn" href="{{.EditPlaceLink .Place.SrcPK}}" title="在数据编辑页里直接打开这个地点的编辑框">编辑</a>{{end}}
|
||
{{if .Images}}<button class="btn" type="button" id="imgs-open">图片 <span class="muted">{{len .Images}}</span></button>{{end}}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="kpis" style="margin-bottom:16px">
|
||
<div class="kpi"><div class="v">{{num .Place.VisitCount}}<small>次</small></div><div class="k">到访次数</div></div>
|
||
<div class="kpi"><div class="v">{{dur .Place.DwellMinutes}}</div><div class="k">累计停留</div></div>
|
||
<div class="kpi"><div class="v">{{if .Place.FirstAt}}{{d .Place.FirstAt}}{{else}}—{{end}}</div><div class="k">首次到访</div></div>
|
||
<div class="kpi"><div class="v">{{if .Place.LastAt}}{{since .Place.LastAt}}{{else}}—{{end}}</div><div class="k">最近到访</div></div>
|
||
</div>
|
||
|
||
{{if .IsAdmin}}
|
||
<div class="card" style="margin-bottom:16px">
|
||
<h2>备注与别名</h2>
|
||
<div class="card-sub">
|
||
别名会替代原名显示在所有页面上;备注{{if .Settings.NotesPublic}}对访客公开{{else}}只有你自己能看到{{end}}。
|
||
「推荐 / 踩雷」是单独的按钮,不用写进备注。按 rond 的地点 ID 保存,重新上传备份也不会丢。
|
||
</div>
|
||
{{if .Flash}}<div class="alert ok" id="note-flash">{{.Flash}}</div>{{end}}
|
||
{{if .RondNote}}
|
||
<div class="card-sub" style="margin-bottom:10px">
|
||
<div style="margin-bottom:4px">
|
||
rond 备份里给这个地点写过的备注(按次到访记录,只读;展示时会拼在你的备注之前):
|
||
</div>
|
||
<div class="note-block">{{.RondNote}}</div>
|
||
</div>
|
||
{{end}}
|
||
<form method="post" action="/admin/place/note" id="note-form">
|
||
<input type="hidden" name="src_pk" value="{{.Place.SrcPK}}">
|
||
<input type="hidden" name="next" value="/places/{{.Place.ID}}">
|
||
<label class="f" style="max-width:420px">
|
||
<span>别名</span>
|
||
<input type="text" name="alias" value="{{.Place.Alias}}" maxlength="60" placeholder="{{.Place.Name}}">
|
||
</label>
|
||
<label class="f" style="max-width:640px">
|
||
<span>备注</span>
|
||
<textarea name="note" rows="3" maxlength="2000" placeholder="例如:和谁来的、什么印象">{{.MyNote}}</textarea>
|
||
</label>
|
||
<button class="btn primary" type="submit">保存</button>
|
||
</form>
|
||
</div>
|
||
{{else if .Place.Note}}
|
||
<div class="card" style="margin-bottom:16px"><h2>备注</h2><div class="note-block">{{.Place.Note}}</div></div>
|
||
{{end}}
|
||
|
||
{{if .IsAdmin}}
|
||
<div class="card" style="margin-bottom:16px">
|
||
<h2>地点图片</h2>
|
||
<div class="card-sub">
|
||
选好图就<b>自动上传并立即生效</b>,不用再点保存。
|
||
上传前会<b>在浏览器里转成 WebP</b>(长边 2000px),服务器只负责收下与转存,不做转码。
|
||
当前存储:<b>{{if eq .ImageBackend "s3"}}对象存储(S3 / R2){{else if eq .ImageBackend "local"}}服务器本地磁盘{{else}}{{.ImageBackend}}{{end}}</b>
|
||
,最多 {{.ImageMaxPer}} 张。传完访客在地点页点「图片」就能看到。
|
||
</div>
|
||
<div id="imgs-admin" data-src="{{.Place.SrcPK}}" data-name="{{.Place.Name}}">
|
||
<div class="imgs-grid" id="imgs-grid">
|
||
{{range .Images}}
|
||
<figure class="imgs-cell" data-id="{{.ID}}">
|
||
<img src="{{.URL}}" alt="{{if $.Place.Name}}{{$.Place.Name}} 的图片{{else}}地点图片{{end}}" loading="lazy">
|
||
<button type="button" class="imgs-del" data-id="{{.ID}}" title="删除这张" aria-label="删除这张图片">×</button>
|
||
</figure>
|
||
{{end}}
|
||
</div>
|
||
<div class="row" style="margin-top:12px;gap:10px">
|
||
<label class="btn primary" for="imgs-input" id="imgs-pick" style="cursor:pointer">+ 选择图片</label>
|
||
<input type="file" id="imgs-input" accept="image/*" multiple hidden>
|
||
<span class="muted" id="imgs-count" style="font-size:12.5px"></span>
|
||
</div>
|
||
<div class="imgs-hint" id="imgs-hint"></div>
|
||
</div>
|
||
</div>
|
||
{{end}}
|
||
|
||
{{if or .IsAdmin .Settings.MarksPublic}}
|
||
<div class="card" style="margin-bottom:16px">
|
||
<h2>推荐 / 踩雷</h2>
|
||
|
||
{{if .IsAdmin}}
|
||
{{/* 站长这里只放自己的结论;访客票数只读展示,避免同一块地方出现两套投票按钮 */}}
|
||
<div class="card-sub">值得再去就👍,踩雷避坑就👎。改主意了点一下已选中的按钮即可取消。</div>
|
||
<div class="vote-row" data-verdict-row data-src="{{.Place.SrcPK}}">
|
||
<span class="vote-label">我的结论</span>
|
||
<span class="vote-btns">
|
||
<button class="vote-btn up {{if eq .Verdict 1}}on{{end}}" type="button" data-verdict="1">👍 推荐</button>
|
||
<button class="vote-btn down {{if eq .Verdict -1}}on{{end}}" type="button" data-verdict="-1">👎 踩雷</button>
|
||
</span>
|
||
<span class="vote-tip" data-cur="{{.Verdict}}"></span>
|
||
</div>
|
||
<div class="vote-row">
|
||
<span class="vote-label">访客票数</span>
|
||
<span class="vote-btns">
|
||
<span class="vote-btn up ro">👍 <span class="up-n">{{num .Votes.Up}}</span></span>
|
||
<span class="vote-btn down ro">👎 <span class="down-n">{{num .Votes.Down}}</span></span>
|
||
</span>
|
||
<span class="vote-tip">只读</span>
|
||
</div>
|
||
{{else}}
|
||
<div class="card-sub">值得再去就👍,踩雷避坑就👎。改主意了点一下已选中的按钮即可取消。</div>
|
||
<div class="vote-row" data-visitor-row data-src="{{.Place.SrcPK}}">
|
||
<span class="vote-label">大家怎么说</span>
|
||
<span class="vote-btns">
|
||
<button class="vote-btn up" type="button" data-vote="1">👍 <span class="up-n">{{.Votes.Up}}</span></button>
|
||
<button class="vote-btn down" type="button" data-vote="-1">👎 <span class="down-n">{{.Votes.Down}}</span></button>
|
||
</span>
|
||
<span class="vote-tip" data-mine="{{.MyVote}}"></span>
|
||
</div>
|
||
{{end}}
|
||
</div>
|
||
<script>
|
||
(function () {
|
||
var POST = { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, credentials: 'same-origin' };
|
||
|
||
// 站长结论:AJAX 提交,避免整页刷新把还没保存的备注冲掉
|
||
var vrow = document.querySelector('.vote-row[data-verdict-row]');
|
||
if (vrow) {
|
||
var cur = Number(vrow.querySelector('.vote-tip').getAttribute('data-cur')) || 0;
|
||
var vtip = vrow.querySelector('.vote-tip');
|
||
var paintVerdict = function () {
|
||
vrow.querySelectorAll('.vote-btn').forEach(function (b) {
|
||
b.classList.toggle('on', Number(b.getAttribute('data-verdict')) === cur);
|
||
});
|
||
vtip.textContent = cur === 1 ? '已标记 · 推荐' : cur === -1 ? '已标记 · 踩雷' : '未标记';
|
||
};
|
||
paintVerdict();
|
||
vrow.querySelectorAll('.vote-btn').forEach(function (b) {
|
||
b.addEventListener('click', function () {
|
||
var v = Number(b.getAttribute('data-verdict'));
|
||
fetch('/api/verdict', Object.assign({
|
||
body: 'src_pk=' + vrow.getAttribute('data-src') + '&verdict=' + (cur === v ? 0 : v)
|
||
}, POST)).then(function (r) { return r.json(); }).then(function (d) {
|
||
if (d.error) { alert(d.error); return; }
|
||
cur = Number(d.verdict) || 0;
|
||
paintVerdict();
|
||
}).catch(function () {});
|
||
});
|
||
});
|
||
}
|
||
|
||
// 访客票:一人一票,再点一次撤回
|
||
var row = document.querySelector('.vote-row[data-visitor-row]');
|
||
if (!row) return;
|
||
var src = row.getAttribute('data-src');
|
||
var mine = Number(row.querySelector('.vote-tip').getAttribute('data-mine')) || 0;
|
||
function paint(m) {
|
||
mine = m;
|
||
row.querySelectorAll('.vote-btn').forEach(function (b) {
|
||
b.classList.toggle('on', Number(b.getAttribute('data-vote')) === mine);
|
||
});
|
||
var tip = row.querySelector('.vote-tip');
|
||
if (tip) tip.textContent = mine === 1 ? '你投了推荐' : mine === -1 ? '你投了踩雷' : '';
|
||
}
|
||
paint(mine);
|
||
row.querySelectorAll('.vote-btn').forEach(function (b) {
|
||
b.addEventListener('click', function () {
|
||
var v = Number(b.getAttribute('data-vote'));
|
||
b.disabled = true;
|
||
fetch('/api/vote', Object.assign({
|
||
body: 'src_pk=' + encodeURIComponent(src) + '&vote=' + v
|
||
}, POST)).then(function (r) { return r.json(); }).then(function (d) {
|
||
b.disabled = false;
|
||
if (d.error) { alert(d.error); return; }
|
||
row.querySelector('.up-n').textContent = d.up;
|
||
row.querySelector('.down-n').textContent = d.down;
|
||
paint(Number(d.mine) || 0);
|
||
}).catch(function () { b.disabled = false; });
|
||
});
|
||
});
|
||
})();
|
||
</script>
|
||
{{end}}
|
||
|
||
<div class="grid g2">
|
||
<div class="card">
|
||
<h2>位置</h2>
|
||
<div class="card-sub">底图:GCJ-02(与记录坐标同系,无需纠偏)</div>
|
||
<div id="place-map" class="map-preview" style="height:280px"></div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>到访记录</h2>
|
||
<div class="card-sub">最近 {{num (len .Visits)}} 次</div>
|
||
<div class="list" style="max-height:280px;overflow-y:auto">
|
||
{{range .Visits}}
|
||
<div class="item" style="grid-template-columns:34px 1fr auto">
|
||
<div class="ic" style="background:{{colorof .Color}}22;width:30px;height:30px;font-size:15px">{{iconor .Icon "📍"}}</div>
|
||
<div>
|
||
<div class="t" style="font-weight:400">{{day .Arrival}}</div>
|
||
<div class="m">{{hm .Arrival}} – {{if .Departure}}{{hm .Departure}}{{else}}进行中{{end}}{{if .Remark}} · {{.Remark}}{{end}}</div>
|
||
</div>
|
||
<div class="r">{{dur .DurationMin}}</div>
|
||
</div>
|
||
{{end}}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
window.addEventListener('load', function () {
|
||
window.rondMap.preview({ id: 'place-map', points: {{json .Points}} });
|
||
});
|
||
</script>
|
||
|
||
{{/* 全屏画廊:单张查看 + 左右切换 + 双击放大。
|
||
以前是把所有图竖着堆在一个滚动页里,手机上既不能左右滑、也不能放大。
|
||
容器常驻(内容由 JS 从缩略图墙同步),所以这里不用 if .Images 包着。 */}}
|
||
<div class="imgs-modal" id="imgs-modal" hidden>
|
||
<div class="imgs-count" id="imgs-counter"></div>
|
||
<button class="imgs-close" type="button" id="imgs-close" aria-label="关闭">×</button>
|
||
<button class="imgs-nav imgs-prev" type="button" id="imgs-prev" aria-label="上一张" hidden>‹</button>
|
||
<button class="imgs-nav imgs-next" type="button" id="imgs-next" aria-label="下一张" hidden>›</button>
|
||
<div class="imgs-stage" id="imgs-stage">
|
||
<img id="imgs-img" alt="{{if .Place.Name}}{{.Place.Name}} 的图片{{else}}地点图片{{end}}">
|
||
</div>
|
||
</div>
|
||
|
||
<script src="/static/js/place-image.js?v={{.AssetV}}"></script>
|
||
<script>
|
||
window.addEventListener('load', function () {
|
||
var grid = document.getElementById('imgs-grid');
|
||
var modal = document.getElementById('imgs-modal');
|
||
var maxPer = {{.ImageMaxPer}};
|
||
var admin = document.getElementById('imgs-admin');
|
||
|
||
/* ---------- 全屏画廊 ----------
|
||
* 地址有两个来源:站长页有缩略图墙,传图/删图之后就地在墙上读(它才是实时真相);
|
||
* 访客页没有那一整块,只能读服务端随页面发下来的这份。页头那个「图片 N」按钮
|
||
* 与画廊共用同一套数据,所以服务端渲染时还没有图的话,这里现补一个。
|
||
*/
|
||
var stage = document.getElementById('imgs-stage');
|
||
var viewImg = document.getElementById('imgs-img');
|
||
var counter = document.getElementById('imgs-counter');
|
||
var prevBtn = document.getElementById('imgs-prev');
|
||
var nextBtn = document.getElementById('imgs-next');
|
||
var serverURLs = {{if .ImageURLs}}{{json .ImageURLs}}{{else}}[]{{end}};
|
||
var urls = [];
|
||
var idx = 0;
|
||
|
||
// 判据必须是「缩略图墙在不在」,而不是「墙里有几张」:站长把图删光时墙还在、只是空了,
|
||
// 这时若按「墙是空的」回退到服务端那份,删掉的图就会被数回来。
|
||
function cellURLs() {
|
||
var g = document.getElementById('imgs-grid');
|
||
if (!g) return serverURLs;
|
||
return [].map.call(g.querySelectorAll('.imgs-cell img'), function (i) {
|
||
return i.getAttribute('src');
|
||
});
|
||
}
|
||
|
||
function paint() {
|
||
if (!urls.length) return;
|
||
viewImg.src = urls[idx];
|
||
counter.textContent = (idx + 1) + ' / ' + urls.length;
|
||
var many = urls.length > 1;
|
||
prevBtn.hidden = nextBtn.hidden = !many;
|
||
// 换图后回到「适应屏幕」状态,否则下一张会继承上一张的放大
|
||
stage.classList.remove('zoomed');
|
||
stage.scrollTop = stage.scrollLeft = 0;
|
||
}
|
||
function goto(i) {
|
||
if (!urls.length) return;
|
||
idx = (i % urls.length + urls.length) % urls.length;
|
||
paint();
|
||
}
|
||
|
||
function openGallery(on) {
|
||
modal.hidden = !on;
|
||
// 打开时锁住背景滚动,否则滑到尽头会把后面的页面一起带着滚
|
||
document.body.style.overflow = on ? 'hidden' : '';
|
||
if (on) {
|
||
urls = cellURLs();
|
||
idx = Math.min(idx, Math.max(0, urls.length - 1));
|
||
paint();
|
||
} else {
|
||
stage.classList.remove('zoomed');
|
||
}
|
||
}
|
||
|
||
function syncGallery() {
|
||
urls = cellURLs();
|
||
var btn = document.getElementById('imgs-open');
|
||
if (!urls.length) {
|
||
if (btn) btn.remove();
|
||
if (modal) modal.hidden = true;
|
||
return;
|
||
}
|
||
if (!btn) {
|
||
btn = document.createElement('button');
|
||
btn.className = 'btn';
|
||
btn.type = 'button';
|
||
btn.id = 'imgs-open';
|
||
var head = document.querySelector('.page-head .row');
|
||
if (head) head.appendChild(btn);
|
||
}
|
||
btn.innerHTML = '图片 <span class="muted">' + urls.length + '</span>';
|
||
if (idx >= urls.length) idx = 0;
|
||
counter.textContent = urls.length + ' 张';
|
||
}
|
||
|
||
modal.addEventListener('click', function (e) {
|
||
var cls = (e.target.className || '').indexOf('imgs-close') >= 0;
|
||
// 点空白或关闭按钮都关掉;点图片本身不关(可能是想放大)
|
||
if (e.target === modal || e.target === stage || cls) openGallery(false);
|
||
});
|
||
prevBtn.addEventListener('click', function (e) { e.stopPropagation(); goto(idx - 1); });
|
||
nextBtn.addEventListener('click', function (e) { e.stopPropagation(); goto(idx + 1); });
|
||
document.addEventListener('click', function (e) {
|
||
if (e.target && e.target.id === 'imgs-open') { idx = 0; openGallery(true); }
|
||
});
|
||
document.addEventListener('keydown', function (e) {
|
||
if (modal.hidden) return;
|
||
if (e.key === 'Escape') openGallery(false);
|
||
else if (e.key === 'ArrowLeft') goto(idx - 1);
|
||
else if (e.key === 'ArrowRight') goto(idx + 1);
|
||
});
|
||
|
||
// 手势:横向滑动=切换,轻点两下=放大/还原。两者必须在**同一个** touchend 里分流——
|
||
// 分成两个监听器时,滑动结束后那个监听器会把它当成一次「点按」,连滑两次就误触发双击放大。
|
||
var tx = 0, ty = 0, tracking = false, panning = false, lastTap = 0;
|
||
function toggleZoom() {
|
||
stage.classList.toggle('zoomed');
|
||
if (!stage.classList.contains('zoomed')) stage.scrollTop = stage.scrollLeft = 0;
|
||
}
|
||
stage.addEventListener('touchstart', function (e) {
|
||
if (e.touches.length !== 1) { tracking = false; return; }
|
||
tracking = true;
|
||
// 放大状态下拖动是「看局部」,不要再当成切换
|
||
panning = stage.classList.contains('zoomed');
|
||
tx = e.touches[0].clientX;
|
||
ty = e.touches[0].clientY;
|
||
}, { passive: true });
|
||
stage.addEventListener('touchend', function (e) {
|
||
if (!tracking) return;
|
||
tracking = false;
|
||
var t = e.changedTouches[0];
|
||
var dx = t.clientX - tx, dy = t.clientY - ty;
|
||
if (!panning && Math.abs(dx) > 40 && Math.abs(dx) > Math.abs(dy) * 1.4) {
|
||
goto(idx + (dx < 0 ? 1 : -1));
|
||
return;
|
||
}
|
||
// 手指基本没动才算「点按」,避免拖动松手被误判成双击
|
||
if (Math.abs(dx) > 12 || Math.abs(dy) > 12) return;
|
||
var now = Date.now();
|
||
if (now - lastTap < 300) { lastTap = 0; toggleZoom(); } else { lastTap = now; }
|
||
}, { passive: true });
|
||
stage.addEventListener('dblclick', function (e) { e.preventDefault(); toggleZoom(); });
|
||
|
||
{{if .IsAdmin}}
|
||
/* ---------- 站长:传图 / 删图 ---------- */
|
||
if (!admin || !window.rondImage) { syncGallery(); return; }
|
||
var srcPK = admin.getAttribute('data-src');
|
||
var input = document.getElementById('imgs-input');
|
||
var pick = document.getElementById('imgs-pick');
|
||
var hint = document.getElementById('imgs-hint');
|
||
var countEl = document.getElementById('imgs-count');
|
||
var ready = {{if .ImageReady}}true{{else}}false{{end}};
|
||
|
||
function setHint(msg, kind) {
|
||
hint.textContent = msg || '';
|
||
hint.className = 'imgs-hint' + (kind ? ' ' + kind : '');
|
||
}
|
||
function refreshCount() {
|
||
var n = grid.querySelectorAll('.imgs-cell').length;
|
||
countEl.textContent = n ? ('已有 ' + n + ' / ' + maxPer + ' 张') : ('还没有图片,最多 ' + maxPer + ' 张');
|
||
}
|
||
|
||
function cell(id, url) {
|
||
var fig = document.createElement('figure');
|
||
fig.className = 'imgs-cell';
|
||
fig.setAttribute('data-id', id);
|
||
var img = document.createElement('img');
|
||
img.src = url; img.loading = 'lazy';
|
||
img.alt = (admin && admin.dataset.name ? admin.dataset.name + ' 的图片' : '地点图片');
|
||
var del = document.createElement('button');
|
||
del.type = 'button'; del.className = 'imgs-del'; del.textContent = '×'; del.title = '删除这张';
|
||
del.setAttribute('aria-label', '删除这张图片');
|
||
del.setAttribute('data-id', id);
|
||
fig.appendChild(img); fig.appendChild(del);
|
||
return fig;
|
||
}
|
||
|
||
refreshCount();
|
||
if (!ready) {
|
||
setHint('当前图片存储配置有问题,先修好再上传(见 conf/app.ini 的 s3_* 与后台「图片存储」)。', 'err');
|
||
pick.classList.add('is-disabled');
|
||
pick.removeAttribute('for');
|
||
pick.title = '图片存储未就绪';
|
||
}
|
||
|
||
// 选中即上传:没有「保存」这一步,所以进度和结果必须在页面上说清楚
|
||
input.addEventListener('change', function () {
|
||
var files = [].slice.call(input.files || []);
|
||
input.value = ''; // 允许连着选同一张
|
||
if (!files.length) return;
|
||
admin.classList.add('imgs-busy');
|
||
var done = 0, failed = 0;
|
||
var finish = function () {
|
||
if (done + failed < files.length) return;
|
||
admin.classList.remove('imgs-busy');
|
||
refreshCount();
|
||
syncGallery();
|
||
if (failed) {
|
||
setHint('完成:成功 ' + done + ' 张,失败 ' + failed + ' 张。' +
|
||
(done ? '成功的那几张已经生效,刷新页面也在。' : ''), 'err');
|
||
} else {
|
||
setHint('已上传 ' + done + ' 张,已即时生效(访客刷新就能看到,不用再点保存)。', 'ok');
|
||
}
|
||
};
|
||
files.forEach(function (f, idx) {
|
||
setHint('正在处理 ' + (idx + 1) + '/' + files.length + ':' + f.name + '(浏览器转码 → 上传)…');
|
||
window.rondImage.toWebP(f).then(function (out) {
|
||
return window.rondImage.upload(srcPK, out.blob, out.width, out.height)
|
||
.then(function (d) {
|
||
if (d.error) throw new Error(d.error);
|
||
grid.appendChild(cell(d.id, d.url));
|
||
done++;
|
||
refreshCount();
|
||
});
|
||
}).catch(function (err) {
|
||
failed++;
|
||
setHint('「' + f.name + '」失败:' + (err && err.message ? err.message : err), 'err');
|
||
}).then(finish);
|
||
});
|
||
});
|
||
|
||
grid.addEventListener('click', function (e) {
|
||
var del = e.target.closest('.imgs-del');
|
||
if (!del) return;
|
||
var id = del.getAttribute('data-id');
|
||
del.disabled = true;
|
||
window.rondImage.remove(id).then(function (d) {
|
||
if (d.error) { setHint('删除失败:' + d.error, 'err'); del.disabled = false; return; }
|
||
var fig = grid.querySelector('.imgs-cell[data-id="' + id + '"]');
|
||
if (fig) fig.remove();
|
||
refreshCount();
|
||
syncGallery();
|
||
setHint('已删除该张,立即生效。', 'ok');
|
||
}).catch(function () { setHint('删除失败', 'err'); del.disabled = false; });
|
||
});
|
||
{{else}}
|
||
syncGallery();
|
||
{{end}}
|
||
});
|
||
</script>
|
||
{{end}}
|