@@ -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 { '&' : '&' , '<' : '<' , '>' : '>' , '"' : '"' , "'" : ''' } [ 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 } ;
} ) ( ) ;