mirror of
https://github.com/getrebuild/rebuild.git
synced 2025-09-23 07:04:45 +08:00
* Refactor batch update editor and improve NTEXT handling * Enable batch approval and update approval UI * Update table and treemap chart rendering options * Add editable record mode to approval flow nodes * Enable editable approval records for approvers * Allow editing details during approval process * Refactor form modal creation and extra button logic
1064 lines
36 KiB
Java
1064 lines
36 KiB
Java
/*!
|
||
Copyright (c) REBUILD <https://getrebuild.com/> and/or its owners. All rights reserved.
|
||
|
||
rebuild is dual-licensed under commercial and open source licenses (GPLv3).
|
||
See LICENSE and COMMERCIAL in the project root for license information.
|
||
*/
|
||
/* global SelectReport, FeedEditorDlg, LightTaskDlg, ApprovalProcessor, SopProcessor */
|
||
|
||
const wpc = window.__PageConfig || {}
|
||
|
||
const TYPE_DIVIDER = '$DIVIDER$'
|
||
const TYPE_REFFORM = '$REFFORM$'
|
||
|
||
//~~ 视图
|
||
class RbViewForm extends React.Component {
|
||
constructor(props) {
|
||
super(props)
|
||
this.state = { ...props }
|
||
|
||
this.onViewEditable = this.props.onViewEditable
|
||
if (this.onViewEditable) this.onViewEditable = wpc.onViewEditable !== false
|
||
if (window.__LAB_VIEWEDITABLE === false) this.onViewEditable = false
|
||
|
||
// temp for `saveSingleFieldValue`
|
||
this.__FormData = {}
|
||
this._verticalLayout42 = window.__LAB_VERTICALLAYOUT
|
||
}
|
||
|
||
render() {
|
||
return (
|
||
<RF>
|
||
{this.state.fjsAlertMessage}
|
||
<div className={`rbview-form form-layout ${this._verticalLayout42 && 'vertical38'}`} ref={(c) => (this._viewForm = c)}>
|
||
{this.state.formComponent}
|
||
</div>
|
||
</RF>
|
||
)
|
||
}
|
||
|
||
componentDidMount() {
|
||
$.get(`/app/${this.props.entity}/view-model?id=${this.props.id}`, (res) => {
|
||
// 有错误
|
||
if (res.error_code > 0 || !!res.data.error) {
|
||
const err = res.data.error || res.error_msg
|
||
this.renderViewError(err)
|
||
return
|
||
}
|
||
|
||
let hadApproval = res.data.hadApproval
|
||
if (hadApproval === 2 || hadApproval === 10) this.onViewEditable = false // be:4.2
|
||
let hadAlert = null
|
||
let hadSop = res.data.hadSop && rb.commercial > 1
|
||
if (wpc.type === 'DetailView') {
|
||
if (hadApproval === 2 || hadApproval === 10) {
|
||
if (window.RbViewPage) window.RbViewPage.setReadonly()
|
||
else $('.J_edit, .J_delete').remove()
|
||
|
||
hadAlert = <RbAlertBox message={hadApproval === 2 ? $L('主记录正在审批中,明细记录不能编辑') : $L('主记录已审批完成,明细记录不能编辑')} />
|
||
}
|
||
hadApproval = null
|
||
}
|
||
|
||
this.__ViewData = {}
|
||
this.__lastModified = res.data.lastModified || 0
|
||
if (res.data.onViewEditable === false) this.onViewEditable = false
|
||
this._verticalLayout42 = this._verticalLayout42 || res.data.verticalLayout === 1 || res.data.verticalLayout === 3
|
||
|
||
let _dividerRefs = []
|
||
const VFORM = (
|
||
<RF>
|
||
{hadAlert}
|
||
{hadApproval && <ApprovalProcessor id={this.props.id} entity={this.props.entity} />}
|
||
{hadSop && <SopProcessor id={this.props.id} entity={this.props.entity} />}
|
||
|
||
<div className="row">
|
||
{res.data.elements.map((item) => {
|
||
if (![TYPE_DIVIDER, TYPE_REFFORM].includes(item.field)) this.__ViewData[item.field] = item.value
|
||
if (item.field === TYPE_REFFORM) this.__hasRefform = true
|
||
|
||
item.$$$parent = this
|
||
if (item.field === TYPE_DIVIDER && item.collapsed) {
|
||
item.ref = (c) => _dividerRefs.push(c)
|
||
}
|
||
return detectViewElement(item, this.props.entity)
|
||
})}
|
||
</div>
|
||
</RF>
|
||
)
|
||
|
||
this.setState({ formComponent: VFORM }, () => {
|
||
// v3.9 默认收起
|
||
_dividerRefs.forEach((d) => d._toggle())
|
||
// v4.0 title
|
||
if (res.data.recordName) {
|
||
const $title = $('.view-header>h3.title')
|
||
// $title.text(`${$title.attr('title')} : ${res.data.recordName}`)
|
||
$title.text(res.data.recordName)
|
||
}
|
||
|
||
this.hideLoading()
|
||
if (window.FrontJS) {
|
||
window.FrontJS.View._trigger('open', [res.data])
|
||
}
|
||
})
|
||
})
|
||
}
|
||
|
||
renderViewError(message) {
|
||
this.setState({ formComponent: _renderError(message) }, () => this.hideLoading())
|
||
$('.view-operating .view-action').empty()
|
||
}
|
||
|
||
hideLoading() {
|
||
const ph = parent && parent.RbViewModal ? parent.RbViewModal.holder(this.state.id) : null
|
||
ph && ph.hideLoading()
|
||
}
|
||
|
||
showAgain(handle) {
|
||
this._checkDrityData(handle)
|
||
}
|
||
|
||
// 脏数据检查
|
||
_checkDrityData(handle) {
|
||
if (!this.__lastModified || !this.state.id) return
|
||
|
||
$.get(`/app/entity/extras/record-last-modified?id=${this.state.id}`, (res) => {
|
||
if (res.error_code === 0) {
|
||
if (res.data.lastModified !== this.__lastModified) {
|
||
handle && handle.showLoading()
|
||
setTimeout(() => location.reload(), 200)
|
||
}
|
||
} else if (res.error_msg === 'NO_EXISTS') {
|
||
this.renderViewError($L('记录已经不存在,可能已被其他用户删除'))
|
||
$('.view-operating').empty()
|
||
}
|
||
})
|
||
}
|
||
|
||
// @see RbForm in `rb-forms.js`
|
||
|
||
setFieldValue(field, value, error) {
|
||
this.__FormData[field] = { value: value, error: error }
|
||
// eslint-disable-next-line no-console
|
||
if (rb.env === 'dev') console.log('FV ...', JSON.stringify(this.__FormData))
|
||
}
|
||
|
||
setFieldUnchanged(field) {
|
||
delete this.__FormData[field]
|
||
// eslint-disable-next-line no-console
|
||
if (rb.env === 'dev') console.log('FV ...', JSON.stringify(this.__FormData))
|
||
}
|
||
|
||
// 保存单个字段值
|
||
saveSingleFieldValue(fieldComp) {
|
||
setTimeout(() => this._saveSingleFieldValue(fieldComp), 30)
|
||
}
|
||
|
||
_saveSingleFieldValue(fieldComp, weakMode) {
|
||
const fieldName = fieldComp.props.field
|
||
const fieldValue = this.__FormData[fieldName]
|
||
// Unchanged
|
||
if (!fieldValue) {
|
||
fieldComp.toggleEditMode(false)
|
||
return
|
||
}
|
||
if (fieldValue.error) return RbHighbar.create(fieldValue.error)
|
||
|
||
const data = {
|
||
metadata: { entity: this.props.entity, id: this.props.id },
|
||
[fieldName]: fieldValue.value,
|
||
}
|
||
|
||
const $btn = $(fieldComp._fieldText).find('.edit-oper .btn').button('loading')
|
||
let url = '/app/entity/record-save?singleField=true'
|
||
if (weakMode) url += '&weakMode=' + weakMode
|
||
$.post(url, JSON.stringify(data), (res) => {
|
||
$btn.button('reset')
|
||
|
||
if (res.error_code === 0) {
|
||
this.setFieldUnchanged(fieldName)
|
||
this.__ViewData[fieldName] = res.data[fieldName]
|
||
fieldComp.toggleEditMode(false, res.data[fieldName])
|
||
|
||
// 刷新列表
|
||
parent && parent.RbListPage && parent.RbListPage.reload(this.props.id, true)
|
||
|
||
// 刷新本页
|
||
if ((res.data && res.data.forceReload) || this.__hasRefform || this.__hasEaButton) {
|
||
setTimeout(() => RbViewPage.reload(), 200)
|
||
}
|
||
} else if (res.error_code === 499) {
|
||
// 重复记录
|
||
// eslint-disable-next-line react/jsx-no-undef
|
||
renderRbcomp(<RepeatedViewer entity={this.props.entity} data={res.data} />)
|
||
} else if (res.error_code === 497) {
|
||
// 弱校验
|
||
const that = this
|
||
const msg_id = res.error_msg.split('$$$$')
|
||
RbAlert.create(msg_id[0], {
|
||
onConfirm: function () {
|
||
this.hide()
|
||
that._saveSingleFieldValue(fieldComp, msg_id[1])
|
||
},
|
||
})
|
||
} else {
|
||
RbHighbar.error(res.error_msg)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
const detectViewElement = function (item, entity) {
|
||
if (!window.detectElement) throw 'detectElement undef'
|
||
item.onView = true
|
||
item.editMode = false
|
||
return window.detectElement(item, entity)
|
||
}
|
||
|
||
const _renderError = (message) => {
|
||
return (
|
||
<div className="alert alert-danger alert-icon mt-5 w-75" style={{ margin: '0 auto' }}>
|
||
<div className="icon">
|
||
<i className="zmdi zmdi-alert-triangle" />
|
||
</div>
|
||
<div className="message" dangerouslySetInnerHTML={{ __html: `<strong>${$L('抱歉!')}!</strong> ${message}` }} />
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// ~ 相关项列表
|
||
class RelatedList extends React.Component {
|
||
constructor(props) {
|
||
super(props)
|
||
this.state = { ...props }
|
||
|
||
// 相关配置
|
||
this.__searchSort = props.isDetail ? 'autoId:asc' : null
|
||
this.__searchKey = null
|
||
this.__pageNo = 1
|
||
|
||
this.__listExtraLink = null
|
||
this.__listClass = null
|
||
this.__listNoData = (
|
||
<div className="list-nodata">
|
||
<span className="zmdi zmdi-info-outline" />
|
||
<p>{$L('暂无数据')}</p>
|
||
</div>
|
||
)
|
||
|
||
// default:CARD
|
||
if (props.showViewMode) {
|
||
this.__viewModeKey = `RelatedListViewMode-${props.entity.split('.')[0]}`
|
||
let vm = $storage.get(this.__viewModeKey)
|
||
if (!vm) vm = props.defaultList ? 'LIST' : null
|
||
this.state.viewMode = vm || 'CARD'
|
||
}
|
||
}
|
||
|
||
render() {
|
||
const optionName = $random('vm-')
|
||
const isListView = this.props.showViewMode && this.state.viewMode === 'LIST'
|
||
const entityName = this.props.entity.split('.')[0] // ENTITY.PKNAME
|
||
|
||
return (
|
||
<div className={`related-list ${this.state.dataList || isListView ? '' : 'rb-loading rb-loading-active'}`} data-entity={entityName}>
|
||
{!(this.state.dataList || isListView) && <RbSpinner />}
|
||
|
||
<div className="related-toolbar">
|
||
<div className="row">
|
||
<div className="col-5">
|
||
<div className="input-group input-search float-left">
|
||
<input className="form-control" type="text" placeholder={$L('快速查询')} maxLength="40" ref={(c) => (this._$quickSearch = c)} onKeyDown={(e) => e.keyCode === 13 && this.search()} />
|
||
<span className="input-group-btn">
|
||
<button className="btn btn-secondary" type="button" onClick={() => this.search()}>
|
||
<i className="icon zmdi zmdi-search" />
|
||
</button>
|
||
</span>
|
||
</div>
|
||
{this.__listExtraLink}
|
||
</div>
|
||
<div className="col-7 text-right">
|
||
<div className="fjs-dock"></div>
|
||
<div className="btn-group w-auto">
|
||
<button type="button" className="btn btn-link pr-0 text-right" data-toggle="dropdown" disabled={isListView}>
|
||
{this.state.sortDisplayText || $L('默认排序')} <i className="icon zmdi zmdi-chevron-down up-1" />
|
||
</button>
|
||
{this.renderSorts()}
|
||
</div>
|
||
|
||
{this.props.showViewMode && (
|
||
<div className="btn-group btn-group-toggle w-auto ml-3 switch-view-mode">
|
||
<label className={`btn btn-light ${this.state.viewMode === 'LIST' ? '' : 'active'}`} title={$L('卡片视图')}>
|
||
<input type="radio" name={optionName} value="CARD" checked={this.state.viewMode !== 'LIST'} onChange={(e) => this.switchViewMode(e)} />
|
||
<i className="icon mdi mdi-view-agenda-outline" />
|
||
</label>
|
||
<label className={`btn btn-light ${this.state.viewMode === 'LIST' ? 'active' : ''}`} title={$L('列表视图')}>
|
||
<input type="radio" name={optionName} value="LIST" checked={this.state.viewMode === 'LIST'} onChange={(e) => this.switchViewMode(e)} />
|
||
<i className="icon mdi mdi-view-module-outline fs-22 down-1" />
|
||
</label>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{this.renderData()}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
renderSorts() {
|
||
return (
|
||
<div className="dropdown-menu dropdown-menu-right" x-placement="bottom-end">
|
||
<a className="dropdown-item" data-sort="modifiedOn:desc" onClick={(e) => this.search(e)}>
|
||
{$L('最近修改')}
|
||
</a>
|
||
<a className="dropdown-item" data-sort="createdOn:desc" onClick={(e) => this.search(e)}>
|
||
{$L('最近创建')}
|
||
</a>
|
||
<a className="dropdown-item" data-sort="createdOn" onClick={(e) => this.search(e)}>
|
||
{$L('最早创建')}
|
||
</a>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
renderData() {
|
||
return (
|
||
<RF>
|
||
{this.state.dataList && this.state.dataList.length === 0 && this.__listNoData}
|
||
|
||
{this.state.dataList && this.state.dataList.length > 0 && (
|
||
<div className={this.__listClass || ''}>
|
||
{(this.state.dataList || []).map((item) => {
|
||
return this.renderItem(item)
|
||
})}
|
||
</div>
|
||
)}
|
||
|
||
{this.state.showMore && (
|
||
<div className="text-center mt-3 pb-3">
|
||
<a className="show-more-pill" onClick={() => this.fetchData(1)}>
|
||
{$L('显示更多')}
|
||
</a>
|
||
</div>
|
||
)}
|
||
</RF>
|
||
)
|
||
}
|
||
|
||
renderItem(item) {
|
||
return <div>{JSON.stringify(item)}</div>
|
||
}
|
||
|
||
componentDidMount() {
|
||
this.fetchData()
|
||
}
|
||
|
||
fetchData(append) {
|
||
this.__pageNo = this.__pageNo || 1
|
||
if (append) this.__pageNo += append
|
||
|
||
const pageSize = 20
|
||
const url = `/project/tasks/related-list?pageNo=${this.__pageNo}&pageSize=${pageSize}&sort=${this.__searchSort || ''}&related=${this.props.mainid}`
|
||
$.get(url, (res) => {
|
||
if (res.error_code !== 0) return RbHighbar.error(res.error_msg)
|
||
|
||
const data = (res.data || {}).data || []
|
||
const list = append ? (this.state.dataList || []).concat(data) : data
|
||
this.setState({ dataList: list, showMore: data.length >= pageSize })
|
||
})
|
||
}
|
||
|
||
search(e) {
|
||
let sort = null
|
||
if (e && e.currentTarget) {
|
||
sort = $(e.currentTarget).data('sort')
|
||
this.setState({ sortDisplayText: $(e.currentTarget).text() })
|
||
}
|
||
|
||
this.__searchSort = sort || this.__searchSort
|
||
this.__searchKey = $(this._$quickSearch).val() || ''
|
||
this.__pageNo = 1
|
||
|
||
this.fetchData()
|
||
}
|
||
|
||
switchViewMode(e, call) {
|
||
const mode = e.currentTarget.value
|
||
this.setState({ viewMode: mode }, () => {
|
||
$storage.set(this.__viewModeKey, mode)
|
||
typeof call === 'function' && call(mode)
|
||
})
|
||
}
|
||
}
|
||
|
||
const APPROVAL_STATE_CLAZZs = {
|
||
2: [$L('审批中'), 'warning'],
|
||
10: [$L('通过'), 'success'],
|
||
11: [$L('驳回'), 'danger'],
|
||
}
|
||
// ~ 业务实体相关项列表
|
||
class EntityRelatedList extends RelatedList {
|
||
constructor(props) {
|
||
super(props)
|
||
this.state.viewOpens = {}
|
||
this.state.viewComponents = {}
|
||
|
||
this.__entity = props.entity.split('.')[0]
|
||
|
||
const openListUrl = `${rb.baseUrl}/app/${this.__entity}/list?via=${this.props.mainid}:${this.props.entity}`
|
||
this.__listExtraLink = (
|
||
<a className="btn btn-light w-auto" href={openListUrl} target="_blank" title={$L('在新页面打开')}>
|
||
<i className="icon zmdi zmdi-open-in-new" />
|
||
</a>
|
||
)
|
||
}
|
||
|
||
renderItem(item) {
|
||
const astate = APPROVAL_STATE_CLAZZs[item[3]]
|
||
return (
|
||
<div key={item[0]} className={`card ${this.state.viewOpens[item[0]] ? 'active' : ''}`} ref={`item-${item[0]}`}>
|
||
<div className="row header-title" onClick={() => this._toggleInsideView(item[0])}>
|
||
<div className="col-9">
|
||
<a href={`#!/View/${this.__entity}/${item[0]}`} onClick={(e) => this._handleView(e)} title={$L('打开')}>
|
||
{item[1]}
|
||
</a>
|
||
</div>
|
||
<div className="col-3 record-meta">
|
||
{item[4] && (
|
||
<a className="edit" onClick={(e) => this._handleEdit(e, item[0])} title={$L('编辑')}>
|
||
<i className="icon zmdi zmdi-edit" />
|
||
</a>
|
||
)}
|
||
|
||
{astate && <span className={`badge badge-pill badge-${astate[1]}`}>{astate[0]}</span>}
|
||
|
||
<span className="fs-12 text-muted" title={`${$L('修改时间')} ${item[2]}`}>
|
||
{$fromNow(item[2])}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="rbview-form-inside">{this.state.viewComponents[item[0]] || <RbSpinner fully={true} />}</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// 显示模式支持: 卡片/列表
|
||
|
||
switchViewMode(e) {
|
||
super.switchViewMode(e, (mode) => {
|
||
// 加载卡片数据
|
||
mode === 'CARD' && this.__fetchData !== true && this.fetchData()
|
||
})
|
||
}
|
||
|
||
renderData() {
|
||
if (this.state.viewMode === 'LIST') {
|
||
// if (!this.state.dataList) this.setState({ dataList: [] }) // Hide loading
|
||
return <EntityRelatedList2 $$$parent={this} ref={(c) => (this._EntityRelatedList2 = c)} />
|
||
} else {
|
||
return super.renderData()
|
||
}
|
||
}
|
||
|
||
search(e) {
|
||
if (this._EntityRelatedList2) {
|
||
this.__searchKey = $(this._$quickSearch).val() || ''
|
||
this._EntityRelatedList2.search(this.__searchKey)
|
||
} else {
|
||
super.search(e)
|
||
}
|
||
}
|
||
|
||
fetchData(append) {
|
||
if (this.state.viewMode === 'LIST') return
|
||
else this.__fetchData = true
|
||
|
||
this.__pageNo = this.__pageNo || 1
|
||
if (append) this.__pageNo += append
|
||
|
||
const pageSize = 20
|
||
const url = `/app/entity/related-list?mainid=${this.props.mainid}&related=${this.props.entity}&pageNo=${this.__pageNo}&pageSize=${pageSize}&sort=${this.__searchSort || ''}&q=${$encode(
|
||
this.__searchKey
|
||
)}`
|
||
|
||
$.get(url, (res) => {
|
||
if (res.error_code !== 0) return RbHighbar.error(res.error_msg)
|
||
|
||
const data = res.data.data || []
|
||
const list = append ? (this.state.dataList || []).concat(data) : data
|
||
|
||
this.setState({ dataList: list, showMore: data.length >= pageSize }, () => {
|
||
if (this.props.autoExpand) {
|
||
data.forEach((item) => {
|
||
// eslint-disable-next-line react/no-string-refs
|
||
const $H = $(this.refs[`item-${item[0]}`]).find('.header-title')
|
||
if ($H.length > 0 && !$H.parent().hasClass('active')) $H[0].click()
|
||
})
|
||
}
|
||
})
|
||
})
|
||
}
|
||
|
||
_handleEdit(e, id) {
|
||
$stopEvent(e, true)
|
||
RbFormModal.create({ id: id, entity: this.__entity, title: $L('编辑%s', this.props.entity2[0]), icon: this.props.entity2[1] }, true)
|
||
}
|
||
|
||
_handleView(e) {
|
||
$stopEvent(e, true)
|
||
RbViewPage.clickView(e.currentTarget)
|
||
}
|
||
|
||
_toggleInsideView(id) {
|
||
const viewOpens = this.state.viewOpens
|
||
viewOpens[id] = !viewOpens[id]
|
||
this.setState({ viewOpens: viewOpens })
|
||
|
||
// 加载视图
|
||
const viewComponents = this.state.viewComponents
|
||
if (!viewComponents[id]) {
|
||
$.get(`/app/${this.__entity}/view-model?id=${id}`, (res) => {
|
||
if (res.error_code > 0 || !!res.data.error) {
|
||
viewComponents[id] = _renderError(res.data.error || res.error_msg)
|
||
} else {
|
||
const _verticalLayout42 = window.__LAB_VERTICALLAYOUT || res.data.verticalLayout === 1 || res.data.verticalLayout === 3
|
||
viewComponents[id] = (
|
||
<div className={`rbview-form form-layout ${_verticalLayout42 && 'vertical38'}`}>
|
||
<div className="row">
|
||
{res.data.elements.map((item) => {
|
||
item.$$$parent = this
|
||
return detectViewElement(item)
|
||
})}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
this.setState({ viewComponents: viewComponents })
|
||
})
|
||
}
|
||
}
|
||
}
|
||
|
||
// 列表模式
|
||
class EntityRelatedList2 extends React.Component {
|
||
constructor(props) {
|
||
super(props)
|
||
this.state = {}
|
||
}
|
||
|
||
render() {
|
||
const p = this.props.$$$parent
|
||
const related = `related:${p.props.entity}:${p.props.mainid}`
|
||
|
||
return (
|
||
<div className="card-table">
|
||
<div className="dataTables_wrapper container-fluid">
|
||
<div className="rb-loading rb-loading-active data-list" ref={(c) => (this._$wrapper2 = c)}>
|
||
{this.state.listConfig && <RbList config={this.state.listConfig} protocolFilter={related} $wrapper={this._$wrapper2} unpin ref={(c) => (this._RbList = c)} />}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
componentDidMount() {
|
||
$.get(`/app/entity/related-list-config?entity=${this.props.$$$parent.__entity}`, (res) => {
|
||
if (res.error_code === 0) {
|
||
this.setState({ listConfig: { ...res.data } })
|
||
}
|
||
})
|
||
}
|
||
|
||
search(q) {
|
||
if (!this._RbList) return
|
||
|
||
const s = {
|
||
entity: this.props.$$$parent.__entity,
|
||
type: 'QUICK',
|
||
values: { 1: q },
|
||
}
|
||
this._RbList.search(s)
|
||
}
|
||
}
|
||
|
||
class MixRelatedList extends React.Component {
|
||
state = { ...this.props }
|
||
|
||
render() {
|
||
const entity = this.props.entity.split('.')[0]
|
||
if (entity === 'Feeds') {
|
||
// eslint-disable-next-line react/jsx-no-undef
|
||
return <LightFeedsList {...this.props} fetchNow />
|
||
} else if (entity === 'ProjectTask') {
|
||
// eslint-disable-next-line react/jsx-no-undef
|
||
return <LightTaskList {...this.props} fetchNow />
|
||
} else if (entity === 'Attachment') {
|
||
// eslint-disable-next-line react/jsx-no-undef
|
||
return <LightAttachmentList {...this.props} fetchNow />
|
||
} else {
|
||
return <EntityRelatedList {...this.props} showViewMode />
|
||
}
|
||
}
|
||
}
|
||
|
||
// for view-addons.js
|
||
// eslint-disable-next-line no-unused-vars
|
||
var _showFilterForAddons = function (opt) {
|
||
renderRbcomp(<AdvFilter entity={opt.entity} filter={opt.filter} confirm={opt.onConfirm} title={$L('附加过滤条件')} inModal canNoFilters />)
|
||
}
|
||
|
||
// 视图页操作类
|
||
const RbViewPage = {
|
||
_RbViewForm: null,
|
||
|
||
/**
|
||
* @param {*} id Record ID
|
||
* @param {*} entity array:[Name, Label, Icon]
|
||
* @param {*} ep Privileges of this entity
|
||
*/
|
||
init(id, entity, ep) {
|
||
this.__id = id
|
||
this.__entity = entity
|
||
this.__ep = ep
|
||
|
||
renderRbcomp(<RbViewForm entity={entity[0]} id={id} onViewEditable={ep && ep.U} />, 'tab-rbview', function () {
|
||
RbViewPage._RbViewForm = this
|
||
setTimeout(() => $('.view-body.loading').removeClass('loading'), 100)
|
||
|
||
// v3.8, v3.9
|
||
wpc.easyAction && window.EasyAction4View && window.EasyAction4View.init(wpc.easyAction)
|
||
})
|
||
|
||
$('.J_close').on('click', () => this.hide())
|
||
$('.J_reload').on('click', () => this.reload())
|
||
$('.J_newpage').attr({ target: '_blank', href: location.href })
|
||
|
||
if (parent && parent.RbListPage) $('.J_newpage').removeClass('hide')
|
||
if (parent && parent.RbViewModal && parent.RbViewModal.mode === 2) $('.J_close').remove()
|
||
|
||
const that = this
|
||
|
||
$('.J_delete').on('click', function () {
|
||
if ($(this).attr('disabled')) return
|
||
|
||
const needEntity = wpc.type === 'DetailList' || wpc.type === 'DetailView' ? null : entity[0]
|
||
renderRbcomp(
|
||
<DeleteConfirm
|
||
id={that.__id}
|
||
entity={needEntity}
|
||
deleteAfter={(deleted) => {
|
||
if (deleted > 0) {
|
||
// 刷新主视图
|
||
parent && parent.RbViewModal && parent.RbViewModal.currentHolder(true)
|
||
that.hide(true)
|
||
}
|
||
}}
|
||
/>
|
||
)
|
||
})
|
||
|
||
$('.J_edit').on('click', () => {
|
||
RbFormModal.create({ id: id, title: $L('编辑%s', entity[1]), entity: entity[0], icon: entity[2] }, true)
|
||
})
|
||
$('.J_assign').on('click', () => DlgAssign.create({ entity: entity[0], ids: [id] }))
|
||
$('.J_share').on('click', () => DlgShare.create({ entity: entity[0], ids: [id] }))
|
||
$('.J_report').on('click', () => SelectReport.create(entity[0], id))
|
||
$('.J_add-detail-menu>a').on('click', function () {
|
||
const iv = { $MAINID$: id }
|
||
const $this = $(this)
|
||
RbFormModal.create({ title: $L('添加%s', $this.data('label')), entity: $this.data('entity'), icon: $this.data('icon'), initialValue: iv, nextAddDetail: true })
|
||
})
|
||
|
||
if (wpc.transformTos && wpc.transformTos.length > 0) {
|
||
this.initTransform(wpc.transformTos)
|
||
$('.J_transform').removeClass('hide')
|
||
} else {
|
||
$('.J_transform').remove()
|
||
}
|
||
|
||
// Privileges
|
||
if (ep) {
|
||
if (ep.D === false) $('.J_delete').remove()
|
||
if (ep.U === false) $('.J_edit, .J_add-detail, .J_add-detail-menu').remove()
|
||
if (ep.A !== true) $('.J_assign').remove()
|
||
if (ep.S !== true) $('.J_share').remove()
|
||
}
|
||
|
||
// Clean buttons
|
||
that._cleanViewActionButton()
|
||
|
||
that.initRecordMeta()
|
||
that.initHistory()
|
||
|
||
setTimeout(() => {
|
||
if (window.parent && window.parent.tourStarted) return
|
||
typeof window.startTour === 'function' && window.startTour()
|
||
}, 1200)
|
||
},
|
||
|
||
// 元数据
|
||
initRecordMeta() {
|
||
$.get(`/app/entity/extras/record-meta?id=${this.__id}`, (res) => {
|
||
// 如果出错就清空操作区
|
||
if (res.error_code !== 0) {
|
||
$('.view-operating').empty()
|
||
return
|
||
}
|
||
|
||
const that = this
|
||
for (let k in res.data) {
|
||
const v = res.data[k]
|
||
if (!v) continue
|
||
const $el = $(`.J_${k}`)
|
||
if ($el.length === 0) continue
|
||
|
||
if (k === 'owningUser') {
|
||
renderRbcomp(<UserShow id={v[0]} name={v[1]} showName={true} deptName={v[2]} onClick={() => this._clickViewUser(v[0])} />, $el[0])
|
||
} else if (k === 'sharingList') {
|
||
const $list = $('<ul class="list-unstyled list-inline mb-0"></ul>').appendTo($('.J_sharingList').empty())
|
||
$(v).each(function () {
|
||
const _this = this
|
||
const $item = $('<li class="list-inline-item"></li>').appendTo($list)
|
||
renderRbcomp(<UserShow id={_this[0]} name={_this[1]} onClick={() => that._clickViewUser(_this[0])} />, $item[0])
|
||
})
|
||
|
||
if (this.__ep && this.__ep.S === true) {
|
||
const $op = $('<li class="list-inline-item"></li>').appendTo($list)[0]
|
||
if (v.length === 0) {
|
||
renderRbcomp(
|
||
<UserShow
|
||
name={$L('添加共享')}
|
||
icon="zmdi zmdi-plus"
|
||
onClick={() => {
|
||
$('.J_share').trigger('click')
|
||
}}
|
||
/>,
|
||
$op
|
||
)
|
||
} else {
|
||
renderRbcomp(<UserShow name={$L('管理共享用户')} icon="zmdi zmdi-more" onClick={() => DlgShareManager.create(this.__id)} />, $op)
|
||
}
|
||
} else if (v.length > 0) {
|
||
const $op = $('<li class="list-inline-item"></li>').appendTo($list)[0]
|
||
renderRbcomp(<UserShow name={$L('查看共享用户')} icon="zmdi zmdi-more" onClick={() => DlgShareManager.create(this.__id, false)} />, $op)
|
||
} else {
|
||
$('.J_sharingList').parent().remove()
|
||
}
|
||
} else if (k === 'createdOn' || k === 'modifiedOn') {
|
||
renderRbcomp(<DateShow date={v} showOrigin />, $el)
|
||
} else {
|
||
$(`<span>${v}</span>`).appendTo($el.empty())
|
||
}
|
||
}
|
||
|
||
// PlainEntity ?
|
||
if (!res.data.owningUser) $('.view-user').remove()
|
||
})
|
||
},
|
||
|
||
// 修改历史
|
||
initHistory() {
|
||
const $into = $('.view-history .view-history-items')
|
||
if ($into.length === 0) return
|
||
|
||
$.get(`/app/entity/extras/record-history?id=${this.__id}`, (res) => {
|
||
if (res.error_code !== 0) return
|
||
|
||
// v3.8 合并显示
|
||
let _data = []
|
||
let prev
|
||
res.data.forEach((item) => {
|
||
// 同样的合并
|
||
if (prev && prev.revisionType === item.revisionType && prev.revisionBy[0] === item.revisionBy[0]) {
|
||
let diff = $moment(item.revisionOn).diff($moment(prev.revisionOn), 'seconds')
|
||
if (Math.abs(diff) < 30) {
|
||
prev._merged = (prev._merged || 1) + 1
|
||
return
|
||
}
|
||
}
|
||
_data.push(item)
|
||
prev = item
|
||
})
|
||
|
||
$into.empty()
|
||
_data.forEach((item, idx) => {
|
||
let content = $L('**%s** 由 %s %s', $fromNow(item.revisionOn), item.revisionBy[1], item.revisionType)
|
||
if (item._merged > 1) content += ` <sup>${item._merged}</sup>`
|
||
|
||
const $item = $(`<li>${content}</li>`).appendTo($into)
|
||
$item.find('b:eq(0)').attr('title', item.revisionOn)
|
||
if (idx > 9) $item.addClass('hide')
|
||
})
|
||
|
||
if (_data.length > 10) {
|
||
$into.after(`<a href="javascript:;" class="J_mores">${$L('显示更多')}</a>`)
|
||
$('.view-history .J_mores').on('click', function () {
|
||
$into.find('li.hide').removeClass('hide')
|
||
$(this).addClass('hide')
|
||
})
|
||
} else if (_data.length === 0) {
|
||
$(`<li>${$L('无')}</li>`).appendTo($into)
|
||
}
|
||
|
||
$('.view-history.invisible2').removeClass('invisible2')
|
||
$('.view-history legend a').attr('href', `${rb.baseUrl}/admin/audit/revision-history?gs=${this.__id}`)
|
||
})
|
||
},
|
||
|
||
// 相关项
|
||
|
||
// 列表
|
||
initVTabs(config) {
|
||
const that = this
|
||
that.__vtabEntities = []
|
||
$(config).each(function () {
|
||
const configThat = this
|
||
const entity = this.entity // Entity.Field
|
||
that.__vtabEntities.push(entity)
|
||
const tabId = `tab-${entity.replace('.', '--')}` // `.` is JS keyword
|
||
|
||
const listProps = {
|
||
entity: entity,
|
||
entity2: [configThat.entityLabel, configThat.icon],
|
||
mainid: that.__id,
|
||
autoExpand: $isTrue(wpc.viewTabsAutoExpand),
|
||
defaultList: $isTrue(wpc.viewTabsDefaultList),
|
||
isDetail: !!this.showAt2,
|
||
}
|
||
|
||
// v3.4 明细显示在下方
|
||
if (this.showAt2 === 2) {
|
||
const $pane = $(`<div class="tab-pane-bottom"><h5><i class="zmdi zmdi-${this.icon}"></i>${this.entityLabel}</h5><div id="${tabId}"></div></div>`).appendTo('.tab-content-bottom')
|
||
$(`<a class="icon zmdi zmdi-chevron-down" title="${$L('展开/收起')}"></a>`)
|
||
.appendTo($pane.find('h5'))
|
||
.on('click', () => $pane.toggleClass('toggle-hide'))
|
||
renderRbcomp(<MixRelatedList {...listProps} />, tabId)
|
||
return
|
||
}
|
||
|
||
const $tabNav = $(
|
||
`<li class="nav-item ${$isTrue(wpc.viewTabsAutoHide) && 'hide'}"><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').on('click', function () {
|
||
$tabPane.find('.related-list').length === 0 && renderRbcomp(<MixRelatedList {...listProps} />, $tabPane)
|
||
})
|
||
})
|
||
this.updateVTabs()
|
||
|
||
// for Admin
|
||
if (rb.isAdminUser) {
|
||
$('.J_view-addons').on('click', function () {
|
||
const type = $(this).data('type')
|
||
RbModal.create(`/p/admin/metadata/view-addons?entity=${that.__entity[0]}&type=${type}`, type === 'TAB' ? $L('配置显示项') : $L('配置新建项'))
|
||
})
|
||
}
|
||
},
|
||
|
||
// 记录数量
|
||
updateVTabs(specEntities) {
|
||
specEntities = specEntities || this.__vtabEntities
|
||
if (!specEntities || specEntities.length === 0) return
|
||
|
||
$.get(`/app/entity/related-counts?mainid=${this.__id}&relateds=${specEntities.join(',')}`, function (res) {
|
||
for (let k in res.data || {}) {
|
||
if (~~res.data[k] > 0) {
|
||
const tabId = `#tab-${k.replace('.', '--')}`
|
||
const $tabNav = $(`.nav-tabs a[href="${tabId}"]`)
|
||
if ($tabNav[0]) {
|
||
$tabNav.parent().removeClass('hide')
|
||
|
||
if ($tabNav.find('.badge').length > 0) $tabNav.find('.badge').text(res.data[k])
|
||
else $(`<span class="badge badge-pill badge-primary">${res.data[k]}</span>`).appendTo($tabNav)
|
||
} else {
|
||
const $tabLine = $(tabId)
|
||
if ($tabLine[0]) {
|
||
let $span = $tabLine.prev().find('span')
|
||
if (!$span[0]) $span = $('<span></span>').appendTo($tabLine.prev())
|
||
$span.text(` (${res.data[k]})`)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
})
|
||
},
|
||
|
||
// 新建相关
|
||
initVAdds(config) {
|
||
const that = this
|
||
$(config).each(function () {
|
||
const item = this
|
||
const $item = $(`<a class="dropdown-item"><i class="icon zmdi zmdi-${item.icon}"></i>${item.entityLabel}</a>`)
|
||
$item.on('click', function () {
|
||
if (item.entity === 'Feeds.relatedRecord') {
|
||
const data = {
|
||
type: 2,
|
||
relatedRecord: { id: that.__id, entity: that.__entity[0], text: `@${that.__id.toUpperCase()}` },
|
||
}
|
||
renderRbcomp(
|
||
<FeedEditorDlg
|
||
{...data}
|
||
call={() => {
|
||
RbHighbar.success($L('保存成功'))
|
||
setTimeout(() => that.reload(), 100)
|
||
}}
|
||
/>
|
||
)
|
||
} else if (item.entity === 'ProjectTask.relatedRecord') {
|
||
renderRbcomp(
|
||
<LightTaskDlg
|
||
relatedRecord={that.__id}
|
||
call={() => {
|
||
RbHighbar.success($L('保存成功'))
|
||
setTimeout(() => that.reload(), 100)
|
||
}}
|
||
/>
|
||
)
|
||
} else {
|
||
const iv = {}
|
||
const entity = item.entity.split('.')
|
||
if (entity.length > 1) iv[entity[1]] = that.__id
|
||
else iv[`&${that.__entity[0]}`] = that.__id
|
||
|
||
const newProps = {
|
||
title: $L('新建%s', item._entityLabel || item.entityLabel),
|
||
entity: entity[0],
|
||
icon: item.icon,
|
||
initialValue: iv,
|
||
}
|
||
RbFormModal.create(newProps)
|
||
}
|
||
})
|
||
|
||
$('.J_add-related .dropdown-divider').before($item)
|
||
})
|
||
},
|
||
|
||
// 记录转换
|
||
initTransform(config) {
|
||
config.forEach((item) => {
|
||
if ($isSysMask(item.transName)) return // v4.0.2
|
||
const $item = $(`<a class="dropdown-item"><i class="icon zmdi zmdi-${item.icon}"></i>${item.transName || item.entityLabel}</a>`)
|
||
$item.on('click', () => renderRbcomp(<DlgTransform {...item} sourceRecord={this.__id} />))
|
||
$('.J_transform .dropdown-divider').before($item)
|
||
})
|
||
},
|
||
|
||
// 通过父级页面打开
|
||
clickView(target) {
|
||
// `#!/View/{entity}/{id}`
|
||
const viewUrl = typeof target === 'string' ? target : $(target).attr('href')
|
||
if (!viewUrl) {
|
||
console.warn('Bad view target : ', target)
|
||
return
|
||
}
|
||
|
||
const urlSpec = viewUrl.split('/')
|
||
if (parent && parent.RbViewModal) {
|
||
parent.RbViewModal.create({ entity: urlSpec[2], id: urlSpec[3] }, true)
|
||
} else {
|
||
// window.open(`${rb.baseUrl}/app/redirect?id=${urlSpec[3]}&type=newtab`)
|
||
window.open(`${rb.baseUrl}/app/${urlSpec[2]}/view/${urlSpec[3]}`)
|
||
}
|
||
return false
|
||
},
|
||
|
||
_clickViewUser(id) {
|
||
return this.clickView(`#!/View/User/${id}`)
|
||
},
|
||
|
||
// 清理操作按钮
|
||
_cleanViewActionButton() {
|
||
$setTimeout(
|
||
() => {
|
||
$cleanMenu('.view-action .J_mores')
|
||
$cleanMenu('.view-action .J_add-related')
|
||
$cleanMenu('.view-action .J_transform')
|
||
$('.view-action .col-lg-6').each(function () {
|
||
if ($(this).children().length === 0) $(this).remove()
|
||
})
|
||
if ($('.view-action').children().length === 0) $('.view-action').addClass('mt-0').empty()
|
||
// v3.6
|
||
$('.view-action.invisible2').removeClass('invisible2')
|
||
},
|
||
20,
|
||
'_cleanViewActionButton'
|
||
)
|
||
},
|
||
|
||
// 隐藏
|
||
hide(reload) {
|
||
if (parent && parent !== window) {
|
||
parent && parent.RbViewModal && parent.RbViewModal.holder(this.__id, 'HIDE')
|
||
if (reload === true) {
|
||
if (parent.RbListPage) parent.RbListPage.reload()
|
||
else setTimeout(() => parent.location.reload(), 200)
|
||
}
|
||
// v3.4
|
||
if (parent.location.href.includes('/app/entity/view')) parent.window.close()
|
||
} else {
|
||
window.close() // Maybe unclose
|
||
}
|
||
},
|
||
|
||
// 重新加載
|
||
reload() {
|
||
parent && parent.RbViewModal && parent.RbViewModal.holder(this.__id, 'LOADING')
|
||
setTimeout(() => location.reload(), 20)
|
||
},
|
||
|
||
// 记录只读
|
||
setReadonly() {
|
||
$(this._RbViewForm._viewForm).addClass('readonly')
|
||
$('.J_edit, .J_delete, .J_add-detail, .J_add-detail-menu').remove()
|
||
this._cleanViewActionButton()
|
||
},
|
||
}
|
||
|
||
// init
|
||
$(document).ready(() => {
|
||
// 回退按钮
|
||
if ($urlp('back') === 'auto' && parent && parent.RbViewModal) {
|
||
$('.J_back')
|
||
.removeClass('hide')
|
||
.on('click', () => history.back())
|
||
}
|
||
// for Dock
|
||
if (parent && parent.location.href.includes('/app/entity/view')) {
|
||
$('.view-header').remove()
|
||
}
|
||
// for WxWork
|
||
const ua = navigator.userAgent || ''
|
||
if (ua.includes('wxwork') && ua.includes('MicroMessenger') && $.browser.desktop) {
|
||
$('.J_home').removeClass('hide')
|
||
}
|
||
// v4.2
|
||
if (window.frameElement && parent && parent.RbListPage && parent.RbListPage._RbList && parent.RbListPage._RbList.jumpView) {
|
||
if ($(window.frameElement).data('subview')) {
|
||
// SubView
|
||
} else {
|
||
$('.J_record-next')
|
||
.removeClass('hide')
|
||
.on('click', () => parent.RbListPage._RbList.jumpView(1))
|
||
$('.J_record-prev')
|
||
.removeClass('hide')
|
||
.on('click', () => parent.RbListPage._RbList.jumpView(-1))
|
||
}
|
||
}
|
||
|
||
// iframe 点击穿透
|
||
if (parent) {
|
||
$(document).on('click', () => parent.$(parent.document).trigger('_clickEventHandler'))
|
||
window._clickEventHandler = () => $(document).trigger('click')
|
||
}
|
||
|
||
if (wpc.entity) {
|
||
RbViewPage.init(wpc.recordId, wpc.entity, wpc.privileges)
|
||
if (wpc.viewTabs) RbViewPage.initVTabs(wpc.viewTabs)
|
||
if (wpc.viewAdds) RbViewPage.initVAdds(wpc.viewAdds)
|
||
}
|
||
})
|