feat: ReducedFeedsList in view

This commit is contained in:
devezhao 2020-03-30 23:36:48 +08:00
parent b8b4e500a3
commit 759be0411e
11 changed files with 190 additions and 79 deletions

View file

@ -92,7 +92,8 @@
"$addResizeHandler": true,
"$lang": true,
"$empty": true,
"$mp": true
"$mp": true,
"converEmoji": true
},
"rules": {
"react/jsx-no-target-blank": 0,

View file

@ -17,10 +17,12 @@ import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.server.Application;
import com.rebuild.server.configuration.ConfigEntry;
import com.rebuild.server.metadata.EntityHelper;
import com.rebuild.server.metadata.MetadataHelper;
import com.rebuild.server.metadata.entity.EasyMeta;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@ -95,6 +97,13 @@ public class ViewAddonsManager extends BaseLayoutManager {
refs.add(getEntityShow(field, mfRefs, applyType));
}
}
// 动态跟进
if (TYPE_TAB.equalsIgnoreCase(applyType)) {
Field relatedRecordOfFeeds = MetadataHelper.getField("Feeds", "relatedRecord");
refs.add(getEntityShow(relatedRecordOfFeeds, Collections.emptySet(), applyType));
}
return refs;
}
@ -164,6 +173,8 @@ public class ViewAddonsManager extends BaseLayoutManager {
? String.format("%s (%s)", EasyMeta.getLabel(field), show.getString("entityLabel"))
: String.format("%s (%s)", show.getString("entityLabel"), EasyMeta.getLabel(field));
show.put("entityLabel", entityLabel);
} else if (fieldEntity.getEntityCode() == EntityHelper.Feeds) {
show.put("entityLabel", "跟进");
}
return show;
}

View file

