mirror of
https://github.com/getrebuild/rebuild.git
synced 2025-09-07 07:09:57 +08:00
Fix 4.1 beta5 (#931)
* Delete old file after report update * Refactor form element entity assignment logic * style * pwa * lang * themeColor * Update privilege checks in TransformManager * Update @rbv * fix: readonly state * beta5 * be * Update Entity2Schema.java * be: file access * Update AdvFilterParser.java * fix:添加明细 * Enhance reference data filter to support view data * Update chart-design.js * Refactor multi-record report generation to use ReportsFile * Update EasyExcelGenerator33.java * Refactor ReportsFile and update report generation logic * Update @rbv
This commit is contained in:
parent
ccf11033e5
commit
be10888e5c
40 changed files with 560 additions and 238 deletions
2
@rbv
2
@rbv
|
@ -1 +1 @@
|
|||
Subproject commit f5eb69d2eb613d5fc8bb353996aa795b9905fb8a
|
||||
Subproject commit 86858da73f2a983ff14bd02af205256d4dbd366d
|
2
pom.xml
2
pom.xml
|
@ -10,7 +10,7 @@
|
|||
</parent>
|
||||
<groupId>com.rebuild</groupId>
|
||||
<artifactId>rebuild</artifactId>
|
||||
<version>4.1.0-beta4</version>
|
||||
<version>4.1.0-beta5</version>
|
||||
<name>rebuild</name>
|
||||
<description>Building your business-systems freely!</description>
|
||||
<url>https://getrebuild.com/</url>
|
||||
|
|
|
@ -76,11 +76,11 @@ public class Application implements ApplicationListener<ApplicationStartedEvent>
|
|||
/**
|
||||
* Rebuild Version
|
||||
*/
|
||||
public static final String VER = "4.1.0-beta4";
|
||||
public static final String VER = "4.1.0-beta5";
|
||||
/**
|
||||
* Rebuild Build [MAJOR]{1}[MINOR]{2}[PATCH]{2}[BUILD]{2}
|
||||
*/
|
||||
public static final int BUILD = 4010004;
|
||||
public static final int BUILD = 4010005;
|
||||
|
||||
static {
|
||||
// Driver for DB
|
||||
|
|
|
@ -395,19 +395,19 @@ public class FormsBuilder extends FormsManager {
|
|||
|
||||
// Check and clean
|
||||
for (Iterator<Object> iter = elements.iterator(); iter.hasNext(); ) {
|
||||
JSONObject el = (JSONObject) iter.next();
|
||||
String fieldName = el.getString("field");
|
||||
JSONObject field = (JSONObject) iter.next();
|
||||
String fieldName = field.getString("field");
|
||||
if (DIVIDER_LINE.equalsIgnoreCase(fieldName)) continue;
|
||||
if (REFFORM_LINE.equalsIgnoreCase(fieldName)) {
|
||||
// v3.6
|
||||
if (viewModel && recordData != null) {
|
||||
String reffield = el.getString("reffield");
|
||||
String reffield = field.getString("reffield");
|
||||
Object v = recordData.getObjectValue(reffield);
|
||||
if (v == null && entity.containsField(reffield)) {
|
||||
v = Application.getQueryFactory().unique(recordData.getPrimary(), reffield)[0];
|
||||
}
|
||||
if (v != null) {
|
||||
el.put("refvalue", new Object[]{ v, entity.getField(reffield).getReferenceEntity().getName() });
|
||||
field.put("refvalue", new Object[]{ v, entity.getField(reffield).getReferenceEntity().getName() });
|
||||
}
|
||||
}
|
||||
continue;
|
||||
|
@ -422,18 +422,18 @@ public class FormsBuilder extends FormsManager {
|
|||
// v2.2 高级控制
|
||||
// v3.8.4 视图下也有效(单字段编辑也算编辑)
|
||||
if (useAdvControl) {
|
||||
Object hiddenOnCreate = el.remove("hiddenOnCreate");
|
||||
Object hiddenOnUpdate = el.remove("hiddenOnUpdate");
|
||||
Object hiddenOnCreate = field.remove("hiddenOnCreate");
|
||||
Object hiddenOnUpdate = field.remove("hiddenOnUpdate");
|
||||
if (hiddenOnCreate == null) {
|
||||
Object displayOnCreate39 = el.remove("displayOnCreate");
|
||||
Object displayOnUpdate39 = el.remove("displayOnUpdate");
|
||||
Object displayOnCreate39 = field.remove("displayOnCreate");
|
||||
Object displayOnUpdate39 = field.remove("displayOnUpdate");
|
||||
if (displayOnCreate39 != null && !(Boolean) displayOnCreate39) hiddenOnCreate = true;
|
||||
if (displayOnUpdate39 != null && !(Boolean) displayOnUpdate39) hiddenOnUpdate = true;
|
||||
}
|
||||
final Object requiredOnCreate = el.remove("requiredOnCreate");
|
||||
final Object requiredOnUpdate = el.remove("requiredOnUpdate");
|
||||
final Object readonlyOnCreate = el.remove("readonlyOnCreate");
|
||||
final Object readonlyOnUpdate = el.remove("readonlyOnUpdate");
|
||||
final Object requiredOnCreate = field.remove("requiredOnCreate");
|
||||
final Object requiredOnUpdate = field.remove("requiredOnUpdate");
|
||||
final Object readonlyOnCreate = field.remove("readonlyOnCreate");
|
||||
final Object readonlyOnUpdate = field.remove("readonlyOnUpdate");
|
||||
// fix v3.3.4 跟随主记录新建/更新
|
||||
boolean isNewState = isNew;
|
||||
if (entity.getMainEntity() != null) {
|
||||
|
@ -457,108 +457,109 @@ public class FormsBuilder extends FormsManager {
|
|||
}
|
||||
// 必填
|
||||
if (requiredOnCreate != null && (Boolean) requiredOnCreate && isNewState) {
|
||||
el.put("nullable", false);
|
||||
field.put("nullable", false);
|
||||
}
|
||||
if (requiredOnUpdate != null && (Boolean) requiredOnUpdate && !isNewState) {
|
||||
el.put("nullable", false);
|
||||
field.put("nullable", false);
|
||||
}
|
||||
// 只读 v3.6
|
||||
if (readonlyOnCreate != null && (Boolean) readonlyOnCreate && isNewState) {
|
||||
el.put("readonly", true);
|
||||
field.put("readonly", true);
|
||||
}
|
||||
if (readonlyOnUpdate != null && (Boolean) readonlyOnUpdate && !isNewState) {
|
||||
el.put("readonly", true);
|
||||
field.put("readonly", true);
|
||||
}
|
||||
}
|
||||
|
||||
// 自动只读的
|
||||
final boolean roViaAuto = el.getBooleanValue("readonly");
|
||||
final boolean roViaAuto = field.getBooleanValue("readonly");
|
||||
|
||||
final Field fieldMeta = entity.getField(fieldName);
|
||||
final EasyField easyField = EasyMetaFactory.valueOf(fieldMeta);
|
||||
final DisplayType dt = easyField.getDisplayType();
|
||||
el.put("label", easyField.getLabel());
|
||||
el.put("type", dt.name());
|
||||
el.put("readonly", (!isNew && !fieldMeta.isUpdatable()) || roViaAuto);
|
||||
field.put("label", easyField.getLabel());
|
||||
field.put("type", dt.name());
|
||||
field.put("readonly", (!isNew && !fieldMeta.isUpdatable()) || roViaAuto);
|
||||
field.put("entity", entity.getName());
|
||||
|
||||
// 优先使用指定值
|
||||
final Boolean nullable = el.getBoolean("nullable");
|
||||
final Boolean nullable = field.getBoolean("nullable");
|
||||
if (nullable != null) {
|
||||
el.put("nullable", nullable);
|
||||
field.put("nullable", nullable);
|
||||
} else {
|
||||
el.put("nullable", fieldMeta.isNullable());
|
||||
field.put("nullable", fieldMeta.isNullable());
|
||||
}
|
||||
|
||||
// 字段扩展配置 FieldExtConfigProps
|
||||
JSONObject fieldExtAttrs = easyField.getExtraAttrs(true);
|
||||
el.putAll(fieldExtAttrs);
|
||||
field.putAll(fieldExtAttrs);
|
||||
|
||||
// 不同字段类型的处理
|
||||
|
||||
if (dt == DisplayType.PICKLIST) {
|
||||
JSONArray options = PickListManager.instance.getPickList(fieldMeta);
|
||||
el.put("options", options);
|
||||
field.put("options", options);
|
||||
} else if (dt == DisplayType.STATE) {
|
||||
JSONArray options = StateManager.instance.getStateOptions(fieldMeta);
|
||||
el.put("options", options);
|
||||
el.remove(EasyFieldConfigProps.STATE_CLASS);
|
||||
field.put("options", options);
|
||||
field.remove(EasyFieldConfigProps.STATE_CLASS);
|
||||
} else if (dt == DisplayType.MULTISELECT) {
|
||||
JSONArray options = MultiSelectManager.instance.getSelectList(fieldMeta);
|
||||
el.put("options", options);
|
||||
field.put("options", options);
|
||||
} else if (dt == DisplayType.TAG) {
|
||||
el.put("options", ObjectUtils.defaultIfNull(el.remove("tagList"), JSONUtils.EMPTY_ARRAY));
|
||||
field.put("options", ObjectUtils.defaultIfNull(field.remove("tagList"), JSONUtils.EMPTY_ARRAY));
|
||||
} else if (dt == DisplayType.DATETIME) {
|
||||
String format = StringUtils.defaultIfBlank(
|
||||
easyField.getExtraAttr(EasyFieldConfigProps.DATETIME_FORMAT), dt.getDefaultFormat());
|
||||
el.put(EasyFieldConfigProps.DATETIME_FORMAT, format);
|
||||
field.put(EasyFieldConfigProps.DATETIME_FORMAT, format);
|
||||
} else if (dt == DisplayType.DATE) {
|
||||
String format = StringUtils.defaultIfBlank(
|
||||
easyField.getExtraAttr(EasyFieldConfigProps.DATE_FORMAT), dt.getDefaultFormat());
|
||||
el.put(EasyFieldConfigProps.DATE_FORMAT, format);
|
||||
field.put(EasyFieldConfigProps.DATE_FORMAT, format);
|
||||
} else if (dt == DisplayType.TIME) {
|
||||
String format = StringUtils.defaultIfBlank(
|
||||
easyField.getExtraAttr(EasyFieldConfigProps.TIME_FORMAT), dt.getDefaultFormat());
|
||||
el.put(EasyFieldConfigProps.TIME_FORMAT, format);
|
||||
field.put(EasyFieldConfigProps.TIME_FORMAT, format);
|
||||
} else if (dt == DisplayType.CLASSIFICATION) {
|
||||
el.put("openLevel", ClassificationManager.instance.getOpenLevel(fieldMeta));
|
||||
field.put("openLevel", ClassificationManager.instance.getOpenLevel(fieldMeta));
|
||||
} else if (dt == DisplayType.REFERENCE || dt == DisplayType.N2NREFERENCE) {
|
||||
Entity refEntity = fieldMeta.getReferenceEntity();
|
||||
boolean quickNew = el.getBooleanValue(EasyFieldConfigProps.REFERENCE_QUICKNEW);
|
||||
boolean quickNew = field.getBooleanValue(EasyFieldConfigProps.REFERENCE_QUICKNEW);
|
||||
if (quickNew) {
|
||||
el.put(EasyFieldConfigProps.REFERENCE_QUICKNEW,
|
||||
field.put(EasyFieldConfigProps.REFERENCE_QUICKNEW,
|
||||
Application.getPrivilegesManager().allowCreate(user, refEntity.getEntityCode()));
|
||||
el.put("referenceEntity", EasyMetaFactory.toJSON(refEntity));
|
||||
field.put("referenceEntity", EasyMetaFactory.toJSON(refEntity));
|
||||
}
|
||||
|
||||
if (dt == DisplayType.REFERENCE && License.isRbvAttached()) {
|
||||
el.put("fillinWithFormData", true);
|
||||
field.put("fillinWithFormData", true);
|
||||
}
|
||||
}
|
||||
|
||||
// 新建记录
|
||||
if (isNew) {
|
||||
if (!fieldMeta.isCreatable()) {
|
||||
el.put("readonly", true);
|
||||
field.put("readonly", true);
|
||||
switch (fieldName) {
|
||||
case EntityHelper.CreatedOn:
|
||||
case EntityHelper.ModifiedOn:
|
||||
el.put("value", CalendarUtils.getUTCDateTimeFormat().format(now));
|
||||
field.put("value", CalendarUtils.getUTCDateTimeFormat().format(now));
|
||||
break;
|
||||
case EntityHelper.CreatedBy:
|
||||
case EntityHelper.ModifiedBy:
|
||||
case EntityHelper.OwningUser:
|
||||
el.put("value", FieldValueHelper.wrapMixValue(formUser.getId(), formUser.getFullName()));
|
||||
field.put("value", FieldValueHelper.wrapMixValue(formUser.getId(), formUser.getFullName()));
|
||||
break;
|
||||
case EntityHelper.OwningDept:
|
||||
Department dept = formUser.getOwningDept();
|
||||
Assert.notNull(dept, "Department of user is unset : " + formUser.getId());
|
||||
el.put("value", FieldValueHelper.wrapMixValue((ID) dept.getIdentity(), dept.getName()));
|
||||
field.put("value", FieldValueHelper.wrapMixValue((ID) dept.getIdentity(), dept.getName()));
|
||||
break;
|
||||
case EntityHelper.ApprovalId:
|
||||
el.put("value", FieldValueHelper.wrapMixValue(null, Language.L("未提交")));
|
||||
field.put("value", FieldValueHelper.wrapMixValue(null, Language.L("未提交")));
|
||||
break;
|
||||
case EntityHelper.ApprovalState:
|
||||
el.put("value", ApprovalState.DRAFT.getState());
|
||||
field.put("value", ApprovalState.DRAFT.getState());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -566,12 +567,12 @@ public class FormsBuilder extends FormsManager {
|
|||
}
|
||||
|
||||
// 默认值
|
||||
if (el.get("value") == null) {
|
||||
if (field.get("value") == null) {
|
||||
if (dt == DisplayType.SERIES
|
||||
|| EntityHelper.ApprovalLastTime.equals(fieldName) || EntityHelper.ApprovalLastRemark.equals(fieldName)
|
||||
|| EntityHelper.ApprovalLastUser.equals(fieldName) || EntityHelper.ApprovalStepUsers.equals(fieldName)
|
||||
|| EntityHelper.ApprovalStepNodeName.equals(fieldName)) {
|
||||
el.put("readonlyw", READONLYW_RO);
|
||||
field.put("readonlyw", READONLYW_RO);
|
||||
} else {
|
||||
Object defaultValue = easyField.exprDefaultValue();
|
||||
if (defaultValue != null) {
|
||||
|
@ -580,13 +581,13 @@ public class FormsBuilder extends FormsManager {
|
|||
if (dt == DisplayType.DECIMAL || dt == DisplayType.NUMBER) {
|
||||
defaultValue = EasyDecimal.clearFlaged(defaultValue);
|
||||
}
|
||||
el.put("value", defaultValue);
|
||||
field.put("value", defaultValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 自动值
|
||||
if (roViaAuto && el.get("value") == null) {
|
||||
if (roViaAuto && field.get("value") == null) {
|
||||
if (dt == DisplayType.EMAIL
|
||||
|| dt == DisplayType.PHONE
|
||||
|| dt == DisplayType.URL
|
||||
|
@ -597,8 +598,8 @@ public class FormsBuilder extends FormsManager {
|
|||
|| dt == DisplayType.SERIES
|
||||
|| dt == DisplayType.TEXT
|
||||
|| dt == DisplayType.NTEXT) {
|
||||
Integer s = el.getInteger("readonlyw");
|
||||
if (s == null) el.put("readonlyw", READONLYW_RO);
|
||||
Integer s = field.getInteger("readonlyw");
|
||||
if (s == null) field.put("readonlyw", READONLYW_RO);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -608,7 +609,7 @@ public class FormsBuilder extends FormsManager {
|
|||
ID parentValue = EntityHelper.isUnsavedId(mainid) ? null
|
||||
: getCascadingFieldParentValue(easyField, mainid, true);
|
||||
if (parentValue != null) {
|
||||
el.put("_cascadingFieldParentValue", parentValue);
|
||||
field.put("_cascadingFieldParentValue", parentValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -621,40 +622,40 @@ public class FormsBuilder extends FormsManager {
|
|||
if (!viewModel && (dt == DisplayType.DECIMAL || dt == DisplayType.NUMBER)) {
|
||||
value = EasyDecimal.clearFlaged(value);
|
||||
}
|
||||
el.put("value", value);
|
||||
field.put("value", value);
|
||||
}
|
||||
|
||||
// 父级级联
|
||||
if ((dt == DisplayType.REFERENCE || dt == DisplayType.N2NREFERENCE) && recordData.getPrimary() != null) {
|
||||
ID parentValue = getCascadingFieldParentValue(easyField, recordData.getPrimary(), false);
|
||||
if (parentValue != null) {
|
||||
el.put("_cascadingFieldParentValue", parentValue);
|
||||
field.put("_cascadingFieldParentValue", parentValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean
|
||||
el.remove(EasyFieldConfigProps.ADV_PATTERN);
|
||||
el.remove(EasyFieldConfigProps.ADV_DESENSITIZED);
|
||||
el.remove("barcodeFormat");
|
||||
el.remove("seriesFormat");
|
||||
field.remove(EasyFieldConfigProps.ADV_PATTERN);
|
||||
field.remove(EasyFieldConfigProps.ADV_DESENSITIZED);
|
||||
field.remove("barcodeFormat");
|
||||
field.remove("seriesFormat");
|
||||
|
||||
String decimalType = el.getString("decimalType");
|
||||
String decimalType = field.getString("decimalType");
|
||||
if (decimalType != null && decimalType.contains("%s")) {
|
||||
el.put("decimalType", decimalType.replace("%s", ""));
|
||||
field.put("decimalType", decimalType.replace("%s", ""));
|
||||
}
|
||||
|
||||
// v3.8 字段权限
|
||||
if (isNew) {
|
||||
if (!fp.isCreatable(fieldMeta, user)) el.put("readonly", true);
|
||||
if (!fp.isCreatable(fieldMeta, user)) field.put("readonly", true);
|
||||
} else {
|
||||
// v4.0 保留占位
|
||||
if (!fp.isReadable(fieldMeta, user)) {
|
||||
el.put("unreadable", true);
|
||||
el.put("readonly", true);
|
||||
el.remove("value");
|
||||
field.put("unreadable", true);
|
||||
field.put("readonly", true);
|
||||
field.remove("value");
|
||||
}
|
||||
else if (!fp.isUpdatable(fieldMeta, user)) el.put("readonly", true);
|
||||
else if (!fp.isUpdatable(fieldMeta, user)) field.put("readonly", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,8 @@ public class TransformManager implements ConfigManager {
|
|||
// 任何修改都会清空
|
||||
private static final Map<Object, Object> WEAK_CACHED = new ConcurrentHashMap<>();
|
||||
|
||||
private TransformManager() { }
|
||||
private TransformManager() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 前端使用
|
||||
|
@ -65,12 +66,15 @@ public class TransformManager implements ConfigManager {
|
|||
String target = cb.getString("target");
|
||||
Entity targetEntity = MetadataHelper.getEntity(target);
|
||||
|
||||
// 普通或主实体
|
||||
if (targetEntity.getMainEntity() == null) {
|
||||
if (!Application.getPrivilegesManager().allowCreate(user, targetEntity.getEntityCode())) {
|
||||
// 允许创建或更新
|
||||
if (!Application.getPrivilegesManager().allowCreate(user, targetEntity.getEntityCode())
|
||||
&& !Application.getPrivilegesManager().allowUpdate(user, targetEntity.getEntityCode())) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// To 明细
|
||||
// 明细实体-允许更新主记录
|
||||
if (!Application.getPrivilegesManager().allowUpdate(user, targetEntity.getMainEntity().getEntityCode())) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ public class Entity2Schema extends Field2Schema {
|
|||
* @return Returns 实体名称
|
||||
*/
|
||||
public String createEntity(String entityName, String entityLabel, String comments, String mainEntity, boolean haveNameField, boolean haveSeriesField) {
|
||||
if (!License.isRbvAttached() && MetadataHelper.getEntities().length >= 120) {
|
||||
if (!License.isRbvAttached() && MetadataHelper.getEntities().length >= 150) {
|
||||
throw new NeedRbvException(Language.L("实体数量超出免费版限制"));
|
||||
}
|
||||
|
||||
|
|
|
@ -93,7 +93,7 @@ public class EasyExcelGenerator extends SetUser {
|
|||
protected Integer writeSheetAt = null;
|
||||
protected ID recordId;
|
||||
@Setter
|
||||
private ID reportId;
|
||||
protected ID reportId;
|
||||
|
||||
protected int phNumber = 1;
|
||||
protected Map<String, Object> phValues = new HashMap<>();
|
||||
|
|
|
@ -67,6 +67,9 @@ public class EasyExcelGenerator33 extends EasyExcelGenerator {
|
|||
|
||||
// 支持多记录导出,会合并到一个 Excel 文件
|
||||
final private List<ID> recordIdMulti;
|
||||
// 默认合并到一个文件,也可以打包成一个 zip
|
||||
@Setter
|
||||
private boolean recordIdMultiMerge2Sheets = true;
|
||||
|
||||
private Set<String> inShapeVars;
|
||||
private Map<String, Object> recordMainHolder;
|
||||
|
@ -284,8 +287,28 @@ public class EasyExcelGenerator33 extends EasyExcelGenerator {
|
|||
public File generate() {
|
||||
if (recordIdMulti == null) return superGenerate();
|
||||
|
||||
// init
|
||||
File targetFile = super.getTargetFile();
|
||||
|
||||
// v4.1-b5
|
||||
if (!recordIdMultiMerge2Sheets) {
|
||||
ReportsFile reportsFile = new ReportsFile();
|
||||
|
||||
for (ID recordId : this.recordIdMulti) {
|
||||
this.recordId = recordId;
|
||||
this.phNumber = 1;
|
||||
this.phValues.clear();
|
||||
|
||||
String reportName = DataReportManager.getPrettyReportName(reportId, recordId, templateFile.getName());
|
||||
try {
|
||||
reportsFile.addFile(superGenerate(), reportName);
|
||||
} catch (IOException e) {
|
||||
throw new ReportsException(e);
|
||||
}
|
||||
}
|
||||
return reportsFile;
|
||||
}
|
||||
|
||||
// init
|
||||
try {
|
||||
FileUtils.copyFile(templateFile, targetFile);
|
||||
} catch (IOException e) {
|
||||
|
@ -299,7 +322,7 @@ public class EasyExcelGenerator33 extends EasyExcelGenerator {
|
|||
// 1.复制模板
|
||||
Sheet newSheet = wb.cloneSheet(0);
|
||||
newSheetAt = wb.getSheetIndex(newSheet);
|
||||
String newSheetName = "A" + newSheetAt;
|
||||
String newSheetName = "" + newSheetAt;
|
||||
try {
|
||||
wb.setSheetName(newSheetAt, newSheetName);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
/*!
|
||||
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.datareport;
|
||||
|
||||
import com.rebuild.core.support.RebuildConfiguration;
|
||||
import com.rebuild.utils.CompressUtils;
|
||||
import com.rebuild.utils.PdfConverter;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 导出报表打包
|
||||
*
|
||||
* @author devezhao
|
||||
* @since 2025/7/19
|
||||
*/
|
||||
public class ReportsFile extends File {
|
||||
private static final long serialVersionUID = -8876458376733911086L;
|
||||
|
||||
private List<File> files = new ArrayList<>();
|
||||
|
||||
public ReportsFile(File parent, String fileName) {
|
||||
super(ObjectUtils.defaultIfNull(parent, RebuildConfiguration.getFileOfTemp("/")),
|
||||
StringUtils.defaultIfBlank(fileName, "RBREPORT-" + System.currentTimeMillis()));
|
||||
}
|
||||
|
||||
public ReportsFile() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public ReportsFile addFile(File file, String reportName) throws IOException {
|
||||
if (!this.exists()) FileUtils.forceMkdir(this);
|
||||
|
||||
if (reportName == null) reportName = file.getName();
|
||||
String fileName = (files.size() + 1) + "-" + reportName;
|
||||
|
||||
File dest = new File(this, fileName);
|
||||
FileUtils.moveFile(file, dest);
|
||||
files.add(dest);
|
||||
return this;
|
||||
}
|
||||
|
||||
public File[] getFiles() {
|
||||
return files.toArray(new File[0]);
|
||||
}
|
||||
|
||||
public File toZip(boolean makePdf) throws IOException {
|
||||
return toZip(makePdf, false);
|
||||
}
|
||||
|
||||
public File toZip(boolean makePdf, boolean keepOrigin) throws IOException {
|
||||
FileFilter filter = null;
|
||||
if (makePdf) {
|
||||
for (File file : files) {
|
||||
File pdfFile = convertPdf(file);
|
||||
FileUtils.copyFile(pdfFile, new File(this, pdfFile.getName()));
|
||||
}
|
||||
|
||||
filter = file -> file.getName().endsWith(".pdf");
|
||||
if (!keepOrigin) filter = null;
|
||||
}
|
||||
|
||||
File zipFile = RebuildConfiguration.getFileOfTemp(this.getName() + ".zip");
|
||||
try {
|
||||
CompressUtils.forceZip(zipFile, this, filter);
|
||||
return zipFile;
|
||||
} catch (IOException ex) {
|
||||
throw new ReportsException("Cannot make zip for reports", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public File convertPdf(File file) {
|
||||
Path pdf = PdfConverter.convert(file.toPath(), PdfConverter.TYPE_PDF);
|
||||
return pdf.toFile();
|
||||
}
|
||||
}
|
|
@ -194,7 +194,7 @@ public class FilesHelper {
|
|||
* @return
|
||||
*/
|
||||
public static boolean isFileAccessable(ID user, ID fileId) {
|
||||
Object[] o = Application.getQueryFactory().uniqueNoFilter(fileId, "folderId");
|
||||
Object[] o = Application.getQueryFactory().uniqueNoFilter(fileId, "inFolder");
|
||||
if (o == null) return true;
|
||||
return getAccessableFolders(user).contains((ID) o[0]);
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@ import cn.devezhao.persist4j.query.compiler.QueryCompiler;
|
|||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
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.metadata.MetadataHelper;
|
||||
|
|
|
@ -261,7 +261,11 @@ public class RobotTriggerObserver extends OperatingObserver {
|
|||
if (ex instanceof DataValidateException) throw ex;
|
||||
// throw of Aviator 抛出
|
||||
//noinspection ConstantValue
|
||||
if (ex instanceof StandardError) throw new DataValidateException(ex.getLocalizedMessage());
|
||||
if (ex instanceof StandardError) {
|
||||
String exMsg = StringUtils.defaultIfBlank(ex.getLocalizedMessage(), ex.getMessage());
|
||||
if (StringUtils.isBlank(exMsg)) exMsg = Language.L("系统繁忙,请稍后重试") + " (StandardError)";
|
||||
throw new DataValidateException(exMsg);
|
||||
}
|
||||
}
|
||||
|
||||
log.error("Trigger execution failed : {} << {}", action, context, ex);
|
||||
|
|
|
@ -11,6 +11,7 @@ import cn.devezhao.commons.CalendarUtils;
|
|||
import cn.devezhao.commons.CodecUtils;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.qiniu.common.QiniuException;
|
||||
import com.qiniu.http.Client;
|
||||
|
@ -28,6 +29,7 @@ import com.rebuild.core.cache.CommonsCache;
|
|||
import com.rebuild.core.support.ConfigurationItem;
|
||||
import com.rebuild.core.support.RebuildConfiguration;
|
||||
import com.rebuild.utils.CommonsUtils;
|
||||
import com.rebuild.utils.JSONUtils;
|
||||
import com.rebuild.utils.OkHttpUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
@ -235,7 +237,8 @@ public class QiniuCloud {
|
|||
resp = bucketManager.delete(this.bucketName, key);
|
||||
if (resp.isOK()) return true;
|
||||
|
||||
throw new RebuildException("Failed to delete file : " + this.bucketName + " < " + key + " : " + resp.bodyString());
|
||||
log.warn("Cannot delete file : {} < {} : {}", this.bucketName, key, resp.bodyString());
|
||||
return false;
|
||||
} catch (QiniuException e) {
|
||||
throw new RebuildException("Failed to delete file : " + this.bucketName + " < " + key, e);
|
||||
}
|
||||
|
@ -497,4 +500,36 @@ public class QiniuCloud {
|
|||
}
|
||||
return fileKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
*
|
||||
* @param filesValue
|
||||
* @return
|
||||
*/
|
||||
public static int deleteFiles(String filesValue) {
|
||||
if (StringUtils.isBlank(filesValue)) return 0;
|
||||
|
||||
if (!JSONUtils.wellFormat(filesValue)) {
|
||||
if (filesValue.startsWith("rb/")) {
|
||||
filesValue = "[\"" + filesValue + "\"]";
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int del = 0;
|
||||
JSONArray fileKeys = JSON.parseArray(filesValue);
|
||||
for (Object fileKey : fileKeys) {
|
||||
if (QiniuCloud.instance().available()) {
|
||||
del += QiniuCloud.instance().delete(fileKey.toString()) ? 1 : 0;
|
||||
} else {
|
||||
File file = RebuildConfiguration.getFileOfData(fileKey.toString());
|
||||
if (file.exists()) {
|
||||
del += file.delete() ? 1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return del;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import com.rebuild.core.support.setup.InstallState;
|
|||
import com.rebuild.utils.AppUtils;
|
||||
import com.rebuild.utils.CommonsUtils;
|
||||
import com.rebuild.web.admin.ProtectedAdmin;
|
||||
import com.rebuild.web.user.signup.LoginController;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
@ -43,6 +44,8 @@ import javax.servlet.http.HttpServletRequest;
|
|||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
import static com.rebuild.web.commons.UseThemeController.THEMES_COLORS;
|
||||
|
||||
/**
|
||||
* 请求拦截
|
||||
* - 检查授权
|
||||
|
@ -94,6 +97,12 @@ public class RebuildWebInterceptor implements AsyncHandlerInterceptor, InstallSt
|
|||
// Lang
|
||||
request.setAttribute(WebConstants.LOCALE, requestEntry.getLocale());
|
||||
request.setAttribute(WebConstants.$BUNDLE, Application.getLanguage().getBundle(requestEntry.getLocale()));
|
||||
// v4.1 theme
|
||||
String theme = (String) ServletUtils.getSessionAttribute(request, LoginController.SK_USER_THEME);
|
||||
if (theme != null) {
|
||||
theme = THEMES_COLORS.get(theme);
|
||||
if (theme != null) request.setAttribute(WebConstants.THEME_COLOR, theme);
|
||||
}
|
||||
|
||||
final String requestUri = requestEntry.getRequestUri();
|
||||
|
||||
|
|
|
@ -67,6 +67,11 @@ public class WebConstants {
|
|||
*/
|
||||
public static final String LOCALE = "locale";
|
||||
|
||||
/**
|
||||
* v4.1 主题色
|
||||
*/
|
||||
public static final String THEME_COLOR = "themeColor";
|
||||
|
||||
/**
|
||||
* CSRF-Token
|
||||
* @see com.rebuild.api.user.AuthTokenManager#TYPE_CSRF_TOKEN
|
||||
|
|
|
@ -85,14 +85,14 @@ public class ProtectedAdmin {
|
|||
public enum PaEntry {
|
||||
SYS("/systems", "通用配置"),
|
||||
SSI("/integration/", "服务集成"),
|
||||
API("/apis-manager", "API 秘钥"),
|
||||
API("/apis-manager", "OpenAPI 秘钥"),
|
||||
AIB("/integration/aibot", "AI 助手"),
|
||||
ENT("/entities;/entity/;/metadata/", "实体管理"),
|
||||
APR("/robot/approval", "审批流程"),
|
||||
TRA("/robot/transform", "记录转换"),
|
||||
TRI("/robot/trigger", "触发器"),
|
||||
SOP("/robot/sop", "业务进度"),
|
||||
REP("/data/report-template", "报表设计"),
|
||||
REP("/data/report-template", "报表模版"),
|
||||
IMP("/data/data-imports", "数据导入"),
|
||||
EXF("/extform", "外部表单"),
|
||||
PRO("/project", "项目"),
|
||||
|
|
|
@ -35,6 +35,8 @@ import java.io.File;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author devezhao
|
||||
|
@ -49,6 +51,15 @@ public class UseThemeController extends BaseController {
|
|||
public static final String[] THEMES = {
|
||||
"default", "dark", "red", "green", "blue", "blue2", "purple"
|
||||
};
|
||||
public static final Map<String, String> THEMES_COLORS = new HashMap<>();
|
||||
static {
|
||||
THEMES_COLORS.put("dark", "#2d333b");
|
||||
THEMES_COLORS.put("red", "#f7615e");
|
||||
THEMES_COLORS.put("green", "#16a88f");
|
||||
THEMES_COLORS.put("blue", "#4873c0");
|
||||
THEMES_COLORS.put("blue2", "#38adff");
|
||||
THEMES_COLORS.put("purple", "#9b52de");
|
||||
}
|
||||
|
||||
@GetMapping("use-theme")
|
||||
public void useTheme(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
|
|
|
@ -25,6 +25,7 @@ import com.rebuild.core.service.files.FilesHelper;
|
|||
import com.rebuild.core.service.project.ProjectManager;
|
||||
import com.rebuild.core.support.i18n.I18nUtils;
|
||||
import com.rebuild.core.support.i18n.Language;
|
||||
import com.rebuild.core.support.integration.QiniuCloud;
|
||||
import com.rebuild.utils.CommonsUtils;
|
||||
import com.rebuild.utils.JSONUtils;
|
||||
import com.rebuild.web.BaseController;
|
||||
|
@ -65,7 +66,7 @@ public class FileListController extends BaseController {
|
|||
path = ServletUtils.readCookie(request, CK_LASTPATH);
|
||||
path = "attachment".equals(path) ? path : "docs";
|
||||
}
|
||||
|
||||
|
||||
// 记住最后一次访问的文件类型
|
||||
ServletUtils.addCookie(response, CK_LASTPATH, path);
|
||||
|
||||
|
@ -168,7 +169,7 @@ public class FileListController extends BaseController {
|
|||
sqlWhere.add(String.format("relatedRecord = '%s'", related));
|
||||
}
|
||||
|
||||
String sql = "select attachmentId,filePath,fileType,fileSize,createdBy,modifiedOn,inFolder,relatedRecord from Attachment where (1=1) and (isDeleted = ?)";
|
||||
String sql = "select attachmentId,filePath,fileType,fileSize,createdBy,modifiedOn,inFolder,relatedRecord,fileName from Attachment where (1=1) and (isDeleted = ?)";
|
||||
sql = sql.replace("(1=1)", StringUtils.join(sqlWhere.iterator(), " and "));
|
||||
if ("size".equals(sort)) {
|
||||
sql += " order by fileSize desc";
|
||||
|
@ -188,7 +189,9 @@ public class FileListController extends BaseController {
|
|||
for (Object[] o : array) {
|
||||
JSONObject item = new JSONObject();
|
||||
item.put("id", o[0]);
|
||||
item.put("filePath", o[1]);
|
||||
String fileName = (String) o[8];
|
||||
if (fileName == null) fileName = QiniuCloud.parseFileName((String) o[1]);
|
||||
item.put("fileName", fileName);
|
||||
item.put("fileType", o[2]);
|
||||
item.put("fileSize", FileUtils.byteCountToDisplaySize(ObjectUtils.toLong(o[3])));
|
||||
item.put("uploadBy", new Object[]{o[4], UserHelper.getName((ID) o[4])});
|
||||
|
|
|
@ -7,6 +7,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
|
||||
package com.rebuild.web.files;
|
||||
|
||||
import cn.devezhao.commons.CodecUtils;
|
||||
import cn.devezhao.commons.web.ServletUtils;
|
||||
import cn.devezhao.persist4j.Record;
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
|
@ -14,6 +15,7 @@ import com.alibaba.fastjson.JSONArray;
|
|||
import com.rebuild.api.RespBody;
|
||||
import com.rebuild.core.Application;
|
||||
import com.rebuild.core.metadata.EntityHelper;
|
||||
import com.rebuild.core.privileges.UserHelper;
|
||||
import com.rebuild.core.service.feeds.FeedsHelper;
|
||||
import com.rebuild.core.service.files.BatchDownload;
|
||||
import com.rebuild.core.service.files.FilesHelper;
|
||||
|
@ -76,7 +78,7 @@ public class FileManagerController extends BaseController {
|
|||
final ID user = getRequestUser(request);
|
||||
String[] files = getParameter(request, "ids", "").split(",");
|
||||
|
||||
Set<ID> willDeletes = new HashSet<>();
|
||||
Set<ID> willDeleteIds = new HashSet<>();
|
||||
for (String file : files) {
|
||||
if (!ID.isId(file)) continue;
|
||||
|
||||
|
@ -84,11 +86,10 @@ public class FileManagerController extends BaseController {
|
|||
if (!FilesHelper.isFileManageable(user, fileId)) {
|
||||
return RespBody.errorl("无权删除他人文件");
|
||||
}
|
||||
|
||||
willDeletes.add(fileId);
|
||||
willDeleteIds.add(fileId);
|
||||
}
|
||||
|
||||
Application.getCommonsService().delete(willDeletes.toArray(new ID[0]));
|
||||
Application.getCommonsService().delete(willDeleteIds.toArray(new ID[0]));
|
||||
return RespBody.ok();
|
||||
}
|
||||
|
||||
|
@ -108,11 +109,8 @@ public class FileManagerController extends BaseController {
|
|||
}
|
||||
|
||||
Record r = EntityHelper.forUpdate(fileId, user);
|
||||
if (inFolder == null) {
|
||||
r.setNull("inFolder");
|
||||
} else {
|
||||
r.setID("inFolder", inFolder);
|
||||
}
|
||||
if (inFolder == null) r.setNull("inFolder");
|
||||
else r.setID("inFolder", inFolder);
|
||||
fileRecords.add(r);
|
||||
}
|
||||
|
||||
|
@ -120,38 +118,48 @@ public class FileManagerController extends BaseController {
|
|||
return RespBody.ok();
|
||||
}
|
||||
|
||||
// TODO 更严格的文件访问权限检查
|
||||
@RequestMapping("check-readable")
|
||||
public RespBody checkReadable(@IdParam ID recordOrFileId, HttpServletRequest request) {
|
||||
final ID user = getRequestUser(request);
|
||||
final int entityCode = recordOrFileId.getEntityCode();
|
||||
public RespBody checkReadable(@IdParam ID fileId, HttpServletRequest request) {
|
||||
String filePath = checkFileReadable(fileId, getRequestUser(request));
|
||||
return filePath == null ? RespBody.error() : RespBody.ok(filePath);
|
||||
}
|
||||
|
||||
// 是否可读取文件
|
||||
static String checkFileReadable(ID fileId, ID user) {
|
||||
Object[] file = Application.getQueryFactory().uniqueNoFilter(fileId, "filePath,relatedRecord,belongEntity");
|
||||
if (file == null) return null;
|
||||
if (UserHelper.isAdmin(user)) return (String) file[0];
|
||||
|
||||
boolean readable;
|
||||
// 文件
|
||||
if (entityCode == EntityHelper.Attachment) {
|
||||
readable = FilesHelper.isFileAccessable(user, recordOrFileId);
|
||||
} else {
|
||||
// 附件
|
||||
if (entityCode == EntityHelper.Feeds || entityCode == EntityHelper.FeedsComment) {
|
||||
readable = FeedsHelper.checkReadable(recordOrFileId, user);
|
||||
} else if (entityCode == EntityHelper.ProjectTask || entityCode == EntityHelper.ProjectTaskComment) {
|
||||
readable = ProjectHelper.checkReadable(recordOrFileId, user);
|
||||
} else {
|
||||
readable = Application.getPrivilegesManager().allowRead(user, recordOrFileId);
|
||||
}
|
||||
if ((int) file[2] <= 0) {
|
||||
if (FilesHelper.isFileAccessable(user, fileId)) return (String) file[0];
|
||||
else return null;
|
||||
}
|
||||
|
||||
return RespBody.ok(readable);
|
||||
// 附件
|
||||
final ID recordId = (ID) file[1];
|
||||
if (recordId == null) return null;
|
||||
|
||||
int entityCode = recordId.getEntityCode();
|
||||
boolean readable;
|
||||
if (entityCode == EntityHelper.Feeds || entityCode == EntityHelper.FeedsComment) {
|
||||
readable = FeedsHelper.checkReadable(recordId, user);
|
||||
} else if (entityCode == EntityHelper.ProjectTask || entityCode == EntityHelper.ProjectTaskComment) {
|
||||
readable = ProjectHelper.checkReadable(recordId, user);
|
||||
} else {
|
||||
readable = Application.getPrivilegesManager().allowRead(user, recordId);
|
||||
}
|
||||
return readable ? (String) file[0] : null;
|
||||
}
|
||||
|
||||
@PostMapping("batch-download")
|
||||
public void batchDownload(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
public void downloadBatch(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
final String files = req.getParameter("files");
|
||||
|
||||
List<String> filePaths = new ArrayList<>();
|
||||
Collections.addAll(filePaths, files.split(","));
|
||||
List<String> filesList = new ArrayList<>();
|
||||
Collections.addAll(filesList, files.split(","));
|
||||
|
||||
BatchDownload bd = new BatchDownload(filePaths);
|
||||
BatchDownload bd = new BatchDownload(filesList);
|
||||
TaskExecutors.run(bd);
|
||||
|
||||
File zipName = bd.getDestZip();
|
||||
|
@ -162,6 +170,20 @@ public class FileManagerController extends BaseController {
|
|||
}
|
||||
}
|
||||
|
||||
@RequestMapping("download")
|
||||
public void download(@IdParam ID fileId, HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
String filePath = checkFileReadable(fileId, getRequestUser(req));
|
||||
if (filePath == null) {
|
||||
resp.sendError(HttpStatus.FORBIDDEN.value(), Language.L("你没有查看此文件的权限"));
|
||||
} else {
|
||||
String fileUrl = CodecUtils.urlEncode(filePath);
|
||||
fileUrl = fileUrl.replace("%2F", "/");
|
||||
fileUrl = String.format("../filex/download/%s?attname=%s",
|
||||
fileUrl, CodecUtils.urlEncode(QiniuCloud.parseFileName(filePath)));
|
||||
resp.sendRedirect(fileUrl);
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("file-edit")
|
||||
public RespBody fileEdit(HttpServletRequest req) throws IOException {
|
||||
final ID user = getRequestUser(req);
|
||||
|
|
|
@ -177,6 +177,12 @@ public class CommonOperatingController extends BaseController {
|
|||
return StringUtils.join(fs, ",");
|
||||
}
|
||||
|
||||
@RequestMapping("check-readable")
|
||||
public RespBody checkReadable(@IdParam ID recordId, HttpServletRequest request) {
|
||||
boolean r = Application.getPrivilegesManager().allowRead(getRequestUser(request), recordId);
|
||||
return RespBody.ok(r);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存记录
|
||||
*
|
||||
|
|
|
@ -28,6 +28,7 @@ import com.rebuild.core.service.dataimport.DataExporter;
|
|||
import com.rebuild.core.service.datareport.DataReportManager;
|
||||
import com.rebuild.core.service.datareport.EasyExcelGenerator;
|
||||
import com.rebuild.core.service.datareport.EasyExcelGenerator33;
|
||||
import com.rebuild.core.service.datareport.ReportsFile;
|
||||
import com.rebuild.core.service.datareport.TemplateFile;
|
||||
import com.rebuild.core.support.CommonsLog;
|
||||
import com.rebuild.core.support.KVStorage;
|
||||
|
@ -95,6 +96,10 @@ public class ReportsController extends BaseController {
|
|||
final ID recordId = recordIds[0];
|
||||
final TemplateFile tt = DataReportManager.instance.buildTemplateFile(reportId);
|
||||
|
||||
String typeOutput = getParameter(request, "output");
|
||||
boolean isHtml = "HTML".equalsIgnoreCase(typeOutput);
|
||||
boolean isPdf = "PDF".equalsIgnoreCase(typeOutput);
|
||||
|
||||
File output = null;
|
||||
try {
|
||||
EasyExcelGenerator reportGenerator;
|
||||
|
@ -109,20 +114,23 @@ public class ReportsController extends BaseController {
|
|||
reportGenerator = EasyExcelGenerator.create(reportId, Arrays.asList(recordIds));
|
||||
}
|
||||
|
||||
if (reportGenerator != null) {
|
||||
// vars in URL
|
||||
String vars = getParameter(request, "vars");
|
||||
if (JSONUtils.wellFormat(vars) && reportGenerator instanceof EasyExcelGenerator33) {
|
||||
JSONObject varsJson = JSON.parseObject(vars);
|
||||
if (varsJson != null) {
|
||||
((EasyExcelGenerator33) reportGenerator).setTempVars(varsJson.getInnerMap());
|
||||
}
|
||||
// vars in URL
|
||||
String vars = getParameter(request, "vars");
|
||||
if (JSONUtils.wellFormat(vars) && reportGenerator instanceof EasyExcelGenerator33) {
|
||||
JSONObject varsJson = JSON.parseObject(vars);
|
||||
if (varsJson != null) {
|
||||
((EasyExcelGenerator33) reportGenerator).setTempVars(varsJson.getInnerMap());
|
||||
}
|
||||
|
||||
reportGenerator.setReportId(reportId);
|
||||
output = reportGenerator.generate();
|
||||
}
|
||||
|
||||
// 4.1-b5 压缩包
|
||||
if (isPdf && recordIds.length > 1 && reportGenerator instanceof EasyExcelGenerator33) {
|
||||
((EasyExcelGenerator33) reportGenerator).setRecordIdMultiMerge2Sheets(false);
|
||||
}
|
||||
|
||||
reportGenerator.setReportId(reportId);
|
||||
output = reportGenerator.generate();
|
||||
|
||||
CommonsLog.createLog(CommonsLog.TYPE_REPORT,
|
||||
getRequestUser(request), reportId, StringUtils.join(recordIds, ";"));
|
||||
// PH__EXPORTTIMES
|
||||
|
@ -138,9 +146,10 @@ public class ReportsController extends BaseController {
|
|||
|
||||
RbAssert.is(output != null, Language.L("无法输出报表,请检查报表模板是否有误"));
|
||||
|
||||
String typeOutput = getParameter(request, "output");
|
||||
boolean isHtml = "HTML".equalsIgnoreCase(typeOutput);
|
||||
boolean isPdf = "PDF".equalsIgnoreCase(typeOutput);
|
||||
if (output instanceof ReportsFile) {
|
||||
output = ((ReportsFile) output).toZip(isPdf);
|
||||
}
|
||||
|
||||
// 请求预览
|
||||
boolean forcePreview = isHtml || getBoolParameter(request, "preview");
|
||||
String fileName = DataReportManager.getPrettyReportName(reportId, recordId, output.getName());
|
||||
|
@ -170,8 +179,7 @@ public class ReportsController extends BaseController {
|
|||
writeSuccess(response, data);
|
||||
|
||||
} else if ("preview".equalsIgnoreCase(typeOutput)) {
|
||||
String fileUrl = String.format(
|
||||
"/filex/download/%s?temp=yes&_onceToken=%s&attname=%s",
|
||||
String fileUrl = String.format("/filex/download/%s?temp=yes&_onceToken=%s&attname=%s",
|
||||
CodecUtils.urlEncode(output.getName()), AuthTokenManager.generateOnceToken(null), CodecUtils.urlEncode(fileName));
|
||||
fileUrl = RebuildConfiguration.getHomeUrl(fileUrl);
|
||||
|
||||
|
@ -187,10 +195,10 @@ public class ReportsController extends BaseController {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// 列表数据导出
|
||||
|
||||
@RequestMapping({ "export/submit", "report/export-list" })
|
||||
@RequestMapping({"export/submit", "report/export-list"})
|
||||
public RespBody export(@PathVariable String entity, HttpServletRequest request) {
|
||||
final ID user = getRequestUser(request);
|
||||
RbAssert.isAllow(
|
||||
|
@ -222,7 +230,7 @@ public class ReportsController extends BaseController {
|
|||
}
|
||||
|
||||
RbAssert.is(output != null, Language.L("无法输出报表,请检查报表模板是否有误"));
|
||||
|
||||
|
||||
String fileName;
|
||||
if (useReport == null) {
|
||||
fileName = String.format("%s-%s.%s",
|
||||
|
@ -237,7 +245,7 @@ public class ReportsController extends BaseController {
|
|||
String.format("%s:%d", entity, exporter.getExportCount()));
|
||||
|
||||
JSONObject data = JSONUtils.toJSONObject(
|
||||
new String[] { "fileKey", "fileName" }, new Object[] { output.getName(), fileName });
|
||||
new String[]{"fileKey", "fileName"}, new Object[]{output.getName(), fileName});
|
||||
if (AppUtils.isMobile(request)) putFileUrl(data);
|
||||
|
||||
return RespBody.ok(data);
|
||||
|
|
|
@ -3488,5 +3488,25 @@
|
|||
"AI 助手会话附加数据":"AI 助手会话附加数据",
|
||||
"AI 助手":"AI 助手",
|
||||
"直接转换":"直接转换",
|
||||
"(必填)":"(必填)"
|
||||
"(必填)":"(必填)",
|
||||
"隐藏明细":"隐藏明细",
|
||||
"修改文件":"修改文件",
|
||||
"指定明细实体布局":"指定明细实体布局",
|
||||
"由于%s,此触发器不会执行":"由于%s,此触发器不会执行",
|
||||
"无任何触发动作":"无任何触发动作",
|
||||
"提交后提示":"提交后提示",
|
||||
"清空配置":"清空配置",
|
||||
"无 (全部权限)":"无 (全部权限)",
|
||||
"无法修改外部文件":"无法修改外部文件",
|
||||
"在线编辑":"在线编辑",
|
||||
"显示明细":"显示明细",
|
||||
"放在桌面":"放在桌面",
|
||||
"API 密钥":"API 密钥",
|
||||
"脱敏读取字段":"脱敏读取字段",
|
||||
"前置校验模式":"前置校验模式",
|
||||
"无权编辑此记录":"无权编辑此记录",
|
||||
"确定要清空配置吗?":"确定要清空配置吗?",
|
||||
"启用列表页单字段编辑":"启用列表页单字段编辑",
|
||||
"无 (不限)":"无 (不限)",
|
||||
"引用记录不存在":"引用记录不存在"
|
||||
}
|
|
@ -336,7 +336,7 @@
|
|||
<index type="unique" field-list="appId"/>
|
||||
</entity>
|
||||
|
||||
<entity name="RebuildApiRequest" type-code="031" description="API 调用日志" queryable="false" parent="false">
|
||||
<entity name="RebuildApiRequest" type-code="031" description="OpenAPI 调用日志" queryable="false" parent="false">
|
||||
<field name="requestId" type="primary"/>
|
||||
<field name="appId" type="string" max-length="20" nullable="false" updatable="false" description="APPID"/>
|
||||
<field name="remoteIp" type="string" max-length="100" nullable="false" updatable="false" description="来源 IP"/>
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
<meta name="renderer" content="webkit" />
|
||||
<meta http-equiv="x-ua-compatible" content="IE=edge" />
|
||||
<link rel="manifest" th:href="@{/assets/manifest.json}" />
|
||||
<meta name="theme-color" th:content="${themeColor ?:'#4285f4'}" />
|
||||
<link rel="shortcut icon" th:href="@{/assets/img/favicon.png}" />
|
||||
<link rel="apple-touch-icon" th:href="@{/assets/img/favicon.png}" />
|
||||
<th:block th:if="${css != 'none'}">
|
||||
|
|
|
@ -342,6 +342,7 @@ form.field-attr label > span {
|
|||
font-size: 15px;
|
||||
color: #999;
|
||||
display: none;
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.table.table-p tr:hover a.easy-control {
|
||||
|
|
|
@ -137,7 +137,7 @@ div.dataTables_wrapper div.dataTables_oper.compact .btn-space {
|
|||
|
||||
.form-layout .type-NTEXT a.text-common {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
right: 10px;
|
||||
bottom: 5px;
|
||||
background-color: #eee;
|
||||
border: 0 none;
|
||||
|
|
|
@ -5038,7 +5038,7 @@ pre.unstyle {
|
|||
font-weight: normal;
|
||||
border: 0 none;
|
||||
background-color: rgb(245, 247, 249);
|
||||
max-width: 100%;
|
||||
max-width: 99%;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
|
|
|
@ -247,6 +247,7 @@ const CTs = {
|
|||
Y: $L('按年'),
|
||||
Q: $L('按季'),
|
||||
M: $L('按月'),
|
||||
W: $L('按周'),
|
||||
D: $L('按日'),
|
||||
H: $L('按时'),
|
||||
I: $L('按时分'),
|
||||
|
|
|
@ -451,14 +451,27 @@ class FileShare extends RbModalHandler {
|
|||
|
||||
componentDidMount() {
|
||||
$(this._dlg._rbmodal).css({ zIndex: 1099 })
|
||||
this._changeTime()
|
||||
|
||||
this._filePath = this.props.file
|
||||
if ($regex.isId(this.props.file)) {
|
||||
$.get(`/files/check-readable?id=${this.props.file}`, (res) => {
|
||||
if (res.data) {
|
||||
this._filePath = res.data
|
||||
this._changeTime()
|
||||
} else {
|
||||
RbHighbar.create($L('你没有查看此文件的权限'))
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this._changeTime()
|
||||
}
|
||||
}
|
||||
|
||||
_changeTime = (e) => {
|
||||
const t = e ? ~~e.target.dataset.time : EXPIRES_TIME[0][0]
|
||||
if (this.state.time === t) return
|
||||
this.setState({ time: t }, () => {
|
||||
$.get(`/filex/make-share?url=${$encode(this.props.file)}&time=${t}&shareUrl=${$encode(this.__shareUrl)}`, (res) => {
|
||||
$.get(`/filex/make-share?url=${$encode(this._filePath)}&time=${t}&shareUrl=${$encode(this.__shareUrl)}`, (res) => {
|
||||
this.__shareUrl = (res.data || {}).shareUrl
|
||||
this.setState({ shareUrl: this.__shareUrl })
|
||||
|
||||
|
|
|
@ -423,15 +423,13 @@ class FileEditDlg extends RbFormHandler {
|
|||
|
||||
render() {
|
||||
const file = this.props.file
|
||||
let fileName = file.filePath
|
||||
fileName = fileName ? $fileCutName(fileName) : null
|
||||
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>
|
||||
<div className="col-sm-7">
|
||||
<input className="form-control form-control-sm" defaultValue={fileName} ref={(c) => (this._$fileName = c)} />
|
||||
<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)
|
||||
|
@ -485,7 +483,7 @@ class FilesList4Docs extends FilesList {
|
|||
<a title={$L('修改')} onClick={(e) => this._handleEdit(item, e)}>
|
||||
<i className="icon zmdi zmdi-edit up-1" />
|
||||
</a>
|
||||
<a title={$L('下载')} onClick={(e) => $stopEvent(e)} href={`${rb.baseUrl}/filex/download/${item.filePath}?attname=${$encode(item.fileName)}`} target="_blank">
|
||||
<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" />
|
||||
</a>
|
||||
{rb.fileSharable && (
|
||||
|
@ -506,7 +504,7 @@ class FilesList4Docs extends FilesList {
|
|||
_handleShare(item, e) {
|
||||
$stopEvent(e)
|
||||
// eslint-disable-next-line react/jsx-no-undef
|
||||
renderRbcomp(<FileShare file={item.filePath} />)
|
||||
renderRbcomp(<FileShare file={item.id} />)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ class FilesList extends React.Component {
|
|||
{(this.state.files || []).map((item) => {
|
||||
const checked = currentActive.includes(item.id)
|
||||
return (
|
||||
<div key={`file-${item.id}`} className={`file-list-item ${checked ? 'active' : ''}`} onClick={(e) => this._handleClick(e, item.id)}>
|
||||
<div key={item.id} className={`file-list-item ${checked ? 'active' : ''}`} onClick={(e) => this._handleClick(e, item.id)}>
|
||||
<div className="check">
|
||||
<div className="custom-control custom-checkbox m-0">
|
||||
<input className="custom-control-input" type="checkbox" checked={checked === true} readOnly />
|
||||
|
@ -36,8 +36,15 @@ class FilesList extends React.Component {
|
|||
<i className="file-icon" data-type={item.fileType || '?'} />
|
||||
</div>
|
||||
<div className="detail">
|
||||
<a onClick={(e) => previewFile(e, item.filePath, item.relatedRecord ? item.relatedRecord[0] : null)} title={$L('预览')}>
|
||||
{$fileCutName(item.filePath)}
|
||||
<a
|
||||
onClick={() => {
|
||||
$.get(`/files/check-readable?id=${item.id}`, (res) => {
|
||||
if (res.data) RbPreview.create(res.data)
|
||||
else RbHighbar.create($L('你没有查看此文件的权限'))
|
||||
})
|
||||
}}
|
||||
title={$L('预览')}>
|
||||
{item.fileName}
|
||||
</a>
|
||||
<div className="extras">
|
||||
<span className="fsize">{item.fileSize}</span>
|
||||
|
@ -118,19 +125,6 @@ class FilesList extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
// 文件预览
|
||||
const previewFile = function (e, path, checkId) {
|
||||
$stopEvent(e)
|
||||
if (checkId) {
|
||||
$.get(`/files/check-readable?id=${checkId}`, (res) => {
|
||||
if (res.data) RbPreview.create(path)
|
||||
else RbHighbar.error($L('你没有查看此文件的权限'))
|
||||
})
|
||||
} else {
|
||||
RbPreview.create(path)
|
||||
}
|
||||
}
|
||||
|
||||
// ~~ 共享列表
|
||||
class SharedFiles extends RbModalHandler {
|
||||
render() {
|
||||
|
|
|
@ -1020,7 +1020,7 @@ class EditableFieldForms extends React.Component {
|
|||
item.isFull = true
|
||||
delete item.referenceQuickNew // v35
|
||||
// eslint-disable-next-line no-undef
|
||||
return detectElement(item, entity.entity)
|
||||
return detectElement(item)
|
||||
})}
|
||||
</LiteForm>
|
||||
)
|
||||
|
|
|
@ -130,9 +130,7 @@ class RbFormModal extends React.Component {
|
|||
readonly={!!formModel.readonlyMessage}
|
||||
ref={(c) => (that._formComponentRef = c)}
|
||||
_disableAutoFillin={that.props._disableAutoFillin}>
|
||||
{formModel.elements.map((item) => {
|
||||
return detectElement(item, entity)
|
||||
})}
|
||||
{formModel.elements.map((item) => detectElement(item))}
|
||||
</RbForm>
|
||||
)
|
||||
|
||||
|
@ -1185,7 +1183,7 @@ class RbFormElement extends React.Component {
|
|||
setReadonly(readonly) {
|
||||
this.setState({ readonly: readonly === true }, () => {
|
||||
// fix 4.0.6 只读变为非只读,富附件需初始化
|
||||
this.onEditModeChanged(readonly === true)
|
||||
this.onEditModeChanged(readonly === true, true)
|
||||
})
|
||||
}
|
||||
// TIP 仅表单有效
|
||||
|
@ -1243,6 +1241,12 @@ class RbFormText extends RbFormElement {
|
|||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
// fix:4.1-b5 禁用时不触发
|
||||
$(this._fieldValue).on('click', (e) => {
|
||||
const $t = e.target || {}
|
||||
if ($t.disabled || $t.readOnly) $stopEvent(e, true)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1431,7 +1435,7 @@ class RbFormNText extends RbFormElement {
|
|||
/>
|
||||
{props.useMdedit && !_readonly37 && <input type="file" className="hide" accept="image/*" data-noname="true" ref={(c) => (this._fieldValue__upload = c)} />}
|
||||
{this._textCommonMenuId && (
|
||||
<a className="badge text-common" data-toggle="dropdown" data-target={`#${this._textCommonMenuId}`}>
|
||||
<a className={`badge text-common ${_readonly37 && 'hide'}`} data-toggle="dropdown" data-target={`#${this._textCommonMenuId}`}>
|
||||
{$L('常用值')}
|
||||
</a>
|
||||
)}
|
||||
|
@ -1509,21 +1513,21 @@ class RbFormNText extends RbFormElement {
|
|||
<div id={this._textCommonMenuId}>
|
||||
<div className="dropdown-menu dropdown-menu-right common-texts">
|
||||
{this.props.textCommon.split(',').map((c) => {
|
||||
let cLN = c.replace(/\\n/g, '\n') // 换行符
|
||||
return (
|
||||
<a
|
||||
key={c}
|
||||
title={c}
|
||||
title={cLN}
|
||||
className="badge text-ellipsis"
|
||||
onClick={() => {
|
||||
c = c.replace(/\\n/g, '\n')
|
||||
if (this._EasyMDE) {
|
||||
this._mdeInsert(c)
|
||||
this._mdeInsert(cLN)
|
||||
} else {
|
||||
const ps = this._fieldValue.selectionStart,
|
||||
pe = this._fieldValue.selectionEnd
|
||||
let val = this.state.value
|
||||
if ($empty(val)) val = c
|
||||
else val = val.substring(0, ps) + c + val.substring(pe)
|
||||
if ($empty(val)) val = cLN
|
||||
else val = val.substring(0, ps) + cLN + val.substring(pe)
|
||||
this.handleChange({ target: { value: val } }, true)
|
||||
// $focus2End(this._fieldValue)
|
||||
}
|
||||
|
@ -1559,6 +1563,9 @@ class RbFormNText extends RbFormElement {
|
|||
_initMde() {
|
||||
const _readonly37 = this.state.readonly
|
||||
|
||||
// fix:4.1-b5
|
||||
this._EasyMDE && this._EasyMDE.toTextArea()
|
||||
|
||||
const mde = new EasyMDE({
|
||||
element: this._fieldValue,
|
||||
status: false,
|
||||
|
@ -2109,12 +2116,16 @@ class RbFormPickList extends RbFormElement {
|
|||
return super.renderViewElement(__findOptionText(this.state.options, this.state.value, true))
|
||||
}
|
||||
|
||||
onEditModeChanged(destroy) {
|
||||
onEditModeChanged(destroy, fromReadonly41) {
|
||||
if (destroy) {
|
||||
super.onEditModeChanged(destroy)
|
||||
if (fromReadonly41) {
|
||||
this.__select2 && $(this._fieldValue).attr('disabled', true)
|
||||
} else {
|
||||
super.onEditModeChanged(destroy)
|
||||
}
|
||||
} else {
|
||||
if (this._isShowRadio39) {
|
||||
// TODO
|
||||
// Nothings
|
||||
} else {
|
||||
this.__select2 = $(this._fieldValue).select2({
|
||||
placeholder: $L('选择%s', this.props.label),
|
||||
|
@ -2142,7 +2153,7 @@ class RbFormPickList extends RbFormElement {
|
|||
if (this._isShowRadio39) {
|
||||
this.handleChange({ target: { value: val } }, true)
|
||||
} else {
|
||||
this.__select2.val(val).trigger('change')
|
||||
this.__select2 && this.__select2.val(val).trigger('change')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2232,9 +2243,13 @@ class RbFormReference extends RbFormElement {
|
|||
return false
|
||||
}
|
||||
|
||||
onEditModeChanged(destroy) {
|
||||
onEditModeChanged(destroy, fromReadonly41) {
|
||||
if (destroy) {
|
||||
super.onEditModeChanged(destroy)
|
||||
if (fromReadonly41) {
|
||||
this.__select2 && $(this._fieldValue).attr('disabled', true)
|
||||
} else {
|
||||
super.onEditModeChanged(destroy)
|
||||
}
|
||||
} else {
|
||||
this.__select2 = $initReferenceSelect2(this._fieldValue, {
|
||||
name: this.props.field,
|
||||
|
@ -2242,18 +2257,22 @@ class RbFormReference extends RbFormElement {
|
|||
entity: this.props.entity,
|
||||
wrapQuery: (query) => {
|
||||
// v4.1 附加过滤条件支持从表单动态取值
|
||||
const varRecord = this.props.referenceDataFilter ? this.props.$$$parent.getFormData() : null
|
||||
if (varRecord) {
|
||||
// FIXME 太长的值过滤,以免 URL 超长
|
||||
for (let k in varRecord) {
|
||||
if (varRecord[k] && (varRecord[k] + '').length > 100) {
|
||||
delete varRecord[k]
|
||||
console.log('Ignore large value of field :', k, varRecord[k])
|
||||
const $$$parent = this.props.$$$parent
|
||||
if (this.props.referenceDataFilter && $$$parent) {
|
||||
let varRecord = $$$parent.getFormData ? $$$parent.getFormData() : $$$parent.__ViewData
|
||||
if (varRecord) {
|
||||
// FIXME 太长的值过滤,以免 URL 超长
|
||||
for (let k in varRecord) {
|
||||
if (varRecord[k] && (varRecord[k] + '').length > 100) {
|
||||
delete varRecord[k]
|
||||
console.log('Ignore large value of field :', k, varRecord[k])
|
||||
}
|
||||
}
|
||||
varRecord['metadata.entity'] = $$$parent.props.entity
|
||||
query.varRecord = $encode(JSON.stringify(varRecord))
|
||||
}
|
||||
varRecord['metadata.entity'] = this.props.$$$parent.props.entity
|
||||
query.varRecord = $encode(JSON.stringify(varRecord))
|
||||
}
|
||||
|
||||
const cascadingValue = this._getCascadingFieldValue()
|
||||
if (cascadingValue) query.cascadingValue = cascadingValue
|
||||
return query
|
||||
|
@ -2546,6 +2565,7 @@ class RbFormN2NReference extends RbFormReference {
|
|||
|
||||
onEditModeChanged(destroy) {
|
||||
super.onEditModeChanged(destroy)
|
||||
|
||||
if (!destroy && this.__select2) {
|
||||
this.__select2.on('select2:select', (e) => __addRecentlyUse(e.params.data.id))
|
||||
}
|
||||
|
@ -2764,13 +2784,17 @@ class RbFormClassification extends RbFormElement {
|
|||
return super.renderViewElement(text)
|
||||
}
|
||||
|
||||
onEditModeChanged(destroy) {
|
||||
onEditModeChanged(destroy, fromReadonly41) {
|
||||
if (destroy) {
|
||||
super.onEditModeChanged(destroy)
|
||||
this.__cached = null
|
||||
if (this.__selector) {
|
||||
this.__selector.hide(true)
|
||||
this.__selector = null
|
||||
if (fromReadonly41) {
|
||||
this.__select2 && $(this._fieldValue).attr('disabled', true)
|
||||
} else {
|
||||
super.onEditModeChanged(destroy)
|
||||
this.__cached = null
|
||||
if (this.__selector) {
|
||||
this.__selector.hide(true)
|
||||
this.__selector = null
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.__select2 = $initReferenceSelect2(this._fieldValue, {
|
||||
|
@ -2897,10 +2921,14 @@ class RbFormMultiSelect extends RbFormElement {
|
|||
return <div className="form-control-plaintext multi-values">{__findMultiTexts(this.props.options, maskValue, true)}</div>
|
||||
}
|
||||
|
||||
onEditModeChanged(destroy) {
|
||||
onEditModeChanged(destroy, fromReadonly41) {
|
||||
if (this._isShowSelect41) {
|
||||
if (destroy) {
|
||||
super.onEditModeChanged(destroy)
|
||||
if (fromReadonly41) {
|
||||
this.__select2 && $(this._fieldValue).attr('disabled', true)
|
||||
} else {
|
||||
super.onEditModeChanged(destroy)
|
||||
}
|
||||
} else {
|
||||
this.__select2 = $(this._fieldValue).select2({
|
||||
placeholder: $L('选择%s', this.props.label),
|
||||
|
@ -2945,7 +2973,16 @@ class RbFormMultiSelect extends RbFormElement {
|
|||
setValue(val) {
|
||||
// eg. {id:3, text:["A", "B"]}
|
||||
if (typeof val === 'object') val = val.id || val
|
||||
super.setValue(val)
|
||||
if (this._isShowSelect41) {
|
||||
let s = []
|
||||
this.props.options &&
|
||||
this.props.options.forEach((o) => {
|
||||
if ((val & o.mask) !== 0) s.push(o.mask)
|
||||
})
|
||||
this.__select2 && this.__select2.val(s).trigger('change')
|
||||
} else {
|
||||
super.setValue(val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3345,10 +3382,14 @@ class RbFormTag extends RbFormElement {
|
|||
this._selected = selected
|
||||
}
|
||||
|
||||
onEditModeChanged(destroy) {
|
||||
onEditModeChanged(destroy, fromReadonly41) {
|
||||
if (destroy) {
|
||||
super.onEditModeChanged(destroy)
|
||||
this._initOptions()
|
||||
if (fromReadonly41) {
|
||||
this.__select2 && $(this._fieldValue).attr('disabled', true)
|
||||
} else {
|
||||
super.onEditModeChanged(destroy)
|
||||
this._initOptions()
|
||||
}
|
||||
} else {
|
||||
this.__select2 = $(this._fieldValue).select2({
|
||||
placeholder: this.props.readonlyw > 0 ? this._placeholderw : $L('输入%s', this.props.label),
|
||||
|
@ -3515,9 +3556,10 @@ var detectElement = function (item, entity) {
|
|||
if (!item.key) {
|
||||
item.key = `field-${item.field === TYPE_DIVIDER || item.field === TYPE_REFFORM ? $random() : item.field}`
|
||||
}
|
||||
// v4.1
|
||||
item.entity = item.entity || entity
|
||||
|
||||
// v4.1-b5
|
||||
if (entity) {
|
||||
item.entity = entity
|
||||
}
|
||||
// 复写的字段组件
|
||||
if (entity && window._CustomizedForms) {
|
||||
const c = window._CustomizedForms.useFormElement(entity, item)
|
||||
|
|
|
@ -302,7 +302,7 @@ class ProTable extends React.Component {
|
|||
const FORM = (
|
||||
<InlineForm entity={entityName} id={model.id} rawModel={model} $$$parent={this} $$$main={this.props.$$$main} key={lineKey} ref={ref} _componentDidUpdate={() => this._componentDidUpdate()}>
|
||||
{model.elements.map((item) => {
|
||||
return detectElement({ ...item, colspan: 4, _disableAutoFillin: _disableAutoFillin === true }, entityName)
|
||||
return detectElement({ ...item, colspan: 4, _disableAutoFillin: _disableAutoFillin === true })
|
||||
})}
|
||||
</InlineForm>
|
||||
)
|
||||
|
|
|
@ -371,8 +371,15 @@ class LightAttachmentList extends RelatedList {
|
|||
<i className="file-icon" data-type={item.fileType} />
|
||||
</div>
|
||||
<div className="detail">
|
||||
<a onClick={() => (parent || window).RbPreview.create(item.filePath)} title={$L('预览')}>
|
||||
{$fileCutName(item.filePath)}
|
||||
<a
|
||||
onClick={() => {
|
||||
$.get(`/files/check-readable?id=${item.id}`, (res) => {
|
||||
if (res.data) (parent || window).RbPreview.create(res.data)
|
||||
else RbHighbar.create($L('你没有查看此文件的权限'))
|
||||
})
|
||||
}}
|
||||
title={$L('预览')}>
|
||||
{item.fileName}
|
||||
</a>
|
||||
<div className="extras">
|
||||
<span className="fsize">{item.fileSize}</span>
|
||||
|
@ -380,7 +387,7 @@ class LightAttachmentList extends RelatedList {
|
|||
</div>
|
||||
<div className="info position-relative">
|
||||
<span className="fop-action">
|
||||
<a title={$L('下载')} href={`${rb.baseUrl}/filex/download/${item.filePath}?attname=${$fileCutName(item.filePath)}`} target="_blank">
|
||||
<a title={$L('下载')} href={`${rb.baseUrl}/files/download?id=${item.id}`} target="_blank">
|
||||
<i className="icon zmdi zmdi-download fs-17" />
|
||||
</a>
|
||||
{rb.fileSharable && (
|
||||
|
@ -389,7 +396,7 @@ class LightAttachmentList extends RelatedList {
|
|||
onClick={(e) => {
|
||||
$stopEvent(e)
|
||||
// eslint-disable-next-line react/jsx-no-undef
|
||||
renderRbcomp(<FileShare file={item.filePath} />)
|
||||
renderRbcomp(<FileShare file={item.id} />)
|
||||
}}>
|
||||
<i className="icon zmdi zmdi-share up-1" />
|
||||
</a>
|
||||
|
@ -410,32 +417,27 @@ class LightAttachmentList extends RelatedList {
|
|||
const pageSize = 20
|
||||
|
||||
const relatedId = this.props.mainid
|
||||
$.get(
|
||||
`/files/list-file?entry=${relatedId.substr(0, 3)}&sort=${this.__searchSort || ''}&q=${$encode(this.__searchKey)}&pageNo=${this.__pageNo}&pageSize=${pageSize}&related=${relatedId}`,
|
||||
(res) => {
|
||||
if (res.error_code !== 0) return RbHighbar.error(res.error_msg)
|
||||
let url = `/files/list-file?entry=${relatedId.substr(0, 3)}&sort=${this.__searchSort || ''}&q=${$encode(this.__searchKey)}&pageNo=${this.__pageNo}&pageSize=${pageSize}&related=${relatedId}`
|
||||
$.get(url, (res) => {
|
||||
if (res.error_code !== 0) return RbHighbar.error(res.error_msg)
|
||||
|
||||
const data = res.data || []
|
||||
const list = append ? (this.state.dataList || []).concat(data) : data
|
||||
this.setState({ dataList: list, showMore: data.length >= pageSize })
|
||||
const data = res.data || []
|
||||
const list = append ? (this.state.dataList || []).concat(data) : data
|
||||
this.setState({ dataList: list, showMore: data.length >= pageSize })
|
||||
|
||||
const files = list.map((item) => item.filePath)
|
||||
if (files.length > 0) {
|
||||
$(this._$downloadForm).find('input').val(files.join(','))
|
||||
$(this._$downloadForm).find('button').attr('disabled', false)
|
||||
}
|
||||
const files = list.map((item) => item.id)
|
||||
if (files.length > 0) {
|
||||
$(this._$downloadForm).find('input').val(files.join(','))
|
||||
$(this._$downloadForm).find('button').attr('disabled', false)
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// v3.1 有权限才加载
|
||||
$.get(`/files/check-readable?id=${this.props.mainid}`, (res) => {
|
||||
if (res.data === true) {
|
||||
this.fetchData()
|
||||
} else {
|
||||
this.setState({ dataList: [] }, () => {})
|
||||
}
|
||||
// v3.1 有读取权限才加载
|
||||
$.get(`/app/entity/check-readable?id=${this.props.mainid}`, (res) => {
|
||||
if (res.data === true) this.fetchData()
|
||||
else this.setState({ dataList: [] }, () => {})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -665,7 +665,7 @@ const RbViewPage = {
|
|||
$('.J_assign').on('click', () => DlgAssign.create({ entity: entity[0], ids: [id] }))
|
||||
$('.J_share').on('click', () => DlgShare.create({ entity: entity[0], ids: [id] }))
|
||||
$('.J_report').on('click', () => SelectReport.create(entity[0], id))
|
||||
$('.J_add-detail-memu>a').on('click', function () {
|
||||
$('.J_add-detail-menu>a').on('click', function () {
|
||||
const iv = { $MAINID$: id }
|
||||
const $this = $(this)
|
||||
RbFormModal.create({ title: $L('添加%s', $this.data('label')), entity: $this.data('entity'), icon: $this.data('icon'), initialValue: iv, _nextAddDetail: true })
|
||||
|
@ -681,7 +681,7 @@ const RbViewPage = {
|
|||
// Privileges
|
||||
if (ep) {
|
||||
if (ep.D === false) $('.J_delete').remove()
|
||||
if (ep.U === false) $('.J_edit, .J_add-detail, .J_add-detail-memu').remove()
|
||||
if (ep.U === false) $('.J_edit, .J_add-detail, .J_add-detail-menu').remove()
|
||||
if (ep.A !== true) $('.J_assign').remove()
|
||||
if (ep.S !== true) $('.J_share').remove()
|
||||
}
|
||||
|
|
|
@ -18,15 +18,19 @@ RbList.queryBefore = function (query) {
|
|||
query = _RbList_queryBefore(query)
|
||||
}
|
||||
|
||||
if (window.__PageConfig.protocolFilter && parent && parent.referenceSearch__dlg && parent.RbFormModal && parent.RbFormModal.__CURRENT35) {
|
||||
const formComp = parent.RbFormModal.__CURRENT35.getFormComp()
|
||||
let varRecord = formComp ? formComp.getFormData() : null
|
||||
const formComp = parent.RbFormModal && parent.RbFormModal.__CURRENT35 ? parent.RbFormModal.__CURRENT35.getFormComp() : null
|
||||
const viewComp = parent.RbViewPage ? parent.RbViewPage._RbViewForm : null
|
||||
if (window.__PageConfig.protocolFilter && parent && parent.referenceSearch__dlg && (formComp || viewComp)) {
|
||||
let varRecord = formComp ? formComp.getFormData() : viewComp ? viewComp.__ViewData : null
|
||||
if (varRecord) {
|
||||
// FIXME 太长的值过滤
|
||||
for (let k in varRecord) {
|
||||
if (varRecord[k] && (varRecord[k] + '').length > 200) delete varRecord[k]
|
||||
if (varRecord[k] && (varRecord[k] + '').length > 100) {
|
||||
console.log('Ignore large value of field :', k, varRecord[k])
|
||||
delete varRecord[k]
|
||||
}
|
||||
}
|
||||
query.protocolFilter__varRecord = { 'metadata.entity': formComp.props.entity, ...varRecord }
|
||||
query.protocolFilter__varRecord = { 'metadata.entity': (formComp || viewComp).props.entity, ...varRecord }
|
||||
}
|
||||
}
|
||||
return query
|
||||
|
|
|
@ -24,7 +24,7 @@ $(document).ready(() => {
|
|||
|
||||
setTimeout(function () {
|
||||
if ($.browser.mobile) {
|
||||
const $a = $('.h5-mobile>a')
|
||||
const $a = $('.h5-mobile>a:eq(0)')
|
||||
$a.parent().html('<a href="' + $a.attr('href') + '">' + $a.html() + '</a>')
|
||||
} else {
|
||||
$('.h5-mobile img').attr('src', `${rb.baseUrl}/commons/barcode/render-qr?w=296&t=${$encode($('.h5-mobile a').attr('href'))}`)
|
||||
|
@ -97,9 +97,27 @@ $(document).ready(() => {
|
|||
RbAlert.create($L('是否需要切换到手机版访问?'), {
|
||||
onConfirm: function () {
|
||||
this.hide()
|
||||
location.href = $('.h5-mobile a').attr('href')
|
||||
location.href = $('.h5-mobile>a:eq(0)').attr('href')
|
||||
},
|
||||
})
|
||||
}, 500)
|
||||
}
|
||||
})
|
||||
|
||||
window.addEventListener('beforeinstallprompt', (e) => {
|
||||
e.preventDefault()
|
||||
let deferredPrompt = e
|
||||
|
||||
$('.h5-mobile.pwa')
|
||||
.removeClass('hide')
|
||||
.find('>a')
|
||||
.on('click', () => {
|
||||
if (deferredPrompt) {
|
||||
deferredPrompt.prompt()
|
||||
deferredPrompt.userChoice.then((choiceRes) => {
|
||||
console.log('', choiceRes.outcome)
|
||||
deferredPrompt = null
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"$schema": "https://json.schemastore.org/web-manifest-combined.json",
|
||||
"name": "REBUILD",
|
||||
"short_name": "REBUILD",
|
||||
"description": "Made by getrebuild.com",
|
||||
"description": "Powered by getrebuild.com",
|
||||
"start_url": "../",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#4285f4",
|
||||
|
|
|
@ -55,6 +55,9 @@
|
|||
text-align: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
.h5-mobile.pwa .icon {
|
||||
line-height: 26px;
|
||||
}
|
||||
.h5-mobile span {
|
||||
float: right;
|
||||
color: rgb(64, 64, 64);
|
||||
|
@ -163,7 +166,7 @@
|
|||
</a>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col">
|
||||
<div class="col-8">
|
||||
<div class="btn-group dropup h5-mobile fs-0">
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" th:href="${mobileUrl}">
|
||||
<i class="icon zmdi zmdi-smartphone-iphone"></i>
|
||||
|
@ -175,8 +178,14 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group dropup h5-mobile pwa fs-0 ml-2 hide">
|
||||
<a>
|
||||
<i class="icon mdi mdi-monitor-arrow-down"></i>
|
||||
<span class="up-1">[[${bundle.L('放在桌面')}]]</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col text-right">
|
||||
<div class="col-4 text-right pl-0">
|
||||
<div class="btn-group">
|
||||
<a class="select-lang dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="icon zmdi zmdi-globe-alt"></i>
|
||||
|
|
Loading…
Add table
Reference in a new issue