* frontjs

* be: 公式日期值

* IMP: 明细使用自有 Service

* 公开注册

* be: this._defaultBackPath

* style: dataTables_oper.invisible2

* varRecord

* fix: 明细未配置或出错

* lib

* feat: autoLocation
This commit is contained in:
REBUILD 企业管理系统 2023-11-18 21:45:17 +08:00 committed by GitHub
parent c9b81f91aa
commit db7f32d1e5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 308 additions and 72 deletions

2
@rbv

@ -1 +1 @@
Subproject commit 416bec1eb0ad62f482dab2975690bada8dcc5eea Subproject commit d46fd910bcf5dc37aed718d5ccb409c886a3dc82

View file

@ -270,7 +270,7 @@
<dependency> <dependency>
<groupId>com.github.devezhao</groupId> <groupId>com.github.devezhao</groupId>
<artifactId>commons</artifactId> <artifactId>commons</artifactId>
<version>6234f298ef</version> <version>1.3.13</version>
<exclusions> <exclusions>
<exclusion> <exclusion>
<artifactId>httpclient</artifactId> <artifactId>httpclient</artifactId>
@ -290,7 +290,7 @@
<dependency> <dependency>
<groupId>com.github.devezhao</groupId> <groupId>com.github.devezhao</groupId>
<artifactId>persist4j</artifactId> <artifactId>persist4j</artifactId>
<version>0b293fa74b</version> <version>1.7.5</version>
<exclusions> <exclusions>
<exclusion> <exclusion>
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>

View file

@ -49,11 +49,11 @@ public class AuthTokenManager {
* 生成 Token * 生成 Token
* *
* @param user * @param user
* @param expires * @param seconds
* @param type * @param type
* @return * @return
*/ */
protected static String generateToken(ID user, int expires, String type) { protected static String generateToken(ID user, int seconds, String type) {
// Type,User,Time,Version // Type,User,Time,Version
String desc = String.format("%s,%s,%d,v2", String desc = String.format("%s,%s,%d,v2",
ObjectUtils.defaultIfNull(type, TYPE_ACCESS_TOKEN), ObjectUtils.defaultIfNull(type, TYPE_ACCESS_TOKEN),
@ -61,7 +61,7 @@ public class AuthTokenManager {
System.nanoTime()); System.nanoTime());
String token = EncryptUtils.toSHA1Hex(desc); String token = EncryptUtils.toSHA1Hex(desc);
Application.getCommonsCache().put(TOKEN_PREFIX + token, desc, expires); Application.getCommonsCache().put(TOKEN_PREFIX + token, desc, seconds);
return token; return token;
} }
@ -84,12 +84,12 @@ public class AuthTokenManager {
} }
/** /**
* @param expires * @param seconds
* @return * @return
* @see #TYPE_CSRF_TOKEN * @see #TYPE_CSRF_TOKEN
*/ */
public static String generateCsrfToken(int expires) { public static String generateCsrfToken(int seconds) {
return generateToken(null, expires, TYPE_CSRF_TOKEN); return generateToken(null, seconds, TYPE_CSRF_TOKEN);
} }
/** /**

View file

@ -111,7 +111,8 @@ public class MetadataSorter {
List<BaseMeta> entities = new ArrayList<>(); List<BaseMeta> entities = new ArrayList<>();
CollectionUtils.addAll(entities, mainEntity.getDetialEntities()); CollectionUtils.addAll(entities, mainEntity.getDetialEntities());
sortByLabel(entities); // SORT: 名称默认是返回按CODE大小
if (entities.size() > 1) sortByLabel(entities);
return entities.toArray(new Entity[0]); return entities.toArray(new Entity[0]);
} }

View file

@ -13,6 +13,7 @@ import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute; import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAspectSupport; import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.transaction.support.TransactionSynchronizationManager;
/** /**
* 手动事物管理默认事务管理见 `application-bean.xml` * 手动事物管理默认事务管理见 `application-bean.xml`
@ -27,6 +28,8 @@ public class TransactionManual {
* 开启一个事物 * 开启一个事物
* *
* @return * @return
* @see #commit(TransactionStatus)
* @see #rollback(TransactionStatus)
*/ */
public static TransactionStatus newTransaction() { public static TransactionStatus newTransaction() {
DefaultTransactionAttribute attr = new DefaultTransactionAttribute(); DefaultTransactionAttribute attr = new DefaultTransactionAttribute();
@ -34,15 +37,6 @@ public class TransactionManual {
return getTxManager().getTransaction(attr); return getTxManager().getTransaction(attr);
} }
/**
* Shadow for TransactionAspectSupport#currentTransactionStatus
*
* @return
*/
public static TransactionStatus currentTransaction() {
return TransactionAspectSupport.currentTransactionStatus();
}
/** /**
* 提交 * 提交
* *
@ -72,4 +66,21 @@ public class TransactionManual {
return Application.getBean(DataSourceTransactionManager.class); return Application.getBean(DataSourceTransactionManager.class);
} }
/**
* Shadow for <tt>TransactionAspectSupport#currentTransactionStatus</tt>
*
* @return
*/
public static TransactionStatus currentTransactionStatus() {
return TransactionAspectSupport.currentTransactionStatus();
}
/**
* Shadow for <tt>TransactionSynchronizationManager.getCurrentTransactionName</tt>
*
* @return
*/
public static String currentTransactionName() {
return TransactionSynchronizationManager.getCurrentTransactionName();
}
} }

View file