@ -94,7 +94,10 @@ public class ViewAddonsControll extends BaseControll implements PortalsConfigura
refs.add(new String[] { e.getName() + ViewAddonsManager.EF_SPLIT + field.getName(), label });
}
// TODO 相关项显示 动态-跟进
// 跟进动态
if (ViewAddonsManager.TYPE_TAB.equalsIgnoreCase(applyType)) {
refs.add(new String[] { "Feeds.relatedRecord", "跟进" });
}
JSON ret = JSONUtils.toJSONObject(
new String[] { "config", "refs" },

View file

@ -15,6 +15,7 @@ import cn.devezhao.persist4j.dialect.FieldType;
import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSON;
import com.rebuild.server.Application;
import com.rebuild.server.business.feeds.FeedsType;
import com.rebuild.server.configuration.portals.FieldValueWrapper;
import com.rebuild.server.metadata.EntityHelper;
import com.rebuild.server.metadata.MetadataHelper;
@ -123,6 +124,10 @@ public class RelatedListControll extends BaseControll {
String masterWhere = "(" + StringUtils.join(relatedFields, " = ''{0}'' or ") + " = ''{0}'')";
masterWhere = MessageFormat.format(masterWhere, recordOfMain);
if (relatedEntity.getEntityCode() == EntityHelper.Feeds) {
masterWhere += " and type = " + FeedsType.FOLLOWUP.getMask();
}
String baseSql = "select %s from " + relatedEntity.getName() + " where " + masterWhere;
Field primaryField = relatedEntity.getPrimaryField();

View file

@ -589,17 +589,17 @@ See LICENSE and COMMERCIAL in the project root for license information.
width: 250px;
}
.rich-content>.mores {
.rich-content>.appends {
font-size: 13px;
margin-bottom: 6px;
line-height: 1.6;
}
.rich-content>.mores span {
.rich-content>.appends span {
color: #666;
}
.rich-content>.mores .badge {
.rich-content>.appends .badge {
font-weight: normal;
padding: .07692rem .3154rem;
}

View file

@ -386,4 +386,36 @@ body {
.rbview-form .img-field .img-upload.img-thumbnail.zoom-in {
background-color: transparent;
}
/* over Feeds */
.feeds-list.inview {
margin-top: -13px;
}
.feeds-list.inview>div {
padding: 12px 1px 0;
position: relative;
}
.feeds-list.inview>div+div {
border-top: 1px solid #eee;
}
.feeds-list.inview .actions {
border: 0 none;
margin: 0;
padding: 0;
position: absolute;
right: 0;
bottom: 0;
}
.feeds-list.inview .feeds>.content .rich-content {
font-size: 1rem;
}
.feeds-list.inview .feeds>.content .rich-content>.appends {
font-size: 12px;
}

View file

@ -5,27 +5,12 @@ rebuild is dual-licensed under commercial and open source licenses (GPLv3).
See LICENSE and COMMERCIAL in the project root for license information.
*/
var EMOJIS = { '赞': 'rb_zan.png', '握手': 'rb_woshou.png', '耶': 'rb_ye.png', '抱拳': 'rb_baoquan.png', 'OK': 'rb_ok.png', '拍手': 'rb_paishou.png', '拜托': 'rb_baituo.png', '差评': 'rb_chaping.png', '微笑': 'rb_weixiao.png', '撇嘴': 'rb_piezui.png', '花痴': 'rb_huachi.png', '发呆': 'rb_fadai.png', '得意': 'rb_deyi.png', '大哭': 'rb_daku.png', '害羞': 'rb_haixiu.png', '闭嘴': 'rb_bizui.png', '睡着': 'rb_shuizhao.png', '敬礼': 'rb_jingli.png', '崇拜': 'rb_chongbai.png', '抱抱': 'rb_baobao.png', '忍住不哭': 'rb_renzhubuku.png', '尴尬': 'rb_ganga.png', '发怒': 'rb_fanu.png', '调皮': 'rb_tiaopi.png', '开心': 'rb_kaixin.png', '惊讶': 'rb_jingya.png', '呵呵': 'rb_hehe.png', '思考': 'rb_sikao.png', '哭笑不得': 'rb_kuxiaobude.png', '抓狂': 'rb_zhuakuang.png', '呕吐': 'rb_outu.png', '偷笑': 'rb_touxiao.png', '笑哭了': 'rb_xiaokule.png', '白眼': 'rb_baiyan.png', '傲慢': 'rb_aoman.png', '饥饿': 'rb_jie.png', '困': 'rb_kun.png', '吓': 'rb_xia.png', '流汗': 'rb_liuhan.png', '憨笑': 'rb_hanxiao.png', '悠闲': 'rb_youxian.png', '奋斗': 'rb_fendou.png', '咒骂': 'rb_zhouma.png', '疑问': 'rb_yiwen.png', '嘘': 'rb_xu.png', '晕': 'rb_yun.png', '惊恐': 'rb_jingkong.png', '衰': 'rb_shuai.png', '骷髅': 'rb_kulou.png', '敲打': 'rb_qiaoda.png', '再见': 'rb_zaijian.png', '无语': 'rb_wuyu.png', '抠鼻': 'rb_koubi.png', '鼓掌': 'rb_guzhang.png', '糗大了': 'rb_qiudale.png', '猥琐的笑': 'rb_weisuodexiao.png', '哼': 'rb_heng.png', '不爽': 'rb_bushuang.png', '打哈欠': 'rb_dahaqian.png', '鄙视': 'rb_bishi.png', '委屈': 'rb_weiqu.png', '安慰': 'rb_anwei.png', '坏笑': 'rb_huaixiao.png', '亲亲': 'rb_qinqin.png', '冷汗': 'rb_lenghan.png', '可怜': 'rb_kelian.png', '生病': 'rb_shengbing.png', '愉快': 'rb_yukuai.png', '幸灾乐祸': 'rb_xingzailehuo.png', '大便': 'rb_dabian.png', '干杯': 'rb_ganbei.png', '钱': 'rb_qian.png' }
// eslint-disable-next-line no-unused-vars
var converEmoji = function (text) {
const es = text.match(/\[(.+?)\]/g)
if (!es) return text
es.forEach((e) => {
const key = e.substr(1, e.length - 2)
if (EMOJIS[key]) {
const img = `<img class="emoji" src="${rb.baseUrl}/assets/img/emoji/${EMOJIS[key]}" alt="${key}" />`
text = text.replace(e, img)
}
})
return text.replace(/\n/g, '<br />')
}
// ~
class AnnouncementModal extends React.Component {
state = { ...this.props }
render() {
const contentHtml = converEmoji(this.props.content.replace(/\n/g, '<br>'))
const contentHtml = converEmoji(this.props.content.replace(/\n/g, '<br />'))
return <div className="modal" tabIndex={this.state.tabIndex || -1} ref={(c) => this._dlg = c}>
<div className="modal-dialog modal-dialog-centered">
<div className="modal-content">

View file

@ -48,59 +48,65 @@ class FeedsList extends React.Component {
</div>
}
{(this.state.data || []).map((item) => {
if (item.deleted) return null
const id = `feeds-${item.id}`
return <div key={id} id={id} className={`${item.id === this.state.focusFeed ? 'focus' : ''}`}>
<div className="feeds">
<div className="user">
<a className="user-show">
<div className="avatar"><img alt="Avatar" src={`${rb.baseUrl}/account/user-avatar/${item.createdBy[0]}`} /></div>
</a>
</div>
<div className="content">
<div className="meta">
<span className="float-right badge">{FeedsTypes[item.type] || '动态'}</span>
<a>{item.createdBy[1]}</a>
<p className="text-muted fs-12 m-0">
<span title={item.createdOn}>{item.createdOnFN}{item.createdOn !== item.modifedOn && <span className="text-danger" title={`编辑于 ${item.modifedOn}`}> (已编辑)</span>}</span>
&nbsp;&nbsp;·&nbsp;&nbsp;
{typeof item.scope === 'string' ? item.scope : <span>{item.scope[1]} <i title="团队成员可见" className="zmdi zmdi-accounts fs-14 down-1"></i></span>}
</p>
</div>
{__renderRichContent(item)}
</div>
</div>
<div className="actions">
<ul className="list-unstyled m-0">
{item.self && <li className="list-inline-item mr-2">
<a data-toggle="dropdown" href="#mores" className="fixed-icon" title="更多"><i className="zmdi zmdi-more"></i>&nbsp;</a>
<div className="dropdown-menu dropdown-menu-right">
{this._renderMoreMenu(item)}
<a className="dropdown-item" onClick={() => this._handleEdit(item)}><i className="icon zmdi zmdi-edit" /> 编辑</a>
<a className="dropdown-item" onClick={() => this._handleDelete(item.id)}><i className="icon zmdi zmdi-delete" />删除</a>
</div>
</li>
}
<li className="list-inline-item mr-3">
<a href="#thumbup" onClick={() => this._handleLike(item.id)} className={`fixed-icon ${item.myLike && 'text-primary'}`}>
<i className="zmdi zmdi-thumb-up"></i> {item.numLike > 0 && <span>({item.numLike})</span>}
</a>
</li>
<li className="list-inline-item">
<a href="#comments" onClick={() => this._toggleComment(item.id)} className={`fixed-icon ${item.shownComments && 'text-primary'}`}>
<i className="zmdi zmdi-comment-outline"></i>评论 {item.numComments > 0 && <span>({item.numComments})</span>}
</a>
</li>
</ul>
</div>
<span className={`${!item.shownComments && 'hide'}`}>{item.shownCommentsReal && <FeedsComments feeds={item.id} />}</span>
</div>
return this.renderItem(item)
})}
</div>
<Pagination ref={(c) => this._pagination = c} call={this.gotoPage} pageSize={40} />
</div>
}
renderItem(item) {
if (item.deleted) return null
const id = `feeds-${item.id}`
return (
<div key={id} id={id} className={`${item.id === this.state.focusFeed ? 'focus' : ''}`}>
<div className="feeds">
<div className="user">
<a className="user-show">
<div className="avatar"><img alt="Avatar" src={`${rb.baseUrl}/account/user-avatar/${item.createdBy[0]}`} /></div>
</a>
</div>
<div className="content">
<div className="meta">
<span className="float-right badge">{FeedsTypes[item.type] || '动态'}</span>
<a>{item.createdBy[1]}</a>
<p className="text-muted fs-12 m-0">
<span title={item.createdOn}>{item.createdOnFN}{item.createdOn !== item.modifedOn && <span className="text-danger" title={`编辑于 ${item.modifedOn}`}> (已编辑)</span>}</span>
&nbsp;&nbsp;·&nbsp;&nbsp;
{typeof item.scope === 'string' ? item.scope : <span>{item.scope[1]} <i title="团队成员可见" className="zmdi zmdi-accounts fs-14 down-1"></i></span>}
</p>
</div>
{__renderRichContent(item)}
</div>
</div>
<div className="actions">
<ul className="list-unstyled m-0">
{item.self && <li className="list-inline-item mr-2">
<a data-toggle="dropdown" href="#mores" className="fixed-icon" title="更多"><i className="zmdi zmdi-more"></i>&nbsp;</a>
<div className="dropdown-menu dropdown-menu-right">
{this._renderMoreMenu(item)}
<a className="dropdown-item" onClick={() => this._handleEdit(item)}><i className="icon zmdi zmdi-edit" /> 编辑</a>
<a className="dropdown-item" onClick={() => this._handleDelete(item.id)}><i className="icon zmdi zmdi-delete" />删除</a>
</div>
</li>
}
<li className="list-inline-item mr-3">
<a href="#thumbup" onClick={() => this._handleLike(item.id)} className={`fixed-icon ${item.myLike && 'text-primary'}`}>
<i className="zmdi zmdi-thumb-up"></i> {item.numLike > 0 && <span>({item.numLike})</span>}
</a>
</li>
<li className="list-inline-item">
<a href="#comments" onClick={() => this._toggleComment(item.id)} className={`fixed-icon ${item.shownComments && 'text-primary'}`}>
<i className="zmdi zmdi-comment-outline"></i>评论 {item.numComments > 0 && <span>({item.numComments})</span>}
</a>
</li>
</ul>
</div>
<span className={`${!item.shownComments && 'hide'}`}>{item.shownCommentsReal && <FeedsComments feeds={item.id} />}</span>
</div>
)
}
componentDidMount = () => this.props.fetchNow && this.fetchFeeds()
/**
* 加载数据
@ -391,13 +397,13 @@ class Pagination extends React.Component {
//
function __renderRichContent(e) {
//
const contentHtml = converEmoji(e.content.replace(/\n/g, '<br>'))
const contentHtml = converEmoji(e.content.replace(/\n/g, '<br />'))
const contentMore = e.contentMore || {}
return <div className="rich-content">
<div className="texts text-break"
dangerouslySetInnerHTML={{ __html: contentHtml }}
/>
<div className="mores">
<div className="appends">
{e.type === 4 && <div>
<div><span>日程时间 : </span> {contentMore.scheduleTime}</div>
{contentMore.finishTime && <div>
@ -420,13 +426,13 @@ function __renderRichContent(e) {
}
</div>
{(e.images || []).length > 0 && <div className="img-field">
${e.images.map((item, idx) => {
return (<span key={'img-' + item}>
<a title={$fileCutName(item)} onClick={() => RbPreview.create(e.images, idx)} className="img-thumbnail img-upload zoom-in">
<img src={`${rb.baseUrl}/filex/img/${item}?imageView2/2/w/100/interlace/1/q/100`} />
</a>
</span>)
})}
{e.images.map((item, idx) => {
return (<span key={'img-' + item}>
<a title={$fileCutName(item)} onClick={() => RbPreview.create(e.images, idx)} className="img-thumbnail img-upload zoom-in">
<img src={`${rb.baseUrl}/filex/img/${item}?imageView2/2/w/100/interlace/1/q/100`} />
</a>
</span>)
})}
</div>
}
{(e.attachments || []).length > 0 && <div className="file-field">

View file

@ -541,4 +541,19 @@ var $mp = {
$mp.__mp = null
}
}
}
var EMOJIS = { '赞': 'rb_zan.png', '握手': 'rb_woshou.png', '耶': 'rb_ye.png', '抱拳': 'rb_baoquan.png', 'OK': 'rb_ok.png', '拍手': 'rb_paishou.png', '拜托': 'rb_baituo.png', '差评': 'rb_chaping.png', '微笑': 'rb_weixiao.png', '撇嘴': 'rb_piezui.png', '花痴': 'rb_huachi.png', '发呆': 'rb_fadai.png', '得意': 'rb_deyi.png', '大哭': 'rb_daku.png', '害羞': 'rb_haixiu.png', '闭嘴': 'rb_bizui.png', '睡着': 'rb_shuizhao.png', '敬礼': 'rb_jingli.png', '崇拜': 'rb_chongbai.png', '抱抱': 'rb_baobao.png', '忍住不哭': 'rb_renzhubuku.png', '尴尬': 'rb_ganga.png', '发怒': 'rb_fanu.png', '调皮': 'rb_tiaopi.png', '开心': 'rb_kaixin.png', '惊讶': 'rb_jingya.png', '呵呵': 'rb_hehe.png', '思考': 'rb_sikao.png', '哭笑不得': 'rb_kuxiaobude.png', '抓狂': 'rb_zhuakuang.png', '呕吐': 'rb_outu.png', '偷笑': 'rb_touxiao.png', '笑哭了': 'rb_xiaokule.png', '白眼': 'rb_baiyan.png', '傲慢': 'rb_aoman.png', '饥饿': 'rb_jie.png', '困': 'rb_kun.png', '吓': 'rb_xia.png', '流汗': 'rb_liuhan.png', '憨笑': 'rb_hanxiao.png', '悠闲': 'rb_youxian.png', '奋斗': 'rb_fendou.png', '咒骂': 'rb_zhouma.png', '疑问': 'rb_yiwen.png', '嘘': 'rb_xu.png', '晕': 'rb_yun.png', '惊恐': 'rb_jingkong.png', '衰': 'rb_shuai.png', '骷髅': 'rb_kulou.png', '敲打': 'rb_qiaoda.png', '再见': 'rb_zaijian.png', '无语': 'rb_wuyu.png', '抠鼻': 'rb_koubi.png', '鼓掌': 'rb_guzhang.png', '糗大了': 'rb_qiudale.png', '猥琐的笑': 'rb_weisuodexiao.png', '哼': 'rb_heng.png', '不爽': 'rb_bushuang.png', '打哈欠': 'rb_dahaqian.png', '鄙视': 'rb_bishi.png', '委屈': 'rb_weiqu.png', '安慰': 'rb_anwei.png', '坏笑': 'rb_huaixiao.png', '亲亲': 'rb_qinqin.png', '冷汗': 'rb_lenghan.png', '可怜': 'rb_kelian.png', '生病': 'rb_shengbing.png', '愉快': 'rb_yukuai.png', '幸灾乐祸': 'rb_xingzailehuo.png', '大便': 'rb_dabian.png', '干杯': 'rb_ganbei.png', '钱': 'rb_qian.png' }
// 转换文字 emoji img 标签
var converEmoji = function (text) {
var es = text.match(/\[(.+?)\]/g)
if (!es) return text
$(es).each(function (e) {
var key = e.substr(1, e.length - 2)
if (EMOJIS[key]) {
var img = '<img class="emoji" src="' + rb.baseUrl + '/assets/img/emoji/' + EMOJIS[key] + '" />'
text = text.replace(e, img)
}
})
return text
}

View file

@ -197,7 +197,7 @@ class RelatedList extends React.Component {
return <div className="card" key={`rr-${item[0]}`}>
<div className="row">
<div className="col-10">
<a href={`#!/View/${this.props.entity}/${item[0]}`} onClick={this._handleView}>{item[1]}</a>
<a href={`#!/View/${this.props.entity.split('.')[0]}/${item[0]}`} onClick={this._handleView}>{item[1]}</a>
</div>
<div className="col-2 text-right">
<span className="fs-12 text-muted" title="最后修改时间">{item[2]}</span>
@ -229,6 +229,57 @@ class RelatedList extends React.Component {
}
}
// ~
// eslint-disable-next-line no-undef
class ReducedFeedsList extends FeedsList {
state = { ...this.props }
render() {
return (
<React.Fragment>
{!this.state.data && <RbSpinner />}
{(this.state.data && this.state.data.length === 0) && <div className="list-nodata"><span className="zmdi zmdi-chart-donut" /><p>暂无相关跟进</p></div>}
<div className="feeds-list inview">
{(this.state.data || []).map((item) => {
return this.renderItem({ ...item, self: false })
})}
</div>
{this.state.showMores
&& <div className="text-center load-mores"><div><button type="button" className="btn btn-secondary" onClick={() => this.fetchFeeds(this.__pageNo + 1)}>加载更多</button></div></div>}
</React.Fragment>
)
}
fetchFeeds(pageNo) {
const filter = { entity: 'Feeds', equation: 'AND', items: [] }
filter.items.push({ field: 'type', op: 'EQ', value: 2 })
filter.items.push({ field: 'relatedRecord', op: 'EQ', value: wpc.recordId })
this.__pageNo = pageNo || 1
const pageSize = 20
$.post(`/feeds/feeds-list?pageNo=${this.__pageNo}&sort=&type=&foucs=&pageSize=${pageSize}`, JSON.stringify(filter), (res) => {
const _data = res.data.data || []
const _list = (this.state.data || []).concat(_data)
this.setState({ data: _list, showMores: _data.length >= pageSize })
})
}
_toggleComment(feeds) {
return window.open(`${rb.baseUrl}/app/list-and-view?id=${feeds}`)
}
}
class MixRelatedList extends React.Component {
state = { ...this.props }
render() {
const entity = this.props.entity.split('.')[0]
if (entity === 'Feeds') {
return <ReducedFeedsList {...this.props} fetchNow={true} />
} else {
return <RelatedList {...this.props} />
}
}
}
//
const RbViewPage = {
_RbViewForm: null,
@ -334,7 +385,7 @@ const RbViewPage = {
const tabNav = $(`<li class="nav-item"><a class="nav-link" href="#${tabId}" data-toggle="tab" title="${this.entityLabel}">${this.entityLabel}</a></li>`).appendTo('.nav-tabs')
const tabPane = $(`<div class="tab-pane" id="${tabId}"></div>`).appendTo('.tab-content')
tabNav.find('a').click(function () {
tabPane.find('.related-list').length === 0 && renderRbcomp(<RelatedList entity={entity} master={that.__id} />, tabPane)
tabPane.find('.related-list').length === 0 && renderRbcomp(<MixRelatedList entity={entity} master={that.__id} />, tabPane)
})
})
this.updateVTabs()

View file

@ -4,6 +4,7 @@
<html>
<head>
<%@ include file="/_include/Head.jsp"%>
<link rel="stylesheet" type="text/css" href="${baseUrl}/assets/css/feeds.css">
<link rel="stylesheet" type="text/css" href="${baseUrl}/assets/css/view-page.css">
<title>${entityLabel}视图</title>
</head>
@ -95,6 +96,7 @@ window.__PageConfig = {
recordId: '${id}'
}
</script>
<script src="${baseUrl}/assets/js/feeds/feeds-list.jsx" type="text/babel"></script>
<script src="${baseUrl}/assets/js/rb-forms.jsx" type="text/babel"></script>
<script src="${baseUrl}/assets/js/rb-forms.exts.jsx" type="text/babel"></script>
<script src="${baseUrl}/assets/js/rb-view.jsx" type="text/babel"></script>