From be10888e5cc553e3a2542213ec4559dd4e360a13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?REBUILD=20=E4=BC=81=E4=B8=9A=E7=AE=A1=E7=90=86=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F?= <42044143+getrebuild@users.noreply.github.com> Date: Sat, 19 Jul 2025 17:09:52 +0800 Subject: [PATCH] Fix 4.1 beta5 (#931) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- @rbv | 2 +- pom.xml | 2 +- .../java/com/rebuild/core/Application.java | 4 +- .../configuration/general/FormsBuilder.java | 127 +++++++++--------- .../general/TransformManager.java | 10 +- .../core/metadata/impl/Entity2Schema.java | 2 +- .../datareport/EasyExcelGenerator.java | 2 +- .../datareport/EasyExcelGenerator33.java | 27 +++- .../core/service/datareport/ReportsFile.java | 89 ++++++++++++ .../core/service/files/FilesHelper.java | 2 +- .../core/service/query/AdvFilterParser.java | 1 - .../service/trigger/RobotTriggerObserver.java | 6 +- .../core/support/integration/QiniuCloud.java | 37 ++++- .../rebuild/web/RebuildWebInterceptor.java | 9 ++ .../java/com/rebuild/web/WebConstants.java | 5 + .../com/rebuild/web/admin/ProtectedAdmin.java | 4 +- .../web/commons/UseThemeController.java | 11 ++ .../rebuild/web/files/FileListController.java | 9 +- .../web/files/FileManagerController.java | 82 ++++++----- .../general/CommonOperatingController.java | 6 + .../web/general/ReportsController.java | 48 ++++--- src/main/resources/i18n/lang.zh_CN.json | 22 ++- src/main/resources/metadata-conf.xml | 2 +- src/main/resources/web/_include/header.html | 1 + .../resources/web/assets/css/form-design.css | 1 + .../resources/web/assets/css/rb-general40.css | 2 +- src/main/resources/web/assets/css/rb-page.css | 2 +- .../web/assets/js/charts/chart-design.js | 1 + .../resources/web/assets/js/file-preview.js | 17 ++- .../web/assets/js/files/files-docs.js | 8 +- .../resources/web/assets/js/files/files.js | 26 ++-- .../web/assets/js/general/rb-approval.js | 2 +- .../web/assets/js/general/rb-forms.js | 122 +++++++++++------ .../assets/js/general/rb-forms.protable.js | 2 +- .../web/assets/js/general/rb-view.append.js | 50 +++---- .../web/assets/js/general/rb-view.js | 4 +- .../web/assets/js/general/reference-search.js | 14 +- src/main/resources/web/assets/js/login.js | 22 ++- src/main/resources/web/assets/manifest.json | 2 +- src/main/resources/web/signup/login.html | 13 +- 40 files changed, 560 insertions(+), 238 deletions(-) create mode 100644 src/main/java/com/rebuild/core/service/datareport/ReportsFile.java diff --git a/@rbv b/@rbv index f5eb69d2e..86858da73 160000 --- a/@rbv +++ b/@rbv @@ -1 +1 @@ -Subproject commit f5eb69d2eb613d5fc8bb353996aa795b9905fb8a +Subproject commit 86858da73f2a983ff14bd02af205256d4dbd366d diff --git a/pom.xml b/pom.xml index d38741227..efc368557 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.rebuild rebuild - 4.1.0-beta4 + 4.1.0-beta5 rebuild Building your business-systems freely! https://getrebuild.com/ diff --git a/src/main/java/com/rebuild/core/Application.java b/src/main/java/com/rebuild/core/Application.java index 956aa4c7e..d324b773e 100644 --- a/src/main/java/com/rebuild/core/Application.java +++ b/src/main/java/com/rebuild/core/Application.java @@ -76,11 +76,11 @@ public class Application implements ApplicationListener /** * 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 diff --git a/src/main/java/com/rebuild/core/configuration/general/FormsBuilder.java b/src/main/java/com/rebuild/core/configuration/general/FormsBuilder.java index da160de46..b17e4a8e0 100644 --- a/src/main/java/com/rebuild/core/configuration/general/FormsBuilder.java +++ b/src/main/java/com/rebuild/core/configuration/general/FormsBuilder.java @@ -395,19 +395,19 @@ public class FormsBuilder extends FormsManager { // Check and clean for (Iterator 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); } } } diff --git a/src/main/java/com/rebuild/core/configuration/general/TransformManager.java b/src/main/java/com/rebuild/core/configuration/general/TransformManager.java index c1faf4542..157ae0cba 100644 --- a/src/main/java/com/rebuild/core/configuration/general/TransformManager.java +++ b/src/main/java/com/rebuild/core/configuration/general/TransformManager.java @@ -43,7 +43,8 @@ public class TransformManager implements ConfigManager { // 任何修改都会清空 private static final Map 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; } diff --git a/src/main/java/com/rebuild/core/metadata/impl/Entity2Schema.java b/src/main/java/com/rebuild/core/metadata/impl/Entity2Schema.java index a6a32535e..214962243 100644 --- a/src/main/java/com/rebuild/core/metadata/impl/Entity2Schema.java +++ b/src/main/java/com/rebuild/core/metadata/impl/Entity2Schema.java @@ -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("实体数量超出免费版限制")); } diff --git a/src/main/java/com/rebuild/core/service/datareport/EasyExcelGenerator.java b/src/main/java/com/rebuild/core/service/datareport/EasyExcelGenerator.java index 5b7982c40..85813388e 100644 --- a/src/main/java/com/rebuild/core/service/datareport/EasyExcelGenerator.java +++ b/src/main/java/com/rebuild/core/service/datareport/EasyExcelGenerator.java @@ -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 phValues = new HashMap<>(); diff --git a/src/main/java/com/rebuild/core/service/datareport/EasyExcelGenerator33.java b/src/main/java/com/rebuild/core/service/datareport/EasyExcelGenerator33.java index 3861fced6..2b6aa276a 100644 --- a/src/main/java/com/rebuild/core/service/datareport/EasyExcelGenerator33.java +++ b/src/main/java/com/rebuild/core/service/datareport/EasyExcelGenerator33.java @@ -67,6 +67,9 @@ public class EasyExcelGenerator33 extends EasyExcelGenerator { // 支持多记录导出,会合并到一个 Excel 文件 final private List recordIdMulti; + // 默认合并到一个文件,也可以打包成一个 zip + @Setter + private boolean recordIdMultiMerge2Sheets = true; private Set inShapeVars; private Map 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) { diff --git a/src/main/java/com/rebuild/core/service/datareport/ReportsFile.java b/src/main/java/com/rebuild/core/service/datareport/ReportsFile.java new file mode 100644 index 000000000..3623793e4 --- /dev/null +++ b/src/main/java/com/rebuild/core/service/datareport/ReportsFile.java @@ -0,0 +1,89 @@ +/*! +Copyright (c) REBUILD 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 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(); + } +} diff --git a/src/main/java/com/rebuild/core/service/files/FilesHelper.java b/src/main/java/com/rebuild/core/service/files/FilesHelper.java index c9ad86610..24fe514ab 100644 --- a/src/main/java/com/rebuild/core/service/files/FilesHelper.java +++ b/src/main/java/com/rebuild/core/service/files/FilesHelper.java @@ -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]); } diff --git a/src/main/java/com/rebuild/core/service/query/AdvFilterParser.java b/src/main/java/com/rebuild/core/service/query/AdvFilterParser.java index f2e7cb5e5..b0dc25345 100644 --- a/src/main/java/com/rebuild/core/service/query/AdvFilterParser.java +++ b/src/main/java/com/rebuild/core/service/query/AdvFilterParser.java @@ -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; diff --git a/src/main/java/com/rebuild/core/service/trigger/RobotTriggerObserver.java b/src/main/java/com/rebuild/core/service/trigger/RobotTriggerObserver.java index fe06d045f..31eeb0778 100644 --- a/src/main/java/com/rebuild/core/service/trigger/RobotTriggerObserver.java +++ b/src/main/java/com/rebuild/core/service/trigger/RobotTriggerObserver.java @@ -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); diff --git a/src/main/java/com/rebuild/core/support/integration/QiniuCloud.java b/src/main/java/com/rebuild/core/support/integration/QiniuCloud.java index c2d386036..ff8b55fca 100644 --- a/src/main/java/com/rebuild/core/support/integration/QiniuCloud.java +++ b/src/main/java/com/rebuild/core/support/integration/QiniuCloud.java @@ -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; + } } diff --git a/src/main/java/com/rebuild/web/RebuildWebInterceptor.java b/src/main/java/com/rebuild/web/RebuildWebInterceptor.java index 27288d84a..ef29786e4 100644 --- a/src/main/java/com/rebuild/web/RebuildWebInterceptor.java +++ b/src/main/java/com/rebuild/web/RebuildWebInterceptor.java @@ -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(); diff --git a/src/main/java/com/rebuild/web/WebConstants.java b/src/main/java/com/rebuild/web/WebConstants.java index be3898dd7..1c735d87a 100644 --- a/src/main/java/com/rebuild/web/WebConstants.java +++ b/src/main/java/com/rebuild/web/WebConstants.java @@ -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 diff --git a/src/main/java/com/rebuild/web/admin/ProtectedAdmin.java b/src/main/java/com/rebuild/web/admin/ProtectedAdmin.java index 2e5012d60..7ce9e7909 100644 --- a/src/main/java/com/rebuild/web/admin/ProtectedAdmin.java +++ b/src/main/java/com/rebuild/web/admin/ProtectedAdmin.java @@ -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", "项目"), diff --git a/src/main/java/com/rebuild/web/commons/UseThemeController.java b/src/main/java/com/rebuild/web/commons/UseThemeController.java index 46fdb7749..709e62de8 100644 --- a/src/main/java/com/rebuild/web/commons/UseThemeController.java +++ b/src/main/java/com/rebuild/web/commons/UseThemeController.java @@ -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 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 { diff --git a/src/main/java/com/rebuild/web/files/FileListController.java b/src/main/java/com/rebuild/web/files/FileListController.java index cc1857219..7d4f2325c 100644 --- a/src/main/java/com/rebuild/web/files/FileListController.java +++ b/src/main/java/com/rebuild/web/files/FileListController.java @@ -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])}); diff --git a/src/main/java/com/rebuild/web/files/FileManagerController.java b/src/main/java/com/rebuild/web/files/FileManagerController.java index 47274dc8e..1e3808328 100644 --- a/src/main/java/com/rebuild/web/files/FileManagerController.java +++ b/src/main/java/com/rebuild/web/files/FileManagerController.java @@ -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 willDeletes = new HashSet<>(); + Set 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 filePaths = new ArrayList<>(); - Collections.addAll(filePaths, files.split(",")); + List 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); diff --git a/src/main/java/com/rebuild/web/general/CommonOperatingController.java b/src/main/java/com/rebuild/web/general/CommonOperatingController.java index 355a5d60d..98cf5ecdb 100644 --- a/src/main/java/com/rebuild/web/general/CommonOperatingController.java +++ b/src/main/java/com/rebuild/web/general/CommonOperatingController.java @@ -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); + } + /** * 保存记录 * diff --git a/src/main/java/com/rebuild/web/general/ReportsController.java b/src/main/java/com/rebuild/web/general/ReportsController.java index a163cdb8c..bbd0c5062 100644 --- a/src/main/java/com/rebuild/web/general/ReportsController.java +++ b/src/main/java/com/rebuild/web/general/ReportsController.java @@ -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); diff --git a/src/main/resources/i18n/lang.zh_CN.json b/src/main/resources/i18n/lang.zh_CN.json index c389ae002..add1ccf03 100644 --- a/src/main/resources/i18n/lang.zh_CN.json +++ b/src/main/resources/i18n/lang.zh_CN.json @@ -3488,5 +3488,25 @@ "AI 助手会话附加数据":"AI 助手会话附加数据", "AI 助手":"AI 助手", "直接转换":"直接转换", - "(必填)":"(必填)" + "(必填)":"(必填)", + "隐藏明细":"隐藏明细", + "修改文件":"修改文件", + "指定明细实体布局":"指定明细实体布局", + "由于%s,此触发器不会执行":"由于%s,此触发器不会执行", + "无任何触发动作":"无任何触发动作", + "提交后提示":"提交后提示", + "清空配置":"清空配置", + "无 (全部权限)":"无 (全部权限)", + "无法修改外部文件":"无法修改外部文件", + "在线编辑":"在线编辑", + "显示明细":"显示明细", + "放在桌面":"放在桌面", + "API 密钥":"API 密钥", + "脱敏读取字段":"脱敏读取字段", + "前置校验模式":"前置校验模式", + "无权编辑此记录":"无权编辑此记录", + "确定要清空配置吗?":"确定要清空配置吗?", + "启用列表页单字段编辑":"启用列表页单字段编辑", + "无 (不限)":"无 (不限)", + "引用记录不存在":"引用记录不存在" } \ No newline at end of file diff --git a/src/main/resources/metadata-conf.xml b/src/main/resources/metadata-conf.xml index 6c6f69f61..6555048a5 100644 --- a/src/main/resources/metadata-conf.xml +++ b/src/main/resources/metadata-conf.xml @@ -336,7 +336,7 @@ - + diff --git a/src/main/resources/web/_include/header.html b/src/main/resources/web/_include/header.html index 63c221e52..7b52b4e96 100644 --- a/src/main/resources/web/_include/header.html +++ b/src/main/resources/web/_include/header.html @@ -4,6 +4,7 @@ + diff --git a/src/main/resources/web/assets/css/form-design.css b/src/main/resources/web/assets/css/form-design.css index 768b1c840..00eafcea4 100644 --- a/src/main/resources/web/assets/css/form-design.css +++ b/src/main/resources/web/assets/css/form-design.css @@ -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 { diff --git a/src/main/resources/web/assets/css/rb-general40.css b/src/main/resources/web/assets/css/rb-general40.css index 39ccba766..05476e6f3 100644 --- a/src/main/resources/web/assets/css/rb-general40.css +++ b/src/main/resources/web/assets/css/rb-general40.css @@ -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; diff --git a/src/main/resources/web/assets/css/rb-page.css b/src/main/resources/web/assets/css/rb-page.css index 7d1fb3ed7..6e20c1cb6 100644 --- a/src/main/resources/web/assets/css/rb-page.css +++ b/src/main/resources/web/assets/css/rb-page.css @@ -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; } diff --git a/src/main/resources/web/assets/js/charts/chart-design.js b/src/main/resources/web/assets/js/charts/chart-design.js index eb9860eb8..ec0b6ee63 100644 --- a/src/main/resources/web/assets/js/charts/chart-design.js +++ b/src/main/resources/web/assets/js/charts/chart-design.js @@ -247,6 +247,7 @@ const CTs = { Y: $L('按年'), Q: $L('按季'), M: $L('按月'), + W: $L('按周'), D: $L('按日'), H: $L('按时'), I: $L('按时分'), diff --git a/src/main/resources/web/assets/js/file-preview.js b/src/main/resources/web/assets/js/file-preview.js index a6f68b595..fdf2365bb 100644 --- a/src/main/resources/web/assets/js/file-preview.js +++ b/src/main/resources/web/assets/js/file-preview.js @@ -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 }) diff --git a/src/main/resources/web/assets/js/files/files-docs.js b/src/main/resources/web/assets/js/files/files-docs.js index 333e1b0b2..ca0f2c5ea 100644 --- a/src/main/resources/web/assets/js/files/files-docs.js +++ b/src/main/resources/web/assets/js/files/files-docs.js @@ -423,15 +423,13 @@ class FileEditDlg extends RbFormHandler { render() { const file = this.props.file - let fileName = file.filePath - fileName = fileName ? $fileCutName(fileName) : null return ( (this._dlg = c)} disposeOnHide>
- (this._$fileName = c)} /> + (this._$fileName = c)} />

