Dd wx sync scope (#712)

* fix: excel模板字段排序

* fix: 多列表导出

* template5

* feat: chart/table export


* feat: 用户同步匹配选项
This commit is contained in:
REBUILD 企业管理系统 2024-01-27 15:50:24 +08:00 committed by GitHub
parent 47a7335391
commit 91010899a0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 323 additions and 200 deletions

2
@rbv

@ -1 +1 @@
Subproject commit 1b45fde01e4b313cd65ad9f73598b3453c047227 Subproject commit e30741d980c3d40f31590401bcb3c4070bbe8268

View file

@ -152,7 +152,9 @@ public class UserContextHolder {
* @see #replaceUser(ID) * @see #replaceUser(ID)
*/ */
public static ID getRestoreUser() { public static ID getRestoreUser() {
return CALLER_PREV.get(); ID prev = CALLER_PREV.get();
if (prev != null) return prev;
return getUser();
} }
// -- // --

View file

@ -56,7 +56,7 @@ public class DataReportManager implements ConfigManager {
* @param user * @param user
* @return * @return
*/ */
public JSONArray getReports(Entity entity, int type, ID user) { public JSONArray getReportTemplates(Entity entity, int type, ID user) {
JSONArray alist = new JSONArray(); JSONArray alist = new JSONArray();
for (ConfigBean e : getReportsRaw(entity)) { for (ConfigBean e : getReportsRaw(entity)) {
if (e.getBoolean("disabled")) continue; if (e.getBoolean("disabled")) continue;
@ -66,7 +66,8 @@ public class DataReportManager implements ConfigManager {
if (type == DataReportManager.TYPE_LIST) { if (type == DataReportManager.TYPE_LIST) {
can = aType == type; can = aType == type;
} else { } else {
can = aType == DataReportManager.TYPE_RECORD || aType == DataReportManager.TYPE_WORD; can = aType == DataReportManager.TYPE_RECORD
|| aType == DataReportManager.TYPE_WORD || aType == DataReportManager.TYPE_HTML5;
} }
if (can) { if (can) {
@ -210,10 +211,12 @@ public class DataReportManager implements ConfigManager {
name = ContentWithFieldVars.replaceWithRecord(name, (ID) idOrEntity); name = ContentWithFieldVars.replaceWithRecord(name, (ID) idOrEntity);
} }
// suffix // Suffix
if (fileName.endsWith(".pdf")) name += ".pdf"; if (fileName.endsWith(".pdf")) name += ".pdf";
else if (fileName.endsWith(".docx")) name += ".docx"; else if (fileName.endsWith(".docx")) name += ".docx";
else name += fileName.endsWith(".xlsx") ? ".xlsx" : ".xls"; else if (fileName.endsWith(".doc")) name += ".doc";
else if (fileName.endsWith(".xlsx")) name += ".xlsx";
else if (fileName.endsWith(".xls")) name += ".xls";
break; break;
} }
} }

View file

@ -17,6 +17,7 @@ import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter; import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet; import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.fill.FillConfig; import com.alibaba.excel.write.metadata.fill.FillConfig;
import com.alibaba.excel.write.metadata.fill.FillWrapper;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONArray;
import com.rebuild.core.Application; import com.rebuild.core.Application;
@ -55,7 +56,6 @@ import java.math.RoundingMode;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@ -69,6 +69,7 @@ import static com.rebuild.core.service.datareport.TemplateExtractor.PH__CURRENTU
import static com.rebuild.core.service.datareport.TemplateExtractor.PH__KEEP; import static com.rebuild.core.service.datareport.TemplateExtractor.PH__KEEP;
import static com.rebuild.core.service.datareport.TemplateExtractor.PH__NUMBER; import static com.rebuild.core.service.datareport.TemplateExtractor.PH__NUMBER;
import static com.rebuild.core.service.datareport.TemplateExtractor.PLACEHOLDER; import static com.rebuild.core.service.datareport.TemplateExtractor.PLACEHOLDER;
import static com.rebuild.core.service.datareport.TemplateExtractor33.NROW_PREFIX2;
/** /**
* 报表生成 easyexcel * 报表生成 easyexcel
@ -80,11 +81,12 @@ import static com.rebuild.core.service.datareport.TemplateExtractor.PLACEHOLDER;
@Slf4j @Slf4j
public class EasyExcelGenerator extends SetUser { public class EasyExcelGenerator extends SetUser {
final protected static String MDATA_KEY = ".";
protected File templateFile; protected File templateFile;
protected Integer writeSheetAt = null; protected Integer writeSheetAt = null;
protected ID recordId; protected ID recordId;
protected boolean hasMain = false;
protected int phNumber = 1; protected int phNumber = 1;
protected Map<String, Object> phValues = new HashMap<>(); protected Map<String, Object> phValues = new HashMap<>();
@ -93,7 +95,7 @@ public class EasyExcelGenerator extends SetUser {
* @param recordId * @param recordId
*/ */
protected EasyExcelGenerator(File template, ID recordId) { protected EasyExcelGenerator(File template, ID recordId) {
this.templateFile = getFixTemplate(template); this.templateFile = template;
this.recordId = recordId; this.recordId = recordId;
} }
@ -105,14 +107,13 @@ public class EasyExcelGenerator extends SetUser {
public File generate() { public File generate() {
final File target = getTargetFile(); final File target = getTargetFile();
List<Map<String, Object>> datas = buildData(); Map<String, List<Map<String, Object>>> datas = buildData();
if (datas.isEmpty()) throw new DefinedException(Language.L("暂无数据")); if (datas.isEmpty()) throw new DefinedException(Language.L("暂无数据"));
Map<String, Object> main = null; Map<String, Object> main = null;
if (this.hasMain) { if (datas.containsKey(MDATA_KEY)) {
Iterator<Map<String, Object>> iter = datas.iterator(); main = datas.get(MDATA_KEY).get(0);
main = iter.next(); datas.remove(MDATA_KEY);
iter.remove();
} }
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build(); FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
@ -123,10 +124,29 @@ public class EasyExcelGenerator extends SetUser {
.registerWriteHandler(new FormulaCellWriteHandler()) .registerWriteHandler(new FormulaCellWriteHandler())
.build(); .build();
// 明细记录 // 引用记录
if (!datas.isEmpty()) { if (datas.size() == 1) {
excelWriter.fill(datas, fillConfig, writeSheet); excelWriter.fill(datas, fillConfig, writeSheet);
} }
// fix: v3.6 有多个引用记录
else if (datas.size() > 1) {
for (Map.Entry<String, List<Map<String, Object>>> e : datas.entrySet()) {
final String refKey = NROW_PREFIX2 + e.getKey().substring(1);
final List<Map<String, Object>> refDatas = e.getValue();
List<Map<String, Object>> refDatasNew = new ArrayList<>();
for (Map<String, Object> map : refDatas) {
Map<String, Object> mapNew = new HashMap<>();
for (Map.Entry<String, Object> ee : map.entrySet()) {
String keyNew = ee.getKey().substring(refKey.length() + 1);
mapNew.put(keyNew, ee.getValue());
}
refDatasNew.add(mapNew);
}
excelWriter.fill(new FillWrapper(refKey, refDatasNew), fillConfig, writeSheet);
}
}
// 主记录 // 主记录
if (main != null) { if (main != null) {
@ -153,9 +173,9 @@ public class EasyExcelGenerator extends SetUser {
protected File getTargetFile() { protected File getTargetFile() {
String suffix = "xls"; String suffix = "xls";
if (templateFile.getName().endsWith(".xlsx")) suffix = "xlsx"; if (templateFile.getName().endsWith(".xlsx")) suffix = "xlsx";
if (templateFile.getName().endsWith(".html")) suffix = "html"; else if (templateFile.getName().endsWith(".html")) suffix = "html";
if (templateFile.getName().endsWith(".docx")) suffix = "docx"; else if (templateFile.getName().endsWith(".docx")) suffix = "docx";
if (templateFile.getName().endsWith(".doc")) suffix = "doc"; else if (templateFile.getName().endsWith(".doc")) suffix = "doc";
return RebuildConfiguration.getFileOfTemp(String.format("RBREPORT-%d.%s", System.currentTimeMillis(), suffix)); return RebuildConfiguration.getFileOfTemp(String.format("RBREPORT-%d.%s", System.currentTimeMillis(), suffix));
} }
@ -165,7 +185,7 @@ public class EasyExcelGenerator extends SetUser {
* *
* @return 第一个为主记录若有 * @return 第一个为主记录若有
*/ */
protected List<Map<String, Object>> buildData() { protected Map<String, List<Map<String, Object>>> buildData() {
Entity entity = MetadataHelper.getEntity(recordId.getEntityCode()); Entity entity = MetadataHelper.getEntity(recordId.getEntityCode());
TemplateExtractor templateExtractor = new TemplateExtractor(templateFile); TemplateExtractor templateExtractor = new TemplateExtractor(templateFile);
@ -213,10 +233,10 @@ public class EasyExcelGenerator extends SetUser {
} }
if (fieldsOfMain.isEmpty() && fieldsOfDetail.isEmpty() && fieldsOfApproval.isEmpty()) { if (fieldsOfMain.isEmpty() && fieldsOfDetail.isEmpty() && fieldsOfApproval.isEmpty()) {
return Collections.emptyList(); return Collections.emptyMap();
} }
final List<Map<String, Object>> datas = new ArrayList<>(); final Map<String, List<Map<String, Object>>> datas = new HashMap<>();
final String baseSql = "select %s,%s from %s where %s = ?"; final String baseSql = "select %s,%s from %s where %s = ?";
if (!fieldsOfMain.isEmpty()) { if (!fieldsOfMain.isEmpty()) {
@ -229,8 +249,8 @@ public class EasyExcelGenerator extends SetUser {
.record(); .record();
Assert.notNull(record, "No record found : " + recordId); Assert.notNull(record, "No record found : " + recordId);
datas.add(buildData(record, varsMapOfMain)); Map<String, Object> d = buildData(record, varsMapOfMain);
this.hasMain = true; datas.put(MDATA_KEY, Collections.singletonList(d));
} }
// 明细 // 明细
@ -246,10 +266,12 @@ public class EasyExcelGenerator extends SetUser {
.list(); .list();
phNumber = 1; phNumber = 1;
List<Map<String, Object>> detailList = new ArrayList<>();
for (Record c : list) { for (Record c : list) {
datas.add(buildData(c, varsMapOfDetail)); detailList.add(buildData(c, varsMapOfDetail));
phNumber++; phNumber++;
} }
datas.put(MDATA_KEY + "detail", detailList);
} }
// 审批 // 审批
@ -263,10 +285,12 @@ public class EasyExcelGenerator extends SetUser {
.list(); .list();
phNumber = 1; phNumber = 1;
List<Map<String, Object>> approvalList = new ArrayList<>();
for (Record c : list) { for (Record c : list) {
datas.add(buildData(c, varsMapOfApproval)); approvalList.add(buildData(c, varsMapOfApproval));
phNumber++; phNumber++;
} }
datas.put(MDATA_KEY + "approval", approvalList);
} }
return datas; return datas;
@ -465,62 +489,6 @@ public class EasyExcelGenerator extends SetUser {
return null; return null;
} }
// 修复模板
private File getFixTemplate(File template) {
return template;
// v34 暂不启用
// String fixName = template.getName();
// fixName = fixName.substring(0, fixName.lastIndexOf(".")) + "__FIX34" + fixName.substring(fixName.lastIndexOf("."));
// File fixTemplate = new File(template.getParent(), fixName);
// if (fixTemplate.exists()) return fixTemplate;
//
// // Use copy
// try {
// FileUtils.copyFile(template, fixTemplate);
// } catch (IOException e) {
// throw new ReportsException(e);
// }
//
// // 替换 `.` > `$`
// try (Workbook wb = WorkbookFactory.create(Files.newInputStream(template.toPath()))) {
// Sheet sheet = wb.getSheetAt(0);
// for (Iterator<Row> iterRow = sheet.rowIterator(); iterRow.hasNext(); ) {
// final Row row = iterRow.next();
//
// for (Iterator<Cell> iterCell = row.cellIterator(); iterCell.hasNext(); ) {
// final Cell cell = iterCell.next();
// final String cellValue = cell.getStringCellValue();
//
// String newCellValue = cellValue;
// Matcher matcher = TemplateExtractor.PATT_V2.matcher(cellValue);
// while (matcher.find()) {
// String varName = matcher.group(1);
// if (varName.contains(".") && !varName.startsWith(".")) {
// newCellValue = newCellValue.replace(".", "$");
// }
// }
//
// if (!cellValue.equals(newCellValue)) {
// log.info("Replace `{}` to `{}` in : {}", cellValue, newCellValue, template);
// cell.setCellValue(newCellValue);
// }
// }
// }
//
// try (FileOutputStream fos = new FileOutputStream(fixTemplate)) {
// wb.write(fos);
// }
// } catch (IOException ex) {
// log.error("Cannot fix template : {}", template, ex);
//
// FileUtils.deleteQuietly(fixTemplate);
// return template;
// }
//
// return fixTemplate;
}
// -- // --
/** /**

View file

@ -41,7 +41,10 @@ import java.util.Map;
import static com.rebuild.core.service.datareport.TemplateExtractor.APPROVAL_PREFIX; import static com.rebuild.core.service.datareport.TemplateExtractor.APPROVAL_PREFIX;
import static com.rebuild.core.service.datareport.TemplateExtractor.NROW_PREFIX; import static com.rebuild.core.service.datareport.TemplateExtractor.NROW_PREFIX;
import static com.rebuild.core.service.datareport.TemplateExtractor33.APPROVAL_PREFIX2;
import static com.rebuild.core.service.datareport.TemplateExtractor33.DETAIL_PREFIX; import static com.rebuild.core.service.datareport.TemplateExtractor33.DETAIL_PREFIX;
import static com.rebuild.core.service.datareport.TemplateExtractor33.DETAIL_PREFIX2;
import static com.rebuild.core.service.datareport.TemplateExtractor33.NROW_PREFIX2;
/** /**
* V33 * V33
@ -65,7 +68,7 @@ public class EasyExcelGenerator33 extends EasyExcelGenerator {
} }
@Override @Override
protected List<Map<String, Object>> buildData() { protected Map<String, List<Map<String, Object>>> buildData() {
final Entity entity = MetadataHelper.getEntity(recordId.getEntityCode()); final Entity entity = MetadataHelper.getEntity(recordId.getEntityCode());
final TemplateExtractor33 templateExtractor33 = this.buildTemplateExtractor33(); final TemplateExtractor33 templateExtractor33 = this.buildTemplateExtractor33();
@ -82,25 +85,25 @@ public class EasyExcelGenerator33 extends EasyExcelGenerator {
final String varName = e.getKey(); final String varName = e.getKey();
final String fieldName = e.getValue(); final String fieldName = e.getValue();
String refName = null; String refKey = null;
if (varName.startsWith(NROW_PREFIX)) { if (varName.startsWith(NROW_PREFIX) || varName.startsWith(NROW_PREFIX2)) {
if (varName.startsWith(APPROVAL_PREFIX)) { if (varName.startsWith(APPROVAL_PREFIX) || varName.startsWith(APPROVAL_PREFIX2)) {
refName = APPROVAL_PREFIX; refKey = APPROVAL_PREFIX;
} else if (varName.startsWith(DETAIL_PREFIX)) { } else if (varName.startsWith(DETAIL_PREFIX) || varName.startsWith(DETAIL_PREFIX2)) {
refName = DETAIL_PREFIX; refKey = DETAIL_PREFIX;
} else { } else {
// 在客户中导出订单下列 AccountId 为订单中引用客户的引用字段 // 在客户中导出订单下列 AccountId 为订单中引用客户的引用字段
// .AccountId.SalesOrder.SalesOrderName // .AccountId.SalesOrder.SalesOrderName or $AccountId$SalesOrder$SalesOrderName
String[] split = varName.substring(1).split("\\."); String[] split = varName.substring(1).split("[.$]");
if (split.length < 2) throw new ReportsException("Bad REF (Miss .detail prefix?) : " + varName); if (split.length < 2) throw new ReportsException("Bad REF (Miss .detail prefix?) : " + varName);
String refName2 = split[0] + split[1]; String refName2 = split[0] + split[1];
refName = varName.substring(0, refName2.length() + 2 /* dots */); refKey = varName.substring(0, refName2.length() + 2 /* dots */);
} }
Map<String, String> varsMapOfRef = varsMapOfRefs.getOrDefault(refName, new HashMap<>()); Map<String, String> varsMapOfRef = varsMapOfRefs.getOrDefault(refKey, new HashMap<>());
varsMapOfRef.put(varName, fieldName); varsMapOfRef.put(varName, fieldName);
varsMapOfRefs.put(refName, varsMapOfRef); varsMapOfRefs.put(refKey, varsMapOfRef);
} else { } else {
varsMapOfMain.put(varName, fieldName); varsMapOfMain.put(varName, fieldName);
@ -114,22 +117,23 @@ public class EasyExcelGenerator33 extends EasyExcelGenerator {
continue; continue;
} }
if (varName.startsWith(NROW_PREFIX)) { if (varName.startsWith(NROW_PREFIX) || varName.startsWith(NROW_PREFIX2)) {
List<String> fieldsOfRef = fieldsOfRefs.getOrDefault(refName, new ArrayList<>()); List<String> fieldsOfRef = fieldsOfRefs.getOrDefault(refKey, new ArrayList<>());
fieldsOfRef.add(fieldName); fieldsOfRef.add(fieldName);
fieldsOfRefs.put(refName, fieldsOfRef); fieldsOfRefs.put(refKey, fieldsOfRef);
} else { } else {
fieldsOfMain.add(fieldName); fieldsOfMain.add(fieldName);
} }
} }
if (fieldsOfMain.isEmpty() && fieldsOfRefs.isEmpty()) { if (fieldsOfMain.isEmpty() && fieldsOfRefs.isEmpty()) {
return Collections.emptyList(); return Collections.emptyMap();
} }
final List<Map<String, Object>> datas = new ArrayList<>(); final Map<String, List<Map<String, Object>>> datas = new HashMap<>();
final String baseSql = "select %s,%s from %s where %s = ?"; final String baseSql = "select %s,%s from %s where %s = ?";
// 主记录
if (!fieldsOfMain.isEmpty()) { if (!fieldsOfMain.isEmpty()) {
String sql = String.format(baseSql, String sql = String.format(baseSql,
StringUtils.join(fieldsOfMain, ","), StringUtils.join(fieldsOfMain, ","),
@ -140,13 +144,14 @@ public class EasyExcelGenerator33 extends EasyExcelGenerator {
.record(); .record();
Assert.notNull(record, "No record found : " + recordId); Assert.notNull(record, "No record found : " + recordId);
this.hasMain = true; Map<String, Object> d = buildData(record, varsMapOfMain);
datas.add(buildData(record, varsMapOfMain)); datas.put(MDATA_KEY, Collections.singletonList(d));
} }
// 相关记录含明细审批
for (Map.Entry<String, List<String>> e : fieldsOfRefs.entrySet()) { for (Map.Entry<String, List<String>> e : fieldsOfRefs.entrySet()) {
final String refName = e.getKey(); final String refKey = e.getKey();
final boolean isApproval = refName.startsWith(APPROVAL_PREFIX); final boolean isApproval = refKey.startsWith(APPROVAL_PREFIX);
String querySql = baseSql; String querySql = baseSql;
if (isApproval) { if (isApproval) {
@ -154,7 +159,7 @@ public class EasyExcelGenerator33 extends EasyExcelGenerator {
querySql = String.format(querySql, StringUtils.join(e.getValue(), ","), querySql = String.format(querySql, StringUtils.join(e.getValue(), ","),
"createdOn,recordId,state,stepId", "RobotApprovalStep", "recordId"); "createdOn,recordId,state,stepId", "RobotApprovalStep", "recordId");
} else if (refName.startsWith(DETAIL_PREFIX)) { } else if (refKey.startsWith(DETAIL_PREFIX)) {
Entity de = entity.getDetailEntity(); Entity de = entity.getDetailEntity();
String sortField = templateExtractor33.getSortField(DETAIL_PREFIX); String sortField = templateExtractor33.getSortField(DETAIL_PREFIX);
@ -164,11 +169,11 @@ public class EasyExcelGenerator33 extends EasyExcelGenerator {
de.getPrimaryField().getName(), de.getName(), MetadataHelper.getDetailToMainField(de).getName()); de.getPrimaryField().getName(), de.getName(), MetadataHelper.getDetailToMainField(de).getName());
} else { } else {
String[] split = refName.substring(1).split("\\."); String[] split = refKey.substring(1).split("[.$]");
Field ref2Field = MetadataHelper.getField(split[1], split[0]); Field ref2Field = MetadataHelper.getField(split[1], split[0]);
Entity ref2Entity = ref2Field.getOwnEntity(); Entity ref2Entity = ref2Field.getOwnEntity();
String sortField = templateExtractor33.getSortField(refName); String sortField = templateExtractor33.getSortField(refKey);
querySql += " order by " + StringUtils.defaultIfBlank(sortField, "createdOn asc"); querySql += " order by " + StringUtils.defaultIfBlank(sortField, "createdOn asc");
String relatedExpr = split[1] + "." + split[0]; String relatedExpr = split[1] + "." + split[0];
@ -193,13 +198,14 @@ public class EasyExcelGenerator33 extends EasyExcelGenerator {
.add("state", 0) .add("state", 0)
.build(UserService.SYSTEM_USER); .build(UserService.SYSTEM_USER);
List<Record> list2 = new ArrayList<>(); List<Record> listReplace = new ArrayList<>();
list2.add(submit); listReplace.add(submit);
list2.addAll(list); listReplace.addAll(list);
list = list2; list = listReplace;
} }
phNumber = 1; phNumber = 1;
List<Map<String, Object>> refDatas = new ArrayList<>();
for (Record c : list) { for (Record c : list) {
// 特殊处理 // 特殊处理
if (isApproval) { if (isApproval) {
@ -207,10 +213,10 @@ public class EasyExcelGenerator33 extends EasyExcelGenerator {
Date approvedTime = c.getDate("approvedTime"); Date approvedTime = c.getDate("approvedTime");
if (approvedTime == null && state > 1) c.setDate("approvedTime", c.getDate("createdOn")); if (approvedTime == null && state > 1) c.setDate("approvedTime", c.getDate("createdOn"));
} }
refDatas.add(buildData(c, varsMapOfRefs.get(refKey)));
datas.add(buildData(c, varsMapOfRefs.get(refName)));
phNumber++; phNumber++;
} }
datas.put(refKey, refDatas);
} }
return datas; return datas;
@ -273,7 +279,6 @@ public class EasyExcelGenerator33 extends EasyExcelGenerator {
this.templateFile = targetFile; this.templateFile = targetFile;
this.writeSheetAt = newSheetAt; this.writeSheetAt = newSheetAt;
this.recordId = recordId; this.recordId = recordId;
this.hasMain = false;
this.phNumber = 1; this.phNumber = 1;
this.phValues.clear(); this.phValues.clear();

View file

@ -51,7 +51,7 @@ public class EasyExcelListGenerator extends EasyExcelGenerator {
* @see DataListBuilderImpl#getJSONResult() * @see DataListBuilderImpl#getJSONResult()
*/ */
@Override @Override
protected List<Map<String, Object>> buildData() { protected Map<String, List<Map<String, Object>>> buildData() {
Entity entity = MetadataHelper.getEntity(queryData.getString("entity")); Entity entity = MetadataHelper.getEntity(queryData.getString("entity"));
TemplateExtractor varsExtractor = new TemplateExtractor(templateFile, Boolean.TRUE); TemplateExtractor varsExtractor = new TemplateExtractor(templateFile, Boolean.TRUE);
Map<String, String> varsMap = varsExtractor.transformVars(entity); Map<String, String> varsMap = varsExtractor.transformVars(entity);
@ -72,7 +72,7 @@ public class EasyExcelListGenerator extends EasyExcelGenerator {
} }
} }
if (validFields.isEmpty()) return Collections.emptyList(); if (validFields.isEmpty()) return Collections.emptyMap();
queryData.put("fields", validFields); // 使用模板字段 queryData.put("fields", validFields); // 使用模板字段
@ -95,7 +95,7 @@ public class EasyExcelListGenerator extends EasyExcelGenerator {
if (varsMap.containsKey(PH__CURRENTDATE)) phValues.put(PH__CURRENTDATE, getPhValue(PH__CURRENTDATE)); if (varsMap.containsKey(PH__CURRENTDATE)) phValues.put(PH__CURRENTDATE, getPhValue(PH__CURRENTDATE));
if (varsMap.containsKey(PH__CURRENTDATETIME)) phValues.put(PH__CURRENTDATETIME, getPhValue(PH__CURRENTDATETIME)); if (varsMap.containsKey(PH__CURRENTDATETIME)) phValues.put(PH__CURRENTDATETIME, getPhValue(PH__CURRENTDATETIME));
return datas; return Collections.singletonMap(MDATA_KEY, datas);
} }
public int getExportCount() { public int getExportCount() {

View file

@ -20,7 +20,7 @@ import org.apache.commons.lang.StringUtils;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -38,7 +38,7 @@ public class TemplateExtractor {
// 列表即多条记录 // 列表即多条记录
public static final String NROW_PREFIX = "."; public static final String NROW_PREFIX = ".";
// 审批节点字段 // 审批节点字段
protected static final String APPROVAL_PREFIX = NROW_PREFIX + "approval"; public static final String APPROVAL_PREFIX = NROW_PREFIX + "approval";
// 占位 // 占位
public static final String PLACEHOLDER = "__"; public static final String PLACEHOLDER = "__";
@ -137,7 +137,7 @@ public class TemplateExtractor {
protected Set<String> extractVars() { protected Set<String> extractVars() {
List<Cell[]> rows = ExcelUtils.readExcel(templateFile); List<Cell[]> rows = ExcelUtils.readExcel(templateFile);
Set<String> vars = new HashSet<>(); Set<String> vars = new LinkedHashSet<>();
for (Cell[] row : rows) { for (Cell[] row : rows) {
for (Cell cell : row) { for (Cell cell : row) {
if (cell.isEmpty()) continue; if (cell.isEmpty()) continue;

View file

@ -26,7 +26,11 @@ import java.util.Set;
public class TemplateExtractor33 extends TemplateExtractor { public class TemplateExtractor33 extends TemplateExtractor {
// 明细字段 // 明细字段
protected static final String DETAIL_PREFIX = NROW_PREFIX + "detail"; public static final String DETAIL_PREFIX = NROW_PREFIX + "detail";
// $
public static final String NROW_PREFIX2 = "$";
public static final String DETAIL_PREFIX2 = NROW_PREFIX2 + "detail";
public static final String APPROVAL_PREFIX2 = NROW_PREFIX2 + "approval";
// 排序 // 排序
private static final String SORT_ASC = ":asc"; private static final String SORT_ASC = ":asc";
@ -56,14 +60,15 @@ public class TemplateExtractor33 extends TemplateExtractor {
Map<String, String> map = new HashMap<>(); Map<String, String> map = new HashMap<>();
for (final String varName : vars) { for (final String varName : vars) {
// 列表型字段 // 列表型字段
if (varName.startsWith(NROW_PREFIX)) { if (varName.startsWith(NROW_PREFIX) || varName.startsWith(NROW_PREFIX2)) {
final String listField = varName.substring(1).replace("$", "."); final String listField = varName.substring(1).replace("$", ".");
// 占位
if (isPlaceholder(listField)) { if (isPlaceholder(listField)) {
map.put(varName, null); map.put(varName, null);
} }
// 审批流程 // 审批流程
else if (varName.startsWith(APPROVAL_PREFIX)) { else if (varName.startsWith(APPROVAL_PREFIX) || varName.startsWith(APPROVAL_PREFIX2)) {
String stepNodeField = listField.substring(APPROVAL_PREFIX.length()); String stepNodeField = listField.substring(APPROVAL_PREFIX.length());
if (approvalEntity != null && MetadataHelper.getLastJoinField(approvalEntity, stepNodeField) != null) { if (approvalEntity != null && MetadataHelper.getLastJoinField(approvalEntity, stepNodeField) != null) {
map.put(varName, stepNodeField); map.put(varName, stepNodeField);
@ -72,7 +77,7 @@ public class TemplateExtractor33 extends TemplateExtractor {
} }
} }
// 明细实体 // 明细实体
else if (varName.startsWith(DETAIL_PREFIX)) { else if (varName.startsWith(DETAIL_PREFIX) || varName.startsWith(DETAIL_PREFIX2)) {
String detailField = listField.substring(DETAIL_PREFIX.length()); String detailField = listField.substring(DETAIL_PREFIX.length());
detailField = getFieldNameWithSort(DETAIL_PREFIX, detailField); detailField = getFieldNameWithSort(DETAIL_PREFIX, detailField);
@ -124,6 +129,7 @@ public class TemplateExtractor33 extends TemplateExtractor {
hasSort = varFieldName + " desc"; hasSort = varFieldName + " desc";
} }
// 有多个字段排序的其排序顺序取决于字段出现在模板中的位置
if (hasSort != null) { if (hasSort != null) {
String useSorts = sortFields.get(refName); String useSorts = sortFields.get(refName);
if (useSorts != null) useSorts += "," + hasSort; if (useSorts != null) useSorts += "," + hasSort;
@ -151,6 +157,8 @@ public class TemplateExtractor33 extends TemplateExtractor {
* @return * @return
*/ */
public static boolean isPlaceholder(String varName) { public static boolean isPlaceholder(String varName) {
return varName.startsWith(PLACEHOLDER) || varName.contains(NROW_PREFIX + PLACEHOLDER); return varName.startsWith(PLACEHOLDER)
|| varName.contains(NROW_PREFIX + PLACEHOLDER)
|| varName.contains(NROW_PREFIX2 + PLACEHOLDER);
} }
} }

View file

@ -100,12 +100,14 @@ public enum ConfigurationItem {
DingtalkSyncUsers(false), DingtalkSyncUsers(false),
DingtalkSyncUsersRole, DingtalkSyncUsersRole,
DingtalkRobotCode, DingtalkRobotCode,
DingtalkSyncUsersMatch("ID"),
// WxWork // WxWork
WxworkCorpid, WxworkAgentid, WxworkSecret, WxworkCorpid, WxworkAgentid, WxworkSecret,
WxworkRxToken, WxworkRxEncodingAESKey, WxworkRxToken, WxworkRxEncodingAESKey,
WxworkAuthFile, WxworkAuthFile,
WxworkSyncUsers(false), WxworkSyncUsers(false),
WxworkSyncUsersRole, WxworkSyncUsersRole,
WxworkSyncUsersMatch("ID"),
// PORTALs // PORTALs
PortalBaiduMapAk, PortalBaiduMapAk,

View file

@ -223,17 +223,16 @@ public class QueryParser {
// 排序 // 排序
StringBuilder sqlSort = new StringBuilder(" order by ");
String sortNode = queryExpr.getString("sort"); String sortNode = queryExpr.getString("sort");
String sortSql = null;
if (StringUtils.isNotBlank(sortNode)) { if (StringUtils.isNotBlank(sortNode)) {
sqlSort.append(StringUtils.defaultString(parseSort(sortNode), "")); sortSql = parseSort(sortNode);
} else if (entity.containsField(EntityHelper.ModifiedOn)) { } else if (entity.containsField(EntityHelper.ModifiedOn)) {
sqlSort.append(EntityHelper.ModifiedOn + " desc"); sortSql = EntityHelper.ModifiedOn + " desc";
} else if (entity.containsField(EntityHelper.CreatedOn)) { } else if (entity.containsField(EntityHelper.CreatedOn)) {
sqlSort.append(EntityHelper.CreatedOn + " desc"); sortSql = EntityHelper.CreatedOn + " desc";
} }
if (sqlSort.length() >= 14) fullSql.append(sqlSort); if (StringUtils.isNotBlank(sortSql)) fullSql.append(" order by ").append(sortSql);
this.sql = fullSql.toString(); this.sql = fullSql.toString();
this.countSql = this.buildCountSql(pkName) + sqlWhere; this.countSql = this.buildCountSql(pkName) + sqlWhere;

View file

@ -37,6 +37,7 @@ import com.rebuild.web.EntityParam;
import com.rebuild.web.IdParam; import com.rebuild.web.IdParam;
import com.rebuild.web.admin.ConfigCommons; import com.rebuild.web.admin.ConfigCommons;
import com.rebuild.web.commons.FileDownloader; import com.rebuild.web.commons.FileDownloader;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@ -47,6 +48,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -164,7 +166,7 @@ public class ReportTemplateController extends BaseController {
} }
@GetMapping("/report-templates/preview") @GetMapping("/report-templates/preview")
public void preview(@IdParam(required = false) ID reportId, public ModelAndView preview(@IdParam(required = false) ID reportId,
HttpServletRequest request, HttpServletResponse response) throws IOException { HttpServletRequest request, HttpServletResponse response) throws IOException {
final TemplateFile tt; final TemplateFile tt;
// 新建时 // 新建时
@ -183,7 +185,7 @@ public class ReportTemplateController extends BaseController {
Object[] random = Application.createQueryNoFilter(sql).unique(); Object[] random = Application.createQueryNoFilter(sql).unique();
if (random == null) { if (random == null) {
response.sendError(400, Language.L("未找到可供预览的记录")); response.sendError(400, Language.L("未找到可供预览的记录"));
return; return null;
} }
File output; File output;
@ -201,6 +203,12 @@ public class ReportTemplateController extends BaseController {
"com.rebuild.rbv.data.WordReportGenerator#create", tt.templateFile, random[0]); "com.rebuild.rbv.data.WordReportGenerator#create", tt.templateFile, random[0]);
output = word.generate(); output = word.generate();
} }
// HTML5
else if (tt.type == DataReportManager.TYPE_HTML5) {
EasyExcelGenerator33 html5 = (EasyExcelGenerator33) CommonsUtils.invokeMethod(
"com.rebuild.rbv.data.Html5ReportGenerator#create", tt.templateContent, random[0]);
output = html5.generate();
}
// EXCEL // EXCEL
else { else {
output = EasyExcelGenerator.create(tt.templateFile, (ID) random[0], tt.isV33).generate(); output = EasyExcelGenerator.create(tt.templateFile, (ID) random[0], tt.isV33).generate();
@ -208,13 +216,19 @@ public class ReportTemplateController extends BaseController {
} catch (ConfigurationException ex) { } catch (ConfigurationException ex) {
response.sendError(500, ex.getLocalizedMessage()); response.sendError(500, ex.getLocalizedMessage());
return; return null;
} }
RbAssert.is(output != null, Language.L("无法输出报表,请检查报表模板是否有误")); RbAssert.is(output != null, Language.L("无法输出报表,请检查报表模板是否有误"));
String attname = "RBREPORT-PREVIEW." + FileNameUtil.getSuffix(output); String attname = "RBREPORT-PREVIEW";
// v3.6
if (tt.type == DataReportManager.TYPE_HTML5) {
return buildHtml5ModelAndView(output, attname);
}
attname += "." + FileNameUtil.getSuffix(output);
String typeOutput = getParameter(request, "output"); String typeOutput = getParameter(request, "output");
if (PdfConverter.TYPE_PDF.equalsIgnoreCase(typeOutput) || PdfConverter.TYPE_HTML.equalsIgnoreCase(typeOutput)) { if (PdfConverter.TYPE_PDF.equalsIgnoreCase(typeOutput) || PdfConverter.TYPE_HTML.equalsIgnoreCase(typeOutput)) {
output = PdfConverter.convert(output.toPath(), typeOutput).toFile(); output = PdfConverter.convert(output.toPath(), typeOutput).toFile();
@ -222,6 +236,7 @@ public class ReportTemplateController extends BaseController {
} }
FileDownloader.downloadTempFile(response, output, attname); FileDownloader.downloadTempFile(response, output, attname);
return null;
} }
@GetMapping("/report-templates/download") @GetMapping("/report-templates/download")
@ -232,4 +247,18 @@ public class ReportTemplateController extends BaseController {
FileDownloader.setDownloadHeaders(request, response, attname, false); FileDownloader.setDownloadHeaders(request, response, attname, false);
FileDownloader.writeLocalFile(template, response); FileDownloader.writeLocalFile(template, response);
} }
/**
* @param html5
* @param title
* @return
* @throws IOException
*/
public static ModelAndView buildHtml5ModelAndView(File html5, String title) throws IOException {
String content = FileUtils.readFileToString(html5, StandardCharsets.UTF_8);
ModelAndView mv = new ModelAndView("/admin/data/template5-view");
mv.getModelMap().put("reportName", title);
mv.getModelMap().put("reportContent", content);
return mv;
}
} }

View file

@ -40,19 +40,21 @@ import com.rebuild.utils.PdfConverter;
import com.rebuild.utils.RbAssert; import com.rebuild.utils.RbAssert;
import com.rebuild.web.BaseController; import com.rebuild.web.BaseController;
import com.rebuild.web.IdParam; import com.rebuild.web.IdParam;
import com.rebuild.web.admin.data.ReportTemplateController;
import com.rebuild.web.commons.FileDownloader; import com.rebuild.web.commons.FileDownloader;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.Arrays;
import java.util.List;
/** /**
* 报表/导出 * 报表/导出
@ -67,10 +69,10 @@ public class ReportsController extends BaseController {
// 报表模板 // 报表模板
@RequestMapping("report/available") @GetMapping("report/available")
public JSON availableReports(@PathVariable String entity, HttpServletRequest request) { public JSON availableReports(@PathVariable String entity, HttpServletRequest request) {
final ID user = getRequestUser(request); final ID user = getRequestUser(request);
JSONArray res = DataReportManager.instance.getReports( JSONArray res = DataReportManager.instance.getReportTemplates(
MetadataHelper.getEntity(entity), MetadataHelper.getEntity(entity),
getIntParameter(request, "type", DataReportManager.TYPE_RECORD), user); getIntParameter(request, "type", DataReportManager.TYPE_RECORD), user);
@ -84,16 +86,11 @@ public class ReportsController extends BaseController {
} }
@RequestMapping({"report/generate", "report/export"}) @RequestMapping({"report/generate", "report/export"})
public void reportGenerate(@PathVariable String entity, public ModelAndView reportGenerate(@PathVariable String entity,
@IdParam(name = "report") ID reportId, @IdParam(name = "report") ID reportId,
HttpServletRequest request, HttpServletResponse response) throws IOException { HttpServletRequest request, HttpServletResponse response) throws IOException {
String record = getParameterNotNull(request, "record"); final ID[] recordIds = getIdArrayParameterNotNull(request, "record");
List<ID> recordIds = new ArrayList<>(); final ID recordId = recordIds[0];
for (String id : record.split(",")) {
if (ID.isId(id)) recordIds.add(ID.valueOf(id));
}
final ID recordId = recordIds.get(0);
final TemplateFile tt = DataReportManager.instance.getTemplateFile(reportId); final TemplateFile tt = DataReportManager.instance.getTemplateFile(reportId);
File output = null; File output = null;
@ -102,9 +99,15 @@ public class ReportsController extends BaseController {
EasyExcelGenerator33 word = (EasyExcelGenerator33) CommonsUtils.invokeMethod( EasyExcelGenerator33 word = (EasyExcelGenerator33) CommonsUtils.invokeMethod(
"com.rebuild.rbv.data.WordReportGenerator#create", reportId, recordId); "com.rebuild.rbv.data.WordReportGenerator#create", reportId, recordId);
output = word.generate(); output = word.generate();
} else if (tt.type == DataReportManager.TYPE_HTML5) {
EasyExcelGenerator33 html5 = (EasyExcelGenerator33) CommonsUtils.invokeMethod(
"com.rebuild.rbv.data.Html5ReportGenerator#create", reportId, recordId);
output = html5.generate();
} else { } else {
// 支持多个 // 支持多个
output = EasyExcelGenerator.create(reportId, recordIds).generate(); output = EasyExcelGenerator.create(reportId, Arrays.asList(recordIds)).generate();
} }
} catch (ExcelRuntimeException ex) { } catch (ExcelRuntimeException ex) {
@ -113,6 +116,13 @@ public class ReportsController extends BaseController {
RbAssert.is(output != null, Language.L("无法输出报表,请检查报表模板是否有误")); RbAssert.is(output != null, Language.L("无法输出报表,请检查报表模板是否有误"));
String fileName = DataReportManager.getReportName(reportId, recordId, output.getName());
// v3.6
if (tt.type == DataReportManager.TYPE_HTML5) {
return ReportTemplateController.buildHtml5ModelAndView(output, fileName);
}
final String typeOutput = getParameter(request, "output"); final String typeOutput = getParameter(request, "output");
final boolean isHtml = "HTML".equalsIgnoreCase(typeOutput); final boolean isHtml = "HTML".equalsIgnoreCase(typeOutput);
final boolean isPdf = "PDF".equalsIgnoreCase(typeOutput); final boolean isPdf = "PDF".equalsIgnoreCase(typeOutput);
@ -122,8 +132,6 @@ public class ReportsController extends BaseController {
output = PdfConverter.convertHtml(output.toPath()).toFile(); output = PdfConverter.convertHtml(output.toPath()).toFile();
} }
final String fileName = DataReportManager.getReportName(reportId, recordId, output.getName());
if (ServletUtils.isAjaxRequest(request)) { if (ServletUtils.isAjaxRequest(request)) {
JSONObject data = JSONUtils.toJSONObject( JSONObject data = JSONUtils.toJSONObject(
new String[] { "fileKey", "fileName" }, new Object[] { output.getName(), fileName }); new String[] { "fileKey", "fileName" }, new Object[] { output.getName(), fileName });
@ -153,6 +161,7 @@ public class ReportsController extends BaseController {
boolean forcePreview = isHtml || isPdf || getBoolParameter(request, "preview"); boolean forcePreview = isHtml || isPdf || getBoolParameter(request, "preview");
FileDownloader.downloadTempFile(response, output, forcePreview ? FileDownloader.INLINE_FORCE : fileName); FileDownloader.downloadTempFile(response, output, forcePreview ? FileDownloader.INLINE_FORCE : fileName);
} }
return null;
} }
// 列表数据导出 // 列表数据导出

View file

@ -454,7 +454,6 @@
"sudo", "sudo",
"super", "super",
"superuser", "superuser",
"support",
"survey", "survey",
"sync", "sync",
"sysadmin", "sysadmin",
@ -503,7 +502,6 @@
"void", "void",
"vote", "vote",
"webmail", "webmail",
"webmaster",
"website", "website",
"widget", "widget",
"widgets", "widgets",

View file

@ -17,6 +17,10 @@
.badge.badge-info.word { .badge.badge-info.word {
background-color: #307cf1; background-color: #307cf1;
} }
.badge.badge-info.html5 {
background-color: #f16529;
background-color: #e44d26;
}
</style> </style>
</head> </head>
<body> <body>

View file

@ -74,9 +74,17 @@
<button class="btn btn-light btn-sm J_syncUsers up-1" type="button"><i class="icon zmdi zmdi-refresh up-1"></i> [[${bundle.L('立即同步')}]]</button> <button class="btn btn-light btn-sm J_syncUsers up-1" type="button"><i class="icon zmdi zmdi-refresh up-1"></i> [[${bundle.L('立即同步')}]]</button>
</td> </td>
</tr> </tr>
<tr>
<td>[[${bundle.L('用户匹配方式')}]]</td>
<td data-id="DingtalkSyncUsersMatch" th:data-value="${DingtalkSyncUsersMatch}" th:data-form-text="${bundle.L('针对已存在用户,采用何种方式进行匹配,如无匹配则新建')}">
<th:block th:if="${DingtalkSyncUsersMatch == 'ID'}">[[${bundle.L('默认')}]]</th:block>
<th:block th:if="${DingtalkSyncUsersMatch == 'EMAIL'}">[[${bundle.L('邮箱')}]]</th:block>
<th:block th:if="${DingtalkSyncUsersMatch == 'NAME'}">[[${bundle.L('用户名')}]]</th:block>
</td>
</tr>
<tr> <tr>
<td>[[${bundle.L('用户默认角色')}]]</td> <td>[[${bundle.L('用户默认角色')}]]</td>
<td data-id="DingtalkSyncUsersRole" th:data-value="${DingtalkSyncUsersRole}" th:data-form-text="${bundle.L('如不指定新同步的用户将不可用,直到你为他们指定了角色')}"> <td data-id="DingtalkSyncUsersRole" th:data-value="${DingtalkSyncUsersRole}" th:data-form-text="${bundle.L('如不指定,新建用户将不可用,直到你为他们指定了角色')}">
[[${DingtalkSyncUsersRoleLabel ?:bundle.L('无')}]] [[${DingtalkSyncUsersRoleLabel ?:bundle.L('无')}]]
</td> </td>
</tr> </tr>

View file

@ -74,9 +74,17 @@
<button class="btn btn-light btn-sm J_syncUsers up-1" type="button"><i class="icon zmdi zmdi-refresh up-1"></i> [[${bundle.L('立即同步')}]]</button> <button class="btn btn-light btn-sm J_syncUsers up-1" type="button"><i class="icon zmdi zmdi-refresh up-1"></i> [[${bundle.L('立即同步')}]]</button>
</td> </td>
</tr> </tr>
<tr>
<td>[[${bundle.L('用户匹配方式')}]]</td>
<td data-id="WxworkSyncUsersMatch" th:data-value="${WxworkSyncUsersMatch}" th:data-form-text="${bundle.L('针对已存在用户,采用何种方式进行匹配,如无匹配则新建')}">
<th:block th:if="${WxworkSyncUsersMatch == 'ID'}">[[${bundle.L('默认')}]]</th:block>
<th:block th:if="${WxworkSyncUsersMatch == 'EMAIL'}">[[${bundle.L('邮箱')}]]</th:block>
<th:block th:if="${WxworkSyncUsersMatch == 'NAME'}">[[${bundle.L('用户名')}]]</th:block>
</td>
</tr>
<tr> <tr>
<td>[[${bundle.L('用户默认角色')}]]</td> <td>[[${bundle.L('用户默认角色')}]]</td>
<td data-id="WxworkSyncUsersRole" th:data-value="${WxworkSyncUsersRole}" th:data-form-text="${bundle.L('如不指定新同步的用户将不可用,直到你为他们指定了角色')}"> <td data-id="WxworkSyncUsersRole" th:data-value="${WxworkSyncUsersRole}" th:data-form-text="${bundle.L('如不指定,新建用户将不可用,直到你为他们指定了角色')}">
[[${WxworkSyncUsersRoleLabel ?:bundle.L('无')}]] [[${WxworkSyncUsersRoleLabel ?:bundle.L('无')}]]
</td> </td>
</tr> </tr>

View file

@ -54,33 +54,38 @@ See LICENSE and COMMERCIAL in the project root for license information.
display: none; display: none;
} }
.chart-box .chart-head .chart-oper a { .chart-box .chart-head .chart-oper > a {
color: #000; color: #000;
opacity: 0.4; opacity: 0.4;
display: inline-block; display: inline-block;
height: 20px; height: 20px;
margin-left: 10px; margin-left: 5px;
padding-left: 3px;
} }
.chart-box .chart-head .chart-oper a:hover { .chart-box .chart-head .chart-oper > a:hover {
opacity: 0.7; opacity: 0.7;
} }
.chart-box .chart-head .chart-oper a .zmdi { .chart-box .chart-head .chart-oper > a .zmdi {
font-size: 1.3rem; font-size: 1.3rem;
vertical-align: middle; vertical-align: middle;
width: 20px;
text-align: center;
} }
.chart-box .chart-head .chart-oper a .zmdi-close { .chart-box .chart-head .chart-oper > a .zmdi-close {
font-size: 1.45rem; font-size: 1.45rem;
} }
.chart-box .chart-head .chart-oper a .zmdi-fullscreen, .chart-box .chart-head .chart-oper > a .zmdi-fullscreen,
.chart-box .chart-head .chart-oper a .zmdi-fullscreen-exit { .chart-box .chart-head .chart-oper > a .zmdi-fullscreen-exit {
font-size: 1.43rem; font-size: 1.43rem;
} }
.chart-box .chart-head .chart-oper .dropdown-menu {
min-width: 140px;
}
.chart-undata { .chart-undata {
margin: 0; margin: 0;
color: #999; color: #999;
@ -271,6 +276,10 @@ See LICENSE and COMMERCIAL in the project root for license information.
top: 0; top: 0;
} }
.chart-box.ApprovalList .progress-wrap .progress-bar {
min-width: 20px;
}
.chart-box.ApprovalList .progress-wrap .progress-bar:hover { .chart-box.ApprovalList .progress-wrap .progress-bar:hover {
cursor: pointer; cursor: pointer;
opacity: 0.8; opacity: 0.8;

View file

@ -147,9 +147,9 @@ code {
border-color: transparent; border-color: transparent;
border-style: solid; border-style: solid;
border-width: 5px 0 5px 5px; border-width: 5px 0 5px 5px;
border-left-color: #ddd; border-left-color: #ccc;
margin-top: 4px; margin-top: 4px;
margin-right: -10px; margin-right: -8px;
} }
.dropdown-submenu:hover > a::after { .dropdown-submenu:hover > a::after {

View file

@ -27,7 +27,7 @@ class ConfigFormDlg extends RbFormHandler {
<div className="form-group row footer"> <div className="form-group row footer">
<div className="col-sm-7 offset-sm-3" ref={(c) => (this._btns = c)}> <div className="col-sm-7 offset-sm-3" ref={(c) => (this._btns = c)}>
<button className="btn btn-primary" type="button" onClick={this.confirm}> <button className="btn btn-primary" type="button" onClick={this.confirm}>
{$L('确定')} {this.confirmText || $L('确定')}
</button> </button>
<a className="btn btn-link" onClick={this.hide}> <a className="btn btn-link" onClick={this.hide}>
{$L('取消')} {$L('取消')}

View file

@ -8,7 +8,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
$(document).ready(function () { $(document).ready(function () {
$('.J_add').on('click', () => renderRbcomp(<ReportEditor />)) $('.J_add').on('click', () => renderRbcomp(<ReportEditor />))
// $('.J_add-html5').on('click', () => renderRbcomp(<ReportEditor isHtml5 />)) $('.J_add-html5').on('click', () => renderRbcomp(<ReportEditor isHtml5 />))
renderRbcomp(<ReportList />, 'dataList') renderRbcomp(<ReportList />, 'dataList')
}) })
@ -31,7 +31,7 @@ class ReportList extends ConfigList {
<tr key={item[0]}> <tr key={item[0]}>
<td> <td>
{isHtml5 ? ( {isHtml5 ? (
<a title={$L('在线模板编辑')} href={`report-template/design?id=${item[0]}`}> <a title={$L('在线模板编辑')} href={`report-template/design?id=${item[0]}`}>
{item[3]} {item[3]}
</a> </a>
) : ( ) : (
@ -39,7 +39,7 @@ class ReportList extends ConfigList {
)} )}
{item[6] === 1 && <span className="badge badge-info badge-arrow3 badge-sm ml-1 excel">EXCEL</span>} {item[6] === 1 && <span className="badge badge-info badge-arrow3 badge-sm ml-1 excel">EXCEL</span>}
{item[6] === 2 && <span className="badge badge-info badge-arrow3 badge-sm ml-1 excel">{$L('EXCEL 列表')}</span>} {item[6] === 2 && <span className="badge badge-info badge-arrow3 badge-sm ml-1 excel">{$L('EXCEL 列表')}</span>}
{isHtml5 && <span className="badge badge-info badge-arrow3 badge-sm ml-1">{$L('在线模板')}</span>} {isHtml5 && <span className="badge badge-info badge-arrow3 badge-sm ml-1 html5">{$L('在线模板')}</span>}
{item[6] === 4 && <span className="badge badge-info badge-arrow3 badge-sm ml-1 word">WORD</span>} {item[6] === 4 && <span className="badge badge-info badge-arrow3 badge-sm ml-1 word">WORD</span>}
{outputType.includes('pdf') && <span className="badge badge-secondary badge-sm ml-1">PDF</span>} {outputType.includes('pdf') && <span className="badge badge-secondary badge-sm ml-1">PDF</span>}
@ -97,6 +97,7 @@ class ReportEditor extends ConfigFormDlg {
constructor(props) { constructor(props) {
super(props) super(props)
this.subtitle = $L('报表模板') this.subtitle = $L('报表模板')
this.confirmText = this.props.isHtml5 && !this.props.id ? $L('下一步') : null
this.hasDetail = true this.hasDetail = true
} }

View file

@ -19,26 +19,35 @@ class BaseChart extends React.Component {
const opActions = ( const opActions = (
<div className="chart-oper"> <div className="chart-oper">
{!this.props.builtin && ( {!this.props.builtin && (
<a className="J_view-source" title={$L('查看来源数据')} href={`${rb.baseUrl}/dashboard/view-chart-source?id=${this.props.id}`} target="_blank"> <a title={$L('查看来源数据')} href={`${rb.baseUrl}/dashboard/view-chart-source?id=${this.props.id}`} target="_blank">
<i className="zmdi zmdi-rss" /> <i className="zmdi zmdi-rss" />
</a> </a>
)} )}
<a title={$L('刷新')} onClick={() => this.loadChartData()}> <a title={$L('刷新')} onClick={() => this.loadChartData()}>
<i className="zmdi zmdi-refresh" /> <i className="zmdi zmdi-refresh" />
</a> </a>
<a className="J_fullscreen d-none d-md-inline-block" title={$L('全屏')} onClick={() => this.toggleFullscreen()}> <a className="d-none d-md-inline-block" title={$L('全屏')} onClick={() => this.toggleFullscreen()}>
<i className={`zmdi zmdi-${this.state.fullscreen ? 'fullscreen-exit' : 'fullscreen'}`} /> <i className={`zmdi zmdi-${this.state.fullscreen ? 'fullscreen-exit' : 'fullscreen'}`} />
</a> </a>
{this.props.isManageable && !this.props.builtin && (
<a className="J_chart-edit d-none d-md-inline-block" title={$L('编辑')} href={`${rb.baseUrl}/dashboard/chart-design?id=${this.props.id}`}> <a className="d-none d-md-inline-block" data-toggle="dropdown">
<i className="zmdi zmdi-edit" /> <i className="icon zmdi zmdi-more-vert" style={{ width: 16 }} />
</a>
<div className="dropdown-menu dropdown-menu-right">
{this.props.isManageable && !this.props.builtin && (
<a className="dropdown-item J_chart-edit" title={$L('编辑')} href={`${rb.baseUrl}/dashboard/chart-design?id=${this.props.id}`}>
{$L('编辑')}
</a>
)}
{this.props.editable && (
<a className="dropdown-item" title={$L('移除')} onClick={() => this.remove()}>
{$L('移除')}
</a>
)}
<a className="dropdown-item" title={$L('导出')} onClick={() => this.export()}>
{$L('导出')}
</a> </a>
)} </div>
{this.props.editable && (
<a title={$L('移除')} onClick={() => this.remove()}>
<i className="zmdi zmdi-close" />
</a>
)}
</div> </div>
) )
@ -119,6 +128,51 @@ class BaseChart extends React.Component {
}) })
} }
export() {
if (this._echarts) {
const base64 = this._echarts.getDataURL({
type: 'png',
pixelRatio: 2,
backgroundColor: '#fff',
})
const $a = document.createElement('a')
$a.href = base64
$a.download = `${this.state.title}.png`
$a.click()
} else {
const table = $(this._$body).find('table.table')[0]
if (table) {
this._exportTable(table)
} else {
RbHighbar.createl('该图表暂不支持导出')
}
}
}
_exportTable(table) {
function reLinks(table, a, b) {
$(table)
.find('a')
.each(function () {
$(this)
.attr(a, `${$(this).attr(b)}`)
.removeAttr(b)
})
}
// Remove
reLinks(table, '__href', 'href')
// https://docs.sheetjs.com/docs/api/utilities/html#html-table-input
// https://docs.sheetjs.com/docs/api/write-options
const wb = window.XLSX.utils.table_to_book(table, { raw: true })
window.XLSX.writeFile(wb, `${this.state.title}.xls`)
// Add
setTimeout(() => reLinks(table, 'href', '__href'), 500)
}
renderError(msg, cb) { renderError(msg, cb) {
this.setState({ chartdata: <div className="chart-undata must-center">{msg || $L('加载失败')}</div> }, cb) this.setState({ chartdata: <div className="chart-undata must-center">{msg || $L('加载失败')}</div> }, cb)
} }
@ -195,16 +249,7 @@ class ChartTable extends BaseChart {
.css('height', $tb.height() - 20) .css('height', $tb.height() - 20)
.perfectScrollbar() .perfectScrollbar()
// let tdActive = null // selected
// const $els = $tb.find('tbody td').on('mousedown', function () {
// if (tdActive === this) {
// $(this).toggleClass('highlight')
// return
// }
// tdActive = this
// $els.removeClass('highlight')
// $(this).addClass('highlight')
// })
$tb.find('table').tableCellsSelection() $tb.find('table').tableCellsSelection()
if (window.render_preview_chart) { if (window.render_preview_chart) {

View file

@ -1091,7 +1091,7 @@ class RbList extends React.Component {
}, 400) }, 400)
if (query.filter && (query.filter.items || []).length > 0) { if (query.filter && (query.filter.items || []).length > 0) {
console.log('API Filter <Body> :\n', JSON.stringify(query.filter)) console.log(`API Filter <Body> :\n %c${JSON.stringify(query.filter)}`, 'color:#e83e8c;font-size:16px')
} }
$.post(`/app/${this._entity}/data-list`, JSON.stringify(RbList.queryBefore(query)), (res) => { $.post(`/app/${this._entity}/data-list`, JSON.stringify(RbList.queryBefore(query)), (res) => {

View file

@ -8,7 +8,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
/* !!! KEEP IT ES5 COMPATIBLE !!! */ /* !!! KEEP IT ES5 COMPATIBLE !!! */
// GA // GA
(function () { ;(function () {
var gaScript = document.createElement('script') var gaScript = document.createElement('script')
gaScript.src = 'https://www.googletagmanager.com/gtag/js?id=G-ZCZHJPMEG7' gaScript.src = 'https://www.googletagmanager.com/gtag/js?id=G-ZCZHJPMEG7'
gaScript.async = true gaScript.async = true
@ -207,18 +207,18 @@ $(function () {
// } // }
}) })
var $addResizeHandler__calls = [] var $addResizeHandler__cbs = []
/** /**
* 窗口 RESIZE 回调 * 窗口 RESIZE 回调
*/ */
var $addResizeHandler = function (call) { var $addResizeHandler = function (callback) {
typeof call === 'function' && $addResizeHandler__calls && $addResizeHandler__calls.push(call) typeof callback === 'function' && $addResizeHandler__cbs && $addResizeHandler__cbs.push(callback)
return function () { return function () {
if (!$addResizeHandler__calls || $addResizeHandler__calls.length === 0) return if (!$addResizeHandler__cbs || $addResizeHandler__cbs.length === 0) return
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
if (rb.env === 'dev') console.log('Calls ' + $addResizeHandler__calls.length + ' handlers of resize ...') if (rb.env === 'dev') console.log('Callbacks ' + $addResizeHandler__cbs.length + ' handlers of resize ...')
$addResizeHandler__calls.forEach(function (call) { $addResizeHandler__cbs.forEach(function (cb) {
call() cb()
}) })
} }
} }
@ -1019,7 +1019,7 @@ var $useMap = function (onLoad) {
} }
// 自动定位有误差 // 自动定位有误差
var $autoLocation = function (call) { var $autoLocation = function (callback) {
$useMap(function () { $useMap(function () {
var geo = new window.BMapGL.Geolocation() var geo = new window.BMapGL.Geolocation()
geo.enableSDKLocation() geo.enableSDKLocation()
@ -1032,7 +1032,7 @@ var $autoLocation = function (call) {
lng: e.longitude, lng: e.longitude,
text: r ? r.address : null, text: r ? r.address : null,
} }
typeof call === 'function' && call(v) typeof callback === 'function' && callback(v)
}) })
} else { } else {
console.log('Geolocation failed :', this.getStatus()) console.log('Geolocation failed :', this.getStatus())

File diff suppressed because one or more lines are too long

View file

@ -73,6 +73,7 @@
<script th:src="@{/assets/lib/charts/gridstack.all.js}"></script> <script th:src="@{/assets/lib/charts/gridstack.all.js}"></script>
<script th:src="@{/assets/lib/charts/echarts.min.js}"></script> <script th:src="@{/assets/lib/charts/echarts.min.js}"></script>
<script th:src="@{/assets/lib/charts/tablecellsselection.js}"></script> <script th:src="@{/assets/lib/charts/tablecellsselection.js}"></script>
<script th:src="@{/assets/lib/charts/xlsx.full.min.js}"></script>
<script th:src="@{/assets/js/charts/charts.js}" type="text/babel"></script> <script th:src="@{/assets/js/charts/charts.js}" type="text/babel"></script>
<th:block th:replace="~{/_include/forms}" /> <th:block th:replace="~{/_include/forms}" />
<script th:src="@{/assets/js/charts/dashboard.js}" type="text/babel"></script> <script th:src="@{/assets/js/charts/dashboard.js}" type="text/babel"></script>

View file

@ -41,7 +41,7 @@
</script> </script>
<script type="text/babel"> <script type="text/babel">
// clear // clear
window.$addResizeHandler__calls = [] window.$addResizeHandler__cbs = []
$(document).ready(function () { $(document).ready(function () {
// 无关闭按钮 // 无关闭按钮