This commit is contained in:
REBUILD 企业管理系统 2025-09-02 10:08:17 +00:00 committed by GitHub
commit d36e803da4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
44 changed files with 316 additions and 156 deletions

2
@rbv

@ -1 +1 @@
Subproject commit 653879f9b8c8cc59876618091fa14265edf7ece8
Subproject commit 64bee3bc986073e62a4d3ee6c94bd3d3d4231e1c

View file

@ -17,6 +17,7 @@ import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.ArrayUtils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
@ -99,12 +100,36 @@ public class TableChart extends ChartData {
sumsRow[i] = dataRaw.length;
}
}
for (int i = numericalIndexStart; i < colLength; i++) {
BigDecimal sum = new BigDecimal(0);
for (Object[] row : dataRaw) {
sum = sum.add(BigDecimal.valueOf(ObjectUtils.toDouble(row[i])));
for (int i = numericalIndexStart, j = 0; i < colLength; i++, j++) {
final Numerical N = getNumericals()[j];
final FormatCalc FC = N.getFormatCalc();
BigDecimal sums = null;
if (FC == FormatCalc.MAX) {
// 最大
for (Object[] row : dataRaw) {
BigDecimal b = BigDecimal.valueOf(ObjectUtils.toDouble(row[i]));
if (sums == null || sums.compareTo(b) < 0) sums = b;
}
} else if (FC == FormatCalc.MIN) {
// 最小
for (Object[] row : dataRaw) {
BigDecimal b = BigDecimal.valueOf(ObjectUtils.toDouble(row[i]));
if (sums == null || sums.compareTo(b) > 0) sums = b;
}
} else {
sums = new BigDecimal(0);
// 求和
for (Object[] row : dataRaw) {
sums = sums.add(BigDecimal.valueOf(ObjectUtils.toDouble(row[i])));
}
// 均值
if (FC == FormatCalc.AVG) {
sums = sums.divide(new BigDecimal(dataRaw.length), N.getScale(), RoundingMode.HALF_UP);
}
}
sumsRow[i] = sum.doubleValue();
sumsRow[i] = sums.doubleValue();
}
dataRawNew[dataRaw.length] = sumsRow;

View file

@ -120,7 +120,7 @@ public class FilesHelper {
o[3] = user.equals(o[3]) || UserHelper.isAdmin(user); // v3.5.1 管理员可删除
o[5] = scopeSpecUsers;
JSONObject folder = JSONUtils.toJSONObject(
new String[] { "id", "text", "private", "self", "parent", "specUsers" }, o);
new String[]{"id", "text", "private", "self", "parent", "specUsers"}, o);
JSONArray children = getAccessableFolders(user, (ID) o[0]);
if (!children.isEmpty()) {
@ -176,14 +176,15 @@ public class FilesHelper {
}
/**
* 是否允许操作文件管理员与创建人允许
* 是否允许操作文件管理员与创建人允许
* v4.2 可访问就可修改
*
* @param user
* @param fileId
* @return
*/
public static boolean isFileManageable(ID user, ID fileId) {
return UserHelper.isAdmin(user) || UserHelper.isSelf(user, fileId);
return isFileAccessable(user, fileId);
}
/**

View file

@ -263,7 +263,7 @@ public class AdvFilterParser extends SetUser {
String field = item.getString("field");
if (field.startsWith("&amp;")) field = field.replace("&amp;", NAME_FIELD_PREFIX); // fix: _$unthy
final boolean hasNameFlag = field.startsWith(NAME_FIELD_PREFIX);
boolean hasNameFlag = field.startsWith(NAME_FIELD_PREFIX);
if (hasNameFlag) field = field.substring(1);
Field lastFieldMeta = VF_ACU.equals(field)
@ -274,6 +274,12 @@ public class AdvFilterParser extends SetUser {
return null;
}
String op = item.getString("op");
// 引用字段`重复`特殊处理
if (hasNameFlag && ParseHelper.REP.equalsIgnoreCase(op)) {
hasNameFlag = false;
}
DisplayType dt = EasyMetaFactory.getDisplayType(lastFieldMeta);
if (dt == DisplayType.CLASSIFICATION || (dt == DisplayType.PICKLIST && hasNameFlag) /* 快速查询 */) {
field = NAME_FIELD_PREFIX + field;
@ -295,7 +301,7 @@ public class AdvFilterParser extends SetUser {
final boolean isN2NUsers = dt == DisplayType.N2NREFERENCE
&& lastFieldMeta.getReferenceEntity().getEntityCode() == EntityHelper.User;
String op = item.getString("op");
// v3.9 区间兼容
if (ParseHelper.BW.equals(op)) {
String valueBegin = item.getString("value");
@ -645,9 +651,10 @@ public class AdvFilterParser extends SetUser {
StringUtils.join(value.split("\\|"), "', '"));
} else if (ParseHelper.REP.equalsIgnoreCase(op)) {
// `in`
int count = NumberUtils.toInt(value, 1);
value = MessageFormat.format(
"( select {0} from {1} group by {0} having (count({0}) > {2}) )",
field, rootEntity.getName(), String.valueOf(NumberUtils.toInt(value, 1)));
"( select {0} from {1} group by {0} having (count({0}) {2} {3}) )",
field, rootEntity.getName(), count < 1 ? "=" : ">", Math.max(count, 1));
}
if (StringUtils.isBlank(value)) {

View file

@ -185,63 +185,48 @@ public class QueryParser {
// 过滤器
List<String> wheres = new ArrayList<>();
List<String> whereAnds = new ArrayList<>();
// Default
String defaultFilter = dataListBuilder == null ? null : dataListBuilder.getDefaultFilter();
if (StringUtils.isNotBlank(defaultFilter)) {
wheres.add(defaultFilter);
whereAnds.add(defaultFilter);
}
// append: ProtocolFilter
String protocolFilter = queryExpr.getString("protocolFilter");
if (StringUtils.isNotBlank(protocolFilter)) {
ProtocolFilterParser fp = new ProtocolFilterParser(protocolFilter);
if (queryExpr.containsKey("protocolFilter__varRecord")) {
fp.setVarRecord(queryExpr.getJSONObject("protocolFilter__varRecord"));
}
String where = fp.toSqlWhere();
// d 强制过滤明细切换支持
if (StringUtils.isNotBlank(where) && protocolFilter.startsWith("via:014-") && entity.getMainEntity() != null) {
ConfigBean filter = AdvFilterManager.instance.getAdvFilter(ID.valueOf(protocolFilter.split(":")[1]));
String filterEntity = ((JSONObject) filter.getJSON("filter")).getString("entity");
Entity filterEntityMeta = MetadataHelper.getEntity(filterEntity);
// 明细使用主实体的
Entity me = entity.getMainEntity();
if (filterEntityMeta.equals(me)) {
Field dtmField = MetadataHelper.getDetailToMainField(entity);
where = String.format("%s in (select %sId from %s where %s)", dtmField.getName(), me.getName(), me.getName(), where);
}
}
if (StringUtils.isNotBlank(where)) {
if (CommonsUtils.DEVLOG) System.out.println("[dev] Parse protocolFilter : " + protocolFilter + " >> " + where);
wheres.add(where);
}
String w = parseProtocolFilter(protocolFilter);
if (w != null) whereAnds.add(w);
}
// append: protocolFilterAnd
String protocolFilterAnd = queryExpr.getString("protocolFilterAnd");
if (StringUtils.isNotBlank(protocolFilterAnd)) {
String w = parseProtocolFilter(protocolFilterAnd);
if (w != null) whereAnds.add(w);
}
// append: AdvFilter
String advFilter = queryExpr.getString("advFilter");
if (ID.isId(advFilter)) {
String where = parseAdvFilter(ID.valueOf(advFilter));
if (StringUtils.isNotBlank(where)) wheres.add(where);
if (StringUtils.isNotBlank(where)) whereAnds.add(where);
}
// append: QuickQuery
JSONObject quickFilter = queryExpr.getJSONObject("filter");
if (quickFilter != null) {
String where = new AdvFilterParser(quickFilter, entity).toSqlWhere();
if (StringUtils.isNotBlank(where)) wheres.add(where);
if (StringUtils.isNotBlank(where)) whereAnds.add(where);
}
// v3.3
JSONObject quickFilterAnd = queryExpr.getJSONObject("filterAnd");
if (quickFilterAnd != null) {
String where = new AdvFilterParser(quickFilterAnd, entity).toSqlWhere();
if (StringUtils.isNotBlank(where)) wheres.add(where);
if (StringUtils.isNotBlank(where)) whereAnds.add(where);
}
final String whereClause = wheres.isEmpty() ? "1=1" : StringUtils.join(wheres.iterator(), " and ");
final String whereClause = whereAnds.isEmpty() ? "1=1" : StringUtils.join(whereAnds.iterator(), " and ");
fullSql.append(" where ").append(whereClause);
// v4.0-b3 分组
@ -369,4 +354,32 @@ public class QueryParser {
return String.format("select %s from %s where ",
StringUtils.join(counts, ","), entity.getName());
}
/**
* @param protocolFilter
* @return
*/
private String parseProtocolFilter(String protocolFilter) {
ProtocolFilterParser fp = new ProtocolFilterParser(protocolFilter);
if (queryExpr.containsKey("protocolFilter__varRecord")) {
fp.setVarRecord(queryExpr.getJSONObject("protocolFilter__varRecord"));
}
String where = fp.toSqlWhere();
// d 强制过滤明细切换支持
if (StringUtils.isNotBlank(where) && protocolFilter.startsWith("via:014-") && entity.getMainEntity() != null) {
ConfigBean filter = AdvFilterManager.instance.getAdvFilter(ID.valueOf(protocolFilter.split(":")[1]));
String filterEntity = ((JSONObject) filter.getJSON("filter")).getString("entity");
Entity filterEntityMeta = MetadataHelper.getEntity(filterEntity);
// 明细使用主实体的
Entity me = entity.getMainEntity();
if (filterEntityMeta.equals(me)) {
Field dtmField = MetadataHelper.getDetailToMainField(entity);
where = String.format("%s in (select %sId from %s where %s)", dtmField.getName(), me.getName(), me.getName(), where);
}
}
if (CommonsUtils.DEVLOG) System.out.println("[dev] Parse protocolFilter : " + protocolFilter + " >> " + where);
return where;
}
}

View file

@ -129,7 +129,7 @@ public class AdminVerfiyController extends BaseController {
// 日志
if ("log".equalsIgnoreCase(type)) {
File logFile = SysbaseHeartbeat.getLastLogbackFile();
FileDownloader.setDownloadHeaders(response, logFile.getName(), false);
FileDownloader.setDownloadHeaders(response, logFile.getName(), true);
FileDownloader.writeLocalFile(logFile, response);
return;
}

View file

@ -67,7 +67,7 @@ public class FilePreviewer extends BaseController {
@GetMapping("/commons/file-editor")
public ModelAndView ooEditor(HttpServletRequest request, HttpServletResponse response) throws IOException {
getRequestUser(request);
getRequestUser(request); // check
return ooPreviewOrEditor(request, response, true);
}
@ -141,15 +141,17 @@ public class FilePreviewer extends BaseController {
ModelAndView mv = createModelAndView("/common/oo-preview");
mv.getModel().put(OnlyofficeServer.name(), OnlyOffice.getOoServer());
String fileName = ((JSONObject) ps[0]).getString("title");
// 编辑模式
if (editor) {
ooConfig.put("type", "desktop");
mv.getModel().put("title", Language.L("文档编辑"));
mv.getModel().put("title", fileName + " - " + Language.L("文档编辑"));
} else {
// https://api.onlyoffice.com/docs/docs-api/usage-api/config/#type
String view = StringUtils.defaultIfBlank(getParameter(request, "view"), "embedded");
ooConfig.put("type", view);
mv.getModel().put("title", Language.L("文档预览"));
mv.getModel().put("title", fileName + " - " + Language.L("文档预览"));
}
if (Application.devMode()) System.out.println("[dev] " + JSONUtils.prettyPrint(ooConfig));
mv.getModel().put("_DocEditorConfig", ooConfig);

View file

@ -24,6 +24,7 @@ import com.rebuild.core.metadata.easymeta.DisplayType;
import com.rebuild.core.metadata.easymeta.EasyField;
import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
import com.rebuild.core.privileges.PrivilegesManager;
import com.rebuild.core.service.general.QuickCodeReindexTask;
import com.rebuild.core.service.trigger.TriggerAction;
import com.rebuild.web.BaseController;
import com.rebuild.web.EntityParam;
@ -61,24 +62,27 @@ public class MetadataGetting extends BaseController {
for (Entity e : MetadataSorter.sortEntities(usesNopriv ? null : user, usesBizz, usesDetail)) {
JSONObject item = (JSONObject) EasyMetaFactory.valueOf(e).toJSON();
item.put("name", item.getString("entity"));
item.put("label", item.getString("entityLabel"));
String L42 = item.getString("entityLabel");
item.put("label", L42);
item.put("quickCode", QuickCodeReindexTask.generateQuickCode(L42));
res.add(item);
}
return res;
}
@GetMapping("fields")
public JSON fields(HttpServletRequest request) {
Entity entity = MetadataHelper.getEntity(getParameterNotNull(request, "entity"));
public JSON fields(@EntityParam Entity entity, HttpServletRequest request) {
// 返回引用实体的字段层级
int appendRefFields = getIntParameter(request, "deep");
// 丰富字段信息
boolean riching = getBoolParameter(request, "riching", true);
// 根据不同的 referer 返回不同的字段列表
// 返回 ID 主键字段
String referer = getParameter(request, "referer");
int forceWith = "withid".equals(referer) ? 1 : 0;
return MetaFormatter.buildFieldsWithRefs(entity, appendRefFields, true, forceWith, field -> {
return MetaFormatter.buildFieldsWithRefs(entity, appendRefFields, riching, forceWith, field -> {
if (!field.isQueryable()) return true;
if (field instanceof Field) {

View file

@ -27,6 +27,7 @@ import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
import com.rebuild.core.privileges.RoleService;
import com.rebuild.core.privileges.UserHelper;
import com.rebuild.core.privileges.bizz.ZeroEntry;
import com.rebuild.core.service.general.QuickCodeReindexTask;
import com.rebuild.core.support.i18n.Language;
import com.rebuild.utils.JSONUtils;
import com.rebuild.utils.RbAssert;
@ -99,7 +100,9 @@ public class ListFieldsController extends BaseController implements ShareTo {
List<Map<String, Object>> fieldList = new ArrayList<>();
for (Field field : MetadataSorter.sortFields(entityMeta)) {
if (canListField(field)) {
fieldList.add(DataListManager.instance.formatField(field));
Map<String, Object> m = DataListManager.instance.formatField(field);
m.put("quickCode", QuickCodeReindexTask.generateQuickCode((String) m.get("label")));
fieldList.add(m);
}
}
@ -121,7 +124,9 @@ public class ListFieldsController extends BaseController implements ShareTo {
for (Field field2 : MetadataSorter.sortFields(refEntity)) {
if (canListField(field2)) {
fieldList.add(DataListManager.instance.formatField(field2, field));
Map<String, Object> m = DataListManager.instance.formatField(field2, field);
m.put("quickCode", QuickCodeReindexTask.generateQuickCode((String) m.get("label")));
fieldList.add(m);
if (deep3 && EasyMetaFactory.getDisplayType(field2) == DisplayType.REFERENCE) {
refFieldsOf2.add(new Field[]{field, field2});
@ -145,7 +150,9 @@ public class ListFieldsController extends BaseController implements ShareTo {
for (Field field3 : MetadataSorter.sortFields(refEntity2)) {
if (canListField(field3)) {
fieldList.add(DataListManager.instance.formatField(field3, parentField));
Map<String, Object> m = DataListManager.instance.formatField(field3, parentField);
m.put("quickCode", QuickCodeReindexTask.generateQuickCode((String) m.get("label")));
fieldList.add(m);
}
}
}

View file

@ -20,6 +20,7 @@ import com.rebuild.core.metadata.easymeta.DisplayType;
import com.rebuild.core.metadata.easymeta.EasyField;
import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
import com.rebuild.core.metadata.impl.EasyFieldConfigProps;
import com.rebuild.core.service.general.QuickCodeReindexTask;
import com.rebuild.core.support.i18n.Language;
import com.rebuild.core.support.state.StateManager;
import com.rebuild.utils.JSONUtils;
@ -71,6 +72,9 @@ public class MetaFormatter {
field.getExtraAttr(EasyFieldConfigProps.DECIMAL_FORMAT));
}
String L42 = res.getString("label");
res.put("quickCode", QuickCodeReindexTask.generateQuickCode(L42));
return res;
}
@ -101,10 +105,10 @@ public class MetaFormatter {
* 获取字段列表
*
* @param entity
* @param deep 几级
* @param deep
* @param riching
* @param forceWith 1=ID, 2=approvalStepNode
* @param filter
* @param filter 过滤
* @return
*/
public static JSONArray buildFieldsWithRefs(Entity entity, int deep, boolean riching, int forceWith, Predicate<BaseMeta> filter) {
@ -203,11 +207,13 @@ public class MetaFormatter {
return res;
}
private static JSONObject buildField(EasyField field, String[] parentsField, boolean rich) {
private static JSONObject buildField(EasyField field, String[] parentField, boolean rich) {
JSONObject item = rich ? buildRichField(field) : (JSONObject) field.toJSON();
if (parentsField != null) {
item.put("name", parentsField[0] + "." + item.get("name"));
item.put("label", parentsField[1] + "." + item.get("label"));
if (parentField != null) {
item.put("name", parentField[0] + "." + item.get("name"));
String L42 = parentField[1] + "." + item.get("label");
item.put("label", L42);
item.put("quickCode", QuickCodeReindexTask.generateQuickCode(L42));
}
return item;
}

View file

@ -16,7 +16,7 @@
<th:block th:replace="~{/_include/nav-left-admin(active='login-logs')}" />
<div class="rb-content">
<div class="main-content container-fluid">
<div class="card card-table">
<div class="card card-table card-topcolor">
<div class="card-body">
<div class="dataTables_wrapper container-fluid">
<div class="row rb-datatable-header">

View file

@ -11,7 +11,7 @@
<th:block th:replace="~{/_include/nav-left-admin(active='recycle-bin')}" />
<div class="rb-content">
<div class="main-content container-fluid">
<div class="card card-table">
<div class="card card-table card-topcolor">
<div class="card-body">
<div class="dataTables_wrapper container-fluid">
<div class="row rb-datatable-header">
@ -52,8 +52,6 @@
<script th:src="@{/assets/js/general/rb-datalist.common.js}" type="text/babel"></script>
<script th:src="@{/assets/js/general/rb-datalist.js}" type="text/babel"></script>
<script th:src="@{/assets/js/admin/recycle-bin.js}" type="text/babel"></script>
<script th:src="@{/assets/lib/prettier/standalone.js}"></script>
<script th:src="@{/assets/lib/prettier/parser-babel.js}"></script>
<script th:src="@{/assets/lib/clipboard.min.js}"></script>
</body>
</html>

View file

@ -52,7 +52,7 @@
<th:block th:replace="~{/_include/nav-left-admin(active='revision-history')}" />
<div class="rb-content">
<div class="main-content container-fluid">
<div class="card card-table">
<div class="card card-table card-topcolor">
<div class="card-body">
<div class="dataTables_wrapper container-fluid">
<div class="row rb-datatable-header">

View file

@ -53,7 +53,7 @@
<a href="departments" class="nav-link active"><span class="icon mdi mdi-account-multiple"></span> [[${bundle.L('部门')}]]</a>
</li>
</ul>
<div class="card card-table">
<div class="card card-table card-topcolor">
<div class="card-body">
<div class="dataTables_wrapper container-fluid">
<div class="row rb-datatable-header">

View file

@ -11,7 +11,7 @@
<th:block th:replace="~{/_include/nav-left-admin(active='teams')}" />
<div class="rb-content">
<div class="main-content container-fluid">
<div class="card card-table">
<div class="card card-table card-topcolor">
<div class="card-body">
<div class="dataTables_wrapper container-fluid">
<div class="row rb-datatable-header">

View file

@ -58,7 +58,7 @@
<a href="departments" class="nav-link"><span class="icon mdi mdi-account-multiple"></span> [[${bundle.L('部门')}]]</a>
</li>
</ul>
<div class="card card-table">
<div class="card card-table card-topcolor">
<div class="card-body">
<div class="dataTables_wrapper container-fluid">
<div class="row rb-datatable-header">

View file

@ -114,8 +114,6 @@
<th:block th:replace="~{/_include/footer}" />
<script th:src="@{/assets/js/admin/config-comps.js}" type="text/babel"></script>
<script th:src="@{/assets/js/admin/apis-manager.js}" type="text/babel"></script>
<script th:src="@{/assets/lib/prettier/standalone.js}"></script>
<script th:src="@{/assets/lib/prettier/parser-babel.js}"></script>
<script th:src="@{/assets/lib/clipboard.min.js}"></script>
</body>
</html>

View file

@ -671,10 +671,8 @@ See LICENSE and COMMERCIAL in the project root for license information.
}
.tcs td.tcs-selected:not(.sel-from) {
/*border: 1px double #4887c7;*/
/*background-color: rgba(51, 122, 183, 0.2) !important;*/
border: 1px double #4285f4;
background-color: #f5f8fd;
background-color: #f5f8fd !important;
}
.chart-box.CNMAP .chart-body {
@ -779,6 +777,15 @@ See LICENSE and COMMERCIAL in the project root for license information.
/* gradient-backgrounds.css */
.gradient-bg .tcs td.tcs-selected:not(.sel-from) {
background-color: rgba(255, 255, 255, 0.5) !important;
}
.gradient-bg .table-bordered th,
.gradient-bg .table-bordered td {
border-color: rgba(0, 0, 0, 0.15);
}
.gradient-bg-1 {
background: linear-gradient(126deg, #c471f5, #fa71cd);
}

View file

@ -348,6 +348,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
width: 100%;
height: 240px;
background-color: #fff;
border-radius: 6px;
}
#asideCharts .chart-box .chart-body {
@ -367,7 +368,6 @@ See LICENSE and COMMERCIAL in the project root for license information.
}
#asideCharts .chart-box:hover .chart-oper a.J_source,
#asideCharts .chart-box:hover .chart-oper a.J_fullscreen,
#asideCharts .chart-box:hover .chart-oper a.J_chart-edit,
#asideCharts .chart-box:hover .chart-oper a.J_export {
display: none !important;
@ -381,11 +381,28 @@ See LICENSE and COMMERCIAL in the project root for license information.
position: fixed;
top: 60px !important;
left: 230px !important;
width: 100% !important;
height: 100% !important;
right: 0 !important;
bottom: 0 !important;
z-index: 9999;
background-color: #fff;
padding: 20px;
margin: 0;
overflow: hidden;
transition: all 0.3s;
}
#asideCharts .charts-wrap > div.fullscreen .chart-box {
height: 100%;
}
@media (min-width: 768px) and (max-width: 1464px) {
#asideCharts .charts-wrap > div.fullscreen {
left: 200px !important;
}
}
.rb-collapsible-sidebar-collapsed #asideCharts .charts-wrap > div.fullscreen {
left: 60px !important;
}
#asideCharts .charts-wrap .ui-sortable-helper {
@ -444,3 +461,21 @@ See LICENSE and COMMERCIAL in the project root for license information.
margin-left: 0;
}
}
.dataTables_wrapper .rb-datatable-header.v42 {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 10px;
}
.dataTables_wrapper .rb-datatable-header.v42 > div {
flex: none;
white-space: nowrap;
width: unset;
max-width: unset;
}
.dataTables_wrapper .rb-datatable-header.v42 > div.right {
text-align: right;
}

View file

@ -3840,10 +3840,14 @@ a.user-show:not([href]) {
}
.page-aside.widgets {
z-index: 2;
z-index: 3;
overflow: hidden;
}
.page-aside.widgets + .main-content {
z-index: 2;
}
.page-aside.widgets .tab-container {
min-width: 279px;
overflow: hidden;

View file

@ -264,9 +264,9 @@ class AppLogsViewer extends RbModal {
<dt className="col-sm-3">{$L('请求地址')}</dt>
<dd className="col-sm-9 text-break">{dataShow[3]}</dd>
<dt className="col-sm-12">{$L('请求数据')}</dt>
<dd className="col-sm-12">{dataShow[4] && <CodeViewport code={dataShow[4]} />}</dd>
<dd className="col-sm-12">{dataShow[4] && <CodeViewport code={dataShow[4]} type="json" />}</dd>
<dt className="col-sm-12">{$L('响应数据')}</dt>
<dd className="col-sm-12 mb-0">{dataShow[5] && <CodeViewport code={dataShow[5]} />}</dd>
<dd className="col-sm-12 mb-0">{dataShow[5] && <CodeViewport code={dataShow[5]} type="json" />}</dd>
</dl>
</div>
) : (

View file

@ -545,10 +545,12 @@ class StartNodeConfig extends RbFormHandler {
<p className="form-text m-0">{$L('符合条件的记录才可以使用/选择此流程')}</p>
</div>
</div>
<div className="form-group mt-5 bosskey-show">
<div className="form-group mt-5 mb-0">
<label className="text-bold">{$L('禁止撤回')}</label>
<label className="custom-control custom-control-sm custom-checkbox">
<input className="custom-control-input" type="checkbox" name="unallowCancel" checked={this.state.unallowCancel === true} onChange={this.handleChange} />
<span className="custom-control-label">{$L('审批后禁止提交人撤回')} (LAB)</span>
<span className="custom-control-label">{$L('审批后禁止提交人撤回')}</span>
</label>
</div>
</div>

View file

@ -169,7 +169,7 @@ CellRenders.renderSimple = function (v, s, k) {
// ~~ 数据详情
class DlgDetails extends RbAlert {
renderContent() {
return this.state.code && <CodeViewport code={this.state.code} />
return this.state.code && <CodeViewport code={this.state.code} type="json" />
}
componentDidMount() {

View file

@ -35,7 +35,9 @@ class ReportList extends ConfigList {
{taggedTitle(item[3])}
</a>
) : (
taggedTitle(item[3])
<a title={$L('模版在线编辑')} href={`${rb.baseUrl}/commons/file-editor?src=${item[0]}`}>
{taggedTitle(item[3])}
</a>
)}
{item[6] === 1 && <span className="badge badge-info badge-arrow3 badge-pill ml-1 excel">EXCEL</span>}
{item[6] === 2 && <span className="badge badge-info badge-arrow3 badge-pill ml-1 excel">{$L('EXCEL 列表')}</span>}

View file

@ -191,7 +191,7 @@ $(document).ready(() => {
// 背景色
function _removeClass($el) {
$el.removeClass(function (index, className) {
return (className.match(/\bgradient-bg-\S+/g) || []).join(' ')
return (className.match(/gradient-bg(?:-\d+)?/g) || []).join(' ')
})
return $el
}
@ -207,7 +207,7 @@ $(document).ready(() => {
renderRbcomp(
<DlgBgcolor
onConfirm={(colorIndex) => {
_removeClass($('#chart-preview >.chart-box')).addClass(`gradient-bg-${colorIndex}`)
_removeClass($('#chart-preview >.chart-box')).addClass(`gradient-bg gradient-bg-${colorIndex}`)
_removeClass($bs.find('>a:eq(1)')).addClass(`gradient-bg-${colorIndex}`)
$bs.find('>a:eq(0)').attr('data-bgcolor', colorIndex)
// check

View file

@ -109,12 +109,13 @@ class BaseChart extends React.Component {
const $stack = $('.chart-grid>.grid-stack')
if (!$stack[0]) {
// in DataList
// $(this._$box).parent().toggleClass('fullscreen')
const $wrap = $(this._$box).parent()
$wrap.toggleClass('fullscreen')
this.resize()
return
}
const $boxParent = $(this._$box).parents('.grid-stack-item')
if (this.state.fullscreen) {
BaseChart.currentFullscreen = this
if (!this.__chartStackHeight) this.__chartStackHeight = $stack.height()
@ -527,9 +528,14 @@ const reOptionMutliYAxis = function (option) {
}
const renderEChart = function (option, $target) {
const c = echarts.init(document.getElementById($target), 'light', {
$target = document.getElementById($target)
const c = echarts.init($target, 'light', {
renderer: navigator.userAgent.match(/(iPhone|iPod|Android|ios|SymbianOS)/i) ? 'svg' : 'canvas',
})
// v4.2 禁用右键
$target.addEventListener('contextmenu', function (e) {
e.preventDefault()
})
if (rb.env === 'dev') console.log(option)
c.setOption(option)
return c

View file

@ -355,7 +355,17 @@ class DlgAddChart extends RbFormHandler {
<div className="form-group row">
<label className="col-sm-3 col-form-label text-sm-right">{$L('图表数据来源')}</label>
<div className="col-sm-7">
<select className="form-control form-control-sm" ref={(c) => (this._$entity = c)} />
<select className="form-control form-control-sm" ref={(c) => (this._$entity = c)}>
{this.state._entities &&
this.state._entities.map((item) => {
if ($isSysMask(item.label)) return null
return (
<option key={item.name} value={item.name} data-pinyin={item.quickCode}>
{item.label}
</option>
)
})}
</select>
</div>
</div>
<div className="form-group row footer">
@ -375,16 +385,11 @@ class DlgAddChart extends RbFormHandler {
componentDidMount() {
$.get('/commons/metadata/entities?detail=true', (res) => {
const _data = res.data || []
_data.forEach((item) => {
if (!$isSysMask(item.label)) {
$(`<option value="${item.name}">${item.label}</option>`).appendTo(this._$entity)
}
})
this.__select2 = $(this._$entity).select2({
allowClear: false,
placeholder: $L('选择数据来源'),
this.setState({ _entities: res.data || [] }, () => {
this.__select2 = $(this._$entity).select2({
allowClear: false,
placeholder: $L('选择数据来源'),
})
})
})
}

View file

@ -9,7 +9,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
// ~~ 图片/文档预览
const TYPE_TEXTS = ['.txt', '.xml', '.json', '.md', '.yml', '.css', '.js', '.htm', '.html', '.log', '.sql', '.conf', '.sh', '.bat', '.java', '.ini']
const TYPE_DOCS = ['.doc', '.docx', '.rtf', '.xls', '.xlsx', '.ppt', '.pptx', '.pdf']
const TYPE_DOCS = ['.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.pdf'] // .rtf, .csv
const TYPE_IMGS = ['.jpg', '.jpeg', '.gif', '.png', '.bmp', '.jfif', '.webp']
const TYPE_AUDIOS = ['.mp3', '.wma', '.m4a', '.flac', '.ogg', '.acc']
const TYPE_VIDEOS = ['.mp4', '.wmv', '.mov', '.avi', '.mkv', '.webm', '.m4v', '.mpg', '.mpge']
@ -57,6 +57,11 @@ class RbPreview extends React.Component {
<h5 className="text-bold">{fileName}</h5>
</div>
<div className="float-right">
{this.props.id && this._isDoc(fileName) && (
<a href={`${rb.baseUrl}/commons/file-editor?src=${this.props.id}`} target="_blank" title={$L('在线编辑')}>
<i className="mdi mdi-microsoft-office fs-17" />
</a>
)}
{rb.fileSharable && (
<a onClick={this.share} title={$L('分享')}>
<i className="zmdi zmdi-share fs-17" />
@ -388,11 +393,12 @@ class RbPreview extends React.Component {
/**
* @param {*} urls string or array of URL
* @param {*} index
* @param {*} id
*/
static create(urls, index) {
static create(urls, index, id) {
if (!urls) return
if (typeof urls === 'string') urls = [urls]
renderRbcomp(<RbPreview urls={urls} currentIndex={index || 0} />)
renderRbcomp(<RbPreview urls={urls} currentIndex={index || 0} id={id} />)
}
}

View file

@ -423,20 +423,30 @@ class FileEditDlg extends RbFormHandler {
render() {
const file = this.props.file
const isOffice = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'].includes($fileExtName(file.fileName))
return (
<RbModal title={$L('修改文件')} ref={(c) => (this._dlg = c)} disposeOnHide>
<div className="form">
<div className="form-group row">
<label className="col-sm-3 col-form-label text-sm-right">{$L('文件名称')}</label>
<label className="col-sm-3 col-form-label text-sm-right">{$L('重命名')}</label>
<div className="col-sm-7">
<input className="form-control form-control-sm" defaultValue={file.fileName} ref={(c) => (this._$fileName = c)} />
<p className="form-text bosskey-show">
<a href={`${rb.baseUrl}/commons/file-editor?src=${file.id}`} target="_blank">
{$L('在线编辑')} (LAB)
</a>
</p>
</div>
</div>
{isOffice && (
<div className="form-group row p-0">
<label className="col-sm-3 col-form-label text-sm-right" />
<div className="col-sm-7">
<div className="form-control-plaintext">
<a href={`${rb.baseUrl}/commons/file-editor?src=${file.id}`} target="_blank">
<i className="mdi mdi-microsoft-office icon" />
&nbsp;
{$L('在线编辑')}
</a>
</div>
</div>
</div>
)}
<div className="form-group row footer">
<div className="col-sm-7 offset-sm-3" ref={(c) => (this._btns = c)}>
<button className="btn btn-primary" type="button" onClick={this._post}>
@ -481,7 +491,7 @@ class FilesList4Docs extends FilesList {
<div className="info position-relative">
<span className="fop-action">
<a title={$L('修改')} onClick={(e) => this._handleEdit(item, e)}>
<i className="icon zmdi zmdi-edit up-1" />
<i className="icon mdi mdi-square-edit-outline fs-17" />
</a>
<a title={$L('下载')} onClick={(e) => $stopEvent(e)} href={`${rb.baseUrl}/files/download?id=${item.id}`} target="_blank">
<i className="icon zmdi zmdi-download fs-17" />

View file

@ -83,7 +83,7 @@ class FilesList extends React.Component {
{this._pageNo === 1 && this.state.files && this.state.files.length === 0 && (
<div className="list-nodata">
<i className="zmdi zmdi-folder-outline" />
<p>{$L('暂无数据')}</p>
<p>{$L('暂无文件')}</p>
</div>
)}
</div>

View file

@ -32,7 +32,7 @@ $(document).ready(() => {
$(_data.fieldList).each(function () {
// eslint-disable-next-line no-undef
if (!$isSysMask(this.label)) render_unset([this.field, this.label])
if (!$isSysMask(this.label)) render_unset([this.field, this.label, this.quickCode])
})
$(_data.configList).each(function () {
const fkey = this.field
@ -119,7 +119,8 @@ $(document).ready(() => {
const q = $trim(e.target.value).toLowerCase()
$('.unset-list .dd-item').each(function () {
const $item = $(this)
if (!q || $item.text().toLowerCase().includes(q) || $item.data('key').toLowerCase().includes(q)) {
const pinyin = $item.data('pinyin')
if (!q || $item.text().toLowerCase().includes(q) || $item.data('key').toLowerCase().includes(q) || (pinyin && pinyin.toLowerCase().includes(q))) {
$item.removeClass('hide')
} else {
$item.addClass('hide')

View file

@ -381,7 +381,7 @@ class FilterItem extends React.Component {
<select className="form-control form-control-sm" ref={(c) => (this._filterField = c)}>
{this.state.fields.map((item) => {
return (
<option value={item.name + NT_SPLIT + item.type} key={`field-${item.name}`} title={item.label}>
<option value={item.name + NT_SPLIT + item.type} key={item.name} title={item.label} data-pinyin={item.quickCode}>
{item.label}
</option>
)
@ -392,7 +392,7 @@ class FilterItem extends React.Component {
<select className="form-control form-control-sm" ref={(c) => (this._filterOp = c)}>
{this.selectOp().map((item) => {
return (
<option value={item} key={`op-${item}`} title={OP_TYPE[item]}>
<option value={item} key={item} title={OP_TYPE[item]}>
{OP_TYPE[item]}
</option>
)
@ -442,8 +442,8 @@ class FilterItem extends React.Component {
op = ['IN', 'NIN']
}
// v3.6-b4,v3.7
if (['TEXT', 'PHONE', 'EMAIL', 'URL', 'DATE', 'DATETIME', 'TIME'].includes(fieldType)) op.push('REP')
// v3.6-b4, v3.7, v4.2
if (['TEXT', 'PHONE', 'EMAIL', 'URL', 'DATE', 'DATETIME', 'TIME', 'REFERENCE', 'ANYREFERENCE'].includes(fieldType)) op.push('REP')
if (this.isApprovalState()) op = ['IN', 'NIN']
else if (this.state.field === VF_ACU) op = ['IN', 'SFU', 'SFB', 'SFT'] // v3.7 准备废弃

View file

@ -876,11 +876,11 @@ const RbListCommon = {
def40 = def40.split(':') // FILTER:LAYOUT
if (def40[0]) {
if (def40[0].startsWith('014-')) wpc.protocolFilter = `via:${def40[0]}`
if (def40[0].startsWith('014-')) wpc.protocolFilterAnd = `via:${def40[0]}`
else console.log('Use listConfig :', def40[0])
}
if (def40[1]) {
if (def40[1].startsWith('014-')) wpc.protocolFilter = `via:${def40[1]}`
if (def40[1].startsWith('014-')) wpc.protocolFilterAnd = `via:${def40[1]}`
else console.log('Use listConfig :', def40[1])
}
}
@ -1211,6 +1211,7 @@ class RbList extends React.Component {
filter: this.lastFilter,
advFilter: this.advFilterId,
protocolFilter: this.props.protocolFilter || wpc.protocolFilter,
protocolFilterAnd: this.props.protocolFilterAnd || wpc.protocolFilterAnd,
sort: sort,
reload: reload,
statsField: wpc.statsField === true && rb.commercial > 0,
@ -2347,6 +2348,7 @@ const CategoryWidget = {
onItemClick={(query) => {
if (!query || query[0] === CategoryWidget.__ALL) wpc.protocolFilter = null
else wpc.protocolFilter = `category:${wpc.entity[0]}:${query.join('$$$$')}`
_RbList().pageNo = 1
RbListPage.reload()
}}
/>,

View file

@ -312,6 +312,7 @@ $(document).ready(() => {
// v4.1 AI
window.attachAibotPageData = function (cb) {
renderRbcomp(
// eslint-disable-next-line react/jsx-no-undef
<DlgAttachRecordList
onConfirm={(s) => {
const qe = RbListPage._RbList.getLastQueryEntry()

View file

@ -810,7 +810,7 @@ class CompCategoryItem extends React.Component {
<select className="form-control form-control-sm" ref={(c) => (this._$field = c)}>
{this.props.fields.map((item) => {
return (
<option key={item.name} value={item.name}>
<option key={item.name} value={item.name} data-pinyin={item.quickCode}>
{item.label}
</option>
)

View file

@ -23,7 +23,7 @@ $(document).ready(() => {
$.get('/commons/metadata/entities?detail=true', (res) => {
$(res.data).each(function () {
if (!$isSysMask(this.label)) {
$(`<option value="${this.name}">${this.label}</option>`).appendTo('.J_menuEntity optgroup:eq(0)')
$(`<option value="${this.name}" data-pinyin="${this.quickCode}">${this.label}</option>`).appendTo('.J_menuEntity optgroup:eq(0)')
}
_entities[this.name] = this
})
@ -33,7 +33,9 @@ $(document).ready(() => {
placeholder: $L('选择关联项'),
allowClear: false,
templateResult: function (res) {
const $span = $('<span class="icon-append"></span>').attr('title', res.text).text(res.text)
console.log(res)
const $span = $('<span></span>').attr('title', res.text).text(res.text)
if (!res.children) $span.addClass('icon-append') // optgroup
const found = _entities[res.id]
if (found) $(`<i class="icon zmdi zmdi-${found.icon}"></i>`).appendTo($span)
return $span

View file

@ -1173,7 +1173,7 @@ class Md2Html extends React.Component {
}
// 替换换行并保持表格换行
let cHtml = marked.parse(md.replace(/(?<!\|)\n(?!\|)/g, '\n\n'))
let cHtml = marked.parse(md.replace(/(?<!\|)\n(?!\|)/g, '\n'))
cHtml = cHtml.replace(/<img src="([^"]+)"/g, function (s, src) {
let srcNew = src + (src.includes('?') ? '&' : '?') + 'imageView2/2/w/1000/interlace/1/q/100'
return s.replace(src, srcNew)
@ -1199,13 +1199,17 @@ class Md2Html extends React.Component {
.each(function () {
const $img = $(this)
let isrc = $img.attr('src')
isrc = isrc.split('/filex/img/')[1].split(/[?&]imageView2/)[0]
imgs.push(isrc)
$img.on('click', (e) => {
$stopEvent(e, true)
const p = parent || window
p.RbPreview.create(imgs, imgs.indexOf(isrc) || 0)
})
if (isrc) {
if (isrc.includes('/filex/img/')) {
isrc = isrc.split('/filex/img/')[1].split(/[?&]imageView2/)[0]
}
imgs.push(isrc)
$img.on('click', (e) => {
$stopEvent(e, true)
const p = parent || window
p.RbPreview.create(imgs, imgs.indexOf(isrc) || 0)
})
}
})
})
}
@ -1318,7 +1322,7 @@ class CodeViewport extends React.Component {
}
componentDidMount() {
this._$code.innerHTML = $formattedCode(this.props.code || '')
this._$code.innerHTML = $formattedCode(this.props.code || '', this.props.type)
if (this._$copy) {
const that = this
@ -1333,7 +1337,10 @@ class CodeViewport extends React.Component {
}
UNSAFE_componentWillReceiveProps(newProps) {
if (newProps.code) this._$code.innerHTML = $formattedCode(newProps.code)
// eslint-disable-next-line eqeqeq
if (newProps.code && newProps.code != this.props.code) {
this._$code.innerHTML = $formattedCode(newProps.code, this.props.type)
}
}
}

View file

@ -472,7 +472,7 @@ var _showNotification = function (state) {
var _Notification = window.Notification || window.mozNotification || window.webkitNotification
if (_Notification) {
if (_Notification.permission === 'granted') {
var n = new _Notification($L('你有 %d 条未读消息', state), {
var n = new _Notification($L('你有 %d 条未读通知', state), {
body: window.rb.appName,
icon: rb.baseUrl + '/assets/img/icon-192x192.png',
tag: 'rbNotification',
@ -1239,14 +1239,18 @@ var $select2MatcherAll = function (params, data) {
return null
}
function _matcher(item, s) {
// 匹配
function _FN(item, s) {
s = s.toLowerCase()
return (item.text || '').toLowerCase().indexOf(s) > -1 || (item.id || '').toLowerCase().indexOf(s) > -1
if ((item.text || '').toLowerCase().indexOf(s) > -1 || (item.id || '').toLowerCase().indexOf(s) > -1) return true
// v4.2
var pinyin = $(item.element).data('pinyin')
return pinyin && pinyin.toLowerCase().indexOf(s) > -1
}
if (data.children) {
var ch = data.children.filter(function (item) {
return _matcher(item, params.term)
return _FN(item, params.term)
})
if (ch.length === 0) return null
@ -1254,7 +1258,7 @@ var $select2MatcherAll = function (params, data) {
data2.children = ch
return data2
} else {
if (_matcher(data, params.term)) {
if (_FN(data, params.term, data.element)) {
return data
}
}
@ -1307,21 +1311,25 @@ var $pages = function (tp, cp) {
// 格式化代码
var $formattedCode = function (c, type) {
if (typeof c === 'object') c = JSON.stringify(c)
if (!window.prettier) return c
try {
// eslint-disable-next-line no-undef
return prettier.format(c, {
parser: type || 'json',
// eslint-disable-next-line no-undef
plugins: prettierPlugins,
printWidth: 10,
})
} catch (err) {
console.log('Cannot format code :', err)
return c
// v4.2
if (type === 'json') {
return JSON.stringify(typeof c === 'object' ? c : JSON.parse(c), null, 2)
}
if (typeof c === 'object') c = JSON.stringify(c)
if (window.prettier) {
try {
return window.prettier.format(c, {
parser: type || 'json',
plugins: window.prettierPlugins,
printWidth: 10,
})
} catch (err) {
console.log('Cannot format code :', err)
return c
}
}
return c
}
// 复制

View file

@ -18,6 +18,7 @@ $(document).ready(function () {
const render_unset = function (data, target) {
const $item = $(`<li class="dd-item" data-key="${data[0]}"><div class="dd-handle"><span>${data[1]}</span></div></li>`).appendTo(target || '.unset-list')
if (data[2]) $item.attr('data-pinyin', data[2])
$item.on('click', function () {
render_item(data)
$item.remove()

View file

@ -61,7 +61,7 @@
<div class="card card-table card-topcolor">
<div class="card-body">
<div class="dataTables_wrapper container-fluid">
<div class="row rb-datatable-header">
<div class="row rb-datatable-header v42">
<div class="col-12 col-md-6 pr-0">
<div class="dataTables_filter">
<div class="adv-search float-left">
@ -87,7 +87,7 @@
<span id="dropdown-menu-advfilter"></span>
</div>
</div>
<div class="col-12 col-md-6 pl-0">
<div class="col-12 col-md-6 pl-2 right">
<div class="dataTables_oper invisible2">
<button class="btn btn-space btn-secondary J_view" type="button" disabled="disabled"><i class="icon mdi mdi-folder-open"></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>
@ -133,6 +133,7 @@
}
</script>
<script th:src="@{/assets/lib/charts/echarts.min.js?v=5.5.0}"></script>
<script th:src="@{/assets/lib/charts/tablecellsselection.js}"></script>
<script th:src="@{/assets/js/charts/charts.js}" type="text/babel"></script>
<script th:src="@{/assets/js/metadata/field-compatible.js}" type="text/babel"></script>
<script th:src="@{/assets/js/metadata/field-valueset.js}" type="text/babel"></script>

View file

@ -9,7 +9,6 @@
position: relative;
display: block;
z-index: 1;
border-radius: 4px;
margin-top: 44px;
border-top: 2px solid var(--rb-theme-color);
}

View file

@ -8,7 +8,6 @@
.iframe-wrap {
margin-top: 44px;
overflow: hidden;
border-radius: 4px;
}
.iframe-wrap iframe {
position: relative;

View file

@ -23,7 +23,7 @@
.sortable-box-title a.search-btn {
position: absolute;
font-size: 1.231rem;
margin-top: -2px;
margin-top: -1px;
margin-left: 5px;
}
.sortable-box-title a.search-btn:hover {

View file

@ -61,7 +61,7 @@
<div class="card card-table card-topcolor">
<div class="card-body">
<div class="dataTables_wrapper container-fluid">
<div class="row rb-datatable-header">
<div class="row rb-datatable-header v42">
<div class="col-12 col-md-6 pr-0">
<div class="dataTables_filter">
<div class="adv-search float-left">
@ -87,7 +87,7 @@
<span id="dropdown-menu-advfilter"></span>
</div>
</div>
<div class="col-12 col-md-6 pl-0">
<div class="col-12 col-md-6 pl-2 right">
<div class="dataTables_oper invisible2">
<button class="btn btn-space btn-secondary J_view" type="button" disabled="disabled"><i class="icon mdi mdi-folder-open"></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>
@ -144,6 +144,7 @@
}
</script>
<script th:src="@{/assets/lib/charts/echarts.min.js?v=5.5.0}"></script>
<script th:src="@{/assets/lib/charts/tablecellsselection.js}"></script>
<script th:src="@{/assets/js/charts/charts.js}" type="text/babel"></script>
<script th:src="@{/assets/js/metadata/field-compatible.js}" type="text/babel"></script>
<script th:src="@{/assets/js/metadata/field-valueset.js}" type="text/babel"></script>