base done

This commit is contained in:
devezhao 2019-08-17 23:51:26 +08:00
parent 4481c77e4a
commit 5c57f86c7c
15 changed files with 234 additions and 88 deletions

View file

@ -20,6 +20,7 @@ package com.rebuild.server.business.datareport;
import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.Query;
import cn.devezhao.persist4j.Record;
import cn.devezhao.persist4j.engine.ID;
import com.rebuild.server.Application;
@ -63,7 +64,7 @@ public class ReportGenerator {
* @param record
*/
public ReportGenerator(ID reportId, ID record) {
this(DataReportManager.instance.getTemplate(MetadataHelper.getEntity(record.getEntityCode()), reportId), record);
this(DataReportManager.instance.getTemplateFile(MetadataHelper.getEntity(record.getEntityCode()), reportId), record);
}
/**
@ -129,7 +130,10 @@ public class ReportGenerator {
String sql = String.format("select %s from %s where %s = ?",
StringUtils.join(validFields, ","), entity.getName(), entity.getPrimaryField().getName());
Record record = Application.getQueryFactory().createQuery(sql, this.user).setParameter(1, this.record).record();
Query query = this.user == null ? Application.createQuery(sql)
: Application.getQueryFactory().createQuery(sql, this.user);
Record record = query.setParameter(1, this.record).record();
for (Iterator<String> iter = record.getAvailableFieldIterator(); iter.hasNext(); ) {
String name = iter.next();

View file

@ -88,6 +88,8 @@ public class TemplateExtractor {
}
/**
* 转换模板中的变量
*
* @param entity
* @return
*/
@ -107,6 +109,8 @@ public class TemplateExtractor {
}
/**
* 转换模板中的变量字段
*
* @param entity
* @param fieldPath
* @return

View file

@ -24,6 +24,7 @@ import com.alibaba.fastjson.JSONArray;
import com.rebuild.server.Application;
import com.rebuild.server.helper.ConfigurationException;
import com.rebuild.server.helper.SysConfiguration;
import com.rebuild.server.metadata.MetadataHelper;
import java.io.File;
import java.util.ArrayList;
@ -94,7 +95,7 @@ public class DataReportManager implements ConfigManager<Entity> {
* @param reportId
* @return
*/
public File getTemplate(Entity entity, ID reportId) {
public File getTemplateFile(Entity entity, ID reportId) {
String template = null;
for (ConfigEntry e : getReportsRaw(entity)) {
if (e.getID("id").equals(reportId)) {
@ -114,6 +115,24 @@ public class DataReportManager implements ConfigManager<Entity> {
return file;
}
/**
* @param reportId
* @return
* @see #getTemplateFile(Entity, ID) 性能好
*/
@Deprecated
public File getTemplateFile(ID reportId) {
Object[] report = Application.createQueryNoFilter(
"select belongEntity from DataReportConfig where configId = ?")
.setParameter(1, reportId)
.unique();
if (report == null || !MetadataHelper.containsEntity((String) report[0])) {
throw new ConfigurationException("No config of report found : " + reportId);
}
return getTemplateFile(MetadataHelper.getEntity((String) report[0]), reportId);
}
@Override
public void clean(Entity cacheKey) {
final String cKey = "DataReportManager-" + cacheKey.getName();

View file

@ -19,10 +19,19 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
package com.rebuild.web.admin.entityhub;
import cn.devezhao.commons.CalendarUtils;
import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSON;
import com.rebuild.server.Application;
import com.rebuild.server.business.datareport.ReportGenerator;
import com.rebuild.server.business.datareport.TemplateExtractor;
import com.rebuild.server.configuration.DataReportManager;
import com.rebuild.server.helper.SysConfiguration;
import com.rebuild.server.metadata.MetadataHelper;
import com.rebuild.server.metadata.entity.EasyMeta;
import com.rebuild.utils.JSONUtils;
import com.rebuild.web.BasePageControll;
import com.rebuild.web.common.FileDownloader;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Controller;
@ -31,9 +40,13 @@ 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.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* TODO
@ -50,10 +63,6 @@ public class DataReportControll extends BasePageControll {
return createModelAndView("/admin/entityhub/data-reports.jsp");
}
@RequestMapping("/data-reports/check-template")
public void checkTemplate(HttpServletRequest request, HttpServletResponse response) throws IOException {
}
@RequestMapping("/data-reports/list")
public void reportList(HttpServletRequest request, HttpServletResponse response) throws IOException {
String belongEntity = getParameter(request, "entity");
@ -66,6 +75,68 @@ public class DataReportControll extends BasePageControll {
writeSuccess(response, array);
}
@RequestMapping("/data-reports/check-template")
public void checkTemplate(HttpServletRequest request, HttpServletResponse response) throws IOException {
String file = getParameterNotNull(request, "file");
String entity = getParameterNotNull(request, "entity");
File template = SysConfiguration.getFileOfData(file);
Entity entityMeta = MetadataHelper.getEntity(entity);
Map<String, String> vars = new TemplateExtractor(template).transformVars(entityMeta);
if (vars.isEmpty()) {
writeFailure(response, "无效模板文件 (缺少字段)");
return;
}
int totalVars = vars.size();
Set<String> invalidVars = new HashSet<>();
for (Map.Entry<String, String> e : vars.entrySet()) {
if (e.getValue() == null) {
invalidVars.add(e.getKey());
}
}
if (invalidVars.size() >= vars.size()) {
writeFailure(response, "无效模板文件 (无效字段)");
return;
}
JSON ret = JSONUtils.toJSONObject("invalidVars", invalidVars);
writeSuccess(response, ret);
}
@RequestMapping("/data-reports/preview")
public void preview(HttpServletRequest request, HttpServletResponse response) throws IOException {
ID reportId = getIdParameterNotNull(request, "id");
Object[] report = Application.createQueryNoFilter(
"select belongEntity from DataReportConfig where configId = ?")
.setParameter(1, reportId)
.unique();
if (report == null || !MetadataHelper.containsEntity((String) report[0])) {
response.sendError(410, "报表模板不存在");
return;
}
Entity entity = MetadataHelper.getEntity((String) report[0]);
String sql = String.format("select %s from %s order by modifiedOn desc",
entity.getPrimaryField().getName(), entity.getName());
Object random[] = Application.createQueryNoFilter(sql).unique();
if (random == null) {
response.sendError(410, "未找到任何记录");
return;
}
File template = DataReportManager.instance.getTemplateFile(entity, reportId);
File file = new ReportGenerator(template, (ID) random[0]).generate();
FileDownloader.setDownloadHeaders(response, file.getName());
FileDownloader.writeLocalFile(file, response);
}
// --
/**
* @param sql
* @param belongEntity

View file

@ -27,10 +27,8 @@ import com.rebuild.server.business.datareport.ReportGenerator;
import com.rebuild.server.configuration.DataReportManager;
import com.rebuild.server.configuration.portals.FormsBuilder;
import com.rebuild.server.metadata.MetadataHelper;
import com.rebuild.utils.JSONUtils;
import com.rebuild.web.BasePageControll;
import com.rebuild.web.common.FileDownloader;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@ -68,41 +66,26 @@ public class ReportsControll extends BasePageControll {
@RequestMapping("available-reports")
public void availableReports(HttpServletRequest request, HttpServletResponse response) throws IOException {
String entity = getParameterNotNull(request, "entity");
JSONArray reports = DataReportManager.instance.getReports(MetadataHelper.getEntity(entity));
Entity entityMeta = MetadataHelper.getEntity(entity);
JSONArray reports = DataReportManager.instance.getReports(entityMeta);
writeSuccess(response, reports);
}
@RequestMapping("report-generate")
public void generateReport(HttpServletRequest request, HttpServletResponse response) throws IOException {
File report = generateReport(request);
writeSuccess(response, JSONUtils.toJSONObject("file", report.getName()));
}
public void reportGenerate(HttpServletRequest request, HttpServletResponse response) throws IOException {
ID user = getRequestUser(request);
ID reportId = getIdParameterNotNull(request, "report");
ID recordId = getIdParameterNotNull(request, "record");
File report = new ReportGenerator(reportId, recordId).generate();
@RequestMapping("report-export")
public void exportReport(HttpServletRequest request, HttpServletResponse response) throws IOException {
File report = generateReport(request);
String attname = request.getParameter("attname");
if (attname == null) {
attname = report.getName();
}
response.setHeader("Content-Disposition", "attachment;filename=" + attname);
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
FileDownloader.writeLocalFile(report.getName(), true, response);
}
/**
* @param request
* @return
*/
private File generateReport(HttpServletRequest request) {
ID user = getRequestUser(request);
ID reportId = getIdParameterNotNull(request, "report");
ID recordId = getIdParameterNotNull(request, "record");
ReportGenerator generator = new ReportGenerator(reportId, recordId);
generator.setUser(user);
File file = generator.generate();
return file;
FileDownloader.setDownloadHeaders(response, attname);
FileDownloader.writeLocalFile(report, response);
}
}

View file

@ -23,6 +23,7 @@ import cn.devezhao.commons.web.ServletUtils;
import com.rebuild.server.helper.QiniuCloud;
import com.rebuild.server.helper.SysConfiguration;
import com.rebuild.web.BaseControll;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.BooleanUtils;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
@ -88,9 +89,7 @@ public class FileDownloader extends BaseControll {
// Local storage || temp
if (!QiniuCloud.instance().available() || temp) {
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
setDownloadHeaders(response, fileName);
writeLocalFile(filePath, temp, response);
} else {
String privateUrl = QiniuCloud.instance().url(filePath);
@ -116,20 +115,41 @@ public class FileDownloader extends BaseControll {
return false;
}
// long size = FileUtils.sizeOf(file);
// response.setHeader("Content-Length", String.valueOf(size));
return writeLocalFile(file, response);
}
/**
* 文件下载
*
* @param file
* @param response
* @return
* @throws IOException
*/
public static boolean writeLocalFile(File file, HttpServletResponse response) throws IOException {
long size = FileUtils.sizeOf(file);
response.setHeader("Content-Length", String.valueOf(size));
try (InputStream fis = new FileInputStream(file)) {
response.setContentLength(fis.available());
OutputStream os = response.getOutputStream();
int count = 0;
byte[] buffer = new byte[1024 * 1024];
while ((count = fis.read(buffer)) != -1) {
os.write(buffer, 0, count);
}
// os.flush();
os.flush();
return true;
}
}
/**
* @param response
* @param attname
*/
public static void setDownloadHeaders(HttpServletResponse response, String attname) {
response.setHeader("Content-Disposition", "attachment;filename=" + attname);
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
}
}

View file

@ -51,7 +51,7 @@
<th>应用实体</th>
<th width="80">启用</th>
<th width="160">更新时间</th>
<th width="80"></th>
<th width="110"></th>
</tr>
</thead>
<tbody id="dataList"></tbody>

View file

@ -126,7 +126,7 @@
.dlg-dash-select .modal-dialog ul li a {
display: block;
border: 1px solid #4285f4;
padding: 10px 12px;
padding: 9px 12px;
}
.dlg-dash-select .modal-dialog ul li a>.icon {
@ -233,5 +233,5 @@
}
.J_dash-select:hover {
color: #4285f4;
color: #4285f4;
}

View file

@ -172,7 +172,7 @@ html.admin .admin-show.row {
}
.alert.min .close {
padding: 1.03rem 0;
padding: 0.95rem 0;
}
.input-search,
@ -239,6 +239,10 @@ html.admin .admin-show.row {
line-height: 1.45;
}
.badge+.badge {
margin-left: 3px;
}
.table tbody tr.muted td:first-child {
border-left: 3px solid #bbb;
padding-left: 17px

View file

@ -264,16 +264,15 @@ body {
}
.reports-select .modal-content {
max-width: 380px;
max-width: 400px;
}
.reports-select ul li a {
display: block;
border: 1px solid #ccc;
padding: 8px 12px;
border-radius: 2px;
border: 1px solid #d5d8de;
padding: 8px 10px;
position: relative;
color: #444;
color: #555;
}
.reports-select ul li a .zmdi {
@ -281,17 +280,20 @@ body {
right: 12px;
top: 11px;
font-size: 1.3rem;
color: #999;
color: #fff;
display: none;
}
.reports-select ul li a:hover {
background-color: #eee;
color: #fff;
background-color: #4285f4;
border-color: #4285f4;
}
.reports-select ul li a:hover .zmdi {
color: #4285f4;
display: inline-block;
}
.reports-select ul li+li {
margin-top: 5px;
margin-top: 6px;
}

View file

@ -1,4 +1,4 @@
/* eslint-disable react/jsx-no-target-blank */
$(document).ready(function () {
$('.J_add').click(() => { renderRbcomp(<ReporEdit />) })
renderRbcomp(<ReportList />, 'dataList')
@ -18,8 +18,9 @@ class ReportList extends ConfigList {
<td>{item[4] ? <span className="badge badge-warning font-weight-light"></span> : <span className="badge badge-success font-weight-light"></span>}</td>
<td>{item[5]}</td>
<td className="actions">
<a className="icon" onClick={() => this.handleEdit(item)}><i className="zmdi zmdi-edit" /></a>
<a className="icon" onClick={() => this.handleDelete(item[0])}><i className="zmdi zmdi-delete" /></a>
<a className="icon" title="修改" href={`${rb.baseUrl}/admin/datas/data-reports/preview?id=${item[0]}`} target="_blank"><i className="zmdi zmdi-eye" /></a>
<a className="icon" title="编辑" onClick={() => this.handleEdit(item)}><i className="zmdi zmdi-edit" /></a>
<a className="icon" title="删除" onClick={() => this.handleDelete(item[0])}><i className="zmdi zmdi-delete" /></a>
</td>
</tr>
})}
@ -69,12 +70,20 @@ class ReporEdit extends ConfigFormDlg {
</div>
<div className="form-group row">
<label className="col-sm-3 col-form-label text-sm-right">模板文件</label>
<div className="col-sm-7">
<div className="file-select">
<input type="file" className="inputfile" id="upload-input" accept=".xlsx,.xls" data-maxsize="5000000" ref={(c) => this.__upload = c} />
<label htmlFor="upload-input" className="btn-secondary"><i className="zmdi zmdi-upload"></i><span>选择文件</span></label>
<div className="col-sm-9">
<div className="float-left">
<div className="file-select">
<input type="file" className="inputfile" id="upload-input" accept=".xlsx,.xls" data-maxsize="5000000" ref={(c) => this.__upload = c} />
<label htmlFor="upload-input" className="btn-secondary"><i className="zmdi zmdi-upload"></i><span>选择文件</span></label>
</div>
</div>
{this.state.uploadFileName && <div className="text-bold">{this.state.uploadFileName}</div>}
<div className="float-left ml-2" style={{ paddingTop: 8 }}>
{this.state.uploadFileName && <div className="text-bold">{this.state.uploadFileName}</div>}
</div>
<div className="clearfix"></div>
{(this.state.invalidVars || []).length > 0 && <div className="text-danger">
存在无效字段 {'${'}{this.state.invalidVars.join('} ${')}{'}'}建议修改
</div>}
</div>
</div>
</React.Fragment>
@ -93,9 +102,12 @@ class ReporEdit extends ConfigFormDlg {
}
componentDidMount() {
super.componentDidMount()
setTimeout(() => {
if (this.__select2) this.__select2.on('change', () => this.checkTemplate())
}, 500)
const that = this
if (this.__upload) {
let that = this
$(this.__upload).html5Uploader({
postUrl: rb.baseUrl + '/filex/upload',
onSelectError: function (field, error) {
@ -105,20 +117,40 @@ class ReporEdit extends ConfigFormDlg {
onSuccess: function (d) {
d = JSON.parse(d.currentTarget.response)
if (d.error_code === 0) {
let name = $fileCutName(d.data)
that.setState({
templateFile: d.data,
uploadFileName: name
})
if (!that.state.name) {
that.setState({ name: name })
}
that.__lastFile = d.data
that.checkTemplate()
} else RbHighbar.error('上传失败,请稍后重试')
}
})
}
}
//
checkTemplate() {
let file = this.__lastFile
let entity = this.__select2.val()
if (!file || !entity) return
$.get(`${rb.baseUrl}/admin/datas/data-reports/check-template?file=${file}&entity=${entity}`, (res) => {
if (res.error_code === 0) {
let fileName = $fileCutName(file)
this.setState({
templateFile: file,
uploadFileName: fileName,
name: this.state.name || fileName,
invalidVars: res.data.invalidVars
})
} else {
this.setState({
templateFile: null,
uploadFileName: null,
invalidVars: null
})
RbHighbar.error(res.error_msg)
}
})
}
confirm = () => {
let post = { name: this.state['name'] }
if (!post.name) { RbHighbar.create('请输入报表名称'); return }

View file

@ -53,6 +53,10 @@ class PreviewTable extends React.Component {
)
}
componentDidMount() {
$('.font-italic.hide').removeClass('hide')
}
formatValue(item) {
if (!item || !item.value) return null

View file

@ -979,7 +979,7 @@ class ClassificationSelector extends React.Component {
}
render() {
return (
<div className="modal selector" ref={(c) => this._dlg = c}>
<div className="modal selector" ref={(c) => this._dlg = c} tabIndex="-1">
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header pb-0">

View file

@ -153,7 +153,7 @@ const detectViewElement = function (item) {
// }
// }
//
//
class SelectReport extends React.Component {
constructor(props) {
super(props)
@ -165,16 +165,16 @@ class SelectReport extends React.Component {
<div className="modal-dialog modal-dialog-centered">
<div className="modal-content">
<div className="modal-header pb-0">
<button className="close" type="button" onClick={() => this.hide()}><span className="zmdi zmdi-close" /></button>
<button className="close" type="button" onClick={() => this.hide()}><i className="zmdi zmdi-close" /></button>
</div>
<div className="modal-body">
<div ref={s => this._scrollbar = s}>
<ul className="list-unstyled">
{(this.state.reports || []).map((item) => {
return <li key={'r-' + item.id}><a target="_blank" href={`${rb.baseUrl}/app/entity/report-export?report=${item.id}&record=${this.props.id}`}>{item.name}<i className="zmdi zmdi-download"></i></a></li>
})}
</ul>
</div>
<h5 className="mt-0 text-bold">选择报表</h5>
<ul className="list-unstyled">
{(this.state.reports || []).map((item) => {
let reportUrl = `${rb.baseUrl}/app/entity/report-generate?report=${item.id}&record=${this.props.id}`
return <li key={'r-' + item.id}><a target="_blank" href={reportUrl} className="text-truncate">{item.name}<i className="zmdi zmdi-download"></i></a></li>
})}
</ul>
</div>
</div>
</div>
@ -194,16 +194,19 @@ class SelectReport extends React.Component {
$(this._dlg).modal({ show: true, keyboard: true })
}
generate() {
}
/**
* @param {*} entity
* @param {*} id
*/
static create(entity, id) {
renderRbcomp(<SelectReport entity={entity} id={id} />)
if (this.__cached) {
this.__cached.show()
return
}
let that = this
renderRbcomp(<SelectReport entity={entity} id={id} />, null, function () {
that.__cached = this
})
}
}

View file

@ -41,7 +41,7 @@ html, body{
<div class="preview-content">
<div id="preview-table">
</div>
<div class="font-italic">
<div class="font-italic hide">
<div class="float-left">打印时间 ${printTime} · 编号 <%=CodecUtils.base64Encode(request.getAttribute("recordId").toString())%></div>
<div class="float-right">${appName} 技术支持</div>
</div>