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)
*/
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
* @return
*/
public JSONArray getReports(Entity entity, int type, ID user) {
public JSONArray getReportTemplates(Entity entity, int type, ID user) {
JSONArray alist = new JSONArray();
for (ConfigBean e : getReportsRaw(entity)) {
if (e.getBoolean("disabled")) continue;
@ -66,7 +66,8 @@ public class DataReportManager implements ConfigManager {
if (type == DataReportManager.TYPE_LIST) {
can = aType == type;
} 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) {
@ -210,10 +211,12 @@ public class DataReportManager implements ConfigManager {
name = ContentWithFieldVars.replaceWithRecord(name, (ID) idOrEntity);
}
// suffix
// Suffix
if (fileName.endsWith(".pdf")) name += ".pdf";
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;
}
}

View file

@ -17,6 +17,7 @@ import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
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.JSONArray;
import com.rebuild.core.Application;
@ -55,7 +56,6 @@ import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
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__NUMBER;
import static com.rebuild.core.service.datareport.TemplateExtractor.PLACEHOLDER;
import static com.rebuild.core.service.datareport.TemplateExtractor33.NROW_PREFIX2;
/**
* 报表生成 easyexcel
@ -80,11 +81,12 @@ import static com.rebuild.core.service.datareport.TemplateExtractor.PLACEHOLDER;
@Slf4j
public class EasyExcelGenerator extends SetUser {
final protected static String MDATA_KEY = ".";
protected File templateFile;
protected Integer writeSheetAt = null;
protected ID recordId;
protected boolean hasMain = false;
protected int phNumber = 1;
protected Map<String, Object> phValues = new HashMap<>();
@ -93,7 +95,7 @@ public class EasyExcelGenerator extends SetUser {
* @param recordId
*/
protected EasyExcelGenerator(File template, ID recordId) {
this.templateFile = getFixTemplate(template);
this.templateFile = template;
this.recordId = recordId;
}
@ -105,14 +107,13 @@ public class EasyExcelGenerator extends SetUser {
public File generate() {
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("暂无数据"));
Map<String, Object> main = null;
if (this.hasMain) {
Iterator<Map<String, Object>> iter = datas.iterator();
main = iter.next();
iter.remove();
if (datas.containsKey(MDATA_KEY)) {
main = datas.get(MDATA_KEY).get(0);
datas.remove(MDATA_KEY);
}
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
@ -123,10 +124,29 @@ public class EasyExcelGenerator extends SetUser {
.registerWriteHandler(new FormulaCellWriteHandler())
.build();
// 明细记录
if (!datas.isEmpty()) {
// 引用记录
if (datas.size() == 1) {
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) {
@ -153,9 +173,9 @@ public class EasyExcelGenerator extends SetUser {
protected File getTargetFile() {
String suffix = "xls";
if (templateFile.getName().endsWith(".xlsx")) suffix = "xlsx";
if (templateFile.getName().endsWith(".html")) suffix = "html";
if (templateFile.getName().endsWith(".docx")) suffix = "docx";
if (templateFile.getName().endsWith(".doc")) suffix = "doc";
else if (templateFile.getName().endsWith(".html")) suffix = "html";
else if (templateFile.getName().endsWith(".docx")) suffix = "docx";
else if (templateFile.getName().endsWith(".doc")) suffix = "doc";
return RebuildConfiguration.getFileOfTemp(String.format("RBREPORT-%d.%s", System.currentTimeMillis(), suffix));
}
@ -165,7 +185,7 @@ public class EasyExcelGenerator extends SetUser {
*
* @return 第一个为主记录若有
*/
protected List<Map<String, Object>> buildData() {
protected Map<String, List<Map<String, Object>>> buildData() {
Entity entity = MetadataHelper.getEntity(recordId.getEntityCode());
TemplateExtractor templateExtractor = new TemplateExtractor(templateFile);
@ -213,10 +233,10 @@ public class EasyExcelGenerator extends SetUser {
}
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 = ?";
if (!fieldsOfMain.isEmpty()) {
@ -229,8 +249,8 @@ public class EasyExcelGenerator extends SetUser {
.record();
Assert.notNull(record, "No record found : " + recordId);
datas.add(buildData(record, varsMapOfMain));
this.hasMain = true;
Map<String, Object> d = buildData(record, varsMapOfMain);
datas.put(MDATA_KEY, Collections.singletonList(d));
}
// 明细
@ -246,10 +266,12 @@ public class EasyExcelGenerator extends SetUser {
.list();
phNumber = 1;
List<Map<String, Object>> detailList = new ArrayList<>();
for (Record c : list) {
datas.add(buildData(c, varsMapOfDetail));
detailList.add(buildData(c, varsMapOfDetail));
phNumber++;
}
datas.put(MDATA_KEY + "detail", detailList);
}
// 审批
@ -263,10 +285,12 @@ public class EasyExcelGenerator extends SetUser {
.list();
phNumber = 1;
List<Map<String, Object>> approvalList = new ArrayList<>();
for (Record c : list) {
datas.add(buildData(c, varsMapOfApproval));
approvalList.add(buildData(c, varsMapOfApproval));
phNumber++;
}
datas.put(MDATA_KEY + "approval", approvalList);
}
return datas;
@ -465,62 +489,6 @@ public class EasyExcelGenerator extends SetUser {
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.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_PREFIX2;
import static com.rebuild.core.service.datareport.TemplateExtractor33.NROW_PREFIX2;
/**
* V33
@ -65,7 +68,7 @@ public class EasyExcelGenerator33 extends EasyExcelGenerator {
}
@Override
protected List<Map<String, Object>> buildData() {
protected Map<String, List<Map<String, Object>>> buildData() {
final Entity entity = MetadataHelper.getEntity(recordId.getEntityCode());
final TemplateExtractor33 templateExtractor33 = this.buildTemplateExtractor33();
@ -82,25 +85,25 @@ public class EasyExcelGenerator33 extends EasyExcelGenerator {
final String varName = e.getKey();
final String fieldName = e.getValue();
String refName = null;
if (varName.startsWith(NROW_PREFIX)) {
if (varName.startsWith(APPROVAL_PREFIX)) {
refName = APPROVAL_PREFIX;
} else if (varName.startsWith(DETAIL_PREFIX)) {
refName = DETAIL_PREFIX;
String refKey = null;
if (varName.startsWith(NROW_PREFIX) || varName.startsWith(NROW_PREFIX2)) {
if (varName.startsWith(APPROVAL_PREFIX) || varName.startsWith(APPROVAL_PREFIX2)) {
refKey = APPROVAL_PREFIX;
} else if (varName.startsWith(DETAIL_PREFIX) || varName.startsWith(DETAIL_PREFIX2)) {
refKey = DETAIL_PREFIX;
} else {
// 在客户中导出订单下列 AccountId 为订单中引用客户的引用字段
// .AccountId.SalesOrder.SalesOrderName
String[] split = varName.substring(1).split("\\.");
// .AccountId.SalesOrder.SalesOrderName or $AccountId$SalesOrder$SalesOrderName
String[] split = varName.substring(1).split("[.$]");
if (split.length < 2) throw new ReportsException("Bad REF (Miss .detail prefix?) : " + varName);
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);
varsMapOfRefs.put(refName, varsMapOfRef);
varsMapOfRefs.put(refKey, varsMapOfRef);
} else {
varsMapOfMain.put(varName, fieldName);
@ -114,22 +117,23 @@ public class EasyExcelGenerator33 extends EasyExcelGenerator {
continue;
}
if (varName.startsWith(NROW_PREFIX)) {
List<String> fieldsOfRef = fieldsOfRefs.getOrDefault(refName, new ArrayList<>());
if (varName.startsWith(NROW_PREFIX) || varName.startsWith(NROW_PREFIX2)) {
List<String> fieldsOfRef = fieldsOfRefs.getOrDefault(refKey, new ArrayList<>());
fieldsOfRef.add(fieldName);
fieldsOfRefs.put(refName, fieldsOfRef);
fieldsOfRefs.put(refKey, fieldsOfRef);
} else {
fieldsOfMain.add(fieldName);
}
}
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 = ?";
// 主记录
if (!fieldsOfMain.isEmpty()) {
String sql = String.format(baseSql,
StringUtils.join(fieldsOfMain, ","),
@ -140,13 +144,14 @@ public class EasyExcelGenerator33 extends EasyExcelGenerator {
.record();
Assert.notNull(record, "No record found : " + recordId);
this.hasMain = true;
datas.add(buildData(record, varsMapOfMain));
Map<String, Object> d = buildData(record, varsMapOfMain);
datas.put(MDATA_KEY, Collections.singletonList(d));
}
// 相关记录含明细审批
for (Map.Entry<String, List<String>> e : fieldsOfRefs.entrySet()) {
final String refName = e.getKey();
final boolean isApproval = refName.startsWith(APPROVAL_PREFIX);
final String refKey = e.getKey();
final boolean isApproval = refKey.startsWith(APPROVAL_PREFIX);
String querySql = baseSql;
if (isApproval) {
@ -154,7 +159,7 @@ public class EasyExcelGenerator33 extends EasyExcelGenerator {
querySql = String.format(querySql, StringUtils.join(e.getValue(), ","),
"createdOn,recordId,state,stepId", "RobotApprovalStep", "recordId");
} else if (refName.startsWith(DETAIL_PREFIX)) {
} else if (refKey.startsWith(DETAIL_PREFIX)) {
Entity de = entity.getDetailEntity();
String sortField = templateExtractor33.getSortField(DETAIL_PREFIX);
@ -164,11 +169,11 @@ public class EasyExcelGenerator33 extends EasyExcelGenerator {
de.getPrimaryField().getName(), de.getName(), MetadataHelper.getDetailToMainField(de).getName());
} else {
String[] split = refName.substring(1).split("\\.");
String[] split = refKey.substring(1).split("[.$]");
Field ref2Field = MetadataHelper.getField(split[1], split[0]);
Entity ref2Entity = ref2Field.getOwnEntity();
String sortField = templateExtractor33.getSortField(refName);
String sortField = templateExtractor33.getSortField(refKey);
querySql += " order by " + StringUtils.defaultIfBlank(sortField, "createdOn asc");
String relatedExpr = split[1] + "." + split[0];
@ -193,13 +198,14 @@ public class EasyExcelGenerator33 extends EasyExcelGenerator {
.add("state", 0)
.build(UserService.SYSTEM_USER);
List<Record> list2 = new ArrayList<>();
list2.add(submit);
list2.addAll(list);
list = list2;
List<Record> listReplace = new ArrayList<>();
listReplace.add(submit);
listReplace.addAll(list);
list = listReplace;
}
phNumber = 1;
List<Map<String, Object>> refDatas = new ArrayList<>();
for (Record c : list) {
// 特殊处理
if (isApproval) {
@ -207,10 +213,10 @@ public class EasyExcelGenerator33 extends EasyExcelGenerator {
Date approvedTime = c.getDate("approvedTime");
if (approvedTime == null && state > 1) c.setDate("approvedTime", c.getDate("createdOn"));
}
datas.add(buildData(c, varsMapOfRefs.get(refName)));
refDatas.add(buildData(c, varsMapOfRefs.get(refKey)));
phNumber++;
}
datas.put(refKey, refDatas);
}
return datas;
@ -273,7 +279,6 @@ public class EasyExcelGenerator33 extends EasyExcelGenerator {
this.templateFile = targetFile;
this.writeSheetAt = newSheetAt;
this.recordId = recordId;
this.hasMain = false;
this.phNumber = 1;
this.phValues.clear();

View file

@ -51,7 +51,7 @@ public class EasyExcelListGenerator extends EasyExcelGenerator {
* @see DataListBuilderImpl#getJSONResult()
*/
@Override
protected List<Map<String, Object>> buildData() {
protected Map<String, List<Map<String, Object>>> buildData() {
Entity entity = MetadataHelper.getEntity(queryData.getString("entity"));
TemplateExtractor varsExtractor = new TemplateExtractor(templateFile, Boolean.TRUE);
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); // 使用模板字段
@ -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__CURRENTDATETIME)) phValues.put(PH__CURRENTDATETIME, getPhValue(PH__CURRENTDATETIME));
return datas;
return Collections.singletonMap(MDATA_KEY, datas);
}
public int getExportCount() {

View file

@ -20,7 +20,7 @@ import org.apache.commons.lang.StringUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -38,7 +38,7 @@ public class TemplateExtractor {
// 列表即多条记录
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 = "__";
@ -137,7 +137,7 @@ public class TemplateExtractor {
protected Set<String> extractVars() {
List<Cell[]> rows = ExcelUtils.readExcel(templateFile);
Set<String> vars = new HashSet<>();
Set<String> vars = new LinkedHashSet<>();
for (Cell[] row : rows) {
for (Cell cell : row) {
if (cell.isEmpty()) continue;

View file

@ -26,7 +26,11 @@ import java.util.Set;
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";
@ -56,14 +60,15 @@ public class TemplateExtractor33 extends TemplateExtractor {
Map<String, String> map = new HashMap<>();
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("$", ".");
// 占位
if (isPlaceholder(listField)) {
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());
if (approvalEntity != null && MetadataHelper.getLastJoinField(approvalEntity, stepNodeField) != null) {
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());
detailField = getFieldNameWithSort(DETAIL_PREFIX, detailField);
@ -124,6 +129,7 @@ public class TemplateExtractor33 extends TemplateExtractor {
hasSort = varFieldName + " desc";
}
// 有多个字段排序的其排序顺序取决于字段出现在模板中的位置
if (hasSort != null) {
String useSorts = sortFields.get(refName);
if (useSorts != null) useSorts += "," + hasSort;
@ -151,6 +157,8 @@ public class TemplateExtractor33 extends TemplateExtractor {
* @return
*/
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),
DingtalkSyncUsersRole,
DingtalkRobotCode,
DingtalkSyncUsersMatch("ID"),
// WxWork
WxworkCorpid, WxworkAgentid, WxworkSecret,
WxworkRxToken, WxworkRxEncodingAESKey,
WxworkAuthFile,
WxworkSyncUsers(false),
WxworkSyncUsersRole,
WxworkSyncUsersMatch("ID"),
// PORTALs
PortalBaiduMapAk,

View file

@ -223,17 +223,16 @@ public class QueryParser {
// 排序
StringBuilder sqlSort = new StringBuilder(" order by ");
String sortNode = queryExpr.getString("sort");
String sortSql = null;
if (StringUtils.isNotBlank(sortNode)) {
sqlSort.append(StringUtils.defaultString(parseSort(sortNode), ""));
sortSql = parseSort(sortNode);
} else if (entity.containsField(EntityHelper.ModifiedOn)) {
sqlSort.append(EntityHelper.ModifiedOn + " desc");
sortSql = EntityHelper.ModifiedOn + " desc";
} 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.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.admin.ConfigCommons;
import com.rebuild.web.commons.FileDownloader;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@ -47,6 +48,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@ -164,7 +166,7 @@ public class ReportTemplateController extends BaseController {
}
@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 {
final TemplateFile tt;
// 新建时
@ -183,7 +185,7 @@ public class ReportTemplateController extends BaseController {
Object[] random = Application.createQueryNoFilter(sql).unique();
if (random == null) {
response.sendError(400, Language.L("未找到可供预览的记录"));
return;
return null;
}
File output;
@ -201,6 +203,12 @@ public class ReportTemplateController extends BaseController {
"com.rebuild.rbv.data.WordReportGenerator#create", tt.templateFile, random[0]);
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
else {
output = EasyExcelGenerator.create(tt.templateFile, (ID) random[0], tt.isV33).generate();
@ -208,13 +216,19 @@ public class ReportTemplateController extends BaseController {
} catch (ConfigurationException ex) {
response.sendError(500, ex.getLocalizedMessage());
return;
return null;
}
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");
if (PdfConverter.TYPE_PDF.equalsIgnoreCase(typeOutput) || PdfConverter.TYPE_HTML.equalsIgnoreCase(typeOutput)) {
output = PdfConverter.convert(output.toPath(), typeOutput).toFile();
@ -222,6 +236,7 @@ public class ReportTemplateController extends BaseController {
}
FileDownloader.downloadTempFile(response, output, attname);
return null;
}
@GetMapping("/report-templates/download")
@ -232,4 +247,18 @@ public class ReportTemplateController extends BaseController {
FileDownloader.setDownloadHeaders(request, response, attname, false);
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.web.BaseController;
import com.rebuild.web.IdParam;
import com.rebuild.web.admin.data.ReportTemplateController;
import com.rebuild.web.commons.FileDownloader;
import lombok.extern.slf4j.Slf4j;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;
/**
* 报表/导出
@ -67,10 +69,10 @@ public class ReportsController extends BaseController {
// 报表模板
@RequestMapping("report/available")
@GetMapping("report/available")
public JSON availableReports(@PathVariable String entity, HttpServletRequest request) {
final ID user = getRequestUser(request);
JSONArray res = DataReportManager.instance.getReports(
JSONArray res = DataReportManager.instance.getReportTemplates(
MetadataHelper.getEntity(entity),
getIntParameter(request, "type", DataReportManager.TYPE_RECORD), user);
@ -84,16 +86,11 @@ public class ReportsController extends BaseController {
}
@RequestMapping({"report/generate", "report/export"})
public void reportGenerate(@PathVariable String entity,
public ModelAndView reportGenerate(@PathVariable String entity,
@IdParam(name = "report") ID reportId,
HttpServletRequest request, HttpServletResponse response) throws IOException {
String record = getParameterNotNull(request, "record");
List<ID> recordIds = new ArrayList<>();
for (String id : record.split(",")) {
if (ID.isId(id)) recordIds.add(ID.valueOf(id));
}
final ID recordId = recordIds.get(0);
final ID[] recordIds = getIdArrayParameterNotNull(request, "record");
final ID recordId = recordIds[0];
final TemplateFile tt = DataReportManager.instance.getTemplateFile(reportId);
File output = null;
@ -102,9 +99,15 @@ public class ReportsController extends BaseController {
EasyExcelGenerator33 word = (EasyExcelGenerator33) CommonsUtils.invokeMethod(
"com.rebuild.rbv.data.WordReportGenerator#create", reportId, recordId);
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 {
// 支持多个
output = EasyExcelGenerator.create(reportId, recordIds).generate();
output = EasyExcelGenerator.create(reportId, Arrays.asList(recordIds)).generate();
}
} catch (ExcelRuntimeException ex) {
@ -113,6 +116,13 @@ public class ReportsController extends BaseController {
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 boolean isHtml = "HTML".equalsIgnoreCase(typeOutput);
final boolean isPdf = "PDF".equalsIgnoreCase(typeOutput);
@ -122,8 +132,6 @@ public class ReportsController extends BaseController {
output = PdfConverter.convertHtml(output.toPath()).toFile();
}
final String fileName = DataReportManager.getReportName(reportId, recordId, output.getName());
if (ServletUtils.isAjaxRequest(request)) {
JSONObject data = JSONUtils.toJSONObject(
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");
FileDownloader.downloadTempFile(response, output, forcePreview ? FileDownloader.INLINE_FORCE : fileName);
}
return null;
}
// 列表数据导出

View file

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

View file

@ -17,6 +17,10 @@
.badge.badge-info.word {
background-color: #307cf1;
}
.badge.badge-info.html5 {
background-color: #f16529;
background-color: #e44d26;
}
</style>
</head>
<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>
</td>
</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>
<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('无')}]]
</td>
</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>
</td>
</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>
<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('无')}]]
</td>
</tr>

View file

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

View file

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

View file

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

View file

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

View file

@ -19,26 +19,35 @@ class BaseChart extends React.Component {
const opActions = (
<div className="chart-oper">
{!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" />
</a>
)}
<a title={$L('刷新')} onClick={() => this.loadChartData()}>
<i className="zmdi zmdi-refresh" />
</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'}`} />
</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}`}>
<i className="zmdi zmdi-edit" />
<a className="d-none d-md-inline-block" data-toggle="dropdown">
<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>
)}
{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) {
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)
.perfectScrollbar()
// let tdActive = null
// const $els = $tb.find('tbody td').on('mousedown', function () {
// if (tdActive === this) {
// $(this).toggleClass('highlight')
// return
// }
// tdActive = this
// $els.removeClass('highlight')
// $(this).addClass('highlight')
// })
// selected
$tb.find('table').tableCellsSelection()
if (window.render_preview_chart) {

View file

@ -1091,7 +1091,7 @@ class RbList extends React.Component {
}, 400)
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) => {

View file

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

View file

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