mirror of
https://github.com/getrebuild/rebuild.git
synced 2025-09-27 00:55:34 +08:00
Merge branch 'master' into develop
This commit is contained in:
commit
09eb2d0d01
48 changed files with 325 additions and 171 deletions
2
@rbv
2
@rbv
|
@ -1 +1 @@
|
|||
Subproject commit 15d7f439a84cef4be39dadada2d51e2e6ab4f375
|
||||
Subproject commit 2e44d4228c89b993077d9a407f08f4a9c85af11b
|
2
pom.xml
2
pom.xml
|
@ -10,7 +10,7 @@
|
|||
</parent>
|
||||
<groupId>com.rebuild</groupId>
|
||||
<artifactId>rebuild</artifactId>
|
||||
<version>3.5.0-beta1</version>
|
||||
<version>3.5.0-beta2</version>
|
||||
<name>rebuild</name>
|
||||
<description>Building your business-systems freely!</description>
|
||||
<!-- UNCOMMENT USE TOMCAT -->
|
||||
|
|
|
@ -74,11 +74,11 @@ public class Application implements ApplicationListener<ApplicationStartedEvent>
|
|||
/**
|
||||
* Rebuild Version
|
||||
*/
|
||||
public static final String VER = "3.5.0-beta1";
|
||||
public static final String VER = "3.5.0-beta2";
|
||||
/**
|
||||
* Rebuild Build [MAJOR]{1}[MINOR]{2}[PATCH]{2}[BUILD]{2}
|
||||
*/
|
||||
public static final int BUILD = 3050000;
|
||||
public static final int BUILD = 3050002;
|
||||
|
||||
static {
|
||||
// Driver for DB
|
||||
|
|
|
@ -78,24 +78,27 @@ public class AutoFillinManager implements ConfigManager {
|
|||
String cascadingField = easyField.getExtraAttr(EasyFieldConfigProps.REFERENCE_CASCADINGFIELD);
|
||||
if (StringUtils.isNotBlank(cascadingField)) {
|
||||
String[] ts = cascadingField.split(MetadataHelper.SPLITER_RE);
|
||||
ConfigBean fake = new ConfigBean()
|
||||
.set("target", ts[0])
|
||||
.set("source", ts[1])
|
||||
.set("whenCreate", true)
|
||||
.set("whenUpdate", true)
|
||||
.set("fillinForce", true);
|
||||
// v35 父级的忽略
|
||||
if (!ts[0].contains(".")) {
|
||||
ConfigBean fake = new ConfigBean()
|
||||
.set("target", ts[0])
|
||||
.set("source", ts[1])
|
||||
.set("whenCreate", true)
|
||||
.set("whenUpdate", true)
|
||||
.set("fillinForce", true);
|
||||
|
||||
// 移除冲突的表单回填配置
|
||||
for (Iterator<ConfigBean> iter = config.iterator(); iter.hasNext(); ) {
|
||||
ConfigBean cb = iter.next();
|
||||
if (StringUtils.equals(cb.getString("source"), fake.getString("source"))
|
||||
&& StringUtils.equals(cb.getString("target"), fake.getString("target"))) {
|
||||
iter.remove();
|
||||
break;
|
||||
// 移除冲突的表单回填配置
|
||||
for (Iterator<ConfigBean> iter = config.iterator(); iter.hasNext(); ) {
|
||||
ConfigBean cb = iter.next();
|
||||
if (StringUtils.equals(cb.getString("source"), fake.getString("source"))
|
||||
&& StringUtils.equals(cb.getString("target"), fake.getString("target"))) {
|
||||
iter.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config.add(fake);
|
||||
config.add(fake);
|
||||
}
|
||||
}
|
||||
|
||||
if (config.isEmpty()) return JSONUtils.EMPTY_ARRAY;
|
||||
|
|
|
@ -275,7 +275,13 @@ public class DataListManager extends BaseLayoutManager {
|
|||
|
||||
for (String s : quickFields) {
|
||||
if (s.startsWith("&")) s = s.substring(1);
|
||||
paneFields.add(s);
|
||||
|
||||
if (entityMeta.containsField(s)) {
|
||||
paneFields.add(s);
|
||||
} else {
|
||||
// 不支持二级
|
||||
if (!s.contains(".")) log.warn("No field in filter pane : {}#{}", entity, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -72,8 +72,8 @@ public class EntityRecordCreator extends JsonRecordCreator {
|
|||
|
||||
final boolean isNew = record.getPrimary() == null;
|
||||
|
||||
// 明细关联主记录
|
||||
if (isNew && isDtmField(field)) return true;
|
||||
// 明细关联主记录 + 位置定位
|
||||
if (isNew && isForceCreateable(field)) return true;
|
||||
|
||||
// 公共字段前台可能会布局出来
|
||||
// 此处忽略检查没问题,因为最后还会复写,即 EntityHelper#bindCommonsFieldsValue
|
||||
|
@ -182,20 +182,14 @@ public class EntityRecordCreator extends JsonRecordCreator {
|
|||
keepFieldValueSafe(record);
|
||||
}
|
||||
|
||||
// 明细关联主记录字段
|
||||
private boolean isDtmField(Field field) {
|
||||
// 强制可新建的字段
|
||||
private boolean isForceCreateable(Field field) {
|
||||
// DTF 字段(明细关联主记录字段)
|
||||
if (field.getType() == FieldType.REFERENCE && entity.getMainEntity() != null) {
|
||||
return field.equals(MetadataHelper.getDetailToMainField(entity));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 强制可新建的字段
|
||||
private boolean isForceCreateable(Field field) {
|
||||
// DTF 字段
|
||||
if (isDtmField(field)) return true;
|
||||
|
||||
// 自动定位的
|
||||
// 启用自动定位的
|
||||
EasyField easyField = EasyMetaFactory.valueOf(field);
|
||||
if (easyField.getDisplayType() == DisplayType.LOCATION) {
|
||||
return BooleanUtils.toBoolean(easyField.getExtraAttr(EasyFieldConfigProps.LOCATION_AUTOLOCATION));
|
||||
|
|
|
@ -214,12 +214,14 @@ public class DynamicMetadataFactory extends ConfigurationMetadataFactory {
|
|||
}
|
||||
|
||||
JSONObject extraAttrs = JSON.parseObject(fieldElement.valueOf("@extra-attrs"));
|
||||
if (extraAttrs.containsKey("_cascadingFieldChild") && extraAttrs.getString("_cascadingFieldChild").contains(".")) {
|
||||
// 优先明细>主实体
|
||||
String _cascadingFieldChild = extraAttrs.getString("_cascadingFieldChild");
|
||||
if (_cascadingFieldChild != null && _cascadingFieldChild.contains(".")) {
|
||||
// 优先使用明细>主实体
|
||||
} else {
|
||||
extraAttrs.put("_cascadingFieldChild", fs[2] + SPLITER + fs[3]);
|
||||
fieldElement.addAttribute("extra-attrs", extraAttrs.toJSONString());
|
||||
}
|
||||
// FIXME v35 多个子级(使用 ; 分割)
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) XmlHelper.dump(rootElement);
|
||||
|
|
|
@ -27,6 +27,7 @@ import com.rebuild.core.metadata.impl.EasyFieldConfigProps;
|
|||
import com.rebuild.core.privileges.UserHelper;
|
||||
import com.rebuild.core.privileges.UserService;
|
||||
import com.rebuild.core.service.query.AdvFilterParser;
|
||||
import com.rebuild.core.service.query.ParseHelper;
|
||||
import com.rebuild.core.support.SetUser;
|
||||
import com.rebuild.core.support.general.FieldValueHelper;
|
||||
import com.rebuild.core.support.i18n.Language;
|
||||
|
@ -217,17 +218,17 @@ public abstract class ChartData extends SetUser implements ChartSpec {
|
|||
}
|
||||
}
|
||||
|
||||
JSONObject filterExp = config.getJSONObject("filter");
|
||||
if (filterExp == null || filterExp.isEmpty()) {
|
||||
return previewFilter + "(1=1)";
|
||||
JSONObject filterExpr = config.getJSONObject("filter");
|
||||
if (ParseHelper.validAdvFilter(filterExpr)) {
|
||||
AdvFilterParser filterParser = new AdvFilterParser(filterExpr);
|
||||
String sqlWhere = filterParser.toSqlWhere();
|
||||
if (sqlWhere != null) {
|
||||
sqlWhere = previewFilter + sqlWhere;
|
||||
}
|
||||
return StringUtils.defaultIfBlank(sqlWhere, "(1=1)");
|
||||
}
|
||||
|
||||
AdvFilterParser filterParser = new AdvFilterParser(filterExp);
|
||||
String sqlWhere = filterParser.toSqlWhere();
|
||||
if (sqlWhere != null) {
|
||||
sqlWhere = previewFilter + sqlWhere;
|
||||
}
|
||||
return StringUtils.defaultIfBlank(sqlWhere, "(1=1)");
|
||||
return previewFilter + "(1=1)";
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -231,7 +231,7 @@ public class RecordCheckout {
|
|||
if (refEntity.getEntityCode() == EntityHelper.User) {
|
||||
String sql = MessageFormat.format(
|
||||
"select userId from User where loginName = ''{0}'' or email = ''{0}'' or fullName = ''{0}''",
|
||||
CommonsUtils.escapeSql(val2Text.toString()));
|
||||
CommonsUtils.escapeSql(val2Text));
|
||||
query = Application.createQueryNoFilter(sql);
|
||||
} else {
|
||||
// 查找引用实体的名称字段和自动编号字段
|
||||
|
@ -248,7 +248,7 @@ public class RecordCheckout {
|
|||
String.format("select %s from %s where ", refEntity.getPrimaryField().getName(), refEntity.getName()));
|
||||
for (String qf : queryFields) {
|
||||
sql.append(
|
||||
String.format("%s = '%s' or ", qf, CommonsUtils.escapeSql(val2Text.toString())));
|
||||
String.format("%s = '%s' or ", qf, CommonsUtils.escapeSql(val2Text)));
|
||||
}
|
||||
sql = new StringBuilder(sql.substring(0, sql.length() - 4));
|
||||
|
||||
|
|
|
@ -105,6 +105,12 @@ public class AdvFilterParser extends SetUser {
|
|||
this.filterExpr = filterExpr;
|
||||
this.rootEntity = rootEntity;
|
||||
this.varRecord = null;
|
||||
|
||||
String entityName = filterExpr.getString("entity");
|
||||
if (entityName != null) {
|
||||
Assert.isTrue(entityName.equalsIgnoreCase(this.rootEntity.getName()),
|
||||
"Filter#2 uses different entities : " + entityName + "/" + this.rootEntity.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -115,6 +121,12 @@ public class AdvFilterParser extends SetUser {
|
|||
this.filterExpr = filterExpr;
|
||||
this.rootEntity = MetadataHelper.getEntity(varRecord.getEntityCode());
|
||||
this.varRecord = License.isRbvAttached() ? varRecord : null;
|
||||
|
||||
String entityName = filterExpr.getString("entity");
|
||||
if (entityName != null) {
|
||||
Assert.isTrue(entityName.equalsIgnoreCase(this.rootEntity.getName()),
|
||||
"Filter#3 uses different entities : " + entityName + "/" + this.rootEntity.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -32,10 +32,7 @@ public class FilterRecordChecker {
|
|||
* @return
|
||||
*/
|
||||
public boolean check(ID recordId) {
|
||||
if (filterExpr == null || filterExpr.isEmpty()
|
||||
|| filterExpr.getJSONArray("items") == null || filterExpr.getJSONArray("items").isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
if (!ParseHelper.validAdvFilter(filterExpr)) return true;
|
||||
|
||||
Entity entity = MetadataHelper.getEntity(recordId.getEntityCode());
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ import com.rebuild.core.service.general.GeneralEntityServiceContextHolder;
|
|||
import com.rebuild.core.service.general.OperatingContext;
|
||||
import com.rebuild.core.service.general.RecordDifference;
|
||||
import com.rebuild.core.service.query.AdvFilterParser;
|
||||
import com.rebuild.core.service.query.ParseHelper;
|
||||
import com.rebuild.core.service.query.QueryHelper;
|
||||
import com.rebuild.core.service.trigger.ActionContext;
|
||||
import com.rebuild.core.service.trigger.ActionType;
|
||||
|
@ -161,7 +162,7 @@ public class FieldAggregation extends TriggerAction {
|
|||
// 聚合数据过滤
|
||||
JSONObject dataFilter = ((JSONObject) actionContext.getActionContent()).getJSONObject("dataFilter");
|
||||
String dataFilterSql = null;
|
||||
if (dataFilter != null && !dataFilter.isEmpty()) {
|
||||
if (ParseHelper.validAdvFilter(dataFilter)) {
|
||||
dataFilterSql = new AdvFilterParser(dataFilter, operatingContext.getFixedRecordId()).toSqlWhere();
|
||||
}
|
||||
|
||||
|
|
|
@ -50,7 +50,8 @@ public class FieldWritebackRefresh {
|
|||
fa.targetEntity = parent.targetEntity;
|
||||
fa.targetRecordIds = Collections.singleton(beforeValue);
|
||||
|
||||
Record fakeSourceRecord = EntityHelper.forUpdate(EntityHelper.newUnsavedId(fa.sourceEntity.getEntityCode()), triggerUser, false);
|
||||
ID fakeSourceId = EntityHelper.newUnsavedId(fa.sourceEntity.getEntityCode());
|
||||
Record fakeSourceRecord = EntityHelper.forUpdate(fakeSourceId, triggerUser, false);
|
||||
OperatingContext oCtx = OperatingContext.create(triggerUser, BizzPermission.NONE, fakeSourceRecord, fakeSourceRecord);
|
||||
fa.targetRecordData = fa.buildTargetRecordData(oCtx, true);
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ import com.rebuild.core.service.trigger.ActionContext;
|
|||
import com.rebuild.core.service.trigger.ActionType;
|
||||
import com.rebuild.core.service.trigger.TriggerException;
|
||||
import com.rebuild.core.support.i18n.Language;
|
||||
import com.rebuild.utils.CommonsUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
@ -223,8 +224,8 @@ public class GroupAggregation extends FieldAggregation {
|
|||
}
|
||||
}
|
||||
|
||||
qFields.add(String.format("%s = '%s'", targetField, val));
|
||||
qFieldsFollow.add(String.format("%s = '%s'", sourceField, val));
|
||||
qFields.add(String.format("%s = '%s'", targetField, CommonsUtils.escapeSql(val)));
|
||||
qFieldsFollow.add(String.format("%s = '%s'", sourceField, CommonsUtils.escapeSql(val)));
|
||||
allNull = false;
|
||||
}
|
||||
|
||||
|
@ -232,11 +233,11 @@ public class GroupAggregation extends FieldAggregation {
|
|||
}
|
||||
|
||||
if (allNull) {
|
||||
// 如果分组字段值全空将会触发全量更新
|
||||
if (!valueChanged.isEmpty()) {
|
||||
this.groupAggregationRefresh = new GroupAggregationRefresh(this, qFieldsRefresh);
|
||||
} else {
|
||||
if (valueChanged.isEmpty()) {
|
||||
log.warn("All values of group-fields are null, ignored");
|
||||
} else {
|
||||
// 如果分组字段值全空将会触发全量更新
|
||||
this.groupAggregationRefresh = new GroupAggregationRefresh(this, qFieldsRefresh, operatingContext.getFixedRecordId());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -244,7 +245,7 @@ public class GroupAggregation extends FieldAggregation {
|
|||
this.followSourceWhere = StringUtils.join(qFieldsFollow.iterator(), " and ");
|
||||
|
||||
if (isGroupUpdate) {
|
||||
this.groupAggregationRefresh = new GroupAggregationRefresh(this, qFieldsRefresh);
|
||||
this.groupAggregationRefresh = new GroupAggregationRefresh(this, qFieldsRefresh, operatingContext.getFixedRecordId());
|
||||
}
|
||||
|
||||
sql = String.format("select %s from %s where ( %s )",
|
||||
|
|
|
@ -16,10 +16,14 @@ import com.rebuild.core.metadata.EntityHelper;
|
|||
import com.rebuild.core.privileges.UserService;
|
||||
import com.rebuild.core.service.general.OperatingContext;
|
||||
import com.rebuild.core.service.trigger.ActionContext;
|
||||
import com.rebuild.utils.CommonsUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 分组聚合目标数据刷新
|
||||
|
@ -39,15 +43,17 @@ public class GroupAggregationRefresh {
|
|||
|
||||
final private GroupAggregation parent;
|
||||
final private List<String[]> fieldsRefresh;
|
||||
final private ID originSourceId;
|
||||
|
||||
// fieldsRefresh = [TargetField, SourceField, Value]
|
||||
protected GroupAggregationRefresh(GroupAggregation parent, List<String[]> fieldsRefresh) {
|
||||
protected GroupAggregationRefresh(GroupAggregation parent, List<String[]> fieldsRefresh, ID originSourceId) {
|
||||
this.parent = parent;
|
||||
this.fieldsRefresh = fieldsRefresh;
|
||||
this.originSourceId = originSourceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO 存在性能问题(可能刷新过多)
|
||||
* NOTE 存在性能问题,因为可能刷新过多
|
||||
*/
|
||||
public void refresh() {
|
||||
List<String> targetFields = new ArrayList<>();
|
||||
|
@ -55,79 +61,52 @@ public class GroupAggregationRefresh {
|
|||
for (String[] s : fieldsRefresh) {
|
||||
targetFields.add(s[0]);
|
||||
if (s[2] != null) {
|
||||
targetWhere.add(String.format("%s = '%s'", s[0], s[2]));
|
||||
targetWhere.add(String.format("%s = '%s'", s[0], CommonsUtils.escapeSql(s[2])));
|
||||
}
|
||||
}
|
||||
|
||||
// 全量刷新,性能较低
|
||||
// 只有1个字段会全量刷新,性能较低
|
||||
if (targetWhere.size() <= 1) {
|
||||
targetWhere.clear();
|
||||
targetWhere.add("(1=1)");
|
||||
log.warn("Force refresh all aggregation target(s)");
|
||||
}
|
||||
|
||||
Entity entity = this.parent.targetEntity;
|
||||
// 1.获取待刷新的目标
|
||||
Entity targetEntity = this.parent.targetEntity;
|
||||
String sql = String.format("select %s,%s from %s where ( %s )",
|
||||
StringUtils.join(targetFields, ","),
|
||||
entity.getPrimaryField().getName(),
|
||||
entity.getName(),
|
||||
targetEntity.getPrimaryField().getName(),
|
||||
targetEntity.getName(),
|
||||
StringUtils.join(targetWhere, " or "));
|
||||
|
||||
Object[][] targetArray = Application.createQueryNoFilter(sql).array();
|
||||
log.info("Maybe refresh target record(s) : {}", targetArray.length);
|
||||
Object[][] targetRecords4Refresh = Application.createQueryNoFilter(sql).array();
|
||||
log.info("Maybe refresh target record(s) : {}", targetRecords4Refresh.length);
|
||||
|
||||
ID triggerUser = UserService.SYSTEM_USER;
|
||||
ActionContext parentAc = parent.getActionContext();
|
||||
|
||||
// 避免重复的无意义更新
|
||||
// NOTE 220905 不能忽略触发源本身
|
||||
// NOTE 220905 更新时不能忽略触发源本身的更新
|
||||
Set<ID> refreshedIds = new HashSet<>();
|
||||
|
||||
for (Object[] o : targetArray) {
|
||||
// 2.逐一刷新目标
|
||||
for (Object[] o : targetRecords4Refresh) {
|
||||
final ID targetRecordId = (ID) o[o.length - 1];
|
||||
// 排重
|
||||
if (refreshedIds.contains(targetRecordId)) continue;
|
||||
else refreshedIds.add(targetRecordId);
|
||||
|
||||
List<String> qFieldsFollow = new ArrayList<>();
|
||||
List<String> qFieldsFollow2 = new ArrayList<>();
|
||||
for (int i = 0; i < o.length - 1; i++) {
|
||||
String[] source = fieldsRefresh.get(i);
|
||||
if (o[i] == null) {
|
||||
qFieldsFollow.add(String.format("%s is null", source[1]));
|
||||
} else {
|
||||
qFieldsFollow.add(String.format("%s = '%s'", source[1], o[i]));
|
||||
qFieldsFollow2.add(String.format("%s = '%s'", source[1], o[i]));
|
||||
qFieldsFollow.add(String.format("%s = '%s'", source[1], CommonsUtils.escapeSql(o[i])));
|
||||
}
|
||||
}
|
||||
|
||||
ID fakeUpdateReferenceId = null;
|
||||
|
||||
// 1.尝试获取触发源
|
||||
for (int i = 0; i < o.length - 1; i++) {
|
||||
Object mayId = o[i];
|
||||
if (ID.isId(mayId) && ((ID) mayId).getEntityCode() > 100) {
|
||||
fakeUpdateReferenceId = (ID) mayId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 2.强制获取
|
||||
if (fakeUpdateReferenceId == null) {
|
||||
sql = String.format("select %s from %s where %s",
|
||||
parent.sourceEntity.getPrimaryField().getName(),
|
||||
parent.sourceEntity.getName(),
|
||||
StringUtils.join(qFieldsFollow2, " or "));
|
||||
Object[] found = Application.getQueryFactory().unique(sql);
|
||||
fakeUpdateReferenceId = found == null ? null : (ID) found[0];
|
||||
} else {
|
||||
|
||||
// 1.1.排重
|
||||
if (refreshedIds.contains(targetRecordId)) continue;
|
||||
else refreshedIds.add(fakeUpdateReferenceId);
|
||||
}
|
||||
|
||||
if (fakeUpdateReferenceId == null) {
|
||||
log.warn("No any source-id found, ignored : {}", Arrays.toString(o));
|
||||
continue;
|
||||
}
|
||||
|
||||
ActionContext actionContext = new ActionContext(null,
|
||||
parentAc.getSourceEntity(), parentAc.getActionContent(), parentAc.getConfigId());
|
||||
|
||||
|
@ -137,7 +116,8 @@ public class GroupAggregationRefresh {
|
|||
ga.targetRecordId = targetRecordId;
|
||||
ga.followSourceWhere = StringUtils.join(qFieldsFollow, " and ");
|
||||
|
||||
Record fakeSourceRecord = EntityHelper.forUpdate(fakeUpdateReferenceId, triggerUser, false);
|
||||
// FIXME v35 可能导致数据聚合条件中的字段变量不准
|
||||
Record fakeSourceRecord = EntityHelper.forUpdate(originSourceId, triggerUser, false);
|
||||
OperatingContext oCtx = OperatingContext.create(triggerUser, BizzPermission.NONE, fakeSourceRecord, fakeSourceRecord);
|
||||
|
||||
try {
|
||||
|
|
|
@ -39,6 +39,8 @@ import java.util.List;
|
|||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* FIXME 异步发送可能失败,日志表未记录
|
||||
*
|
||||
* @author devezhao zhaofang123@gmail.com
|
||||
* @since 2019/05/25
|
||||
*/
|
||||
|
|
|
@ -20,8 +20,10 @@ import com.rebuild.core.metadata.easymeta.DisplayType;
|
|||
import com.rebuild.core.metadata.easymeta.EasyField;
|
||||
import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
|
||||
import com.rebuild.core.metadata.impl.EasyFieldConfigProps;
|
||||
import com.rebuild.core.service.general.OperatingContext;
|
||||
import com.rebuild.core.service.trigger.ActionContext;
|
||||
import com.rebuild.core.support.i18n.Language;
|
||||
import com.rebuild.utils.CommonsUtils;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
@ -70,6 +72,12 @@ public class TargetWithMatchFields {
|
|||
return o == null ? new ID[0] : (ID[]) o;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param actionContext
|
||||
* @param m
|
||||
* @return
|
||||
* @see GroupAggregation#prepare(OperatingContext)
|
||||
*/
|
||||
private Object match(ActionContext actionContext, boolean m) {
|
||||
if (sourceEntity != null) return targetRecordId; // 已做匹配
|
||||
|
||||
|
@ -186,6 +194,7 @@ public class TargetWithMatchFields {
|
|||
}
|
||||
}
|
||||
|
||||
val = CommonsUtils.escapeSql(val);
|
||||
qFields.add(String.format("%s = '%s'", targetField, val));
|
||||
qFieldsFollow.add(String.format("%s = '%s'", sourceField, val));
|
||||
allNull = false;
|
||||
|
|
|
@ -91,6 +91,9 @@ public enum ConfigurationItem {
|
|||
// 2FA
|
||||
Login2FAMode(0),
|
||||
|
||||
// App
|
||||
MobileAppPath,
|
||||
|
||||
// DingTalk
|
||||
DingtalkAgentid, DingtalkAppkey, DingtalkAppsecret, DingtalkCorpid,
|
||||
DingtalkPushAeskey, DingtalkPushToken,
|
||||
|
|
|
@ -14,8 +14,8 @@ import cn.devezhao.persist4j.engine.ID;
|
|||
import com.rebuild.core.Application;
|
||||
import com.rebuild.core.metadata.EntityHelper;
|
||||
import com.rebuild.core.privileges.UserService;
|
||||
import com.rebuild.utils.CommonsUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.StringEscapeUtils;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
@ -63,7 +63,7 @@ public class ShortUrls {
|
|||
*/
|
||||
public static boolean invalid(String shortKey) {
|
||||
String dsql = String.format(
|
||||
"delete from `short_url` where SHORT_KEY = '%s'", StringEscapeUtils.escapeSql(shortKey));
|
||||
"delete from `short_url` where SHORT_KEY = '%s'", CommonsUtils.escapeSql(shortKey));
|
||||
int a = Application.getSqlExecutor().execute(dsql);
|
||||
return a > 0;
|
||||
}
|
||||
|
|
|
@ -165,6 +165,8 @@ public class ProtocolFilterParser {
|
|||
// 可能同时存在父子级
|
||||
String cascadingFieldParent = field.getExtraAttrs().getString("_cascadingFieldParent");
|
||||
String cascadingFieldChild = field.getExtraAttrs().getString("_cascadingFieldChild");
|
||||
// v35 多个使用第一个
|
||||
if (cascadingFieldChild != null) cascadingFieldChild = cascadingFieldChild.split(";")[0];
|
||||
|
||||
ID cascadingValueId = ID.valueOf(cascadingValue);
|
||||
List<String> parentAndChind = new ArrayList<>();
|
||||
|
|
|
@ -18,11 +18,18 @@ import com.rebuild.core.metadata.EntityHelper;
|
|||
import com.rebuild.core.metadata.MetadataHelper;
|
||||
import com.rebuild.core.privileges.UserService;
|
||||
import com.rebuild.core.service.query.AdvFilterParser;
|
||||
import com.rebuild.core.service.query.ParseHelper;
|
||||
import org.apache.commons.lang.BooleanUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang.math.NumberUtils;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 列表查询解析
|
||||
|
@ -270,7 +277,9 @@ public class QueryParser {
|
|||
ConfigBean advFilter = AdvFilterManager.instance.getAdvFilter(filterId);
|
||||
if (advFilter != null) {
|
||||
JSONObject filterExpr = (JSONObject) advFilter.getJSON("filter");
|
||||
return new AdvFilterParser(filterExpr, entity).toSqlWhere();
|
||||
if (ParseHelper.validAdvFilter(filterExpr)) {
|
||||
return new AdvFilterParser(filterExpr, entity).toSqlWhere();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -110,10 +110,10 @@ public class CommonsUtils {
|
|||
* @return
|
||||
* @see StringEscapeUtils#escapeSql(String)
|
||||
*/
|
||||
public static String escapeSql(String text) {
|
||||
public static String escapeSql(Object text) {
|
||||
// https://github.com/getrebuild/rebuild/issues/594
|
||||
text = text.replace("\\'", "'");
|
||||
return StringEscapeUtils.escapeSql(text);
|
||||
text = text.toString().replace("\\'", "'");
|
||||
return StringEscapeUtils.escapeSql((String) text);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -256,7 +256,8 @@ public class RebuildWebInterceptor implements AsyncHandlerInterceptor, InstallSt
|
|||
|| requestUri.startsWith("/commons/barcode/render")
|
||||
|| requestUri.startsWith("/commons/theme/")
|
||||
|| requestUri.startsWith("/account/user-avatar/")
|
||||
|| requestUri.startsWith("/rbmob/env");
|
||||
|| requestUri.startsWith("/rbmob/env")
|
||||
|| requestUri.startsWith("/h5app-download");
|
||||
}
|
||||
|
||||
private boolean isHtmlRequest(String requestUri, HttpServletRequest request) {
|
||||
|
|
|
@ -18,8 +18,8 @@ import com.rebuild.core.Application;
|
|||
import com.rebuild.core.configuration.RebuildApiService;
|
||||
import com.rebuild.core.metadata.EntityHelper;
|
||||
import com.rebuild.core.support.i18n.I18nUtils;
|
||||
import com.rebuild.utils.CommonsUtils;
|
||||
import com.rebuild.web.BaseController;
|
||||
import org.apache.commons.lang.StringEscapeUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
|
@ -88,7 +88,7 @@ public class ApisManagerController extends BaseController {
|
|||
String sql = "select remoteIp,requestTime,responseTime,requestUrl,requestBody,responseBody,requestId from RebuildApiRequest" +
|
||||
" where appId = ? and requestTime > ? and (1=1) order by requestTime desc";
|
||||
if (StringUtils.isNotBlank(q)) {
|
||||
q = StringEscapeUtils.escapeSql(q);
|
||||
q = CommonsUtils.escapeSql(q);
|
||||
sql = sql.replace("(1=1)", String.format("(requestBody like '%%%s%%' or responseBody like '%%%s%%')", q, q));
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ public class ConfigCommons {
|
|||
Object[][] array = Application.createQuery(sql).setLimit(500).array();
|
||||
for (Object[] o : array) {
|
||||
o[2] = EasyMetaFactory.getLabel(MetadataHelper.getEntity((String) o[2]));
|
||||
o[5] = I18nUtils.formatDate((Date) o[5]);
|
||||
if (o[5] instanceof Date) o[5] = I18nUtils.formatDate((Date) o[5]);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
|
|
@ -130,7 +130,8 @@ public class GeneralListController extends EntityController {
|
|||
paneFields.add(EasyMetaFactory.valueOf(listEntity.getField(field)).toJSON());
|
||||
}
|
||||
}
|
||||
mv.getModel().put("paneFields", paneFields);
|
||||
|
||||
if (!paneFields.isEmpty()) mv.getModel().put("paneFields", paneFields);
|
||||
}
|
||||
|
||||
// 查询页签 v3.3
|
||||
|
|
|
@ -56,17 +56,21 @@ public class ListAndViewRedirection extends BaseController {
|
|||
|| entity.getEntityCode() == EntityHelper.ProjectTaskComment) {
|
||||
Object[] found = findProjectAndTaskId(anyId);
|
||||
if (found != null) {
|
||||
url = MessageFormat.format(
|
||||
"../project/{0}/tasks#!/View/ProjectTask/{1}", found[1], found[0]);
|
||||
url = MessageFormat.format("../project/{0}/tasks#!/View/ProjectTask/{1}", found[1], found[0]);
|
||||
}
|
||||
|
||||
} else if (entity.getEntityCode() == EntityHelper.User) {
|
||||
url = MessageFormat.format(
|
||||
"../admin/bizuser/users#!/View/{0}/{1}", entity.getName(), anyId);
|
||||
url = MessageFormat.format("../admin/bizuser/users#!/View/{0}/{1}", entity.getName(), anyId);
|
||||
} else if (entity.getEntityCode() == EntityHelper.Department) {
|
||||
url = MessageFormat.format("../admin/bizuser/departments#!/View/{0}/{1}", entity.getName(), anyId);
|
||||
} else if (entity.getEntityCode() == EntityHelper.Team) {
|
||||
url = MessageFormat.format("../admin/bizuser/teams#!/View/{0}/{1}", entity.getName(), anyId);
|
||||
} else if (entity.getEntityCode() == EntityHelper.Role) {
|
||||
url = MessageFormat.format("../admin/bizuser/role/{0}", anyId);
|
||||
|
||||
} else if (MetadataHelper.isBusinessEntity(entity)) {
|
||||
if ("dock".equalsIgnoreCase(type)) {
|
||||
url = String.format("entity/view/{%s", anyId);
|
||||
url = String.format("entity/view?id=%s", anyId);
|
||||
} else if ("newtab".equalsIgnoreCase(type)) {
|
||||
url = String.format("%s/view/%s", entity.getName(), anyId);
|
||||
} else {
|
||||
|
|
|
@ -24,6 +24,7 @@ import com.rebuild.core.service.NoRecordFoundException;
|
|||
import com.rebuild.core.service.project.ProjectHelper;
|
||||
import com.rebuild.core.service.project.ProjectManager;
|
||||
import com.rebuild.core.service.query.AdvFilterParser;
|
||||
import com.rebuild.core.service.query.ParseHelper;
|
||||
import com.rebuild.core.support.general.FieldValueHelper;
|
||||
import com.rebuild.core.support.i18n.I18nUtils;
|
||||
import com.rebuild.core.support.i18n.Language;
|
||||
|
@ -106,7 +107,7 @@ public class ProjectTaskController extends BaseController {
|
|||
|
||||
// 高级查询
|
||||
JSON advFilter = ServletUtils.getRequestJson(request);
|
||||
if (advFilter != null) {
|
||||
if (ParseHelper.validAdvFilter((JSONObject) advFilter)) {
|
||||
String filterSql = new AdvFilterParser((JSONObject) advFilter).toSqlWhere();
|
||||
if (filterSql != null) {
|
||||
queryWhere += " and (" + filterSql + ")";
|
||||
|
|
|
@ -84,7 +84,7 @@ public class FieldAggregationController extends BaseController {
|
|||
|
||||
// 源字段
|
||||
|
||||
JSONArray sourceFields = MetaFormatter.buildFieldsWithRefs(sourceEntity, 3, field -> {
|
||||
JSONArray sourceFields = MetaFormatter.buildFieldsWithRefs(sourceEntity, 3, true, field -> {
|
||||
if (field instanceof EasyField) {
|
||||
EasyField easyField = (EasyField) field;
|
||||
return !easyField.isQueryable() || easyField.getDisplayType() == DisplayType.BARCODE;
|
||||
|
@ -104,6 +104,7 @@ public class FieldAggregationController extends BaseController {
|
|||
for (Field field : MetadataSorter.sortFields(targetEntity,
|
||||
DisplayType.NUMBER, DisplayType.DECIMAL, DisplayType.DATE, DisplayType.DATETIME,
|
||||
DisplayType.N2NREFERENCE, DisplayType.NTEXT, DisplayType.FILE)) {
|
||||
|
||||
EasyField easyField = EasyMetaFactory.valueOf(field);
|
||||
if (easyField.isBuiltin()) continue;
|
||||
|
||||
|
|
|
@ -117,7 +117,7 @@ public class FieldWritebackController extends BaseController {
|
|||
|
||||
// 源字段
|
||||
|
||||
JSONArray sourceFields = MetaFormatter.buildFieldsWithRefs(sourceEntity, 3, field -> {
|
||||
JSONArray sourceFields = MetaFormatter.buildFieldsWithRefs(sourceEntity, 3, true, field -> {
|
||||
if (field instanceof EasyField) {
|
||||
EasyField easyField = (EasyField) field;
|
||||
return easyField.getDisplayType() == DisplayType.BARCODE;
|
||||
|
@ -134,7 +134,7 @@ public class FieldWritebackController extends BaseController {
|
|||
|
||||
JSONArray targetFields = new JSONArray();
|
||||
if (targetEntity != null) {
|
||||
targetFields = MetaFormatter.buildFieldsWithRefs(targetEntity, 1, field -> {
|
||||
targetFields = MetaFormatter.buildFieldsWithRefs(targetEntity, 1, true, field -> {
|
||||
EasyField easyField = (EasyField) field;
|
||||
return easyField.getDisplayType() == DisplayType.SERIES
|
||||
|| easyField.getDisplayType() == DisplayType.BARCODE
|
||||
|
|
|
@ -54,7 +54,7 @@ public class GroupAggregationController extends BaseController {
|
|||
paddingType2(sourceGroupFields, sourceEntity);
|
||||
|
||||
// 源-聚合字段
|
||||
JSONArray sourceFields = MetaFormatter.buildFieldsWithRefs(sourceEntity, 3, field -> {
|
||||
JSONArray sourceFields = MetaFormatter.buildFieldsWithRefs(sourceEntity, 3, true, field -> {
|
||||
if (field instanceof EasyField) {
|
||||
EasyField easyField = (EasyField) field;
|
||||
return !easyField.isQueryable() || easyField.getDisplayType() == DisplayType.BARCODE;
|
||||
|
|
|
@ -118,6 +118,10 @@ public class LoginController extends LoginAction {
|
|||
// H5
|
||||
String mobileUrl = RebuildConfiguration.getMobileUrl("/");
|
||||
mv.getModel().put("mobileUrl", mobileUrl);
|
||||
// App
|
||||
if (RebuildConfiguration.get(ConfigurationItem.MobileAppPath) != null) {
|
||||
mv.getModel().put("mobileUrl", RebuildConfiguration.getHomeUrl("h5app-download"));
|
||||
}
|
||||
|
||||
if (License.isCommercial()) {
|
||||
// DingTalk
|
||||
|
|
|
@ -105,7 +105,6 @@
|
|||
<i class="logo-img white"></i>
|
||||
<b th:title="${bundle.L('还原')}"><span class="zmdi zmdi-replay"></span></b>
|
||||
</a>
|
||||
<input type="file" class="hide" accept="image/*" data-maxsize="10485760" data-local="true" />
|
||||
<p class="mt-2 text-dark hide">[[${bundle.L('请分别上传深色与白色 LOGO,透明背景,建议尺寸 300 × 60')}]]</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -145,7 +144,6 @@
|
|||
<i class="bgimg-img"></i>
|
||||
<b th:title="${bundle.L('还原')}"><span class="zmdi zmdi-replay"></span></b>
|
||||
</a>
|
||||
<input type="file" class="hide" accept="image/*" data-maxsize="10485760" data-local="true" />
|
||||
<p class="mt-2 text-dark hide">[[${bundle.L('建议尺寸 1920 × 1080')}]]</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -287,8 +285,14 @@
|
|||
<td>[[${bundle.L('手机版菜单样式')}]] <sup class="rbv"></sup></td>
|
||||
<td th:data-id="${commercial > 1 ? 'MobileNavStyle' : ''}" th:data-value="${MobileNavStyle}" data-optional="true">[[${MobileNavStyle ?:'34'}]]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>[[${bundle.L('APP 安装包')}]] <sup class="rbv"></sup></td>
|
||||
<td th:data-id="${commercial > 1 ? 'MobileAppPath' : ''}" th:data-value="${MobileAppPath}" data-optional="true" id="_MobileAppPath">[[${MobileAppPath ?:bundle.L('无')}]]</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<input type="file" class="hide file_4image" accept="image/*" data-maxsize="104857600" data-local="true" data-noname="true" />
|
||||
<input type="file" class="hide file_MobileAppPath" accept=".apk,.ipa" />
|
||||
<div class="edit-footer">
|
||||
<button class="btn btn-link" type="button">[[${bundle.L('取消')}]]</button>
|
||||
<button class="btn btn-primary" type="button">[[${bundle.L('保存')}]]</button>
|
||||
|
|
|
@ -1098,7 +1098,8 @@ a.link.link-icon::after {
|
|||
margin-right: -1px !important;
|
||||
}
|
||||
|
||||
.type-DECIMAL em.vflag {
|
||||
.type-DECIMAL em.vflag,
|
||||
.type-LOCATION em.vflag {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
|
@ -2177,6 +2178,10 @@ th.column-fixed {
|
|||
content: '\f2fb';
|
||||
}
|
||||
|
||||
.form-line.hover fieldset legend > span {
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.form-line.v33 {
|
||||
background-color: #ebebeb;
|
||||
border-radius: 2px;
|
||||
|
@ -3132,6 +3137,7 @@ form {
|
|||
margin-left: 10px;
|
||||
font-weight: bold;
|
||||
line-height: 20px;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.page-head-title .sub-title::before {
|
||||
|
@ -5014,7 +5020,7 @@ pre.unstyle {
|
|||
|
||||
.tablesort thead th.sorted.ascending::after,
|
||||
.tablesort thead th.sorted.descending::after {
|
||||
font: normal normal normal 18px/1 "Material Design Icons";
|
||||
font: normal normal normal 18px/1 'Material Design Icons';
|
||||
content: '\F0140';
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
|
@ -5377,4 +5383,4 @@ div.dataTables_wrapper.compact div.dataTables_oper .btn-space {
|
|||
|
||||
.dataTables_oper.invisible2 > .btn.J_view {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
|
||||
const wpc = window.__PageConfig
|
||||
|
||||
let val_MobileAppPath
|
||||
|
||||
$(document).ready(() => {
|
||||
const $dl = $('#_DefaultLanguage')
|
||||
$dl.text(wpc._LANGS[$dl.text()] || '中文')
|
||||
|
@ -18,6 +20,10 @@ $(document).ready(() => {
|
|||
if (~~$d.attr('data-value') <= 0) $d.text($L('不启用'))
|
||||
})
|
||||
|
||||
// v35
|
||||
val_MobileAppPath = $('#_MobileAppPath').data('value')
|
||||
if (val_MobileAppPath) $(`<a href="${rb.baseUrl}/h5app-download" target="_blank">${$fileCutName(val_MobileAppPath)}</a>`).appendTo($('#_MobileAppPath').empty())
|
||||
|
||||
// UC
|
||||
UCenter.query((res) => {
|
||||
const bindAccount = res.bindAccount
|
||||
|
@ -76,7 +82,7 @@ useEditComp = function (name) {
|
|||
)
|
||||
} else if ('PasswordPolicy' === name) {
|
||||
// 借用贵宝地
|
||||
_toggleImage('.applogo')
|
||||
_toggleImage('.applogo', true)
|
||||
_toggleImage('.bgimg')
|
||||
|
||||
return (
|
||||
|
@ -114,33 +120,75 @@ useEditComp = function (name) {
|
|||
<option value="3">{$L('仅邮箱')}</option>
|
||||
</select>
|
||||
)
|
||||
} else if ('MobileAppPath' === name) {
|
||||
setTimeout(() => {
|
||||
$initUploader(
|
||||
$('.file_MobileAppPath'),
|
||||
(res) => {
|
||||
let $span = $('.btn_MobileAppPath span')
|
||||
if (!$span[0]) $span = $('<span></span>').appendTo('.btn_MobileAppPath')
|
||||
$span.text(` (${res.percent.toFixed(0)}%)`)
|
||||
},
|
||||
(res) => {
|
||||
$('#_MobileAppPath a:eq(0)').text($fileCutName(res.key))
|
||||
changeValue({ target: { name: 'MobileAppPath', value: res.key } })
|
||||
setTimeout(() => $('.btn_MobileAppPath span').remove(), 1000)
|
||||
}
|
||||
)
|
||||
}, 1000)
|
||||
|
||||
return (
|
||||
<RF>
|
||||
<button className="btn btn-light btn-sm btn_MobileAppPath" type="button" onClick={() => $('.file_MobileAppPath')[0].click()}>
|
||||
<i className="icon zmdi zmdi-upload"></i> {$L('上传')}
|
||||
</button>
|
||||
<span className="d-inline-block down-2">
|
||||
<a className="ml-1" href={`${rb.baseUrl}/h5app-download`} target="_blank">
|
||||
{val_MobileAppPath ? $fileCutName(val_MobileAppPath) : null}
|
||||
</a>
|
||||
{val_MobileAppPath && (
|
||||
<a
|
||||
title={$L('移除')}
|
||||
className="ml-1"
|
||||
onClick={() => {
|
||||
$('#_MobileAppPath a').text('')
|
||||
changeValue({ target: { name: 'MobileAppPath', value: null } })
|
||||
}}>
|
||||
<i className="mdi mdi-close" />
|
||||
</a>
|
||||
)}
|
||||
</span>
|
||||
</RF>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const _toggleImage = function (el) {
|
||||
let _$imgCurrent
|
||||
const _toggleImage = function (el, init) {
|
||||
const $file = $('.file_4image')
|
||||
if (init) {
|
||||
$initUploader($file, null, (res) => {
|
||||
_$imgCurrent.find('>i').css('background-image', `url(${rb.baseUrl}/filex/img/${res.key}?local=true)`)
|
||||
changeValue({ target: { name: _$imgCurrent.data('id'), value: res.key } })
|
||||
})
|
||||
}
|
||||
|
||||
const $img = $(el).addClass('edit')
|
||||
$img.find('p').removeClass('hide')
|
||||
|
||||
let $current
|
||||
const $input = $img.find('input')
|
||||
$initUploader($input, null, (res) => {
|
||||
$current.find('>i').css('background-image', `url(${rb.baseUrl}/filex/img/${res.key.replace(/ /g, '%20')}?local=true)`)
|
||||
changeValue({ target: { name: $current.data('id'), value: res.key } })
|
||||
})
|
||||
|
||||
$img
|
||||
.find('a')
|
||||
.attr('title', $L('点击上传'))
|
||||
.on('click', function () {
|
||||
$current = $(this)
|
||||
$input[0].click()
|
||||
_$imgCurrent = $(this)
|
||||
$file[0].click()
|
||||
})
|
||||
$img.find('a>b').on('click', function (e) {
|
||||
$stopEvent(e, true)
|
||||
$current = $(this).parent()
|
||||
_$imgCurrent = $(this).parent()
|
||||
|
||||
$current.find('>i').css('background-image', `url(${rb.baseUrl}/assets/img/s.gif)`)
|
||||
changeValue({ target: { name: $current.data('id'), value: '' } })
|
||||
_$imgCurrent.find('>i').css('background-image', `url(${rb.baseUrl}/assets/img/s.gif)`)
|
||||
changeValue({ target: { name: _$imgCurrent.data('id'), value: '' } })
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -51,10 +51,10 @@ const enableEditMode = function () {
|
|||
if (!c) c = <input className="form-control form-control-sm" />
|
||||
|
||||
renderRbcomp(
|
||||
<React.Fragment>
|
||||
<RF>
|
||||
{React.cloneElement(c, { name: name, onChange: changeValue, defaultValue: value, placeholder: optional ? $L('(选填)') : null })}
|
||||
{formText && <p className="mt-2 text-dark" dangerouslySetInnerHTML={{ __html: formText }} />}
|
||||
</React.Fragment>,
|
||||
</RF>,
|
||||
$item
|
||||
)
|
||||
})
|
||||
|
|
|
@ -9,7 +9,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
const wpc = window.__PageConfig
|
||||
const __gExtConfig = {}
|
||||
|
||||
const SHOW_REPEATABLE = ['TEXT', 'DATE', 'EMAIL', 'URL', 'PHONE', 'REFERENCE', 'CLASSIFICATION']
|
||||
const SHOW_REPEATABLE = ['TEXT', 'DATE', 'EMAIL', 'URL', 'PHONE', 'REFERENCE', 'CLASSIFICATION', 'ANYREFERENCE']
|
||||
const SHOW_DEFAULTVALUE = ['TEXT', 'NTEXT', 'EMAIL', 'PHONE', 'URL', 'NUMBER', 'DECIMAL', 'DATETIME', 'DATE', 'TIME', 'BOOL', 'CLASSIFICATION', 'REFERENCE', 'N2NREFERENCE']
|
||||
const SHOW_ADVDESENSITIZED = ['TEXT', 'PHONE', 'EMAIL', 'NUMBER', 'DECIMAL']
|
||||
const SHOW_ADVPATTERN = ['TEXT']
|
||||
|
|
|
@ -67,11 +67,12 @@ const renderList = function () {
|
|||
}
|
||||
|
||||
function exportFields() {
|
||||
const rows = [[$L('内部标识'), $L('字段名称'), $L('类型'), $L('必填'), $L('备注')].join(', ')]
|
||||
const rows = [[$L('内部标识'), $L('字段名称'), $L('类型'), $L('必填'), $L('只读'), $L('备注')].join(', ')]
|
||||
rows.push([`${wpc.entityName}Id`, $L('主键'), 'ID', 'N', 'Y', ''].join(', '))
|
||||
fields_data.forEach((item) => {
|
||||
let type = item.displayType
|
||||
if (item.displayTypeRef) type += ` (${item.displayTypeRef[1]})`
|
||||
rows.push([item.fieldName, item.fieldLabel, type, item.nullable ? 'N' : 'Y', item.comments ? item.comments.replace(/[,;]/, ' ') : ''].join(', '))
|
||||
rows.push([item.fieldName, item.fieldLabel, type, item.nullable ? 'N' : 'Y', item.creatable ? 'N' : 'Y', item.comments ? item.comments.replace(/[,;]/, ' ') : ''].join(', '))
|
||||
})
|
||||
|
||||
const encodedUri = encodeURI('data:text/csv;charset=utf-8,\ufeff' + rows.join('\n'))
|
||||
|
|
|
@ -514,12 +514,13 @@ class DlgEditRefform extends DlgEditField {
|
|||
<option value="">{$L('无')}</option>
|
||||
{Object.keys(_ValidFields).map((k) => {
|
||||
const field = _ValidFields[k]
|
||||
if (field.displayTypeName === 'REFERENCE' && !(field.fieldName === 'approvalId'))
|
||||
if (['REFERENCE', 'ANYREFERENCE'].includes(field.displayTypeName) && field.fieldName !== 'approvalId') {
|
||||
return (
|
||||
<option key={field.fieldName} value={field.fieldName}>
|
||||
{field.fieldLabel}
|
||||
</option>
|
||||
)
|
||||
}
|
||||
return null
|
||||
})}
|
||||
</select>
|
||||
|
|
|
@ -1459,7 +1459,7 @@ const CellRenders = {
|
|||
} else if (parent && parent.RbViewModal) {
|
||||
parent.RbViewModal.create({ id: v.id, entity: v.entity }, wpc.forceSubView)
|
||||
} else {
|
||||
window.open(`${rb.baseUrl}/app/redirect?id=${v.id}`)
|
||||
window.open(`${rb.baseUrl}/app/redirect?id=${v.id}&type=newtab`)
|
||||
}
|
||||
e && $stopEvent(e, true)
|
||||
return false
|
||||
|
|
|
@ -2472,9 +2472,25 @@ class RbFormAvatar extends RbFormElement {
|
|||
}
|
||||
|
||||
class RbFormLocation extends RbFormElement {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this._autoLocation = props.locationAutoLocation && props.$$$parent.isNew && !props.value
|
||||
}
|
||||
|
||||
renderElement() {
|
||||
const lnglat = this._parseLnglat(this.state.value)
|
||||
if (this.props.readonly) return super.renderElement(lnglat ? lnglat.text : null)
|
||||
if (this.props.readonly) {
|
||||
return (
|
||||
<RF>
|
||||
{super.renderElement(lnglat ? lnglat.text : null)}
|
||||
{this._autoLocation && (
|
||||
<em className="vflag">
|
||||
<i className="zmdi zmdi-pin-drop flash infinite slow fs-14" ref={(c) => (this._$icon = c)} />
|
||||
</em>
|
||||
)}
|
||||
</RF>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="input-group has-append">
|
||||
|
@ -2489,6 +2505,7 @@ class RbFormLocation extends RbFormElement {
|
|||
placeholder={this.props.readonlyw > 0 ? $L('自动值') : null}
|
||||
onClick={() => this._showMap(lnglat)}
|
||||
/>
|
||||
|
||||
<span className={`zmdi zmdi-close clean ${this.state.value ? '' : 'hide'}`} onClick={() => this.handleClear()} title={$L('清除')} />
|
||||
<div className="input-group-append">
|
||||
<button className="btn btn-secondary" type="button" onClick={() => this._showMap(lnglat)}>
|
||||
|
@ -2570,14 +2587,15 @@ class RbFormLocation extends RbFormElement {
|
|||
componentDidMount() {
|
||||
super.componentDidMount()
|
||||
|
||||
const props = this.props
|
||||
if (props.locationAutoLocation && props.$$$parent.isNew && !props.value) {
|
||||
if (this._autoLocation) {
|
||||
$(this._$icon).addClass('animated')
|
||||
// eslint-disable-next-line no-undef
|
||||
$autoLocation((v) => {
|
||||
$(this._$icon).removeClass('animated')
|
||||
v = v && v.text ? `${v.text}$$$$${v.lng},${v.lat}` : null
|
||||
v && this.handleChange({ target: { value: v } }, true)
|
||||
|
||||
if (this.props.readonly) $(this._$icon).remove()
|
||||
else $(this._$icon).removeClass('animated')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -2765,7 +2783,7 @@ class RbFormDivider extends React.Component {
|
|||
<div className={`form-line hover -v33 ${this.state.collapsed && 'collapsed'}`} ref={(c) => (this._$formLine = c)}>
|
||||
<fieldset>
|
||||
<legend onClick={() => this._toggle()} title={$L('展开/收起')}>
|
||||
{this.props.label || ''}
|
||||
{this.props.label && <span>{this.props.label}</span>}
|
||||
</legend>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
@ -2805,8 +2823,11 @@ class RbFormRefform extends React.Component {
|
|||
|
||||
const $$$parent = this.props.$$$parent
|
||||
if ($$$parent && $$$parent.__ViewData && $$$parent.__ViewData[this.props.reffield]) {
|
||||
const s = $$$parent.__ViewData[this.props.reffield]
|
||||
this._renderViewFrom({ ...s })
|
||||
// 避免循环嵌套死循环
|
||||
if (($$$parent.__nestDepth || 0) < 3) {
|
||||
const s = $$$parent.__ViewData[this.props.reffield]
|
||||
this._renderViewFrom({ ...s })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2819,6 +2840,10 @@ class RbFormRefform extends React.Component {
|
|||
return
|
||||
}
|
||||
|
||||
// 支持嵌套
|
||||
this.__ViewData = {}
|
||||
this.__nestDepth = (this.props.$$$parent.__nestDepth || 0) + 1
|
||||
|
||||
const VFORM = (
|
||||
<RF>
|
||||
<a title={$L('在新页面打开')} className="close open-in-new" href={`${rb.baseUrl}/app/redirect?id=${props.id}&type=newtab`} target="_blank">
|
||||
|
@ -2826,6 +2851,7 @@ class RbFormRefform extends React.Component {
|
|||
</a>
|
||||
<div className="row">
|
||||
{res.data.elements.map((item) => {
|
||||
if (![TYPE_DIVIDER, TYPE_REFFORM].includes(item.field)) this.__ViewData[item.field] = item.value
|
||||
item.$$$parent = this
|
||||
// eslint-disable-next-line no-undef
|
||||
return detectViewElement(item, props.entity)
|
||||
|
@ -2840,7 +2866,7 @@ class RbFormRefform extends React.Component {
|
|||
|
||||
// 确定元素类型
|
||||
var detectElement = function (item, entity) {
|
||||
if (!item.key) item.key = `field-${item.field === TYPE_DIVIDER ? $random() : item.field}`
|
||||
if (!item.key) item.key = `field-${item.field === TYPE_DIVIDER || item.field === TYPE_REFFORM ? $random() : item.field}`
|
||||
|
||||
if (entity && window._CustomizedForms) {
|
||||
const c = window._CustomizedForms.useFormElement(entity, item)
|
||||
|
@ -2898,6 +2924,7 @@ var detectElement = function (item, entity) {
|
|||
} else if (item.field === TYPE_DIVIDER || item.field === '$LINE$') {
|
||||
return <RbFormDivider {...item} />
|
||||
} else if (item.field === TYPE_REFFORM) {
|
||||
console.log(item)
|
||||
return <RbFormRefform {...item} />
|
||||
} else {
|
||||
return <RbFormUnsupportted {...item} />
|
||||
|
|
|
@ -8,8 +8,8 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
/* !!! KEEP IT ES5 COMPATIBLE !!! */
|
||||
|
||||
// GA
|
||||
;(function () {
|
||||
const gaScript = document.createElement('script')
|
||||
(function () {
|
||||
var gaScript = document.createElement('script')
|
||||
gaScript.src = 'https://www.googletagmanager.com/gtag/js?id=G-ZCZHJPMEG7'
|
||||
gaScript.async = true
|
||||
gaScript.onload = function () {
|
||||
|
@ -20,7 +20,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
gtag('js', new Date())
|
||||
gtag('config', 'G-ZCZHJPMEG7')
|
||||
}
|
||||
const s = document.getElementsByTagName('script')[0]
|
||||
var s = document.getElementsByTagName('script')[0]
|
||||
s.parentNode.insertBefore(gaScript, s)
|
||||
})()
|
||||
|
||||
|
@ -259,7 +259,7 @@ var _initNav = function () {
|
|||
$sidebar.toggleClass('rb-collapsible-sidebar-collapsed')
|
||||
$('.sidebar-elements>li>a').tooltip('toggleEnabled')
|
||||
|
||||
const collapsed = $sidebar.hasClass('rb-collapsible-sidebar-collapsed')
|
||||
var collapsed = $sidebar.hasClass('rb-collapsible-sidebar-collapsed')
|
||||
$.cookie('rb.sidebarCollapsed', collapsed, { expires: 180 })
|
||||
|
||||
if (collapsed) {
|
||||
|
|
|
@ -61,7 +61,7 @@ class RbViewForm extends React.Component {
|
|||
{hadApproval && <ApprovalProcessor id={this.props.id} entity={this.props.entity} />}
|
||||
<div className="row">
|
||||
{res.data.elements.map((item) => {
|
||||
if (!(item.field === TYPE_DIVIDER || item.field === TYPE_REFFORM)) this.__ViewData[item.field] = item.value
|
||||
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
|
||||
return detectViewElement(item, this.props.entity)
|
||||
|
|
|
@ -10,7 +10,7 @@ const CALC_MODES2 = {
|
|||
...FormulaAggregation.CALC_MODES,
|
||||
RBJOIN: $L('连接'),
|
||||
RBJOIN2: $L('去重连接'),
|
||||
// RBJOIN3: $L('去重连接*'),
|
||||
// RBJOIN3: $L('去重连接*N'),
|
||||
}
|
||||
|
||||
const __LAB_MATCHFIELDS = false
|
||||
|
@ -70,7 +70,7 @@ class ContentFieldAggregation extends ActionContentSpec {
|
|||
<label className="col-md-12 col-lg-3 col-form-label text-lg-right"></label>
|
||||
<div className="col-md-12 col-lg-9">
|
||||
<div>
|
||||
<h5 className="mt-0 text-bold">{$L('目标实体/记录匹配规则')}</h5>
|
||||
<h5 className="mt-0 text-bold">{$L('目标实体/记录匹配规则')} (LAB)</h5>
|
||||
<textarea className="formula-code" style={{ height: 72 }} ref={(c) => (this._$matchFields = c)} placeholder="## [{ sourceField:XXX, targetField:XXX }]" />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -406,6 +406,22 @@ class ContentFieldAggregation extends ActionContentSpec {
|
|||
return false
|
||||
}
|
||||
|
||||
if (this.state.showMatchFields) {
|
||||
let badFormat = !content.targetEntityMatchFields
|
||||
if (!badFormat) {
|
||||
try {
|
||||
badFormat = JSON.parse(content.targetEntityMatchFields)
|
||||
badFormat = !$.isArray(badFormat)
|
||||
} catch (err) {
|
||||
badFormat = true
|
||||
}
|
||||
}
|
||||
if (badFormat) {
|
||||
RbHighbar.create($L('请正确填写目标实体/记录匹配规则'))
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return content
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ class ContentFieldWriteback extends ActionContentSpec {
|
|||
<label className="col-md-12 col-lg-3 col-form-label text-lg-right"></label>
|
||||
<div className="col-md-12 col-lg-9">
|
||||
<div>
|
||||
<h5 className="mt-0 text-bold">{$L('目标实体/记录匹配规则')}</h5>
|
||||
<h5 className="mt-0 text-bold">{$L('目标实体/记录匹配规则')} (LAB)</h5>
|
||||
<textarea className="formula-code" style={{ height: 72 }} ref={(c) => (this._$matchFields = c)} placeholder="## [{ sourceField:XXX, targetField:XXX }]" />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -377,6 +377,22 @@ class ContentFieldWriteback extends ActionContentSpec {
|
|||
return false
|
||||
}
|
||||
|
||||
if (this.state.showMatchFields) {
|
||||
let badFormat = !content.targetEntityMatchFields
|
||||
if (!badFormat) {
|
||||
try {
|
||||
badFormat = JSON.parse(content.targetEntityMatchFields)
|
||||
badFormat = !$.isArray(badFormat)
|
||||
} catch (err) {
|
||||
badFormat = true
|
||||
}
|
||||
}
|
||||
if (badFormat) {
|
||||
RbHighbar.create($L('请正确填写目标实体/记录匹配规则'))
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const one2one = this.state.targetEntities.find((x) => `${x[2]}.${x[0]}` === content.targetEntity)
|
||||
if (one2one && one2one[3] === 'one2one') content.one2one = true
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ const CALC_MODES2 = {
|
|||
...FormulaAggregation.CALC_MODES,
|
||||
RBJOIN: $L('连接'),
|
||||
RBJOIN2: $L('去重连接'),
|
||||
// RBJOIN3: $L('去重连接*'),
|
||||
// RBJOIN3: $L('去重连接*N'),
|
||||
}
|
||||
|
||||
// ~~ 分组聚合
|
||||
|
|
|
@ -79,7 +79,7 @@
|
|||
if (parent && parent.RbViewModal && parent.RbViewModal.mode === 1) {
|
||||
parent.RbViewModal.create({ id: v.id, entity: v.entity })
|
||||
} else {
|
||||
const openUrl = `${rb.baseUrl}/app/redirect?id=${v.id}&type=dock`
|
||||
const openUrl = `${rb.baseUrl}/app/redirect?id=${v.id}&type=newtab`
|
||||
window.open(openUrl, '_blank')
|
||||
}
|
||||
e && $stopEvent(e)
|
||||
|
|
|
@ -201,7 +201,7 @@
|
|||
<button class="btn btn-primary btn-outline J_temp-auth" type="button">[[${bundle.L('获取')}]]</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="form-group row hide">
|
||||
<label class="col-2 col-form-label">[[${bundle.L('客户端权限')}]]</label>
|
||||
<div class="col-5 pl-0">
|
||||
<div class="form-control-plaintext text-muted">[[${bundle.L('包括获取您的位置信息,以及通知推送')}]]</div>
|
||||
|
|
Loading…
Add table
Reference in a new issue