mirror of https://github.com/jeecgboot/jeecg-boot
Merge branch 'master' into master
commit
8979dd7ae9
|
@ -314,5 +314,10 @@
|
|||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<artifactId>jeecg-boot-starter-chatgpt</artifactId>
|
||||
</dependency>
|
||||
<!-- minidao -->
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework</groupId>
|
||||
<artifactId>minidao-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -1,6 +1,6 @@
|
|||
package org.jeecg.common.util.encryption;
|
||||
|
||||
import org.apache.shiro.codec.Base64;
|
||||
import org.apache.shiro.lang.codec.Base64;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
|
|
|
@ -1,255 +1,255 @@
|
|||
package org.jeecg.common.util.sqlparse;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.sf.jsqlparser.JSQLParserException;
|
||||
import net.sf.jsqlparser.expression.*;
|
||||
import net.sf.jsqlparser.parser.CCJSqlParserManager;
|
||||
import net.sf.jsqlparser.schema.Column;
|
||||
import net.sf.jsqlparser.schema.Table;
|
||||
import net.sf.jsqlparser.statement.Statement;
|
||||
import net.sf.jsqlparser.statement.select.*;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.common.util.sqlparse.vo.SelectSqlInfo;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 解析所有表名和字段的类
|
||||
*/
|
||||
@Slf4j
|
||||
public class JSqlParserAllTableManager {
|
||||
|
||||
private final String sql;
|
||||
private final Map<String, SelectSqlInfo> allTableMap = new HashMap<>();
|
||||
/**
|
||||
* 别名对应实际表名
|
||||
*/
|
||||
private final Map<String, String> tableAliasMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 解析后的sql
|
||||
*/
|
||||
private String parsedSql = null;
|
||||
|
||||
JSqlParserAllTableManager(String selectSql) {
|
||||
this.sql = selectSql;
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始解析
|
||||
*
|
||||
* @return
|
||||
* @throws JSQLParserException
|
||||
*/
|
||||
public Map<String, SelectSqlInfo> parse() throws JSQLParserException {
|
||||
// 1. 创建解析器
|
||||
CCJSqlParserManager mgr = new CCJSqlParserManager();
|
||||
// 2. 使用解析器解析sql生成具有层次结构的java类
|
||||
Statement stmt = mgr.parse(new StringReader(this.sql));
|
||||
if (stmt instanceof Select) {
|
||||
Select selectStatement = (Select) stmt;
|
||||
SelectBody selectBody = selectStatement.getSelectBody();
|
||||
this.parsedSql = selectBody.toString();
|
||||
// 3. 解析select查询sql的信息
|
||||
if (selectBody instanceof PlainSelect) {
|
||||
PlainSelect plainSelect = (PlainSelect) selectBody;
|
||||
// 4. 合并 fromItems
|
||||
List<FromItem> fromItems = new ArrayList<>();
|
||||
fromItems.add(plainSelect.getFromItem());
|
||||
// 4.1 处理join的表
|
||||
List<Join> joins = plainSelect.getJoins();
|
||||
if (joins != null) {
|
||||
joins.forEach(join -> fromItems.add(join.getRightItem()));
|
||||
}
|
||||
// 5. 处理 fromItems
|
||||
for (FromItem fromItem : fromItems) {
|
||||
// 5.1 通过表名的方式from
|
||||
if (fromItem instanceof Table) {
|
||||
this.addSqlInfoByTable((Table) fromItem);
|
||||
}
|
||||
// 5.2 通过子查询的方式from
|
||||
else if (fromItem instanceof SubSelect) {
|
||||
this.handleSubSelect((SubSelect) fromItem);
|
||||
}
|
||||
}
|
||||
// 6. 解析 selectFields
|
||||
List<SelectItem> selectItems = plainSelect.getSelectItems();
|
||||
for (SelectItem selectItem : selectItems) {
|
||||
// 6.1 查询的是全部字段
|
||||
if (selectItem instanceof AllColumns) {
|
||||
// 当 selectItem 为 AllColumns 时,fromItem 必定为 Table
|
||||
String tableName = plainSelect.getFromItem(Table.class).getName();
|
||||
// 此处必定不为空,因为在解析 fromItem 时,已经将表名添加到 allTableMap 中
|
||||
SelectSqlInfo sqlInfo = this.allTableMap.get(tableName);
|
||||
assert sqlInfo != null;
|
||||
// 设置为查询全部字段
|
||||
sqlInfo.setSelectAll(true);
|
||||
sqlInfo.setSelectFields(null);
|
||||
sqlInfo.setRealSelectFields(null);
|
||||
}
|
||||
// 6.2 查询的是带表别名( u.* )的全部字段
|
||||
else if (selectItem instanceof AllTableColumns) {
|
||||
AllTableColumns allTableColumns = (AllTableColumns) selectItem;
|
||||
String aliasName = allTableColumns.getTable().getName();
|
||||
// 通过别名获取表名
|
||||
String tableName = this.tableAliasMap.get(aliasName);
|
||||
if (tableName == null) {
|
||||
tableName = aliasName;
|
||||
}
|
||||
SelectSqlInfo sqlInfo = this.allTableMap.get(tableName);
|
||||
// 如果此处为空,则说明该字段是通过子查询获取的,所以可以不处理,只有实际表才需要处理
|
||||
if (sqlInfo != null) {
|
||||
// 设置为查询全部字段
|
||||
sqlInfo.setSelectAll(true);
|
||||
sqlInfo.setSelectFields(null);
|
||||
sqlInfo.setRealSelectFields(null);
|
||||
}
|
||||
}
|
||||
// 6.3 各种字段表达式处理
|
||||
else if (selectItem instanceof SelectExpressionItem) {
|
||||
SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
|
||||
Expression expression = selectExpressionItem.getExpression();
|
||||
Alias alias = selectExpressionItem.getAlias();
|
||||
this.handleExpression(expression, alias, plainSelect.getFromItem());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.warn("暂时尚未处理该类型的 SelectBody: {}", selectBody.getClass().getName());
|
||||
throw new JeecgBootException("暂时尚未处理该类型的 SelectBody");
|
||||
}
|
||||
} else {
|
||||
// 非 select 查询sql,不做处理
|
||||
throw new JeecgBootException("非 select 查询sql,不做处理");
|
||||
}
|
||||
return this.allTableMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理子查询
|
||||
*
|
||||
* @param subSelect
|
||||
*/
|
||||
private void handleSubSelect(SubSelect subSelect) {
|
||||
try {
|
||||
String subSelectSql = subSelect.getSelectBody().toString();
|
||||
// 递归调用解析
|
||||
Map<String, SelectSqlInfo> map = JSqlParserUtils.parseAllSelectTable(subSelectSql);
|
||||
if (map != null) {
|
||||
this.assignMap(map);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("解析子查询出错", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理查询字段表达式
|
||||
*
|
||||
* @param expression
|
||||
*/
|
||||
private void handleExpression(Expression expression, Alias alias, FromItem fromItem) {
|
||||
// 处理函数式字段 CONCAT(name,'(',age,')')
|
||||
if (expression instanceof Function) {
|
||||
Function functionExp = (Function) expression;
|
||||
List<Expression> expressions = functionExp.getParameters().getExpressions();
|
||||
for (Expression expItem : expressions) {
|
||||
this.handleExpression(expItem, null, fromItem);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 处理字段上的子查询
|
||||
if (expression instanceof SubSelect) {
|
||||
this.handleSubSelect((SubSelect) expression);
|
||||
return;
|
||||
}
|
||||
// 不处理字面量
|
||||
if (expression instanceof StringValue ||
|
||||
expression instanceof NullValue ||
|
||||
expression instanceof LongValue ||
|
||||
expression instanceof DoubleValue ||
|
||||
expression instanceof HexValue ||
|
||||
expression instanceof DateValue ||
|
||||
expression instanceof TimestampValue ||
|
||||
expression instanceof TimeValue
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理字段
|
||||
if (expression instanceof Column) {
|
||||
Column column = (Column) expression;
|
||||
// 查询字段名
|
||||
String fieldName = column.getColumnName();
|
||||
String aliasName = fieldName;
|
||||
if (alias != null) {
|
||||
aliasName = alias.getName();
|
||||
}
|
||||
String tableName;
|
||||
if (column.getTable() != null) {
|
||||
// 通过列的表名获取 sqlInfo
|
||||
// 例如 user.name,这里的 tableName 就是 user
|
||||
tableName = column.getTable().getName();
|
||||
// 有可能是别名,需要转换为真实表名
|
||||
if (this.tableAliasMap.get(tableName) != null) {
|
||||
tableName = this.tableAliasMap.get(tableName);
|
||||
}
|
||||
} else {
|
||||
// 当column的table为空时,说明是 fromItem 中的字段
|
||||
tableName = ((Table) fromItem).getName();
|
||||
}
|
||||
SelectSqlInfo $sqlInfo = this.allTableMap.get(tableName);
|
||||
if ($sqlInfo != null) {
|
||||
$sqlInfo.addSelectField(aliasName, fieldName);
|
||||
} else {
|
||||
log.warn("发生意外情况,未找到表名为 {} 的 SelectSqlInfo", tableName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据表名添加sqlInfo
|
||||
*
|
||||
* @param table
|
||||
*/
|
||||
private void addSqlInfoByTable(Table table) {
|
||||
String tableName = table.getName();
|
||||
// 解析 aliasName
|
||||
if (table.getAlias() != null) {
|
||||
this.tableAliasMap.put(table.getAlias().getName(), tableName);
|
||||
}
|
||||
SelectSqlInfo sqlInfo = new SelectSqlInfo(this.parsedSql);
|
||||
sqlInfo.setFromTableName(table.getName());
|
||||
this.allTableMap.put(sqlInfo.getFromTableName(), sqlInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并map
|
||||
*
|
||||
* @param source
|
||||
*/
|
||||
private void assignMap(Map<String, SelectSqlInfo> source) {
|
||||
for (Map.Entry<String, SelectSqlInfo> entry : source.entrySet()) {
|
||||
SelectSqlInfo sqlInfo = this.allTableMap.get(entry.getKey());
|
||||
if (sqlInfo == null) {
|
||||
this.allTableMap.put(entry.getKey(), entry.getValue());
|
||||
} else {
|
||||
// 合并
|
||||
if (sqlInfo.getSelectFields() == null) {
|
||||
sqlInfo.setSelectFields(entry.getValue().getSelectFields());
|
||||
} else {
|
||||
sqlInfo.getSelectFields().addAll(entry.getValue().getSelectFields());
|
||||
}
|
||||
if (sqlInfo.getRealSelectFields() == null) {
|
||||
sqlInfo.setRealSelectFields(entry.getValue().getRealSelectFields());
|
||||
} else {
|
||||
sqlInfo.getRealSelectFields().addAll(entry.getValue().getRealSelectFields());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//package org.jeecg.common.util.sqlparse;
|
||||
//
|
||||
//import lombok.extern.slf4j.Slf4j;
|
||||
//import net.sf.jsqlparser.JSQLParserException;
|
||||
//import net.sf.jsqlparser.expression.*;
|
||||
//import net.sf.jsqlparser.parser.CCJSqlParserManager;
|
||||
//import net.sf.jsqlparser.schema.Column;
|
||||
//import net.sf.jsqlparser.schema.Table;
|
||||
//import net.sf.jsqlparser.statement.Statement;
|
||||
//import net.sf.jsqlparser.statement.select.*;
|
||||
//import org.jeecg.common.exception.JeecgBootException;
|
||||
//import org.jeecg.common.util.sqlparse.vo.SelectSqlInfo;
|
||||
//
|
||||
//import java.io.StringReader;
|
||||
//import java.util.ArrayList;
|
||||
//import java.util.HashMap;
|
||||
//import java.util.List;
|
||||
//import java.util.Map;
|
||||
//
|
||||
///**
|
||||
// * 解析所有表名和字段的类
|
||||
// */
|
||||
//@Slf4j
|
||||
//public class JSqlParserAllTableManager {
|
||||
//
|
||||
// private final String sql;
|
||||
// private final Map<String, SelectSqlInfo> allTableMap = new HashMap<>();
|
||||
// /**
|
||||
// * 别名对应实际表名
|
||||
// */
|
||||
// private final Map<String, String> tableAliasMap = new HashMap<>();
|
||||
//
|
||||
// /**
|
||||
// * 解析后的sql
|
||||
// */
|
||||
// private String parsedSql = null;
|
||||
//
|
||||
// JSqlParserAllTableManager(String selectSql) {
|
||||
// this.sql = selectSql;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 开始解析
|
||||
// *
|
||||
// * @return
|
||||
// * @throws JSQLParserException
|
||||
// */
|
||||
// public Map<String, SelectSqlInfo> parse() throws JSQLParserException {
|
||||
// // 1. 创建解析器
|
||||
// CCJSqlParserManager mgr = new CCJSqlParserManager();
|
||||
// // 2. 使用解析器解析sql生成具有层次结构的java类
|
||||
// Statement stmt = mgr.parse(new StringReader(this.sql));
|
||||
// if (stmt instanceof Select) {
|
||||
// Select selectStatement = (Select) stmt;
|
||||
// SelectBody selectBody = selectStatement.getSelectBody();
|
||||
// this.parsedSql = selectBody.toString();
|
||||
// // 3. 解析select查询sql的信息
|
||||
// if (selectBody instanceof PlainSelect) {
|
||||
// PlainSelect plainSelect = (PlainSelect) selectBody;
|
||||
// // 4. 合并 fromItems
|
||||
// List<FromItem> fromItems = new ArrayList<>();
|
||||
// fromItems.add(plainSelect.getFromItem());
|
||||
// // 4.1 处理join的表
|
||||
// List<Join> joins = plainSelect.getJoins();
|
||||
// if (joins != null) {
|
||||
// joins.forEach(join -> fromItems.add(join.getRightItem()));
|
||||
// }
|
||||
// // 5. 处理 fromItems
|
||||
// for (FromItem fromItem : fromItems) {
|
||||
// // 5.1 通过表名的方式from
|
||||
// if (fromItem instanceof Table) {
|
||||
// this.addSqlInfoByTable((Table) fromItem);
|
||||
// }
|
||||
// // 5.2 通过子查询的方式from
|
||||
// else if (fromItem instanceof SubSelect) {
|
||||
// this.handleSubSelect((SubSelect) fromItem);
|
||||
// }
|
||||
// }
|
||||
// // 6. 解析 selectFields
|
||||
// List<SelectItem> selectItems = plainSelect.getSelectItems();
|
||||
// for (SelectItem selectItem : selectItems) {
|
||||
// // 6.1 查询的是全部字段
|
||||
// if (selectItem instanceof AllColumns) {
|
||||
// // 当 selectItem 为 AllColumns 时,fromItem 必定为 Table
|
||||
// String tableName = plainSelect.getFromItem(Table.class).getName();
|
||||
// // 此处必定不为空,因为在解析 fromItem 时,已经将表名添加到 allTableMap 中
|
||||
// SelectSqlInfo sqlInfo = this.allTableMap.get(tableName);
|
||||
// assert sqlInfo != null;
|
||||
// // 设置为查询全部字段
|
||||
// sqlInfo.setSelectAll(true);
|
||||
// sqlInfo.setSelectFields(null);
|
||||
// sqlInfo.setRealSelectFields(null);
|
||||
// }
|
||||
// // 6.2 查询的是带表别名( u.* )的全部字段
|
||||
// else if (selectItem instanceof AllTableColumns) {
|
||||
// AllTableColumns allTableColumns = (AllTableColumns) selectItem;
|
||||
// String aliasName = allTableColumns.getTable().getName();
|
||||
// // 通过别名获取表名
|
||||
// String tableName = this.tableAliasMap.get(aliasName);
|
||||
// if (tableName == null) {
|
||||
// tableName = aliasName;
|
||||
// }
|
||||
// SelectSqlInfo sqlInfo = this.allTableMap.get(tableName);
|
||||
// // 如果此处为空,则说明该字段是通过子查询获取的,所以可以不处理,只有实际表才需要处理
|
||||
// if (sqlInfo != null) {
|
||||
// // 设置为查询全部字段
|
||||
// sqlInfo.setSelectAll(true);
|
||||
// sqlInfo.setSelectFields(null);
|
||||
// sqlInfo.setRealSelectFields(null);
|
||||
// }
|
||||
// }
|
||||
// // 6.3 各种字段表达式处理
|
||||
// else if (selectItem instanceof SelectExpressionItem) {
|
||||
// SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
|
||||
// Expression expression = selectExpressionItem.getExpression();
|
||||
// Alias alias = selectExpressionItem.getAlias();
|
||||
// this.handleExpression(expression, alias, plainSelect.getFromItem());
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// log.warn("暂时尚未处理该类型的 SelectBody: {}", selectBody.getClass().getName());
|
||||
// throw new JeecgBootException("暂时尚未处理该类型的 SelectBody");
|
||||
// }
|
||||
// } else {
|
||||
// // 非 select 查询sql,不做处理
|
||||
// throw new JeecgBootException("非 select 查询sql,不做处理");
|
||||
// }
|
||||
// return this.allTableMap;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 处理子查询
|
||||
// *
|
||||
// * @param subSelect
|
||||
// */
|
||||
// private void handleSubSelect(SubSelect subSelect) {
|
||||
// try {
|
||||
// String subSelectSql = subSelect.getSelectBody().toString();
|
||||
// // 递归调用解析
|
||||
// Map<String, SelectSqlInfo> map = JSqlParserUtils.parseAllSelectTable(subSelectSql);
|
||||
// if (map != null) {
|
||||
// this.assignMap(map);
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// log.error("解析子查询出错", e);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 处理查询字段表达式
|
||||
// *
|
||||
// * @param expression
|
||||
// */
|
||||
// private void handleExpression(Expression expression, Alias alias, FromItem fromItem) {
|
||||
// // 处理函数式字段 CONCAT(name,'(',age,')')
|
||||
// if (expression instanceof Function) {
|
||||
// Function functionExp = (Function) expression;
|
||||
// List<Expression> expressions = functionExp.getParameters().getExpressions();
|
||||
// for (Expression expItem : expressions) {
|
||||
// this.handleExpression(expItem, null, fromItem);
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
// // 处理字段上的子查询
|
||||
// if (expression instanceof SubSelect) {
|
||||
// this.handleSubSelect((SubSelect) expression);
|
||||
// return;
|
||||
// }
|
||||
// // 不处理字面量
|
||||
// if (expression instanceof StringValue ||
|
||||
// expression instanceof NullValue ||
|
||||
// expression instanceof LongValue ||
|
||||
// expression instanceof DoubleValue ||
|
||||
// expression instanceof HexValue ||
|
||||
// expression instanceof DateValue ||
|
||||
// expression instanceof TimestampValue ||
|
||||
// expression instanceof TimeValue
|
||||
// ) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // 处理字段
|
||||
// if (expression instanceof Column) {
|
||||
// Column column = (Column) expression;
|
||||
// // 查询字段名
|
||||
// String fieldName = column.getColumnName();
|
||||
// String aliasName = fieldName;
|
||||
// if (alias != null) {
|
||||
// aliasName = alias.getName();
|
||||
// }
|
||||
// String tableName;
|
||||
// if (column.getTable() != null) {
|
||||
// // 通过列的表名获取 sqlInfo
|
||||
// // 例如 user.name,这里的 tableName 就是 user
|
||||
// tableName = column.getTable().getName();
|
||||
// // 有可能是别名,需要转换为真实表名
|
||||
// if (this.tableAliasMap.get(tableName) != null) {
|
||||
// tableName = this.tableAliasMap.get(tableName);
|
||||
// }
|
||||
// } else {
|
||||
// // 当column的table为空时,说明是 fromItem 中的字段
|
||||
// tableName = ((Table) fromItem).getName();
|
||||
// }
|
||||
// SelectSqlInfo $sqlInfo = this.allTableMap.get(tableName);
|
||||
// if ($sqlInfo != null) {
|
||||
// $sqlInfo.addSelectField(aliasName, fieldName);
|
||||
// } else {
|
||||
// log.warn("发生意外情况,未找到表名为 {} 的 SelectSqlInfo", tableName);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 根据表名添加sqlInfo
|
||||
// *
|
||||
// * @param table
|
||||
// */
|
||||
// private void addSqlInfoByTable(Table table) {
|
||||
// String tableName = table.getName();
|
||||
// // 解析 aliasName
|
||||
// if (table.getAlias() != null) {
|
||||
// this.tableAliasMap.put(table.getAlias().getName(), tableName);
|
||||
// }
|
||||
// SelectSqlInfo sqlInfo = new SelectSqlInfo(this.parsedSql);
|
||||
// sqlInfo.setFromTableName(table.getName());
|
||||
// this.allTableMap.put(sqlInfo.getFromTableName(), sqlInfo);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 合并map
|
||||
// *
|
||||
// * @param source
|
||||
// */
|
||||
// private void assignMap(Map<String, SelectSqlInfo> source) {
|
||||
// for (Map.Entry<String, SelectSqlInfo> entry : source.entrySet()) {
|
||||
// SelectSqlInfo sqlInfo = this.allTableMap.get(entry.getKey());
|
||||
// if (sqlInfo == null) {
|
||||
// this.allTableMap.put(entry.getKey(), entry.getValue());
|
||||
// } else {
|
||||
// // 合并
|
||||
// if (sqlInfo.getSelectFields() == null) {
|
||||
// sqlInfo.setSelectFields(entry.getValue().getSelectFields());
|
||||
// } else {
|
||||
// sqlInfo.getSelectFields().addAll(entry.getValue().getSelectFields());
|
||||
// }
|
||||
// if (sqlInfo.getRealSelectFields() == null) {
|
||||
// sqlInfo.setRealSelectFields(entry.getValue().getRealSelectFields());
|
||||
// } else {
|
||||
// sqlInfo.getRealSelectFields().addAll(entry.getValue().getRealSelectFields());
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
|
|
@ -1,190 +1,190 @@
|
|||
package org.jeecg.common.util.sqlparse;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.sf.jsqlparser.JSQLParserException;
|
||||
import net.sf.jsqlparser.expression.*;
|
||||
import net.sf.jsqlparser.parser.CCJSqlParserManager;
|
||||
import net.sf.jsqlparser.schema.Column;
|
||||
import net.sf.jsqlparser.schema.Table;
|
||||
import net.sf.jsqlparser.statement.Statement;
|
||||
import net.sf.jsqlparser.statement.select.*;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.common.util.sqlparse.vo.SelectSqlInfo;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
public class JSqlParserUtils {
|
||||
|
||||
/**
|
||||
* 解析 查询(select)sql的信息,
|
||||
* 此方法会展开所有子查询到一个map里,
|
||||
* key只存真实的表名,如果查询的没有真实的表名,则会被忽略。
|
||||
* value只存真实的字段名,如果查询的没有真实的字段名,则会被忽略。
|
||||
* <p>
|
||||
* 例如:SELECT a.*,d.age,(SELECT count(1) FROM sys_depart) AS count FROM (SELECT username AS foo, realname FROM sys_user) a, demo d
|
||||
* 解析后的结果为:{sys_user=[username, realname], demo=[age], sys_depart=[]}
|
||||
*
|
||||
* @param selectSql
|
||||
* @return
|
||||
*/
|
||||
public static Map<String, SelectSqlInfo> parseAllSelectTable(String selectSql) throws JSQLParserException {
|
||||
if (oConvertUtils.isEmpty(selectSql)) {
|
||||
return null;
|
||||
}
|
||||
// log.info("解析查询Sql:{}", selectSql);
|
||||
JSqlParserAllTableManager allTableManager = new JSqlParserAllTableManager(selectSql);
|
||||
return allTableManager.parse();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 查询(select)sql的信息,子查询嵌套
|
||||
*
|
||||
* @param selectSql
|
||||
* @return
|
||||
*/
|
||||
public static SelectSqlInfo parseSelectSqlInfo(String selectSql) throws JSQLParserException {
|
||||
if (oConvertUtils.isEmpty(selectSql)) {
|
||||
return null;
|
||||
}
|
||||
// log.info("解析查询Sql:{}", selectSql);
|
||||
// 使用 JSqlParer 解析sql
|
||||
// 1、创建解析器
|
||||
CCJSqlParserManager mgr = new CCJSqlParserManager();
|
||||
// 2、使用解析器解析sql生成具有层次结构的java类
|
||||
Statement stmt = mgr.parse(new StringReader(selectSql));
|
||||
if (stmt instanceof Select) {
|
||||
Select selectStatement = (Select) stmt;
|
||||
// 3、解析select查询sql的信息
|
||||
return JSqlParserUtils.parseBySelectBody(selectStatement.getSelectBody());
|
||||
} else {
|
||||
// 非 select 查询sql,不做处理
|
||||
throw new JeecgBootException("非 select 查询sql,不做处理");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 select 查询sql的信息
|
||||
*
|
||||
* @param selectBody
|
||||
* @return
|
||||
*/
|
||||
private static SelectSqlInfo parseBySelectBody(SelectBody selectBody) {
|
||||
// 判断是否使用了union等操作
|
||||
if (selectBody instanceof SetOperationList) {
|
||||
// 如果使用了union等操作,则只解析第一个查询
|
||||
List<SelectBody> selectBodyList = ((SetOperationList) selectBody).getSelects();
|
||||
return JSqlParserUtils.parseBySelectBody(selectBodyList.get(0));
|
||||
}
|
||||
// 简单的select查询
|
||||
if (selectBody instanceof PlainSelect) {
|
||||
SelectSqlInfo sqlInfo = new SelectSqlInfo(selectBody);
|
||||
PlainSelect plainSelect = (PlainSelect) selectBody;
|
||||
FromItem fromItem = plainSelect.getFromItem();
|
||||
// 解析 aliasName
|
||||
if (fromItem.getAlias() != null) {
|
||||
sqlInfo.setFromTableAliasName(fromItem.getAlias().getName());
|
||||
}
|
||||
// 解析 表名
|
||||
if (fromItem instanceof Table) {
|
||||
// 通过表名的方式from
|
||||
Table fromTable = (Table) fromItem;
|
||||
sqlInfo.setFromTableName(fromTable.getName());
|
||||
} else if (fromItem instanceof SubSelect) {
|
||||
// 通过子查询的方式from
|
||||
SubSelect fromSubSelect = (SubSelect) fromItem;
|
||||
SelectSqlInfo subSqlInfo = JSqlParserUtils.parseBySelectBody(fromSubSelect.getSelectBody());
|
||||
sqlInfo.setFromSubSelect(subSqlInfo);
|
||||
}
|
||||
// 解析 selectFields
|
||||
List<SelectItem> selectItems = plainSelect.getSelectItems();
|
||||
for (SelectItem selectItem : selectItems) {
|
||||
if (selectItem instanceof AllColumns || selectItem instanceof AllTableColumns) {
|
||||
// 全部字段
|
||||
sqlInfo.setSelectAll(true);
|
||||
sqlInfo.setSelectFields(null);
|
||||
sqlInfo.setRealSelectFields(null);
|
||||
break;
|
||||
} else if (selectItem instanceof SelectExpressionItem) {
|
||||
// 获取单个查询字段名
|
||||
SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
|
||||
Expression expression = selectExpressionItem.getExpression();
|
||||
Alias alias = selectExpressionItem.getAlias();
|
||||
JSqlParserUtils.handleExpression(sqlInfo, expression, alias);
|
||||
}
|
||||
}
|
||||
return sqlInfo;
|
||||
} else {
|
||||
log.warn("暂时尚未处理该类型的 SelectBody: {}", selectBody.getClass().getName());
|
||||
throw new JeecgBootException("暂时尚未处理该类型的 SelectBody");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理查询字段表达式
|
||||
*
|
||||
* @param sqlInfo
|
||||
* @param expression
|
||||
* @param alias 是否有别名,无传null
|
||||
*/
|
||||
private static void handleExpression(SelectSqlInfo sqlInfo, Expression expression, Alias alias) {
|
||||
// 处理函数式字段 CONCAT(name,'(',age,')')
|
||||
if (expression instanceof Function) {
|
||||
JSqlParserUtils.handleFunctionExpression((Function) expression, sqlInfo);
|
||||
return;
|
||||
}
|
||||
// 处理字段上的子查询
|
||||
if (expression instanceof SubSelect) {
|
||||
SubSelect subSelect = (SubSelect) expression;
|
||||
SelectSqlInfo subSqlInfo = JSqlParserUtils.parseBySelectBody(subSelect.getSelectBody());
|
||||
// 注:字段上的子查询,必须只查询一个字段,否则会报错,所以可以放心合并
|
||||
sqlInfo.getSelectFields().addAll(subSqlInfo.getSelectFields());
|
||||
sqlInfo.getRealSelectFields().addAll(subSqlInfo.getAllRealSelectFields());
|
||||
return;
|
||||
}
|
||||
// 不处理字面量
|
||||
if (expression instanceof StringValue ||
|
||||
expression instanceof NullValue ||
|
||||
expression instanceof LongValue ||
|
||||
expression instanceof DoubleValue ||
|
||||
expression instanceof HexValue ||
|
||||
expression instanceof DateValue ||
|
||||
expression instanceof TimestampValue ||
|
||||
expression instanceof TimeValue
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 查询字段名
|
||||
String selectField = expression.toString();
|
||||
// 实际查询字段名
|
||||
String realSelectField = selectField;
|
||||
// 判断是否有别名
|
||||
if (alias != null) {
|
||||
selectField = alias.getName();
|
||||
}
|
||||
// 获取真实字段名
|
||||
if (expression instanceof Column) {
|
||||
Column column = (Column) expression;
|
||||
realSelectField = column.getColumnName();
|
||||
}
|
||||
sqlInfo.addSelectField(selectField, realSelectField);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理函数式字段
|
||||
*
|
||||
* @param functionExp
|
||||
* @param sqlInfo
|
||||
*/
|
||||
private static void handleFunctionExpression(Function functionExp, SelectSqlInfo sqlInfo) {
|
||||
List<Expression> expressions = functionExp.getParameters().getExpressions();
|
||||
for (Expression expression : expressions) {
|
||||
JSqlParserUtils.handleExpression(sqlInfo, expression, null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//package org.jeecg.common.util.sqlparse;
|
||||
//
|
||||
//import lombok.extern.slf4j.Slf4j;
|
||||
//import net.sf.jsqlparser.JSQLParserException;
|
||||
//import net.sf.jsqlparser.expression.*;
|
||||
//import net.sf.jsqlparser.parser.CCJSqlParserManager;
|
||||
//import net.sf.jsqlparser.schema.Column;
|
||||
//import net.sf.jsqlparser.schema.Table;
|
||||
//import net.sf.jsqlparser.statement.Statement;
|
||||
//import net.sf.jsqlparser.statement.select.*;
|
||||
//import org.jeecg.common.exception.JeecgBootException;
|
||||
//import org.jeecg.common.util.oConvertUtils;
|
||||
//import org.jeecg.common.util.sqlparse.vo.SelectSqlInfo;
|
||||
//
|
||||
//import java.io.StringReader;
|
||||
//import java.util.List;
|
||||
//import java.util.Map;
|
||||
//
|
||||
//@Slf4j
|
||||
//public class JSqlParserUtils {
|
||||
//
|
||||
// /**
|
||||
// * 解析 查询(select)sql的信息,
|
||||
// * 此方法会展开所有子查询到一个map里,
|
||||
// * key只存真实的表名,如果查询的没有真实的表名,则会被忽略。
|
||||
// * value只存真实的字段名,如果查询的没有真实的字段名,则会被忽略。
|
||||
// * <p>
|
||||
// * 例如:SELECT a.*,d.age,(SELECT count(1) FROM sys_depart) AS count FROM (SELECT username AS foo, realname FROM sys_user) a, demo d
|
||||
// * 解析后的结果为:{sys_user=[username, realname], demo=[age], sys_depart=[]}
|
||||
// *
|
||||
// * @param selectSql
|
||||
// * @return
|
||||
// */
|
||||
// public static Map<String, SelectSqlInfo> parseAllSelectTable(String selectSql) throws JSQLParserException {
|
||||
// if (oConvertUtils.isEmpty(selectSql)) {
|
||||
// return null;
|
||||
// }
|
||||
// // log.info("解析查询Sql:{}", selectSql);
|
||||
// JSqlParserAllTableManager allTableManager = new JSqlParserAllTableManager(selectSql);
|
||||
// return allTableManager.parse();
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 解析 查询(select)sql的信息,子查询嵌套
|
||||
// *
|
||||
// * @param selectSql
|
||||
// * @return
|
||||
// */
|
||||
// public static SelectSqlInfo parseSelectSqlInfo(String selectSql) throws JSQLParserException {
|
||||
// if (oConvertUtils.isEmpty(selectSql)) {
|
||||
// return null;
|
||||
// }
|
||||
// // log.info("解析查询Sql:{}", selectSql);
|
||||
// // 使用 JSqlParer 解析sql
|
||||
// // 1、创建解析器
|
||||
// CCJSqlParserManager mgr = new CCJSqlParserManager();
|
||||
// // 2、使用解析器解析sql生成具有层次结构的java类
|
||||
// Statement stmt = mgr.parse(new StringReader(selectSql));
|
||||
// if (stmt instanceof Select) {
|
||||
// Select selectStatement = (Select) stmt;
|
||||
// // 3、解析select查询sql的信息
|
||||
// return JSqlParserUtils.parseBySelectBody(selectStatement.getSelectBody());
|
||||
// } else {
|
||||
// // 非 select 查询sql,不做处理
|
||||
// throw new JeecgBootException("非 select 查询sql,不做处理");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 解析 select 查询sql的信息
|
||||
// *
|
||||
// * @param selectBody
|
||||
// * @return
|
||||
// */
|
||||
// private static SelectSqlInfo parseBySelectBody(SelectBody selectBody) {
|
||||
// // 判断是否使用了union等操作
|
||||
// if (selectBody instanceof SetOperationList) {
|
||||
// // 如果使用了union等操作,则只解析第一个查询
|
||||
// List<SelectBody> selectBodyList = ((SetOperationList) selectBody).getSelects();
|
||||
// return JSqlParserUtils.parseBySelectBody(selectBodyList.get(0));
|
||||
// }
|
||||
// // 简单的select查询
|
||||
// if (selectBody instanceof PlainSelect) {
|
||||
// SelectSqlInfo sqlInfo = new SelectSqlInfo(selectBody);
|
||||
// PlainSelect plainSelect = (PlainSelect) selectBody;
|
||||
// FromItem fromItem = plainSelect.getFromItem();
|
||||
// // 解析 aliasName
|
||||
// if (fromItem.getAlias() != null) {
|
||||
// sqlInfo.setFromTableAliasName(fromItem.getAlias().getName());
|
||||
// }
|
||||
// // 解析 表名
|
||||
// if (fromItem instanceof Table) {
|
||||
// // 通过表名的方式from
|
||||
// Table fromTable = (Table) fromItem;
|
||||
// sqlInfo.setFromTableName(fromTable.getName());
|
||||
// } else if (fromItem instanceof SubSelect) {
|
||||
// // 通过子查询的方式from
|
||||
// SubSelect fromSubSelect = (SubSelect) fromItem;
|
||||
// SelectSqlInfo subSqlInfo = JSqlParserUtils.parseBySelectBody(fromSubSelect.getSelectBody());
|
||||
// sqlInfo.setFromSubSelect(subSqlInfo);
|
||||
// }
|
||||
// // 解析 selectFields
|
||||
// List<SelectItem> selectItems = plainSelect.getSelectItems();
|
||||
// for (SelectItem selectItem : selectItems) {
|
||||
// if (selectItem instanceof AllColumns || selectItem instanceof AllTableColumns) {
|
||||
// // 全部字段
|
||||
// sqlInfo.setSelectAll(true);
|
||||
// sqlInfo.setSelectFields(null);
|
||||
// sqlInfo.setRealSelectFields(null);
|
||||
// break;
|
||||
// } else if (selectItem instanceof SelectExpressionItem) {
|
||||
// // 获取单个查询字段名
|
||||
// SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
|
||||
// Expression expression = selectExpressionItem.getExpression();
|
||||
// Alias alias = selectExpressionItem.getAlias();
|
||||
// JSqlParserUtils.handleExpression(sqlInfo, expression, alias);
|
||||
// }
|
||||
// }
|
||||
// return sqlInfo;
|
||||
// } else {
|
||||
// log.warn("暂时尚未处理该类型的 SelectBody: {}", selectBody.getClass().getName());
|
||||
// throw new JeecgBootException("暂时尚未处理该类型的 SelectBody");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 处理查询字段表达式
|
||||
// *
|
||||
// * @param sqlInfo
|
||||
// * @param expression
|
||||
// * @param alias 是否有别名,无传null
|
||||
// */
|
||||
// private static void handleExpression(SelectSqlInfo sqlInfo, Expression expression, Alias alias) {
|
||||
// // 处理函数式字段 CONCAT(name,'(',age,')')
|
||||
// if (expression instanceof Function) {
|
||||
// JSqlParserUtils.handleFunctionExpression((Function) expression, sqlInfo);
|
||||
// return;
|
||||
// }
|
||||
// // 处理字段上的子查询
|
||||
// if (expression instanceof SubSelect) {
|
||||
// SubSelect subSelect = (SubSelect) expression;
|
||||
// SelectSqlInfo subSqlInfo = JSqlParserUtils.parseBySelectBody(subSelect.getSelectBody());
|
||||
// // 注:字段上的子查询,必须只查询一个字段,否则会报错,所以可以放心合并
|
||||
// sqlInfo.getSelectFields().addAll(subSqlInfo.getSelectFields());
|
||||
// sqlInfo.getRealSelectFields().addAll(subSqlInfo.getAllRealSelectFields());
|
||||
// return;
|
||||
// }
|
||||
// // 不处理字面量
|
||||
// if (expression instanceof StringValue ||
|
||||
// expression instanceof NullValue ||
|
||||
// expression instanceof LongValue ||
|
||||
// expression instanceof DoubleValue ||
|
||||
// expression instanceof HexValue ||
|
||||
// expression instanceof DateValue ||
|
||||
// expression instanceof TimestampValue ||
|
||||
// expression instanceof TimeValue
|
||||
// ) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // 查询字段名
|
||||
// String selectField = expression.toString();
|
||||
// // 实际查询字段名
|
||||
// String realSelectField = selectField;
|
||||
// // 判断是否有别名
|
||||
// if (alias != null) {
|
||||
// selectField = alias.getName();
|
||||
// }
|
||||
// // 获取真实字段名
|
||||
// if (expression instanceof Column) {
|
||||
// Column column = (Column) expression;
|
||||
// realSelectField = column.getColumnName();
|
||||
// }
|
||||
// sqlInfo.addSelectField(selectField, realSelectField);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 处理函数式字段
|
||||
// *
|
||||
// * @param functionExp
|
||||
// * @param sqlInfo
|
||||
// */
|
||||
// private static void handleFunctionExpression(Function functionExp, SelectSqlInfo sqlInfo) {
|
||||
// List<Expression> expressions = functionExp.getParameters().getExpressions();
|
||||
// for (Expression expression : expressions) {
|
||||
// JSqlParserUtils.handleExpression(sqlInfo, expression, null);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
|
|
@ -1,101 +1,101 @@
|
|||
package org.jeecg.common.util.sqlparse.vo;
|
||||
|
||||
import lombok.Data;
|
||||
import net.sf.jsqlparser.statement.select.SelectBody;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* select 查询 sql 的信息
|
||||
*/
|
||||
@Data
|
||||
public class SelectSqlInfo {
|
||||
|
||||
/**
|
||||
* 查询的表名,如果是子查询,则此处为null
|
||||
*/
|
||||
private String fromTableName;
|
||||
/**
|
||||
* 表别名
|
||||
*/
|
||||
private String fromTableAliasName;
|
||||
/**
|
||||
* 通过子查询获取的表信息,例如:select name from (select * from user) u
|
||||
* 如果不是子查询,则为null
|
||||
*/
|
||||
private SelectSqlInfo fromSubSelect;
|
||||
/**
|
||||
* 查询的字段集合,如果是 * 则为null,如果设了别名则为别名
|
||||
*/
|
||||
private Set<String> selectFields;
|
||||
/**
|
||||
* 真实的查询字段集合,如果是 * 则为null,如果设了别名则为原始字段名
|
||||
*/
|
||||
private Set<String> realSelectFields;
|
||||
/**
|
||||
* 是否是查询所有字段
|
||||
*/
|
||||
private boolean selectAll;
|
||||
|
||||
/**
|
||||
* 解析之后的 SQL (关键字都是大写)
|
||||
*/
|
||||
private final String parsedSql;
|
||||
|
||||
public SelectSqlInfo(String parsedSql) {
|
||||
this.parsedSql = parsedSql;
|
||||
}
|
||||
|
||||
public SelectSqlInfo(SelectBody selectBody) {
|
||||
this.parsedSql = selectBody.toString();
|
||||
}
|
||||
|
||||
public void addSelectField(String selectField, String realSelectField) {
|
||||
if (this.selectFields == null) {
|
||||
this.selectFields = new HashSet<>();
|
||||
}
|
||||
if (this.realSelectFields == null) {
|
||||
this.realSelectFields = new HashSet<>();
|
||||
}
|
||||
this.selectFields.add(selectField);
|
||||
this.realSelectFields.add(realSelectField);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有字段,包括子查询里的。
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Set<String> getAllRealSelectFields() {
|
||||
Set<String> fields = new HashSet<>();
|
||||
// 递归获取所有字段,起个直观的方法名为:
|
||||
this.recursiveGetAllFields(this, fields);
|
||||
return fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归获取所有字段
|
||||
*/
|
||||
private void recursiveGetAllFields(SelectSqlInfo sqlInfo, Set<String> fields) {
|
||||
if (!sqlInfo.isSelectAll() && sqlInfo.getRealSelectFields() != null) {
|
||||
fields.addAll(sqlInfo.getRealSelectFields());
|
||||
}
|
||||
if (sqlInfo.getFromSubSelect() != null) {
|
||||
recursiveGetAllFields(sqlInfo.getFromSubSelect(), fields);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SelectSqlInfo{" +
|
||||
"fromTableName='" + fromTableName + '\'' +
|
||||
", fromSubSelect=" + fromSubSelect +
|
||||
", aliasName='" + fromTableAliasName + '\'' +
|
||||
", selectFields=" + selectFields +
|
||||
", realSelectFields=" + realSelectFields +
|
||||
", selectAll=" + selectAll +
|
||||
"}";
|
||||
}
|
||||
|
||||
}
|
||||
//package org.jeecg.common.util.sqlparse.vo;
|
||||
//
|
||||
//import lombok.Data;
|
||||
//import net.sf.jsqlparser.statement.select.SelectBody;
|
||||
//
|
||||
//import java.util.HashSet;
|
||||
//import java.util.Set;
|
||||
//
|
||||
///**
|
||||
// * select 查询 sql 的信息
|
||||
// */
|
||||
//@Data
|
||||
//public class SelectSqlInfo {
|
||||
//
|
||||
// /**
|
||||
// * 查询的表名,如果是子查询,则此处为null
|
||||
// */
|
||||
// private String fromTableName;
|
||||
// /**
|
||||
// * 表别名
|
||||
// */
|
||||
// private String fromTableAliasName;
|
||||
// /**
|
||||
// * 通过子查询获取的表信息,例如:select name from (select * from user) u
|
||||
// * 如果不是子查询,则为null
|
||||
// */
|
||||
// private SelectSqlInfo fromSubSelect;
|
||||
// /**
|
||||
// * 查询的字段集合,如果是 * 则为null,如果设了别名则为别名
|
||||
// */
|
||||
// private Set<String> selectFields;
|
||||
// /**
|
||||
// * 真实的查询字段集合,如果是 * 则为null,如果设了别名则为原始字段名
|
||||
// */
|
||||
// private Set<String> realSelectFields;
|
||||
// /**
|
||||
// * 是否是查询所有字段
|
||||
// */
|
||||
// private boolean selectAll;
|
||||
//
|
||||
// /**
|
||||
// * 解析之后的 SQL (关键字都是大写)
|
||||
// */
|
||||
// private final String parsedSql;
|
||||
//
|
||||
// public SelectSqlInfo(String parsedSql) {
|
||||
// this.parsedSql = parsedSql;
|
||||
// }
|
||||
//
|
||||
// public SelectSqlInfo(SelectBody selectBody) {
|
||||
// this.parsedSql = selectBody.toString();
|
||||
// }
|
||||
//
|
||||
// public void addSelectField(String selectField, String realSelectField) {
|
||||
// if (this.selectFields == null) {
|
||||
// this.selectFields = new HashSet<>();
|
||||
// }
|
||||
// if (this.realSelectFields == null) {
|
||||
// this.realSelectFields = new HashSet<>();
|
||||
// }
|
||||
// this.selectFields.add(selectField);
|
||||
// this.realSelectFields.add(realSelectField);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 获取所有字段,包括子查询里的。
|
||||
// *
|
||||
// * @return
|
||||
// */
|
||||
// public Set<String> getAllRealSelectFields() {
|
||||
// Set<String> fields = new HashSet<>();
|
||||
// // 递归获取所有字段,起个直观的方法名为:
|
||||
// this.recursiveGetAllFields(this, fields);
|
||||
// return fields;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 递归获取所有字段
|
||||
// */
|
||||
// private void recursiveGetAllFields(SelectSqlInfo sqlInfo, Set<String> fields) {
|
||||
// if (!sqlInfo.isSelectAll() && sqlInfo.getRealSelectFields() != null) {
|
||||
// fields.addAll(sqlInfo.getRealSelectFields());
|
||||
// }
|
||||
// if (sqlInfo.getFromSubSelect() != null) {
|
||||
// recursiveGetAllFields(sqlInfo.getFromSubSelect(), fields);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public String toString() {
|
||||
// return "SelectSqlInfo{" +
|
||||
// "fromTableName='" + fromTableName + '\'' +
|
||||
// ", fromSubSelect=" + fromSubSelect +
|
||||
// ", aliasName='" + fromTableAliasName + '\'' +
|
||||
// ", selectFields=" + selectFields +
|
||||
// ", realSelectFields=" + realSelectFields +
|
||||
// ", selectAll=" + selectAll +
|
||||
// "}";
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
|
|
@ -6,15 +6,12 @@ import org.apache.shiro.SecurityUtils;
|
|||
import org.jeecg.common.api.CommonAPI;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.common.system.util.JwtUtil;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.common.util.CommonUtils;
|
||||
import org.jeecg.common.util.SpringContextUtils;
|
||||
import org.jeecg.config.JeecgBaseConfig;
|
||||
import org.jeecg.config.firewall.interceptor.enums.LowCodeUrlsEnum;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
@ -70,6 +67,9 @@ public class LowCodeModeInterceptor implements HandlerInterceptor {
|
|||
Set<String> hasRoles = null;
|
||||
if (loginUser == null) {
|
||||
loginUser = commonAPI.getUserByName(JwtUtil.getUserNameByToken(SpringContextUtils.getHttpServletRequest()));
|
||||
}
|
||||
|
||||
if (loginUser != null) {
|
||||
//当前登录人拥有的角色
|
||||
hasRoles = commonAPI.queryUserRolesById(loginUser.getId());
|
||||
}
|
||||
|
|
|
@ -1,75 +0,0 @@
|
|||
package org.jeecg.test.sqlinjection;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.sf.jsqlparser.JSQLParserException;
|
||||
import org.jeecg.common.util.SqlInjectionUtil;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
|
||||
/**
|
||||
* SQL注入攻击检查测试
|
||||
* @author: liusq
|
||||
* @date: 2023年09月08日
|
||||
*/
|
||||
@Slf4j
|
||||
public class TestInjectWithSqlParser {
|
||||
/**
|
||||
* 注入测试
|
||||
*
|
||||
* @param sql
|
||||
* @return
|
||||
*/
|
||||
private boolean isExistSqlInject(String sql) {
|
||||
try {
|
||||
SqlInjectionUtil.specialFilterContentForOnlineReport(sql);
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
log.info("===================================================");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void test() throws JSQLParserException {
|
||||
//不存在sql注入
|
||||
assertFalse(isExistSqlInject("select * from fm_time where dept_id=:sqlparamsmap.id and time=:sqlparamsmap.time"));
|
||||
assertFalse(isExistSqlInject("select * from test"));
|
||||
assertFalse(isExistSqlInject("select load_file(\"C:\\\\benben.txt\")"));
|
||||
assertFalse(isExistSqlInject("WITH SUB1 AS (SELECT user FROM t1) SELECT * FROM T2 WHERE id > 123 "));
|
||||
|
||||
//存在sql注入
|
||||
assertTrue(isExistSqlInject("or 1= 1 --"));
|
||||
assertTrue(isExistSqlInject("select * from test where sleep(%23)"));
|
||||
assertTrue(isExistSqlInject("select * from test where id=1 and multipoint((select * from(select * from(select user())a)b));"));
|
||||
assertTrue(isExistSqlInject("select * from users;show databases;"));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where id=1 and length((select group_concat(table_name) from information_schema.tables where table_schema=database()))>13"));
|
||||
assertTrue(isExistSqlInject("update user set name = '123'"));
|
||||
assertTrue(isExistSqlInject("SELECT * FROM users WHERE username = 'admin' AND password = '123456' OR 1=1;--"));
|
||||
assertTrue(isExistSqlInject("select * from users where id=1 and (select count(*) from information_schema.tables where table_schema='数据库名')>4 %23"));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where sleep(5) %23"));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where id in (select id from other)"));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where id in (select id from other)"));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where 2=2.0 or 2 != 4"));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where 1!=2.0"));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where id=floor(2.0)"));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where not true"));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where 1 or id > 0"));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where 'tom' or id > 0"));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where '-2.3' "));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where 2 "));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where (3+2) "));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where -1 IS TRUE"));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where 'hello' is null "));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where '2022-10-31' and id > 0"));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where id > 0 or 1!=2.0 "));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where id > 0 or 1 in (1,3,4) "));
|
||||
assertTrue(isExistSqlInject("select * from dc_device UNION select name from other"));
|
||||
assertTrue(isExistSqlInject("(SELECT 6240 FROM (SELECT(SLEEP(5))and 1=2)vidl)"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
package org.jeecg.test.sqlinjection;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.sf.jsqlparser.JSQLParserException;
|
||||
import org.jeecg.common.util.SqlInjectionUtil;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
|
||||
/**
|
||||
* SQL注入攻击检查测试
|
||||
* @author: liusq
|
||||
* @date: 2023年09月08日
|
||||
*/
|
||||
@Slf4j
|
||||
public class TestSqlInjectForDict {
|
||||
/**
|
||||
* 注入测试
|
||||
*
|
||||
* @param sql
|
||||
* @return
|
||||
*/
|
||||
private boolean isExistSqlInject(String sql) {
|
||||
try {
|
||||
SqlInjectionUtil.specialFilterContentForDictSql(sql);
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
log.info("===================================================");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void test() throws JSQLParserException {
|
||||
//不存在sql注入
|
||||
assertFalse(isExistSqlInject("sys_user,realname,id"));
|
||||
assertFalse(isExistSqlInject("oa_officialdoc_organcode,organ_name,id"));
|
||||
assertFalse(isExistSqlInject("onl_cgform_head where table_type!=3 and copy_type=0,table_txt,table_name"));
|
||||
assertFalse(isExistSqlInject("onl_cgform_head where copy_type = 0,table_txt,table_name"));
|
||||
|
||||
//存在sql注入
|
||||
assertTrue(isExistSqlInject("or 1= 1 --"));
|
||||
assertTrue(isExistSqlInject("select * from test where sleep(%23)"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
package org.jeecg.test.sqlinjection;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.sf.jsqlparser.JSQLParserException;
|
||||
import org.jeecg.common.util.SqlInjectionUtil;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
|
||||
/**
|
||||
* SQL注入攻击检查测试
|
||||
* @author: liusq
|
||||
* @date: 2023年09月08日
|
||||
*/
|
||||
@Slf4j
|
||||
public class TestSqlInjectForOnlineReport {
|
||||
/**
|
||||
* 注入测试
|
||||
*
|
||||
* @param sql
|
||||
* @return
|
||||
*/
|
||||
private boolean isExistSqlInject(String sql) {
|
||||
try {
|
||||
SqlInjectionUtil.specialFilterContentForOnlineReport(sql);
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
log.info("===================================================");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void test() throws JSQLParserException {
|
||||
//不存在sql注入
|
||||
assertFalse(isExistSqlInject("select * from fm_time where dept_id=:sqlparamsmap.id and time=:sqlparamsmap.time"));
|
||||
assertFalse(isExistSqlInject("select * from test"));
|
||||
assertFalse(isExistSqlInject("select load_file(\"C:\\\\benben.txt\")"));
|
||||
assertFalse(isExistSqlInject("select * from dc_device where id in (select id from other)"));
|
||||
assertFalse(isExistSqlInject("select * from dc_device UNION select name from other"));
|
||||
|
||||
//存在sql注入
|
||||
assertTrue(isExistSqlInject("(SELECT 6240 FROM (SELECT(SLEEP(5))and 1=2)vidl)"));
|
||||
assertTrue(isExistSqlInject("or 1= 1 --"));
|
||||
assertTrue(isExistSqlInject("select * from test where sleep(%23)"));
|
||||
assertTrue(isExistSqlInject("select * from test where SLEEP(3)"));
|
||||
assertTrue(isExistSqlInject("select * from test where id=1 and multipoint((select * from(select * from(select user())a)b));"));
|
||||
assertTrue(isExistSqlInject("select * from users;show databases;"));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where id=1 and length((select group_concat(table_name) from information_schema.tables where table_schema=database()))>13"));
|
||||
assertTrue(isExistSqlInject("update user set name = '123'"));
|
||||
assertTrue(isExistSqlInject("SELECT * FROM users WHERE username = 'admin' AND password = '123456' OR 1=1;--"));
|
||||
assertTrue(isExistSqlInject("select * from users where id=1 and (select count(*) from information_schema.tables where table_schema='数据库名')>4 %23"));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where sleep(5) %23"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
package org.jeecg.test.sqlinjection;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.sql.SqlInjectionUtils;
|
||||
import org.jeecg.common.util.SqlInjectionUtil;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @Description: SQL注入测试类
|
||||
* @author: scott
|
||||
* @date: 2023年08月14日 9:55
|
||||
*/
|
||||
public class TestSqlInjection {
|
||||
|
||||
|
||||
/**
|
||||
* 表名带别名,同时有html编码字符
|
||||
*/
|
||||
@Test
|
||||
public void testSpecialSQL() {
|
||||
String tableName = "sys_user t";
|
||||
//解决使用参数tableName=sys_user t&复测,漏洞仍然存在
|
||||
if (tableName.contains(" ")) {
|
||||
tableName = tableName.substring(0, tableName.indexOf(" "));
|
||||
}
|
||||
//【issues/4393】 sys_user , (sys_user), sys_user%20, %60sys_user%60
|
||||
String reg = "\\s+|\\(|\\)|`";
|
||||
tableName = tableName.replaceAll(reg, "");
|
||||
System.out.println(tableName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 测试sql是否含sql注入风险
|
||||
* <p>
|
||||
* mybatis plus的方法
|
||||
*/
|
||||
@Test
|
||||
public void sqlInjectionCheck() {
|
||||
String sql = "select * from sys_user";
|
||||
System.out.println(SqlInjectionUtils.check(sql));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 测试sql是否有SLEEP风险
|
||||
* <p>
|
||||
* mybatisPlus的方法
|
||||
*/
|
||||
@Test
|
||||
public void sqlSleepCheck() {
|
||||
SqlInjectionUtil.checkSqlAnnotation("(SELECT 6240 FROM (SELECT(SLEEP(5))and 1=2)vidl)");
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试sql是否含sql注入风险
|
||||
* <p>
|
||||
* 自定义方法
|
||||
*/
|
||||
@Test
|
||||
public void sqlInjectionCheck2() {
|
||||
String sql = "select * from sys_user";
|
||||
SqlInjectionUtil.specialFilterContentForOnlineReport(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段定义只能是是字母 数字 下划线的组合(不允许有空格、转义字符串等)
|
||||
* <p>
|
||||
* 判断字段名是否符合规范
|
||||
*/
|
||||
@Test
|
||||
public void testFieldSpecification() {
|
||||
List<String> list = new ArrayList();
|
||||
list.add("Hello World!");
|
||||
list.add("Hello%20World!");
|
||||
list.add("HelloWorld!");
|
||||
list.add("Hello World");
|
||||
list.add("age");
|
||||
list.add("user_name");
|
||||
list.add("user_name%20");
|
||||
list.add("user_name%20 ");
|
||||
|
||||
for (String input : list) {
|
||||
boolean containsSpecialChars = isValidString(input);
|
||||
System.out.println("input:" + input + " ,包含空格和特殊字符: " + containsSpecialChars);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段定义只能是是字母 数字 下划线的组合(不允许有空格、转义字符串等)
|
||||
*
|
||||
* @param input
|
||||
* @return
|
||||
*/
|
||||
private static boolean isValidString(String input) {
|
||||
Pattern pattern = Pattern.compile("^[a-zA-Z0-9_]+$");
|
||||
return pattern.matcher(input).matches();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
package org.jeecg.test.sqlparse;
|
||||
|
||||
import net.sf.jsqlparser.JSQLParserException;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.common.util.sqlparse.JSqlParserUtils;
|
||||
import org.jeecg.common.util.sqlparse.vo.SelectSqlInfo;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 针对 JSqlParserUtils 的单元测试
|
||||
*/
|
||||
public class JSqlParserUtilsTest {
|
||||
|
||||
private static final String[] sqlList = new String[]{
|
||||
"select * from sys_user",
|
||||
"select u.* from sys_user u",
|
||||
"select u.*, c.name from sys_user u, demo c",
|
||||
"select u.age, c.name from sys_user u, demo c",
|
||||
"select sex, age, c.name from sys_user, demo c",
|
||||
// 别名测试
|
||||
"select username as realname from sys_user",
|
||||
"select username as realname, u.realname as aaa, u.id bbb from sys_user u",
|
||||
// 不存在真实地查询字段
|
||||
"select count(1) from sys_user",
|
||||
// 函数式字段
|
||||
"select max(sex), id from sys_user",
|
||||
// 复杂嵌套函数式字段
|
||||
"select CONCAT(CONCAT(' _ ', sex), ' - ' , birthday) as info, id from sys_user",
|
||||
// 更复杂的嵌套函数式字段
|
||||
"select CONCAT(CONCAT(101,'_',NULL, DATE(create_time),'_',sex),' - ',birthday) as info, id from sys_user",
|
||||
// 子查询SQL
|
||||
"select u.name1 as name2 from (select username as name1 from sys_user) u",
|
||||
// 多层嵌套子查询SQL
|
||||
"select u2.name2 as name3 from (select u1.name1 as name2 from (select username as name1 from sys_user) u1) u2",
|
||||
// 字段子查询SQL
|
||||
"select id, (select username as name1 from sys_user u2 where u1.id = u2.id) as name2 from sys_user u1",
|
||||
// 带条件的SQL(不解析where条件里的字段,但不影响解析查询字段)
|
||||
"select username as name1 from sys_user where realname LIKE '%张%'",
|
||||
// 多重复杂关联表查询解析,包含的表为:sys_user, sys_depart, sys_dict_item, demo
|
||||
"" +
|
||||
"SELECT " +
|
||||
" u.*, d.age, sd.item_text AS sex, (SELECT count(sd.id) FROM sys_depart sd) AS count " +
|
||||
"FROM " +
|
||||
" (SELECT sd.username AS foo, sd.realname FROM sys_user sd) u, " +
|
||||
" demo d " +
|
||||
"LEFT JOIN sys_dict_item AS sd ON d.sex = sd.item_value " +
|
||||
"WHERE sd.dict_id = '3d9a351be3436fbefb1307d4cfb49bf2'",
|
||||
};
|
||||
|
||||
@Test
|
||||
public void testParseSelectSql() {
|
||||
System.out.println("-----------------------------------------");
|
||||
for (String sql : sqlList) {
|
||||
System.out.println("待测试的sql:" + sql);
|
||||
try {
|
||||
// 解析所有的表名,key=表名,value=解析后的sql信息
|
||||
Map<String, SelectSqlInfo> parsedMap = JSqlParserUtils.parseAllSelectTable(sql);
|
||||
assert parsedMap != null;
|
||||
for (Map.Entry<String, SelectSqlInfo> entry : parsedMap.entrySet()) {
|
||||
System.out.println("表名:" + entry.getKey());
|
||||
this.printSqlInfo(entry.getValue(), 1);
|
||||
}
|
||||
} catch (JSQLParserException e) {
|
||||
System.out.println("SQL解析出现异常:" + e.getMessage());
|
||||
}
|
||||
System.out.println("-----------------------------------------");
|
||||
}
|
||||
}
|
||||
|
||||
private void printSqlInfo(SelectSqlInfo sqlInfo, int level) {
|
||||
String beforeStr = this.getBeforeStr(level);
|
||||
if (sqlInfo.getFromTableName() == null) {
|
||||
// 子查询
|
||||
System.out.println(beforeStr + "子查询:" + sqlInfo.getFromSubSelect().getParsedSql());
|
||||
this.printSqlInfo(sqlInfo.getFromSubSelect(), level + 1);
|
||||
} else {
|
||||
// 非子查询
|
||||
System.out.println(beforeStr + "查询的表名:" + sqlInfo.getFromTableName());
|
||||
}
|
||||
if (oConvertUtils.isNotEmpty(sqlInfo.getFromTableAliasName())) {
|
||||
System.out.println(beforeStr + "查询的表别名:" + sqlInfo.getFromTableAliasName());
|
||||
}
|
||||
if (sqlInfo.isSelectAll()) {
|
||||
System.out.println(beforeStr + "查询的字段:*");
|
||||
} else {
|
||||
System.out.println(beforeStr + "查询的字段:" + sqlInfo.getSelectFields());
|
||||
System.out.println(beforeStr + "真实的字段:" + sqlInfo.getRealSelectFields());
|
||||
if (sqlInfo.getFromTableName() == null) {
|
||||
System.out.println(beforeStr + "所有的字段(包括子查询):" + sqlInfo.getAllRealSelectFields());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 打印前缀,根据层级来打印
|
||||
private String getBeforeStr(int level) {
|
||||
if (level == 0) {
|
||||
return "";
|
||||
}
|
||||
StringBuilder beforeStr = new StringBuilder();
|
||||
for (int i = 0; i < level; i++) {
|
||||
beforeStr.append(" ");
|
||||
}
|
||||
beforeStr.append("- ");
|
||||
return beforeStr.toString();
|
||||
}
|
||||
|
||||
}
|
|
@ -4,14 +4,14 @@ import lombok.extern.slf4j.Slf4j;
|
|||
import org.jeecg.common.constant.SymbolConstant;
|
||||
import org.jeecg.common.exception.JeecgSqlInjectionException;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.common.util.sqlparse.JSqlParserUtils;
|
||||
import org.jeecg.common.util.sqlparse.vo.SelectSqlInfo;
|
||||
import org.jeecg.config.JeecgBaseConfig;
|
||||
import org.jeecg.config.firewall.SqlInjection.IDictTableWhiteListHandler;
|
||||
import org.jeecg.config.firewall.interceptor.LowCodeModeInterceptor;
|
||||
import org.jeecg.modules.system.entity.SysTableWhiteList;
|
||||
import org.jeecg.modules.system.security.DictQueryBlackListHandler;
|
||||
import org.jeecg.modules.system.service.ISysTableWhiteListService;
|
||||
import org.jeecgframework.minidao.sqlparser.impl.vo.SelectSqlInfo;
|
||||
import org.jeecgframework.minidao.util.MiniDaoUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
@ -65,7 +65,7 @@ public class DictTableWhiteListHandlerImpl implements IDictTableWhiteListHandler
|
|||
public boolean isPassBySql(String sql) {
|
||||
Map<String, SelectSqlInfo> parsedMap = null;
|
||||
try {
|
||||
parsedMap = JSqlParserUtils.parseAllSelectTable(sql);
|
||||
parsedMap = MiniDaoUtil.parseAllSelectTable(sql);
|
||||
} catch (Exception e) {
|
||||
log.warn("校验sql语句,解析报错:{}", e.getMessage());
|
||||
}
|
||||
|
@ -127,7 +127,7 @@ public class DictTableWhiteListHandlerImpl implements IDictTableWhiteListHandler
|
|||
log.info("字典拼接的查询SQL:{}", sql);
|
||||
try {
|
||||
// 进行SQL解析
|
||||
JSqlParserUtils.parseSelectSqlInfo(sql);
|
||||
MiniDaoUtil.parseSelectSqlInfo(sql);
|
||||
} catch (Exception e) {
|
||||
// 如果SQL解析失败,则通过字段名和表名进行校验
|
||||
return checkWhiteList(tableName, new HashSet<>(Arrays.asList(fields)));
|
||||
|
|
|
@ -78,7 +78,9 @@ public class CustomUndertowMetricsHandler {
|
|||
// 获取当前 session,如果不存在则创建
|
||||
Session session = sessionManager.getSession(exchange, sessionConfig);
|
||||
if (session == null) {
|
||||
sessionManager.createSession(exchange, sessionConfig);
|
||||
try {
|
||||
sessionManager.createSession(exchange, sessionConfig);
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
|
||||
// 执行下一个 Handler
|
||||
|
|
|
@ -382,7 +382,7 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
|
|||
SwaggerInfo info = new SwaggerInfo();
|
||||
|
||||
info.setDescription("OpenAPI 接口列表");
|
||||
info.setVersion("3.7.1");
|
||||
info.setVersion("3.8.0");
|
||||
info.setTitle("OpenAPI 接口列表");
|
||||
info.setTermsOfService("https://jeecg.com");
|
||||
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
import { render } from '@/common/renderUtils';
|
||||
//列表数据
|
||||
export const columns = [
|
||||
<#list columns as po>
|
||||
<#if po.isShowList =='Y' && po.fieldName !='id' && po.fieldName !='delFlag'>
|
||||
{
|
||||
title: '${po.filedComment}',
|
||||
align:"center",
|
||||
<#if po.sort=='Y'>
|
||||
sorter: true,
|
||||
</#if>
|
||||
<#if po.classType=='date'>
|
||||
dataIndex: '${po.fieldName}',
|
||||
<#elseif po.fieldDbType=='Blob'>
|
||||
dataIndex: '${po.fieldName}String'
|
||||
<#elseif po.classType=='umeditor'>
|
||||
dataIndex: '${po.fieldName}',
|
||||
<#elseif po.classType=='pca'>
|
||||
dataIndex: '${po.fieldName}',
|
||||
<#elseif po.classType=='file'>
|
||||
dataIndex: '${po.fieldName}',
|
||||
<#elseif po.classType=='image'>
|
||||
dataIndex: '${po.fieldName}',
|
||||
customRender:render.renderImage,
|
||||
<#elseif po.classType=='switch'>
|
||||
dataIndex: '${po.fieldName}',
|
||||
<#assign switch_extend_arr=['Y','N']>
|
||||
<#if po.dictField?default("")?contains("[")>
|
||||
<#assign switch_extend_arr=po.dictField?eval>
|
||||
</#if>
|
||||
<#list switch_extend_arr as a>
|
||||
<#if a_index == 0>
|
||||
<#assign switch_extend_arr1=a>
|
||||
<#else>
|
||||
<#assign switch_extend_arr2=a>
|
||||
</#if>
|
||||
</#list>
|
||||
customRender:({text}) => {
|
||||
return render.renderSwitch(text, [{text:'是',value:'${switch_extend_arr1}'},{text:'否',value:'${switch_extend_arr2}'}])
|
||||
},
|
||||
<#elseif po.classType == 'sel_tree' || po.classType=='list' || po.classType=='list_multi' || po.classType=='sel_search' || po.classType=='radio' || po.classType=='checkbox' || po.classType=='sel_depart' || po.classType=='sel_user' || po.classType=='popup_dict'>
|
||||
dataIndex: '${po.fieldName}_dictText'
|
||||
<#else>
|
||||
dataIndex: '${po.fieldName}'
|
||||
</#if>
|
||||
},
|
||||
</#if>
|
||||
</#list>
|
||||
];
|
|
@ -0,0 +1,512 @@
|
|||
<#include "/common/utils.ftl">
|
||||
<route lang="json5" type="page">
|
||||
{
|
||||
layout: 'default',
|
||||
style: {
|
||||
navigationStyle: 'custom',
|
||||
navigationBarTitleText: '${tableVo.ftlDescription}',
|
||||
},
|
||||
}
|
||||
</route>
|
||||
|
||||
<template>
|
||||
<PageLayout :navTitle="navTitle" :backRouteName="backRouteName">
|
||||
<scroll-view class="scrollArea" scroll-y>
|
||||
<view class="form-container">
|
||||
<wd-form ref="form" :model="myFormData">
|
||||
<wd-cell-group border>
|
||||
<#list columns as po><#rt/>
|
||||
<#assign form_field_dictCode="">
|
||||
<#if po.dictTable?default("")?trim?length gt 1 && po.dictText?default("")?trim?length gt 1 && po.dictField?default("")?trim?length gt 1>
|
||||
<#assign form_field_dictCode="${po.dictTable},${po.dictText},${po.dictField}">
|
||||
<#elseif po.dictField?default("")?trim?length gt 1>
|
||||
<#assign form_field_dictCode="${po.dictField}">
|
||||
</#if>
|
||||
<view class="{ 'mt-14px': ${po_index % 2} == 0 }">
|
||||
<#if po.fieldName !='id' && po.isShow =='Y' && po.fieldName !='delFlag'><#rt/>
|
||||
<#if po.classType =='image'>
|
||||
<!-- 图片 -->
|
||||
<wd-cell
|
||||
:title="get4Label('${po.filedComment}')"
|
||||
title-width="100px"
|
||||
<#if po.nullable == 'N'>
|
||||
:required="true"
|
||||
</#if>
|
||||
>
|
||||
<online-image
|
||||
v-model:value="myFormData[${autoStringSuffix(po)}]"
|
||||
name=${autoStringSuffix(po)}
|
||||
<#if po.uploadnum??>
|
||||
:maxNum="${po.uploadnum}"
|
||||
</#if>
|
||||
/>
|
||||
</wd-cell>
|
||||
<#elseif po.classType =='file'>
|
||||
<wd-cell
|
||||
:title="get4Label('${po.filedComment}')"
|
||||
title-width="100px"
|
||||
<#if po.nullable == 'N'>
|
||||
:required="true"
|
||||
</#if>
|
||||
>
|
||||
<!-- #ifndef APP-PLUS -->
|
||||
<online-file
|
||||
v-model:value="myFormData[${autoStringSuffix(po)}]"
|
||||
name=${autoStringSuffix(po)}
|
||||
></online-file>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<online-file-custom
|
||||
v-model:value="myFormData[${autoStringSuffix(po)}]"
|
||||
name=${autoStringSuffix(po)}
|
||||
></online-file-custom>
|
||||
<!-- #endif -->
|
||||
</wd-cell>
|
||||
<#elseif po.classType =='datetime' || po.classType =='time'>
|
||||
<DateTime
|
||||
:label="get4Label('${po.filedComment}')"
|
||||
labelWidth="100px"
|
||||
type="${po.classType}"
|
||||
<#if po.classType =='datetime'>
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
<#else>
|
||||
format="HH:mm:ss"
|
||||
</#if>
|
||||
name=${autoStringSuffix(po)}
|
||||
v-model="myFormData[${autoStringSuffix(po)}]"
|
||||
></DateTime>
|
||||
<#elseif po.classType =='date'>
|
||||
<online-date
|
||||
:label="get4Label('${po.filedComment}')"
|
||||
labelWidth="100px"
|
||||
<#if po.extendParams?exists && po.extendParams.picker?exists>
|
||||
:type="getDateExtendType('${po.extendParams.picker}')"
|
||||
<#else>
|
||||
type="${po.classType}"
|
||||
</#if>
|
||||
name=${autoStringSuffix(po)}
|
||||
v-model:value="myFormData[${autoStringSuffix(po)}]"
|
||||
></online-date>
|
||||
<#elseif po.classType =='switch'>
|
||||
<#assign switch_extend_arr=['Y','N']>
|
||||
<#if po.dictField?default("")?contains("[")>
|
||||
<#assign switch_extend_arr=po.dictField?eval>
|
||||
</#if>
|
||||
<!-- 开关 -->
|
||||
<wd-cell
|
||||
:label="get4Label('${po.filedComment}')"
|
||||
name=${autoStringSuffix(po)}
|
||||
title-width="100px"
|
||||
center
|
||||
>
|
||||
<wd-switch
|
||||
:label="get4Label('${po.filedComment}')"
|
||||
name=${autoStringSuffix(po)}
|
||||
size="18px"
|
||||
v-model="myFormData[${autoStringSuffix(po)}]"
|
||||
active-value="${switch_extend_arr[0]}"
|
||||
inactive-value="${switch_extend_arr[1]}"
|
||||
/>
|
||||
</wd-cell>
|
||||
<#elseif po.classType =='list' || po.classType =='sel_search'>
|
||||
<online-select
|
||||
:label="get4Label('${po.filedComment}')"
|
||||
labelWidth="100px"
|
||||
type="${po.classType}"
|
||||
name=${autoStringSuffix(po)}
|
||||
dict="${form_field_dictCode}"
|
||||
v-model="myFormData[${autoStringSuffix(po)}]"
|
||||
></online-select>
|
||||
<#elseif po.classType =='checkbox'>
|
||||
<online-checkbox
|
||||
:label="get4Label('${po.filedComment}')"
|
||||
labelWidth="100px"
|
||||
type="${po.classType}"
|
||||
name=${autoStringSuffix(po)}
|
||||
dict="${form_field_dictCode}"
|
||||
v-model="myFormData[${autoStringSuffix(po)}]"
|
||||
></online-checkbox>
|
||||
<#elseif po.classType =='radio'>
|
||||
<online-radio
|
||||
:label="get4Label('${po.filedComment}')"
|
||||
labelWidth="100px"
|
||||
type="${po.classType}"
|
||||
name=${autoStringSuffix(po)}
|
||||
dict="${form_field_dictCode}"
|
||||
v-model="myFormData[${autoStringSuffix(po)}]"
|
||||
></online-radio>
|
||||
<#elseif po.classType =='list_multi'>
|
||||
<online-multi
|
||||
:label="get4Label('${po.filedComment}')"
|
||||
labelWidth="100px"
|
||||
type="${po.classType}"
|
||||
name=${autoStringSuffix(po)}
|
||||
dict="${form_field_dictCode}"
|
||||
v-model="myFormData[${autoStringSuffix(po)}]"
|
||||
></online-multi>
|
||||
<#elseif po.classType =='textarea' || po.classType =='markdown' || po.classType =='umeditor'>
|
||||
<wd-textarea
|
||||
:label="get4Label('${po.filedComment}')"
|
||||
labelWidth="100px"
|
||||
type="${po.classType}"
|
||||
name=${autoStringSuffix(po)}
|
||||
prop=${autoStringSuffix(po)}
|
||||
clearable
|
||||
:maxlength="300"
|
||||
v-model="myFormData[${autoStringSuffix(po)}]"
|
||||
></wd-textarea>
|
||||
<#elseif po.classType =='password'>
|
||||
<wd-input
|
||||
:label="get4Label('${po.filedComment}')"
|
||||
labelWidth="100px"
|
||||
show-password
|
||||
name=${autoStringSuffix(po)}
|
||||
prop=${autoStringSuffix(po)}
|
||||
clearable
|
||||
v-model="myFormData[${autoStringSuffix(po)}]"
|
||||
></wd-input>
|
||||
<#elseif po.classType =='popup_dict'>
|
||||
<PopupDict
|
||||
labelWidth="100px"
|
||||
:label="get4Label('${po.filedComment}')"
|
||||
v-model="myFormData[${autoStringSuffix(po)}]"
|
||||
:multi="${po.extendParams.popupMulti?c}"
|
||||
dictCode="${po.dictTable},${po.dictText},${po.dictField}"
|
||||
></PopupDict>
|
||||
<#elseif po.classType =='popup'>
|
||||
<#assign sourceFields = po.dictField?default("")?trim?split(",")/>
|
||||
<#assign targetFields = po.dictText?default("")?trim?split(",")/>
|
||||
<Popup
|
||||
labelWidth="100px"
|
||||
:label="get4Label('${po.filedComment}')"
|
||||
v-model="myFormData[${autoStringSuffix(po)}]"
|
||||
:multi="${po.extendParams.popupMulti?c}"
|
||||
code="${po.dictTable}"
|
||||
:setFieldsValue="setFieldsValue"
|
||||
:fieldConfig="[
|
||||
<#list sourceFields as fieldName>
|
||||
{ source: '${fieldName}', target: '${dashedToCamel(targetFields[fieldName_index])}' },
|
||||
</#list>
|
||||
]"
|
||||
></Popup>
|
||||
<#elseif po.classType =='link_table'>
|
||||
<online-popup-link-record
|
||||
:label="get4Label('${po.filedComment}')"
|
||||
labelWidth="100px"
|
||||
name=${autoStringSuffix(po)}
|
||||
:formSchema="getFormSchema('${po.dictTable}','${po.dictField}','${po.dictText}')"
|
||||
v-model:value="myFormData[${autoStringSuffix(po)}]"
|
||||
></online-popup-link-record>
|
||||
<#elseif po.classType =='sel_user'>
|
||||
<select-user
|
||||
labelWidth="100px"
|
||||
:label="get4Label('${po.filedComment}')"
|
||||
name=${autoStringSuffix(po)}
|
||||
v-model="myFormData[${autoStringSuffix(po)}]"
|
||||
<#if po.extendParams?exists && po.extendParams.text?exists>labelKey="${po.extendParams.text}"</#if>
|
||||
<#if po.extendParams?exists && po.extendParams.store?exists>rowKey="${po.extendParams.store}"</#if>
|
||||
></select-user>
|
||||
<#elseif po.classType =='sel_depart'>
|
||||
<select-dept
|
||||
labelWidth="100px"
|
||||
:label="get4Label('${po.filedComment}')"
|
||||
name=${autoStringSuffix(po)}
|
||||
v-model="myFormData[${autoStringSuffix(po)}]"
|
||||
<#if po.extendParams?exists && po.extendParams.text?exists>labelKey="${po.extendParams.text}"</#if>
|
||||
<#if po.extendParams?exists && po.extendParams.store?exists>rowKey="${po.extendParams.store}"</#if>
|
||||
:multiple="${po.extendParams.multi?default('true')}"
|
||||
></select-dept>
|
||||
<#elseif po.classType =='cat_tree'>
|
||||
<CategorySelect
|
||||
labelWidth="100px"
|
||||
:label="get4Label('${po.filedComment}')"
|
||||
v-model="myFormData[${autoStringSuffix(po)}]"
|
||||
pcode="${po.dictField?default("")}"
|
||||
></CategorySelect>
|
||||
<#elseif po.classType =='sel_tree'>
|
||||
<TreeSelect
|
||||
labelWidth="100px"
|
||||
:label="get4Label('${po.filedComment}')"
|
||||
v-model="myFormData[${autoStringSuffix(po)}]"
|
||||
<#if po.dictText??>
|
||||
<#if po.dictText?split(',')[2]?? && po.dictText?split(',')[0]??>
|
||||
dict="${po.dictTable},${po.dictText?split(',')[2]},${po.dictText?split(',')[0]}"
|
||||
<#elseif po.dictText?split(',')[1]??>
|
||||
pidField="${po.dictText?split(',')[1]}"
|
||||
<#elseif po.dictText?split(',')[3]??>
|
||||
hasChildField="${po.dictText?split(',')[3]}"
|
||||
</#if>
|
||||
</#if>
|
||||
:pidValue="`${po.dictField}`"
|
||||
></TreeSelect>
|
||||
<#elseif po.fieldDbType=='int' || po.fieldDbType=='double' || po.fieldDbType=='BigDecimal'>
|
||||
<wd-input
|
||||
label-width="100px"
|
||||
v-model="myFormData[${autoStringSuffix(po)}]"
|
||||
:label="get4Label('${po.filedComment}')"
|
||||
name=${autoStringSuffix(po)}
|
||||
prop=${autoStringSuffix(po)}
|
||||
placeholder="请选择${po.filedComment}"
|
||||
inputMode="numeric"
|
||||
:rules="[
|
||||
<#if po.fieldName != 'id'>
|
||||
<#assign fieldValidType = po.fieldValidType!''>
|
||||
<#-- 非空校验 -->
|
||||
<#if po.nullable == 'N' || fieldValidType == '*'>
|
||||
{ required: true, message: '请输入${po.filedComment}!'},
|
||||
<#elseif fieldValidType!=''>
|
||||
{ required: false},
|
||||
</#if>
|
||||
<#-- 6到16位数字 -->
|
||||
<#if fieldValidType == 'n6-16'>
|
||||
{ pattern: /^\d{6,16}$|^(?=\d+\.\d+)[\d.]{7,17}$/, message: '请输入6到16位数字!'},
|
||||
<#-- 6到16位任意字符 -->
|
||||
<#elseif fieldValidType == '*6-16'>
|
||||
{ pattern: /^.{6,16}$/, message: '请输入6到16位任意字符!'},
|
||||
<#-- 6到18位字母 -->
|
||||
<#elseif fieldValidType == 's6-18'>
|
||||
{ pattern: /^[a-z|A-Z]{6,18}$/, message: '请输入6到18位字母!'},
|
||||
<#-- 网址 -->
|
||||
<#elseif fieldValidType == 'url'>
|
||||
{ pattern: /^((ht|f)tps?):\/\/[\w\-]+(\.[\w\-]+)+([\w\-.,@?^=%&:\/~+#]*[\w\-@?^=%&\/~+#])?$/, message: '请输入正确的网址!'},
|
||||
<#-- 电子邮件 -->
|
||||
<#elseif fieldValidType == 'e'>
|
||||
{ pattern: /^([\w]+\.*)([\w]+)@[\w]+\.\w{3}(\.\w{2}|)$/, message: '请输入正确的电子邮件!'},
|
||||
<#-- 手机号码 -->
|
||||
<#elseif fieldValidType == 'm'>
|
||||
{ pattern: /^1[3456789]\d{9}$/, message: '请输入正确的手机号码!'},
|
||||
<#-- 邮政编码 -->
|
||||
<#elseif fieldValidType == 'p'>
|
||||
{ pattern: /^[0-9]\d{5}$/, message: '请输入正确的邮政编码!'},
|
||||
<#-- 字母 -->
|
||||
<#elseif fieldValidType == 's'>
|
||||
{ pattern: /^[A-Z|a-z]+$/, message: '请输入字母!'},
|
||||
<#-- 数字 -->
|
||||
<#elseif fieldValidType == 'n'>
|
||||
{ pattern: /^-?\d+\.?\d*$/, message: '请输入数字!'},
|
||||
<#-- 整数 -->
|
||||
<#elseif fieldValidType == 'z'>
|
||||
{ pattern: /^-?\d+$/, message: '请输入整数!'},
|
||||
<#-- 金额 -->
|
||||
<#elseif fieldValidType == 'money'>
|
||||
{ pattern: /^(([1-9][0-9]*)|([0]\.\d{0,2}|[1-9][0-9]*\.\d{0,2}))$/, message: '请输入正确的金额!'},
|
||||
<#-- 正则校验 -->
|
||||
<#elseif fieldValidType != '' && fieldValidType != '*'>
|
||||
{ pattern: '${fieldValidType}', message: '不符合校验规则!'},
|
||||
<#-- 无校验 -->
|
||||
<#else>
|
||||
<#t>
|
||||
</#if>
|
||||
</#if>
|
||||
]"
|
||||
clearable
|
||||
/>
|
||||
<#else>
|
||||
<wd-input
|
||||
label-width="100px"
|
||||
v-model="myFormData[${autoStringSuffix(po)}]"
|
||||
:label="get4Label('${po.filedComment}')"
|
||||
name=${autoStringSuffix(po)}
|
||||
prop=${autoStringSuffix(po)}
|
||||
placeholder="请选择${po.filedComment}"
|
||||
:rules="[
|
||||
<#if po.fieldName != 'id'>
|
||||
<#assign fieldValidType = po.fieldValidType!''>
|
||||
<#-- 非空校验 -->
|
||||
<#if po.nullable == 'N' || fieldValidType == '*'>
|
||||
{ required: true, message: '请输入${po.filedComment}!'},
|
||||
<#elseif fieldValidType!=''>
|
||||
{ required: false},
|
||||
</#if>
|
||||
<#-- 6到16位数字 -->
|
||||
<#if fieldValidType == 'n6-16'>
|
||||
{ pattern: /^\d{6,16}$|^(?=\d+\.\d+)[\d.]{7,17}$/, message: '请输入6到16位数字!'},
|
||||
<#-- 6到16位任意字符 -->
|
||||
<#elseif fieldValidType == '*6-16'>
|
||||
{ pattern: /^.{6,16}$/, message: '请输入6到16位任意字符!'},
|
||||
<#-- 6到18位字母 -->
|
||||
<#elseif fieldValidType == 's6-18'>
|
||||
{ pattern: /^[a-z|A-Z]{6,18}$/, message: '请输入6到18位字母!'},
|
||||
<#-- 网址 -->
|
||||
<#elseif fieldValidType == 'url'>
|
||||
{ pattern: /^((ht|f)tps?):\/\/[\w\-]+(\.[\w\-]+)+([\w\-.,@?^=%&:\/~+#]*[\w\-@?^=%&\/~+#])?$/, message: '请输入正确的网址!'},
|
||||
<#-- 电子邮件 -->
|
||||
<#elseif fieldValidType == 'e'>
|
||||
{ pattern: /^([\w]+\.*)([\w]+)@[\w]+\.\w{3}(\.\w{2}|)$/, message: '请输入正确的电子邮件!'},
|
||||
<#-- 手机号码 -->
|
||||
<#elseif fieldValidType == 'm'>
|
||||
{ pattern: /^1[3456789]\d{9}$/, message: '请输入正确的手机号码!'},
|
||||
<#-- 邮政编码 -->
|
||||
<#elseif fieldValidType == 'p'>
|
||||
{ pattern: /^[0-9]\d{5}$/, message: '请输入正确的邮政编码!'},
|
||||
<#-- 字母 -->
|
||||
<#elseif fieldValidType == 's'>
|
||||
{ pattern: /^[A-Z|a-z]+$/, message: '请输入字母!'},
|
||||
<#-- 数字 -->
|
||||
<#elseif fieldValidType == 'n'>
|
||||
{ pattern: /^-?\d+\.?\d*$/, message: '请输入数字!'},
|
||||
<#-- 整数 -->
|
||||
<#elseif fieldValidType == 'z'>
|
||||
{ pattern: /^-?\d+$/, message: '请输入整数!'},
|
||||
<#-- 金额 -->
|
||||
<#elseif fieldValidType == 'money'>
|
||||
{ pattern: /^(([1-9][0-9]*)|([0]\.\d{0,2}|[1-9][0-9]*\.\d{0,2}))$/, message: '请输入正确的金额!'},
|
||||
<#-- 正则校验 -->
|
||||
<#elseif fieldValidType != '' && fieldValidType != '*'>
|
||||
{ pattern: '${fieldValidType}', message: '不符合校验规则!'},
|
||||
<#-- 无校验 -->
|
||||
<#else>
|
||||
<#t>
|
||||
</#if>
|
||||
</#if>
|
||||
]"
|
||||
clearable
|
||||
/>
|
||||
</#if>
|
||||
</#if>
|
||||
</view>
|
||||
</#list>
|
||||
</wd-cell-group>
|
||||
</wd-form>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="footer">
|
||||
<wd-button :disabled="loading" block :loading="loading" @click="handleSubmit">提交</wd-button>
|
||||
</view>
|
||||
</PageLayout>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { http } from '@/utils/http'
|
||||
import { useToast } from 'wot-design-uni'
|
||||
import { useRouter } from '@/plugin/uni-mini-router'
|
||||
import { ref, onMounted, computed,reactive } from 'vue'
|
||||
import OnlineImage from '@/components/online/view/online-image.vue'
|
||||
import OnlineFile from '@/components/online/view/online-file.vue'
|
||||
import OnlineFileCustom from '@/components/online/view/online-file-custom.vue'
|
||||
import OnlineSelect from '@/components/online/view/online-select.vue'
|
||||
import OnlineTime from '@/components/online/view/online-time.vue'
|
||||
import OnlineDate from '@/components/online/view/online-date.vue'
|
||||
import OnlineRadio from '@/components/online/view/online-radio.vue'
|
||||
import OnlineCheckbox from '@/components/online/view/online-checkbox.vue'
|
||||
import OnlineMulti from '@/components/online/view/online-multi.vue'
|
||||
import OnlinePopupLinkRecord from '@/components/online/view/online-popup-link-record.vue'
|
||||
import SelectDept from '@/components/SelectDept/SelectDept.vue'
|
||||
import SelectUser from '@/components/SelectUser/SelectUser.vue'
|
||||
defineOptions({
|
||||
name: '${entityName}Form',
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
const toast = useToast()
|
||||
const router = useRouter()
|
||||
const form = ref(null)
|
||||
// 定义响应式数据
|
||||
const myFormData = reactive({})
|
||||
const loading = ref(false)
|
||||
const navTitle = ref('新增')
|
||||
const dataId = ref('')
|
||||
const backRouteName = ref('${entityName}List')
|
||||
// 定义 initForm 方法
|
||||
const initForm = (item) => {
|
||||
console.log('initForm item', item)
|
||||
if(item?.dataId){
|
||||
dataId.value = item.dataId;
|
||||
navTitle.value = item.dataId?'编辑':'新增';
|
||||
initData();
|
||||
}
|
||||
}
|
||||
// 初始化数据
|
||||
const initData = () => {
|
||||
http.get("/${entityPackagePath}/${entityName?uncap_first}/queryById",{id:dataId.value}).then((res) => {
|
||||
if (res.success) {
|
||||
let obj = res.result
|
||||
Object.assign(myFormData, { ...obj })
|
||||
}else{
|
||||
toast.error(res?.message || '表单加载失败!')
|
||||
}
|
||||
})
|
||||
}
|
||||
const handleSuccess = () => {
|
||||
uni.$emit('refreshList');
|
||||
router.back()
|
||||
}
|
||||
// 提交表单
|
||||
const handleSubmit = () => {
|
||||
let url = dataId.value?'/${entityPackagePath}/${entityName?uncap_first}/edit':'/${entityPackagePath}/${entityName?uncap_first}/add';
|
||||
form.value
|
||||
.validate()
|
||||
.then(({ valid, errors }) => {
|
||||
if (valid) {
|
||||
loading.value = true;
|
||||
http.post(url,myFormData).then((res) => {
|
||||
loading.value = false;
|
||||
if (res.success) {
|
||||
toast.success('保存成功');
|
||||
handleSuccess()
|
||||
}else{
|
||||
toast.error(res?.message || '表单保存失败!')
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error, 'error')
|
||||
loading.value = false;
|
||||
})
|
||||
}
|
||||
// 标题
|
||||
const get4Label = computed(() => {
|
||||
return (label) => {
|
||||
return label && label.length > 4 ? label.substring(0, 4) : label;
|
||||
}
|
||||
})
|
||||
|
||||
// 标题
|
||||
const getFormSchema = computed(() => {
|
||||
return (dictTable,dictCode,dictText) => {
|
||||
return {
|
||||
dictCode,
|
||||
dictTable,
|
||||
dictText
|
||||
};
|
||||
}
|
||||
})
|
||||
/**
|
||||
* 获取日期控件的扩展类型
|
||||
* @param picker
|
||||
* @returns {string}
|
||||
*/
|
||||
const getDateExtendType = (picker: string) => {
|
||||
let mapField = {
|
||||
month: 'year-month',
|
||||
year: 'year',
|
||||
quarter: 'quarter',
|
||||
week: 'week',
|
||||
day: 'date',
|
||||
}
|
||||
return picker && mapField[picker]
|
||||
? mapField[picker]
|
||||
: 'date'
|
||||
}
|
||||
//设置pop返回值
|
||||
const setFieldsValue = (data) => {
|
||||
Object.assign(myFormData, {...data })
|
||||
}
|
||||
// onLoad 生命周期钩子
|
||||
onLoad((option) => {
|
||||
initForm(option)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.footer {
|
||||
width: 100%;
|
||||
padding: 10px 20px;
|
||||
padding-bottom: calc(constant(safe-area-inset-bottom) + 10px);
|
||||
padding-bottom: calc(env(safe-area-inset-bottom) + 10px);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,148 @@
|
|||
<route lang="json5" type="page">
|
||||
{
|
||||
layout: 'default',
|
||||
style: {
|
||||
navigationBarTitleText: '${tableVo.ftlDescription}',
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
}
|
||||
</route>
|
||||
<template>
|
||||
<PageLayout navTitle="${tableVo.ftlDescription}" backRouteName="index" routeMethod="pushTab">
|
||||
<view class="wrap">
|
||||
<z-paging
|
||||
ref="paging"
|
||||
:fixed="false"
|
||||
v-model="dataList"
|
||||
@query="queryList"
|
||||
:default-page-size="15"
|
||||
>
|
||||
<template v-for="item in dataList" :key="item.id">
|
||||
<wd-swipe-action>
|
||||
<view class="list" @click="handleEdit(item)">
|
||||
<template v-for="(cItem, cIndex) in columns" :key="cIndex">
|
||||
<view v-if="cIndex < 3" class="box" :style="getBoxStyle">
|
||||
<view class="field ellipsis">{{ cItem.title }}</view>
|
||||
<view class="value cu-text-grey">{{ item[cItem.dataIndex] }}</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
<template #right>
|
||||
<view class="action">
|
||||
<view class="button" @click="handleAction('del', item)">删除</view>
|
||||
</view>
|
||||
</template>
|
||||
</wd-swipe-action>
|
||||
</template>
|
||||
</z-paging>
|
||||
<view class="add u-iconfont u-icon-add" @click="handleAdd"></view>
|
||||
</view>
|
||||
</PageLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { http } from '@/utils/http'
|
||||
import usePageList from '@/hooks/usePageList'
|
||||
import {columns} from './${entityName}Data';
|
||||
defineOptions({
|
||||
name: '${entityName}List',
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
}
|
||||
})
|
||||
//分页加载配置
|
||||
let { toast, router, paging, dataList, queryList } = usePageList('/${entityPackagePath}/${entityName?uncap_first}/list');
|
||||
|
||||
//样式
|
||||
const getBoxStyle = computed(() => {
|
||||
return { width: "calc(33% - 5px)" }
|
||||
})
|
||||
|
||||
// 其他操作
|
||||
const handleAction = (val, item) => {
|
||||
if (val == 'del') {
|
||||
http.delete("/${entityPackagePath}/${entityName?uncap_first}/delete?id="+item.id,{id:item.id}).then((res) => {
|
||||
toast.success('删除成功~')
|
||||
paging.value.reload()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// go 新增页
|
||||
const handleAdd = () => {
|
||||
router.push({
|
||||
name: '${entityName}Form'
|
||||
})
|
||||
}
|
||||
|
||||
//go 编辑页
|
||||
const handleEdit = (record) => {
|
||||
router.push({
|
||||
name: '${entityName}Form',
|
||||
params: {dataId: record.id},
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 监听刷新列表事件
|
||||
uni.$on('refreshList', () => {
|
||||
queryList(1,10)
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wrap {
|
||||
height: 100%;
|
||||
}
|
||||
:deep(.wd-swipe-action) {
|
||||
margin-top: 10px;
|
||||
background-color: #fff;
|
||||
}
|
||||
.list {
|
||||
padding: 10px 10px;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
.box {
|
||||
width: 33%;
|
||||
.field {
|
||||
margin-bottom: 10px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.action {
|
||||
width: 60px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
color: #fff;
|
||||
&:first-child {
|
||||
background-color: #fa4350;
|
||||
}
|
||||
}
|
||||
}
|
||||
.add {
|
||||
height: 70upx;
|
||||
width: 70upx;
|
||||
text-align: center;
|
||||
line-height: 70upx;
|
||||
background-color: #fff;
|
||||
border-radius: 50%;
|
||||
position: fixed;
|
||||
bottom: 80upx;
|
||||
right: 30upx;
|
||||
box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.1);
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
|
@ -40,7 +40,7 @@
|
|||
<seata.version>1.5.2</seata.version>
|
||||
|
||||
<xxl-job-core.version>2.4.1</xxl-job-core.version>
|
||||
<fastjson.version>2.0.56</fastjson.version>
|
||||
<fastjson.version>2.0.57</fastjson.version>
|
||||
<aviator.version>5.2.6</aviator.version>
|
||||
<pegdown.version>1.6.0</pegdown.version>
|
||||
<commonmark.version>0.17.0</commonmark.version>
|
||||
|
@ -56,7 +56,8 @@
|
|||
<dm8.version>8.1.1.49</dm8.version>
|
||||
|
||||
<!-- 积木报表-->
|
||||
<jimureport-spring-boot-starter.version>1.9.5</jimureport-spring-boot-starter.version>
|
||||
<jimureport-spring-boot-starter.version>1.9.5.1</jimureport-spring-boot-starter.version>
|
||||
<minidao.version>1.10.7</minidao.version>
|
||||
<!-- 持久层 -->
|
||||
<mybatis-plus.version>3.5.3.2</mybatis-plus.version>
|
||||
<dynamic-datasource-spring-boot-starter.version>4.1.3</dynamic-datasource-spring-boot-starter.version>
|
||||
|
@ -67,9 +68,9 @@
|
|||
<aliyun-java-sdk-dysmsapi.version>2.1.0</aliyun-java-sdk-dysmsapi.version>
|
||||
<aliyun.oss.version>3.11.2</aliyun.oss.version>
|
||||
<!-- shiro -->
|
||||
<shiro.version>1.13.0</shiro.version>
|
||||
<java-jwt.version>4.5.0</java-jwt.version>
|
||||
<shiro.version>2.0.4</shiro.version>
|
||||
<shiro-redis.version>3.2.3</shiro-redis.version>
|
||||
<java-jwt.version>4.5.0</java-jwt.version>
|
||||
<codegenerate.version>1.4.9</codegenerate.version>
|
||||
<autopoi-web.version>1.4.11</autopoi-web.version>
|
||||
<minio.version>8.0.3</minio.version>
|
||||
|
@ -254,7 +255,7 @@
|
|||
<dependency>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<artifactId>hibernate-re</artifactId>
|
||||
<version>3.8.0-GA</version>
|
||||
<version>3.8.0.2</version>
|
||||
</dependency>
|
||||
|
||||
<!--mongon db-->
|
||||
|
@ -359,7 +360,7 @@
|
|||
<dependency>
|
||||
<groupId>org.jeecgframework</groupId>
|
||||
<artifactId>weixin4j</artifactId>
|
||||
<version>2.0.2</version>
|
||||
<version>2.0.4</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>commons-beanutils</artifactId>
|
||||
|
@ -383,6 +384,22 @@
|
|||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- minidao -->
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework</groupId>
|
||||
<artifactId>minidao-spring-boot-starter</artifactId>
|
||||
<version>${minidao.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>druid</artifactId>
|
||||
<groupId>com.alibaba</groupId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<artifactId>jsqlparser</artifactId>
|
||||
<groupId>com.github.jsqlparser</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- 积木报表-->
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework.jimureport</groupId>
|
||||
|
@ -418,7 +435,7 @@
|
|||
<dependency>
|
||||
<groupId>org.jeecgframework.jimureport</groupId>
|
||||
<artifactId>jimureport-nosql-starter</artifactId>
|
||||
<version>${jimureport-spring-boot-starter.version}</version>
|
||||
<version>1.9.5.2</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.calcite</groupId>
|
||||
|
@ -430,7 +447,7 @@
|
|||
<dependency>
|
||||
<groupId>org.jeecgframework.jimureport</groupId>
|
||||
<artifactId>jimubi-spring-boot-starter</artifactId>
|
||||
<version>1.9.4</version>
|
||||
<version>1.9.5</version>
|
||||
</dependency>
|
||||
<!-- AI集成 -->
|
||||
<dependency>
|
||||
|
|
|
@ -27,6 +27,7 @@ export { default as JDictSelectTag } from './src/jeecg/components/JDictSelectTag
|
|||
export { default as JTreeSelect } from './src/jeecg/components/JTreeSelect.vue';
|
||||
export { default as JSearchSelect } from './src/jeecg/components/JSearchSelect.vue';
|
||||
export { default as JSelectUserByDept } from './src/jeecg/components/JSelectUserByDept.vue';
|
||||
export { default as JSelectUserByDepartment } from './src/jeecg/components/JSelectUserByDepartment.vue';
|
||||
export { default as JEditor } from './src/jeecg/components/JEditor.vue';
|
||||
export { default as JImageUpload } from './src/jeecg/components/JImageUpload.vue';
|
||||
// Jeecg自定义校验
|
||||
|
|
|
@ -64,6 +64,7 @@ import JInput from './jeecg/components/JInput.vue';
|
|||
import JTreeSelect from './jeecg/components/JTreeSelect.vue';
|
||||
import JEllipsis from './jeecg/components/JEllipsis.vue';
|
||||
import JSelectUserByDept from './jeecg/components/JSelectUserByDept.vue';
|
||||
import JSelectUserByDepartment from './jeecg/components/JSelectUserByDepartment.vue';
|
||||
import JUpload from './jeecg/components/JUpload/JUpload.vue';
|
||||
import JSearchSelect from './jeecg/components/JSearchSelect.vue';
|
||||
import JAddInput from './jeecg/components/JAddInput.vue';
|
||||
|
@ -159,6 +160,7 @@ componentMap.set('JInput', JInput);
|
|||
componentMap.set('JTreeSelect', JTreeSelect);
|
||||
componentMap.set('JEllipsis', JEllipsis);
|
||||
componentMap.set('JSelectUserByDept', JSelectUserByDept);
|
||||
componentMap.set('JSelectUserByDepartment', JSelectUserByDepartment);
|
||||
componentMap.set('JUpload', JUpload);
|
||||
componentMap.set('JSearchSelect', JSearchSelect);
|
||||
componentMap.set('JAddInput', JAddInput);
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
<!--用户选择组件-->
|
||||
<template>
|
||||
<div>
|
||||
<JSelectBiz @handleOpen="handleOpen" :loading="loadingEcho" v-bind="attrs" @change="handleSelectChange"></JSelectBiz>
|
||||
<JSelectUserByDepartmentModal
|
||||
v-if="modalShow"
|
||||
:selectedUser="selectOptions"
|
||||
:modalTitle="modalTitle"
|
||||
:rowKey="rowKey"
|
||||
:labelKey="labelKey"
|
||||
:isRadioSelection="isRadioSelection"
|
||||
:params="params"
|
||||
@register="regModal"
|
||||
@change="setValue"
|
||||
@close="() => (modalShow = false)"
|
||||
v-bind="attrs"
|
||||
></JSelectUserByDepartmentModal>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch, provide } from 'vue';
|
||||
import JSelectUserByDepartmentModal from './modal/JSelectUserByDepartmentModal.vue';
|
||||
import JSelectBiz from './base/JSelectBiz.vue';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
import { useAttrs } from '/@/hooks/core/useAttrs';
|
||||
import { SelectValue } from 'ant-design-vue/es/select';
|
||||
import { isArray, isString, isObject } from '/@/utils/is';
|
||||
import { getTableList as getTableListOrigin } from '/@/api/common/api';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
|
||||
defineOptions({ name: 'JSelectUserByDepartment' });
|
||||
const props = defineProps({
|
||||
value: propTypes.oneOfType([propTypes.string, propTypes.array]),
|
||||
modalTitle: {
|
||||
type: String,
|
||||
default: '部门用户选择',
|
||||
},
|
||||
rowKey: {
|
||||
type: String,
|
||||
default: 'username',
|
||||
},
|
||||
labelKey: {
|
||||
type: String,
|
||||
default: 'realname',
|
||||
},
|
||||
//查询参数
|
||||
params: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
isRadioSelection: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['options-change', 'change', 'update:value']);
|
||||
const { createMessage } = useMessage();
|
||||
//注册model
|
||||
const [regModal, { openModal }] = useModal();
|
||||
// 是否显示弹窗
|
||||
const modalShow = ref(false);
|
||||
//下拉框选项值
|
||||
const selectOptions: any = ref<SelectValue>([]);
|
||||
//下拉框选中值
|
||||
let selectValues: any = reactive<object>({
|
||||
value: [],
|
||||
change: false,
|
||||
});
|
||||
// 是否正在加载回显数据
|
||||
const loadingEcho = ref<boolean>(false);
|
||||
//下发 selectOptions,xxxBiz组件接收
|
||||
provide('selectOptions', selectOptions);
|
||||
//下发 selectValues,xxxBiz组件接收
|
||||
provide('selectValues', selectValues);
|
||||
//下发 loadingEcho,xxxBiz组件接收
|
||||
provide('loadingEcho', loadingEcho);
|
||||
|
||||
const attrs: any = useAttrs();
|
||||
|
||||
// 打开弹窗
|
||||
function handleOpen() {
|
||||
modalShow.value = true;
|
||||
setTimeout(() => {
|
||||
openModal(true, {
|
||||
isUpdate: false,
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
const handleSelectChange = (data) => {
|
||||
selectOptions.value = selectOptions.value.filter((item) => data.includes(item[props.rowKey]));
|
||||
setValue(selectOptions.value);
|
||||
};
|
||||
// 设置下拉框的值
|
||||
const setValue = (data) => {
|
||||
selectOptions.value = data.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
label: item[props.labelKey],
|
||||
value: item[props.rowKey],
|
||||
};
|
||||
});
|
||||
selectValues.value = data.map((item) => item[props.rowKey]);
|
||||
// 更新value
|
||||
emit('update:value', selectValues.value);
|
||||
// 触发change事件(不转是因为basicForm提交时会自动将字符串转化为数组)
|
||||
emit('change', selectValues.value);
|
||||
// 触发options-change事件
|
||||
emit('options-change', selectOptions.value);
|
||||
};
|
||||
// 翻译
|
||||
const transform = () => {
|
||||
let value = props.value;
|
||||
let len;
|
||||
if (isArray(value) || isString(value)) {
|
||||
if (isArray(value)) {
|
||||
len = value.length;
|
||||
value = value.join(',');
|
||||
} else {
|
||||
len = value.split(',').length;
|
||||
}
|
||||
value = value.trim();
|
||||
if (value) {
|
||||
// 如果value的值在selectedUser中存在,则不请求翻译
|
||||
let isNotRequestTransform = false;
|
||||
isNotRequestTransform = value.split(',').every((value) => !!selectOptions.value.find((item) => item[props.rowKey] === value));
|
||||
if (isNotRequestTransform) {
|
||||
selectValues.value = value.split(',')
|
||||
return;
|
||||
}
|
||||
const params = { isMultiTranslate: true, pageSize: len, [props.rowKey]: value };
|
||||
if (isObject(attrs.params)) {
|
||||
Object.assign(params, attrs.params);
|
||||
}
|
||||
getTableListOrigin(params).then((result: any) => {
|
||||
const records = result.records ?? [];
|
||||
selectValues.value = records.map((item) => item[props.rowKey]);
|
||||
selectOptions.value = records.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
label: item[props.labelKey],
|
||||
value: item[props.rowKey],
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
selectValues.value = [];
|
||||
}
|
||||
};
|
||||
// 监听value变化
|
||||
watch(
|
||||
() => props.value,
|
||||
() => {
|
||||
transform();
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.j-select-row {
|
||||
@width: 82px;
|
||||
.left {
|
||||
width: calc(100% - @width - 8px);
|
||||
}
|
||||
.right {
|
||||
width: @width;
|
||||
}
|
||||
.full {
|
||||
width: 100%;
|
||||
}
|
||||
:deep(.ant-select-search__field) {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,833 @@
|
|||
<template>
|
||||
<BasicModal
|
||||
wrapClassName="JSelectUserByDepartmentModal"
|
||||
v-bind="$attrs"
|
||||
@register="register"
|
||||
:title="modalTitle"
|
||||
width="800px"
|
||||
@ok="handleOk"
|
||||
destroyOnClose
|
||||
@visible-change="visibleChange"
|
||||
>
|
||||
<div class="j-select-user-by-dept">
|
||||
<div class="modal-content">
|
||||
<!-- 左侧搜索和组织列表 -->
|
||||
<div class="left-content">
|
||||
<!-- 搜索框 -->
|
||||
<div class="search-box">
|
||||
<a-input v-model:value.trim="searchText" placeholder="搜索" @change="handleSearch" @pressEnter="handleSearch" allowClear />
|
||||
</div>
|
||||
<!-- 组织架构 -->
|
||||
<div class="tree-box">
|
||||
<template v-if="searchText.length">
|
||||
<template v-if="searchResult.depart.length || searchResult.user.length">
|
||||
<div class="search-result">
|
||||
<template v-if="searchResult.user.length">
|
||||
<div class="search-user">
|
||||
<p class="search-user-title">人员</p>
|
||||
<template v-for="item in searchResult.user" :key="item.id">
|
||||
<div class="search-user-item" @click="handleSearchUserCheck(item)">
|
||||
<a-checkbox v-model:checked="item.checked" />
|
||||
<div class="right">
|
||||
<div class="search-user-item-circle">
|
||||
<img v-if="item.avatar" :src="getFileAccessHttpUrl(item.avatar)" alt="avatar" />
|
||||
</div>
|
||||
<div class="search-user-item-info">
|
||||
<div class="search-user-item-name">{{ item.realname }}</div>
|
||||
<div class="search-user-item-org">{{ item.orgCodeTxt }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="searchResult.depart.length">
|
||||
<div class="search-depart">
|
||||
<p class="search-depart-title">部门</p>
|
||||
<template v-for="item in searchResult.depart" :key="item.id">
|
||||
<div class="search-depart-item" @click="handleSearchDepartClick(item)">
|
||||
<a-checkbox v-model:checked="item.checked" @click.stop @change="($event) => handleSearchDepartCheck($event, item)" />
|
||||
<div class="search-depart-item-name">{{ item.departName }}</div>
|
||||
<RightOutlined />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="no-data">
|
||||
<a-empty description="暂无数据" />
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-breadcrumb v-if="breadcrumb.length">
|
||||
<a-breadcrumb-item @click="handleBreadcrumbClick()">
|
||||
<HomeOutlined />
|
||||
</a-breadcrumb-item>
|
||||
<template v-for="item in breadcrumb" :key="item?.id">
|
||||
<a-breadcrumb-item @click="handleBreadcrumbClick(item)">
|
||||
<span>{{ item.departName }}</span>
|
||||
</a-breadcrumb-item>
|
||||
</template>
|
||||
</a-breadcrumb>
|
||||
<div v-if="currentDepartUsers.length">
|
||||
<!-- 当前部门用户树 -->
|
||||
<div class="depart-users-tree">
|
||||
<div v-if="!currentDepartTree.length" class="allChecked">
|
||||
<a-checkbox v-model:checked="currentDepartAllUsers" @change="handleAllUsers">全选</a-checkbox>
|
||||
</div>
|
||||
<template v-for="item in currentDepartUsers" :key="item.id">
|
||||
<div class="depart-users-tree-item" @click="handleDepartUsersTreeCheck(item)">
|
||||
<a-checkbox v-model:checked="item.checked" />
|
||||
<div class="right">
|
||||
<div class="depart-users-tree-item-circle">
|
||||
<img v-if="item.avatar" :src="getFileAccessHttpUrl(item.avatar)" alt="avatar" />
|
||||
</div>
|
||||
<div class="depart-users-tree-item-name">{{ item.realname }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 部门树 -->
|
||||
<div v-if="currentDepartTree.length" class="depart-tree">
|
||||
<template v-for="item in currentDepartTree" :key="item.id">
|
||||
<div class="depart-tree-item" @click="handleDepartTreeClick(item)">
|
||||
<a-checkbox v-model:checked="item.checked" @click.stop @change="($event) => handleDepartTreeCheck($event, item)" />
|
||||
<div class="depart-tree-item-name">{{ item.departName }}</div>
|
||||
<RightOutlined />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="currentDepartTree.length === 0 && currentDepartUsers.length === 0" class="no-data">
|
||||
<a-empty description="暂无数据" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右侧已选人员展示 -->
|
||||
<div class="right-content">
|
||||
<div class="selected-header"> 已选人员:{{ selectedUsers.length }}人 </div>
|
||||
<div class="selected-users">
|
||||
<div class="content">
|
||||
<div v-for="user in selectedUsers" :key="user.id" class="user-avatar" @click="handleDelUser(user)">
|
||||
<div class="avatar-circle">
|
||||
<img v-if="user.avatar" :src="getFileAccessHttpUrl(user.avatar)" alt="avatar" />
|
||||
<div class="mask">
|
||||
<CloseOutlined></CloseOutlined>
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-name">{{ user.realname }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BasicModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue';
|
||||
import { RightOutlined, HomeOutlined, CloseOutlined } from '@ant-design/icons-vue';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { queryTreeList, getTableList as getTableListOrigin } from '/@/api/common/api';
|
||||
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
|
||||
import { isArray } from '/@/utils/is';
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
|
||||
defineOptions({ name: 'JSelectUserByDepartmentModal' });
|
||||
const props = defineProps({
|
||||
// ...selectProps,
|
||||
//回传value字段名
|
||||
rowKey: {
|
||||
type: String,
|
||||
default: 'id',
|
||||
},
|
||||
//回传文本字段名
|
||||
labelKey: {
|
||||
type: String,
|
||||
default: 'name',
|
||||
},
|
||||
modalTitle: {
|
||||
type: String,
|
||||
default: '部门用户选择',
|
||||
},
|
||||
selectedUser: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
//查询参数
|
||||
params: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
//最大选择数量
|
||||
maxSelectCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
// 是否单选
|
||||
isRadioSelection: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['close', 'register', 'change']);
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
const { createMessage } = useMessage();
|
||||
// 搜索文本
|
||||
const searchText = ref('');
|
||||
const breadcrumb = ref<any[]>([]);
|
||||
// 部门树(整颗树)
|
||||
const departTree = ref([]);
|
||||
// 当前部门树
|
||||
const currentDepartTree = ref<any[]>([]);
|
||||
// 选中的部门节点
|
||||
const checkedDepartIds = ref<string[]>([]);
|
||||
// 当前部门用户
|
||||
const currentDepartUsers = ref([]);
|
||||
// 已选用户
|
||||
const selectedUsers = ref<any[]>([]);
|
||||
// 全选
|
||||
const currentDepartAllUsers = ref(false);
|
||||
// 搜索结构
|
||||
const searchResult: any = reactive({
|
||||
depart: [],
|
||||
user: [],
|
||||
});
|
||||
// 映射部门和人员的关系
|
||||
const cacheDepartUser = {};
|
||||
//注册弹框
|
||||
const [register, { closeModal }] = useModalInner(async (data) => {
|
||||
// 初始化
|
||||
if (props.selectedUser.length) {
|
||||
// 编辑时,传进来已选中的数据
|
||||
selectedUsers.value = props.selectedUser;
|
||||
}
|
||||
getQueryTreeList();
|
||||
});
|
||||
const visibleChange = (visible) => {
|
||||
if (visible === false) {
|
||||
setTimeout(() => {
|
||||
emit('close');
|
||||
}, 300);
|
||||
}
|
||||
};
|
||||
const handleOk = () => {
|
||||
if (selectedUsers.value.length == 0) {
|
||||
createMessage.warning('请选择人员');
|
||||
return;
|
||||
}
|
||||
if (props.isRadioSelection && selectedUsers.value.length > 1) {
|
||||
createMessage.warning('只允许选择一个用户');
|
||||
return;
|
||||
}
|
||||
if (props.maxSelectCount && selectedUsers.value.length > props.maxSelectCount) {
|
||||
createMessage.warning(`最多只能选择${props.maxSelectCount}个用户`);
|
||||
return;
|
||||
}
|
||||
emit('change', selectedUsers.value);
|
||||
closeModal();
|
||||
};
|
||||
// 搜索人员/部门
|
||||
const handleSearch = () => {
|
||||
if (searchText.value) {
|
||||
defHttp
|
||||
.get({
|
||||
url: `/sys/user/listAll`,
|
||||
params: {
|
||||
column: 'createTime',
|
||||
order: 'desc',
|
||||
pageNo: 1,
|
||||
pageSize: 100,
|
||||
realname: `*${searchText.value}*`,
|
||||
},
|
||||
})
|
||||
.then((result: any) => {
|
||||
result.records?.forEach((item) => {
|
||||
const findItem = selectedUsers.value.find((user) => user.id == item.id);
|
||||
if (findItem) {
|
||||
// 能在右侧找到说明选中了,左侧同样需要选中。
|
||||
item.checked = true;
|
||||
} else {
|
||||
item.checked = false;
|
||||
}
|
||||
});
|
||||
searchResult.user = result.records ?? [];
|
||||
});
|
||||
searchResult.depart = getDepartByName(searchText.value) ?? [];
|
||||
} else {
|
||||
searchResult.user = [];
|
||||
searchResult.depart = [];
|
||||
}
|
||||
};
|
||||
// 面包屑
|
||||
const handleBreadcrumbClick = (item?) => {
|
||||
// 先清空
|
||||
currentDepartUsers.value = [];
|
||||
if (item) {
|
||||
const findIndex = breadcrumb.value.findIndex((o) => o.id === item.id);
|
||||
if (findIndex != -1) {
|
||||
breadcrumb.value = breadcrumb.value.filter((item, index) => {
|
||||
console.log(item);
|
||||
return index <= findIndex;
|
||||
});
|
||||
}
|
||||
const data = getDepartTreeNodeById(item.id, departTree.value);
|
||||
currentDepartTree.value = data.children;
|
||||
} else {
|
||||
// 根节点
|
||||
currentDepartTree.value = departTree.value;
|
||||
breadcrumb.value = [];
|
||||
}
|
||||
};
|
||||
// 点击部门树复选框触发
|
||||
const handleDepartTreeCheck = (e, item) => {
|
||||
const { target } = e;
|
||||
if (target.checked) {
|
||||
// 选中
|
||||
getUsersByDeptId(item['id']).then((users) => {
|
||||
addUsers(users);
|
||||
});
|
||||
checkedDepartIds.value.push((item as any).id);
|
||||
// 检查父节点下所有子节点是否选中
|
||||
const parentItem = getDepartTreeParentById(item.id);
|
||||
if (parentItem?.children) {
|
||||
const isChildAllChecked = parentItem.children.every((item) => item.checked);
|
||||
if (isChildAllChecked) {
|
||||
parentItem.checked = true;
|
||||
} else {
|
||||
parentItem.checked = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 取消选中
|
||||
const findIndex = checkedDepartIds.value.findIndex((o: any) => o.id === item.id);
|
||||
if (findIndex != -1) {
|
||||
checkedDepartIds.value.splice(findIndex, 1);
|
||||
}
|
||||
// 如果父节点是选中,则需要取消
|
||||
const parentItem = getDepartTreeParentById(item.id);
|
||||
if (parentItem) {
|
||||
parentItem.checked = false;
|
||||
}
|
||||
getUsersByDeptId(item['id']).then((users) => {
|
||||
users.forEach((item) => {
|
||||
const findIndex = selectedUsers.value.findIndex((user) => user.id === item.id);
|
||||
if (findIndex != -1) {
|
||||
selectedUsers.value.splice(findIndex, 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
// 点击部门树节点触发
|
||||
const handleDepartTreeClick = (item) => {
|
||||
breadcrumb.value = [...breadcrumb.value, item];
|
||||
if (item.children) {
|
||||
// 有子节点,则显示部门
|
||||
if (item.checked) {
|
||||
// 父节点勾选,则子节点全部勾选
|
||||
item.children.forEach((item) => {
|
||||
item.checked = true;
|
||||
});
|
||||
}
|
||||
currentDepartTree.value = item.children;
|
||||
defHttp
|
||||
.get({
|
||||
url: '/sys/sysDepart/getUsersByDepartId',
|
||||
params: {
|
||||
id: item['id'],
|
||||
},
|
||||
})
|
||||
.then((res: any) => {
|
||||
const result = res ?? [];
|
||||
if (item.checked) {
|
||||
// 父节点勾选,则默认勾选
|
||||
result.forEach((item) => {
|
||||
item.checked = true;
|
||||
});
|
||||
}
|
||||
// 右侧勾选了,则默认勾选(用户存在多部门,在别的部门被选中了)
|
||||
if (selectedUsers.value.length) {
|
||||
result.forEach((item) => {
|
||||
const findItem = selectedUsers.value.find((user) => user.id === item.id);
|
||||
if (findItem) {
|
||||
// 说明在selectedUsers中被找到
|
||||
item.checked = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
currentDepartUsers.value = result;
|
||||
});
|
||||
} else {
|
||||
// 没有子节点,则显示用户
|
||||
currentDepartTree.value = [];
|
||||
getTableList({
|
||||
departId: item['id'],
|
||||
}).then((res: any) => {
|
||||
if (res?.records) {
|
||||
let checked = true;
|
||||
res.records.forEach((item) => {
|
||||
const findItem = selectedUsers.value.find((user) => user.id == item.id);
|
||||
if (findItem) {
|
||||
// 能在右侧找到说明选中了,左侧同样需要选中。
|
||||
item.checked = true;
|
||||
} else {
|
||||
item.checked = false;
|
||||
checked = false;
|
||||
}
|
||||
});
|
||||
currentDepartAllUsers.value = checked;
|
||||
currentDepartUsers.value = res.records;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
// 点击部门用户树复选框触发
|
||||
const handleDepartUsersTreeCheck = (item) => {
|
||||
item.checked = !item.checked;
|
||||
if (item.checked) {
|
||||
addUsers(item);
|
||||
} else {
|
||||
selectedUsers.value = selectedUsers.value.filter((user) => user.id !== item.id);
|
||||
}
|
||||
if (item.checked == false) {
|
||||
// 有一个是false,则全选false
|
||||
currentDepartAllUsers.value = false;
|
||||
}
|
||||
};
|
||||
// 全选
|
||||
const handleAllUsers = ({ target }) => {
|
||||
const { checked } = target;
|
||||
if (checked) {
|
||||
currentDepartUsers.value.forEach((item: any) => (item.checked = true));
|
||||
addUsers(currentDepartUsers.value);
|
||||
} else {
|
||||
currentDepartUsers.value.forEach((item: any) => (item.checked = false));
|
||||
selectedUsers.value = selectedUsers.value.filter((user) => {
|
||||
const userId = user.id;
|
||||
const findItem = currentDepartUsers.value.find((item: any) => item.id === userId);
|
||||
if (findItem) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
// 删除人员
|
||||
const handleDelUser = (item) => {
|
||||
const findIndex = selectedUsers.value.findIndex((user) => user.id === item.id);
|
||||
if (findIndex != -1) {
|
||||
selectedUsers.value.splice(findIndex, 1);
|
||||
}
|
||||
const findItem: any = currentDepartUsers.value.find((user: any) => user.id === item.id);
|
||||
if (findItem) {
|
||||
findItem.checked = false;
|
||||
currentDepartAllUsers.value = false;
|
||||
}
|
||||
};
|
||||
// 点击搜索用户复选框
|
||||
const handleSearchUserCheck = (item) => {
|
||||
item.checked = !item.checked;
|
||||
if (item.checked) {
|
||||
addUsers(item);
|
||||
} else {
|
||||
selectedUsers.value = selectedUsers.value.filter((user) => user.id !== item.id);
|
||||
}
|
||||
};
|
||||
// 点击搜索部门复选框
|
||||
const handleSearchDepartCheck = (e, item) => {
|
||||
handleDepartTreeCheck(e, item);
|
||||
};
|
||||
// 点击搜索部门
|
||||
const handleSearchDepartClick = (item) => {
|
||||
searchResult.depart = [];
|
||||
searchResult.user = [];
|
||||
breadcrumb.value = getPathToNodeById(item.id);
|
||||
handleDepartTreeClick(item);
|
||||
};
|
||||
// 添加人员到右侧
|
||||
const addUsers = (users) => {
|
||||
let newUsers: any = [];
|
||||
if (isArray(users)) {
|
||||
// selectedUsers里面没有才添加(防止重复)
|
||||
newUsers = users.filter((user: any) => !selectedUsers.value.find((item) => item.id === user.id));
|
||||
} else {
|
||||
if (!selectedUsers.value.find((user) => user.id === users.id)) {
|
||||
// selectedUsers里面没有才添加(防止重复)
|
||||
newUsers = [users];
|
||||
}
|
||||
}
|
||||
selectedUsers.value = [...selectedUsers.value, ...newUsers];
|
||||
const result = currentDepartUsers.value.every((item: any) => !!item.checked);
|
||||
currentDepartAllUsers.value = result;
|
||||
};
|
||||
// 解析参数
|
||||
const parseParams = (params) => {
|
||||
if (props?.params) {
|
||||
return {
|
||||
...params,
|
||||
...props.params,
|
||||
};
|
||||
}
|
||||
return params;
|
||||
};
|
||||
const getQueryTreeList = (params?) => {
|
||||
params = parseParams(params);
|
||||
queryTreeList({ ...params }).then((res) => {
|
||||
if (res) {
|
||||
departTree.value = res;
|
||||
currentDepartTree.value = res;
|
||||
}
|
||||
});
|
||||
};
|
||||
// 根据部门id获取用户
|
||||
const getTableList = (params) => {
|
||||
params = parseParams(params);
|
||||
return getTableListOrigin({ ...params });
|
||||
};
|
||||
const getUsersByDeptId = (id) => {
|
||||
return new Promise<any[]>((resolve) => {
|
||||
if (cacheDepartUser[id]) {
|
||||
resolve(cacheDepartUser[id]);
|
||||
} else {
|
||||
getTableList({
|
||||
departId: id,
|
||||
}).then((res: any) => {
|
||||
cacheDepartUser[id] = res.records ?? [];
|
||||
if (res?.records?.length) {
|
||||
resolve(res.records ?? []);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
// 根据id获取根节点到当前节点路径
|
||||
const getPathToNodeById = (id: string, tree = departTree.value, path = []): any[] => {
|
||||
for (const node of tree) {
|
||||
if ((node as any).id === id) {
|
||||
return [...path];
|
||||
}
|
||||
if ((node as any).children) {
|
||||
const foundPath = getPathToNodeById(id, (node as any).children, [...path, node]);
|
||||
if (foundPath.length) {
|
||||
return foundPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
return [];
|
||||
};
|
||||
// 根据id获取部门树父节点数据
|
||||
const getDepartTreeParentById = (id: string, tree = departTree.value, parent = null): any => {
|
||||
for (const node of tree) {
|
||||
if ((node as any).id === id) {
|
||||
return parent;
|
||||
}
|
||||
if ((node as any).children) {
|
||||
const found = getDepartTreeParentById(id, (node as any).children, node);
|
||||
if (found) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
// 通过名称搜索部门支持模糊
|
||||
const getDepartByName = (name: string, tree = departTree.value): any[] => {
|
||||
const result: any[] = [];
|
||||
const search = (nodes: any[]) => {
|
||||
for (const node of nodes) {
|
||||
if (node.departName?.toLowerCase().includes(name.toLowerCase())) {
|
||||
result.push(node);
|
||||
}
|
||||
if (node.children?.length) {
|
||||
search(node.children);
|
||||
}
|
||||
}
|
||||
};
|
||||
search(tree);
|
||||
return result;
|
||||
};
|
||||
// 根据id获取部门树当前节点数据
|
||||
const getDepartTreeNodeById = (id: string, tree = departTree.value): any => {
|
||||
for (const node of tree) {
|
||||
if ((node as any).id === id) {
|
||||
return node;
|
||||
}
|
||||
if ((node as any).children) {
|
||||
const found = getDepartTreeNodeById(id, (node as any).children);
|
||||
if (found) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
</script>
|
||||
<style lang="less">
|
||||
.JSelectUserByDepartmentModal {
|
||||
.scroll-container {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="less" scoped>
|
||||
.j-select-user-by-dept {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.modal-content {
|
||||
display: flex;
|
||||
padding: 20px;
|
||||
padding-bottom: 0;
|
||||
padding-left: 0;
|
||||
height: 400px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.left-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
border-right: 1px solid #e8e8e8;
|
||||
.search-box {
|
||||
margin: 0 16px 16px 16px;
|
||||
}
|
||||
:deep(.ant-breadcrumb) {
|
||||
font-size: 12px;
|
||||
margin-left: 16px;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
li {
|
||||
.ant-breadcrumb-link {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
&:last-child {
|
||||
.ant-breadcrumb-link {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.tree-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
.no-data {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
.depart-tree {
|
||||
.depart-tree-item {
|
||||
padding: 0 16px;
|
||||
line-height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #f4f6fa;
|
||||
}
|
||||
}
|
||||
.depart-tree-item-name {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin: 0 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.depart-users-tree {
|
||||
.allChecked {
|
||||
padding: 0 16px;
|
||||
margin-bottom: 16px;
|
||||
padding-top: 12px;
|
||||
:deep(.ant-checkbox-wrapper) {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
.depart-users-tree-item {
|
||||
line-height: 50px;
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #f4f6fa;
|
||||
}
|
||||
.right {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 8px;
|
||||
}
|
||||
.depart-users-tree-item-circle {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
background-color: #aaa;
|
||||
overflow: hidden;
|
||||
img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.depart-users-tree-item-name {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.search-depart {
|
||||
margin-bottom: 8px;
|
||||
.search-depart-title {
|
||||
padding-left: 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.search-depart-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #f4f6fa;
|
||||
}
|
||||
.search-depart-item-name {
|
||||
margin-left: 8px;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-user {
|
||||
margin-bottom: 8px;
|
||||
.search-user-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
padding-left: 16px;
|
||||
}
|
||||
.search-user-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #f4f6fa;
|
||||
}
|
||||
.right {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 8px;
|
||||
}
|
||||
.search-user-item-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
margin-left: 8px;
|
||||
}
|
||||
.search-user-item-circle {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
background-color: #aaa;
|
||||
}
|
||||
.search-user-item-name {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.search-user-item-org {
|
||||
color: #999;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
.right-content {
|
||||
width: 400px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-left: 16px;
|
||||
.selected-header {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.selected-users {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.content {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
.user-avatar {
|
||||
text-align: center;
|
||||
width: 70px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.avatar-circle {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
margin: 0 auto 8px;
|
||||
position: relative;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
&:hover {
|
||||
.mask {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.mask {
|
||||
opacity: 0;
|
||||
transition: opacity;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
font-size: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
.user-name {
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -139,6 +139,7 @@ export type ComponentType =
|
|||
| 'JTreeSelect'
|
||||
| 'JEllipsis'
|
||||
| 'JSelectUserByDept'
|
||||
| 'JSelectUserByDepartment'
|
||||
| 'JUpload'
|
||||
| 'JSearchSelect'
|
||||
| 'JAddInput'
|
||||
|
|
|
@ -39,6 +39,12 @@ export function registerJVxeTable(app: App) {
|
|||
function preventClosingPopUp(this: any, params) {
|
||||
// 获取组件增强
|
||||
let col = params.column.params;
|
||||
// update-begin--author:liaozhiyang---date:20250429---for:【issues/8178】使用原生vxe-table组件编辑模式下失去焦点报错
|
||||
if (col === undefined) {
|
||||
// 说明使用的是纯原生的vxe-table
|
||||
return;
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20250429---for:【issues/8178】使用原生vxe-table组件编辑模式下失去焦点报错
|
||||
let { $event } = params;
|
||||
const interceptor = getEnhanced(col.type).interceptor;
|
||||
// 执行增强
|
||||
|
|
|
@ -258,6 +258,20 @@ export const schemas: FormSchema[] = [
|
|||
label: '选中用户',
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
{
|
||||
field: 'user4',
|
||||
component: 'JSelectUserByDepartment',
|
||||
label: '部门选择用户',
|
||||
helpMessage: ['component模式'],
|
||||
defaultValue: '',
|
||||
componentProps: {
|
||||
labelKey: 'realname',
|
||||
rowKey: 'username',
|
||||
},
|
||||
colProps: {
|
||||
span: 12,
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'role2',
|
||||
component: 'JSelectRole',
|
||||
|
|
|
@ -144,7 +144,7 @@ export const formSchema: FormSchema[] = [
|
|||
{
|
||||
field: 'userIds',
|
||||
label: '指定用户',
|
||||
component: 'JSelectUser',
|
||||
component: 'JSelectUserByDepartment',
|
||||
required: true,
|
||||
componentProps: {
|
||||
rowKey: 'id',
|
||||
|
|
Loading…
Reference in New Issue