JeeSiteJava EEJava企业架构快速开发平台
时隔3年,偶得OSC举办的2016最优秀的开源项目之一,让Gem兴奋了一下,也再次燃起了对JeeSite升级的强烈欲望。感谢OSC提供码云这么好的平台,感谢红薯大哥的亲笔祝福!
很抱歉,由于近年来工作原因比较忙,JeeSite得到的是极少维护,在这样的情况下,依然得到了大家的深深青睐,这让Gem感到非常羞愧,在此忠诚感谢您们的鼓励和支持,否则也没有JeeSite的今天。
JeeSite自开源以来被用到了企业、政府、医疗、金融、互联网等各个领域中,JeeSite的设计思想和开发模式也深入支持者的内心,也帮助了不少刚毕业的大学生去快速学习和实践。这次升级的规划Gem也结合了以往的经验和总结各方面的应用案例,对架构做一次全部重构,也纳入一些新的思想。从开发者模式、底层架构、逻辑处理到用户界面都有很大的进步,最重要的是安全稳定,降低学习成本,提高开发效率。
由于时代的变革,技术的演变,这次规划架构的变化很大,所以将不考虑之前版本的兼容。既然有了规划,就的去实现,在不影响正常工作的情况下,所有计划均安排在本人业余时间,这样也就意味着,我会牺牲掉业余时间去学习新的知识。话说回来,规划结果也是我非常期待的,可以把我近年来的一个个的想法,一个个的目标,在这里实现。由于时间上不能保证,也很抱歉不能给大家一个准确的公布时间,如果大家有什么建议,可在此留言。
关于开源许可,目前孪生产品或直接拿来改名销售的太多,有可理解的也不可理解,在这里不说太多了,也不发表什么看法,总之新版Licence应该会有下变更,从之前的开源协议ALv2变更为GPLv3或AGPL,核心代码暂不开放,不影响二次开发。
关于版本号,感谢江南白衣大哥的springside4对我的启发和帮助很大,所以4作为jeesite的第二个里程碑版本号。
在做这一方面研究的时候,本人参考了很多资料和框架,如MyBatis-Mapper、MyBatis-Plus等等,这些都做的很不错,本来想集成一个,尝试了下还是有多处地方不顺手,不易扩展,不能解决我的本意,既能使用方便又不能失灵活,所以决定自己试着完成一套Dao层架构,精简开发。
在此之前我先考虑API的写法,通俗易懂,大众思维。持久层实体类采用 @Table注解配置,自动生成增删改通用SQL,不需要在mapper.xml里写重复又费时的SQL,遇见复杂的情况下支持扩展。而报表统计分析的情况下又能支持mybatis原生写法,在写sql的时候,又能调用之前实体配置的一些参数。从而减少开发和后期维护成本。
众多的持久层框架@Column注解定义都是分布到get或属性上,或者干脆直接使用属性作为字段名,这在JeeSite是不推荐的,JeeSite的实体不仅仅是物理实体,它是与Model实体结合的一个产物,视乎记得JFinal作者也说过这一点,也是推荐的一个做法。总合考虑,将@Column所有定义到类头,而不是分布到各个属性或方法上,主要是有以下三点原因:
以定义员工实体举例,配置如下:(注意代码上的注释)
@Table(name="${_prefix}sys_employee", alias="a", columns={
@Column(includeEntity=BaseEntity.class), // 支持Include
@Column(includeEntity=DataEntity.class), // 支持Include,如:自动导入status、create_by、create_date等字段
@Column(name="emp_code", label="员工编码", isPK=true), // 支持设置主键PK字段,调用get方法时自动加入主键唯一条件
@Column(name="emp_name", label="名称", queryType=QueryType.LIKE), // 支持设置查询字段类型,如LIKE自动在查询值前后加 % 符号。
@Column(name="emp_name_en", label="英文名", queryType=QueryType.LIKE),
@Column(name="emp_no", label="工号"), // 字段名到Java属性名的转换,采用驼峰命名法规则自动进行转换
// 驼峰命名法转换不了的,支持设置特殊对象属性,如mapper.xml的sql中 a.office_code AS "office.officeCode" 的写法
@Column(name="office_code", attrName="office.officeCode", label="机构编码"),
@Column(name="office_name", attrName="office.officeName", label="机构名称", queryType=QueryType.LIKE),
@Column(name="company_code", attrName="company.companyCode", label="公司编码"),
@Column(name="company_name", attrName="company.companyName", label="公司名称", queryType=QueryType.LIKE),
@Column(name="sex", label="性别"),
@Column(name="birthday", label="生日"),
@Column(name="photo", label="员工照片", isQuery=false), // 支持设置非查询字段,添加查询条件时忽略该字段
@Column(name="email", label="电子邮件"),
@Column(name="mobile", label="手机号码"),
@Column(name="phone", label="办公电话"),
@Column(name="fax", label="传真号码"),
@Column(name="qq", label="QQ号"),
@Column(name="weixin", label="微信号"),
@Column(name="stations", label="岗位"),
},
// 支持联合查询,如左右连接查询,支持设置查询自定义关联表的返回字段列
joinTable={
@JoinTable(type=Type.LEFT_JOIN, entity=Office.class, alias="o",
on="o.office_code = a.office_name",
columns={@Column(includeEntity=Office.class)}),
@JoinTable(type=Type.LEFT_JOIN, entity=Company.class, alias="c",
on="c.company_code = a.company_name",
columns={@Column(includeEntity=Company.class)}),
},
// 支持扩展Column、Form、Where等,主要用于该注解实现不了的复杂情况,扩展SQL写法,这里设置的是sqlMap的key
extWhereKeys="dsfOffice, dsfCompany"
// 自动设置默认排序
orderBy="a.update_date DESC"
)
public class Employee extends DataEntity {
private static final long serialVersionUID = 1L;
private String empCode; // 员工编码
private String empName; // 名称
private String empNameEn; // 英文名
private String empNo; // 工号
private Office office; // 机构编码
private Company company; // 公司编码
private String sex; // 性别
private Date birthday; // 生日
private String photo; // 员工照片
private String email; // 电子邮件
private String mobile; // 手机号码
private String phone; // 办公电话
private String fax; // 传真号码
private String qq; // QQ号
private String weixin; // 微信号
private String stations; // 岗位
/// 省略 get set 方法
}
请仔细看上面的代码和注释,其以上之外,还支持是否为插入字段,是否为更新字段等等。
再举一个例子,扩展上面介绍的Employee表,与用户表联合查询单独定义实体,用户员工实体:
@Table(name="${_prefix}sys_user", alias="a", columns={
@Column(includeEntity=User.class),
}, joinTable={
@JoinTable(type=Type.JOIN, entity=Employee.class, alias="e",
on="e.emp_code = a.ref_code AND a.user_type=#{USER_TYPE_EMPLOYEE}",
columns={@Column(includeEntity=Employee.class)}),
@JoinTable(type=Type.LEFT_JOIN, entity=Office.class, alias="o",
on="o.office_code = a.office_name", attrName="employee.office",
columns={@Column(includeEntity=Office.class)}),
@JoinTable(type=Type.LEFT_JOIN, entity=Company.class, alias="c",
on="c.company_code = a.company_name", attrName="employee.company",
columns={@Column(includeEntity=Company.class)}),
}, extWhereKeys="dsfOffice, dsfCompany", orderBy="a.update_date DESC"
)
public class EmpUser extends User {
private static final long serialVersionUID = 1L;
public EmpUser() {
this(null);
}
public EmpUser(String id){
super(id);
}
@Valid
public Employee getEmployee(){
Employee employee = (Employee)super.getRefObj();
if (employee == null){
employee = new Employee();
}
return employee;
}
public void setEmployee(Employee employee){
super.setRefObj(employee);
}
}
注解配置完成了,下面看看如何使用
贴了这么多配置代码,下面介绍下用法。
你的Dao只需要继承CrudDao即可享受便捷体验,是不是特Easy,如下:
/**
* 员工管理DAO接口
* @author ThinkGem
*/
@MyBatisDao(entity = Employee.class)
public interface EmployeeDao extends CrudDao {
}
EmployeeDao继承CrudDao后,里面的方法你都可以调用,如下方法:
/**
* DAO实现增删改接口
* @author ThinkGem
*/
public interface CrudDao extends QueryDao {
/**
* 插入数据
*/
public int insert(T entity);
/**
* 批量插入数据
*/
public int insertBatch(List entityList);
/**
* 更新数据 By PK
*/
public int update(T entity);
/**
* 更新数据 By Entity
*/
public int updateByEntity(T entity, T whereEntity);
/**
* 更新状态数据 By PK
*/
public int updateStatus(T entity);
/**
* 更新状态数据 By Entity
*/
public int updateStatusByEntity(T entity, T whereEntity);
/**
* 删除数据 By PK(如果有status字段,则为逻辑删除,更新status字段为1,否则物理删除)
*/
public int delete(T whereEntity);
/**
* 删除数据 By Entity(如果有status字段,则为逻辑删除,更新status字段为1,否则物理删除)
*/
public int deleteByEntity(T whereEntity);
/**
* 获取单条数据
* @param entity
* @return entity
*/
public T get(T entity);
/**
* 获取单条数据
* @param entity
* @return entity
*/
public T getByEntity(T entity);
/**
* 查询数据列表,如果需要分页,请设置分页对象,如:entity.setPage(new Page(pageNo, pageSize));
* @param entity
* @return
*/
public List findList(T entity);
/**
* 查询数据总数
* @param entity
* @return
*/
public long findCount(T entity);
}
调用举例:
// 查询一条,更新
Employee employee = new Employee();
employee.setEmpCode('E001');
employee = employeeDao.get(employee);
employee.setMobile('18666666666');
employeeDao.update(employee);
// 列表查询、统计
Employee employee = new Employee();
employee.setEmpName('小王');
employee.setPage(new Page(1, 20)); // 分页查询
List list = employeeDao.findList(employee);
Long count = employeeDao.findCount(employee);
// 批量插入
employeeDao.insertBatch(list);
是不是有种事半功倍的感觉,小小的配置,可以实现几乎可以完成原来需要写代码的80%时间。
也许你会觉着配置复杂,难以理解,只要你用上了相信你就会爱不释手。
还有一个惊喜,这些配置也可以通过代码生成工具快速生成,喜欢不喜欢。
嗯!基本增删改查,批量操作,按实体属性查询,按实体属性更新,以及统计都有了 ↓↓↓ 可是 ↓↓↓
这么多还是还不够,比如,我们想实现,日期范围查询怎么办?某个实体属性,实现双重查询(如那么既能eq又能like)怎么办?想实现or、is null,括号查询怎么办?这些都么关系,已经替你考虑了,如下:
////////// 日期范围查询,gte,lte ////////////////
public Date getCreateDate_gte(){
return sqlMap.getWhere().getValue("create_date", QueryType.GTE);
}
public void setCreateDate_gte(Date createDate){
createDate = DateUtils.getOfDayFirst(createDate); // 将日期的时间改为0点0分0秒
sqlMap.getWhere().and("create_date", QueryType.GTE, createDate);
}
public Date getCreateDate_lte(){
return sqlMap.getWhere().getValue("create_date", QueryType.LTE);
}
public void setCreateDate_lte(Date createDate){
createDate = DateUtils.getOfDayLast(createDate); // 将日期的时间改为23点59分59秒
sqlMap.getWhere().and("create_date", QueryType.LTE, createDate);
}
////////// 双重字段查询,支持eq,支持like ////////////////
public String getTableName() {
return StringUtils.lowerCase(tableName);
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
public String getTableName_like() {
return sqlMap.getWhere().getValue("table_name", QueryType.LIKE);
}
public void setTableName_like(String tableName) {
sqlMap.getWhere().and("table_name", QueryType.LIKE, tableName);
}
////////// 实现 or、is null,括号 ////////////////
public String getParentTableName_isNull() {
return this.getParentTableName();
}
public void setParentTableName_isNull(String parentTableName) {
if (StringUtils.isBlank(parentTableName)){
sqlMap.getWhere().andBracket("parent_table_name", QueryType.IS_NULL, null, 2)
.or("parent_table_name", QueryType.EQ_FORCE, "", 3).endBracket();
this.setParentTableName(null);
}else{
this.setParentTableName(parentTableName);
}
}
还有一种情况,如所有的配置都配置好了,我只需要在sql返回值里加一个简单的统计数,多返回一列,你可以这样写:
// 实体类定义
@Table(name="${_prefix}gen_table", alias="a", columns={
// @Column 。。。此处省略 。。。
},
// 扩展Column里指定一个Key名字,类里并定义一个需要返回的属性和get set
extColumnKeys="extColumn"
)
public class GenTable extends DataEntity {
private Long childNum; // 子表个数
public Long getChildNum() {
return childNum;
}
public void setChildNum(Long childNum) {
this.childNum = childNum;
}
}
// Service 里,通过sqlMap设置你刚定义的Key即可,如下
public Page findPage(Page page, GenTable genTable) {
// 添加扩展列,查询子表个数
String extColumn = "(SELECT count(1) FROM "+MapperHelper.getTableName(genTable)
+" WHERE parent_table_name=a.table_name) AS \"childNum\"";
genTable.getSqlMap().add("extColumn", extColumn);
return super.findPage(page, genTable);
}
如果以上仍得不到你的满足,怎么办,那你可以写Mapper.xml了,比如EmployeeDao.xml一些通用的字段、条件,你就不需要在xml再写一遍了,你只需要补充SQL即可(相同id,如id="findList"则会自动覆盖默认设置):
这样的Dao,你满意吗?编码原来如此简单,提高了效率,又不损失灵活,是不是很有趣呢。
/**
* 指定实体的物理表属性
* @author ThinkGem
*/
public @interface Table {
/**
* 物理表名
*/
String name() default "";
/**
* 当前表别名
*/
String alias() default "a";
/**
* 表列定义
*/
Column[] columns();
/**
* 查询,关联表
*/
JoinTable[] joinTable() default {};
/**
* 指定排序
*/
String orderBy() default "";
/**
* 表说明
*/
String comment() default "";
/**
* 扩展ColumnSQL,在这里指定sqlMap的key。
* 例如:\@Table(extColumnKeys="dataScopeColumn");
* Service里设置:sqlMap.put("extColumn", "column_name AS \"columnName\"");
* 在执行查询的时候,该语句放到自动会加到Where最后并执行。
* 注意:如果设置,必须后台代码中设置,否则可能造成sql注入漏洞
*/
String extColumnKeys() default "";
/**
* 扩展FromSQL,在这里指定sqlMap的key。
* 例如:\@Table(extFromKeys="dataScopeFrom");
* Service里设置:sqlMap.put("dataScopeFrom", "JOIN table_name t on t.pk=a.pk");
* 在执行查询的时候,该语句放到自动会加到Where最后并执行。
* 注意:如果设置,必须后台代码中设置,否则可能造成sql注入漏洞
*/
String extFromKeys() default "";
/**
* 扩展WhereSQL,在这里指定sqlMap的key。
* 例如:\@Table(extWhereKeys="dataScopeWhere");
* Service里设置:sqlMap.put("dataScopeWhere", "AND column_name='value'");
* 在执行查询的时候,该语句放到自动会加到Where最后并执行。
* 注意:如果设置,必须后台代码中设置,否则可能造成sql注入漏洞
*/
String extWhereKeys() default "";
}
/**
* 定义物理表列属性(不继承父类注解)
* @author ThinkGem
*/
public @interface Column {
/**
* 字段名(例如:config_key)
*/
String name() default "";
/**
* 属性名,若不指定,则根据name()字段名进行驼峰命名法转换(例如:config_key 转换为 configKey)
*/
String attrName() default "";
/**
* 属性名,定义的类型
*/
Class> type() default Class.class;
/**
* 标签名
*/
String label() default "";
/**
* 字段备注
*/
String comment() default "";
/**
* 是否主键(update、delete时的条件)
*/
boolean isPK() default false;
/**
* 是否插入字段
*/
boolean isInsert() default true;
/**
* 是否更新字段
*/
boolean isUpdate() default true;
/**
* 是否是查询字段
*/
boolean isQuery() default true;
/**
* 查询类型
*/
QueryType queryType() default QueryType.EQ;
/**
* 包含嵌入一个实体
*/
Class> includeEntity() default Class.class;
}
/**
* 指定实体的物理表的关联表属性
* @author ThinkGem
*/
public @interface JoinTable {
/**
* 连接类型
*/
Type type() default Type.JOIN;
public enum Type{
JOIN("JOIN"), // INNER JOIN
LEFT_JOIN("LEFT JOIN"),
RIGHT_JOIN("RIGHT JOIN");
private final String value;
Type(String value) { this.value = value; }
public String value() { return this.value; }
}
/**
* 连接的表,指定实体Class
*/
Class> entity();
/**
* 当前表别名
*/
String alias();
/**
* 连接表条件
*/
String on();
/**
* 对应主表中对应的属性名,若不指定,则根据entity()进行首字母小写得到属性名(例如:Config 转换为 config)
*/
String attrName() default "";
/**
* 连接表,返回的列,若不指定,则读取entity()的所有列。
*/
Column[] columns() default {};
}
/**
* 查询类型
* @author ThinkGem
*/
public enum QueryType{
EQ("="),
NE("!="),
GT(">"),
GTE(">="),
LT("<"),
LTE("<="),
IN("IN"),
NOT_IN("NOT IN"),
LIKE("LIKE", "%", "%"),
LEFT_LIKE("LIKE", "%", ""),
RIGHT_LIKE("LIKE", "", "%"),
IS_NULL("IS NULL"),
IS_NOT_NULL("IS NOT NULL"),
// 强制条件,不管值是不是空字符串都加载这个查询条件
EQ_FORCE("=", true),
NE_FORCE("!=", true),
;
}
© 著作权归作者所有
对于业务逻辑层的开发重复代码很多,尽管有代码生成器,但从代码量总的来说还是比较多,所以就有了以下抽象类及工具,对一些常用操作进行封装。
对通用新增、删除、编辑、查询,代码操作进行封装简化。你只需要写你的业务逻辑代码就可以了。
对特有树状结构特有字段如(所有父级编码、所有排序号编码、是否是叶子节点、当前节点层次)进行更新,比如,通过所有父级编码可快速查询到所有子级的数据;通过所有排序号,可快速对整个树结构进行排序;通过是否叶子节点快速得知是否有下级;根据当前层次快速知道当前节点在树中的级别。
对通用数据权限进行简化封装,将颗粒度降到人员身上,支持人员与数据,角色与数据权限定制。数据权限不仅仅支持公司、部门、角色,还可以通过配置支持你的业务字段数据信息过滤,如订单的区域、内容管理的栏目、故障单类型等等。
对事务处理使用Spring事务@Transactional注解,进行方法级别的事务控制,不用单独处理事务及回滚。如配置传播行为,进行事务继承,子事务,事务回滚行为等,配置隔离级别读取未提交的数据等。
TreeService -> CrudService -> QueryService -> BaseService
TreeDao -> CrudDao -> QueryDao -> BaseDao
TreeEntity -> DataEntity -> BaseEntity
/**
* 新建实体对象
* @return
*/
protected T newEntity();
/**
* 新建实体对象(带一个String构造参数)
* @return
*/
protected T newEntity(String id);
/**
* 获取单条数据
* @param id 主键
* @return
*/
public T get(String id);
/**
* 获取单条数据
* @param entity
* @return
*/
public T get(T entity);
/**
* 获取单条数据,如果获取不到,则实例化一个空实体
* @param id 主键编号
* @param isNewRecord 如果是新记录,则验证主键编号是否存在。
* 如果存在抛出ValidationException异常。
* @return
*/
public T get(String id, boolean isNewRecord);
/**
* 获取单条数据,如果获取不到,则实例化一个空实体(多个主键情况下调用)
* @param pkClass 主键类型数组
* @param pkValue 主键数据值数组
* @param isNewRecord 如果是新记录,则验证主键编号是否存在。
* 如果存在抛出ValidationException异常。
* @return
*/
public T get(Class>[] pkClass, Object[] pkValue, boolean isNewRecord);
/**
* 列表查询数据
* @param entity
* @return
*/
public List findList(T entity);
/**
* 分页查询数据
* @param page 分页对象
* @param entity
* @return
*/
public Page findPage(Page page, T entity);
/**
* 查询列表总数
* @param entity
* @return
*/
public long findCount(T entity);
该类继承QueryService抽象类
/**
* 保存数据(插入或更新)
* @param entity
*/
@Transactional(readOnly = false)
public void save(T entity)
/**
* 插入数据
* @param entity
*/
@Transactional(readOnly = false)
public void insert(T entity);
/**
* 更新数据
* @param entity
*/
@Transactional(readOnly = false)
public void update(T entity);
/**
* 更新状态(级联更新子节点)
* @param entity
*/
@Transactional(readOnly = false)
public void updateStatus(T entity);
/**
* 删除数据
* @param entity
*/
@Transactional(readOnly = false)
public void delete(T entity);
该类继承CrudService抽象类
/**
* 根据父节点获取子节点最后一条记录
*/
public T getLastByParentCode(T entity);
/**
* 保存数据(插入或更新)
* 实现自动保存字段:所有父级编号、所有排序号、是否是叶子节点、节点的层次级别等数据
* 实现级联更新所有子节点数据:同父级自动保存字段
*/
@Transactional(readOnly = false)
public void save(T entity);
/**
* 更新parent_codes、tree_sorts、tree_level字段值
*/
@Transactional(readOnly = false, isolation = Isolation.READ_UNCOMMITTED) // 可读取未提交数据
private void updateParentCodes(T entity);
/**
* 更新当前节点排序号
*/
@Transactional(readOnly = false)
public void updateTreeSort(T entity);
/**
* 更新tree_leaf字段值
*/
@Transactional(readOnly = false, isolation = Isolation.READ_UNCOMMITTED) // 可读取未提交数据
private void updateTreeLeaf(T entity);
/**
* 修正本表树结构的所有父级编号
* 包含:数据修复(parentCodes、treeLeaf、treeLevel)字段
*/
@Transactional(readOnly = false) // 可读取未提交数据
public void updateFixParentCodes();
/**
* 按父级编码修正树结构的所有父级编号
* 包含:数据修复(parentCodes、treeLeaf、treeLevel)字段
*/
@Transactional(readOnly = false) // 可读取未提交数据
public void updateFixParentCodes(String parentCode);
/**
* 预留接口事件,更新子节点
* @param childEntity 当前操作节点的子节点
* @param parentEntity 当前操作节点
*/
protected void updateChildNode(T childEntity, T parentEntity);
/**
* 更新状态(级联更新子节点)
* @param entity
*/
@Transactional(readOnly = false)
public void updateStatus(T entity);
/**
* 删除数据(级联删除子节点)
* @param entity
*/
@Transactional(readOnly = false)
public void delete(T entity);
/**
* 将不同级别无序的树列表进行排序,前提是sourcelist每一级是有序的
* 举例如下:
* List targetList = Lists.newArrayList();
* List sourcelist = service.findList(category);
* service.execTreeSort(targetList, sourcelist, "0");
* @param sourceList 源数据列表
* @param targetList 目标数据列表
* @param parentCode 目标数据列表的顶级节点
*/
public void execTreeSort(List sourceList, List targetList, String parentCode);
/**
* 将简单列表code,parentCode转换为嵌套列表形式code,childList[code,childList[...]]
* 举例如下:
* List targetList = Lists.newArrayList();
* List sourcelist = service.findList(category);
* service.execChildListBulid(targetList, sourcelist, "0");
* @param sourceList 源数据列表
* @param targetList 目标数据列表
* @param parentCode 目标数据列表的顶级节点
*/
public void execChildListBulid(List sourceList, List targetList, String parentCode);
本次对数据权限这块进行了全面的升级,让数据权限颗粒度细化到人员身上,支持人员与权限和角色与权限。
调用示例:
/**
* 添加数据权限过滤条件
*/
public void addDataScopeFilter(T entity){
// 举例1:公司数据权限过滤,实体类@Table注解extWhereKeys="dsf"
company.getSqlMap().getDataScope().addFilter("dsf", "Company", "a.company_code");
// 举例2:部门数据权限过滤,实体类@Table注解extWhereKeys="dsf"
office.getSqlMap().getDataScope().addFilter("dsf", "Office", "a.office_code");
// 举例3:角色数据权限过滤,实体类@Table注解extWhereKeys="dsf"
role.getSqlMap().getDataScope().addFilter("dsf", "Role", "a.role_code");
// 举例4:用户、员工数据权限根据部门过滤,实体类@Table注解extWhereKeys="dsfOffice"
empUser.getSqlMap().getDataScope().addFilter("dsfOffice",
"Office", "e.office_code", "a.create_by");
// 举例5:用户、员工数据权限根据公司过滤,实体类@Table注解extWhereKeys="dsfCompany"
empUser.getSqlMap().getDataScope().addFilter("dsfCompany",
"Company", "e.company_code", "a.create_by");
}
API接口:
/**
* 数据范围过滤
* @param sqlMapKey sqlMap的键值,举例:如设置“dsf”数据范围过滤,则:
* exists方式对应:sqlMap.dsf; join方式对应:sqlMap.dsfForm 和 sqlMap.dsfWhere
* @param ctrlTypes 控制类型,多个用“,”隔开,举例:
* 控制角色:Role
* 控制部门:Office
* 控制公司:Company
* @param bizCtrlDataFields 业务表对应过滤表别名或需要过滤的表别名加权限字段,多个使用“,”分隔,
* 长度必须与tableTypes保持一致,举例:
* 业务表控制角色:a.role_code
* 业务表控制部门:a.office_code
* 业务表控制公司:a.company_code
* @param bizCtrlUserField 业务表对应表别名或用户字段。过滤只可以查看本人数据。
* 不设置的话,如果没有范围权限,则查不到任何数据,举例:
* 业务表a.create_by"
* @example
* 1)在Service中调用如下两种方式:
* // 添加数据权限过滤条件(控制角色)
* entity.getSqlMap().getDataScope().addFilter("dsf", "Role", "a.role_code");
* // 添加数据权限过滤条件(控制公司和部门)
* entity.getSqlMap().getDataScope().addFilter("dsf",
* "Office,Company", "a.office_code,a.company_code");
* 2)MyBatis \@Table 注解中调用如下:
* 采用 EXISTS方式调用 : \@Table(extWhereKeys="dsf")
* 采用JOIN方式调用 : \@Table(extFromKeys="dsf",extWhereKeys="dsf")
* 3)MyBatis Mapper 中调用如下两种方式:
* 采用 EXISTS方式调用 : 将 ${sqlMap.dsf} 放在Where后
* 采用JOIN方式调用 : 将 ${sqlMap.dsfFrom} 放在Form后 ,将 ${sqlMap.dsfWhere} 放在Where后
*/
public QueryDataScope addFilter(String sqlMapKey, String ctrlTypes, String bizCtrlDataFields, String bizCtrlUserField);
事务管理对于企业应用来说是至关重要的,当出现异常情况,它也可以保证数据的一致性。
JeeSite主要使用Spring的@Transactional注解,也称声明式事务管理,是建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需通过基于@Transactional注解的方式,便可以将事务规则应用到业务逻辑中。
属性 | 类型 | 描述 |
---|---|---|
传播性 | 枚举型 | 可选的传播性设置(默认值:Propagation.REQUIRED) |
隔离性 | 枚举型 | 可选的隔离性级别(默认值:Isolation.ISOLATION_DEFAULT) |
只读性 | 布尔型 | 读写型事务 vs. 只读型事务 |
超时 | int型 | 事务超时(以秒为单位) |
回滚异常类(rollbackFor) | Class 类的实例,必须是Throwable 的子类 | 异常类,遇到时必须进行回滚。默认情况下checked exceptions不进行回滚,仅unchecked exceptions(即RuntimeException的子类)才进行事务回滚。 |
不回滚异常类(noRollbackFor) | Class 类的实例,必须是Throwable的子类 | 异常类,遇到时必须不回滚。 |
所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在Propagation定义中包括了如下几个表示传播行为的常量:
隔离级别是指若干个并发的事务之间的隔离程度。Isolation 接口中定义了五个表示隔离级别的常量:
© 著作权归作者所有
一个不得不说的话题,经过近几年的发展,Web前端开发已经不是一个新有的岗位了,前端技术发展非常迅速,技术更新换代也很快,对于前端工程师来说是一个很大的挑战“挣扎期”。
从统计来看,中级前端的待遇是略高于中级后端的。这对于中小企业、创业公司来说组建一个专有的前端团队还是很一件很不容易的事情,无形中增加了人力成本, 话说招前端工程师简单,但能招聘到合适的前端工程师来说,是一件非常不容易的事情。
为了解决这个事情,中小企业、创业公司都在思考一个问题,做企业应用软件,如果不去组建专门的前端团队,能有一个很好的开发平台,很好的框架,让后端工程师具备一些基本的前端知识,就可以去做出很漂亮的界面就好了。
这个想法很好,但是你会说,可能吗,会一点基础前端就能做好吗?专业的事情还是有专业的人来做,前后端分离是趋势,表现逻辑分离意义很大。没有绝对完美的事情,尽管种种诱惑,唯心自问合适自己吗?我不否认,这还要针对产品、针对项目来选择解决方案。但JeeSite的中心思想是快速快发,快速交付,控制成本,对于一个想快速交付项目来说不见得是一件好事。
重口难调,也许你不赞同这些看法,作者欢迎提问,当然你也可以将JeeSite完全作为服务端代码,快速提供数据接口,自由实现或选型一套前端UI。
好了,既然是奔着快速交付,控制成本来的,我们就依这个角度去思考方案:
Beetl模板语言类似JS语言和习俗,只需要将Beetl语言放入定界符号里即可,如默认的是<% %>,那JeeSite是怎样选择的呢:
设想定义:<% %>
优点:jsp标准定界符,比较容易被理解,是后端运行的语法
缺点:html后缀的模板,不能和html标签混用,否则IDE会提示语法错误
设想定义:@ 回车符
优点:简单
缺点:多行beetl语法时,比较麻烦,并且会出现多余的很多空格空行
设想定义:[ ]、[@ ]、[@ ]
有点:可以和html混用
缺点:与js的[]冲突,必须使用\转义
**设想定义:[@ @]、[# #]、# #、@ @**
优点:基本没有标示符冲突
缺点:感官、阅读、写法稍微差点
设想定义:、
优点:使用html注释,没有标示符冲突
缺点:使用IDE高亮时,没有写代码的感觉,像是在写注释
最终选择:<% %>
经过上述分析,最终还是回归默认,使用jsp标准定界符<%%>来作为模板语言的定界符,一方同比较容易被理解,明确是后端运行的语法,另一方面冲突少,边界比较好界定
<% layout('/layouts/default.html', {title: '菜单管理', libs: ['validate'], bodyClass: ''}){ %>
菜单管理
<% } %>
调用默认布局 /layouts/default.html,自动引入页面头部和尾部内容,通过参数设置要加载的css和js类库,参数如下:
title参数: 设置页面的标题名字
libs参数: 设置页面要引入的css和js类库,支持类库如下:
默认引入:layer、select2、WdatePicker
bodyClass参数: 设置body的class属性值
以下工具类可通过@类型快速调用,如:${@Global.getConfig('key')}
以下是Beetl函数及扩展函数
数据类型格式化
日期格式化:
Today is ${date,dateFormat="yyyy-MM-dd"}
Today is ${date,dateFormat}
如果date为日期类型可简写:
${date,“yyyy-MM-dd”}
数值格式化:
Salary is ${salary,numberFormat="##.##"}
生成一个form标签,支持指定model属性,类似SpringMVC的 标签的属性,自动给表单内的控件绑定属性值
<#form:form id="inputForm" model="${user}" action="${ctx}/sys/user" method="POST" class="form-horizontal">
表单内容
#form:form>
支持上传文件:
<#form:form id="inputForm" model="${user}" action="${ctx}/sys/user" method="POST" enctype="multipart/form-data" class="form-horizontal">
表单内容
#form:form>
控件属性:
var p = {
// 标签参数
id: id!, // 表单ID
model: model!, // 绑定Model对象,例如:${user!}
action: action!, // 表单请求地址
method: method!, // 请求方法,默认 post
enctype: enctype!, // 发送之前进行数据编码,上传文件时指定:multipart/form-data
};
自动绑定form:form上指定的model下的userName属性,类似SpringMVC的 标签的属性
<#form:input path="userName" maxlength="100" class="form-control required "/>
日期格式化:
<#form:input path="userName" maxlength="100" dataFormat="date" class="form-control required "/>
数值格式化:
<#form:input path="userName" maxlength="100" dataFormat="number" class="form-control required "/>
不自动绑定,把path改为name就可以:
<#form:input name="userName" value="${user.userName}" maxlength="100" class="form-control required "/>
控件属性:
var p = {
// 标签参数
id: id!, // 元素ID,如果不填写,则与name相同
path: path!, // 绑定form上model中属性的值
name: name!, // 元素名称,不填写
value: value!, // 元素值
type: type!'text', // 元素的类型,默认text
dataFormat: dataFormat!'', // 数据格式化,支持如下值:
// date: 日期;
// datetime: 日期时间;
// number: 数值类型,保留2位小数
};
根据字典类型设置下拉数据:
<#form:select path="userType" dictType="sys_user_type" class="form-control required " />
增加一个空白选项:
<#form:select path="roleType" dictType="sys_role_type" blankOption="true" class="form-control " />
多选下拉列表:
<#form:select path="roleType" dictType="sys_role_type" multiple="true" class="form-control " />
手动设置下拉框值,类似SrpingMVC的 标签的属性:
<#form:select path="moduleCodes" items="${moduleList}" itemLabel="moduleName" itemValue="moduleCode" class="form-control required" />
控件属性:
var p = {
// 标签参数
id: id!, // 元素ID,如果不填写,则与name相同
path: path!, // 绑定form上model中属性的值
name: name!, // 元素名称,不填写
value: value!, // 元素值
dictType: dictType!, // 字典类型,从字典里获取,自动设置items、itemLabel、itemValue
items: items![], // 列表数据,可接受对象集合,如:List
itemLabel: itemLabel!'', // 指定列表数据中的什么属性名作为option的标签名
itemValue: itemValue!'', // 指定列表数据中的什么属性名作为option的value值
multiple: multiple!'false', // 是否为多选框
blankOption: @ObjectUtils.toBoolean(blankOption!false), // 是否默认有个空白选择项目
};
类似<#form:select/>标签的使用方法
<#form:radio path="menuType" dictType="sys_menu_type" class="form-control required" />
控件属性:
var p = {
// 标签参数
id: id!, // 元素ID,如果不填写,则与name相同
path: path!, // 绑定form上model中属性的值
name: name!, // 元素名称,不填写
value: value!, // 元素值
dictType: dictType!, // 字典类型,从字典里获取,自动设置items、itemLabel、itemValue
items: items!([]), // 列表数据,可接受对象集合,如:List
itemLabel: itemLabel!'', // 指定列表数据中的什么属性名作为option的标签名
itemValue: itemValue!'', // 指定列表数据中的什么属性名作为option的value值
};
类似<#form:select/>标签的使用方法,后台接受moduleCodes为字符串,选择多个自动使用“,”分隔,相比SpringMVC必须是List方便的多
<#form:checkbox path="moduleCodes" items="${moduleList}" itemLabel="moduleName" itemValue="moduleCode" class="form-control required" />
生成一个复选框按钮,后台接受replaceFile为Global.YES或Global.NO值:
<#form:checkbox path="replaceFile" label="是否替换现有文件" class="form-control"/>
控件属性:
var p = {
// 标签参数
id: id!, // 元素ID,如果不填写,则与name相同
path: path!, // 绑定form上model中属性的值
name: name!, // 元素名称,不填写
value: value!, // 元素值
dictType: dictType!'', // 字典类型,从字典里获取,自动设置items、itemLabel、itemValue
items: items!([]), // 列表数据,可接受对象集合,如:List
itemLabel: itemLabel!'', // 指定列表数据中的什么属性名作为option的标签名
itemValue: itemValue!'', // 指定列表数据中的什么属性名作为option的value值
label: label!, // 只有一个复选按钮的情况下设置
};
<#form:textarea path="remarks" rows="3" maxlength="200" class="form-control"/>
控件属性:
var p = {
// 标签参数
id: id!, // 元素ID,如果不填写,则与name相同
path: path!, // 绑定form上model中属性的值
name: name!, // 元素名称,不填写
value: value!, // 元素值
};
<#form:hidden path="menuCode" />
控件属性:
var p = {
// 标签参数
id: id!, // 元素ID,如果不填写,则与name相同
path: path!, // 绑定form上model中属性的值
name: name!, // 元素名称,不填写
value: value!, // 元素值
type: type!'hidden', // 元素的类型,默认hidden
};
封装layer+zTree实现树结构选择组件,使用场景如:部门选择,行政区划选择,栏目列表选择等
<#form:treeselect id="parent" title="上级菜单"
name="parent.id" value="${menu.parent.id!}"
labelName="parent.menuName" labelValue="${menu.parent.menuName!}"
url="${ctx}/sys/menu/treeData?excludeCode=${menu.menuCode}&sysCode=${menu.sysCode}&isShowCode=2"
class="" allowClear="true" canSelectRoot="true" canSelectParent="true"/>
组件属性:
var p = {
// 标签参数
id: id!, // 元素ID
name: name!, // 隐藏域名称
value: value!, // 隐藏域值
labelName: labelName!, // 标签框名称
labelValue: labelValue!, // 标签框值
class: class!'', // 标签框的CSS类名
placeholder: placeholder!, // 标签框的预期值的提示信息
dataMsgRequired: thisTag.attrs['data-msg-required'], // 必填错误提示信息
btnClass: btnClass!, // 标签框后面的按钮CSS类名
title: title!'选项', // 对话框标题
boxWidth: boxWidth!300, // 对话框宽度,默认300像素
boxHeight: boxHeight!400, // 对话框高度,默认400像素
url: url!, // 树结构,数据源地址 [{id, pid, name}]
readonly: @ObjectUtils.toBoolean(readonly!false), // 是否只读模式
allowInput: @ObjectUtils.toBoolean(allowInput!false), // 是否允许label框输入
allowClear: @ObjectUtils.toBoolean(allowClear!true), // 是否允许清空选择内容
checkbox: @ObjectUtils.toBoolean(checkbox!false), // 是否显示复选框,是否支持多选,如果设置canSelectParent=true则返回父节点数据
expandLevel: @ObjectUtils.toInteger(expandLevel!(-1)), // 默认展开层次级别(默认:如果有1个根节点,则展开一级节点,否则不展开)
canSelectRoot: @ObjectUtils.toBoolean(canSelectRoot!false), // 可以选择跟节点
canSelectParent: @ObjectUtils.toBoolean(canSelectParent!false), // 可以选择父级节点
returnFullName: @ObjectUtils.toBoolean(returnFullName!false), // 是否返回全路径,包含所有上级信息,以 returnFullNameSplit 参数分隔
returnFullNameSplit: returnFullNameSplit!'/', // 是否返回全路径,的分隔符,默认“/”
};
<#form:iconselect path="menuIcon" class=""/>
组件属性:
var p = {
// 标签参数
id: id!, // 元素ID,如果不填写,则与name相同
path: path!, // 绑定form上model中属性的值
name: name!, // 元素名称,不填写
value: value!, // 元素值
class: class!'', // 隐藏域和标签框的CSS类名
};
<#form:validcode name="validCode" isRequired="true" isRemote="true" />
组件属性:
var p = {
id: id!name, // 验证码输入框ID
name: name!, // 验证码输入框名称(必填)
isRequired: @ObjectUtils.toBoolean(isRequired!true), // 是否必填,默认必填
dataMsgRequired: thisTag.attrs['data-msg-required'], // 必填错误提示信息
isRemote: @ObjectUtils.toBoolean(isRemote!true), // 是否支持实时远程验证
dataMsgRemote: thisTag.attrs['data-msg-remote'], // 必填错误提示信息
isLazy: @ObjectUtils.toBoolean(isLazy!false), // 是否懒加载验证码图片,原noRefresh参数
};
<#form:listselect id="userSelect" title="用户"
url="${ctx}/sys/user/userSelect?userType=${role.userType}" allowClear="false"
checkbox="true" itemCode="userCode" itemName="userName"/>
组件属性:
var p = {
// 标签参数
id: id!, // 元素ID
path: path!, // 绑定form上model中属性的值
name: name!, // 隐藏域名称
value: value!, // 隐藏域值
labelPath: labelPath!, // 绑定form上model中属性的值
labelName: labelName!, // 标签框名称
labelValue: labelValue!, // 标签框值
class: class!'', // 标签框的CSS类名
placeholder: placeholder!, // 标签框的预期值的提示信息
dataMsgRequired: thisTag.attrs['data-msg-required'], // 必填错误提示信息
btnClass: btnClass!, // 标签框后面的按钮CSS类名
title: title!'选项', // 对话框标题
boxWidth: boxWidth!'$(top.window).width() - 100', // 对话框宽度
boxHeight: boxHeight!'$(top.window).height() - 100', // 对话框高度
url: url!, // 树结构,数据源地址 [{id, pid, name}]
readonly: @ObjectUtils.toBoolean(readonly!false), // 是否只读模式
allowInput: @ObjectUtils.toBoolean(allowInput!false), // 是否允许label框输入
allowClear: @ObjectUtils.toBoolean(allowClear!true), // 是否允许清空选择内容
checkbox: @ObjectUtils.toBoolean(checkbox!false), // 是否显示复选框,是否支持多选,如果设置canSelectParent=true则返回父节点数据
itemCode: itemCode!, // 选择后结果集中的Code属性名,返回到隐藏域的值
itemName: itemName!, // 选择后结果集中的Name属性名,返回到输入框的值
};
1、文件上传:
<#form:fileupload id="upload1" bizKey="${user.id}" bizType="user_upload1"
uploadType="all" class="required" readonly="false"/>
后台代码:FileUploadUtils.saveFileUpload(user.getId(), "user_upload1");
2、图片上传:
<#form:fileupload id="upload2" bizKey="${user.id}" bizType="user_upload2"
uploadType="image" class="required" readonly="false"/>
后台代码:FileUploadUtils.saveFileUpload(user.getId(), "user_upload2");
3、返回路径:
<#form:fileupload id="upload3" returnPath="true"
filePathInputId="upload3Path" fileNameInputId="upload3Name"
uploadType="image" readonly="false" maxUploadNum="3" isMini="false"/>
<#form:input name="upload3Path" class="form-control"/>
<#form:input name="upload3Name" class="form-control"/>
组件属性:
var p = {
// 标签参数
id: id!, // 元素ID
bizKey: bizKey!, // 业务表的主键值(与附件关联的业务数据)
bizType: bizType!, // 业务表的上传类型(全网唯一,推荐格式:实体名_上传类型,例如,文章图片:article_photo)
returnPath: @ObjectUtils.toBoolean(returnPath!false), // 是否是返回文件路径到输入框(默认false),可将路径直接保存到某个字段里
filePathInputId: filePathInputId!, // 设置文件URL存放的输入框的ID,当returnPath为true的时候,返回文件URL到这个输入框
fileNameInputId: fileNameInputId!, // 设置文件名称存放的输入框的ID,当returnPath为true的时候,返回文件名称到这个输入框
uploadType: uploadType!'', // 上传文件类型:all、file、image、media,若不设置,则自动根据上传文件后缀获取
class: class!'', // 标签框的CSS类名,设置 required 加入必填验证
readonly: @ObjectUtils.toBoolean(readonly!false), // 是否只读模式,只读模式下为查看模式,只允许下载
allowSuffixes: allowSuffixes!'', // 允许上传的后缀,前台的限制,不能超越file.*AllowSuffixes的设置,例如:.jpg,.png,
maxUploadNum: @ObjectUtils.toInteger(maxUploadNum!300), // 多文件下允许最多上传几个,默认300个,设置-1代表不限制
imageMaxWidth: @ObjectUtils.toInteger(imageMaxWidth!1024), // 图片压缩,最大宽度(uploadType为image生效),设置-1代表不做任何处理
imageMaxHeight: @ObjectUtils.toInteger(imageMaxHeight!768), // 图片压缩,最大宽度(uploadType为image生效),设置-1代表不做任何处理
isLazy: @ObjectUtils.toBoolean(isLazy!false), // 设置为ture需要点击上传按钮才上传文件,否则选择后就直接上传
isMini: @ObjectUtils.toBoolean(isMini!false), // 是否是精简上传窗口,无边距,无边框
preview: preview!'', // 是否显示预览按钮,接受参数:weboffice
};
<#form:imageclip name="imageBase64" btnText="修改头像" btnClass="btn-block"
imageId="avatarImg" imageDefaultSrc="${ctxStatic+'/images/user1.jpg'}"
circle="true"/>
后台代码:
// 如果设置了头像,则保存头像
if (StringUtils.isNotBlank(imageBase64)){
if ("EMPTY".equals(imageBase64)){
user.setAvatar(StringUtils.EMPTY);
}else{
String imageUrl = "avatar/"+user.getUserCode()
+"."+FileUtils.getFileExtensionByImageBase64(imageBase64);
String fileName = Global.getUserfilesBaseDir(imageUrl);
FileUtils.writeToFileByImageBase64(fileName, imageBase64);
user.setAvatar(Global.USERFILES_BASE_URL + imageUrl);
}
}
组件属性:
var p = {
// 标签参数
id: id!, // 元素ID,如果不填写,则与name相同
path: path!, // 绑定form上model中属性的值
name: name!, // 元素名称,不填写
value: value!, // 元素值
class: class!'', // 隐藏域的CSS类名
btnText: btnText!'选择图片', // 按钮的名字
btnClass: btnClass!'', // 按钮的CSS类名
imageId: imageId!'', // 裁剪后base64返回到img的id
imageDefaultSrc: imageDefaultSrc!'', // 图片默认地址,清除后使用地址
circle: circle!'false', // 是否圆形图片
};
<#form:ueditor name="text" maxlength="10000" height="200" class="required"
simpleToolbars="false" readonly="false" outline="false"/>
组件属性:
var p = {
// 标签参数
id: id!, // 元素ID,如果不填写,则与name相同
path: path!, // 绑定form上model中属性的值
name: name!, // 元素名称,不填写
value: value!, // 元素值
class: class!'', // 标签框的CSS类名,设置 required 加入必填验证
maxlength: maxlength!'', // 编辑器最大输入字数,为空代表无限制
height: height!'200', // 编辑器的高度,默认200
simpleToolbars: @ObjectUtils.toBoolean(simpleToolbars!false), // 是否是简单的工具条
readonly: @ObjectUtils.toBoolean(readonly!false), // 是否只读模式
outline: @ObjectUtils.toBoolean(outline!false), // 大纲视图
options: options!'', // UE附加选项,逗号隔开。
};
/**
* 输出日志
*/
log(msg);
/**
* 输出错误日志
*/
error(msg);
/**
* URL 编码
*/
js.encodeUrl(url);
/**
* URL 解码
*/
js.decodeUrl(url);
/**
* 得到 IE 版本,如果是IE返回:IE版本号,否则返回:false
* if (js.ie && js.ie <= 8){ alert('浏览器版本过低') }
*/
js.ie;
/**
* 安全取值,复杂类型或嵌套类型时,取不到属性中的属性时不抛出异常
* js.val(jsonObj, 'user.office.name');
*/
js.val(jsonObj, attrName);
/**
* 返回HashCode唯一值(默认忽略大小写)
* @param str 要获取的字符串HashCode值
* @param caseSensitive 是否大小写敏感(默认false)
* @usage js.hashCode(str);
*/
js.hashCode(str, caseSensitive);
/**
* 异步加载文件,loadFile v1.0
* js.loadFile(file文件路径, callback成功回调, error失败回调)
* js.loadFile('js/test.js',function(){},function(data){});
* js.loadFile(['js/test.js','css/test.css'],function(){},function(data){});
*/
js.loadFile(file, callback, error);
/**
* 打开一个Window窗体
*/
js.windowOpen(url, name, width, height);
/**
* 关闭当前Window窗体
*/
js.windowClose();
/**
* 给URL地址添加参数,如果原来有参数则用&前缀,如果没有则用?前缀
*/
js.addParam(url, params);
/**
* 获取URL地址的参数
*/
js.getParam(paramName, url);
/**
* 查看Object的内容,手机调试用
* @param obj
*/
js.alertObj(obj);
/**
* 获取字典标签
* js.getDictLabel(${@DictUtils.getDictListJson('sys_menu_type')}, val, '未知', true)
*/
js.getDictLabel(dictListJson, value, defaultValue, inCss);
/////////////////////////////////////// message dialog
/**
* 显示加载框
* @param message 加载框提示信息
* @param ignoreMessageIfExists 如果已经有加载框现在,则忽略message信息的设置
* @usage js.loading('正在保存...');
*/
js.loading(message, ignoreMessageIfExists);
/**
* 关闭加载框
* @param timeout 关闭延迟时间
* @param forceClose 是否强制关闭
* @usage js.closeLoading(1000, true);
*/
js.closeLoading(timeout, forceClose);
/**
* 得到layer对话框对象
* js.layer.msg();
*/
js.layer;
/**
* 显示提示框
* @param message 提示消息
* @param title 提示标题
* @param type 提示类型(success、error、warning、info)
* @param timeout 自动关闭毫秒(默认4000毫秒)
*/
js.showMessage(message, title, type, timeout);
/**
* 显示错误提示框
*/
js.showErrorMessage(responseText);
/**
* 关闭提示框
*/
js.closeMessage();
/**
* 提示对话框
* @param message 提示消息
* @param options 对话框选项
* @param closed 对话框关闭回调方法
* @usage js.alert('你好!', function(){})
* @usage js.alert('你好!', {icon: 2}, function(){})
*/
js.alert(message, options, closed);
/**
* 确认对话框
* @param message 确认信息
* @param urlOrFun 确认后的跳转的地址,或调用的方法
* @param data 如果urlOrFun是地址,该参数是调用地址的参数信息
* @param callback 执行ajax的回调方法,如果为空,则直接通过location=urlOrFun跳转。
* @param dataType 返回数据类型(默认json)
* @param async 是否异步(默认true)
* @param loadingMessage 调用loading(loadingMessage)的提示信息。
* @usage js.confirm('确认删除吗?', '$ctx/biz/delete?id=123', function(data){alert('删除成功')}, 'json', true, '正在删除...');
* @usage js.confirm('确认删除吗?', '$ctx/biz/delete', {id: '123'}, function(data){alert('删除成功')}, 'json', true, '正在删除...');
* @usage js.confirm('确认删除吗?', function(data){alert('删除成功')});
*/
js.confirm(message, urlOrFun, data, callback, dataType, async, loadingMessage);
/////////////////////////////////////// js template
/**
* 根据js模板生成代码,使用laytpl引擎
* @param id 模板ID
* @param data 模板数据(可选)
* @param callback 如果填写,则为异步渲染
* @usage
* 模板格式: //
* 调用方法: js.template('dempTpl', data);
* 模版语法:
* 输出一个普通字段,不转义html: {{ d.field }}
* 输出一个普通字段,并转义html: {{= d.field }}
* JavaScript脚本: {{# JavaScript statement }}
*/
js.template(id, data, callback);
/////////////////////////////////////// ajax form
/**
* AJAX 提交
* js.ajaxSubmit('/sys/user/save', {param: 1}, function(data){})
*/
js.ajaxSubmit(url, data, callback, dataType, async, message);
/**
* AJAX 提交表单(支持文件上传)
* js.ajaxSubmitForm($(form), function(data){})
*/
js.ajaxSubmitForm(formJqueryObj, callback, dataType, async, message);
/////////////////////////////////////// string
/**
* String两边去空格
*/
js.trim(str);
/**
* String的startWith
*/
js.startWith(str, start);
/**
* String的endWith
*/
js.endWith(str, end);
/**
* 截取字符串,区别汉字和英文
*/
js.abbr(name, maxLength);
/////////////////////////////////////// number
/**
* 格式化数值
* @param num 待格式化的树
* @param cent 保留小数位数
* @param isThousand 是否进行千分位格式化
*/
js.formatNumber(num, cent, isThousand);
/**
* 金额格式化(千位符,小数四舍五入)金额每隔三位加一个逗号
* @param s 要格式化的数值
* @param n 小数位数
*/
js.formatMoney(s, n);
/**
* 数值前补零
*/
js.numberPad(num, n);
/////////////////////////////////////// date
/**
* 日期格式化
* @param date 日期 new Date()
* @param f 格式化字符串 yyyy-MM-dd HH:mm:ss
*/
js.formatDate(date, f);
/**
* 字符串转为日期
* @param date
*/
js.parseDate(date);
/**
* 日期加减
* @param date
* @param dadd 天数
*/
js.addDate(date, dadd);
/**
* 快速选择日期方法
* @param type 1今日,2本周,3本月,4本季度,5上月
* @param beginDateId 开始日期控件的ID
* @param endDateId 结束日期控件的ID
*/
js.quickSelectDate(type, beginDateId, endDateId);
/////////////////////////////////////// cookie
/**
* cookie 操作
* @param name Cookie名称
* @param value Cookie值,填写表示设置,不填写表示获取
* @parma options:{expires:7} 如果是数字,则expires单位为天。
*/
js.cookie(name, value, options);
/////////////////////////////////////// tabPage
/**
* 得到TabPage对象
*/
js.tabPage;
/**
* 初始化TAB页面
* @param id
*/
js.initTabPage(id, options);
/**
* 添加TAB页面
* @param $this 点击的对象
* @param title 提示标题
* @param url 访问的路径
* @param closeable 是否有关闭按钮
* @param refresh 打开后是否刷新重新加载
*/
js.addTabPage($this, title, url, closeable, refresh);
/**
* 获取当前TAB页面
* @param currentTabCallback(contents, contentWindow) 当前页面回调方法,传入当前tab页面的contents和contentWindow
*/
js.getCurrentTabPage(currentTabCallback);
/**
* 获取当前页面的上一个TAB页面,并激活上级页面
* @param preTabCallback(contents, contentWindow) 传入上一个tab页面的contents和contentWindow
* @param isCloseCurrentTab 是否关闭当前页签
*/
js.getPrevTabPage(preTabCallback, isCloseCurrentTab);
/**
* 关闭当前TAB页面,并激活上级页面
* @param preTabCallback(contents, contentWindow) 关闭前的回调方法,传入上一个tab页面的contents和contentWindow
*/
js.closeCurrentTabPage(preTabCallback);
数据表格是一个必不可少的元素,在选择这个选型的时候尝试了很多开源组件,最终选择jqGrid,只是因为它接近经典思维,用着还算顺手,最主要的是遇见什么问题都可以自行解决和修复问题,有人说jqGrid不好看,这没关系这完全而已自行编写CSS改造它,下面看看一个简单的例子:
<#form:form id="searchForm" model="${config}" action="${ctx}/sys/config/listData" method="post" class="form-inline "
data-page-no="${parameter.pageNo}" data-page-size="${parameter.pageSize}" data-order-by="${parameter.orderBy}">
参数名称:<#form:input path="configName" maxlength="100" class="form-control" />
参数键名:<#form:input path="configKey_like" maxlength="100" class="form-control" />
#form:form>
// 初始化DataGrid对象
$('#dataGrid').dataGrid({
// 查询数据表单
searchForm: $('#searchForm'),
// 设置数据表格列
columnModel: [
{header:'参数名称', name:'configName', index:'a.config_name', width:200, formatter: function(val, obj, row, act){
return ''+val+'';
}},
{header:'参数键名', name:'configKey', index:'a.config_key', width:200},
{header:'参数键值', name:'configValue', sortable:false, width:260, classes:"nowrap"},
{header:'操作', name:'actions', width:100, sortable:false, title:false, formatter: function(val, obj, row, act){
var actions = [];
<% if(hasPermi('sys:config:edit')){ %>
actions.push(' ');
<% } %>
<% if(hasPermi('sys:config:delete')){ %>
actions.push(' ');
<% } %>
return actions.join('');
}}
],
// 加载成功后执行事件
ajaxSuccess: function(data){
}
});
是不是比你使用foreach方便的多,封装后名字叫dataGrid,这只是展示了冰山一角,它支持所有jqGrid参数,即简化了代码编写,又不失功能
【点击图片看大图】 小灯泡图标:待完成功能