mirror of
https://github.com/getrebuild/rebuild.git
synced 2024-09-20 15:35:55 +08:00
Merge pull request #98 from getrebuild/batch-modify-106
RB-106 Batch updates
This commit is contained in:
commit
0a531a5e4d
|
@ -52,6 +52,7 @@
|
|||
"$same": true,
|
||||
"$unmount": true,
|
||||
"$initUserSelect2": true,
|
||||
"$initReferenceSelect2": true,
|
||||
"$keepModalOpen": true,
|
||||
"rb": true,
|
||||
"renderRbcomp": true,
|
||||
|
|
2
pom.xml
2
pom.xml
|
@ -127,7 +127,7 @@
|
|||
<dependency>
|
||||
<groupId>com.github.devezhao</groupId>
|
||||
<artifactId>persist4j</artifactId>
|
||||
<version>5422daff8e</version>
|
||||
<version>a3a3ffc9f3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
|
|
|
@ -98,7 +98,7 @@ public class LoginToken extends BaseApi {
|
|||
|
||||
User loginUser = Application.getUserStore().getUser(user);
|
||||
if (!loginUser.isActive()
|
||||
|| !Application.getSecurityManager().allowed(loginUser.getId(), ZeroEntry.AllowLogin)) {
|
||||
|| !Application.getSecurityManager().allow(loginUser.getId(), ZeroEntry.AllowLogin)) {
|
||||
return Languages.lang("UnactiveUserTip");
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
|
||||
package com.rebuild.server;
|
||||
|
||||
import cn.devezhao.commons.excel.Cell;
|
||||
import cn.devezhao.persist4j.PersistManagerFactory;
|
||||
import cn.devezhao.persist4j.Query;
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
|
@ -91,6 +92,7 @@ public final class Application {
|
|||
SerializeConfig.getGlobalInstance().put(Date.class, RbDateCodec.instance);
|
||||
SerializeConfig.getGlobalInstance().put(StandardRecord.class, RbRecordCodec.instance);
|
||||
SerializeConfig.getGlobalInstance().put(QueryedRecord.class, RbRecordCodec.instance);
|
||||
SerializeConfig.getGlobalInstance().put(Cell.class, ToStringSerializer.instance);
|
||||
}
|
||||
|
||||
// 调试模式/开发模式
|
||||
|
|
45
src/main/java/com/rebuild/server/SecurityException.java
Normal file
45
src/main/java/com/rebuild/server/SecurityException.java
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
rebuild - Building your business-systems freely.
|
||||
Copyright (C) 2018-2019 devezhao <zhaofang123@gmail.com>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.rebuild.server;
|
||||
|
||||
/**
|
||||
* 业务安全异常
|
||||
*
|
||||
* @author ZHAO
|
||||
* @since 2019/12/3
|
||||
*/
|
||||
public class SecurityException extends RebuildException {
|
||||
private static final long serialVersionUID = 8786241299394987035L;
|
||||
|
||||
public SecurityException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public SecurityException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
|
||||
public SecurityException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public SecurityException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
|
@ -64,7 +64,7 @@ public class ChartsFactory {
|
|||
}
|
||||
|
||||
Entity entity = MetadataHelper.getEntity(e);
|
||||
if (user == null || !Application.getSecurityManager().allowedR(user, entity.getEntityCode())) {
|
||||
if (user == null || !Application.getSecurityManager().allowRead(user, entity.getEntityCode())) {
|
||||
throw new ChartsException("没有读取 [" + EasyMeta.getLabel(entity) + "] 的权限");
|
||||
}
|
||||
|
||||
|
|
|
@ -51,16 +51,20 @@ import java.util.List;
|
|||
*/
|
||||
public class DataExporter extends SetUser<DataExporter> {
|
||||
|
||||
private JSONObject query;
|
||||
/**
|
||||
* 最大行数
|
||||
*/
|
||||
public static final int MAX_ROWS = 65535 - 1;
|
||||
|
||||
final private JSONObject queryData;
|
||||
// 字段
|
||||
private List<Field> headFields = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* @param query
|
||||
* @param queryData
|
||||
*/
|
||||
public DataExporter(JSONObject query) {
|
||||
this.query = query;
|
||||
public DataExporter(JSONObject queryData) {
|
||||
this.queryData = queryData;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -80,7 +84,7 @@ public class DataExporter extends SetUser<DataExporter> {
|
|||
* @param dest
|
||||
*/
|
||||
public void export(File dest) {
|
||||
DefaultDataListControl control = new DefaultDataListControl(query, getUser());
|
||||
DefaultDataListControl control = new DefaultDataListControl(queryData, getUser());
|
||||
EasyExcel.write(dest)
|
||||
.registerWriteHandler(new ColumnWidthStrategy())
|
||||
.registerWriteHandler(this.buildStyle())
|
||||
|
@ -106,7 +110,7 @@ public class DataExporter extends SetUser<DataExporter> {
|
|||
}
|
||||
|
||||
/**
|
||||
* 数据
|
||||
* 构建数据
|
||||
*
|
||||
* @param control
|
||||
* @return
|
||||
|
|
|
@ -18,7 +18,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
|
||||
package com.rebuild.server.business.dataimport;
|
||||
|
||||
import cn.devezhao.commons.RegexUtils;
|
||||
import cn.devezhao.commons.excel.Cell;
|
||||
import cn.devezhao.persist4j.Entity;
|
||||
import cn.devezhao.persist4j.Field;
|
||||
|
@ -26,21 +25,13 @@ import cn.devezhao.persist4j.Query;
|
|||
import cn.devezhao.persist4j.Record;
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import com.rebuild.server.Application;
|
||||
import com.rebuild.server.configuration.portals.ClassificationManager;
|
||||
import com.rebuild.server.configuration.portals.PickListManager;
|
||||
import com.rebuild.server.helper.state.StateManager;
|
||||
import com.rebuild.server.helper.task.HeavyTask;
|
||||
import com.rebuild.server.metadata.EntityHelper;
|
||||
import com.rebuild.server.metadata.ExtRecordCreator;
|
||||
import com.rebuild.server.metadata.MetadataHelper;
|
||||
import com.rebuild.server.metadata.entity.DisplayType;
|
||||
import com.rebuild.server.metadata.entity.EasyMeta;
|
||||
import com.rebuild.utils.JSONUtils;
|
||||
import org.apache.commons.lang.StringEscapeUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang.math.NumberUtils;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
|
@ -60,105 +51,78 @@ public class DataImporter extends HeavyTask<Integer> {
|
|||
private static final ThreadLocal<ID> IN_IMPORTING = new ThreadLocal<>();
|
||||
|
||||
final private ImportRule rule;
|
||||
final private ID owningUser;
|
||||
|
||||
private int successed = 0;
|
||||
private Map<Integer, Object> iLogging = new LinkedHashMap<>();
|
||||
private ID owningUser;
|
||||
|
||||
// 记录每一行的错误日志
|
||||
private Map<Integer, Object> eachLogs = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* @param rule
|
||||
*/
|
||||
public DataImporter(ImportRule rule) {
|
||||
this(rule, Application.getCurrentUser());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param rule
|
||||
* @param owningUser
|
||||
*/
|
||||
public DataImporter(ImportRule rule, ID owningUser) {
|
||||
this.rule = rule;
|
||||
this.owningUser = rule.getDefaultOwningUser() == null ? owningUser : rule.getDefaultOwningUser();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer exec() throws Exception {
|
||||
try {
|
||||
final List<Cell[]> rows = new DataFileParser(rule.getSourceFile()).parse();
|
||||
this.setTotal(rows.size() - 1);
|
||||
final List<Cell[]> rows = new DataFileParser(rule.getSourceFile()).parse();
|
||||
this.setTotal(rows.size() - 1);
|
||||
|
||||
// 指定的所属用户
|
||||
setUser(this.owningUser);
|
||||
IN_IMPORTING.set(owningUser);
|
||||
owningUser = rule.getDefaultOwningUser() != null ? rule.getDefaultOwningUser() : getUser();
|
||||
IN_IMPORTING.set(owningUser);
|
||||
|
||||
int rowNo = 0;
|
||||
for (final Cell[] row : rows) {
|
||||
if (isInterrupt()) {
|
||||
this.setInterrupted();
|
||||
break;
|
||||
}
|
||||
if (rowNo++ == 0 || row == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
Record record = checkoutRecord(row);
|
||||
if (record != null) {
|
||||
record = Application.getEntityService(rule.getToEntity().getEntityCode()).createOrUpdate(record);
|
||||
this.successed++;
|
||||
iLogging.put(rowNo, record.getPrimary());
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
iLogging.put(rowNo, ex.getLocalizedMessage());
|
||||
LOG.warn(rowNo + " > " + ex);
|
||||
} finally {
|
||||
this.addCompleted();
|
||||
}
|
||||
for (final Cell[] row : rows) {
|
||||
if (isInterrupt()) {
|
||||
this.setInterrupted();
|
||||
break;
|
||||
}
|
||||
} finally {
|
||||
IN_IMPORTING.remove();
|
||||
|
||||
Cell firstCell = row == null || row.length == 0 ? null : row[0];
|
||||
if (firstCell == null || firstCell.getRowNo() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
Record record = checkoutRecord(row);
|
||||
if (record != null) {
|
||||
record = Application.getEntityService(rule.getToEntity().getEntityCode()).createOrUpdate(record);
|
||||
this.addSucceeded();
|
||||
eachLogs.put(firstCell.getRowNo(), record.getPrimary());
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
eachLogs.put(firstCell.getRowNo(), ex.getLocalizedMessage());
|
||||
LOG.error(firstCell.getRowNo() + " > " + ex);
|
||||
}
|
||||
this.addCompleted();
|
||||
}
|
||||
return this.successed;
|
||||
|
||||
return this.getSucceeded();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
public int getSuccessed() {
|
||||
return successed;
|
||||
|
||||
@Override
|
||||
protected void completedAfter() {
|
||||
super.completedAfter();
|
||||
IN_IMPORTING.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误日志(按错误行)
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Map<Integer, Object> getLogging() {
|
||||
return iLogging;
|
||||
public Map<Integer, Object> getEachLogs() {
|
||||
return eachLogs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cells
|
||||
* @param row
|
||||
* @return
|
||||
*/
|
||||
protected Record checkoutRecord(Cell[] cells) {
|
||||
protected Record checkoutRecord(Cell[] row) {
|
||||
Record recordNew = EntityHelper.forNew(rule.getToEntity().getEntityCode(), this.owningUser);
|
||||
|
||||
for (Map.Entry<Field, Integer> e : rule.getFiledsMapping().entrySet()) {
|
||||
int cellIndex = e.getValue();
|
||||
if (cells.length > cellIndex) {
|
||||
Field field = e.getKey();
|
||||
Cell cellValue = cells[cellIndex];
|
||||
Object value = checkoutFieldValue(field, cellValue, true);
|
||||
|
||||
if (value != null) {
|
||||
recordNew.setObjectValue(field.getName(), value);
|
||||
} else if (cellValue != Cell.NULL && !cellValue.isEmpty()) {
|
||||
LOG.warn("Invalid value of cell : " + cellValue + " > " + field.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 新建
|
||||
Record record = recordNew;
|
||||
Record record = new RecordCheckout(rule.getFiledsMapping()).checkout(recordNew, row);
|
||||
|
||||
// 检查重复
|
||||
if (rule.getRepeatOpt() < ImportRule.REPEAT_OPT_IGNORE) {
|
||||
|
@ -173,9 +137,7 @@ public class DataImporter extends HeavyTask<Integer> {
|
|||
record = EntityHelper.forUpdate(repeat, this.owningUser);
|
||||
for (Iterator<String> iter = recordNew.getAvailableFieldIterator(); iter.hasNext(); ) {
|
||||
String field = iter.next();
|
||||
if (EntityHelper.OwningUser.equals(field) || EntityHelper.OwningDept.equals(field)
|
||||
|| EntityHelper.CreatedBy.equals(field) || EntityHelper.CreatedOn.equals(field)
|
||||
|| EntityHelper.ModifiedBy.equals(field) || EntityHelper.ModifiedOn.equals(field)) {
|
||||
if (MetadataHelper.isCommonsField(field)) {
|
||||
continue;
|
||||
}
|
||||
record.setObjectValue(field, recordNew.getObjectValue(field));
|
||||
|
@ -191,182 +153,6 @@ public class DataImporter extends HeavyTask<Integer> {
|
|||
return record;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param field
|
||||
* @param cell
|
||||
* @param validate
|
||||
* @return
|
||||
*/
|
||||
protected Object checkoutFieldValue(Field field, Cell cell, boolean validate) {
|
||||
final DisplayType dt = EasyMeta.getDisplayType(field);
|
||||
if (dt == DisplayType.NUMBER) {
|
||||
return cell.asLong();
|
||||
} else if (dt == DisplayType.DECIMAL) {
|
||||
return cell.asDouble();
|
||||
} else if (dt == DisplayType.DATE || dt == DisplayType.DATETIME) {
|
||||
return checkoutDateValue(field, cell);
|
||||
} else if (dt == DisplayType.PICKLIST) {
|
||||
return checkoutPickListValue(field, cell);
|
||||
} else if (dt == DisplayType.CLASSIFICATION) {
|
||||
return checkoutClassificationValue(field, cell);
|
||||
} else if (dt == DisplayType.REFERENCE) {
|
||||
return checkoutReferenceValue(field, cell);
|
||||
} else if (dt == DisplayType.BOOL) {
|
||||
return cell.asBool();
|
||||
} else if (dt == DisplayType.STATE) {
|
||||
return checkoutStateValue(field, cell);
|
||||
}
|
||||
|
||||
// 格式验证
|
||||
if (validate) {
|
||||
if (dt == DisplayType.EMAIL) {
|
||||
String email = cell.asString();
|
||||
return RegexUtils.isEMail(email) ? email : null;
|
||||
} else if (dt == DisplayType.URL) {
|
||||
String url = cell.asString();
|
||||
return RegexUtils.isUrl(url) ? url : null;
|
||||
} else if (dt == DisplayType.PHONE) {
|
||||
String tel = cell.asString();
|
||||
return RegexUtils.isCNMobile(tel) || RegexUtils.isTel(tel)? tel : null;
|
||||
}
|
||||
}
|
||||
return cell.asString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param field
|
||||
* @param cell
|
||||
* @return
|
||||
*/
|
||||
private ID checkoutPickListValue(Field field, Cell cell) {
|
||||
String val = cell.asString();
|
||||
if (StringUtils.isBlank(val)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 支持ID
|
||||
if (ID.isId(val) && ID.valueOf(val).getEntityCode() == EntityHelper.PickList) {
|
||||
ID iid = ID.valueOf(val);
|
||||
if (PickListManager.instance.getLabel(iid) != null) {
|
||||
return iid;
|
||||
} else {
|
||||
LOG.warn("No item of PickList found by ID : " + iid);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return PickListManager.instance.findItemByLabel(val, field);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param field
|
||||
* @param cell
|
||||
* @return
|
||||
*/
|
||||
private Integer checkoutStateValue(Field field, Cell cell) {
|
||||
final String val = cell.asString();
|
||||
if (StringUtils.isBlank(val)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Integer state = StateManager.instance.getState(field, val);
|
||||
if (state != null) {
|
||||
return state;
|
||||
}
|
||||
|
||||
// 兼容状态值
|
||||
if (NumberUtils.isNumber(val)) {
|
||||
return NumberUtils.toInt(val);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param field
|
||||
* @param cell
|
||||
* @return
|
||||
*/
|
||||
private ID checkoutClassificationValue(Field field, Cell cell) {
|
||||
String val = cell.asString();
|
||||
if (StringUtils.isBlank(val)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 支持ID
|
||||
if (ID.isId(val) && ID.valueOf(val).getEntityCode() == EntityHelper.ClassificationData) {
|
||||
ID iid = ID.valueOf(val);
|
||||
if (ClassificationManager.instance.getName(iid) != null) {
|
||||
return iid;
|
||||
} else {
|
||||
LOG.warn("No item of Classification found by ID : " + iid);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return ClassificationManager.instance.findItemByName(val, field);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param field
|
||||
* @param cell
|
||||
* @return
|
||||
*/
|
||||
private ID checkoutReferenceValue(Field field, Cell cell) {
|
||||
String val = cell.asString();
|
||||
if (StringUtils.isBlank(val)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Entity oEntity = field.getReferenceEntity();
|
||||
|
||||
// 支持ID导入
|
||||
if (ID.isId(val) && ID.valueOf(val).getEntityCode().intValue() == oEntity.getEntityCode()) {
|
||||
return ID.valueOf(val);
|
||||
}
|
||||
|
||||
Object textVal = checkoutFieldValue(oEntity.getNameField(), cell, false);
|
||||
if (textVal == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Query query;
|
||||
if (oEntity.getEntityCode() == EntityHelper.User) {
|
||||
String sql = MessageFormat.format(
|
||||
"select userId from User where loginName = ''{0}'' or email = ''{0}'' or fullName = ''{0}''",
|
||||
StringEscapeUtils.escapeSql(textVal.toString()));
|
||||
query = Application.createQueryNoFilter(sql);
|
||||
} else {
|
||||
String sql = String.format("select %s from %s where %s = ?",
|
||||
oEntity.getPrimaryField().getName(), oEntity.getName(), oEntity.getNameField().getName());
|
||||
query = Application.createQueryNoFilter(sql).setParameter(1, textVal);
|
||||
}
|
||||
|
||||
Object[] found = query.unique();
|
||||
return found != null ? (ID) found[0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param field
|
||||
* @param cell
|
||||
* @return
|
||||
*/
|
||||
private Date checkoutDateValue(Field field, Cell cell) {
|
||||
Date date = cell.asDate();
|
||||
if (date != null) {
|
||||
return date;
|
||||
}
|
||||
if (cell.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String date2str = cell.asString();
|
||||
// 2017/11/19 11:07
|
||||
if (date2str.contains("/")) {
|
||||
return cell.asDate(new String[] { "yyyy/M/d H:m:s", "yyyy/M/d H:m", "yyyy/M/d" });
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param repeatFields
|
||||
* @param data
|
||||
|
@ -405,11 +191,11 @@ public class DataImporter extends HeavyTask<Integer> {
|
|||
// --
|
||||
|
||||
/**
|
||||
* 是否导入模式
|
||||
* 是否为导入状态
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static boolean isInImporting() {
|
||||
public static boolean inImportingState() {
|
||||
return IN_IMPORTING.get() != null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,267 @@
|
|||
/*
|
||||
rebuild - Building your business-systems freely.
|
||||
Copyright (C) 2018-2019 devezhao <zhaofang123@gmail.com>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.rebuild.server.business.dataimport;
|
||||
|
||||
import cn.devezhao.commons.RegexUtils;
|
||||
import cn.devezhao.commons.excel.Cell;
|
||||
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;
|
||||
import com.rebuild.server.configuration.portals.ClassificationManager;
|
||||
import com.rebuild.server.configuration.portals.PickListManager;
|
||||
import com.rebuild.server.helper.state.StateManager;
|
||||
import com.rebuild.server.metadata.EntityHelper;
|
||||
import com.rebuild.server.metadata.entity.DisplayType;
|
||||
import com.rebuild.server.metadata.entity.EasyMeta;
|
||||
import org.apache.commons.lang.StringEscapeUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang.math.NumberUtils;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 从 Cell[] 中解析结果 Record
|
||||
*
|
||||
* @author devezhao
|
||||
* @since 2019/12/4
|
||||
*/
|
||||
public class RecordCheckout {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(RecordCheckout.class);
|
||||
|
||||
final private Map<Field, Integer> fieldsMapping;
|
||||
|
||||
/**
|
||||
* @param fieldsMapping
|
||||
*/
|
||||
protected RecordCheckout(Map<Field, Integer> fieldsMapping) {
|
||||
this.fieldsMapping = fieldsMapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param row
|
||||
* @return
|
||||
*/
|
||||
public Record checkout(Record record, Cell[] row) {
|
||||
for (Map.Entry<Field, Integer> e : this.fieldsMapping.entrySet()) {
|
||||
int cellIndex = e.getValue();
|
||||
if (cellIndex > row.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Field field = e.getKey();
|
||||
Cell cellValue = row[cellIndex];
|
||||
Object value = checkoutFieldValue(field, cellValue, true);
|
||||
|
||||
if (value != null) {
|
||||
record.setObjectValue(field.getName(), value);
|
||||
} else if (cellValue != Cell.NULL && !cellValue.isEmpty()) {
|
||||
LOG.warn("Invalid value of cell : " + cellValue + " > " + field.getName());
|
||||
}
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param field
|
||||
* @param cell
|
||||
* @param validate 验证格式,如邮箱/URL等
|
||||
* @return
|
||||
*/
|
||||
protected Object checkoutFieldValue(Field field, Cell cell, boolean validate) {
|
||||
final DisplayType dt = EasyMeta.getDisplayType(field);
|
||||
if (dt == DisplayType.NUMBER) {
|
||||
return cell.asLong();
|
||||
} else if (dt == DisplayType.DECIMAL) {
|
||||
return cell.asDouble();
|
||||
} else if (dt == DisplayType.DATE || dt == DisplayType.DATETIME) {
|
||||
return checkoutDateValue(field, cell);
|
||||
} else if (dt == DisplayType.PICKLIST) {
|
||||
return checkoutPickListValue(field, cell);
|
||||
} else if (dt == DisplayType.CLASSIFICATION) {
|
||||
return checkoutClassificationValue(field, cell);
|
||||
} else if (dt == DisplayType.REFERENCE) {
|
||||
return checkoutReferenceValue(field, cell);
|
||||
} else if (dt == DisplayType.BOOL) {
|
||||
return cell.asBool();
|
||||
} else if (dt == DisplayType.STATE) {
|
||||
return checkoutStateValue(field, cell);
|
||||
}
|
||||
|
||||
// 格式验证
|
||||
if (validate) {
|
||||
if (dt == DisplayType.EMAIL) {
|
||||
String email = cell.asString();
|
||||
return RegexUtils.isEMail(email) ? email : null;
|
||||
} else if (dt == DisplayType.URL) {
|
||||
String url = cell.asString();
|
||||
return RegexUtils.isUrl(url) ? url : null;
|
||||
} else if (dt == DisplayType.PHONE) {
|
||||
String tel = cell.asString();
|
||||
return RegexUtils.isCNMobile(tel) || RegexUtils.isTel(tel)? tel : null;
|
||||
}
|
||||
}
|
||||
return cell.asString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param field
|
||||
* @param cell
|
||||
* @return
|
||||
* @see PickListManager
|
||||
*/
|
||||
protected ID checkoutPickListValue(Field field, Cell cell) {
|
||||
String val = cell.asString();
|
||||
if (StringUtils.isBlank(val)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 支持ID
|
||||
if (ID.isId(val) && ID.valueOf(val).getEntityCode() == EntityHelper.PickList) {
|
||||
ID iid = ID.valueOf(val);
|
||||
if (PickListManager.instance.getLabel(iid) != null) {
|
||||
return iid;
|
||||
} else {
|
||||
LOG.warn("No item of PickList found by ID : " + iid);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return PickListManager.instance.findItemByLabel(val, field);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param field
|
||||
* @param cell
|
||||
* @return
|
||||
* @see StateManager
|
||||
*/
|
||||
protected Integer checkoutStateValue(Field field, Cell cell) {
|
||||
final String val = cell.asString();
|
||||
if (StringUtils.isBlank(val)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Integer state = StateManager.instance.getState(field, val);
|
||||
if (state != null) {
|
||||
return state;
|
||||
}
|
||||
|
||||
// 兼容状态值
|
||||
if (NumberUtils.isNumber(val)) {
|
||||
return NumberUtils.toInt(val);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param field
|
||||
* @param cell
|
||||
* @return
|
||||
* @see ClassificationManager
|
||||
*/
|
||||
protected ID checkoutClassificationValue(Field field, Cell cell) {
|
||||
String val = cell.asString();
|
||||
if (StringUtils.isBlank(val)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 支持ID
|
||||
if (ID.isId(val) && ID.valueOf(val).getEntityCode() == EntityHelper.ClassificationData) {
|
||||
ID iid = ID.valueOf(val);
|
||||
if (ClassificationManager.instance.getName(iid) != null) {
|
||||
return iid;
|
||||
} else {
|
||||
LOG.warn("No item of Classification found by ID : " + iid);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return ClassificationManager.instance.findItemByName(val, field);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param field
|
||||
* @param cell
|
||||
* @return
|
||||
*/
|
||||
protected ID checkoutReferenceValue(Field field, Cell cell) {
|
||||
String val = cell.asString();
|
||||
if (StringUtils.isBlank(val)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Entity refEntity = field.getReferenceEntity();
|
||||
|
||||
// 支持ID导入
|
||||
if (ID.isId(val) && ID.valueOf(val).getEntityCode().intValue() == refEntity.getEntityCode()) {
|
||||
return ID.valueOf(val);
|
||||
}
|
||||
|
||||
Object val2Text = checkoutFieldValue(refEntity.getNameField(), cell, false);
|
||||
if (val2Text == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Query query;
|
||||
if (refEntity.getEntityCode() == EntityHelper.User) {
|
||||
String sql = MessageFormat.format(
|
||||
"select userId from User where loginName = ''{0}'' or email = ''{0}'' or fullName = ''{0}''",
|
||||
StringEscapeUtils.escapeSql(val2Text.toString()));
|
||||
query = Application.createQueryNoFilter(sql);
|
||||
} else {
|
||||
// 查找引用实体的名称字段
|
||||
String sql = String.format("select %s from %s where %s = ?",
|
||||
refEntity.getPrimaryField().getName(), refEntity.getName(), refEntity.getNameField().getName());
|
||||
query = Application.createQueryNoFilter(sql).setParameter(1, val2Text);
|
||||
}
|
||||
|
||||
Object[] found = query.unique();
|
||||
return found != null ? (ID) found[0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param field
|
||||
* @param cell
|
||||
* @return
|
||||
*/
|
||||
protected Date checkoutDateValue(Field field, Cell cell) {
|
||||
Date date = cell.asDate();
|
||||
if (date != null) {
|
||||
return date;
|
||||
}
|
||||
if (cell.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String date2str = cell.asString();
|
||||
// 2017/11/19 11:07
|
||||
if (date2str.contains("/")) {
|
||||
return cell.asDate(new String[] { "yyyy/M/d H:m:s", "yyyy/M/d H:m", "yyyy/M/d" });
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -85,7 +85,7 @@ public class RBStore {
|
|||
}
|
||||
|
||||
if (d == null) {
|
||||
throw new RebuildException("无法读取远程数据");
|
||||
throw new RebuildException("无法读取 RB 仓库数据");
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ public class AutoAssign implements TriggerAction {
|
|||
final ID recordId = operatingContext.getAnyRecord().getPrimary();
|
||||
|
||||
if (!allowNoPermissionAssign) {
|
||||
if (!Application.getSecurityManager().allowed(
|
||||
if (!Application.getSecurityManager().allow(
|
||||
operatingContext.getOperator(), recordId, BizzPermission.ASSIGN)) {
|
||||
LOG.warn("No privileges to assign record of target: " + recordId);
|
||||
return;
|
||||
|
|
|
@ -61,7 +61,7 @@ public class AutoShare extends AutoAssign {
|
|||
final ID recordId = operatingContext.getAnyRecord().getPrimary();
|
||||
|
||||
if (!allowNoPermissionShare) {
|
||||
if (!Application.getSecurityManager().allowed(
|
||||
if (!Application.getSecurityManager().allow(
|
||||
operatingContext.getOperator(), recordId, BizzPermission.SHARE)) {
|
||||
LOG.warn("No privileges to share record of target: " + recordId);
|
||||
return;
|
||||
|
|
|
@ -107,7 +107,7 @@ public class FieldAggregation implements TriggerAction {
|
|||
|
||||
// 如果当前用户对目标记录无修改权限
|
||||
if (!allowNoPermissionUpdate) {
|
||||
if (!Application.getSecurityManager().allowed(
|
||||
if (!Application.getSecurityManager().allow(
|
||||
operatingContext.getOperator(), targetRecordId, BizzPermission.UPDATE)) {
|
||||
LOG.warn("No privileges to update record of target: " + this.targetRecordId);
|
||||
return;
|
||||
|
|
|
@ -111,16 +111,7 @@ public class BaseLayoutManager extends ShareToManager<ID> {
|
|||
}
|
||||
|
||||
Object[][] cached = getAllConfig(belongEntity, applyType);
|
||||
for (Object[] c : cached) {
|
||||
if (!c[0].equals(detected)) {
|
||||
continue;
|
||||
}
|
||||
return new ConfigEntry()
|
||||
.set("id", c[0])
|
||||
.set("shareTo", c[1])
|
||||
.set("config", JSON.parse((String) c[3]));
|
||||
}
|
||||
return null;
|
||||
return findEntry(cached, detected);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -137,17 +128,26 @@ public class BaseLayoutManager extends ShareToManager<ID> {
|
|||
}
|
||||
|
||||
Object[][] cached = getAllConfig((String) o[0], (String) o[1]);
|
||||
for (Object[] c : cached) {
|
||||
if (!c[0].equals(cfgid)) {
|
||||
return findEntry(cached, cfgid);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cached
|
||||
* @param cfgid
|
||||
* @return
|
||||
*/
|
||||
private ConfigEntry findEntry(Object[][] cached, ID cfgid) {
|
||||
for (Object[] c : cached) {
|
||||
if (!c[0].equals(cfgid)) {
|
||||
continue;
|
||||
}
|
||||
return new ConfigEntry()
|
||||
.set("id", c[0])
|
||||
.set("shareTo", c[1])
|
||||
.set("config", JSON.parse((String) c[3]));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return new ConfigEntry()
|
||||
.set("id", c[0])
|
||||
.set("shareTo", c[1])
|
||||
.set("config", JSON.parse((String) c[3]));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clean(ID cacheKey) {
|
||||
|
|
|
@ -112,7 +112,7 @@ public class DataListManager extends BaseLayoutManager {
|
|||
Field parentField = entityMeta.getField(fieldPath[0]);
|
||||
if (!filter) {
|
||||
formatted = formatField(lastField, parentField);
|
||||
} else if (Application.getSecurityManager().allowedR(user, lastField.getOwnEntity().getEntityCode())) {
|
||||
} else if (Application.getSecurityManager().allowRead(user, lastField.getOwnEntity().getEntityCode())) {
|
||||
formatted = formatField(lastField, parentField);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -146,10 +146,10 @@ public class FormsBuilder extends FormsManager {
|
|||
// 明细无需审批
|
||||
approvalState = null;
|
||||
|
||||
if (!Application.getSecurityManager().allowedU(user, masterRecordId)) {
|
||||
if (!Application.getSecurityManager().allowUpdate(user, masterRecordId)) {
|
||||
return formatModelError("你没有权限向此记录添加明细");
|
||||
}
|
||||
} else if (!Application.getSecurityManager().allowedC(user, entityMeta.getEntityCode())) {
|
||||
} else if (!Application.getSecurityManager().allowCreate(user, entityMeta.getEntityCode())) {
|
||||
return formatModelError("没有新建权限");
|
||||
} else {
|
||||
approvalState = getHadApproval(entityMeta, null);
|
||||
|
@ -158,7 +158,7 @@ public class FormsBuilder extends FormsManager {
|
|||
}
|
||||
// 查看(视图)
|
||||
else if (viewMode) {
|
||||
if (!Application.getSecurityManager().allowedR(user, record)) {
|
||||
if (!Application.getSecurityManager().allowRead(user, record)) {
|
||||
return formatModelError("你无权读取此记录或记录已被删除");
|
||||
}
|
||||
|
||||
|
@ -167,7 +167,7 @@ public class FormsBuilder extends FormsManager {
|
|||
}
|
||||
// 编辑
|
||||
else {
|
||||
if (!Application.getSecurityManager().allowedU(user, record)) {
|
||||
if (!Application.getSecurityManager().allowUpdate(user, record)) {
|
||||
return formatModelError("你没有编辑此记录的权限");
|
||||
}
|
||||
|
||||
|
|
|
@ -133,7 +133,7 @@ public class NavManager extends BaseLayoutManager {
|
|||
}
|
||||
|
||||
Entity entityMeta = MetadataHelper.getEntity(entity);
|
||||
return !Application.getSecurityManager().allowedR(user, entityMeta.getEntityCode());
|
||||
return !Application.getSecurityManager().allowRead(user, entityMeta.getEntityCode());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ public class ViewAddonsManager extends BaseLayoutManager {
|
|||
if (e.getMasterEntity() != null) {
|
||||
continue;
|
||||
}
|
||||
if (Application.getSecurityManager().allowed(user, e.getEntityCode(), useAction)) {
|
||||
if (Application.getSecurityManager().allow(user, e.getEntityCode(), useAction)) {
|
||||
refs.add(EasyMeta.getEntityShow(e));
|
||||
}
|
||||
}
|
||||
|
@ -105,7 +105,7 @@ public class ViewAddonsManager extends BaseLayoutManager {
|
|||
String e = (String) o;
|
||||
if (MetadataHelper.containsEntity(e)) {
|
||||
Entity entityMeta = MetadataHelper.getEntity(e);
|
||||
if (Application.getSecurityManager().allowed(user, entityMeta.getEntityCode(), useAction)) {
|
||||
if (Application.getSecurityManager().allow(user, entityMeta.getEntityCode(), useAction)) {
|
||||
addons.add(EasyMeta.getEntityShow(entityMeta));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,7 +83,7 @@ public class RecentlyUsedCache {
|
|||
List<ID> data = new ArrayList<>();
|
||||
for (int i = 0; i < limit && i < exists.size(); i++) {
|
||||
final ID raw = exists.get(i);
|
||||
if (!Application.getSecurityManager().allowedR(user, raw)) {
|
||||
if (!Application.getSecurityManager().allowRead(user, raw)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
rebuild - Building your business-systems freely.
|
||||
Copyright (C) 2018-2019 devezhao <zhaofang123@gmail.com>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.rebuild.server.helper.datalist;
|
||||
|
||||
import cn.devezhao.persist4j.Entity;
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.rebuild.server.Application;
|
||||
import com.rebuild.server.helper.SetUser;
|
||||
import com.rebuild.server.metadata.MetadataHelper;
|
||||
import com.rebuild.server.service.query.ParserTokens;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 批量选择记录(在列表中)查询器
|
||||
*
|
||||
* @author ZHAO
|
||||
* @since 2019/12/1
|
||||
*/
|
||||
public class BatchOperatorQuery extends SetUser<BatchOperatorQuery> {
|
||||
|
||||
/**
|
||||
* 选中数据
|
||||
*/
|
||||
public static final int DR_SELECTED = 1;
|
||||
/**
|
||||
* 当前页数据
|
||||
*/
|
||||
public static final int DR_PAGED = 2;
|
||||
/**
|
||||
* 查询后数据
|
||||
*/
|
||||
public static final int DR_QUERYED = 3;
|
||||
/**
|
||||
* 全部数据
|
||||
*/
|
||||
public static final int DR_ALL = 4;
|
||||
|
||||
private int dataRange;
|
||||
private JSONObject queryData;
|
||||
|
||||
/**
|
||||
* @param dataRange
|
||||
* @param queryData
|
||||
*/
|
||||
public BatchOperatorQuery(int dataRange, JSONObject queryData) {
|
||||
this.dataRange = dataRange;
|
||||
this.queryData = queryData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对查询数据进行包装(根据数据范围)
|
||||
*
|
||||
* @param maxRows
|
||||
* @return
|
||||
*/
|
||||
public JSONObject wrapQueryData(int maxRows) {
|
||||
if (this.dataRange != DR_PAGED) {
|
||||
queryData.put("pageNo", 1);
|
||||
queryData.put("pageSize", maxRows); // Max
|
||||
}
|
||||
if (this.dataRange == DR_SELECTED || this.dataRange == DR_ALL) {
|
||||
queryData.remove("filter");
|
||||
queryData.remove("advFilter");
|
||||
}
|
||||
if (this.dataRange == DR_SELECTED) {
|
||||
JSONObject idsItem = new JSONObject();
|
||||
idsItem.put("op", ParserTokens.IN);
|
||||
idsItem.put("field", getEntity().getPrimaryField().getName());
|
||||
idsItem.put("value", queryData.getString("_selected"));
|
||||
|
||||
JSONArray items = new JSONArray();
|
||||
items.add(idsItem);
|
||||
JSONObject filter = new JSONObject();
|
||||
filter.put("items", items);
|
||||
queryData.put("filter", filter);
|
||||
}
|
||||
|
||||
queryData.put("reload", Boolean.FALSE);
|
||||
queryData.put("fields", new String[] { getEntity().getPrimaryField().getName() });
|
||||
return queryData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 SQL from 后面的子句(含 from)
|
||||
*
|
||||
* @return
|
||||
* @see QueryParser
|
||||
*/
|
||||
protected String getFromClauseSql() {
|
||||
QueryParser queryParser = new QueryParser(wrapQueryData(Integer.MAX_VALUE));
|
||||
String fullSql = queryParser.toSql();
|
||||
return fullSql.substring(fullSql.indexOf(" from ")).trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接获取记录 ID[]
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public ID[] getQueryedRecords() {
|
||||
if (this.dataRange == DR_SELECTED) {
|
||||
String selected = queryData.getString("_selected");
|
||||
|
||||
Set<ID> ids = new HashSet<>();
|
||||
for (String s : selected.split("\\|")) {
|
||||
if (ID.isId(s)) {
|
||||
ids.add(ID.valueOf(s));
|
||||
}
|
||||
}
|
||||
return ids.toArray(new ID[0]);
|
||||
}
|
||||
|
||||
String sql = String.format("select %s %s",
|
||||
getEntity().getPrimaryField().getName(), getFromClauseSql());
|
||||
int pageNo = queryData.getIntValue("pageNo");
|
||||
int pageSize = queryData.getIntValue("pageSize");
|
||||
|
||||
Object[][] array = Application.getQueryFactory().createQuery(sql, getUser())
|
||||
.setLimit(pageSize, pageNo * pageSize - pageSize)
|
||||
.setTimeout(60)
|
||||
.array();
|
||||
|
||||
Set<ID> ids = new HashSet<>();
|
||||
for (Object[] o : array) {
|
||||
ids.add((ID) o[0]);
|
||||
}
|
||||
return ids.toArray(new ID[0]);
|
||||
}
|
||||
|
||||
private Entity getEntity() {
|
||||
String entityName = queryData.getString("entity");
|
||||
return MetadataHelper.getEntity(entityName);
|
||||
}
|
||||
}
|
|
@ -186,6 +186,6 @@ public class DataListWrapper {
|
|||
|
||||
int fieldIndex = queryJoinFields.get(fieldPath[0]);
|
||||
Object check = original[fieldIndex];
|
||||
return check == null || Application.getSecurityManager().allowedR(user, (ID) check);
|
||||
return check == null || Application.getSecurityManager().allowRead(user, (ID) check);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ public class QueryParser {
|
|||
* @param queryExpr
|
||||
* @param dataListControl
|
||||
*/
|
||||
public QueryParser(JSONObject queryExpr, DataListControl dataListControl) {
|
||||
protected QueryParser(JSONObject queryExpr, DataListControl dataListControl) {
|
||||
this.queryExpr = queryExpr;
|
||||
this.dataListControl = dataListControl;
|
||||
this.entity = dataListControl != null ?
|
||||
|
@ -91,11 +91,11 @@ public class QueryParser {
|
|||
/**
|
||||
* @return
|
||||
*/
|
||||
public String toCountSql() {
|
||||
protected String toCountSql() {
|
||||
doParseIfNeed();
|
||||
return countSql;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
|
@ -145,7 +145,7 @@ public class QueryParser {
|
|||
return;
|
||||
}
|
||||
|
||||
StringBuilder sqlBase = new StringBuilder("select ");
|
||||
StringBuilder fullSql = new StringBuilder("select ");
|
||||
|
||||
JSONArray fieldsNode = queryExpr.getJSONArray("fields");
|
||||
int fieldIndex = -1;
|
||||
|
@ -153,7 +153,7 @@ public class QueryParser {
|
|||
for (Object o : fieldsNode) {
|
||||
// 在 DataListManager 中已验证字段有效,此处不再次验证
|
||||
String field = o.toString().trim();
|
||||
sqlBase.append(field).append(',');
|
||||
fullSql.append(field).append(',');
|
||||
fieldIndex++;
|
||||
|
||||
if (field.split("\\.").length > 1) {
|
||||
|
@ -165,24 +165,24 @@ public class QueryParser {
|
|||
|
||||
// 最后增加一个主键列
|
||||
String pkName = entity.getPrimaryField().getName();
|
||||
sqlBase.append(pkName);
|
||||
fullSql.append(pkName);
|
||||
fieldIndex++;
|
||||
|
||||
// NOTE 查询出关联记录 ID 以便验证权限
|
||||
if (!queryJoinFields.isEmpty()) {
|
||||
this.queryJoinFields = new HashMap<>();
|
||||
for (String field : queryJoinFields) {
|
||||
sqlBase.append(',').append(field);
|
||||
fullSql.append(',').append(field);
|
||||
fieldIndex++;
|
||||
this.queryJoinFields.put(field, fieldIndex);
|
||||
}
|
||||
}
|
||||
|
||||
sqlBase.append(" from ").append(entity.getName());
|
||||
fullSql.append(" from ").append(entity.getName());
|
||||
|
||||
// 过滤器
|
||||
|
||||
StringBuilder sqlWhere = new StringBuilder(" where (1=1)");
|
||||
StringBuilder sqlWhere = new StringBuilder("(1=1)");
|
||||
|
||||
// Default
|
||||
String defaultFilter = dataListControl == null ? null : dataListControl.getDefaultFilter();
|
||||
|
@ -208,7 +208,7 @@ public class QueryParser {
|
|||
sqlWhere.append(" and ").append(where);
|
||||
}
|
||||
}
|
||||
sqlBase.append(sqlWhere);
|
||||
fullSql.append(" where ").append(sqlWhere);
|
||||
|
||||
// 排序
|
||||
|
||||
|
@ -223,17 +223,18 @@ public class QueryParser {
|
|||
sqlSort.append(EntityHelper.CreatedOn + " desc");
|
||||
}
|
||||
if (sqlSort.length() > 10) {
|
||||
sqlBase.append(sqlSort);
|
||||
fullSql.append(sqlSort);
|
||||
}
|
||||
|
||||
this.sql = sqlBase.toString();
|
||||
this.sql = fullSql.toString();
|
||||
this.countSql = new StringBuilder("select ")
|
||||
.append("count(").append(pkName).append(')')
|
||||
.append(" from ")
|
||||
.append(entity.getName())
|
||||
.append(" where ")
|
||||
.append(sqlWhere)
|
||||
.toString();
|
||||
|
||||
|
||||
int pageNo = NumberUtils.toInt(queryExpr.getString("pageNo"), 1);
|
||||
int pageSize = NumberUtils.toInt(queryExpr.getString("pageSize"), 20);
|
||||
this.limit = new int[] { pageSize, pageNo * pageSize - pageSize };
|
||||
|
|
|
@ -28,8 +28,8 @@ import org.apache.commons.logging.LogFactory;
|
|||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 耗时操作可通过此类进行,例如大批量删除/修改等。此类提供了进度相关的约定,如总计执行条目,已完成条目/百分比。
|
||||
* 集成此类应该处理线程的 <code>isInterrupted</code> 方法,以便任务可以被终止。
|
||||
* 耗时操作可通过此类进行,例如大批量删除/修改等。此类提供了进度相关的约定,如总计执行条目,已完成条目/百分比等。
|
||||
* 继承此类应该处理线程的 <code>isInterrupted</code> 方法,以便任务可以被终止。
|
||||
* 使用此类应该总是使用 TaskExecutors 调用。
|
||||
*
|
||||
* @author devezhao
|
||||
|
@ -37,19 +37,23 @@ import java.util.Date;
|
|||
*
|
||||
* @see TaskExecutors
|
||||
*/
|
||||
public abstract class HeavyTask<T> extends SetUser<HeavyTask> implements Runnable {
|
||||
public abstract class HeavyTask<T> extends SetUser<HeavyTask<T>> implements Runnable {
|
||||
|
||||
protected static final Log LOG = LogFactory.getLog(HeavyTask.class);
|
||||
|
||||
volatile private boolean interrupt = false;
|
||||
volatile private boolean interruptState = false;
|
||||
|
||||
/**
|
||||
* @see SetUser
|
||||
*/
|
||||
private ID threadUser;
|
||||
|
||||
|
||||
private int total = -1;
|
||||
private int completed = 0;
|
||||
|
||||
private Date beginTime;
|
||||
private int succeeded = 0;
|
||||
|
||||
final private Date beginTime;
|
||||
private Date completedTime;
|
||||
|
||||
private String errorMessage;
|
||||
|
@ -59,7 +63,7 @@ public abstract class HeavyTask<T> extends SetUser<HeavyTask> implements Runnabl
|
|||
}
|
||||
|
||||
@Override
|
||||
public HeavyTask setUser(ID user) {
|
||||
public HeavyTask<T> setUser(ID user) {
|
||||
this.threadUser = user;
|
||||
return super.setUser(user);
|
||||
}
|
||||
|
@ -76,12 +80,12 @@ public abstract class HeavyTask<T> extends SetUser<HeavyTask> implements Runnabl
|
|||
this.completed++;
|
||||
}
|
||||
|
||||
protected Date getBeginTime() {
|
||||
return beginTime;
|
||||
}
|
||||
|
||||
protected Date getCompletedTime() {
|
||||
return completedTime;
|
||||
protected Date getCompletedTime() {
|
||||
return completedTime;
|
||||
}
|
||||
|
||||
protected void addSucceeded() {
|
||||
succeeded++;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -91,13 +95,15 @@ public abstract class HeavyTask<T> extends SetUser<HeavyTask> implements Runnabl
|
|||
*/
|
||||
public long getElapsedTime() {
|
||||
if (getCompletedTime() != null) {
|
||||
return getCompletedTime().getTime() - getBeginTime().getTime();
|
||||
return getCompletedTime().getTime() - beginTime.getTime();
|
||||
} else {
|
||||
return CalendarUtils.now().getTime() - getBeginTime().getTime();
|
||||
return CalendarUtils.now().getTime() - beginTime.getTime();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 总计数量
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int getTotal() {
|
||||
|
@ -105,6 +111,8 @@ public abstract class HeavyTask<T> extends SetUser<HeavyTask> implements Runnabl
|
|||
}
|
||||
|
||||
/**
|
||||
* 完成数量
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int getCompleted() {
|
||||
|
@ -112,6 +120,8 @@ public abstract class HeavyTask<T> extends SetUser<HeavyTask> implements Runnabl
|
|||
}
|
||||
|
||||
/**
|
||||
* 完成进度百分比
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public double getCompletedPercent() {
|
||||
|
@ -125,11 +135,31 @@ public abstract class HeavyTask<T> extends SetUser<HeavyTask> implements Runnabl
|
|||
}
|
||||
|
||||
/**
|
||||
* 是否完成
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isCompleted() {
|
||||
return completedTime != null || (total != -1 && getCompleted() >= getTotal());
|
||||
return getCompletedTime() != null || (total != -1 && getCompleted() >= getTotal());
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功数量
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int getSucceeded() {
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误消息(如有)
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
// 中断处理。是否允许中断由子类决定
|
||||
|
||||
|
@ -181,18 +211,4 @@ public abstract class HeavyTask<T> extends SetUser<HeavyTask> implements Runnabl
|
|||
Application.getSessionStore().clean();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
public boolean hasError() {
|
||||
return errorMessage != null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,9 @@ import cn.devezhao.commons.CalendarUtils;
|
|||
import cn.devezhao.persist4j.Entity;
|
||||
import cn.devezhao.persist4j.Field;
|
||||
import cn.devezhao.persist4j.Record;
|
||||
import cn.devezhao.persist4j.dialect.FieldType;
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import cn.devezhao.persist4j.engine.NullValue;
|
||||
import cn.devezhao.persist4j.record.JsonRecordCreator;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.rebuild.server.Application;
|
||||
|
@ -31,26 +33,48 @@ import com.rebuild.server.metadata.entity.EasyMeta;
|
|||
import com.rebuild.server.service.DataSpecificationException;
|
||||
import com.rebuild.server.service.bizz.privileges.User;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 标准 Record 解析
|
||||
*
|
||||
* @author Zhao Fangfang
|
||||
* @since 1.0, 2013-6-26
|
||||
*/
|
||||
public class ExtRecordCreator extends JsonRecordCreator {
|
||||
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(ExtRecordCreator.class);
|
||||
|
||||
/**
|
||||
* 更新时。是移除不允许更新的字段还是抛出异常
|
||||
*/
|
||||
private boolean strictMode;
|
||||
|
||||
/**
|
||||
* @param entity
|
||||
* @param source
|
||||
* @param editor
|
||||
*/
|
||||
public ExtRecordCreator(Entity entity, JSONObject source, ID editor) {
|
||||
super(entity, source, editor);
|
||||
this(entity, source, editor, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param entity
|
||||
* @param source
|
||||
* @param editor
|
||||
* @param strictMode
|
||||
*/
|
||||
public ExtRecordCreator(Entity entity, JSONObject source, ID editor, boolean strictMode) {
|
||||
super(entity, source, editor);
|
||||
this.strictMode = strictMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void afterCreate(Record record, boolean isNew) {
|
||||
super.afterCreate(record, isNew);
|
||||
|
@ -93,29 +117,52 @@ public class ExtRecordCreator extends JsonRecordCreator {
|
|||
|
||||
@Override
|
||||
public void verify(Record record, boolean isNew) {
|
||||
if (!isNew) {
|
||||
return;
|
||||
List<String> notAllowed = new ArrayList<>();
|
||||
// 新建
|
||||
if (isNew) {
|
||||
for (Field field : entity.getFields()) {
|
||||
if (MetadataHelper.isSystemField(field)) {
|
||||
continue;
|
||||
}
|
||||
EasyMeta easy = EasyMeta.valueOf(field);
|
||||
if (easy.getDisplayType() == DisplayType.SERIES) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object hasVal = record.getObjectValue(field.getName());
|
||||
if ((hasVal == null || NullValue.is(hasVal)) && !field.isNullable()) {
|
||||
notAllowed.add(easy.getLabel());
|
||||
}
|
||||
}
|
||||
|
||||
if (!notAllowed.isEmpty()) {
|
||||
throw new DataSpecificationException(StringUtils.join(notAllowed, "/") + " 不允许为空");
|
||||
}
|
||||
}
|
||||
|
||||
List<String> notNulls = new ArrayList<>();
|
||||
for (Field field : entity.getFields()) {
|
||||
if (MetadataHelper.isSystemField(field)) {
|
||||
continue;
|
||||
}
|
||||
EasyMeta easy = EasyMeta.valueOf(field);
|
||||
if (easy.getDisplayType() == DisplayType.SERIES) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object hasVal = record.getObjectValue(field.getName());
|
||||
if (hasVal == null && !field.isNullable()) {
|
||||
notNulls.add(easy.getLabel());
|
||||
}
|
||||
}
|
||||
|
||||
if (notNulls.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
throw new DataSpecificationException(StringUtils.join(notNulls, "/") + " 不能为空");
|
||||
// 更新
|
||||
else {
|
||||
for (String fieldName : record.getAvailableFields()) {
|
||||
Field field = record.getEntity().getField(fieldName);
|
||||
if (EntityHelper.ModifiedOn.equalsIgnoreCase(fieldName)
|
||||
|| EntityHelper.ModifiedBy.equalsIgnoreCase(fieldName)
|
||||
|| field.getType() == FieldType.PRIMARY) {
|
||||
continue;
|
||||
}
|
||||
|
||||
EasyMeta easy = EasyMeta.valueOf(field);
|
||||
if (!easy.isUpdatable()) {
|
||||
if (strictMode) {
|
||||
notAllowed.add(easy.getLabel());
|
||||
} else {
|
||||
record.removeValue(fieldName);
|
||||
LOG.warn("Remove non-updatable field : " + fieldName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!notAllowed.isEmpty()) {
|
||||
throw new DataSpecificationException(StringUtils.join(notAllowed, "/") + " 不允许修改");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -86,7 +86,7 @@ public class MetadataSorter {
|
|||
|
||||
if (user == null) {
|
||||
list.add(entity);
|
||||
} else if (Application.getSecurityManager().allowedR(user, entity.getEntityCode())) {
|
||||
} else if (Application.getSecurityManager().allowRead(user, entity.getEntityCode())) {
|
||||
list.add(entity);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,10 +27,14 @@ import cn.devezhao.persist4j.metadata.BaseMeta;
|
|||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.rebuild.server.RebuildException;
|
||||
import com.rebuild.server.configuration.RobotTriggerManager;
|
||||
import com.rebuild.server.metadata.EntityHelper;
|
||||
import com.rebuild.server.metadata.MetadataHelper;
|
||||
import com.rebuild.utils.JSONUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 元数据元素封装
|
||||
|
@ -104,10 +108,7 @@ public class EasyMeta implements BaseMeta {
|
|||
* @return
|
||||
*/
|
||||
public DisplayType getDisplayType() {
|
||||
if (!isField()) {
|
||||
throw new UnsupportedOperationException("Field only");
|
||||
}
|
||||
|
||||
Assert.isTrue(isField(), "Field supports only");
|
||||
Object[] ext = getMetaExt();
|
||||
if (ext != null) {
|
||||
return (DisplayType) ext[2];
|
||||
|
@ -180,10 +181,7 @@ public class EasyMeta implements BaseMeta {
|
|||
* @return
|
||||
*/
|
||||
public String getIcon() {
|
||||
if (isField()) {
|
||||
throw new UnsupportedOperationException("Entity only");
|
||||
}
|
||||
|
||||
Assert.isTrue(!isField(), "Entity supports only");
|
||||
String customIcon = null;
|
||||
Object[] ext = getMetaExt();
|
||||
if (ext != null) {
|
||||
|
@ -197,14 +195,12 @@ public class EasyMeta implements BaseMeta {
|
|||
|
||||
/**
|
||||
* 字段扩展配置
|
||||
*
|
||||
*
|
||||
* @return
|
||||
* @see FieldExtConfigProps
|
||||
*/
|
||||
public JSONObject getFieldExtConfig() {
|
||||
if (!isField()) {
|
||||
throw new UnsupportedOperationException("Field only");
|
||||
}
|
||||
|
||||
Assert.isTrue(isField(), "Field supports only");
|
||||
Object[] ext = getMetaExt();
|
||||
if (ext == null || StringUtils.isBlank((String) ext[3])) {
|
||||
JSONObject extConfig = getExtraAttrsJson().getJSONObject("extConfig");
|
||||
|
@ -212,11 +208,48 @@ public class EasyMeta implements BaseMeta {
|
|||
}
|
||||
return JSON.parseObject((String) ext[3]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 字段扩展配置
|
||||
*
|
||||
* @param name
|
||||
* @return
|
||||
*
|
||||
* @see #getFieldExtConfig()
|
||||
* @see FieldExtConfigProps
|
||||
*/
|
||||
public Object getPropOfFieldExtConfig(String name) {
|
||||
return getFieldExtConfig().get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 此方法除了判断元数据,还会判断其他业务规则
|
||||
*
|
||||
* @return
|
||||
* @see Field#isUpdatable()
|
||||
* @see RobotTriggerManager#getAutoReadonlyFields(String)
|
||||
*/
|
||||
public boolean isUpdatable() {
|
||||
Assert.isTrue(isField(), "Field supports only");
|
||||
final Field field = (Field) baseMeta;
|
||||
if (!field.isUpdatable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Set<String> set = RobotTriggerManager.instance.getAutoReadonlyFields(field.getOwnEntity().getName());
|
||||
return set.contains(field.getName()) ? false : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
private boolean isField() {
|
||||
return baseMeta instanceof Field;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
private Object[] getMetaExt() {
|
||||
Object[] ext;
|
||||
if (isField()) {
|
||||
|
@ -226,13 +259,21 @@ public class EasyMeta implements BaseMeta {
|
|||
}
|
||||
return ext;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
private JSONObject getExtraAttrsJson() {
|
||||
return StringUtils.isBlank(getExtraAttrs())
|
||||
? JSONUtils.EMPTY_OBJECT : JSON.parseObject(getExtraAttrs());
|
||||
}
|
||||
|
||||
// 将字段类型转成 DisplayType
|
||||
/**
|
||||
* 将字段类型转成 DisplayType
|
||||
*
|
||||
* @param field
|
||||
* @return
|
||||
*/
|
||||
private DisplayType converBuiltinFieldType(Field field) {
|
||||
Type ft = field.getType();
|
||||
if (ft == FieldType.PRIMARY) {
|
||||
|
@ -313,24 +354,26 @@ public class EasyMeta implements BaseMeta {
|
|||
}
|
||||
|
||||
/**
|
||||
* 获取字段 Label(支持两级字段,如 owningUser.fullName)
|
||||
*
|
||||
* @param entity
|
||||
* @param joinFields
|
||||
* @param fieldPath
|
||||
* @return
|
||||
*/
|
||||
public static String getLabel(Entity entity, String joinFields) {
|
||||
String[] fieldPath = joinFields.split("\\.");
|
||||
Field firstField = entity.getField(fieldPath[0]);
|
||||
if (fieldPath.length == 1) {
|
||||
public static String getLabel(Entity entity, String fieldPath) {
|
||||
String[] fieldPathSplit = fieldPath.split("\\.");
|
||||
Field firstField = entity.getField(fieldPathSplit[0]);
|
||||
if (fieldPathSplit.length == 1) {
|
||||
return getLabel(firstField);
|
||||
}
|
||||
|
||||
|
||||
Entity refEntity = firstField.getReferenceEntity();
|
||||
Field secondField = refEntity.getField(fieldPath[1]);
|
||||
Field secondField = refEntity.getField(fieldPathSplit[1]);
|
||||
return String.format("%s.%s", getLabel(firstField), getLabel(secondField));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return [Name, Label, Icon]
|
||||
* @return returns [Name, Label, Icon]
|
||||
*/
|
||||
public static String[] getEntityShow(Entity entity) {
|
||||
EasyMeta em = valueOf(entity);
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
rebuild - Building your business-systems freely.
|
||||
Copyright (C) 2018-2019 devezhao <zhaofang123@gmail.com>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.rebuild.server.metadata.entity;
|
||||
|
||||
/**
|
||||
* 字段扩展属性常量
|
||||
*
|
||||
* @author ZHAO
|
||||
* @since 2019/12/3
|
||||
*/
|
||||
public class FieldExtConfigProps {
|
||||
|
||||
/**
|
||||
* 是否允许负数
|
||||
*/
|
||||
public static final String NUMBER_NOTNEGATIVE = "notNegative";
|
||||
/**
|
||||
* 是否允许负数
|
||||
*/
|
||||
public static final String DECIMAL_NOTNEGATIVE = NUMBER_NOTNEGATIVE;
|
||||
|
||||
/**
|
||||
* 日期格式
|
||||
*/
|
||||
public static final String DATE_DATEFORMAT = "dateFormat";
|
||||
/**
|
||||
* 日期格式
|
||||
*/
|
||||
public static final String DATETIME_DATEFORMAT = DATE_DATEFORMAT;
|
||||
|
||||
/**
|
||||
* 允许上传数量
|
||||
*/
|
||||
public static final String FILE_UPLOADNUMBER = "uploadNumber";
|
||||
/**
|
||||
* 允许上传数量
|
||||
*/
|
||||
public static final String IMAGE_UPLOADNUMBER = FILE_UPLOADNUMBER;
|
||||
|
||||
/**
|
||||
* 自动编号规则
|
||||
*/
|
||||
public static final String SERIES_SERIESFORMAT = "seriesFormat";
|
||||
/**
|
||||
* 自动编号归零方式
|
||||
*/
|
||||
public static final String SERIES_SERIESZERO = "seriesZero";
|
||||
|
||||
/**
|
||||
* 使用哪个分类数据
|
||||
*/
|
||||
public static final String CLASSIFICATION_USECLASSIFICATION = "useClassification";
|
||||
|
||||
/**
|
||||
* 使用哪个状态类
|
||||
*/
|
||||
public static final String STATE_STATECLASS = "stateClass";
|
||||
}
|
|
@ -25,6 +25,7 @@ import com.rebuild.server.Application;
|
|||
import com.rebuild.server.metadata.EntityHelper;
|
||||
import com.rebuild.server.service.OperatingContext;
|
||||
import com.rebuild.server.service.notification.NotificationObserver;
|
||||
import com.rebuild.server.service.notification.NotificationOnce;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
|
@ -36,23 +37,22 @@ import java.util.Set;
|
|||
*/
|
||||
public class BulkAssign extends BulkOperator {
|
||||
|
||||
public BulkAssign(BulkContext context, GeneralEntityService ges) {
|
||||
protected BulkAssign(BulkContext context, GeneralEntityService ges) {
|
||||
super(context, ges);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer exec() {
|
||||
ID[] records = prepareRecords();
|
||||
final ID[] records = prepareRecords();
|
||||
this.setTotal(records.length);
|
||||
|
||||
int assigned = 0;
|
||||
ID firstAssigned = null;
|
||||
BulkOperatorTx.begin();
|
||||
NotificationOnce.begin();
|
||||
for (ID id : records) {
|
||||
if (Application.getSecurityManager().allowedA(context.getOpUser(), id)) {
|
||||
if (Application.getSecurityManager().allowAssign(context.getOpUser(), id)) {
|
||||
int a = ges.assign(id, context.getToUser(), context.getCascades());
|
||||
if (a > 0) {
|
||||
assigned += a;
|
||||
this.addSucceeded();
|
||||
if (firstAssigned == null) {
|
||||
firstAssigned = id;
|
||||
}
|
||||
|
@ -63,9 +63,8 @@ public class BulkAssign extends BulkOperator {
|
|||
this.addCompleted();
|
||||
}
|
||||
|
||||
Set<ID> affected = BulkOperatorTx.getInTxSet();
|
||||
BulkOperatorTx.end();
|
||||
|
||||
// 合并通知发送
|
||||
Set<ID> affected = NotificationOnce.end();
|
||||
if (firstAssigned != null && !affected.isEmpty()) {
|
||||
Record notificationNeeds = EntityHelper.forUpdate(firstAssigned, context.getOpUser());
|
||||
notificationNeeds.setID(EntityHelper.OwningUser, context.getToUser());
|
||||
|
@ -75,6 +74,6 @@ public class BulkAssign extends BulkOperator {
|
|||
new NotificationObserver().update(null, operatingContext);
|
||||
}
|
||||
|
||||
return assigned;
|
||||
return getSucceeded();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
rebuild - Building your business-systems freely.
|
||||
Copyright (C) 2018-2019 devezhao <zhaofang123@gmail.com>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.rebuild.server.service.base;
|
||||
|
||||
import cn.devezhao.persist4j.Record;
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import cn.devezhao.persist4j.record.JsonRecordCreator;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.rebuild.server.Application;
|
||||
import com.rebuild.server.helper.datalist.BatchOperatorQuery;
|
||||
import com.rebuild.server.metadata.EntityHelper;
|
||||
import com.rebuild.server.service.DataSpecificationException;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
/**
|
||||
* 批量修改
|
||||
*
|
||||
* @author devezhao
|
||||
* @since 2019/12/2
|
||||
*/
|
||||
public class BulkBacthUpdate extends BulkOperator {
|
||||
|
||||
/**
|
||||
* 修改为
|
||||
*/
|
||||
public static final String OP_SET = "SET";
|
||||
/**
|
||||
* 置空
|
||||
*/
|
||||
public static final String OP_NULL = "NULL";
|
||||
|
||||
protected BulkBacthUpdate(BulkContext context, GeneralEntityService ges) {
|
||||
super(context, ges);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer exec() throws Exception {
|
||||
final ID[] willUpdates = prepareRecords();
|
||||
this.setTotal(willUpdates.length);
|
||||
|
||||
JSONArray updateContents = context.getCustomData().getJSONArray("updateContents");
|
||||
// 转化成标准 FORM 格式
|
||||
JSONObject formJson = new JSONObject();
|
||||
for (Object o : updateContents) {
|
||||
JSONObject item = (JSONObject) o;
|
||||
String field = item.getString("field");
|
||||
String op = item.getString("op");
|
||||
String value = item.getString("value");
|
||||
|
||||
if (OP_NULL.equalsIgnoreCase(op)) {
|
||||
formJson.put(field, StringUtils.EMPTY);
|
||||
} else if (OP_SET.equalsIgnoreCase(op)) {
|
||||
formJson.put(field, value);
|
||||
}
|
||||
}
|
||||
JSONObject metadata = new JSONObject();
|
||||
metadata.put("entity", context.getMainEntity().getName());
|
||||
formJson.put(JsonRecordCreator.META_FIELD, metadata);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Converter to : " + formJson);
|
||||
}
|
||||
|
||||
for (ID id : willUpdates) {
|
||||
if (Application.getSecurityManager().allowUpdate(context.getOpUser(), id)) {
|
||||
// 更新记录
|
||||
formJson.getJSONObject(JsonRecordCreator.META_FIELD).put("id", id.toLiteral());
|
||||
|
||||
try {
|
||||
Record record = EntityHelper.parse(formJson, context.getOpUser());
|
||||
ges.update(record);
|
||||
this.addSucceeded();
|
||||
} catch (DataSpecificationException ex) {
|
||||
LOG.warn("Couldn't update : " + id + " Ex : " + ex);
|
||||
}
|
||||
} else {
|
||||
LOG.warn("No have privileges to UPDATE : " + context.getOpUser() + " > " + id);
|
||||
}
|
||||
this.addCompleted();
|
||||
}
|
||||
|
||||
return getSucceeded();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ID[] prepareRecords() {
|
||||
JSONObject customData = context.getCustomData();
|
||||
int dataRange = customData.getIntValue("_dataRange");
|
||||
BatchOperatorQuery query = new BatchOperatorQuery(dataRange, customData.getJSONObject("queryData"));
|
||||
return query.getQueryedRecords();
|
||||
}
|
||||
}
|
|
@ -40,16 +40,19 @@ public class BulkContext {
|
|||
private Permission action;
|
||||
// [目标用户]
|
||||
private ID toUser;
|
||||
|
||||
// 待操作记录
|
||||
private ID[] records;
|
||||
// 待操作记录(通过过滤条件获得)
|
||||
private JSONObject filterExp;
|
||||
// [待操作记录所依附的主记录]
|
||||
private ID targetRecord;
|
||||
// [级联操作实体]
|
||||
private String[] cascades;
|
||||
|
||||
final private Entity mainEntity;
|
||||
|
||||
// [特定数据] 默认为高级查询表达式
|
||||
// 如果为查询条件,其必须含有查询项,否则将抛出异常
|
||||
private JSONObject customData;
|
||||
|
||||
final private Entity mainEntity;
|
||||
|
||||
/**
|
||||
* @param opUser
|
||||
|
@ -57,15 +60,15 @@ public class BulkContext {
|
|||
* @param toUser
|
||||
* @param cascades
|
||||
* @param records
|
||||
* @param filterExp
|
||||
* @param customData
|
||||
* @param recordMaster
|
||||
*/
|
||||
private BulkContext(ID opUser, Permission action, ID toUser, String[] cascades, ID[] records, JSONObject filterExp, ID recordMaster) {
|
||||
private BulkContext(ID opUser, Permission action, ID toUser, String[] cascades, ID[] records, JSONObject customData, ID recordMaster) {
|
||||
this.opUser = opUser;
|
||||
this.action = action;
|
||||
this.toUser = toUser;
|
||||
this.records = records;
|
||||
this.filterExp = filterExp;
|
||||
this.customData = customData;
|
||||
this.targetRecord = recordMaster;
|
||||
this.cascades = cascades;
|
||||
this.mainEntity = detecteMainEntity();
|
||||
|
@ -96,6 +99,15 @@ public class BulkContext {
|
|||
this(opUser, action, null, null, records, null, targetRecord);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param opUser
|
||||
* @param action
|
||||
* @param customData
|
||||
*/
|
||||
public BulkContext(ID opUser, Permission action, JSONObject customData) {
|
||||
this(opUser, action, null, null, null, customData, null);
|
||||
}
|
||||
|
||||
public ID getOpUser() {
|
||||
return opUser;
|
||||
}
|
||||
|
@ -116,8 +128,8 @@ public class BulkContext {
|
|||
return records;
|
||||
}
|
||||
|
||||
public JSONObject getFilterExp() {
|
||||
return filterExp;
|
||||
public JSONObject getCustomData() {
|
||||
return customData;
|
||||
}
|
||||
|
||||
public ID getTargetRecord() {
|
||||
|
@ -133,8 +145,8 @@ public class BulkContext {
|
|||
return MetadataHelper.getEntity(targetRecord.getEntityCode());
|
||||
} else if (records != null && records.length > 0) {
|
||||
return MetadataHelper.getEntity(records[0].getEntityCode());
|
||||
} else if (filterExp != null) {
|
||||
return MetadataHelper.getEntity(filterExp.getString("entity"));
|
||||
} else if (customData != null) {
|
||||
return MetadataHelper.getEntity(customData.getString("entity"));
|
||||
}
|
||||
throw new RebuildException("No record for operate");
|
||||
}
|
||||
|
|
|
@ -30,21 +30,20 @@ import com.rebuild.server.service.DataSpecificationException;
|
|||
*/
|
||||
public class BulkDelete extends BulkOperator {
|
||||
|
||||
public BulkDelete(BulkContext context, GeneralEntityService ges) {
|
||||
protected BulkDelete(BulkContext context, GeneralEntityService ges) {
|
||||
super(context, ges);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer exec() {
|
||||
ID[] records = prepareRecords();
|
||||
final ID[] records = prepareRecords();
|
||||
this.setTotal(records.length);
|
||||
|
||||
int affected = 0;
|
||||
for (ID id : records) {
|
||||
if (Application.getSecurityManager().allowedD(context.getOpUser(), id)) {
|
||||
if (Application.getSecurityManager().allowDelete(context.getOpUser(), id)) {
|
||||
try {
|
||||
int a = ges.delete(id, context.getCascades());
|
||||
affected += (a > 0 ? 1 : 0);
|
||||
ges.delete(id, context.getCascades());
|
||||
this.addSucceeded();
|
||||
} catch (DataSpecificationException ex) {
|
||||
LOG.warn("Couldn't delete : " + id + " Ex : " + ex);
|
||||
}
|
||||
|
@ -53,8 +52,7 @@ public class BulkDelete extends BulkOperator {
|
|||
}
|
||||
this.addCompleted();
|
||||
}
|
||||
|
||||
this.completedAfter();
|
||||
return affected;
|
||||
|
||||
return getSucceeded();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,17 +19,23 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
package com.rebuild.server.service.base;
|
||||
|
||||
import cn.devezhao.persist4j.Entity;
|
||||
import cn.devezhao.persist4j.Query;
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import cn.devezhao.persist4j.util.support.QueryHelper;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.rebuild.server.Application;
|
||||
import com.rebuild.server.helper.task.HeavyTask;
|
||||
import com.rebuild.server.metadata.MetadataHelper;
|
||||
import com.rebuild.server.service.query.AdvFilterParser;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 批量操作
|
||||
*
|
||||
*
|
||||
* @author devezhao
|
||||
* @since 10/16/2018
|
||||
*/
|
||||
|
@ -39,9 +45,9 @@ public abstract class BulkOperator extends HeavyTask<Integer> {
|
|||
|
||||
final protected BulkContext context;
|
||||
final protected GeneralEntityService ges;
|
||||
|
||||
|
||||
private ID[] records;
|
||||
|
||||
|
||||
/**
|
||||
* @param context
|
||||
* @param ges 可避免多次经由拦截器检查
|
||||
|
@ -51,7 +57,7 @@ public abstract class BulkOperator extends HeavyTask<Integer> {
|
|||
this.context = context;
|
||||
this.ges = ges;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取待操作记录
|
||||
*
|
||||
|
@ -67,17 +73,26 @@ public abstract class BulkOperator extends HeavyTask<Integer> {
|
|||
setTotal(this.records.length);
|
||||
return this.records;
|
||||
}
|
||||
|
||||
JSONObject filterExp = context.getFilterExp();
|
||||
|
||||
AdvFilterParser filterParser = new AdvFilterParser(filterExp);
|
||||
String sqlWhere = filterParser.toSqlWhere();
|
||||
|
||||
Entity entity = MetadataHelper.getEntity(filterExp.getString("entity"));
|
||||
String sql = "select %s from %s where (1=1) and " + sqlWhere;
|
||||
sql = String.format(sql, entity.getPrimaryField().getName(), entity.getName());
|
||||
|
||||
// TODO 解析过滤并查询结果
|
||||
throw new UnsupportedOperationException();
|
||||
JSONObject asFilterExp = context.getCustomData();
|
||||
AdvFilterParser filterParser = new AdvFilterParser(asFilterExp);
|
||||
String sqlWhere = filterParser.toSqlWhere();
|
||||
// `(1=1)`.length < 10
|
||||
if (sqlWhere.length() < 10) {
|
||||
throw new SecurityException("Must specify filter items : " + sqlWhere);
|
||||
}
|
||||
|
||||
Entity entity = MetadataHelper.getEntity(asFilterExp.getString("entity"));
|
||||
String sql = String.format("select %s from %s where (1=1) and %s",
|
||||
entity.getPrimaryField().getName(), entity.getName(), sqlWhere);
|
||||
|
||||
// NOTE 注意没有分页
|
||||
Query query = Application.getQueryFactory().createQuery(sql, context.getOpUser());
|
||||
Object[][] array = QueryHelper.readArray(query);
|
||||
Set<ID> ids = new HashSet<>();
|
||||
for (Object[] o : array) {
|
||||
ids.add((ID) o[0]);
|
||||
}
|
||||
return ids.toArray(new ID[0]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
/*
|
||||
rebuild - Building your business-systems freely.
|
||||
Copyright (C) 2019 devezhao <zhaofang123@gmail.com>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.rebuild.server.service.base;
|
||||
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Using:
|
||||
* <tt>begin</tt>
|
||||
* <tt>[getInTxSet]</tt>
|
||||
* <tt>[isInTx]</tt>
|
||||
* <tt>end</tt>
|
||||
*
|
||||
* @author devezhao zhaofang123@gmail.com
|
||||
* @since 2019/03/23
|
||||
*/
|
||||
public class BulkOperatorTx {
|
||||
|
||||
private static final ThreadLocal<Set<ID>> STATE = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
*/
|
||||
public static void begin() {
|
||||
STATE.set(new LinkedHashSet<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
public static Set<ID> getInTxSet() {
|
||||
return STATE.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
public static boolean isInTx() {
|
||||
return STATE.get() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public static void end() {
|
||||
STATE.remove();
|
||||
}
|
||||
}
|
|
@ -25,6 +25,7 @@ import com.rebuild.server.Application;
|
|||
import com.rebuild.server.metadata.EntityHelper;
|
||||
import com.rebuild.server.service.OperatingContext;
|
||||
import com.rebuild.server.service.notification.NotificationObserver;
|
||||
import com.rebuild.server.service.notification.NotificationOnce;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
|
@ -36,23 +37,22 @@ import java.util.Set;
|
|||
*/
|
||||
public class BulkShare extends BulkOperator {
|
||||
|
||||
public BulkShare(BulkContext context, GeneralEntityService ges) {
|
||||
protected BulkShare(BulkContext context, GeneralEntityService ges) {
|
||||
super(context, ges);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer exec() {
|
||||
ID[] records = prepareRecords();
|
||||
final ID[] records = prepareRecords();
|
||||
this.setTotal(records.length);
|
||||
|
||||
int shared = 0;
|
||||
ID firstShared = null;
|
||||
BulkOperatorTx.begin();
|
||||
NotificationOnce.begin();
|
||||
for (ID id : records) {
|
||||
if (Application.getSecurityManager().allowedS(context.getOpUser(), id)) {
|
||||
if (Application.getSecurityManager().allowShare(context.getOpUser(), id)) {
|
||||
int a = ges.share(id, context.getToUser(), context.getCascades());
|
||||
if (a > 0) {
|
||||
shared += a;
|
||||
this.addSucceeded();
|
||||
if (firstShared == null) {
|
||||
firstShared = id;
|
||||
}
|
||||
|
@ -62,10 +62,9 @@ public class BulkShare extends BulkOperator {
|
|||
}
|
||||
this.addCompleted();
|
||||
}
|
||||
|
||||
Set<ID> affected = BulkOperatorTx.getInTxSet();
|
||||
BulkOperatorTx.end();
|
||||
|
||||
|
||||
// 合并通知发送
|
||||
Set<ID> affected = NotificationOnce.end();
|
||||
if (firstShared != null && !affected.isEmpty()) {
|
||||
Record notificationNeeds = EntityHelper.forNew(EntityHelper.ShareAccess, context.getOpUser());
|
||||
notificationNeeds.setID("shareTo", context.getToUser());
|
||||
|
@ -75,8 +74,7 @@ public class BulkShare extends BulkOperator {
|
|||
context.getOpUser(), BizzPermission.SHARE, null, notificationNeeds, affected.toArray(new ID[0]));
|
||||
new NotificationObserver().update(null, operatingContext);
|
||||
}
|
||||
|
||||
this.completedAfter();
|
||||
return shared;
|
||||
|
||||
return getSucceeded();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,25 +35,25 @@ public class BulkUnshare extends BulkOperator {
|
|||
|
||||
@Override
|
||||
protected Integer exec() {
|
||||
ID[] records = prepareRecords();
|
||||
final ID[] records = prepareRecords();
|
||||
this.setTotal(records.length);
|
||||
|
||||
int unshared = 0;
|
||||
ID realTarget = context.getTargetRecord();
|
||||
final ID realTarget = context.getTargetRecord();
|
||||
|
||||
// 只需要验证主记录权限
|
||||
if (!Application.getSecurityManager().allowedS(context.getOpUser(), realTarget)) {
|
||||
if (!Application.getSecurityManager().allowShare(context.getOpUser(), realTarget)) {
|
||||
this.setCompleted(records.length);
|
||||
return unshared;
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (ID id : records) {
|
||||
int a = ges.unshare(realTarget, id);
|
||||
unshared += (a > 0 ? 1 : 0);
|
||||
if (a > 0) {
|
||||
this.addSucceeded();
|
||||
}
|
||||
this.addCompleted();
|
||||
}
|
||||
|
||||
this.completedAfter();
|
||||
return unshared;
|
||||
|
||||
return getSucceeded();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -151,7 +151,7 @@ public class GeneralEntityService extends ObservableService {
|
|||
}
|
||||
|
||||
for (ID id : e.getValue()) {
|
||||
if (Application.getSecurityManager().allowedD(currentUser, id)) {
|
||||
if (Application.getSecurityManager().allowDelete(currentUser, id)) {
|
||||
if (recycleBin != null) {
|
||||
recycleBin.add(id, record);
|
||||
}
|
||||
|
@ -381,7 +381,9 @@ public class GeneralEntityService extends ObservableService {
|
|||
return new BulkShare(context, this);
|
||||
} else if (context.getAction() == UNSHARE) {
|
||||
return new BulkUnshare(context, this);
|
||||
}
|
||||
} else if (context.getAction() == BizzPermission.UPDATE) {
|
||||
return new BulkBacthUpdate(context, this);
|
||||
}
|
||||
throw new UnsupportedOperationException("Unsupported bulk action : " + context.getAction());
|
||||
}
|
||||
|
||||
|
@ -467,9 +469,7 @@ public class GeneralEntityService extends ObservableService {
|
|||
Assert.isNull(recordOfNew.getPrimary(), "Must be new record");
|
||||
|
||||
Entity entity = recordOfNew.getEntity();
|
||||
if (MetadataHelper.isBizzEntity(entity.getEntityCode())
|
||||
|| !MetadataHelper.hasPrivilegesField(entity)) {
|
||||
LOG.warn("Could't append Bizz and non-business entities : " + entity.getName());
|
||||
if (MetadataHelper.isBizzEntity(entity.getEntityCode()) || !MetadataHelper.hasPrivilegesField(entity)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -494,7 +494,7 @@ public class GeneralEntityService extends ObservableService {
|
|||
Field[] seriesFields = MetadataSorter.sortFields(record.getEntity(), DisplayType.SERIES);
|
||||
for (Field field : seriesFields) {
|
||||
// 导入模式,不强制生成
|
||||
if (record.hasValue(field.getName()) && DataImporter.isInImporting()) {
|
||||
if (record.hasValue(field.getName()) && DataImporter.inImportingState()) {
|
||||
continue;
|
||||
}
|
||||
record.setString(field.getName(), SeriesGeneratorFactory.generate(field));
|
||||
|
|
|
@ -75,7 +75,7 @@ public class PrivilegesGuardInterceptor implements MethodInterceptor, Guard {
|
|||
if (AdminGuard.class.isAssignableFrom(invocationClass) && !UserHelper.isAdmin(caller)) {
|
||||
throw new AccessDeniedException("非法操作请求 (E" + ((ServiceSpec) invocation.getThis()).getEntityCode() + ")");
|
||||
}
|
||||
// 仅 EntityService 或子类需要继续验证角色权限
|
||||
// 仅 EntityService 或子类会验证角色权限
|
||||
if (!EntityService.class.isAssignableFrom(invocationClass)) {
|
||||
return;
|
||||
}
|
||||
|
@ -88,9 +88,8 @@ public class PrivilegesGuardInterceptor implements MethodInterceptor, Guard {
|
|||
}
|
||||
|
||||
BulkContext context = (BulkContext) first;
|
||||
|
||||
Entity entity = context.getMainEntity();
|
||||
if (!Application.getSecurityManager().allowed(caller, entity.getEntityCode(), context.getAction())) {
|
||||
if (!Application.getSecurityManager().allow(caller, entity.getEntityCode(), context.getAction())) {
|
||||
LOG.error("User [ " + caller + " ] not allowed execute action [ " + context.getAction() + " ]. Entity : " + context.getMainEntity());
|
||||
throw new AccessDeniedException(formatHumanMessage(context.getAction(), entity, null));
|
||||
}
|
||||
|
@ -99,8 +98,8 @@ public class PrivilegesGuardInterceptor implements MethodInterceptor, Guard {
|
|||
|
||||
Object idOrRecord = invocation.getArguments()[0];
|
||||
|
||||
ID recordId = null;
|
||||
Entity entity = null;
|
||||
ID recordId;
|
||||
Entity entity;
|
||||
|
||||
if (idOrRecord instanceof Record) {
|
||||
recordId = ((Record) idOrRecord).getPrimary();
|
||||
|
@ -114,18 +113,20 @@ public class PrivilegesGuardInterceptor implements MethodInterceptor, Guard {
|
|||
|
||||
Permission action = getPermissionByMethod(invocation.getMethod(), recordId == null);
|
||||
|
||||
boolean isAllowed = false;
|
||||
boolean allowed;
|
||||
if (action == BizzPermission.CREATE) {
|
||||
// 明细实体
|
||||
if (entity.getMasterEntity() != null) {
|
||||
Field field = MetadataHelper.getSlaveToMasterField(entity);
|
||||
Assert.notNull(field, "No STM field found : " + entity);
|
||||
|
||||
ID masterId = ((Record) idOrRecord).getID(field.getName());
|
||||
if (masterId == null || !Application.getSecurityManager().allowedU(caller, masterId)) {
|
||||
if (masterId == null || !Application.getSecurityManager().allowUpdate(caller, masterId)) {
|
||||
throw new AccessDeniedException("你没有添加明细的权限");
|
||||
}
|
||||
isAllowed = true;
|
||||
allowed = true;
|
||||
} else {
|
||||
isAllowed = Application.getSecurityManager().allowed(caller, entity.getEntityCode(), action);
|
||||
allowed = Application.getSecurityManager().allow(caller, entity.getEntityCode(), action);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
@ -133,17 +134,17 @@ public class PrivilegesGuardInterceptor implements MethodInterceptor, Guard {
|
|||
throw new IllegalArgumentException("No primary in record!");
|
||||
}
|
||||
|
||||
isAllowed = Application.getSecurityManager().allowed(caller, recordId, action);
|
||||
allowed = Application.getSecurityManager().allow(caller, recordId, action);
|
||||
}
|
||||
|
||||
// 无权限操作
|
||||
if (!isAllowed && IN_NOPERMISSION_PASS.get() != null) {
|
||||
isAllowed = true;
|
||||
if (!allowed && IN_NOPERMISSION_PASS.get() != null) {
|
||||
allowed = true;
|
||||
IN_NOPERMISSION_PASS.remove();
|
||||
LOG.warn("Allow no permission passed : " + recordId);
|
||||
}
|
||||
|
||||
if (!isAllowed) {
|
||||
if (!allowed) {
|
||||
LOG.error("User [ " + caller + " ] not allowed execute action [ " + action + " ]. " + (recordId == null ? "Entity : " + entity : "Record : " + recordId));
|
||||
throw new AccessDeniedException(formatHumanMessage(action, entity, recordId));
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import cn.devezhao.bizz.privileges.impl.BizzDepthEntry;
|
|||
import cn.devezhao.bizz.privileges.impl.BizzPermission;
|
||||
import cn.devezhao.bizz.security.member.Role;
|
||||
import cn.devezhao.persist4j.Entity;
|
||||
import cn.devezhao.persist4j.Field;
|
||||
import cn.devezhao.persist4j.Filter;
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import com.rebuild.server.Application;
|
||||
|
@ -35,8 +36,6 @@ import com.rebuild.server.metadata.MetadataHelper;
|
|||
import com.rebuild.server.service.EntityService;
|
||||
import com.rebuild.server.service.bizz.RoleService;
|
||||
import com.rebuild.server.service.bizz.UserService;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
|
@ -51,10 +50,8 @@ import org.springframework.util.Assert;
|
|||
*/
|
||||
public class SecurityManager {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(SecurityManager.class);
|
||||
|
||||
final private UserStore theUserStore;
|
||||
final private RecordOwningCache theRecordOwning;
|
||||
final private RecordOwningCache theRecordOwningCache;
|
||||
|
||||
/**
|
||||
* @param us
|
||||
|
@ -62,7 +59,7 @@ public class SecurityManager {
|
|||
*/
|
||||
protected SecurityManager(UserStore us, RecordOwningCache roc) {
|
||||
this.theUserStore = us;
|
||||
this.theRecordOwning = roc;
|
||||
this.theRecordOwningCache = roc;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -70,7 +67,7 @@ public class SecurityManager {
|
|||
* @return
|
||||
*/
|
||||
public ID getOwningUser(ID record) {
|
||||
return theRecordOwning.getOwningUser(record);
|
||||
return theRecordOwningCache.getOwningUser(record);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -87,18 +84,7 @@ public class SecurityManager {
|
|||
} else if (u.isAdmin()) {
|
||||
return Privileges.ROOT;
|
||||
}
|
||||
return u.getOwningRole().getPrivileges(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取真实的权限实体
|
||||
*
|
||||
* @param entity
|
||||
* @return
|
||||
*/
|
||||
public int getPrivilegesEntity(int entity) {
|
||||
Entity em = MetadataHelper.getEntity(entity);
|
||||
return em.getMasterEntity() == null ? entity : em.getMasterEntity().getEntityCode();
|
||||
return u.getOwningRole().getPrivileges(convert2MasterEntity(entity));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -108,8 +94,8 @@ public class SecurityManager {
|
|||
* @param entity
|
||||
* @return
|
||||
*/
|
||||
public boolean allowedC(ID user, int entity) {
|
||||
return allowed(user, entity, BizzPermission.CREATE);
|
||||
public boolean allowCreate(ID user, int entity) {
|
||||
return allow(user, entity, BizzPermission.CREATE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -119,8 +105,8 @@ public class SecurityManager {
|
|||
* @param entity
|
||||
* @return
|
||||
*/
|
||||
public boolean allowedD(ID user, int entity) {
|
||||
return allowed(user, entity, BizzPermission.DELETE);
|
||||
public boolean allowDelete(ID user, int entity) {
|
||||
return allow(user, entity, BizzPermission.DELETE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -130,8 +116,8 @@ public class SecurityManager {
|
|||
* @param entity
|
||||
* @return
|
||||
*/
|
||||
public boolean allowedU(ID user, int entity) {
|
||||
return allowed(user, entity, BizzPermission.UPDATE);
|
||||
public boolean allowUpdate(ID user, int entity) {
|
||||
return allow(user, entity, BizzPermission.UPDATE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -141,8 +127,8 @@ public class SecurityManager {
|
|||
* @param entity
|
||||
* @return
|
||||
*/
|
||||
public boolean allowedR(ID user, int entity) {
|
||||
return allowed(user, entity, BizzPermission.READ);
|
||||
public boolean allowRead(ID user, int entity) {
|
||||
return allow(user, entity, BizzPermission.READ);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -152,8 +138,8 @@ public class SecurityManager {
|
|||
* @param entity
|
||||
* @return
|
||||
*/
|
||||
public boolean allowedA(ID user, int entity) {
|
||||
return allowed(user, entity, BizzPermission.ASSIGN);
|
||||
public boolean allowAssign(ID user, int entity) {
|
||||
return allow(user, entity, BizzPermission.ASSIGN);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -163,8 +149,8 @@ public class SecurityManager {
|
|||
* @param entity
|
||||
* @return
|
||||
*/
|
||||
public boolean allowedS(ID user, int entity) {
|
||||
return allowed(user, entity, BizzPermission.SHARE);
|
||||
public boolean allowShare(ID user, int entity) {
|
||||
return allow(user, entity, BizzPermission.SHARE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -174,8 +160,8 @@ public class SecurityManager {
|
|||
* @param target
|
||||
* @return
|
||||
*/
|
||||
public boolean allowedD(ID user, ID target) {
|
||||
return allowed(user, target, BizzPermission.DELETE);
|
||||
public boolean allowDelete(ID user, ID target) {
|
||||
return allow(user, target, BizzPermission.DELETE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -185,8 +171,8 @@ public class SecurityManager {
|
|||
* @param target
|
||||
* @return
|
||||
*/
|
||||
public boolean allowedU(ID user, ID target) {
|
||||
return allowed(user, target, BizzPermission.UPDATE);
|
||||
public boolean allowUpdate(ID user, ID target) {
|
||||
return allow(user, target, BizzPermission.UPDATE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -196,8 +182,8 @@ public class SecurityManager {
|
|||
* @param target
|
||||
* @return
|
||||
*/
|
||||
public boolean allowedR(ID user, ID target) {
|
||||
return allowed(user, target, BizzPermission.READ);
|
||||
public boolean allowRead(ID user, ID target) {
|
||||
return allow(user, target, BizzPermission.READ);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -207,8 +193,8 @@ public class SecurityManager {
|
|||
* @param target
|
||||
* @return
|
||||
*/
|
||||
public boolean allowedA(ID user, ID target) {
|
||||
return allowed(user, target, BizzPermission.ASSIGN);
|
||||
public boolean allowAssign(ID user, ID target) {
|
||||
return allow(user, target, BizzPermission.ASSIGN);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -218,8 +204,8 @@ public class SecurityManager {
|
|||
* @param target
|
||||
* @return
|
||||
*/
|
||||
public boolean allowedS(ID user, ID target) {
|
||||
return allowed(user, target, BizzPermission.SHARE);
|
||||
public boolean allowShare(ID user, ID target) {
|
||||
return allow(user, target, BizzPermission.SHARE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -230,8 +216,8 @@ public class SecurityManager {
|
|||
* @param action 权限动作
|
||||
* @return
|
||||
*/
|
||||
public boolean allowed(ID user, int entity, Permission action) {
|
||||
Boolean a = allowedUser(user);
|
||||
public boolean allow(ID user, int entity, Permission action) {
|
||||
Boolean a = userAllow(user);
|
||||
if (a != null) {
|
||||
return a;
|
||||
}
|
||||
|
@ -261,7 +247,7 @@ public class SecurityManager {
|
|||
action = convert2MasterAction(action);
|
||||
}
|
||||
|
||||
Privileges ep = role.getPrivileges(getPrivilegesEntity(entity));
|
||||
Privileges ep = role.getPrivileges(convert2MasterEntity(entity));
|
||||
return ep.allowed(action);
|
||||
}
|
||||
|
||||
|
@ -273,8 +259,8 @@ public class SecurityManager {
|
|||
* @param action 权限动作
|
||||
* @return
|
||||
*/
|
||||
public boolean allowed(ID user, ID target, Permission action) {
|
||||
Boolean a = allowedUser(user);
|
||||
public boolean allow(ID user, ID target, Permission action) {
|
||||
Boolean a = userAllow(user);
|
||||
if (a != null) {
|
||||
return a;
|
||||
}
|
||||
|
@ -302,7 +288,7 @@ public class SecurityManager {
|
|||
action = convert2MasterAction(action);
|
||||
}
|
||||
|
||||
Privileges ep = role.getPrivileges(getPrivilegesEntity(entity));
|
||||
Privileges ep = role.getPrivileges(convert2MasterEntity(entity));
|
||||
|
||||
boolean allowed = ep.allowed(action);
|
||||
if (!allowed) {
|
||||
|
@ -317,7 +303,7 @@ public class SecurityManager {
|
|||
return true;
|
||||
}
|
||||
|
||||
ID targetUserId = theRecordOwning.getOwningUser(target);
|
||||
ID targetUserId = theRecordOwningCache.getOwningUser(target);
|
||||
if (targetUserId == null) {
|
||||
return false;
|
||||
}
|
||||
|
@ -325,7 +311,7 @@ public class SecurityManager {
|
|||
if (BizzDepthEntry.PRIVATE.equals(depth)) {
|
||||
allowed = user.equals(targetUserId);
|
||||
if (!allowed) {
|
||||
return allowedViaShare(user, target, action);
|
||||
return allowViaShare(user, target, action);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -337,7 +323,7 @@ public class SecurityManager {
|
|||
if (BizzDepthEntry.LOCAL.equals(depth)) {
|
||||
allowed = accessUserDept.equals(targetUser.getOwningDept());
|
||||
if (!allowed) {
|
||||
return allowedViaShare(user, target, action);
|
||||
return allowViaShare(user, target, action);
|
||||
}
|
||||
return true;
|
||||
} else if (BizzDepthEntry.DEEPDOWN.equals(depth)) {
|
||||
|
@ -347,7 +333,7 @@ public class SecurityManager {
|
|||
|
||||
allowed = accessUserDept.isChildrenAll(targetUser.getOwningDept());
|
||||
if (!allowed) {
|
||||
return allowedViaShare(user, target, action);
|
||||
return allowViaShare(user, target, action);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -363,7 +349,7 @@ public class SecurityManager {
|
|||
* @param action
|
||||
* @return
|
||||
*/
|
||||
public boolean allowedViaShare(ID user, ID target, Permission action) {
|
||||
public boolean allowViaShare(ID user, ID target, Permission action) {
|
||||
|
||||
// TODO 目前只共享了读取权限
|
||||
// TODO 性能优化-缓存
|
||||
|
@ -392,6 +378,17 @@ public class SecurityManager {
|
|||
int rightsVal = rights == null ? 0 : (int) rights[0];
|
||||
return (rightsVal & BizzPermission.READ.getMask()) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取真实的权限实体。如明细的权限依赖主实体
|
||||
*
|
||||
* @param entity
|
||||
* @return
|
||||
*/
|
||||
private int convert2MasterEntity(int entity) {
|
||||
Entity em = MetadataHelper.getEntity(entity);
|
||||
return em.getMasterEntity() == null ? entity : em.getMasterEntity().getEntityCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换明细实体的权限。<tt>删除/新建/更新</tt>明细记录,等于修改主实体,因此要转换成<tt>更新</tt>权限
|
||||
|
@ -414,14 +411,52 @@ public class SecurityManager {
|
|||
*/
|
||||
private ID getMasterRecordId(ID slaveId) {
|
||||
Entity entity = MetadataHelper.getEntity(slaveId.getEntityCode());
|
||||
Entity masterEntity = entity.getMasterEntity();
|
||||
Assert.isTrue(masterEntity != null, "Non slave entty : " + slaveId);
|
||||
|
||||
String sql = "select %s from %s where %s = '%s'";
|
||||
sql = String.format(sql, masterEntity.getPrimaryField().getName(), entity.getName(), entity.getPrimaryField().getName(), slaveId.toLiteral());
|
||||
Object[] primary = Application.getQueryFactory().createQueryNoFilter(sql).unique();
|
||||
Field stmField = MetadataHelper.getSlaveToMasterField(entity);
|
||||
Assert.isTrue(stmField != null, "Non slave entty : " + slaveId);
|
||||
|
||||
Object[] primary = Application.getQueryFactory().uniqueNoFilter(slaveId, stmField.getName());
|
||||
return primary == null ? null : (ID) primary[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 扩展权限
|
||||
*
|
||||
* @param user
|
||||
* @param entry
|
||||
* @return
|
||||
* @see ZeroPrivileges
|
||||
* @see ZeroPermission
|
||||
*/
|
||||
public boolean allow(ID user, ZeroEntry entry) {
|
||||
Boolean a = userAllow(user);
|
||||
if (a != null) {
|
||||
return a;
|
||||
}
|
||||
|
||||
Role role = theUserStore.getUser(user).getOwningRole();
|
||||
if (RoleService.ADMIN_ROLE.equals(role.getIdentity())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (role.hasPrivileges(entry.name())) {
|
||||
return role.getPrivileges(entry.name()).allowed(ZeroPermission.ZERO);
|
||||
}
|
||||
return entry.getDefaultVal();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param user
|
||||
* @returny
|
||||
*/
|
||||
private Boolean userAllow(ID user) {
|
||||
if (UserService.ADMIN_USER.equals(user)) {
|
||||
return true;
|
||||
}
|
||||
if (!theUserStore.getUser(user).isActive()) {
|
||||
return false;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建查询过滤器
|
||||
|
@ -447,44 +482,4 @@ public class SecurityManager {
|
|||
}
|
||||
return new EntityQueryFilter(theUser, action);
|
||||
}
|
||||
|
||||
/**
|
||||
* 扩展权限
|
||||
*
|
||||
* @param user
|
||||
* @param entry
|
||||
* @return
|
||||
* @see ZeroPrivileges
|
||||
* @see ZeroPermission
|
||||
*/
|
||||
public boolean allowed(ID user, ZeroEntry entry) {
|
||||
Boolean a = allowedUser(user);
|
||||
if (a != null) {
|
||||
return a;
|
||||
}
|
||||
|
||||
Role role = theUserStore.getUser(user).getOwningRole();
|
||||
if (RoleService.ADMIN_ROLE.equals(role.getIdentity())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (role.hasPrivileges(entry.name())) {
|
||||
return role.getPrivileges(entry.name()).allowed(ZeroPermission.ZERO);
|
||||
}
|
||||
return entry.getDefaultVal();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param user
|
||||
* @return
|
||||
*/
|
||||
private Boolean allowedUser(ID user) {
|
||||
if (UserService.ADMIN_USER.equals(user)) {
|
||||
return true;
|
||||
}
|
||||
if (!theUserStore.getUser(user).isActive()) {
|
||||
return false;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,11 +26,29 @@ package com.rebuild.server.service.bizz.privileges;
|
|||
*/
|
||||
public enum ZeroEntry {
|
||||
|
||||
/**
|
||||
* 允许登录
|
||||
*/
|
||||
AllowLogin(true),
|
||||
/**
|
||||
* 允许批量修改
|
||||
*/
|
||||
AllowBatchUpdate(false),
|
||||
/**
|
||||
* 允许导入
|
||||
*/
|
||||
AllowDataImport(false),
|
||||
/**
|
||||
* 允许导出
|
||||
*/
|
||||
AllowDataExport(false),
|
||||
/**
|
||||
* 允许自定义导航菜单
|
||||
*/
|
||||
AllowCustomNav(true),
|
||||
/**
|
||||
* 允许自定义列表显示列
|
||||
*/
|
||||
AllowCustomDataList(true),
|
||||
|
||||
;
|
||||
|
|
|
@ -24,7 +24,6 @@ import com.rebuild.server.metadata.EntityHelper;
|
|||
import com.rebuild.server.metadata.entity.EasyMeta;
|
||||
import com.rebuild.server.service.OperatingContext;
|
||||
import com.rebuild.server.service.OperatingObserver;
|
||||
import com.rebuild.server.service.base.BulkOperatorTx;
|
||||
import org.apache.commons.lang.ArrayUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
|
@ -39,8 +38,8 @@ public class NotificationObserver extends OperatingObserver {
|
|||
@Override
|
||||
public void onAssign(OperatingContext context) {
|
||||
final ID related = context.getAfterRecord().getPrimary();
|
||||
if (BulkOperatorTx.isInTx()) {
|
||||
BulkOperatorTx.getInTxSet().add(related);
|
||||
if (NotificationOnce.didBegin()) {
|
||||
NotificationOnce.getMergeSet().add(related);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -56,8 +55,8 @@ public class NotificationObserver extends OperatingObserver {
|
|||
@Override
|
||||
public void onShare(OperatingContext context) {
|
||||
final ID related = context.getAfterRecord().getID("recordId");
|
||||
if (BulkOperatorTx.isInTx()) {
|
||||
BulkOperatorTx.getInTxSet().add(related);
|
||||
if (NotificationOnce.didBegin()) {
|
||||
NotificationOnce.getMergeSet().add(related);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
rebuild - Building your business-systems freely.
|
||||
Copyright (C) 2018-2019 devezhao <zhaofang123@gmail.com>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.rebuild.server.service.notification;
|
||||
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 消息合并发送,用于批量操作时合并消息。
|
||||
* Using:
|
||||
* <tt>begin</tt>
|
||||
* <tt>[didBegin]</tt>
|
||||
* <tt>[getMergeSet]</tt>
|
||||
* <tt>end</tt>
|
||||
*
|
||||
* @author ZHAO
|
||||
* @since 2019/12/2
|
||||
*/
|
||||
public class NotificationOnce {
|
||||
|
||||
private static final ThreadLocal<Set<ID>> STATE = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* 启动合并,此后线程中的消息发送将忽略
|
||||
*/
|
||||
public static void begin() {
|
||||
STATE.set(new LinkedHashSet<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否开始了
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static boolean didBegin() {
|
||||
return STATE.get() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并结束
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Set<ID> end() {
|
||||
Set<ID> set = getMergeSet();
|
||||
STATE.remove();
|
||||
return Collections.unmodifiableSet(set);
|
||||
}
|
||||
|
||||
/**
|
||||
* 被合并到一次发送的记录
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected static Set<ID> getMergeSet() {
|
||||
return STATE.get();
|
||||
}
|
||||
}
|
|
@ -170,8 +170,8 @@ public class AppUtils {
|
|||
* @return
|
||||
* @see com.rebuild.server.service.bizz.privileges.SecurityManager
|
||||
*/
|
||||
public static boolean allowed(HttpServletRequest request, ZeroEntry entry) {
|
||||
return Application.getSecurityManager().allowed(getRequestUser(request), entry);
|
||||
public static boolean allow(HttpServletRequest request, ZeroEntry entry) {
|
||||
return Application.getSecurityManager().allow(getRequestUser(request), entry);
|
||||
}
|
||||
|
||||
public static final String SK_LOCALE = WebUtils.KEY_PREFIX + ".LOCALE";
|
||||
|
|
|
@ -70,7 +70,7 @@ public class JSONUtils {
|
|||
* @param valuesArray
|
||||
* @return
|
||||
*/
|
||||
public static JSONArray toJSONArray(String[] keys, Object[][] valuesArray) {
|
||||
public static JSONArray toJSONObjectArray(String[] keys, Object[][] valuesArray) {
|
||||
List<Map<String, Object>> array = new ArrayList<>();
|
||||
for (Object[] o : valuesArray) {
|
||||
Map<String, Object> map = new HashMap<>(keys.length);
|
||||
|
|
|
@ -100,7 +100,7 @@ public abstract class BaseEntityControll extends BasePageControll {
|
|||
};
|
||||
Map<String, Boolean> actionMap = new HashMap<>();
|
||||
for (Permission act : actions) {
|
||||
actionMap.put(act.getName(), Application.getSecurityManager().allowed(user, record, act));
|
||||
actionMap.put(act.getName(), Application.getSecurityManager().allow(user, record, act));
|
||||
}
|
||||
mv.getModel().put("entityPrivileges", JSON.toJSONString(actionMap));
|
||||
} else {
|
||||
|
|
|
@ -70,7 +70,7 @@ public class SysConfigurationControll extends BasePageControll {
|
|||
return mv;
|
||||
}
|
||||
|
||||
@RequestMapping("systems/submail")
|
||||
@RequestMapping("integration/submail")
|
||||
public ModelAndView pageIntegrationSubmail() {
|
||||
ModelAndView mv = createModelAndView("/admin/integration/submail.jsp");
|
||||
mv.getModel().put("smsAccount",
|
||||
|
|
|
@ -85,7 +85,7 @@ public class RolePrivilegesControll extends BaseEntityControll {
|
|||
@RequestMapping("role-list")
|
||||
public void roleList(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
Object[][] array = Application.createQuery("select roleId,name,isDisabled from Role").array();
|
||||
JSON retJson = JSONUtils.toJSONArray(new String[] { "id", "name", "disabled" }, array);
|
||||
JSON retJson = JSONUtils.toJSONObjectArray(new String[] { "id", "name", "disabled" }, array);
|
||||
writeSuccess(response, retJson);
|
||||
}
|
||||
|
||||
|
@ -107,7 +107,7 @@ public class RolePrivilegesControll extends BaseEntityControll {
|
|||
}
|
||||
}
|
||||
|
||||
JSON retJson = JSONUtils.toJSONArray(new String[] { "name", "definition" }, array);
|
||||
JSON retJson = JSONUtils.toJSONObjectArray(new String[] { "name", "definition" }, array);
|
||||
writeSuccess(response, retJson);
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
package com.rebuild.web.admin.entityhub;
|
||||
|
||||
import cn.devezhao.commons.CodecUtils;
|
||||
import cn.devezhao.commons.ThreadPool;
|
||||
import cn.devezhao.commons.excel.Cell;
|
||||
import cn.devezhao.commons.web.ServletUtils;
|
||||
import cn.devezhao.persist4j.Entity;
|
||||
|
@ -27,14 +26,11 @@ import cn.devezhao.persist4j.Field;
|
|||
import cn.devezhao.persist4j.engine.ID;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.alibaba.fastjson.serializer.SerializeConfig;
|
||||
import com.alibaba.fastjson.serializer.ToStringSerializer;
|
||||
import com.rebuild.server.Application;
|
||||
import com.rebuild.server.business.dataimport.DataFileParser;
|
||||
import com.rebuild.server.business.dataimport.DataImporter;
|
||||
import com.rebuild.server.business.dataimport.ImportRule;
|
||||
import com.rebuild.server.helper.SysConfiguration;
|
||||
import com.rebuild.server.helper.task.HeavyTask;
|
||||
import com.rebuild.server.helper.task.TaskExecutors;
|
||||
import com.rebuild.server.metadata.EntityHelper;
|
||||
import com.rebuild.server.metadata.MetadataHelper;
|
||||
|
@ -57,20 +53,15 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author devezhao
|
||||
* @since 01/03/2019
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/admin/datas/")
|
||||
public class DataImportControll extends BasePageControll {
|
||||
|
||||
static {
|
||||
SerializeConfig.getGlobalInstance().put(Cell.class, ToStringSerializer.instance);
|
||||
}
|
||||
|
||||
@RequestMapping("/data-importer")
|
||||
public ModelAndView pageDataImports(HttpServletRequest request) {
|
||||
public ModelAndView pageDataImports() {
|
||||
return createModelAndView("/admin/entityhub/data-importer.jsp");
|
||||
}
|
||||
|
||||
|
@ -84,9 +75,9 @@ public class DataImportControll extends BasePageControll {
|
|||
return;
|
||||
}
|
||||
|
||||
DataFileParser parser = null;
|
||||
DataFileParser parser;
|
||||
int count = -1;
|
||||
List<Cell[]> preview = null;
|
||||
List<Cell[]> preview;
|
||||
try {
|
||||
parser = new DataFileParser(tmp);
|
||||
preview = parser.parse(11);
|
||||
|
@ -100,15 +91,16 @@ public class DataImportControll extends BasePageControll {
|
|||
new String[] { "count", "preview" }, new Object[] { count, preview });
|
||||
writeSuccess(response, ret);
|
||||
}
|
||||
|
||||
@RequestMapping("/data-importer/check-user-privileges")
|
||||
|
||||
// 检查所属用户权限
|
||||
@RequestMapping("/data-importer/check-user")
|
||||
public void checkUserPrivileges(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
ID ouser = getIdParameterNotNull(request, "ouser");
|
||||
ID user = getIdParameterNotNull(request, "user");
|
||||
String entity = getParameterNotNull(request, "entity");
|
||||
|
||||
Entity entityMeta = MetadataHelper.getEntity(entity);
|
||||
boolean canCreated = Application.getSecurityManager().allowedC(ouser, entityMeta.getEntityCode());
|
||||
boolean canUpdated = Application.getSecurityManager().allowedU(ouser, entityMeta.getEntityCode());
|
||||
boolean canCreated = Application.getSecurityManager().allowCreate(user, entityMeta.getEntityCode());
|
||||
boolean canUpdated = Application.getSecurityManager().allowUpdate(user, entityMeta.getEntityCode());
|
||||
|
||||
JSON ret = JSONUtils.toJSONObject(
|
||||
new String[] { "canCreate", "canUpdate" }, new Object[] { canCreated, canUpdated });
|
||||
|
@ -123,10 +115,10 @@ public class DataImportControll extends BasePageControll {
|
|||
List<Map<String, Object>> list = new ArrayList<>();
|
||||
for (Field field : MetadataSorter.sortFields(entityBase)) {
|
||||
String fieldName = field.getName();
|
||||
if (EntityHelper.OwningDept.equals(fieldName)) {
|
||||
if (EntityHelper.OwningDept.equals(fieldName)
|
||||
|| MetadataHelper.isApprovalField(fieldName) || MetadataHelper.isSystemField(fieldName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
EasyMeta easyMeta = new EasyMeta(field);
|
||||
if (easyMeta.getDisplayType() == DisplayType.FILE || easyMeta.getDisplayType() == DisplayType.IMAGE) {
|
||||
continue;
|
||||
|
@ -136,7 +128,7 @@ public class DataImportControll extends BasePageControll {
|
|||
map.put("name", fieldName);
|
||||
map.put("label", easyMeta.getLabel());
|
||||
map.put("type", easyMeta.getDisplayType().getDisplayName());
|
||||
map.put("isNullable", field.isNullable());
|
||||
map.put("nullable", field.isNullable());
|
||||
|
||||
String defaultValue = null;
|
||||
if (EntityHelper.CreatedOn.equals(fieldName) || EntityHelper.ModifiedOn.equals(fieldName)) {
|
||||
|
@ -158,7 +150,7 @@ public class DataImportControll extends BasePageControll {
|
|||
@RequestMapping("/data-importer/import-submit")
|
||||
public void importSubmit(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
JSONObject idata = (JSONObject) ServletUtils.getRequestJson(request);
|
||||
ImportRule irule = null;
|
||||
ImportRule irule;
|
||||
try {
|
||||
irule = ImportRule.parse(idata);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
|
@ -166,7 +158,7 @@ public class DataImportControll extends BasePageControll {
|
|||
return;
|
||||
}
|
||||
|
||||
DataImporter importer = new DataImporter(irule, getRequestUser(request));
|
||||
DataImporter importer = new DataImporter(irule);
|
||||
if (getBoolParameter(request, "preview")) {
|
||||
// TODO 导入预览
|
||||
} else {
|
||||
|
@ -175,57 +167,7 @@ public class DataImportControll extends BasePageControll {
|
|||
writeSuccess(response, ret);
|
||||
}
|
||||
}
|
||||
|
||||
// 导入状态
|
||||
@RequestMapping("/data-importer/import-state")
|
||||
public void importState(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
String taskid = getParameterNotNull(request, "taskid");
|
||||
HeavyTask<?> task = TaskExecutors.getTask(taskid);
|
||||
if (task == null) {
|
||||
writeFailure(response, "无效任务 : " + taskid);
|
||||
return;
|
||||
}
|
||||
|
||||
writeSuccess(response, formatTaskState((DataImporter) task));
|
||||
}
|
||||
|
||||
// 导入取消
|
||||
@RequestMapping("/data-importer/import-cancel")
|
||||
public void importCancel(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
String taskid = getParameterNotNull(request, "taskid");
|
||||
HeavyTask<?> task = TaskExecutors.getTask(taskid);
|
||||
if (task == null) {
|
||||
writeFailure(response, "无效任务 : " + taskid);
|
||||
return;
|
||||
}
|
||||
if (task.isCompleted()) {
|
||||
writeFailure(response, "无法终止,因为导入任务已执行完成");
|
||||
return;
|
||||
}
|
||||
|
||||
task.interrupt();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
if (task.isInterrupted()) {
|
||||
writeSuccess(response, formatTaskState((DataImporter) task));
|
||||
return;
|
||||
}
|
||||
ThreadPool.waitFor(200);
|
||||
}
|
||||
writeFailure(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param task
|
||||
* @return
|
||||
*/
|
||||
private JSON formatTaskState(DataImporter task) {
|
||||
JSON state = JSONUtils.toJSONObject(
|
||||
new String[] { "total", "complete", "success", "isCompleted", "isInterrupted", "elapsedTime" },
|
||||
new Object[] { task.getTotal(), task.getCompleted(),
|
||||
task.getSuccessed(), task.isCompleted(), task.isInterrupted(), task.getElapsedTime() });
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param file
|
||||
* @return
|
||||
|
|
|
@ -18,33 +18,86 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
|
||||
package com.rebuild.web.base;
|
||||
|
||||
import cn.devezhao.commons.ThreadPool;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.rebuild.server.helper.task.HeavyTask;
|
||||
import com.rebuild.server.helper.task.TaskExecutors;
|
||||
import com.rebuild.utils.JSONUtils;
|
||||
import com.rebuild.web.BaseControll;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
*
|
||||
* 任务操作入口类
|
||||
*
|
||||
* @author devezhao
|
||||
* @since 09/29/2018
|
||||
*
|
||||
* @see HeavyTask
|
||||
* @see TaskExecutors
|
||||
*/
|
||||
@RequestMapping("/commons/task/")
|
||||
@Controller
|
||||
public class HeavyTaskControll extends BaseControll {
|
||||
|
||||
// 任务状态
|
||||
@RequestMapping("state")
|
||||
public void checkState(HttpServletRequest request, HttpServletResponse response) {
|
||||
String taskid = getParameterNotNull(request, "taskid");
|
||||
HeavyTask<?> task = TaskExecutors.getTask(taskid);
|
||||
JSON ret = JSONUtils.toJSONObject(
|
||||
new String[] { "taskid", "completed", "hasError" },
|
||||
new Object[] { taskid, task.getCompletedPercent(), task.getErrorMessage() });
|
||||
writeSuccess(response, ret);
|
||||
if (task == null) {
|
||||
writeFailure(response, "无效任务 : " + taskid);
|
||||
return;
|
||||
}
|
||||
|
||||
JSON state = formatTaskState(task);
|
||||
writeSuccess(response, state);
|
||||
}
|
||||
|
||||
// 中断任务
|
||||
@RequestMapping("cancel")
|
||||
public void importCancel(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
String taskid = getParameterNotNull(request, "taskid");
|
||||
HeavyTask<?> task = TaskExecutors.getTask(taskid);
|
||||
if (task == null) {
|
||||
writeFailure(response, "无效任务 : " + taskid);
|
||||
return;
|
||||
}
|
||||
if (task.isCompleted()) {
|
||||
writeFailure(response, "无法终止,因为任务已经完成");
|
||||
return;
|
||||
}
|
||||
|
||||
task.interrupt();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
if (task.isInterrupted()) {
|
||||
writeSuccess(response, formatTaskState(task));
|
||||
return;
|
||||
}
|
||||
ThreadPool.waitFor(200);
|
||||
}
|
||||
writeFailure(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化任务状态信息
|
||||
*
|
||||
* @param task
|
||||
* @return
|
||||
*/
|
||||
public static JSON formatTaskState(HeavyTask<?> task) {
|
||||
JSONObject state = new JSONObject();
|
||||
state.put("progress", task.getCompletedPercent());
|
||||
state.put("completed", task.getCompleted());
|
||||
state.put("succeeded", task.getSucceeded());
|
||||
state.put("isCompleted", task.isCompleted());
|
||||
state.put("isInterrupted", task.isInterrupted());
|
||||
state.put("elapsedTime", task.getElapsedTime());
|
||||
state.put("hasError", task.getErrorMessage());
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,13 +22,7 @@ import cn.devezhao.persist4j.Entity;
|
|||
import cn.devezhao.persist4j.Field;
|
||||
import cn.devezhao.persist4j.dialect.FieldType;
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.rebuild.server.Application;
|
||||
import com.rebuild.server.configuration.portals.ClassificationManager;
|
||||
import com.rebuild.server.configuration.portals.FieldPortalAttrs;
|
||||
import com.rebuild.server.configuration.portals.MultiSelectManager;
|
||||
import com.rebuild.server.configuration.portals.PickListManager;
|
||||
import com.rebuild.server.helper.state.StateManager;
|
||||
import com.rebuild.server.metadata.EntityHelper;
|
||||
import com.rebuild.server.metadata.MetadataHelper;
|
||||
import com.rebuild.server.metadata.MetadataSorter;
|
||||
|
@ -97,14 +91,9 @@ public class MetadataGetting extends BaseControll {
|
|||
|
||||
int entityCode = field.getReferenceEntity().getEntityCode();
|
||||
if (!(MetadataHelper.isBizzEntity(entityCode) || entityCode == EntityHelper.RobotApprovalConfig)) {
|
||||
// 显示父级字段
|
||||
Map<String, Object> parent = new HashMap<>();
|
||||
parent.put("name", field.getName());
|
||||
parent.put("label", easyField.getLabel());
|
||||
parent.put("type", easyField.getDisplayType().name());
|
||||
parent.put("creatable", field.isCreatable());
|
||||
list.add(parent);
|
||||
|
||||
// 父级引用字段
|
||||
list.add(buildField(field));
|
||||
// 引用实体字段
|
||||
putFields(list, field.getReferenceEntity(), false, easyField, fromType);
|
||||
}
|
||||
}
|
||||
|
@ -127,14 +116,9 @@ public class MetadataGetting extends BaseControll {
|
|||
continue;
|
||||
}
|
||||
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("name", field.getName());
|
||||
map.put("creatable", field.isCreatable());
|
||||
EasyMeta easyMeta = new EasyMeta(field);
|
||||
map.put("label", easyMeta.getLabel());
|
||||
DisplayType dt = easyMeta.getDisplayType();
|
||||
map.put("type", dt.name());
|
||||
if (dt == DisplayType.REFERENCE) {
|
||||
Map<String, Object> map = buildField(field);
|
||||
// 引用字段处理
|
||||
if (EasyMeta.getDisplayType(field) == DisplayType.REFERENCE) {
|
||||
Entity refEntity = field.getReferenceEntity();
|
||||
// Bizz 字段前台有特殊处理
|
||||
boolean isBizzField = MetadataHelper.isBizzEntity(refEntity.getEntityCode());
|
||||
|
@ -158,6 +142,22 @@ public class MetadataGetting extends BaseControll {
|
|||
dest.add(map);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param field
|
||||
* @return
|
||||
*/
|
||||
public static Map<String, Object> buildField(Field field) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
EasyMeta easyMeta = new EasyMeta(field);
|
||||
map.put("name", field.getName());
|
||||
map.put("label", easyMeta.getLabel());
|
||||
map.put("type", easyMeta.getDisplayType().name());
|
||||
map.put("nullable", field.isNullable());
|
||||
map.put("creatable", field.isCreatable());
|
||||
map.put("updatable", field.isUpdatable());
|
||||
return map;
|
||||
}
|
||||
|
||||
// 哪些实体引用了指定实体
|
||||
@RequestMapping("references")
|
||||
|
@ -172,7 +172,7 @@ public class MetadataGetting extends BaseControll {
|
|||
references.add(own);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
List<String[]> list = new ArrayList<>();
|
||||
for (Entity e : references) {
|
||||
EasyMeta easy = new EasyMeta(e);
|
||||
|
@ -180,66 +180,4 @@ public class MetadataGetting extends BaseControll {
|
|||
}
|
||||
writeSuccess(response, list);
|
||||
}
|
||||
|
||||
// --
|
||||
|
||||
// PickList/State 值列表
|
||||
@RequestMapping({ "picklist", "field-options" })
|
||||
public void fetchPicklist(HttpServletRequest request, HttpServletResponse response) {
|
||||
String entity = getParameterNotNull(request, "entity");
|
||||
String field = getParameterNotNull(request, "field");
|
||||
|
||||
Field fieldMeta = getRealField(entity, field);
|
||||
DisplayType dt = EasyMeta.getDisplayType(fieldMeta);
|
||||
|
||||
JSON options = null;
|
||||
if (dt == DisplayType.STATE) {
|
||||
options = StateManager.instance.getStateOptions(fieldMeta);
|
||||
}
|
||||
else if (dt == DisplayType.MULTISELECT) {
|
||||
options = MultiSelectManager.instance.getSelectList(fieldMeta);
|
||||
}
|
||||
else {
|
||||
options = PickListManager.instance.getPickList(fieldMeta);
|
||||
}
|
||||
writeSuccess(response, options);
|
||||
}
|
||||
|
||||
// Classification 值列表
|
||||
@RequestMapping("classification")
|
||||
public void fetchClassification(HttpServletRequest request, HttpServletResponse response) {
|
||||
String entity = getParameterNotNull(request, "entity");
|
||||
String field = getParameterNotNull(request, "field");
|
||||
|
||||
Field fieldMeta = getRealField(entity, field);
|
||||
ID useClassification = ClassificationManager.instance.getUseClassification(fieldMeta, true);
|
||||
if (useClassification == null) {
|
||||
writeFailure(response, "分类字段配置有误");
|
||||
return;
|
||||
}
|
||||
|
||||
ID parent = getIdParameter(request, "parent");
|
||||
String sql = "select itemId,name from ClassificationData where dataId = ? and isHide = 'F' and ";
|
||||
if (parent != null) {
|
||||
sql += "parent = '" + parent + "'";
|
||||
} else {
|
||||
sql += "parent is null";
|
||||
}
|
||||
sql += " order by code, name";
|
||||
Object[][] data = Application.createQueryNoFilter(sql)
|
||||
.setParameter(1, useClassification)
|
||||
.setLimit(500) // 最多显示
|
||||
.array();
|
||||
writeSuccess(response, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param entity
|
||||
* @param field
|
||||
* @return
|
||||
*/
|
||||
private Field getRealField(String entity, String field) {
|
||||
Entity entityMeta = MetadataHelper.getEntity(entity);
|
||||
return MetadataHelper.getLastJoinField(entityMeta, field);
|
||||
}
|
||||
}
|
||||
|
|
104
src/main/java/com/rebuild/web/base/PicklistDataControll.java
Normal file
104
src/main/java/com/rebuild/web/base/PicklistDataControll.java
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
rebuild - Building your business-systems freely.
|
||||
Copyright (C) 2018-2019 devezhao <zhaofang123@gmail.com>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.rebuild.web.base;
|
||||
|
||||
import cn.devezhao.persist4j.Entity;
|
||||
import cn.devezhao.persist4j.Field;
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.rebuild.server.Application;
|
||||
import com.rebuild.server.configuration.portals.ClassificationManager;
|
||||
import com.rebuild.server.configuration.portals.MultiSelectManager;
|
||||
import com.rebuild.server.configuration.portals.PickListManager;
|
||||
import com.rebuild.server.helper.state.StateManager;
|
||||
import com.rebuild.server.metadata.MetadataHelper;
|
||||
import com.rebuild.server.metadata.entity.DisplayType;
|
||||
import com.rebuild.server.metadata.entity.EasyMeta;
|
||||
import com.rebuild.web.BaseControll;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 下拉列表型字段值列表
|
||||
*
|
||||
* @author ZHAO
|
||||
* @since 2019/12/1
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/commons/metadata/")
|
||||
public class PicklistDataControll extends BaseControll {
|
||||
|
||||
// for PickList/State
|
||||
@RequestMapping({ "picklist", "field-options" })
|
||||
public void fetchPicklist(HttpServletRequest request, HttpServletResponse response) {
|
||||
String entity = getParameterNotNull(request, "entity");
|
||||
String field = getParameterNotNull(request, "field");
|
||||
|
||||
Field fieldMeta = getRealField(entity, field);
|
||||
DisplayType dt = EasyMeta.getDisplayType(fieldMeta);
|
||||
|
||||
JSON options;
|
||||
if (dt == DisplayType.STATE) {
|
||||
options = StateManager.instance.getStateOptions(fieldMeta);
|
||||
}
|
||||
else if (dt == DisplayType.MULTISELECT) {
|
||||
options = MultiSelectManager.instance.getSelectList(fieldMeta);
|
||||
}
|
||||
else {
|
||||
options = PickListManager.instance.getPickList(fieldMeta);
|
||||
}
|
||||
writeSuccess(response, options);
|
||||
}
|
||||
|
||||
// for Classification
|
||||
@RequestMapping("classification")
|
||||
public void fetchClassification(HttpServletRequest request, HttpServletResponse response) {
|
||||
String entity = getParameterNotNull(request, "entity");
|
||||
String field = getParameterNotNull(request, "field");
|
||||
|
||||
Field fieldMeta = getRealField(entity, field);
|
||||
ID useClassification = ClassificationManager.instance.getUseClassification(fieldMeta, true);
|
||||
if (useClassification == null) {
|
||||
writeFailure(response, "分类字段配置有误");
|
||||
return;
|
||||
}
|
||||
|
||||
ID parent = getIdParameter(request, "parent");
|
||||
String sql = "select itemId,name from ClassificationData where dataId = ? and isHide = 'F' and ";
|
||||
if (parent != null) {
|
||||
sql += "parent = '" + parent + "'";
|
||||
} else {
|
||||
sql += "parent is null";
|
||||
}
|
||||
sql += " order by code, name";
|
||||
Object[][] data = Application.createQueryNoFilter(sql)
|
||||
.setParameter(1, useClassification)
|
||||
.setLimit(500) // 最多显示
|
||||
.array();
|
||||
writeSuccess(response, data);
|
||||
}
|
||||
|
||||
private Field getRealField(String entity, String fieldPath) {
|
||||
Entity entityMeta = MetadataHelper.getEntity(entity);
|
||||
return MetadataHelper.getLastJoinField(entityMeta, fieldPath);
|
||||
}
|
||||
}
|
|
@ -71,7 +71,7 @@ public class DataListSettingsControll extends BaseControll implements PortalsCon
|
|||
public void sets(@PathVariable String entity,
|
||||
HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
ID user = getRequestUser(request);
|
||||
Assert.isTrue(Application.getSecurityManager().allowed(user, ZeroEntry.AllowCustomDataList), "没有权限");
|
||||
Assert.isTrue(Application.getSecurityManager().allow(user, ZeroEntry.AllowCustomDataList), "没有权限");
|
||||
|
||||
JSON config = ServletUtils.getRequestJson(request);
|
||||
ID cfgid = getIdParameter(request, "id");
|
||||
|
@ -116,7 +116,7 @@ public class DataListSettingsControll extends BaseControll implements PortalsCon
|
|||
}
|
||||
Entity refEntity = field.getReferenceEntity();
|
||||
// 无权限的不返回
|
||||
if (!Application.getSecurityManager().allowedR(user, refEntity.getEntityCode())) {
|
||||
if (!Application.getSecurityManager().allowRead(user, refEntity.getEntityCode())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ public class NavSettings extends BaseControll implements PortalsConfiguration {
|
|||
@RequestMapping(value = "nav-settings", method = RequestMethod.POST)
|
||||
public void sets(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
ID user = getRequestUser(request);
|
||||
Assert.isTrue(Application.getSecurityManager().allowed(user, ZeroEntry.AllowCustomNav), "没有权限");
|
||||
Assert.isTrue(Application.getSecurityManager().allow(user, ZeroEntry.AllowCustomNav), "没有权限");
|
||||
|
||||
JSON config = ServletUtils.getRequestJson(request);
|
||||
ID cfgid = getIdParameter(request, "id");
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
rebuild - Building your business-systems freely.
|
||||
Copyright (C) 2018-2019 devezhao <zhaofang123@gmail.com>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.rebuild.web.base.general;
|
||||
|
||||
import cn.devezhao.bizz.privileges.impl.BizzPermission;
|
||||
import cn.devezhao.commons.web.ServletUtils;
|
||||
import cn.devezhao.persist4j.Entity;
|
||||
import cn.devezhao.persist4j.Field;
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.rebuild.server.Application;
|
||||
import com.rebuild.server.configuration.portals.MultiSelectManager;
|
||||
import com.rebuild.server.configuration.portals.PickListManager;
|
||||
import com.rebuild.server.helper.state.StateManager;
|
||||
import com.rebuild.server.metadata.MetadataHelper;
|
||||
import com.rebuild.server.metadata.MetadataSorter;
|
||||
import com.rebuild.server.metadata.entity.DisplayType;
|
||||
import com.rebuild.server.metadata.entity.EasyMeta;
|
||||
import com.rebuild.server.metadata.entity.FieldExtConfigProps;
|
||||
import com.rebuild.server.service.EntityService;
|
||||
import com.rebuild.server.service.ServiceSpec;
|
||||
import com.rebuild.server.service.base.BulkContext;
|
||||
import com.rebuild.server.service.bizz.privileges.ZeroEntry;
|
||||
import com.rebuild.utils.JSONUtils;
|
||||
import com.rebuild.web.BaseControll;
|
||||
import com.rebuild.web.base.MetadataGetting;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 批量修改
|
||||
*
|
||||
* @author ZHAO
|
||||
* @since 2019/12/1
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/app/{entity}/")
|
||||
public class BatchUpdateControll extends BaseControll {
|
||||
|
||||
@RequestMapping("batch-update/submit")
|
||||
public void submit(@PathVariable String entity,
|
||||
HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
ID user = getRequestUser(request);
|
||||
Assert.isTrue(Application.getSecurityManager().allow(user, ZeroEntry.AllowBatchUpdate), "没有权限");
|
||||
|
||||
JSONObject requestData = (JSONObject) ServletUtils.getRequestJson(request);
|
||||
|
||||
int dataRange = getIntParameter(request, "dr", 2);
|
||||
requestData.put("_dataRange", dataRange);
|
||||
requestData.put("entity", entity);
|
||||
BulkContext bulkContext = new BulkContext(user, BizzPermission.UPDATE, requestData);
|
||||
|
||||
Entity entityMeta = MetadataHelper.getEntity(entity);
|
||||
ServiceSpec ies = Application.getService(entityMeta.getEntityCode());
|
||||
String taskid = ((EntityService) ies).bulkAsync(bulkContext);
|
||||
|
||||
writeSuccess(response, taskid);
|
||||
}
|
||||
|
||||
// 获取可更新字段
|
||||
@RequestMapping("batch-update/fields")
|
||||
public void getFields(@PathVariable String entity, HttpServletResponse response) throws IOException {
|
||||
Entity entityMeta = MetadataHelper.getEntity(entity);
|
||||
|
||||
List<Map<String, Object>> updatableFields = new ArrayList<>();
|
||||
for (Field field : MetadataSorter.sortFields(entityMeta)) {
|
||||
if (MetadataHelper.isSystemField(field) || !field.isUpdatable()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
EasyMeta easyMeta = EasyMeta.valueOf(field);
|
||||
if (!easyMeta.isUpdatable()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DisplayType dt = easyMeta.getDisplayType();
|
||||
// 不支持的字段
|
||||
if (dt == DisplayType.FILE || dt == DisplayType.IMAGE || dt == DisplayType.AVATAR
|
||||
|| dt == DisplayType.LOCATION || dt == DisplayType.SERIES || dt == DisplayType.ANYREFERENCE
|
||||
|| dt == DisplayType.NTEXT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
updatableFields.add(this.buildField(field, dt));
|
||||
}
|
||||
writeSuccess(response, updatableFields);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param field
|
||||
* @param dt
|
||||
* @return
|
||||
*/
|
||||
private Map<String, Object> buildField(Field field, DisplayType dt) {
|
||||
Map<String, Object> map = MetadataGetting.buildField(field);
|
||||
|
||||
// 字段选项
|
||||
if (dt == DisplayType.PICKLIST) {
|
||||
map.put("options", PickListManager.instance.getPickList(field));
|
||||
} else if (dt == DisplayType.STATE) {
|
||||
map.put("options", StateManager.instance.getStateOptions(field));
|
||||
} else if (dt == DisplayType.MULTISELECT) {
|
||||
map.put("options", MultiSelectManager.instance.getSelectList(field));
|
||||
} else if (dt == DisplayType.BOOL) {
|
||||
JSONArray options = new JSONArray();
|
||||
options.add(JSONUtils.toJSONObject(new String[] { "id", "text" }, new Object[] { true, "是"}));
|
||||
options.add(JSONUtils.toJSONObject(new String[] { "id", "text" }, new Object[] { false, "否"}));
|
||||
map.put("options", options);
|
||||
} else if (dt == DisplayType.NUMBER || dt == DisplayType.DECIMAL) {
|
||||
map.put(FieldExtConfigProps.NUMBER_NOTNEGATIVE,
|
||||
EasyMeta.valueOf(field).getPropOfFieldExtConfig(FieldExtConfigProps.NUMBER_NOTNEGATIVE));
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
}
|
|
@ -23,10 +23,12 @@ import cn.devezhao.persist4j.engine.ID;
|
|||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.rebuild.server.Application;
|
||||
import com.rebuild.server.business.dataimport.DataExporter;
|
||||
import com.rebuild.server.helper.datalist.BatchOperatorQuery;
|
||||
import com.rebuild.server.service.bizz.privileges.ZeroEntry;
|
||||
import com.rebuild.web.BaseControll;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
@ -41,25 +43,18 @@ import java.io.IOException;
|
|||
@Controller
|
||||
public class DataExportControll extends BaseControll {
|
||||
|
||||
@RequestMapping("/app/entity/data-export-submit")
|
||||
public void export(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
@RequestMapping("/app/{entity}/data-export/submit")
|
||||
public void export(@PathVariable String entity,
|
||||
HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
ID user = getRequestUser(request);
|
||||
Assert.isTrue(Application.getSecurityManager().allowed(user, ZeroEntry.AllowDataExport), "没有权限");
|
||||
Assert.isTrue(Application.getSecurityManager().allow(user, ZeroEntry.AllowDataExport), "没有权限");
|
||||
|
||||
int dataRange = getIntParameter(request, "dr", 2);
|
||||
JSONObject query = (JSONObject) ServletUtils.getRequestJson(request);
|
||||
if (query == null) {
|
||||
query = new JSONObject();
|
||||
}
|
||||
|
||||
if (dataRange == 3 || dataRange == 2) {
|
||||
query.put("pageSize", 65535); // Max rows
|
||||
query.put("pageNo", 1);
|
||||
query.put("reload", false);
|
||||
}
|
||||
JSONObject queryData = (JSONObject) ServletUtils.getRequestJson(request);
|
||||
queryData = new BatchOperatorQuery(dataRange, queryData).wrapQueryData(DataExporter.MAX_ROWS);
|
||||
|
||||
try {
|
||||
File file = new DataExporter(query).setUser(user).export();
|
||||
File file = new DataExporter(queryData).setUser(user).export();
|
||||
writeSuccess(response, file.getName());
|
||||
} catch (Exception ex) {
|
||||
writeFailure(response, ex.getLocalizedMessage());
|
||||
|
|
|
@ -62,7 +62,7 @@ public class GeneralDataListControll extends BaseEntityControll {
|
|||
}
|
||||
|
||||
Entity thatEntity = MetadataHelper.getEntity(entity);
|
||||
if (!Application.getSecurityManager().allowedR(user, thatEntity.getEntityCode())) {
|
||||
if (!Application.getSecurityManager().allowRead(user, thatEntity.getEntityCode())) {
|
||||
response.sendError(403, "你没有访问此实体的权限");
|
||||
return null;
|
||||
}
|
||||
|
@ -79,9 +79,11 @@ public class GeneralDataListControll extends BaseEntityControll {
|
|||
|
||||
// 列表相关权限
|
||||
mv.getModel().put(ZeroEntry.AllowCustomDataList.name(),
|
||||
Application.getSecurityManager().allowed(user, ZeroEntry.AllowCustomDataList));
|
||||
Application.getSecurityManager().allow(user, ZeroEntry.AllowCustomDataList));
|
||||
mv.getModel().put(ZeroEntry.AllowDataExport.name(),
|
||||
Application.getSecurityManager().allowed(user, ZeroEntry.AllowDataExport));
|
||||
Application.getSecurityManager().allow(user, ZeroEntry.AllowDataExport));
|
||||
mv.getModel().put(ZeroEntry.AllowBatchUpdate.name(),
|
||||
Application.getSecurityManager().allow(user, ZeroEntry.AllowBatchUpdate));
|
||||
|
||||
// 展开 WIDGET 面板
|
||||
String asideCollapsed = ServletUtils.readCookie(request, "rb.asideCollapsed");
|
||||
|
|
|
@ -53,7 +53,7 @@ public class GeneralModelControll extends BaseEntityControll {
|
|||
final ID user = getRequestUser(request);
|
||||
Entity thatEntity = MetadataHelper.getEntity(entity);
|
||||
|
||||
if (!Application.getSecurityManager().allowedR(user, thatEntity.getEntityCode())) {
|
||||
if (!Application.getSecurityManager().allowRead(user, thatEntity.getEntityCode())) {
|
||||
response.sendError(403, "你没有访问此实体的权限");
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ import com.rebuild.utils.JSONUtils;
|
|||
import com.rebuild.web.BasePageControll;
|
||||
import com.rebuild.web.common.FileDownloader;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
|
@ -42,22 +43,21 @@ import java.io.File;
|
|||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 视图打印
|
||||
* 报表/打印
|
||||
*
|
||||
* @author devezhao
|
||||
* @since 2019/8/3
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/app/entity/")
|
||||
@RequestMapping("/app/{entity}/")
|
||||
public class ReportsControll extends BasePageControll {
|
||||
|
||||
@RequestMapping("print")
|
||||
public ModelAndView printPreview(HttpServletRequest request) {
|
||||
public ModelAndView printPreview(@PathVariable String entity, HttpServletRequest request) {
|
||||
ID user = getRequestUser(request);
|
||||
ID recordId = getIdParameterNotNull(request, "id");
|
||||
Entity entity = MetadataHelper.getEntity(recordId.getEntityCode());
|
||||
|
||||
JSON model = FormsBuilder.instance.buildView(entity.getName(), user, recordId);
|
||||
JSON model = FormsBuilder.instance.buildView(entity, user, recordId);
|
||||
|
||||
ModelAndView mv = createModelAndView("/general-entity/print-preview.jsp");
|
||||
mv.getModel().put("contentBody", model);
|
||||
|
@ -67,17 +67,16 @@ public class ReportsControll extends BasePageControll {
|
|||
return mv;
|
||||
}
|
||||
|
||||
@RequestMapping("available-reports")
|
||||
public void availableReports(HttpServletRequest request, HttpServletResponse response) {
|
||||
String entity = getParameterNotNull(request, "entity");
|
||||
@RequestMapping("reports/available")
|
||||
public void availableReports(@PathVariable String entity, HttpServletResponse response) {
|
||||
Entity entityMeta = MetadataHelper.getEntity(entity);
|
||||
|
||||
JSONArray reports = DataReportManager.instance.getReports(entityMeta);
|
||||
writeSuccess(response, reports);
|
||||
}
|
||||
|
||||
@RequestMapping({ "report-generate", "report-export" })
|
||||
public void reportGenerate(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
@RequestMapping({ "reports/generate", "reports/export" })
|
||||
public void reportGenerate(@PathVariable String entity,
|
||||
HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
ID reportId = getIdParameterNotNull(request, "report");
|
||||
ID recordId = getIdParameterNotNull(request, "record");
|
||||
|
||||
|
|
|
@ -104,7 +104,9 @@ public class LanguagesControll extends BaseControll {
|
|||
|
||||
String storeLocale = Application.getSessionStore().getLocale();
|
||||
if (!locale.equalsIgnoreCase(storeLocale)) {
|
||||
if (AppUtils.devMode()) Languages.instance.reset();
|
||||
if (AppUtils.devMode()) {
|
||||
Languages.instance.reset();
|
||||
}
|
||||
ServletUtils.setSessionAttribute(request, SK_LOCALE, locale);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ import javax.servlet.http.HttpServletResponse;
|
|||
* @since 09/20/2018
|
||||
*/
|
||||
@Controller
|
||||
public class MiscPagesForward extends BasePageControll {
|
||||
public class PagesForward extends BasePageControll {
|
||||
|
||||
@RequestMapping(value={ "/p/**/*", "/admin/p/**/*" }, method = RequestMethod.GET)
|
||||
public ModelAndView page(HttpServletRequest request) {
|
|
@ -96,7 +96,7 @@ public class ChartDesignControll extends BaseEntityControll {
|
|||
throw new IllegalParameterException("无效图表参数");
|
||||
}
|
||||
|
||||
if (!Application.getSecurityManager().allowedR(getRequestUser(request), entityMeta.getEntityCode())) {
|
||||
if (!Application.getSecurityManager().allowRead(getRequestUser(request), entityMeta.getEntityCode())) {
|
||||
response.sendError(403, "你没有读取 [" + EasyMeta.getLabel(entityMeta) + "] 的权限,因此无法设计此图表");
|
||||
return null;
|
||||
}
|
||||
|
@ -107,7 +107,8 @@ public class ChartDesignControll extends BaseEntityControll {
|
|||
for (Field field : MetadataSorter.sortFields(entityMeta)) {
|
||||
EasyMeta easy = EasyMeta.valueOf(field);
|
||||
DisplayType dt = easy.getDisplayType();
|
||||
if (dt == DisplayType.IMAGE || dt == DisplayType.FILE || dt == DisplayType.ANYREFERENCE) {
|
||||
if (dt == DisplayType.IMAGE || dt == DisplayType.FILE || dt == DisplayType.ANYREFERENCE
|
||||
|| dt == DisplayType.AVATAR || dt == DisplayType.LOCATION || dt == DisplayType.MULTISELECT) {
|
||||
continue;
|
||||
}
|
||||
String type = "text";
|
||||
|
|
|
@ -130,7 +130,7 @@ public class FileManagerControll extends BaseControll {
|
|||
if (record.getEntityCode() == EntityHelper.Feeds || record.getEntityCode() == EntityHelper.FeedsComment) {
|
||||
OK = FeedsHelper.checkReadable(record, user);
|
||||
} else {
|
||||
OK = Application.getSecurityManager().allowedR(user, record);
|
||||
OK = Application.getSecurityManager().allowRead(user, record);
|
||||
}
|
||||
writeSuccess(response, OK);
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ final JSONArray navArray = NavManager.instance.getNavForPortal(request);
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% if (AppUtils.allowed(request, ZeroEntry.AllowCustomNav)) { %>
|
||||
<% if (AppUtils.allow(request, ZeroEntry.AllowCustomNav)) { %>
|
||||
<div class="bottom-widget">
|
||||
<a class="nav-settings" href="javascript:;" title="设置导航菜单"><i class="icon zmdi zmdi-apps"></i></a>
|
||||
</div>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<li class="divider">系统</li>
|
||||
<li class="${param['activeNav'] == 'systems' ? 'active' : ''}"><a href="${baseUrl}/admin/systems"><i class="icon zmdi zmdi-settings"></i><span>通用配置</span></a></li>
|
||||
<li class="parent">
|
||||
<a><i class="icon zmdi zmdi-cloud-outline-alt"></i><span>三方服务集成</span></a>
|
||||
<a><i class="icon zmdi zmdi-puzzle-piece"></i><span>三方服务集成</span></a>
|
||||
<ul class="sub-menu">
|
||||
<li class="title">三方服务集成</li>
|
||||
<li class="nav-items">
|
||||
|
@ -28,7 +28,7 @@
|
|||
<li class="${param['activeNav'] == 'apis-manager' ? 'active' : ''} bosskey-show"><a href="${baseUrl}/admin/apis-manager"><i class="icon zmdi zmdi-key"></i><span>API 秘钥管理</span></a></li>
|
||||
<li class="divider">业务/实体</li>
|
||||
<li class="${param['activeNav'] == 'entities' ? 'active' : ''}"><a href="${baseUrl}/admin/entities"><i class="icon zmdi zmdi-widgets"></i><span>实体管理</span></a></li>
|
||||
<li class="${param['activeNav'] == 'classifications' ? 'active' : ''}"><a href="${baseUrl}/admin/entityhub/classifications"><i class="icon x21 zmdi zmdi-layers"></i><span>分类数据</span></a></li>
|
||||
<li class="${param['activeNav'] == 'classifications' ? 'active' : ''}"><a href="${baseUrl}/admin/entityhub/classifications"><i class="icon zmdi zmdi-layers"></i><span>分类数据</span></a></li>
|
||||
<li class="${param['activeNav'] == 'robot-approval' ? 'active' : ''}"><a href="${baseUrl}/admin/robot/approvals"><i class="icon zmdi zmdi-assignment-check"></i><span>审批流程</span></a></li>
|
||||
<li class="${param['activeNav'] == 'robot-trigger' ? 'active' : ''}"><a href="${baseUrl}/admin/robot/triggers"><i class="icon zmdi zmdi-rotate-cw"></i><span>触发器</span></a></li>
|
||||
<li class="${param['activeNav'] == 'data-reports' ? 'active' : ''}"><a href="${baseUrl}/admin/datas/data-reports"><i class="icon zmdi zmdi-map"></i><span>报表模板</span></a></li>
|
||||
|
|
|
@ -13,7 +13,7 @@ final User currentUser = Application.getUserStore().getUser(AppUtils.getRequestU
|
|||
<a class="rb-toggle-left-sidebar" title="展开/收缩导航菜单"><span class="icon zmdi zmdi-menu"></span></a>
|
||||
</div>
|
||||
<div class="search-container">
|
||||
<input class="form-control form-control-sm search-input-gs" type="text" name="search" maxlength="100" placeholder="搜索" />
|
||||
<input class="form-control form-control-sm search-input-gs" type="text" name="search" maxlength="100" placeholder="搜索" autocomplete="off" />
|
||||
<div class="search-models animated fadeIn faster"></div>
|
||||
</div>
|
||||
<div class="rb-right-navbar">
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
</div>
|
||||
</aside>
|
||||
<div class="main-content container-fluid">
|
||||
<div class="alert alert-warning alert-icon alert-dismissible alert-sm hide J_tips">
|
||||
<div class="alert alert-warning alert-icon alert-icon-border alert-dismissible alert-sm hide J_tips">
|
||||
<div class="icon"><span class="zmdi zmdi-info-outline"></span></div>
|
||||
<div class="message"><a class="close" data-dismiss="alert"><span class="zmdi zmdi-close"></span></a><p>1</p></div>
|
||||
</div>
|
||||
|
@ -128,16 +128,16 @@
|
|||
<td class="text-center"><i data-action="Z" class="priv R4"></i></td>
|
||||
<td colspan="5" class="text-muted">需具备相应实体的读取权限</td>
|
||||
</tr>
|
||||
<!--
|
||||
<tr>
|
||||
<td class="name"><a data-name="AllowBatchUpdate">允许批量修改</a></td>
|
||||
<td class="text-center"><i data-action="Z" class="priv R0"></i></td>
|
||||
<td colspan="5">需具备相应实体的修改权限</td>
|
||||
<td colspan="5" class="text-muted">需具备相应实体的修改权限</td>
|
||||
</tr>
|
||||
<!--
|
||||
<tr>
|
||||
<td class="name"><a data-name="AllowDataImport">允许导入数据</a></td>
|
||||
<td class="text-center"><i data-action="Z" class="priv R0"></i></td>
|
||||
<td colspan="5">需具备相应实体的创建权限</td>
|
||||
<td colspan="5" class="text-muted">需具备相应实体的创建权限</td>
|
||||
</tr>
|
||||
-->
|
||||
<tr>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
</span>
|
||||
</div>
|
||||
<div class="main-content container-fluid">
|
||||
<div class="alert alert-warning alert-icon alert-dismissible alert-sm hide J_tips">
|
||||
<div class="alert alert-warning alert-icon alert-icon-border alert-dismissible alert-sm hide J_tips">
|
||||
<div class="icon"><span class="zmdi zmdi-info-outline"></span></div>
|
||||
<div class="message"><a class="close" data-dismiss="alert"><span class="zmdi zmdi-close"></span></a><p></p></div>
|
||||
</div>
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<div class="page-head-title">分类数据<span class="sub-title">${name}</span></div>
|
||||
</div>
|
||||
<div class="float-right pt-1">
|
||||
<button class="btn btn-secondary J_imports" type="button"><i class="zmdi zmdi-cloud-download"></i> 导入公共数据</button>
|
||||
<button class="btn btn-secondary J_imports" type="button"><i class="icon zmdi zmdi-cloud-outline-alt"></i> 从 RB 仓库导入</button>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
|
|
@ -5,13 +5,43 @@
|
|||
<%@ include file="/_include/Head.jsp"%>
|
||||
<title>数据导入</title>
|
||||
<style type="text/css">
|
||||
.fuelux .wizard .step-content{padding:30px}
|
||||
.fuelux .wizard>.steps-container>.steps li.complete:hover{cursor:default;}
|
||||
#fieldsMapping th, #fieldsMapping td{padding:6px 0;vertical-align:middle;border-bottom:1px dotted #dee2e6;border-top:0 none;}
|
||||
#fieldsMapping thead th{border-bottom:1px solid #dee2e6;padding-top:9px;}
|
||||
#fieldsMapping td>em{font-style:normal;background-color:#eee;display:inline-block;min-width:30px;font-size:12px;text-align:center;margin-right:4px;padding-top:1px;color:#777}
|
||||
#fieldsMapping td>i.zmdi{float:right;color:#aaa;font-size:1.4rem;margin-right:10px}
|
||||
#ouser-warn .alert{margin-top:10px;margin-bottom:0}
|
||||
.fuelux .wizard .step-content {
|
||||
padding: 30px
|
||||
}
|
||||
.fuelux .wizard > .steps-container > .steps li.complete:hover {
|
||||
cursor: default;
|
||||
}
|
||||
#fieldsMapping th, #fieldsMapping td {
|
||||
padding: 6px 0;
|
||||
vertical-align: middle;
|
||||
border-bottom: 1px dotted #dee2e6;
|
||||
border-top: 0 none;
|
||||
}
|
||||
#fieldsMapping thead th {
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
padding-top: 9px;
|
||||
}
|
||||
#fieldsMapping td > em {
|
||||
font-style: normal;
|
||||
background-color: #eee;
|
||||
display: inline-block;
|
||||
min-width: 30px;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
margin-right: 4px;
|
||||
padding-top: 1px;
|
||||
color: #777
|
||||
}
|
||||
#fieldsMapping td > i.zmdi {
|
||||
float: right;
|
||||
color: #aaa;
|
||||
font-size: 1.4rem;
|
||||
margin-right: 10px
|
||||
}
|
||||
#user-warn .alert {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 0
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
@ -55,16 +85,15 @@
|
|||
<label for="upload-input" class="btn-secondary"><i class="zmdi zmdi-upload"></i><span>选择文件</span></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="float-left ml-2" style="padding-top:7px">
|
||||
<div class="float-left ml-2 pt-1">
|
||||
<div class="text-bold text-italic J_upload-input"></div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
<div class="form-text mb-0">
|
||||
<ul class="mb-0 pl-4">
|
||||
<li>支持上传 Excel/CSV 文件,文件大小不超过 20M</li>
|
||||
<li>有合并单元格的数据请处理过后再上传,否则可能出现表头识别有误</li>
|
||||
<li>系统默认仅识别第一个 SHEET,且会将首行识别为表头</li>
|
||||
<li>更多导入帮助请 <a href="https://getrebuild.com/docs/admin/data-import" target="_blank">参考文档</a></li>
|
||||
<li>更多导入帮助请 <a href="https://getrebuild.com/docs/admin/data-import" target="_blank" class="link">参考文档</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -72,7 +101,7 @@
|
|||
<div class="form-group row">
|
||||
<label class="col-md-12 col-xl-3 col-lg-4 col-form-label text-lg-right">遇到重复记录时</label>
|
||||
<div class="col-md-12 col-xl-6 col-lg-8">
|
||||
<div style="margin-top:6px;">
|
||||
<div class="mt-1">
|
||||
<label class="custom-control custom-control-sm custom-radio custom-control-inline">
|
||||
<input class="custom-control-input" type="radio" name="repeatOpt" value="1" checked="checked"><span class="custom-control-label">覆盖 (更新)</span>
|
||||
</label>
|
||||
|
@ -95,15 +124,13 @@
|
|||
<div class="col-md-12 col-xl-6 col-lg-8">
|
||||
<select class="form-control form-control-sm" id="toUser">
|
||||
</select>
|
||||
<div class="form-text mb-0">
|
||||
不选择则默认为当前用户,如字段映射中指定了用户则以映射为准
|
||||
</div>
|
||||
<div id="ouser-warn"></div>
|
||||
<div class="form-text mb-0">不选择则默认为当前用户,如字段映射中指定了用户则以映射为准</div>
|
||||
<div id="user-warn"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row footer">
|
||||
<div class="col-md-12 col-xl-6 col-lg-8 offset-xl-3 offset-lg-4">
|
||||
<button class="btn btn-primary J_step1-btn" type="button" data-loading-text="正在预处理">下一步</button>
|
||||
<button class="btn btn-primary btn-space J_step1-btn" type="button" data-loading-text="正在预处理">下一步</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -128,9 +155,8 @@
|
|||
</div>
|
||||
<div class="form-group row footer">
|
||||
<div class="col-md-12 col-xl-6 col-lg-8 offset-xl-3 offset-lg-4">
|
||||
<button class="btn btn-primary J_step2-btn" type="button">开始导入</button>
|
||||
|
||||
<button class="btn btn-link J_step2-return" type="button">返回上一步</button>
|
||||
<button class="btn btn-primary btn-space J_step2-btn" type="button">开始导入</button>
|
||||
<button class="btn btn-link btn-space J_step2-return" type="button">返回上一步</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -142,11 +168,9 @@
|
|||
<div class="col-6 text-right text-muted">耗时 <span class="J_import_time">00:00:00</span></div>
|
||||
</div>
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-bar-striped progress-bar-animated J_import-bar" style="width:0"></div>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<button class="btn btn-danger J_step3-cancel" type="button">终止导入</button>
|
||||
<a class="btn btn-link J_step3-logs hide" href="data-importer">继续导入</a>
|
||||
<button class="btn btn-danger btn-space J_step3-cancel" type="button">终止导入</button>
|
||||
<a class="btn btn-link btn-space J_step3-logs hide" href="data-importer">继续导入</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<div class="tab-container">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="nav-item"><a class="nav-link active" href="#MANUAL" data-toggle="tab">手动</a></li>
|
||||
<li class="nav-item"><a class="nav-link J_imports" href="#IMPORTS" data-toggle="tab">导入公共数据</a></li>
|
||||
<li class="nav-item"><a class="nav-link J_imports" href="#IMPORTS" data-toggle="tab"><i class="icon zmdi zmdi-cloud-outline-alt"></i> 从 RB 仓库导入</a></li>
|
||||
</ul>
|
||||
<div class="tab-content m-0 pb-0">
|
||||
<div class="tab-pane active" id="MANUAL">
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
<div class="rb-content">
|
||||
<div class="field-head">
|
||||
<h4 class="float-left">字段列表</h4>
|
||||
<div class="float-right"><span class="not-nullable">必填字段</span><span class="readonly">只读字段</span></div>
|
||||
<div class="float-right"><span class="not-nullable">必填</span><span class="readonly">只读</span></div>
|
||||
</div>
|
||||
<div class="rb-scroller">
|
||||
<div class="field-list dd-list">
|
||||
|
|
|
@ -4,11 +4,6 @@
|
|||
<head>
|
||||
<%@ include file="/_include/Head.jsp"%>
|
||||
<title>API 秘钥管理</title>
|
||||
<style type="text/css">
|
||||
.syscfg h5{background-color:#eee;margin:0;padding:10px;}
|
||||
.syscfg .table td{padding:10px;}
|
||||
.syscfg .table td p{margin:0;color:#999;font-weight:normal;font-size:12px;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="rb-wrapper rb-fixed-sidebar rb-collapsible-sidebar rb-collapsible-sidebar-hide-logo rb-color-header">
|
||||
|
|
|
@ -5,11 +5,6 @@
|
|||
<head>
|
||||
<%@ include file="/_include/Head.jsp"%>
|
||||
<title>缓存系统配置</title>
|
||||
<style type="text/css">
|
||||
.syscfg h5{background-color:#eee;margin:0;padding:10px;}
|
||||
.syscfg .table td{padding:10px;}
|
||||
.syscfg .table td p{margin:0;color:#999;font-weight:normal;font-size:12px;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="rb-wrapper rb-fixed-sidebar rb-collapsible-sidebar rb-collapsible-sidebar-hide-logo rb-color-header">
|
||||
|
@ -43,10 +38,10 @@
|
|||
</tbody>
|
||||
</table>
|
||||
<c:if test="${!cacheStatus}">
|
||||
<div class="alert alert-warning alert-icon mt-6">
|
||||
<div class="icon"><span class="zmdi zmdi-alert-triangle"></span></div>
|
||||
<div class="message">REDIS 缓存服务未配置或配置有误,当前已启用 EHCACHE 内建缓存</div>
|
||||
</div>
|
||||
<div class="alert alert-warning alert-icon mt-6">
|
||||
<div class="icon"><span class="zmdi zmdi-alert-triangle"></span></div>
|
||||
<div class="message">REDIS 未配置或配置有误,当前已启用 EHCACHE 内建缓存</div>
|
||||
</div>
|
||||
</c:if>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -5,11 +5,6 @@
|
|||
<head>
|
||||
<%@ include file="/_include/Head.jsp"%>
|
||||
<title>云存储配置</title>
|
||||
<style type="text/css">
|
||||
.syscfg h5{background-color:#eee;margin:0;padding:10px;}
|
||||
.syscfg .table td{padding:10px;}
|
||||
.syscfg .table td p{margin:0;color:#999;font-weight:normal;font-size:12px;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="rb-wrapper rb-fixed-sidebar rb-collapsible-sidebar rb-collapsible-sidebar-hide-logo rb-color-header">
|
||||
|
@ -48,10 +43,10 @@
|
|||
</tbody>
|
||||
</table>
|
||||
<c:if test="${!storageStatus}">
|
||||
<div class="alert alert-warning alert-icon mt-6">
|
||||
<div class="icon"><span class="zmdi zmdi-alert-triangle"></span></div>
|
||||
<div class="message">七牛云存储账户配置有误,当前已启用本地文件存储</div>
|
||||
</div>
|
||||
<div class="alert alert-warning alert-icon mt-6">
|
||||
<div class="icon"><span class="zmdi zmdi-alert-triangle"></span></div>
|
||||
<div class="message">七牛云存储账户未配置或配置有误,当前已启用本地文件存储</div>
|
||||
</div>
|
||||
</c:if>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -5,11 +5,6 @@
|
|||
<head>
|
||||
<%@ include file="/_include/Head.jsp"%>
|
||||
<title>短信/邮件配置</title>
|
||||
<style type="text/css">
|
||||
.syscfg h5{background-color:#eee;margin:0;padding:10px;}
|
||||
.syscfg .table td{padding:10px;}
|
||||
.syscfg .table td p{margin:0;color:#999;font-weight:normal;font-size:12px;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="rb-wrapper rb-fixed-sidebar rb-collapsible-sidebar rb-collapsible-sidebar-hide-logo rb-color-header">
|
||||
|
|
|
@ -360,12 +360,4 @@ a.ui-draggable.ui-draggable-dragging {
|
|||
|
||||
.chart-type>a.active>i.C370 {
|
||||
background-position: -864px -1520px;
|
||||
}
|
||||
|
||||
.chart-option>div {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.chart-option>div.active {
|
||||
display: block;
|
||||
}
|
|
@ -47,8 +47,7 @@
|
|||
|
||||
.tools-bar {
|
||||
height: 44px;
|
||||
padding: 0 25px;
|
||||
padding-top: 7px
|
||||
padding: 7px 25px 0;
|
||||
}
|
||||
|
||||
.tools-bar h4 {
|
||||
|
@ -61,18 +60,37 @@
|
|||
}
|
||||
|
||||
.chart-grid {
|
||||
overflow: scroll;
|
||||
overflow-x: hidden;
|
||||
padding: 2px 5px 25px 15px;
|
||||
padding: 2px 15px 25px 15px;
|
||||
margin-top: 8px;
|
||||
min-height: 300px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
@media (max-width:768px) {
|
||||
.chart-grid {
|
||||
overflow: hidden;
|
||||
padding-bottom: 5px;
|
||||
padding-right: 15px;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.grid-stack>.grid-stack-item>.grid-stack-item-content {
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.tools-bar .col-sm-6 {
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.grid-stack.grid-stack-one-column-mode>.grid-stack-item {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width:576px) {
|
||||
.tools-bar .col-sm-6 {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,4 +143,8 @@
|
|||
|
||||
.J_dash-select:hover {
|
||||
color: #4285f4;
|
||||
}
|
||||
|
||||
.shareTo--wrap .custom-control {
|
||||
margin-top: 0;
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
.rb-content .main-content {
|
||||
margin-right: 300px;
|
||||
margin-right: 280px;
|
||||
}
|
||||
|
||||
.rb-right-sidebar.field-aside {
|
||||
width: 300px;
|
||||
width: 280px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.readonly {
|
||||
|
|
|
@ -5795,8 +5795,8 @@ input[type=submit].btn-block {
|
|||
font-weight: 700
|
||||
}
|
||||
|
||||
.alert-dismissible {
|
||||
padding-right: 4.27rem
|
||||
.alert-dismissible .message>p {
|
||||
padding-right: 2.4rem
|
||||
}
|
||||
|
||||
.alert-dismissible .close {
|
||||
|
@ -20258,6 +20258,7 @@ fieldset[disabled] .btn-color.btn-evernote:hover {
|
|||
margin-right: 8px;
|
||||
color: #696969;
|
||||
width: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dropdown-menu>.dropdown-item:active,
|
||||
|
|
|
@ -141,11 +141,16 @@ body {
|
|||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.text-wrap {
|
||||
.text-wrap,
|
||||
.text-break {
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.text-overflow {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.badge.text-id {
|
||||
font-weight: normal;
|
||||
color: #777;
|
||||
|
@ -161,6 +166,10 @@ a.badge.text-id:hover {
|
|||
border-color: #4285f4;
|
||||
}
|
||||
|
||||
.bg-eee {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.up-1 {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
@ -245,6 +254,16 @@ a.btn {
|
|||
color: #0d5bdd !important;
|
||||
}
|
||||
|
||||
.btn-link.disabled,
|
||||
.btn-link.disabled:hover,
|
||||
.btn-link.disabled:active,
|
||||
.btn-link[disabled],
|
||||
.btn-link[disabled]:hover,
|
||||
.btn-link[disabled]:active {
|
||||
color: #aaa !important;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.btn .icon.x14 {
|
||||
font-size: 1.431rem
|
||||
}
|
||||
|
@ -258,6 +277,15 @@ a.btn {
|
|||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.rb-left-sidebar .sidebar-elements a>.icon.zmdi-layers {
|
||||
font-size: 22px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.rb-left-sidebar .sidebar-elements a>.icon.zmdi-puzzle-piece {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.rb-color-header .rb-top-header .rb-navbar-header .rb-toggle-left-sidebar .icon {
|
||||
color: rgba(255, 255, 255, .6)
|
||||
}
|
||||
|
@ -905,11 +933,12 @@ a {
|
|||
|
||||
.rbhighbar {
|
||||
position: fixed;
|
||||
min-width: 421px;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
margin-left: -210px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
z-index: 1099;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.rbhighbar.faster {
|
||||
|
@ -917,16 +946,26 @@ a {
|
|||
animation-duration: 200ms
|
||||
}
|
||||
|
||||
.rbhighbar>.alert {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
max-width: 80%;
|
||||
min-width: 421px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@media (max-width:440px) {
|
||||
.rbhighbar {
|
||||
min-width: auto;
|
||||
margin: 0;
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.rbhighbar>.alert {
|
||||
max-width: 100%;
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.rbhighbar a {
|
||||
.rbhighbar>.alert>.message a {
|
||||
color: #fff
|
||||
}
|
||||
|
||||
|
@ -1211,6 +1250,46 @@ i.split.ui-draggable-dragging {
|
|||
margin-left: 1px;
|
||||
}
|
||||
|
||||
.form.batch-form {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.form.batch-form .form-group:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.batch-contents>div {
|
||||
position: relative;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.batch-contents>div .badge {
|
||||
font-size: 1rem;
|
||||
font-weight: normal;
|
||||
padding: 1px 10px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.batch-contents>div .badge.badge-warning {
|
||||
border: 1px solid #fbbc05
|
||||
}
|
||||
|
||||
.batch-contents>div a.del {
|
||||
float: right;
|
||||
font-size: 1.2rem;
|
||||
padding: 1px 5px;
|
||||
margin-right: -5px;
|
||||
color: #999;
|
||||
font-weight: bold;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.batch-contents>div:hover a.del {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* DataList ends */
|
||||
|
||||
.unselect {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
|
@ -1779,12 +1858,7 @@ form.dropzone .note {
|
|||
}
|
||||
|
||||
form.simple {
|
||||
max-width: 888px;
|
||||
}
|
||||
|
||||
.rb-left-sidebar .sidebar-elements>li>a .icon.x21,
|
||||
.rb-left-sidebar .sidebar-elements .sub-menu li>a .icon.x21 {
|
||||
font-size: 21px;
|
||||
max-width: 900px;
|
||||
}
|
||||
|
||||
.fs-0 {
|
||||
|
@ -2977,4 +3051,21 @@ a.icon-link>.zmdi {
|
|||
font-size: 1.231rem;
|
||||
margin-top: 2px;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.syscfg h5 {
|
||||
background-color: #eee;
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.syscfg .table td {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.syscfg .table td p {
|
||||
margin: 0;
|
||||
color: #999;
|
||||
font-weight: normal;
|
||||
font-size: 12px;
|
||||
}
|
|
@ -220,10 +220,10 @@ let render_option = (() => {
|
|||
|
||||
const ct = select.data('type')
|
||||
// Option
|
||||
$('.chart-option>div').removeClass('active')
|
||||
$('.chart-option>div').addClass('hide')
|
||||
let ctOpt = $('.J_opt-' + ct)
|
||||
if (ctOpt.length === 0) $('.chart-option>.J_opt-UNDEF').addClass('active')
|
||||
else ctOpt.addClass('active')
|
||||
if (ctOpt.length === 0) $('.J_opt-UNDEF').removeClass('hide')
|
||||
else ctOpt.removeClass('hide')
|
||||
|
||||
// Sort
|
||||
let sorts = $('.axis-editor .J_sort').removeClass('disabled')
|
||||
|
|
|
@ -82,9 +82,9 @@ let rendered_charts = []
|
|||
let win_resize = function (t) {
|
||||
if (on_resizestart === true) return
|
||||
$setTimeout(() => {
|
||||
let cg = $('.chart-grid')
|
||||
if ($(window).width() >= 768) cg.height($(window).height() - 142)
|
||||
else cg.height('auto')
|
||||
// let cg = $('.chart-grid')
|
||||
// if ($(window).width() >= 768) cg.height($(window).height() - 142)
|
||||
// else cg.height('auto')
|
||||
$(rendered_charts).each((idx, item) => { item.resize() })
|
||||
}, t || 400, 'resize-charts')
|
||||
}
|
||||
|
|
|
@ -322,7 +322,7 @@ class DlgImports extends RbModalHandler {
|
|||
return
|
||||
}
|
||||
|
||||
let cp = res.data.completed
|
||||
let cp = res.data.progress
|
||||
if (cp >= 1) {
|
||||
RbHighbar.success('导入完成')
|
||||
this.__mpro.end()
|
||||
|
|
|
@ -32,16 +32,19 @@ $(document).ready(() => {
|
|||
ientry.entity = entity
|
||||
})
|
||||
}
|
||||
$.get(`${rb.baseUrl}/commons/metadata/entities`, (res) => {
|
||||
$.get(`${rb.baseUrl}/commons/metadata/entities?slave=true`, (res) => {
|
||||
$(res.data).each(function () {
|
||||
$('<option value="' + this.name + '">' + this.label + '</option>').appendTo('#toEntity')
|
||||
})
|
||||
$('#toEntity').select2({
|
||||
|
||||
let entityS2 = $('#toEntity').select2({
|
||||
allowClear: false
|
||||
}).on('change', function () {
|
||||
fileds_render($(this).val())
|
||||
check_ouser()
|
||||
}).trigger('change')
|
||||
check_user()
|
||||
})
|
||||
if ($urlp('entity')) entityS2.val($urlp('entity'))
|
||||
entityS2.trigger('change')
|
||||
})
|
||||
|
||||
$('input[name=repeatOpt]').click(function () {
|
||||
|
@ -71,7 +74,7 @@ $(document).ready(() => {
|
|||
}
|
||||
}).on('change', function () {
|
||||
ientry.owning_user = $(this).val() || null
|
||||
check_ouser()
|
||||
check_user()
|
||||
})
|
||||
|
||||
$('.J_step1-btn').click(step_mapping)
|
||||
|
@ -107,25 +110,18 @@ const init_upload = () => {
|
|||
})
|
||||
}
|
||||
|
||||
const check_ouser = () => {
|
||||
$setTimeout(check_ouser0, 200, 'check_ouser')
|
||||
}
|
||||
const check_ouser0 = () => {
|
||||
if (!(ientry.entity && ientry.owning_user)) return
|
||||
$.get(`${rb.baseUrl}/admin/datas/data-importer/check-user-privileges?ouser=${ientry.owning_user}&entity=${ientry.entity}`, (res) => {
|
||||
// 检查所属用户权限
|
||||
const check_user = () => $setTimeout(check_user0, 200, 'check_user')
|
||||
const check_user0 = () => {
|
||||
if (!ientry.entity || !ientry.owning_user) return
|
||||
$.get(`${rb.baseUrl}/admin/datas/data-importer/check-user?user=${ientry.owning_user}&entity=${ientry.entity}`, (res) => {
|
||||
let hasError = []
|
||||
if (res.data.canCreate !== true) hasError.push('新建')
|
||||
if (res.data.canUpdate !== true) hasError.push('更新')
|
||||
if (hasError.length >= 2) {
|
||||
$('.J_step1-btn').attr('disabled', true)
|
||||
renderRbcomp(<RbAlertBox type="danger" message={'选择的用户无' + hasError.join('及') + '权限,请选择其他用户'} />, 'ouser-warn')
|
||||
if (hasError.length > 0) {
|
||||
renderRbcomp(<RbAlertBox message={`选择的用户无 ${hasError.join('/')} 权限。但作为管理员,你可以强制导入`} />, 'user-warn')
|
||||
} else {
|
||||
$('.J_step1-btn').attr('disabled', false)
|
||||
if (hasError.length > 0) {
|
||||
renderRbcomp(<RbAlertBox message={'选择的用户无' + hasError.join('/') + '权限,可能导致部分数据导入失败'} />, 'ouser-warn')
|
||||
} else {
|
||||
$('#ouser-warn').empty()
|
||||
}
|
||||
$('#user-warn').empty()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -161,7 +157,7 @@ const step_import = () => {
|
|||
if (field) fm[field] = col
|
||||
})
|
||||
$(fields_cached).each((idx, item) => {
|
||||
if (item.isNullable === true || !!item.defaultValue) {
|
||||
if (item.nullable === true || !!item.defaultValue) {
|
||||
// Not be must
|
||||
} else if (fm[item.name] === undefined) {
|
||||
RbHighbar.create(item.label + ' 为必填字段,请选择')
|
||||
|
@ -172,14 +168,20 @@ const step_import = () => {
|
|||
if (!fm) return
|
||||
ientry.fields_mapping = fm
|
||||
|
||||
step_import_show()
|
||||
$.post(`${rb.baseUrl}/admin/datas/data-importer/import-submit`, JSON.stringify(ientry), function (res) {
|
||||
if (res.error_code === 0) {
|
||||
import_inprogress = true
|
||||
import_taskid = res.data.taskid
|
||||
location.hash = '#task=' + import_taskid
|
||||
import_state(import_taskid)
|
||||
} else RbHighbar.error(res.error_msg)
|
||||
RbAlert.create('请再次确认导入选项和字段映射。开始导入吗?', {
|
||||
confirm: function () {
|
||||
this.disabled(true)
|
||||
$.post(`${rb.baseUrl}/admin/datas/data-importer/import-submit`, JSON.stringify(ientry), (res) => {
|
||||
if (res.error_code === 0) {
|
||||
this.hide()
|
||||
step_import_show()
|
||||
import_inprogress = true
|
||||
import_taskid = res.data.taskid
|
||||
location.hash = '#task=' + import_taskid
|
||||
import_state(import_taskid)
|
||||
} else RbHighbar.error(res.error_msg)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
const step_import_show = () => {
|
||||
|
@ -188,7 +190,7 @@ const step_import_show = () => {
|
|||
$('.steps li[data-step=3], .step-content .step-pane[data-step=3]').addClass('active')
|
||||
}
|
||||
const import_state = (taskid, inLoad) => {
|
||||
$.get(`${rb.baseUrl}/admin/datas/data-importer/import-state?taskid=${taskid}`, (res) => {
|
||||
$.get(`${rb.baseUrl}/commons/task/state?taskid=${taskid}`, (res) => {
|
||||
if (res.error_code !== 0) {
|
||||
if (inLoad === true) step_upload()
|
||||
else RbHighbar.error(res.error_msg)
|
||||
|
@ -196,7 +198,7 @@ const import_state = (taskid, inLoad) => {
|
|||
return
|
||||
}
|
||||
if (!res.data) {
|
||||
setTimeout(() => { import_state(taskid) }, 1000)
|
||||
setTimeout(() => import_state(taskid), 1000)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -205,9 +207,9 @@ const import_state = (taskid, inLoad) => {
|
|||
|
||||
if (_data.isCompleted === true) {
|
||||
$('.J_import-bar').css('width', '100%')
|
||||
$('.J_import_state').text('导入完成。共成功导入 ' + _data.success + ' 条数据')
|
||||
$('.J_import_state').text('导入完成。共成功导入 ' + _data.succeeded + ' 条数据')
|
||||
} else if (_data.isInterrupted === true) {
|
||||
$('.J_import_state').text('导入被终止。已成功导入 ' + _data.success + ' 条数据')
|
||||
$('.J_import_state').text('导入被终止。已成功导入 ' + _data.succeeded + ' 条数据')
|
||||
}
|
||||
if (_data.isCompleted === true || _data.isInterrupted === true) {
|
||||
$('.J_step3-cancel').attr('disabled', true).text('导入完成')
|
||||
|
@ -217,18 +219,18 @@ const import_state = (taskid, inLoad) => {
|
|||
}
|
||||
|
||||
if (_data.total > -1) {
|
||||
$('.J_import_state').text('正在导入 ... ' + _data.complete + ' / ' + _data.total)
|
||||
$('.J_import-bar').css('width', (_data.complete * 100 / _data.total) + '%')
|
||||
$('.J_import_state').text('正在导入 ... ' + _data.completed + ' / ' + _data.total)
|
||||
$('.J_import-bar').css('width', (_data.progress * 100) + '%')
|
||||
}
|
||||
setTimeout(() => { import_state(taskid) }, 500)
|
||||
})
|
||||
}
|
||||
const import_cancel = () => {
|
||||
RbAlert.create('确认要终止导入?请注意已导入数据无法自动删除', {
|
||||
RbAlert.create('确认终止导入?请注意已导入数据无法自动删除', {
|
||||
type: 'danger',
|
||||
confirmText: '确认终止',
|
||||
confirm: function () {
|
||||
$.post(`${rb.baseUrl}/admin/datas/data-importer/import-cancel?taskid=${import_taskid}`, (res) => {
|
||||
$.post(`${rb.baseUrl}/commons/task/cancel?taskid=${import_taskid}`, (res) => {
|
||||
if (res.error_code > 0) RbHighbar.error(res.error_msg)
|
||||
})
|
||||
this.hide()
|
||||
|
@ -241,7 +243,7 @@ const render_fieldsMapping = (columns, fields) => {
|
|||
let fields_map = {}
|
||||
let fields_select = $('<select><option value="">无</option></select>')
|
||||
$(fields).each((idx, item) => {
|
||||
let canNull = item.isNullable === false ? ' [必填]' : ''
|
||||
let canNull = item.nullable === false ? ' [必填]' : ''
|
||||
if (item.defaultValue) canNull = ''
|
||||
$('<option value="' + item.name + '">' + item.label + canNull + '</option>').appendTo(fields_select)
|
||||
fields_map[item.name] = item
|
||||
|
|
|
@ -11,7 +11,7 @@ class DlgAssign extends RbModalHandler {
|
|||
return (<RbModal title={this.types[1]} ref={(c) => this._dlg = c}>
|
||||
<div className="form">
|
||||
{this.onView === true ? null : (
|
||||
<div className="form-group row">
|
||||
<div className="form-group row pb-0">
|
||||
<label className="col-sm-3 col-form-label text-sm-right">{this.types[1]}哪些记录</label>
|
||||
<div className="col-sm-7">
|
||||
<div className="form-control-plaintext">{'选中的记录 (' + this.state.ids.length + '条)'}</div>
|
||||
|
@ -141,21 +141,18 @@ class DlgUnshare extends RbModalHandler {
|
|||
<label className="col-sm-3 col-form-label text-sm-right">取消哪些用户</label>
|
||||
<div className="col-sm-7">
|
||||
<div className="mt-1">
|
||||
<label className="custom-control custom-control-sm custom-radio custom-control-inline">
|
||||
<label className="custom-control custom-control-sm custom-radio custom-control-inline mb-2">
|
||||
<input className="custom-control-input" name="whichUsers" type="radio" checked={this.state.whichUsers === 'ALL'} onChange={() => this.whichMode(true)} />
|
||||
<span className="custom-control-label">全部用户</span>
|
||||
</label>
|
||||
<label className="custom-control custom-control-sm custom-radio custom-control-inline">
|
||||
<label className="custom-control custom-control-sm custom-radio custom-control-inline mb-2">
|
||||
<input className="custom-control-input" name="whichUsers" type="radio" checked={this.state.whichUsers === 'SPEC'} onChange={() => this.whichMode()} />
|
||||
<span className="custom-control-label">指定用户</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={'form-group row pt-0 ' + (this.state.whichUsers === 'ALL' ? 'hide' : '')}>
|
||||
<label className="col-sm-3 col-form-label text-sm-right"></label>
|
||||
<div className="col-sm-7">
|
||||
<select className="form-control form-control-sm" ref={(c) => this._toUser = c} />
|
||||
<div className={'mb-2 ' + (this.state.whichUsers === 'ALL' ? 'hide' : '')}>
|
||||
<select className="form-control form-control-sm" ref={(c) => this._toUser = c} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-group row footer">
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
'button': function (state) {
|
||||
return this.each(function () {
|
||||
var el = $(this)
|
||||
if (el.prop('nodeName') !== 'BUTTON') return
|
||||
if (!(el.prop('nodeName') === 'BUTTON' || el.prop('nodeName') === 'A')) return
|
||||
if (state === 'loading') {
|
||||
el.attr('disabled', true)
|
||||
var loadingText = el.data('loading-text')
|
||||
|
|
|
@ -112,7 +112,8 @@ class RbModalHandler extends React.Component {
|
|||
if (state && $.type(state) === 'object') this.setState(state, callback)
|
||||
else callback()
|
||||
}
|
||||
hide = () => {
|
||||
hide = (e) => {
|
||||
if (e && e.target && $(e.target).attr('disabled')) return
|
||||
// eslint-disable-next-line react/no-string-refs
|
||||
let dlg = this._dlg || this.refs['dlg']
|
||||
if (dlg) dlg.hide()
|
||||
|
@ -223,17 +224,16 @@ class RbAlert extends React.Component {
|
|||
// -- Usage
|
||||
/**
|
||||
* @param {*} message
|
||||
* @param {*} titleExt
|
||||
* @param {*} titleOrExt
|
||||
* @param {*} ext
|
||||
*/
|
||||
static create(message, titleExt, ext) {
|
||||
let title = titleExt
|
||||
if ($.type(titleExt) === 'object') {
|
||||
title = null
|
||||
ext = titleExt
|
||||
static create(message, titleOrExt, ext) {
|
||||
if (typeof titleOrExt === 'object') {
|
||||
ext = titleOrExt
|
||||
titleOrExt = null
|
||||
}
|
||||
ext = ext || {}
|
||||
let props = { ...ext, title: title }
|
||||
let props = { ...ext, title: titleOrExt }
|
||||
if (ext.html === true) props.htmlMessage = message
|
||||
else props.message = message
|
||||
renderRbcomp(<RbAlert {...props} />, null, ext.call)
|
||||
|
@ -250,25 +250,23 @@ class RbHighbar extends React.Component {
|
|||
render() {
|
||||
let icon = this.props.type === 'success' ? 'check' : 'info-outline'
|
||||
icon = this.props.type === 'danger' ? 'close-circle-o' : icon
|
||||
let content = this.props.htmlMessage ? <div className="message" dangerouslySetInnerHTML={{ __html: this.props.htmlMessage }} /> : <div className="message">{this.props.message}</div>
|
||||
return (<div ref={(c) => this._rbhighbar = c} className={'rbhighbar animated faster ' + this.state.animatedClass}>
|
||||
<div className={'alert alert-dismissible alert-' + (this.props.type || 'warning')}>
|
||||
<button className="close" type="button" onClick={() => this.close()}><span className="zmdi zmdi-close" /></button>
|
||||
<div className="icon"><span className={'zmdi zmdi-' + icon} /></div>
|
||||
let content = this.props.htmlMessage
|
||||
? <div className="message pl-0" dangerouslySetInnerHTML={{ __html: this.props.htmlMessage }} />
|
||||
: <div className="message pl-0">{this.props.message}</div>
|
||||
|
||||
return (<div ref={(c) => this._rbhighbar = c} className={`rbhighbar animated faster ${this.state.animatedClass}`}>
|
||||
<div className={`alert alert-dismissible alert-${(this.props.type || 'warning')}`}>
|
||||
<button className="close" type="button" onClick={this.close}><i className="zmdi zmdi-close" /></button>
|
||||
<div className="icon"><i className={`zmdi zmdi-${icon}`} /></div>
|
||||
{content}
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
setTimeout(() => { this.close() }, this.props.timeout || 3000)
|
||||
}
|
||||
|
||||
close() {
|
||||
this.setState({ animatedClass: 'fadeOut' }, () => {
|
||||
$unmount($(this._rbhighbar).parent())
|
||||
})
|
||||
setTimeout(() => this.close(), this.props.timeout || 3000)
|
||||
}
|
||||
close = () => this.setState({ animatedClass: 'fadeOut' }, () => $unmount($(this._rbhighbar).parent()))
|
||||
|
||||
// -- Usage
|
||||
/**
|
||||
|
@ -301,7 +299,20 @@ class RbHighbar extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
// ~~ 加载界面
|
||||
// ~~ 提示条
|
||||
function RbAlertBox(props) {
|
||||
let type = (props || {}).type || 'warning'
|
||||
let icon = type === 'success' ? 'check' : (type === 'danger' ? 'close-circle-o' : 'info-outline')
|
||||
return <div className={`alert alert-icon alert-icon-border alert-dismissible alert-sm alert-${type}`}>
|
||||
<div className="icon"><i className={`zmdi zmdi-${icon}`} /></div>
|
||||
<div className="message">
|
||||
<a className="close" data-dismiss="alert"><i className="zmdi zmdi-close" /></a>
|
||||
<p>{props.message || 'INMESSAGE'}</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
// ~~ 加载动画
|
||||
function RbSpinner(props) {
|
||||
let spinner = <div className="rb-spinner">
|
||||
<svg width="40px" height="40px" viewBox="0 0 66 66" xmlns="http://www.w3.org/2000/svg">
|
||||
|
@ -312,19 +323,6 @@ function RbSpinner(props) {
|
|||
return spinner
|
||||
}
|
||||
|
||||
// ~~ 提示条幅
|
||||
function RbAlertBox(props) {
|
||||
let icon = props.type === 'success' ? 'check' : 'info-outline'
|
||||
if (props.type === 'danger') icon = 'close-circle-o'
|
||||
return (<div className={'alert alert-icon alert-dismissible alert-sm alert-' + (props.type || 'warning')}>
|
||||
<div className="icon"><span className={'zmdi zmdi-' + icon} /></div>
|
||||
<div className="message">
|
||||
<a className="close" data-dismiss="alert"><span className="zmdi zmdi-close" /></a>
|
||||
<p>{props.message}</p>
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
|
||||
// ~~ 用户选择器
|
||||
class UserSelector extends React.Component {
|
||||
constructor(props) {
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
const wpc = window.__PageConfig || {}
|
||||
/* eslint-disable react/no-string-refs */
|
||||
/* eslint-disable react/prop-types */
|
||||
|
||||
// ~~ 数据列表
|
||||
const COLUMN_MIN_WIDTH = 30
|
||||
const COLUMN_MAX_WIDTH = 800
|
||||
const FIXED_FOOTER = true
|
||||
|
||||
class RbList extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
|
@ -145,9 +147,9 @@ class RbList extends React.Component {
|
|||
})
|
||||
|
||||
let oper = $('.dataTables_oper')
|
||||
oper.find('.J_delete, .J_view, .J_edit').attr('disabled', true)
|
||||
oper.find('.J_delete, .J_view, .J_edit, .J_assign, .J_share, .J_unshare').attr('disabled', true)
|
||||
let len = this.__selectedRows.length
|
||||
if (len > 0) oper.find('.J_delete').attr('disabled', false)
|
||||
if (len > 0) oper.find('.J_delete, .J_assign, .J_share, .J_unshare').attr('disabled', false)
|
||||
if (len === 1) oper.find('.J_view, .J_edit').attr('disabled', false)
|
||||
}
|
||||
|
||||
|
@ -276,6 +278,9 @@ class RbList extends React.Component {
|
|||
|
||||
// 外部接口
|
||||
|
||||
/**
|
||||
* 分页设置
|
||||
*/
|
||||
setPage(pageNo, pageSize) {
|
||||
this.pageNo = pageNo || this.pageNo
|
||||
if (pageSize) {
|
||||
|
@ -285,6 +290,9 @@ class RbList extends React.Component {
|
|||
this.fetchList()
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置高级过滤器ID
|
||||
*/
|
||||
setAdvFilter(id) {
|
||||
this.advFilter = id
|
||||
this.fetchList(this.__buildQuick())
|
||||
|
@ -293,23 +301,39 @@ class RbList extends React.Component {
|
|||
else $storage.remove(this.__defaultFilterKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取选中行
|
||||
*/
|
||||
getSelectedRows() {
|
||||
return this.__selectedRows
|
||||
return this.__selectedRows || []
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取选中 ID[]
|
||||
*/
|
||||
getSelectedIds() {
|
||||
if (!this.__selectedRows || this.__selectedRows.length < 1) { RbHighbar.create('未选中任何记录'); return [] }
|
||||
return this.__selectedRows.map((item) => { return item[0] })
|
||||
let ids = this.getSelectedRows().map((item) => { return item[0] })
|
||||
if (ids.length < 1) RbHighbar.create('未选中任何记录')
|
||||
return ids
|
||||
}
|
||||
|
||||
getQueryedTotal() {
|
||||
return this._pagination.state.rowsTotal
|
||||
/**
|
||||
* 获取最后查询记录熟虑
|
||||
*/
|
||||
getLastQueryTotal() {
|
||||
return this._pagination ? this._pagination.state.rowsTotal : 0
|
||||
}
|
||||
|
||||
getLastQueryEntry() {
|
||||
return this.__lastQueryEntry
|
||||
/**
|
||||
* 获取最后查询过滤数据
|
||||
*/
|
||||
getLastQueryData() {
|
||||
return JSON.parse(JSON.stringify(this.__lastQueryEntry))
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索
|
||||
*/
|
||||
search(filter, fromAdv) {
|
||||
let afHold = this.advFilter
|
||||
if (fromAdv === true) this.advFilter = null
|
||||
|
@ -321,9 +345,11 @@ class RbList extends React.Component {
|
|||
this.lastFilter = null
|
||||
}
|
||||
}
|
||||
|
||||
reload = () => this.fetchList()
|
||||
|
||||
// @el - search element
|
||||
searchQuick = (el) => this.search(this.__buildQuick(el))
|
||||
|
||||
__buildQuick(el) {
|
||||
el = $(el || '.input-search>input')
|
||||
let q = el.val()
|
||||
|
@ -331,10 +357,6 @@ class RbList extends React.Component {
|
|||
return { entity: this.props.config.entity, type: 'QUICK', values: { 1: q }, qfields: el.data('fields') }
|
||||
}
|
||||
|
||||
reload() {
|
||||
this.fetchList()
|
||||
}
|
||||
|
||||
// 渲染完成后回调
|
||||
static renderAfter() {
|
||||
}
|
||||
|
@ -343,18 +365,22 @@ class RbList extends React.Component {
|
|||
// 列表(单元格)渲染
|
||||
var CellRenders = {
|
||||
__renders: {},
|
||||
|
||||
addRender(type, func) {
|
||||
this.__renders[type] = func
|
||||
},
|
||||
|
||||
clickView(v) {
|
||||
RbViewModal.create({ id: v[0], entity: v[2][0] })
|
||||
return false
|
||||
},
|
||||
|
||||
render(value, type, width, key) {
|
||||
let style = { width: (width || COLUMN_MIN_WIDTH) + 'px' }
|
||||
if (!value) return this.renderSimple(value, style, key)
|
||||
else return (this.__renders[type] || this.renderSimple)(value, style, key)
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {*} v 值
|
||||
* @param {*} s 样式
|
||||
|
@ -419,7 +445,7 @@ CellRenders.addRender('MULTISELECT', function (v, s, k) {
|
|||
</div></td>
|
||||
})
|
||||
|
||||
// 分页组件
|
||||
// ~分页组件
|
||||
class RbListPagination extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
@ -486,57 +512,9 @@ class RbListPagination extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
// 导出列表数据
|
||||
class DataExport extends RbFormHandler {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state.dataRange = '2'
|
||||
}
|
||||
|
||||
render() {
|
||||
let _list = this.props.listRef
|
||||
return <RbModal ref={(c) => this._dlg = c} title="数据导出" disposeOnHide={true} width="480">
|
||||
<div className="pl-2 mb-4">
|
||||
<label className="custom-control custom-control-sm custom-radio">
|
||||
<input className="custom-control-input" name="dataRange" type="radio" checked={this.state.dataRange === '1'} value="1" onChange={this.handleChange} />
|
||||
<span className="custom-control-label">当前页的数据 ({_list.state.rowsData.length}条)</span>
|
||||
</label>
|
||||
<label className="custom-control custom-control-sm custom-radio">
|
||||
<input className="custom-control-input" name="dataRange" type="radio" checked={this.state.dataRange === '2'} value="2" onChange={this.handleChange} />
|
||||
<span className="custom-control-label">查询后的数据 ({_list.getQueryedTotal()}条)</span>
|
||||
</label>
|
||||
<label className="custom-control custom-control-sm custom-radio">
|
||||
<input className="custom-control-input" name="dataRange" type="radio" checked={this.state.dataRange === '3'} value="3" onChange={this.handleChange} />
|
||||
<span className="custom-control-label">全部数据</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="dialog-footer" ref={(c) => this._btns = c}>
|
||||
<a className="btn btn-link btn-space" onClick={this.hide}>取消</a>
|
||||
<button className="btn btn-primary btn-space" type="button" data-loading-text="请稍后" onClick={this._export}>确定</button>
|
||||
</div>
|
||||
</RbModal>
|
||||
}
|
||||
|
||||
_export = () => {
|
||||
let dr = ~~this.state.dataRange
|
||||
let filter = dr < 3 ? this.props.listRef.getLastQueryEntry() : {}
|
||||
|
||||
this.disabled(true)
|
||||
$.post(`${rb.baseUrl}/app/entity/data-export-submit?dr=${dr}`, JSON.stringify(filter), (res) => {
|
||||
if (res.error_code === 0) {
|
||||
this.hide()
|
||||
let url = `${rb.baseUrl}/filex/download/${res.data}?temp=yes`
|
||||
window.open(url)
|
||||
} else RbHighbar.error(res.error_msg)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 列表页操作类
|
||||
const RbListPage = {
|
||||
_RbList: null,
|
||||
|
||||
/**
|
||||
* @param {*} config DataList config
|
||||
* @param {*} entity [Name, Label, Icon]
|
||||
|
@ -572,25 +550,29 @@ const RbListPage = {
|
|||
}
|
||||
})
|
||||
$('.J_assign').click(() => {
|
||||
if ($('.J_assign').attr('disabled')) return
|
||||
let ids = this._RbList.getSelectedIds()
|
||||
ids.length > 0 && DlgAssign.create({ entity: entity[0], ids: ids })
|
||||
})
|
||||
$('.J_share').click(() => {
|
||||
if ($('.J_share').attr('disabled')) return
|
||||
let ids = this._RbList.getSelectedIds()
|
||||
ids.length > 0 && DlgShare.create({ entity: entity[0], ids: ids })
|
||||
})
|
||||
$('.J_unshare').click(() => {
|
||||
if ($('.J_unshare').attr('disabled')) return
|
||||
let ids = this._RbList.getSelectedIds()
|
||||
ids.length > 0 && DlgUnshare.create({ entity: entity[0], ids: ids })
|
||||
})
|
||||
$('.J_columns').click(() => RbModal.create(`${rb.baseUrl}/p/general-entity/show-fields?entity=${entity[0]}`, '设置列显示'))
|
||||
$('.J_export').click(() => renderRbcomp(<DataExport listRef={RbListPage._RbList} />))
|
||||
$('.J_export').click(() => renderRbcomp(<DataExport listRef={RbListPage._RbList} entity={entity[0]} />))
|
||||
$('.J_batch').click(() => renderRbcomp(<BatchUpdate listRef={RbListPage._RbList} entity={entity[0]} />))
|
||||
|
||||
// Privileges
|
||||
if (ep) {
|
||||
if (ep.C === false) $('.J_new').remove()
|
||||
if (ep.D === false) $('.J_delete').remove()
|
||||
if (ep.U === false) $('.J_edit').remove()
|
||||
if (ep.U === false) $('.J_edit, .J_batch').remove()
|
||||
if (ep.A === false) $('.J_assign').remove()
|
||||
if (ep.S === false) $('.J_share, .J_unshare').remove()
|
||||
$cleanMenu('.J_action')
|
||||
|
@ -608,7 +590,6 @@ const RbListPage = {
|
|||
|
||||
// 高级查询操作类
|
||||
const AdvFilters = {
|
||||
|
||||
/**
|
||||
* @param {*} el 控件
|
||||
* @param {*} entity 实体
|
||||
|
@ -985,3 +966,385 @@ $(document).ready(() => {
|
|||
ChartsWidget.init()
|
||||
}
|
||||
})
|
||||
|
||||
// ~~列表记录批量操作
|
||||
class BatchOperator extends RbFormHandler {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state.dataRange = 2
|
||||
}
|
||||
|
||||
render() {
|
||||
const _listRef = this.props.listRef
|
||||
const selectedRows = _listRef.getSelectedRows().length
|
||||
const pageRows = _listRef.state.rowsData.length
|
||||
const queryRows = _listRef.getLastQueryTotal()
|
||||
return <RbModal title={this.state.title} disposeOnHide={true} ref={(c) => this._dlg = c}>
|
||||
<div className="form batch-form">
|
||||
<div className="form-group">
|
||||
<label className="text-bold">选择数据范围</label>
|
||||
<div>
|
||||
{selectedRows > 0 && <label className="custom-control custom-control-sm custom-radio mb-2">
|
||||
<input className="custom-control-input" name="dataRange" type="radio" checked={~~this.state.dataRange === 1} value="1" onChange={this.handleChange} />
|
||||
<span className="custom-control-label">选中的数据 ({selectedRows}条)</span>
|
||||
</label>}
|
||||
<label className="custom-control custom-control-sm custom-radio mb-2">
|
||||
<input className="custom-control-input" name="dataRange" type="radio" checked={~~this.state.dataRange === 2} value="2" onChange={this.handleChange} />
|
||||
<span className="custom-control-label">当前页的数据 ({pageRows}条)</span>
|
||||
</label>
|
||||
<label className="custom-control custom-control-sm custom-radio mb-2">
|
||||
<input className="custom-control-input" name="dataRange" type="radio" checked={~~this.state.dataRange === 3} value="3" onChange={this.handleChange} />
|
||||
<span className="custom-control-label">查询后的数据 ({queryRows}条)</span>
|
||||
</label>
|
||||
<label className="custom-control custom-control-sm custom-radio mb-1">
|
||||
<input className="custom-control-input" name="dataRange" type="radio" checked={~~this.state.dataRange === 10} value="10" onChange={this.handleChange} />
|
||||
<span className="custom-control-label">全部数据</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{this.renderOperator()}
|
||||
</div>
|
||||
<div className="dialog-footer" ref={(c) => this._btns = c}>
|
||||
<a className="btn btn-link btn-space" onClick={this.hide}>取消</a>
|
||||
<button className="btn btn-primary btn-space" type="button" data-loading-text="请稍后" onClick={this.confirm}>确定</button>
|
||||
</div>
|
||||
</RbModal>
|
||||
}
|
||||
|
||||
getQueryData() {
|
||||
let qd = this.props.listRef.getLastQueryData()
|
||||
if (~~this.state.dataRange === 1) qd._selected = this.props.listRef.getSelectedIds().join('|')
|
||||
return qd
|
||||
}
|
||||
|
||||
// 子类复写
|
||||
|
||||
renderOperator() { }
|
||||
confirm = () => { }
|
||||
}
|
||||
|
||||
// ~ 数据导出
|
||||
class DataExport extends BatchOperator {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state.title = '数据导出'
|
||||
}
|
||||
|
||||
confirm = () => {
|
||||
this.disabled(true)
|
||||
$.post(`${rb.baseUrl}/app/${this.props.entity}/data-export/submit?dr=${this.state.dataRange}`, JSON.stringify(this.getQueryData()), (res) => {
|
||||
if (res.error_code === 0) {
|
||||
this.hide()
|
||||
let url = `${rb.baseUrl}/filex/download/${res.data}?temp=yes`
|
||||
window.open(url)
|
||||
} else {
|
||||
this.disabled(false)
|
||||
RbHighbar.error(res.error_msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ~ 批量修改
|
||||
class BatchUpdate extends BatchOperator {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state.title = '批量修改'
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
$.get(`${rb.baseUrl}/app/${this.props.entity}/batch-update/fields`, (res) => this.setState({ fields: res.data }))
|
||||
}
|
||||
|
||||
renderOperator() {
|
||||
return <div className="form-group">
|
||||
<label className="text-bold">修改内容</label>
|
||||
<div>
|
||||
<div className="batch-contents">
|
||||
{(this.state.updateContents || []).map((item) => {
|
||||
return <div key={`update-${item.field}`}>
|
||||
<div className="row">
|
||||
<div className="col-4">
|
||||
<span className="badge badge-light">{this._fieldLabel(item.field)}</span>
|
||||
</div>
|
||||
<div className="col-2 pl-0 pr-0">
|
||||
<span className="badge badge-warning">{BUE_OPTYPES[item.op]}</span>
|
||||
</div>
|
||||
<div className="col-6">
|
||||
{item.op !== 'NULL' && <span className="badge badge-light">{item.text || item.value}</span>}
|
||||
<a className="del" onClick={() => this.removeItem(item.field)}><i className="zmdi zmdi-close"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
})}
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
{this.state.fields && <BatchUpdateEditor ref={(c) => this._editor = c} fields={this.state.fields} entity={this.props.entity} />}
|
||||
<div className="mt-1">
|
||||
<button className="btn btn-primary btn-sm bordered" onClick={this.addItem}>添加</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
_fieldLabel(fieldName) {
|
||||
let field = this.state.fields.find((item) => { return fieldName === item.name })
|
||||
return field ? field.label : `[${fieldName}.toUpperCase()]`
|
||||
}
|
||||
|
||||
addItem = () => {
|
||||
let item = this._editor.buildItem()
|
||||
if (!item) return
|
||||
|
||||
let contents = this.state.updateContents || []
|
||||
$(contents).each(function () {
|
||||
if (item.field === this.field) {
|
||||
RbHighbar.create('修改字段已经存在')
|
||||
item = null
|
||||
return false
|
||||
}
|
||||
})
|
||||
if (!item) return
|
||||
|
||||
contents.push(item)
|
||||
this.setState({ updateContents: contents })
|
||||
}
|
||||
|
||||
removeItem(fieldName) {
|
||||
let contents = []
|
||||
this.state.updateContents.forEach((item) => {
|
||||
if (fieldName !== item.field) contents.push(item)
|
||||
})
|
||||
this.setState({ updateContents: contents })
|
||||
}
|
||||
|
||||
confirm = () => {
|
||||
if (!this.state.updateContents || this.state.updateContents.length === 0) { RbHighbar.create('请添加修改内容'); return }
|
||||
let _data = { queryData: this.getQueryData(), updateContents: this.state.updateContents }
|
||||
// eslint-disable-next-line no-console
|
||||
if (rb.env === 'dev') console.log(JSON.stringify(_data))
|
||||
|
||||
let that = this
|
||||
RbAlert.create('请再次确认修改数据范围和修改内容。开始修改吗?', {
|
||||
confirm: function () {
|
||||
this.hide()
|
||||
that.disabled(true)
|
||||
$.post(`${rb.baseUrl}/app/${that.props.entity}/batch-update/submit?dr=${that.state.dataRange}`, JSON.stringify(_data), (res) => {
|
||||
if (res.error_code === 0) {
|
||||
let mp = new Mprogress({ template: 2, start: true, parent: '.rbmodal .modal-body' })
|
||||
that.__checkState(res.data, mp)
|
||||
} else {
|
||||
that.disabled(false)
|
||||
RbHighbar.error(res.error_msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
__checkState(taskid, mp) {
|
||||
$.get(`${rb.baseUrl}/commons/task/state?taskid=${taskid}`, (res) => {
|
||||
if (res.error_code === 0) {
|
||||
if (res.data.hasError) {
|
||||
mp && mp.end()
|
||||
RbHighbar.error(res.data.hasError)
|
||||
return
|
||||
}
|
||||
|
||||
let cp = res.data.progress
|
||||
if (cp >= 1) {
|
||||
mp && mp.end()
|
||||
$(this._btns).find('.btn-primary').text('修改成功')
|
||||
RbHighbar.success(`成功修改 ${res.data.succeeded} 条记录`)
|
||||
setTimeout(() => {
|
||||
this.hide()
|
||||
window.RbListPage && window.RbListPage.reload()
|
||||
}, 500)
|
||||
} else {
|
||||
mp && mp.set(cp)
|
||||
setTimeout(() => { this.__checkState(taskid, mp) }, 1000)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const BUE_OPTYPES = { 'SET': '修改为', 'NULL': '置空', 'PREFIX': '前添加', 'SUFFIX': '后添加', 'PLUS': '加上', 'MINUS': '减去' }
|
||||
// ~ 批量修改编辑器
|
||||
class BatchUpdateEditor extends React.Component {
|
||||
state = { ...this.props, selectOp: 'SET' }
|
||||
|
||||
componentDidMount() {
|
||||
let fieldS2 = $(this._field).select2({
|
||||
allowClear: false
|
||||
}).on('change', () => {
|
||||
this.setState({ selectField: fieldS2.val() })
|
||||
})
|
||||
let opS2 = $(this._op).select2({
|
||||
allowClear: false
|
||||
}).on('change', () => {
|
||||
this.setState({ selectOp: opS2.val() })
|
||||
})
|
||||
fieldS2.trigger('change')
|
||||
this.__select2 = [fieldS2, opS2]
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.__select2.forEach((item) => { item.select2('destroy') })
|
||||
this.__select2 = null
|
||||
this.__destroyLastValueComp()
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div className="row">
|
||||
<div className="col-4">
|
||||
<select className="form-control form-control-sm" ref={(c) => this._field = c}>
|
||||
{this.props.fields.map((item) => {
|
||||
return <option value={item.name} key={`field-${item.name}`}>{item.label}</option>
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
<div className="col-2 pl-0 pr-0">{this.renderOp()}</div>
|
||||
<div className="col-6">
|
||||
<div className={`${this.state.selectOp === 'NULL' ? 'hide' : ''}`}>
|
||||
{(this.state.selectField && this.state.selectOp) && this.renderValue()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
renderOp() {
|
||||
return <select className="form-control form-control-sm" ref={(c) => this._op = c}>
|
||||
<option value="SET">修改为</option>
|
||||
<option value="NULL">置空</option>
|
||||
</select>
|
||||
}
|
||||
|
||||
renderValue() {
|
||||
if (this.state.selectOp === 'NULL' || !this.state.selectField) return null // set Null
|
||||
|
||||
const field = this.props.fields.find((item) => { return this.state.selectField === item.name })
|
||||
const fieldKey = `fv-${field.name}`
|
||||
if (field.type === 'PICKLIST' || field.type === 'STATE' || field.type === 'MULTISELECT' || field.type === 'BOOL'
|
||||
|| field.type === 'REFERENCE' || field.type === 'CLASSIFICATION') {
|
||||
return <select className="form-control form-control-sm" multiple={field.type === 'MULTISELECT'} ref={(c) => this._value = c} key={fieldKey}>
|
||||
{(field.options || []).map((item) => {
|
||||
let itemId = item.id || item.mask
|
||||
if (item.id === false) itemId = 'false' // for BOOL
|
||||
return <option key={`value-${itemId}`} value={itemId}>{item.text}</option>
|
||||
})}
|
||||
</select>
|
||||
} else {
|
||||
return <input className="form-control form-control-sm" placeholder={`输入${field.label}`} ref={(c) => this._value = c} key={fieldKey} />
|
||||
}
|
||||
}
|
||||
|
||||
__destroyLastValueComp() {
|
||||
if (this.__lastSelect2) {
|
||||
this.__lastSelect2.select2('destroy')
|
||||
this.__lastSelect2 = null
|
||||
}
|
||||
if (this.__lastDatetimepicker) {
|
||||
this.__lastDatetimepicker.datetimepicker('remove')
|
||||
this.__lastDatetimepicker = null
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
// Unchanged
|
||||
if (prevState.selectField === this.state.selectField && prevState.selectOp === this.state.selectOp) return
|
||||
if (this.state.selectOp === 'NULL') return
|
||||
this.__destroyLastValueComp()
|
||||
|
||||
const field = this.props.fields.find((item) => { return this.state.selectField === item.name })
|
||||
if (this._value.tagName === 'SELECT') {
|
||||
if (field.type === 'REFERENCE' || field.type === 'CLASSIFICATION') {
|
||||
this.__lastSelect2 = $initReferenceSelect2(this._value, {
|
||||
name: field.name,
|
||||
label: field.label,
|
||||
entity: this.props.entity,
|
||||
searchType: field.type === 'CLASSIFICATION' ? 'classification' : null
|
||||
})
|
||||
} else {
|
||||
this.__lastSelect2 = $(this._value).select2({
|
||||
placeholder: `选择${field.label}`
|
||||
})
|
||||
}
|
||||
this.__lastSelect2.val(null).trigger('change')
|
||||
|
||||
} else if (field.type === 'DATE' || field.type === 'DATETIME') {
|
||||
this.__lastDatetimepicker = $(this._value).datetimepicker({
|
||||
componentIcon: 'zmdi zmdi-calendar',
|
||||
navIcons: { rightIcon: 'zmdi zmdi-chevron-right', leftIcon: 'zmdi zmdi-chevron-left' },
|
||||
format: field.type === 'DATE' ? 'yyyy-mm-dd' : 'yyyy-mm-dd hh:ii:ss',
|
||||
minView: field.type === 'DATE' ? 'month' : 0,
|
||||
weekStart: 1,
|
||||
autoclose: true,
|
||||
language: 'zh',
|
||||
todayHighlight: true,
|
||||
showMeridian: false,
|
||||
keyboardNavigation: false,
|
||||
minuteStep: 5
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
buildItem() {
|
||||
let item = { field: this.state.selectField, op: this.state.selectOp }
|
||||
const field = this.props.fields.find((item) => { return this.state.selectField === item.name })
|
||||
if (item.op === 'NULL') {
|
||||
if (!field.nullable) {
|
||||
RbHighbar.create(`${field.label}不允许为空`)
|
||||
return null
|
||||
} else {
|
||||
return item
|
||||
}
|
||||
}
|
||||
|
||||
item.value = $(this._value).val()
|
||||
if (!item.value || item.value.length === 0) {
|
||||
RbHighbar.create('修改值不能为空')
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
if (field.type === 'MULTISELECT') {
|
||||
let maskTotal = 0
|
||||
item.value.forEach((mask) => maskTotal += ~~mask)
|
||||
item.value = maskTotal
|
||||
} else if (field.type === 'NUMBER' || field.type === 'DECIMAL') {
|
||||
if (isNaN(item.value)) {
|
||||
RbHighbar.create(`${field.label}格式不正确`)
|
||||
return null
|
||||
} else if (field.notNegative === 'true' && ~~item.value < 0) {
|
||||
RbHighbar.create(`${field.label}不允许为负数`)
|
||||
return null
|
||||
}
|
||||
} else if (field.type === 'EMAIL') {
|
||||
if (!$regex.isMail(item.value)) {
|
||||
RbHighbar.create(`${field.label}格式不正确`)
|
||||
return null
|
||||
}
|
||||
} else if (field.type === 'URL') {
|
||||
if (!$regex.isUrl(item.value)) {
|
||||
RbHighbar.create(`${field.label}格式不正确`)
|
||||
return null
|
||||
}
|
||||
} else if (field.type === 'PHONE') {
|
||||
if (!$regex.isTel(item.value)) {
|
||||
RbHighbar.create(`${field.label}格式不正确`)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
if (this._value.tagName === 'SELECT') {
|
||||
let texts = $(this._value).select2('data').map((o) => { return o.text })
|
||||
item.text = texts.join(', ')
|
||||
$(this._value).val(null).trigger('change')
|
||||
} else {
|
||||
$(this._value).val('')
|
||||
}
|
||||
return item
|
||||
}
|
||||
}
|
|
@ -783,40 +783,22 @@ class RbFormReference extends RbFormElement {
|
|||
|
||||
const entity = this.props.$$$parent.props.entity
|
||||
const field = this.props.field
|
||||
let search_input = null
|
||||
this.__select2 = $(this._fvalue).select2({
|
||||
placeholder: '选择' + this.props.label,
|
||||
minimumInputLength: 0,
|
||||
maximumSelectionLength: 1,
|
||||
ajax: {
|
||||
url: rb.baseUrl + '/commons/search/reference',
|
||||
delay: 300,
|
||||
data: function (params) {
|
||||
search_input = params.term
|
||||
return { entity: entity, field: field, q: params.term }
|
||||
},
|
||||
processResults: function (data) {
|
||||
return { results: data.data }
|
||||
}
|
||||
},
|
||||
language: {
|
||||
noResults: () => { return (search_input || '').length > 0 ? '未找到结果' : '输入关键词搜索' },
|
||||
inputTooShort: () => { return '输入关键词搜索' },
|
||||
searching: () => { return '搜索中...' },
|
||||
maximumSelected: () => { return '只能选择 1 项' }
|
||||
}
|
||||
|
||||
this.__select2 = $initReferenceSelect2(this._fvalue, {
|
||||
name: field,
|
||||
label: this.props.label,
|
||||
entity: entity
|
||||
})
|
||||
|
||||
let that = this
|
||||
let s2 = this.__select2
|
||||
$setTimeout(function () {
|
||||
let val = that.props.value
|
||||
const that = this
|
||||
$setTimeout(() => {
|
||||
let val = this.props.value
|
||||
if (val) {
|
||||
let o = new Option(val[1], val[0], true, true)
|
||||
s2.append(o).trigger('change')
|
||||
this.__select2.append(o).trigger('change')
|
||||
}
|
||||
|
||||
s2.on('change', function (e) {
|
||||
this.__select2.on('change', function (e) {
|
||||
let v = e.target.value
|
||||
if (v) {
|
||||
$.post(`${rb.baseUrl}/commons/search/recently-add?id=${v}`)
|
||||
|
@ -870,30 +852,11 @@ class RbFormClassification extends RbFormElement {
|
|||
super.componentDidMount()
|
||||
if (this.state.viewMode === true) return
|
||||
|
||||
const entity = this.props.$$$parent.props.entity
|
||||
const field = this.props.field
|
||||
let search_input = null
|
||||
this.__select2 = $(this._fvalue).select2({
|
||||
placeholder: '选择' + this.props.label,
|
||||
minimumInputLength: 0,
|
||||
maximumSelectionLength: 1,
|
||||
ajax: {
|
||||
url: rb.baseUrl + '/commons/search/classification',
|
||||
delay: 300,
|
||||
data: function (params) {
|
||||
search_input = params.term
|
||||
return { entity: entity, field: field, q: params.term }
|
||||
},
|
||||
processResults: function (data) {
|
||||
return { results: data.data }
|
||||
}
|
||||
},
|
||||
language: {
|
||||
noResults: () => { return (search_input || '').length > 0 ? '未找到结果' : '输入关键词搜索' },
|
||||
inputTooShort: () => { return '输入关键词搜索' },
|
||||
searching: () => { return '搜索中...' },
|
||||
maximumSelected: () => { return '只能选择 1 项' }
|
||||
}
|
||||
this.__select2 = $initReferenceSelect2(this._fvalue, {
|
||||
name: this.props.field,
|
||||
label: this.props.label,
|
||||
entity: this.props.$$$parent.props.entity,
|
||||
searchType: 'classification'
|
||||
})
|
||||
|
||||
// In edits
|
||||
|
|
|
@ -406,6 +406,33 @@ var $initUserSelect2 = function (el, multiple) {
|
|||
return s
|
||||
}
|
||||
|
||||
// 初始化引用字段搜索
|
||||
var $initReferenceSelect2 = function (el, field) {
|
||||
var search_input = null
|
||||
return $(el).select2({
|
||||
placeholder: '选择' + field.label,
|
||||
minimumInputLength: 0,
|
||||
maximumSelectionLength: 1,
|
||||
ajax: {
|
||||
url: rb.baseUrl + '/commons/search/' + (field.searchType || 'reference'),
|
||||
delay: 300,
|
||||
data: function (params) {
|
||||
search_input = params.term
|
||||
return { entity: field.entity, field: field.name, q: params.term }
|
||||
},
|
||||
processResults: function (data) {
|
||||
return { results: data.data }
|
||||
}
|
||||
},
|
||||
language: {
|
||||
noResults: function () { return (search_input || '').length > 0 ? '未找到结果' : '输入关键词搜索' },
|
||||
inputTooShort: function () { return '输入关键词搜索' },
|
||||
searching: function () { return '搜索中...' },
|
||||
maximumSelected: function () { return '只能选择 1 项' }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 保持模态窗口(如果需要)
|
||||
var $keepModalOpen = function () {
|
||||
if ($('.rbmodal.show, .rbview.show').length > 0) {
|
||||
|
|
|
@ -174,7 +174,7 @@ class SelectReport extends React.Component {
|
|||
<div>
|
||||
<ul className="list-unstyled">
|
||||
{(this.state.reports || []).map((item) => {
|
||||
let reportUrl = `${rb.baseUrl}/app/entity/report-export?report=${item.id}&record=${this.props.id}`
|
||||
let reportUrl = `${rb.baseUrl}/app/${this.props.entity}/reports/export?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>
|
||||
|
@ -187,9 +187,7 @@ class SelectReport extends React.Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
$.get(`${rb.baseUrl}/app/entity/available-reports?entity=${this.props.entity}`, (res) => {
|
||||
this.setState({ reports: res.data })
|
||||
})
|
||||
$.get(`${rb.baseUrl}/app/${this.props.entity}/reports/available`, (res) => this.setState({ reports: res.data }))
|
||||
$(this._dlg).modal({ show: true, keyboard: true })
|
||||
}
|
||||
hide = () => $(this._dlg).modal('hide')
|
||||
|
|
|
@ -58,16 +58,16 @@
|
|||
<div class="data-info mt-3">
|
||||
<h5>图表选项</h5>
|
||||
<div class="pl-1 mt-3 chart-option">
|
||||
<div class="J_opt-UNDEF active">
|
||||
此图表无选项
|
||||
<div class="J_opt-UNDEF">
|
||||
当前图表无选项
|
||||
</div>
|
||||
<div class="admin-show J_opt-TABLE J_opt-INDEX J_opt-LINE J_opt-BAR J_opt-PIE J_opt-FUNNEL J_opt-TREEMAP">
|
||||
<div class="hide admin-show J_opt-TABLE J_opt-INDEX J_opt-LINE J_opt-BAR J_opt-PIE J_opt-FUNNEL J_opt-TREEMAP">
|
||||
<label class="custom-control custom-control-sm custom-checkbox mb-2">
|
||||
<input class="custom-control-input" type="checkbox" data-name="noPrivileges">
|
||||
<span class="custom-control-label"> 使用全部数据 <i class="zmdi zmdi-help zicon" title="不启用则仅能使用权限范围内的数据"></i></span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="J_opt-TABLE">
|
||||
<div class="hide J_opt-TABLE">
|
||||
<label class="custom-control custom-control-sm custom-checkbox mb-2">
|
||||
<input class="custom-control-input" type="checkbox" data-name="showLineNumber">
|
||||
<span class="custom-control-label"> 显示行号</span>
|
||||
|
|
|
@ -7,11 +7,6 @@
|
|||
<link rel="stylesheet" type="text/css" href="${baseUrl}/assets/lib/charts/gridstack.css">
|
||||
<link rel="stylesheet" type="text/css" href="${baseUrl}/assets/css/charts.css">
|
||||
<link rel="stylesheet" type="text/css" href="${baseUrl}/assets/css/dashboard.css">
|
||||
<style type="text/css">
|
||||
.shareTo--wrap .custom-control {
|
||||
margin-top: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="rb-wrapper rb-fixed-sidebar rb-collapsible-sidebar rb-collapsible-sidebar-hide-logo">
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<div class="tab-container">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="nav-item"><a class="nav-link active" href="attachment">附件</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="docs">文件</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="docs">文档</a></li>
|
||||
</ul>
|
||||
<div class="tab-content rb-scroller">
|
||||
<div class="tab-pane active" id="navTree">
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
<html>
|
||||
<head>
|
||||
<%@ include file="/_include/Head.jsp"%>
|
||||
<title>文件管理</title>
|
||||
<title>文档管理</title>
|
||||
<link rel="stylesheet" type="text/css" href="${baseUrl}/assets/css/files.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="rb-wrapper rb-fixed-sidebar rb-collapsible-sidebar rb-collapsible-sidebar-hide-logo rb-aside">
|
||||
<jsp:include page="/_include/NavTop.jsp">
|
||||
<jsp:param value="文件管理" name="pageTitle"/>
|
||||
<jsp:param value="文档管理" name="pageTitle"/>
|
||||
</jsp:include>
|
||||
<jsp:include page="/_include/NavLeft.jsp">
|
||||
<jsp:param value="nav_entity-Attachment" name="activeNav"/>
|
||||
|
@ -20,7 +20,7 @@
|
|||
<div class="tab-container">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="nav-item"><a class="nav-link" href="attachment">附件</a></li>
|
||||
<li class="nav-item"><a class="nav-link active" href="docs">文件</a></li>
|
||||
<li class="nav-item"><a class="nav-link active" href="docs">文档</a></li>
|
||||
</ul>
|
||||
<div class="tab-content rb-scroller">
|
||||
<div class="tab-pane active" id="navTree">
|
||||
|
@ -40,7 +40,7 @@
|
|||
<div class="file-header">
|
||||
<div class="file-path">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item active">全部文件</li>
|
||||
<li class="breadcrumb-item active">全部文档</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="file-operator row">
|
||||
|
|
|
@ -71,7 +71,7 @@
|
|||
<div class="dataTables_oper">
|
||||
<button class="btn btn-space btn-secondary J_view" disabled="disabled"><i class="icon zmdi zmdi-folder"></i> 打开</button>
|
||||
<button class="btn btn-space btn-secondary J_edit" disabled="disabled"><i class="icon zmdi zmdi-border-color"></i> 编辑</button>
|
||||
<button class="btn btn-space btn-primary J_new" data-url="${baseUrl}/entity/${entity}/new"><i class="icon zmdi zmdi-plus"></i> 新建</button>
|
||||
<button class="btn btn-space btn-primary J_new"><i class="icon zmdi zmdi-plus"></i> 新建</button>
|
||||
<div class="btn-group btn-space J_action">
|
||||
<button class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown">更多 <i class="icon zmdi zmdi-more-vert"></i></button>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
|
@ -80,11 +80,10 @@
|
|||
<a class="dropdown-item J_share"><i class="icon zmdi zmdi-portable-wifi"></i> 共享</a>
|
||||
<a class="dropdown-item J_unshare"><i class="icon zmdi zmdi-portable-wifi-off"></i> 取消共享</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<c:if test="${AllowCustomDataList}">
|
||||
<a class="dropdown-item J_columns"><i class="icon zmdi zmdi-code-setting"></i> 列显示</a>
|
||||
</c:if><c:if test="${AllowDataExport}">
|
||||
<a class="dropdown-item J_export"><i class="icon zmdi zmdi-cloud-download"></i> 导出</a>
|
||||
</c:if>
|
||||
<c:if test="${AllowDataExport}"><a class="dropdown-item J_export"><i class="icon zmdi zmdi-cloud-download"></i> 数据导出</a></c:if>
|
||||
<a class="dropdown-item admin-show" href="${baseUrl}/admin/datas/data-importer?entity=${entityName}" target="_blank"><i class="icon zmdi zmdi-cloud-upload"></i> 数据导入</a>
|
||||
<c:if test="${AllowBatchUpdate}"><a class="dropdown-item J_batch"><i class="icon zmdi zmdi-flash"></i> 批量修改</a></c:if>
|
||||
<c:if test="${AllowCustomDataList}"><a class="dropdown-item J_columns"><i class="icon zmdi zmdi-code-setting"></i> 列显示</a></c:if>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -109,14 +108,14 @@ window.__PageConfig = {
|
|||
advFilter: true
|
||||
}
|
||||
</script>
|
||||
<script src="${baseUrl}/assets/lib/charts/echarts.min.js"></script>
|
||||
<script src="${baseUrl}/assets/js/charts/charts.jsx" type="text/babel"></script>
|
||||
<script src="${baseUrl}/assets/js/rb-datalist.jsx" type="text/babel"></script>
|
||||
<script src="${baseUrl}/assets/js/rb-forms.jsx" type="text/babel"></script>
|
||||
<script src="${baseUrl}/assets/js/rb-forms.exts.jsx" type="text/babel"></script>
|
||||
<script src="${baseUrl}/assets/js/rb-advfilter.jsx" type="text/babel"></script>
|
||||
<script src="${baseUrl}/assets/js/rb-assignshare.jsx" type="text/babel"></script>
|
||||
<script src="${baseUrl}/assets/js/rb-approval.jsx" type="text/babel"></script>
|
||||
<script src="${baseUrl}/assets/lib/charts/echarts.min.js"></script>
|
||||
<script src="${baseUrl}/assets/js/charts/charts.jsx" type="text/babel"></script>
|
||||
<script src="${baseUrl}/assets/js/settings-share2.jsx" type="text/babel"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
<a class="dropdown-item J_assign"><i class="icon zmdi zmdi-mail-reply-all"></i> 分派</a>
|
||||
<a class="dropdown-item J_share"><i class="icon zmdi zmdi-portable-wifi"></i> 共享</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item J_print" target="_blank" href="${baseUrl}/app/entity/print?id=${id}"><i class="icon zmdi zmdi-print"></i> 打印</a>
|
||||
<a class="dropdown-item J_print" target="_blank" href="${baseUrl}/app/${entityName}/print?id=${id}"><i class="icon zmdi zmdi-print"></i> 打印</a>
|
||||
<a class="dropdown-item J_report"><i class="icon zmdi zmdi-map"></i> 报表</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -74,11 +74,10 @@
|
|||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<a class="dropdown-item J_delete"><i class="icon zmdi zmdi-delete"></i> 删除</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<c:if test="${AllowCustomDataList}">
|
||||
<a class="dropdown-item J_columns"><i class="icon zmdi zmdi-code-setting"></i> 列显示</a>
|
||||
</c:if><c:if test="${AllowDataExport}">
|
||||
<a class="dropdown-item J_export"><i class="icon zmdi zmdi-cloud-download"></i> 导出</a>
|
||||
</c:if>
|
||||
<c:if test="${AllowDataExport}"><a class="dropdown-item J_export"><i class="icon zmdi zmdi-cloud-download"></i> 数据导出</a></c:if>
|
||||
<a class="dropdown-item admin-show" href="${baseUrl}/admin/datas/data-importer?entity=${entityName}" target="_blank"><i class="icon zmdi zmdi-cloud-upload"></i> 数据导入</a>
|
||||
<c:if test="${AllowBatchUpdate}"><a class="dropdown-item J_batch"><i class="icon zmdi zmdi-flash"></i> 批量修改</a></c:if>
|
||||
<c:if test="${AllowCustomDataList}"><a class="dropdown-item J_columns"><i class="icon zmdi zmdi-code-setting"></i> 列显示</a></c:if>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -103,13 +102,13 @@ window.__PageConfig = {
|
|||
advFilter: true
|
||||
}
|
||||
</script>
|
||||
<script src="${baseUrl}/assets/lib/charts/echarts.min.js"></script>
|
||||
<script src="${baseUrl}/assets/js/charts/charts.jsx" type="text/babel"></script>
|
||||
<script src="${baseUrl}/assets/js/rb-datalist.jsx" type="text/babel"></script>
|
||||
<script src="${baseUrl}/assets/js/rb-forms.jsx" type="text/babel"></script>
|
||||
<script src="${baseUrl}/assets/js/rb-forms.exts.jsx" type="text/babel"></script>
|
||||
<script src="${baseUrl}/assets/js/rb-advfilter.jsx" type="text/babel"></script>
|
||||
<script src="${baseUrl}/assets/js/rb-approval.jsx" type="text/babel"></script>
|
||||
<script src="${baseUrl}/assets/lib/charts/echarts.min.js"></script>
|
||||
<script src="${baseUrl}/assets/js/charts/charts.jsx" type="text/babel"></script>
|
||||
<script src="${baseUrl}/assets/js/settings-share2.jsx" type="text/babel"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<a class="dropdown-item J_delete"><i class="icon zmdi zmdi-delete"></i> 删除</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item J_print" target="_blank" href="${baseUrl}/app/entity/print?id=${id}"><i class="icon zmdi zmdi-print"></i> 打印</a>
|
||||
<a class="dropdown-item J_print" target="_blank" href="${baseUrl}/app/${entityName}/print?id=${id}"><i class="icon zmdi zmdi-print"></i> 打印</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -67,7 +67,7 @@ public abstract class TestSupportWithUser extends TestSupport {
|
|||
protected ID addRecordOfTestAllFields() {
|
||||
Entity testEntity = MetadataHelper.getEntity(TEST_ENTITY);
|
||||
// 自动添加权限
|
||||
if (!Application.getSecurityManager().allowedR(getSessionUser(), testEntity.getEntityCode())) {
|
||||
if (!Application.getSecurityManager().allowCreate(getSessionUser(), testEntity.getEntityCode())) {
|
||||
Record p = EntityHelper.forNew(EntityHelper.RolePrivileges, UserService.SYSTEM_USER);
|
||||
p.setID("roleId", SIMPLE_ROLE);
|
||||
p.setInt("entity", testEntity.getEntityCode());
|
||||
|
|
|
@ -58,7 +58,8 @@ public class DataImporterTest extends TestSupport {
|
|||
JSONObject rule = JSON.parseObject("{ file:'dataimports-test.xlsx', entity:'TestAllFields', repeat_opt:2, repeat_fields:['TestAllFieldsName'], owning_user:'001-0000000000000001', fields_mapping:{TestAllFieldsName:5} }");
|
||||
ImportRule importsEnter = ImportRule.parse(rule);
|
||||
|
||||
DataImporter dataImports = new DataImporter(importsEnter, UserService.ADMIN_USER);
|
||||
DataImporter dataImports = new DataImporter(importsEnter);
|
||||
dataImports.setUser(UserService.ADMIN_USER);
|
||||
dataImports.run();
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue