mirror of
https://github.com/getrebuild/rebuild.git
synced 2025-03-13 15:44:26 +08:00
Fix 2.9.4 (#481)
* fix: same record&action * fix: TriggerSource * fix: delete details * func: TEXT * better picklist edit * feat: default-value {CURRENT} * fix: GroupAggregation * simple sort on trigger-list * feat: NTEXT height * fix: INDEX href * better: Chart maxHeight * protable tip * cache _AdvFilter * DATAVALIDATE v2 * fix #483 * Bool N * better install * error handle * txtSuffix * helper method
This commit is contained in:
parent
247a577aef
commit
3ad0fb9ffe
67 changed files with 947 additions and 245 deletions
2
@rbv
2
@rbv
|
@ -1 +1 @@
|
|||
Subproject commit 20c457c31c7c569bc5590d495447e62f8a1b6cb8
|
||||
Subproject commit 09666fcd0f8b89634c85030e78132726720e80fd
|
2
pom.xml
2
pom.xml
|
@ -10,7 +10,7 @@
|
|||
</parent>
|
||||
<groupId>com.rebuild</groupId>
|
||||
<artifactId>rebuild</artifactId>
|
||||
<version>2.9.3</version>
|
||||
<version>2.9.4</version>
|
||||
<name>rebuild</name>
|
||||
<description>Building your business-systems freely!</description>
|
||||
<!-- UNCOMMENT USE TOMCAT -->
|
||||
|
|
|
@ -65,11 +65,11 @@ public class Application implements ApplicationListener<ApplicationStartedEvent>
|
|||
/**
|
||||
* Rebuild Version
|
||||
*/
|
||||
public static final String VER = "2.9.3";
|
||||
public static final String VER = "2.9.4";
|
||||
/**
|
||||
* Rebuild Build [MAJOR]{1}[MINOR]{2}[PATCH]{2}[BUILD]{2}
|
||||
*/
|
||||
public static final int BUILD = 2090311;
|
||||
public static final int BUILD = 2090412;
|
||||
|
||||
static {
|
||||
// Driver for DB
|
||||
|
|
|
@ -22,8 +22,6 @@ public class DefinedException extends RebuildException {
|
|||
public static final int CODE_RECORDS_REPEATED = 499;
|
||||
// 审批警告
|
||||
public static final int CODE_APPROVE_WARN = 498;
|
||||
// 不在安全使用范围(IP、时段)
|
||||
public static final int CODE_UNSAFE_USE = 497;
|
||||
|
||||
// 错误码
|
||||
private int errorCode = Controller.CODE_ERROR;
|
||||
|
|
|
@ -39,6 +39,7 @@ public class EasyBool extends EasyField implements MixValue {
|
|||
@Override
|
||||
public Object exprDefaultValue() {
|
||||
String valueExpr = (String) getRawMeta().getDefaultValue();
|
||||
if ("N".equals(valueExpr)) return null;
|
||||
return StringUtils.isBlank(valueExpr) ? Boolean.FALSE : BooleanUtils.toBoolean(valueExpr);
|
||||
}
|
||||
|
||||
|
|
|
@ -60,6 +60,13 @@ public class EasyN2NReference extends EasyReference {
|
|||
String valueExpr = (String) getRawMeta().getDefaultValue();
|
||||
if (StringUtils.isBlank(valueExpr)) return null;
|
||||
|
||||
if (valueExpr.contains(VAR_CURRENT)) {
|
||||
Object id = exprCurrent();
|
||||
if (id == null) return null;
|
||||
else if (id instanceof ID[]) return id;
|
||||
else return new ID[] {(ID) id};
|
||||
}
|
||||
|
||||
List<ID> idArray = new ArrayList<>();
|
||||
for (String id : valueExpr.split(",")) {
|
||||
if (ID.isId(id)) idArray.add(ID.valueOf(id));
|
||||
|
|
|
@ -7,12 +7,22 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
|
||||
package com.rebuild.core.metadata.easymeta;
|
||||
|
||||
import cn.devezhao.bizz.security.member.Team;
|
||||
import cn.devezhao.persist4j.Entity;
|
||||
import cn.devezhao.persist4j.Field;
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.rebuild.core.Application;
|
||||
import com.rebuild.core.UserContextHolder;
|
||||
import com.rebuild.core.metadata.EntityHelper;
|
||||
import com.rebuild.core.privileges.bizz.Department;
|
||||
import com.rebuild.core.privileges.bizz.User;
|
||||
import com.rebuild.core.support.general.FieldValueHelper;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author devezhao
|
||||
|
@ -21,6 +31,8 @@ import com.rebuild.core.support.general.FieldValueHelper;
|
|||
public class EasyReference extends EasyField implements MixValue {
|
||||
private static final long serialVersionUID = -5001745527956303569L;
|
||||
|
||||
protected static final String VAR_CURRENT = "{CURRENT}";
|
||||
|
||||
protected EasyReference(Field field, DisplayType displayType) {
|
||||
super(field, displayType);
|
||||
}
|
||||
|
@ -44,7 +56,44 @@ public class EasyReference extends EasyField implements MixValue {
|
|||
@Override
|
||||
public Object exprDefaultValue() {
|
||||
String valueExpr = (String) getRawMeta().getDefaultValue();
|
||||
return ID.isId(valueExpr) ? ID.valueOf(valueExpr) : null;
|
||||
if (StringUtils.isBlank(valueExpr)) return null;
|
||||
|
||||
if (valueExpr.contains(VAR_CURRENT)) {
|
||||
Object id = exprCurrent();
|
||||
if (id instanceof ID[]) return ((ID[]) id)[0];
|
||||
else return id;
|
||||
} else {
|
||||
return ID.isId(valueExpr) ? ID.valueOf(valueExpr) : null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #VAR_CURRENT
|
||||
* @return returns ID or ID[]
|
||||
*/
|
||||
protected Object exprCurrent() {
|
||||
final ID cu = UserContextHolder.getUser(true);
|
||||
if (cu == null) return null;
|
||||
|
||||
Entity ref = getRawMeta().getReferenceEntity();
|
||||
if (ref.getEntityCode() == EntityHelper.User) return cu;
|
||||
|
||||
User user = Application.getUserStore().getUser(cu);
|
||||
|
||||
if (ref.getEntityCode() == EntityHelper.Department) {
|
||||
Department dept = user.getOwningDept();
|
||||
return dept == null ? null : dept.getIdentity();
|
||||
}
|
||||
|
||||
if (ref.getEntityCode() == EntityHelper.Team) {
|
||||
List<ID> ts = new ArrayList<>();
|
||||
for (Team t : user.getOwningTeams()) {
|
||||
ts.add((ID) t.getIdentity());
|
||||
}
|
||||
return ts.isEmpty() ? null : ts.toArray(new ID[0]);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -394,31 +394,28 @@ public class ApprovalStepService extends InternalPersistService {
|
|||
public boolean txAutoApproved(ID recordId, ID useApprover, ID useApproval) {
|
||||
final ApprovalState currentState = ApprovalHelper.getApprovalState(recordId);
|
||||
|
||||
// 其他状态不能自动审批
|
||||
if (!(currentState == ApprovalState.PROCESSING || currentState == ApprovalState.APPROVED)) {
|
||||
|
||||
if (useApprover == null) useApprover = UserService.SYSTEM_USER;
|
||||
if (useApproval == null) useApproval = APPROVAL_NOID;
|
||||
|
||||
ID stepId = createStepIfNeed(recordId, useApproval,
|
||||
FlowNode.NODE_AUTOAPPROVAL, useApprover, false, FlowNode.NODE_ROOT);
|
||||
Record step = EntityHelper.forUpdate(stepId, useApprover, false);
|
||||
step.setInt("state", ApprovalState.APPROVED.getState());
|
||||
step.setString("remark", Language.L("自动审批"));
|
||||
super.update(step);
|
||||
|
||||
// 更新记录审批状态
|
||||
Record recordOfMain = EntityHelper.forUpdate(recordId, UserService.SYSTEM_USER, false);
|
||||
recordOfMain.setID(EntityHelper.ApprovalId, useApproval);
|
||||
recordOfMain.setString(EntityHelper.ApprovalStepNode, FlowNode.NODE_AUTOAPPROVAL);
|
||||
super.update(recordOfMain);
|
||||
|
||||
Application.getEntityService(recordId.getEntityCode()).approve(recordId, ApprovalState.APPROVED, useApprover);
|
||||
return true;
|
||||
|
||||
} else {
|
||||
if (currentState == ApprovalState.PROCESSING || currentState == ApprovalState.APPROVED) {
|
||||
log.warn("Invalid state {} for auto approval", currentState);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (useApprover == null) useApprover = UserService.SYSTEM_USER;
|
||||
if (useApproval == null) useApproval = APPROVAL_NOID;
|
||||
|
||||
ID stepId = createStepIfNeed(recordId, useApproval,
|
||||
FlowNode.NODE_AUTOAPPROVAL, useApprover, false, FlowNode.NODE_ROOT);
|
||||
Record step = EntityHelper.forUpdate(stepId, useApprover, false);
|
||||
step.setInt("state", ApprovalState.APPROVED.getState());
|
||||
step.setString("remark", Language.L("自动审批"));
|
||||
super.update(step);
|
||||
|
||||
// 更新记录审批状态
|
||||
Record recordOfMain = EntityHelper.forUpdate(recordId, UserService.SYSTEM_USER, false);
|
||||
recordOfMain.setID(EntityHelper.ApprovalId, useApproval);
|
||||
recordOfMain.setString(EntityHelper.ApprovalStepNode, FlowNode.NODE_AUTOAPPROVAL);
|
||||
super.update(recordOfMain);
|
||||
|
||||
Application.getEntityService(recordId.getEntityCode()).approve(recordId, ApprovalState.APPROVED, useApprover);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,13 +30,15 @@ public class BulkDelete extends BulkOperator {
|
|||
final ID[] records = prepareRecords();
|
||||
this.setTotal(records.length);
|
||||
|
||||
String lastError = null;
|
||||
for (ID id : records) {
|
||||
if (Application.getPrivilegesManager().allowDelete(context.getOpUser(), id)) {
|
||||
try {
|
||||
ges.delete(id, context.getCascades());
|
||||
this.addSucceeded();
|
||||
} catch (DataSpecificationException ex) {
|
||||
log.warn("Cannot delete `{}` because : {}", id, ex.getLocalizedMessage());
|
||||
lastError = ex.getLocalizedMessage();
|
||||
log.warn("Cannot delete `{}` because : {}", id, lastError);
|
||||
}
|
||||
} else {
|
||||
log.warn("No have privileges to DELETE : {} < {}", id, context.getOpUser());
|
||||
|
@ -44,6 +46,10 @@ public class BulkDelete extends BulkOperator {
|
|||
this.addCompleted();
|
||||
}
|
||||
|
||||
if (getSucceeded() == 0 && lastError != null) {
|
||||
throw new DataSpecificationException(lastError);
|
||||
}
|
||||
|
||||
return getSucceeded();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ import com.rebuild.core.service.general.recyclebin.RecycleStore;
|
|||
import com.rebuild.core.service.general.series.SeriesGeneratorFactory;
|
||||
import com.rebuild.core.service.notification.NotificationObserver;
|
||||
import com.rebuild.core.service.trigger.*;
|
||||
import com.rebuild.core.support.HeavyStopWatcher;
|
||||
import com.rebuild.core.service.trigger.impl.GroupAggregation;
|
||||
import com.rebuild.core.support.i18n.Language;
|
||||
import com.rebuild.core.support.task.TaskExecutors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
@ -104,16 +104,22 @@ public class GeneralEntityService extends ObservableService implements EntitySer
|
|||
boolean checkDetailsRepeated = rcm == GeneralEntityServiceContextHolder.RCM_CHECK_DETAILS
|
||||
|| rcm == GeneralEntityServiceContextHolder.RCM_CHECK_ALL;
|
||||
|
||||
// 明细实体 Service
|
||||
final EntityService des = Application.getEntityService(record.getEntity().getDetailEntity().getEntityCode());
|
||||
|
||||
// 先删除
|
||||
for (Record d : details) {
|
||||
if (d instanceof DeleteRecord) {
|
||||
delete(d.getPrimary());
|
||||
continue;
|
||||
}
|
||||
if (d instanceof DeleteRecord) des.delete(d.getPrimary());
|
||||
}
|
||||
|
||||
// 再保存
|
||||
for (Record d : details) {
|
||||
if (d instanceof DeleteRecord) continue;
|
||||
|
||||
if (checkDetailsRepeated) {
|
||||
d.setID(dtf, mainid); // for check
|
||||
|
||||
List<Record> repeated = getAndCheckRepeated(d, 20);
|
||||
List<Record> repeated = des.getAndCheckRepeated(d, 20);
|
||||
if (!repeated.isEmpty()) {
|
||||
throw new RepeatedRecordsException(repeated);
|
||||
}
|
||||
|
@ -121,9 +127,9 @@ public class GeneralEntityService extends ObservableService implements EntitySer
|
|||
|
||||
if (d.getPrimary() == null) {
|
||||
d.setID(dtf, mainid);
|
||||
create(d);
|
||||
des.create(d);
|
||||
} else {
|
||||
update(d);
|
||||
des.update(d);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,7 +149,36 @@ public class GeneralEntityService extends ObservableService implements EntitySer
|
|||
if (!checkModifications(record, BizzPermission.UPDATE)) {
|
||||
return record;
|
||||
}
|
||||
return super.update(record);
|
||||
|
||||
// 先更新
|
||||
record = super.update(record);
|
||||
|
||||
// 传导给明细(若有)
|
||||
// 仅分组聚合触发器
|
||||
Entity de = record.getEntity().getDetailEntity();
|
||||
if (de != null) {
|
||||
TriggerAction[] hasTriggers = RobotTriggerManager.instance.getActions(de, TriggerWhen.UPDATE);
|
||||
boolean hasGroupAggregation = false;
|
||||
for (TriggerAction ta : hasTriggers) {
|
||||
if (ta instanceof GroupAggregation) {
|
||||
hasGroupAggregation = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasGroupAggregation) {
|
||||
RobotTriggerManual triggerManual = new RobotTriggerManual();
|
||||
ID opUser = UserService.SYSTEM_USER;
|
||||
|
||||
for (ID did : queryDetails(record.getPrimary(), de, 1)) {
|
||||
Record dUpdate = EntityHelper.forUpdate(did, opUser, false);
|
||||
triggerManual.onUpdate(
|
||||
OperatingContext.create(opUser, BizzPermission.UPDATE, dUpdate, dUpdate));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -161,9 +196,7 @@ public class GeneralEntityService extends ObservableService implements EntitySer
|
|||
|
||||
Map<String, Set<ID>> recordsOfCascaded = getCascadedRecords(record, cascades, BizzPermission.DELETE);
|
||||
for (Map.Entry<String, Set<ID>> e : recordsOfCascaded.entrySet()) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Cascade delete - " + e.getKey() + " > " + e.getValue());
|
||||
}
|
||||
log.info("Cascading delete - {} > {} ", e.getKey(), e.getValue());
|
||||
|
||||
for (ID id : e.getValue()) {
|
||||
if (Application.getPrivilegesManager().allowDelete(currentUser, id)) {
|
||||
|
@ -173,7 +206,7 @@ public class GeneralEntityService extends ObservableService implements EntitySer
|
|||
try {
|
||||
deleted = this.deleteInternal(id);
|
||||
} catch (DataSpecificationException ex) {
|
||||
log.warn("Cannot delete : " + id + " Ex : " + ex);
|
||||
log.warn("Cannot delete {} because {}", id, ex.getLocalizedMessage());
|
||||
} finally {
|
||||
if (deleted > 0) {
|
||||
affected++;
|
||||
|
@ -182,7 +215,7 @@ public class GeneralEntityService extends ObservableService implements EntitySer
|
|||
}
|
||||
}
|
||||
} else {
|
||||
log.warn("No have privileges to DELETE : " + currentUser + " > " + id);
|
||||
log.warn("No have privileges to DELETE : {} > {}", currentUser, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -203,25 +236,12 @@ public class GeneralEntityService extends ObservableService implements EntitySer
|
|||
return 0;
|
||||
}
|
||||
|
||||
// 手动删除明细记录,以执行触发器
|
||||
Entity hasDetailEntity = MetadataHelper.getEntity(record.getEntityCode()).getDetailEntity();
|
||||
if (hasDetailEntity != null) {
|
||||
TriggerAction[] hasTriggers = RobotTriggerManager.instance.getActions(hasDetailEntity, TriggerWhen.DELETE);
|
||||
|
||||
if (hasTriggers != null && hasTriggers.length > 0) {
|
||||
String sql = String.format("select %s from %s where %s = ?",
|
||||
hasDetailEntity.getPrimaryField().getName(), hasDetailEntity.getName(),
|
||||
MetadataHelper.getDetailToMainField(hasDetailEntity).getName());
|
||||
|
||||
Object[][] details = Application.getQueryFactory().createQueryNoFilter(sql)
|
||||
.setParameter(1, record).array();
|
||||
|
||||
HeavyStopWatcher.createWatcher("PERFORMANCE ISSUE", "DELETE:" + details.length);
|
||||
for (Object[] d : details) {
|
||||
// 明细无约束检查
|
||||
super.delete((ID) d[0]);
|
||||
}
|
||||
HeavyStopWatcher.clean();
|
||||
// 手动删除明细记录,以执行系统规则(如触发器、附件删除等)
|
||||
Entity de = MetadataHelper.getEntity(record.getEntityCode()).getDetailEntity();
|
||||
if (de != null) {
|
||||
for (ID did : queryDetails(record, de, 0)) {
|
||||
// 明细无约束检查 checkModifications
|
||||
super.delete(did);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -241,11 +261,9 @@ public class GeneralEntityService extends ObservableService implements EntitySer
|
|||
int affected = 0;
|
||||
if (to.equals(Application.getRecordOwningCache().getOwningUser(record))) {
|
||||
// No need to change
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("The record owner has not changed, ignore : {}", record);
|
||||
}
|
||||
log.debug("The record owner has not changed, ignore : {}", record);
|
||||
} else {
|
||||
assignBefore = countObservers() > 0 ? record(assignAfter) : null;
|
||||
assignBefore = countObservers() > 0 ? recordSnap(assignAfter) : null;
|
||||
|
||||
delegateService.update(assignAfter);
|
||||
Application.getRecordOwningCache().cleanOwningUser(record);
|
||||
|
@ -254,9 +272,8 @@ public class GeneralEntityService extends ObservableService implements EntitySer
|
|||
|
||||
Map<String, Set<ID>> cass = getCascadedRecords(record, cascades, BizzPermission.ASSIGN);
|
||||
for (Map.Entry<String, Set<ID>> e : cass.entrySet()) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Cascading assign - {} > {}", e.getKey(), e.getValue());
|
||||
}
|
||||
log.info("Cascading assign - {} > {}", e.getKey(), e.getValue());
|
||||
|
||||
for (ID casid : e.getValue()) {
|
||||
affected += assign(casid, to, null);
|
||||
}
|
||||
|
@ -315,10 +332,11 @@ public class GeneralEntityService extends ObservableService implements EntitySer
|
|||
}
|
||||
|
||||
} else {
|
||||
// // 可以共享给自己
|
||||
// if (to.equals(Application.getRecordOwningCache().getOwningUser(record))) {
|
||||
// log.debug("Share to the same user as the record, ignore : {}", record);
|
||||
// }
|
||||
// 可以共享给自己
|
||||
if (log.isDebugEnabled()
|
||||
&& to.equals(Application.getRecordOwningCache().getOwningUser(record))) {
|
||||
log.debug("Share to the same user as the record, ignore : {}", record);
|
||||
}
|
||||
|
||||
delegateService.create(sharedAfter);
|
||||
affected = 1;
|
||||
|
@ -327,7 +345,7 @@ public class GeneralEntityService extends ObservableService implements EntitySer
|
|||
|
||||
Map<String, Set<ID>> cass = getCascadedRecords(record, cascades, BizzPermission.SHARE);
|
||||
for (Map.Entry<String, Set<ID>> e : cass.entrySet()) {
|
||||
log.debug("Cascade share - {} > {}", e.getKey(), e.getValue());
|
||||
log.info("Cascading share - {} > {}", e.getKey(), e.getValue());
|
||||
|
||||
for (ID casid : e.getValue()) {
|
||||
affected += share(casid, to, null, rights);
|
||||
|
@ -351,7 +369,7 @@ public class GeneralEntityService extends ObservableService implements EntitySer
|
|||
unsharedBefore.setNull("belongEntity");
|
||||
unsharedBefore.setNull("recordId");
|
||||
unsharedBefore.setNull("shareTo");
|
||||
unsharedBefore = record(unsharedBefore);
|
||||
unsharedBefore = recordSnap(unsharedBefore);
|
||||
}
|
||||
|
||||
delegateService.delete(accessId);
|
||||
|
@ -692,9 +710,23 @@ public class GeneralEntityService extends ObservableService implements EntitySer
|
|||
// 触发器
|
||||
|
||||
ID opUser = UserContextHolder.getUser();
|
||||
Record before = approvalRecord.clone();
|
||||
RobotTriggerManual triggerManual = new RobotTriggerManual();
|
||||
|
||||
// 传导给明细(若有)
|
||||
|
||||
Entity de = approvalRecord.getEntity().getDetailEntity();
|
||||
TriggerAction[] hasTriggers = de == null ? null : RobotTriggerManager.instance.getActions(de,
|
||||
state == ApprovalState.APPROVED ? TriggerWhen.APPROVED : TriggerWhen.REVOKED);
|
||||
|
||||
if (hasTriggers != null && hasTriggers.length > 0) {
|
||||
for (ID did : queryDetails(record, de, 0)) {
|
||||
Record dAfter = EntityHelper.forUpdate(did, opUser, false);
|
||||
triggerManual.onApproved(
|
||||
OperatingContext.create(opUser, BizzPermission.UPDATE, null, dAfter));
|
||||
}
|
||||
}
|
||||
|
||||
Record before = approvalRecord.clone();
|
||||
if (state == ApprovalState.REVOKED) {
|
||||
before.setInt(EntityHelper.ApprovalState, ApprovalState.APPROVED.getState());
|
||||
triggerManual.onRevoked(
|
||||
|
@ -705,4 +737,19 @@ public class GeneralEntityService extends ObservableService implements EntitySer
|
|||
OperatingContext.create(opUser, BizzPermission.UPDATE, before, approvalRecord));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<ID> queryDetails(ID mainid, Entity detail, int maxSize) {
|
||||
String sql = String.format("select %s from %s where %s = ?",
|
||||
detail.getPrimaryField().getName(), detail.getName(),
|
||||
MetadataHelper.getDetailToMainField(detail).getName());
|
||||
|
||||
Query query = Application.createQueryNoFilter(sql).setParameter(1, mainid);
|
||||
if (maxSize > 0) query.setLimit(maxSize);
|
||||
|
||||
Object[][] array = query.array();
|
||||
List<ID> ids = new ArrayList<>();
|
||||
|
||||
for (Object[] o : array) ids.add((ID) o[0]);
|
||||
return ids;
|
||||
}
|
||||
}
|
|
@ -81,7 +81,7 @@ public abstract class ObservableService extends Observable implements ServiceSpe
|
|||
|
||||
@Override
|
||||
public Record update(Record record) {
|
||||
final Record before = countObservers() > 0 ? record(record) : null;
|
||||
final Record before = countObservers() > 0 ? recordSnap(record) : null;
|
||||
|
||||
record = delegateService.update(record);
|
||||
|
||||
|
@ -99,7 +99,7 @@ public abstract class ObservableService extends Observable implements ServiceSpe
|
|||
Record deleted = null;
|
||||
if (countObservers() > 0) {
|
||||
deleted = EntityHelper.forUpdate(recordId, currentUser);
|
||||
deleted = record(deleted);
|
||||
deleted = recordSnap(deleted);
|
||||
|
||||
// 删除前触发,做一些状态保持
|
||||
setChanged();
|
||||
|
@ -121,7 +121,7 @@ public abstract class ObservableService extends Observable implements ServiceSpe
|
|||
* @param base
|
||||
* @return
|
||||
*/
|
||||
protected Record record(Record base) {
|
||||
protected Record recordSnap(Record base) {
|
||||
final ID primaryId = base.getPrimary();
|
||||
Assert.notNull(primaryId, "Record primary cannot be null");
|
||||
|
||||
|
@ -161,7 +161,10 @@ public abstract class ObservableService extends Observable implements ServiceSpe
|
|||
log.warn("RecycleBin inactivated! DELETE {} by {}", recordId, currentUser);
|
||||
}
|
||||
|
||||
if (recycleBin != null) recycleBin.add(recordId);
|
||||
return recycleBin;
|
||||
if (recycleBin != null && recycleBin.add(recordId)) {
|
||||
return recycleBin;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -106,9 +106,8 @@ public class OperatingContext {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
String clearTxt = "{ Operator: %s (), Action: %s, Record(s): %s(%d) }";
|
||||
return String.format(clearTxt,
|
||||
getOperator(), getAction().getName(), getAnyRecord().getPrimary(), getAffected().length);
|
||||
return String.format("[ Action:%s, Record(s):%s(%d) ]",
|
||||
getAction().getName(), getAnyRecord().getPrimary(), getAffected().length);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -17,6 +17,7 @@ import com.rebuild.core.metadata.MetadataHelper;
|
|||
import com.rebuild.core.privileges.UserService;
|
||||
import com.rebuild.core.service.general.recyclebin.RecycleBinCleanerJob;
|
||||
import com.rebuild.core.service.trigger.RobotTriggerObserver;
|
||||
import com.rebuild.core.service.trigger.TriggerSource;
|
||||
import com.rebuild.utils.JSONUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
@ -126,9 +127,9 @@ public class RevisionHistoryObserver extends OperatingObserver {
|
|||
record.setString("revisionContent", JSONUtils.EMPTY_ARRAY_STR);
|
||||
}
|
||||
|
||||
OperatingContext triggerSource = RobotTriggerObserver.getTriggerSource();
|
||||
TriggerSource triggerSource = RobotTriggerObserver.getTriggerSource();
|
||||
if (triggerSource != null) {
|
||||
record.setID("channelWith", triggerSource.getAnyRecord().getPrimary());
|
||||
record.setID("channelWith", triggerSource.getOriginRecord());
|
||||
}
|
||||
|
||||
if (context.getOperationIp() != null) {
|
||||
|
|
|
@ -15,6 +15,7 @@ import com.alibaba.fastjson.JSONArray;
|
|||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.rebuild.core.Application;
|
||||
import com.rebuild.core.metadata.MetadataHelper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
@ -26,6 +27,7 @@ import java.util.List;
|
|||
* @author devezhao
|
||||
* @since 2019/8/19
|
||||
*/
|
||||
@Slf4j
|
||||
public class RecycleBean implements Serializable {
|
||||
private static final long serialVersionUID = -1058552856844427594L;
|
||||
|
||||
|
@ -53,6 +55,11 @@ public class RecycleBean implements Serializable {
|
|||
.append(" = ?")
|
||||
.toString();
|
||||
Record queryed = Application.createQueryNoFilter(sql).setParameter(1, this.recordId).record();
|
||||
if (queryed == null) {
|
||||
log.warn("Serialize record not exists : {}", this.recordId);
|
||||
return null;
|
||||
}
|
||||
|
||||
JSONObject s = (JSONObject) queryed.serialize();
|
||||
|
||||
Entity detailEntity = entity.getDetailEntity();
|
||||
|
|
|
@ -47,9 +47,10 @@ public class RecycleStore {
|
|||
* 添加待转存记录
|
||||
*
|
||||
* @param recordId
|
||||
* @return
|
||||
*/
|
||||
public void add(ID recordId) {
|
||||
this.add(recordId, null);
|
||||
public boolean add(ID recordId) {
|
||||
return this.add(recordId, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -57,10 +58,14 @@ public class RecycleStore {
|
|||
*
|
||||
* @param recordId
|
||||
* @param with
|
||||
* @return
|
||||
*/
|
||||
public void add(ID recordId, ID with) {
|
||||
public boolean add(ID recordId, ID with) {
|
||||
JSON s = new RecycleBean(recordId).serialize();
|
||||
if (s == null) return false;
|
||||
|
||||
data.add(new Object[] { recordId, s, with });
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -76,13 +81,13 @@ public class RecycleStore {
|
|||
* @return
|
||||
*/
|
||||
public int store() {
|
||||
Record record = EntityHelper.forNew(EntityHelper.RecycleBin, UserService.SYSTEM_USER);
|
||||
record.setID("deletedBy", this.user);
|
||||
record.setDate("deletedOn", CalendarUtils.now());
|
||||
final Record base = EntityHelper.forNew(EntityHelper.RecycleBin, UserService.SYSTEM_USER);
|
||||
base.setID("deletedBy", this.user);
|
||||
base.setDate("deletedOn", CalendarUtils.now());
|
||||
|
||||
int affected = 0;
|
||||
for (Object[] o : data) {
|
||||
Record clone = record.clone();
|
||||
Record clone = base.clone();
|
||||
ID recordId = (ID) o[0];
|
||||
Entity belongEntity = MetadataHelper.getEntity(recordId.getEntityCode());
|
||||
clone.setString("belongEntity", belongEntity.getName());
|
||||
|
|
|
@ -123,7 +123,7 @@ public class AdvFilterParser extends SetUser {
|
|||
indexItemSqls.put(index, itemSql.trim());
|
||||
this.includeFields.add(item.getString("field"));
|
||||
}
|
||||
if (Application.devMode()) log.info("Parse item : {} >> {}", item, itemSql);
|
||||
if (Application.devMode()) log.info("[dev] Parse item : {} >> {}", item, itemSql);
|
||||
}
|
||||
|
||||
if (indexItemSqls.isEmpty()) return null;
|
||||
|
|
|
@ -8,13 +8,19 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
package com.rebuild.core.service.query;
|
||||
|
||||
import cn.devezhao.persist4j.Entity;
|
||||
import cn.devezhao.persist4j.Field;
|
||||
import cn.devezhao.persist4j.Query;
|
||||
import cn.devezhao.persist4j.Record;
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.rebuild.core.Application;
|
||||
import com.rebuild.core.metadata.MetadataHelper;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author devezhao
|
||||
|
@ -55,4 +61,48 @@ public class QueryHelper {
|
|||
}
|
||||
return Application.createQueryNoFilter(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取完整记录
|
||||
*
|
||||
* @param recordId
|
||||
* @return
|
||||
*/
|
||||
public static Record recordNoFilter(ID recordId) {
|
||||
Entity entity = MetadataHelper.getEntity(recordId.getEntityCode());
|
||||
|
||||
List<String> fields = new ArrayList<>();
|
||||
for (Field field : entity.getFields()) {
|
||||
fields.add(field.getName());
|
||||
}
|
||||
|
||||
String sql = String.format("select %s from %s where %s = ?",
|
||||
StringUtils.join(fields, ","), entity.getName(),
|
||||
entity.getPrimaryField().getName());
|
||||
|
||||
Record record = Application.createQueryNoFilter(sql).setParameter(1, recordId).record();
|
||||
Assert.notNull(record, "RECORD NOT EXISTS : " + recordId);
|
||||
return record;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取明细(完整)记录
|
||||
*
|
||||
* @param mainId
|
||||
* @return
|
||||
*/
|
||||
public static List<Record> detailsNoFilter(ID mainId) {
|
||||
Entity detailEntity = MetadataHelper.getEntity(mainId.getEntityCode()).getDetailEntity();
|
||||
|
||||
List<String> fields = new ArrayList<>();
|
||||
for (Field field : detailEntity.getFields()) {
|
||||
fields.add(field.getName());
|
||||
}
|
||||
|
||||
String sql = String.format("select %s from %s where %s = ?",
|
||||
StringUtils.join(fields, ","), detailEntity.getName(),
|
||||
MetadataHelper.getDetailToMainField(detailEntity).getName());
|
||||
|
||||
return Application.createQueryNoFilter(sql).setParameter(1, mainId).list();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,4 +34,11 @@ public class RobotTriggerManual extends RobotTriggerObserver {
|
|||
public void onRevoked(OperatingContext context) {
|
||||
execAction(context, TriggerWhen.REVOKED);
|
||||
}
|
||||
|
||||
// -- PUBLIC
|
||||
|
||||
@Override
|
||||
public void onUpdate(OperatingContext context) {
|
||||
super.onUpdate(context);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ package com.rebuild.core.service.trigger;
|
|||
import cn.devezhao.persist4j.engine.ID;
|
||||
import cn.devezhao.persist4j.metadata.MissingMetaExcetion;
|
||||
import com.googlecode.aviator.exception.ExpressionRuntimeException;
|
||||
import com.rebuild.core.Application;
|
||||
import com.rebuild.core.RebuildException;
|
||||
import com.rebuild.core.metadata.EntityHelper;
|
||||
import com.rebuild.core.service.general.OperatingContext;
|
||||
|
@ -19,8 +18,10 @@ import com.rebuild.core.service.general.RepeatedRecordsException;
|
|||
import com.rebuild.core.support.CommonsLog;
|
||||
import com.rebuild.core.support.i18n.Language;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.NamedThreadLocal;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Observable;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import static com.rebuild.core.support.CommonsLog.TYPE_TRIGGER;
|
||||
|
@ -34,8 +35,15 @@ import static com.rebuild.core.support.CommonsLog.TYPE_TRIGGER;
|
|||
@Slf4j
|
||||
public class RobotTriggerObserver extends OperatingObserver {
|
||||
|
||||
private static final ThreadLocal<OperatingContext> TRIGGER_SOURCE = new ThreadLocal<>();
|
||||
private static final ThreadLocal<String> TRIGGER_SOURCE_LAST = new ThreadLocal<>();
|
||||
private static final ThreadLocal<TriggerSource> TRIGGER_SOURCE = new NamedThreadLocal<>("Trigger source");
|
||||
|
||||
private static final ThreadLocal<Boolean> SKIP_TRIGGERS = new NamedThreadLocal<>("Skip triggers");
|
||||
|
||||
@Override
|
||||
public void update(final Observable o, Object arg) {
|
||||
if (isSkipTriggers(false)) return;
|
||||
super.update(o, arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(OperatingContext context) {
|
||||
|
@ -102,7 +110,6 @@ public class RobotTriggerObserver extends OperatingObserver {
|
|||
*/
|
||||
protected void execAction(OperatingContext context, TriggerWhen when) {
|
||||
final ID primaryId = context.getAnyRecord().getPrimary();
|
||||
final String sourceName = primaryId + ":" + when.name().charAt(0);
|
||||
|
||||
TriggerAction[] beExecuted = when == TriggerWhen.DELETE
|
||||
? DELETE_ACTION_HOLDS.get(primaryId)
|
||||
|
@ -111,25 +118,33 @@ public class RobotTriggerObserver extends OperatingObserver {
|
|||
return;
|
||||
}
|
||||
|
||||
final boolean originTriggerSource = getTriggerSource() == null;
|
||||
final TriggerSource triggerSource = getTriggerSource();
|
||||
final boolean originTriggerSource = triggerSource == null;
|
||||
|
||||
// 设置原始触发源
|
||||
if (originTriggerSource) {
|
||||
TRIGGER_SOURCE.set(context);
|
||||
TRIGGER_SOURCE.set(new TriggerSource(context, when));
|
||||
|
||||
} else {
|
||||
// 自己触发自己,避免无限执行
|
||||
boolean x = primaryId.equals(getTriggerSource().getAnyRecord().getPrimary());
|
||||
boolean xor = x || sourceName.equals(TRIGGER_SOURCE_LAST.get());
|
||||
if (x || xor) {
|
||||
if (Application.devMode()) log.warn("Self trigger, ignore : {}", sourceName);
|
||||
return;
|
||||
// 是否自己触发自己,避免无限执行
|
||||
boolean isOriginRecord = primaryId.equals(triggerSource.getOriginRecord());
|
||||
|
||||
String lastKey = triggerSource.getLastSourceKey();
|
||||
triggerSource.addNext(context, when);
|
||||
String currentKey = triggerSource.getLastSourceKey();
|
||||
|
||||
if (isOriginRecord && lastKey.equals(currentKey)) {
|
||||
if (!triggerSource.isSkipOnce()) {
|
||||
log.warn("Self trigger, ignore : {} < {}", currentKey, lastKey);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TRIGGER_SOURCE_LAST.set(sourceName);
|
||||
|
||||
int depth = triggerSource == null ? 1 : triggerSource.getSourceDepth();
|
||||
try {
|
||||
for (TriggerAction action : beExecuted) {
|
||||
log.info("Trigger [ {} ] executing on record ({}) : {}", action.getType(), when.name(), primaryId);
|
||||
log.info("Trigger.{} [ {} ] executing on record ({}) : {}", depth, action.getType(), when.name(), primaryId);
|
||||
|
||||
try {
|
||||
action.execute(context);
|
||||
|
@ -162,8 +177,8 @@ public class RobotTriggerObserver extends OperatingObserver {
|
|||
|
||||
} finally {
|
||||
if (originTriggerSource) {
|
||||
log.info("Clear trigger-source : {}", getTriggerSource());
|
||||
TRIGGER_SOURCE.remove();
|
||||
TRIGGER_SOURCE_LAST.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -182,19 +197,39 @@ public class RobotTriggerObserver extends OperatingObserver {
|
|||
return effectId;
|
||||
}
|
||||
|
||||
// --
|
||||
|
||||
/**
|
||||
* 获取当前(线程)触发源(如有)
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static OperatingContext getTriggerSource() {
|
||||
public static TriggerSource getTriggerSource() {
|
||||
return TRIGGER_SOURCE.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制自执行
|
||||
*/
|
||||
public static void forceTriggerSelf() {
|
||||
TRIGGER_SOURCE_LAST.set(null);
|
||||
public static void forceTriggerSelfOnce() {
|
||||
getTriggerSource().setSkipOnce();
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳过触发器的执行
|
||||
*/
|
||||
public static void setSkipTriggers() {
|
||||
SKIP_TRIGGERS.set(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param once
|
||||
* @return
|
||||
* @see #setSkipTriggers()
|
||||
*/
|
||||
public static boolean isSkipTriggers(boolean once) {
|
||||
Boolean is = SKIP_TRIGGERS.get();
|
||||
if (is != null && once) SKIP_TRIGGERS.remove();
|
||||
return is != null && is;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import com.rebuild.core.service.general.OperatingContext;
|
|||
|
||||
/**
|
||||
* 触发动作/操作定义。
|
||||
* 注意:如果是异步处理将没有事物,同时会丢失一些线程量
|
||||
* 注意:如果是异步处理将没有事物,同时会丢失一些线程量(如果需要请手动设置)
|
||||
*
|
||||
* @author devezhao zhaofang123@gmail.com
|
||||
* @since 2019/05/23
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
/*!
|
||||
Copyright (c) REBUILD <https://getrebuild.com/> and/or its owners. All rights reserved.
|
||||
|
||||
rebuild is dual-licensed under commercial and open source licenses (GPLv3).
|
||||
See LICENSE and COMMERCIAL in the project root for license information.
|
||||
*/
|
||||
|
||||
package com.rebuild.core.service.trigger;
|
||||
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import com.rebuild.core.service.general.OperatingContext;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author RB
|
||||
* @since 2022/07/04
|
||||
*/
|
||||
public class TriggerSource {
|
||||
|
||||
private final List<Object[]> sources = new ArrayList<>();
|
||||
private boolean skipOnce = false;
|
||||
|
||||
protected TriggerSource(OperatingContext origin, TriggerWhen originAction) {
|
||||
addNext(origin, originAction);
|
||||
}
|
||||
|
||||
public void addNext(OperatingContext next, TriggerWhen nextAction) {
|
||||
sources.add(new Object[] { next, nextAction });
|
||||
}
|
||||
|
||||
public OperatingContext getOrigin() {
|
||||
return (OperatingContext) sources.get(0)[0];
|
||||
}
|
||||
|
||||
public ID getOriginRecord() {
|
||||
return getOrigin().getAnyRecord().getPrimary();
|
||||
}
|
||||
|
||||
public OperatingContext getLast() {
|
||||
return (OperatingContext) sources.get(sources.size() - 1)[0];
|
||||
}
|
||||
|
||||
public String getLastSourceKey() {
|
||||
Object[] last = sources.get(sources.size() - 1);
|
||||
return ((OperatingContext) last[0]).getAnyRecord().getPrimary()
|
||||
+ ":" + ((TriggerWhen) last[1]).name().charAt(0);
|
||||
}
|
||||
|
||||
public int getSourceDepth() {
|
||||
return sources.size();
|
||||
}
|
||||
|
||||
public void setSkipOnce() {
|
||||
skipOnce = true;
|
||||
}
|
||||
|
||||
public boolean isSkipOnce() {
|
||||
boolean skipOnceHold = skipOnce;
|
||||
skipOnce = false;
|
||||
return skipOnceHold;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Object[] s : sources) {
|
||||
sb.append(((TriggerWhen) s[1]).name().charAt(0)).append("#").append(s[0]).append(" >> ");
|
||||
}
|
||||
return sb.append("END").toString();
|
||||
}
|
||||
}
|
|
@ -13,6 +13,10 @@ import com.rebuild.core.service.general.EntityService;
|
|||
/**
|
||||
* 触发动作定义
|
||||
*
|
||||
* 动作传导:
|
||||
* 1. 主记录删除/审批/撤销审批会传导至明细
|
||||
* 2. 主记录更新(仅分组聚合)会传导至明细
|
||||
*
|
||||
* @author devezhao zhaofang123@gmail.com
|
||||
* @see BizzPermission
|
||||
* @since 2019/05/23
|
||||
|
|
|
@ -14,6 +14,8 @@ import java.util.Date;
|
|||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Wrap {@link Date}
|
||||
*
|
||||
* @author devezhao
|
||||
* @since 2021/4/12
|
||||
*/
|
||||
|
|
|
@ -44,6 +44,7 @@ public class AviatorUtils {
|
|||
addCustomFunction(new CurrentDateFunction());
|
||||
addCustomFunction(new LocationDistanceFunction());
|
||||
addCustomFunction(new RequestFunctuin());
|
||||
addCustomFunction(new TextFunction());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -18,7 +18,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Usage: LOCATIONDISTANCE(location1, location2)
|
||||
* Usage: LOCATIONDISTANCE($location1, $location2)
|
||||
* Return: Number (米)
|
||||
*
|
||||
* @author devezhao
|
||||
|
|
|
@ -18,7 +18,7 @@ import java.io.IOException;
|
|||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Usage: REQUEST(url, [defaultValue])
|
||||
* Usage: REQUEST($url, [$defaultValue])
|
||||
* Return: String
|
||||
*
|
||||
* @author devezhao
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/*!
|
||||
Copyright (c) REBUILD <https://getrebuild.com/> and/or its owners. All rights reserved.
|
||||
|
||||
rebuild is dual-licensed under commercial and open source licenses (GPLv3).
|
||||
See LICENSE and COMMERCIAL in the project root for license information.
|
||||
*/
|
||||
|
||||
|
||||
package com.rebuild.core.service.trigger.aviator;
|
||||
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import com.googlecode.aviator.runtime.function.AbstractFunction;
|
||||
import com.googlecode.aviator.runtime.type.AviatorObject;
|
||||
import com.googlecode.aviator.runtime.type.AviatorString;
|
||||
import com.rebuild.core.support.general.FieldValueHelper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Usage: TEXT($id, [$defaultValue])
|
||||
* Return: String
|
||||
*
|
||||
* @author RB
|
||||
* @since 2022/7/5
|
||||
*/
|
||||
@Slf4j
|
||||
public class TextFunction extends AbstractFunction {
|
||||
private static final long serialVersionUID = 8632984920156129174L;
|
||||
|
||||
private static final AviatorString BLANK = new AviatorString("");
|
||||
|
||||
@Override
|
||||
public AviatorObject call(Map<String, Object> env, AviatorObject arg1) {
|
||||
return call(env, arg1, BLANK);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
|
||||
Object o = arg1.getValue(env);
|
||||
if (!ID.isId(o)) return arg2;
|
||||
|
||||
ID anyid = o instanceof ID ? (ID) o : ID.valueOf(o.toString());
|
||||
String text = FieldValueHelper.getLabel(anyid, null);
|
||||
|
||||
return text == null ? arg2 : new AviatorString(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "TEXT";
|
||||
}
|
||||
}
|
|
@ -57,7 +57,7 @@ public class FieldAggregation extends TriggerAction {
|
|||
final protected int maxTriggerDepth;
|
||||
// 此触发器可能产生连锁反应
|
||||
// 如触发器 A 调用 B,而 B 又调用了 C ... 以此类推。此处记录其深度
|
||||
protected static final ThreadLocal<List<String>> TRIGGERS_CHAIN = new ThreadLocal<>();
|
||||
protected static final ThreadLocal<List<String>> TRIGGER_CHAIN = new ThreadLocal<>();
|
||||
|
||||
// 源实体
|
||||
protected Entity sourceEntity;
|
||||
|
@ -90,7 +90,7 @@ public class FieldAggregation extends TriggerAction {
|
|||
* @return
|
||||
*/
|
||||
protected List<String> checkTriggerChain(String chainName) {
|
||||
List<String> tschain = TRIGGERS_CHAIN.get();
|
||||
List<String> tschain = TRIGGER_CHAIN.get();
|
||||
if (tschain == null) {
|
||||
tschain = new ArrayList<>();
|
||||
} else {
|
||||
|
@ -98,7 +98,7 @@ public class FieldAggregation extends TriggerAction {
|
|||
|
||||
// 在整个触发链上只触发一次,避免循环调用
|
||||
if (tschain.contains(chainName)) {
|
||||
if (Application.devMode()) log.warn("Record triggered only once on trigger-chain : {}", chainName);
|
||||
if (Application.devMode()) log.warn("[dev] Record triggered only once on trigger-chain : {}", chainName);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -174,7 +174,7 @@ public class FieldAggregation extends TriggerAction {
|
|||
|
||||
// 会关联触发下一触发器(如有)
|
||||
tschain.add(chainName);
|
||||
TRIGGERS_CHAIN.set(tschain);
|
||||
TRIGGER_CHAIN.set(tschain);
|
||||
|
||||
ServiceSpec useService = MetadataHelper.isBusinessEntity(targetEntity)
|
||||
? Application.getEntityService(targetEntity.getEntityCode())
|
||||
|
@ -225,6 +225,6 @@ public class FieldAggregation extends TriggerAction {
|
|||
|
||||
@Override
|
||||
public void clean() {
|
||||
TRIGGERS_CHAIN.remove();
|
||||
TRIGGER_CHAIN.remove();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -118,7 +118,7 @@ public class FieldWriteback extends FieldAggregation {
|
|||
if (!tschainAdded) {
|
||||
tschainAdded = true;
|
||||
tschain.add(chainName);
|
||||
TRIGGERS_CHAIN.set(tschain);
|
||||
TRIGGER_CHAIN.set(tschain);
|
||||
}
|
||||
|
||||
Record targetRecord = targetRecordData.clone();
|
||||
|
|
|
@ -7,6 +7,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
|
||||
package com.rebuild.core.service.trigger.impl;
|
||||
|
||||
import cn.devezhao.bizz.privileges.impl.BizzPermission;
|
||||
import cn.devezhao.commons.CalendarUtils;
|
||||
import cn.devezhao.persist4j.Record;
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
|
@ -25,7 +26,6 @@ import com.rebuild.core.privileges.UserService;
|
|||
import com.rebuild.core.service.general.OperatingContext;
|
||||
import com.rebuild.core.service.trigger.ActionContext;
|
||||
import com.rebuild.core.service.trigger.ActionType;
|
||||
import com.rebuild.core.service.trigger.RobotTriggerObserver;
|
||||
import com.rebuild.core.service.trigger.TriggerException;
|
||||
import com.rebuild.core.support.i18n.Language;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
@ -43,6 +43,8 @@ import java.util.*;
|
|||
@Slf4j
|
||||
public class GroupAggregation extends FieldAggregation {
|
||||
|
||||
private GroupAggregationRefresh groupAggregationRefresh;
|
||||
|
||||
public GroupAggregation(ActionContext context) {
|
||||
super(context);
|
||||
}
|
||||
|
@ -52,6 +54,15 @@ public class GroupAggregation extends FieldAggregation {
|
|||
return ActionType.GROUPAGGREGATION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clean() {
|
||||
super.clean();
|
||||
|
||||
if (groupAggregationRefresh != null) {
|
||||
groupAggregationRefresh.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare(OperatingContext operatingContext) throws TriggerException {
|
||||
if (sourceEntity != null) return; // 已经初始化
|
||||
|
@ -78,13 +89,18 @@ public class GroupAggregation extends FieldAggregation {
|
|||
groupFieldsMapping.put(sourceField, targetField);
|
||||
}
|
||||
|
||||
if (groupFieldsMapping.isEmpty()) {
|
||||
log.warn("No group-fields specified");
|
||||
return;
|
||||
}
|
||||
|
||||
// 1.源记录数据
|
||||
|
||||
String ql = String.format("select %s from %s where %s = ?",
|
||||
StringUtils.join(groupFieldsMapping.keySet().iterator(), ","),
|
||||
sourceEntity.getName(), sourceEntity.getPrimaryField().getName());
|
||||
|
||||
Record sourceRecord = Application.getQueryFactory().createQueryNoFilter(ql)
|
||||
Record sourceRecord = Application.createQueryNoFilter(ql)
|
||||
.setParameter(1, actionContext.getSourceRecord())
|
||||
.record();
|
||||
|
||||
|
@ -92,16 +108,24 @@ public class GroupAggregation extends FieldAggregation {
|
|||
|
||||
List<String> qFields = new ArrayList<>();
|
||||
List<String> qFieldsFollow = new ArrayList<>();
|
||||
List<String[]> qFieldsRefresh = new ArrayList<>();
|
||||
boolean allNull = true;
|
||||
|
||||
for (Map.Entry<String, String> e : groupFieldsMapping.entrySet()) {
|
||||
String sourceField = e.getKey();
|
||||
String targetField = e.getValue();
|
||||
|
||||
Object val = sourceRecord.getObjectValue(sourceField);
|
||||
if (val != null) {
|
||||
if (val == null) {
|
||||
qFields.add(String.format("%s is null", targetField));
|
||||
qFieldsFollow.add(String.format("%s is null", sourceField));
|
||||
} else {
|
||||
//noinspection ConstantConditions
|
||||
EasyField sourceFieldEasy = EasyMetaFactory.valueOf(
|
||||
Objects.requireNonNull(MetadataHelper.getLastJoinField(sourceEntity, sourceField)));
|
||||
MetadataHelper.getLastJoinField(sourceEntity, sourceField));
|
||||
//noinspection ConstantConditions
|
||||
EasyField targetFieldEasy = EasyMetaFactory.valueOf(
|
||||
Objects.requireNonNull(MetadataHelper.getLastJoinField(targetEntity, targetField)));
|
||||
MetadataHelper.getLastJoinField(targetEntity, targetField));
|
||||
|
||||
// @see Dimension#getSqlName
|
||||
|
||||
|
@ -110,7 +134,8 @@ public class GroupAggregation extends FieldAggregation {
|
|||
|| sourceFieldEasy.getDisplayType() == DisplayType.DATETIME) {
|
||||
|
||||
String formatKey = sourceFieldEasy.getDisplayType() == DisplayType.DATE
|
||||
? EasyFieldConfigProps.DATE_FORMAT : EasyFieldConfigProps.DATETIME_FORMAT;
|
||||
? EasyFieldConfigProps.DATE_FORMAT
|
||||
: EasyFieldConfigProps.DATETIME_FORMAT;
|
||||
int sourceFieldLength = StringUtils.defaultIfBlank(
|
||||
sourceFieldEasy.getExtraAttr(formatKey), sourceFieldEasy.getDisplayType().getDefaultFormat())
|
||||
.length();
|
||||
|
@ -154,10 +179,7 @@ public class GroupAggregation extends FieldAggregation {
|
|||
// 需要匹配等级的值
|
||||
if (sourceFieldLevel != targetFieldLevel) {
|
||||
ID parent = getItemWithLevel((ID) val, targetFieldLevel);
|
||||
if (parent == null) {
|
||||
log.error("Bad source value of classification (Maybe levels?) : {}", val);
|
||||
return;
|
||||
}
|
||||
Assert.isTrue(parent != null, Language.L("分类字段等级不兼容"));
|
||||
|
||||
val = parent;
|
||||
sourceRecord.setID(sourceField, (ID) val);
|
||||
|
@ -171,21 +193,28 @@ public class GroupAggregation extends FieldAggregation {
|
|||
|
||||
qFields.add(String.format("%s = '%s'", targetField, val));
|
||||
qFieldsFollow.add(String.format("%s = '%s'", sourceField, val));
|
||||
allNull = false;
|
||||
}
|
||||
|
||||
qFieldsRefresh.add(new String[] { targetField, sourceField, val == null ? null : val.toString() });
|
||||
}
|
||||
|
||||
if (qFields.isEmpty()) {
|
||||
log.warn("Value(s) of group-field not specified");
|
||||
if (allNull) {
|
||||
log.warn("All group-fields are null");
|
||||
return;
|
||||
}
|
||||
|
||||
this.followSourceWhere = StringUtils.join(qFieldsFollow.iterator(), " and ");
|
||||
|
||||
if (operatingContext.getAction() == BizzPermission.UPDATE) {
|
||||
this.groupAggregationRefresh = new GroupAggregationRefresh(this, qFieldsRefresh);
|
||||
}
|
||||
|
||||
ql = String.format("select %s from %s where ( %s )",
|
||||
targetEntity.getPrimaryField().getName(), targetEntity.getName(),
|
||||
StringUtils.join(qFields.iterator(), " and "));
|
||||
|
||||
Object[] targetRecord = Application.getQueryFactory().createQueryNoFilter(ql).unique();
|
||||
Object[] targetRecord = Application.createQueryNoFilter(ql).unique();
|
||||
if (targetRecord != null) {
|
||||
targetRecordId = (ID) targetRecord[0];
|
||||
return;
|
||||
|
@ -217,14 +246,13 @@ public class GroupAggregation extends FieldAggregation {
|
|||
PrivilegesGuardContextHolder.getSkipGuardOnce();
|
||||
}
|
||||
|
||||
RobotTriggerObserver.forceTriggerSelf();
|
||||
targetRecordId = newTargetRecord.getPrimary();
|
||||
}
|
||||
|
||||
private ID getItemWithLevel(ID itemId, int specLevel) {
|
||||
ID current = itemId;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
Object[] o = Application.getQueryFactory().createQueryNoFilter(
|
||||
Object[] o = Application.createQueryNoFilter(
|
||||
"select level,parent from ClassificationData where itemId = ?")
|
||||
.setParameter(1, current)
|
||||
.unique();
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
/*!
|
||||
Copyright (c) REBUILD <https://getrebuild.com/> and/or its owners. All rights reserved.
|
||||
|
||||
rebuild is dual-licensed under commercial and open source licenses (GPLv3).
|
||||
See LICENSE and COMMERCIAL in the project root for license information.
|
||||
*/
|
||||
|
||||
package com.rebuild.core.service.trigger.impl;
|
||||
|
||||
import cn.devezhao.bizz.privileges.impl.BizzPermission;
|
||||
import cn.devezhao.persist4j.Entity;
|
||||
import cn.devezhao.persist4j.Record;
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import com.rebuild.core.Application;
|
||||
import com.rebuild.core.UserContextHolder;
|
||||
import com.rebuild.core.metadata.EntityHelper;
|
||||
import com.rebuild.core.service.general.OperatingContext;
|
||||
import com.rebuild.core.service.trigger.ActionContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 分组聚合目标数据刷新。
|
||||
* 场景举例:
|
||||
* 1. 新建产品A + 仓库A分组(组合A+A)
|
||||
* 2. 之后修改了仓库A > B(组合A+B),此时原(组合A+A)纪录不会更新
|
||||
* 3. 这里需要强制更新相关原纪录
|
||||
* 4. NOTE 如果组合值均为空,则无法匹配任何目标记录,此时需要全量刷新(通过任一字段必填解决)
|
||||
*
|
||||
* @author RB
|
||||
* @since 2022/7/8
|
||||
*/
|
||||
@Slf4j
|
||||
public class GroupAggregationRefresh {
|
||||
|
||||
final private GroupAggregation parent;
|
||||
final private List<String[]> fieldsRefresh;
|
||||
|
||||
// fieldsRefresh = [TargetField, SourceField, Value]
|
||||
protected GroupAggregationRefresh(GroupAggregation parent, List<String[]> fieldsRefresh) {
|
||||
this.parent = parent;
|
||||
this.fieldsRefresh = fieldsRefresh;
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
List<String> targetFields = new ArrayList<>();
|
||||
List<String> targetWhere = new ArrayList<>();
|
||||
for (String[] s : fieldsRefresh) {
|
||||
targetFields.add(s[0]);
|
||||
if (s[2] != null) {
|
||||
targetWhere.add(String.format("%s = '%s'", s[0], s[2]));
|
||||
}
|
||||
}
|
||||
|
||||
Entity entity = this.parent.targetEntity;
|
||||
String sql = String.format("select %s,%s from %s where ( %s )",
|
||||
StringUtils.join(targetFields, ","),
|
||||
entity.getPrimaryField().getName(),
|
||||
entity.getName(),
|
||||
StringUtils.join(targetWhere, " or "));
|
||||
Object[][] targetArray = Application.createQueryNoFilter(sql).array();
|
||||
log.info("Effect {} target(s) ...", targetArray.length);
|
||||
|
||||
ID triggerUser = UserContextHolder.getUser();
|
||||
ActionContext parentAc = parent.getActionContext();
|
||||
|
||||
for (Object[] o : targetArray) {
|
||||
ID targetRecordId = (ID) o[o.length - 1];
|
||||
if (targetRecordId.equals(parent.targetRecordId)) continue;
|
||||
|
||||
ActionContext actionContext = new ActionContext(null,
|
||||
parentAc.getSourceEntity(), parentAc.getActionContent(), parentAc.getConfigId());
|
||||
|
||||
GroupAggregation ga = new GroupAggregation(actionContext);
|
||||
ga.sourceEntity = parent.sourceEntity;
|
||||
ga.targetEntity = parent.targetEntity;
|
||||
ga.targetRecordId = targetRecordId;
|
||||
|
||||
List<String> qFieldsFollow = 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]));
|
||||
}
|
||||
}
|
||||
ga.followSourceWhere = StringUtils.join(qFieldsFollow, " and ");
|
||||
|
||||
Record record = EntityHelper.forUpdate((ID) o[0], triggerUser, false);
|
||||
OperatingContext oCtx = OperatingContext.create(triggerUser, BizzPermission.NONE, record, record);
|
||||
|
||||
try {
|
||||
ga.execute(oCtx);
|
||||
} catch (Exception ex) {
|
||||
log.error("Error on refresh trigger ({}) record : {}", parentAc.getConfigId(), o[0], ex);
|
||||
} finally {
|
||||
ga.clean();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -67,7 +67,7 @@ public class HeavyStopWatcher {
|
|||
* @return
|
||||
*/
|
||||
public static StopWatch clean() {
|
||||
return clean(0);
|
||||
return clean(1000);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -23,6 +23,8 @@ import org.apache.commons.lang.StringUtils;
|
|||
*/
|
||||
public class KVStorage {
|
||||
|
||||
private static final Object SETNULL = new Object();
|
||||
|
||||
/**
|
||||
* 存储
|
||||
*
|
||||
|
@ -43,6 +45,13 @@ public class KVStorage {
|
|||
setValue("custom." + key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key
|
||||
*/
|
||||
public static void removeCustomValue(String key) {
|
||||
setCustomValue(key, SETNULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key
|
||||
* @param value
|
||||
|
@ -53,6 +62,15 @@ public class KVStorage {
|
|||
.setParameter(1, key)
|
||||
.unique();
|
||||
|
||||
// 删除
|
||||
if (value == SETNULL) {
|
||||
if (exists != null) {
|
||||
Application.getCommonsService().delete((ID) exists[0]);
|
||||
Application.getCommonsCache().evict(key);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Record record;
|
||||
if (exists == null) {
|
||||
record = EntityHelper.forNew(EntityHelper.SystemConfig, UserService.SYSTEM_USER, false);
|
||||
|
|
|
@ -84,7 +84,7 @@ public class QiniuCloud {
|
|||
this.auth = Auth.create(account[0], account[1]);
|
||||
this.bucketName = account[2];
|
||||
} else {
|
||||
log.warn("No QiniuCloud configuration! Using local storage.");
|
||||
log.info("No QiniuCloud configuration! Using local storage.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import com.rebuild.core.configuration.NavBuilder;
|
|||
import com.rebuild.core.privileges.UserService;
|
||||
import com.rebuild.core.rbstore.BusinessModelImporter;
|
||||
import com.rebuild.core.rbstore.ClassificationImporter;
|
||||
import com.rebuild.core.support.License;
|
||||
import com.rebuild.core.support.RebuildConfiguration;
|
||||
import com.rebuild.core.support.distributed.UseRedis;
|
||||
import com.rebuild.core.support.task.TaskExecutors;
|
||||
|
@ -37,7 +38,11 @@ import redis.clients.jedis.Jedis;
|
|||
import redis.clients.jedis.JedisPool;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.io.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.sql.*;
|
||||
import java.time.DateTimeException;
|
||||
import java.time.ZoneId;
|
||||
|
@ -61,6 +66,8 @@ public class Installer implements InstallState {
|
|||
|
||||
private JSONObject installProps;
|
||||
|
||||
private String EXISTS_SN;
|
||||
|
||||
private Installer() { }
|
||||
|
||||
/**
|
||||
|
@ -105,7 +112,7 @@ public class Installer implements InstallState {
|
|||
File dest = RebuildConfiguration.getFileOfData(INSTALL_FILE);
|
||||
try {
|
||||
FileUtils.deleteQuietly(dest);
|
||||
try (OutputStream os = new FileOutputStream(dest)) {
|
||||
try (OutputStream os = Files.newOutputStream(dest.toPath())) {
|
||||
installProps.store(os, "REBUILD (v2) INSTALLER MAGIC !!! DO NOT EDIT !!!");
|
||||
log.info("Saved installation file : " + dest);
|
||||
}
|
||||
|
@ -165,6 +172,12 @@ public class Installer implements InstallState {
|
|||
if (!((BaseCacheTemplate<?>) o).reinjectJedisPool(pool)) break;
|
||||
}
|
||||
|
||||
// L
|
||||
if (EXISTS_SN != null) {
|
||||
System.setProperty("SN", EXISTS_SN);
|
||||
License.siteApiNoCache("api/authority/query");
|
||||
}
|
||||
|
||||
Application.init();
|
||||
}
|
||||
|
||||
|
@ -345,7 +358,7 @@ public class Installer implements InstallState {
|
|||
return;
|
||||
}
|
||||
|
||||
ub.setWhere("LOGIN_NAME = 'admin'");
|
||||
ub.setWhere(String.format("USER_ID = '%s'", UserService.ADMIN_USER));
|
||||
executeSql(ub.toSql());
|
||||
}
|
||||
|
||||
|
@ -410,23 +423,29 @@ public class Installer implements InstallState {
|
|||
*/
|
||||
public boolean isRbDatabase() {
|
||||
String rbSql = SqlBuilder.buildSelect("system_config")
|
||||
.addColumn("VALUE")
|
||||
.setWhere("ITEM = 'DBVer'")
|
||||
.addColumns("ITEM", "VALUE")
|
||||
.setWhere("ITEM = 'DBVer' or ITEM = 'SN'")
|
||||
.toSql();
|
||||
|
||||
EXISTS_SN = null;
|
||||
try (Connection conn = getConnection(null)) {
|
||||
try (Statement stmt = conn.createStatement()) {
|
||||
try (ResultSet rs = stmt.executeQuery(rbSql)) {
|
||||
if (rs.next()) {
|
||||
String dbVer = rs.getString(1);
|
||||
log.info("Check exists REBUILD database version : " + dbVer);
|
||||
return true;
|
||||
while (rs.next()) {
|
||||
String name = rs.getString(1);
|
||||
String value = rs.getString(2);
|
||||
if ("DBVer".equalsIgnoreCase(name)) {
|
||||
log.info("Check exists REBUILD database version : {}", value);
|
||||
} else if ("SN".equalsIgnoreCase(name)) {
|
||||
EXISTS_SN = value;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (SQLException ex) {
|
||||
log.info("Check REBUILD database error : " + ex.getLocalizedMessage());
|
||||
log.error("Check REBUILD database error : " + ex.getLocalizedMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -54,6 +54,9 @@ public class RebuildWebInterceptor implements AsyncHandlerInterceptor, InstallSt
|
|||
private static final ThreadLocal<RequestEntry> REQUEST_ENTRY = new NamedThreadLocal<>("RequestEntry");
|
||||
|
||||
private static final int CODE_STARTING = 600;
|
||||
private static final int CODE_DENIEDMSG = 601;
|
||||
private static final int CODE_MAINTAIN = 602;
|
||||
private static final int CODE_UNSAFE_USE = 603;
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
|
||||
|
@ -64,7 +67,7 @@ public class RebuildWebInterceptor implements AsyncHandlerInterceptor, InstallSt
|
|||
throw new DefinedException(CODE_STARTING, "Please wait while REBUILD starting up ...");
|
||||
}
|
||||
if (SystemDiagnosis._DENIEDMSG != null) {
|
||||
throw new DefinedException(CODE_STARTING, SystemDiagnosis._DENIEDMSG);
|
||||
throw new DefinedException(CODE_DENIEDMSG, SystemDiagnosis._DENIEDMSG);
|
||||
}
|
||||
|
||||
final String ipAddr = ServletUtils.getRemoteAddr(request);
|
||||
|
@ -175,7 +178,7 @@ public class RebuildWebInterceptor implements AsyncHandlerInterceptor, InstallSt
|
|||
}
|
||||
|
||||
@Override
|
||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
|
||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
|
||||
// Notings
|
||||
}
|
||||
|
||||
|
@ -296,13 +299,13 @@ public class RebuildWebInterceptor implements AsyncHandlerInterceptor, InstallSt
|
|||
Object allowIp = CommonsUtils.invokeMethod(
|
||||
"com.rebuild.rbv.commons.SafeUses#checkIp", ipAddr);
|
||||
if (!(Boolean) allowIp) {
|
||||
throw new DefinedException(DefinedException.CODE_UNSAFE_USE, Language.L("你的 IP 地址不在允许范围内"));
|
||||
throw new DefinedException(CODE_UNSAFE_USE, Language.L("你的 IP 地址不在允许范围内"));
|
||||
}
|
||||
|
||||
Object allowTime = CommonsUtils.invokeMethod(
|
||||
"com.rebuild.rbv.commons.SafeUses#checkTime", CalendarUtils.now());
|
||||
if (!(Boolean) allowTime) {
|
||||
throw new DefinedException(DefinedException.CODE_UNSAFE_USE, Language.L("当前时间不允许使用"));
|
||||
throw new DefinedException(CODE_UNSAFE_USE, Language.L("当前时间不允许使用"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -74,6 +74,7 @@ public class MetaFieldController extends BaseController {
|
|||
map.put("fieldLabel", easyMeta.getLabel());
|
||||
map.put("comments", easyMeta.getComments());
|
||||
map.put("displayType", Language.L(easyMeta.getDisplayType()));
|
||||
map.put("displayTypeName", easyMeta.getDisplayType().name());
|
||||
map.put("nullable", field.isNullable());
|
||||
map.put("builtin", easyMeta.isBuiltin());
|
||||
map.put("creatable", field.isCreatable());
|
||||
|
|
|
@ -22,6 +22,7 @@ import com.rebuild.utils.JSONUtils;
|
|||
import com.rebuild.web.BaseController;
|
||||
import com.rebuild.web.user.signup.LoginController;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
|
@ -120,12 +121,13 @@ public class InstallController extends BaseController implements InstallState {
|
|||
if (info.length() > 80) {
|
||||
info = info.substring(0, 80) + "...";
|
||||
}
|
||||
pool.destroy();
|
||||
|
||||
return RespBody.ok(Language.L("连接成功 : %s", info));
|
||||
|
||||
} catch (Exception ex) {
|
||||
return RespBody.errorl("连接错误 : %s", ThrowableUtils.getRootCause(ex).getLocalizedMessage());
|
||||
} finally {
|
||||
IOUtils.closeQuietly(pool);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@ import com.rebuild.utils.AppUtils;
|
|||
import com.rebuild.utils.CommonsUtils;
|
||||
import com.rebuild.web.BaseController;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
@ -47,12 +46,12 @@ public class CommonPageView extends BaseController {
|
|||
@GetMapping("/*.txt")
|
||||
public void txtSuffix(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
String url = request.getRequestURI();
|
||||
url = url.substring(url.lastIndexOf("/") + 1);
|
||||
|
||||
String name = url.substring(url.lastIndexOf("/") + 1);
|
||||
String content = null;
|
||||
|
||||
// WXWORK
|
||||
if (url.startsWith("WW_verify_")) {
|
||||
if (name.startsWith("WW_verify_")) {
|
||||
String fileKey = RebuildConfiguration.get(ConfigurationItem.WxworkAuthFile);
|
||||
File file = RebuildConfiguration.getFileOfData(fileKey);
|
||||
if (file.exists() && file.isFile()) {
|
||||
|
@ -61,7 +60,12 @@ public class CommonPageView extends BaseController {
|
|||
}
|
||||
// OTHERS
|
||||
else {
|
||||
content = CommonsUtils.getStringOfRes("web/" + url);
|
||||
File file = RebuildConfiguration.getFileOfData(name);
|
||||
if (file.exists() && file.isFile()) {
|
||||
content = FileUtils.readFileToString(file);
|
||||
} else {
|
||||
content = CommonsUtils.getStringOfRes("web/" + name);
|
||||
}
|
||||
}
|
||||
|
||||
if (content == null) {
|
||||
|
|
|
@ -41,6 +41,7 @@ import com.rebuild.web.KnownExceptionConverter;
|
|||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.ArrayUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.springframework.transaction.UnexpectedRollbackException;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
@ -187,6 +188,9 @@ public class GeneralOperatingController extends BaseController {
|
|||
} catch (AccessDeniedException | DataSpecificationException known) {
|
||||
log.warn(">>>>> {}", known.getLocalizedMessage());
|
||||
return RespBody.error(known.getLocalizedMessage());
|
||||
} catch (UnexpectedRollbackException rolledback) {
|
||||
log.error("ROLLEDBACK", rolledback);
|
||||
return RespBody.error("ROLLEDBACK OCCURED");
|
||||
}
|
||||
|
||||
return JSONUtils.toJSONObject(
|
||||
|
|
|
@ -11,6 +11,7 @@ import cn.devezhao.commons.web.ServletUtils;
|
|||
import cn.devezhao.persist4j.Entity;
|
||||
import cn.devezhao.persist4j.Field;
|
||||
import cn.devezhao.persist4j.Record;
|
||||
import cn.devezhao.persist4j.dialect.FieldType;
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
|
@ -167,6 +168,21 @@ public class ApprovalAdminController extends BaseController {
|
|||
fields.add(new String[] { field.getName(), EasyMetaFactory.getLabel(field)} );
|
||||
}
|
||||
}
|
||||
// 引用实体字段
|
||||
for (Field field : refFields) {
|
||||
if (field.getType() != FieldType.REFERENCE) continue;
|
||||
if (MetadataHelper.isCommonsField(field)) continue;
|
||||
|
||||
String parentName = field.getName() + ".";
|
||||
String parentLabel = EasyMetaFactory.getLabel(field) + ".";
|
||||
|
||||
Field[] refFields2 = MetadataSorter.sortFields(field.getReferenceEntity(), DisplayType.REFERENCE, DisplayType.N2NREFERENCE);
|
||||
for (Field field2 : refFields2) {
|
||||
if (isRefUserOrDeptField(field2, filterNames, false)) {
|
||||
fields.add(new String[] { parentName + field2.getName(), parentLabel + EasyMetaFactory.getLabel(field2)} );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return JSONUtils.toJSONObjectArray(
|
||||
new String[] { "id", "text" }, fields.toArray(new String[0][]));
|
||||
|
@ -212,7 +228,7 @@ public class ApprovalAdminController extends BaseController {
|
|||
shows.add(new String[] { idOrField, fieldText });
|
||||
}
|
||||
|
||||
} else if (entity.containsField(idOrField)) {
|
||||
} else if (MetadataHelper.getLastJoinField(entity, idOrField) != null) {
|
||||
String fieldLabel = EasyMetaFactory.getLabel(entity, idOrField);
|
||||
shows.add(new String[] { idOrField, fieldLabel });
|
||||
}
|
||||
|
|
|
@ -1087,7 +1087,7 @@
|
|||
"未选中任何记录":"未选中任何记录",
|
||||
"未配置":"未配置",
|
||||
"本人":"本人",
|
||||
"本公式仅做前端计算,如公式中所用字段未布局/未显示,则无法进行计算。你也可以通过 [触发器 (自动更新)](/admin/robot/triggers) 实现更强大的计算规则":"本公式仅做前端计算,如公式中所用字段未布局/未显示,则无法进行计算。你也可以通过 [触发器 (自动更新)](/admin/robot/triggers) 实现更强大的计算规则",
|
||||
"本公式仅做前端计算,如公式中所用字段未布局/未显示,则无法进行计算。你也可以通过 [触发器 (字段更新)](/admin/robot/triggers) 实现更强大的计算规则":"本公式仅做前端计算,如公式中所用字段未布局/未显示,则无法进行计算。你也可以通过 [触发器 (字段更新)](/admin/robot/triggers) 实现更强大的计算规则",
|
||||
"本周":"本周",
|
||||
"本地存储":"本地存储",
|
||||
"本季度":"本季度",
|
||||
|
@ -1605,7 +1605,7 @@
|
|||
"通用":"通用",
|
||||
"通知":"通知",
|
||||
"通知类型":"通知类型",
|
||||
"通过 [触发器 (自动更新)](/admin/robot/triggers) 可以实现更加强大的回填规则":"通过 [触发器 (自动更新)](/admin/robot/triggers) 可以实现更加强大的回填规则",
|
||||
"通过 [触发器 (字段更新)](/admin/robot/triggers) 可以实现更加强大的回填规则":"通过 [触发器 (字段更新)](/admin/robot/triggers) 可以实现更加强大的回填规则",
|
||||
"通过明细实体可以更好的组织业务关系。例如订单明细通常依附于订单,而非独立存在":"通过明细实体可以更好的组织业务关系。例如订单明细通常依附于订单,而非独立存在",
|
||||
"遇到重复记录时":"遇到重复记录时",
|
||||
"邮件":"邮件",
|
||||
|
@ -1822,7 +1822,7 @@
|
|||
"无可用选项":"无可用选项",
|
||||
"你的密码将在 **%d** 天后过期":"你的密码将在 **%d** 天后过期",
|
||||
"图表加载失败":"图表加载失败",
|
||||
"校验失败时的提示内容":"校验失败时的提示内容",
|
||||
"校验未通过时的提示内容":"校验未通过时的提示内容",
|
||||
"确认退出登录?":"确认退出登录?",
|
||||
"输入新邮箱":"输入新邮箱",
|
||||
"确认删除此筛选?":"确认删除此筛选?",
|
||||
|
@ -1832,13 +1832,13 @@
|
|||
"字段更新":"字段更新",
|
||||
"正在加载":"正在加载",
|
||||
"密码已被系统强制修改":"密码已被系统强制修改",
|
||||
"不符合数据校验条件":"不符合数据校验条件",
|
||||
"数据校验未通过":"数据校验未通过",
|
||||
"发布成功":"发布成功",
|
||||
"视图":"视图",
|
||||
"原邮箱":"原邮箱",
|
||||
"版本":"版本",
|
||||
"点击复制分享链接":"点击复制分享链接",
|
||||
"不符合校验条件的数据/记录在操作时会失败":"不符合校验条件的数据/记录在操作时会失败",
|
||||
"符合校验条件的数据/记录在操作时会提示失败":"符合校验条件的数据/记录在操作时会提示失败",
|
||||
"输入邮箱":"输入邮箱",
|
||||
"输入验证码":"输入验证码",
|
||||
"添加任务":"添加任务",
|
||||
|
@ -2210,5 +2210,7 @@
|
|||
"解锁":"解锁",
|
||||
"列表模板":"列表模板",
|
||||
"未启用":"未启用",
|
||||
"请至少添加 1 个更新规则":"请至少添加 1 个更新规则"
|
||||
"请至少添加 1 个更新规则":"请至少添加 1 个更新规则",
|
||||
"确认移除此明细?":"确认移除此明细?",
|
||||
"高度 (行数)":"高度 (行数)"
|
||||
}
|
||||
|
|
|
@ -102,7 +102,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="protips" th:utext="${bundle.L('通过 [触发器 (自动更新)](/admin/robot/triggers) 可以实现更加强大的回填规则')}"></p>
|
||||
<p class="protips" th:utext="${bundle.L('通过 [触发器 (字段更新)](/admin/robot/triggers) 可以实现更加强大的回填规则')}"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -241,6 +241,7 @@
|
|||
<select th:case="'BOOL'" class="form-control form-control-sm J_defaultValue" th:data-o="${fieldDefaultValue}">
|
||||
<option value="T">[[${bundle.L('是')}]]</option>
|
||||
<option value="F" selected>[[${bundle.L('否')}]]</option>
|
||||
<option value="N">[[${bundle.L('无')}]]</option>
|
||||
</select>
|
||||
<div th:case="*" class="input-group">
|
||||
<input
|
||||
|
@ -264,7 +265,7 @@
|
|||
<div class="form-control-plaintext formula" id="calcFormula2" th:_title="${bundle.L('无')}">[[${calcFormula ?: calcFormula}]]</div>
|
||||
<p
|
||||
class="form-text"
|
||||
th:utext="${bundle.L('本公式仅做前端计算,如公式中所用字段未布局/未显示,则无法进行计算。你也可以通过 [触发器 (自动更新)](/admin/robot/triggers) 实现更强大的计算规则')}"
|
||||
th:utext="${bundle.L('本公式仅做前端计算,如公式中所用字段未布局/未显示,则无法进行计算。你也可以通过 [触发器 (字段更新)](/admin/robot/triggers) 实现更强大的计算规则')}"
|
||||
></p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -345,7 +346,14 @@
|
|||
</span>
|
||||
</label>
|
||||
<div>
|
||||
<input type="text" class="form-control form-control-sm" id="advPattern" th:placeholder="${bundle.L('格式验证 (支持 Java 正则表达式)')}" data-toggle="dropdown" autocomplete="off" />
|
||||
<input
|
||||
type="text"
|
||||
class="form-control form-control-sm"
|
||||
id="advPattern"
|
||||
th:placeholder="${bundle.L('格式验证 (支持 Java 正则表达式)')}"
|
||||
data-toggle="dropdown"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<div class="dropdown-menu common-patt">
|
||||
<h5>[[${bundle.L('常用')}]]</h5>
|
||||
<a class="badge" data-patt="^([0-9A-Z]{15}|[0-9A-Z]{17}|[0-9A-Z]{18}|[0-9A-Z]{20})$">[[${bundle.L('税号')}]]</a>
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
<table class="table table-hover table-striped table-fixed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>[[${bundle.L('名称')}]]</th>
|
||||
<th class="use-sort pointer" data-sort-index="3">[[${bundle.L('名称')}]]</th>
|
||||
<th>[[${bundle.L('源实体')}]]</th>
|
||||
<th>[[${bundle.L('触发类型')}]]</th>
|
||||
<th>[[${bundle.L('触发动作')}]]</th>
|
||||
|
|
|
@ -194,6 +194,12 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.chart.ApprovalList .table,
|
||||
.chart.FeedsSchedule .table,
|
||||
.chart.ProjectTasks .table {
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.chart.ApprovalList .table th,
|
||||
.chart.FeedsSchedule .table th,
|
||||
.chart.ProjectTasks .table th {
|
||||
|
|
|
@ -98,12 +98,13 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
position: absolute;
|
||||
display: inline-block;
|
||||
height: 32px;
|
||||
width: 16px;
|
||||
font-size: 1.2rem;
|
||||
width: 1.4rem;
|
||||
right: 40px;
|
||||
top: 2px;
|
||||
text-align: center;
|
||||
padding-top: 6px;
|
||||
color: #737373 !important;
|
||||
font-size: 1.308rem;
|
||||
}
|
||||
|
||||
.J_defaultValue-clear:hover {
|
||||
|
@ -232,3 +233,8 @@ a#entityIcon:hover {
|
|||
.common-patt .badge:hover {
|
||||
color: #4285f4 !important;
|
||||
}
|
||||
|
||||
.input-group-append > .btn-secondary + .btn-secondary {
|
||||
border-top-right-radius: 2px !important;
|
||||
border-bottom-right-radius: 2px !important;
|
||||
}
|
||||
|
|
|
@ -819,7 +819,7 @@ textarea.row2x {
|
|||
}
|
||||
|
||||
textarea.row3x {
|
||||
height: 70px !important;
|
||||
height: 72px !important;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
|
@ -941,8 +941,8 @@ select.form-control:not([disabled]) {
|
|||
}
|
||||
|
||||
.rbform .type-BOOL .mt-1 + .form-text {
|
||||
margin-top: -5px;
|
||||
margin-bottom: 0.5rem;
|
||||
margin-top: -7px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.rbform .select2-container--default .select2-selection--multiple .select2-selection__clear,
|
||||
|
@ -4654,6 +4654,23 @@ pre.unstyle {
|
|||
right: 0;
|
||||
}
|
||||
|
||||
.protable .table thead th .tipping {
|
||||
position: absolute;
|
||||
font-size: 1.21rem;
|
||||
margin-left: 6px;
|
||||
margin-top: 1px;
|
||||
cursor: help;
|
||||
color: #777;
|
||||
}
|
||||
|
||||
.protable .table thead th .tipping:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.protable .table thead th.required .tipping {
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.protable .table tbody .col-index[title] {
|
||||
background-color: #ea4335;
|
||||
color: #fff;
|
||||
|
@ -4738,3 +4755,7 @@ pre.unstyle {
|
|||
.dropdown-menu.entity-switch .dropdown-item.current .icon {
|
||||
color: #4285f4;
|
||||
}
|
||||
|
||||
.nav-tabs > li.nav-item a.nav-link .icon {
|
||||
margin-left: 0;
|
||||
}
|
|
@ -67,9 +67,31 @@ class ConfigList extends React.Component {
|
|||
componentDidMount() {
|
||||
dlgActionAfter_List = this
|
||||
this.loadData()
|
||||
|
||||
// 搜索
|
||||
const $btn = $('.input-search .btn').click(() => this.loadData())
|
||||
$('.input-search .form-control').keydown((e) => (e.which === 13 ? $btn.trigger('click') : true))
|
||||
|
||||
$('.data-list .table th.use-sort').on('click', () => this._sortByName())
|
||||
}
|
||||
|
||||
// 简单排序
|
||||
_sortByName(data, callback) {
|
||||
const $sort = $('.data-list .table th.use-sort')
|
||||
const index = ~~($sort.data('sort-index') || 1)
|
||||
|
||||
if (!data) this.__asc = !this.__asc
|
||||
|
||||
const s = data || this.state.data
|
||||
s.sort((a, b) => {
|
||||
if (this.__asc) {
|
||||
return (a[index] || '').localeCompare(b[index] || '')
|
||||
} else {
|
||||
return (b[index] || '').localeCompare(a[index] || '')
|
||||
}
|
||||
})
|
||||
|
||||
this.setState({ data: s }, callback)
|
||||
}
|
||||
|
||||
// 加载数据
|
||||
|
@ -82,7 +104,7 @@ class ConfigList extends React.Component {
|
|||
|
||||
$.get(`${this.requestUrl}?entity=${entity || ''}&q=${$encode(q)}`, (res) => {
|
||||
if (res.error_code === 0) {
|
||||
this.setState({ data: res.data || [] }, () => {
|
||||
this._sortByName(res.data || [], () => {
|
||||
$('.rb-loading-active').removeClass('rb-loading-active')
|
||||
$('.dataTables_info').text($L('共 %d 项', this.state.data.length))
|
||||
|
||||
|
|
|
@ -62,8 +62,15 @@ $(document).ready(() => {
|
|||
render_preview()
|
||||
}
|
||||
|
||||
let _AdvFilter
|
||||
$('.J_filter').on('click', () => {
|
||||
renderRbcomp(<AdvFilter title={$L('数据过滤条件')} entity={wpc.sourceEntity} filter={dataFilter} inModal={true} confirm={saveFilter} canNoFilters={true} />)
|
||||
if (_AdvFilter) {
|
||||
_AdvFilter.show()
|
||||
} else {
|
||||
renderRbcomp(<AdvFilter title={$L('数据过滤条件')} entity={wpc.sourceEntity} filter={dataFilter} onConfirm={saveFilter} inModal canNoFilters />, null, function () {
|
||||
_AdvFilter = this
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const $cts = $('.chart-type > a').on('click', function () {
|
||||
|
|
|
@ -5,6 +5,9 @@ rebuild is dual-licensed under commercial and open source licenses (GPLv3).
|
|||
See LICENSE and COMMERCIAL in the project root for license information.
|
||||
*/
|
||||
|
||||
// in `chart-design`
|
||||
const __PREVIEW = !!(window.__PageConfig || {}).chartConfig
|
||||
|
||||
// 图表基类
|
||||
class BaseChart extends React.Component {
|
||||
constructor(props) {
|
||||
|
@ -130,7 +133,7 @@ class ChartIndex extends BaseChart {
|
|||
<div className="chart index" ref={(c) => (this._chart = c)}>
|
||||
<div className="data-item must-center text-truncate w-auto">
|
||||
<p>{data.index.label || this.label}</p>
|
||||
<a href={`${rb.baseUrl}/dashboard/view-chart-source?id=${this.props.id}`}>
|
||||
<a href={__PREVIEW ? null : `${rb.baseUrl}/dashboard/view-chart-source?id=${this.props.id}`}>
|
||||
<strong>{data.index.data}</strong>
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -238,9 +238,9 @@ const add_widget = function (item) {
|
|||
const gsi = `<div class="grid-stack-item"><div id="${chid}" class="grid-stack-item-content"></div></div>`
|
||||
// Use gridstar
|
||||
if (item.size_x || item.size_y) {
|
||||
gridstack.addWidget(gsi, (item.col || 1) - 1, (item.row || 1) - 1, item.size_x || 2, item.size_y || 2, 2, 12, 2, 12)
|
||||
gridstack.addWidget(gsi, (item.col || 1) - 1, (item.row || 1) - 1, item.size_x || 2, item.size_y || 2, true, 2, 12, 2, 24)
|
||||
} else {
|
||||
gridstack.addWidget(gsi, item.x, item.y, item.w, item.h, item.x === undefined, 2, 12, 2, 12)
|
||||
gridstack.addWidget(gsi, item.x, item.y, item.w, item.h, item.x === undefined, 2, 12, 2, 24)
|
||||
}
|
||||
|
||||
item.editable = dash_editable
|
||||
|
|
|
@ -15,6 +15,8 @@ const SHOW_ADVDESENSITIZED = ['TEXT', 'PHONE', 'EMAIL', 'NUMBER', 'DECIMAL']
|
|||
const SHOW_ADVPATTERN = ['TEXT', 'PHONE', 'EMAIL']
|
||||
const SHOW_SCANCODE = ['TEXT']
|
||||
|
||||
const CURRENT_BIZZ = '{CURRENT}'
|
||||
|
||||
$(document).ready(function () {
|
||||
const dt = wpc.fieldType
|
||||
const extConfig = wpc.extConfig
|
||||
|
@ -288,15 +290,13 @@ const _handleSeries = function () {
|
|||
}
|
||||
|
||||
const _handleDate = function (dt) {
|
||||
$('.J_defaultValue')
|
||||
.datetimepicker({
|
||||
format: dt === 'DATE' ? 'yyyy-mm-dd' : 'yyyy-mm-dd hh:ii:ss',
|
||||
minView: dt === 'DATE' ? 2 : 0,
|
||||
clearBtn: true,
|
||||
})
|
||||
.attr('readonly', true)
|
||||
$('.J_defaultValue').datetimepicker({
|
||||
format: dt === 'DATE' ? 'yyyy-mm-dd' : 'yyyy-mm-dd hh:ii:ss',
|
||||
minView: dt === 'DATE' ? 2 : 0,
|
||||
clearBtn: true,
|
||||
})
|
||||
|
||||
$(`<button class="btn btn-secondary mw-auto" type="button" title="${$L('日期公式')}"><i class="icon zmdi zmdi-settings-square"></i></button>`)
|
||||
$(`<button class="btn btn-secondary" type="button" title="${$L('日期公式')}"><i class="icon zmdi zmdi-settings-square"></i></button>`)
|
||||
.appendTo('.J_defaultValue-append')
|
||||
.on('click', () => renderRbcomp(<FormulaDate type={dt} onConfirm={(expr) => $('.J_defaultValue').val(expr)} />))
|
||||
}
|
||||
|
@ -361,7 +361,7 @@ const _handleClassification = function (useClassification) {
|
|||
}
|
||||
}
|
||||
|
||||
const $append = $(`<button class="btn btn-secondary mw-auto" type="button" title="${$L('选择默认值')}"><i class="icon zmdi zmdi-search"></i></button>`).appendTo('.J_defaultValue-append')
|
||||
const $append = $(`<button class="btn btn-secondary" type="button" title="${$L('选择默认值')}"><i class="icon zmdi zmdi-search"></i></button>`).appendTo('.J_defaultValue-append')
|
||||
|
||||
$.get(`/admin/metadata/classification/info?id=${useClassification}`, (res) => {
|
||||
$('#useClassification a')
|
||||
|
@ -432,7 +432,7 @@ const _handleReference = function (isN2N) {
|
|||
}
|
||||
}
|
||||
|
||||
const $append = $(`<button class="btn btn-secondary mw-auto" type="button" title="${$L('选择默认值')}"><i class="icon zmdi zmdi-search"></i></button>`).appendTo('.J_defaultValue-append')
|
||||
const $append = $(`<button class="btn btn-secondary" type="button" title="${$L('选择默认值')}"><i class="icon zmdi zmdi-search"></i></button>`).appendTo('.J_defaultValue-append')
|
||||
$dv.attr('readonly', true)
|
||||
$append.on('click', () => _showSearcher())
|
||||
|
||||
|
@ -456,13 +456,26 @@ const _handleReference = function (isN2N) {
|
|||
_ReferenceSearcher.hide()
|
||||
}
|
||||
|
||||
// Bizz
|
||||
if (['User', 'Department', 'Team'].includes(referenceEntity)) {
|
||||
const $current = $(`<button class="btn btn-secondary" type="button" title="${$L('当前用户')}"><i class="icon zmdi zmdi-account-o"></i></button>`).appendTo('.J_defaultValue-append')
|
||||
$current.on('click', () => {
|
||||
$dv.attr('data-value-id', CURRENT_BIZZ).val(CURRENT_BIZZ)
|
||||
$dvClear.removeClass('hide')
|
||||
})
|
||||
$dvClear.css({ right: 75 })
|
||||
}
|
||||
|
||||
_loadRefsLabel($dv, $dvClear)
|
||||
}
|
||||
|
||||
const _loadRefsLabel = function ($dv, $dvClear) {
|
||||
const dvid = $dv.val()
|
||||
if (dvid) {
|
||||
$.get(`/commons/search/read-labels?ids=${dvid}&ignoreMiss=true`, (res) => {
|
||||
|
||||
if (dvid === CURRENT_BIZZ) {
|
||||
$dvClear && $dvClear.removeClass('hide')
|
||||
} else if (dvid) {
|
||||
$.get(`/commons/search/read-labels?ids=${encodeURIComponent(dvid)}&ignoreMiss=true`, (res) => {
|
||||
if (res.data) {
|
||||
const ids = []
|
||||
const labels = []
|
||||
|
|
|
@ -52,7 +52,7 @@ $(document).ready(function () {
|
|||
$item.remove()
|
||||
})
|
||||
} else {
|
||||
render_item({ ...field, isFull: this.isFull || false, colspan: this.colspan, tip: this.tip || null })
|
||||
render_item({ ...field, isFull: this.isFull || false, colspan: this.colspan, tip: this.tip || null, height: this.height || null })
|
||||
AdvControl.set(this)
|
||||
}
|
||||
})
|
||||
|
@ -128,7 +128,7 @@ $(document).ready(function () {
|
|||
const item = { field: $this.data('field') }
|
||||
if (item.field === DIVIDER_LINE) {
|
||||
item.colspan = 4
|
||||
item.label = $this.find('span').text()
|
||||
item.label = $this.find('span').text() || ''
|
||||
} else {
|
||||
item.colspan = 2 // default
|
||||
if ($this.parent().hasClass('w-100')) item.colspan = 4
|
||||
|
@ -140,6 +140,8 @@ $(document).ready(function () {
|
|||
if (tip) item.tip = tip
|
||||
item.__newLabel = $this.find('span').text()
|
||||
if (item.__newLabel === $this.data('label')) delete item.__newLabel
|
||||
const height = $this.attr('data-height')
|
||||
if (height) item.height = height
|
||||
|
||||
AdvControl.append(item)
|
||||
}
|
||||
|
@ -198,6 +200,8 @@ const render_item = function (data) {
|
|||
else if (data.nullable === false) $handle.addClass('not-nullable')
|
||||
// 填写提示
|
||||
if (data.tip) $('<i class="J_tip zmdi zmdi-info-outline"></i>').appendTo($handle.find('span')).attr('title', data.tip)
|
||||
// 高度
|
||||
if (data.height) $handle.attr('data-height', data.height)
|
||||
|
||||
const $action = $('<div class="dd-action"></div>').appendTo($handle)
|
||||
if (data.displayType) {
|
||||
|
@ -219,7 +223,7 @@ const render_item = function (data) {
|
|||
$(`<a title="${$L('修改')}"><i class="zmdi zmdi-edit"></i></a>`)
|
||||
.appendTo($action)
|
||||
.click(function () {
|
||||
const call = function (nv) {
|
||||
const _onConfirm = function (nv) {
|
||||
// 字段名
|
||||
if (nv.fieldLabel) $item.find('.dd-handle>span').text(nv.fieldLabel)
|
||||
else $item.find('.dd-handle>span').text($item.find('.dd-handle').data('label'))
|
||||
|
@ -232,15 +236,20 @@ const render_item = function (data) {
|
|||
if ($tip.length === 0) $tip = $('<i class="J_tip zmdi zmdi-info-outline"></i>').appendTo($item.find('.dd-handle span'))
|
||||
$tip.attr('title', nv.fieldTips)
|
||||
}
|
||||
|
||||
// NTEXT 高度
|
||||
if (data.displayTypeName === 'NTEXT') $item.find('.dd-handle').attr('data-height', nv.fieldHeight || '')
|
||||
}
|
||||
|
||||
const ov = {
|
||||
fieldTips: $item.find('.dd-handle>span>i').attr('title'),
|
||||
fieldLabel: $item.find('.dd-handle>span').text(),
|
||||
fieldLabelOld: $item.find('.dd-handle').data('label'),
|
||||
fieldHeight: $item.find('.dd-handle').attr('data-height') || null,
|
||||
}
|
||||
if (ov.fieldLabelOld === ov.fieldLabel) ov.fieldLabel = null
|
||||
|
||||
renderRbcomp(<DlgEditField call={call} {...ov} />)
|
||||
renderRbcomp(<DlgEditField onConfirm={_onConfirm} {...ov} displayType={data.displayTypeName} />)
|
||||
})
|
||||
|
||||
$(`<a title="${$L('移除')}"><i class="zmdi zmdi-close"></i></a>`)
|
||||
|
@ -256,11 +265,12 @@ const render_item = function (data) {
|
|||
$(`<a title="${$L('修改')}"><i class="zmdi zmdi-edit"></i></a>`)
|
||||
.appendTo($action)
|
||||
.click(function () {
|
||||
const call = function (nv) {
|
||||
const _onConfirm = function (nv) {
|
||||
$item.find('.dd-handle span').text(nv.dividerName || '')
|
||||
}
|
||||
|
||||
const ov = $item.find('.dd-handle span').text()
|
||||
renderRbcomp(<DlgEditDivider call={call} dividerName={ov || ''} />)
|
||||
renderRbcomp(<DlgEditDivider onConfirm={_onConfirm} dividerName={ov || ''} />)
|
||||
})
|
||||
|
||||
$(`<a title="${$L('移除')}"><i class="zmdi zmdi-close"></i></a>`)
|
||||
|
@ -316,6 +326,12 @@ class DlgEditField extends RbAlert {
|
|||
maxLength="200"
|
||||
/>
|
||||
</div>
|
||||
{this.props.displayType === 'NTEXT' && (
|
||||
<div className="form-group">
|
||||
<label>{$L('高度 (行数)')}</label>
|
||||
<input type="number" className="form-control form-control-sm" name="fieldHeight" value={this.state.fieldHeight || ''} onChange={this.handleChange} placeholder={$L('默认')} />
|
||||
</div>
|
||||
)}
|
||||
<div className="form-group">
|
||||
<label>
|
||||
{$L('字段名称')} <span>({$L('部分内置字段不能修改')})</span>
|
||||
|
@ -347,7 +363,7 @@ class DlgEditField extends RbAlert {
|
|||
}
|
||||
|
||||
confirm = () => {
|
||||
typeof this.props.call === 'function' && this.props.call(this.state || {})
|
||||
typeof this.props.onConfirm === 'function' && this.props.onConfirm(this.state || {})
|
||||
this.hide()
|
||||
}
|
||||
}
|
||||
|
@ -397,6 +413,7 @@ const AdvControl = {
|
|||
append: function (item) {
|
||||
this.$controls.find(`tr[data-field="${item.field}"] input`).each(function () {
|
||||
const $this = $(this)
|
||||
if ($this.prop('disabled')) return
|
||||
item[$this.attr('name')] = $this.prop('checked')
|
||||
})
|
||||
},
|
||||
|
@ -404,6 +421,7 @@ const AdvControl = {
|
|||
set: function (item) {
|
||||
this.$controls.find(`tr[data-field="${item.field}"] input`).each(function () {
|
||||
const $this = $(this)
|
||||
if ($this.prop('disabled')) return
|
||||
const v = item[$this.attr('name')]
|
||||
if (v === true || v === false) $this.attr('checked', v)
|
||||
})
|
||||
|
|
|
@ -10,9 +10,9 @@ const isMulti = $urlp('multi') === 'true'
|
|||
const maxOptions = isMulti ? 20 : 40
|
||||
|
||||
$(document).ready(function () {
|
||||
const query = 'entity=' + $urlp('entity') + '&field=' + $urlp('field')
|
||||
const query = `entity=${$urlp('entity')}&field=${$urlp('field')}`
|
||||
|
||||
$.get(`/admin/field/picklist-gets?isAll=true&${query}`, function (res) {
|
||||
$.get(`/admin/field/picklist-gets?isAll=true&${query}`, (res) => {
|
||||
$(res.data).each(function () {
|
||||
if (this.hide === true) {
|
||||
render_unset([this.id, this.text])
|
||||
|
@ -23,30 +23,37 @@ $(document).ready(function () {
|
|||
})
|
||||
})
|
||||
|
||||
$('.J_confirm').click(function () {
|
||||
$('.J_confirm').on('click', function () {
|
||||
if ($('.J_config>li').length > maxOptions) {
|
||||
RbHighbar.create($L('最多支持 %d 个选项', maxOptions))
|
||||
return false
|
||||
}
|
||||
|
||||
const id = $('.J_text').attr('attr-id')
|
||||
const text = $val('.J_text')
|
||||
|
||||
if (!text) {
|
||||
RbHighbar.create($L('请输入选项值'))
|
||||
return false
|
||||
}
|
||||
|
||||
let exists = false
|
||||
let exists = null
|
||||
$('.J_config .dd3-content, .unset-list .dd-handle>span').each(function () {
|
||||
if ($(this).text() === text) exists = true
|
||||
if ($(this).text() === text) {
|
||||
exists = $(this).parent().attr('data-key')
|
||||
}
|
||||
})
|
||||
|
||||
if (exists) {
|
||||
RbHighbar.create($L('选项值重复'))
|
||||
return false
|
||||
if (exists !== id) {
|
||||
RbHighbar.create($L('选项值重复'))
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const id = $('.J_text').attr('attr-id')
|
||||
$('.J_text').val('').attr('attr-id', '')
|
||||
$('.J_confirm').text($L('添加'))
|
||||
|
||||
if (!id) {
|
||||
render_item([$random(), text])
|
||||
} else {
|
||||
|
@ -57,7 +64,7 @@ $(document).ready(function () {
|
|||
return false
|
||||
})
|
||||
|
||||
$('.J_save').click(function () {
|
||||
$('.J_save').on('click', function () {
|
||||
const show_items = []
|
||||
$('.J_config>li').each(function () {
|
||||
const $this = $(this)
|
||||
|
@ -87,6 +94,7 @@ $(document).ready(function () {
|
|||
show: show_items,
|
||||
hide: hide_items,
|
||||
}
|
||||
|
||||
const $btn = $(this)
|
||||
const delConfirm = function () {
|
||||
$btn.button('loading')
|
||||
|
@ -109,7 +117,7 @@ $(document).ready(function () {
|
|||
|
||||
render_unset_after = function (item) {
|
||||
const $del = $(`<a href="javascript:;" class="action">[${$L('删除')}]</a>`).appendTo(item.find('.dd-handle'))
|
||||
$del.click(() => {
|
||||
$del.on('click', () => {
|
||||
$del.text(`[${$L('保存后删除')}]`)
|
||||
$del.parent().parent().attr('data-del', 'force')
|
||||
return false
|
||||
|
@ -122,14 +130,15 @@ render_item_after = function (item, data) {
|
|||
|
||||
const $edit = $(`<a title="${$L('修改')}"><i class="zmdi zmdi-edit"></i></a>`)
|
||||
item.find('.dd3-action').prepend($edit)
|
||||
$edit.click(function () {
|
||||
$edit.on('click', function () {
|
||||
$('.J_confirm').text($L('修改'))
|
||||
$('.J_text').val(data[1]).attr('attr-id', data[0]).focus()
|
||||
const txt = $edit.parent().prev().text()
|
||||
$('.J_text').val(txt).attr('attr-id', data[0]).focus()
|
||||
})
|
||||
|
||||
const $def = $(`<a title="${$L('设为默认')}" class="J_def"><i class="zmdi zmdi-${isMulti ? 'check-square' : 'check-circle'}"></i></a>`)
|
||||
item.find('.dd3-action').prepend($def)
|
||||
$def.click(function () {
|
||||
$def.on('click', function () {
|
||||
if (item.hasClass('active')) {
|
||||
item.removeClass('active')
|
||||
$def.attr('title', $L('设为默认'))
|
||||
|
@ -147,6 +156,6 @@ render_item_after = function (item, data) {
|
|||
.find('.dd3-action>a.J_del')
|
||||
.attr('title', '删除')
|
||||
.off('click')
|
||||
.click(() => item.remove())
|
||||
.on('click', () => item.remove())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ class AdvFilter extends React.Component {
|
|||
|
||||
this.state = { items: [], ...props, ...extras }
|
||||
this._itemsRef = []
|
||||
this._htmlid = `useEquation-${$random()}`
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -79,15 +80,15 @@ class AdvFilter extends React.Component {
|
|||
<div className="mb-1">
|
||||
<div className="item mt-1">
|
||||
<label className="custom-control custom-control-sm custom-radio custom-control-inline mb-2">
|
||||
<input className="custom-control-input" type="radio" name="useEquation" value="OR" checked={this.state.useEquation === 'OR'} onChange={this.handleChange} />
|
||||
<input className="custom-control-input" type="radio" name={this._htmlid} data-id="useEquation" value="OR" checked={this.state.useEquation === 'OR'} onChange={this.handleChange} />
|
||||
<span className="custom-control-label pl-1">{$L('或关系')}</span>
|
||||
</label>
|
||||
<label className="custom-control custom-control-sm custom-radio custom-control-inline mb-2">
|
||||
<input className="custom-control-input" type="radio" name="useEquation" value="AND" checked={this.state.useEquation === 'AND'} onChange={this.handleChange} />
|
||||
<input className="custom-control-input" type="radio" name={this._htmlid} data-id="useEquation" value="AND" checked={this.state.useEquation === 'AND'} onChange={this.handleChange} />
|
||||
<span className="custom-control-label pl-1">{$L('且关系')}</span>
|
||||
</label>
|
||||
<label className="custom-control custom-control-sm custom-radio custom-control-inline mb-2">
|
||||
<input className="custom-control-input" type="radio" name="useEquation" value="9999" checked={this.state.useEquation === '9999'} onChange={this.handleChange} />
|
||||
<input className="custom-control-input" type="radio" name={this._htmlid} data-id="useEquation" value="9999" checked={this.state.useEquation === '9999'} onChange={this.handleChange} />
|
||||
<span className="custom-control-label pl-1">
|
||||
{$L('高级表达式')}
|
||||
<a href="https://getrebuild.com/docs/manual/basic#%E9%AB%98%E7%BA%A7%E8%A1%A8%E8%BE%BE%E5%BC%8F" target="_blank">
|
||||
|
@ -187,7 +188,12 @@ class AdvFilter extends React.Component {
|
|||
|
||||
handleChange = (e) => {
|
||||
const name = e.target.dataset.id || e.target.name
|
||||
this.setState({ [name]: e.target.value })
|
||||
const value = e.target.value
|
||||
this.setState({ [name]: value }, () => {
|
||||
if (name === 'useEquation' && value === '9999') {
|
||||
if (this.state.equation === 'AND') this.setState({ equation: null })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onRef = (c) => this._itemsRef.push(c)
|
||||
|
|
|
@ -438,7 +438,7 @@ var $stopEvent = function (e, preventDefault) {
|
|||
}
|
||||
|
||||
/**
|
||||
* 是否为 true 或 'true'
|
||||
* 是否为 true
|
||||
*/
|
||||
var $isTrue = function (a) {
|
||||
return a === true || a === 'true' || a === 'T'
|
||||
|
|
|
@ -23,13 +23,16 @@ const AdvFilters = {
|
|||
this.__entity = entity
|
||||
|
||||
this.__el.find('.J_advfilter').on('click', () => {
|
||||
this.showAdvFilter(null, this.current)
|
||||
// this.showAdvFilter(null, this.current) // 2.9.4 取消 useCopyId,可能引起误解
|
||||
this.showAdvFilter()
|
||||
this.current = null
|
||||
})
|
||||
const $all = $('.adv-search .dropdown-item:eq(0)')
|
||||
$all.on('click', () => this._effectFilter($all, 'aside'))
|
||||
|
||||
this.loadFilters()
|
||||
|
||||
this.__savedCached = []
|
||||
},
|
||||
|
||||
loadFilters() {
|
||||
|
@ -184,9 +187,15 @@ const AdvFilters = {
|
|||
}
|
||||
} else {
|
||||
this.current = id
|
||||
this._getFilter(id, (res) => {
|
||||
if (this.__savedCached[id]) {
|
||||
const res = this.__savedCached[id]
|
||||
renderRbcomp(<AdvFilter {...props} title={$L('修改高级查询')} filter={res.filter} filterName={res.name} shareTo={res.shareTo} />)
|
||||
})
|
||||
} else {
|
||||
this._getFilter(id, (res) => {
|
||||
this.__savedCached[id] = res
|
||||
renderRbcomp(<AdvFilter {...props} title={$L('修改高级查询')} filter={res.filter} filterName={res.name} shareTo={res.shareTo} />)
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -201,6 +210,7 @@ const AdvFilters = {
|
|||
if (res.error_code === 0) {
|
||||
$storage.set(_RbList().__defaultFilterKey, res.data.id)
|
||||
that.loadFilters()
|
||||
if (that.current) that.__savedCached[that.current] = null
|
||||
} else {
|
||||
RbHighbar.error(res.error_msg)
|
||||
}
|
||||
|
|
|
@ -668,7 +668,7 @@ CellRenders.addRender('STATE', function (v, s, k) {
|
|||
}
|
||||
})
|
||||
|
||||
CellRenders.addRender('DECIMAL', function (v, s, k) {
|
||||
const renderNumber = function (v, s, k) {
|
||||
if ((v + '').substr(0, 1) === '-') {
|
||||
return (
|
||||
<td key={k}>
|
||||
|
@ -680,7 +680,9 @@ CellRenders.addRender('DECIMAL', function (v, s, k) {
|
|||
} else {
|
||||
return CellRenders.renderSimple(v, s, k)
|
||||
}
|
||||
})
|
||||
}
|
||||
CellRenders.addRender('DECIMAL', renderNumber)
|
||||
CellRenders.addRender('NUMBER', renderNumber)
|
||||
|
||||
CellRenders.addRender('MULTISELECT', function (v, s, k) {
|
||||
const vLen = (v.text || []).length
|
||||
|
|
|
@ -861,13 +861,22 @@ class RbFormDecimal extends RbFormNumber {
|
|||
class RbFormTextarea extends RbFormElement {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this._height = this.props.useMdedit ? 0 : ~~this.props.height
|
||||
if (this._height && this._height > 0) {
|
||||
if (this._height === 1) this._height = 37
|
||||
else this._height = this._height * 20 + 12
|
||||
}
|
||||
}
|
||||
|
||||
renderElement() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<textarea
|
||||
ref={(c) => (this._fieldValue = c)}
|
||||
ref={(c) => {
|
||||
this._fieldValue = c
|
||||
this._height > 0 && c && $(c).attr('style', `height:${this._height}px !important`)
|
||||
}}
|
||||
className={`form-control form-control-sm row3x ${this.state.hasError ? 'is-invalid' : ''} ${this.props.useMdedit && this.props.readonly ? 'cm-readonly' : ''}`}
|
||||
title={this.state.hasError}
|
||||
value={this.state.value || ''}
|
||||
|
@ -884,12 +893,15 @@ class RbFormTextarea extends RbFormElement {
|
|||
renderViewElement() {
|
||||
if (!this.state.value) return super.renderViewElement()
|
||||
|
||||
const style = {}
|
||||
if (this._height > 0) style.maxHeight = this._height
|
||||
|
||||
if (this.props.useMdedit) {
|
||||
const md2html = SimpleMDE.prototype.markdown(this.state.value)
|
||||
return <div className="form-control-plaintext mdedit-content" ref={(c) => (this._textarea = c)} dangerouslySetInnerHTML={{ __html: md2html }} />
|
||||
return <div className="form-control-plaintext mdedit-content" ref={(c) => (this._textarea = c)} dangerouslySetInnerHTML={{ __html: md2html }} style={style} />
|
||||
} else {
|
||||
return (
|
||||
<div className="form-control-plaintext" ref={(c) => (this._textarea = c)}>
|
||||
<div className="form-control-plaintext" ref={(c) => (this._textarea = c)} style={style}>
|
||||
{this.state.value.split('\n').map((line, idx) => {
|
||||
return <p key={`line-${idx}`}>{line}</p>
|
||||
})}
|
||||
|
@ -1755,24 +1767,24 @@ class RbFormBool extends RbFormElement {
|
|||
renderElement() {
|
||||
return (
|
||||
<div className="mt-1">
|
||||
<label className="custom-control custom-radio custom-control-inline">
|
||||
<label className="custom-control custom-radio custom-control-inline mb-1">
|
||||
<input
|
||||
className="custom-control-input"
|
||||
name={`${this._htmlid}T`}
|
||||
type="radio"
|
||||
checked={$isTrue(this.state.value)}
|
||||
checked={this.state.value === 'T'}
|
||||
data-value="T"
|
||||
onChange={this.changeValue}
|
||||
disabled={this.props.readonly}
|
||||
/>
|
||||
<span className="custom-control-label">{this._Options['T']}</span>
|
||||
</label>
|
||||
<label className="custom-control custom-radio custom-control-inline">
|
||||
<label className="custom-control custom-radio custom-control-inline mb-1">
|
||||
<input
|
||||
className="custom-control-input"
|
||||
name={`${this._htmlid}F`}
|
||||
type="radio"
|
||||
checked={!$isTrue(this.state.value)}
|
||||
checked={this.state.value === 'F'}
|
||||
data-value="F"
|
||||
onChange={this.changeValue}
|
||||
disabled={this.props.readonly}
|
||||
|
|
|
@ -47,6 +47,7 @@ class ProTable extends React.Component {
|
|||
return (
|
||||
<th key={item.field} data-field={item.field} style={colStyles} className={item.nullable ? '' : 'required'}>
|
||||
{item.label}
|
||||
{item.tip && <i className="tipping zmdi zmdi-info-outline" title={item.tip} />}
|
||||
<i className="dividing hide" />
|
||||
</th>
|
||||
)
|
||||
|
@ -75,6 +76,13 @@ class ProTable extends React.Component {
|
|||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{(this.state.inlineForms || []).length === 0 && (
|
||||
<div className="text-center text-muted mt-6" style={{ paddingTop: 2, paddingBottom: 1 }}>
|
||||
<i className="x14 zmdi zmdi-playlist-plus mr-1 fs-16" />
|
||||
{$L('请添加明细')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -96,6 +104,7 @@ class ProTable extends React.Component {
|
|||
this._initModel = res.data // 新建用
|
||||
this.setState({ formFields: res.data.elements }, () => {
|
||||
$(this._$scroller).perfectScrollbar()
|
||||
// $(this._$scroller).find('thead .tipping').tooltip({})
|
||||
})
|
||||
|
||||
// 编辑
|
||||
|
|
|
@ -808,7 +808,7 @@ const RbViewPage = {
|
|||
const entity = e.entity.split('.')
|
||||
if (entity.length > 1) iv[entity[1]] = that.__id
|
||||
else iv[`&${that.__entity[0]}`] = that.__id
|
||||
RbFormModal.create({ title: `${title}`, entity: entity[0], icon: e.icon, initialValue: iv })
|
||||
RbFormModal.create({ title: $L('新建%s', title), entity: entity[0], icon: e.icon, initialValue: iv })
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -200,7 +200,7 @@ function _handle512Change() {
|
|||
|
||||
// 立即执行
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function _useExecDirect() {
|
||||
function useExecManual() {
|
||||
$('.footer .btn-light').removeClass('hide')
|
||||
$(`<a class="dropdown-item">${$L('立即执行')} <sup class="rbv" title="${$L('增值功能')}"></sup></a>`)
|
||||
.appendTo('.footer .dropdown-menu')
|
||||
|
@ -214,7 +214,7 @@ function _useExecDirect() {
|
|||
confirm: function () {
|
||||
this.disabled(true)
|
||||
// eslint-disable-next-line no-undef
|
||||
$.post(`/admin/robot/trigger/exec-direct?id=${wpc.configId}`, () => {
|
||||
$.post(`/admin/robot/trigger/exec-manual?id=${wpc.configId}`, () => {
|
||||
this.hide()
|
||||
RbHighbar.success($L('执行成功'))
|
||||
})
|
||||
|
|
|
@ -361,5 +361,5 @@ renderContentComp = function (props) {
|
|||
})
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
_useExecDirect()
|
||||
useExecManual()
|
||||
}
|
||||
|
|
|
@ -546,5 +546,5 @@ renderContentComp = function (props) {
|
|||
})
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
_useExecDirect()
|
||||
useExecManual()
|
||||
}
|
||||
|
|
|
@ -419,4 +419,7 @@ renderContentComp = function (props) {
|
|||
contentComp = this
|
||||
$('#react-content [data-toggle="tooltip"]').tooltip()
|
||||
})
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
useExecManual()
|
||||
}
|
||||
|
|
|
@ -13,24 +13,24 @@
|
|||
.zmdi.err400,
|
||||
.zmdi.err401,
|
||||
.zmdi.err403,
|
||||
.zmdi.err404,
|
||||
.zmdi.err600 {
|
||||
.zmdi.err404 {
|
||||
color: #4285f4 !important;
|
||||
}
|
||||
.zmdi.err600::before {
|
||||
content: '\f17a';
|
||||
.zmdi.err600::before,
|
||||
.zmdi.err602::before {
|
||||
color: #fbbc05;
|
||||
font-size: 5rem;
|
||||
font-size: 6rem;
|
||||
content: '\f17a';
|
||||
}
|
||||
.zmdi.err497::before {
|
||||
.zmdi.err601::before,
|
||||
.zmdi.err603::before {
|
||||
content: '\f119';
|
||||
}
|
||||
|
||||
.error-description > pre:empty {
|
||||
display: none;
|
||||
}
|
||||
.error-container a[target='_blank']:hover {
|
||||
text-decoration: underline;
|
||||
text-decoration: underline;
|
||||
}
|
||||
@media (max-width: 576px) {
|
||||
.rb-error .error-container {
|
||||
|
|
Loading…
Reference in a new issue