Fix 2.8.3 (#452)

* Update rb-forms.js

* fix: CustomEntityPrivileges for detail-entity

* better: file preview

* fix: auto-fill field deleted
This commit is contained in:
RB 2022-04-08 18:06:16 +08:00 committed by GitHub
parent 7f214334c6
commit 3ec4591144
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 171 additions and 90 deletions

View file

@ -10,7 +10,7 @@
</parent>
<groupId>com.rebuild</groupId>
<artifactId>rebuild</artifactId>
<version>2.8.2</version>
<version>2.8.3</version>
<name>rebuild</name>
<description>Building your business-systems freely!</description>
<!-- UNCOMMENT USE TOMCAT -->

View file

@ -64,11 +64,11 @@ public class Application implements ApplicationListener<ApplicationStartedEvent>
/**
* Rebuild Version
*/
public static final String VER = "2.8.2";
public static final String VER = "2.8.3";
/**
* Rebuild Build [MAJOR]{1}[MINOR]{2}[PATCH]{2}[BUILD]{2}
*/
public static final int BUILD = 2080205;
public static final int BUILD = 2080307;
static {
// Driver for DB

View file

@ -57,7 +57,8 @@ public class AutoFillinManager implements ConfigManager {
// 内置字段无配置 @see field-edit.html
if (easyField.isBuiltin()) return JSONUtils.EMPTY_ARRAY;
final List<ConfigBean> config = getConfig(field);
final List<ConfigBean> config = new ArrayList<>();
for (ConfigBean cb : getConfig(field)) config.add(cb.clone());
// 父级级联
// 利用表单回填做父级级联字段回填
@ -70,7 +71,7 @@ public class AutoFillinManager implements ConfigManager {
.set("whenCreate", true)
.set("whenUpdate", true)
.set("fillinForce", true);
// 移除冲突的表单回填配置
for (Iterator<ConfigBean> iter = config.iterator(); iter.hasNext(); ) {
ConfigBean cb = iter.next();
@ -89,28 +90,28 @@ public class AutoFillinManager implements ConfigManager {
Entity sourceEntity = MetadataHelper.getEntity(source.getEntityCode());
Entity targetEntity = field.getOwnEntity();
Set<String> sourceFields = new HashSet<>();
for (ConfigBean e : config) {
for (Iterator<ConfigBean> iter = config.iterator(); iter.hasNext(); ) {
ConfigBean e = iter.next();
String sourceField = e.getString("source");
String targetField = e.getString("target");
if (!MetadataHelper.checkAndWarnField(sourceEntity, sourceField)
|| !MetadataHelper.checkAndWarnField(targetEntity, targetField)) {
iter.remove();
continue;
}
sourceFields.add(sourceField);
}
if (sourceFields.isEmpty()) {
return JSONUtils.EMPTY_ARRAY;
}
if (sourceFields.isEmpty()) return JSONUtils.EMPTY_ARRAY;
String ql = String.format("select %s from %s where %s = ?",
StringUtils.join(sourceFields, ","),
sourceEntity.getName(),
sourceEntity.getPrimaryField().getName());
Record sourceRecord = Application.createQueryNoFilter(ql).setParameter(1, source).record();
if (sourceRecord == null) {
return JSONUtils.EMPTY_ARRAY;
}
if (sourceRecord == null) return JSONUtils.EMPTY_ARRAY;
JSONArray fillin = new JSONArray();
for (ConfigBean e : config) {

View file

@ -479,7 +479,7 @@ public class FormsBuilder extends FormsManager {
}
// 处理日期格式
if (field.getDisplayType() == DisplayType.REFERENCE && value != null && ((ID) value).getLabelRaw() != null) {
if (field.getDisplayType() == DisplayType.REFERENCE && value instanceof ID && ((ID) value).getLabelRaw() != null) {
Field nameField = field.getRawMeta().getReferenceEntity().getNameField();
if (nameField.getType() == FieldType.DATE || nameField.getType() == FieldType.TIMESTAMP) {
Object newLabel = EasyMetaFactory.valueOf(nameField).wrapValue(((ID) value).getLabelRaw());

View file

@ -405,7 +405,7 @@ public class PrivilegesManager {
* @param action
* @param ep
* @return
* @see RoleBaseQueryFilter#buildCustomFilter(Privileges)
* @see RoleBaseQueryFilter#buildCustomFilter(Privileges, Field)
*/
private boolean andPassCustomFilter(ID user, ID target, Permission action, Privileges ep) {
if (!(ep instanceof CustomEntityPrivileges)) return true;

View file

@ -18,6 +18,8 @@ import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.Filter;
import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.core.metadata.EntityHelper;
import com.rebuild.core.metadata.MetadataHelper;
@ -26,6 +28,7 @@ import com.rebuild.core.privileges.bizz.CustomEntityPrivileges;
import com.rebuild.core.privileges.bizz.Department;
import com.rebuild.core.privileges.bizz.User;
import com.rebuild.core.service.query.AdvFilterParser;
import com.rebuild.utils.JSONUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
@ -127,7 +130,7 @@ public class RoleBaseQueryFilter implements Filter, QueryFilter {
owningFormat = dtmField.getName() + "." + owningFormat;
}
final String customFilter = buildCustomFilter(ep);
final String customFilter = buildCustomFilter(ep, dtmField);
final String shareFilter = buildShareFilter(entity, dtmField);
final DepthEntry depth = ep.superlative(useAction);
@ -183,19 +186,19 @@ public class RoleBaseQueryFilter implements Filter, QueryFilter {
* 共享权限
*
* @param entity
* @param detailToMainField
* @param dtmField
* @return
*/
private String buildShareFilter(Entity entity, Field detailToMainField) {
private String buildShareFilter(Entity entity, Field dtmField) {
if (user == null) return null;
String shareFilter = "exists (select rights from ShareAccess where belongEntity = '%s' and shareTo = '%s' and recordId = ^%s)";
// 明细实体使用主实体的共享
if (detailToMainField != null) {
// 使用主实体的共享明细实体
if (dtmField != null) {
shareFilter = String.format(shareFilter,
detailToMainField.getOwnEntity().getMainEntity().getName(),
user.getId(), detailToMainField.getName());
dtmField.getOwnEntity().getMainEntity().getName(),
user.getId(), dtmField.getName());
} else {
shareFilter = String.format(shareFilter,
entity.getName(), user.getId(), entity.getPrimaryField().getName());
@ -207,16 +210,33 @@ public class RoleBaseQueryFilter implements Filter, QueryFilter {
* 自定义权限
*
* @param ep
* @param dtmField
* @return
* @see PrivilegesManager#andPassCustomFilter(ID, ID, Permission, Privileges)
*/
private String buildCustomFilter(Privileges ep) {
private String buildCustomFilter(Privileges ep, Field dtmField) {
if (user == null || useAction == null
|| !(ep instanceof CustomEntityPrivileges)) return null;
JSONObject hasFilter = ((CustomEntityPrivileges) ep).getCustomFilter(useAction);
if (hasFilter == null) return null;
// 兼容转换明细实体
if (dtmField != null) {
final JSONArray items = hasFilter.getJSONArray("items");
JSONArray items2 = new JSONArray();
for (Object item : items) {
JSONObject c = (JSONObject) JSONUtils.clone((JSON) item);
c.put("field", String.format("%s.%s", dtmField.getName(), c.getString("field")));
items2.add(c);
}
hasFilter = JSONUtils.toJSONObject(
new String[] { "entity", "items" },
new Object[] { dtmField.getOwnEntity().getName(), items2 });
}
AdvFilterParser advFilterParser = new AdvFilterParser(hasFilter);
advFilterParser.setUser(user.getId());
return advFilterParser.toSqlWhere();

View file

@ -23,6 +23,7 @@ import java.util.Map;
* @since 2021/11/15
*/
public class CustomEntityPrivileges extends EntityPrivileges {
private static final long serialVersionUID = 2658045031880710476L;
private Map<String, JSON> customAdvFilters = new HashMap<>();

View file

@ -110,16 +110,19 @@ public class AutoFillinController extends BaseController {
for (Object[] o : array) {
String sourceField = (String) o[1];
String targetField = (String) o[2];
if (!MetadataHelper.checkAndWarnField(sourceEntity, sourceField)
|| !MetadataHelper.checkAndWarnField(targetEntity, targetField)) {
continue;
}
String sourceFieldLabel = sourceEntity.containsField(sourceField)
? EasyMetaFactory.getLabel(sourceEntity.getField(sourceField))
: String.format("[%s]", sourceField.toUpperCase());
String targetFieldLabel = targetEntity.containsField(targetField)
? EasyMetaFactory.getLabel(targetEntity.getField(targetField))
: String.format("[%s]", targetField.toUpperCase());
JSON rule = JSONUtils.toJSONObject(
new String[]{ "id", "sourceField", "sourceFieldLabel", "targetField", "targetFieldLabel", "extConfig" },
new Object[]{ o[0],
sourceField, EasyMetaFactory.getLabel(sourceEntity.getField(sourceField)),
targetField, EasyMetaFactory.getLabel(targetEntity.getField(targetField)),
sourceField, sourceFieldLabel,
targetField, targetFieldLabel,
JSON.parse((String) o[3])});
rules.add(rule);
}

View file

@ -422,9 +422,8 @@ a.btn {
}
.input-search input + .btn-input-clear::before {
font-family: 'Material-Design-Iconic-Font';
font-family: 'Material-Design-Iconic-Font', serif;
font-size: 1.308rem;
content: '\f135';
content: '\f136';
}
@ -947,7 +946,7 @@ select.form-control:not([disabled]) {
.type-N2NREFERENCE .select2-container--default .select2-selection--multiple::after {
content: '\f2f9';
font-family: 'Material-Design-Iconic-Font';
font-family: 'Material-Design-Iconic-Font', serif;
font-size: 1.6rem;
font-weight: 400;
line-height: 35px;
@ -1010,7 +1009,6 @@ select.form-control:not([disabled]) {
.img-field .img-upload[disabled] .zmdi {
opacity: 1 !important;
color: #dbdbdb !important;
cursor: default !important;
cursor: not-allowed !important;
}
@ -1095,6 +1093,15 @@ select.form-control:not([disabled]) {
cursor: default;
}
.img-field.barcode .img-thumbnail.w-auto {
width: auto;
height: auto;
}
.img-field.barcode .img-thumbnail.w-auto > img {
width: auto;
}
.file-field .file-select label.btn-secondary {
line-height: 35px;
}
@ -1594,7 +1601,7 @@ th.column-fixed {
transform: translateX(100%);
box-shadow: inset 10px 0 8px -8px rgba(0, 0, 0, 0.15);
transition: box-shadow 0.3s;
width: 20px;
/*width: 20px;*/
/* fix: Safari */
width: 24px;
}
@ -4336,7 +4343,7 @@ html.external-auth .auth-body.must-center .login {
.user-popup .infos p.phone,
.user-popup .infos p.email {
font-family: 'Material-Design-Iconic-Font';
font-family: 'Material-Design-Iconic-Font', serif;
}
.user-popup .infos p.phone::after,
@ -4400,7 +4407,7 @@ pre.unstyle {
}
.dropdown-item.check::after {
font-family: 'Material-Design-Iconic-Font';
font-family: 'Material-Design-Iconic-Font', serif;
content: '\f26b';
float: right;
font-size: 1.2rem;
@ -4476,7 +4483,7 @@ pre.unstyle {
}
.sign-pad-canvas .pen-colors > a.active::after {
font-family: 'Material-Design-Iconic-Font';
font-family: 'Material-Design-Iconic-Font', serif;
content: '\f26b';
font-size: 1.3rem;
}

View file

@ -95,7 +95,8 @@ class RbPreview extends React.Component {
onLoad={() => this.setState({ imgRendered: true })}
onError={() => {
RbHighbar.error($L('无法读取图片'))
this.hide()
// this.hide()
// Qiniu: {"error":"xxx is not within the limit, area is out of range [1, 24999999]"}
}}
/>
</div>
@ -181,8 +182,9 @@ class RbPreview extends React.Component {
const currentUrl = this.props.urls[this.state.currentIndex]
const fileName = $fileCutName(currentUrl)
if (this._isDoc(fileName)) {
const ispdf = fileName.toLowerCase().endsWith('.pdf')
const setPreviewUrl = function (url) {
const previewUrl = (rb._officePreviewUrl || 'https://view.officeapps.live.com/op/embed.aspx?src=') + $encode(url)
const previewUrl = ispdf ? url : (rb._officePreviewUrl || 'https://view.officeapps.live.com/op/embed.aspx?src=') + $encode(url)
that.setState({ previewUrl: previewUrl, errorMsg: null })
}

View file

@ -30,8 +30,6 @@ $(document).ready(function () {
})
const $copy = $('.btn-primary.copy').on('click', () => {
if (rb.commercial < 1) return
const sourceEntity = $val('#copySourceEntity')
if (!sourceEntity) RbHighbar.create($L('请选择从哪个实体复制'))

View file

@ -193,6 +193,7 @@ class ReferenceSearcher extends RbModal {
destroy() {
this.setState({ destroy: true })
window.referenceSearch__dlg = null
}
}

View file

@ -313,11 +313,12 @@ class RbForm extends React.Component {
}
componentDidMount() {
// 新纪录初始值
if (this.isNew) {
this.props.children.map((child) => {
const val = child.props.value
if (val && child.props.readonly !== true) {
// 复合型值 {id:xxx, text:xxx}
// {id:xxx, text:xxx}
this.setFieldValue(child.props.field, typeof val === 'object' ? val.id : val)
}
})
@ -340,16 +341,16 @@ class RbForm extends React.Component {
// 设置字段值
setFieldValue(field, value, error) {
this.__FormData[field] = { value: value, error: error }
if (!error && this._onFieldValueChange_calls) this._onFieldValueChange_calls.forEach((c) => c({ name: field, value: value }))
if (!error) this._onFieldValueChangeCall(field, value)
// eslint-disable-next-line no-console
if (rb.env === 'dev') console.log('FV1 ... ' + JSON.stringify(this.__FormData))
}
// 避免无意义更新
setFieldUnchanged(field) {
setFieldUnchanged(field, originValue) {
delete this.__FormData[field]
if (this._onFieldValueChange_calls) this._onFieldValueChange_calls.forEach((c) => c({ name: field }))
this._onFieldValueChangeCall(field, originValue)
// eslint-disable-next-line no-console
if (rb.env === 'dev') console.log('FV2 ... ' + JSON.stringify(this.__FormData))
@ -361,6 +362,11 @@ class RbForm extends React.Component {
c.push(call)
this._onFieldValueChange_calls = c
}
_onFieldValueChangeCall(field, value) {
if (this._onFieldValueChange_calls) {
this._onFieldValueChange_calls.forEach((c) => c({ name: field, value: value }))
}
}
// 保存并添加明细
static __NEXT_ADDDETAIL = 102
@ -471,6 +477,7 @@ class RbFormElement extends React.Component {
if (props.colspan === 4 || props.isFull === true) colspan = 12
else if (props.colspan === 1) colspan = 3
else if (props.colspan === 3) colspan = 9
else if (props.colspan === 9) colspan = 4
const editable = props.$$$parent.onViewEditable && props.onView && !props.readonly
@ -528,8 +535,8 @@ class RbFormElement extends React.Component {
title={this.state.hasError}
type="text"
value={value || ''}
onChange={(e) => this.handleChange(e)}
onBlur={this.props.readonly ? null : () => this.checkValue()}
onChange={(e) => this.handleChange(e, this.props.readonly ? false : true)}
// onBlur={this.props.readonly ? null : () => this.checkValue()}
readOnly={this.props.readonly}
maxLength={this.props.maxLength || 200}
/>
@ -573,7 +580,7 @@ class RbFormElement extends React.Component {
if (this.isValueUnchanged() && !this.props.$$$parent.isNew) {
if (err) this.props.$$$parent.setFieldValue(this.props.field, this.state.value, errMsg)
else this.props.$$$parent.setFieldUnchanged(this.props.field)
else this.props.$$$parent.setFieldUnchanged(this.props.field, this.state.value)
} else {
this.setState({ hasError: err })
this.props.$$$parent.setFieldValue(this.props.field, this.state.value, errMsg)
@ -760,8 +767,8 @@ class RbFormNumber extends RbFormText {
title={this.state.hasError}
type="text"
value={this._removeComma(value) || ''}
onChange={(e) => this.handleChange(e)}
onBlur={this.props.readonly ? null : () => this.checkValue()}
onChange={(e) => this.handleChange(e, this.props.readonly ? false : true)}
// onBlur={this.props.readonly ? null : () => this.checkValue()}
readOnly={this.props.readonly}
maxLength="29"
/>
@ -774,51 +781,56 @@ class RbFormNumber extends RbFormText {
// 表单计算视图下无效
if (this.props.calcFormula && !this.props.onView) {
const calcFormula = this.props.calcFormula.replace(new RegExp('×', 'ig'), '*').replace(new RegExp('÷', 'ig'), '/')
const watchFields = calcFormula.match(/\{([a-z0-9]+)\}/gi) || []
const fixed = this.props.decimalFormat ? (this.props.decimalFormat.split('.')[1] || '').length : 0
this.calcFormula__values = {}
// 初始值
// 等待字段初始化完毕
setTimeout(() => {
const calcFormulaValues = {}
const watchFields = calcFormula.match(/\{([a-z0-9]+)\}/gi) || []
watchFields.forEach((item) => {
const name = item.substr(1, item.length - 2)
const fieldComp = this.props.$$$parent.refs[`fieldcomp-${name}`]
if (fieldComp && fieldComp.state.value) {
this.calcFormula__values[name] = this._removeComma(fieldComp.state.value)
calcFormulaValues[name] = this._removeComma(fieldComp.state.value)
}
})
}, 400)
// 小数位
const fixed = this.props.decimalFormat ? (this.props.decimalFormat.split('.')[1] || '').length : 0
// 表单计算
this.props.$$$parent.onFieldValueChange((s) => {
if (!watchFields.includes(`{${s.name}}`)) {
if (rb.env === 'dev') console.log('onFieldValueChange :', s)
return false
} else if (rb.env === 'dev') {
console.log('onFieldValueChange for calcFormula :', s)
}
// 表单计算
this.props.$$$parent.onFieldValueChange((s) => {
if (!watchFields.includes(`{${s.name}}`)) return
if (s.value) {
calcFormulaValues[s.name] = this._removeComma(s.value)
} else {
delete calcFormulaValues[s.name]
}
if (s.value) {
this.calcFormula__values[s.name] = this._removeComma(s.value)
} else {
delete this.calcFormula__values[s.name]
}
let formula = calcFormula
for (let key in calcFormulaValues) {
formula = formula.replace(new RegExp(`{${key}}`, 'ig'), calcFormulaValues[key] || 0)
}
let formula = calcFormula
for (let key in this.calcFormula__values) {
formula = formula.replace(new RegExp(`{${key}}`, 'ig'), this.calcFormula__values[key] || 0)
}
if (formula.includes('{')) {
this.setValue(null)
return false
}
if (formula.includes('{')) {
this.setValue(null)
return
}
try {
let calcv = null
eval(`calcv = ${formula}`)
if (!isNaN(calcv)) this.setValue(calcv.toFixed(fixed))
} catch (err) {
if (rb.env === 'dev') console.log(err)
}
})
try {
let calcv = null
eval(`calcv = ${formula}`)
if (!isNaN(calcv)) this.setValue(calcv.toFixed(fixed))
} catch (err) {
if (rb.env === 'dev') console.log(err)
}
return true
})
}, 200)
}
}
@ -858,8 +870,8 @@ class RbFormTextarea extends RbFormElement {
className={`form-control form-control-sm row3x ${this.state.hasError ? 'is-invalid' : ''} ${this.props.useMdedit && this.props.readonly ? 'cm-readonly' : ''}`}
title={this.state.hasError}
value={this.state.value || ''}
onChange={(e) => this.handleChange(e)}
onBlur={this.props.readonly ? null : () => this.checkValue()}
onChange={(e) => this.handleChange(e, this.props.readonly ? false : true)}
// onBlur={this.props.readonly ? null : () => this.checkValue()}
readOnly={this.props.readonly}
maxLength="6000"
/>
@ -972,14 +984,14 @@ class RbFormDateTime extends RbFormElement {
title={this.state.hasError}
type="text"
value={this.state.value || ''}
onChange={(e) => this.handleChange(e)}
onBlur={this.props.readonly ? null : () => this.checkValue()}
onChange={(e) => this.handleChange(e, this.props.readonly ? false : true)}
// onBlur={this.props.readonly ? null : () => this.checkValue()}
maxLength="20"
/>
<span className={'zmdi zmdi-close clean ' + (this.state.value ? '' : 'hide')} onClick={() => this.handleClear()} />
<div className="input-group-append">
<button className="btn btn-secondary" type="button" ref={(c) => (this._fieldValue__icon = c)}>
<i className="icon zmdi zmdi-calendar" />
<i className={`icon zmdi zmdi-${this._icon || 'calendar'}`} />
</button>
</div>
</div>
@ -1025,6 +1037,39 @@ class RbFormDateTime extends RbFormElement {
}
}
class RbFormTime extends RbFormDateTime {
constructor(props) {
super(props)
this._icon = 'time'
}
onEditModeChanged(destroy) {
if (destroy) {
super.onEditModeChanged(destroy)
} else if (!this.props.readonly) {
const format = (this.props.timeFormat || 'hh:ii:ss').replace('mm', 'ii').toLowerCase()
const minView = format.length === 2 ? 1 : 0
const that = this
this.__datetimepicker = $(this._fieldValue)
.datetimepicker({
format: format,
startView: 1,
minView: minView,
maxView: 1,
pickerPosition: this._getAutoPosition(),
title: $L('选择时间'),
})
.on('changeDate', function () {
const val = $(this).val()
that.handleChange({ target: { value: val } }, true)
})
$(this._fieldValue__icon).on('click', () => this.__datetimepicker.datetimepicker('show'))
}
}
}
class RbFormImage extends RbFormElement {
constructor(props) {
super(props)
@ -1371,7 +1416,8 @@ class RbFormReference extends RbFormElement {
componentWillUnmount() {
super.componentWillUnmount()
if (this._ReferenceSearcher) {
if (this._ReferenceSearcher && !this._hasCascadingField) {
this._ReferenceSearcher.destroy()
this._ReferenceSearcher = null
}
@ -1715,7 +1761,7 @@ class RbFormBool extends RbFormElement {
<label className="custom-control custom-radio custom-control-inline">
<input
className="custom-control-input"
name={'radio-' + this.props.field}
name={`radio-${this.props.field}`}
type="radio"
checked={!$isTrue(this.state.value)}
data-value="F"
@ -1766,8 +1812,8 @@ class RbFormBarcode extends RbFormElement {
const codeUrl = `${rb.baseUrl}/commons/barcode/render${isbar ? '' : '-qr'}?t=${$encode(this.state.value)}`
return (
<div className="img-field barcode">
<a className="img-thumbnail" title={this.state.value}>
<img src={codeUrl} alt={this.state.value} className={isbar ? 'w-auto' : ''} />
<a className={`img-thumbnail ${isbar && 'w-auto'}`} title={this.state.value}>
<img src={codeUrl} alt={this.state.value} />
</a>
</div>
)
@ -2071,6 +2117,8 @@ var detectElement = function (item) {
return <RbFormFile {...item} />
} else if (item.type === 'DATETIME' || item.type === 'DATE') {
return <RbFormDateTime {...item} />
} else if (item.type === 'TIME') {
return <RbFormTime {...item} />
} else if (item.type === 'PICKLIST') {
return <RbFormPickList {...item} />
} else if (item.type === 'REFERENCE') {