Merge branch 'master' into develop

This commit is contained in:
RB 2023-11-30 16:47:01 +08:00
commit 09eb2d0d01
48 changed files with 325 additions and 171 deletions

2
@rbv

@ -1 +1 @@
Subproject commit 15d7f439a84cef4be39dadada2d51e2e6ab4f375
Subproject commit 2e44d4228c89b993077d9a407f08f4a9c85af11b

View file

@ -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 -->

View file

@ -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

View file

@ -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;

View file

@ -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);
}
}
}

View file

@ -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));

View file

@ -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);

View file

@ -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)";
}
/**

View file

@ -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));

View file

@ -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());
}
}
/**

View file

@ -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());

View file

@ -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();
}

View file

@ -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);

View file

@ -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 )",

View file

@ -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 {

View file

@ -39,6 +39,8 @@ import java.util.List;
import java.util.Set;
/**
* FIXME 异步发送可能失败日志表未记录
*
* @author devezhao zhaofang123@gmail.com
* @since 2019/05/25
*/

View file

@ -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;

View file

@ -91,6 +91,9 @@ public enum ConfigurationItem {
// 2FA
Login2FAMode(0),
// App
MobileAppPath,
// DingTalk
DingtalkAgentid, DingtalkAppkey, DingtalkAppsecret, DingtalkCorpid,
DingtalkPushAeskey, DingtalkPushToken,

View file

@ -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;
}

View file

@ -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<>();

View file

@ -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;
}

View file

@ -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);
}
/**

View file

@ -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) {

View file

@ -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));
}

View file

@ -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;
}

View file

@ -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

View file

@ -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 {

View file

@ -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 + ")";

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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>

View file

@ -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;
}
}

View file

@ -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: '' } })
})
}

View file

@ -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
)
})

View file

@ -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']

View file

@ -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'))

View file

@ -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>

View file

@ -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

View file

@ -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} />

View file

@ -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) {

View file

@ -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)

View file

@ -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
}
}

View file

@ -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

View file

@ -10,7 +10,7 @@ const CALC_MODES2 = {
...FormulaAggregation.CALC_MODES,
RBJOIN: $L('连接'),
RBJOIN2: $L('去重连接'),
// RBJOIN3: $L('去重连接*'),
// RBJOIN3: $L('去重连接*N'),
}
// ~~ 分组聚合

View file

@ -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)

View file

@ -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>