完善添加数据操作的批量执行功能

This commit is contained in:
datagear 2018-11-30 15:58:10 +08:00
parent b4db6d0364
commit fe8ecd6fd9
20 changed files with 688 additions and 140 deletions

View File

@ -8,6 +8,7 @@ import java.sql.Connection;
import org.datagear.model.Model;
import org.datagear.model.support.PropertyPathInfo;
import org.datagear.persistence.support.ExpressionEvaluationContext;
/**
* 持久化管理器
@ -17,6 +18,21 @@ import org.datagear.model.support.PropertyPathInfo;
*/
public interface PersistenceManager
{
/**
* 获取指定{@linkplain Model}的表名称
*
* @param model
* @return
*/
String getTableName(Model model);
/**
* 获取此{@linkplain PersistenceManager}使用的{@linkplain DialectSource}
*
* @return
*/
DialectSource getDialectSource();
/**
* 插入数据
*
@ -27,6 +43,21 @@ public interface PersistenceManager
*/
void insert(Connection cn, Model model, Object obj) throws PersistenceException;
/**
* 插入数据
*
* @param cn
* @param dialect
* @param table
* @param model
* @param obj
* @param expressionEvaluationContext
* @return
* @throws PersistenceException
*/
int insert(Connection cn, Dialect dialect, String table, Model model, Object obj,
ExpressionEvaluationContext expressionEvaluationContext) throws PersistenceException;
/**
* 更新数据
*
@ -41,6 +72,22 @@ public interface PersistenceManager
int update(Connection cn, Model model, Object originalObj, Object updateObj, boolean updateMultipleProperty)
throws PersistenceException;
/**
* 更新数据
*
* @param cn
* @param dialect
* @param table
* @param model
* @param originalObj
* @param updateObj
* @param expressionEvaluationContext
* @return
* @throws PersistenceException
*/
int update(Connection cn, Dialect dialect, String table, Model model, Object originalObj, Object updateObj,
ExpressionEvaluationContext expressionEvaluationContext) throws PersistenceException;
/**
* 插入单元属性值
*

View File

@ -19,6 +19,7 @@ import org.datagear.model.support.MU;
import org.datagear.persistence.UnsupportedModelCharacterException;
import org.datagear.persistence.support.ExpressionResolver.Expression;
import org.springframework.core.convert.ConversionService;
import org.springframework.expression.ExpressionException;
import org.springframework.expression.spel.standard.SpelExpressionParser;
/**
@ -280,7 +281,7 @@ public abstract class AbstractExpressionModelPersistenceOperation extends Abstra
expressionValues.add(value);
expressionEvaluationContext.putCachedValue(expression, value);
}
catch (Exception e)
catch (ExpressionException e)
{
throw new VariableExpressionErrorException(expression, e);
}

View File

@ -78,6 +78,7 @@ public class DefaultPersistenceManager extends AbstractModelDataAccessObject imp
this.selectPersistenceOperation = new SelectPersistenceOperation();
}
@Override
public DialectSource getDialectSource()
{
return dialectSource;
@ -164,6 +165,12 @@ public class DefaultPersistenceManager extends AbstractModelDataAccessObject imp
this.selectPersistenceOperation = selectPersistenceOperation;
}
@Override
public String getTableName(Model model)
{
return super.getTableName(model);
}
@Override
public void insert(Connection cn, Model model, Object obj) throws PersistenceException
{
@ -172,6 +179,13 @@ public class DefaultPersistenceManager extends AbstractModelDataAccessObject imp
this.insertPersistenceOperation.insert(cn, dialect, getTableName(model), model, obj);
}
@Override
public int insert(Connection cn, Dialect dialect, String table, Model model, Object obj,
ExpressionEvaluationContext expressionEvaluationContext) throws PersistenceException
{
return this.insertPersistenceOperation.insert(cn, dialect, table, model, obj, expressionEvaluationContext);
}
@Override
public int update(Connection cn, Model model, Object originalObj, Object updateObj, boolean updateMultipleProperty)
throws PersistenceException
@ -181,6 +195,14 @@ public class DefaultPersistenceManager extends AbstractModelDataAccessObject imp
return this.updatePersistenceOperation.update(cn, dialect, getTableName(model), model, originalObj, updateObj);
}
@Override
public int update(Connection cn, Dialect dialect, String table, Model model, Object originalObj, Object updateObj,
ExpressionEvaluationContext expressionEvaluationContext) throws PersistenceException
{
return this.updatePersistenceOperation.update(cn, dialect, table, model, originalObj, updateObj,
expressionEvaluationContext);
}
@Override
public void insertSinglePropValue(Connection cn, Model model, Object obj, PropertyPathInfo propertyPathInfo,
Object propValue) throws PersistenceException

View File

@ -94,6 +94,14 @@ public class ExpressionEvaluationContext
this.valueCache.put(cacheKey, value);
}
/**
* 清除所有缓存值
*/
public void clearCachedValue()
{
this.valueCache.clear();
}
/**
* 获取{@linkplain #getVariableExpressionBean()}{@linkplain VariableExpressionBean#getIndex()}的值
*

View File

@ -22,4 +22,10 @@ public class SqlExpressionErrorException extends ExpressionErrorException
{
super(expression, cause);
}
@Override
public SQLException getCause()
{
return (SQLException) super.getCause();
}
}

View File

@ -5,6 +5,7 @@
package org.datagear.persistence.support;
import org.datagear.persistence.support.ExpressionResolver.Expression;
import org.springframework.expression.ExpressionException;
/**
* 变量表达式执行出错
@ -16,8 +17,14 @@ public class VariableExpressionErrorException extends ExpressionErrorException
{
private static final long serialVersionUID = 1L;
public VariableExpressionErrorException(Expression expression, Exception cause)
public VariableExpressionErrorException(Expression expression, ExpressionException cause)
{
super(expression, cause);
}
@Override
public ExpressionException getCause()
{
return (ExpressionException) super.getCause();
}
}

View File

@ -24,12 +24,15 @@ public class OperationMessage
/** 消息内容 */
private String message;
/** 消息详细内容 */
private String detail;
/** 是否是Throwable详细内容 */
private boolean throwableDetail;
/** 操作返回数据 */
private Object data;
/** 错误消息时的异常 */
private String throwableTrace;
public OperationMessage()
{
super();
@ -63,16 +66,6 @@ public class OperationMessage
return MessageType.FAIL.equals(this.type);
}
/**
* 操作是否严重失败产生{@linkplain #getException() 异常}
*
* @return
*/
public boolean isFatalFail()
{
return isFail() && hasThrowableTrace();
}
public MessageType getType()
{
return type;
@ -103,6 +96,31 @@ public class OperationMessage
this.message = message;
}
public boolean hasDetail()
{
return this.detail != null && !this.detail.isEmpty();
}
public String getDetail()
{
return detail;
}
public void setDetail(String detail)
{
this.detail = detail;
}
public boolean isThrowableDetail()
{
return throwableDetail;
}
public void setThrowableDetail(boolean throwableDetail)
{
this.throwableDetail = throwableDetail;
}
public boolean hasData()
{
return (this.data != null);
@ -118,24 +136,10 @@ public class OperationMessage
this.data = data;
}
public boolean hasThrowableTrace()
{
return (this.throwableTrace != null && !this.throwableTrace.isEmpty());
}
public String getThrowableTrace()
{
return throwableTrace;
}
public void setThrowableTrace(String throwableTrace)
{
this.throwableTrace = throwableTrace;
}
public void setThrowable(Throwable throwable)
{
this.throwableTrace = printThrowableTrace(throwable);
this.detail = printThrowableTrace(throwable);
this.throwableDetail = true;
}
@Override
@ -247,24 +251,24 @@ public class OperationMessage
}
/**
* 构建操作严重失败消息
* 构建Throwable操作失败消息
*
* @param code
* @param message
* @param throwable
* @return
*/
public static OperationMessage valueOfFatalFail(String code, String message, Throwable throwable)
public static OperationMessage valueOfThrowableFail(String code, String message, Throwable throwable)
{
OperationMessage om = new OperationMessage(MessageType.FAIL, code, message);
om.setThrowableTrace(printThrowableTrace(throwable));
om.setThrowable(throwable);
return om;
}
/**
* 构建操作严重失败消息
* 构建Throwable操作失败消息
*
* @param code
* @param message
@ -272,11 +276,11 @@ public class OperationMessage
* @param throwable
* @return
*/
public static OperationMessage valueOfFatalFail(String code, String message, Object data, Throwable throwable)
public static OperationMessage valueOfThrowableFail(String code, String message, Object data, Throwable throwable)
{
OperationMessage om = new OperationMessage(MessageType.FAIL, code, message);
om.setThrowable(throwable);
om.setData(data);
om.setThrowableTrace(printThrowableTrace(throwable));
return om;
}