@ -96,7 +96,7 @@ public interface EntityService extends ServiceSpec {
* 检查并获取如有重复记录 * 检查并获取如有重复记录
* *
* @param checkRecord * @param checkRecord
* @param limit * @param limit 最大查重返回数量
* @return * @return
*/ */
List<Record> getAndCheckRepeated(Record checkRecord, int limit); List<Record> getAndCheckRepeated(Record checkRecord, int limit);

View file

@ -170,19 +170,24 @@ public class GeneralEntityService extends ObservableService implements EntitySer
record = record.getPrimary() == null ? create(record) : update(record); record = record.getPrimary() == null ? create(record) : update(record);
if (!hasDetails) return record; if (!hasDetails) return record;
// 主记录+明细记录处理 // 明细记录处理
final String dtfField = MetadataHelper.getDetailToMainField(record.getEntity().getDetailEntity()).getName(); final Entity detailEntity = record.getEntity().getDetailEntity();
final String dtfField = MetadataHelper.getDetailToMainField(detailEntity).getName();
final ID mainid = record.getPrimary(); final ID mainid = record.getPrimary();
final boolean checkDetailsRepeated = rcm == GeneralEntityServiceContextHolder.RCM_CHECK_DETAILS final boolean checkDetailsRepeated = rcm == GeneralEntityServiceContextHolder.RCM_CHECK_DETAILS
|| rcm == GeneralEntityServiceContextHolder.RCM_CHECK_ALL; || rcm == GeneralEntityServiceContextHolder.RCM_CHECK_ALL;
// 明细可能有自己的 Service
EntityService des = Application.getEntityService(detailEntity.getEntityCode());
if (des.getEntityCode() == 0) des = this;
// 先删除 // 先删除
for (int i = 0; i < details.size(); i++) { for (int i = 0; i < details.size(); i++) {
Record d = details.get(i); Record d = details.get(i);
if (d instanceof DeleteRecord) { if (d instanceof DeleteRecord) {
delete(d.getPrimary()); des.delete(d.getPrimary());
detaileds.put(i, d.getPrimary()); detaileds.put(i, d.getPrimary());
} }
} }
@ -195,7 +200,7 @@ public class GeneralEntityService extends ObservableService implements EntitySer
if (checkDetailsRepeated) { if (checkDetailsRepeated) {
d.setID(dtfField, mainid); // for check d.setID(dtfField, mainid); // for check
List<Record> repeated = getAndCheckRepeated(d, 20); List<Record> repeated = des.getAndCheckRepeated(d, 20);
if (!repeated.isEmpty()) { if (!repeated.isEmpty()) {
throw new RepeatedRecordsException(repeated); throw new RepeatedRecordsException(repeated);
} }
@ -203,9 +208,9 @@ public class GeneralEntityService extends ObservableService implements EntitySer
if (d.getPrimary() == null) { if (d.getPrimary() == null) {
d.setID(dtfField, mainid); d.setID(dtfField, mainid);
create(d); des.create(d);
} else { } else {
update(d); des.update(d);
} }
detaileds.put(i, d.getPrimary()); detaileds.put(i, d.getPrimary());
} }
@ -850,4 +855,9 @@ public class GeneralEntityService extends ObservableService implements EntitySer
} }
return specTriggers.toArray(new TriggerAction[0]); return specTriggers.toArray(new TriggerAction[0]);
} }
@Override
public String toString() {
return getEntityCode() + "#" + super.toString();
}
} }

View file

