Merge branch 'master' into develop

This commit is contained in:
RB 2022-03-22 14:49:44 +08:00
commit 6658f272be
12 changed files with 122 additions and 62 deletions

View file

@ -40,7 +40,7 @@ public enum DisplayType {
BARCODE(EasyBarCode.class, "二维码", FieldType.STRING, 300, null, false, true),
N2NREFERENCE(EasyN2NReference.class, "多引用", FieldType.REFERENCE_LIST, -1, null),
LOCATION(EasyLocation.class, "位置", FieldType.STRING, 100, null),
SIGN(EasySign.class, "签名", FieldType.TEXT, 32767, null, false, false),
SIGN(EasySign.class, "签名", FieldType.TEXT, 32767, null, false, true),
// 内部

View file

@ -169,7 +169,7 @@ public class DataExporter extends SetUser {
if (cellVal.toString().equals(FieldValueHelper.NO_READ_PRIVILEGES)) {
cellVal = Language.L("[无权限]");
} else if (!dt.isExportable()) {
} else if (!dt.isExportable() || (dt == DisplayType.SIGN || dt == DisplayType.BARCODE)) {
cellVal = Language.L("[暂不支持]");
} else if (dt == DisplayType.DECIMAL || dt == DisplayType.NUMBER) {
cellVal = cellVal.toString().replace(",", ""); // 移除千分位

View file

@ -8,6 +8,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.core.service.datareport;
import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.Record;
import cn.devezhao.persist4j.engine.ID;
import com.alibaba.excel.EasyExcel;
@ -21,14 +22,20 @@ import com.rebuild.core.metadata.easymeta.EasyField;
import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
import com.rebuild.core.support.RebuildConfiguration;
import com.rebuild.core.support.SetUser;
import com.rebuild.core.support.general.BarCodeSupport;
import com.rebuild.core.support.general.FieldValueHelper;
import com.rebuild.core.support.i18n.Language;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.Assert;
import org.springframework.util.Base64Utils;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.*;
/**
@ -115,10 +122,19 @@ public class EasyExcelGenerator extends SetUser {
TemplateExtractor templateExtractor = new TemplateExtractor(this.template, true);
Map<String, String> varsMap = templateExtractor.transformVars(entity);
Map<String, String> varsMapOfMain = new HashMap<>();
Map<String, String> varsMapOfDetail = new HashMap<>();
List<String> fieldsOfMain = new ArrayList<>();
List<String> fieldsOfDetail = new ArrayList<>();
for (Map.Entry<String, String> e : varsMap.entrySet()) {
if (e.getKey().startsWith(TemplateExtractor.NROW_PREFIX)) {
varsMapOfDetail.put(e.getKey(), e.getValue());
} else {
varsMapOfMain.put(e.getKey(), e.getValue());
}
String validField = e.getValue();
// 无效字段
if (validField == null) {
@ -138,18 +154,19 @@ public class EasyExcelGenerator extends SetUser {
}
final List<Map<String, Object>> datas = new ArrayList<>();
final String baseSql = "select %s from %s where %s = ?";
final String baseSql = "select %s,%s from %s where %s = ?";
if (!fieldsOfMain.isEmpty()) {
String sql = String.format(baseSql,
StringUtils.join(fieldsOfMain, ","), entity.getName(), entity.getPrimaryField().getName());
StringUtils.join(fieldsOfMain, ","),
entity.getPrimaryField().getName(), entity.getName(), entity.getPrimaryField().getName());
Record record = Application.createQuery(sql, this.getUser())
.setParameter(1, this.recordId)
.record();
Assert.notNull(record, "No record found : " + this.recordId);
Map<String, Object> data = buildData(record, varsMap);
datas.add(data);
datas.add(buildData(record, varsMapOfMain));
this.hasMain = true;
}
@ -158,14 +175,16 @@ public class EasyExcelGenerator extends SetUser {
String sql = String.format(baseSql + " order by modifiedOn desc",
StringUtils.join(fieldsOfDetail, ","),
entity.getDetailEntity().getPrimaryField().getName(),
entity.getDetailEntity().getName(),
MetadataHelper.getDetailToMainField(entity.getDetailEntity()).getName());
List<Record> list = Application.createQuery(sql, this.getUser())
.setParameter(1, this.recordId)
.list();
for (Record c : list) {
datas.add(buildData(c, varsMap));
datas.add(buildData(c, varsMapOfDetail));
}
return datas;
}
@ -182,6 +201,7 @@ public class EasyExcelGenerator extends SetUser {
final String unsupportFieldTip = Language.L("[暂不支持]");
final Map<String, Object> data = new HashMap<>();
// 无效字段填充
for (Map.Entry<String, String> e : varsMap.entrySet()) {
if (e.getValue() == null) {
@ -193,17 +213,13 @@ public class EasyExcelGenerator extends SetUser {
}
}
for (Iterator<String> iter = record.getAvailableFieldIterator(); iter.hasNext(); ) {
final String fieldName = iter.next();
for (final String fieldName : varsMap.values()) {
if (fieldName == null) continue;
EasyField easyField = EasyMetaFactory.valueOf(
Objects.requireNonNull(MetadataHelper.getLastJoinField(entity, fieldName)));
DisplayType dt = easyField.getDisplayType();
if (!dt.isExportable() && dt != DisplayType.SIGN) {
data.put(fieldName, unsupportFieldTip);
continue;
}
// 替换成变量名
String varName = fieldName;
for (Map.Entry<String, String> e : varsMap.entrySet()) {
@ -216,13 +232,21 @@ public class EasyExcelGenerator extends SetUser {
varName = varName.substring(1);
}
if (!dt.isExportable()) {
data.put(varName, unsupportFieldTip);
continue;
}
Object fieldValue = record.getObjectValue(fieldName);
if (fieldValue == null) {
if (dt == DisplayType.BARCODE) {
data.put(varName, buildBarcodeData(easyField.getRawMeta(), record.getPrimary()));
} else if (fieldValue == null) {
data.put(varName, StringUtils.EMPTY);
} else {
if (dt == DisplayType.SIGN) {
fieldValue = buildImgData((String) fieldValue);
fieldValue = buildSignData((String) fieldValue);
} else {
fieldValue = FieldValueHelper.wrapFieldValue(fieldValue, easyField, true);
@ -236,7 +260,23 @@ public class EasyExcelGenerator extends SetUser {
return data;
}
private byte[] buildImgData(String base64img) {
private byte[] buildSignData(String base64img) {
// data:image/png;base64,xxx
return Base64Utils.decodeFromString(base64img.split("base64,")[1]);
}
private byte[] buildBarcodeData(Field barcodeField, ID recordId) {
BufferedImage bi = BarCodeSupport.getBarCodeImage(barcodeField, recordId);
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
ImageIO.write(bi, "png", baos);
String base64 = Base64.encodeBase64String(baos.toByteArray());
return buildSignData("base64," + base64);
} catch (IOException e) {
log.error("Cannot encode image of barcode : {}", recordId, e);
}
return null;
}
}

View file

@ -63,11 +63,10 @@ public class BarCodeSupport {
String barcodeType = EasyMetaFactory.valueOf(field).getExtraAttr("barcodeType");
if (TYPE_BARCODE.equalsIgnoreCase(barcodeType)) {
return createBarCode(content);
}
return createBarCode(content, 0);
} else {
// 默认为二维码
else {
return createQRCode(content);
return createQRCode(content, 0);
}
}
@ -75,10 +74,11 @@ public class BarCodeSupport {
* QR_CODE
*
* @param content
* @param w
* @return
*/
public static BufferedImage createQRCode(String content) {
BitMatrix bitMatrix = createCode(content, BarcodeFormat.QR_CODE, 320);
public static BufferedImage createQRCode(String content, int w) {
BitMatrix bitMatrix = createCode(content, BarcodeFormat.QR_CODE, w <= 0 ? 320 : w);
return MatrixToImageWriter.toBufferedImage(bitMatrix);
}
@ -86,10 +86,11 @@ public class BarCodeSupport {
* CODE_128
*
* @param content
* @param h
* @return
*/
public static BufferedImage createBarCode(String content) {
BitMatrix bitMatrix = createCode(content, BarcodeFormat.CODE_128, 320);
public static BufferedImage createBarCode(String content, int h) {
BitMatrix bitMatrix = createCode(content, BarcodeFormat.CODE_128, h <= 0 ? 80 : h);
return MatrixToImageWriter.toBufferedImage(bitMatrix);
}

View file

@ -9,6 +9,7 @@ package com.rebuild.utils;
import cn.devezhao.commons.ObjectUtils;
import cn.devezhao.commons.runtime.MemoryInformationBean;
import org.apache.commons.lang3.StringUtils;
import oshi.SystemInfo;
import oshi.hardware.GlobalMemory;
import oshi.hardware.NetworkIF;
@ -82,10 +83,22 @@ public class OshiUtils {
List<NetworkIF> nets = getSI().getHardware().getNetworkIFs();
if (nets == null || nets.isEmpty()) return "localhost";
String bestipv4 = null;
for (NetworkIF net : nets) {
for (String ip : net.getIPv4addr()) {
if (bestipv4 == null) bestipv4 = ip;
break;
}
if (net.isKnownVmMacAddr()) continue;
String[] ipsv4 = net.getIPv4addr();
if (ipsv4 != null && ipsv4.length > 0) return ipsv4[0];
}
return "127.0.0.1";
if (ipsv4.length > 0) {
bestipv4 = ipsv4[0];
break;
}
}
return StringUtils.defaultString(bestipv4, "127.0.0.1");
}
}

View file

@ -110,7 +110,7 @@ public class ReportTemplateController extends BaseController {
File template = DataReportManager.instance.getTemplateFile(entity, reportId);
file = new EasyExcelGenerator(template, (ID) random[0]).generate();
} catch (ConfigurationException ex) {
response.sendError(400, Language.L("未找到可供预览的记录"));
response.sendError(500, ex.getLocalizedMessage());
return;
}

View file

@ -51,14 +51,15 @@ public class BarCodeGeneratorController extends BaseController {
@GetMapping({"/commons/barcode/render-qr", "/commons/barcode/render"})
public void render(HttpServletRequest request, HttpServletResponse response) throws IOException {
String content = getParameterNotNull(request, "t");
int w = getIntParameter(request, "w", 0);
// 4小时缓存
ServletUtils.addCacheHead(response, 240);
if (request.getRequestURI().endsWith("render-qr")) {
writeTo(BarCodeSupport.createQRCode(content), response);
writeTo(BarCodeSupport.createQRCode(content, w), response);
} else {
writeTo(BarCodeSupport.createBarCode(content), response);
writeTo(BarCodeSupport.createBarCode(content, w), response);
}
}

View file

@ -41,7 +41,7 @@ class ConfigFormDlg extends RbFormHandler {
componentDidMount() {
if (this._entity) {
$.get('/commons/metadata/entities', (res) => {
$.get(`/commons/metadata/entities?detail=${this.hasDetail === true}`, (res) => {
this.setState({ entities: res.data }, () => {
this.__select2 = $(this._entity).select2({
placeholder: $L('选择实体'),

View file

@ -68,6 +68,7 @@ class ReporEdit extends ConfigFormDlg {
constructor(props) {
super(props)
this.subtitle = $L('报表模板')
this.hasDetail = true
}
renderFrom() {

View file

@ -14,8 +14,8 @@ $(document).ready(() => {
class PreviewTable extends React.Component {
render() {
const rows = [] // [[]]
let crtRow = []
let crtSpan = 0
let currentRow = []
let currentSpan = 0
this.props.data.elements.forEach((c) => {
let colspan = c.colspan || 2
@ -23,22 +23,22 @@ class PreviewTable extends React.Component {
// set
c.colspan = colspan
if (crtSpan + colspan > 4) {
rows.push(crtRow)
crtRow = [c]
crtSpan = colspan
} else if (crtSpan + colspan === 4) {
crtRow.push(c)
rows.push(crtRow)
crtRow = []
crtSpan = 0
if (currentSpan + colspan > 4) {
rows.push(currentRow)
currentRow = [c]
currentSpan = colspan
} else if (currentSpan + colspan === 4) {
currentRow.push(c)
rows.push(currentRow)
currentRow = []
currentSpan = 0
} else {
crtRow.push(c)
crtSpan += colspan
currentRow.push(c)
currentSpan += colspan
}
})
// last
if (crtRow.length > 0) rows.push(crtRow)
if (currentRow.length > 0) rows.push(currentRow)
return (
<table className="table table-bordered table-sm table-fixed">
@ -103,6 +103,7 @@ class PreviewTable extends React.Component {
cells.push(<th>{c4.label}</th>)
colSpan = 1
cells.push(<td colSpan={colSpan}>{this.formatValue(c4)}</td>)
return cells
}
componentDidMount = () => $('.font-italic.hide').removeClass('hide')

View file

@ -1049,6 +1049,7 @@ class RbFormTime extends RbFormDateTime {
class RbFormImage extends RbFormElement {
constructor(props) {
super(props)
this._inputid = `${props.field}-input${$random()}`
if (props.value) this.state.value = [...props.value] // clone
if (this.props.uploadNumber) {
@ -1085,8 +1086,8 @@ class RbFormImage extends RbFormElement {
)
})}
<span title={$L('上传图片需要 %s ', `${this.__minUpload}~${this.__maxUpload}`)} className={showUpload ? '' : 'hide'}>
<input ref={(c) => (this._fieldValue__input = c)} type="file" className="inputfile" id={`${this.props.field}-input`} accept="image/*" />
<label htmlFor={`${this.props.field}-input`} className="img-thumbnail img-upload">
<input ref={(c) => (this._fieldValue__input = c)} type="file" className="inputfile" id={this._inputid} accept="image/*" />
<label htmlFor={this._inputid} className="img-thumbnail img-upload">
<span className="zmdi zmdi-image-alt" />
</label>
</span>
@ -1195,8 +1196,8 @@ class RbFormFile extends RbFormImage {
)
})}
<div className={`file-select ${showUpload ? '' : 'hide'}`}>
<input type="file" className="inputfile" ref={(c) => (this._fieldValue__input = c)} id={`${this.props.field}-input`} />
<label htmlFor={`${this.props.field}-input`} title={$L('上传文件需要 %d ', `${this.__minUpload}~${this.__maxUpload}`)} className="btn-secondary">
<input type="file" className="inputfile" ref={(c) => (this._fieldValue__input = c)} id={this._inputid} />
<label htmlFor={this._inputid} title={$L('上传文件需要 %d ', `${this.__minUpload}~${this.__maxUpload}`)} className="btn-secondary">
<i className="zmdi zmdi-upload" />
<span>{$L('上传文件')}</span>
</label>
@ -1770,10 +1771,10 @@ class RbFormBarcode extends RbFormElement {
renderElement() {
if (this.state.value) return this.renderViewElement()
else
return (
<div className="form-control-plaintext barcode text-muted">
{$L('自动值')} ({this.props.barcodeType === 'QRCODE' ? $L('二维码') : $L('条形')})
{$L('自动值')} ({this.props.barcodeType === 'BARCODE' ? $L('条形码') : $L('二维')})
</div>
)
}
@ -1781,11 +1782,12 @@ class RbFormBarcode extends RbFormElement {
renderViewElement() {
if (!this.state.value) return super.renderViewElement()
const codeUrl = `${rb.baseUrl}/commons/barcode/render${this.props.barcodeType === 'BARCODE' ? '' : '-qr'}?t=${$encode(this.state.value)}`
const isbar = this.props.barcodeType === 'BARCODE'
const codeUrl = `${rb.baseUrl}/commons/barcode/render${isbar ? '' : '-qr'}?t=${$encode(this.state.value)}`
return (
<div className="img-field barcode">
<a className="img-thumbnail" title={this.state.value}>
<img src={codeUrl} alt={this.state.value} />
<img src={codeUrl} alt={this.state.value} className={isbar ? 'w-auto' : ''} />
</a>
</div>
)
@ -1795,14 +1797,15 @@ class RbFormBarcode extends RbFormElement {
class RbFormAvatar extends RbFormElement {
constructor(props) {
super(props)
this._inputid = `${props.field}-input${$random()}`
}
renderElement() {
return (
<div className="img-field avatar">
<span title={this.props.readonly ? null : $L('选择头像')}>
{!this.props.readonly && <input ref={(c) => (this._fieldValue__input = c)} type="file" className="inputfile" id={`${this.props.field}-input`} accept="image/*" />}
<label htmlFor={`${this.props.field}-input`} className="img-thumbnail img-upload" disabled={this.props.readonly}>
{!this.props.readonly && <input ref={(c) => (this._fieldValue__input = c)} type="file" className="inputfile" id={this._inputid} accept="image/*" />}
<label htmlFor={this._inputid} className="img-thumbnail img-upload" disabled={this.props.readonly}>
<img src={this._formatUrl(this.state.value)} alt="Avatar" />
</label>
</span>
@ -1980,7 +1983,6 @@ class RbFormSign extends RbFormElement {
<div className="img-field sign sign-edit">
<span title={this.props.readonly ? null : $L('签名')}>
<label
htmlFor={`${this.props.field}-input`}
className="img-thumbnail img-upload"
onClick={() => {
if (!this.props.readonly) {

View file

@ -42,6 +42,7 @@
<a class="dropdown-item J_delete"><i class="icon zmdi zmdi-delete"></i> [[${bundle.L('删除')}]]</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item J_print" target="_blank" th:href="|${baseUrl}/app/${entityName}/print?id=${id}|"><i class="icon zmdi zmdi-print"></i> [[${bundle.L('打印')}]]</a>
<a class="dropdown-item J_report"><i class="icon zmdi zmdi-map"></i> [[${bundle.L('报表')}]]</a>
</div>
</div>
<div class="col-12 col-lg-6 btn-group J_trans hide">