View File

@ -17,6 +17,7 @@ import org.datagear.web.convert.ClassDataConverter;
import org.datagear.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.NoSuchMessageException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@ -400,6 +401,9 @@ public abstract class AbstractController
/**
* 获取I18N消息内容
* <p>
* 如果找不到对应消息码的消息则返回<code>"???[code]???"<code>例如{@code "???error???"}
* </p>
*
* @param request
* @param code
@ -408,7 +412,14 @@ public abstract class AbstractController
*/
protected String getMessage(HttpServletRequest request, String code, Object... args)
{
return this.messageSource.getMessage(code, args, WebUtils.getLocale(request));
try
{
return this.messageSource.getMessage(code, args, WebUtils.getLocale(request));
}
catch (NoSuchMessageException e)
{
return "???" + code + "???";
}
}
/**

View File

@ -155,6 +155,8 @@ public abstract class AbstractSchemaModelController extends AbstractController
private boolean readonly;
private boolean customCommit = false;
public AbstractSchemaModelExecutor(HttpServletRequest request, HttpServletResponse response,
org.springframework.ui.Model springModel, String schemaId, String tableName, boolean readonly)
{
@ -165,6 +167,21 @@ public abstract class AbstractSchemaModelController extends AbstractController
this.schemaId = schemaId;
this.tableName = tableName;
this.readonly = readonly;
this.customCommit = false;
}
public AbstractSchemaModelExecutor(HttpServletRequest request, HttpServletResponse response,
org.springframework.ui.Model springModel, String schemaId, String tableName, boolean readonly,
boolean customCommit)
{
super();
this.request = request;
this.response = response;
this.springModel = springModel;
this.schemaId = schemaId;
this.tableName = tableName;
this.readonly = readonly;
this.customCommit = customCommit;
}
protected void doExecute() throws Throwable
@ -185,7 +202,8 @@ public abstract class AbstractSchemaModelController extends AbstractController
doExecute(request, response, springModel, this._schema, model);
commitConnection();
if (!customCommit)
commitConnection();
}
catch (Throwable e)
{
@ -207,7 +225,8 @@ public abstract class AbstractSchemaModelController extends AbstractController
{
doExecute(request, response, springModel, this._schema, model);
commitConnection();
if (!customCommit)
commitConnection();
}
catch (Exception e)
{
@ -285,6 +304,13 @@ public abstract class AbstractSchemaModelController extends AbstractController
super(request, response, springModel, schemaId, tableName, readonly);
}
public ReturnExecutor(HttpServletRequest request, HttpServletResponse response,
org.springframework.ui.Model springModel, String schemaId, String tableName, boolean readonly,
boolean customCommit)
{
super(request, response, springModel, schemaId, tableName, readonly, customCommit);
}
public T execute() throws Throwable
{
doExecute();
@ -330,6 +356,13 @@ public abstract class AbstractSchemaModelController extends AbstractController
super(request, response, springModel, schemaId, tableName, readonly);
}
public VoidExecutor(HttpServletRequest request, HttpServletResponse response,
org.springframework.ui.Model springModel, String schemaId, String tableName, boolean readonly,
boolean customCommit)
{
super(request, response, springModel, schemaId, tableName, readonly, customCommit);
}
public void execute() throws Throwable
{
doExecute();

View File

@ -11,7 +11,9 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.SQLNonTransientException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@ -29,16 +31,20 @@ import org.datagear.model.Property;
import org.datagear.model.support.PropertyPath;
import org.datagear.model.support.PropertyPathInfo;
import org.datagear.persistence.ColumnPropertyPath;
import org.datagear.persistence.DialectSource;
import org.datagear.persistence.Dialect;
import org.datagear.persistence.PagingData;
import org.datagear.persistence.PagingQuery;
import org.datagear.persistence.PersistenceManager;
import org.datagear.persistence.QueryResultMetaInfo;
import org.datagear.persistence.columnconverter.LOBConversionContext;
import org.datagear.persistence.columnconverter.LOBConversionContext.LOBConversionSetting;
import org.datagear.persistence.support.ExpressionEvaluationContext;
import org.datagear.persistence.support.SelectOptions;
import org.datagear.persistence.support.SqlExpressionErrorException;
import org.datagear.persistence.support.VariableExpressionErrorException;
import org.datagear.web.OperationMessage;
import org.datagear.web.convert.ClassDataConverter;
import org.datagear.web.convert.ConverterException;
import org.datagear.web.convert.ModelDataConverter;
import org.datagear.web.format.DateFormatter;
import org.datagear.web.format.SqlDateFormatter;
@ -52,6 +58,8 @@ import org.datagear.web.vo.PropertyPathDisplayName;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.MessageSource;
import org.springframework.expression.ParseException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
@ -78,9 +86,6 @@ public class DataController extends AbstractSchemaModelController
@Autowired
private SelectOptions selectOptions;
@Autowired
private DialectSource dialectSource;
@Autowired
private ModelDataConverter modelDataConverter;
@ -148,16 +153,6 @@ public class DataController extends AbstractSchemaModelController
this.selectOptions = selectOptions;
}
public DialectSource getDialectSource()
{
return dialectSource;
}
public void setDialectSource(DialectSource dialectSource)
{
this.dialectSource = dialectSource;
}
public ModelDataConverter getModelDataConverter()
{
return modelDataConverter;
@ -307,7 +302,30 @@ public class DataController extends AbstractSchemaModelController
@ResponseBody
public ResponseEntity<OperationMessage> saveAdd(HttpServletRequest request, HttpServletResponse response,
org.springframework.ui.Model springModel, @PathVariable("schemaId") String schemaId,
@PathVariable("tableName") String tableName) throws Throwable
@PathVariable("tableName") String tableName,
@RequestParam(value = "batchCount", required = false) Integer batchCount,
@RequestParam(value = "batchHandleErrorMode", required = false) BatchHandleErrorMode batchHandleErrorMode)
throws Throwable
{
if (batchCount != null && batchCount >= 0)
return saveAddBatch(request, response, springModel, schemaId, tableName, batchCount, batchHandleErrorMode);
else
return saveAddSingle(request, response, springModel, schemaId, tableName);
}
/**
* 单个添加
*
* @param request
* @param response
* @param springModel
* @param schemaId
* @param tableName
* @return
* @throws Throwable
*/
protected ResponseEntity<OperationMessage> saveAddSingle(HttpServletRequest request, HttpServletResponse response,
org.springframework.ui.Model springModel, String schemaId, String tableName) throws Throwable
{
final Object dataParam = getParamMap(request, "data");
@ -332,6 +350,186 @@ public class DataController extends AbstractSchemaModelController
return responseEntity;
}
/**
* 批量添加
*
* @param request
* @param response
* @param springModel
* @param schemaId
* @param tableName
* @param batchCount
* @param batchHandleErrorMode
* @return
* @throws Throwable
*/
protected ResponseEntity<OperationMessage> saveAddBatch(HttpServletRequest request, HttpServletResponse response,
org.springframework.ui.Model springModel, String schemaId, String tableName, final Integer batchCount,
final BatchHandleErrorMode batchHandleErrorMode) throws Throwable
{
final Object dataParam = getParamMap(request, "data");
final int batchCountInt = (batchCount == null ? 0 : batchCount.intValue());
ResponseEntity<OperationMessage> responseEntity = new ReturnExecutor<ResponseEntity<OperationMessage>>(request,
response, springModel, schemaId, tableName, false, true)
{
@Override
protected ResponseEntity<OperationMessage> execute(HttpServletRequest request, HttpServletResponse response,
org.springframework.ui.Model springModel, Schema schema, Model model) throws Throwable
{
Connection cn = getConnection();
List<BatchUnitResult> batchResults = new ArrayList<BatchUnitResult>();
int successCount = 0;
int failCount = 0;
Dialect dialect = persistenceManager.getDialectSource().getDialect(cn);
String table = persistenceManager.getTableName(model);
ExpressionEvaluationContext context = new ExpressionEvaluationContext();
int index = 0;
for (; index < batchCountInt; index++)
{
try
{
Object data = modelDataConverter.convert(dataParam, model);
persistenceManager.insert(cn, dialect, table, model, data, context);
BatchUnitResult batchUnitResult = new BatchUnitResult(index);
batchResults.add(batchUnitResult);
successCount++;
}
catch (Exception e)
{
if (isBatchBreakException(e) && index == 0)
throw e;
else
{
BatchUnitResult batchUnitResult = new BatchUnitResult(index, e.getMessage());
batchResults.add(batchUnitResult);
failCount++;
if (BatchHandleErrorMode.ROLLBACK.equals(batchHandleErrorMode))
{
rollbackConnection();
break;
}
else if (BatchHandleErrorMode.ABORT.equals(batchHandleErrorMode))
{
commitConnection();
break;
}
}
}
finally
{
context.clearCachedValue();
context.incrementVariableIndex();
}
}
if (BatchHandleErrorMode.IGNORE.equals(batchHandleErrorMode))
commitConnection();
ResponseEntity<OperationMessage> responseEntity = null;
if (successCount == batchCountInt)// 全部成功
{
OperationMessage operationMessage = buildOperationMessageSuccess(request,
buildMessageCode("batchOperationSuccess"), batchCountInt, batchCountInt, 0);
operationMessage.setDetail(toBatchUnitResultHtml(request, batchResults));
responseEntity = buildOperationMessageResponseEntity(HttpStatus.OK, operationMessage);
}
else if (failCount == batchCountInt)// 全部失败
{
OperationMessage operationMessage = buildOperationMessageFail(request,
buildMessageCode("batchOperationFail"), batchCountInt, 0, batchCountInt);
operationMessage.setDetail(toBatchUnitResultHtml(request, batchResults));
responseEntity = buildOperationMessageResponseEntity(HttpStatus.BAD_REQUEST, operationMessage);
}
else
{
OperationMessage operationMessage = null;
if (BatchHandleErrorMode.IGNORE.equals(batchHandleErrorMode))
{
operationMessage = buildOperationMessageFail(request,
buildMessageCode("batchOperationFinish.ignore"), batchCountInt, successCount,
failCount);
}
else if (BatchHandleErrorMode.ROLLBACK.equals(batchHandleErrorMode))
{
operationMessage = buildOperationMessageFail(request,
buildMessageCode("batchOperationFinish.rollback"), batchCountInt, successCount);
}
else if (BatchHandleErrorMode.ABORT.equals(batchHandleErrorMode))
{
operationMessage = buildOperationMessageFail(request,
buildMessageCode("batchOperationFinish.abort"), batchCountInt, successCount,
batchCountInt - successCount);
}
else
throw new UnsupportedOperationException();
operationMessage.setDetail(toBatchUnitResultHtml(request, batchResults));
responseEntity = buildOperationMessageResponseEntity(HttpStatus.BAD_REQUEST, operationMessage);
}
return responseEntity;
}
}.execute();
return responseEntity;
}
protected String toBatchUnitResultHtml(HttpServletRequest request, List<BatchUnitResult> batchUnitResults)
{
StringBuilder sb = new StringBuilder();
String mcs = buildMessageCode("batchUnitResult.successHtml");
String mcf = buildMessageCode("batchUnitResult.failHtml");
for (BatchUnitResult batchUnitResult : batchUnitResults)
{
if (batchUnitResult.isSuccess())
sb.append(getMessage(request, mcs, batchUnitResult.getIndex()));
else
sb.append(getMessage(request, mcf, batchUnitResult.getIndex(), batchUnitResult.getErrorMessage()));
}
return sb.toString();
}
/**
* 判断异常是否是批处理断裂异常
* <p>
* 如果是那么批处理没有继续执行的必要
* </p>
*
* @param e
* @return
*/
protected boolean isBatchBreakException(Exception e)
{
if (e instanceof ConverterException)
return true;
// 变量表达式语法错误
if (e instanceof VariableExpressionErrorException && e.getCause() instanceof ParseException)
return true;
// SQL语法错误
if (e instanceof SqlExpressionErrorException && e.getCause() instanceof SQLNonTransientException)
return true;
return false;
}
@RequestMapping("/{schemaId}/{tableName}/edit")
public String edit(HttpServletRequest request, HttpServletResponse response,
org.springframework.ui.Model springModel, @PathVariable("schemaId") String schemaId,
@ -1222,6 +1420,12 @@ public class DataController extends AbstractSchemaModelController
return fileInfo;
}
@Override
protected String buildMessageCode(String code)
{
return super.buildMessageCode("data", code);
}
/**
* 获取{@linkplain QueryResultMetaInfo}{@linkplain PropertyPathDisplayName}列表
*
@ -1349,4 +1553,107 @@ public class DataController extends AbstractSchemaModelController
{
}
}
/**
* 批量操作单元执行结果
*
* @author datagear@163.com
*
*/
public static class BatchUnitResult implements Serializable
{
private static final long serialVersionUID = 1L;
private int index;
/** 当操作出错时的错误消息 */
private String errorMessage;
public BatchUnitResult(int index)
{
super();
this.index = index;
}
public BatchUnitResult(int index, String errorMessage)
{
super();
this.index = index;
this.errorMessage = errorMessage;
}
public int getIndex()
{
return index;
}
protected void setIndex(int index)
{
this.index = index;
}
/**
* 操作是否成功
*
* @return
*/
public boolean isSuccess()
{
return (this.errorMessage == null);
}
/**
* 操作是否失败
*
* @return
*/
public boolean isFail()
{
return (this.errorMessage != null);
}
public String getErrorMessage()
{
return errorMessage;
}
protected void setErrorMessage(String errorMessage)
{
this.errorMessage = errorMessage;
}
@Override
public String toString()
{
return getClass().getSimpleName() + " [index=" + index + ", errorMessage=" + errorMessage + "]";
}
}
/**
* 批量出错处理方式
*
* @author datagear@163.com
*
*/
public static enum BatchHandleErrorMode
{
/** 忽略 */
IGNORE,
/** 终止 */
ABORT,
/** 回滚 */
ROLLBACK
}
protected static class BatchInsertTask implements Runnable
{
@Override
public void run()
{
// TODO Auto-generated method stub
}
}
}

