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 @@
<head>
<meta charset="utf-8">
<!-- 11-04 UGC 总开关关着时本页不展示,直接回首页(js/ugc.js) -->
<script src="../js/ugc.js?v=26110458" data-guard></script>
<script src="../js/ugc.js?v=26110459" data-guard></script>
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover">
<meta name="format-detection" content="telephone=no">
<title>孕妈广场</title>
@@ -668,6 +668,10 @@ button{font:inherit;color:inherit;border:0;background:none;padding:0;cursor:poin
.bar .ib.pop .ic{animation:pop .35s ease}
.bar.typing .ib{display:none}
.bar:not(.typing) .send{display:none}
/* 11-05 评论 / 回复带图(js/cm-pics.js):输入框里加图片按钮,选了图在底栏上方排一行缩略图 */
.bar{position:relative}
#cmPics{position:absolute;left:0;right:0;bottom:100%;padding:8px 14px 6px;background:#fff;border-top:1px solid var(--line)}
#cpic{width:28px;height:28px}
/* ---------- 投稿(11-01:用户可投稿 → 审核中 → 后台通过/不通过 → 下次打开提醒) ---------- */
.nav .nsp{flex:none;width:36px}
@@ -872,7 +876,8 @@ button{font:inherit;color:inherit;border:0;background:none;padding:0;cursor:poin
</div>
<div id="dsc"></div>
<div class="bar" id="bar">
<label class="ip"><svg class="ic"><use href="#i-pen"/></svg><input id="cin" type="text" maxlength="200" placeholder="说点什么…" enterkeyhint="send"><button class="send" id="csend" type="button" disabled>发送</button></label>
<label class="ip"><svg class="ic"><use href="#i-pen"/></svg><input id="cin" type="text" maxlength="200" placeholder="说点什么…" enterkeyhint="send"><button class="cmp-btn" id="cpic" type="button" aria-label="加图片"></button><button class="send" id="csend" type="button" disabled>发送</button></label>
<div class="cmp-strip" id="cmPics"></div>
<button class="ib lv" id="blk" type="button" aria-label="点赞"><svg class="ic"><use href="#i-heart"/></svg><span></span></button>
<button class="ib fv" id="bfv" type="button" aria-label="收藏"><svg class="ic"><use href="#i-star"/></svg><span></span></button>
<button class="ib" id="bcm" type="button" aria-label="评论"><svg class="ic"><use href="#i-chat"/></svg><span></span></button>
@@ -895,12 +900,13 @@ button{font:inherit;color:inherit;border:0;background:none;padding:0;cursor:poin
<div id="sheet" role="dialog" aria-modal="true"><i class="sg"></i><div class="sbd" id="sbd"></div></div>
<div class="toast" id="toast"></div>
<script src="topics.js?v=26110458"></script>
<script src="data.js?v=26110458"></script>
<script src="feed.js?v=26110458"></script>
<script src="../js/ava-pool.js?v=26110458"></script> <!-- 11-05 装饰头像分配表(各模块互不重复) -->
<script src="ceshi/meta.js?v=26110458"></script>
<script src="../js/share-card.js?v=26110458"></script>
<script src="topics.js?v=26110459"></script>
<script src="data.js?v=26110459"></script>
<script src="feed.js?v=26110459"></script>
<script src="../js/ava-pool.js?v=26110459"></script> <!-- 11-05 装饰头像分配表(各模块互不重复) -->
<script src="ceshi/meta.js?v=26110459"></script>
<script src="../js/share-card.js?v=26110459"></script>
<script src="../js/cm-pics.js?v=26110459"></script> <!-- 11-05 评论 / 回复带图 -->
<script>
(function () {
'use strict';
@@ -967,7 +973,7 @@ button{font:inherit;color:inherit;border:0;background:none;padding:0;cursor:poin
var CMD = null, CMERR = false;
/* 头像编码:数字 = av/NNNN.webp(旧代码头像);'av:c012' = av/c012.webp(卡通/可爱卡通);'/…' = 站内绝对路径(内置头像库 /_bridge/avatars/);其余 = assets/img/mom/xxx.jpg */
var avP = function (a) { return typeof a === 'number' ? 'av/' + ('000' + a).slice(-4) + '.webp' : a.indexOf('av:') === 0 ? 'av/' + a.slice(3) + '.webp' : a.charAt(0) === '/' ? a : '../assets/img/mom/' + a + '.jpg'; };
fetch('cm.json?v=26110458').then(function (r) { return r.json(); }).then(function (j) {
fetch('cm.json?v=26110459').then(function (r) { return r.json(); }).then(function (j) {
CMD = j; if (openId && NBY[openId]) { renderComments(NBY[openId]); syncBar(NBY[openId]); }
}).catch(function () { CMERR = true; if (openId && NBY[openId]) renderComments(NBY[openId]); });
function commentsOf(n) {
@@ -995,22 +1001,23 @@ button{font:inherit;color:inherit;border:0;background:none;padding:0;cursor:poin
var RC = {};
var myCm = function (id) {
var old = (ME.cm[id] || []).filter(function (c) { return !c.id; }).map(function (c) { return { t: c.t, ts: c.ts, s: c.s, st: 'local' }; });
var srv = RC[id] ? RC[id].mine.map(function (c) { return { id: c.id, t: c.text, ts: c.at, s: c.stage, st: c.status, why: c.reason, parent: c.parent, to: c.parentName }; }) : [];
var srv = RC[id] ? RC[id].mine.map(function (c) { return { id: c.id, t: c.text, ts: c.at, s: c.stage, st: c.status, why: c.reason, parent: c.parent, to: c.parentName, img: c.images }; }) : [];
return old.concat(srv.reverse());
};
var realCm = function (id) { return RC[id] ? RC[id].list : []; };
/* 11-03 用户:可以回复别人的评论。回复也先审核;楼里只有一层,回复回复也挂在同一楼,写「回复 XX」 */
var REPLY = null;
function extraReplies(id, k) {
var o = realCm(id).filter(function (c) { return c.parent === k; }).map(function (c) { return { n: c.nickname || '孕妈', t: c.text, to: c.parentName, ts: c.at }; });
var m = myCm(id).filter(function (c) { return c.parent === k; }).map(function (c) { return { n: MYNAME, me: 1, t: c.t, to: c.to, st: c.st, why: c.why, ts: c.ts }; });
var o = realCm(id).filter(function (c) { return c.parent === k; }).map(function (c) { return { n: c.nickname || '孕妈', t: c.text, to: c.parentName, ts: c.at, img: c.images }; });
var m = myCm(id).filter(function (c) { return c.parent === k; }).map(function (c) { return { n: MYNAME, me: 1, t: c.t, to: c.to, st: c.st, why: c.why, ts: c.ts, img: c.img }; });
return o.concat(m).sort(function (x, y) { return x.ts - y.ts; }); /* 楼里按时间先后 */
}
var cpH = function (a) { return window.MBCmPics ? MBCmPics.html(a) : ''; }; /* 11-05 评论里的图 */
function stTag(c) { return c.st === 'pending' ? '<span class="ci-st pending">审核中 · 通过后大家才能看到</span>' : c.st === 'rejected' ? '<span class="ci-st rejected">未通过' + (c.why ? ':' + esc(c.why) : '') + '</span>' : c.st === 'local' ? '<span class="ci-st local">仅自己可见</span>' : ''; }
function rpHTML(list, k, owner) {
return list.length ? '<div class="rp">' + list.map(function (x) {
return '<p' + (x.me ? ' class="rme' + (x.st === 'rejected' ? ' rej' : '') + '"' : ' data-rk="' + esc(k) + '" data-rn="' + esc(x.n) + '"') + '><b>' + esc(x.n) + '</b>' + (x.au ? '<span class="au">作者</span>' : '') + (x.me ? '<span class="au me">我</span>' : '') +
(x.to && x.to !== owner ? ' 回复 <b>' + esc(x.to) + '</b>' : '') + ':' + esc(x.t) + (x.me && x.st && x.st !== 'approved' ? '<br>' + stTag(x) : '') + '</p>';
(x.to && x.to !== owner ? ' 回复 <b>' + esc(x.to) + '</b>' : '') + ':' + esc(x.t) + cpH(x.img) + (x.me && x.st && x.st !== 'approved' ? '<br>' + stTag(x) : '') + '</p>';
}).join('') + '</div>' : '';
}
var cmN = function (n) { return commentsOf(n).length + realCm(n.id).length + myCm(n.id).filter(function (c) { return c.st !== 'rejected'; }).length; }; /* 含回复 */
@@ -1807,13 +1814,13 @@ button{font:inherit;color:inherit;border:0;background:none;padding:0;cursor:poin
var st = c.s || myStage();
var mk = c.id && c.st === 'approved' ? 'r:' + c.id : ''; /* 通过了的自己的评论,别人能回复,回复挂在这里 */
return '<div class="ci mine' + (c.st === 'rejected' ? ' rejected' : '') + '"><span class="me">我</span><div class="ci-b"' + (mk ? ' data-rk="' + mk + '" data-rn="' + esc(MYNAME) + '"' : '') + '><div class="ci-n"><b>' + esc(MYNAME) + '</b><span class="mt">我</span>' + (st ? '<i class="stg">' + esc(st) + '</i>' : '') + '</div>' +
'<p class="ci-t">' + esc(c.t) + '</p>' + (mk ? '<div class="ci-s"><span class="rpy">回复</span></div>' : '') + stTag(c) + (mk ? rpHTML(extraReplies(openId, mk), mk, MYNAME) : '') + '</div></div>'; /* 11-02 自己的评论也不显示时间 */
(c.t ? '<p class="ci-t">' + esc(c.t) + '</p>' : '') + cpH(c.img) + (mk ? '<div class="ci-s"><span class="rpy">回复</span></div>' : '') + stTag(c) + (mk ? rpHTML(extraReplies(openId, mk), mk, MYNAME) : '') + '</div></div>'; /* 11-02 自己的评论也不显示时间 */
}
var on = !!ME.cl[c.key], n = c.likes + (on ? 1 : 0);
var rp = rpHTML(c.r.concat(extraReplies(openId, c.key)), c.key, c.n);
return '<div class="ci"><img src="' + (c.av || avSrc(c.n)) + '" alt="" loading="lazy"><div class="ci-b" data-rk="' + esc(c.key) + '" data-rn="' + esc(c.n) + '"><div class="ci-n"><b>' + esc(c.n) + '</b>' + (c.s ? '<i class="stg">' + esc(c.s) + '</i>' : '') +
'<span class="lk' + (on ? ' on' : '') + '" data-cl="' + esc(c.key) + '" data-base="' + c.likes + '" role="button"><svg class="ic"><use href="#i-heart' + (on ? '-f' : '') + '"/></svg>' + n + '</span></div>' +
'<p class="ci-t">' + esc(c.t) + '</p><div class="ci-s"><span class="rpy">回复</span></div>' /* 11-02 用户:评论时间先不显示 */ + rp + '</div></div>';
(c.t ? '<p class="ci-t">' + esc(c.t) + '</p>' : '') + cpH(c.img) + '<div class="ci-s"><span class="rpy">回复</span></div>' /* 11-02 用户:评论时间先不显示 */ + rp + '</div></div>';
}
function agoOf(ts) {
var s = Math.max(0, (Date.now() - ts) / 1000);
@@ -1827,7 +1834,7 @@ button{font:inherit;color:inherit;border:0;background:none;padding:0;cursor:poin
$('#cmh').innerHTML = '评论 <small>共 ' + cmN(n) + ' 条</small>';
if (!CMD && !n.real && !CMERR) { $('#cml').innerHTML = mine.map(function (c) { return commentHTML(c, true); }).join('') + '<div class="cm-ld">评论加载中…</div>'; return; }
/* 别人发的、已审核通过的真实评论排在模拟评论前面 */
var rl = realCm(n.id).filter(function (c) { return !c.parent; }).map(function (c) { return { key: 'r:' + c.id, n: c.nickname || '孕妈', av: c.avatar || '', s: c.stage, t: c.text, likes: 0, r: [] }; });
var rl = realCm(n.id).filter(function (c) { return !c.parent; }).map(function (c) { return { key: 'r:' + c.id, n: c.nickname || '孕妈', av: c.avatar || '', s: c.stage, t: c.text, img: c.images, likes: 0, r: [] }; });
$('#cml').innerHTML = mine.map(function (c) { return commentHTML(c, true); }).join('') + rl.concat(cs).map(function (c) { return commentHTML(c); }).join('');
}
function syncBar(n, popK) {
@@ -1858,7 +1865,7 @@ button{font:inherit;color:inherit;border:0;background:none;padding:0;cursor:poin
'<div class="cm-in"><span class="me">我</span><button type="button" id="cmgo">留下你的温柔一句…</button></div>' +
'<div id="cml"></div><div class="cm-end">— 没有更多评论了 —</div></div>';
renderComments(n); syncBar(n); loadRC(n);
$('#cin').value = ''; $('#bar').classList.remove('typing'); $('#csend').disabled = true;
$('#cin').value = ''; if (CP) CP.clear(); $('#bar').classList.remove('typing'); $('#csend').disabled = true;
$('#dsc').scrollTop = 0; $('#dnav').classList.remove('on');
var dt = $('#dt'); dt.classList.add('show'); dt.setAttribute('aria-hidden', 'false');
if (push) { history.pushState({ gcNote: id }, '', setQuery(cur, id)); pushed = true; }
@@ -1921,30 +1928,38 @@ button{font:inherit;color:inherit;border:0;background:none;padding:0;cursor:poin
var w = $('#cmw'); if (w) $('#dsc').scrollTo({ top: w.offsetTop - 50, behavior: 'smooth' });
});
var cin = $('#cin');
/* 11-05 评论 / 回复可带图(最多 3 张,js/cm-pics.js):选了图底栏保持展开,只有图没有字也能发 */
var CP = window.MBCmPics ? MBCmPics.picker({ strip: $('#cmPics'), toast: toast, onChange: function () { $('#csend').disabled = !canSend(); $('#cpic').classList.toggle('on', CP.count() > 0); if (CP.count()) $('#bar').classList.add('typing'); } }) : null;
var canSend = function () { return !!cin.value.trim() || !!(CP && CP.count()); };
if (CP) { $('#cpic').innerHTML = MBCmPics.ICON; $('#cpic').addEventListener('mousedown', function (e) { e.preventDefault(); }); $('#cpic').addEventListener('click', function (e) { e.preventDefault(); $('#bar').classList.add('typing'); CP.pick(); }); }
else $('#cpic').remove();
cin.addEventListener('focus', function () { $('#bar').classList.add('typing'); });
cin.addEventListener('blur', function () { setTimeout(function () { if (!cin.value.trim()) { $('#bar').classList.remove('typing'); REPLY = null; cin.placeholder = '说点什么…'; } }, 120); });
cin.addEventListener('input', function () { $('#csend').disabled = !cin.value.trim(); });
cin.addEventListener('blur', function () { setTimeout(function () { if (!canSend()) { $('#bar').classList.remove('typing'); REPLY = null; cin.placeholder = '说点什么…'; } }, 120); });
cin.addEventListener('input', function () { $('#csend').disabled = !canSend(); });
cin.addEventListener('keydown', function (e) { if (e.key === 'Enter') { e.preventDefault(); send(); } });
$('#csend').addEventListener('click', send);
function send() {
var n = NBY[openId], v = cin.value.trim();
if (!n || !v || send.busy) return;
if (!n || !canSend() || send.busy) return;
if (CP && CP.busy()) { toast('图片还在上传,稍等一下'); return; }
var imgs = CP ? CP.urls() : [];
if (!v && !imgs.length) { toast('图片没传上去,删掉重新选一下'); return; }
if (!OPENID) { toast('请在小程序里打开后再评论'); return; }
/* 11-03:评论先交后台审核,通过后才公开;审核中只有自己看得到 */
send.busy = true; $('#csend').disabled = true;
fetch(API + '/comment?' + idq(), { method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ kind: 'post', target: n.id, title: n.title, text: v.slice(0, 200), stage: myStage(), parent: REPLY ? REPLY.k : '', parentName: REPLY ? REPLY.n : '' }) })
body: JSON.stringify({ kind: 'post', target: n.id, title: n.title, text: v.slice(0, 200), images: imgs, stage: myStage(), parent: REPLY ? REPLY.k : '', parentName: REPLY ? REPLY.n : '' }) })
.then(function (r) { return r.json(); }).then(function (j) {
send.busy = false;
if (!j || j.code !== 200) { $('#csend').disabled = !cin.value.trim(); toast((j && j.message) || '没发出去,稍后再试'); return; }
if (!j || j.code !== 200) { $('#csend').disabled = !canSend(); toast((j && j.message) || '没发出去,稍后再试'); return; }
var rc = RC[n.id] || (RC[n.id] = { list: [], mine: [] }); rc.mine.unshift(j.result);
var wasReply = !!REPLY; REPLY = null; cin.placeholder = '说点什么…';
haptic();
cin.value = ''; $('#csend').disabled = true; cin.blur(); $('#bar').classList.remove('typing');
cin.value = ''; if (CP) CP.clear(); $('#csend').disabled = true; cin.blur(); $('#bar').classList.remove('typing');
if (openId === n.id) { renderComments(n); syncBar(n); }
if (!wasReply) { var w = $('#cmw'); if (w) $('#dsc').scrollTo({ top: w.offsetTop - 50, behavior: 'smooth' }); }
toast(wasReply ? '回复已提交,审核通过后大家就能看到' : '已提交,审核通过后大家就能看到');
}).catch(function () { send.busy = false; $('#csend').disabled = !cin.value.trim(); toast('网络不太好,稍后再试'); });
}).catch(function () { send.busy = false; $('#csend').disabled = !canSend(); toast('网络不太好,稍后再试'); });
}
/* ================= 投稿(2026-11-01) =================