{$L('在线编辑')} (LAB) @@ -485,7 +483,7 @@ class FilesList4Docs extends FilesList { this._handleEdit(item, e)}> - $stopEvent(e)} href={`${rb.baseUrl}/filex/download/${item.filePath}?attname=${$encode(item.fileName)}`} target="_blank"> + $stopEvent(e)} href={`${rb.baseUrl}/files/download?id=${item.id}`} target="_blank"> {rb.fileSharable && ( @@ -506,7 +504,7 @@ class FilesList4Docs extends FilesList { _handleShare(item, e) { $stopEvent(e) // eslint-disable-next-line react/jsx-no-undef - renderRbcomp() + renderRbcomp() } } diff --git a/src/main/resources/web/assets/js/files/files.js b/src/main/resources/web/assets/js/files/files.js index 85e245533..c1d5a86f0 100644 --- a/src/main/resources/web/assets/js/files/files.js +++ b/src/main/resources/web/assets/js/files/files.js @@ -25,7 +25,7 @@ class FilesList extends React.Component { {(this.state.files || []).map((item) => { const checked = currentActive.includes(item.id) return ( -

this._handleClick(e, item.id)}> +
this._handleClick(e, item.id)}>
@@ -36,8 +36,15 @@ class FilesList extends React.Component {
- previewFile(e, item.filePath, item.relatedRecord ? item.relatedRecord[0] : null)} title={$L('预览')}> - {$fileCutName(item.filePath)} + { + $.get(`/files/check-readable?id=${item.id}`, (res) => { + if (res.data) RbPreview.create(res.data) + else RbHighbar.create($L('你没有查看此文件的权限')) + }) + }} + title={$L('预览')}> + {item.fileName}
{item.fileSize} @@ -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() { diff --git a/src/main/resources/web/assets/js/general/rb-approval.js b/src/main/resources/web/assets/js/general/rb-approval.js index 10a3b0d1e..b8e615e7e 100644 --- a/src/main/resources/web/assets/js/general/rb-approval.js +++ b/src/main/resources/web/assets/js/general/rb-approval.js @@ -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) })} ) diff --git a/src/main/resources/web/assets/js/general/rb-forms.js b/src/main/resources/web/assets/js/general/rb-forms.js index 2bbfe21d4..e0cb17dff 100644 --- a/src/main/resources/web/assets/js/general/rb-forms.js +++ b/src/main/resources/web/assets/js/general/rb-forms.js @@ -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))} ) @@ -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 {
) + + // 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 && (this._fieldValue__upload = c)} />} {this._textCommonMenuId && ( - + {$L('常用值')} )} @@ -1509,21 +1513,21 @@ class RbFormNText extends RbFormElement {
{this.props.textCommon.split(',').map((c) => { + let cLN = c.replace(/\\n/g, '\n') // 换行符 return ( { - 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
{__findMultiTexts(this.props.options, maskValue, true)}
} - 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) diff --git a/src/main/resources/web/assets/js/general/rb-forms.protable.js b/src/main/resources/web/assets/js/general/rb-forms.protable.js index 43aeb7a02..f355e2c62 100644 --- a/src/main/resources/web/assets/js/general/rb-forms.protable.js +++ b/src/main/resources/web/assets/js/general/rb-forms.protable.js @@ -302,7 +302,7 @@ class ProTable extends React.Component { const FORM = ( this._componentDidUpdate()}> {model.elements.map((item) => { - return detectElement({ ...item, colspan: 4, _disableAutoFillin: _disableAutoFillin === true }, entityName) + return detectElement({ ...item, colspan: 4, _disableAutoFillin: _disableAutoFillin === true }) })} ) diff --git a/src/main/resources/web/assets/js/general/rb-view.append.js b/src/main/resources/web/assets/js/general/rb-view.append.js index 3740076f1..91d0d8729 100644 --- a/src/main/resources/web/assets/js/general/rb-view.append.js +++ b/src/main/resources/web/assets/js/general/rb-view.append.js @@ -371,8 +371,15 @@ class LightAttachmentList extends RelatedList {
- (parent || window).RbPreview.create(item.filePath)} title={$L('预览')}> - {$fileCutName(item.filePath)} + { + $.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}
{item.fileSize} @@ -380,7 +387,7 @@ class LightAttachmentList extends RelatedList {
- + {rb.fileSharable && ( @@ -389,7 +396,7 @@ class LightAttachmentList extends RelatedList { onClick={(e) => { $stopEvent(e) // eslint-disable-next-line react/jsx-no-undef - renderRbcomp() + renderRbcomp() }}> @@ -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: [] }, () => {}) }) } } diff --git a/src/main/resources/web/assets/js/general/rb-view.js b/src/main/resources/web/assets/js/general/rb-view.js index 03f2cb97b..4cac02533 100644 --- a/src/main/resources/web/assets/js/general/rb-view.js +++ b/src/main/resources/web/assets/js/general/rb-view.js @@ -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() } diff --git a/src/main/resources/web/assets/js/general/reference-search.js b/src/main/resources/web/assets/js/general/reference-search.js index c6f94f135..1e27ead3c 100644 --- a/src/main/resources/web/assets/js/general/reference-search.js +++ b/src/main/resources/web/assets/js/general/reference-search.js @@ -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 diff --git a/src/main/resources/web/assets/js/login.js b/src/main/resources/web/assets/js/login.js index 0090bdb4c..001daf278 100644 --- a/src/main/resources/web/assets/js/login.js +++ b/src/main/resources/web/assets/js/login.js @@ -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.html() + '') } 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 + }) + } + }) +}) diff --git a/src/main/resources/web/assets/manifest.json b/src/main/resources/web/assets/manifest.json index ae7820453..bb8ce19f9 100644 --- a/src/main/resources/web/assets/manifest.json +++ b/src/main/resources/web/assets/manifest.json @@ -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", diff --git a/src/main/resources/web/signup/login.html b/src/main/resources/web/signup/login.html index bcbc7a5b1..eda5e35d9 100644 --- a/src/main/resources/web/signup/login.html +++ b/src/main/resources/web/signup/login.html @@ -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 @@
-
+