View File

@ -49,8 +49,8 @@ bracketRight=\uFF09
dataTables.noData=\u6CA1\u6709\u6570\u636E
dataTables.zeroRecords=\u6CA1\u6709\u7ED3\u679C
batchSet.batchSetSwitchTitle=\u6279\u91CF\u6DFB\u52A0\u8BBE\u7F6E
batchSet.batchCount=\u6279\u91CF\u6DFB\u52A0\u6570\u76EE
batchSet.batchSetSwitchTitle=\u6279\u91CF\u6267\u884C\u8BBE\u7F6E
batchSet.batchCount=\u6267\u884C\u6B21\u6570
batchSet.batchHandleErrorMode=\u51FA\u9519\u65F6
batchSet.batchHandleErrorMode.ignore=\u5FFD\u7565
batchSet.batchHandleErrorMode.abort=\u7EC8\u6B62
@ -98,6 +98,13 @@ data.clearWithShortcut=\u6E05\u9664\uFF08CTRL+SHIFT+BACKSPACE\uFF09
data.conditionPanelWithShortcut=\u9AD8\u7EA7\u67E5\u8BE2\uFF08CTRL+SHIFT+&#8595\uFF09
data.likeTitle=\u5305\u542B\uFF08CTRL+SHIFT+!\uFF09
data.notLikeTitle=\u4E0D\u5305\u542B\uFF08CTRL+SHIFT+!\uFF09
data.batchOperationSuccess=\u6279\u91CF\u6267\u884C\u6210\u529F\uFF0C\u603B\u6570\uFF1A[{0}]\uFF0C\u6210\u529F\u6570\uFF1A[{1}]\uFF0C\u5931\u8D25\u6570\uFF1A[{2}]
data.batchOperationFinish.ignore=\u6279\u91CF\u6267\u884C\u5B8C\u6210\uFF08\u5FFD\u7565\uFF09\uFF0C\u603B\u6570\uFF1A[{0}]\uFF0C\u6210\u529F\u6570\uFF1A[{1}]\uFF0C\u5931\u8D25\u6570\uFF1A[{2}]
data.batchOperationFinish.abort=\u6279\u91CF\u6267\u884C\u5B8C\u6210\uFF08\u7EC8\u6B62\uFF09\uFF0C\u603B\u6570\uFF1A[{0}]\uFF0C\u6210\u529F\u6570\uFF1A[{1}]\uFF0C\u672A\u6267\u884C\uFF1A[{2}]
data.batchOperationFinish.rollback=\u6279\u91CF\u6267\u884C\u5B8C\u6210\uFF08\u64A4\u9500\uFF09\uFF0C\u603B\u6570\uFF1A[{0}]\uFF0C\u6210\u529F\u6570\uFF1A[0]\uFF0C\u64A4\u9500\u6570\uFF1A[{1}]
data.batchOperationFail=\u6279\u91CF\u6267\u884C\u5931\u8D25\uFF0C\u603B\u6570\uFF1A[{0}]\uFF0C\u6210\u529F\u6570\uFF1A[{1}]\uFF0C\u5931\u8D25\u6570\uFF1A[{2}]
data.batchUnitResult.successHtml=<div class=\u0022 message-detail-item \u0022>[\u6210\u529F]&nbsp;\u7F16\u53F7\uFF1A<span class='index'>{0}</span></div>
data.batchUnitResult.failHtml=<div class=\u0022 message-detail-item \u0022>[\u5931\u8D25]&nbsp;\u7F16\u53F7\uFF1A<span class='index'>{0}</span>\uFF0C\u9519\u8BEF\u6D88\u606F\uFF1A<span class='fail-message'>{1}</span></div>
#driverEntity
driverEntity.addDriverEntity=\u6DFB\u52A0\u6570\u636E\u5E93\u9A71\u52A8\u7A0B\u5E8F

