v26110459 广场帖子 / 专题页评论和回复可带图(最多 3 张,审核后公开)

This commit is contained in:
deploy
2026-11-05 09:50:56 +00:00
parent 4d1e5e95f1
commit 565a13eb60
26 changed files with 261 additions and 127 deletions

View File

@@ -3,7 +3,7 @@
(function () {
'use strict';
/* 页面版本号(YYMMDD+序号):显示在「关于兜爸心选」页底部;bump 时同步 head 注释与 ?v= */
var VER = '26110458';
var VER = '26110459';
const $ = (s, c) => (c || document).querySelector(s);
const $$ = (s, c) => Array.from((c || document).querySelectorAll(s));

96
js/cm-pics.js Normal file
View File

@@ -0,0 +1,96 @@
/* 11-05 评论 / 回复带图(用户:「评论的时候,或者回复别人评论时候,可以加上上传图片功能」)
孕妈广场帖子详情(guangchang/index.html)和专题页评论区(topic/_shared/tools.js)共用。
· 每条最多 3 张;选图后先在本机压到长边 1600、JPEG 0.85,再传 /api/upload(同投稿),拿回本站地址随评论提交。
· 后端 /plaza/comment 带 images[](只认本站 /api/upload 地址),图片和文字一起进后台「评论审核」,通过后大家才看得到。
· 显示:评论文字下面一排小方图(最多 3 张),点开全屏看大图,点任意处关闭。
用法:
var P = MBCmPics.picker({ strip: 放缩略图的元素, onChange: fn })
P.pick() 打开相册 · P.urls() 已传好的地址 · P.busy() 还有没传完的 · P.count() 已选张数 · P.clear() 清空
MBCmPics.html(images) → 评论里那排图的 HTML(没有图返回 '') */
(function () {
if (window.MBCmPics) return;
var MAX = 3;
var st = document.createElement('style');
st.textContent =
'.cmp-strip{display:flex;gap:8px;padding:8px 2px 2px;overflow-x:auto}.cmp-strip:empty{display:none}' +
'.cmp-t{position:relative;flex:none;width:58px;height:58px;border-radius:10px;overflow:hidden;background:#F1E9E5}' +
'.cmp-t img{width:100%;height:100%;object-fit:cover;display:block}' +
'.cmp-t.up::after{content:"上传中";position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.38);color:#fff;font-size:11px}' +
'.cmp-t.bad::after{content:"失败";position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(200,40,40,.5);color:#fff;font-size:11px}' +
'.cmp-t button{position:absolute;right:2px;top:2px;z-index:1;width:18px;height:18px;border-radius:50%;background:rgba(0,0,0,.55);color:#fff;font-size:13px;line-height:18px;text-align:center;padding:0;border:0}' +
'.cmp-g{display:flex;gap:6px;margin-top:6px}' +
'.cmp-g img{width:76px;height:76px;border-radius:8px;object-fit:cover;background:#F1E9E5;cursor:zoom-in}' +
'.cmp-btn{flex:none;display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;padding:0;border:0;border-radius:50%;background:none;color:#9C8781}' +
'.cmp-btn svg{width:21px;height:21px}' +
'.cmp-btn.on{color:#F4593C}' +
'.cmp-lb{position:fixed;inset:0;z-index:10000;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.92)}' +
'.cmp-lb img{max-width:100%;max-height:100%;object-fit:contain}';
(document.head || document.documentElement).appendChild(st);
var ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="15" rx="3"/><circle cx="9" cy="10.5" r="1.8"/><path d="M21 16l-5-5-8 8"/></svg>';
var esc = function (s) { return String(s).replace(/[&<>"']/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]; }); };
function shrink(f) {
return new Promise(function (res) {
var u = URL.createObjectURL(f), im = new Image();
im.onload = function () {
var k = Math.min(1, 1600 / Math.max(im.width, im.height)), c = document.createElement('canvas');
c.width = Math.round(im.width * k); c.height = Math.round(im.height * k);
c.getContext('2d').drawImage(im, 0, 0, c.width, c.height); URL.revokeObjectURL(u);
c.toBlob(function (b) { res(b || f); }, 'image/jpeg', .85);
};
im.onerror = function () { URL.revokeObjectURL(u); res(f); };
im.src = u;
});
}
function picker(o) {
var pics = [], ups = 0, strip = o.strip;
var fi = document.createElement('input'); fi.type = 'file'; fi.accept = 'image/*'; fi.multiple = true; fi.hidden = true;
document.body.appendChild(fi);
var ch = function () { if (o.onChange) o.onChange(); };
fi.addEventListener('change', function () {
var fs = [].slice.call(fi.files || []); fi.value = '';
if (pics.length + fs.length > MAX && o.toast) o.toast('每条最多带 ' + MAX + ' 张图');
fs.slice(0, MAX - pics.length).forEach(function (f) {
var it = { url: null, el: document.createElement('span') };
it.el.className = 'cmp-t up';
it.el.innerHTML = '<img alt=""><button type="button" aria-label="删除这张">×</button>';
it.el.querySelector('img').src = URL.createObjectURL(f);
it.el.querySelector('button').addEventListener('mousedown', function (e) { e.preventDefault(); });
it.el.querySelector('button').onclick = function () { pics = pics.filter(function (x) { return x !== it; }); it.el.remove(); ch(); };
strip.appendChild(it.el); pics.push(it); ups++;
shrink(f).then(function (b) { var fd = new FormData(); fd.append('file', b, 'comment.jpg'); return fetch('/api/upload', { method: 'POST', body: fd }); })
.then(function (r) { return r.json(); }).then(function (j) { it.url = j && j.result && j.result.url || null; if (!it.url) throw 0; it.el.classList.remove('up'); })
.catch(function () { if (o.toast) o.toast('图片上传失败,可以删掉重新选'); it.el.classList.remove('up'); it.el.classList.add('bad'); })
.then(function () { ups--; ch(); });
});
ch();
});
return {
pick: function () { if (pics.length >= MAX) { if (o.toast) o.toast('每条最多带 ' + MAX + ' 张图'); return; } fi.click(); },
urls: function () { return pics.map(function (x) { return x.url; }).filter(Boolean); },
busy: function () { return ups > 0; },
count: function () { return pics.length; },
clear: function () { pics = []; strip.innerHTML = ''; ch(); }
};
}
function html(imgs) {
imgs = (imgs || []).filter(function (u) { return typeof u === 'string' && /^(https?:)?\/\//.test(u) || /^\//.test(u); }).slice(0, MAX);
return imgs.length ? '<div class="cmp-g">' + imgs.map(function (u) { return '<img src="' + esc(u) + '" alt="" loading="lazy" data-cmp="' + esc(u) + '">'; }).join('') + '</div>' : '';
}
/* 点评论里的图 → 全屏大图;点任意处关 */
document.addEventListener('click', function (e) {
var im = e.target.closest && e.target.closest('[data-cmp]');
if (!im) return;
e.preventDefault(); e.stopPropagation();
var lb = document.createElement('div'); lb.className = 'cmp-lb';
lb.innerHTML = '<img alt="" src="' + esc(im.dataset.cmp) + '">';
lb.addEventListener('click', function () { lb.remove(); });
document.body.appendChild(lb);
}, true);
window.MBCmPics = { picker: picker, html: html, ICON: ICON, MAX: MAX };
})();

View File

@@ -244,5 +244,5 @@
}
/* feed.js 首页默认不加载(index.html 只引了 topics.js),这里按需拉一次 */
if (window.GC_FEED) init();
else { var fs = document.createElement('script'); fs.src = 'guangchang/feed.js?v=26110458'; fs.onload = fs.onerror = init; document.body.appendChild(fs); }
else { var fs = document.createElement('script'); fs.src = 'guangchang/feed.js?v=26110459'; fs.onload = fs.onerror = init; document.body.appendChild(fs); }
})();

View File

@@ -6,9 +6,9 @@
(function () {
var sec = document.getElementById('gcHome');
if (!sec) return;
var l = document.createElement('link'); l.rel = 'stylesheet'; l.href = 'js/gch/v4.css?v=26110458';
var l = document.createElement('link'); l.rel = 'stylesheet'; l.href = 'js/gch/v4.css?v=26110459';
document.head.appendChild(l);
var s = document.createElement('script'); s.src = 'js/gch/v4.js?v=26110458';
var s = document.createElement('script'); s.src = 'js/gch/v4.js?v=26110459';
s.onload = function () { if (sec.querySelector('.g4-foot')) merge(); else sec.addEventListener('gc4-ready', merge, { once: true }); }; /* v4 要先拉 feed.js 才渲染 */
document.body.appendChild(s);

View File

@@ -9,7 +9,7 @@
if (/[?&]kit=0(?:&|$)/.test(location.search)) return;
var box = document.getElementById('kit'), sec = document.getElementById('kitHome');
if (!box || !sec) return;
var V = '26110458';
var V = '26110459';
var l = document.createElement('link'); l.rel = 'stylesheet'; l.href = 'js/kit.css?v=' + V; document.head.appendChild(l);
document.documentElement.classList.add('has-kit');

View File

@@ -8,7 +8,7 @@
postMessage 不是实时投递,微信在分享那一刻才派发,所以每次换内容都要再报一次;子页面没引 jweixin,这里按需加载。 */
(function (g) {
'use strict';
var V = '26110458';
var V = '26110459';
var isWx = /MicroMessenger/i.test(navigator.userAgent || '');
var waiting = [], loading = false;
function ready(cb) {