@ -73,26 +73,28 @@ public class RecordDifference {
if (record != null) { if (record != null) {
JSONObject recordSerialize = (JSONObject) record.serialize(); JSONObject recordSerialize = (JSONObject) record.serialize();
for (Map.Entry<String, Object> e : recordSerialize.entrySet()) { for (Map.Entry<String, Object> e : recordSerialize.entrySet()) {
String field = e.getKey(); String fieldName = e.getKey();
if (!diffCommons && isIgnoreField(entity.getField(field))) continue; if (!entity.containsField(fieldName)) continue;
if (!diffCommons && isIgnoreField(entity.getField(fieldName))) continue;
Object beforeVal = e.getValue(); Object beforeVal = e.getValue();
if (NullValue.is(beforeVal)) beforeVal = null; if (NullValue.is(beforeVal)) beforeVal = null;
merged.put(field, new Object[]{beforeVal, null}); merged.put(fieldName, new Object[]{beforeVal, null});
} }
} }
if (after != null) { if (after != null) {
JSONObject afterSerialize = (JSONObject) after.serialize(); JSONObject afterSerialize = (JSONObject) after.serialize();
for (Map.Entry<String, Object> e : afterSerialize.entrySet()) { for (Map.Entry<String, Object> e : afterSerialize.entrySet()) {
String field = e.getKey(); String fieldName = e.getKey();
if (!diffCommons && isIgnoreField(entity.getField(field))) continue; if (!entity.containsField(fieldName)) continue;
if (!diffCommons && isIgnoreField(entity.getField(fieldName))) continue;
Object afterVal = e.getValue(); Object afterVal = e.getValue();
if (NullValue.is(afterVal)) continue; if (NullValue.is(afterVal)) continue;
Object[] mergedValue = merged.computeIfAbsent(field, k -> new Object[]{null, null}); Object[] mergedValue = merged.computeIfAbsent(fieldName, k -> new Object[]{null, null});
mergedValue[1] = afterVal; mergedValue[1] = afterVal;
} }
} }

View file

@ -27,6 +27,7 @@ import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
import com.rebuild.core.metadata.impl.EasyFieldConfigProps; import com.rebuild.core.metadata.impl.EasyFieldConfigProps;
import com.rebuild.core.privileges.UserHelper; import com.rebuild.core.privileges.UserHelper;
import com.rebuild.core.privileges.bizz.Department; import com.rebuild.core.privileges.bizz.Department;
import com.rebuild.core.support.License;
import com.rebuild.core.support.SetUser; import com.rebuild.core.support.SetUser;
import com.rebuild.core.support.i18n.Language; import com.rebuild.core.support.i18n.Language;
import com.rebuild.utils.CommonsUtils; import com.rebuild.utils.CommonsUtils;
@ -108,12 +109,12 @@ public class AdvFilterParser extends SetUser {
/** /**
* @param filterExpr * @param filterExpr
* @param varRecord 条件中包含字段变量将从此记录中提取实际值注意如果包括字段变量但是字段无值则过滤项会被忽略应在配置条件时考虑此问题设置不允许为空 * @param varRecord 条件中包含字段变量将从该记录中提取实际值替换
*/ */
public AdvFilterParser(JSONObject filterExpr, ID varRecord) { public AdvFilterParser(JSONObject filterExpr, ID varRecord) {
this.filterExpr = filterExpr; this.filterExpr = filterExpr;
this.rootEntity = MetadataHelper.getEntity(varRecord.getEntityCode()); this.rootEntity = MetadataHelper.getEntity(varRecord.getEntityCode());
this.varRecord = varRecord; this.varRecord = License.isRbvAttached() ? varRecord : null;
} }
/** /**

View file

@ -162,7 +162,7 @@ public class FieldAggregation extends TriggerAction {
JSONObject dataFilter = ((JSONObject) actionContext.getActionContent()).getJSONObject("dataFilter"); JSONObject dataFilter = ((JSONObject) actionContext.getActionContent()).getJSONObject("dataFilter");
String dataFilterSql = null; String dataFilterSql = null;
if (dataFilter != null && !dataFilter.isEmpty()) { if (dataFilter != null && !dataFilter.isEmpty()) {
dataFilterSql = new AdvFilterParser(dataFilter).toSqlWhere(); dataFilterSql = new AdvFilterParser(dataFilter, operatingContext.getFixedRecordId()).toSqlWhere();
} }
String filterSql = followSourceWhere; String filterSql = followSourceWhere;

View file

@ -468,7 +468,13 @@ public class FieldWriteback extends FieldAggregation {
} else if (dt == DisplayType.DECIMAL) { } else if (dt == DisplayType.DECIMAL) {
targetRecord.setDouble(targetField, ObjectUtils.toDouble(newValue)); targetRecord.setDouble(targetField, ObjectUtils.toDouble(newValue));
} else if (dt == DisplayType.DATE || dt == DisplayType.DATETIME) { } else if (dt == DisplayType.DATE || dt == DisplayType.DATETIME) {
targetRecord.setDate(targetField, (Date) newValue); if (newValue instanceof Date) {
targetRecord.setDate(targetField, (Date) newValue);
} else {
Date newValueCast = CalendarUtils.parse(newValue.toString());
if (newValueCast == null) log.warn("Cannot cast string to date : {}", newValue);
else targetRecord.setDate(targetField, newValueCast);
}
} else { } else {
newValue = checkoutFieldValue(newValue, targetFieldEasy); newValue = checkoutFieldValue(newValue, targetFieldEasy);
if (newValue != null) { if (newValue != null) {

View file

@ -339,7 +339,7 @@ public class FileDownloader extends BaseController {
// 火狐 Safari 中文名乱码问题 // 火狐 Safari 中文名乱码问题
String UA = StringUtils.defaultIfBlank(request.getHeader("user-agent"), "").toUpperCase(); String UA = StringUtils.defaultIfBlank(request.getHeader("user-agent"), "").toUpperCase();
if (UA.contains("FIREFOX") || UA.contains("SAFARI")) { if (UA.contains("FIREFOX") || UA.contains("SAFARI") || UA.contains("APPLEWEBKIT")) {
attname = CodecUtils.urlDecode(attname); attname = CodecUtils.urlDecode(attname);
attname = new String(attname.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1); attname = new String(attname.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
} }

View file

@ -210,7 +210,7 @@ public class FeedsListController extends BaseController {
} }
private static final String ITEM_SQL = "select" + private static final String ITEM_SQL = "select" +
" feedsId,createdBy,createdOn,modifiedOn,content,images,attachments,scope,type,relatedRecord,contentMore" + " feedsId,createdBy,createdOn,modifiedOn,content,images,attachments,scope,type,relatedRecord,contentMore,autoLocation" +
" from Feeds where "; " from Feeds where ";
private JSONObject buildItem(Object[] o, ID user) { private JSONObject buildItem(Object[] o, ID user) {
@ -226,6 +226,8 @@ public class FeedsListController extends BaseController {
} }
item.put("type", o[8]); item.put("type", o[8]);
item.put("numComments", FeedsHelper.getNumOfComment((ID) o[0])); item.put("numComments", FeedsHelper.getNumOfComment((ID) o[0]));
// v35
item.put("autoLocation", o[11]);
// 相关记录 // 相关记录
ID related = (ID) o[9]; ID related = (ID) o[9];

View file

@ -66,7 +66,10 @@ public class CommonOperatingController extends BaseController {
public RespBody get(@IdParam ID recordId, HttpServletRequest request) { public RespBody get(@IdParam ID recordId, HttpServletRequest request) {
final String fields = getParameter(request, "fields"); final String fields = getParameter(request, "fields");
Record record = Application.getQueryFactory() Record record = Application.getQueryFactory()
.record(recordId, StringUtils.isBlank(fields) ? new String[0] : fields.split(",")); .record(recordId, StringUtils.isBlank(fields) ? new String[0] : fields.split("[,;]"));
if (record == null) {
return RespBody.error("无权读取此记录或记录已被删除");
}
return RespBody.ok(record); return RespBody.ok(record);
} }

View file

@ -130,8 +130,8 @@ public class ReportsController extends BaseController {
if (AppUtils.isMobile(request)) { if (AppUtils.isMobile(request)) {
String fileUrl = String.format( String fileUrl = String.format(
"/filex/download/%s?temp=yes&_onceToken=%s&attname=%s", "/filex/download/%s?temp=yes&_csrfToken=%s&attname=%s",
CodecUtils.urlEncode(output.getName()), AuthTokenManager.generateOnceToken(null), fileName); CodecUtils.urlEncode(output.getName()), AuthTokenManager.generateCsrfToken(90), CodecUtils.urlEncode(fileName));
data.put("fileUrl", fileUrl); data.put("fileUrl", fileUrl);
} }
writeSuccess(response, data); writeSuccess(response, data);
@ -139,12 +139,11 @@ public class ReportsController extends BaseController {
} else if ("preview".equalsIgnoreCase(typeOutput)) { } else if ("preview".equalsIgnoreCase(typeOutput)) {
String fileUrl = String.format( String fileUrl = String.format(
"/filex/download/%s?temp=yes&_onceToken=%s&attname=%s", "/filex/download/%s?temp=yes&_onceToken=%s&attname=%s",
CodecUtils.urlEncode(output.getName()), AuthTokenManager.generateOnceToken(null), fileName); CodecUtils.urlEncode(output.getName()), AuthTokenManager.generateOnceToken(null), CodecUtils.urlEncode(fileName));
fileUrl = RebuildConfiguration.getHomeUrl(fileUrl); fileUrl = RebuildConfiguration.getHomeUrl(fileUrl);
String previewUrl = StringUtils.defaultIfBlank( String previewUrl = StringUtils.defaultIfBlank(
RebuildConfiguration.get(ConfigurationItem.PortalOfficePreviewUrl), RebuildConfiguration.get(ConfigurationItem.PortalOfficePreviewUrl), "https://view.officeapps.live.com/op/embed.aspx?src=");
"https://view.officeapps.live.com/op/embed.aspx?src=");
previewUrl += CodecUtils.urlEncode(fileUrl); previewUrl += CodecUtils.urlEncode(fileUrl);
response.sendRedirect(previewUrl); response.sendRedirect(previewUrl);

View file

@ -8,6 +8,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.web.robot.approval; package com.rebuild.web.robot.approval;
import cn.devezhao.commons.web.ServletUtils; import cn.devezhao.commons.web.ServletUtils;
import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.Record; import cn.devezhao.persist4j.Record;
import cn.devezhao.persist4j.engine.ID; import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
@ -79,7 +80,8 @@ public class ApprovalController extends BaseController {
@GetMapping("state") @GetMapping("state")
public RespBody getApprovalState(HttpServletRequest request, @IdParam(name = "record") ID recordId) { public RespBody getApprovalState(HttpServletRequest request, @IdParam(name = "record") ID recordId) {
if (!MetadataHelper.hasApprovalField(MetadataHelper.getEntity(recordId.getEntityCode()))) { final Entity approvalEntity = MetadataHelper.getEntity(recordId.getEntityCode());
if (!MetadataHelper.hasApprovalField(approvalEntity)) {
return RespBody.error("NOT AN APPROVAL ENTITY"); return RespBody.error("NOT AN APPROVAL ENTITY");
} }
@ -87,6 +89,7 @@ public class ApprovalController extends BaseController {
final ApprovalStatus status = ApprovalHelper.getApprovalStatus(recordId); final ApprovalStatus status = ApprovalHelper.getApprovalStatus(recordId);
JSONObject data = new JSONObject(); JSONObject data = new JSONObject();
data.put("entityName", approvalEntity.getName());
int stateVal = status.getCurrentState().getState(); int stateVal = status.getCurrentState().getState();
data.put("state", stateVal); data.put("state", stateVal);

View file

@ -113,6 +113,10 @@ public class SignUpController extends BaseController {
@PostMapping("signup-confirm") @PostMapping("signup-confirm")
public RespBody signupConfirm(HttpServletRequest request) { public RespBody signupConfirm(HttpServletRequest request) {
if (!RebuildConfiguration.getBool(ConfigurationItem.OpenSignUp)) {
return RespBody.errorl("管理员未开放公开注册");
}
JSONObject data = (JSONObject) ServletUtils.getRequestJson(request); JSONObject data = (JSONObject) ServletUtils.getRequestJson(request);
String email = data.getString("email"); String email = data.getString("email");

View file

@ -2725,5 +2725,112 @@
"系统即将开始维护,暂时禁止登录":"系统即将开始维护,暂时禁止登录", "系统即将开始维护,暂时禁止登录":"系统即将开始维护,暂时禁止登录",
"本月..":"本月..", "本月..":"本月..",
"指定..天":"指定..天", "指定..天":"指定..天",
"本周..":"本周.." "本周..":"本周..",
"永久":"永久",
"同步更新":"同步更新",
"查看位置":"查看位置",
"内容粗体":"内容粗体",
"执行内容/结果":"执行内容/结果",
"批量审批完成。成功 %d 条,失败 %d 条":"批量审批完成。成功 %d 条,失败 %d 条",
"无法获取位置":"无法获取位置",
"客户端不支持":"客户端不支持",
"同步删除":"同步删除",
"点击文件名保存":"点击文件名保存",
"谁能使用这个报表":"谁能使用这个报表",
"输入手机或邮箱,多个请使用逗号分开":"输入手机或邮箱,多个请使用逗号分开",
"预计执行时间 (最多显示近 9 次)":"预计执行时间 (最多显示近 9 次)",
"在线预览":"在线预览",
"执行顺序":"执行顺序",
"发布位置":"发布位置",
"批量审批":"批量审批",
"数据库语句执行超时":"数据库语句执行超时",
"通过规则匹配":"通过规则匹配",
"转换条件":"转换条件",
"已允许获取位置":"已允许获取位置",
"获取位置":"获取位置",
"未被允许推送通知":"未被允许推送通知",
"表单引用":"表单引用",
"全部粗体":"全部粗体",
"请选择%s":"请选择%s",
"手机版菜单样式":"手机版菜单样式",
"免费版不支持引用表单功能 [(查看详情)](https://getrebuild.com/docs/rbv-features)":"免费版不支持引用表单功能 [(查看详情)](https://getrebuild.com/docs/rbv-features)",
"技术细节":"技术细节",
"符合聚合数据条件的记录才会被聚合":"符合聚合数据条件的记录才会被聚合",
"选择内部接收用户":"选择内部接收用户",
"在线模板编辑器":"在线模板编辑器",
"默认粗体":"默认粗体",
"请求地址":"请求地址",
"启用后会推送全部字段/数据(包括明细实体),否则将仅推送修改的字段/数据":"启用后会推送全部字段/数据(包括明细实体),否则将仅推送修改的字段/数据",
"在顶部显示":"在顶部显示",
"请再次确认审批数据范围和审批结果。开始审批吗?":"请再次确认审批数据范围和审批结果。开始审批吗?",
"去重连接":"去重连接",
"导出格式":"导出格式",
"允许批量":"允许批量",
"暂无成员":"暂无成员",
"永久有效":"永久有效",
"详情模式选项":"详情模式选项",
"已做审批":"已做审批",
"允许批量审批":"允许批量审批",
"已允许推送通知":"已允许推送通知",
"包括获取您的位置信息,以及通知推送":"包括获取您的位置信息,以及通知推送",
"明细加载失败,请稍后重试":"明细加载失败,请稍后重试",
"不允许批量审批":"不允许批量审批",
"手动输入":"手动输入",
"暂无使用用户":"暂无使用用户",
"推送通知":"推送通知",
"客户端权限":"客户端权限",
"目标实体/记录匹配规则":"目标实体/记录匹配规则",
"明细显示在下方":"明细显示在下方",
"主角色":"主角色",
"留空表示所有用户均可使用":"留空表示所有用户均可使用",
"EXCEL 列表":"EXCEL 列表",
"批量":"批量",
"页面布局":"页面布局",
"无效审批状态":"无效审批状态",
"请设置过滤条件":"请设置过滤条件",
"请设置高级表达式":"请设置高级表达式",
"成功":"成功",
"适合页面":"适合页面",
"仅处于待你审批,且允许批量审批的记录才能审批成功":"仅处于待你审批,且允许批量审批的记录才能审批成功",
"上传文件。需要 %s 个":"上传文件。需要 %s 个",
"定位失败":"定位失败",
"所有用户":"所有用户",
"副字段显示":"副字段显示",
"清除缓存":"清除缓存",
"仍旧导入 (新建)":"仍旧导入 (新建)",
"转换后同步更新":"转换后同步更新",
"请选择审批结果":"请选择审批结果",
"表达式返回了非布尔值,这会导致触发器执行错误。是否继续?":"表达式返回了非布尔值,这会导致触发器执行错误。是否继续?",
"编号":"编号",
"使用用户":"使用用户",
"未激活":"未激活",
"%s个维度 %s个数值":"%s个维度 %s个数值",
"转换后同步删除":"转换后同步删除",
"无效模板文件 (无法读取模板文件)":"无效模板文件 (无法读取模板文件)",
"请输入%s":"请输入%s",
"没有任何符合批量审批条件的记录":"没有任何符合批量审批条件的记录",
"引用字段":"引用字段",
"符合转换条件的记录才允许被转换":"符合转换条件的记录才允许被转换",
"在线模板":"在线模板",
"编辑数据列表":"编辑数据列表",
"在侧栏显示":"在侧栏显示",
"启用后调用返回结果必须为 `SUCCESS`,否则将视为调用失败":"启用后调用返回结果必须为 `SUCCESS`,否则将视为调用失败",
"查询面板":"查询面板",
"保存并返回":"保存并返回",
"查看 HTML":"查看 HTML",
"默认字体":"默认字体",
"你提交的 %s 已审批通过":"你提交的 %s 已审批通过",
"悼念模式":"悼念模式",
"免费版不支持 WORD 模板功能 [(查看详情)](https://getrebuild.com/docs/rbv-features)":"免费版不支持 WORD 模板功能 [(查看详情)](https://getrebuild.com/docs/rbv-features)",
"表达式可能存在错误,这会导致触发器执行错误。是否继续?":"表达式可能存在错误,这会导致触发器执行错误。是否继续?",
"90天":"90天",
"触发过程":"触发过程",
"免费版不支持批量审批功能 [(查看详情)](https://getrebuild.com/docs/rbv-features)":"免费版不支持批量审批功能 [(查看详情)](https://getrebuild.com/docs/rbv-features)",
"加载更多":"加载更多",
"调用量":"调用量",
"非审核人":"非审核人",
"仅更新不要新建":"仅更新不要新建",
"输入内容":"输入内容",
"定位中":"定位中",
"查看 PDF":"查看 PDF"
} }

View file

@ -431,6 +431,7 @@
<field name="relatedRecord" type="any-reference" description="相关记录" cascade="ignore"/> <field name="relatedRecord" type="any-reference" description="相关记录" cascade="ignore"/>
<field name="scheduleTime" type="timestamp" description="日程时间"/> <field name="scheduleTime" type="timestamp" description="日程时间"/>
<field name="scope" type="string" max-length="20" default-value="ALL" description="可见范围 (ALL/SELF/$TeamID)" queryable="false"/> <field name="scope" type="string" max-length="20" default-value="ALL" description="可见范围 (ALL/SELF/$TeamID)" queryable="false"/>
<field name="autoLocation" type="string" max-length="100" description="发布位置" extra-attrs="{displayType:'LOCATION'}"/>
<index field-list="createdOn,scope,type,createdBy"/> <index field-list="createdOn,scope,type,createdBy"/>
<index field-list="relatedRecord"/> <index field-list="relatedRecord"/>
<index field-list="type,scheduleTime,createdBy"/> <index field-list="type,scheduleTime,createdBy"/>

View file

@ -618,6 +618,7 @@ create table if not exists `feeds` (
`RELATED_RECORD` char(20) comment '相关记录', `RELATED_RECORD` char(20) comment '相关记录',
`SCHEDULE_TIME` timestamp null default null comment '日程时间', `SCHEDULE_TIME` timestamp null default null comment '日程时间',
`SCOPE` varchar(20) default 'ALL' comment '可见范围 (ALL/SELF/$TeamID)', `SCOPE` varchar(20) default 'ALL' comment '可见范围 (ALL/SELF/$TeamID)',
`AUTO_LOCATION` varchar(100) comment '发布位置',
`CREATED_BY` char(20) not null comment '创建人', `CREATED_BY` char(20) not null comment '创建人',
`CREATED_ON` timestamp not null default current_timestamp comment '创建时间', `CREATED_ON` timestamp not null default current_timestamp comment '创建时间',
`MODIFIED_ON` timestamp not null default current_timestamp comment '修改时间', `MODIFIED_ON` timestamp not null default current_timestamp comment '修改时间',
@ -885,4 +886,4 @@ insert into `project_plan_config` (`CONFIG_ID`, `PROJECT_ID`, `PLAN_NAME`, `SEQ`
-- DB Version (see `db-upgrade.sql`) -- DB Version (see `db-upgrade.sql`)
insert into `system_config` (`CONFIG_ID`, `ITEM`, `VALUE`) insert into `system_config` (`CONFIG_ID`, `ITEM`, `VALUE`)
values ('021-9000000000000001', 'DBVer', 53); values ('021-9000000000000001', 'DBVer', 54);

View file

@ -1,6 +1,10 @@
-- Database upgrade scripts for rebuild 1.x and 2.x -- Database upgrade scripts for rebuild 1.x and 2.x
-- Each upgraded starts with `-- #VERSION` -- Each upgraded starts with `-- #VERSION`
-- #54 (v3.5)
alter table `feeds`
add column `AUTO_LOCATION` varchar(100) comment '发布位置';
-- #53 (v3.5) -- #53 (v3.5)
alter table `revision_history` alter table `revision_history`
add column `AUTO_ID` bigint(20) not null auto_increment comment '保证顺序', add column `AUTO_ID` bigint(20) not null auto_increment comment '保证顺序',

View file

@ -64,9 +64,6 @@ button[disabled] {
code { code {
color: #e83e8c; color: #e83e8c;
}
.code-view {
font-family: ui-monospace, 'Cascadia Mono', 'Segoe UI Mono', 'Liberation Mono', Menlo, Monaco, Consolas, monospace; font-family: ui-monospace, 'Cascadia Mono', 'Segoe UI Mono', 'Liberation Mono', Menlo, Monaco, Consolas, monospace;
} }
@ -5008,6 +5005,20 @@ pre.unstyle {
.tablesort thead th.sorted { .tablesort thead th.sorted {
cursor: pointer; cursor: pointer;
background-color: #dee2e6; background-color: #dee2e6;
position: relative;
}
.tablesort thead th.sorted.ascending::after,
.tablesort thead th.sorted.descending::after {
font: normal normal normal 18px/1 "Material Design Icons";
content: '\F0140';
position: absolute;
right: 10px;
top: 9.5px;
}
.tablesort thead th.sorted.descending::after {
content: '\F0143';
} }
.map-suggestion.dropdown-menu { .map-suggestion.dropdown-menu {
@ -5354,3 +5365,12 @@ div.dataTables_wrapper.compact div.dataTables_oper .btn-space {
.table.table-btm-line tbody { .table.table-btm-line tbody {
border-bottom: 1px solid #dee2e6; border-bottom: 1px solid #dee2e6;
} }
.dataTables_oper.invisible2 > .btn,
.dataTables_oper.invisible2 > .btn-group {
display: none;
}
.dataTables_oper.invisible2 > .btn.J_view {
display: inline-block;
}

View file

@ -272,7 +272,7 @@ class ApprovalReferral extends RbModalHandler {
$mp.end() $mp.end()
if (res.error_code === 0) { if (res.error_code === 0) {
RbHighbar.success(res.data > 0 ? $L('已转审 %d 条审批记录') : $L('批量转审完成')) RbHighbar.success(res.data > 0 ? $L('已转审 %d 条审批记录', res.data) : $L('批量转审完成'))
setTimeout(() => that.hide(), 1500) setTimeout(() => that.hide(), 1500)
} else { } else {
RbHighbar.error(res.error_msg) RbHighbar.error(res.error_msg)

View file

@ -37,10 +37,10 @@ class ReportList extends ConfigList {
) : ( ) : (
item[3] item[3]
)} )}
{item[6] === 1 && <span className="badge badge-info badge-arrow3 badge-sm ml-1 excel">{$L('EXCEL')}</span>} {item[6] === 1 && <span className="badge badge-info badge-arrow3 badge-sm ml-1 excel">EXCEL</span>}
{item[6] === 2 && <span className="badge badge-info badge-arrow3 badge-sm ml-1 excel">{$L('EXCEL 列表')}</span>} {item[6] === 2 && <span className="badge badge-info badge-arrow3 badge-sm ml-1 excel">{$L('EXCEL 列表')}</span>}
{isHtml5 && <span className="badge badge-info badge-arrow3 badge-sm ml-1">{$L('在线模板')}</span>} {isHtml5 && <span className="badge badge-info badge-arrow3 badge-sm ml-1">{$L('在线模板')}</span>}
{item[6] === 4 && <span className="badge badge-info badge-arrow3 badge-sm ml-1 word">{$L('WORD')}</span>} {item[6] === 4 && <span className="badge badge-info badge-arrow3 badge-sm ml-1 word">WORD</span>}
{outputType.includes('pdf') && <span className="badge badge-secondary badge-sm ml-1">PDF</span>} {outputType.includes('pdf') && <span className="badge badge-secondary badge-sm ml-1">PDF</span>}
{outputType.includes('html') && <span className="badge badge-secondary badge-sm ml-1">HTML</span>} {outputType.includes('html') && <span className="badge badge-secondary badge-sm ml-1">HTML</span>}

View file

@ -680,7 +680,7 @@ class ApprovalList extends BaseChart {
<tbody> <tbody>
{data.data.map((item, idx) => { {data.data.map((item, idx) => {
return ( return (
<tr key={'approval-' + idx}> <tr key={`approval-${idx}`}>
<td className="user-avatar cell-detail user-info"> <td className="user-avatar cell-detail user-info">
<img src={`${rb.baseUrl}/account/user-avatar/${item[0]}`} alt="Avatar" /> <img src={`${rb.baseUrl}/account/user-avatar/${item[0]}`} alt="Avatar" />
<span>{item[1]}</span> <span>{item[1]}</span>
@ -689,7 +689,9 @@ class ApprovalList extends BaseChart {
</span> </span>
</td> </td>
<td className="cell-detail"> <td className="cell-detail">
<a href={`${rb.baseUrl}/app/redirect?id=${item[3]}`}>{item[4]}</a> <a href={`${rb.baseUrl}/app/redirect?id=${item[3]}&type=newtab`} target="_blank">
{item[4]}
</a>
<span className="cell-detail-description">{item[6]}</span> <span className="cell-detail-description">{item[6]}</span>
</td> </td>
<td className="actions text-right text-nowrap"> <td className="actions text-right text-nowrap">
@ -795,7 +797,7 @@ class FeedsSchedule extends BaseChart {
} }
return ( return (
<tr key={'schedule-' + idx}> <tr key={`schedule-${idx}`}>
<td> <td>
<a href={`${rb.baseUrl}/app/redirect?id=${item.id}`} className="content text-break" dangerouslySetInnerHTML={{ __html: item.content }} /> <a href={`${rb.baseUrl}/app/redirect?id=${item.id}`} className="content text-break" dangerouslySetInnerHTML={{ __html: item.content }} />
</td> </td>
@ -1118,10 +1120,7 @@ class ProjectTasks extends BaseChart {
$.post('/app/entity/common-save', JSON.stringify(data), (res) => { $.post('/app/entity/common-save', JSON.stringify(data), (res) => {
if (res.error_code > 0) return RbHighbar.error(res.error_msg) if (res.error_code > 0) return RbHighbar.error(res.error_msg)
$target $target.parents('tr').removeClass('status-0 status-1').addClass(`status-${data.status}`)
.parents('tr')
.removeClass('status-0 status-1')
.addClass('status-' + data.status)
}) })
} }
} }
@ -1231,7 +1230,7 @@ class DataList extends BaseChart {
data-id={lastCell.id} data-id={lastCell.id}
onDoubleClick={(e) => { onDoubleClick={(e) => {
$stopEvent(e, true) $stopEvent(e, true)
window.open(`${rb.baseUrl}/app/redirect?id=${lastCell.id}`) window.open(`${rb.baseUrl}/app/redirect?id=${lastCell.id}&type=newtab`)
}}> }}>
{row.map((c, idx) => { {row.map((c, idx) => {
if (idx === lastIndex) return null // Last is ID if (idx === lastIndex) return null // Last is ID

View file

@ -588,10 +588,29 @@ function __renderRichContent(e) {
} }
} }
let AL = e.autoLocation ? e.autoLocation.split('$$$$') : false
if (AL) {
AL = {
lng: AL[1].split(',')[0],
lat: AL[1].split(',')[1],
text: AL[0],
}
}
return ( return (
<div className="rich-content"> <div className="rich-content">
<div className="texts text-break" dangerouslySetInnerHTML={{ __html: contentHtml }} /> <div className="texts text-break" dangerouslySetInnerHTML={{ __html: contentHtml }} />
<div className="appends"> <div className="appends">
{AL && (
<div>
<span>
<i className="icon mdi mdi-cellphone-marker mr-1" />
</span>
<a title={$L('查看位置')} onClick={() => BaiduMapModal.view(AL)}>
{AL.text}
</a>
</div>
)}
{e.type === 4 && ( {e.type === 4 && (
<div> <div>
<div> <div>
@ -616,7 +635,7 @@ function __renderRichContent(e) {
<i className={`icon zmdi zmdi-${e.relatedRecord.icon}`} /> <i className={`icon zmdi zmdi-${e.relatedRecord.icon}`} />
{` ${e.relatedRecord.entityLabel} : `} {` ${e.relatedRecord.entityLabel} : `}
</span> </span>
<a href={`${rb.baseUrl}/app/redirect?id=${e.relatedRecord.id}`} title={$L('查看记录')}> <a href={`${rb.baseUrl}/app/redirect?id=${e.relatedRecord.id}&type=newtab`} target="_blank" title={$L('查看记录')}>
{e.relatedRecord.text} {e.relatedRecord.text}
</a> </a>
</div> </div>

View file

@ -119,7 +119,7 @@ $(document).ready(() => {
$item.addClass('hide') $item.addClass('hide')
} }
}) })
}) }, 200)
}) })
setTimeout(() => $input[0].focus(), 20) setTimeout(() => $input[0].focus(), 20)

View file

@ -7,6 +7,9 @@ See LICENSE and COMMERCIAL in the project root for license information.
/* global InitModels */ /* global InitModels */
window.__LAB_SHOWNDETAIL = false window.__LAB_SHOWNDETAIL = false
window.bosskeyTrigger = function () {
window.__LAB_SHOWNDETAIL = true
}
$(document).ready(() => { $(document).ready(() => {
const $bnew = $('.btn-primary.new').on('click', () => { const $bnew = $('.btn-primary.new').on('click', () => {

View file

@ -91,7 +91,7 @@ const RbListPage = {
if (ep.A !== true) $('.J_assign').remove() if (ep.A !== true) $('.J_assign').remove()
if (ep.S !== true) $('.J_share, .J_unshare').remove() if (ep.S !== true) $('.J_share, .J_unshare').remove()
$cleanMenu('.J_action') $cleanMenu('.J_action')
$('.dataTables_oper.invisible').removeClass('invisible') $('.dataTables_oper.invisible2').removeClass('invisible2')
} }
// Filter Pane // Filter Pane

View file

@ -220,7 +220,7 @@ class RbFormModal extends React.Component {
} }
// 获取当前表单对象 // 获取当前表单对象
getCurrentForm() { getFormComp() {
return this._formComponentRef return this._formComponentRef
} }
@ -230,17 +230,20 @@ class RbFormModal extends React.Component {
* @param {*} forceNew * @param {*} forceNew
*/ */
static create(props, forceNew) { static create(props, forceNew) {
const that = this
if (forceNew === true) { if (forceNew === true) {
renderRbcomp(<RbFormModal {...props} disposeOnHide />) renderRbcomp(<RbFormModal {...props} disposeOnHide />, function () {
that.__CURRENT35 = this
})
return return
} }
if (this.__HOLDER) { if (this.__HOLDER) {
this.__HOLDER.show(props) this.__HOLDER.show(props)
} else { } else {
const that = this
renderRbcomp(<RbFormModal {...props} />, null, function () { renderRbcomp(<RbFormModal {...props} />, null, function () {
that.__HOLDER = this that.__HOLDER = this
that.__CURRENT35 = this
}) })
} }
} }
@ -648,6 +651,9 @@ class RbForm extends React.Component {
const keys = Object.keys(this._ProTables) const keys = Object.keys(this._ProTables)
for (let i = 0; i < keys.length; i++) { for (let i = 0; i < keys.length; i++) {
const _ProTable = this._ProTables[keys[i]] const _ProTable = this._ProTables[keys[i]]
// 明细未配置或出错
if (!_ProTable._initModel) continue
const details = _ProTable.buildFormData() const details = _ProTable.buildFormData()
if (!details) return if (!details) return

View file

@ -22,7 +22,7 @@ class ProTable extends React.Component {
render() { render() {
if (this.state.hasError) { if (this.state.hasError) {
$('.detail-form-table .btn-group .btn').attr('disabled', true) // $('.detail-form-table .btn-group .btn').attr('disabled', true)
return <RbAlertBox message={this.state.hasError} /> return <RbAlertBox message={this.state.hasError} />
} }
@ -143,6 +143,12 @@ class ProTable extends React.Component {
} }
addLine(model) { addLine(model) {
// 明细未配置或出错
if (!model) {
if (this.state.hasError) RbHighbar.create(this.state.hasError)
return
}
const lineKey = `${this.props.entity.entity}-${model.id ? model.id : $random()}` const lineKey = `${this.props.entity.entity}-${model.id ? model.id : $random()}`
const ref = React.createRef() const ref = React.createRef()
const FORM = ( const FORM = (

View file

@ -90,7 +90,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-md-6"> <div class="col-12 col-md-6">
<div class="dataTables_oper invisible"> <div class="dataTables_oper invisible2">
<button class="btn btn-space btn-secondary J_view" type="button" disabled="disabled"><i class="icon zmdi zmdi-folder"></i> [[${bundle.L('打开')}]]</button> <button class="btn btn-space btn-secondary J_view" type="button" disabled="disabled"><i class="icon zmdi zmdi-folder"></i> [[${bundle.L('打开')}]]</button>
<button class="btn btn-space btn-secondary J_edit" type="button" disabled="disabled"><i class="icon zmdi zmdi-edit"></i> [[${bundle.L('编辑')}]]</button> <button class="btn btn-space btn-secondary J_edit" type="button" disabled="disabled"><i class="icon zmdi zmdi-edit"></i> [[${bundle.L('编辑')}]]</button>
<div class="btn-group btn-space J_action"> <div class="btn-group btn-space J_action">

View file

@ -36,6 +36,9 @@
outline: 0 none; outline: 0 none;
background-color: transparent; background-color: transparent;
} }
.sortable-box-title .search-input > input:focus {
border-color: #4285f4;
}
</style> </style>
</head> </head>
<body class="dialog"> <body class="dialog">

View file

@ -90,7 +90,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-md-6"> <div class="col-12 col-md-6">
<div class="dataTables_oper invisible"> <div class="dataTables_oper invisible2">
<button class="btn btn-space btn-secondary J_view" type="button" disabled="disabled"><i class="icon zmdi zmdi-folder"></i> [[${bundle.L('打开')}]]</button> <button class="btn btn-space btn-secondary J_view" type="button" disabled="disabled"><i class="icon zmdi zmdi-folder"></i> [[${bundle.L('打开')}]]</button>
<button class="btn btn-space btn-secondary J_edit" type="button" disabled="disabled"><i class="icon zmdi zmdi-edit"></i> [[${bundle.L('编辑')}]]</button> <button class="btn btn-space btn-secondary J_edit" type="button" disabled="disabled"><i class="icon zmdi zmdi-edit"></i> [[${bundle.L('编辑')}]]</button>
<button class="btn btn-space btn-primary J_new" type="button" disabled="disabled"><i class="icon zmdi zmdi-plus"></i> [[${bundle.L('新建')}]]</button> <button class="btn btn-space btn-primary J_new" type="button" disabled="disabled"><i class="icon zmdi zmdi-plus"></i> [[${bundle.L('新建')}]]</button>

View file

@ -3,7 +3,7 @@
// 建议将上方匹配路径设置为 `/` 以便观察效果 // 建议将上方匹配路径设置为 `/` 以便观察效果
const demoEntityName = 'Account' // TODO 修改为你要测试的实体 const demoEntityName = 'Account' // TODO 修改为你要测试的实体
const demoFieldName = 'AccountName' // TODO 修改为你要测试的实体字段 const demoFieldName = 'AccountName' // TODO 修改为你要测试的实体字段文本字段
let _List, _Form, _View let _List, _Form, _View
@ -78,6 +78,7 @@ function demoForList() {
// 指定字段加粗加红显示 // 指定字段加粗加红显示
const fieldKey = `${demoEntityName}.${demoFieldName}` const fieldKey = `${demoEntityName}.${demoFieldName}`
_List.regCellRender(fieldKey, function (v) { _List.regCellRender(fieldKey, function (v) {
// 如果返回 false 则按照默认样式显示
return <strong className="text-danger">{JSON.stringify(v)}</strong> return <strong className="text-danger">{JSON.stringify(v)}</strong>
}) })
@ -104,7 +105,31 @@ function demoForForm() {
const lookFieldKey = `${demoEntityName}.${demoFieldName}` const lookFieldKey = `${demoEntityName}.${demoFieldName}`
_Form.onFieldValueChange(function (fieldKey, fieldValue, recordId) { _Form.onFieldValueChange(function (fieldKey, fieldValue, recordId) {
if (lookFieldKey === fieldKey) { if (lookFieldKey === fieldKey) {
RbHighbar.create(`记录: ${recordId || ''} 的新值 : ${fieldValue}`) RbHighbar.create(`记录: ${recordId || ''} 的新值 : ${fieldValue || ''}`)
} }
}) })
_Form.onOpen(() => {
// 获取字段组件便便进行相关操作
const fieldComp = _Form.getFieldComp(demoFieldName)
// 设为必填
fieldComp.setNullable(false)
// 设置 Tip
fieldComp.setTip('危险品')
fieldComp.setTip(<b className="text-danger">危险品</b>)
// 显示隐藏
_Form.onFieldValueChange(function (fieldKey, fieldValue) {
if (lookFieldKey === fieldKey) {
// 输入 hide 隐藏
if (fieldValue === 'hide') {
fieldComp.setHidden(true)
// 3 秒后重新显示
setTimeout(() => fieldComp.setHidden(false), 3000)
}
}
})
})
} }