View File

@ -25,6 +25,8 @@ boolean clientOperation = ("true".equalsIgnoreCase(getStringValue(request, "clie
boolean readonly = ("true".equalsIgnoreCase(getStringValue(request, "readonly")));
//忽略表单渲染和处理的属性名允许为null
String ignorePropertyName = getStringValue(request, "ignorePropertyName", "");
//是否开启批量执行功能允许为null
boolean batchSet = ("true".equalsIgnoreCase(getStringValue(request, "batchSet")));
%>
<html>
<head>
@ -59,6 +61,7 @@ String ignorePropertyName = getStringValue(request, "ignorePropertyName", "");
pageObj.originalData = $.unref(<%writeJson(application, out, data);%>);
pageObj.data = $.unref($.ref(pageObj.originalData));
pageObj.clientOperation = <%=clientOperation%>;
pageObj.batchSet = <%=batchSet%>;
pageObj.superBuildPropertyActionOptions = pageObj.buildPropertyActionOptions;
pageObj.buildPropertyActionOptions = function(property, propertyConcreteModel, extraRequestParams, extraPageParams)
@ -103,23 +106,48 @@ String ignorePropertyName = getStringValue(request, "ignorePropertyName", "");
var thisForm = this;
var param = $.extend(formParam, {"data" : data, "originalData" : pageObj.originalData});
$.post(pageObj.url(pageObj.submitAction), param, function(operationMessage)
$.ajax(pageObj.url(pageObj.submitAction),
{
pageObj.data = $.unref(operationMessage.data);
//如果有初始数据,则更新为已保存至后台的数据
//注意不能直接赋值pageObj.data因为是同一个引用有可能会被修改而pageObj.originalData不应该被修改
if(pageObj.originalData)
pageObj.originalData = $.unref($.ref(operationMessage.data));
if(pageParam && pageParam.afterSave)
close = (pageParam.afterSave(operationMessage.data) != false);
var pageObjParent = pageObj.parent();
if(pageObjParent && pageObjParent.refresh)
pageObjParent.refresh();
if(close && !$(thisForm).modelform("isDialogPinned"))
pageObj.close();
"data" : param,
"success" : function(operationMessage)
{
var $form = $(thisForm);
var batchSubmit = $form.modelform("isBatchSubmit");
var isDialogPinned = $form.modelform("isDialogPinned");
if(batchSubmit)
;
else
{
pageObj.data = $.unref(operationMessage.data);
//如果有初始数据,则更新为已保存至后台的数据
//注意不能直接赋值pageObj.data因为是同一个引用有可能会被修改而pageObj.originalData不应该被修改
if(pageObj.originalData)
pageObj.originalData = $.unref($.ref(operationMessage.data));
if(pageParam && pageParam.afterSave)
close = (pageParam.afterSave(operationMessage.data) != false);
var pageObjParent = pageObj.parent();
if(pageObjParent && pageObjParent.refresh)
pageObjParent.refresh();
if(close && !isDialogPinned)
pageObj.close();
}
},
"complete" : function()
{
var $form = $(thisForm);
var batchSubmit = $form.modelform("isBatchSubmit");
if(batchSubmit)
{
var pageObjParent = pageObj.parent();
if(pageObjParent && pageObjParent.refresh)
pageObjParent.refresh();
}
}
});
}
@ -160,7 +188,7 @@ String ignorePropertyName = getStringValue(request, "ignorePropertyName", "");
pageObj.downloadSinglePropertyValueFile(property, propertyConcreteModel);
},
validationRequiredAsAdd : ("saveAdd" == pageObj.submitAction),
batchSet : ("saveAdd" == pageObj.submitAction),
batchSet : pageObj.batchSet,
labels : pageObj.formLabels,
dateFormat : "<c:out value='${dateFormat}' />",
timestampFormat : "<c:out value='${sqlTimestampFormat}' />",

View File

@ -79,7 +79,7 @@ List<PropertyPathDisplayName> conditionSource = (List<PropertyPathDisplayName>)r
<%if(!readonly){%>
pageObj.element("input[name=addButton]").click(function()
{
pageObj.open(pageObj.url("add"), { pinTitleButton : true });
pageObj.open(pageObj.url("", "add", "batchSet=true"), { pinTitleButton : true });
});
pageObj.element("input[name=editButton]").click(function()

View File

@ -25,6 +25,8 @@ String submitAction = getStringValue(request, "submitAction");
boolean clientOperation = ("true".equalsIgnoreCase(getStringValue(request, "clientOperation")));
//是否只读操作允许为null
boolean readonly = ("true".equalsIgnoreCase(getStringValue(request, "readonly")));
//是否开启批量执行功能允许为null
boolean batchSet = ("true".equalsIgnoreCase(getStringValue(request, "batchSet")));
PropertyPath propertyPathObj = ModelUtils.toPropertyPath(propertyPath);
PropertyPathInfo propertyPathInfoObj = ModelUtils.toPropertyPathInfoConcrete(model, propertyPathObj, data);
@ -63,6 +65,7 @@ boolean isPrivatePropertyModel = ModelUtils.isPrivatePropertyModelTail(propertyP
pageObj.data = ($.unref(<%writeJson(application, out, data);%>) || {});
pageObj.propertyPath = "<%=WebUtils.escapeJavaScriptStringValue(propertyPath)%>";
pageObj.clientOperation = <%=clientOperation%>;
pageObj.batchSet = <%=batchSet%>;
pageObj.superBuildPropertyActionOptions = pageObj.buildPropertyActionOptions;
pageObj.buildPropertyActionOptions = function(property, propertyConcreteModel, extraRequestParams, extraPageParams)
@ -168,7 +171,7 @@ boolean isPrivatePropertyModel = ModelUtils.isPrivatePropertyModelTail(propertyP
pageObj.downloadSinglePropertyValueFile(property, propertyConcreteModel);
},
validationRequiredAsAdd : ("saveAdd" == pageObj.submitAction),
batchSet : ("saveAddMultiplePropValueElement" == pageObj.submitAction),
batchSet : pageObj.batchSet,
labels : pageObj.formLabels,
dateFormat : "<c:out value='${dateFormat}' />",
timestampFormat : "<c:out value='${sqlTimestampFormat}' />",

View File

@ -186,10 +186,13 @@ boolean isPrivatePropertyModel = ModelUtils.isPrivatePropertyModelTail(propertyP
<%if(!readonly){%>
pageObj.addMultiplePropValueElement = function()
{
var url = undefined;
var options = undefined;
if(pageObj.clientOperation)
{
url = pageObj.url("addMultiplePropValueElement");
var index = pageObj.table.DataTable().rows().data().length;
options = pageObj.buildActionOptions(property, propertyModel,
@ -208,6 +211,8 @@ boolean isPrivatePropertyModel = ModelUtils.isPrivatePropertyModelTail(propertyP
}
else
{
url = pageObj.url("", "addMultiplePropValueElement", "batchSet=true");
options = pageObj.buildActionOptions(property, propertyModel,
{
"propertyPath" : $.propertyPath.concatElementIndex(pageObj.propertyPath, 0)
@ -217,7 +222,7 @@ boolean isPrivatePropertyModel = ModelUtils.isPrivatePropertyModelTail(propertyP
options.pinTitleButton = true;
pageObj.open(pageObj.url("addMultiplePropValueElement"), options);
pageObj.open(url, options);
};
<%if(isPrivatePropertyModel){%>

View File

@ -21,16 +21,20 @@
*
* @param tableName 可选,操作对应的表名,默认是当前表名
* @param action 操作名称
* @param param URL后面加的参数不需要以'?'开头
*/
url : function(tableName, action)
url : function(tableName, action, param)
{
if(!action)
if(action == undefined)
{
action = tableName;
tableName = this.tableName;
}
return contextPath + $.toPath("data", this.schemaId, tableName, action);
if(!tableName)
tableName = this.tableName;
return contextPath + $.toPath("data", this.schemaId, tableName, action) + (param ? "?" + param : "");
},
/**

View File

@ -14,9 +14,9 @@ if(__operationMessage != null){
<div class="message">
<%=__operationMessage.getMessage()%>
</div>
<%if(__operationMessage.hasThrowableTrace()){%>
<div class="throwable">
<pre><%=__operationMessage.getThrowableTrace()%></pre>
<%if(__operationMessage.hasDetail()){%>
<div class="message-detail">
<pre><%=__operationMessage.getDetail()%></pre>
</div>
<%}%>
</div>

View File

@ -253,7 +253,7 @@
},
/**
* 获取除数据外的参数对象比如options.batchCountParamNameoptions.batchHandleErrorModeParamName参数
* 获取除数据外的参数对象比如options.batchCountParamNameoptions.batchHandleErrorModeParamName参数
*/
param : function()
{
@ -273,6 +273,18 @@
return param;
},
/**
* 是否是批量提交
*/
isBatchSubmit : function()
{
var options = this.options;
var batchCount = parseInt($("input[name='"+options.batchCountParamName+"']", this.element).val());
return (batchCount >= 0);
},
/**
* 表单所处的对话框是否设置为钉住
*/

View File

@ -368,7 +368,7 @@
{
content = "<span class='tooltip-icon ui-icon ui-icon-circle-check'></span>"
+"<div class='content-value'>" + content +"</div>";
return $._tip("ui-state-default", content, (delayMs || 2000));
return $._tip("ui-state-default", content, (delayMs || 2500));
},
/**
@ -378,7 +378,7 @@
{
content = "<span class='tooltip-icon ui-icon ui-icon-alert'></span>"
+"<div class='content-value'>" + content +"</div>";
return $._tip("ui-state-error", content, (delayMs || 3500));
return $._tip("ui-state-error", content, (delayMs || 4100));
},
/**
@ -388,7 +388,7 @@
{
content = "<span class='tooltip-icon ui-icon ui-icon-info'></span>"
+"<div class='content-value'>" + content +"</div>";
return $._tip("ui-state-highlight", content, (delayMs || 3500));
return $._tip("ui-state-highlight", content, (delayMs || 4100));
},
/**
@ -1342,86 +1342,127 @@
return null;
}
};
$.handleAjaxError = function(jqXHR, errorThrown)
$.handleAjaxOperationMessage = function(jqXHR, errorThrown)
{
if(!window._showResponseErrorDetail)
if(!window._showAjaxOperationMessageDetail)
{
window._showResponseErrorDetail = function()
window._showAjaxOperationMessageDetail = function()
{
$.closeTip();
var $errorDetail = $("#responseError");
var $operationMessageParent = $("#__operationMessageParent");
var $dialog = $("<div id='dialog-"+new Date().getTime()+"' class='error-dialog'></div>").appendTo(document.body);
var $errorContent = $("<div class='ui-state-error error-content' />").appendTo($dialog);
$errorContent.html($(".throwable", $errorDetail).html());
var isSuccessMessage = ("true" == $operationMessageParent.attr("success"));
var $dialog = $("<div id='dialog-"+new Date().getTime()+"' class='operation-message-dialog'></div>").appendTo(document.body);
var $messageDetail = $("<div class='message-detail' />");
if(!isSuccessMessage)
$messageDetail.addClass("ui-state-error");
$messageDetail.appendTo($dialog);
$messageDetail.html($(".message-detail", $operationMessageParent).html());
$._dialog($dialog,
{
title : $(".message", $errorDetail).text(),
title : $(".message", $operationMessageParent).text(),
modal : true,
height: "60%",
position: {my: "center top", at: "center top+3"},
classes:
{
"ui-dialog": "ui-corner-all ui-widget-shadow ui-state-error"
"ui-dialog": "ui-corner-all ui-widget-shadow" + (isSuccessMessage ? "" : "ui-state-error")
}
});
var $dialogWidget = $dialog.dialog("widget");
$(".ui-dialog-titlebar", $dialogWidget).addClass("ui-state-error");
$(".ui-dialog-title", $dialogWidget).prepend("<span class='ui-icon ui-icon-alert' style='margin-right:0.3em;'></span>");
$(".ui-dialog-titlebar-close", $dialogWidget).addClass("ui-state-error");
$(".ui-dialog-title", $dialogWidget).prepend("<span class='ui-icon "+(isSuccessMessage ? "ui-icon-circle-check" : "ui-icon-alert")+"'></span>");
if(!isSuccessMessage)
{
$dialogWidget.addClass("ui-state-error");
$(".ui-dialog-titlebar", $dialogWidget).addClass("ui-state-error");
$(".ui-dialog-titlebar-close", $dialogWidget).addClass("ui-state-error");
}
};
}
if(jqXHR.status != 200 && jqXHR.responseText)
var isSuccessMessage = (jqXHR.status == 200);
if(jqXHR.responseText)
{
var $errorDiv = $("#responseError");
if($errorDiv.length == 0)
$errorDiv = $("<div id='responseError' style='display:none;' />").appendTo(document.body);
var $operationMessageParent = $("#__operationMessageParent");
if($operationMessageParent.length == 0)
$operationMessageParent = $("<div id='__operationMessageParent' style='display:none;' />").appendTo(document.body);
var operationMessage = $.getResponseJson(jqXHR);
var hasMessage = false;
//操作消息的JSON响应
if(operationMessage)
if(operationMessage && operationMessage.type && operationMessage.code && operationMessage.message)
{
$errorDiv.empty();
$operationMessageParent.empty();
var $omdiv = $("<div class='operation-message "+operationMessage.type+"' />").appendTo($errorDiv);
var $omdiv = $("<div class='operation-message "+operationMessage.type+"' />").appendTo($operationMessageParent);
var $mdiv = $("<div class='message' />").appendTo($omdiv).html(operationMessage.message);
if(operationMessage.throwableTrace)
if(operationMessage.detail)
{
var $ddiv = $("<div class='throwable' />").appendTo($omdiv);
var $pre = $("<pre />").appendTo($ddiv).html(operationMessage.throwableTrace);
var $ddiv = $("<div class='message-detail' />").appendTo($omdiv);
if(operationMessage.throwableDetail)
$("<pre />").appendTo($ddiv).html(operationMessage.detail);
else
$("<div />").appendTo($ddiv).html(operationMessage.detail);
}
hasMessage = true;
}
else
{
if(isSuccessMessage)
hasMessage = false;
else
{
//操作消息的HTML响应
$operationMessageParent.html(jqXHR.responseText);
hasMessage = true;
}
}
//操作消息的HTML响应
else
$errorDiv.html(jqXHR.responseText);
var message = $(".message", $errorDiv).html();
if($(".throwable", $errorDiv).length > 0)
message += "&nbsp;<span class='ui-icon ui-icon-comment error-detail-icon' onclick='_showResponseErrorDetail();'></span>";
$.tipError(message);
if(hasMessage)
{
$operationMessageParent.attr("success", isSuccessMessage);
var message = $(".message", $operationMessageParent).html();
if($(".message-detail", $operationMessageParent).length > 0)
message += "<span class='ui-icon ui-icon-comment message-detail-icon' onclick='_showAjaxOperationMessageDetail();'></span>";
if(isSuccessMessage)
$.tipSuccess(message);
else
$.tipError(message);
}
}
else
{
var msg = (jqXHR.statusText || "Error");
if(errorThrown && errorThrown.message)
if(isSuccessMessage)
;
else
{
if(msg)
msg += " : ";
var msg = (jqXHR.statusText || "Error");
msg += errorThrown.message;
if(errorThrown && errorThrown.message)
{
if(msg)
msg += " : ";
msg += errorThrown.message;
}
$.tipError(msg);
}
$.tipError(msg);
}
};
@ -1454,18 +1495,12 @@
$(document).ajaxError(function(event, jqXHR, ajaxSettings, thrownError)
{
$.handleAjaxError(jqXHR, thrownError);
$.handleAjaxOperationMessage(jqXHR, thrownError);
});
$(document).ajaxSuccess(function(event, jqXHR, ajaxSettings, thrownError)
{
var responseJson = $.getResponseJson(jqXHR);
//确定是操作消息JSON
if(responseJson && responseJson.type && responseJson.code && responseJson.message)
{
$.tipSuccess(responseJson.message);
}
$.handleAjaxOperationMessage(jqXHR, thrownError);
});
})
(jQuery);

View File

@ -175,6 +175,9 @@ form .form-foot .ui-button{
padding: 1px;
height: 20px;
}
.ui-dialog .ui-dialog-title .ui-icon{
margin-right: 0.3em;
}
/*可保持ui-state-active状态组件样式默认图标是灰色这了改成白色*/
.stated-active.ui-state-active .ui-icon, .stated-active.ui-state-active:focus .ui-icon, .stated-active.ui-state-active:hover .ui-icon{
background-image: url(default/jquery-ui-1.12.1/images/ui-icons_ffffff_256x240.png);
@ -343,8 +346,9 @@ table.dataTable tbody tr.selected .checkbox .ui-icon{
.ui-tooltip .ui-tooltip-content .tooltip-icon{
margin-right: 0.41em;
}
.ui-tooltip .ui-tooltip-content .error-detail-icon{
.ui-tooltip .ui-tooltip-content .message-detail-icon{
cursor: pointer;
margin-left: 1em;
}
.ui-tooltip .ui-tooltip-content .content{
padding-left:0.5em;
@ -361,10 +365,10 @@ table.dataTable tbody tr.selected .checkbox .ui-icon{
overflow: hidden;
}
/*错误消息对话框*/
.error-dialog{
/*操作消息对话框*/
.operation-message-dialog{
}
.error-dialog .error-content{
.operation-message-dialog .message-detail{
display: inline-block;
width: 100%;
height: 98%;
@ -373,9 +377,13 @@ table.dataTable tbody tr.selected .checkbox .ui-icon{
border: 0;
overflow: auto;
}
.error-dialog .error-content pre{
.operation-message-dialog .message-detail pre{
margin: 0;
}
.operation-message-dialog .message-detail .message-detail-item{
padding: 0.2em 0;
white-space: nowrap;
}
/*确认对话框*/
.dialog-confirm{}