diff --git a/dependencies/.flattened-pom.xml b/dependencies/.flattened-pom.xml new file mode 100644 index 0000000..142e1b3 --- /dev/null +++ b/dependencies/.flattened-pom.xml @@ -0,0 +1,497 @@ + + + 4.0.0 + cd.casic.boot + dependencies + 2.0.0-jdk17 + pom + dependencies + 管理整个项目的依赖版本 + + 2.9.2 + 8.0.0.RELEASE + 1.6.0 + 5.1.0 + 1.6.2 + 1.2.83 + 3.47.1.0 + 3.5.16 + 0.33.0 + 8.6.0 + 1.27.1 + 2.3.1 + 2.7.0 + 4.3.1 + 3.36.0 + 0.9.0 + 2.14.5 + 3.3.4 + 2.3.0 + 2.2.7 + 2.17.0 + 6.0.0-M16 + 1.18.1 + 3.5.8 + 4.5.0 + 1.18.34 + 9.0.0 + 5.2.0 + 2.4 + 3.0.6 + 2.0.8.3 + 3.12.1 + 1.2.13 + 1.4.13 + 5.8.32 + 2.0.0-jdk17 + 0.1.55 + 42.7.4 + 3.3.3 + 23.5.0.24.07 + 4.1.113.Final + 6.6.5 + 1.1.4 + 33.3.1-jre + 1.12.777 + 3.11.1 + 1.7.8 + 4.0.3 + 1.2.23 + 3.0.6 + 8.1.3.140 + + + + + io.netty + netty-bom + ${netty.version} + pom + import + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + cd.casic.boot + commons + ${revision} + + + cd.casic.boot + spring-boot-starter-biz-data-permission + ${revision} + + + cd.casic.boot + spring-boot-starter-biz-ip + ${revision} + + + cd.casic.boot + spring-boot-starter-biz-tenant + ${revision} + + + cd.casic.boot + spring-boot-starter-excel + ${revision} + + + cd.casic.boot + spring-boot-starter-job + ${revision} + + + cd.casic.boot + spring-boot-starter-mongo + ${revision} + + + cd.casic.boot + spring-boot-starter-monitor + ${revision} + + + cd.casic.boot + spring-boot-starter-mq + ${revision} + + + org.apache.rocketmq + rocketmq-spring-boot-starter + ${rocketmq-spring.version} + + + cd.casic.boot + spring-boot-starter-mybatis + ${revision} + + + cd.casic.boot + spring-boot-starter-protection + ${revision} + + + cd.casic.boot + spring-boot-starter-redis + ${revision} + + + cd.casic.boot + spring-boot-starter-security + ${revision} + + + cd.casic.boot + spring-boot-starter-test + ${revision} + + + cd.casic.boot + spring-boot-starter-web + ${revision} + + + cd.casic.boot + spring-boot-starter-websocket + ${revision} + + + io.github.mouzt + bizlog-sdk + ${bizlog-sdk.version} + + + org.springframework.boot + spring-boot-starter + + + + + org.springframework.boot + spring-boot-configuration-processor + ${spring.boot.version} + + + com.github.xiaoymin + knife4j-openapi3-jakarta-spring-boot-starter + ${knife4j.version} + + + org.springdoc + springdoc-openapi-starter-webmvc-api + ${springdoc.version} + + + com.alibaba + druid-spring-boot-3-starter + ${druid.version} + + + org.mybatis + mybatis + ${mybatis.version} + + + com.baomidou + mybatis-plus-spring-boot3-starter + ${mybatis-plus.version} + + + com.baomidou + mybatis-plus-generator + ${mybatis-plus.version} + + + com.baomidou + dynamic-datasource-spring-boot3-starter + ${dynamic-datasource.version} + + + com.github.yulichang + mybatis-plus-join-boot-starter + ${mybatis-plus-join.version} + + + com.fhs-opensource + easy-trans-spring-boot-starter + ${easy-trans.version} + + + org.springframework + spring-context + + + org.springframework.cloud + spring-cloud-commons + + + + + com.fhs-opensource + easy-trans-mybatis-plus-extend + ${easy-trans.version} + + + com.fhs-opensource + easy-trans-anno + ${easy-trans.version} + + + org.redisson + redisson-spring-boot-starter + ${redisson.version} + + + org.springframework.boot + spring-boot-starter-actuator + + + + + com.baomidou + lock4j-redisson-spring-boot-starter + ${lock4j.version} + + + org.redisson + redisson-spring-boot-starter + + + + + org.apache.skywalking + apm-toolkit-trace + ${skywalking.version} + + + org.apache.skywalking + apm-toolkit-logback-1.x + ${skywalking.version} + + + org.apache.skywalking + apm-toolkit-opentracing + ${skywalking.version} + + + io.opentracing + opentracing-api + ${opentracing.version} + + + io.opentracing + opentracing-util + ${opentracing.version} + + + io.opentracing + opentracing-noop + ${opentracing.version} + + + de.codecentric + spring-boot-admin-starter-server + ${spring-boot-admin.version} + + + de.codecentric + spring-boot-admin-server-cloud + + + + + de.codecentric + spring-boot-admin-starter-client + ${spring-boot-admin.version} + + + org.mockito + mockito-inline + ${mockito-inline.version} + + + org.springframework.boot + spring-boot-starter-test + ${spring.boot.version} + + + org.ow2.asm + asm + + + org.mockito + mockito-core + + + + + com.github.fppt + jedis-mock + ${jedis-mock.version} + + + uk.co.jemos.podam + podam + ${podam.version} + + + org.projectlombok + lombok + ${lombok.version} + + + org.mapstruct + mapstruct + ${mapstruct.version} + + + org.mapstruct + mapstruct-jdk8 + ${mapstruct.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + cn.hutool + hutool-all + ${hutool-5.version} + + + org.dromara.hutool + hutool-extra + ${hutool-6.version} + + + com.alibaba + easyexcel + ${easyexcel.verion} + + + commons-io + commons-io + ${commons-io.version} + + + org.apache.commons + commons-compress + ${commons-compress.version} + + + org.apache.tika + tika-core + ${tika-core.version} + + + org.apache.velocity + velocity-engine-core + ${velocity.version} + + + com.alibaba + fastjson + ${fastjson.version} + + + com.google.guava + guava + ${guava.version} + + + com.alibaba + transmittable-thread-local + ${transmittable-thread-local.version} + + + commons-net + commons-net + ${commons-net.version} + + + com.jcraft + jsch + ${jsch.version} + + + com.amazonaws + aws-java-sdk-s3 + ${aws-java-sdk-s3.version} + + + org.lionsoul + ip2region + ${ip2region.version} + + + org.jsoup + jsoup + ${jsoup.version} + + + com.github.oshi + oshi-core + ${oshi-version} + + + org.pf4j + pf4j + ${pf4j.version} + + + org.slf4j + slf4j-log4j12 + + + + + org.pf4j + pf4j-spring + ${pf4j-spring.version} + + + com.dameng + DmJdbcDriver18 + ${dm8.jdbc.version} + + + com.oracle.database.jdbc + ojdbc8 + ${oracle.version} + + + org.postgresql + postgresql + ${postgresql.version} + + + org.opengauss + opengauss-jdbc + ${opengauss.jdbc.version} + + + cn.com.kingbase + kingbase8 + ${kingbase.jdbc.version} + + + org.xerial + sqlite-jdbc + ${sqlite.version} + + + com.gitee.anwena + mongo-plus-boot-starter + ${anwena.version} + + + + diff --git a/dependencies/pom.xml b/dependencies/pom.xml index 6ee4e52..35082ea 100644 --- a/dependencies/pom.xml +++ b/dependencies/pom.xml @@ -30,6 +30,11 @@ 8.1.3.140 8.6.0 5.1.0 + 3.47.1.0 + 23.5.0.24.07 + 42.7.4 + + 2.0.8.3 2.3.1 @@ -120,6 +125,11 @@ spring-boot-starter-job ${revision} + + cd.casic.boot + spring-boot-starter-mongo + ${revision} + cd.casic.boot spring-boot-starter-monitor @@ -172,12 +182,7 @@ spring-boot-starter-websocket ${revision} - - - - - io.github.mouzt bizlog-sdk @@ -524,6 +529,18 @@ ${dm8.jdbc.version} + + com.oracle.database.jdbc + ojdbc8 + ${oracle.version} + + + + org.postgresql + postgresql + ${postgresql.version} + + org.opengauss opengauss-jdbc @@ -535,6 +552,19 @@ kingbase8 ${kingbase.jdbc.version} + + + org.xerial + sqlite-jdbc + ${sqlite.version} + + + + com.gitee.anwena + mongo-plus-boot-starter + ${anwena.version} + + diff --git a/framework/commons/src/main/java/cd/casic/framework/commons/util/network/IpUtil.java b/framework/commons/src/main/java/cd/casic/framework/commons/util/network/IpUtil.java index 3567d4a..5301bbd 100644 --- a/framework/commons/src/main/java/cd/casic/framework/commons/util/network/IpUtil.java +++ b/framework/commons/src/main/java/cd/casic/framework/commons/util/network/IpUtil.java @@ -96,6 +96,5 @@ public class IpUtil { String bathPath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + "/"; // log.info("当前域名:[{}]", bathPath); return bathPath; - // return "http://wuwenbin.me/"; } } diff --git a/framework/pom.xml b/framework/pom.xml index fa80347..6696e58 100644 --- a/framework/pom.xml +++ b/framework/pom.xml @@ -25,6 +25,8 @@ spring-boot-starter-test spring-boot-starter-web spring-boot-starter-websocket + spring-boot-starter-mongo + spring-boot-starter-plugin framework diff --git a/framework/spring-boot-starter-mongo/pom.xml b/framework/spring-boot-starter-mongo/pom.xml new file mode 100644 index 0000000..6e8fc4f --- /dev/null +++ b/framework/spring-boot-starter-mongo/pom.xml @@ -0,0 +1,40 @@ + + + 4.0.0 + + framework + cd.casic.boot + ${revision} + + + spring-boot-starter-mongo + ${project.artifactId} + + spring-boot-starter-mongo + + + + cd.casic.boot + commons + + + + org.springframework.boot + spring-boot-starter-data-mongodb + + + + com.gitee.anwena + mongo-plus-boot-starter + + + + + cd.casic.boot + spring-boot-starter-test + test + + + + diff --git a/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/config/TransactionConfig.java b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/config/TransactionConfig.java new file mode 100644 index 0000000..77c0eef --- /dev/null +++ b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/config/TransactionConfig.java @@ -0,0 +1,20 @@ +package cd.casic.framework.mongo.core.config; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.data.mongodb.MongoDatabaseFactory; +import org.springframework.data.mongodb.MongoTransactionManager; + +/** + * @author mianbin + * @date 2023-08-08 9:39 + * @description + */ +@AutoConfiguration +public class TransactionConfig { + + @Bean + public MongoTransactionManager transactionManager(MongoDatabaseFactory factory) { + return new MongoTransactionManager(factory); + } +} diff --git a/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/constant/ECompare.java b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/constant/ECompare.java new file mode 100644 index 0000000..b4ceaba --- /dev/null +++ b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/constant/ECompare.java @@ -0,0 +1,14 @@ +package cd.casic.framework.mongo.core.constant; + +public enum ECompare { + EQ, + NE, + LE, + LT, + GE, + GT, + BW, + IN, + NIN, + ISNULL +} diff --git a/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/constant/EConditionType.java b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/constant/EConditionType.java new file mode 100644 index 0000000..7ee7959 --- /dev/null +++ b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/constant/EConditionType.java @@ -0,0 +1,6 @@ +package cd.casic.framework.mongo.core.constant; + +public enum EConditionType { + AND, + OR, +} diff --git a/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/constant/ESortType.java b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/constant/ESortType.java new file mode 100644 index 0000000..bce289e --- /dev/null +++ b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/constant/ESortType.java @@ -0,0 +1,6 @@ +package cd.casic.framework.mongo.core.constant; + +public enum ESortType { + ASC, + DESC +} diff --git a/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/entity/Condition.java b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/entity/Condition.java new file mode 100644 index 0000000..1398061 --- /dev/null +++ b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/entity/Condition.java @@ -0,0 +1,46 @@ +package cd.casic.framework.mongo.core.entity; + + +import cd.casic.framework.mongo.core.constant.ECompare; +import cd.casic.framework.mongo.core.constant.EConditionType; +import cd.casic.framework.mongo.core.wrapper.ConditionWrapper; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; + +@Data +@NoArgsConstructor +public class Condition { + /** + * 嵌套比较类型 + * */ + private EConditionType conditionType = EConditionType.AND; + /** + * 比较类型 + * */ + private ECompare type; + + /** + * 集合对应的列 + * */ + private String column; + + /** + * 比较值 + * */ + private Map args; + + private ConditionWrapper conditionWrapper; + + public Condition(ECompare type, String column, Map args){ + this.type = type; + this.column = column; + this.args = args; + } + + public Condition(ECompare type, String column){ + this.type = type; + this.column = column; + } +} diff --git a/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/entity/Page.java b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/entity/Page.java new file mode 100644 index 0000000..dfd30a0 --- /dev/null +++ b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/entity/Page.java @@ -0,0 +1,28 @@ +package cd.casic.framework.mongo.core.entity; + +import lombok.Data; + +import java.util.Collections; +import java.util.List; + +@Data +public class Page{ + /** + * 查询数据列表 + */ + private List records = Collections.emptyList(); + + /** + * 总数 + */ + private long total; + /** + * 当前页 + */ + private int pageNum = 1; + /** + * 每页大小,默认10 + */ + private int pageSize = 10; +} + diff --git a/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/entity/SelectField.java b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/entity/SelectField.java new file mode 100644 index 0000000..44ca4ec --- /dev/null +++ b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/entity/SelectField.java @@ -0,0 +1,15 @@ +package cd.casic.framework.mongo.core.entity; + +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class SelectField { + + private String column; + + public SelectField(String column){ + this.column = column; + } +} diff --git a/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/entity/SortCondition.java b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/entity/SortCondition.java new file mode 100644 index 0000000..7b87351 --- /dev/null +++ b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/entity/SortCondition.java @@ -0,0 +1,19 @@ +package cd.casic.framework.mongo.core.entity; + +import cd.casic.framework.mongo.core.constant.ESortType; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class SortCondition { + + /** + * 排序类型 + */ + private ESortType sortType = ESortType.ASC; + + private String column; +} diff --git a/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/impl/MongoServiceImpl.java b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/impl/MongoServiceImpl.java new file mode 100644 index 0000000..8f3e96b --- /dev/null +++ b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/impl/MongoServiceImpl.java @@ -0,0 +1,169 @@ +package cd.casic.framework.mongo.core.impl; + +import cd.casic.framework.mongo.core.entity.Page; +import cd.casic.framework.mongo.core.sdk.MongoService; +import cd.casic.framework.mongo.core.wrapper.LambdaQueryWrapper; +import cd.casic.framework.mongo.utils.ClassUtils; +import cd.casic.framework.mongo.utils.QueryBuildUtils; +import com.mongodb.client.result.DeleteResult; +import com.mongodb.client.result.UpdateResult; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; + +import java.io.Serializable; +import java.lang.reflect.Field; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +@SuppressWarnings("all") +public class MongoServiceImpl implements MongoService { + + private final Class targetClass = (Class) ClassUtils.getClass(this); + + @Autowired + protected MongoTemplate mongoTemplate; + + @Override + public T getOne(LambdaQueryWrapper queryWrapper) { + Query query = QueryBuildUtils.buildQuery(queryWrapper); + return mongoTemplate.findOne(query, targetClass); + } + + @Override + public boolean save(T entity) { + return Objects.nonNull(mongoTemplate.save(entity)); + } + + @Override + public boolean saveBatch(Collection entityList) { + mongoTemplate.insertAll(entityList); + return true; + } + + @Override + public boolean removeById(Serializable id) { + + Criteria criteria = Criteria.where("_id").is(id); + Query query = new Query(criteria); + DeleteResult deleteResult = mongoTemplate.remove(query, targetClass); + return deleteResult.getDeletedCount() > 0; + } + + @Override + public long remove(LambdaQueryWrapper queryWrapper) { + Query query = QueryBuildUtils.buildQuery(queryWrapper); + DeleteResult deleteResult = mongoTemplate.remove(query, targetClass); + return deleteResult.getDeletedCount(); + } + + @Override + public boolean updateById(T entity) { + + Criteria criteria = Criteria.where("_id").is(ClassUtils.getId(entity)); + Query query = new Query(criteria); + Update update = getUpdate(entity); + UpdateResult updateResult = mongoTemplate.updateFirst(query,update, targetClass); + return updateResult.getModifiedCount() > 0; + } + + private Update getUpdate(T entity){ + + Update update = new Update(); + for (Field field : entity.getClass().getDeclaredFields()){ + try { + field.setAccessible(true); + Object result = field.get(entity); + if (Objects.nonNull(result)){ + update.set(field.getName(), result); + } + } catch (Exception e) { + + } + } + return update; + } + + @Override + public long update(T entity, LambdaQueryWrapper queryWrapper) { + + Query query = QueryBuildUtils.buildQuery(queryWrapper); + Update update = getUpdate(entity); + UpdateResult updateResult = mongoTemplate.updateFirst(query,update,targetClass); + return updateResult.getModifiedCount(); + } + + @Override + public long update(Update update, LambdaQueryWrapper queryWrapper) { + Query query = QueryBuildUtils.buildQuery(queryWrapper); + UpdateResult updateResult = mongoTemplate.updateFirst(query,update,targetClass); + return updateResult.getModifiedCount(); + } + + @Override + public boolean upsert(T entity, LambdaQueryWrapper queryWrapper) { + + Query query = QueryBuildUtils.buildQuery(queryWrapper); + Update update = getUpdate(entity); + UpdateResult updateResult = mongoTemplate.upsert(query,update,targetClass); + return updateResult.getModifiedCount() > 0; + } + + @Override + public T getById(Serializable id) { + + Criteria criteria = Criteria.where("_id").is(id); + Query query = new Query(criteria); + return mongoTemplate.findOne(query, targetClass); + } + + @Override + public long count(LambdaQueryWrapper queryWrapper) { + + Query query = QueryBuildUtils.buildQuery(queryWrapper); + return mongoTemplate.count(query, targetClass); + } + + @Override + public List list(LambdaQueryWrapper queryWrapper) { + + Query query = QueryBuildUtils.buildQuery(queryWrapper); + return mongoTemplate.find(query, targetClass); + } + + @Override + public Page page(LambdaQueryWrapper queryWrapper, int pageNum, int pageSize) { + + Query query = QueryBuildUtils.buildQuery(queryWrapper); + Page page = new Page<>(); + page.setPageNum(pageNum); + page.setPageSize(pageSize); + long total = mongoTemplate.count(query, targetClass); + page.setTotal(total); + if (total <= 0){ + return page; + } + query.skip((long) (pageNum - 1) * pageSize).limit(pageSize); + List list = mongoTemplate.find(query, targetClass); + page.setRecords(list); + return page; + } + + @Override + public boolean exist(LambdaQueryWrapper queryWrapper) { + + Query query = QueryBuildUtils.buildQuery(queryWrapper); + return mongoTemplate.exists(query, targetClass); + } + + @Override + public Collection listByIds(Collection idList) { + + Criteria criteria = Criteria.where("_id").in(idList); + Query query = new Query(criteria); + return mongoTemplate.find(query, targetClass); + } +} diff --git a/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/sdk/Compare.java b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/sdk/Compare.java new file mode 100644 index 0000000..6cb52b5 --- /dev/null +++ b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/sdk/Compare.java @@ -0,0 +1,67 @@ +package cd.casic.framework.mongo.core.sdk; + +import java.io.Serializable; +import java.util.Collection; + +public interface Compare extends Serializable { + + default LambdaQueryWrapper eq(R column, Object value){ + return eq(true, column, value); + } + + default LambdaQueryWrapper ne(R column, Object value){ + return ne(true,column,value); + } + + default LambdaQueryWrapper le(R column, Object value){ + return le(true,column,value); + } + + default LambdaQueryWrapper lt(R column, Object value){ + return lt(true,column,value); + } + + default LambdaQueryWrapper ge(R column, Object value){ + return ge(true,column,value); + } + + default LambdaQueryWrapper gt(R column, Object value){ + return gt(true,column,value); + } + + default LambdaQueryWrapper between(R column, Object leftValue, Object rightValue){ + return between(true,column,leftValue,rightValue); + } + + default LambdaQueryWrapper in(R column, Collection values){ + return in(true,column,values); + } + + default LambdaQueryWrapper notIn(R column, Collection values){ + return notIn(true,column,values); + } + + default LambdaQueryWrapper isNull(R column){ + return isNull(true, column); + } + + LambdaQueryWrapper eq(Boolean condition,R column, Object value); + + LambdaQueryWrapper ne(Boolean condition,R column, Object value); + + LambdaQueryWrapper le(Boolean condition,R column, Object value); + + LambdaQueryWrapper lt(Boolean condition,R column, Object value); + + LambdaQueryWrapper ge(Boolean condition,R column, Object value); + + LambdaQueryWrapper gt(Boolean condition,R column, Object value); + + LambdaQueryWrapper between(Boolean condition,R column, Object leftValue, Object rightValue); + + LambdaQueryWrapper in(Boolean condition, R column, Collection values); + + LambdaQueryWrapper notIn(Boolean condition,R column, Collection values); + + LambdaQueryWrapper isNull(Boolean condition, R column); +} diff --git a/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/sdk/Function.java b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/sdk/Function.java new file mode 100644 index 0000000..f0668c4 --- /dev/null +++ b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/sdk/Function.java @@ -0,0 +1,24 @@ +package cd.casic.framework.mongo.core.sdk; + +import java.io.Serializable; + +public interface Function> extends Serializable { + + default LambdaQueryWrapper orderByAsc(R column){ + return orderByAsc(true,column); + } + + default LambdaQueryWrapper orderByDesc(R column){ + return orderByDesc(true,column); + } + + LambdaQueryWrapper orderByAsc(boolean condition, R column); + + LambdaQueryWrapper orderByDesc(boolean condition, R column); + + LambdaQueryWrapper skip(Long skip); + + LambdaQueryWrapper limit(Integer limit); + + LambdaQueryWrapper select(R... columns); +} diff --git a/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/sdk/MongoService.java b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/sdk/MongoService.java new file mode 100644 index 0000000..40405e5 --- /dev/null +++ b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/sdk/MongoService.java @@ -0,0 +1,42 @@ +package cd.casic.framework.mongo.core.sdk; + +import cd.casic.framework.mongo.core.entity.Page; +import cd.casic.framework.mongo.core.wrapper.LambdaQueryWrapper; +import org.springframework.data.mongodb.core.query.Update; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; + +public interface MongoService { + + T getOne(LambdaQueryWrapper queryWrapper); + + boolean save(T entity); + + boolean saveBatch(Collection entityList); + + boolean removeById(Serializable id); + + long remove(LambdaQueryWrapper queryWrapper); + + boolean updateById(T entity); + + long update(T entity, LambdaQueryWrapper queryWrapper); + + long update(Update update, LambdaQueryWrapper queryWrapper); + + boolean upsert(T entity, LambdaQueryWrapper queryWrapper); + + T getById(Serializable id); + + Collection listByIds(Collection idList); + + long count(LambdaQueryWrapper queryWrapper); + + List list(LambdaQueryWrapper queryWrapper); + + Page page(LambdaQueryWrapper queryWrapper, int pageNum, int pageSize); + + boolean exist(LambdaQueryWrapper queryWrapper); +} diff --git a/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/sdk/Nested.java b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/sdk/Nested.java new file mode 100644 index 0000000..d29f6ea --- /dev/null +++ b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/sdk/Nested.java @@ -0,0 +1,15 @@ +package cd.casic.framework.mongo.core.sdk; + +import java.io.Serializable; +import java.util.function.Function; + +public interface Nested extends Serializable { + + default LambdaQueryWrapper and(Function function){ + return and(true,function); + } + + LambdaQueryWrapper or(); + + LambdaQueryWrapper and(boolean condition, Function function); +} diff --git a/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/sdk/SFunction.java b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/sdk/SFunction.java new file mode 100644 index 0000000..2b9a4aa --- /dev/null +++ b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/sdk/SFunction.java @@ -0,0 +1,9 @@ +package cd.casic.framework.mongo.core.sdk; + +import java.io.Serializable; + +@FunctionalInterface +public interface SFunction extends Serializable { + + R apply(T t); +} diff --git a/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/wrapper/ConditionWrapper.java b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/wrapper/ConditionWrapper.java new file mode 100644 index 0000000..a7df0d0 --- /dev/null +++ b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/wrapper/ConditionWrapper.java @@ -0,0 +1,27 @@ +package cd.casic.framework.mongo.core.wrapper; + +import cd.casic.framework.mongo.core.entity.Condition; +import cd.casic.framework.mongo.core.entity.SelectField; +import cd.casic.framework.mongo.core.entity.SortCondition; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Collections; +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ConditionWrapper { + private List fields = Collections.emptyList(); + + private List conditions = Collections.emptyList(); + + private List sortConditions = Collections.emptyList(); + + private Long skip; + + private Integer limit; + +} diff --git a/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/wrapper/LambdaQueryWrapper.java b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/wrapper/LambdaQueryWrapper.java new file mode 100644 index 0000000..97b2cd7 --- /dev/null +++ b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/wrapper/LambdaQueryWrapper.java @@ -0,0 +1,210 @@ +package cd.casic.framework.mongo.core.wrapper; + +import cd.casic.framework.mongo.core.constant.ECompare; +import cd.casic.framework.mongo.core.constant.EConditionType; +import cd.casic.framework.mongo.core.constant.ESortType; +import cd.casic.framework.mongo.core.entity.Condition; +import cd.casic.framework.mongo.core.entity.SelectField; +import cd.casic.framework.mongo.core.entity.SortCondition; +import cd.casic.framework.mongo.core.sdk.Compare; +import cd.casic.framework.mongo.core.sdk.Function; +import cd.casic.framework.mongo.core.sdk.Nested; +import cd.casic.framework.mongo.core.sdk.SFunction; +import cd.casic.framework.mongo.utils.ConvertUtils; + +import java.util.*; +import java.util.stream.Collectors; + +public class LambdaQueryWrapper + implements + Compare, SFunction>, + Function, SFunction>, + Nested, LambdaQueryWrapper> { + + private final List fields = new ArrayList<>(5); + + private final List conditions = new ArrayList<>(5); + + private final List sortConditions = new ArrayList<>(5); + + private Long skip; + + private Integer limit; + + public ConditionWrapper getCondition(){ + return new ConditionWrapper(fields,conditions,sortConditions,skip,limit); + } + + private String getFieldMeta(SFunction column){ + return ConvertUtils.convertToFieldName(column); + } + + + @Override + public LambdaQueryWrapper eq(Boolean condition, SFunction column, Object value) { + if(condition){ + Map map = new HashMap<>(); + map.put("eq",value); + conditions.add(new Condition(ECompare.EQ, getFieldMeta(column), map)); + } + return this; + } + + @Override + public LambdaQueryWrapper ne(Boolean condition, SFunction column, Object value) { + if(condition){ + Map map = new HashMap<>(); + map.put("ne",value); + conditions.add(new Condition(ECompare.NE,getFieldMeta(column), map)); + } + return this; + } + + @Override + public LambdaQueryWrapper le(Boolean condition, SFunction column, Object value) { + if(condition){ + Map map = new HashMap<>(); + map.put("le",value); + conditions.add(new Condition(ECompare.LE,getFieldMeta(column), map)); + } + return this; + } + + @Override + public LambdaQueryWrapper lt(Boolean condition, SFunction column, Object value) { + if(condition){ + Map map = new HashMap<>(); + map.put("lt",value); + conditions.add(new Condition(ECompare.LT,getFieldMeta(column), map)); + } + return this; + } + + @Override + public LambdaQueryWrapper ge(Boolean condition, SFunction column, Object value) { + if(condition){ + Map map = new HashMap<>(); + map.put("ge",value); + conditions.add(new Condition(ECompare.GE,getFieldMeta(column), map)); + } + return this; + } + + @Override + public LambdaQueryWrapper gt(Boolean condition, SFunction column, Object value) { + if(condition){ + Map map = new HashMap<>(); + map.put("gt",value); + conditions.add(new Condition(ECompare.GT,getFieldMeta(column), map)); + } + return this; + } + + @Override + public LambdaQueryWrapper between(Boolean condition, SFunction column, Object leftValue, Object rightValue) { + if(condition){ + Map map = new HashMap<>(); + map.put("bw1",leftValue); + map.put("bw2",rightValue); + conditions.add(new Condition(ECompare.BW,getFieldMeta(column), map)); + } + return this; + } + + @Override + public LambdaQueryWrapper in(Boolean condition, SFunction column, Collection values) { + if(condition){ + Map map = new HashMap<>(); + map.put("in",values); + conditions.add(new Condition(ECompare.IN,getFieldMeta(column), map)); + } + return this; + } + + @Override + public LambdaQueryWrapper notIn(Boolean condition, SFunction column, Collection values) { + if(condition){ + Map map = new HashMap<>(); + map.put("notIn", values); + conditions.add(new Condition(ECompare.NIN,getFieldMeta(column), map)); + } + return this; + } + + @Override + public LambdaQueryWrapper isNull(Boolean condition, SFunction column) { + if (condition){ + conditions.add(new Condition(ECompare.ISNULL,getFieldMeta(column))); + } + return this; + } + + @Override + public LambdaQueryWrapper or() { + if (conditions.size() == 0){ + throw new RuntimeException("not first use or"); + } + Condition lastCondition = conditions.get(conditions.size() - 1); + ConditionWrapper conditionWrapper = lastCondition.getConditionWrapper(); + if (Objects.isNull(conditionWrapper)){ + conditionWrapper = new ConditionWrapper(fields,conditions,sortConditions,skip,limit); + } + List sub = conditionWrapper.getConditions(); + Condition condition = sub.get(sub.size() - 1); + condition.setConditionType(EConditionType.OR); + return this; + } + + @Override + public LambdaQueryWrapper and(boolean condition, java.util.function.Function, LambdaQueryWrapper> function) { + if (condition){ + LambdaQueryWrapper apply = function.apply(new LambdaQueryWrapper<>()); + if (this.conditions.size() == 0){ + throw new RuntimeException("not first use and"); + } + ConditionWrapper conditionWrapper = apply.getCondition(); + Condition c = new Condition(); + c.setConditionWrapper(conditionWrapper); + this.conditions.add(c); + } + return this; + } + + @Override + public LambdaQueryWrapper orderByAsc(boolean condition, SFunction column) { + if (condition){ + sortConditions.add(new SortCondition(ESortType.ASC,getFieldMeta(column))); + } + return this; + } + + @Override + public LambdaQueryWrapper orderByDesc(boolean condition, SFunction column) { + if (condition){ + sortConditions.add(new SortCondition(ESortType.DESC,getFieldMeta(column))); + } + return this; + } + + @Override + public LambdaQueryWrapper skip(Long skip) { + this.skip = skip; + return this; + } + + @Override + public LambdaQueryWrapper limit(Integer limit) { + this.limit = limit; + return this; + } + + @SafeVarargs + @Override + public final LambdaQueryWrapper select(SFunction... columns) { + if (Objects.nonNull(columns) && columns.length > 0){ + List fields = Arrays.stream(columns).map(column -> new SelectField(getFieldMeta(column))).collect(Collectors.toList()); + this.fields.addAll(fields); + } + return this; + } +} diff --git a/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/wrapper/Wrappers.java b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/wrapper/Wrappers.java new file mode 100644 index 0000000..30390de --- /dev/null +++ b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/core/wrapper/Wrappers.java @@ -0,0 +1,8 @@ +package cd.casic.framework.mongo.core.wrapper; + +public final class Wrappers { + + public static LambdaQueryWrapper lambdaQuery(){ + return new LambdaQueryWrapper<>(); + } +} diff --git a/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/utils/ClassUtils.java b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/utils/ClassUtils.java new file mode 100644 index 0000000..7f4f01e --- /dev/null +++ b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/utils/ClassUtils.java @@ -0,0 +1,82 @@ +package cd.casic.framework.mongo.utils; + +import org.springframework.data.annotation.Id; + +import java.io.Serializable; +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +public class ClassUtils { + + private ClassUtils(){ + } + + /** + * 缓存目标类型上的泛型值 + */ + private static final Map, Class> CACHE = new ConcurrentHashMap<>(64); + + /** + * 缓存mongo实体的主键字段 + */ + private static final Map, Field> FIELD_CACHE = new ConcurrentHashMap<>(64); + + + /** + * 获取集合实体的主键值 + */ + public static Serializable getId(Object object){ + + Field field = getIdField(object); + field.setAccessible(true); + try { + return (Serializable) field.get(object); + } catch (IllegalAccessException e) { + throw new RuntimeException("not exist id value"); + } + } + + /** + * 获取集合实体的主键字段 + */ + public static Field getIdField(Object object){ + + Class clazz = object.getClass(); + Field result = FIELD_CACHE.get(clazz); + if (Objects.nonNull(result)){ + return result; + } + for (Field field : clazz.getDeclaredFields()){ + Id annotation = field.getAnnotation(Id.class); + if (Objects.nonNull(annotation)){ + FIELD_CACHE.put(clazz,field); + return field; + } + } + throw new RuntimeException("no exist id"); + } + + /** + * 获取对象上的泛型 + */ + public static Class getClass(Object object){ + + if (Objects.isNull(object)){ + return null; + } + Class clazz = object.getClass(); + Class result = CACHE.get(clazz); + if (Objects.nonNull(result)){ + return result; + } + Type aClass = object.getClass().getGenericSuperclass(); + Type subType = ((ParameterizedType) aClass).getActualTypeArguments()[0]; + result = (Class) subType; + CACHE.put(clazz,result); + return result; + } +} diff --git a/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/utils/ConvertUtils.java b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/utils/ConvertUtils.java new file mode 100644 index 0000000..540fd0b --- /dev/null +++ b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/utils/ConvertUtils.java @@ -0,0 +1,62 @@ +package cd.casic.framework.mongo.utils; + +import cd.casic.framework.mongo.core.sdk.SFunction; + +import java.io.Serializable; +import java.lang.invoke.SerializedLambda; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +public class ConvertUtils { + + public static final String GET = "get"; + + public static final String IS = "is"; + + private static final Map,String> CLASS_FIELD_META_MAP = new ConcurrentHashMap<>(); + + private ConvertUtils(){ + } + + public static String convertToFieldName(SFunction function){ + + SerializedLambda lambda = getSerializedLambda(function); + String cacheDate = CLASS_FIELD_META_MAP.get(function.getClass()); + if (Objects.nonNull(cacheDate)){ + return cacheDate; + } + String methodName = lambda.getImplMethodName(); + if (methodName.startsWith(GET)){ + methodName = methodName.substring(3); + } + else if (methodName.startsWith(IS)){ + methodName = methodName.substring(2); + } + else { + throw new IllegalArgumentException("无效的getter方法" + methodName); + } + try{ + String fieldMeta = StringUtils.firstToLowerCase(methodName); + CLASS_FIELD_META_MAP.put(function.getClass(),fieldMeta); + return fieldMeta; + }catch (Exception e){ + throw new RuntimeException(e); + } + } + + public static SerializedLambda getSerializedLambda(Serializable serializable){ + + SerializedLambda serializedLambda; + try{ + Method method = serializable.getClass().getDeclaredMethod("writeReplace"); + method.setAccessible(Boolean.TRUE); + serializedLambda = (SerializedLambda) method.invoke(serializable); + }catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e){ + throw new RuntimeException(e); + } + return serializedLambda; + } +} diff --git a/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/utils/QueryBuildUtils.java b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/utils/QueryBuildUtils.java new file mode 100644 index 0000000..a06bbaa --- /dev/null +++ b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/utils/QueryBuildUtils.java @@ -0,0 +1,174 @@ +package cd.casic.framework.mongo.utils; + + +import cd.casic.framework.mongo.core.constant.ECompare; +import cd.casic.framework.mongo.core.constant.EConditionType; +import cd.casic.framework.mongo.core.constant.ESortType; +import cd.casic.framework.mongo.core.entity.Condition; +import cd.casic.framework.mongo.core.entity.SortCondition; +import cd.casic.framework.mongo.core.wrapper.ConditionWrapper; +import cd.casic.framework.mongo.core.wrapper.LambdaQueryWrapper; +import org.springframework.data.domain.Sort; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.CriteriaDefinition; +import org.springframework.data.mongodb.core.query.Field; +import org.springframework.data.mongodb.core.query.Query; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class QueryBuildUtils { + + private static final Map> HANDLERS = new ConcurrentHashMap<>(); + + static { + HANDLERS.put(ECompare.EQ,QueryBuildUtils::eqHandle); + HANDLERS.put(ECompare.NE,QueryBuildUtils::neHandle); + HANDLERS.put(ECompare.LE,QueryBuildUtils::leHandle); + HANDLERS.put(ECompare.LT,QueryBuildUtils::ltHandle); + HANDLERS.put(ECompare.GE,QueryBuildUtils::geHandle); + HANDLERS.put(ECompare.GT,QueryBuildUtils::gtHandle); + HANDLERS.put(ECompare.BW,QueryBuildUtils::bwHandle); + HANDLERS.put(ECompare.IN,QueryBuildUtils::inHandle); + HANDLERS.put(ECompare.NIN,QueryBuildUtils::ninHandle); + HANDLERS.put(ECompare.ISNULL,QueryBuildUtils::isNullHandle); + } + + public static Query buildQuery(LambdaQueryWrapper queryWrapper){ + return buildQuery(queryWrapper.getCondition()); + } + + public static Query buildQuery(ConditionWrapper wrapper){ + Criteria criteria = new Criteria(); + + //构建查询参数 + Map map = buildCondition(wrapper); + Query query = new Query((CriteriaDefinition) map.get("criteria")); + + //构建排序参数 + List sortConditions = wrapper.getSortConditions(); + if (Objects.nonNull(sortConditions)){ + query.with(Sort.by(buildSort(sortConditions))); + } + + //构建分页参数 + if (Objects.nonNull(wrapper.getSkip())){ + query.skip(wrapper.getSkip()); + } + if (Objects.nonNull(wrapper.getLimit())){ + query.limit(wrapper.getLimit()); + } + + //构建查询列 + if(Objects.nonNull(wrapper.getFields()) && wrapper.getFields().size() > 0){ + Field fields = query.fields(); + wrapper.getFields().forEach(item ->fields.include(item.getColumn())); + } + + return query; + } + + public static Map buildCondition(ConditionWrapper wrapper){ + + Map map = new HashMap<>(); + Criteria criteria = new Criteria(); + if (Objects.isNull(wrapper) || Objects.isNull(wrapper.getConditions()) || wrapper.getConditions().size() ==0){ + map.put("criteria",criteria); + map.put("critters",new Criteria[]{criteria}); + return map; + } + List conditions = wrapper.getConditions(); + + boolean isOr = false; + Criteria[] critters = new Criteria[conditions.size()]; + for (int i = 0; i < conditions.size(); i++) { + Condition condition = conditions.get(i); + if (Objects.nonNull(condition.getConditionWrapper()) && Objects.isNull(condition.getColumn()) && condition.getConditionWrapper().getConditions().size() > 0){ + Criteria curCriteria = new Criteria(); + Condition first = condition.getConditionWrapper().getConditions().get(0); + Map map1 = buildCondition(condition.getConditionWrapper()); + if (first.getConditionType() == EConditionType.OR){ + curCriteria.orOperator((Criteria[]) map1.get("critters")); + }else { + curCriteria.andOperator((Criteria[]) map1.get("critters")); + } + critters[i] = curCriteria; + continue; + } + ECompare type = condition.getType(); + if (condition.getConditionType() == EConditionType.OR){ + isOr = true; + } + Function handler = HANDLERS.get(type); + if (Objects.isNull(handler)){ + throw new RuntimeException("buildQuery error not have queryType" + type); + } + Criteria curCriteria = handler.apply(condition); + critters[i] = curCriteria; + } + if (isOr){ + criteria.orOperator(critters); + }else { + criteria.andOperator(critters); + } + map.put("criteria",criteria); + map.put("critters",critters); + return map; + } + + private static List buildSort(List sortConditions){ + return sortConditions.stream().map(sortCondition -> { + if (sortCondition.getSortType() == ESortType.ASC){ + return Sort.Order.asc(sortCondition.getColumn()); + }else { + return Sort.Order.desc(sortCondition.getColumn()); + } + }).collect(Collectors.toList()); + } + + public static Criteria eqHandle(Condition condition){ + return Criteria.where(condition.getColumn()).is(condition.getArgs().get("eq")); + } + + public static Criteria neHandle(Condition condition){ + return Criteria.where(condition.getColumn()).ne(condition.getArgs().get("ne")); + } + + public static Criteria leHandle(Condition condition){ + return Criteria.where(condition.getColumn()).lte(condition.getArgs().get("le")); + } + + public static Criteria ltHandle(Condition condition){ + return Criteria.where(condition.getColumn()).lt(condition.getArgs().get("lt")); + } + + public static Criteria geHandle(Condition condition){ + return Criteria.where(condition.getColumn()).gte(condition.getArgs().get("ge")); + } + + public static Criteria gtHandle(Condition condition){ + return Criteria.where(condition.getColumn()).gt(condition.getArgs().get("gt")); + } + + public static Criteria bwHandle(Condition condition){ + return Criteria.where(condition.getColumn()).gte(condition.getArgs().get("bw1")).lte(condition.getArgs().get("bw2")); + } + + public static Criteria inHandle(Condition condition){ + Map map = condition.getArgs(); + Collection in = (Collection) map.get("in"); + return Criteria.where(condition.getColumn()).in(in); + } + + public static Criteria ninHandle(Condition condition){ + Map map = condition.getArgs(); + List notIn = (List) map.get("notIn"); + return Criteria.where(condition.getColumn()).nin(notIn); + } + + public static Criteria isNullHandle(Condition condition){ + return Criteria.where(condition.getColumn()).isNull(); + } +} diff --git a/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/utils/StringUtils.java b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/utils/StringUtils.java new file mode 100644 index 0000000..6d0c0bf --- /dev/null +++ b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/utils/StringUtils.java @@ -0,0 +1,11 @@ +package cd.casic.framework.mongo.utils; + +public class StringUtils { + + public static String firstToLowerCase(final String str){ + if (null == str || str.length() == 0){ + return ""; + } + return str.substring(0,1).toLowerCase() + str.substring(1); + } +} diff --git a/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/utils/UpdateBuilder.java b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/utils/UpdateBuilder.java new file mode 100644 index 0000000..6d67b6e --- /dev/null +++ b/framework/spring-boot-starter-mongo/src/main/java/cd/casic/framework/mongo/utils/UpdateBuilder.java @@ -0,0 +1,51 @@ +package cd.casic.framework.mongo.utils; + +import cd.casic.framework.mongo.core.sdk.SFunction; +import org.springframework.data.mongodb.core.query.Update; + +/** + * @author songqiang + * @date 2023-08-01 11:21 + * @description 构造update + */ +public class UpdateBuilder { + + private Update update; + + private UpdateBuilder() { + + } + + public static UpdateBuilder getInstance() { + UpdateBuilder builder = new UpdateBuilder<>(); + builder.setUpdate(new Update()); + return builder; + } + + public UpdateBuilder set(SFunction column, Object value) { + this.update.set(getFieldMeta(column), value); + return this; + } + + public UpdateBuilder incr(SFunction column, Number value) { + this.update.inc(getFieldMeta(column), value); + return this; + } + + public UpdateBuilder decr(SFunction column, Integer value) { + this.update.inc(getFieldMeta(column), -value); + return this; + } + + private void setUpdate(Update update) { + this.update = update; + } + + public Update build() { + return update; + } + + private String getFieldMeta(SFunction column){ + return ConvertUtils.convertToFieldName(column); + } +} diff --git a/framework/spring-boot-starter-mongo/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/framework/spring-boot-starter-mongo/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..8686060 --- /dev/null +++ b/framework/spring-boot-starter-mongo/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +cd.casic.framework.mongo.core.config.TransactionConfig diff --git a/framework/spring-boot-starter-plugin/pom.xml b/framework/spring-boot-starter-plugin/pom.xml new file mode 100644 index 0000000..19fd756 --- /dev/null +++ b/framework/spring-boot-starter-plugin/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + framework + cd.casic.boot + ${revision} + + + spring-boot-starter-plugin + jar + + ${project.artifactId} + + + + cd.casic.boot + commons + + + + cd.casic.boot + spring-boot-starter-biz-data-permission + + + + cd.casic.boot + spring-boot-starter-websocket + + + + cd.casic.boot + spring-boot-starter-mongo + + + + org.xerial + sqlite-jdbc + + + + org.pf4j + pf4j + + + + com.github.xiaoymin + knife4j-openapi3-jakarta-spring-boot-starter + + + + org.springdoc + springdoc-openapi-starter-webmvc-api + + + + + cd.casic.boot + spring-boot-starter-test + test + + + + diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/BasePlugin.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/BasePlugin.java new file mode 100644 index 0000000..b56e9b6 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/BasePlugin.java @@ -0,0 +1,29 @@ +package cd.casic.plugin; + +import org.pf4j.Plugin; +import org.pf4j.PluginWrapper; + +/** + * @Author:mianbin + * @Package:cd.casic.plugin + * @Project:ops + * @name:BasePlugin + * @Date:2024/03/18 9:25 + * @Filename:BasePlugin + * @description:插件基类 + */ +public abstract class BasePlugin extends Plugin { + + public BasePlugin(PluginWrapper wrapper) { + super(wrapper); + } + + /** + * 根据插件主类获取插件包 + * + * @return String + */ + public String scanPackage() { + return this.getClass().getPackage().getName(); + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/BufferedPluginBundleResource.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/BufferedPluginBundleResource.java new file mode 100644 index 0000000..f504f22 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/BufferedPluginBundleResource.java @@ -0,0 +1,151 @@ +package cd.casic.plugin; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DataBufferUtils; +import org.springframework.lang.Nullable; +import org.springframework.stereotype.Component; +import org.springframework.util.Assert; +import org.springframework.util.FileSystemUtils; +import org.springframework.web.server.ServerWebInputException; + +import java.io.BufferedOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + + +@Slf4j +@Component +public class BufferedPluginBundleResource implements DisposableBean { + + private final AtomicReference jsBundle = new AtomicReference<>(); + private final AtomicReference cssBundle = new AtomicReference<>(); + + private final ReadWriteLock jsLock = new ReentrantReadWriteLock(); + private final ReadWriteLock cssLock = new ReentrantReadWriteLock(); + + private Path tempDir; + + public FileSystemResource getJsBundle(String version, List dataBuffers) { + String fileName = tempFileName(version, ".js"); + jsLock.readLock().lock(); + try { + FileSystemResource jsBundleResource = jsBundle.get(); + if (getResourceIfNotChange(fileName, jsBundleResource) != null) { + return jsBundleResource; + } + } finally { + jsLock.readLock().unlock(); + } + + jsLock.writeLock().lock(); + try { + FileSystemResource oldJsBundle = jsBundle.get(); + FileSystemResource newJsBundle = writeBundle(fileName, dataBuffers); + jsBundle.compareAndSet(oldJsBundle, newJsBundle); + return newJsBundle; + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + jsLock.writeLock().unlock(); + } + } + + public FileSystemResource getCssBundle(String version, List dataBuffers) { + String fileName = tempFileName(version, ".css"); + try { + cssLock.readLock().lock(); + FileSystemResource cssBundleResource = cssBundle.get(); + if (getResourceIfNotChange(fileName, cssBundleResource) != null) { + return cssBundleResource; + } + } finally { + cssLock.readLock().unlock(); + } + cssLock.writeLock().lock(); + try { + FileSystemResource oldCssBundle = cssBundle.get(); + FileSystemResource newCssBundle = writeBundle(fileName, dataBuffers); + cssBundle.compareAndSet(oldCssBundle, newCssBundle); + return newCssBundle; + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + cssLock.writeLock().unlock(); + } + } + + @Nullable + private Resource getResourceIfNotChange(String fileName, Resource resource) { + if (resource != null && resource.exists() && fileName.equals(resource.getFilename())) { + return resource; + } + return null; + } + + private FileSystemResource writeBundle(String fileName, List dataBuffers) throws IOException { + Path filePath = createTempFileToStore(fileName); + // 打开文件输出流 + try (FileOutputStream fileOutputStream = new FileOutputStream(filePath.toFile()); + BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream)) { + + // 遍历 DataBuffer 列表并写入文件 + for (DataBuffer dataBuffer : dataBuffers) { + // 将 DataBuffer 转换为字节数组 + byte[] bytes = new byte[dataBuffer.readableByteCount()]; + dataBuffer.read(bytes); + // 写入文件 + bufferedOutputStream.write(bytes); + // 释放 DataBuffer + DataBufferUtils.release(dataBuffer); + } + } + return new FileSystemResource(filePath); + } + + + private Path createTempFileToStore(String fileName) { + try { + if (tempDir == null || !Files.exists(tempDir)) { + this.tempDir = Files.createTempDirectory("ops-plugin-bundle"); + } + Path path = tempDir.resolve(fileName); + log.info("Create temp file: {}", path); + Files.deleteIfExists(path); + return Files.createFile(path); + } catch (IOException e) { + throw new ServerWebInputException("Failed to create temp file.", null, e); + } + } + + /** + * 生成临时文件名 + * + * @param v + * @param suffix + * @return + */ + private String tempFileName(String v, String suffix) { + Assert.notNull(v, "Version must not be null"); + Assert.notNull(suffix, "Suffix must not be null"); + return v + suffix; + } + + @Override + public void destroy() throws Exception { + if (tempDir != null && Files.exists(tempDir)) { + FileSystemUtils.deleteRecursively(tempDir); + } + this.jsBundle.set(null); + this.cssBundle.set(null); + } +} \ No newline at end of file diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/FrontResourceProcessor.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/FrontResourceProcessor.java new file mode 100644 index 0000000..8019eed --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/FrontResourceProcessor.java @@ -0,0 +1,106 @@ +package cd.casic.plugin; + +import cd.casic.plugin.utils.FrontResourceUtils; +import cn.hutool.core.io.IoUtil; +import cn.hutool.crypto.digest.DigestUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.pf4j.PluginWrapper; +import org.pf4j.RuntimeMode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.Resource; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DefaultDataBufferFactory; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 前端资源处理器,将各个插件的css和js资源合并 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class FrontResourceProcessor { + + @Autowired + private PluginManager pluginManager; + + public List uglifyJsBundle() { + List dataBuffers = new ArrayList<>(); + List startedPlugins = pluginManager.getStartedPlugins(); + String plugins = String.format("this.enabledPluginNames = [%s];", + startedPlugins.stream() + .map(PluginWrapper::getPluginId) + .collect(Collectors.joining("','", "'", "'"))); + + for (PluginWrapper pluginWrapper : startedPlugins) { + String pluginName = pluginWrapper.getPluginId(); + Resource jsBundleResource = FrontResourceUtils.getJsBundleResource(pluginManager, pluginName, + FrontResourceUtils.JS_BUNDLE); + + if (jsBundleResource != null) { + try (InputStream inputStream = jsBundleResource.getInputStream()) { + byte[] bytes = IoUtil.readBytes(inputStream); + DataBuffer dataBuffer = DefaultDataBufferFactory.sharedInstance.wrap(bytes); + dataBuffers.add(dataBuffer); + // Add a new line after each plugin bundle to avoid syntax error + byte[] newLineBytes = "\n".getBytes(StandardCharsets.UTF_8); + dataBuffers.add(DefaultDataBufferFactory.sharedInstance.wrap(newLineBytes)); + } catch (IOException e) { + log.error("Failed to read plugin bundle resource", e); + } + } + } + + // Add the plugins JavaScript object at the end + byte[] pluginsBytes = plugins.getBytes(StandardCharsets.UTF_8); + DataBuffer pluginsDataBuffer = DefaultDataBufferFactory.sharedInstance.wrap(pluginsBytes); + dataBuffers.add(pluginsDataBuffer); + + + return dataBuffers; + } + + public List uglifyCssBundle() { + List cssDataBuffers = new ArrayList<>(); + for (PluginWrapper pluginWrapper : pluginManager.getStartedPlugins()) { + String pluginName = pluginWrapper.getPluginId(); + Resource resource = FrontResourceUtils.getJsBundleResource(pluginManager, pluginName, FrontResourceUtils.CSS_BUNDLE); + + if (resource != null) { + try (InputStream inputStream = resource.getInputStream()) { + byte[] cssBytes = IoUtil.readBytes(inputStream); // Assuming you have Apache Commons IO for this + + DataBuffer dataBuffer = new DefaultDataBufferFactory().wrap(cssBytes); + cssDataBuffers.add(dataBuffer); + } catch (IOException e) { + log.error("Failed to read plugin css bundle resource for plugin: {}", pluginName, e); + } + } + } + + return cssDataBuffers; + } + + public String generateJsBundleVersion() { + if (RuntimeMode.DEVELOPMENT.equals(pluginManager.getRuntimeMode())) { + return String.valueOf(System.currentTimeMillis()); + } + var compactVersion = pluginManager.getStartedPlugins() + .stream() + .sorted(Comparator.comparing(PluginWrapper::getPluginId)) + .map(pluginWrapper -> pluginWrapper.getPluginId() + ":" + + pluginWrapper.getDescriptor().getVersion() + ) + .collect(Collectors.joining()); + + return DigestUtil.sha256Hex(compactVersion); + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/IPluginSystemProperties.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/IPluginSystemProperties.java new file mode 100644 index 0000000..0d24004 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/IPluginSystemProperties.java @@ -0,0 +1,61 @@ + +package cd.casic.plugin; + +import org.pf4j.RuntimeMode; + +import java.util.Set; + +/** + * 插件系统属性接口,定义了插件系统的一些核心配置和操作 + */ +public interface IPluginSystemProperties { + + /** + * 检查插件系统是否启用 + * + * @return 如果插件系统启用则返回true,否则返回false + */ + Boolean enable(); + + /** + * 获取REST API路径前缀 + * + * @return REST API路径前缀字符串 + */ + String restPathPrefix(); + + /** + * 检查插件ID是否作为REST路径前缀启用 + * + * @return 如果启用插件ID作为REST路径前缀则返回true,否则返回false + */ + Boolean enablePluginIdAsRestPrefix(); + + /** + * 获取运行时模式 + * + * @return 当前运行时模式 + */ + RuntimeMode runtimeMode(); + + /** + * 获取插件根目录路径 + * + * @return 插件根目录路径字符串 + */ + String pluginsRoot(); + + /** + * 获取已启用插件的ID集合 + * + * @return 包含已启用插件ID的集合 + */ + Set enabledPlugins(); + + /** + * 获取已禁用插件的ID集合 + * + * @return 包含已禁用插件ID的集合 + */ + Set disabledPlugins(); +} \ No newline at end of file diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginApplicationContextManager.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginApplicationContextManager.java new file mode 100644 index 0000000..626882e --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginApplicationContextManager.java @@ -0,0 +1,32 @@ +package cd.casic.plugin; + +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @Author:mianbin + * @Package:cd.casic.plugin + * @Project:ops + * @name:PluginApplicationContextHolder + * @Date:2024/03/18 10:04 + * @Filename:PluginApplicationContextHolder + * @description:插件的上下文管理器 + */ +public class PluginApplicationContextManager { + + private final static Map pluginApplicationMap = new ConcurrentHashMap<>(); + + public static void addPluginApplicationContext(String pluginId, AnnotationConfigApplicationContext applicationContext) { + pluginApplicationMap.put(pluginId, applicationContext); + } + + public static void removePluginApplicationContext(String pluginId) { + pluginApplicationMap.remove(pluginId); + } + + public static AnnotationConfigApplicationContext getApplicationContext(String pluginId) { + return pluginApplicationMap.get(pluginId); + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginBean.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginBean.java new file mode 100644 index 0000000..1ba14d8 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginBean.java @@ -0,0 +1,47 @@ +package cd.casic.plugin; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * @Author:mianbin + * @Package:cd.casic.plugin + * @Project:ops + * @name:PluginBean + * @Date:2024/03/18 14:48 + * @Filename:PluginBean + * @description:Ops 插件的bean + */ +@Data +public class PluginBean implements Serializable { + private static final long serialVersionUID = 7817277417501722377L; +// @ApiModelProperty(value="插件ID",name="id") + private Long id; + +// @ApiModelProperty(value="插件名",name="name") + private String name; + +// @ApiModelProperty(value="插件路径",name="path") + private String path; + +// @ApiModelProperty(value="插件描述",name="desc") + private String desc; + +// @ApiModelProperty(value="插件版本",name="version") + private String version; + +// @ApiModelProperty(value="插件作者",name="author") + private String author; + +// @ApiModelProperty(value="创建时间",name="createTime") + private Date createTime; + +// @ApiModelProperty(value="更新时间",name="updateTime") + private Date updateTime; + +// @ApiModelProperty(value="插件状态",name="status") + private Integer status = 0; + +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginCache.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginCache.java new file mode 100644 index 0000000..7e93832 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginCache.java @@ -0,0 +1,38 @@ +package cd.casic.plugin; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * @Author:mianbin + * @Package:cd.casic.plugin + * @Project:ops + * @name:PluginCache + * @Date:2024/03/18 9:34 + * @Filename:PluginCache + * @description:已安装插件的存储 + */ +public class PluginCache { + private static final Map pluginMaps = Collections.synchronizedMap(new HashMap<>()); + + public static void put(String pluginId, PluginInfo plugin) { + pluginMaps.putIfAbsent(pluginId, plugin); + } + + public static PluginInfo getPlugin(String pluginId) { + return pluginMaps.get(pluginId); + } + + public static void remove(String pluginId) { + pluginMaps.remove(pluginId); + } + + public static Map getAllPlugin() { + return pluginMaps; + } + + public static Boolean isExist(String pluginId) { + return pluginMaps.containsKey(pluginId); + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginClassLoaderCache.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginClassLoaderCache.java new file mode 100644 index 0000000..7f087d0 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginClassLoaderCache.java @@ -0,0 +1,24 @@ +package cd.casic.plugin; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class PluginClassLoaderCache { + private static final Map loaderMap = Collections.synchronizedMap(new HashMap<>()); + public static void put(String pluginId, ClassLoader loader) { + loaderMap.putIfAbsent(pluginId, loader); + } + + public static ClassLoader getPlugin(String pluginId) { + return loaderMap.get(pluginId); + } + + public static void remove(String pluginId) { + loaderMap.remove(pluginId); + } + + public static Map getAllPlugin() { + return loaderMap; + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginConfigWrapper.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginConfigWrapper.java new file mode 100644 index 0000000..b68d75b --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginConfigWrapper.java @@ -0,0 +1,44 @@ +package cd.casic.plugin; + +import lombok.Data; + +import java.util.Objects; + +@Data +public class PluginConfigWrapper { + + /** + * 插件中的配置文件名称 + */ + private final String fileName; + + /** + * 配置文件实现类的Class定义 + */ + private final Class configClass; + + + + public PluginConfigWrapper(String fileName, Class configClass) { + this.fileName = fileName; + this.configClass = configClass; + } + + @Override + public boolean equals(Object o) { + if (this == o){ + return true; + } + if (!(o instanceof PluginConfigWrapper)){ + return false; + } + PluginConfigWrapper that = (PluginConfigWrapper) o; + return getFileName().equals(that.getFileName()) && + getConfigClass().equals(that.getConfigClass()); + } + + @Override + public int hashCode() { + return Objects.hash(getFileName(), getConfigClass()); + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginDescriptorCache.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginDescriptorCache.java new file mode 100644 index 0000000..4c6309c --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginDescriptorCache.java @@ -0,0 +1,25 @@ +package cd.casic.plugin; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class PluginDescriptorCache { + private static final Map pluginMaps = Collections.synchronizedMap(new HashMap<>()); + public static void put(String pluginId, PluginDescriptorStorage pluginDescriptorStorage) { + pluginMaps.putIfAbsent(pluginId, pluginDescriptorStorage); + } + + public static PluginDescriptorStorage getPlugin(String pluginId) { + return pluginMaps.get(pluginId); + } + + public static void remove(String pluginId) { + pluginMaps.remove(pluginId); + } + + public static Map getAllPlugin() { + return pluginMaps; + } +} + diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginDescriptorStorage.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginDescriptorStorage.java new file mode 100644 index 0000000..b2faac4 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginDescriptorStorage.java @@ -0,0 +1,70 @@ +package cd.casic.plugin; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import lombok.*; +import lombok.experimental.Accessors; +import org.pf4j.Plugin; +import org.pf4j.PluginDependency; + +import java.util.List; + +/** + * PF4J要用到的DefaultDescriptorStorage里一堆protected方法,所以另外弄了个类 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@TableName("sys_plugins") +@Accessors(chain = true) +public class PluginDescriptorStorage { + @TableId + private String pluginId; + private String pluginDescription; + private String pluginClass = Plugin.class.getName(); + private String version; + private String requires = "*"; // SemVer format + private String provider; + @TableField(typeHandler = JacksonTypeHandler.class) + private List dependencies; + private String license; + private String mapperXmlDir; + private String staticDir; + + private Integer enable; + + private String path; + + @TableField(exist = false) + private String configFileName; + + @TableField(exist = false) + private List configFileActive; + + private String pluginDirPath; + + @Getter + public enum EnableStatus { + /** + * 启用 + */ + ENABLE(1, "启用"), + /** + * 禁用 + */ + DISABLE(0, "禁用"); + private final Integer code; + private final String value; + + EnableStatus(Integer code, String value) { + this.value = value; + this.code = code; + } + + } + +} + diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginInfo.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginInfo.java new file mode 100644 index 0000000..f86c51a --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginInfo.java @@ -0,0 +1,155 @@ +package cd.casic.plugin; + +import cd.casic.plugin.utils.PluginsUtils; +import cn.hutool.setting.dialect.Props; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.pf4j.PluginWrapper; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.web.servlet.HandlerInterceptor; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @Author:mianbin + * @Package:cd.casic.plugin + * @Project:ops + * @name:PluginDetail + * @Date:2024/03/18 9:34 + * @Filename:PluginDetail + * @description:插件信息类 + */ +@Slf4j +@Data +public class PluginInfo { + + // jar或zip的list集合 + private List> classList; + private ApplicationContext mainApplicationContext; + private Boolean applicationContextIsRefresh = false; + private AnnotationConfigApplicationContext pluginApplicationContext; + private PluginWrapper pluginWrapper; + private List> adminGroupsClassList = new ArrayList<>(); + private List websocketPaths = new ArrayList<>(); + private String pluginId; + private String mapperXmlDir; + private final BasePlugin basePlugin; + private List handlerInterceptorList = new ArrayList<>(); + private Set staticClassPathLocations = new HashSet<>(); + private Set staticFileLocations = new HashSet<>(); + private List> controllers = new ArrayList<>(); + private Set pluginConfigObjects = new HashSet<>(); + private Map webSocketPathMap = new ConcurrentHashMap<>(); + // private ConcurrentHashMap, Object> beanCache = new ConcurrentHashMap<>(); + + // TODO 这个map用于替代前面的ClassList + private Map>> classGroups = new ConcurrentHashMap<>(); + + public PluginInfo(PluginWrapper pluginWrapper, ApplicationContext applicationContext) { + this.classList = new ArrayList<>(); + this.pluginWrapper = pluginWrapper; + this.pluginId = pluginWrapper.getPluginId(); + this.pluginApplicationContext = getContext(); + this.mainApplicationContext = applicationContext; + this.basePlugin = (BasePlugin) pluginWrapper.getPlugin(); + this.pluginApplicationContext.setParent(mainApplicationContext); + Props setting = PluginsUtils.getSetting(pluginWrapper.getPluginId()); + if (!setting.isEmpty()) { + this.mapperXmlDir = setting.getStr("mybatis.mapper.location", null); + String locations = setting.getStr("static.locations", null); + if (StringUtils.isNotBlank(locations)) { + loadResources(locations); + } + } + } + + + public AnnotationConfigApplicationContext getContext() { + AnnotationConfigApplicationContext pluginApplicationContext = + PluginApplicationContextManager.getApplicationContext(pluginWrapper.getPluginId()); + if (pluginApplicationContext == null) + pluginApplicationContext = new AnnotationConfigApplicationContext(); + pluginApplicationContext.setClassLoader(pluginWrapper.getPluginClassLoader()); + PluginApplicationContextManager.addPluginApplicationContext(pluginWrapper.getPluginId(), pluginApplicationContext); + return PluginApplicationContextManager.getApplicationContext(pluginWrapper.getPluginId()); + } + + private void loadResources(String locations) { + String[] staticLocations = locations.split(","); + for (String staticLocation : staticLocations) { + if (staticLocation.contains("classpath:")) { + staticLocation = staticLocation.replace("classpath:", ""); + if (StringUtils.isNotBlank(staticLocation) && staticLocation.startsWith("/")) { + staticLocation = staticLocation.substring(1); + } + this.staticClassPathLocations.add(staticLocation); + } else { + this.staticFileLocations.add(staticLocation); + } + } + } + + public String getMapperXmlDir() { + if (StringUtils.isNotBlank(mapperXmlDir) && mapperXmlDir.startsWith("classpath:")) { + mapperXmlDir = mapperXmlDir.replace("classpath:", ""); + } + if (StringUtils.isNotBlank(mapperXmlDir) && mapperXmlDir.startsWith("/")) { + mapperXmlDir = mapperXmlDir.substring(1); + } + return mapperXmlDir; + } + + /** + * 清理ApplicationContext + */ + public void clearApplicationContext() { + Optional.of(this.getPluginId()) + .ifPresent(var -> PluginApplicationContextManager.removePluginApplicationContext(var.trim())); + Optional.of(this.getPluginApplicationContext()) + .ifPresent(var -> { + var.getDefaultListableBeanFactory().destroySingletons(); + var.close(); + }); + this.applicationContextIsRefresh = false; + this.pluginApplicationContext = null; + } + + /** + * @param c class + * @return java.util.List> + * @description 获取插件内实现指定类的bean + * @author dolphin + */ + public T getPluginBean(Class c) { + try { + return pluginApplicationContext.getBean(c); + } catch (Exception e) { + log.error("要删除的bean:{}不存在" , c.getName()); + return null; + } + } + + public void addController(Class controller) { + this.controllers.add(controller); + } + + public void addPluginConfigObject(Object config) { + this.pluginConfigObjects.add(config); + } + + public void addGroupClass(String groupName, Class clazz) { + classGroups.computeIfAbsent(groupName, k -> new ArrayList<>()).add(clazz); + } + + public List> getGroupClass(String groupName) { + List> result = new ArrayList<>(); + List> classes = classGroups.get(groupName); + if (classes != null) { + result.addAll(classes); + } + return result; + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginLifecycle.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginLifecycle.java new file mode 100644 index 0000000..26b7230 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginLifecycle.java @@ -0,0 +1,27 @@ +package cd.casic.plugin; + +/** + * @Author:mianbin + * @Package:cd.casic.plugin + * @Project:ops + * @name:PluginLifecycle + * @Date:2024/06/15 17:31 + * @Filename:PluginLifecycle + * @description:插件生命周期的操作 + */ +public interface PluginLifecycle { + + /** + * 插件启动前的操作,比如需要单独对插件进行某些特定操作, + * + */ + public void beforeWork(); + + + /** + * 料理后世方的操作 + */ + public void AfterWork(); + + +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginManager.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginManager.java new file mode 100644 index 0000000..a4a9213 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginManager.java @@ -0,0 +1,392 @@ +package cd.casic.plugin; + +import cd.casic.plugin.event.PluginLoadedEvent; +import cd.casic.plugin.event.PluginUnloadedEvent; +import cd.casic.plugin.pf4j.OpsJarPluginRepository; +import cd.casic.plugin.pf4j.OpsPluginStatusProvider; +import cd.casic.plugin.pf4j.OpsPropertiesPluginDescriptorFinder; +import cd.casic.plugin.pf4j.OpsYamlPluginDescriptorFinder; +import cd.casic.plugin.register.StartPluginManagerHandler; +import cd.casic.plugin.utils.PluginsUtils; +import cd.casic.plugin.utils.SM4EncryptUtil; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.pf4j.*; +import org.pf4j.util.FileUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * @Author:mianbin + * @Package:cd.casic.plugin + * @Project:ops + * @name:PluginManager + * @Date:2024/03/18 15:03 + * @Filename:PluginManager + * @description:插件核心管理类 + */ +@Slf4j +@Component +public class PluginManager extends DefaultPluginManager implements PluginManagerService, ApplicationContextAware, PluginLifecycle { + + @Getter + ApplicationContext applicationContext; + + @Getter + private final PluginProperties pluginProperties; + + @Autowired + private StartPluginManagerHandler startPluginManagerHandler; + + @Autowired + private BufferedPluginBundleResource bufferedPluginBundleResource; + + @Autowired + private FrontResourceProcessor frontResourceProcessor; + + public PluginManager(@Autowired PluginProperties pluginProperties) { + this.pluginsRoots.add(Paths.get(pluginProperties.getPluginsRoot())); + this.pluginProperties = pluginProperties; + this.setRuntimeMode(pluginProperties.getRuntimeMode()); + super.initialize(); + } + + @Override + protected void initialize() { + // 这里不需要任何操作,只是覆盖父类的initialize方法 + // 如果不重写的话,在调用构造函数的时候会调用父类的无参构造函数,导致父类initialize方法被调用。 + // 父类initialize方法中会调用createPluginStatusProvider方法,该方法需要使用到OpsPluginSystemProperties,而此时还未被注入 + } + + + @Override + public PluginInfo install(Path path) throws Exception { + + // TODO 在install的时候生成插件目录结构 + /* + 工作目录 + │ + ├─plugins + │ ├─example-mybatis-plugin@0.1 插件目录,id@version,存放jar、database、resource + │ │ ├─ops-module-plugin-example-mybatis-plugin.jar 插件jar包,jar包命名不影响 + │ │ ├─database 存放sqlite数据库文件 + │ │ └─resource 存放静态资源 + │ └─example-redis-plugin@1.1.4 + │ ├─ops-module-plugin-example-redis-plugin.jar + │ ├─database + │ └─resource + */ + + String pluginId = null; + try { + this.beforeWork(); + pluginId = this.loadPlugin(path); + super.startPlugin(pluginId); + log.info("install plugin [{}] success", pluginId); + return PluginCache.getPlugin(pluginId); + } catch (Exception e) { + log.error("安装插件出现异常:{}", e.getMessage()); + if (StringUtils.isNotBlank(pluginId)) { + PluginCache.remove(pluginId); + } + throw new Exception(e.getMessage()); + } + } + + @Override + public void installAfter(String pluginId) { + try { + super.stopPlugin(pluginId); + } catch (Exception e) { + e.printStackTrace(); + log.error("install plugin after error:{}", e.getMessage()); + } + } + + @Override + public void unInstall(String pluginId, boolean isUpdate) throws Exception { + // TODO unInstall的时候除掉插件资源 + log.info("准备卸载插件:" + pluginId); + PluginInfo plugin = PluginCache.getPlugin(pluginId); + plugin.clearApplicationContext(); + PluginCache.remove(pluginId); + //删除插件 + PluginsUtils.forceDelete(plugin.getPluginWrapper().getPluginPath().toFile()); + } + + @Override + public void initPlugins(List plugins) throws Exception { + // 初始化插件处理器 + startPluginManagerHandler.initialize(); + for (PluginBean plugin : plugins) { + File file = new File(pluginsRoots.get(0) + File.separator + plugin.getPath()); + if (!file.exists()) { + continue; + } + String pluginId = this.loadPlugin(file.toPath().toAbsolutePath()); + PluginInfo pluginInfo = PluginCache.getPlugin(pluginId); + if (plugin.getStatus() == 1) { + this.startPlugin(pluginInfo.getPluginId()); + } + } + } + + @Override + public List getInstallPlugins() { + return getPlugins(); + } + + /** + * 加载插件 + * + * @param pluginPath 插件路径 + * @return String + */ + @Override + public String loadPlugin(Path pluginPath) { + Optional.ofNullable(pluginPath) + .filter(path -> Files.exists(path)) + .orElseThrow(() -> new IllegalArgumentException(String.format("plugin %s 不存在", pluginPath))); + + log.debug("Loading plugin from '{}'", pluginPath); + + PluginWrapper pluginWrapper = loadPluginFromPath(pluginPath); + PluginInfo pluginInfo = new PluginInfo(pluginWrapper, applicationContext); + try { + resolvePlugins(); + sendPluginEvenet(new PluginLoadedEvent(this, pluginInfo)); + } catch (Exception e) { + log.error("加载插件出现异常:{}", e.getMessage()); + sendPluginEvenet(new PluginUnloadedEvent(this, pluginInfo)); + } + return pluginWrapper.getDescriptor().getPluginId(); + } + + @Override + public PluginWrapper loadPluginFromPath(Path pluginPath) { + try { + pluginPath = FileUtils.expandIfZip(pluginPath); + } catch (Exception var3) { + log.info(MessageFormat.format("路径:{0}的插件包解压缩失败,失败原因{1} ", pluginPath, var3)); + return null; + } + return super.loadPluginFromPath(pluginPath); + } + + @Override + public void loadPlugins() { + log.debug("Lookup plugins in '{}'", pluginsRoots); + // check for plugins roots + if (pluginsRoots.isEmpty()) { + log.warn("No plugins roots configured"); + return; + } + pluginsRoots.forEach(path -> { + if (Files.notExists(path) || !Files.isDirectory(path)) { + log.warn("No '{}' root", path); + } + }); + + // get all plugin paths from repository + List pluginPaths = pluginRepository.getPluginPaths(); + + // check for no plugins + if (pluginPaths.isEmpty()) { + log.info("No plugins"); + return; + } + + log.debug("Found {} possible plugins: {}", pluginPaths.size(), pluginPaths); + + // load plugins from plugin paths + for (Path pluginPath : pluginPaths) { + try { + PluginWrapper pluginWrapper = this.loadPluginFromPath(pluginPath); + if (ObjectUtils.isNotEmpty(pluginWrapper)) { + String license = pluginWrapper.getDescriptor().getLicense(); + if (SM4EncryptUtil.checkLicense(pluginWrapper.getDescriptor().getLicense(), pluginWrapper.getPluginId())) { + PluginInfo plugin = new PluginInfo(pluginWrapper, applicationContext); + // startPluginManagerHandler.registry(plugin); + sendPluginEvenet(new PluginLoadedEvent(this, plugin)); + } + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + + // resolve plugins + try { + resolvePlugins(); + } catch (PluginRuntimeException e) { + log.error(e.getMessage(), e); + } + } + + @Override + public boolean unloadPlugin(String pluginId) { + if (pluginId == null) { + throw new IllegalArgumentException(String.format("plugin %s 不存在", pluginId)); + } + log.info("unloading plugin {}", pluginId); + try { + super.unloadPlugin(pluginId, true); + } catch (Exception e) { + log.info("卸载插件出现异常,{}", e.getMessage()); + return false; + } finally { + sendPluginEvenet(new PluginUnloadedEvent(this, PluginCache.getPlugin(pluginId))); + System.gc(); + } + return true; + } + + + @Override + public void unloadPlugins() { + for (PluginWrapper pluginWrapper : new ArrayList<>(resolvedPlugins)) { + this.unloadPlugin(pluginWrapper.getPluginId()); + } + } + + @Override + public PluginState startPlugin(String pluginId) { + PluginState pluginState = null; + try { + PluginInfo plugin = PluginCache.getPlugin(pluginId); + if (plugin.getPluginWrapper().getPluginState().equals(PluginState.DISABLED)) return PluginState.STOPPED; + + if (ObjectUtils.isEmpty((plugin.getPluginApplicationContext()))) { + plugin.setPluginApplicationContext(plugin.getContext()); + plugin.getPluginApplicationContext().setParent(applicationContext); + } + + startPluginManagerHandler.registry(plugin); + pluginState = super.startPlugin(pluginId); + + // todo 这里有uglify存在循环遍历的问题,后面改 + bufferedPluginBundleResource.getJsBundle(String.valueOf(System.currentTimeMillis()), frontResourceProcessor.uglifyJsBundle()); + bufferedPluginBundleResource.getCssBundle(String.valueOf(System.currentTimeMillis()), frontResourceProcessor.uglifyCssBundle()); +// bufferedPluginBundleResource.getHtmlBundle(String.valueOf(System.currentTimeMillis()), frontResourceProcessor.uglifyHtmlBundle()); + } catch (Exception e) { + e.printStackTrace(); + log.error("plugin start error : {}", e.getMessage()); + } + return pluginState; + } + + protected PluginState stopPlugin(String pluginId, boolean stopDependents) { + checkPluginId(pluginId); + + PluginWrapper pluginWrapper = getPlugin(pluginId); + PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor(); + PluginState pluginState = pluginWrapper.getPluginState(); + if (PluginState.STOPPED == pluginState) { + log.debug("Already stopped plugin '{}'", getPluginLabel(pluginDescriptor)); + return PluginState.STOPPED; + } + + // test for disabled plugin + if (PluginState.DISABLED == pluginState) { + // do nothing + return pluginState; + } + + if (stopDependents) { + List dependents = dependencyResolver.getDependents(pluginId); + while (!dependents.isEmpty()) { + String dependent = dependents.remove(0); + stopPlugin(dependent, false); + dependents.addAll(0, dependencyResolver.getDependents(dependent)); + } + } + + log.info("Stop plugin '{}'", getPluginLabel(pluginDescriptor)); + pluginWrapper.getPlugin().stop(); + pluginWrapper.setPluginState(PluginState.STOPPED); + startedPlugins.remove(pluginWrapper); + + try { + // todo 没得到plugin + PluginInfo plugin = PluginCache.getPlugin(pluginId); + // todo 停止事件 + startPluginManagerHandler.unRegistry(plugin); + plugin.clearApplicationContext(); + } catch (Exception e) { + log.error("plugin stop error : {}", e.getMessage()); + } + + firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState)); + this.AfterWork(); + return pluginWrapper.getPluginState(); + } + + public void setRuntimeMode(RuntimeMode runtimeMode) { + this.runtimeMode = runtimeMode; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + @Override + protected PluginDescriptorFinder createPluginDescriptorFinder() { + return new CompoundPluginDescriptorFinder() + .add(new OpsPropertiesPluginDescriptorFinder()) + .add(new OpsYamlPluginDescriptorFinder()); + } + + @Override + protected PluginRepository createPluginRepository() { +// return new CompoundPluginRepository() +// .add(new DevelopmentPluginRepository(getPluginsRoots()), this::isDevelopment) +// .add(new OpsJarPluginRepository(getPluginsRoots()), this::isNotDevelopment); + // .add(new DefaultPluginRepository(getPluginsRoots()), this::isNotDevelopment); + return new OpsJarPluginRepository(getPluginsRoots()); + } + + @Override + protected PluginLoader createPluginLoader() { + return new CompoundPluginLoader() + .add(new JarPluginLoader(this)) + .add(new DefaultPluginLoader(this)); + } + + @Override + public boolean isDevelopment() { + return this.getRuntimeMode().equals(RuntimeMode.DEVELOPMENT); + } + + @Override + protected PluginStatusProvider createPluginStatusProvider() { + if (OpsPluginStatusProvider.isPropertySet(pluginProperties)) { + return new OpsPluginStatusProvider(pluginProperties); + } + return super.createPluginStatusProvider(); + } + + @Override + public void beforeWork() { + + } + + @Override + public void AfterWork() { + + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginManagerService.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginManagerService.java new file mode 100644 index 0000000..a4811c1 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginManagerService.java @@ -0,0 +1,69 @@ +package cd.casic.plugin; + +import cd.casic.framework.commons.util.spring.SpringUtils; +import org.pf4j.PluginState; +import org.pf4j.PluginWrapper; +import org.springframework.context.ApplicationEvent; + +import java.nio.file.Path; +import java.util.List; + +/** + * @Author:mianbin + * @Package:cd.casic.plugin + * @Project:ops + * @name:PluginManagerService + * @Date:2024/03/18 14:46 + * @Filename:PluginManagerService + * @description:插件基础安装类 + */ +public interface PluginManagerService { + /** + * @param path 插件路径 + * @description 安装插件 + */ + PluginInfo install(Path path) throws Exception; + + /** + * 安装插件后置操作 + * + * @param pluginId pluginId + */ + void installAfter(String pluginId); + + /** + * @description 卸载插件 + */ + void unInstall(String pluginId, boolean isUpdate) throws Exception; + + /** + * 启动插件 + * + * @param pluginId + * @return + */ + PluginState startPlugin(String pluginId); + + /** + * 停止插件 + * + * @param pluginId pluginId + * @return PluginState + */ + PluginState stopPlugin(String pluginId); + + /** + * @description 插件初始化 + */ + void initPlugins(List plugins) throws Exception; + + /** + * @description 获取所有插件 + */ + List getInstallPlugins(); + + + default void sendPluginEvenet(ApplicationEvent applicationEvent) { + SpringUtils.publishEvent(applicationEvent); + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginProperties.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginProperties.java new file mode 100644 index 0000000..7a629ae --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginProperties.java @@ -0,0 +1,53 @@ +package cd.casic.plugin; + +import cn.hutool.core.io.FileUtil; +import lombok.Data; +import org.pf4j.RuntimeMode; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashSet; +import java.util.Set; + +@Data +@ConfigurationProperties(prefix = "cd.casic.ops.plugin") +public class PluginProperties implements InitializingBean { + /** + * 是否启用插件系统 + */ + private Boolean enable; + /** + * 插件根目录 + */ + private String pluginsRoot; + /** + * 运行模式,可以是 development 或者 deployment + */ + private RuntimeMode runtimeMode; + /** + * 插件接口RESTful API的路径前缀 + */ + private String restPathPrefix; + /** + * 是否在RESTful API前缀中包含插件ID,默认为true + */ + private boolean enablePluginIdAsRestPrefix = true; + /** + * 已启用的插件集合 + */ + private Set enabledPlugins = new HashSet<>(); + /** + * 已禁用的插件集合 + */ + private Set disabledPlugins = new HashSet<>(); + + @Override + public void afterPropertiesSet() { + Path pluginsRootPath = Paths.get(this.pluginsRoot); + if (!pluginsRootPath.toFile().exists()) { + FileUtil.mkdir(pluginsRootPath.toFile()); + } + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginYamlConfigurationParser.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginYamlConfigurationParser.java new file mode 100644 index 0000000..34df992 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginYamlConfigurationParser.java @@ -0,0 +1,72 @@ +package cd.casic.plugin; + +import cd.casic.plugin.utils.YamlUtils; +import cn.hutool.core.util.StrUtil; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.TreeTraversingParser; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.fasterxml.jackson.dataformat.yaml.YAMLParser; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; + +import java.io.InputStream; +import java.nio.file.Path; + +public class PluginYamlConfigurationParser { + private final ObjectMapper objectMapper; + private final YAMLFactory yamlFactory; + + public PluginYamlConfigurationParser(){ + this.objectMapper = new ObjectMapper(); + this.yamlFactory = new YAMLFactory(); + } + + public Object parse(PluginInfo pluginInfo, PluginConfigWrapper pluginConfigWrapper) throws Exception { + Class configClass = pluginConfigWrapper.getConfigClass(); + if(configClass == null){ + throw new IllegalArgumentException("pluginConfigDefinition : " + pluginConfigWrapper + " " + + "configClass can not be null"); + } + String yamlFileName = pluginConfigWrapper.getFileName(); + if(StrUtil.isEmpty(yamlFileName)){ + throw new IllegalArgumentException("pluginConfigDefinition : " + pluginConfigWrapper + " " + + "fileName can not be empty"); + } + + Path yamlPath = YamlUtils.getYamlPath(pluginInfo.getPluginWrapper().getPluginPath(), yamlFileName); + Resource resource = new FileSystemResource(yamlPath); + Object o = convert(resource, configClass); + if(o == null){ + return configClass.getConstructor().newInstance(); + } + return o; + } + + + private Object convert(Resource resource, Class configClass) throws Exception { + InputStream inputStream = null; + YAMLParser yamlParser = null; + TreeTraversingParser treeTraversingParser = null; + try { + inputStream = resource.getInputStream(); + yamlParser = yamlFactory.createParser(inputStream); + final JsonNode node = objectMapper.readTree(yamlParser); + if(node == null){ + return configClass.getConstructor().newInstance(); + } + treeTraversingParser = new TreeTraversingParser(node); + return objectMapper.readValue(treeTraversingParser, configClass); + } finally { + if(treeTraversingParser != null){ + treeTraversingParser.close(); + } + if(yamlParser != null){ + yamlParser.close(); + } + if(inputStream != null){ + inputStream.close(); + } + } + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginYamlProcessor.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginYamlProcessor.java new file mode 100644 index 0000000..ca3d542 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/PluginYamlProcessor.java @@ -0,0 +1,43 @@ +package cd.casic.plugin; + +import cd.casic.plugin.utils.PluginDescriptorUtils; +import org.springframework.beans.factory.config.YamlProcessor; +import org.springframework.core.io.Resource; + +import java.util.ArrayList; +import java.util.List; + +import static cd.casic.plugin.utils.PluginDescriptorUtils.*; + + +public class PluginYamlProcessor extends YamlProcessor { + /** + * 检查这些必须有的属性是否存在 + */ + private static final DocumentMatcher DEFAULT_DOCUMENT_MATCHER = properties -> { + if (properties.containsKey(PLUGIN_ID) + && properties.containsKey(PLUGIN_VERSION) + && properties.containsKey(PLUGIN_CLASS)){ + return MatchStatus.FOUND; + } + return MatchStatus.NOT_FOUND; + }; + + public PluginYamlProcessor(Resource... resources) { + setResources(resources); + setDocumentMatchers(DEFAULT_DOCUMENT_MATCHER); + } + + /** + * 读取yaml文件 + * @return PluginDescriptorStorage 插件的相关配置信息以及后续用于插件信息持久化存储的持久化对象 + */ + public PluginDescriptorStorage loadYaml() { + List pluginDescriptor = new ArrayList<>(); + this.process(((properties, map) -> { + pluginDescriptor.add(PluginDescriptorUtils.propertiesToStorage(properties)); + })); + + return pluginDescriptor.get(0); + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/annotation/AdminGroup.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/annotation/AdminGroup.java new file mode 100644 index 0000000..1a54110 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/annotation/AdminGroup.java @@ -0,0 +1,38 @@ +package cd.casic.plugin.annotation; + + +import cd.casic.plugin.constants.OpsConstants; + +import java.lang.annotation.*; + +/** + * @Author:mianbin + * @Package:cd.casic.plugin.annotation + * @Project:ops + * @name:AdminGroup + * @Date:2024/03/18 17:06 + * @Filename:AdminGroup + * @description:自定义菜单Group注解 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.ANNOTATION_TYPE}) +@Documented +public @interface AdminGroup { + /** 菜单名称 */ + String name(); + + /** 菜单组id */ + String groupId(); + + /** 菜单图标 */ + String icon() default "fa-circle-o"; + + /** 菜单url */ + String url() default ""; + + /** 菜单角色 */ + String[] role() default {OpsConstants.ROLE_ADMIN}; + + /** 菜单序号 */ + int seq() default 99; +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/annotation/AdminGroups.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/annotation/AdminGroups.java new file mode 100644 index 0000000..191c5f4 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/annotation/AdminGroups.java @@ -0,0 +1,20 @@ +package cd.casic.plugin.annotation; + +import java.lang.annotation.*; + +/** + * @Author:mianbin + * @Package:cd.casic.plugin.annotation + * @Project:ops + * @name:AdminGroups + * @Date:2024/03/18 17:05 + * @Filename:AdminGroups + * @description:自定义菜单groups注解 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +@Documented +public @interface AdminGroups { + /** 菜单组 */ + AdminGroup[] groups(); +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/annotation/InterceptPath.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/annotation/InterceptPath.java new file mode 100644 index 0000000..88b8171 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/annotation/InterceptPath.java @@ -0,0 +1,22 @@ +package cd.casic.plugin.annotation; + +import java.lang.annotation.*; + +/** + * @Author:mianbin + * @Package:cd.casic.plugin.annotation + * @Project:ops + * @name:InterceptPath + * @Date:2024/03/18 16:09 + * @Filename:InterceptPath + * @description:插件拦截器 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +public @interface InterceptPath { + /** + * 拦截的路径 + */ + String[] value(); +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/annotation/PluginConfiguration.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/annotation/PluginConfiguration.java new file mode 100644 index 0000000..e6a94ec --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/annotation/PluginConfiguration.java @@ -0,0 +1,17 @@ +package cd.casic.plugin.annotation; + +import java.lang.annotation.*; + +/** + * 用于标记插件内部的独立配置类 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface PluginConfiguration { + String fileName() default ""; // 插件配置文件的文件名 + + String deploySuffix() default ""; // 插件配置文件在deployment模式下的后缀 + + String devSuffix() default ""; // 插件配置文件在development模式下的后缀 +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/config/OpsPluginConfig.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/config/OpsPluginConfig.java new file mode 100644 index 0000000..28ceb18 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/config/OpsPluginConfig.java @@ -0,0 +1,29 @@ +package cd.casic.plugin.config; + +import cd.casic.plugin.PluginManager; +import cd.casic.plugin.PluginProperties; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 配置类:用于定义和管理插件系统的Bean + */ +@Configuration +@EnableConfigurationProperties({PluginProperties.class}) +public class OpsPluginConfig { + /** + * 定义并创建一个PluginManager Bean + *

+ * 此Bean负责管理和操作插件系统,仅在没有已定义的PluginManager Bean时创建 + * + * @param pluginProperties 插件系统的属性配置,用于初始化PluginManager + * @return 初始化并配置好的PluginManager实例 + */ + @Bean + @ConditionalOnMissingBean + public PluginManager pluginManager(PluginProperties pluginProperties) { + return new PluginManager(pluginProperties); + } +} \ No newline at end of file diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/constants/OpsConstants.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/constants/OpsConstants.java new file mode 100644 index 0000000..a896d40 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/constants/OpsConstants.java @@ -0,0 +1,53 @@ +package cd.casic.plugin.constants; + +/** + * @Author:mianbin + * @Package:cd.casic.plugin.constants + * @Project:ops + * @name:OpsConstants + * @Date:2024/03/18 9:30 + * @Filename:OpsConstants + * @description:Todo + */ +public class OpsConstants { + /** + * 插件目录 + */ + public static final String PLUGIN_PATH = "/plugins"; + /** + * 插件路径 + */ + public static final String PLUGINS_DIR = "resources/plugins"; + /** + * 插件静态资源目录 + */ + public static final String PLUGINS_RESOURCES_DIR = "resources/pluginResources"; + //public static final String PLUGINS_RESOURCES_DIR = "ops-module-plugins/ops-module-plugins-example-web/src/main/resources/static"; + + /** + * 角色:管理员 + */ + public static final String ROLE_ADMIN = "admin"; + /** + * 角色:用户 + */ + public static final String ROLE_USER = "user"; + /** + * 角色:编辑 + */ + public static final String ROLE_EDITOR = "editor"; + + + /** + * todo 区分插件类型,目前只支持两种,一种为zip ,一种为jar包形式,主要使用zip形式,别面jar包被人轻易破解 + */ + public static class Suffix { + + private Suffix() { + throw new IllegalStateException(); + } + + public static final String JAR = "jar"; + public static final String ZIP = "zip"; + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/constants/PluginBuildTypeEnum.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/constants/PluginBuildTypeEnum.java new file mode 100644 index 0000000..62d34d8 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/constants/PluginBuildTypeEnum.java @@ -0,0 +1,27 @@ +package cd.casic.plugin.constants; + +/** + * @Author:mianbin + * @Package:cd.casic.plugin.constants + * @Project:ops + * @name:PluginBuildTypeEnum + * @Date:2024/03/21 19:53 + * @Filename:PluginBuildTypeEnum + * @description:插件构建方式区分 + */ +public enum PluginBuildTypeEnum { + + /** + * 构建 + */ + BUILD, + /** + * 注册 + */ + REGISTER, + /** + * 卸载 + */ + UNREGISTER + +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/OpsMainAppReadyEvent.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/OpsMainAppReadyEvent.java new file mode 100644 index 0000000..9d7a715 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/OpsMainAppReadyEvent.java @@ -0,0 +1,20 @@ +package cd.casic.plugin.event; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationEvent; + +/** + * @Author:mianbin + * @Package:cd.casic.plugin.event + * @Project:ops + * @name:OpsMainAppReadyEvent + * @Date:2024/03/22 10:29 + * @Filename:OpsMainAppReadyEvent + * @description:主程序启动完毕,此事件发布到插件应用程序,此时,插件还未启动 + */ +public class OpsMainAppReadyEvent extends ApplicationEvent { + + public OpsMainAppReadyEvent(ApplicationContext mainApplicationContext) { + super(mainApplicationContext); + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/OpsMainAppStartedEvent.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/OpsMainAppStartedEvent.java new file mode 100644 index 0000000..2f798c6 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/OpsMainAppStartedEvent.java @@ -0,0 +1,20 @@ +package cd.casic.plugin.event; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationEvent; + +/** + * @Author:mianbin + * @Package:cd.casic.plugin.event + * @Project:ops + * @name:OpsMainAppStartedEvent + * @Date:2024/03/22 10:31 + * @Filename:OpsMainAppStartedEvent + * @description:主程序启动完毕,此事件发布到插件应用程序,此时,原神启动 + */ +public class OpsMainAppStartedEvent extends ApplicationEvent { + + public OpsMainAppStartedEvent(ApplicationContext mainApplicationContext) { + super(mainApplicationContext); + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginDirCheckEvent.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginDirCheckEvent.java new file mode 100644 index 0000000..7a1972b --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginDirCheckEvent.java @@ -0,0 +1,13 @@ +package cd.casic.plugin.event; + +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +@Getter +public class PluginDirCheckEvent extends ApplicationEvent { + + public PluginDirCheckEvent(Object source) { + super(source); + } + +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginLoadedEvent.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginLoadedEvent.java new file mode 100644 index 0000000..96e6a9f --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginLoadedEvent.java @@ -0,0 +1,30 @@ +package cd.casic.plugin.event; + +import cd.casic.plugin.PluginCache; +import cd.casic.plugin.PluginClassLoaderCache; +import cd.casic.plugin.PluginInfo; +import cd.casic.plugin.PluginManager; +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +@Getter +public class PluginLoadedEvent extends ApplicationEvent { + + private final PluginManager pluginManager; + + private final PluginInfo pluginInfo; + + public PluginLoadedEvent(Object source, PluginInfo pluginInfo) { + super(source); + this.pluginManager = (PluginManager) source; + this.pluginInfo = pluginInfo; + + if (!PluginCache.isExist(pluginInfo.getPluginId())) { + PluginCache.put(pluginInfo.getPluginId(), pluginInfo); + } + if (PluginClassLoaderCache.getPlugin(pluginInfo.getPluginId()) == null) { + PluginClassLoaderCache.put(pluginInfo.getPluginId(), pluginInfo.getPluginWrapper().getPluginClassLoader()); + } + } + +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginRestartedEvent.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginRestartedEvent.java new file mode 100644 index 0000000..8933a3f --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginRestartedEvent.java @@ -0,0 +1,19 @@ +package cd.casic.plugin.event; + +import org.springframework.context.ApplicationEvent; + +/** + * @Author:mianbin + * @Package:cd.casic.plugin.event + * @Project:ops + * @name:PluginRestartedEvent + * @Date:2024/03/22 10:51 + * @Filename:PluginRestartedEvent + * @description:插件重启,在插件管理器中重启插件,和同目录上面的OpsMian****的事件不同 + */ +public class PluginRestartedEvent extends ApplicationEvent { + + public PluginRestartedEvent(Object source) { + super(source); + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginStartedEvent.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginStartedEvent.java new file mode 100644 index 0000000..b8ad5ca --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginStartedEvent.java @@ -0,0 +1,20 @@ +package cd.casic.plugin.event; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationEvent; + +/** + * @Author:mianbin + * @Package:cd.casic.plugin.event + * @Project:ops + * @name:PluginStartedEvent + * @Date:2024/03/22 10:57 + * @Filename:PluginStartedEvent + * @description:发布到插件管理器 + */ +public class PluginStartedEvent extends ApplicationEvent { + + public PluginStartedEvent(ApplicationContext pluginApplicationContext) { + super(pluginApplicationContext); + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginStateChangedEvent.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginStateChangedEvent.java new file mode 100644 index 0000000..9f6527c --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginStateChangedEvent.java @@ -0,0 +1,19 @@ +package cd.casic.plugin.event; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationEvent; + +/** + * @Author:mianbin + * @Package:cd.casic.plugin.event + * @Project:ops + * @name:PluginStateChangedEvent + * @Date:2024/03/22 11:02 + * @Filename:PluginStateChangedEvent + * @description:插件状态,这个是考虑到插件的更新 + */ +public class PluginStateChangedEvent extends ApplicationEvent { + public PluginStateChangedEvent(ApplicationContext mainApplicationContext) { + super(mainApplicationContext); + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginStoppedEvent.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginStoppedEvent.java new file mode 100644 index 0000000..5c76b01 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginStoppedEvent.java @@ -0,0 +1,19 @@ +package cd.casic.plugin.event; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationEvent; + +/** + * @Author:mianbin + * @Package:cd.casic.plugin.event + * @Project:ops + * @name:PluginStoppedEvent + * @Date:2024/03/22 11:05 + * @Filename:PluginStoppedEvent + * @description:插件停止,停止后应该删除一切内容 + */ +public class PluginStoppedEvent extends ApplicationEvent { + public PluginStoppedEvent(ApplicationContext pluginApplicationContext) { + super(pluginApplicationContext); + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginUnloadedEvent.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginUnloadedEvent.java new file mode 100644 index 0000000..ab30894 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginUnloadedEvent.java @@ -0,0 +1,26 @@ +package cd.casic.plugin.event; + +import cd.casic.plugin.*; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEvent; + +@Getter +@Slf4j +public class PluginUnloadedEvent extends ApplicationEvent { + + private final PluginManager pluginManager; + + private final PluginInfo pluginInfo; + + public PluginUnloadedEvent(Object source, PluginInfo pluginInfo) { + super(source); + this.pluginManager = (PluginManager) source; + this.pluginInfo = pluginInfo; + + String startErrorPluginId = this.pluginInfo.getPluginId(); + PluginCache.remove(startErrorPluginId); + PluginClassLoaderCache.remove(startErrorPluginId); + PluginApplicationContextManager.removePluginApplicationContext(startErrorPluginId); + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/execption/PluginException.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/execption/PluginException.java new file mode 100644 index 0000000..c665cdf --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/execption/PluginException.java @@ -0,0 +1,29 @@ +package cd.casic.plugin.execption; + +/** + * @Author:mianbin + * @Package:cd.casic.plugin.execption + * @Project:ops + * @name:PluginException + * @Date:2024/03/21 19:54 + * @Filename:PluginException + * @description:毫无疑问,看这个名字就知道是插件异常类 + */ +public class PluginException extends RuntimeException { + + public PluginException() { + super(); + } + + public PluginException(String s) { + super(s); + } + + public PluginException(String message, Throwable cause) { + super(message, cause); + } + + public PluginException(Throwable cause) { + super(cause); + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/pf4j/OpsJarPluginRepository.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/pf4j/OpsJarPluginRepository.java new file mode 100644 index 0000000..a9cc4dd --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/pf4j/OpsJarPluginRepository.java @@ -0,0 +1,55 @@ +package cd.casic.plugin.pf4j; + +import lombok.extern.slf4j.Slf4j; +import org.pf4j.JarPluginRepository; + +import java.io.File; +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@Slf4j +public class OpsJarPluginRepository extends JarPluginRepository { + + public OpsJarPluginRepository(Path... pluginsRoots) { + this(Arrays.asList(pluginsRoots)); + } + + public OpsJarPluginRepository(List pluginsRoots) { + super(pluginsRoots); + } + + @Override + public List getPluginPaths() { + List pluginPaths = new ArrayList<>(); + for (Path pluginRoot : pluginsRoots){ + File rootDir = pluginRoot.toFile(); + + scanForJars(rootDir.toPath(), pluginPaths); + + } + return pluginPaths; + } + + private void scanForJars(Path directory, List pluginPaths) { + try (DirectoryStream stream = Files.newDirectoryStream(directory)) { + for (Path entry : stream) { + if (Files.isDirectory(entry)) { + // 如果是目录,递归进入下一层 + scanForJars(entry, pluginPaths); + } else if (entry.getFileName().toString().endsWith(".jar")) { + // 如果是.jar文件,添加到结果列表 + pluginPaths.add(entry); + } + } + } catch (IOException e) { + System.err.println("Error scanning directory " + directory + ": " + e.getMessage()); + } + } + + +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/pf4j/OpsPluginDescriptor.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/pf4j/OpsPluginDescriptor.java new file mode 100644 index 0000000..6fbef82 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/pf4j/OpsPluginDescriptor.java @@ -0,0 +1,20 @@ +package cd.casic.plugin.pf4j; + +import lombok.Getter; +import org.pf4j.DefaultPluginDescriptor; + +import java.util.List; + +@Getter +public class OpsPluginDescriptor extends DefaultPluginDescriptor { + private final String configFileName; + private final List configFileActive; + + public OpsPluginDescriptor(String pluginId, String pluginDescription, String pluginClass, + String version, String requires, String provider, String license, + String configFileName, List configFileActive){ + super(pluginId, pluginDescription, pluginClass, version, requires, provider, license); + this.configFileActive = configFileActive; + this.configFileName = configFileName; + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/pf4j/OpsPluginStatusProvider.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/pf4j/OpsPluginStatusProvider.java new file mode 100644 index 0000000..e83b393 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/pf4j/OpsPluginStatusProvider.java @@ -0,0 +1,50 @@ +package cd.casic.plugin.pf4j; + +import cd.casic.plugin.PluginProperties; +import cn.hutool.core.collection.CollectionUtil; +import org.pf4j.PluginStatusProvider; + +import java.util.HashSet; +import java.util.Set; + +public class OpsPluginStatusProvider implements PluginStatusProvider { + + private final Set enabledPlugins = new HashSet<>(); + private final Set disabledPlugins = new HashSet<>(); + + public OpsPluginStatusProvider(PluginProperties pluginProperties) { + this.enabledPlugins.addAll(pluginProperties.getEnabledPlugins()); + this.disabledPlugins.addAll(pluginProperties.getDisabledPlugins()); + } + + public static boolean isPropertySet(PluginProperties pluginProperties) { + return CollectionUtil.isNotEmpty(pluginProperties.getEnabledPlugins()) + || CollectionUtil.isNotEmpty(pluginProperties.getDisabledPlugins()); + } + + @Override + public boolean isPluginDisabled(String pluginId) { + if (disabledPlugins.contains(pluginId)) { + return true; + } + return !enabledPlugins.isEmpty() && !enabledPlugins.contains(pluginId); + } + + @Override + public void disablePlugin(String pluginId) { + if (isPluginDisabled(pluginId)) { + return; + } + disabledPlugins.add(pluginId); + enabledPlugins.remove(pluginId); + } + + @Override + public void enablePlugin(String pluginId) { + if (!isPluginDisabled(pluginId)) { + return; + } + enabledPlugins.add(pluginId); + disabledPlugins.remove(pluginId); + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/pf4j/OpsPropertiesPluginDescriptorFinder.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/pf4j/OpsPropertiesPluginDescriptorFinder.java new file mode 100644 index 0000000..fe89008 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/pf4j/OpsPropertiesPluginDescriptorFinder.java @@ -0,0 +1,60 @@ +package cd.casic.plugin.pf4j; + +import cd.casic.plugin.PluginDescriptorCache; +import cd.casic.plugin.PluginDescriptorStorage; +import cd.casic.plugin.utils.PluginDescriptorUtils; +import org.pf4j.DevelopmentPluginClasspath; +import org.pf4j.PluginDescriptor; +import org.pf4j.PluginRuntimeException; +import org.pf4j.PropertiesPluginDescriptorFinder; +import org.pf4j.util.FileUtils; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Properties; + +public class OpsPropertiesPluginDescriptorFinder extends PropertiesPluginDescriptorFinder { + static final DevelopmentPluginClasspath PLUGIN_CLASSPATH = new DevelopmentPluginClasspath(); + + public OpsPropertiesPluginDescriptorFinder(){ + super(); + } + + public OpsPropertiesPluginDescriptorFinder(String propertiesFileName){ + super(propertiesFileName); + } + + @Override + protected Path getPropertiesPath(Path pluginPath, String propertiesFileName) { + if (Files.isDirectory(pluginPath)) { + for (String location : PLUGIN_CLASSPATH.getClassesDirectories()) { + Path path = pluginPath.resolve(location).resolve(propertiesFileName); + Resource propertyResource = new FileSystemResource(path); + if (propertyResource.exists()) { + return path; + } + } + throw new PluginRuntimeException( + "Unable to find plugin descriptor file: " + DEFAULT_PROPERTIES_FILE_NAME); + } + + // it's a zip or jar file + try { + return FileUtils.getPath(pluginPath, propertiesFileName); + } catch (IOException e) { + throw new PluginRuntimeException(e); + } + } + + @Override + public PluginDescriptor find(Path pluginPath) { + Properties properties = readProperties(pluginPath); + PluginDescriptorStorage storage = PluginDescriptorUtils.propertiesToStorage(properties); + PluginDescriptorCache.put(storage.getPluginId(), storage); + return PluginDescriptorUtils.storageToDescriptor(storage); + } + +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/pf4j/OpsYamlPluginDescriptorFinder.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/pf4j/OpsYamlPluginDescriptorFinder.java new file mode 100644 index 0000000..96b5bc8 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/pf4j/OpsYamlPluginDescriptorFinder.java @@ -0,0 +1,64 @@ +package cd.casic.plugin.pf4j; + +import cd.casic.plugin.PluginDescriptorCache; +import cd.casic.plugin.PluginDescriptorStorage; +import cd.casic.plugin.PluginYamlProcessor; +import cd.casic.plugin.utils.PluginDescriptorUtils; +import cd.casic.plugin.utils.YamlUtils; +import lombok.extern.slf4j.Slf4j; +import org.pf4j.PluginDescriptor; +import org.pf4j.PluginDescriptorFinder; +import org.pf4j.PluginRuntimeException; +import org.pf4j.util.FileUtils; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; + +import java.nio.file.Files; +import java.nio.file.Path; + +@Slf4j +public class OpsYamlPluginDescriptorFinder implements PluginDescriptorFinder { + public static final String DEFAULT_YAML_FILE_NAME = "plugin.yaml"; + + private final String yamlFileName; + + public OpsYamlPluginDescriptorFinder() { + this(DEFAULT_YAML_FILE_NAME); + } + + public OpsYamlPluginDescriptorFinder(String yamlFileName) { + this.yamlFileName = yamlFileName; + } + + @Override + public boolean isApplicable(Path pluginPath) { + return Files.exists(pluginPath) + && (Files.isDirectory(pluginPath) + || FileUtils.isJarFile(pluginPath)); + } + + @Override + public PluginDescriptor find(Path pluginPath) { + Path yamlPath = null; + try { + yamlPath = YamlUtils.getYamlPath(pluginPath, yamlFileName); + if (yamlPath == null) { + throw new PluginRuntimeException("Cannot find the plugin manifest path"); + } + log.debug("Lookup plugin descriptor in '{}'", yamlPath); + if (Files.notExists(yamlPath)) { + throw new PluginRuntimeException("Cannot find '{}' path", yamlPath); + } + + Resource yamlResource = new FileSystemResource(yamlPath); + PluginYamlProcessor pluginYamlProcessor = new PluginYamlProcessor(yamlResource); + PluginDescriptorStorage pluginDescriptor = pluginYamlProcessor.loadYaml(); + PluginDescriptorCache.put(pluginDescriptor.getPluginId(), pluginDescriptor); + OpsPluginDescriptor defaultPluginDescriptor = PluginDescriptorUtils.storageToDescriptor(pluginDescriptor); + pluginDescriptor.getDependencies().forEach(defaultPluginDescriptor::addDependency); + return defaultPluginDescriptor; + } finally { + FileUtils.closePath(yamlPath); + } + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/AnnotationHandler.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/AnnotationHandler.java new file mode 100644 index 0000000..f0e53d5 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/AnnotationHandler.java @@ -0,0 +1,145 @@ +package cd.casic.plugin.register; + +import cd.casic.plugin.BasePlugin; +import cd.casic.plugin.PluginClassLoaderCache; +import cd.casic.plugin.PluginInfo; +import cd.casic.plugin.annotation.AdminGroups; +import cd.casic.plugin.annotation.InterceptPath; +import cd.casic.plugin.register.group.AnnotationUtils; +import cd.casic.plugin.register.group.ClassGroupHandler; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.annotations.Mapper; +import org.pf4j.PluginWrapper; +import org.pf4j.util.FileUtils; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Controller; +import org.springframework.stereotype.Repository; +import org.springframework.stereotype.Service; +import org.springframework.web.bind.annotation.RestController; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.stream.Collectors; + +/** + * @Author:mianbin + * @Package:cd.casic.plugin.register + * @Project:ops + * @name:AnnotationHandler + * @Date:2024/03/18 15:59 + * @Filename:AnnotationHandler + * @deprecated 不再使用,改用{@link ClassGroupHandler} 实现扫包后分组存储。 + */ +@Slf4j +@Deprecated +public class AnnotationHandler implements BasePluginHandler { + private final static Class[] REGISTER_ANNO = { + Bean.class, + Mapper.class, + Service.class, + Component.class, + Repository.class, + Controller.class, + Configuration.class, + RestController.class, + InterceptPath.class, + }; + + @Override + public void initialize() throws Exception { + + } + + @Override + public void registry(PluginInfo plugin) throws Exception { + List> classList = new ArrayList<>(); + List> adminGroupsClassList = new ArrayList<>(); + Set classPackageName = scanClassPackageName(plugin.getBasePlugin().scanPackage(), plugin.getBasePlugin().getWrapper()); + for (String packageName : classPackageName) { + ClassLoader loader = PluginClassLoaderCache.getPlugin(plugin.getPluginId()); + log.info("Load class {} using classloader {} for plugin {}", packageName, PluginClassLoaderCache.getPlugin(plugin.getPluginId()), plugin.getPluginId()); + Class clazz = loader.loadClass(packageName); +// Class clazz = plugin.getPluginWrapper().getPluginClassLoader().loadClass(packageName); +// log.info("Load class {} using classloader {} for plugin {}", packageName, plugin.getPluginWrapper().getPluginClassLoader(), plugin.getPluginId()); + if (!BasePlugin.class.isAssignableFrom(clazz)) { + classList.add(clazz); + } + AdminGroups annotation = clazz.getAnnotation(AdminGroups.class); + if (annotation != null) { + adminGroupsClassList.add(clazz); + } + } + plugin.setClassList(classList); + plugin.setAdminGroupsClassList(adminGroupsClassList); + List> pluginClassList = plugin.getClassList().stream().filter(item -> !item.isInterface()).collect(Collectors.toList()); + if (!pluginClassList.isEmpty()) { + List> registryClassList = new ArrayList<>(); + for (Class aClass : pluginClassList) { + // 原本这里使用Collections.disjoint()实现,是有问题的,结果总是true。 + // 因为aClass.getAnnotations()获得的是Proxy数组,而REGIS_ANNO是Class数组 + if(AnnotationUtils.hasAnnotations(aClass, false, Bean.class, + Mapper.class, + Service.class, + Component.class, + Repository.class, + Controller.class, + Configuration.class, + RestController.class, + InterceptPath.class)){ + registryClassList.add(aClass); + } + } + if (!registryClassList.isEmpty()) { + plugin.getPluginApplicationContext().register(registryClassList.toArray(new Class[0])); + // plugin.getBeanCache().putAll(BeanUtils.getTempBeans(registryClassList)); + } + } + } + + @Override + public void unRegistry(PluginInfo plugin) throws Exception { + // 不做操作,直接通过关闭PluginApplicationContext完成注销 + } + + /** + * 扫描jar包中的类。 + * + * @param basePackage 包名 + * @param pluginWrapper jar的PluginWrapper + * @return 类全路径 + * @throws IOException 扫描异常 + */ + public static Set scanClassPackageName(String basePackage, PluginWrapper pluginWrapper) throws IOException { + Path pluginPath = pluginWrapper.getPluginPath(); + Set classPackageNames = new HashSet<>(); + File jarFile = null; + if(Files.isDirectory(pluginPath)){ + List jars = FileUtils.getJars(pluginPath); + jarFile = jars.get(0); + }else if(pluginPath.toFile().getName().toLowerCase().endsWith(".jar")){ + jarFile = pluginPath.toFile(); + }else { + throw new RuntimeException("不正确的pluginPath"); + } + try (JarFile jar = new JarFile(jarFile)) { + Enumeration jarEntries = jar.entries(); + while (jarEntries.hasMoreElements()) { + JarEntry entry = jarEntries.nextElement(); + String jarEntryName = entry.getName(); + if (jarEntryName.contains(".class") && jarEntryName.replaceAll("/", ".").startsWith(basePackage)) { + String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replace("/", "."); + classPackageNames.add(className); + } + } + } + return classPackageNames; + } + +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/ApplicationContextPluginHandler.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/ApplicationContextPluginHandler.java new file mode 100644 index 0000000..36b783e --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/ApplicationContextPluginHandler.java @@ -0,0 +1,54 @@ +package cd.casic.plugin.register; + +import cd.casic.plugin.PluginInfo; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; + +import java.util.Arrays; + +/** + * @Author:mianbin + * @Package:cd.casic.plugin.register + * @Project:ops + * @name:ApplicationContextPluginHandler + * @Date:2024/03/19 14:48 + * @Filename:ApplicationContextPluginHandler + * @description:Todo + */ +public class ApplicationContextPluginHandler implements BasePluginHandler { + @Override + public void initialize() throws Exception { + + } + + @Override + public void registry(PluginInfo plugin) throws Exception { + if (plugin.getApplicationContextIsRefresh()) { + return; + } + plugin.getPluginApplicationContext().setClassLoader(plugin.getPluginWrapper().getPluginClassLoader()); + plugin.getPluginApplicationContext().getDefaultListableBeanFactory() + .registerSingleton(plugin.getPluginWrapper().getPluginId().trim(), + plugin.getPluginWrapper().getPlugin()); + plugin.getPluginApplicationContext().refresh(); + plugin.setApplicationContextIsRefresh(true); + } + + @Override + public void unRegistry(PluginInfo plugin) throws Exception { + // 获取插件ApplicationContext的DefaultListableBeanFactory实例 + DefaultListableBeanFactory beanFactory = plugin.getPluginApplicationContext().getDefaultListableBeanFactory(); + + // 根据插件ID获取Bean的名称 +// String beanName = plugin.getPluginWrapper().getPluginId().trim(); +// +// // 删除已注册的Bean +// if (beanFactory.containsBeanDefinition(beanName)) { +// beanFactory.removeBeanDefinition(beanName); +// } + + String[] beanNames = beanFactory.getBeanNamesForType(plugin.getPluginWrapper().getPlugin().getClass()); + Arrays.stream(beanNames) + .filter(beanName -> beanName.equals(plugin.getPluginWrapper().getPluginId().trim())) + .forEach(beanFactory::destroySingleton); + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/BasePluginHandler.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/BasePluginHandler.java new file mode 100644 index 0000000..be8e9eb --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/BasePluginHandler.java @@ -0,0 +1,40 @@ +package cd.casic.plugin.register; + + +import cd.casic.plugin.PluginInfo; + +/** + * @Author:mianbin + * @Package:cd.casic.plugin.register + * @Project:ops + * @name:BasePluginHandler + * @Date:2024/03/18 15:48 + * @Filename:BasePluginHandler + * @description:基础插件注册类 + */ +public interface BasePluginHandler { + + /** + * 插件组件初始化 + * + * @throws Exception + */ + void initialize() throws Exception; + + /** + * 插件组件注册 + * + * @param plugin + * @throws Exception + */ + void registry(PluginInfo plugin) throws Exception; + + /** + * 插件组件卸载注册 + * + * @param plugin + * @throws Exception + */ + void unRegistry(PluginInfo plugin) throws Exception; + +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/ControllerHandler.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/ControllerHandler.java new file mode 100644 index 0000000..a928343 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/ControllerHandler.java @@ -0,0 +1,169 @@ +package cd.casic.plugin.register; + +import cd.casic.plugin.PluginInfo; +import cd.casic.plugin.PluginProperties; +import cd.casic.plugin.register.group.filter.impl.ControllerFilter; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationContext; +import org.springframework.util.ReflectionUtils; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.*; + +/** + * @Author:mianbin + * @Package:cd.casic.plugin.register + * @Project:ops + * @name:ControllerHandler + * @Date:2024/03/19 14:22 + * @Filename:ControllerHandler + * @description:Todo + */ +@Slf4j +public class ControllerHandler implements BasePluginHandler { + + RequestMappingHandlerMapping requestMappingHandlerMapping; + + Method getMappingForMethod; + + @Override + public void initialize() throws Exception { + + // 这里反射获取 getMappingForMethod + requestMappingHandlerMapping = SpringUtil.getBean(RequestMappingHandlerMapping.class); + getMappingForMethod = ReflectionUtils.findMethod(RequestMappingHandlerMapping.class, "getMappingForMethod", Method.class, Class.class); + getMappingForMethod.setAccessible(true); + } + + @Override + public void registry(PluginInfo plugin) throws Exception { + ApplicationContext applicationContext = plugin.getMainApplicationContext(); + PluginProperties pluginProperties = applicationContext.getBean(PluginProperties.class); + for (Class aClass : plugin.getGroupClass(ControllerFilter.GROUP_NAME)) { + setPathPrefix(plugin.getPluginId(), aClass, pluginProperties); + plugin.addController(aClass); + Object bean = plugin.getPluginApplicationContext().getBean(aClass); + Method[] methods = aClass.getMethods(); + for (Method method : methods) { + if (method.getAnnotation(RequestMapping.class) != null + || method.getAnnotation(GetMapping.class) != null + || method.getAnnotation(PostMapping.class) != null + || method.getAnnotation(DeleteMapping.class) != null + || method.getAnnotation(PutMapping.class) != null + || method.getAnnotation(PatchMapping.class) != null) { + RequestMappingInfo requestMappingInfo = (RequestMappingInfo) getMappingForMethod.invoke(requestMappingHandlerMapping, method, aClass); + requestMappingHandlerMapping.registerMapping(requestMappingInfo, bean, method); + } + } + } + } + + @Override + public void unRegistry(PluginInfo plugin) throws Exception { + for (RequestMappingInfo requestMappingInfo : getRequestMappingInfo(plugin)) { + requestMappingHandlerMapping.unregisterMapping(requestMappingInfo); + } + } + + List getRequestMappingInfo(PluginInfo plugin) throws Exception { + List requestMappingInfoList = new ArrayList<>(); + for (Class aClass : plugin.getGroupClass(ControllerFilter.GROUP_NAME)) { + Method[] methods = aClass.getMethods(); + for (Method method : methods) { + RequestMappingInfo requestMappingInfo = (RequestMappingInfo) getMappingForMethod.invoke(requestMappingHandlerMapping, method, aClass); + requestMappingInfoList.add(requestMappingInfo); + } + } + return requestMappingInfoList; + } + + + /** + * 设置请求路径前缀 + * + * @param aClass controller 类 + */ + private void setPathPrefix(String pluginId, Class aClass, PluginProperties pluginProperties) { + RequestMapping requestMapping = aClass.getAnnotation(RequestMapping.class); + if (requestMapping == null) { + return; + } + String pathPrefix = pluginProperties.getRestPathPrefix(); + if (pluginProperties.isEnablePluginIdAsRestPrefix()) { + if (StrUtil.isNotEmpty(pathPrefix)) { + pathPrefix = joiningPath(pathPrefix, pluginId); + } else { + pathPrefix = pluginId; + } + } else { + if (StrUtil.isEmpty(pathPrefix)) { + // 不启用插件id作为路径前缀, 并且路径前缀为空, 则直接返回。 + return; + } + } + handleRequestMapping(requestMapping, pathPrefix); + } + + @SuppressWarnings("unchecked") + private void handleRequestMapping(RequestMapping requestMapping, String pathPrefix) { + InvocationHandler invocationHandler = Proxy.getInvocationHandler(requestMapping); + Set definePaths = new HashSet<>(); + definePaths.addAll(Arrays.asList(requestMapping.path())); + definePaths.addAll(Arrays.asList(requestMapping.value())); + try { + Field field = invocationHandler.getClass().getDeclaredField("memberValues"); + field.setAccessible(true); + Map memberValues = (Map) field.get(invocationHandler); + String[] newPath = new String[definePaths.size()]; + int i = 0; + for (String definePath : definePaths) { + // 解决插件启用、禁用后, 路径前缀重复的问题。 + if (definePath.contains(pathPrefix)) { + newPath[i++] = definePath; + } else { + newPath[i++] = joiningPath(pathPrefix, definePath); + } + } + if (newPath.length == 0) { + newPath = new String[]{pathPrefix}; + } + memberValues.put("path", newPath); + memberValues.put("value", new String[]{}); + } catch (Exception e) { + log.error("Define Plugin RestController pathPrefix error : {}", e.getMessage(), e); + } + } + + /** + * 拼接路径 + * + * @param path1 路径1 + * @param path2 路径2 + * @return 拼接的路径 + */ + private String joiningPath(String path1, String path2) { + if (path1 != null && path2 != null) { + if (path1.endsWith("/") && path2.startsWith("/")) { + return path1 + path2.substring(1); + } else if (!path1.endsWith("/") && !path2.startsWith("/")) { + return path1 + "/" + path2; + } else { + return path1 + path2; + } + } else if (path1 != null) { + return path1; + } else if (path2 != null) { + return path2; + } else { + return ""; + } + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/MybatisHandler.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/MybatisHandler.java new file mode 100644 index 0000000..8dc6f41 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/MybatisHandler.java @@ -0,0 +1,163 @@ +package cd.casic.plugin.register; + +import cd.casic.plugin.PluginInfo; +import cd.casic.plugin.register.mybatis.mybatisplus.MybatisPlusHandler; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.builder.xml.XMLMapperBuilder; +import org.apache.ibatis.executor.ErrorContext; +import org.apache.ibatis.io.Resources; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.session.defaults.DefaultSqlSession; +import org.mybatis.spring.mapper.MapperFactoryBean; +import org.pf4j.util.FileUtils; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.GenericBeanDefinition; +import org.springframework.util.ClassUtils; + +import java.io.File; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.net.JarURLConnection; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.regex.Pattern; + +/** + * @Author:mianbin + * @Package:cd.casic.plugin.register + * @Project:ops + * @name:MybatisHandler + * @Date:2024/03/19 14:51 + * @Filename:MybatisHandler + * @deprecated 不再使用,采用{@link MybatisPlusHandler} 统一实现 + */ +@Deprecated +public class MybatisHandler implements BasePluginHandler { + + @Override + public void initialize() throws Exception { + + } + + @Override + public void registry(PluginInfo plugin) throws Exception { + List> mapperClassList = getMapperList(plugin); + if (mapperClassList.isEmpty()) return; + + //注册mapper + for (Class mapperClass : mapperClassList) { + GenericBeanDefinition definition = new GenericBeanDefinition(); + definition.getConstructorArgumentValues().addGenericArgumentValue(mapperClass); + definition.setBeanClass(MapperFactoryBean.class); + definition.getPropertyValues().add("addToConfig", true); + definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); + plugin.getPluginApplicationContext().registerBeanDefinition(mapperClass.getName(), definition); + } + //注册mapper.xml + SqlSessionFactory sqlSessionFactory = (SqlSessionFactory) plugin.getMainApplicationContext().getBean("sqlSessionFactory"); + Configuration configuration = sqlSessionFactory.getConfiguration(); + try { + Resources.setDefaultClassLoader(plugin.getPluginWrapper().getPluginClassLoader()); + Path pluginPath = plugin.getPluginWrapper().getPluginPath(); + String xmlLocationPattern = plugin.getMapperXmlDir(); + xmlLocationPattern = xmlLocationPattern.replaceAll("\\*\\*", "<>").replaceAll("\\*", "<>") + .replaceAll("\\.", "\\.").replaceAll("<>", ".*"); + File jarFile = null; + if(Files.isDirectory(pluginPath)){ + List jars = FileUtils.getJars(pluginPath); + jarFile = jars.get(0); + }else if(pluginPath.toFile().getName().toLowerCase().endsWith(".jar")){ + jarFile = pluginPath.toFile(); + }else { + throw new RuntimeException("不正确的pluginPath"); + } + Enumeration jarEntries = new JarFile(jarFile).entries(); + while (jarEntries.hasMoreElements()) { + JarEntry entry = jarEntries.nextElement(); + String jarEntryName = entry.getName(); + if (Pattern.matches(xmlLocationPattern, jarEntryName) && jarEntryName.endsWith(".xml")) { + URL url = new URL("jar:file:" + jarFile.getAbsolutePath() + "!/" + jarEntryName); + JarURLConnection jarConnection = (JarURLConnection) url.openConnection(); + InputStream in = jarConnection.getInputStream(); + try { + XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(in, + configuration, url.getPath(), configuration.getSqlFragments()); + xmlMapperBuilder.parse(); + in.close(); + } catch (Exception e) { + throw new Exception("Failed to parse mapping resource: '" + url.getPath() + "'", e); + } finally { + if (in != null) { + in.close(); + } + ErrorContext.instance().reset(); + JarFile currJarFile = jarConnection.getJarFile(); + currJarFile.close(); + } + } + } + } finally { + Resources.setDefaultClassLoader(ClassUtils.getDefaultClassLoader()); + } + } + + @Override + public void unRegistry(PluginInfo plugin) throws Exception { + List> mapperClassList = getMapperList(plugin); + if (mapperClassList.isEmpty()) return; + + for (Class mapperClass : mapperClassList) { + String[] beanNames = plugin.getPluginApplicationContext().getBeanNamesForType(mapperClass); + Arrays.stream(beanNames).forEach(beanName -> plugin.getPluginApplicationContext().removeBeanDefinition(beanName)); + } + SqlSessionFactory sqlSessionFactory = (SqlSessionFactory) plugin.getMainApplicationContext().getBean("sqlSessionFactory"); + Configuration configuration = sqlSessionFactory.getConfiguration(); + clearValues(configuration, "mappedStatements"); + clearValues(configuration, "caches"); + clearValues(configuration, "resultMaps"); + clearValues(configuration, "parameterMaps"); + clearValues(configuration, "keyGenerators"); + clearValues(configuration, "sqlFragments"); + // Field loadedResourcesField = configuration.getClass().getDeclaredField("loadedResources"); + // loadedResourcesField.setAccessible(true); + // ((Set) loadedResourcesField.get(configuration)).clear(); + } + + /** + * @description 获取所有Mapper接口 + * @author dolphin + * @date 2021/11/13 8:31 + */ + private List> getMapperList(PluginInfo plugin){ + List> mapperClassList = new ArrayList<>(); + + for (Class aClass : plugin.getClassList()) { + Mapper annotation = aClass.getAnnotation(Mapper.class); + if (annotation != null) { + mapperClassList.add(aClass); + } + } + return mapperClassList; + } + + private void clearValues(Configuration configuration, String fieldName) throws Exception { + + Field field = configuration.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + Map map = (Map) field.get(configuration); + DefaultSqlSession.StrictMap newMap = new DefaultSqlSession.StrictMap(); + for (Object key : map.keySet()) { + try { + newMap.put((String) key, map.get(key)); + } catch (IllegalArgumentException ex) { + newMap.put((String) key, ex.getMessage()); + } + } + field.set(configuration, newMap); + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/ResourcesHandler.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/ResourcesHandler.java new file mode 100644 index 0000000..ce3533a --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/ResourcesHandler.java @@ -0,0 +1,90 @@ +package cd.casic.plugin.register; + +import cd.casic.plugin.PluginInfo; +import cd.casic.plugin.constants.OpsConstants; +import cn.hutool.core.io.FileUtil; +import org.pf4j.util.FileUtils; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.net.JarURLConnection; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Enumeration; +import java.util.List; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * @Author:mianbin + * @Package:cd.casic.plugin.register + * @Project:ops + * @name:ResourcesHandler + * @Date:2024/03/19 14:54 + * @Filename:ResourcesHandler + * @description:Todo + */ +public class ResourcesHandler implements BasePluginHandler { + + @Override + public void initialize() throws Exception { + + } + + @Override + public void registry(PluginInfo plugin) throws Exception { + Path pluginPath = plugin.getPluginWrapper().getPluginPath(); + Set staticClassPathLocations = plugin.getStaticClassPathLocations(); + File jarFile = null; + if(Files.isDirectory(pluginPath)){ + List jars = FileUtils.getJars(pluginPath); + jarFile = jars.get(0); + }else if(pluginPath.toFile().getName().toLowerCase().endsWith(".jar")){ + jarFile = pluginPath.toFile(); + }else { + throw new RuntimeException("不正确的pluginPath"); + } + Enumeration jarEntries = new JarFile(jarFile).entries(); + File file = new File(OpsConstants.PLUGINS_RESOURCES_DIR + File.separator + plugin.getPluginId()); + if (!file.exists()) { + FileUtil.mkdir(file); + } + FileUtil.clean(file); + while (jarEntries.hasMoreElements()) { + JarEntry entry = jarEntries.nextElement(); + String jarEntryName = entry.getName(); + for (String staticClassPathLocation : staticClassPathLocations) { //staticClassPathLocation里读取到所有静态资源的位置 然后将以插件为单位 打包到web的static目录下 即可访问 + if (!staticClassPathLocation.equals(jarEntryName) && jarEntryName.startsWith(staticClassPathLocation) + && !jarEntryName.endsWith(".class") && !entry.isDirectory()) { + URL url = new URL("jar:file:" + jarFile.getAbsolutePath() + "!/" + jarEntryName); + JarURLConnection jarConnection = (JarURLConnection) url.openConnection(); + InputStream in = jarConnection.getInputStream(); + File file1 = new File(file.getAbsolutePath() + File.separator + jarEntryName); + FileUtil.touch(file1.getAbsolutePath()); + int index; + byte[] bytes = new byte[1024]; + FileOutputStream downloadFile = new FileOutputStream(file.getAbsolutePath() + File.separator + jarEntryName); + while ((index = in.read(bytes)) != -1) { + downloadFile.write(bytes, 0, index); + downloadFile.flush(); + } + downloadFile.close(); + in.close(); + JarFile currJarFile = jarConnection.getJarFile(); + currJarFile.close(); + } + } + } + } + + @Override + public void unRegistry(PluginInfo plugin) throws Exception { + File file = new File(OpsConstants.PLUGINS_RESOURCES_DIR + File.separator + plugin.getPluginId()); + if (file.exists()) { + FileUtil.del(file.getAbsolutePath()); + } + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/SpringBeanRegisterHandler.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/SpringBeanRegisterHandler.java new file mode 100644 index 0000000..b4f86ce --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/SpringBeanRegisterHandler.java @@ -0,0 +1,42 @@ +package cd.casic.plugin.register; + +import cd.casic.plugin.PluginInfo; +import cd.casic.plugin.register.group.filter.impl.BasicBeanFilter; +import cd.casic.plugin.register.group.filter.impl.ControllerFilter; +import cd.casic.plugin.register.group.filter.impl.MapperFilter; +import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition; +import org.springframework.beans.factory.support.BeanNameGenerator; +import org.springframework.context.annotation.AnnotationBeanNameGenerator; + +import java.util.ArrayList; +import java.util.List; + +public class SpringBeanRegisterHandler implements BasePluginHandler { + @Override + public void initialize() throws Exception { + + } + + @Override + public void registry(PluginInfo plugin) throws Exception { + List> basicBeanClassList = new ArrayList<>(); + basicBeanClassList.addAll(plugin.getGroupClass(BasicBeanFilter.GROUP_NAME)); + basicBeanClassList.addAll(plugin.getGroupClass(MapperFilter.GROUP_NAME)); + basicBeanClassList.addAll(plugin.getGroupClass(ControllerFilter.GROUP_NAME)); +// if(!basicBeanClassList.isEmpty()){ +// plugin.getPluginApplicationContext().register(basicBeanClassList.toArray(new Class[0])); +// } + basicBeanClassList.forEach(clazz -> { + AnnotatedGenericBeanDefinition beanDefinition = new AnnotatedGenericBeanDefinition(clazz); + beanDefinition.setBeanClass(clazz); + BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator(); + String beanName = beanNameGenerator.generateBeanName(beanDefinition, plugin.getPluginApplicationContext()); + plugin.getPluginApplicationContext().registerBeanDefinition(beanName, beanDefinition); + }); + } + + @Override + public void unRegistry(PluginInfo plugin) throws Exception { + + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/SpringDocHandler.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/SpringDocHandler.java new file mode 100644 index 0000000..0476777 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/SpringDocHandler.java @@ -0,0 +1,69 @@ +package cd.casic.plugin.register; + +import cd.casic.plugin.PluginInfo; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.extra.spring.SpringUtil; +import lombok.extern.slf4j.Slf4j; +import org.springdoc.api.AbstractOpenApiResource; +import org.springdoc.core.service.OpenAPIService; +import org.springframework.context.ApplicationContext; + +import java.lang.reflect.Field; +import java.util.Locale; +import java.util.Set; + +/** + * 插件系统集成SpringDoc。swagger能够根据插件启停自动更新接口文档 + */ +@Slf4j +public class SpringDocHandler implements BasePluginHandler { + private Set> restControllers; + private OpenAPIService openAPIService; + + @SuppressWarnings("unchecked") + @Override + public void initialize() { + ApplicationContext applicationContext = SpringUtil.getApplicationContext(); + AbstractOpenApiResource openApiResource = applicationContext.getBean(AbstractOpenApiResource.class); + try { + // 获取OpenApiResource的ADDITIONAL_REST_CONTROLLERS字段 + Field additionalRestControllers = ReflectUtil.getField(openApiResource.getClass(), "ADDITIONAL_REST_CONTROLLERS"); + additionalRestControllers.setAccessible(true); + restControllers = (Set>) additionalRestControllers.get(openApiResource); + } catch (IllegalAccessException e) { + restControllers = null; + } + openAPIService = applicationContext.getBean(OpenAPIService.class); + } + + @Override + public void registry(PluginInfo plugin) throws Exception { + if(restControllers != null){ + // 将插件中的Controller类添加到OpenApiResource的ADDITIONAL_REST_CONTROLLERS字段 + restControllers.addAll(plugin.getControllers()); + refresh(); + } + } + + @Override + public void unRegistry(PluginInfo plugin) throws Exception { + if(restControllers != null && !restControllers.isEmpty()){ + for (Class clazz : plugin.getControllers()) { + // 从OpenApiResource的ADDITIONAL_REST_CONTROLLERS字段中移除插件中的Controller类 + restControllers.remove(clazz); + } + refresh(); + plugin.getControllers().clear(); + } + } + + private void refresh(){ + if(openAPIService != null){ + // 手动刷新OpenApiResource + openAPIService.setCachedOpenAPI(null, Locale.getDefault()); + // openAPIService.resetCalculatedOpenAPI(); + } + } + + +} \ No newline at end of file diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/StartPluginManagerHandler.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/StartPluginManagerHandler.java new file mode 100644 index 0000000..a9fdfab --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/StartPluginManagerHandler.java @@ -0,0 +1,74 @@ +package cd.casic.plugin.register; + +import cd.casic.plugin.PluginInfo; +import cd.casic.plugin.register.config.ConfigurationFileHandler; +import cd.casic.plugin.register.config.SpringAutoConfigurationFileHandler; +import cd.casic.plugin.register.database.DatabaseHandler; +import cd.casic.plugin.register.group.ClassGroupHandler; +import cd.casic.plugin.register.mongoplus.MongoHandler; +import cd.casic.plugin.register.mybatis.mybatisplus.MybatisPlusHandler; +import cd.casic.plugin.register.websocket.WebSocketHandler; +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * @Author:mianbin + * @Package:cd.casic.plugin.handler + * @Project:ops + * @name:LoadPluginHandle + * @Date:2024/03/18 15:46 + * @Filename:LoadPluginHandle + * @description:Todo + */ +@Component +@Slf4j +public class StartPluginManagerHandler implements BasePluginHandler { + List pluginRegisterList = Collections.synchronizedList(new ArrayList<>()); + + @PostConstruct + @Override + public void initialize() throws Exception { + pluginRegisterList.clear(); + pluginRegisterList.add(new ClassGroupHandler()); + // pluginRegisterList.add(new AnnotationHandler()); + pluginRegisterList.add(new ConfigurationFileHandler()); + pluginRegisterList.add(new SpringAutoConfigurationFileHandler()); + pluginRegisterList.add(new SpringBeanRegisterHandler()); + pluginRegisterList.add(new MybatisPlusHandler()); + // pluginRegisterList.add(new MybatisHandler()); + pluginRegisterList.add(new ResourcesHandler()); + pluginRegisterList.add(new ApplicationContextPluginHandler()); + pluginRegisterList.add(new DatabaseHandler()); + pluginRegisterList.add(new ControllerHandler()); + pluginRegisterList.add(new WebSocketHandler()); + pluginRegisterList.add(new SpringDocHandler()); + pluginRegisterList.add(new MongoHandler()); + for (BasePluginHandler pluginHandle : pluginRegisterList) { + pluginHandle.initialize(); + } + } + + @Override + public void registry(PluginInfo plugin) throws Exception { + for (BasePluginHandler pluginHandler : pluginRegisterList) { + pluginHandler.registry(plugin); + } + } + + @Override + public void unRegistry(PluginInfo plugin) throws Exception { + for (BasePluginHandler pluginHandler : pluginRegisterList) { + pluginHandler.unRegistry(plugin); + } +// for (int i = pluginRegisterList.size() - 1; i >= 0; i--){ +// pluginRegisterList.get(i).unRegistry(plugin); +// } + plugin.getClassList().clear(); + plugin.setClassList(null); + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/config/ConfigFileUtils.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/config/ConfigFileUtils.java new file mode 100644 index 0000000..d136ba8 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/config/ConfigFileUtils.java @@ -0,0 +1,35 @@ +package cd.casic.plugin.register.config; + +import org.pf4j.util.StringUtils; + +public class ConfigFileUtils { + /** + * 拼接插件独立配置文件的文件名和环境后缀 + * @param fileName 插件独立配置文件文件名 + * @param suffix 配置文件环境后缀,PluginConfiguration中定义 + * @return 拼接后的文件名 + */ + public static String joinConfigFileName(String fileName, String suffix){ + if(StringUtils.isNullOrEmpty(fileName)){ + return null; + } + String fileNamePrefix; + String fileNamePrefixSuffix; + + if(fileName.lastIndexOf(".") == -1) { + fileNamePrefix = fileName; + fileNamePrefixSuffix = ""; + } else { + int index = fileName.lastIndexOf("."); + fileNamePrefix = fileName.substring(0, index); + fileNamePrefixSuffix = fileName.substring(index); + } + if(suffix == null){ + suffix = ""; + } + if(StringUtils.isNotNullOrEmpty(suffix) && !suffix.startsWith("-")){ + suffix = "-" + suffix; + } + return fileNamePrefix + suffix + fileNamePrefixSuffix; + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/config/ConfigurationFileHandler.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/config/ConfigurationFileHandler.java new file mode 100644 index 0000000..59554e0 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/config/ConfigurationFileHandler.java @@ -0,0 +1,126 @@ +package cd.casic.plugin.register.config; + +import cd.casic.plugin.PluginConfigWrapper; +import cd.casic.plugin.PluginInfo; +import cd.casic.plugin.PluginProperties; +import cd.casic.plugin.PluginYamlConfigurationParser; +import cd.casic.plugin.annotation.PluginConfiguration; +import cd.casic.plugin.register.BasePluginHandler; +import org.pf4j.RuntimeMode; +import org.pf4j.util.StringUtils; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; + +import java.util.ArrayList; +import java.util.List; + +/** + * 配置文件处理器,用于处理插件独立的配置文件 + */ +public class ConfigurationFileHandler implements BasePluginHandler { + private final PluginYamlConfigurationParser configurationParser; + + public ConfigurationFileHandler(){ + this.configurationParser = new PluginYamlConfigurationParser(); + } + + @Override + public void initialize() throws Exception { + + } + + @Override + public void registry(PluginInfo plugin) throws Exception { + PluginProperties pluginProperties = plugin.getMainApplicationContext().getBean(PluginProperties.class); + RuntimeMode runtimeMode = pluginProperties.getRuntimeMode(); + for(Class aClass : plugin.getClassList()){ + PluginConfiguration configDefinition = aClass.getAnnotation(PluginConfiguration.class); + if(configDefinition == null){ + continue; + } + String fileName = getConfigFileName(configDefinition, runtimeMode); + Object parseObject; + if(!StringUtils.isNullOrEmpty(fileName)){ + PluginConfigWrapper pluginConfigWrapper = + new PluginConfigWrapper(fileName, aClass); + parseObject = configurationParser.parse(plugin, pluginConfigWrapper); + } else { + try { + parseObject = aClass.getDeclaredConstructor().newInstance(); + } catch (NoSuchMethodException | InstantiationException | IllegalAccessException e) { + throw new RuntimeException("Failed to instantiate class " + aClass.getName(), e); + } + } + + String name = aClass.getSimpleName(); + DefaultListableBeanFactory listableBeanFactory = plugin.getPluginApplicationContext().getDefaultListableBeanFactory(); + if(!listableBeanFactory.containsSingleton(name)) { + listableBeanFactory.registerSingleton(name, parseObject); + } + plugin.addPluginConfigObject(parseObject); + } + } + + @Override + public void unRegistry(PluginInfo plugin) throws Exception { + /*上面的写法没有考虑到资源泄露,这里把clear,关闭资源*/ + List objectsToClear = new ArrayList<>(plugin.getPluginConfigObjects()); + plugin.getPluginConfigObjects().clear(); + for (Object obj : objectsToClear) { + if (obj instanceof AutoCloseable) { + ((AutoCloseable) obj).close(); + } + } + } + + /** + * 获取插件独立配置文件的文件名 + * @param pluginConfiguration 插件配置类上的自定义注解 + * @param runtimeMode 插件系统运行模式 + * @return 插件独立配置文件的文件名 + */ + private String getConfigFileName(PluginConfiguration pluginConfiguration, RuntimeMode runtimeMode){ + String fileName = pluginConfiguration.fileName(); + if(StringUtils.isNullOrEmpty(fileName)){ + return null; + } + String suffix = ""; + if(runtimeMode == RuntimeMode.DEPLOYMENT){ + // deployment模式 + suffix = pluginConfiguration.deploySuffix(); + } else if(runtimeMode == RuntimeMode.DEVELOPMENT){ + // development模式 + suffix = pluginConfiguration.devSuffix(); + } + return joinConfigFileName(fileName, suffix); + } + + /** + * 拼接插件独立配置文件的文件名和环境后缀 + * @param fileName 插件独立配置文件文件名 + * @param suffix 配置文件环境后缀,PluginConfiguration中定义 + * @return 拼接后的文件名 + */ + private String joinConfigFileName(String fileName, String suffix){ + if(StringUtils.isNullOrEmpty(fileName)){ + return null; + } + String fileNamePrefix; + String fileNamePrefixSuffix; + + if(fileName.lastIndexOf(".") == -1) { + fileNamePrefix = fileName; + fileNamePrefixSuffix = ""; + } else { + int index = fileName.lastIndexOf("."); + fileNamePrefix = fileName.substring(0, index); + fileNamePrefixSuffix = fileName.substring(index); + } + if(suffix == null){ + suffix = ""; + } + if(StringUtils.isNotNullOrEmpty(suffix) && !suffix.startsWith("-")){ + suffix = "-" + suffix; + } + return fileNamePrefix + suffix + fileNamePrefixSuffix; + } +} \ No newline at end of file diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/config/SpringAutoConfigurationFileHandler.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/config/SpringAutoConfigurationFileHandler.java new file mode 100644 index 0000000..cc604c7 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/config/SpringAutoConfigurationFileHandler.java @@ -0,0 +1,286 @@ +package cd.casic.plugin.register.config; + +import cd.casic.plugin.PluginInfo; +import cd.casic.plugin.pf4j.OpsPluginDescriptor; +import cd.casic.plugin.register.BasePluginHandler; +import cd.casic.plugin.utils.YamlUtils; +import cn.hutool.core.util.StrUtil; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.pf4j.PluginDescriptor; +import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.boot.env.PropertiesPropertySourceLoader; +import org.springframework.boot.env.PropertySourceLoader; +import org.springframework.boot.env.YamlPropertySourceLoader; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Environment; +import org.springframework.core.env.PropertySource; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +public class SpringAutoConfigurationFileHandler implements BasePluginHandler { + List propertySourceLoaders = new ArrayList<>(); + + /** + * 活动文件的属性名称,就是dev,prod等 + */ + private static final String ACTIVE_PROFILES_PROPERTY = "spring.profiles.active"; + + /** + * 配置文件的属性名称 + */ + private static final String INCLUDE_PROFILES_PROPERTY = "spring.profiles.include"; + + + @Override + public void initialize() throws Exception { + propertySourceLoaders.add(new YamlPropertySourceLoader()); + propertySourceLoaders.add(new PropertiesPropertySourceLoader()); + } + + @Override + public void registry(PluginInfo plugin) throws Exception { + PluginDescriptor pluginDescriptor = plugin.getPluginWrapper().getDescriptor(); + ConfigurableEnvironment environment = plugin.getPluginApplicationContext().getEnvironment(); + + if(!(pluginDescriptor instanceof OpsPluginDescriptor)){ + return; + } + + List resourcesFromDescriptor = getConfigResourceFromDescriptor(plugin); + + List> propProfiles = getPropProfiles(resourcesFromDescriptor); + if(ObjectUtils.isEmpty(propProfiles)){ + return; + } + for (PropertySource propertySource : propProfiles) { + environment.getPropertySources().addLast(propertySource); + } + + // 发现原始文件中配置的 profiles + List profiles = getProfiles(environment); + if(!ObjectUtils.isEmpty(profiles)){ + loadProfilesConfig(plugin.getPluginWrapper().getPluginPath(), (OpsPluginDescriptor) pluginDescriptor, environment, profiles); + } + + ConfigurationPropertiesBindingPostProcessor.register(plugin.getPluginApplicationContext()); + } + + @Override + public void unRegistry(PluginInfo plugin) throws Exception { + // 无需处理,后续关闭pluginApplicationContext即可 + } + + private List getConfigResourceFromDescriptor(PluginInfo plugin){ + log.info("插件 {} 尝试从插件描述中读取配置文件名", plugin.getPluginId()); + PluginDescriptor descriptor = plugin.getPluginWrapper().getDescriptor(); + if(!(descriptor instanceof OpsPluginDescriptor)){ + return Collections.emptyList(); + } + + OpsPluginDescriptor opsPluginDescriptor = (OpsPluginDescriptor) descriptor; + String configFileName = opsPluginDescriptor.getConfigFileName(); + if(StrUtil.isEmpty(configFileName)){ + log.info("插件 {} 的插件描述中未设置配置文件名", plugin.getPluginId()); + return Collections.emptyList(); + } + + List configFileActiveNames = getConfigFileActiveNames(configFileName, opsPluginDescriptor.getConfigFileActive()); + log.info("插件{} 的配置文件为 {} {}", plugin.getPluginId(), configFileName, configFileActiveNames); + Path pluginPath = plugin.getPluginWrapper().getPluginPath(); + + List configFileResources = configFileActiveNames.stream() + .map(configFileActiveName -> YamlUtils.getYamlPath(pluginPath, configFileActiveName)) + .filter(Objects::nonNull) + .map(FileSystemResource::new) + .collect(Collectors.toList()); + + configFileResources.add(new FileSystemResource(YamlUtils.getYamlPath(pluginPath, configFileName))); + + return configFileResources; + } + + private List getConfigResourceFromAnnotation(){ + return Collections.emptyList(); + } + + private List getConfigFileActiveNames(String configFileName, List configFileActives){ + return configFileActives.stream() + .map(configFileActive -> ConfigFileUtils.joinConfigFileName(configFileName, configFileActive)) + .collect(Collectors.toList()); + } + + /** + * 从 Resource 中解析出 PropertySource + * @param resources resources + * @return List + * @throws IOException 加载文件 IOException 异常 + */ + private List> getPropProfiles(List resources) throws IOException { + List> propProfiles = new ArrayList<>(); + if(resources == null || resources.isEmpty()){ + return propProfiles; + } + for (Resource resource : resources) { + if(resource == null || !resource.exists()){ + continue; + } + String filename = resource.getFilename(); + if(ObjectUtils.isEmpty(filename)){ + log.error("File name is empty!"); + return null; + } + for (PropertySourceLoader propertySourceLoader : propertySourceLoaders) { + if(!canLoadFileExtension(propertySourceLoader, filename)){ + continue; + } + log.info("正在从 {} 读取插件配置", filename); + List> propertySources = propertySourceLoader.load(filename, resource); + if(ObjectUtils.isEmpty(propertySources)){ + continue; + } + propProfiles.addAll(propertySources); + } + } + return propProfiles; + } + + /** + * 根据文件后缀判断是否可解析 + * @param loader PropertySourceLoader + * @param name 文件名称 + * @return boolean + */ + private boolean canLoadFileExtension(PropertySourceLoader loader, String name) { + return Arrays.stream(loader.getFileExtensions()) + .anyMatch((fileExtension) -> StringUtils.endsWithIgnoreCase(name, + fileExtension)); + } + + /** + * 加载 spring.profiles.active/spring.profiles.include 定义的配置 + * @param environment ConfigurableEnvironment + * @param profiles 主配置文件中定义的值 + * @throws Exception Exception + */ + private void loadProfilesConfig(Path pluginPath, OpsPluginDescriptor opsPluginDescriptor, + ConfigurableEnvironment environment, List profiles) throws Exception { + // 解析当前文件名称 + for (Profile profile : profiles) { + String name = profile.getName(); + String fileName = ConfigFileUtils.joinConfigFileName(opsPluginDescriptor.getConfigFileName(), name); + + + Path configFilePath = YamlUtils.getYamlPath(pluginPath, fileName); + FileSystemResource configFileResource = new FileSystemResource(configFilePath); + if(ObjectUtils.isEmpty(configFileResource)){ + continue; + } + List> propProfiles = getPropProfiles(Collections.singletonList(configFileResource)); + if(ObjectUtils.isEmpty(propProfiles)){ + return; + } + for (PropertySource propertySource : propProfiles) { + environment.getPropertySources().addLast(propertySource); + } + } + // 重新设置 ActiveProfiles + String[] names = profiles.stream() + .filter((profile) -> profile != null && !profile.isDefaultProfile()) + .map(Profile::getName) + .toArray(String[]::new); + environment.setActiveProfiles(names); + } + + private List getProfiles(Environment environment) { + List profiles = new ArrayList<>(); + Set activatedViaProperty = getProfilesActivatedViaProperty(environment); + profiles.addAll(getOtherActiveProfiles(environment, activatedViaProperty)); + profiles.addAll(activatedViaProperty); + profiles.removeIf( + (profile) -> (profile != null && profile.isDefaultProfile())); + return profiles; + } + + private Set getProfilesActivatedViaProperty(Environment environment) { + if (!environment.containsProperty(ACTIVE_PROFILES_PROPERTY) + && !environment.containsProperty(INCLUDE_PROFILES_PROPERTY)) { + return Collections.emptySet(); + } + Binder binder = Binder.get(environment); + Set activeProfiles = new LinkedHashSet<>(); + activeProfiles.addAll(getProfiles(binder, INCLUDE_PROFILES_PROPERTY)); + activeProfiles.addAll(getProfiles(binder, ACTIVE_PROFILES_PROPERTY)); + return activeProfiles; + } + + private List getOtherActiveProfiles(Environment environment, Set activatedViaProperty) { + return Arrays.stream(environment.getActiveProfiles()).map(Profile::new) + .filter((profile) -> !activatedViaProperty.contains(profile)) + .collect(Collectors.toList()); + } + + private Set getProfiles(Binder binder, String name) { + return binder.bind(name, String[].class).map(this::asProfileSet) + .orElse(Collections.emptySet()); + } + + private Set asProfileSet(String[] profileNames) { + List profiles = new ArrayList<>(); + for (String profileName : profileNames) { + profiles.add(new Profile(profileName)); + } + return new LinkedHashSet<>(profiles); + } + + @Getter + private static class Profile { + + private final String name; + + private final boolean defaultProfile; + + Profile(String name) { + this(name, false); + } + + Profile(String name, boolean defaultProfile) { + Assert.notNull(name, "Name must not be null"); + this.name = name; + this.defaultProfile = defaultProfile; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null || obj.getClass() != getClass()) { + return false; + } + return ((Profile) obj).name.equals(this.name); + } + + @Override + public int hashCode() { + return this.name.hashCode(); + } + + @Override + public String toString() { + return this.name; + } + + } + +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/database/DataBaseProperty.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/database/DataBaseProperty.java new file mode 100644 index 0000000..0a44ea3 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/database/DataBaseProperty.java @@ -0,0 +1,12 @@ +package cd.casic.plugin.register.database; + + +import cd.casic.plugin.utils.dBUtils.DBEnums; + +public interface DataBaseProperty { + DBEnums type(); + String driver(); + String url(); + String username(); + String password(); +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/database/DatabaseHandler.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/database/DatabaseHandler.java new file mode 100644 index 0000000..496f0c7 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/database/DatabaseHandler.java @@ -0,0 +1,84 @@ +package cd.casic.plugin.register.database; + +import cd.casic.plugin.PluginInfo; +import cd.casic.plugin.pf4j.OpsPluginDescriptor; +import cd.casic.plugin.register.BasePluginHandler; +import cd.casic.plugin.register.group.filter.impl.DataBaseEntityFilter; +import cd.casic.plugin.utils.dBUtils.SQLGenerator; +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; +import org.pf4j.PluginDescriptor; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +import java.util.List; + +@Slf4j +public class DatabaseHandler implements BasePluginHandler { + @Override + public void initialize() throws Exception { + + } + @Override + public void registry(PluginInfo plugin) throws Exception { + DataBaseProperty dataBaseProperty; + SQLGenerator sqlGenerator; + PluginDescriptor descriptor = plugin.getPluginWrapper().getDescriptor(); + if(!(descriptor instanceof OpsPluginDescriptor)){ + return; + } + OpsPluginDescriptor opsPluginDescriptor = (OpsPluginDescriptor) descriptor; + if(StrUtil.isNotEmpty(opsPluginDescriptor.getConfigFileName())){ + // 使用插件独立数据源 + AnnotationConfigApplicationContext pluginApplicationContext = plugin.getPluginApplicationContext(); + try{ + dataBaseProperty = pluginApplicationContext.getBean(DataBaseProperty.class); + }catch (Exception e){ + dataBaseProperty = null; + log.info("插件未配置独立数据源"); + } + } else { + dataBaseProperty = null; + } + try { + sqlGenerator = plugin.getMainApplicationContext().getBean(SQLGenerator.class); + }catch (Exception e){ + log.error("无法获取SqlGenerator类型的Bean对象"); + return; + } + List> entities = plugin.getGroupClass(DataBaseEntityFilter.GROUP_NAME); + for(Class entity : entities){ + sqlGenerator.sqlDeleteGenerator(entity, dataBaseProperty, plugin.getPluginId()); + sqlGenerator.sqlGenerator(entity, dataBaseProperty, plugin.getPluginId()); + } + } + + @Override + public void unRegistry(PluginInfo plugin) throws Exception { + DataBaseProperty dataBaseProperty; + SQLGenerator sqlGenerator; + PluginDescriptor descriptor = plugin.getPluginWrapper().getDescriptor(); + if(!(descriptor instanceof OpsPluginDescriptor)){ + return; + } + OpsPluginDescriptor opsPluginDescriptor = (OpsPluginDescriptor) descriptor; + if(StrUtil.isNotEmpty(opsPluginDescriptor.getConfigFileName())){ + // 使用插件独立数据源 + AnnotationConfigApplicationContext pluginApplicationContext = plugin.getPluginApplicationContext(); + try{ + dataBaseProperty = pluginApplicationContext.getBean(DataBaseProperty.class); + }catch (Exception e){ + log.info("插件未配置独立数据源"); + return; + } + } else { + return; + } + try { + sqlGenerator = plugin.getMainApplicationContext().getBean(SQLGenerator.class); + }catch (Exception e){ + log.error("无法获取SqlGenerator类型的Bean对象"); + return; + } + sqlGenerator.destroyDataSource(dataBaseProperty, plugin.getPluginId()); + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/group/AnnotationUtils.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/group/AnnotationUtils.java new file mode 100644 index 0000000..73b58de --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/group/AnnotationUtils.java @@ -0,0 +1,20 @@ +package cd.casic.plugin.register.group; + +import java.lang.annotation.Annotation; + +public class AnnotationUtils { + + @SafeVarargs + public static boolean hasAnnotations(Class clazz, boolean allRequired, Class... annotations){ + if(clazz == null) return false; + if(annotations == null) return false; + for(Class annotation : annotations){ + if(clazz.isAnnotationPresent(annotation)){ + if(!allRequired) return true; + }else{ + if(allRequired) return false; + } + } + return allRequired; + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/group/ClassGroupHandler.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/group/ClassGroupHandler.java new file mode 100644 index 0000000..602e38a --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/group/ClassGroupHandler.java @@ -0,0 +1,111 @@ +package cd.casic.plugin.register.group; + +import cd.casic.plugin.BasePlugin; +import cd.casic.plugin.PluginClassLoaderCache; +import cd.casic.plugin.PluginInfo; +import cd.casic.plugin.register.BasePluginHandler; +import cd.casic.plugin.register.group.filter.PluginClassFilter; +import cd.casic.plugin.register.group.filter.impl.*; +import lombok.extern.slf4j.Slf4j; +import org.pf4j.PluginWrapper; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.stream.Collectors; + +/** + * 加载插件中所有的类,并且分组存放到pluginInfo的map中,便于后面的handler进行处理 + */ +@Slf4j +public class ClassGroupHandler implements BasePluginHandler { + private final List classFilters = new ArrayList<>(); + + @Override + public void initialize() throws Exception { + classFilters.add(new BasicBeanFilter()); + classFilters.add(new ControllerFilter()); + classFilters.add(new DataBaseEntityFilter()); + classFilters.add(new MapperFilter()); + classFilters.add(new PluginConfigurationFilter()); + classFilters.add(new WebSocketFilter()); + } + + /** + * 将类分组存到PluginInfo的map中,方便后面其他Handler进行处理 + * @param plugin PluginInfo + */ + @Override + public void registry(PluginInfo plugin) throws Exception { + List> classList = new ArrayList<>(); + Set classPackageName = scanClassPackageName(plugin.getBasePlugin().scanPackage(), plugin.getBasePlugin().getWrapper()); + for (String packageName : classPackageName) { + ClassLoader loader = PluginClassLoaderCache.getPlugin(plugin.getPluginId()); + log.warn("Load class {} using classloader {} for plugin {}", packageName, PluginClassLoaderCache.getPlugin(plugin.getPluginId()), plugin.getPluginId()); + Class clazz = loader.loadClass(packageName); + if (!BasePlugin.class.isAssignableFrom(clazz)) { + classList.add(clazz); + } + } + plugin.setClassList(classList); + /*这里分组其实只是为了分组而分组,目前没有特别明确的用途*/ + List> pluginClassList = plugin.getClassList().stream().filter(item -> !item.isInterface()).collect(Collectors.toList()); + if (!pluginClassList.isEmpty()) { + for (Class clazz : pluginClassList) { + boolean grouped = false; + for(PluginClassFilter filter : classFilters){ + if(filter.filter(clazz)){ + log.info("将类 {} 添加到 {} 分组", clazz, filter.groupName()); + plugin.addGroupClass(filter.groupName(), clazz); + grouped = true; + } + } + if(!grouped){ + log.info("类 {} 不属于任何已知分组", clazz); + plugin.addGroupClass("unknown", clazz); + } + } + } + } + + /** + * 卸载插件时,将插件的类列表清空 + * @param plugin PluginInfo + */ + @Override + public void unRegistry(PluginInfo plugin) throws Exception { + plugin.getClassList().clear(); + } + + /** + * 扫描插件所在包,获取插件中所有的类名 + * @param basePackage 插件所在包名 + * @param pluginWrapper 插件对象包装类 + * @return 所有的类名集合 + */ + private Set scanClassPackageName(String basePackage, PluginWrapper pluginWrapper) throws IOException { + Path pluginPath = pluginWrapper.getPluginPath(); + if(pluginPath == null || !Files.exists(pluginPath)){ + throw new RuntimeException("错误的插件路径"); + } + File pluginFile = pluginPath.toFile(); + Set classPackageNames = new HashSet<>(); + + try (JarFile jar = new JarFile(pluginFile)) { + Enumeration jarEntries = jar.entries(); + while (jarEntries.hasMoreElements()) { + JarEntry entry = jarEntries.nextElement(); + String jarEntryName = entry.getName(); + if (jarEntryName.contains(".class") && jarEntryName.replaceAll("/", ".").startsWith(basePackage)) { + String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replace("/", "."); + classPackageNames.add(className); + } + } + } + return classPackageNames; + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/group/filter/PluginClassFilter.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/group/filter/PluginClassFilter.java new file mode 100644 index 0000000..9579281 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/group/filter/PluginClassFilter.java @@ -0,0 +1,9 @@ +package cd.casic.plugin.register.group.filter; + +public interface PluginClassFilter{ + String groupName(); + + void initialize(); + + boolean filter(Class clazz); +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/group/filter/impl/BasicBeanFilter.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/group/filter/impl/BasicBeanFilter.java new file mode 100644 index 0000000..38b7239 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/group/filter/impl/BasicBeanFilter.java @@ -0,0 +1,30 @@ +package cd.casic.plugin.register.group.filter.impl; + +import cd.casic.plugin.register.group.AnnotationUtils; +import cd.casic.plugin.register.group.filter.PluginClassFilter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; + +public class BasicBeanFilter implements PluginClassFilter { + public static final String GROUP_NAME = "basic_bean"; + + @Override + public String groupName() { + return GROUP_NAME; + } + + @Override + public void initialize() { + + } + + @Override + public boolean filter(Class clazz) { + return AnnotationUtils.hasAnnotations(clazz, false, Bean.class, + Service.class, + Component.class, + Configuration.class); + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/group/filter/impl/ControllerFilter.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/group/filter/impl/ControllerFilter.java new file mode 100644 index 0000000..6b3cd19 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/group/filter/impl/ControllerFilter.java @@ -0,0 +1,28 @@ +package cd.casic.plugin.register.group.filter.impl; + +import cd.casic.plugin.register.group.AnnotationUtils; +import cd.casic.plugin.register.group.filter.PluginClassFilter; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RestController; + +/** + * Controller 过滤器,筛选带有Controller或者RestController注解的类 + */ +public class ControllerFilter implements PluginClassFilter { + public static final String GROUP_NAME = "spring_controller"; + + @Override + public String groupName() { + return GROUP_NAME; + } + + @Override + public void initialize() { + + } + + @Override + public boolean filter(Class clazz) { + return AnnotationUtils.hasAnnotations(clazz, false, Controller.class, RestController.class); + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/group/filter/impl/DataBaseEntityFilter.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/group/filter/impl/DataBaseEntityFilter.java new file mode 100644 index 0000000..ad1ee0b --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/group/filter/impl/DataBaseEntityFilter.java @@ -0,0 +1,24 @@ +package cd.casic.plugin.register.group.filter.impl; + + +import cd.casic.plugin.register.group.AnnotationUtils; +import cd.casic.plugin.register.group.filter.PluginClassFilter; +import cd.casic.plugin.utils.dBUtils.annotation.Entity; + +public class DataBaseEntityFilter implements PluginClassFilter { + public static final String GROUP_NAME = "database_entity"; + @Override + public String groupName() { + return GROUP_NAME; + } + + @Override + public void initialize() { + + } + + @Override + public boolean filter(Class clazz) { + return AnnotationUtils.hasAnnotations(clazz, false, Entity.class); + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/group/filter/impl/MapperFilter.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/group/filter/impl/MapperFilter.java new file mode 100644 index 0000000..e3c3969 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/group/filter/impl/MapperFilter.java @@ -0,0 +1,24 @@ +package cd.casic.plugin.register.group.filter.impl; + +import cd.casic.plugin.register.group.filter.PluginClassFilter; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Repository; + +public class MapperFilter implements PluginClassFilter { + public static final String GROUP_NAME = "mapper"; + + @Override + public String groupName() { + return GROUP_NAME; + } + + @Override + public void initialize() { + + } + + @Override + public boolean filter(Class clazz) { + return clazz.isAnnotationPresent(Mapper.class) || clazz.isAnnotationPresent(Repository.class); + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/group/filter/impl/PluginConfigurationFilter.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/group/filter/impl/PluginConfigurationFilter.java new file mode 100644 index 0000000..a9dc6db --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/group/filter/impl/PluginConfigurationFilter.java @@ -0,0 +1,29 @@ +package cd.casic.plugin.register.group.filter.impl; + + +import cd.casic.plugin.BasePlugin; +import cd.casic.plugin.annotation.PluginConfiguration; +import cd.casic.plugin.register.group.filter.PluginClassFilter; + +public class PluginConfigurationFilter implements PluginClassFilter { + public static final String GROUP_NAME = "plugin_config"; + + @Override + public String groupName() { + return GROUP_NAME; + } + + @Override + public void initialize() { + + } + + @Override + public boolean filter(Class clazz) { + if(BasePlugin.class.isAssignableFrom(clazz)){ + return false; + } + + return clazz.isAnnotationPresent(PluginConfiguration.class); + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/group/filter/impl/WebSocketFilter.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/group/filter/impl/WebSocketFilter.java new file mode 100644 index 0000000..2d7c892 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/group/filter/impl/WebSocketFilter.java @@ -0,0 +1,25 @@ +package cd.casic.plugin.register.group.filter.impl; + + +import cd.casic.plugin.register.group.filter.PluginClassFilter; +import jakarta.websocket.server.ServerEndpoint; + +public class WebSocketFilter implements PluginClassFilter { + + public static final String GROUP_NAME = "websocket"; + + @Override + public String groupName() { + return GROUP_NAME; + } + + @Override + public void initialize() { + + } + + @Override + public boolean filter(Class clazz) { + return clazz.isAnnotationPresent(ServerEndpoint.class); + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/mongoplus/MongoHandler.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/mongoplus/MongoHandler.java new file mode 100644 index 0000000..861f23c --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/mongoplus/MongoHandler.java @@ -0,0 +1,112 @@ +package cd.casic.plugin.register.mongoplus; + +import cd.casic.plugin.PluginInfo; +import cd.casic.plugin.register.BasePluginHandler; +import com.anwen.mongo.annotation.collection.CollectionName; +import com.anwen.mongo.conn.CollectionManager; +import com.anwen.mongo.conn.ConnectMongoDB; +import com.anwen.mongo.convert.CollectionNameConvert; +import com.anwen.mongo.manager.MongoPlusClient; +import com.anwen.mongo.mapper.BaseMapper; +import com.anwen.mongo.service.IService; +import com.anwen.mongo.service.impl.ServiceImpl; +import com.mongodb.MongoException; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import lombok.extern.slf4j.Slf4j; +import org.bson.Document; +import org.springframework.beans.BeansException; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +@Slf4j +public class MongoHandler implements BasePluginHandler { + private AnnotationConfigApplicationContext applicationContext; + + @Override + public void initialize() throws Exception { + log.info("Start to hand the Mongo Class for plugin"); + } + + @Override + public void registry(PluginInfo plugin) throws Exception { + try { + applicationContext = plugin.getPluginApplicationContext(); + + // 注册AutoConfigurationPackages, 用于插件可自动配置 +// AutoConfigurationPackages.register(applicationContext.getDefaultListableBeanFactory(), +// plugin.getBasePlugin().scanPackage()); +// +// Set.of(MongoPlusConfiguration.class, MongoPlusAutoConfiguration.class, OverrideMongoConfiguration.class, +// MongoSpringProperty.class, MongoDBFieldProperty.class, MongoTransactionManagerAutoConfiguration.class, +// MongoPropertyConfiguration.class, ) +// ClassLoader pluginClassLoader = pluginRegistryInfo.getPluginClassLoader(); +// for (String autoConfigClassPackage : installAutoConfigClassString) { +// Class aClass = Class.forName(autoConfigClassPackage, false, pluginClassLoader); +// autoConfigurationClassSet.add(aClass); +// } +// for (Class autoConfigurationClass : autoConfigurationClassSet) { +// pluginApplicationContext.registerBean(autoConfigurationClass); +// } + + applicationContext.getBeansOfType(IService.class) + .values() + .stream() + .filter(s -> s instanceof ServiceImpl) + .forEach(s -> { + Class clazz = s.getGenericityClass(); + ServiceImpl serviceImpl = (ServiceImpl) s; + serviceImpl.setClazz(clazz); + String database = initFactory(clazz); + // 这里需要将MongoPlusClient给工厂 + serviceImpl.setDatabase(database); + serviceImpl.setBaseMapper(applicationContext.getBean("baseMapper", BaseMapper.class)); + }); + }catch(BeansException e){ + log.info(e.getMessage()); + } + } + + private String initFactory(Class clazz) { + String collectionName = clazz.getSimpleName().toLowerCase(); + final String[] dataBaseName = {""}; + if (clazz.isAnnotationPresent(CollectionName.class)) { + CollectionName annotation = clazz.getAnnotation(CollectionName.class); + collectionName = annotation.value(); + dataBaseName[0] = annotation.database(); + } + try { + String finalCollectionName = collectionName; + final String[] finalDataBaseName = {dataBaseName[0]}; + List mongoDatabaseList = new ArrayList<>(); + MongoPlusClient mongoPlusClient = applicationContext.getBean("mongoPlusClient", MongoPlusClient.class); + String database = mongoPlusClient.getBaseProperty().getDatabase(); + Arrays.stream(database.split(",")).collect(Collectors.toList()).forEach(db -> { + CollectionNameConvert collectionNameConvert = applicationContext.getBean("collectionNameConvert", CollectionNameConvert.class); + CollectionManager collectionManager = new CollectionManager(mongoPlusClient.getMongoClient(), collectionNameConvert, db); + ConnectMongoDB connectMongodb = new ConnectMongoDB(mongoPlusClient.getMongoClient(), db, finalCollectionName); + MongoDatabase mongoDatabase = mongoPlusClient.getMongoClient().getDatabase(db); + mongoDatabaseList.add(mongoDatabase); + if (Objects.equals(db, finalDataBaseName[0])) { + MongoCollection collection = connectMongodb.open(mongoDatabase); + collectionManager.setCollectionMap(finalCollectionName, collection); + } + mongoPlusClient.getCollectionManager().put(db, collectionManager); + }); + mongoPlusClient.setMongoDatabase(mongoDatabaseList); + } catch (MongoException e) { + log.error("Failed to connect to MongoDB: {}", e.getMessage(), e); + } + return dataBaseName[0]; + } + + @Override + public void unRegistry(PluginInfo plugin) throws Exception { + // 不做操作,直接通过关闭PluginApplicationContext完成注销 + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/mybatis/MapperHandler.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/mybatis/MapperHandler.java new file mode 100644 index 0000000..db9160e --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/mybatis/MapperHandler.java @@ -0,0 +1,101 @@ +package cd.casic.plugin.register.mybatis; + +import cd.casic.plugin.PluginInfo; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionTemplate; +import org.mybatis.spring.mapper.MapperFactoryBean; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; +import org.springframework.beans.factory.support.GenericBeanDefinition; +import org.springframework.context.annotation.*; + +import java.util.ArrayList; +import java.util.List; + +public class MapperHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(MapperHandler.class); + + private static final String MAPPER_INTERFACE_NAMES = "MybatisMapperInterfaceNames"; + + private final ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver(); + + + public MapperHandler() { + } + + /** + * 处理插件中的Mapper + * @param pluginInfo 插件信息 + * @param processMapper Mapper的具体处理者 + */ + public void processMapper(PluginInfo pluginInfo, + ProcessMapper processMapper){ + AnnotationConfigApplicationContext applicationContext = pluginInfo.getPluginApplicationContext(); + // TODO 这里可以把类进行分组,就不用每次都扫mapper + List> mapperClassList = new ArrayList<>(); + + for (Class aClass : pluginInfo.getClassList()) { + Mapper annotation = aClass.getAnnotation(Mapper.class); + if (annotation != null) { + mapperClassList.add(aClass); + } + } + + String pluginId = pluginInfo.getPluginId(); + for (Class mapperClass : mapperClassList) { + if (mapperClass == null) { + continue; + } +// BeanNameGenerator beanNameGenerator = new PluginAnnotationBeanNameGenerator(pluginId); + AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(mapperClass); + ScopeMetadata scopeMetadata = scopeMetadataResolver.resolveScopeMetadata(abd); + abd.setScope(scopeMetadata.getScopeName()); +// String beanName = beanNameGenerator.generateBeanName(abd, applicationContext); + String beanName = abd.getBeanClassName(); + BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName); + AnnotationConfigUtils.processCommonDefinitionAnnotations(abd); + BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, applicationContext); + try { + processMapper.process(definitionHolder, mapperClass); + } catch (Exception e) { + LOGGER.error("process mapper '{}' error. {}", mapperClass.getName(), e.getMessage(), e); + } + } + } + + + /** + * 公共注册生成代理Mapper接口 + * @param holder ignore + * @param mapperClass ignore + * @param sqlSessionFactory ignore + * @param sqlSessionTemplate ignore + */ + public void commonProcessMapper(BeanDefinitionHolder holder, + Class mapperClass, + SqlSessionFactory sqlSessionFactory, + SqlSessionTemplate sqlSessionTemplate) { + GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition(); + definition.getConstructorArgumentValues().addGenericArgumentValue(mapperClass); + definition.setBeanClass(MapperFactoryBean.class); + definition.getPropertyValues().add("addToConfig", true); + definition.getPropertyValues().add("sqlSessionFactory", sqlSessionFactory); + definition.getPropertyValues().add("sqlSessionTemplate", sqlSessionTemplate); + definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); + } + + + + @FunctionalInterface + public interface ProcessMapper{ + void process(BeanDefinitionHolder holder, Class mapperClass) throws Exception; + } + + +} \ No newline at end of file diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/mybatis/MybatisCommonConfig.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/mybatis/MybatisCommonConfig.java new file mode 100644 index 0000000..bf670a9 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/mybatis/MybatisCommonConfig.java @@ -0,0 +1,38 @@ +package cd.casic.plugin.register.mybatis; + +import java.util.Set; + +/** + * Mybatis基础配置 + */ +public interface MybatisCommonConfig { + + /** + * 数据库表对应的实体类的包名集合。可配置多个 + * @return Set + */ + Set entityPackage(); + + /** + * mybatis xml mapper 匹配规则
+ * ? 匹配一个字符
+ * * 匹配零个或多个字符
+ * ** 匹配路径中的零或多个目录
+ * 例如:
+ * 文件路径配置为

file:D://xml/*PluginMapper.xml


+ * resources路径配置为

classpath:xml/mapper/*PluginMapper.xml


+ * 包路径配置为

package:com.plugin.xml.mapper.*PluginMapper.xml


+ * @return Set + */ + Set xmlLocationsMatch(); + + /** + * 插件是否自主启用配置. 默认进行禁用, 使用主程序的配置 + * @return 返回true, 表示进行插件自主进行Mybatis相关配置 + */ + default boolean enableOneselfConfig(){ + return false; + } + + +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/mybatis/PluginFollowCoreConfig.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/mybatis/PluginFollowCoreConfig.java new file mode 100644 index 0000000..1ebe43e --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/mybatis/PluginFollowCoreConfig.java @@ -0,0 +1,123 @@ +package cd.casic.plugin.register.mybatis; + +import com.baomidou.mybatisplus.core.MybatisConfiguration; +import org.apache.ibatis.mapping.DatabaseIdProvider; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.scripting.LanguageDriver; +import org.apache.ibatis.scripting.LanguageDriverRegistry; +import org.apache.ibatis.session.SqlSessionFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.util.ReflectionUtils; + +import javax.sql.DataSource; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 插件跟随主程序时, 获取主程序的Mybatis定义的一些配置 + */ +public class PluginFollowCoreConfig { + + private final ApplicationContext mainApplicationContext; + + public PluginFollowCoreConfig(ApplicationContext mainApplicationContext) { + this.mainApplicationContext = mainApplicationContext; + } + + + public DataSource getDataSource(){ + return mainApplicationContext.getBean(DataSource.class); + } + + public MybatisConfiguration getMybatisPlusConfiguration(){ + MybatisConfiguration configuration = new MybatisConfiguration(); + try { + Map customizerMap = + mainApplicationContext.getBeansOfType(com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer.class); + if(!customizerMap.isEmpty()){ + for (com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer customizer : customizerMap.values()) { + customizer.customize(configuration); + } + } + } catch (Exception e){ + // ignore + } + return configuration; + } + + public Interceptor[] getInterceptor(){ + Map, Interceptor> interceptorMap = new HashMap<>(); + try { + SqlSessionFactory sqlSessionFactory = mainApplicationContext.getBean(SqlSessionFactory.class); + // 先从 SqlSessionFactory 工厂中获取拦截器 + List interceptors = sqlSessionFactory.getConfiguration().getInterceptors(); + if(interceptors != null){ + for (Interceptor interceptor : interceptors) { + if(interceptor == null){ + continue; + } + interceptorMap.put(interceptor.getClass(), interceptor); + } + } + } catch (Exception e){ + // ignore + } + // 再从定义Bean中获取拦截器 + Map beanInterceptorMap = mainApplicationContext.getBeansOfType(Interceptor.class); + if(!beanInterceptorMap.isEmpty()){ + beanInterceptorMap.forEach((k, v)->{ + // 如果Class一致, 则会覆盖 + interceptorMap.put(v.getClass(), v); + }); + } + if(interceptorMap.isEmpty()) { + return null; + } else { + return interceptorMap.values().toArray(new Interceptor[0]); + } + } + + public DatabaseIdProvider getDatabaseIdProvider(){ + String[] beanNamesForType = mainApplicationContext.getBeanNamesForType(DatabaseIdProvider.class, false, false); + if(beanNamesForType.length > 0){ + return mainApplicationContext.getBean(DatabaseIdProvider.class); + } + return null; + } + + @SuppressWarnings("unchecked") + public LanguageDriver[] getLanguageDriver(){ + Map, LanguageDriver> languageDriverMap = new HashMap<>(); + try { + SqlSessionFactory sqlSessionFactory = mainApplicationContext.getBean(SqlSessionFactory.class); + LanguageDriverRegistry languageRegistry = sqlSessionFactory.getConfiguration() + .getLanguageRegistry(); + // 先从 SqlSessionFactory 工厂中获取LanguageDriver + Field proxyTypesField = ReflectionUtils.findField(languageRegistry.getClass(), "LANGUAGE_DRIVER_MAP"); + Map, LanguageDriver> driverMap = null; + if(proxyTypesField != null){ + proxyTypesField.setAccessible(true); + driverMap = (Map, LanguageDriver>) proxyTypesField.get(languageRegistry); + } + if(driverMap != null){ + languageDriverMap.putAll(driverMap); + } + } catch (Exception e){ + // ignore + } + Map beansLanguageDriver = mainApplicationContext.getBeansOfType(LanguageDriver.class); + if(!beansLanguageDriver.isEmpty()){ + beansLanguageDriver.forEach((k, v)->{ + // 如果Class一致, 则会覆盖 + languageDriverMap.put(v.getClass(), v); + }); + } + if(languageDriverMap.isEmpty()){ + return null; + } + return languageDriverMap.values().toArray(new LanguageDriver[0]); + } + +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/mybatis/PluginResourceFinder.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/mybatis/PluginResourceFinder.java new file mode 100644 index 0000000..70a6ad2 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/mybatis/PluginResourceFinder.java @@ -0,0 +1,121 @@ +package cd.casic.plugin.register.mybatis; + +import cd.casic.plugin.PluginInfo; +import cd.casic.plugin.utils.ResourceUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.core.type.ClassMetadata; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; +import org.springframework.util.ClassUtils; + +import java.io.IOException; +import java.util.*; + +/** + * 插件资源发现者 + * @author starBlues + * @version 2.4.0 + */ +public class PluginResourceFinder { + + private static final Logger LOGGER = LoggerFactory.getLogger(PluginResourceFinder.class); + + private final ClassLoader classLoader; + private final ResourcePatternResolver resourcePatternResolver; + private final MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory(); + + + public PluginResourceFinder(PluginInfo pluginInfo) { + this.classLoader = pluginInfo.getPluginWrapper().getPluginClassLoader(); + this.resourcePatternResolver = new PathMatchingResourcePatternResolver(classLoader); + } + + /** + * 获取插件中xml资源 + * @param xmlLocationsMatchSet xml资源匹配集合 + * @return xml Resource 数组 + * @throws IOException 获取xml资源异常 + */ + public Resource[] getXmlResource(Set xmlLocationsMatchSet) throws IOException { + if(xmlLocationsMatchSet == null || xmlLocationsMatchSet.isEmpty()){ + return null; + } + List resources = new ArrayList<>(); + for (String xmlLocationsMatch : xmlLocationsMatchSet) { + if(xmlLocationsMatchSet.isEmpty()){ + continue; + } + List loadResources = getXmlResources(xmlLocationsMatch); + if(loadResources != null && !loadResources.isEmpty()){ + resources.addAll(loadResources); + } + } + + if(resources.isEmpty()){ + return null; + } + + return resources.toArray(new Resource[0]); + } + + + + /** + * 获取插件的实体类及其别名 + * @param packagePatterns 实体类包名 + * @return class 数组 + * @throws IOException 获取医院异常 + */ + public Class[] getAliasesClasses(Set packagePatterns) throws IOException { + if(packagePatterns == null || packagePatterns.isEmpty()){ + return null; + } + Set> aliasesClasses = new HashSet<>(); + for (String packagePattern : packagePatterns) { + Resource[] resources = resourcePatternResolver.getResources( + ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + + ClassUtils.convertClassNameToResourcePath(packagePattern) + "/**/*.class"); + for (Resource resource : resources) { + try { + ClassMetadata classMetadata = metadataReaderFactory.getMetadataReader(resource).getClassMetadata(); + Class clazz = classLoader.loadClass(classMetadata.getClassName()); + aliasesClasses.add(clazz); + } catch (Throwable e) { + LOGGER.warn("Cannot load the '{}'. Cause by {}", resource, e.toString()); + } + } + } + return aliasesClasses.toArray(new Class[0]); + } + + /** + * 获取Xml资源 + * @param mybatisMapperXmlLocationMatch mybatis xml 批量规则 + * @return 匹配到的xml资源 + * @throws IOException IO 异常 + */ + private List getXmlResources(String mybatisMapperXmlLocationMatch) throws IOException { + String matchLocation = ResourceUtils.getMatchLocation(mybatisMapperXmlLocationMatch); + if(matchLocation == null){ + LOGGER.error("mybatisMapperXmlLocation {} illegal", mybatisMapperXmlLocationMatch); + return null; + } + try { + Resource[] resources = resourcePatternResolver.getResources(matchLocation); + if(resources.length > 0){ + return Arrays.asList(resources); + } else { + return null; + } + } catch (IOException e) { + LOGGER.error("mybatis xml resource '{}' match error : {}", mybatisMapperXmlLocationMatch, + e.getMessage(), e); + throw e; + } + } + +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/mybatis/mybatisplus/MybatisPlusConfig.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/mybatis/mybatisplus/MybatisPlusConfig.java new file mode 100644 index 0000000..dcf74c9 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/mybatis/mybatisplus/MybatisPlusConfig.java @@ -0,0 +1,27 @@ +package cd.casic.plugin.register.mybatis.mybatisplus; + +import cd.casic.plugin.register.mybatis.MybatisCommonConfig; +import com.baomidou.mybatisplus.core.MybatisConfiguration; +import com.baomidou.mybatisplus.core.config.GlobalConfig; +import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean; + +public interface MybatisPlusConfig extends MybatisCommonConfig { + + /** + * 插件自主配置Mybatis-Plus的MybatisSqlSessionFactoryBean + * MybatisSqlSessionFactoryBean 具体配置说明参考 Mybatis-plus 官网 + * @param sqlSessionFactoryBean MybatisSqlSessionFactoryBean + */ + default void oneselfConfig(MybatisSqlSessionFactoryBean sqlSessionFactoryBean){ + } + + /** + * 重写设置配置 + * 只有 enableOneselfConfig 返回 false, 实现该方法才生效 + * @param configuration 当前 MybatisConfiguration + * @param globalConfig 当前全局配置GlobalConfig + */ + default void reSetMainConfig(MybatisConfiguration configuration, GlobalConfig globalConfig){ + + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/mybatis/mybatisplus/MybatisPlusHandler.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/mybatis/mybatisplus/MybatisPlusHandler.java new file mode 100644 index 0000000..e6d2866 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/mybatis/mybatisplus/MybatisPlusHandler.java @@ -0,0 +1,133 @@ +package cd.casic.plugin.register.mybatis.mybatisplus; + +import cd.casic.plugin.PluginInfo; +import cd.casic.plugin.register.BasePluginHandler; +import cd.casic.plugin.register.mybatis.MapperHandler; +import cd.casic.plugin.register.mybatis.PluginFollowCoreConfig; +import cd.casic.plugin.register.mybatis.PluginResourceFinder; +import com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties; +import com.baomidou.mybatisplus.core.MybatisConfiguration; +import com.baomidou.mybatisplus.core.config.GlobalConfig; +import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean; +import org.apache.ibatis.io.Resources; +import org.apache.ibatis.mapping.DatabaseIdProvider; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.scripting.LanguageDriver; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionTemplate; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.core.io.Resource; +import org.springframework.util.ClassUtils; + +import java.io.IOException; +import java.util.Set; + +public class MybatisPlusHandler implements BasePluginHandler { + @Override + public void initialize() throws Exception { + + } + + @Override + public void registry(PluginInfo plugin) throws IOException { + MybatisPlusConfig config = getObjectByInterfaceClass(plugin.getPluginConfigObjects(), MybatisPlusConfig.class); + if(config == null) return; + final MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean(); + + if(config.enableOneselfConfig()){ + config.oneselfConfig(factory); + } else { + PluginFollowCoreConfig followCoreConfig = new PluginFollowCoreConfig( + plugin.getMainApplicationContext() + ); + MybatisConfiguration mybatisPlusConfiguration = followCoreConfig.getMybatisPlusConfiguration(); + factory.setDataSource(followCoreConfig.getDataSource()); + factory.setConfiguration(mybatisPlusConfiguration); + Interceptor[] interceptor = followCoreConfig.getInterceptor(); + if(interceptor != null && interceptor.length > 0){ + factory.setPlugins(interceptor); + } + DatabaseIdProvider databaseIdProvider = followCoreConfig.getDatabaseIdProvider(); + if(databaseIdProvider != null){ + factory.setDatabaseIdProvider(databaseIdProvider); + } + LanguageDriver[] languageDriver = followCoreConfig.getLanguageDriver(); + if(languageDriver != null){ + factory.setScriptingLanguageDrivers(languageDriver); + } + // 配置mybatis-plus私有的配置 + GlobalConfig globalConfig = mybatisPlusFollowCoreConfig(factory, plugin.getMainApplicationContext()); + config.reSetMainConfig(mybatisPlusConfiguration, globalConfig); + } + + PluginResourceFinder pluginResourceFinder = new PluginResourceFinder(plugin); + + Class[] aliasesClasses = pluginResourceFinder.getAliasesClasses(config.entityPackage()); + if(aliasesClasses != null && aliasesClasses.length > 0){ + factory.setTypeAliases(aliasesClasses); + } + + Resource[] xmlResource = pluginResourceFinder.getXmlResource(config.xmlLocationsMatch()); + if(xmlResource != null && xmlResource.length > 0){ + factory.setMapperLocations(xmlResource); + } + ClassLoader defaultClassLoader = Resources.getDefaultClassLoader(); + try { + Resources.setDefaultClassLoader(plugin.getPluginWrapper().getPluginClassLoader()); + SqlSessionFactory sqlSessionFactory = factory.getObject(); + if(sqlSessionFactory == null){ + throw new Exception("Get mybatis-plus sqlSessionFactory is null"); + } + SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory); + MapperHandler mapperHandler = new MapperHandler(); + mapperHandler.processMapper(plugin, (holder, mapperClass) -> + mapperHandler.commonProcessMapper(holder, mapperClass, sqlSessionFactory, sqlSessionTemplate)); + DefaultListableBeanFactory beanFactory = plugin.getPluginApplicationContext().getDefaultListableBeanFactory(); + beanFactory.registerSingleton("sqlSessionFactory", sqlSessionFactory); + beanFactory.registerSingleton("sqlSession", sqlSessionTemplate); + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + Resources.setDefaultClassLoader(defaultClassLoader); + } + } + + @Override + public void unRegistry(PluginInfo plugin) throws Exception { + // 不做操作,直接通过关闭PluginApplicationContext完成注销。 + } + + + private GlobalConfig mybatisPlusFollowCoreConfig(MybatisSqlSessionFactoryBean factory, + ApplicationContext mainApplicationContext){ + MybatisPlusProperties plusProperties = mainApplicationContext.getBean(MybatisPlusProperties.class); + + GlobalConfig currentGlobalConfig = new GlobalConfig(); + currentGlobalConfig.setBanner(false); + GlobalConfig globalConfig = plusProperties.getGlobalConfig(); + if(globalConfig != null){ + currentGlobalConfig.setDbConfig(globalConfig.getDbConfig()); + currentGlobalConfig.setIdentifierGenerator(globalConfig.getIdentifierGenerator()); + currentGlobalConfig.setMetaObjectHandler(globalConfig.getMetaObjectHandler()); + currentGlobalConfig.setSqlInjector(globalConfig.getSqlInjector()); + } + factory.setGlobalConfig(currentGlobalConfig); + return currentGlobalConfig; + } + + // TODO 临时放这里,先跑通mybatis-plus集成测试 + public static T getObjectByInterfaceClass(Set sourceObject, Class interfaceClass){ + if(sourceObject == null || sourceObject.isEmpty()){ + return null; + } + for (Object configSingletonObject : sourceObject) { + Set> allInterfacesForClassAsSet = ClassUtils + .getAllInterfacesAsSet(configSingletonObject); + if(allInterfacesForClassAsSet.contains(interfaceClass)){ + return (T) configSingletonObject; + } + } + return null; + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/websocket/BaseServerEndpoint.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/websocket/BaseServerEndpoint.java new file mode 100644 index 0000000..a16c36e --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/websocket/BaseServerEndpoint.java @@ -0,0 +1,7 @@ +package cd.casic.plugin.register.websocket; + +import org.springframework.beans.factory.DisposableBean; + +public interface BaseServerEndpoint extends DisposableBean { + +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/websocket/WebSocketHandler.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/websocket/WebSocketHandler.java new file mode 100644 index 0000000..85e521d --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/websocket/WebSocketHandler.java @@ -0,0 +1,235 @@ +package cd.casic.plugin.register.websocket; + +import cd.casic.plugin.PluginInfo; +import cd.casic.plugin.register.BasePluginHandler; +import cd.casic.plugin.register.group.filter.impl.WebSocketFilter; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import jakarta.servlet.ServletContext; +import jakarta.websocket.DeploymentException; +import jakarta.websocket.EndpointConfig; +import jakarta.websocket.Session; +import jakarta.websocket.server.ServerContainer; +import jakarta.websocket.server.ServerEndpoint; +import jakarta.websocket.server.ServerEndpointConfig; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.pf4j.util.StringUtils; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentSkipListMap; + +@Slf4j +@SuppressWarnings("unchecked") +public class WebSocketHandler implements BasePluginHandler { + private ApplicationContext mainApplicationContext; + + @Override + public void initialize() throws Exception { + this.mainApplicationContext = SpringUtil.getApplicationContext(); + } + + @Override + public void registry(PluginInfo plugin) throws Exception { + ServerContainer serverContainer = getServerContainer(); + if (serverContainer == null) return; + + List> webSocketClassList = plugin.getGroupClass(WebSocketFilter.GROUP_NAME); + if(webSocketClassList.isEmpty()) return; + String pluginId = plugin.getPluginId(); + webSocketClassList.forEach(websocketClass -> { + ServerEndpoint serverEndpoint = websocketClass.getDeclaredAnnotation(ServerEndpoint.class); + if (serverEndpoint == null) { + log.warn("WebSocket class {} doesn't has annotation {}", websocketClass.getName(), ServerEndpoint.class.getName()); + return; + } + String sourcePath = serverEndpoint.value(); + if (StringUtils.isNullOrEmpty(sourcePath)) { + return; + } + String processPath = sourcePath; + if(!processPath.startsWith("/")){ + processPath = "/".concat(processPath); + } + UriTemplate uriTemplate; + try { + uriTemplate = new UriTemplate(processPath); + } catch (DeploymentException e) { + log.error("Websocket path validate failed.", e); + return; + } + String newWebsocketPath = "/".concat(pluginId).concat(processPath); + String newWebsocketTemplatePath = "/".concat(pluginId).concat(uriTemplate.getPath()); + Map annotationsUpdater; + try { + InvocationHandler invocationHandler = Proxy.getInvocationHandler(serverEndpoint); + Field field = invocationHandler.getClass().getDeclaredField("memberValues"); + field.setAccessible(true); + annotationsUpdater = (Map) field.get(invocationHandler); + } catch (Exception e) { + log.error("Process and update websocket path '{}' annotation exception.", sourcePath, e); + return; + } + try { + annotationsUpdater.put("value", newWebsocketPath); + serverContainer.addEndpoint(websocketClass); + plugin.getWebSocketPathMap().put(newWebsocketPath, newWebsocketTemplatePath); + log.info("Succeed to create websocket service for path {}", newWebsocketPath); + } catch (Exception e) { + log.error("Create websocket service for websocket class " + websocketClass.getName() + " failed.", e); + } finally { + annotationsUpdater.put("value", sourcePath); + } + }); + + } + + @Override + public void unRegistry(PluginInfo plugin){ + ServerContainer serverContainer = getServerContainer(); + if (serverContainer == null) { + log.warn("Not found ServerContainer, So websocket can't used!"); + return; + } + + Class containerClass = serverContainer.getClass(); + try { + Field configExactMatchMapField = containerClass.getDeclaredField("configExactMatchMap"); + configExactMatchMapField.setAccessible(true); + Map configExactMatchMap = (Map) configExactMatchMapField.get(serverContainer); + + Field configTemplateMatchMapField = containerClass.getDeclaredField("configTemplateMatchMap"); + configTemplateMatchMapField.setAccessible(true); + Map> configTemplateMatchMap = + (Map>) configTemplateMatchMapField.get(serverContainer); + + Map webSocketPathMap = plugin.getWebSocketPathMap(); + webSocketPathMap.forEach((webSocketPath, newWebsocketTemplatePath)->{ + configExactMatchMap.remove(webSocketPath); + log.debug("Removed websocket config for path {}", webSocketPath); + configTemplateMatchMap.forEach((key, value) -> value.remove(newWebsocketTemplatePath)); + log.debug("Removed websocket session for path {}", webSocketPath); + log.info("Remove websocket for path {} success.", webSocketPath); + }); + }catch (Exception e){ + throw new RuntimeException(e); + } + + + } + + /** + * 得到 Tomcat ServerContainer + * @return ServerContainer + */ + private ServerContainer getServerContainer() { + try { + mainApplicationContext.getBean(ServerEndpointExporter.class); + } catch (BeansException e) { + log.debug("The required bean of {} not found, if you want to use plugin websocket, please create it.", ServerEndpointExporter.class.getName()); + return null; + } + if (!(mainApplicationContext instanceof WebApplicationContext)) { + return null; + } + WebApplicationContext webApplicationContext = (WebApplicationContext) mainApplicationContext; + ServletContext servletContext = webApplicationContext.getServletContext(); + if (servletContext == null) { + log.warn("Servlet context is null."); + return null; + } + Object obj = servletContext.getAttribute("javax.websocket.server.ServerContainer"); + if (!(obj instanceof ServerContainer)) { + return null; + } + return (ServerContainer) obj; + } + + /** + * 关闭session + * @param session session + * @param websocketPath websocketPath 路径 + * @return 如果需要关闭并且关闭成功, 则返回true。 否则返回false + * @throws Exception 关闭异常 + */ + @Deprecated + private boolean closeSession(Session session, String websocketPath) throws Exception{ + EndpointConfig endpointConfig = (EndpointConfig) session.getClass().getDeclaredField("endpointConfig").get(session); + ServerEndpointConfig perEndpointConfig = (ServerEndpointConfig) endpointConfig.getClass().getDeclaredField("perEndpointConfig").get(endpointConfig); + String path = perEndpointConfig.getPath(); + if (path.equals(websocketPath)) { + session.close(); + log.info("Closed websocket session {} for path {}", session.getId(), websocketPath); + return true; + } + return false; + } + + /** + * websocket路径解析类,主要用于处理参数 + */ + @Getter + private static class UriTemplate { + + private final Map paramMap = new ConcurrentHashMap<>(); + private final String path; + + private UriTemplate(String path) throws DeploymentException { + if (StrUtil.isEmpty(path) || !path.startsWith("/") || path.contains("/../") || path.contains("/./") || path.contains("//")) { + throw new DeploymentException(String.format("The path [%s] is not valid.", path)); + } + StringBuilder normalized = new StringBuilder(path.length()); + Set paramNames = new HashSet<>(); + + // Include empty segments. + String[] segments = path.split("/", -1); + int paramCount = 0; + + for (int i = 0; i < segments.length; i++) { + String segment = segments[i]; + if (segment.isEmpty()) { + if (i == 0 || (i == segments.length - 1 && paramCount == 0)) { + // Ignore the first empty segment as the path must always + // start with '/' + // Ending with a '/' is also OK for instances used for + // matches but not for parameterised templates. + continue; + } else { + // As per EG discussion, all other empty segments are + // invalid + throw new DeploymentException(String.format("The path [%s] contains one or more empty segments which is not permitted", path)); + } + } + normalized.append('/'); + if (segment.startsWith("{") && segment.endsWith("}")) { + segment = segment.substring(1, segment.length() - 1); + normalized.append('{'); + normalized.append(paramCount++); + normalized.append('}'); + if (!paramNames.add(segment)) { + throw new DeploymentException(String.format("The parameter [%s] appears more than once in the path which is not permitted", segment)); + } + paramMap.put(segment, paramCount - 1); + } else { + if (segment.contains("{") || segment.contains("}")) { + throw new DeploymentException(String.format("The segment [%s] is not valid in the provided path [%s]", segment, path)); + } + normalized.append(segment); + } + } + this.path = normalized.toString(); + } + + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/BeanUtils.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/BeanUtils.java new file mode 100644 index 0000000..3020c6c --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/BeanUtils.java @@ -0,0 +1,226 @@ +package cd.casic.plugin.utils; + +import lombok.NoArgsConstructor; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.lang.NonNull; + +import java.io.*; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +@NoArgsConstructor +public class BeanUtils { + + public static T getFieldValue(@NonNull Object target, @NonNull String path) { + String[] fieldPath = path.split("\\."); + Object obj = target; + int i = 0; + while (i < fieldPath.length) { + if (obj == null) break; + if ("*".equals(fieldPath[i])) { + // merge map + if (obj instanceof Map) { + obj = ((Map) obj).values(); + } else if (obj instanceof Collection) { + obj = obj; + } else { + // non-support object fields + return null; + } + } else { + if (obj instanceof Collection) { + List values = new ArrayList<>(); + for (Object item : (Collection) obj) { + Object value = getFieldValue(item, item.getClass(), fieldPath[i]); + values.add(value); + } + obj = values; + } else { + obj = getFieldValue(obj, obj.getClass(), fieldPath[i]); + } + } + i++; + } + return (T) obj; + } + + public static Class getFieldClass(@NonNull Object target, @NonNull String fieldName) { + try { + return target.getClass().getDeclaredField(fieldName).getType(); + } catch (Exception e) { + return null; + } + } + + + private static Object getFieldValue(@NonNull Object target, @NonNull Class clazz, @NonNull String fieldName) { + if (Map.class.isAssignableFrom(clazz)) { + return ((Map) target).get(fieldName); + } + + try { + Field field = target instanceof Class + ? ((Class) target).getDeclaredField(fieldName) + : clazz.getDeclaredField(fieldName); + field.setAccessible(true); + return field.get(target); + } catch (NoSuchFieldException nsfe) { + if (clazz.getSuperclass() != null) { + return target instanceof Class + ? getFieldValue(((Class) target).getSuperclass(), clazz, fieldName) + : getFieldValue(target, clazz.getSuperclass(), fieldName); + } else { + return null; + } + } catch (Exception e) { + return null; + } + } + + public static void setFieldValue(@NonNull Object target, @NonNull String fieldName, Object value) { + setFieldValue(target, target.getClass(), fieldName, value); + } + + private static void setFieldValue(@NonNull Object target, @NonNull Class clazz, @NonNull String fieldName, Object value) { + try { + Field field = target instanceof Class + ? ((Class) target).getDeclaredField(fieldName) + : clazz.getDeclaredField(fieldName); + field.setAccessible(true); + field.set(target, value); + } catch (NoSuchFieldException nsfe) { + if (clazz.getSuperclass() != null) { + setFieldValue(target, clazz.getSuperclass(), fieldName, value); + } else { + throw new RuntimeException("Set field " + fieldName + " failed.", nsfe); + } + } catch (Exception e) { + throw new RuntimeException("Set field " + fieldName + " failed.", e); + } + } + + public static T deepClone(@NonNull T o) { + try { + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(byteOut); + out.writeObject(o); + out.flush(); + ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(byteOut.toByteArray())); + return (T) o.getClass().cast(in.readObject()); + } catch (Exception e) { + throw new RuntimeException("Failed to copy Object " + o.getClass().getName(), e); + } + } + + public static Method getDeclaredMethod(@NonNull Class clazz, @NonNull String methodName, Class... parameterTypes) { + Method method = null; + Class clz = clazz; + while (clz != Object.class) { + try { + method = parameterTypes.length > 0 + ? clz.getDeclaredMethod(methodName, parameterTypes) + : clz.getDeclaredMethod(methodName); + break; + } catch (NoSuchMethodException e) { + clz = clz.getSuperclass(); + } + } + if (method != null) method.setAccessible(true); + return method; + } + + public static Method getMethod(@NonNull Class clazz, @NonNull String methodName, Class... parameterTypes) { + Method method; + try { + method = parameterTypes.length > 0 + ? clazz.getMethod(methodName, parameterTypes) + : clazz.getMethod(methodName); + } catch (NoSuchMethodException e) { + return null; + } + if (method != null) method.setAccessible(true); + return method; + } + + public static R callMethod(O object, @NonNull String methodName, Object... parameters) { + Class clazz = (Class) object.getClass(); + return callMethod(clazz, object, methodName, parameters); + } + + /** + * This method doesn't always function as expected. Be 100% sure + * and tested when you use it. + *

+ * As known, this method is not worked for following case: + * * parameter type is primitive number, e.g. int.class + * * parameter type is general type, e.g. Object.class + */ + public static R callMethod(Class clazz, Object object, @NonNull String methodName, Object... parameters) { + if (object == null) return null; + + Method method; + // try get method from `getMethod` + if (parameters.length == 0) { + method = getMethod(clazz, methodName); + } else { + method = getMethod(clazz, methodName, + Arrays.stream(parameters).map(Object::getClass).toArray(Class[]::new)); + } + + // try get method from `getDeclaredMethod` + if (method == null) { + if (parameters.length == 0) { + method = getDeclaredMethod(clazz, methodName); + } else { + method = getDeclaredMethod(clazz, methodName, + Arrays.stream(parameters).map(Object::getClass).toArray(Class[]::new)); + } + } + + if (method == null) return null; + + try { + return (R) method.invoke(object, parameters); + } catch (IllegalAccessException | InvocationTargetException ignore) { + } + return null; + } + + public static String getBeanName(BeanFactory beanFactory, Object bean) { + String beanName = null; + Map beans = BeanUtils.getFieldValue(beanFactory, "disposableBeans"); + if (beans != null) { + beanName = beans.entrySet().stream() + .filter(entry -> entry.getValue() == bean).findAny() + .map(Map.Entry::getKey).orElse(null); + } + if (beanName == null) { + beans = BeanUtils.getFieldValue(beanFactory, "singletonObjects"); + if (beans != null) { + beanName = beans.entrySet().stream() + .filter(entry -> entry.getValue() == bean).findAny() + .map(Map.Entry::getKey).orElse(null); + } + } + return beanName; + } + + public static Map, ?> getTempBeans(List> classList){ + // 创建临时上下文 + AnnotationConfigApplicationContext tempContext = new AnnotationConfigApplicationContext(); + // 注册需要即时使用的类 + classList.forEach(tempContext::register); + // 刷新临时上下文 + tempContext.refresh(); + // 获取bean + Map, ?> tempBean = classList.stream().collect(Collectors.toMap(Function.identity(), tempContext::getBean)); + // 关闭临时上下文 + tempContext.close(); + return tempBean; + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/CommonUtils.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/CommonUtils.java new file mode 100644 index 0000000..00b1df9 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/CommonUtils.java @@ -0,0 +1,152 @@ +package cd.casic.plugin.utils; + +import cd.casic.plugin.PluginProperties; +import cn.hutool.core.util.StrUtil; +import org.pf4j.util.StringUtils; + +import java.io.File; +import java.util.Comparator; +import java.util.List; +import java.util.function.Function; + +/** + * 通用工具 + */ +public class CommonUtils { + + private CommonUtils() { + } + + /** + * list按照int排序. 数字越大, 越排在前面 + * + * @param list list集合 + * @param orderImpl 排序实现 + * @param T + * @return List + */ + public static List order(List list, Function orderImpl) { + if (list == null) { + return null; + } + list.sort(Comparator.comparing(orderImpl, Comparator.nullsLast(Comparator.reverseOrder()))); + return list; + } + + + /** + * 得到插件接口前缀 + * + * @param configuration 配置 + * @param pluginId 插件id + * @return 接口前缀 + */ + public static String getPluginRestPrefix(PluginProperties pluginProperties, String pluginId) { + String pathPrefix = pluginProperties.getRestPathPrefix(); + if (pluginProperties.isEnablePluginIdAsRestPrefix()) { + if (StrUtil.isNotEmpty(pathPrefix)) { + pathPrefix = restJoiningPath(pathPrefix, pluginId); + } else { + pathPrefix = pluginId; + } + return pathPrefix; + } else { + if (StrUtil.isEmpty(pathPrefix)) { + // 不启用插件id作为路径前缀, 并且路径前缀为空, 则直接返回。 + return null; + } + } + return pathPrefix; + } + + + /** + * rest接口拼接路径 + * + * @param path1 路径1 + * @param path2 路径2 + * @return 拼接的路径 + */ + public static String restJoiningPath(String path1, String path2) { + if (path1 != null && path2 != null) { + if (path1.endsWith("/") && path2.startsWith("/")) { + return path1 + path2.substring(1); + } else if (!path1.endsWith("/") && !path2.startsWith("/")) { + return path1 + "/" + path2; + } else { + return path1 + path2; + } + } else if (path1 != null) { + return path1; + } else if (path2 != null) { + return path2; + } else { + return ""; + } + } + + + /** + * 拼接url路径 + * + * @param paths 拼接的路径 + * @return 拼接的路径 + */ + public static String joiningPath(String... paths) { + if (paths == null || paths.length == 0) { + return ""; + } + StringBuilder stringBuilder = new StringBuilder(); + int length = paths.length; + for (int i = 0; i < length; i++) { + String path = paths[i]; + if (StringUtils.isNullOrEmpty(path)) { + continue; + } + if ((i < length - 1) && path.endsWith("/")) { + path = path.substring(path.lastIndexOf("/")); + } + if (path.startsWith("/")) { + stringBuilder.append(path); + } else { + stringBuilder.append("/").append(path); + } + } + + return stringBuilder.toString(); + } + + /** + * 拼接file路径 + * + * @param paths 拼接的路径 + * @return 拼接的路径 + */ + public static String joiningFilePath(String... paths) { + if (paths == null || paths.length == 0) { + return ""; + } + StringBuilder stringBuilder = new StringBuilder(); + int length = paths.length; + for (int i = 0; i < length; i++) { + String path = paths[i]; + if (StringUtils.isNullOrEmpty(path)) { + continue; + } + if (i > 0) { + if (path.startsWith(File.separator) || path.startsWith("/") || + path.startsWith("\\") || path.startsWith("//")) { + stringBuilder.append(path); + } else { + stringBuilder.append(File.separator).append(path); + } + } else { + stringBuilder.append(path); + } + } + + return stringBuilder.toString(); + } + + +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/FrontResourceUtils.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/FrontResourceUtils.java new file mode 100644 index 0000000..3d898fd --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/FrontResourceUtils.java @@ -0,0 +1,47 @@ +package cd.casic.plugin.utils; + +import lombok.extern.slf4j.Slf4j; +import org.pf4j.PluginManager; +import org.pf4j.PluginWrapper; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +@Slf4j +public abstract class FrontResourceUtils { + private static final String FRONT_BUNDLE_LOCATION = "/static/ui"; + public static final String JS_BUNDLE = "/assets/main.js"; + public static final String CSS_BUNDLE = "/assets/style.css"; + + @Nullable + public static Resource getJsBundleResource(PluginManager pluginManager, String pluginName, + String bundleName) { + Assert.hasText(pluginName, "The pluginName must not be blank"); + Assert.hasText(bundleName, "Bundle name must not be blank"); + DefaultResourceLoader resourceLoader = getResourceLoader(pluginManager, pluginName); + if (resourceLoader == null) { + return null; + } + String path = FRONT_BUNDLE_LOCATION + bundleName; + String simplifyPath = StringUtils.cleanPath(path); + Resource resource = resourceLoader.getResource(simplifyPath); + log.info("Get {} for plugin {} in the {} using {}", bundleName, pluginName, simplifyPath, resourceLoader); + return resource.exists() ? resource : null; + } + + @Nullable + public static DefaultResourceLoader getResourceLoader(PluginManager pluginManager, + String pluginName) { + Assert.notNull(pluginManager, "Plugin manager must not be null"); + PluginWrapper plugin = pluginManager.getPlugin(pluginName); + if (plugin == null) { + return null; + } + return new DefaultResourceLoader(plugin.getPluginClassLoader()); + } + + +} + diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/PluginDescriptorUtils.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/PluginDescriptorUtils.java new file mode 100644 index 0000000..8830f92 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/PluginDescriptorUtils.java @@ -0,0 +1,118 @@ +package cd.casic.plugin.utils; + +import cd.casic.plugin.PluginDescriptorStorage; +import cd.casic.plugin.pf4j.OpsPluginDescriptor; +import cn.hutool.core.util.StrUtil; +import org.pf4j.PluginDependency; + +import java.util.*; +import java.util.stream.Collectors; + +public class PluginDescriptorUtils { + public static final String PLUGIN_ID = "plugin.id"; + public static final String PLUGIN_DESCRIPTION = "plugin.description"; + public static final String PLUGIN_CLASS = "plugin.class"; + public static final String PLUGIN_VERSION = "plugin.version"; + public static final String PLUGIN_PROVIDER = "plugin.provider"; + public static final String PLUGIN_DEPENDENCIES = "plugin.dependencies"; + public static final String PLUGIN_REQUIRES = "plugin.requires"; + public static final String PLUGIN_LICENSE = "plugin.license"; + public static final String MYBATIS_MAPPER_LOCATION = "mybatis.mapper.location"; + public static final String STATIC_LOCATIONS = "static.locations"; + public static final String CONFIG_FILE_NAME = "plugin.config.file"; + public static final String CONFIG_FILE_ACTIVE = "plugin.config.active"; + + public static PluginDescriptorStorage propertiesToStorage(Properties properties){ + PluginDescriptorStorage pluginDescriptor = new PluginDescriptorStorage(); + + String id = properties.getProperty(PLUGIN_ID); + pluginDescriptor.setPluginId(id); + + String description = properties.getProperty(PLUGIN_DESCRIPTION); + if (StrUtil.isEmptyIfStr(description)) { + pluginDescriptor.setPluginDescription(""); + } else { + pluginDescriptor.setPluginDescription(description); + } + + String clazz = properties.getProperty(PLUGIN_CLASS); + if (!StrUtil.isEmptyIfStr(clazz)) { + pluginDescriptor.setPluginClass(clazz); + } + + String version = properties.getProperty(PLUGIN_VERSION); + if (!StrUtil.isEmptyIfStr(version)) { + pluginDescriptor.setVersion(version); + } + + String provider = properties.getProperty(PLUGIN_PROVIDER); + pluginDescriptor.setProvider(provider); + + String requires = properties.getProperty(PLUGIN_REQUIRES); + if (StrUtil.isEmptyIfStr(requires)) { + pluginDescriptor.setRequires("0.0.0"); + }else{ + pluginDescriptor.setRequires(requires); + } + + String mapperXmlDir = properties.getProperty(MYBATIS_MAPPER_LOCATION); + pluginDescriptor.setMapperXmlDir(mapperXmlDir); + + String staticDir = properties.getProperty(STATIC_LOCATIONS); + pluginDescriptor.setStaticDir(staticDir); + + String license = properties.getProperty(PLUGIN_LICENSE); + pluginDescriptor.setLicense(license); + + String dependencies = properties.getProperty(PLUGIN_DEPENDENCIES); + if(!StrUtil.isEmptyIfStr(dependencies)){ + List dependencyList = Arrays.stream(dependencies.trim().split(",")) + .map(dependency->new PluginDependency(dependency.trim())) + .collect(Collectors.toList()); + pluginDescriptor.setDependencies(dependencyList); + }else { + pluginDescriptor.setDependencies(Collections.emptyList()); + } + + String configFileName = properties.getProperty(CONFIG_FILE_NAME); + if(!StrUtil.isEmptyIfStr(configFileName)){ + pluginDescriptor.setConfigFileName(configFileName); + } + + List configFileActives = new ArrayList<>(); + for (String key : properties.stringPropertyNames()) { + if (key.startsWith(CONFIG_FILE_ACTIVE)) { + configFileActives.add(properties.getProperty(key)); + } + } + if(!configFileActives.isEmpty()){ + pluginDescriptor.setConfigFileActive(configFileActives); + }else{ + pluginDescriptor.setConfigFileActive(Collections.emptyList()); + } + + return pluginDescriptor; + } + +// public static DefaultPluginDescriptor storageToDescriptor(PluginDescriptorStorage pluginDescriptor){ +// return new DefaultPluginDescriptor(pluginDescriptor.getPluginId(), +// pluginDescriptor.getPluginDescription(), +// pluginDescriptor.getPluginClass(), +// pluginDescriptor.getVersion(), +// pluginDescriptor.getRequires(), +// pluginDescriptor.getProvider(), +// pluginDescriptor.getLicense()); +// } + + public static OpsPluginDescriptor storageToDescriptor(PluginDescriptorStorage pluginDescriptor){ + return new OpsPluginDescriptor(pluginDescriptor.getPluginId(), + pluginDescriptor.getPluginDescription(), + pluginDescriptor.getPluginClass(), + pluginDescriptor.getVersion(), + pluginDescriptor.getRequires(), + pluginDescriptor.getProvider(), + pluginDescriptor.getLicense(), + pluginDescriptor.getConfigFileName(), + pluginDescriptor.getConfigFileActive()); + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/PluginsUtils.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/PluginsUtils.java new file mode 100644 index 0000000..288bda7 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/PluginsUtils.java @@ -0,0 +1,121 @@ +package cd.casic.plugin.utils; + +import cd.casic.plugin.PluginCache; +import cd.casic.plugin.PluginDescriptorCache; +import cd.casic.plugin.PluginDescriptorStorage; +import cd.casic.plugin.PluginInfo; +import cn.hutool.core.util.StrUtil; +import cn.hutool.setting.dialect.Props; +import org.pf4j.PluginState; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * @Author:mianbin + * @Package:cd.casic.plugin.utils + * @Project:ops + * @name:PluginsUtils + * @Date:2024/03/18 10:19 + * @Filename:PluginsUtils + * @description:插件工具类 + */ +public class PluginsUtils { + /** + * param jarFile jarFile + * @description 加载插件配置文件 + * @author dolphin + */ + public static Props getSetting(String pluginId){ + // TODO 这个地方如果按照原来的写法,既要区分file是jar还是目录,又要区分是properties还是yaml,而且还要重复读取 + // TODO 目前暂时是弄了个PluginDescriptorCache,在两个DescriptorFinder中读取配置时就写Cache + // TODO 上面的做法有待完善 +// Enumeration entries = new JarFile(jarFile).entries(); +// while (entries.hasMoreElements()) { +// JarEntry jarEntry = entries.nextElement(); +// String entryName = jarEntry.getName(); +// if (entryName.equals("plugin.properties")) { +// URL url = new URL("jar:file:" + jarFile.getPath() + "!/" + entryName); +// JarURLConnection jarConnection = (JarURLConnection) url.openConnection(); +// InputStream in = jarConnection.getInputStream(); +// String read = IoUtil.read(in, CharsetUtil.UTF_8); +// in.close(); +// JarFile currJarFile = jarConnection.getJarFile(); +// currJarFile.close(); +// File file = new File("resources/temp"); +// File tempFile = new File(file.getAbsolutePath() + "/plugin.properties"); +// FileWriter writer = new FileWriter(tempFile); +// writer.write(read); +// return new Props(tempFile, CharsetUtil.CHARSET_UTF_8); +// }else if(entryName.equals("plugin.yaml")){ +// Path path = FileUtils.getPath(Paths.get(jarFile.getPath()), "plugin.yaml"); +// FileSystemResource resource = new FileSystemResource(path); +// PluginDescriptorStorage pluginDescriptorStorage = new PluginYamlProcessor(resource).loadYaml(); +// Props props = new Props(); +// props.put("mybatis.mapper.location", pluginDescriptorStorage.getMapperXmlDir()); +// props.put("static.locations", pluginDescriptorStorage.getStaticDir()); +// return props; +// +// } +// } + PluginDescriptorStorage descriptor = PluginDescriptorCache.getPlugin(pluginId); + if(descriptor == null){ +// throw new Exception("加载插件:读取配置文件失败"); + }else{ + Props props = new Props(); + if(!StrUtil.isEmptyIfStr(descriptor.getMapperXmlDir())){ + props.put("mybatis.mapper.location", descriptor.getMapperXmlDir()); + } + if(!StrUtil.isEmptyIfStr(descriptor.getStaticDir())){ + props.put("static.locations", descriptor.getStaticDir()); + } + + return props; + } + return null; + } + + /** + * @description 强制删除 + * @author dolphin + */ + public static void forceDelete(File file) { + if (!file.exists()) { + return; + } + boolean result = file.delete(); + if (result) { + return; + } + int tryCount = 0; + while (!result && tryCount++ < 5) { + System.gc(); + try { + Thread.sleep(150); + } catch (InterruptedException e) { + e.printStackTrace(); + } + result = file.delete(); + } + } + + public static List getAllPluginProxyClass(Class clazz) { + List result = new ArrayList<>(); + Map allPlugin = PluginCache.getAllPlugin(); + for (String key : allPlugin.keySet()) { + PluginInfo pluginInfo = allPlugin.get(key); + if (!pluginInfo.getPluginWrapper().getPluginState().equals(PluginState.STARTED)) { + continue; + } + T pluginBean = pluginInfo.getPluginApplicationContext().getBean(clazz); + if (pluginBean != null) { + result.add(pluginBean); + } + Optional.of(pluginBean).ifPresent(result::add); + } + return result; + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/ResourceUtils.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/ResourceUtils.java new file mode 100644 index 0000000..378ce41 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/ResourceUtils.java @@ -0,0 +1,92 @@ +package cd.casic.plugin.utils; + +import cd.casic.plugin.PluginInfo; +import org.pf4j.PluginWrapper; +import org.pf4j.RuntimeMode; +import org.pf4j.util.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 对资源解析的工具类 + */ +public class ResourceUtils { + + private static final Logger LOGGER = LoggerFactory.getLogger(ResourceUtils.class); + + public final static String TYPE_FILE = "file"; + public final static String TYPE_CLASSPATH = "classpath"; + public final static String TYPE_PACKAGE = "package"; + + public static final String ROOT_PLUGIN_SIGN = "~"; + + public final static String TYPE_SPLIT = ":"; + + /** + * 获取匹配路绝 + * @param locationMatch 原始匹配路径。规则为: file:xxx, classpath:xxx , package:xxx + * @return 整合出完整的匹配路绝 + */ + public static String getMatchLocation(String locationMatch){ + if(StringUtils.isNullOrEmpty(locationMatch)){ + return null; + } + String classPathType = TYPE_CLASSPATH + TYPE_SPLIT; + if(isClasspath(locationMatch)){ + return locationMatch.replaceFirst(classPathType, ""); + } + String fileType = TYPE_FILE + TYPE_SPLIT; + if(isFile(locationMatch)){ + return locationMatch.replaceFirst(fileType, ""); + } + String packageType = TYPE_PACKAGE + TYPE_SPLIT; + if(isPackage(locationMatch)){ + String location = locationMatch.replaceFirst(packageType, ""); + return location.replace(".", "/"); + } + LOGGER.error("locationMatch {} illegal", locationMatch); + return null; + } + + public static boolean isClasspath(String locationMatch){ + return locationMatch.startsWith(TYPE_CLASSPATH + TYPE_SPLIT); + } + + public static boolean isFile(String locationMatch){ + return locationMatch.startsWith(TYPE_FILE + TYPE_SPLIT); + } + + public static boolean isPackage(String locationMatch){ + return locationMatch.startsWith(TYPE_PACKAGE + TYPE_SPLIT); + } + + /** + * 根据 ~ 标记获取, 得到绝对路径 + * @param pluginRegistryInfo pluginRegistryInfo + * @param rootDir 根目录 + * @return java.lang.String + **/ + public static String getAbsolutePath(PluginInfo pluginRegistryInfo, String rootDir){ + if(StringUtils.isNullOrEmpty(rootDir)){ + return rootDir; + } + String home = null; + if(rootDir.startsWith(ResourceUtils.ROOT_PLUGIN_SIGN)){ + String pluginRootDir; + PluginWrapper pluginWrapper = pluginRegistryInfo.getPluginWrapper(); + RuntimeMode runtimeMode = pluginWrapper.getRuntimeMode(); + if(runtimeMode == RuntimeMode.DEVELOPMENT){ + pluginRootDir = pluginWrapper.getPluginPath().toString(); + } else { + pluginRootDir = System.getProperty("user.dir"); + } + // 如果root路径中开始存在ROOT_PLUGIN_SIGN,则说明进行插件根路替换 + home = rootDir.replaceFirst("\\" + ResourceUtils.ROOT_PLUGIN_SIGN, ""); + home = CommonUtils.joiningFilePath(pluginRootDir, home); + } else { + home = rootDir; + } + return home; + } + +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/SM4EncryptUtil.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/SM4EncryptUtil.java new file mode 100644 index 0000000..6081ea1 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/SM4EncryptUtil.java @@ -0,0 +1,47 @@ +package cd.casic.plugin.utils; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.crypto.SmUtil; +import cn.hutool.crypto.symmetric.SM4; +import lombok.extern.slf4j.Slf4j; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +@Slf4j +public class SM4EncryptUtil { + private static DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); +// @Value("${encryption-key}") + private static String key = "abcdabcdabcdabcd"; + + public static String encrypt(String str) { + SM4 sm4 = SmUtil.sm4(key.getBytes()); + String s = sm4.encryptHex(str.getBytes()); + return s; + } + public static String decrypt(String str) { + SM4 sm4 = SmUtil.sm4(key.getBytes()); + String s = sm4.decryptStr(str); + return s; + } + + public static void main(String[] args) { + + String encrypt = encrypt(LocalDateTime.now().plusMonths(1).format(dateTimeFormatter)); + System.out.println(encrypt); + System.out.println(decrypt(encrypt)); + } + public static boolean checkLicense(String license, String pluginId) { + // 逻辑是 不存在证书直接放行 存在证书需要判断证书是否过期 + if (ObjectUtil.isNotEmpty(license)) { + String decrypt = SM4EncryptUtil.decrypt(license); + LocalDateTime overDueTime = LocalDateTime.parse(decrypt, dateTimeFormatter); + LocalDateTime now = LocalDateTime.now(); + if (now.isAfter(overDueTime)) { + log.warn("plugin:[{}] license expired!", pluginId); + return false; + } + } + return true; + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/YamlUtils.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/YamlUtils.java new file mode 100644 index 0000000..624c2c8 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/YamlUtils.java @@ -0,0 +1,35 @@ +package cd.casic.plugin.utils; + +import org.pf4j.DevelopmentPluginClasspath; +import org.pf4j.PluginRuntimeException; +import org.pf4j.util.FileUtils; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class YamlUtils { + static final DevelopmentPluginClasspath PLUGIN_CLASSPATH = new DevelopmentPluginClasspath(); + + public static Path getYamlPath(Path pluginPath, String yamlFileName) { + if (Files.isDirectory(pluginPath)) { + for (String location : PLUGIN_CLASSPATH.getClassesDirectories()) { + Path path = pluginPath.resolve(location).resolve(yamlFileName); + Resource propertyResource = new FileSystemResource(path); + if (propertyResource.exists()) { + return path; + } + } + throw new PluginRuntimeException( + "Unable to find plugin yaml file: " + yamlFileName); + } else { + try { + return FileUtils.getPath(pluginPath, yamlFileName); + } catch (IOException e) { + throw new PluginRuntimeException(e); + } + } + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/dBUtils/AnnotationScanner.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/dBUtils/AnnotationScanner.java new file mode 100644 index 0000000..7e00308 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/dBUtils/AnnotationScanner.java @@ -0,0 +1,77 @@ +package cd.casic.plugin.utils.dBUtils; + +import java.io.File; +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.net.JarURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +public class AnnotationScanner { + + public static List> scanPackageForAnnotatedClasses(String packageName, Class annotationClass,ClassLoader classLoader) throws IOException, ClassNotFoundException { + List> annotatedClasses = new ArrayList<>(); + + // 获取包所在的URL + Enumeration urls = classLoader.getResources(packageName.replace('.', '/')); +// + while (urls.hasMoreElements()) { + URL url = urls.nextElement(); + String protocol = url.getProtocol(); + + if ("file".equals(protocol)) { + // 处理文件系统中的包 + File packageDir = new File(url.getFile()); + scanDirectoryForAnnotatedClasses(packageDir, packageName, annotationClass, annotatedClasses); + } else if ("jar".equals(protocol)) { + // 处理JAR文件中的包 + JarFile jarFile = ((JarURLConnection) url.openConnection()).getJarFile(); + scanJarFileForAnnotatedClasses(jarFile, packageName, annotationClass, annotatedClasses, classLoader); + } + } + + return annotatedClasses; + } + + private static void scanDirectoryForAnnotatedClasses(File directory, String packageName, Class annotationClass, List> annotatedClasses) throws ClassNotFoundException { + File[] files = directory.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + scanDirectoryForAnnotatedClasses(file, packageName + "." + file.getName(), annotationClass, annotatedClasses); + } else if (file.isFile() && file.getName().endsWith(".class")) { + String className = packageName + '.' + file.getName().substring(0, file.getName().length() - 6); + Class clazz = Class.forName(className); + if (clazz.isAnnotationPresent(annotationClass)) { + annotatedClasses.add(clazz); + } + } + } + } + } + + private static void scanJarFileForAnnotatedClasses(JarFile jarFile, String packageName, Class annotationClass, List> annotatedClasses,ClassLoader classLoader) { + Enumeration entries = jarFile.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String entryName = entry.getName(); + if (entryName.endsWith(".class") && entryName.startsWith(packageName.replace('.', '/')) && !entryName.contains("$")) { // 忽略内部类 + String className = entryName.substring(0, entryName.length() - 6).replace('/', '.'); + try { +// Class clazz = Class.forName(className); + Class clazz = classLoader.loadClass(className); + if (clazz.isAnnotationPresent(annotationClass)) { + annotatedClasses.add(clazz); + } + } catch (Exception e) { + // ignore + System.err.println("Class not found: " + className);; + } + } + } + } +} \ No newline at end of file diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/dBUtils/DBEnums.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/dBUtils/DBEnums.java new file mode 100644 index 0000000..202d67d --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/dBUtils/DBEnums.java @@ -0,0 +1,26 @@ +package cd.casic.plugin.utils.dBUtils; + +import lombok.Getter; + +@Getter +public enum DBEnums { + MYSQL("mysql"), + SQLITE("sqlite"); + + final String DBType; + + DBEnums(String DBType) { + this.DBType = DBType; + } + + public static DBEnums getEnumByString(String type){ + switch (type){ + case "mysql": + return MYSQL; + case "sqlite": + return SQLITE; + default: + return null; + } + } +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/dBUtils/SQLGenerator.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/dBUtils/SQLGenerator.java new file mode 100644 index 0000000..7f57505 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/dBUtils/SQLGenerator.java @@ -0,0 +1,222 @@ +package cd.casic.plugin.utils.dBUtils; + +import cd.casic.plugin.register.database.DataBaseProperty; +import cd.casic.plugin.utils.dBUtils.annotation.Entity; +import cd.casic.plugin.utils.dBUtils.annotation.SQL; +import com.baomidou.dynamic.datasource.DynamicRoutingDataSource; +import com.mysql.cj.jdbc.MysqlDataSource; +import jakarta.annotation.Resource; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.stereotype.Component; +import org.sqlite.SQLiteDataSource; + +import javax.sql.DataSource; +import java.io.IOException; +import java.lang.reflect.Field; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.sql.Connection; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +@Data +@Slf4j +@Component +public class SQLGenerator { + @Resource + private DynamicRoutingDataSource dynamicRoutingDataSource; + + private String mapToSqlType(Class fieldType) { + // 简化的类型映射,针对MySQL数据库 + if (fieldType == int.class || fieldType == Integer.class) { + return "INTEGER"; + } else if (fieldType == long.class || fieldType == Long.class) { + return "BIGINT"; + } else if (fieldType == float.class || fieldType == Float.class) { + return "FLOAT"; + } else if (fieldType == double.class || fieldType == Double.class) { + return "DOUBLE"; + } else if (fieldType == boolean.class || fieldType == Boolean.class) { + return "BOOLEAN"; // 或者 "TINYINT(1)" + } else if (fieldType == String.class) { + return "VARCHAR(255)"; + } else if (fieldType == java.util.Date.class || fieldType == java.sql.Date.class) { + return "DATETIME"; // 或者 "TIMESTAMP" + } else if (fieldType == java.sql.Timestamp.class) { + return "TIMESTAMP"; + } else if (fieldType == byte[].class) { + return "BLOB"; + } else if (fieldType.isEnum()) { + return "ENUM"; // 需要进一步处理,如列出所有枚举值 + } else if (fieldType.isArray() || Collection.class.isAssignableFrom(fieldType)) { + throw new IllegalArgumentException("Arrays and Collections are not directly supported. Consider using a join table or serialization."); + } else { + throw new IllegalArgumentException("Unsupported field type: " + fieldType.getName()); + } + } + + public void sqlDeleteGenerator(Class c, DataBaseProperty dataBaseProperty, String pluginId) { + StringBuilder sql = new StringBuilder("\n"); + String tableName = "plugin_" + (c.toString().substring(c.toString().lastIndexOf(".") + 1)).toLowerCase(); + + sql.append("drop table if exists ").append(tableName); + String dbPath = "ops-module-plugins/plugins/" + pluginId + "/datasource/database.db"; + + this.executeSql(sql.toString(), dataBaseProperty, pluginId); + log.info(sql.toString()); + } + + public void sqlGenerator(Class c, DataBaseProperty dataBaseProperty, String pluginId) { + + String dbPath = "ops-module-plugins/plugins/" + pluginId + "/datasource/database.db"; + +// log.info(pluginRoot); + Entity entityAnnotation = c.getAnnotation(Entity.class); + if (ObjectUtils.isEmpty(entityAnnotation)) { + return; + } + + StringBuilder sql = new StringBuilder("\n"); + String tableName = "plugin_" + (c.toString().substring(c.toString().lastIndexOf(".") + 1)).toLowerCase(); + + sql.append("create table ").append(tableName).append("(").append("\n"); + int count = c.getDeclaredFields().length; + int index = 0; + boolean pkFlag = false; + for (Field field : c.getDeclaredFields()) { + SQL annotation = field.getAnnotation(SQL.class); + index += 1; + sql.append(field.getName()).append(" ").append(mapToSqlType(field.getType())).append(" "); + if (ObjectUtils.isNotEmpty(annotation) && ObjectUtils.isNotEmpty(annotation.defaultValue())) { + sql.append(" default " + "'").append(annotation.defaultValue()).append("'"); + } + if (ObjectUtils.isNotEmpty(annotation) && annotation.notNull()) { + sql.append(" not null "); + } + if (ObjectUtils.isNotEmpty(annotation) && annotation.pK()) { + // TODO 这是如果dataBaseProperty是null(即使用主程序的数据源时),数据库类型先写死。 + DBEnums type = dataBaseProperty == null ? DBEnums.MYSQL : dataBaseProperty.type(); + if (pkFlag) { + log.error("Duplicate Primary Key"); + throw new RuntimeException("Duplicate Primary Key"); + } + this.genPrimaryKey(sql, type, tableName); + pkFlag = true; + if (annotation.autoIncrement()) { + this.genAutoIncrement(sql, type); + } + } + if (index != count) { + sql.append("," + "\n"); + } else { + sql.append("\n"); + } + } + sql.append(")"); + log.info(sql.toString()); + this.executeSql(sql.toString(), dataBaseProperty, pluginId); + } + + public void destroyDataSource(DataBaseProperty dataBaseProperty, String pluginId){ + if(dataBaseProperty == null) return; + String dataSourceId = pluginId + "-" + dataBaseProperty.type().getDBType(); + if(dynamicRoutingDataSource.getDataSources().containsKey(dataSourceId)){ + dynamicRoutingDataSource.removeDataSource(dataSourceId); + } + } + + private void executeSql(String sql, DataBaseProperty dataBaseProperty, String pluginId){ + try{ + Connection connection; + if(Objects.isNull(dataBaseProperty)){ + connection = dynamicRoutingDataSource.getConnection(); + }else{ + String dataSourceId = pluginId + "-" + dataBaseProperty.type().getDBType(); + if(!dynamicRoutingDataSource.getDataSources().containsKey(dataSourceId)){ + DataSource dataSource; + if(dataBaseProperty.type().equals(DBEnums.SQLITE)){ + dataSource = DataSourceBuilder.create().type(SQLiteDataSource.class) + .driverClassName(dataBaseProperty.driver()) + .url(dataBaseProperty.url()).build(); + }else { + dataSource = DataSourceBuilder.create() + .type(MysqlDataSource.class) + .driverClassName(dataBaseProperty.driver()) + .username(dataBaseProperty.username()).password(dataBaseProperty.password()) + .url(dataBaseProperty.url()).build(); + } + dynamicRoutingDataSource.addDataSource(dataSourceId, dataSource); + } + connection = dynamicRoutingDataSource.getDataSource(dataSourceId).getConnection(); + } + connection.createStatement().execute(sql); + }catch(Exception e){ + log.error(e.getMessage()); + } + } + + private void genPrimaryKey(StringBuilder sql, DBEnums dbEnums, String tableName) { + switch (dbEnums) { + case MYSQL: + sql.append("primary key "); + break; + case SQLITE: + sql.append("constraint ").append(tableName).append("_pk primary key "); + break; + default: + log.error("Unsupported Database"); + throw new RuntimeException("Unsupported Database"); + } + } + + private void genAutoIncrement(StringBuilder sql, DBEnums dbEnums) { + switch (dbEnums) { + case MYSQL: + sql.append(" AUTO_INCREMENT"); + break; + case SQLITE: + sql.append(" AUTOINCREMENT"); + break; + default: + log.error("Unsupported Database"); + throw new RuntimeException("Unsupported Database"); + } + } + + private String getSQLiteDBLocation(String pluginRoot, ClassLoader classLoader, String pluginId) { + List pluginFolders = new ArrayList<>(); + try { + Path rootPath = Paths.get(pluginRoot); + if (!Files.isDirectory(rootPath)) { + System.out.println("提供的路径不是一个有效的目录:" + pluginRoot); + } + this.findPluginFoldersRecursively(rootPath, pluginFolders); + } catch (IOException e) { + throw new RuntimeException(e); + } + return "s"; + } + + private void findPluginFoldersRecursively(Path folder,List pluginFolders) throws IOException + { + try (DirectoryStream stream = Files.newDirectoryStream(folder)) { + for (Path entry : stream) { + if (Files.isDirectory(entry)) { + String folderName = entry.getFileName().toString(); + if (folderName.contains("datasource")) { + pluginFolders.add(entry.toString()); + } + findPluginFoldersRecursively(entry, pluginFolders); // 递归查找子目录 + } + } + } + } +} + diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/dBUtils/annotation/Entity.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/dBUtils/annotation/Entity.java new file mode 100644 index 0000000..d2e0e95 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/dBUtils/annotation/Entity.java @@ -0,0 +1,10 @@ +package cd.casic.plugin.utils.dBUtils.annotation; + +import java.lang.annotation.*; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Entity { + +} diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/dBUtils/annotation/SQL.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/dBUtils/annotation/SQL.java new file mode 100644 index 0000000..bd4c0c5 --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/utils/dBUtils/annotation/SQL.java @@ -0,0 +1,14 @@ +package cd.casic.plugin.utils.dBUtils.annotation; + +import java.lang.annotation.*; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface SQL { + boolean pK() default false; + boolean notNull() default false; + String defaultValue() default ""; + boolean autoIncrement() default false; +} + diff --git a/framework/spring-boot-starter-plugin/src/main/resources/application.properties b/framework/spring-boot-starter-plugin/src/main/resources/application.properties new file mode 100644 index 0000000..3041f6a --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.application.name=spring-boot-starter-plugin diff --git a/framework/spring-boot-starter-plugin/src/test/java/cd/casic/plugin/SpringBootStarterPluginApplicationTests.java b/framework/spring-boot-starter-plugin/src/test/java/cd/casic/plugin/SpringBootStarterPluginApplicationTests.java new file mode 100644 index 0000000..4220aec --- /dev/null +++ b/framework/spring-boot-starter-plugin/src/test/java/cd/casic/plugin/SpringBootStarterPluginApplicationTests.java @@ -0,0 +1,13 @@ +package cd.casic.plugin; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class SpringBootStarterPluginApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/system-framework/pom.xml b/system-framework/pom.xml index d3d1b25..ca2d3eb 100644 --- a/system-framework/pom.xml +++ b/system-framework/pom.xml @@ -22,4 +22,8 @@ 3. 业务相关 + + system-plugin-example-web + + diff --git a/system-framework/system-plugin-example-web/pom.xml b/system-framework/system-plugin-example-web/pom.xml new file mode 100644 index 0000000..f6c277d --- /dev/null +++ b/system-framework/system-plugin-example-web/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + cd.casic.boot + system-framework + ${revision} + + + system-plugin-example-web + ${project.artifactId} + + system-plugin-example-web + + + 17 + + + + + cd.casic.boot + spring-boot-starter-plugin + ${revision} + compile + + + + + diff --git a/system-framework/system-plugin-example-web/src/main/java/cd/casic/server/system/plugin/example/web/ExampleController.java b/system-framework/system-plugin-example-web/src/main/java/cd/casic/server/system/plugin/example/web/ExampleController.java new file mode 100644 index 0000000..2f9883c --- /dev/null +++ b/system-framework/system-plugin-example-web/src/main/java/cd/casic/server/system/plugin/example/web/ExampleController.java @@ -0,0 +1,22 @@ +package cd.casic.server.system.plugin.example.web; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @Author:mianbin + * @Package:cd.casic.opsmodulepluginsexampleweb + * @Project:ops + * @name:ExampleController + * @Date:2024/03/19 16:04 + * @Filename:ExampleController + * @description:Todo + */ +@RestController +public class ExampleController { + + @GetMapping("/ok") + public String init() { + return "are u ok!!!"; + } +} diff --git a/system-framework/system-plugin-example-web/src/main/java/cd/casic/server/system/plugin/example/web/FrontResourceController.java b/system-framework/system-plugin-example-web/src/main/java/cd/casic/server/system/plugin/example/web/FrontResourceController.java new file mode 100644 index 0000000..3046733 --- /dev/null +++ b/system-framework/system-plugin-example-web/src/main/java/cd/casic/server/system/plugin/example/web/FrontResourceController.java @@ -0,0 +1,97 @@ +package cd.casic.server.system.plugin.example.web; + +import cd.casic.plugin.BufferedPluginBundleResource; +import cd.casic.plugin.FrontResourceProcessor; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.InputStreamResource; +import org.springframework.http.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.net.URI; +import java.time.Instant; +import java.util.concurrent.TimeUnit; + +@RestController +public class FrontResourceController { + + @Resource + BufferedPluginBundleResource bufferedPluginBundleResource; + + @Resource + FrontResourceProcessor frontResourceProcessor; + + private static final CacheControl MAX_CACHE_CONTROL = CacheControl.maxAge(365, TimeUnit.DAYS); + + @GetMapping("/plugins/-/bundle.js") + public ResponseEntity fetchJsBundle(HttpServletRequest request, + @RequestParam(required = false) String v) throws IOException { + if (v != null) { + FileSystemResource fsRes = bufferedPluginBundleResource.getJsBundle(v, frontResourceProcessor.uglifyJsBundle()); + + Instant lastModified = Instant.ofEpochMilli(fsRes.lastModified()); + if (isModified(request, lastModified)) { + HttpHeaders headers = new HttpHeaders(); + headers.setCacheControl(MAX_CACHE_CONTROL); + headers.setContentType(MediaType.parseMediaType("application/x-javascript")); + headers.setLastModified(lastModified.toEpochMilli()); + + return ResponseEntity.ok() + .headers(headers) + .body(new InputStreamResource(fsRes.getInputStream())); + } else { + return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build(); + } + } else { + String newVersion = frontResourceProcessor.generateJsBundleVersion(); + String redirectUrl = buildJsBundleUri("js", newVersion).toString(); + + return ResponseEntity.status(HttpStatus.TEMPORARY_REDIRECT) + .header(HttpHeaders.LOCATION, redirectUrl) + .build(); + } + } + + @GetMapping("/plugins/-/bundle.css") + public ResponseEntity fetchCssBundle(HttpServletRequest request, + @RequestParam(required = false) String v) throws IOException { + if (v != null) { + FileSystemResource fsRes = bufferedPluginBundleResource.getCssBundle(v, frontResourceProcessor.uglifyCssBundle()); + + Instant lastModified = Instant.ofEpochMilli(fsRes.lastModified()); + if (isModified(request, lastModified)) { + HttpHeaders headers = new HttpHeaders(); + headers.setCacheControl(MAX_CACHE_CONTROL); + headers.setContentType(MediaType.parseMediaType("text/css")); + headers.setLastModified(lastModified.toEpochMilli()); + + return ResponseEntity.ok() + .headers(headers) + .body(new InputStreamResource(fsRes.getInputStream())); + } else { + return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build(); + } + } else { + String newVersion = frontResourceProcessor.generateJsBundleVersion(); + String redirectUrl = buildJsBundleUri("css", newVersion).toString(); + + return ResponseEntity.status(HttpStatus.TEMPORARY_REDIRECT) + .header(HttpHeaders.LOCATION, redirectUrl) + .build(); + } + } + private boolean isModified(HttpServletRequest request, Instant lastModified) { + long ifModifiedSince = request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE); + return ifModifiedSince <= 0 || !lastModified.isBefore(Instant.ofEpochMilli(ifModifiedSince)); + } + + private URI buildJsBundleUri(String type, String version) { + return URI.create( + "/plugins/-/bundle." + type + "?v=" + version); + } + +} diff --git a/system-framework/system-plugin-example-web/src/main/java/cd/casic/server/system/plugin/example/web/OperationButtonExtension.java b/system-framework/system-plugin-example-web/src/main/java/cd/casic/server/system/plugin/example/web/OperationButtonExtension.java new file mode 100644 index 0000000..2cb55c2 --- /dev/null +++ b/system-framework/system-plugin-example-web/src/main/java/cd/casic/server/system/plugin/example/web/OperationButtonExtension.java @@ -0,0 +1,22 @@ +package cd.casic.server.system.plugin.example.web; + +import org.pf4j.ExtensionPoint; + +public interface OperationButtonExtension extends ExtensionPoint { + + /** + * 按钮名称 + * + * @return 按钮名称 + */ + String name(); + + /** + * 响应点击事件 + * + * @return 结果 + */ + String onClick(); + + +} diff --git a/system-framework/system-plugin-example-web/src/main/java/cd/casic/server/system/plugin/example/web/OpsModulePluginsExampleWebApplication.java b/system-framework/system-plugin-example-web/src/main/java/cd/casic/server/system/plugin/example/web/OpsModulePluginsExampleWebApplication.java new file mode 100644 index 0000000..8911ef7 --- /dev/null +++ b/system-framework/system-plugin-example-web/src/main/java/cd/casic/server/system/plugin/example/web/OpsModulePluginsExampleWebApplication.java @@ -0,0 +1,13 @@ +package cd.casic.server.system.plugin.example.web; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication(scanBasePackages = {"cd.casic.server.system"}) +public class OpsModulePluginsExampleWebApplication { + + public static void main(String[] args) { + SpringApplication.run(OpsModulePluginsExampleWebApplication.class, args); + } + +} diff --git a/system-framework/system-plugin-example-web/src/main/java/cd/casic/server/system/plugin/example/web/PluginManagerController.java b/system-framework/system-plugin-example-web/src/main/java/cd/casic/server/system/plugin/example/web/PluginManagerController.java new file mode 100644 index 0000000..8d1c3fb --- /dev/null +++ b/system-framework/system-plugin-example-web/src/main/java/cd/casic/server/system/plugin/example/web/PluginManagerController.java @@ -0,0 +1,168 @@ +package cd.casic.server.system.plugin.example.web; + +import cd.casic.plugin.PluginDescriptorStorage; +import cd.casic.plugin.PluginManager; +import cd.casic.plugin.PluginProperties; +import cd.casic.plugin.utils.SM4EncryptUtil; +import cd.casic.server.system.plugin.example.web.service.impl.PluginStorageServiceImpl; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.pf4j.PluginState; +import org.pf4j.PluginWrapper; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * @Author:mianbin + * @Package:cd.casic.opsmodulepluginsexampleweb.controller + * @Project:ops + * @name:PluginManagerControoler + * @Date:2024/03/20 15:05 + * @Filename:PluginManagerControoler + * @description:Todo + */ +@RestController +@RequestMapping("/plugin") +@Slf4j +public class PluginManagerController { + @Resource + private PluginProperties pluginProperties; + @Resource + private PluginStorageServiceImpl pluginStorageService; + + @Resource + private PluginManager pluginManager; + + @PostMapping("upload") + public boolean uploadPlugin(@RequestParam("file") MultipartFile file) throws IOException { + InputStream is = file.getInputStream(); + Files.copy(is, Paths.get(pluginProperties.getPluginsRoot()).resolve(Objects.requireNonNull(file.getOriginalFilename()))); + is.close(); + return true; + } + + @GetMapping("reload") + public void reloadPlugins() { + pluginManager.unloadPlugins(); + pluginManager.loadPlugins(); + } + + @GetMapping("load") + public String loadPlugin(@RequestParam() String pluginId) { + PluginWrapper plugin = pluginManager.getPlugin(pluginId); + if (plugin != null) { + return pluginManager.loadPlugin(plugin.getPluginPath()); + } else { + return "Plugin not found"; + } + } + + @GetMapping("unload") + public boolean unloadPlugin(@RequestParam() String pluginId) { + return pluginManager.unloadPlugin(pluginId); + } + + @GetMapping("start") + public PluginState startPlugin(@RequestParam() String pluginId) { + return pluginManager.startPlugin(pluginId); + } + + @GetMapping("stop") + public PluginState stopPlugin(@RequestParam() String pluginId) { + return pluginManager.stopPlugin(pluginId); + } + + @GetMapping("disable") + public boolean disablePlugin(@RequestParam() String pluginId) { + this.setPluginDisable(pluginId); + return pluginManager.disablePlugin(pluginId); + } + + @GetMapping("enable") + public boolean enablePlugin(@RequestParam() String pluginId) { + this.setPluginEnable(pluginId); + return pluginManager.enablePlugin(pluginId); + } + + @GetMapping("delete") + public boolean deletePlugin(@RequestParam() String pluginId) { + return pluginManager.deletePlugin(pluginId); + } + + @GetMapping("lists") + public List listPlugin() { + return getPlugins(); + } + + + @GetMapping("list") + public JSONArray extensions(@RequestParam String extensionClass) throws ClassNotFoundException { + List extensions = pluginManager.getExtensions(Class.forName(extensionClass)); + JSONArray result = new JSONArray(); + for (Object extension : extensions) { + if (extension instanceof OperationButtonExtension) { + PluginWrapper plugin = pluginManager.whichPlugin(extension.getClass()); + result.add(new JSONObject().fluentPut("class", extension.getClass().getName()) + .fluentPut("name", ((OperationButtonExtension) extension).name()).fluentPut("plugin", new JSONObject() + .fluentPut("id", plugin.getPluginId()) + .fluentPut("version", plugin.getDescriptor().getVersion()) + .fluentPut("path", plugin.getPluginPath().toString()) + .fluentPut("state", plugin.getPluginState()) + .fluentPut("class", plugin.getDescriptor().getPluginClass()))); + } + } + return result; + } + + @GetMapping("click") + public String click(@RequestParam String extensionClass, @RequestParam String name) throws ClassNotFoundException { + List extensions = pluginManager.getExtensions(Class.forName(extensionClass)); + for (Object extension : extensions) { + if (extension instanceof OperationButtonExtension && ((OperationButtonExtension) extension).name().equals(name)) { + return ((OperationButtonExtension) extension).onClick(); + } + } + return "Extension not found or unavailable"; + } + + private List getPlugins() { + ArrayList list = new ArrayList<>(); + List pluginDescriptors = pluginStorageService.list(); + for (PluginDescriptorStorage pluginDescriptor : pluginDescriptors){ + // 校验license + if (SM4EncryptUtil.checkLicense(pluginDescriptor.getLicense(), pluginDescriptor.getPluginId())){ + list.add(new JSONObject() + .fluentPut("id", pluginDescriptor.getPluginId()) + .fluentPut("version", pluginDescriptor.getVersion()) + .fluentPut("path", pluginDescriptor.getPluginDirPath()) + .fluentPut("state", pluginManager.getPlugin(pluginDescriptor.getPluginId()).getPluginState()) + .fluentPut("class", pluginDescriptor.getPluginClass()) + ); + } + } + return list; + } + private void setPluginEnable(String pluginId) { + PluginWrapper plugin = pluginManager.getPlugin(pluginId); + PluginDescriptorStorage descriptorStorage = pluginStorageService.convert(plugin); + descriptorStorage.setEnable(1); // 1 是启用 + pluginStorageService.updateById(descriptorStorage); + } + + private void setPluginDisable(String pluginId) { + PluginWrapper plugin = pluginManager.getPlugin(pluginId); + PluginDescriptorStorage descriptorStorage = pluginStorageService.convert(plugin); + descriptorStorage.setEnable(0); // 0 是禁用 + pluginStorageService.updateById(descriptorStorage); + } +} diff --git a/system-framework/system-plugin-example-web/src/main/java/cd/casic/server/system/plugin/example/web/config/WebSocketConfig.java b/system-framework/system-plugin-example-web/src/main/java/cd/casic/server/system/plugin/example/web/config/WebSocketConfig.java new file mode 100644 index 0000000..4051e12 --- /dev/null +++ b/system-framework/system-plugin-example-web/src/main/java/cd/casic/server/system/plugin/example/web/config/WebSocketConfig.java @@ -0,0 +1,15 @@ +package cd.casic.server.system.plugin.example.web.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; + +@Configuration +public class WebSocketConfig { + + @Bean + public ServerEndpointExporter serverEndpointExporter() { + return new ServerEndpointExporter(); + } + +} \ No newline at end of file diff --git a/system-framework/system-plugin-example-web/src/main/java/cd/casic/server/system/plugin/example/web/mapper/PluginStorageMapper.java b/system-framework/system-plugin-example-web/src/main/java/cd/casic/server/system/plugin/example/web/mapper/PluginStorageMapper.java new file mode 100644 index 0000000..1143117 --- /dev/null +++ b/system-framework/system-plugin-example-web/src/main/java/cd/casic/server/system/plugin/example/web/mapper/PluginStorageMapper.java @@ -0,0 +1,9 @@ +package cd.casic.server.system.plugin.example.web.mapper; + +import cd.casic.plugin.PluginDescriptorStorage; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface PluginStorageMapper extends BaseMapper{ +} \ No newline at end of file diff --git a/system-framework/system-plugin-example-web/src/main/java/cd/casic/server/system/plugin/example/web/service/PluginStorageService.java b/system-framework/system-plugin-example-web/src/main/java/cd/casic/server/system/plugin/example/web/service/PluginStorageService.java new file mode 100644 index 0000000..4914a2d --- /dev/null +++ b/system-framework/system-plugin-example-web/src/main/java/cd/casic/server/system/plugin/example/web/service/PluginStorageService.java @@ -0,0 +1,7 @@ +package cd.casic.server.system.plugin.example.web.service; + +import cd.casic.plugin.PluginDescriptorStorage; +import com.baomidou.mybatisplus.extension.service.IService; + +public interface PluginStorageService extends IService { +} diff --git a/system-framework/system-plugin-example-web/src/main/java/cd/casic/server/system/plugin/example/web/service/impl/PluginStorageServiceImpl.java b/system-framework/system-plugin-example-web/src/main/java/cd/casic/server/system/plugin/example/web/service/impl/PluginStorageServiceImpl.java new file mode 100644 index 0000000..ac3b079 --- /dev/null +++ b/system-framework/system-plugin-example-web/src/main/java/cd/casic/server/system/plugin/example/web/service/impl/PluginStorageServiceImpl.java @@ -0,0 +1,126 @@ +package cd.casic.server.system.plugin.example.web.service.impl; + +import cd.casic.plugin.PluginDescriptorStorage; +import cd.casic.plugin.PluginManager; +import cd.casic.plugin.PluginProperties; +import cd.casic.plugin.event.PluginDirCheckEvent; +import cd.casic.plugin.event.PluginLoadedEvent; +import cd.casic.plugin.event.PluginUnloadedEvent; +import cd.casic.plugin.utils.SM4EncryptUtil; +import cd.casic.server.system.plugin.example.web.mapper.PluginStorageMapper; +import cd.casic.server.system.plugin.example.web.service.PluginStorageService; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.pf4j.PluginDescriptor; +import org.pf4j.PluginState; +import org.pf4j.PluginWrapper; +import org.springframework.boot.context.event.ApplicationStartedEvent; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; + +@Service +@Slf4j +public class PluginStorageServiceImpl extends ServiceImpl implements PluginStorageService { + @Resource + private PluginManager pluginManager; + @Resource + private PluginProperties pluginProperties; + + private Boolean Cleaned = false; + + @EventListener + public void initializePlugins(ApplicationStartedEvent event) { + pluginManager.unloadPlugins(); + Path pluginRoot = Paths.get(pluginProperties.getPluginsRoot()); + List list = this.list(); + if (ObjectUtil.isNotEmpty(list)) { + if (!list.get(0).getPluginDirPath().equals(pluginProperties.getPluginsRoot())) { + this.removeBatchByIds(list); + } + list.stream() + .filter(pluginDescriptorStorage -> Paths.get(pluginDescriptorStorage.getPath()).startsWith(pluginRoot) + && SM4EncryptUtil.checkLicense(pluginDescriptorStorage.getLicense(), pluginDescriptorStorage.getPluginId())) + .forEach(pluginDescriptorStorage -> { + Path pluginPath = Paths.get(pluginDescriptorStorage.getPath()); + String pluginId = pluginManager.loadPlugin(pluginPath); + if (PluginDescriptorStorage.EnableStatus.ENABLE.getCode().equals(pluginDescriptorStorage.getEnable())) { + pluginManager.startPlugin(pluginId); + } + }); +// } + } + + } + + @EventListener + public void pluginDirCheck(PluginDirCheckEvent event) throws IOException { +// // 跟之前工作目录路径不同就清空数据库 +// String lastDirTxtPath = pluginProperties.getLastDirTxtPath(); +// String pluginsRoot = pluginProperties.getPluginsRoot(); +// String line = null; +// +// FileReader fileReader = new FileReader(lastDirTxtPath); +// BufferedReader bufferedReader = new BufferedReader(fileReader); +// line = bufferedReader.readLine(); +// bufferedReader.close(); +// fileReader.close(); +// +// if (!line.equals(pluginsRoot)){ +// if (!Cleaned){ +// this.remove(new LambdaQueryWrapper()); +// Cleaned = true; +// FileWriter fileWriter = new FileWriter(lastDirTxtPath); +// BufferedWriter bufferedWriter = new BufferedWriter(fileWriter); +// bufferedWriter.write(pluginsRoot); +// bufferedWriter.flush(); +// bufferedWriter.close(); +// fileWriter.close(); +// } +// } + + } + + @EventListener + public void insertPlugin(PluginLoadedEvent event) { + PluginWrapper pluginWrapper = event.getPluginInfo().getPluginWrapper(); + PluginDescriptorStorage pluginDescriptorStorage = convert(pluginWrapper); + if (this.getById(pluginDescriptorStorage.getPluginId()) != null + || !SM4EncryptUtil.checkLicense(pluginDescriptorStorage.getLicense() + , pluginDescriptorStorage.getPluginId())) return; + this.save(pluginDescriptorStorage); + } + + @EventListener + public void deletePlugin(PluginUnloadedEvent event) { + PluginWrapper pluginWrapper = event.getPluginInfo().getPluginWrapper(); + this.remove(new LambdaQueryWrapper() + .eq(PluginDescriptorStorage::getPluginId, pluginWrapper.getPluginId())); + } + + public PluginDescriptorStorage convert(PluginWrapper pluginWrapper) { + PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor(); + return PluginDescriptorStorage.builder() + .pluginId(pluginDescriptor.getPluginId()) + .pluginClass(pluginDescriptor.getPluginClass()) + .pluginDescription(pluginDescriptor.getPluginDescription()) + .license(pluginDescriptor.getLicense()) + .provider(pluginDescriptor.getProvider()) + .version(pluginDescriptor.getVersion()) + .dependencies(pluginDescriptor.getDependencies()) + .requires(pluginDescriptor.getRequires()) + .enable(pluginWrapper.getPluginState().equals(PluginState.DISABLED) ? + PluginDescriptorStorage.EnableStatus.DISABLE.getCode() : + PluginDescriptorStorage.EnableStatus.ENABLE.getCode()) + .path(pluginWrapper.getPluginPath().toString()) + .pluginDirPath(pluginProperties.getPluginsRoot()) + .build(); + } +} diff --git a/system-framework/system-plugin-example-web/src/main/resources/application.yml b/system-framework/system-plugin-example-web/src/main/resources/application.yml new file mode 100644 index 0000000..acad00e --- /dev/null +++ b/system-framework/system-plugin-example-web/src/main/resources/application.yml @@ -0,0 +1,211 @@ +server: + port: 8080 + +spring: + application: + name: ops-plugin + jmx: + enabled: true + + thymeleaf: + prefix: classpath:/templates/ + encoding: UTF-8 + cache: false + suffix: .html + servlet: + content-type: text/html + profiles: + active: dev + + main: + allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。 + # allow-bean-definition-overriding: true # Servlet 配置 + servlet: + # 文件上传相关配置项 + multipart: + max-file-size: 64MB # 单个文件大小 + max-request-size: 128MB # 设置总上传的文件大小 + enabled: true + mvc: + pathmatch: + matching-strategy: ANT_PATH_MATCHER # 解决 SpringFox 与 SpringBoot 2.6.x 不兼容的问题,参见 SpringFoxHandlerProviderBeanPostProcessor 类 + # throw-exception-if-no-handler-found: true # 404 错误时抛出异常,方便统一处理 + # static-path-pattern: /static/** # 静态资源路径; 注意:如果不配置,则 throw-exception-if-no-handler-found 不生效!!! TODO 芋艿:不能配置,会导致 swagger 不生效 + + # Jackson 配置项 + jackson: + serialization: + write-dates-as-timestamps: true # 设置 Date 的格式,使用时间戳 + write-date-timestamps-as-nanoseconds: false # 设置不使用 nanoseconds 的格式。例如说 1611460870.401,而是直接 1611460870401 + write-durations-as-timestamps: true # 设置 Duration 的格式,使用时间戳 + fail-on-empty-beans: false # 允许序列化无属性的 Bean + + # 数据源配置项 + autoconfigure: + exclude: + - com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure # 排除 Druid 的自动配置,使用 dynamic-datasource-spring-boot-starter 配置多数据源 + datasource: + druid: # Druid 【监控】相关的全局配置 + web-stat-filter: + enabled: true + stat-view-servlet: + enabled: true + allow: # 设置白名单,不填则允许所有访问 + url-pattern: /druid/* + login-username: # 控制台管理用户名和密码 + login-password: + filter: + stat: + enabled: true + log-slow-sql: true # 慢 SQL 记录 + slow-sql-millis: 100 + merge-sql: true + wall: + config: + multi-statement-allow: true + dynamic: # 多数据源配置 + druid: # Druid 【连接池】相关的全局配置 + initial-size: 1 # 初始连接数 + min-idle: 1 # 最小连接池数量 + max-active: 20 # 最大连接池数量 + max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒 + time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒 + min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒 + max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒 + validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效 + test-while-idle: true + test-on-borrow: false + test-on-return: false + primary: mysql + datasource: + # mysql: + # name: ops-pro + # url: jdbc:mysql://120.48.68.245:13306/${spring.datasource.dynamic.datasource.mysql.name}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 + # username: root + # password: 1qaz!QAZ + mysql: + name: ops_pro + url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.mysql.name}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 + username: root + password: 1qaz!QAZ + driver-class-name: org.sqlite.JDBC + url: jdbc:sqlite:D:/db/sqlitest.db + # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 + + + +logging: + level: + org.pf4j: debug + +ops: + info: + version: 2.0.0 + base-package: cd.casic + web: + admin-ui: + url: 127.0.0.1 # Admin 管理后台 UI 的地址 + security: + permit-all_urls: + - /admin-api/mp/open/** # 微信公众号开放平台,微信回调接口,不需要登录 + websocket: + enable: true # websocket的开关 + path: /websocket/message # 路径 + maxOnlineCount: 0 # 最大连接人数 + sessionMap: true # 保存sessionMap + swagger: + title: Ops平台 + description: 提供管理后台、用户 App 的所有功能 + version: ${ops.info.version} + url: ${ops.web.admin-ui.url} + license: MIT + codegen: + base-package: ${ops.info.base-package} + db-schemas: ${spring.datasource.dynamic.datasource.master.name} + front-type: 10 # 前端模版的类型,参见 CodegenFrontTypeEnum 枚举类 + error-code: # 错误码相关配置项 + constants-class-list: + - cd.casic.ops.module.infra.enums.ErrorCodeConstants + - cd.casic.ops.module.member.enums.ErrorCodeConstants + - cd.casic.ops.module.system.enums.ErrorCodeConstants + #- cd.casic.ops.module.mp.enums.ErrorCodeConstants + tenant: # 多租户相关配置项 + enable: true + ignore-urls: + - /admin-api/system/tenant/get-id-by-name # 基于名字获取租户,不许带租户编号 + - /admin-api/system/captcha/get # 获取图片验证码,和租户无关 + - /admin-api/system/captcha/check # 校验图片验证码,和租户无关 + - /admin-api/infra/file/*/get/** # 获取图片,和租户无关 + - /admin-api/system/sms/callback/* # 短信回调接口,无法带上租户编号 + - /admin-api/pay/notify/callback/* # 支付回调通知,不携带租户编号 + - /jmreport/* # 积木报表,无法携带租户编号 + - /admin-api/mp/open/** # 微信公众号开放平台,微信回调接口,无法携带租户编号 + - /admin-api/leafAlloc/** # id配置接口,和租户无关 + ignore-tables: + - system_tenant + - system_tenant_package + - system_dict_data + - system_dict_type + - system_error_code + - system_menu + - system_sensitive_word + - system_oauth2_client + - system_notify_template + - infra_codegen_column + - infra_codegen_table + - infra_test_demo + - infra_config + - infra_file_config + - infra_file + - infra_file_content + - infra_job + - infra_job_log + - infra_job_log + - infra_data_source_config + - jimu_dict + - jimu_dict_item + - jimu_report + - jimu_report_data_source + - jimu_report_db + - jimu_report_db_field + - jimu_report_db_param + - jimu_report_link + - jimu_report_map + - jimu_report_share + - rep_demo_dxtj + - rep_demo_employee + - rep_demo_gongsi + - rep_demo_jianpiao + - tmp_report_data_1 + - tmp_report_data_income + trade: + order: + app-id: 1 # 商户编号 + expire-time: 2h # 支付的过期时间 + +debug: false + +mongo-plus: + data: + mongodb: + host: 127.0.0.1 #ip + port: 27017 #端口 + database: learn #数据库名 + connectTimeoutMS: 50000 #在超时之前等待连接打开的最长时间(以毫秒为单位) + +cd: + casic: + ops: + plugin: + # 测试的时候写相对于工程根目录的相对路径,Paths.get就能读到 + enable: true + mode: development # 插件运行模式,可选值:development、deployment + rest-path-prefix: plugins + enable-plugin-id-as-rest-prefix: true + plugins-root: ops\ops-module-plugins\ops-module-plugins-example # 插件根目录 + #upload-path: ops-module-plugins\ops-module-plugins-example # 插件jar包上传路径 +# plugins-root: ${user.home}/.ops/plugins # 上线之后可以通过环境变量设置固定的路径 + +springdoc: + swagger-ui: + disable-swagger-default-url: true \ No newline at end of file diff --git a/system-framework/system-plugin-example-web/src/main/resources/sql/sys_plugins.sql b/system-framework/system-plugin-example-web/src/main/resources/sql/sys_plugins.sql new file mode 100644 index 0000000..2e588aa --- /dev/null +++ b/system-framework/system-plugin-example-web/src/main/resources/sql/sys_plugins.sql @@ -0,0 +1,73 @@ +-- MySQL dump 10.13 Distrib 8.0.36, for Win64 (x86_64) +-- +-- Host: 127.0.0.1 Database: ops_pro +-- ------------------------------------------------------ +-- Server version 8.0.36 + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!50503 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `sys_plugins` +-- + +DROP TABLE IF EXISTS `sys_plugins`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `sys_plugins` ( + `plugin_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `plugin_description` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, + `plugin_class` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `version` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `requires` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, + `provider` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, + `dependencies` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, + `license` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, + `mapper_xml_dir` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, + `static_dir` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, + `plugin_dir_path` varchar(100) DEFAULT NULL, + `enable` tinyint(3) unsigned zerofill NOT NULL COMMENT '是否启用,0是禁用,1是启用', + `path` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '插件路径,之后要改相对路径', + PRIMARY KEY (`plugin_id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2024-06-14 13:55:37 + +######################mysql5.7############################ +DROP TABLE IF EXISTS `sys_plugins`; +CREATE TABLE `sys_plugins` ( + `plugin_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, + `plugin_description` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL, + `plugin_class` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL, + `version` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL, + `requires` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL, + `provider` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL, + `dependencies` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL, + `license` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL, + `mapper_xml_dir` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL, + `static_dir` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL, + `plugin_dir_path` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL, + `enable` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL, + `path` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL, + PRIMARY KEY (`plugin_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Compact; + +SET FOREIGN_KEY_CHECKS = 1; \ No newline at end of file diff --git a/system-framework/system-plugin-example-web/src/main/resources/static/easyui.css b/system-framework/system-plugin-example-web/src/main/resources/static/easyui.css new file mode 100644 index 0000000..bd18303 --- /dev/null +++ b/system-framework/system-plugin-example-web/src/main/resources/static/easyui.css @@ -0,0 +1,3706 @@ +.f-row { + display: -webkit-box; + display: -webkit-flex; + display: -moz-flex; + display: -ms-flexbox; + display: flex; + position: relative; +} +.f-column { + display: -webkit-box; + display: -webkit-flex; + display: -moz-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-direction: normal; + -webkit-box-orient: vertical; + -webkit-flex-direction: column; + -moz-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + position: relative; +} +.f-full { + -webkit-box-flex: 1 1 auto; + -ms-flex: 1 1 auto; + flex: 1 1 auto; +} +.f-noshrink { + -webkit-flex-shrink: 0; + -moz-flex-shrink: 0; + -ms-flex-negative: 0; + flex-shrink: 0; +} +.f-content-center { + -webkit-box-pack: center; + -ms-flex-pack: center; + -webkit-justify-content: center; + -moz-justify-content: center; + justify-content: center; + -webkit-box-align: center; + -ms-flex-align: center; + -webkit-align-items: center; + -moz-align-items: center; + align-items: center; +} +.f-vcenter { + -webkit-box-align: center; + -ms-flex-align: center; + -webkit-align-items: center; + -moz-align-items: center; + align-items: center; +} +* { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + -o-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; +} +.panel { + overflow: hidden; + text-align: left; + margin: 0; + border: 0; + -moz-border-radius: 0 0 0 0; + -webkit-border-radius: 0 0 0 0; + border-radius: 0 0 0 0; +} +.panel-header, +.panel-body { + border-width: 1px; + border-style: solid; +} +.panel-header { + padding: 5px; + position: relative; +} +.panel-title { + background: url('images/blank.gif') no-repeat; +} +.panel-header-noborder { + border-width: 0 0 1px 0; +} +.panel-body { + overflow: auto; + border-top-width: 0; + padding: 0; +} +.panel-body-noheader { + border-top-width: 1px; +} +.panel-body-noborder { + border-width: 0px; +} +.panel-body-nobottom { + border-bottom-width: 0; +} +.panel-with-icon { + padding-left: 18px; +} +.panel-icon, +.panel-tool { + position: absolute; + top: 50%; + margin-top: -8px; + height: 16px; + overflow: hidden; +} +.panel-icon { + left: 5px; + width: 16px; +} +.panel-tool { + right: 5px; + width: auto; +} +.panel-tool a { + display: inline-block; + width: 16px; + height: 16px; + opacity: 0.6; + filter: alpha(opacity=60); + margin: 0 0 0 2px; + vertical-align: top; +} +.panel-tool a:hover { + opacity: 1; + filter: alpha(opacity=100); + background-color: #eaf2ff; + -moz-border-radius: 3px 3px 3px 3px; + -webkit-border-radius: 3px 3px 3px 3px; + border-radius: 3px 3px 3px 3px; +} +.panel-loading { + padding: 11px 0px 10px 30px; +} +.panel-noscroll { + overflow: hidden; +} +.panel-fit, +.panel-fit body { + height: 100%; + margin: 0; + padding: 0; + border: 0; + overflow: hidden; +} +.panel-loading { + background: url('images/loading.gif') no-repeat 10px 10px; +} +.panel-tool-close { + background: url('images/panel_tools.png') no-repeat -16px 0px; +} +.panel-tool-min { + background: url('images/panel_tools.png') no-repeat 0px 0px; +} +.panel-tool-max { + background: url('images/panel_tools.png') no-repeat 0px -16px; +} +.panel-tool-restore { + background: url('images/panel_tools.png') no-repeat -16px -16px; +} +.panel-tool-collapse { + background: url('images/panel_tools.png') no-repeat -32px 0; +} +.panel-tool-expand { + background: url('images/panel_tools.png') no-repeat -32px -16px; +} +.panel-header, +.panel-body { + border-color: #95B8E7; +} +.panel-header { + background-color: #E0ECFF; + background: -webkit-linear-gradient(top,#EFF5FF 0,#E0ECFF 100%); + background: -moz-linear-gradient(top,#EFF5FF 0,#E0ECFF 100%); + background: -o-linear-gradient(top,#EFF5FF 0,#E0ECFF 100%); + background: linear-gradient(to bottom,#EFF5FF 0,#E0ECFF 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#EFF5FF,endColorstr=#E0ECFF,GradientType=0); + color: #0E2D5F; +} +.panel-body { + background-color: #ffffff; + color: #000000; + font-size: 14px; +} +.panel-title { + font-size: 14px; + font-weight: bold; + color: #0E2D5F; + height: 20px; + line-height: 20px; +} +.panel-footer { + border: 1px solid #95B8E7; + overflow: hidden; + background: #F4F4F4; + color: #000000; +} +.panel-footer-noborder { + border-width: 1px 0 0 0; +} +.panel-hleft, +.panel-hright { + position: relative; +} +.panel-hleft>.panel-body, +.panel-hright>.panel-body { + position: absolute; +} +.panel-hleft>.panel-header { + float: left; +} +.panel-hright>.panel-header { + float: right; +} +.panel-hleft>.panel-body { + border-top-width: 1px; + border-left-width: 0; +} +.panel-hright>.panel-body { + border-top-width: 1px; + border-right-width: 0; +} +.panel-hleft>.panel-body-nobottom { + border-bottom-width: 1px; + border-right-width: 0; +} +.panel-hright>.panel-body-nobottom { + border-bottom-width: 1px; + border-left-width: 0; +} +.panel-hleft>.panel-footer { + position: absolute; + right: 0; +} +.panel-hright>.panel-footer { + position: absolute; + left: 0; +} +.panel-hleft>.panel-header-noborder { + border-width: 0 1px 0 0; +} +.panel-hright>.panel-header-noborder { + border-width: 0 0 0 1px; +} +.panel-hleft>.panel-body-noborder { + border-width: 0; +} +.panel-hright>.panel-body-noborder { + border-width: 0; +} +.panel-hleft>.panel-body-noheader { + border-left-width: 1px; +} +.panel-hright>.panel-body-noheader { + border-right-width: 1px; +} +.panel-hleft>.panel-footer-noborder { + border-width: 0 0 0 1px; +} +.panel-hright>.panel-footer-noborder { + border-width: 0 1px 0 0; +} +.panel-hleft>.panel-header .panel-icon, +.panel-hright>.panel-header .panel-icon { + margin-top: 0; + top: 5px; + left: 50%; + margin-left: -8px; +} +.panel-hleft>.panel-header .panel-title, +.panel-hright>.panel-header .panel-title { + position: absolute; + min-width: 16px; + left: 25px; + top: 5px; + bottom: auto; + white-space: nowrap; + word-wrap: normal; + -webkit-transform: rotate(90deg); + -webkit-transform-origin: 0 0; + -moz-transform: rotate(90deg); + -moz-transform-origin: 0 0; + -o-transform: rotate(90deg); + -o-transform-origin: 0 0; + transform: rotate(90deg); + transform-origin: 0 0; +} +.panel-hleft>.panel-header .panel-title-up, +.panel-hright>.panel-header .panel-title-up { + position: absolute; + min-width: 16px; + left: 21px; + top: auto; + bottom: 0px; + text-align: right; + white-space: nowrap; + word-wrap: normal; + -webkit-transform: rotate(-90deg); + -webkit-transform-origin: 0 0; + -moz-transform: rotate(-90deg); + -moz-transform-origin: 0 0; + -o-transform: rotate(-90deg); + -o-transform-origin: 0 0; + transform: rotate(-90deg); + transform-origin: 0 16px; +} +.panel-hleft>.panel-header .panel-with-icon.panel-title-up, +.panel-hright>.panel-header .panel-with-icon.panel-title-up { + padding-left: 0; + padding-right: 18px; +} +.panel-hleft>.panel-header .panel-tool, +.panel-hright>.panel-header .panel-tool { + top: auto; + bottom: 5px; + width: 16px; + height: auto; + left: 50%; + margin-left: -8px; + margin-top: 0; +} +.panel-hleft>.panel-header .panel-tool a, +.panel-hright>.panel-header .panel-tool a { + margin: 2px 0 0 0; +} +.accordion { + overflow: hidden; + border-width: 1px; + border-style: solid; +} +.accordion .accordion-header { + border-width: 0 0 1px; + cursor: pointer; +} +.accordion .accordion-body { + border-width: 0 0 1px; +} +.accordion-noborder { + border-width: 0; +} +.accordion-noborder .accordion-header { + border-width: 0 0 1px; +} +.accordion-noborder .accordion-body { + border-width: 0 0 1px; +} +.accordion-collapse { + background: url('images/accordion_arrows.png') no-repeat 0 0; +} +.accordion-expand { + background: url('images/accordion_arrows.png') no-repeat -16px 0; +} +.accordion { + background: #ffffff; + border-color: #95B8E7; +} +.accordion .accordion-header { + background: #E0ECFF; + filter: none; +} +.accordion .accordion-header-selected { + background: #ffe48d; +} +.accordion .accordion-header-selected .panel-title { + color: #000000; +} +.accordion .panel-last > .accordion-header { + border-bottom-color: #E0ECFF; +} +.accordion .panel-last > .accordion-body { + border-bottom-color: #ffffff; +} +.accordion .panel-last > .accordion-header-selected, +.accordion .panel-last > .accordion-header-border { + border-bottom-color: #95B8E7; +} +.accordion> .panel-hleft { + float: left; +} +.accordion> .panel-hleft>.panel-header { + border-width: 0 1px 0 0; +} +.accordion> .panel-hleft> .panel-body { + border-width: 0 1px 0 0; +} +.accordion> .panel-hleft.panel-last > .accordion-header { + border-right-color: #E0ECFF; +} +.accordion> .panel-hleft.panel-last > .accordion-body { + border-right-color: #ffffff; +} +.accordion> .panel-hleft.panel-last > .accordion-header-selected, +.accordion> .panel-hleft.panel-last > .accordion-header-border { + border-right-color: #95B8E7; +} +.accordion> .panel-hright { + float: right; +} +.accordion> .panel-hright>.panel-header { + border-width: 0 0 0 1px; +} +.accordion> .panel-hright> .panel-body { + border-width: 0 0 0 1px; +} +.accordion> .panel-hright.panel-last > .accordion-header { + border-left-color: #E0ECFF; +} +.accordion> .panel-hright.panel-last > .accordion-body { + border-left-color: #ffffff; +} +.accordion> .panel-hright.panel-last > .accordion-header-selected, +.accordion> .panel-hright.panel-last > .accordion-header-border { + border-left-color: #95B8E7; +} +.window { + overflow: hidden; + padding: 5px; + border-width: 1px; + border-style: solid; +} +.window .window-header { + background: transparent; + padding: 0px 0px 6px 0px; +} +.window .window-body { + border-width: 1px; + border-style: solid; + border-top-width: 0px; +} +.window .window-body-noheader { + border-top-width: 1px; +} +.window .panel-body-nobottom { + border-bottom-width: 0; +} +.window .window-header .panel-icon, +.window .window-header .panel-tool { + top: 50%; + margin-top: -11px; +} +.window .window-header .panel-icon { + left: 1px; +} +.window .window-header .panel-tool { + right: 1px; +} +.window .window-header .panel-with-icon { + padding-left: 18px; +} +.window-proxy { + position: absolute; + overflow: hidden; +} +.window-proxy-mask { + position: absolute; + filter: alpha(opacity=5); + opacity: 0.05; +} +.window-mask { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + filter: alpha(opacity=40); + opacity: 0.40; + font-size: 1px; + overflow: hidden; +} +.window, +.window-shadow { + position: absolute; + -moz-border-radius: 5px 5px 5px 5px; + -webkit-border-radius: 5px 5px 5px 5px; + border-radius: 5px 5px 5px 5px; +} +.window-shadow { + background: #ccc; + -moz-box-shadow: 2px 2px 3px #cccccc; + -webkit-box-shadow: 2px 2px 3px #cccccc; + box-shadow: 2px 2px 3px #cccccc; + filter: progid:DXImageTransform.Microsoft.Blur(pixelRadius=2,MakeShadow=false,ShadowOpacity=0.2); +} +.window, +.window .window-body { + border-color: #95B8E7; +} +.window { + background-color: #E0ECFF; + background: -webkit-linear-gradient(top,#EFF5FF 0,#E0ECFF 20%); + background: -moz-linear-gradient(top,#EFF5FF 0,#E0ECFF 20%); + background: -o-linear-gradient(top,#EFF5FF 0,#E0ECFF 20%); + background: linear-gradient(to bottom,#EFF5FF 0,#E0ECFF 20%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#EFF5FF,endColorstr=#E0ECFF,GradientType=0); +} +.window-proxy { + border: 1px dashed #95B8E7; +} +.window-proxy-mask, +.window-mask { + background: #ccc; +} +.window .panel-footer { + border: 1px solid #95B8E7; + position: relative; + top: -1px; +} +.window-thinborder { + padding: 0; +} +.window-thinborder .window-header { + padding: 5px 5px 6px 5px; +} +.window-thinborder .window-body { + border-width: 0px; +} +.window-thinborder .window-footer { + border-left: transparent; + border-right: transparent; + border-bottom: transparent; +} +.window-thinborder .window-header .panel-icon, +.window-thinborder .window-header .panel-tool { + margin-top: -9px; + margin-left: 5px; + margin-right: 5px; +} +.window-noborder { + border: 0; +} +.window.panel-hleft .window-header { + padding: 0 6px 0 0; +} +.window.panel-hright .window-header { + padding: 0 0 0 6px; +} +.window.panel-hleft>.panel-header .panel-title { + top: auto; + left: 16px; +} +.window.panel-hright>.panel-header .panel-title { + top: auto; + right: 16px; +} +.window.panel-hleft>.panel-header .panel-title-up, +.window.panel-hright>.panel-header .panel-title-up { + bottom: 0; +} +.window.panel-hleft .window-body { + border-width: 1px 1px 1px 0; +} +.window.panel-hright .window-body { + border-width: 1px 0 1px 1px; +} +.window.panel-hleft .window-header .panel-icon { + top: 1px; + margin-top: 0; + left: 0; +} +.window.panel-hright .window-header .panel-icon { + top: 1px; + margin-top: 0; + left: auto; + right: 1px; +} +.window.panel-hleft .window-header .panel-tool, +.window.panel-hright .window-header .panel-tool { + margin-top: 0; + top: auto; + bottom: 1px; + right: auto; + margin-right: 0; + left: 50%; + margin-left: -11px; +} +.window.panel-hright .window-header .panel-tool { + left: auto; + right: 1px; +} +.window-thinborder.panel-hleft .window-header { + padding: 5px 6px 5px 5px; +} +.window-thinborder.panel-hright .window-header { + padding: 5px 5px 5px 6px; +} +.window-thinborder.panel-hleft>.panel-header .panel-title { + left: 21px; +} +.window-thinborder.panel-hleft>.panel-header .panel-title-up, +.window-thinborder.panel-hright>.panel-header .panel-title-up { + bottom: 5px; +} +.window-thinborder.panel-hleft .window-header .panel-icon, +.window-thinborder.panel-hright .window-header .panel-icon { + margin-top: 5px; +} +.window-thinborder.panel-hleft .window-header .panel-tool, +.window-thinborder.panel-hright .window-header .panel-tool { + left: 16px; + bottom: 5px; +} +.dialog-content { + overflow: auto; +} +.dialog-toolbar { + position: relative; + padding: 2px 5px; +} +.dialog-tool-separator { + float: left; + height: 24px; + border-left: 1px solid #ccc; + border-right: 1px solid #fff; + margin: 2px 1px; +} +.dialog-button { + position: relative; + top: -1px; + padding: 5px; + text-align: right; +} +.dialog-button .l-btn { + margin-left: 5px; +} +.dialog-toolbar, +.dialog-button { + background: #F4F4F4; + border-width: 1px; + border-style: solid; +} +.dialog-toolbar { + border-color: #95B8E7 #95B8E7 #dddddd #95B8E7; +} +.dialog-button { + border-color: #dddddd #95B8E7 #95B8E7 #95B8E7; +} +.window-thinborder .dialog-toolbar { + border-left: transparent; + border-right: transparent; + border-top-color: #F4F4F4; +} +.window-thinborder .dialog-button { + top: 0px; + padding: 5px 8px 8px 8px; + border-left: transparent; + border-right: transparent; + border-bottom: transparent; +} +.drawer.window-shadow { + box-shadow: 0 4px 12px #ccc; + border-radius: 0; + position: fixed; +} +.drawer.layout-collapsed { + box-shadow: none; +} +.drawer-mask.window-mask { + position: fixed; +} +.drawer.layout-panel-east, +.drawer.layout-panel-west { + bottom: 0; +} +.l-btn { + text-decoration: none; + display: inline-block; + overflow: hidden; + margin: 0; + padding: 0; + cursor: pointer; + outline: none; + text-align: center; + vertical-align: middle; + line-height: normal; +} +.l-btn-plain { + border-width: 0; + padding: 1px; +} +.l-btn-left { + display: inline-block; + position: relative; + overflow: hidden; + margin: 0; + padding: 0; + vertical-align: top; +} +.l-btn-text { + display: inline-block; + vertical-align: top; + width: auto; + line-height: 28px; + font-size: 14px; + padding: 0; + margin: 0 6px; +} +.l-btn-icon { + display: inline-block; + width: 16px; + height: 16px; + line-height: 16px; + position: absolute; + top: 50%; + margin-top: -8px; + font-size: 1px; +} +.l-btn span span .l-btn-empty { + display: inline-block; + margin: 0; + width: 16px; + height: 24px; + font-size: 1px; + vertical-align: top; +} +.l-btn span .l-btn-icon-left { + padding: 0 0 0 20px; + background-position: left center; +} +.l-btn span .l-btn-icon-right { + padding: 0 20px 0 0; + background-position: right center; +} +.l-btn-icon-left .l-btn-text { + margin: 0 6px 0 26px; +} +.l-btn-icon-left .l-btn-icon { + left: 6px; +} +.l-btn-icon-right .l-btn-text { + margin: 0 26px 0 6px; +} +.l-btn-icon-right .l-btn-icon { + right: 6px; +} +.l-btn-icon-top .l-btn-text { + margin: 20px 4px 0 4px; +} +.l-btn-icon-top .l-btn-icon { + top: 4px; + left: 50%; + margin: 0 0 0 -8px; +} +.l-btn-icon-bottom .l-btn-text { + margin: 0 4px 20px 4px; +} +.l-btn-icon-bottom .l-btn-icon { + top: auto; + bottom: 4px; + left: 50%; + margin: 0 0 0 -8px; +} +.l-btn-left .l-btn-empty { + margin: 0 6px; + width: 16px; +} +.l-btn-plain:hover { + padding: 0; +} +.l-btn-focus { + outline: #0000FF dotted thin; +} +.l-btn-large .l-btn-text { + line-height: 44px; +} +.l-btn-large .l-btn-icon { + width: 32px; + height: 32px; + line-height: 32px; + margin-top: -16px; +} +.l-btn-large .l-btn-icon-left .l-btn-text { + margin-left: 40px; +} +.l-btn-large .l-btn-icon-right .l-btn-text { + margin-right: 40px; +} +.l-btn-large .l-btn-icon-top .l-btn-text { + margin-top: 36px; + line-height: 24px; + min-width: 32px; +} +.l-btn-large .l-btn-icon-top .l-btn-icon { + margin: 0 0 0 -16px; +} +.l-btn-large .l-btn-icon-bottom .l-btn-text { + margin-bottom: 36px; + line-height: 24px; + min-width: 32px; +} +.l-btn-large .l-btn-icon-bottom .l-btn-icon { + margin: 0 0 0 -16px; +} +.l-btn-large .l-btn-left .l-btn-empty { + margin: 0 6px; + width: 32px; +} +.l-btn { + color: #444; + background: #fafafa; + background-repeat: repeat-x; + border: 1px solid #bbb; + background: -webkit-linear-gradient(top,#ffffff 0,#eeeeee 100%); + background: -moz-linear-gradient(top,#ffffff 0,#eeeeee 100%); + background: -o-linear-gradient(top,#ffffff 0,#eeeeee 100%); + background: linear-gradient(to bottom,#ffffff 0,#eeeeee 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#ffffff,endColorstr=#eeeeee,GradientType=0); + -moz-border-radius: 5px 5px 5px 5px; + -webkit-border-radius: 5px 5px 5px 5px; + border-radius: 5px 5px 5px 5px; +} +.l-btn:hover { + background: #eaf2ff; + color: #000000; + border: 1px solid #b7d2ff; + filter: none; +} +.l-btn-plain { + background: transparent; + border-width: 0; + filter: none; +} +.l-btn-outline { + border-width: 1px; + border-color: #b7d2ff; + padding: 0; +} +.l-btn-plain:hover { + background: #eaf2ff; + color: #000000; + border: 1px solid #b7d2ff; + -moz-border-radius: 5px 5px 5px 5px; + -webkit-border-radius: 5px 5px 5px 5px; + border-radius: 5px 5px 5px 5px; +} +.l-btn-disabled, +.l-btn-disabled:hover { + opacity: 0.5; + cursor: default; + background: #fafafa; + color: #444; + background: -webkit-linear-gradient(top,#ffffff 0,#eeeeee 100%); + background: -moz-linear-gradient(top,#ffffff 0,#eeeeee 100%); + background: -o-linear-gradient(top,#ffffff 0,#eeeeee 100%); + background: linear-gradient(to bottom,#ffffff 0,#eeeeee 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#ffffff,endColorstr=#eeeeee,GradientType=0); +} +.l-btn-disabled .l-btn-text, +.l-btn-disabled .l-btn-icon { + filter: alpha(opacity=50); +} +.l-btn-plain-disabled, +.l-btn-plain-disabled:hover { + background: transparent; + filter: alpha(opacity=50); +} +.l-btn-selected, +.l-btn-selected:hover { + background: #ddd; + filter: none; +} +.l-btn-plain-selected, +.l-btn-plain-selected:hover { + background: #ddd; +} +.textbox { + position: relative; + border: 1px solid #95B8E7; + background-color: #fff; + vertical-align: middle; + display: inline-block; + overflow: hidden; + white-space: nowrap; + margin: 0; + padding: 0; + -moz-border-radius: 5px 5px 5px 5px; + -webkit-border-radius: 5px 5px 5px 5px; + border-radius: 5px 5px 5px 5px; +} +.textbox .textbox-text { + font-size: 14px; + border: 0; + margin: 0; + padding: 0 4px; + white-space: normal; + vertical-align: top; + outline-style: none; + resize: none; + -moz-border-radius: 5px 5px 5px 5px; + -webkit-border-radius: 5px 5px 5px 5px; + border-radius: 5px 5px 5px 5px; + height: 28px; + line-height: 28px; +} +.textbox textarea.textbox-text { + line-height: normal; +} +.textbox.textbox-autoheight { + height: auto; +} +.textbox.textbox-autoheight textarea.textbox-text { + padding: 6px 4px; + overflow: hidden; + line-height: 16px; +} +.textbox .textbox-text::-ms-clear, +.textbox .textbox-text::-ms-reveal { + display: none; +} +.textbox textarea.textbox-text { + white-space: pre-wrap; +} +.textbox .textbox-prompt { + font-size: 14px; + color: #aaa; +} +.textbox .textbox-bgicon { + background-position: 3px center; + padding-left: 21px; +} +.textbox .textbox-button, +.textbox .textbox-button:hover { + position: absolute; + top: 0; + padding: 0; + vertical-align: top; + -moz-border-radius: 0 0 0 0; + -webkit-border-radius: 0 0 0 0; + border-radius: 0 0 0 0; +} +.textbox .textbox-button-right, +.textbox .textbox-button-right:hover { + right: 0; + border-width: 0 0 0 1px; +} +.textbox .textbox-button-left, +.textbox .textbox-button-left:hover { + left: 0; + border-width: 0 1px 0 0; +} +.textbox .textbox-button-top, +.textbox .textbox-button-top:hover { + left: 0; + border-width: 0 0 1px 0; +} +.textbox .textbox-button-bottom, +.textbox .textbox-button-bottom:hover { + top: auto; + bottom: 0; + left: 0; + border-width: 1px 0 0 0; +} +.textbox-addon { + position: absolute; + top: 0; +} +.textbox-label { + display: inline-block; + width: 80px; + height: 30px; + line-height: 30px; + vertical-align: middle; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + margin: 0; + padding-right: 5px; +} +.textbox-label-after { + padding-left: 5px; + padding-right: 0; +} +.textbox-label-top { + display: block; + width: auto; + padding: 0; +} +.textbox-disabled, +.textbox-label-disabled { + opacity: 0.6; + filter: alpha(opacity=60); +} +.textbox-icon { + display: inline-block; + width: 18px; + height: 20px; + overflow: hidden; + vertical-align: top; + background-position: center center; + cursor: pointer; + opacity: 0.6; + filter: alpha(opacity=60); + text-decoration: none; + outline-style: none; +} +.textbox-icon-disabled, +.textbox-icon-readonly { + cursor: default; +} +.textbox-icon:hover { + opacity: 1.0; + filter: alpha(opacity=100); +} +.textbox-icon-disabled:hover { + opacity: 0.6; + filter: alpha(opacity=60); +} +.textbox-focused { + border-color: #6b9cde; + -moz-box-shadow: 0 0 3px 0 #95B8E7; + -webkit-box-shadow: 0 0 3px 0 #95B8E7; + box-shadow: 0 0 3px 0 #95B8E7; +} +.textbox-invalid { + border-color: #ffa8a8; + background-color: #fff3f3; +} +.form-floating-label.form-field .textbox-text { + padding: 0; +} +.form-floating-label.form-field .textbox-label { + position: relative; + height: 20px; + line-height: 20px; + transition: all .3s; + font-size: 12px; + z-index: 9; +} +.form-floating-label.form-field-empty .textbox-label { + cursor: text; + font-size: 14px; + transform: translate(0,25px); +} +.form-floating-label.form-field-empty.form-field-focused .textbox-label { + cursor: default; + font-size: 12px; + transform: translate(0,0); +} +.passwordbox-open { + background: url('images/passwordbox_open.png') no-repeat center center; +} +.passwordbox-close { + background: url('images/passwordbox_close.png') no-repeat center center; +} +.filebox .textbox-value { + vertical-align: top; + position: absolute; + top: 0; + left: -5000px; +} +.filebox-label { + display: inline-block; + position: absolute; + width: 100%; + height: 100%; + cursor: pointer; + left: 0; + top: 0; + z-index: 10; + background: url('images/blank.gif') no-repeat; +} +.l-btn-disabled .filebox-label { + cursor: default; +} +.combo-arrow { + width: 18px; + height: 20px; + overflow: hidden; + display: inline-block; + vertical-align: top; + cursor: pointer; + opacity: 0.6; + filter: alpha(opacity=60); +} +.combo-arrow-hover { + opacity: 1.0; + filter: alpha(opacity=100); +} +.combo-panel { + overflow: auto; +} +.combo-arrow { + background: url('images/combo_arrow.png') no-repeat center center; +} +.combo-panel { + background-color: #ffffff; +} +.combo-arrow { + background-color: #E0ECFF; +} +.combo-arrow-hover { + background-color: #eaf2ff; +} +.combo-arrow:hover { + background-color: #eaf2ff; +} +.combo .textbox-icon-disabled:hover { + cursor: default; +} +.combobox-item, +.combobox-group, +.combobox-stick { + font-size: 14px; + padding: 6px 4px; + line-height: 20px; +} +.combobox-item-disabled { + opacity: 0.5; + filter: alpha(opacity=50); +} +.combobox-gitem { + padding-left: 10px; +} +.combobox-group, +.combobox-stick { + font-weight: bold; +} +.combobox-stick { + position: absolute; + top: 1px; + left: 1px; + right: 1px; + background: inherit; +} +.combobox-item-hover { + background-color: #eaf2ff; + color: #000000; +} +.combobox-item-selected { + background-color: #ffe48d; + color: #000000; +} +.combobox-icon { + display: inline-block; + width: 16px; + height: 16px; + vertical-align: middle; + margin-right: 2px; +} +.tagbox { + cursor: text; +} +.tagbox .textbox-text { + float: left; +} +.tagbox-label { + position: relative; + display: block; + margin: 4px 0 0 4px; + padding: 0 20px 0 4px; + float: left; + vertical-align: top; + text-decoration: none; + -moz-border-radius: 5px 5px 5px 5px; + -webkit-border-radius: 5px 5px 5px 5px; + border-radius: 5px 5px 5px 5px; + background: #eaf2ff; + color: #000000; +} +.tagbox-remove { + background: url('images/tagbox_icons.png') no-repeat -16px center; + position: absolute; + display: block; + width: 16px; + height: 16px; + right: 2px; + top: 50%; + margin-top: -8px; + opacity: 0.6; + filter: alpha(opacity=60); +} +.tagbox-remove:hover { + opacity: 1; + filter: alpha(opacity=100); +} +.textbox-disabled .tagbox-label { + cursor: default; +} +.textbox-disabled .tagbox-remove:hover { + cursor: default; + opacity: 0.6; + filter: alpha(opacity=60); +} +.layout { + position: relative; + overflow: hidden; + margin: 0; + padding: 0; + z-index: 0; +} +.layout-panel { + position: absolute; + overflow: hidden; +} +.layout-body { + min-width: 1px; + min-height: 1px; +} +.layout-panel-east, +.layout-panel-west { + z-index: 2; +} +.layout-panel-north, +.layout-panel-south { + z-index: 3; +} +.layout-expand { + position: absolute; + padding: 0px; + font-size: 1px; + cursor: pointer; + z-index: 1; +} +.layout-expand .panel-header, +.layout-expand .panel-body { + background: transparent; + filter: none; + overflow: hidden; +} +.layout-expand .panel-header { + border-bottom-width: 0px; +} +.layout-expand .panel-body { + position: relative; +} +.layout-expand .panel-body .panel-icon { + margin-top: 0; + top: 0; + left: 50%; + margin-left: -8px; +} +.layout-expand-west .panel-header .panel-icon, +.layout-expand-east .panel-header .panel-icon { + display: none; +} +.layout-expand-title { + position: absolute; + top: 0; + left: 21px; + white-space: nowrap; + word-wrap: normal; + -webkit-transform: rotate(90deg); + -webkit-transform-origin: 0 0; + -moz-transform: rotate(90deg); + -moz-transform-origin: 0 0; + -o-transform: rotate(90deg); + -o-transform-origin: 0 0; + transform: rotate(90deg); + transform-origin: 0 0; +} +.layout-expand-title-up { + position: absolute; + top: 0; + left: 0; + text-align: right; + padding-left: 5px; + white-space: nowrap; + word-wrap: normal; + -webkit-transform: rotate(-90deg); + -webkit-transform-origin: 0 0; + -moz-transform: rotate(-90deg); + -moz-transform-origin: 0 0; + -o-transform: rotate(-90deg); + -o-transform-origin: 0 0; + transform: rotate(-90deg); + transform-origin: 0 0; +} +.layout-expand-with-icon { + top: 18px; +} +.layout-expand .panel-body-noheader .layout-expand-title, +.layout-expand .panel-body-noheader .panel-icon { + top: 5px; +} +.layout-expand .panel-body-noheader .layout-expand-with-icon { + top: 23px; +} +.layout-split-proxy-h, +.layout-split-proxy-v { + position: absolute; + font-size: 1px; + display: none; + z-index: 5; +} +.layout-split-proxy-h { + width: 5px; + cursor: e-resize; +} +.layout-split-proxy-v { + height: 5px; + cursor: n-resize; +} +.layout-mask { + position: absolute; + background: #fafafa; + filter: alpha(opacity=10); + opacity: 0.10; + z-index: 4; +} +.layout-button-up { + background: url('images/layout_arrows.png') no-repeat -16px -16px; +} +.layout-button-down { + background: url('images/layout_arrows.png') no-repeat -16px 0; +} +.layout-button-left { + background: url('images/layout_arrows.png') no-repeat 0 0; +} +.layout-button-right { + background: url('images/layout_arrows.png') no-repeat 0 -16px; +} +.layout-split-proxy-h, +.layout-split-proxy-v { + background-color: #aac5e7; +} +.layout-split-north { + border-bottom: 5px solid #E6EEF8; +} +.layout-split-south { + border-top: 5px solid #E6EEF8; +} +.layout-split-east { + border-left: 5px solid #E6EEF8; +} +.layout-split-west { + border-right: 5px solid #E6EEF8; +} +.layout-expand { + background-color: #E0ECFF; +} +.layout-expand-over { + background-color: #E0ECFF; +} +.tabs-container { + overflow: hidden; +} +.tabs-header { + border-width: 1px; + border-style: solid; + border-bottom-width: 0; + position: relative; + padding: 0; + padding-top: 2px; + overflow: hidden; +} +.tabs-scroller-left, +.tabs-scroller-right { + position: absolute; + top: auto; + bottom: 0; + width: 18px; + font-size: 1px; + display: none; + cursor: pointer; + border-width: 1px; + border-style: solid; +} +.tabs-scroller-left { + left: 0; +} +.tabs-scroller-right { + right: 0; +} +.tabs-tool { + position: absolute; + bottom: 0; + padding: 1px; + overflow: hidden; + border-width: 1px; + border-style: solid; +} +.tabs-header-plain .tabs-tool { + padding: 0 1px; +} +.tabs-wrap { + position: relative; + left: 0; + overflow: hidden; + width: 100%; + margin: 0; + padding: 0; +} +.tabs-scrolling { + margin-left: 18px; + margin-right: 18px; +} +.tabs-disabled { + opacity: 0.3; + filter: alpha(opacity=30); +} +.tabs { + list-style-type: none; + height: 26px; + margin: 0px; + padding: 0px; + padding-left: 4px; + width: 50000px; + border-style: solid; + border-width: 0 0 1px 0; +} +.tabs li { + float: left; + display: inline-block; + margin: 0 4px -1px 0; + padding: 0; + position: relative; + border: 0; +} +.tabs li .tabs-inner { + display: inline-block; + text-decoration: none; + cursor: hand; + cursor: pointer; + margin: 0; + padding: 0 10px; + height: 25px; + line-height: 25px; + text-align: center; + white-space: nowrap; + border-width: 1px; + border-style: solid; + -moz-border-radius: 5px 5px 0 0; + -webkit-border-radius: 5px 5px 0 0; + border-radius: 5px 5px 0 0; +} +.tabs li.tabs-selected .tabs-inner { + font-weight: bold; + outline: none; +} +.tabs li.tabs-selected .tabs-inner:hover { + cursor: default; + pointer: default; +} +.tabs li .tabs-close, +.tabs-p-tool { + position: absolute; + font-size: 1px; + display: block; + height: 12px; + padding: 0; + top: 50%; + margin-top: -6px; + overflow: hidden; +} +.tabs li .tabs-close { + width: 12px; + right: 5px; + opacity: 0.6; + filter: alpha(opacity=60); +} +.tabs-p-tool { + right: 16px; +} +.tabs-p-tool a { + display: inline-block; + font-size: 1px; + width: 12px; + height: 12px; + margin: 0; + opacity: 0.6; + filter: alpha(opacity=60); +} +.tabs li .tabs-close:hover, +.tabs-p-tool a:hover { + opacity: 1; + filter: alpha(opacity=100); + cursor: hand; + cursor: pointer; +} +.tabs-with-icon { + padding-left: 18px; +} +.tabs-icon { + position: absolute; + width: 16px; + height: 16px; + left: 10px; + top: 50%; + margin-top: -8px; +} +.tabs-title { + font-size: 14px; +} +.tabs-closable { + padding-right: 8px; +} +.tabs-panels { + margin: 0px; + padding: 0px; + border-width: 1px; + border-style: solid; + border-top-width: 0; + overflow: hidden; +} +.tabs-header-bottom { + border-width: 0 1px 1px 1px; + padding: 0 0 2px 0; +} +.tabs-header-bottom .tabs { + border-width: 1px 0 0 0; +} +.tabs-header-bottom .tabs li { + margin: -1px 4px 0 0; +} +.tabs-header-bottom .tabs li .tabs-inner { + -moz-border-radius: 0 0 5px 5px; + -webkit-border-radius: 0 0 5px 5px; + border-radius: 0 0 5px 5px; +} +.tabs-header-bottom .tabs-tool { + top: 0; +} +.tabs-header-bottom .tabs-scroller-left, +.tabs-header-bottom .tabs-scroller-right { + top: 0; + bottom: auto; +} +.tabs-panels-top { + border-width: 1px 1px 0 1px; +} +.tabs-header-left { + float: left; + border-width: 1px 0 1px 1px; + padding: 0; +} +.tabs-header-right { + float: right; + border-width: 1px 1px 1px 0; + padding: 0; +} +.tabs-header-left .tabs-wrap, +.tabs-header-right .tabs-wrap { + height: 100%; +} +.tabs-header-left .tabs { + height: 100%; + padding: 4px 0 0 2px; + border-width: 0 1px 0 0; +} +.tabs-header-right .tabs { + height: 100%; + padding: 4px 2px 0 0; + border-width: 0 0 0 1px; +} +.tabs-header-left .tabs li, +.tabs-header-right .tabs li { + display: block; + width: 100%; + position: relative; +} +.tabs-header-left .tabs li { + left: auto; + right: 0; + margin: 0 -1px 4px 0; + float: right; +} +.tabs-header-right .tabs li { + left: 0; + right: auto; + margin: 0 0 4px -1px; + float: left; +} +.tabs-justified li .tabs-inner { + padding-left: 0; + padding-right: 0; +} +.tabs-header-left .tabs li .tabs-inner { + display: block; + text-align: left; + padding-left: 10px; + padding-right: 10px; + -moz-border-radius: 5px 0 0 5px; + -webkit-border-radius: 5px 0 0 5px; + border-radius: 5px 0 0 5px; +} +.tabs-header-right .tabs li .tabs-inner { + display: block; + text-align: left; + padding-left: 10px; + padding-right: 10px; + -moz-border-radius: 0 5px 5px 0; + -webkit-border-radius: 0 5px 5px 0; + border-radius: 0 5px 5px 0; +} +.tabs-panels-right { + float: right; + border-width: 1px 1px 1px 0; +} +.tabs-panels-left { + float: left; + border-width: 1px 0 1px 1px; +} +.tabs-header-noborder, +.tabs-panels-noborder { + border: 0px; +} +.tabs-header-plain { + border: 0px; + background: transparent; +} +.tabs-pill { + padding-bottom: 3px; +} +.tabs-header-bottom .tabs-pill { + padding-top: 3px; + padding-bottom: 0; +} +.tabs-header-left .tabs-pill { + padding-right: 3px; +} +.tabs-header-right .tabs-pill { + padding-left: 3px; +} +.tabs-header .tabs-pill li .tabs-inner { + -moz-border-radius: 5px 5px 5px 5px; + -webkit-border-radius: 5px 5px 5px 5px; + border-radius: 5px 5px 5px 5px; +} +.tabs-header-narrow, +.tabs-header-narrow .tabs-narrow { + padding: 0; +} +.tabs-narrow li, +.tabs-header-bottom .tabs-narrow li { + margin-left: 0; + margin-right: -1px; +} +.tabs-narrow li.tabs-last, +.tabs-header-bottom .tabs-narrow li.tabs-last { + margin-right: 0; +} +.tabs-header-left .tabs-narrow, +.tabs-header-right .tabs-narrow { + padding-top: 0; +} +.tabs-header-left .tabs-narrow li { + margin-bottom: -1px; + margin-right: -1px; +} +.tabs-header-left .tabs-narrow li.tabs-last, +.tabs-header-right .tabs-narrow li.tabs-last { + margin-bottom: 0; +} +.tabs-header-right .tabs-narrow li { + margin-bottom: -1px; + margin-left: -1px; +} +.tabs-scroller-left { + background: #E0ECFF url('images/tabs_icons.png') no-repeat 1px center; +} +.tabs-scroller-right { + background: #E0ECFF url('images/tabs_icons.png') no-repeat -15px center; +} +.tabs li .tabs-close { + background: url('images/tabs_icons.png') no-repeat -34px center; +} +.tabs li .tabs-inner:hover { + background: #eaf2ff; + color: #000000; + filter: none; +} +.tabs li.tabs-selected .tabs-inner { + background-color: #ffffff; + color: #0E2D5F; + background: -webkit-linear-gradient(top,#EFF5FF 0,#ffffff 100%); + background: -moz-linear-gradient(top,#EFF5FF 0,#ffffff 100%); + background: -o-linear-gradient(top,#EFF5FF 0,#ffffff 100%); + background: linear-gradient(to bottom,#EFF5FF 0,#ffffff 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#EFF5FF,endColorstr=#ffffff,GradientType=0); +} +.tabs-header-bottom .tabs li.tabs-selected .tabs-inner { + background: -webkit-linear-gradient(top,#ffffff 0,#EFF5FF 100%); + background: -moz-linear-gradient(top,#ffffff 0,#EFF5FF 100%); + background: -o-linear-gradient(top,#ffffff 0,#EFF5FF 100%); + background: linear-gradient(to bottom,#ffffff 0,#EFF5FF 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#ffffff,endColorstr=#EFF5FF,GradientType=0); +} +.tabs-header-left .tabs li.tabs-selected .tabs-inner { + background: -webkit-linear-gradient(left,#EFF5FF 0,#ffffff 100%); + background: -moz-linear-gradient(left,#EFF5FF 0,#ffffff 100%); + background: -o-linear-gradient(left,#EFF5FF 0,#ffffff 100%); + background: linear-gradient(to right,#EFF5FF 0,#ffffff 100%); + background-repeat: repeat-y; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#EFF5FF,endColorstr=#ffffff,GradientType=1); +} +.tabs-header-right .tabs li.tabs-selected .tabs-inner { + background: -webkit-linear-gradient(left,#ffffff 0,#EFF5FF 100%); + background: -moz-linear-gradient(left,#ffffff 0,#EFF5FF 100%); + background: -o-linear-gradient(left,#ffffff 0,#EFF5FF 100%); + background: linear-gradient(to right,#ffffff 0,#EFF5FF 100%); + background-repeat: repeat-y; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#ffffff,endColorstr=#EFF5FF,GradientType=1); +} +.tabs li .tabs-inner { + color: #0E2D5F; + background-color: #E0ECFF; + background: -webkit-linear-gradient(top,#EFF5FF 0,#E0ECFF 100%); + background: -moz-linear-gradient(top,#EFF5FF 0,#E0ECFF 100%); + background: -o-linear-gradient(top,#EFF5FF 0,#E0ECFF 100%); + background: linear-gradient(to bottom,#EFF5FF 0,#E0ECFF 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#EFF5FF,endColorstr=#E0ECFF,GradientType=0); +} +.tabs-header, +.tabs-tool { + background-color: #E0ECFF; +} +.tabs-header-plain { + background: transparent; +} +.tabs-header, +.tabs-scroller-left, +.tabs-scroller-right, +.tabs-tool, +.tabs, +.tabs-panels, +.tabs li .tabs-inner, +.tabs li.tabs-selected .tabs-inner, +.tabs-header-bottom .tabs li.tabs-selected .tabs-inner, +.tabs-header-left .tabs li.tabs-selected .tabs-inner, +.tabs-header-right .tabs li.tabs-selected .tabs-inner { + border-color: #95B8E7; +} +.tabs-p-tool a:hover, +.tabs li a:hover.tabs-close, +.tabs-scroller-over { + background-color: #eaf2ff; +} +.tabs li.tabs-selected .tabs-inner { + border-bottom: 1px solid #ffffff; +} +.tabs-header-bottom .tabs li.tabs-selected .tabs-inner { + border-top: 1px solid #ffffff; +} +.tabs-header-left .tabs li.tabs-selected .tabs-inner { + border-right: 1px solid #ffffff; +} +.tabs-header-right .tabs li.tabs-selected .tabs-inner { + border-left: 1px solid #ffffff; +} +.tabs-header .tabs-pill li.tabs-selected .tabs-inner { + background: #ffe48d; + color: #000000; + filter: none; + border-color: #95B8E7; +} +.datagrid .panel-body { + overflow: hidden; + position: relative; +} +.datagrid-view { + position: relative; + overflow: hidden; +} +.datagrid-view1, +.datagrid-view2 { + position: absolute; + overflow: hidden; + top: 0; +} +.datagrid-view1 { + left: 0; +} +.datagrid-view2 { + right: 0; +} +.datagrid-mask { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + opacity: 0.3; + filter: alpha(opacity=30); + display: none; +} +.datagrid-mask-msg { + position: absolute; + top: 50%; + margin-top: -20px; + padding: 10px 5px 10px 30px; + width: auto; + height: 16px; + border-width: 2px; + border-style: solid; + display: none; +} +.datagrid-empty { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 25px; + line-height: 25px; + text-align: center; +} +.datagrid-sort-icon { + padding: 0; + display: none; +} +.datagrid-toolbar { + height: auto; + padding: 1px 2px; + border-width: 0 0 1px 0; + border-style: solid; +} +.datagrid-btn-separator { + float: left; + height: 24px; + border-left: 1px solid #ccc; + border-right: 1px solid #fff; + margin: 2px 1px; +} +.datagrid .datagrid-pager { + display: block; + margin: 0; + border-width: 1px 0 0 0; + border-style: solid; +} +.datagrid .datagrid-pager-top { + border-width: 0 0 1px 0; +} +.datagrid-header { + overflow: hidden; + cursor: default; + border-width: 0 0 1px 0; + border-style: solid; +} +.datagrid-header-inner { + float: left; + width: 10000px; +} +.datagrid-header-row, +.datagrid-row { + height: 32px; +} +.datagrid-header td, +.datagrid-body td, +.datagrid-footer td { + border-width: 0 1px 1px 0; + border-style: dotted; + margin: 0; + padding: 0; +} +.datagrid-cell, +.datagrid-cell-group, +.datagrid-header-rownumber, +.datagrid-cell-rownumber { + margin: 0; + padding: 0 4px; + white-space: nowrap; + word-wrap: normal; + overflow: hidden; + height: 18px; + line-height: 18px; + font-size: 14px; +} +.datagrid-header .datagrid-cell { + height: auto; +} +.datagrid-header .datagrid-cell span { + font-size: 14px; +} +.datagrid-cell-group { + text-align: center; + text-overflow: ellipsis; +} +.datagrid-header-rownumber, +.datagrid-cell-rownumber { + width: 30px; + text-align: center; + margin: 0; + padding: 0; +} +.datagrid-body { + margin: 0; + padding: 0; + overflow: auto; + zoom: 1; +} +.datagrid-view1 .datagrid-body-inner { + padding-bottom: 20px; +} +.datagrid-view1 .datagrid-body { + overflow: hidden; +} +.datagrid-footer { + overflow: hidden; +} +.datagrid-footer-inner { + border-width: 1px 0 0 0; + border-style: solid; + width: 10000px; + float: left; +} +.datagrid-row-editing .datagrid-cell { + height: auto; +} +.datagrid-header-check, +.datagrid-cell-check { + padding: 0; + width: 27px; + height: 18px; + font-size: 1px; + text-align: center; + overflow: hidden; +} +.datagrid-header-check input, +.datagrid-cell-check input { + margin: 0; + padding: 0; + width: 15px; + height: 18px; +} +.datagrid-resize-proxy { + position: absolute; + width: 1px; + height: 10000px; + top: 0; + cursor: e-resize; + display: none; +} +.datagrid-body .datagrid-editable { + margin: 0; + padding: 0; +} +.datagrid-body .datagrid-editable table { + width: 100%; + height: 100%; +} +.datagrid-body .datagrid-editable td { + border: 0; + margin: 0; + padding: 0; +} +.datagrid-view .datagrid-editable-input { + margin: 0; + padding: 2px 4px; + border: 1px solid #95B8E7; + font-size: 14px; + outline-style: none; + -moz-border-radius: 0 0 0 0; + -webkit-border-radius: 0 0 0 0; + border-radius: 0 0 0 0; +} +.datagrid-view .validatebox-invalid { + border-color: #ffa8a8; +} +.datagrid-sort .datagrid-sort-icon { + display: inline; + padding: 0 13px 0 0; + background: url('images/datagrid_icons.png') no-repeat -64px center; +} +.datagrid-sort-desc .datagrid-sort-icon { + display: inline; + padding: 0 13px 0 0; + background: url('images/datagrid_icons.png') no-repeat -16px center; +} +.datagrid-sort-asc .datagrid-sort-icon { + display: inline; + padding: 0 13px 0 0; + background: url('images/datagrid_icons.png') no-repeat 0px center; +} +.datagrid-row-collapse { + background: url('images/datagrid_icons.png') no-repeat -48px center; +} +.datagrid-row-expand { + background: url('images/datagrid_icons.png') no-repeat -32px center; +} +.datagrid-mask-msg { + background: #ffffff url('images/loading.gif') no-repeat scroll 5px center; +} +.datagrid-header, +.datagrid-td-rownumber { + background-color: #efefef; + background: -webkit-linear-gradient(top,#F9F9F9 0,#efefef 100%); + background: -moz-linear-gradient(top,#F9F9F9 0,#efefef 100%); + background: -o-linear-gradient(top,#F9F9F9 0,#efefef 100%); + background: linear-gradient(to bottom,#F9F9F9 0,#efefef 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#F9F9F9,endColorstr=#efefef,GradientType=0); +} +.datagrid-cell-rownumber { + color: #000000; +} +.datagrid-resize-proxy { + background: #aac5e7; +} +.datagrid-mask { + background: #ccc; +} +.datagrid-mask-msg { + border-color: #95B8E7; +} +.datagrid-toolbar, +.datagrid-pager { + background: #F4F4F4; +} +.datagrid-header, +.datagrid-toolbar, +.datagrid-pager, +.datagrid-footer-inner { + border-color: #dddddd; +} +.datagrid-header td, +.datagrid-body td, +.datagrid-footer td { + border-color: #ccc; +} +.datagrid-htable, +.datagrid-btable, +.datagrid-ftable { + color: #000000; + border-collapse: separate; +} +.datagrid-row-alt { + background: #fafafa; +} +.datagrid-row-over, +.datagrid-header td.datagrid-header-over { + background: #eaf2ff; + color: #000000; + cursor: default; +} +.datagrid-row-selected { + background: #ffe48d; + color: #000000; +} +.datagrid-row-editing .textbox, +.datagrid-row-editing .textbox-text { + -moz-border-radius: 0 0 0 0; + -webkit-border-radius: 0 0 0 0; + border-radius: 0 0 0 0; +} +.datagrid-header .datagrid-filter-row td.datagrid-header-over { + background: inherit; +} +.datagrid-split-proxy { + position: absolute; + left: 0; + top: 0; + width: 1px; + height: 100%; + border-left: 1px solid #ffab3f; +} +.datagrid-moving-proxy { + border: 1px solid #ffab3f; + height: 32px; + line-height: 32px; + padding: 0 4px; +} +.propertygrid .datagrid-view1 .datagrid-body td { + padding-bottom: 1px; + border-width: 0 1px 0 0; +} +.propertygrid .datagrid-group { + overflow: hidden; + border-width: 0 0 1px 0; + border-style: solid; +} +.propertygrid .datagrid-group span { + font-weight: bold; +} +.propertygrid .datagrid-view1 .datagrid-body td { + border-color: #dddddd; +} +.propertygrid .datagrid-view1 .datagrid-group { + border-color: #E0ECFF; +} +.propertygrid .datagrid-view2 .datagrid-group { + border-color: #dddddd; +} +.propertygrid .datagrid-group, +.propertygrid .datagrid-view1 .datagrid-body, +.propertygrid .datagrid-view1 .datagrid-row-over, +.propertygrid .datagrid-view1 .datagrid-row-selected { + background: #E0ECFF; +} +.datalist .datagrid-header { + border-width: 0; +} +.datalist .datagrid-group, +.m-list .m-list-group { + height: 25px; + line-height: 25px; + font-weight: bold; + overflow: hidden; + background-color: #efefef; + border-style: solid; + border-width: 0 0 1px 0; + border-color: #ccc; +} +.datalist .datagrid-group-expander { + display: none; +} +.datalist .datagrid-group-title { + padding: 0 4px; +} +.datalist .datagrid-btable { + width: 100%; + table-layout: fixed; +} +.datalist .datagrid-row td { + border-style: solid; + border-left-color: transparent; + border-right-color: transparent; + border-bottom-width: 0; +} +.datalist-lines .datagrid-row td { + border-bottom-width: 1px; +} +.datalist .datagrid-cell, +.m-list li { + width: auto; + height: auto; + padding: 2px 4px; + line-height: 18px; + position: relative; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} +.datalist-link, +.m-list li>a { + display: block; + position: relative; + cursor: pointer; + color: #000000; + text-decoration: none; + overflow: hidden; + margin: -2px -4px; + padding: 2px 4px; + padding-right: 16px; + line-height: 18px; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} +.datalist-link::after, +.m-list li>a::after { + position: absolute; + display: block; + width: 8px; + height: 8px; + content: ''; + right: 6px; + top: 50%; + margin-top: -4px; + border-style: solid; + border-width: 1px 1px 0 0; + -ms-transform: rotate(45deg); + -moz-transform: rotate(45deg); + -webkit-transform: rotate(45deg); + -o-transform: rotate(45deg); + transform: rotate(45deg); +} +.m-list { + margin: 0; + padding: 0; + list-style: none; +} +.m-list li { + border-style: solid; + border-width: 0 0 1px 0; + border-color: #ccc; +} +.m-list li>a:hover { + background: #eaf2ff; + color: #000000; +} +.m-list .m-list-group { + padding: 0 4px; +} +.pagination { + zoom: 1; + padding: 2px; +} +.pagination table { + float: left; + height: 30px; +} +.pagination td { + border: 0; +} +.pagination-btn-separator { + float: left; + height: 24px; + border-left: 1px solid #ccc; + border-right: 1px solid #fff; + margin: 3px 1px; +} +.pagination .pagination-num { + border-width: 1px; + border-style: solid; + margin: 0 2px; + padding: 2px; + width: 3em; + height: auto; + text-align: center; + font-size: 14px; +} +.pagination-page-list { + margin: 0px 6px; + padding: 1px 2px; + width: auto; + height: auto; + border-width: 1px; + border-style: solid; +} +.pagination-info { + float: right; + margin: 0 6px; + padding: 0; + height: 30px; + line-height: 30px; + font-size: 14px; +} +.pagination span { + font-size: 14px; +} +.pagination-link .l-btn-text { + box-sizing: border-box; + text-align: center; + margin: 0; + padding: 0 .5em; + width: auto; + min-width: 28px; +} +.pagination-first { + background: url('images/pagination_icons.png') no-repeat 0 center; +} +.pagination-prev { + background: url('images/pagination_icons.png') no-repeat -16px center; +} +.pagination-next { + background: url('images/pagination_icons.png') no-repeat -32px center; +} +.pagination-last { + background: url('images/pagination_icons.png') no-repeat -48px center; +} +.pagination-load { + background: url('images/pagination_icons.png') no-repeat -64px center; +} +.pagination-loading { + background: url('images/loading.gif') no-repeat center center; +} +.pagination-page-list, +.pagination .pagination-num { + border-color: #95B8E7; +} +.calendar { + border-width: 1px; + border-style: solid; + padding: 1px; + overflow: hidden; +} +.calendar table { + table-layout: fixed; + border-collapse: separate; + font-size: 14px; + width: 100%; + height: 100%; +} +.calendar table td, +.calendar table th { + font-size: 14px; +} +.calendar-noborder { + border: 0; +} +.calendar-header { + position: relative; + height: 36px; +} +.calendar-title { + text-align: center; + height: 36px; + line-height: 36px; +} +.calendar-title span { + position: relative; + display: inline-block; + top: 0px; + padding: 0 3px; + height: 28px; + line-height: 28px; + font-size: 14px; + cursor: pointer; + -moz-border-radius: 5px 5px 5px 5px; + -webkit-border-radius: 5px 5px 5px 5px; + border-radius: 5px 5px 5px 5px; +} +.calendar-prevmonth, +.calendar-nextmonth, +.calendar-prevyear, +.calendar-nextyear { + position: absolute; + top: 50%; + margin-top: -8px; + width: 16px; + height: 16px; + cursor: pointer; + font-size: 1px; + -moz-border-radius: 5px 5px 5px 5px; + -webkit-border-radius: 5px 5px 5px 5px; + border-radius: 5px 5px 5px 5px; +} +.calendar-prevmonth { + left: 30px; + background: url('images/calendar_arrows.png') no-repeat -16px 0; +} +.calendar-nextmonth { + right: 30px; + background: url('images/calendar_arrows.png') no-repeat -32px 0; +} +.calendar-prevyear { + left: 10px; + background: url('images/calendar_arrows.png') no-repeat 0px 0; +} +.calendar-nextyear { + right: 10px; + background: url('images/calendar_arrows.png') no-repeat -48px 0; +} +.calendar-body { + position: relative; +} +.calendar-body th, +.calendar-body td { + text-align: center; +} +.calendar-day { + border: 0; + padding: 1px; + cursor: pointer; + -moz-border-radius: 5px 5px 5px 5px; + -webkit-border-radius: 5px 5px 5px 5px; + border-radius: 5px 5px 5px 5px; +} +.calendar-other-month { + opacity: 0.3; + filter: alpha(opacity=30); +} +.calendar-disabled { + opacity: 0.6; + filter: alpha(opacity=60); + cursor: default; +} +.calendar-menu { + position: absolute; + top: 0; + left: 0; + width: 180px; + height: 150px; + padding: 5px; + font-size: 14px; + display: none; + overflow: hidden; +} +.calendar-menu-year-inner { + text-align: center; + padding-bottom: 5px; +} +.calendar-menu-year { + width: 80px; + line-height: 26px; + text-align: center; + border-width: 1px; + border-style: solid; + outline-style: none; + resize: none; + margin: 0; + padding: 0; + font-weight: bold; + font-size: 14px; + -moz-border-radius: 5px 5px 5px 5px; + -webkit-border-radius: 5px 5px 5px 5px; + border-radius: 5px 5px 5px 5px; +} +.calendar-menu-prev, +.calendar-menu-next { + display: inline-block; + width: 25px; + height: 28px; + vertical-align: top; + cursor: pointer; + -moz-border-radius: 5px 5px 5px 5px; + -webkit-border-radius: 5px 5px 5px 5px; + border-radius: 5px 5px 5px 5px; +} +.calendar-menu-prev { + margin-right: 10px; + background: url('images/calendar_arrows.png') no-repeat 5px center; +} +.calendar-menu-next { + margin-left: 10px; + background: url('images/calendar_arrows.png') no-repeat -44px center; +} +.calendar-menu-month { + text-align: center; + cursor: pointer; + font-weight: bold; + -moz-border-radius: 5px 5px 5px 5px; + -webkit-border-radius: 5px 5px 5px 5px; + border-radius: 5px 5px 5px 5px; +} +.calendar-body th, +.calendar-menu-month { + color: #4d4d4d; +} +.calendar-day { + color: #000000; +} +.calendar-sunday { + color: #CC2222; +} +.calendar-saturday { + color: #00ee00; +} +.calendar-today { + color: #0000ff; +} +.calendar-menu-year { + border-color: #95B8E7; +} +.calendar { + border-color: #95B8E7; +} +.calendar-header { + background: #E0ECFF; +} +.calendar-body, +.calendar-menu { + background: #ffffff; +} +.calendar-body th { + background: #F4F4F4; + padding: 4px 0; +} +.calendar-hover, +.calendar-nav-hover, +.calendar-menu-hover { + background-color: #eaf2ff; + color: #000000; +} +.calendar-hover { + border: 1px solid #b7d2ff; + padding: 0; +} +.calendar-selected { + background-color: #ffe48d; + color: #000000; + border: 1px solid #ffab3f; + padding: 0; +} +.calendar-info { + background-color: #E0ECFF; + font-size: 28px; + height: 70px; + padding: 10px 20px; +} +.calendar-info .year { + font-size: 16px; +} +.datebox-calendar-inner { + height: 250px; +} +.datebox-button { + padding: 4px 0; + text-align: center; +} +.datebox-button a { + line-height: 22px; + font-size: 14px; + font-weight: bold; + text-decoration: none; + opacity: 0.6; + filter: alpha(opacity=60); +} +.datebox-button a:hover { + opacity: 1.0; + filter: alpha(opacity=100); +} +.datebox-current, +.datebox-close { + float: left; +} +.datebox-close { + float: right; +} +.datebox .combo-arrow { + background-image: url('images/datebox_arrow.png'); + background-position: center center; +} +.datebox-button { + background-color: #F4F4F4; +} +.datebox-button a { + color: #444; +} +.spinner-arrow { + display: inline-block; + overflow: hidden; + vertical-align: top; + margin: 0; + padding: 0; + opacity: 1.0; + filter: alpha(opacity=100); + width: 18px; +} +.spinner-arrow.spinner-button-top, +.spinner-arrow.spinner-button-bottom, +.spinner-arrow.spinner-button-left, +.spinner-arrow.spinner-button-right { + background-color: #E0ECFF; +} +.spinner-arrow-up, +.spinner-arrow-down { + opacity: 0.6; + filter: alpha(opacity=60); + display: block; + font-size: 1px; + width: 18px; + height: 10px; + width: 100%; + height: 50%; + color: #444; + outline-style: none; + background-color: #E0ECFF; +} +.spinner-button-updown { + opacity: 1.0; +} +.spinner-button-updown .spinner-button-top, +.spinner-button-updown .spinner-button-bottom { + position: relative; + display: block; + width: 100%; + height: 50%; +} +.spinner-button-updown .spinner-arrow-up, +.spinner-button-updown .spinner-arrow-down { + opacity: 1.0; + filter: alpha(opacity=100); + cursor: pointer; + width: 16px; + height: 16px; + top: 50%; + left: 50%; + margin-top: -8px; + margin-left: -8px; + position: absolute; +} +.spinner-button-updown .spinner-button-top, +.spinner-button-updown .spinner-button-bottom { + cursor: pointer; + opacity: 0.6; + filter: alpha(opacity=60); +} +.spinner-button-updown .spinner-button-top:hover, +.spinner-button-updown .spinner-button-bottom:hover { + opacity: 1.0; + filter: alpha(opacity=100); +} +.spinner-button-updown .spinner-arrow-up, +.spinner-button-updown .spinner-arrow-down, +.spinner-button-updown .spinner-arrow-up:hover, +.spinner-button-updown .spinner-arrow-down:hover { + background-color: transparent; +} +.spinner-arrow-hover { + background-color: #eaf2ff; + opacity: 1.0; + filter: alpha(opacity=100); +} +.spinner-button-top:hover, +.spinner-button-bottom:hover, +.spinner-button-left:hover, +.spinner-button-right:hover, +.spinner-arrow-up:hover, +.spinner-arrow-down:hover { + opacity: 1.0; + filter: alpha(opacity=100); + background-color: #eaf2ff; +} +.textbox-disabled .spinner-button-top:hover, +.textbox-disabled .spinner-button-bottom:hover, +.textbox-disabled .spinner-button-left:hover, +.textbox-disabled .spinner-button-right:hover, +.textbox-icon-disabled .spinner-arrow-up:hover, +.textbox-icon-disabled .spinner-arrow-down:hover { + opacity: 0.6; + filter: alpha(opacity=60); + background-color: #E0ECFF; + cursor: default; +} +.spinner .textbox-icon-disabled { + opacity: 0.6; + filter: alpha(opacity=60); +} +.spinner-arrow-up { + background: url('images/spinner_arrows.png') no-repeat 1px center; + background-color: #E0ECFF; +} +.spinner-arrow-down { + background: url('images/spinner_arrows.png') no-repeat -15px center; + background-color: #E0ECFF; +} +.spinner-button-up { + background: url('images/spinner_arrows.png') no-repeat -32px center; +} +.spinner-button-down { + background: url('images/spinner_arrows.png') no-repeat -48px center; +} +.progressbar { + border-width: 1px; + border-style: solid; + -moz-border-radius: 5px 5px 5px 5px; + -webkit-border-radius: 5px 5px 5px 5px; + border-radius: 5px 5px 5px 5px; + overflow: hidden; + position: relative; +} +.progressbar-text { + text-align: center; + position: absolute; +} +.progressbar-value { + position: relative; + overflow: hidden; + width: 0; + -moz-border-radius: 5px 0 0 5px; + -webkit-border-radius: 5px 0 0 5px; + border-radius: 5px 0 0 5px; +} +.progressbar { + border-color: #95B8E7; +} +.progressbar-text { + color: #000000; + font-size: 14px; +} +.progressbar-value, +.progressbar-value .progressbar-text { + background-color: #ffe48d; + color: #000000; +} +.searchbox-button { + width: 18px; + height: 20px; + overflow: hidden; + display: inline-block; + vertical-align: top; + cursor: pointer; + opacity: 0.6; + filter: alpha(opacity=60); +} +.searchbox-button-hover { + opacity: 1.0; + filter: alpha(opacity=100); +} +.searchbox .l-btn-plain { + border: 0; + padding: 0; + vertical-align: top; + opacity: 0.6; + filter: alpha(opacity=60); + -moz-border-radius: 0 0 0 0; + -webkit-border-radius: 0 0 0 0; + border-radius: 0 0 0 0; +} +.searchbox .l-btn-plain:hover { + border: 0; + padding: 0; + opacity: 1.0; + filter: alpha(opacity=100); + -moz-border-radius: 0 0 0 0; + -webkit-border-radius: 0 0 0 0; + border-radius: 0 0 0 0; +} +.searchbox a.m-btn-plain-active { + -moz-border-radius: 0 0 0 0; + -webkit-border-radius: 0 0 0 0; + border-radius: 0 0 0 0; +} +.searchbox .m-btn-active { + border-width: 0 1px 0 0; + -moz-border-radius: 0 0 0 0; + -webkit-border-radius: 0 0 0 0; + border-radius: 0 0 0 0; +} +.searchbox .textbox-button-right { + border-width: 0 0 0 1px; +} +.searchbox .textbox-button-left { + border-width: 0 1px 0 0; +} +.searchbox-button { + background: url('images/searchbox_button.png') no-repeat center center; +} +.searchbox .l-btn-plain { + background: #E0ECFF; +} +.searchbox .l-btn-plain-disabled, +.searchbox .l-btn-plain-disabled:hover { + opacity: 0.5; + filter: alpha(opacity=50); +} +.slider-disabled { + opacity: 0.5; + filter: alpha(opacity=50); +} +.slider-h { + height: 22px; +} +.slider-v { + width: 22px; +} +.slider-inner { + position: relative; + height: 6px; + top: 7px; + border-width: 1px; + border-style: solid; + border-radius: 5px; +} +.slider-handle { + position: absolute; + display: block; + outline: none; + width: 20px; + height: 20px; + top: 50%; + margin-top: -10px; + margin-left: -10px; +} +.slider-tip { + position: absolute; + display: inline-block; + line-height: 12px; + font-size: 14px; + white-space: nowrap; + top: -22px; +} +.slider-rule { + position: relative; + top: 15px; +} +.slider-rule span { + position: absolute; + display: inline-block; + font-size: 0; + height: 5px; + border-width: 0 0 0 1px; + border-style: solid; +} +.slider-rulelabel { + position: relative; + top: 20px; +} +.slider-rulelabel span { + position: absolute; + display: inline-block; + font-size: 14px; +} +.slider-v .slider-inner { + width: 6px; + left: 7px; + top: 0; + float: left; +} +.slider-v .slider-handle { + left: 50%; + margin-top: -10px; +} +.slider-v .slider-tip { + left: -10px; + margin-top: -6px; +} +.slider-v .slider-rule { + float: left; + top: 0; + left: 16px; +} +.slider-v .slider-rule span { + width: 5px; + height: 'auto'; + border-left: 0; + border-width: 1px 0 0 0; + border-style: solid; +} +.slider-v .slider-rulelabel { + float: left; + top: 0; + left: 23px; +} +.slider-handle { + background: url('images/slider_handle.png') no-repeat; +} +.slider-inner { + border-color: #95B8E7; + background: #E0ECFF; +} +.slider-rule span { + border-color: #95B8E7; +} +.slider-rulelabel span { + color: #000000; +} +.menu { + position: absolute; + margin: 0; + padding: 2px; + border-width: 1px; + border-style: solid; + overflow: hidden; +} +.menu-inline { + position: relative; +} +.menu-item { + position: relative; + margin: 0; + padding: 0; + overflow: hidden; + white-space: nowrap; + cursor: pointer; + border-width: 1px; + border-style: solid; +} +.menu-text { + height: 20px; + line-height: 20px; + float: left; + padding-left: 28px; +} +.menu-icon { + position: absolute; + width: 16px; + height: 16px; + left: 2px; + top: 50%; + margin-top: -8px; +} +.menu-rightarrow { + position: absolute; + width: 16px; + height: 16px; + right: 0; + top: 50%; + margin-top: -8px; +} +.menu-line { + position: absolute; + left: 26px; + top: 0; + height: 2000px; + font-size: 1px; +} +.menu-sep { + margin: 3px 0px 3px 25px; + font-size: 1px; +} +.menu-noline .menu-line { + display: none; +} +.menu-noline .menu-sep { + margin-left: 0; + margin-right: 0; +} +.menu-active { + -moz-border-radius: 5px 5px 5px 5px; + -webkit-border-radius: 5px 5px 5px 5px; + border-radius: 5px 5px 5px 5px; +} +.menu-item-disabled { + opacity: 0.5; + filter: alpha(opacity=50); + cursor: default; +} +.menu-text, +.menu-text span { + font-size: 14px; +} +.menu-shadow { + position: absolute; + -moz-border-radius: 5px 5px 5px 5px; + -webkit-border-radius: 5px 5px 5px 5px; + border-radius: 5px 5px 5px 5px; + background: #ccc; + -moz-box-shadow: 2px 2px 3px #cccccc; + -webkit-box-shadow: 2px 2px 3px #cccccc; + box-shadow: 2px 2px 3px #cccccc; + filter: progid:DXImageTransform.Microsoft.Blur(pixelRadius=2,MakeShadow=false,ShadowOpacity=0.2); +} +.menu-rightarrow { + background: url('images/menu_arrows.png') no-repeat -32px center; +} +.menu-line { + border-left: 1px solid #ccc; + border-right: 1px solid #fff; +} +.menu-sep { + border-top: 1px solid #ccc; + border-bottom: 1px solid #fff; +} +.menu { + background-color: #fafafa; + border-color: #ddd; + color: #444; +} +.menu-content { + background: #ffffff; +} +.menu-item { + border-color: transparent; + _border-color: #fafafa; +} +.menu-active { + border-color: #b7d2ff; + color: #000000; + background: #eaf2ff; +} +.menu-active-disabled { + border-color: transparent; + background: transparent; + color: #444; +} +.m-btn-downarrow, +.s-btn-downarrow { + display: inline-block; + position: absolute; + width: 16px; + height: 16px; + font-size: 1px; + right: 0; + top: 50%; + margin-top: -8px; +} +.m-btn-active, +.s-btn-active { + background: #eaf2ff; + color: #000000; + border: 1px solid #b7d2ff; + filter: none; +} +.m-btn-plain-active, +.s-btn-plain-active { + background: transparent; + padding: 0; + border-width: 1px; + border-style: solid; + -moz-border-radius: 5px 5px 5px 5px; + -webkit-border-radius: 5px 5px 5px 5px; + border-radius: 5px 5px 5px 5px; +} +.m-btn .l-btn-left .l-btn-text { + margin-right: 20px; +} +.m-btn .l-btn-icon-right .l-btn-text { + margin-right: 40px; +} +.m-btn .l-btn-icon-right .l-btn-icon { + right: 20px; +} +.m-btn .l-btn-icon-top .l-btn-text { + margin-right: 4px; + margin-bottom: 14px; +} +.m-btn .l-btn-icon-bottom .l-btn-text { + margin-right: 4px; + margin-bottom: 34px; +} +.m-btn .l-btn-icon-bottom .l-btn-icon { + top: auto; + bottom: 20px; +} +.m-btn .l-btn-icon-top .m-btn-downarrow, +.m-btn .l-btn-icon-bottom .m-btn-downarrow { + top: auto; + bottom: 0px; + left: 50%; + margin-left: -8px; +} +.m-btn-line { + display: inline-block; + position: absolute; + font-size: 1px; + display: none; +} +.m-btn .l-btn-left .m-btn-line { + right: 0; + width: 16px; + height: 500px; + border-style: solid; + border-color: #aac5e7; + border-width: 0 0 0 1px; +} +.m-btn .l-btn-icon-top .m-btn-line, +.m-btn .l-btn-icon-bottom .m-btn-line { + left: 0; + bottom: 0; + width: 500px; + height: 16px; + border-width: 1px 0 0 0; +} +.m-btn-large .l-btn-icon-right .l-btn-text { + margin-right: 56px; +} +.m-btn-large .l-btn-icon-bottom .l-btn-text { + margin-bottom: 50px; +} +.m-btn-downarrow, +.s-btn-downarrow { + background: url('images/menu_arrows.png') no-repeat 0 center; +} +.m-btn-plain-active, +.s-btn-plain-active { + border-color: #b7d2ff; + background-color: #eaf2ff; + color: #000000; +} +.s-btn:hover .m-btn-line, +.s-btn-active .m-btn-line, +.s-btn-plain-active .m-btn-line { + display: inline-block; +} +.l-btn:hover .s-btn-downarrow, +.s-btn-active .s-btn-downarrow, +.s-btn-plain-active .s-btn-downarrow { + border-style: solid; + border-color: #aac5e7; + border-width: 0 0 0 1px; +} +.messager-body { + padding: 10px 10px 30px 10px; + overflow: auto; +} +.messager-button { + text-align: center; + padding: 5px; +} +.messager-button .l-btn { + width: 70px; +} +.messager-icon { + float: left; + width: 32px; + height: 32px; + margin: 0 10px 10px 0; +} +.messager-error { + background: url('images/messager_icons.png') no-repeat scroll -64px 0; +} +.messager-info { + background: url('images/messager_icons.png') no-repeat scroll 0 0; +} +.messager-question { + background: url('images/messager_icons.png') no-repeat scroll -32px 0; +} +.messager-warning { + background: url('images/messager_icons.png') no-repeat scroll -96px 0; +} +.messager-progress { + padding: 10px; +} +.messager-p-msg { + margin-bottom: 5px; +} +.messager-body .messager-input { + width: 100%; + padding: 4px 0; + outline-style: none; + border: 1px solid #95B8E7; +} +.window-thinborder .messager-button { + padding-bottom: 8px; +} +.messager-tip { + box-shadow: 0 1px 6px #ccc; + height: auto; +} +.messager-tip .messager-body { + margin: 0; + padding: 0 10px; + height: 40px; + line-height: 40px; +} +.messager-tip .messager-body>.f-row { + align-items: center; +} +.messager-tip .messager-icon { + width: 16px; + height: 16px; + margin: 0 10px 0 0; +} +.messager-tip .messager-info { + background: url('images/messager_icons16.png') no-repeat scroll 0 0; +} +.messager-tip .messager-question { + background: url('images/messager_icons16.png') no-repeat scroll -16px 0; +} +.messager-tip .messager-error { + background: url('images/messager_icons16.png') no-repeat scroll -32px 0; +} +.messager-tip .messager-warning { + background: url('images/messager_icons16.png') no-repeat scroll -48px 0; +} +.tree { + margin: 0; + padding: 0; + list-style-type: none; +} +.tree li { + white-space: nowrap; +} +.tree li ul { + list-style-type: none; + margin: 0; + padding: 0; +} +.tree-node { + height: 26px; + white-space: nowrap; + cursor: pointer; +} +.tree-hit { + cursor: pointer; +} +.tree-expanded, +.tree-collapsed, +.tree-folder, +.tree-file, +.tree-checkbox, +.tree-indent { + display: inline-block; + width: 16px; + height: 18px; + margin: 4px 0; + vertical-align: middle; + overflow: hidden; +} +.tree-expanded { + background: url('images/tree_icons.png') no-repeat -18px 0px; +} +.tree-expanded-hover { + background: url('images/tree_icons.png') no-repeat -50px 0px; +} +.tree-collapsed { + background: url('images/tree_icons.png') no-repeat 0px 0px; +} +.tree-collapsed-hover { + background: url('images/tree_icons.png') no-repeat -32px 0px; +} +.tree-lines .tree-expanded, +.tree-lines .tree-root-first .tree-expanded { + background: url('images/tree_icons.png') no-repeat -144px 0; +} +.tree-lines .tree-collapsed, +.tree-lines .tree-root-first .tree-collapsed { + background: url('images/tree_icons.png') no-repeat -128px 0; +} +.tree-lines .tree-node-last .tree-expanded, +.tree-lines .tree-root-one .tree-expanded { + background: url('images/tree_icons.png') no-repeat -80px 0; +} +.tree-lines .tree-node-last .tree-collapsed, +.tree-lines .tree-root-one .tree-collapsed { + background: url('images/tree_icons.png') no-repeat -64px 0; +} +.tree-line { + background: url('images/tree_icons.png') no-repeat -176px 0; +} +.tree-join { + background: url('images/tree_icons.png') no-repeat -192px 0; +} +.tree-joinbottom { + background: url('images/tree_icons.png') no-repeat -160px 0; +} +.tree-folder { + background: url('images/tree_icons.png') no-repeat -208px 0; +} +.tree-folder-open { + background: url('images/tree_icons.png') no-repeat -224px 0; +} +.tree-file { + background: url('images/tree_icons.png') no-repeat -240px 0; +} +.tree-loading { + background: url('images/loading.gif') no-repeat center center; +} +.tree-checkbox0 { + background: url('images/tree_icons.png') no-repeat -208px -18px; +} +.tree-checkbox1 { + background: url('images/tree_icons.png') no-repeat -224px -18px; +} +.tree-checkbox2 { + background: url('images/tree_icons.png') no-repeat -240px -18px; +} +.tree-title { + font-size: 14px; + display: inline-block; + text-decoration: none; + vertical-align: middle; + white-space: nowrap; + padding: 0 2px; + margin: 4px 0; + height: 18px; + line-height: 18px; +} +.tree-node-proxy { + font-size: 14px; + line-height: 20px; + padding: 0 2px 0 20px; + border-width: 1px; + border-style: solid; + z-index: 9900000; +} +.tree-dnd-icon { + display: inline-block; + position: absolute; + width: 16px; + height: 18px; + left: 2px; + top: 50%; + margin-top: -9px; +} +.tree-dnd-yes { + background: url('images/tree_icons.png') no-repeat -256px 0; +} +.tree-dnd-no { + background: url('images/tree_icons.png') no-repeat -256px -18px; +} +.tree-node-top { + border-top: 1px dotted red; +} +.tree-node-bottom { + border-bottom: 1px dotted red; +} +.tree-node-append .tree-title { + border: 1px dotted red; +} +.tree-editor { + border: 1px solid #95B8E7; + font-size: 14px; + height: 26px; + line-height: 26px; + padding: 0 4px; + margin: 0; + width: 80px; + outline-style: none; + vertical-align: middle; + position: absolute; + top: 0; +} +.tree-node-proxy { + background-color: #ffffff; + color: #000000; + border-color: #95B8E7; +} +.tree-node-hover { + background: #eaf2ff; + color: #000000; +} +.tree-node-selected { + background: #ffe48d; + color: #000000; +} +.tree-node-disabled { + opacity: 0.5; + cursor: default; +} +.tree-node-hidden { + display: none; +} +.inputbox { + display: inline-block; + vertical-align: middle; + overflow: hidden; + white-space: nowrap; + margin: 0; + padding: 0; +} +.validatebox-invalid { + border-color: #ffa8a8; + background-color: #fff3f3; + color: #000; +} +.tooltip { + position: absolute; + display: none; + z-index: 9900000; + outline: none; + opacity: 1; + filter: alpha(opacity=100); + padding: 5px; + border-width: 1px; + border-style: solid; + border-radius: 5px; + -moz-border-radius: 5px 5px 5px 5px; + -webkit-border-radius: 5px 5px 5px 5px; + border-radius: 5px 5px 5px 5px; +} +.tooltip-content { + font-size: 14px; +} +.tooltip-arrow-outer, +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + line-height: 0; + font-size: 0; + border-style: solid; + border-width: 6px; + border-color: transparent; +} +.tooltip-arrow { + display: none \9; +} +.tooltip-right .tooltip-arrow-outer { + left: 0; + top: 50%; + margin: -6px 0 0 -13px; +} +.tooltip-right .tooltip-arrow { + left: 0; + top: 50%; + margin: -6px 0 0 -12px; +} +.tooltip-left .tooltip-arrow-outer { + right: 0; + top: 50%; + margin: -6px -13px 0 0; +} +.tooltip-left .tooltip-arrow { + right: 0; + top: 50%; + margin: -6px -12px 0 0; +} +.tooltip-top .tooltip-arrow-outer { + bottom: 0; + left: 50%; + margin: 0 0 -13px -6px; +} +.tooltip-top .tooltip-arrow { + bottom: 0; + left: 50%; + margin: 0 0 -12px -6px; +} +.tooltip-bottom .tooltip-arrow-outer { + top: 0; + left: 50%; + margin: -13px 0 0 -6px; +} +.tooltip-bottom .tooltip-arrow { + top: 0; + left: 50%; + margin: -12px 0 0 -6px; +} +.tooltip { + background-color: #ffffff; + border-color: #95B8E7; + color: #000000; +} +.tooltip-right .tooltip-arrow-outer { + border-right-color: #95B8E7; +} +.tooltip-right .tooltip-arrow { + border-right-color: #ffffff; +} +.tooltip-left .tooltip-arrow-outer { + border-left-color: #95B8E7; +} +.tooltip-left .tooltip-arrow { + border-left-color: #ffffff; +} +.tooltip-top .tooltip-arrow-outer { + border-top-color: #95B8E7; +} +.tooltip-top .tooltip-arrow { + border-top-color: #ffffff; +} +.tooltip-bottom .tooltip-arrow-outer { + border-bottom-color: #95B8E7; +} +.tooltip-bottom .tooltip-arrow { + border-bottom-color: #ffffff; +} +.switchbutton { + text-decoration: none; + display: inline-block; + overflow: hidden; + vertical-align: middle; + margin: 0; + padding: 0; + cursor: pointer; + background: #bbb; + border: 1px solid #bbb; + -moz-border-radius: 5px 5px 5px 5px; + -webkit-border-radius: 5px 5px 5px 5px; + border-radius: 5px 5px 5px 5px; +} +.switchbutton-inner { + display: inline-block; + overflow: hidden; + position: relative; + top: -1px; + left: -1px; +} +.switchbutton-on, +.switchbutton-off, +.switchbutton-handle { + display: inline-block; + text-align: center; + height: 100%; + float: left; + font-size: 14px; + -moz-border-radius: 5px 5px 5px 5px; + -webkit-border-radius: 5px 5px 5px 5px; + border-radius: 5px 5px 5px 5px; +} +.switchbutton-on { + background: #ffe48d; + color: #000000; +} +.switchbutton-off { + background-color: #ffffff; + color: #000000; +} +.switchbutton-on, +.switchbutton-reversed .switchbutton-off { + -moz-border-radius: 5px 0 0 5px; + -webkit-border-radius: 5px 0 0 5px; + border-radius: 5px 0 0 5px; +} +.switchbutton-off, +.switchbutton-reversed .switchbutton-on { + -moz-border-radius: 0 5px 5px 0; + -webkit-border-radius: 0 5px 5px 0; + border-radius: 0 5px 5px 0; +} +.switchbutton-handle { + position: absolute; + top: 0; + left: 50%; + background-color: #ffffff; + color: #000000; + border: 1px solid #bbb; + -moz-box-shadow: 0 0 3px 0 #bbb; + -webkit-box-shadow: 0 0 3px 0 #bbb; + box-shadow: 0 0 3px 0 #bbb; +} +.switchbutton-value { + position: absolute; + top: 0; + left: -5000px; +} +.switchbutton-disabled { + opacity: 0.5; + filter: alpha(opacity=50); +} +.switchbutton-disabled, +.switchbutton-readonly { + cursor: default; +} +.switchbutton:focus { + -moz-box-shadow: 0 0 3px 0 #bbb; + -webkit-box-shadow: 0 0 3px 0 #bbb; + box-shadow: 0 0 3px 0 #bbb; + outline: none; +} +.radiobutton { + position: relative; + border: 2px solid #ffab3f; + border-radius: 50%; +} +.radiobutton-inner { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + background: #ffab3f; + border-radius: 50%; + transform: scale(.6); +} +.radiobutton-disabled { + opacity: 0.6; +} +.radiobutton-value { + position: absolute; + overflow: hidden; + width: 1px; + height: 1px; + left: -100px; +} +.checkbox { + position: relative; + border: 2px solid #ffab3f; + -moz-border-radius: 5px 5px 5px 5px; + -webkit-border-radius: 5px 5px 5px 5px; + border-radius: 5px 5px 5px 5px; +} +.checkbox-checked { + border: 0; + background: #ffab3f; +} +.checkbox-inner { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; +} +.checkbox path { + stroke-width: 2px; +} +.checkbox-disabled { + opacity: 0.6; +} +.checkbox-value { + position: absolute; + overflow: hidden; + width: 1px; + height: 1px; + left: -100px; +} +.sidemenu .tree-hit { + background-image: none; +} +.sidemenu-default-icon { + background-image: none; + width: 0; +} +.sidemenu .accordion .accordion-header, +.sidemenu .accordion .accordion-body { + border-bottom-color: transparent; + background: transparent; +} +.sidemenu .accordion .accordion-header { + color: #0E2D5F; +} +.sidemenu .accordion-header .panel-title { + height: 30px; + line-height: 30px; + color: #0E2D5F; +} +.sidemenu .accordion-header:hover { + background: #eaf2ff; + color: #0E2D5F; +} +.sidemenu .tree-node-hover { + background: #eaf2ff; + color: #0E2D5F; +} +.sidemenu .tree-node-selected { + border-right: 2px solid #ffab3f; + color: #000000; + background: #ffe48d; +} +.sidemenu .tree-node { + height: 40px; +} +.sidemenu .tree-title { + margin: 11px 0; +} +.sidemenu .tree-node-nonleaf { + position: relative; +} +.sidemenu .tree-node-nonleaf::after { + display: inline-block; + content: ''; + position: absolute; + top: 50%; + margin-top: -8px; + background: url('images/accordion_arrows.png') no-repeat 0 0; + width: 16px; + height: 16px; + right: 5px; +} +.sidemenu .tree-node-nonleaf-collapsed::after { + background: url('images/accordion_arrows.png') no-repeat -16px 0; +} +.sidemenu-collapsed .panel-icon { + left: 50%; + margin-left: -8px; +} +.sidemenu-collapsed .collapsed-icon { + position: relative; +} +.sidemenu-collapsed .collapsed-text { + text-align: center; +} +.sidemenu-tooltip { + padding: 0; + margin: 0 -12px; + border: 0; +} +.sidemenu-tooltip.tooltip-left { + margin: 0 12px; +} +.sidemenu-tooltip .tooltip-arrow-outer, +.sidemenu-tooltip .tooltip-arrow { + display: none; +} +.timepicker-panel .clock-wrap { + position: relative; +} +.timepicker-panel .clock { + position: relative; + background: #E0ECFF; + color: #0E2D5F; + border-radius: 50%; + position: absolute; + left: 50%; + top: 50%; +} +.timepicker-panel .clock .item { + width: 32px; + height: 32px; + left: 50%; + top: 50%; + margin-left: -16px; + margin-top: -16px; + position: absolute; + user-select: none; + border-radius: 50%; + z-index: 9; + cursor: pointer; +} +.timepicker-panel .clock .item-selected { + background: #ffab3f; + color: #000000; +} +.timepicker-panel .clock .hand { + width: 2px; + bottom: 50%; + left: 50%; + margin-left: -1px; + top: 20px; + -webkit-transform-origin: center bottom; + transform-origin: center bottom; + position: absolute; + will-change: transform; + z-index: 1; + background-color: #ffab3f; +} +.timepicker-panel .clock .hand .drag { + top: -16px; + left: -15px; + width: 4px; + height: 4px; + border: 14px solid #ffab3f; + position: absolute; + box-sizing: content-box; + border-radius: 100%; + background-color: #000000; +} +.timepicker-panel .clock .center { + top: 50%; + left: 50%; + width: 6px; + height: 6px; + position: absolute; + transform: translate(-50%,-50%); + border-radius: 50%; + background-color: #ffab3f; +} +.timepicker-panel .panel-header { + height: 70px; + border: 0; + font-size: 36px; + position: relative; +} +.timepicker-panel .body { + position: relative; +} +.timepicker-panel .panel-header .ampm { + font-size: 16px; + padding-left: 10px; + position: absolute; + right: 20px; +} +.timepicker-panel .panel-header .sep { + opacity: 0.6; +} +.timepicker-panel .panel-header .title { + cursor: pointer; + opacity: 0.6; +} +.timepicker-panel .panel-header .title:hover { + opacity: 1.0; +} +.timepicker-panel .panel-header .title-selected, +.timepicker-panel .panel-header .title-selected:hover { + cursor: default; + opacity: 1.0; +} diff --git a/system-framework/system-plugin-example-web/src/main/resources/static/jquery-2.0.0.min.js b/system-framework/system-plugin-example-web/src/main/resources/static/jquery-2.0.0.min.js new file mode 100644 index 0000000..1c3e55b --- /dev/null +++ b/system-framework/system-plugin-example-web/src/main/resources/static/jquery-2.0.0.min.js @@ -0,0 +1,6 @@ +/*! jQuery v2.0.0 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license +//@ sourceMappingURL=jquery.min.map +*/ +(function(e,undefined){var t,n,r=typeof undefined,i=e.location,o=e.document,s=o.documentElement,a=e.jQuery,u=e.$,l={},c=[],f="2.0.0",p=c.concat,h=c.push,d=c.slice,g=c.indexOf,m=l.toString,y=l.hasOwnProperty,v=f.trim,x=function(e,n){return new x.fn.init(e,n,t)},b=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,w=/\S+/g,T=/^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,k=/^-ms-/,N=/-([\da-z])/gi,E=function(e,t){return t.toUpperCase()},S=function(){o.removeEventListener("DOMContentLoaded",S,!1),e.removeEventListener("load",S,!1),x.ready()};x.fn=x.prototype={jquery:f,constructor:x,init:function(e,t,n){var r,i;if(!e)return this;if("string"==typeof e){if(r="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:T.exec(e),!r||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof x?t[0]:t,x.merge(this,x.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:o,!0)),C.test(r[1])&&x.isPlainObject(t))for(r in t)x.isFunction(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return i=o.getElementById(r[2]),i&&i.parentNode&&(this.length=1,this[0]=i),this.context=o,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?n.ready(e):(e.selector!==undefined&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return d.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,t,n,r,i,o,s=arguments[0]||{},a=1,u=arguments.length,l=!1;for("boolean"==typeof s&&(l=s,s=arguments[1]||{},a=2),"object"==typeof s||x.isFunction(s)||(s={}),u===a&&(s=this,--a);u>a;a++)if(null!=(e=arguments[a]))for(t in e)n=s[t],r=e[t],s!==r&&(l&&r&&(x.isPlainObject(r)||(i=x.isArray(r)))?(i?(i=!1,o=n&&x.isArray(n)?n:[]):o=n&&x.isPlainObject(n)?n:{},s[t]=x.extend(l,o,r)):r!==undefined&&(s[t]=r));return s},x.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=a),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){(e===!0?--x.readyWait:x.isReady)||(x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(o,[x]),x.fn.trigger&&x(o).trigger("ready").off("ready")))},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray,isWindow:function(e){return null!=e&&e===e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[m.call(e)]||"object":typeof e},isPlainObject:function(e){if("object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!y.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(t){return!1}return!0},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||o;var r=C.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:JSON.parse,parseXML:function(e){var t,n;if(!e||"string"!=typeof e)return null;try{n=new DOMParser,t=n.parseFromString(e,"text/xml")}catch(r){t=undefined}return(!t||t.getElementsByTagName("parsererror").length)&&x.error("Invalid XML: "+e),t},noop:function(){},globalEval:function(e){var t,n=eval;e=x.trim(e),e&&(1===e.indexOf("use strict")?(t=o.createElement("script"),t.text=e,o.head.appendChild(t).parentNode.removeChild(t)):n(e))},camelCase:function(e){return e.replace(k,"ms-").replace(N,E)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,s=j(e);if(n){if(s){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(s){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:function(e){return null==e?"":v.call(e)},makeArray:function(e,t){var n=t||[];return null!=e&&(j(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:g.call(t,e,n)},merge:function(e,t){var n=t.length,r=e.length,i=0;if("number"==typeof n)for(;n>i;i++)e[r++]=t[i];else while(t[i]!==undefined)e[r++]=t[i++];return e.length=r,e},grep:function(e,t,n){var r,i=[],o=0,s=e.length;for(n=!!n;s>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,s=j(e),a=[];if(s)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(a[a.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(a[a.length]=r);return p.apply([],a)},guid:1,proxy:function(e,t){var n,r,i;return"string"==typeof t&&(n=e[t],t=e,e=n),x.isFunction(e)?(r=d.call(arguments,2),i=function(){return e.apply(t||this,r.concat(d.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):undefined},access:function(e,t,n,r,i,o,s){var a=0,u=e.length,l=null==n;if("object"===x.type(n)){i=!0;for(a in n)x.access(e,t,a,n[a],!0,o,s)}else if(r!==undefined&&(i=!0,x.isFunction(r)||(s=!0),l&&(s?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(x(e),n)})),t))for(;u>a;a++)t(e[a],n,s?r:r.call(e[a],a,t(e[a],n)));return i?e:l?t.call(e):u?t(e[0],n):o},now:Date.now,swap:function(e,t,n,r){var i,o,s={};for(o in t)s[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=s[o];return i}}),x.ready.promise=function(t){return n||(n=x.Deferred(),"complete"===o.readyState?setTimeout(x.ready):(o.addEventListener("DOMContentLoaded",S,!1),e.addEventListener("load",S,!1))),n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){l["[object "+t+"]"]=t.toLowerCase()});function j(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}t=x(o),function(e,undefined){var t,n,r,i,o,s,a,u,l,c,f,p,h,d,g,m,y="sizzle"+-new Date,v=e.document,b={},w=0,T=0,C=ot(),k=ot(),N=ot(),E=!1,S=function(){return 0},j=typeof undefined,D=1<<31,A=[],L=A.pop,q=A.push,H=A.push,O=A.slice,F=A.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},P="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",R="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",W=M.replace("w","w#"),$="\\["+R+"*("+M+")"+R+"*(?:([*^$|!~]?=)"+R+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+W+")|)|)"+R+"*\\]",B=":("+M+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+$.replace(3,8)+")*)|.*)\\)|)",I=RegExp("^"+R+"+|((?:^|[^\\\\])(?:\\\\.)*)"+R+"+$","g"),z=RegExp("^"+R+"*,"+R+"*"),_=RegExp("^"+R+"*([>+~]|"+R+")"+R+"*"),X=RegExp(R+"*[+~]"),U=RegExp("="+R+"*([^\\]'\"]*)"+R+"*\\]","g"),Y=RegExp(B),V=RegExp("^"+W+"$"),G={ID:RegExp("^#("+M+")"),CLASS:RegExp("^\\.("+M+")"),TAG:RegExp("^("+M.replace("w","w*")+")"),ATTR:RegExp("^"+$),PSEUDO:RegExp("^"+B),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+R+"*(even|odd|(([+-]|)(\\d*)n|)"+R+"*(?:([+-]|)"+R+"*(\\d+)|))"+R+"*\\)|)","i"),"boolean":RegExp("^(?:"+P+")$","i"),needsContext:RegExp("^"+R+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+R+"*((?:-\\d)?\\d*)"+R+"*\\)|)(?=[^-]|$)","i")},J=/^[^{]+\{\s*\[native \w/,Q=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,K=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,et=/'|\\/g,tt=/\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g,nt=function(e,t){var n="0x"+t-65536;return n!==n?t:0>n?String.fromCharCode(n+65536):String.fromCharCode(55296|n>>10,56320|1023&n)};try{H.apply(A=O.call(v.childNodes),v.childNodes),A[v.childNodes.length].nodeType}catch(rt){H={apply:A.length?function(e,t){q.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function it(e){return J.test(e+"")}function ot(){var e,t=[];return e=function(n,i){return t.push(n+=" ")>r.cacheLength&&delete e[t.shift()],e[n]=i}}function st(e){return e[y]=!0,e}function at(e){var t=c.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function ut(e,t,n,r){var i,o,s,a,u,f,d,g,x,w;if((t?t.ownerDocument||t:v)!==c&&l(t),t=t||c,n=n||[],!e||"string"!=typeof e)return n;if(1!==(a=t.nodeType)&&9!==a)return[];if(p&&!r){if(i=Q.exec(e))if(s=i[1]){if(9===a){if(o=t.getElementById(s),!o||!o.parentNode)return n;if(o.id===s)return n.push(o),n}else if(t.ownerDocument&&(o=t.ownerDocument.getElementById(s))&&m(t,o)&&o.id===s)return n.push(o),n}else{if(i[2])return H.apply(n,t.getElementsByTagName(e)),n;if((s=i[3])&&b.getElementsByClassName&&t.getElementsByClassName)return H.apply(n,t.getElementsByClassName(s)),n}if(b.qsa&&(!h||!h.test(e))){if(g=d=y,x=t,w=9===a&&e,1===a&&"object"!==t.nodeName.toLowerCase()){f=gt(e),(d=t.getAttribute("id"))?g=d.replace(et,"\\$&"):t.setAttribute("id",g),g="[id='"+g+"'] ",u=f.length;while(u--)f[u]=g+mt(f[u]);x=X.test(e)&&t.parentNode||t,w=f.join(",")}if(w)try{return H.apply(n,x.querySelectorAll(w)),n}catch(T){}finally{d||t.removeAttribute("id")}}}return kt(e.replace(I,"$1"),t,n,r)}o=ut.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},l=ut.setDocument=function(e){var t=e?e.ownerDocument||e:v;return t!==c&&9===t.nodeType&&t.documentElement?(c=t,f=t.documentElement,p=!o(t),b.getElementsByTagName=at(function(e){return e.appendChild(t.createComment("")),!e.getElementsByTagName("*").length}),b.attributes=at(function(e){return e.className="i",!e.getAttribute("className")}),b.getElementsByClassName=at(function(e){return e.innerHTML="

",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),b.sortDetached=at(function(e){return 1&e.compareDocumentPosition(c.createElement("div"))}),b.getById=at(function(e){return f.appendChild(e).id=y,!t.getElementsByName||!t.getElementsByName(y).length}),b.getById?(r.find.ID=function(e,t){if(typeof t.getElementById!==j&&p){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},r.filter.ID=function(e){var t=e.replace(tt,nt);return function(e){return e.getAttribute("id")===t}}):(r.find.ID=function(e,t){if(typeof t.getElementById!==j&&p){var n=t.getElementById(e);return n?n.id===e||typeof n.getAttributeNode!==j&&n.getAttributeNode("id").value===e?[n]:undefined:[]}},r.filter.ID=function(e){var t=e.replace(tt,nt);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),r.find.TAG=b.getElementsByTagName?function(e,t){return typeof t.getElementsByTagName!==j?t.getElementsByTagName(e):undefined}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},r.find.CLASS=b.getElementsByClassName&&function(e,t){return typeof t.getElementsByClassName!==j&&p?t.getElementsByClassName(e):undefined},d=[],h=[],(b.qsa=it(t.querySelectorAll))&&(at(function(e){e.innerHTML="",e.querySelectorAll("[selected]").length||h.push("\\["+R+"*(?:value|"+P+")"),e.querySelectorAll(":checked").length||h.push(":checked")}),at(function(e){var t=c.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&h.push("[*^$]="+R+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||h.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),h.push(",.*:")})),(b.matchesSelector=it(g=f.webkitMatchesSelector||f.mozMatchesSelector||f.oMatchesSelector||f.msMatchesSelector))&&at(function(e){b.disconnectedMatch=g.call(e,"div"),g.call(e,"[s!='']:x"),d.push("!=",B)}),h=h.length&&RegExp(h.join("|")),d=d.length&&RegExp(d.join("|")),m=it(f.contains)||f.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},S=f.compareDocumentPosition?function(e,n){if(e===n)return E=!0,0;var r=n.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(n);return r?1&r||!b.sortDetached&&n.compareDocumentPosition(e)===r?e===t||m(v,e)?-1:n===t||m(v,n)?1:u?F.call(u,e)-F.call(u,n):0:4&r?-1:1:e.compareDocumentPosition?-1:1}:function(e,n){var r,i=0,o=e.parentNode,s=n.parentNode,a=[e],l=[n];if(e===n)return E=!0,0;if(!o||!s)return e===t?-1:n===t?1:o?-1:s?1:u?F.call(u,e)-F.call(u,n):0;if(o===s)return lt(e,n);r=e;while(r=r.parentNode)a.unshift(r);r=n;while(r=r.parentNode)l.unshift(r);while(a[i]===l[i])i++;return i?lt(a[i],l[i]):a[i]===v?-1:l[i]===v?1:0},c):c},ut.matches=function(e,t){return ut(e,null,null,t)},ut.matchesSelector=function(e,t){if((e.ownerDocument||e)!==c&&l(e),t=t.replace(U,"='$1']"),!(!b.matchesSelector||!p||d&&d.test(t)||h&&h.test(t)))try{var n=g.call(e,t);if(n||b.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(r){}return ut(t,c,null,[e]).length>0},ut.contains=function(e,t){return(e.ownerDocument||e)!==c&&l(e),m(e,t)},ut.attr=function(e,t){(e.ownerDocument||e)!==c&&l(e);var n=r.attrHandle[t.toLowerCase()],i=n&&n(e,t,!p);return i===undefined?b.attributes||!p?e.getAttribute(t):(i=e.getAttributeNode(t))&&i.specified?i.value:null:i},ut.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},ut.uniqueSort=function(e){var t,n=[],r=0,i=0;if(E=!b.detectDuplicates,u=!b.sortStable&&e.slice(0),e.sort(S),E){while(t=e[i++])t===e[i]&&(r=n.push(i));while(r--)e.splice(n[r],1)}return e};function lt(e,t){var n=t&&e,r=n&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function ct(e,t,n){var r;return n?undefined:(r=e.getAttributeNode(t))&&r.specified?r.value:e[t]===!0?t.toLowerCase():null}function ft(e,t,n){var r;return n?undefined:r=e.getAttribute(t,"type"===t.toLowerCase()?1:2)}function pt(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function ht(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function dt(e){return st(function(t){return t=+t,st(function(n,r){var i,o=e([],n.length,t),s=o.length;while(s--)n[i=o[s]]&&(n[i]=!(r[i]=n[i]))})})}i=ut.getText=function(e){var t,n="",r=0,o=e.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=i(e)}else if(3===o||4===o)return e.nodeValue}else for(;t=e[r];r++)n+=i(t);return n},r=ut.selectors={cacheLength:50,createPseudo:st,match:G,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(tt,nt),e[3]=(e[4]||e[5]||"").replace(tt,nt),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||ut.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&ut.error(e[0]),e},PSEUDO:function(e){var t,n=!e[5]&&e[2];return G.CHILD.test(e[0])?null:(e[4]?e[2]=e[4]:n&&Y.test(n)&&(t=gt(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(tt,nt).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=C[e+" "];return t||(t=RegExp("(^|"+R+")"+e+"("+R+"|$)"))&&C(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=ut.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),s="last"!==e.slice(-4),a="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,p,h,d,g=o!==s?"nextSibling":"previousSibling",m=t.parentNode,v=a&&t.nodeName.toLowerCase(),x=!u&&!a;if(m){if(o){while(g){f=t;while(f=f[g])if(a?f.nodeName.toLowerCase()===v:1===f.nodeType)return!1;d=g="only"===e&&!d&&"nextSibling"}return!0}if(d=[s?m.firstChild:m.lastChild],s&&x){c=m[y]||(m[y]={}),l=c[e]||[],h=l[0]===w&&l[1],p=l[0]===w&&l[2],f=h&&m.childNodes[h];while(f=++h&&f&&f[g]||(p=h=0)||d.pop())if(1===f.nodeType&&++p&&f===t){c[e]=[w,h,p];break}}else if(x&&(l=(t[y]||(t[y]={}))[e])&&l[0]===w)p=l[1];else while(f=++h&&f&&f[g]||(p=h=0)||d.pop())if((a?f.nodeName.toLowerCase()===v:1===f.nodeType)&&++p&&(x&&((f[y]||(f[y]={}))[e]=[w,p]),f===t))break;return p-=i,p===r||0===p%r&&p/r>=0}}},PSEUDO:function(e,t){var n,i=r.pseudos[e]||r.setFilters[e.toLowerCase()]||ut.error("unsupported pseudo: "+e);return i[y]?i(t):i.length>1?(n=[e,e,"",t],r.setFilters.hasOwnProperty(e.toLowerCase())?st(function(e,n){var r,o=i(e,t),s=o.length;while(s--)r=F.call(e,o[s]),e[r]=!(n[r]=o[s])}):function(e){return i(e,0,n)}):i}},pseudos:{not:st(function(e){var t=[],n=[],r=s(e.replace(I,"$1"));return r[y]?st(function(e,t,n,i){var o,s=r(e,null,i,[]),a=e.length;while(a--)(o=s[a])&&(e[a]=!(t[a]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:st(function(e){return function(t){return ut(e,t).length>0}}),contains:st(function(e){return function(t){return(t.textContent||t.innerText||i(t)).indexOf(e)>-1}}),lang:st(function(e){return V.test(e||"")||ut.error("unsupported lang: "+e),e=e.replace(tt,nt).toLowerCase(),function(t){var n;do if(n=p?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===f},focus:function(e){return e===c.activeElement&&(!c.hasFocus||c.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!r.pseudos.empty(e)},header:function(e){return Z.test(e.nodeName)},input:function(e){return K.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:dt(function(){return[0]}),last:dt(function(e,t){return[t-1]}),eq:dt(function(e,t,n){return[0>n?n+t:n]}),even:dt(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:dt(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:dt(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:dt(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}};for(t in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})r.pseudos[t]=pt(t);for(t in{submit:!0,reset:!0})r.pseudos[t]=ht(t);function gt(e,t){var n,i,o,s,a,u,l,c=k[e+" "];if(c)return t?0:c.slice(0);a=e,u=[],l=r.preFilter;while(a){(!n||(i=z.exec(a)))&&(i&&(a=a.slice(i[0].length)||a),u.push(o=[])),n=!1,(i=_.exec(a))&&(n=i.shift(),o.push({value:n,type:i[0].replace(I," ")}),a=a.slice(n.length));for(s in r.filter)!(i=G[s].exec(a))||l[s]&&!(i=l[s](i))||(n=i.shift(),o.push({value:n,type:s,matches:i}),a=a.slice(n.length));if(!n)break}return t?a.length:a?ut.error(e):k(e,u).slice(0)}function mt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function yt(e,t,r){var i=t.dir,o=r&&"parentNode"===i,s=T++;return t.first?function(t,n,r){while(t=t[i])if(1===t.nodeType||o)return e(t,n,r)}:function(t,r,a){var u,l,c,f=w+" "+s;if(a){while(t=t[i])if((1===t.nodeType||o)&&e(t,r,a))return!0}else while(t=t[i])if(1===t.nodeType||o)if(c=t[y]||(t[y]={}),(l=c[i])&&l[0]===f){if((u=l[1])===!0||u===n)return u===!0}else if(l=c[i]=[f],l[1]=e(t,r,a)||n,l[1]===!0)return!0}}function vt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xt(e,t,n,r,i){var o,s=[],a=0,u=e.length,l=null!=t;for(;u>a;a++)(o=e[a])&&(!n||n(o,r,i))&&(s.push(o),l&&t.push(a));return s}function bt(e,t,n,r,i,o){return r&&!r[y]&&(r=bt(r)),i&&!i[y]&&(i=bt(i,o)),st(function(o,s,a,u){var l,c,f,p=[],h=[],d=s.length,g=o||Ct(t||"*",a.nodeType?[a]:a,[]),m=!e||!o&&t?g:xt(g,p,e,a,u),y=n?i||(o?e:d||r)?[]:s:m;if(n&&n(m,y,a,u),r){l=xt(y,h),r(l,[],a,u),c=l.length;while(c--)(f=l[c])&&(y[h[c]]=!(m[h[c]]=f))}if(o){if(i||e){if(i){l=[],c=y.length;while(c--)(f=y[c])&&l.push(m[c]=f);i(null,y=[],l,u)}c=y.length;while(c--)(f=y[c])&&(l=i?F.call(o,f):p[c])>-1&&(o[l]=!(s[l]=f))}}else y=xt(y===s?y.splice(d,y.length):y),i?i(null,s,y,u):H.apply(s,y)})}function wt(e){var t,n,i,o=e.length,s=r.relative[e[0].type],u=s||r.relative[" "],l=s?1:0,c=yt(function(e){return e===t},u,!0),f=yt(function(e){return F.call(t,e)>-1},u,!0),p=[function(e,n,r){return!s&&(r||n!==a)||((t=n).nodeType?c(e,n,r):f(e,n,r))}];for(;o>l;l++)if(n=r.relative[e[l].type])p=[yt(vt(p),n)];else{if(n=r.filter[e[l].type].apply(null,e[l].matches),n[y]){for(i=++l;o>i;i++)if(r.relative[e[i].type])break;return bt(l>1&&vt(p),l>1&&mt(e.slice(0,l-1)).replace(I,"$1"),n,i>l&&wt(e.slice(l,i)),o>i&&wt(e=e.slice(i)),o>i&&mt(e))}p.push(n)}return vt(p)}function Tt(e,t){var i=0,o=t.length>0,s=e.length>0,u=function(u,l,f,p,h){var d,g,m,y=[],v=0,x="0",b=u&&[],T=null!=h,C=a,k=u||s&&r.find.TAG("*",h&&l.parentNode||l),N=w+=null==C?1:Math.random()||.1;for(T&&(a=l!==c&&l,n=i);null!=(d=k[x]);x++){if(s&&d){g=0;while(m=e[g++])if(m(d,l,f)){p.push(d);break}T&&(w=N,n=++i)}o&&((d=!m&&d)&&v--,u&&b.push(d))}if(v+=x,o&&x!==v){g=0;while(m=t[g++])m(b,y,l,f);if(u){if(v>0)while(x--)b[x]||y[x]||(y[x]=L.call(p));y=xt(y)}H.apply(p,y),T&&!u&&y.length>0&&v+t.length>1&&ut.uniqueSort(p)}return T&&(w=N,a=C),b};return o?st(u):u}s=ut.compile=function(e,t){var n,r=[],i=[],o=N[e+" "];if(!o){t||(t=gt(e)),n=t.length;while(n--)o=wt(t[n]),o[y]?r.push(o):i.push(o);o=N(e,Tt(i,r))}return o};function Ct(e,t,n){var r=0,i=t.length;for(;i>r;r++)ut(e,t[r],n);return n}function kt(e,t,n,i){var o,a,u,l,c,f=gt(e);if(!i&&1===f.length){if(a=f[0]=f[0].slice(0),a.length>2&&"ID"===(u=a[0]).type&&9===t.nodeType&&p&&r.relative[a[1].type]){if(t=(r.find.ID(u.matches[0].replace(tt,nt),t)||[])[0],!t)return n;e=e.slice(a.shift().value.length)}o=G.needsContext.test(e)?0:a.length;while(o--){if(u=a[o],r.relative[l=u.type])break;if((c=r.find[l])&&(i=c(u.matches[0].replace(tt,nt),X.test(a[0].type)&&t.parentNode||t))){if(a.splice(o,1),e=i.length&&mt(a),!e)return H.apply(n,i),n;break}}}return s(e,f)(i,t,!p,n,X.test(e)),n}r.pseudos.nth=r.pseudos.eq;function Nt(){}Nt.prototype=r.filters=r.pseudos,r.setFilters=new Nt,b.sortStable=y.split("").sort(S).join("")===y,l(),[0,0].sort(S),b.detectDuplicates=E,at(function(e){if(e.innerHTML="","#"!==e.firstChild.getAttribute("href")){var t="type|href|height|width".split("|"),n=t.length;while(n--)r.attrHandle[t[n]]=ft}}),at(function(e){if(null!=e.getAttribute("disabled")){var t=P.split("|"),n=t.length;while(n--)r.attrHandle[t[n]]=ct}}),x.find=ut,x.expr=ut.selectors,x.expr[":"]=x.expr.pseudos,x.unique=ut.uniqueSort,x.text=ut.getText,x.isXMLDoc=ut.isXML,x.contains=ut.contains}(e);var D={};function A(e){var t=D[e]={};return x.each(e.match(w)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?D[e]||A(e):x.extend({},e);var t,n,r,i,o,s,a=[],u=!e.once&&[],l=function(f){for(t=e.memory&&f,n=!0,s=i||0,i=0,o=a.length,r=!0;a&&o>s;s++)if(a[s].apply(f[0],f[1])===!1&&e.stopOnFalse){t=!1;break}r=!1,a&&(u?u.length&&l(u.shift()):t?a=[]:c.disable())},c={add:function(){if(a){var n=a.length;(function s(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&c.has(n)||a.push(n):n&&n.length&&"string"!==r&&s(n)})})(arguments),r?o=a.length:t&&(i=n,l(t))}return this},remove:function(){return a&&x.each(arguments,function(e,t){var n;while((n=x.inArray(t,a,n))>-1)a.splice(n,1),r&&(o>=n&&o--,s>=n&&s--)}),this},has:function(e){return e?x.inArray(e,a)>-1:!(!a||!a.length)},empty:function(){return a=[],o=0,this},disable:function(){return a=u=t=undefined,this},disabled:function(){return!a},lock:function(){return u=undefined,t||c.disable(),this},locked:function(){return!u},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],!a||n&&!u||(r?u.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!n}};return c},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var s=o[0],a=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=a&&a.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===r?n.promise():this,a?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var s=o[2],a=o[3];r[o[1]]=s.add,a&&s.add(function(){n=a},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=s.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=d.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),s=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?d.call(arguments):r,n===a?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},a,u,l;if(r>1)for(a=Array(r),u=Array(r),l=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(s(t,l,n)).fail(o.reject).progress(s(t,u,a)):--i;return i||o.resolveWith(l,n),o.promise()}}),x.support=function(t){var n=o.createElement("input"),r=o.createDocumentFragment(),i=o.createElement("div"),s=o.createElement("select"),a=s.appendChild(o.createElement("option"));return n.type?(n.type="checkbox",t.checkOn=""!==n.value,t.optSelected=a.selected,t.reliableMarginRight=!0,t.boxSizingReliable=!0,t.pixelPosition=!1,n.checked=!0,t.noCloneChecked=n.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!a.disabled,n=o.createElement("input"),n.value="t",n.type="radio",t.radioValue="t"===n.value,n.setAttribute("checked","t"),n.setAttribute("name","t"),r.appendChild(n),t.checkClone=r.cloneNode(!0).cloneNode(!0).lastChild.checked,t.focusinBubbles="onfocusin"in e,i.style.backgroundClip="content-box",i.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===i.style.backgroundClip,x(function(){var n,r,s="padding:0;margin:0;border:0;display:block;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box",a=o.getElementsByTagName("body")[0];a&&(n=o.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",a.appendChild(n).appendChild(i),i.innerHTML="",i.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%",x.swap(a,null!=a.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===i.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(i,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(i,null)||{width:"4px"}).width,r=i.appendChild(o.createElement("div")),r.style.cssText=i.style.cssText=s,r.style.marginRight=r.style.width="0",i.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),a.removeChild(n))}),t):t}({});var L,q,H=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,O=/([A-Z])/g;function F(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=x.expando+Math.random()}F.uid=1,F.accepts=function(e){return e.nodeType?1===e.nodeType||9===e.nodeType:!0},F.prototype={key:function(e){if(!F.accepts(e))return 0;var t={},n=e[this.expando];if(!n){n=F.uid++;try{t[this.expando]={value:n},Object.defineProperties(e,t)}catch(r){t[this.expando]=n,x.extend(e,t)}}return this.cache[n]||(this.cache[n]={}),n},set:function(e,t,n){var r,i=this.key(e),o=this.cache[i];if("string"==typeof t)o[t]=n;else if(x.isEmptyObject(o))this.cache[i]=t;else for(r in t)o[r]=t[r]},get:function(e,t){var n=this.cache[this.key(e)];return t===undefined?n:n[t]},access:function(e,t,n){return t===undefined||t&&"string"==typeof t&&n===undefined?this.get(e,t):(this.set(e,t,n),n!==undefined?n:t)},remove:function(e,t){var n,r,i=this.key(e),o=this.cache[i];if(t===undefined)this.cache[i]={};else{x.isArray(t)?r=t.concat(t.map(x.camelCase)):t in o?r=[t]:(r=x.camelCase(t),r=r in o?[r]:r.match(w)||[]),n=r.length;while(n--)delete o[r[n]]}},hasData:function(e){return!x.isEmptyObject(this.cache[e[this.expando]]||{})},discard:function(e){delete this.cache[this.key(e)]}},L=new F,q=new F,x.extend({acceptData:F.accepts,hasData:function(e){return L.hasData(e)||q.hasData(e)},data:function(e,t,n){return L.access(e,t,n)},removeData:function(e,t){L.remove(e,t)},_data:function(e,t,n){return q.access(e,t,n)},_removeData:function(e,t){q.remove(e,t)}}),x.fn.extend({data:function(e,t){var n,r,i=this[0],o=0,s=null;if(e===undefined){if(this.length&&(s=L.get(i),1===i.nodeType&&!q.get(i,"hasDataAttrs"))){for(n=i.attributes;n.length>o;o++)r=n[o].name,0===r.indexOf("data-")&&(r=x.camelCase(r.substring(5)),P(i,r,s[r]));q.set(i,"hasDataAttrs",!0)}return s}return"object"==typeof e?this.each(function(){L.set(this,e)}):x.access(this,function(t){var n,r=x.camelCase(e);if(i&&t===undefined){if(n=L.get(i,e),n!==undefined)return n;if(n=L.get(i,r),n!==undefined)return n;if(n=P(i,r,undefined),n!==undefined)return n}else this.each(function(){var n=L.get(this,r);L.set(this,r,t),-1!==e.indexOf("-")&&n!==undefined&&L.set(this,e,t)})},null,t,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){L.remove(this,e)})}});function P(e,t,n){var r;if(n===undefined&&1===e.nodeType)if(r="data-"+t.replace(O,"-$1").toLowerCase(),n=e.getAttribute(r),"string"==typeof n){try{n="true"===n?!0:"false"===n?!1:"null"===n?null:+n+""===n?+n:H.test(n)?JSON.parse(n):n}catch(i){}L.set(e,t,n)}else n=undefined;return n}x.extend({queue:function(e,t,n){var r;return e?(t=(t||"fx")+"queue",r=q.get(e,t),n&&(!r||x.isArray(n)?r=q.access(e,t,x.makeArray(n)):r.push(n)),r||[]):undefined},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),s=function(){x.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),o.cur=i,i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,s,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return q.get(e,n)||q.access(e,n,{empty:x.Callbacks("once memory").add(function(){q.remove(e,[t+"queue",n])})})}}),x.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),n>arguments.length?x.queue(this[0],e):t===undefined?this:this.each(function(){var n=x.queue(this,e,t); + x._queueHooks(this,e),"fx"===e&&"inprogress"!==n[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,t){var n,r=1,i=x.Deferred(),o=this,s=this.length,a=function(){--r||i.resolveWith(o,[o])};"string"!=typeof e&&(t=e,e=undefined),e=e||"fx";while(s--)n=q.get(o[s],e+"queueHooks"),n&&n.empty&&(r++,n.empty.add(a));return a(),i.promise(t)}});var R,M,W=/[\t\r\n]/g,$=/\r/g,B=/^(?:input|select|textarea|button)$/i;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[x.propFix[e]||e]})},addClass:function(e){var t,n,r,i,o,s=0,a=this.length,u="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];a>s;s++)if(n=this[s],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(W," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,s=0,a=this.length,u=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];a>s;s++)if(n=this[s],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(W," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e,i="boolean"==typeof t;return x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var o,s=0,a=x(this),u=t,l=e.match(w)||[];while(o=l[s++])u=i?u:!a.hasClass(o),a[u?"addClass":"removeClass"](o)}else(n===r||"boolean"===n)&&(this.className&&q.set(this,"__className__",this.className),this.className=this.className||e===!1?"":q.get(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(W," ").indexOf(t)>=0)return!0;return!1},val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=x.isFunction(e),this.each(function(n){var i,o=x(this);1===this.nodeType&&(i=r?e.call(this,n,o.val()):e,null==i?i="":"number"==typeof i?i+="":x.isArray(i)&&(i=x.map(i,function(e){return null==e?"":e+""})),t=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],t&&"set"in t&&t.set(this,i,"value")!==undefined||(this.value=i))});if(i)return t=x.valHooks[i.type]||x.valHooks[i.nodeName.toLowerCase()],t&&"get"in t&&(n=t.get(i,"value"))!==undefined?n:(n=i.value,"string"==typeof n?n.replace($,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,s=o?null:[],a=o?i+1:r.length,u=0>i?a:o?i:0;for(;a>u;u++)if(n=r[u],!(!n.selected&&u!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;s.push(t)}return s},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),s=i.length;while(s--)r=i[s],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,t,n){var i,o,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===r?x.prop(e,t,n):(1===s&&x.isXMLDoc(e)||(t=t.toLowerCase(),i=x.attrHooks[t]||(x.expr.match.boolean.test(t)?M:R)),n===undefined?i&&"get"in i&&null!==(o=i.get(e,t))?o:(o=x.find.attr(e,t),null==o?undefined:o):null!==n?i&&"set"in i&&(o=i.set(e,n,t))!==undefined?o:(e.setAttribute(t,n+""),n):(x.removeAttr(e,t),undefined))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(w);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.boolean.test(n)&&(e[r]=!1),e.removeAttribute(n)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,t,n){var r,i,o,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return o=1!==s||!x.isXMLDoc(e),o&&(t=x.propFix[t]||t,i=x.propHooks[t]),n!==undefined?i&&"set"in i&&(r=i.set(e,n,t))!==undefined?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){return e.hasAttribute("tabindex")||B.test(e.nodeName)||e.href?e.tabIndex:-1}}}}),M={set:function(e,t,n){return t===!1?x.removeAttr(e,n):e.setAttribute(n,n),n}},x.each(x.expr.match.boolean.source.match(/\w+/g),function(e,t){var n=x.expr.attrHandle[t]||x.find.attr;x.expr.attrHandle[t]=function(e,t,r){var i=x.expr.attrHandle[t],o=r?undefined:(x.expr.attrHandle[t]=undefined)!=n(e,t,r)?t.toLowerCase():null;return x.expr.attrHandle[t]=i,o}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,t){return x.isArray(t)?e.checked=x.inArray(x(e).val(),t)>=0:undefined}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var I=/^key/,z=/^(?:mouse|contextmenu)|click/,_=/^(?:focusinfocus|focusoutblur)$/,X=/^([^.]*)(?:\.(.+)|)$/;function U(){return!0}function Y(){return!1}function V(){try{return o.activeElement}catch(e){}}x.event={global:{},add:function(e,t,n,i,o){var s,a,u,l,c,f,p,h,d,g,m,y=q.get(e);if(y){n.handler&&(s=n,n=s.handler,o=s.selector),n.guid||(n.guid=x.guid++),(l=y.events)||(l=y.events={}),(a=y.handle)||(a=y.handle=function(e){return typeof x===r||e&&x.event.triggered===e.type?undefined:x.event.dispatch.apply(a.elem,arguments)},a.elem=e),t=(t||"").match(w)||[""],c=t.length;while(c--)u=X.exec(t[c])||[],d=m=u[1],g=(u[2]||"").split(".").sort(),d&&(p=x.event.special[d]||{},d=(o?p.delegateType:p.bindType)||d,p=x.event.special[d]||{},f=x.extend({type:d,origType:m,data:i,handler:n,guid:n.guid,selector:o,needsContext:o&&x.expr.match.needsContext.test(o),namespace:g.join(".")},s),(h=l[d])||(h=l[d]=[],h.delegateCount=0,p.setup&&p.setup.call(e,i,g,a)!==!1||e.addEventListener&&e.addEventListener(d,a,!1)),p.add&&(p.add.call(e,f),f.handler.guid||(f.handler.guid=n.guid)),o?h.splice(h.delegateCount++,0,f):h.push(f),x.event.global[d]=!0);e=null}},remove:function(e,t,n,r,i){var o,s,a,u,l,c,f,p,h,d,g,m=q.hasData(e)&&q.get(e);if(m&&(u=m.events)){t=(t||"").match(w)||[""],l=t.length;while(l--)if(a=X.exec(t[l])||[],h=g=a[1],d=(a[2]||"").split(".").sort(),h){f=x.event.special[h]||{},h=(r?f.delegateType:f.bindType)||h,p=u[h]||[],a=a[2]&&RegExp("(^|\\.)"+d.join("\\.(?:.*\\.|)")+"(\\.|$)"),s=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||a&&!a.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));s&&!p.length&&(f.teardown&&f.teardown.call(e,d,m.handle)!==!1||x.removeEvent(e,h,m.handle),delete u[h])}else for(h in u)x.event.remove(e,h+t[l],n,r,!0);x.isEmptyObject(u)&&(delete m.handle,q.remove(e,"events"))}},trigger:function(t,n,r,i){var s,a,u,l,c,f,p,h=[r||o],d=y.call(t,"type")?t.type:t,g=y.call(t,"namespace")?t.namespace.split("."):[];if(a=u=r=r||o,3!==r.nodeType&&8!==r.nodeType&&!_.test(d+x.event.triggered)&&(d.indexOf(".")>=0&&(g=d.split("."),d=g.shift(),g.sort()),c=0>d.indexOf(":")&&"on"+d,t=t[x.expando]?t:new x.Event(d,"object"==typeof t&&t),t.isTrigger=i?2:3,t.namespace=g.join("."),t.namespace_re=t.namespace?RegExp("(^|\\.)"+g.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=undefined,t.target||(t.target=r),n=null==n?[t]:x.makeArray(n,[t]),p=x.event.special[d]||{},i||!p.trigger||p.trigger.apply(r,n)!==!1)){if(!i&&!p.noBubble&&!x.isWindow(r)){for(l=p.delegateType||d,_.test(l+d)||(a=a.parentNode);a;a=a.parentNode)h.push(a),u=a;u===(r.ownerDocument||o)&&h.push(u.defaultView||u.parentWindow||e)}s=0;while((a=h[s++])&&!t.isPropagationStopped())t.type=s>1?l:p.bindType||d,f=(q.get(a,"events")||{})[t.type]&&q.get(a,"handle"),f&&f.apply(a,n),f=c&&a[c],f&&x.acceptData(a)&&f.apply&&f.apply(a,n)===!1&&t.preventDefault();return t.type=d,i||t.isDefaultPrevented()||p._default&&p._default.apply(h.pop(),n)!==!1||!x.acceptData(r)||c&&x.isFunction(r[d])&&!x.isWindow(r)&&(u=r[c],u&&(r[c]=null),x.event.triggered=d,r[d](),x.event.triggered=undefined,u&&(r[c]=u)),t.result}},dispatch:function(e){e=x.event.fix(e);var t,n,r,i,o,s=[],a=d.call(arguments),u=(q.get(this,"events")||{})[e.type]||[],l=x.event.special[e.type]||{};if(a[0]=e,e.delegateTarget=this,!l.preDispatch||l.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),t=0;while((i=s[t++])&&!e.isPropagationStopped()){e.currentTarget=i.elem,n=0;while((o=i.handlers[n++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(o.namespace))&&(e.handleObj=o,e.data=o.data,r=((x.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,a),r!==undefined&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return l.postDispatch&&l.postDispatch.call(this,e),e.result}},handlers:function(e,t){var n,r,i,o,s=[],a=t.delegateCount,u=e.target;if(a&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!==this;u=u.parentNode||this)if(u.disabled!==!0||"click"!==e.type){for(r=[],n=0;a>n;n++)o=t[n],i=o.selector+" ",r[i]===undefined&&(r[i]=o.needsContext?x(i,this).index(u)>=0:x.find(i,this,null,[u]).length),r[i]&&r.push(o);r.length&&s.push({elem:u,handlers:r})}return t.length>a&&s.push({elem:this,handlers:t.slice(a)}),s},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,t){var n,r,i,s=t.button;return null==e.pageX&&null!=t.clientX&&(n=e.target.ownerDocument||o,r=n.documentElement,i=n.body,e.pageX=t.clientX+(r&&r.scrollLeft||i&&i.scrollLeft||0)-(r&&r.clientLeft||i&&i.clientLeft||0),e.pageY=t.clientY+(r&&r.scrollTop||i&&i.scrollTop||0)-(r&&r.clientTop||i&&i.clientTop||0)),e.which||s===undefined||(e.which=1&s?1:2&s?3:4&s?2:0),e}},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,o=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=z.test(i)?this.mouseHooks:I.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new x.Event(o),t=r.length;while(t--)n=r[t],e[n]=o[n];return 3===e.target.nodeType&&(e.target=e.target.parentNode),s.filter?s.filter(e,o):e},special:{load:{noBubble:!0},focus:{trigger:function(){return this!==V()&&this.focus?(this.focus(),!1):undefined},delegateType:"focusin"},blur:{trigger:function(){return this===V()&&this.blur?(this.blur(),!1):undefined},delegateType:"focusout"},click:{trigger:function(){return"checkbox"===this.type&&this.click&&x.nodeName(this,"input")?(this.click(),!1):undefined},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==undefined&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)},x.Event=function(e,t){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.getPreventDefault&&e.getPreventDefault()?U:Y):this.type=e,t&&x.extend(this,t),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,undefined):new x.Event(e,t)},x.Event.prototype={isDefaultPrevented:Y,isPropagationStopped:Y,isImmediatePropagationStopped:Y,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=U,e&&e.preventDefault&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=U,e&&e.stopPropagation&&e.stopPropagation()},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=U,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&o.addEventListener(e,r,!0)},teardown:function(){0===--n&&o.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,t,n,r,i){var o,s;if("object"==typeof e){"string"!=typeof t&&(n=n||t,t=undefined);for(s in e)this.on(s,t,n,e[s],i);return this}if(null==n&&null==r?(r=t,n=t=undefined):null==r&&("string"==typeof t?(r=n,n=undefined):(r=n,n=t,t=undefined)),r===!1)r=Y;else if(!r)return this;return 1===i&&(o=r,r=function(e){return x().off(e),o.apply(this,arguments)},r.guid=o.guid||(o.guid=x.guid++)),this.each(function(){x.event.add(this,e,r,n,t)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,x(e.delegateTarget).off(r.namespace?r.origType+"."+r.namespace:r.origType,r.selector,r.handler),this;if("object"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return(t===!1||"function"==typeof t)&&(n=t,t=undefined),n===!1&&(n=Y),this.each(function(){x.event.remove(this,e,n,t)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];return n?x.event.trigger(e,t,n,!0):undefined}});var G=/^.[^:#\[\.,]*$/,J=x.expr.match.needsContext,Q={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n,r,i=this.length;if("string"!=typeof e)return t=this,this.pushStack(x(e).filter(function(){for(r=0;i>r;r++)if(x.contains(t[r],this))return!0}));for(n=[],r=0;i>r;r++)x.find(e,this[r],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=(this.selector?this.selector+" ":"")+e,n},has:function(e){var t=x(e,this),n=t.length;return this.filter(function(){var e=0;for(;n>e;e++)if(x.contains(this,t[e]))return!0})},not:function(e){return this.pushStack(Z(this,e||[],!0))},filter:function(e){return this.pushStack(Z(this,e||[],!1))},is:function(e){return!!e&&("string"==typeof e?J.test(e)?x(e,this.context).index(this[0])>=0:x.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,o=[],s=J.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(s?s.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?g.call(x(e),this[0]):g.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function K(e,t){while((e=e[t])&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return K(e,"nextSibling")},prev:function(e){return K(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return x.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(Q[e]||x.unique(i),"p"===e[0]&&i.reverse()),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,t,n){var r=[],i=n!==undefined;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&x(e).is(n))break;r.push(e)}return r},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function Z(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(G.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return g.call(t,e)>=0!==n})}var et=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,tt=/<([\w:]+)/,nt=/<|&#?\w+;/,rt=/<(?:script|style|link)/i,it=/^(?:checkbox|radio)$/i,ot=/checked\s*(?:[^=]|=\s*.checked.)/i,st=/^$|\/(?:java|ecma)script/i,at=/^true\/(.*)/,ut=/^\s*\s*$/g,lt={option:[1,""],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};lt.optgroup=lt.option,lt.tbody=lt.tfoot=lt.colgroup=lt.caption=lt.col=lt.thead,lt.th=lt.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===undefined?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||o).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=ct(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=ct(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(gt(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&ht(gt(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++)1===e.nodeType&&(x.cleanData(gt(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var t=this[0]||{},n=0,r=this.length;if(e===undefined&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!rt.test(e)&&!lt[(tt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(et,"<$1>");try{for(;r>n;n++)t=this[n]||{},1===t.nodeType&&(x.cleanData(gt(t,!1)),t.innerHTML=e);t=0}catch(i){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=p.apply([],e);var r,i,o,s,a,u,l=0,c=this.length,f=this,h=c-1,d=e[0],g=x.isFunction(d);if(g||!(1>=c||"string"!=typeof d||x.support.checkClone)&&ot.test(d))return this.each(function(r){var i=f.eq(r);g&&(e[0]=d.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(r=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),i=r.firstChild,1===r.childNodes.length&&(r=i),i)){for(o=x.map(gt(r,"script"),ft),s=o.length;c>l;l++)a=r,l!==h&&(a=x.clone(a,!0,!0),s&&x.merge(o,gt(a,"script"))),t.call(this[l],a,l);if(s)for(u=o[o.length-1].ownerDocument,x.map(o,pt),l=0;s>l;l++)a=o[l],st.test(a.type||"")&&!q.access(a,"globalEval")&&x.contains(u,a)&&(a.src?x._evalUrl(a.src):x.globalEval(a.textContent.replace(ut,"")))}return this}}),x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=[],i=x(e),o=i.length-1,s=0;for(;o>=s;s++)n=s===o?this:this.clone(!0),x(i[s])[t](n),h.apply(r,n.get());return this.pushStack(r)}}),x.extend({clone:function(e,t,n){var r,i,o,s,a=e.cloneNode(!0),u=x.contains(e.ownerDocument,e);if(!(x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(s=gt(a),o=gt(e),r=0,i=o.length;i>r;r++)mt(o[r],s[r]);if(t)if(n)for(o=o||gt(e),s=s||gt(a),r=0,i=o.length;i>r;r++)dt(o[r],s[r]);else dt(e,a);return s=gt(a,"script"),s.length>0&&ht(s,!u&>(e,"script")),a},buildFragment:function(e,t,n,r){var i,o,s,a,u,l,c=0,f=e.length,p=t.createDocumentFragment(),h=[];for(;f>c;c++)if(i=e[c],i||0===i)if("object"===x.type(i))x.merge(h,i.nodeType?[i]:i);else if(nt.test(i)){o=o||p.appendChild(t.createElement("div")),s=(tt.exec(i)||["",""])[1].toLowerCase(),a=lt[s]||lt._default,o.innerHTML=a[1]+i.replace(et,"<$1>")+a[2],l=a[0];while(l--)o=o.firstChild;x.merge(h,o.childNodes),o=p.firstChild,o.textContent=""}else h.push(t.createTextNode(i));p.textContent="",c=0;while(i=h[c++])if((!r||-1===x.inArray(i,r))&&(u=x.contains(i.ownerDocument,i),o=gt(p.appendChild(i),"script"),u&&ht(o),n)){l=0;while(i=o[l++])st.test(i.type||"")&&n.push(i)}return p},cleanData:function(e){var t,n,r,i=e.length,o=0,s=x.event.special;for(;i>o;o++){if(n=e[o],x.acceptData(n)&&(t=q.access(n)))for(r in t.events)s[r]?x.event.remove(n,r):x.removeEvent(n,r,t.handle);L.discard(n),q.discard(n)}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"text",async:!1,global:!1,success:x.globalEval})}});function ct(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function ft(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function pt(e){var t=at.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function ht(e,t){var n=e.length,r=0;for(;n>r;r++)q.set(e[r],"globalEval",!t||q.get(t[r],"globalEval"))}function dt(e,t){var n,r,i,o,s,a,u,l;if(1===t.nodeType){if(q.hasData(e)&&(o=q.access(e),s=x.extend({},o),l=o.events,q.set(t,s),l)){delete s.handle,s.events={};for(i in l)for(n=0,r=l[i].length;r>n;n++)x.event.add(t,i,l[i][n])}L.hasData(e)&&(a=L.access(e),u=x.extend({},a),L.set(t,u))}}function gt(e,t){var n=e.getElementsByTagName?e.getElementsByTagName(t||"*"):e.querySelectorAll?e.querySelectorAll(t||"*"):[];return t===undefined||t&&x.nodeName(e,t)?x.merge([e],n):n}function mt(e,t){var n=t.nodeName.toLowerCase();"input"===n&&it.test(e.type)?t.checked=e.checked:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}x.fn.extend({wrapAll:function(e){var t;return x.isFunction(e)?this.each(function(t){x(this).wrapAll(e.call(this,t))}):(this[0]&&(t=x(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this)},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var yt,vt,xt=/^(none|table(?!-c[ea]).+)/,bt=/^margin/,wt=RegExp("^("+b+")(.*)$","i"),Tt=RegExp("^("+b+")(?!px)[a-z%]+$","i"),Ct=RegExp("^([+-])=("+b+")","i"),kt={BODY:"block"},Nt={position:"absolute",visibility:"hidden",display:"block"},Et={letterSpacing:0,fontWeight:400},St=["Top","Right","Bottom","Left"],jt=["Webkit","O","Moz","ms"];function Dt(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=jt.length;while(i--)if(t=jt[i]+n,t in e)return t;return r}function At(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function Lt(t){return e.getComputedStyle(t,null)}function qt(e,t){var n,r,i,o=[],s=0,a=e.length;for(;a>s;s++)r=e[s],r.style&&(o[s]=q.get(r,"olddisplay"),n=r.style.display,t?(o[s]||"none"!==n||(r.style.display=""),""===r.style.display&&At(r)&&(o[s]=q.access(r,"olddisplay",Pt(r.nodeName)))):o[s]||(i=At(r),(n&&"none"!==n||!i)&&q.set(r,"olddisplay",i?n:x.css(r,"display"))));for(s=0;a>s;s++)r=e[s],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[s]||"":"none"));return e}x.fn.extend({css:function(e,t){return x.access(this,function(e,t,n){var r,i,o={},s=0;if(x.isArray(t)){for(r=Lt(e),i=t.length;i>s;s++)o[t[s]]=x.css(e,t[s],!1,r);return o}return n!==undefined?x.style(e,t,n):x.css(e,t)},e,t,arguments.length>1)},show:function(){return qt(this,!0)},hide:function(){return qt(this)},toggle:function(e){var t="boolean"==typeof e;return this.each(function(){(t?e:At(this))?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=yt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,s,a=x.camelCase(t),u=e.style;return t=x.cssProps[a]||(x.cssProps[a]=Dt(u,a)),s=x.cssHooks[t]||x.cssHooks[a],n===undefined?s&&"get"in s&&(i=s.get(e,!1,r))!==undefined?i:u[t]:(o=typeof n,"string"===o&&(i=Ct.exec(n))&&(n=(i[1]+1)*i[2]+parseFloat(x.css(e,t)),o="number"),null==n||"number"===o&&isNaN(n)||("number"!==o||x.cssNumber[a]||(n+="px"),x.support.clearCloneStyle||""!==n||0!==t.indexOf("background")||(u[t]="inherit"),s&&"set"in s&&(n=s.set(e,n,r))===undefined||(u[t]=n)),undefined)}},css:function(e,t,n,r){var i,o,s,a=x.camelCase(t);return t=x.cssProps[a]||(x.cssProps[a]=Dt(e.style,a)),s=x.cssHooks[t]||x.cssHooks[a],s&&"get"in s&&(i=s.get(e,!0,n)),i===undefined&&(i=yt(e,t,r)),"normal"===i&&t in Et&&(i=Et[t]),""===n||n?(o=parseFloat(i),n===!0||x.isNumeric(o)?o||0:i):i}}),yt=function(e,t,n){var r,i,o,s=n||Lt(e),a=s?s.getPropertyValue(t)||s[t]:undefined,u=e.style;return s&&(""!==a||x.contains(e.ownerDocument,e)||(a=x.style(e,t)),Tt.test(a)&&bt.test(t)&&(r=u.width,i=u.minWidth,o=u.maxWidth,u.minWidth=u.maxWidth=u.width=a,a=s.width,u.width=r,u.minWidth=i,u.maxWidth=o)),a};function Ht(e,t,n){var r=wt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function Ot(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,s=0;for(;4>o;o+=2)"margin"===n&&(s+=x.css(e,n+St[o],!0,i)),r?("content"===n&&(s-=x.css(e,"padding"+St[o],!0,i)),"margin"!==n&&(s-=x.css(e,"border"+St[o]+"Width",!0,i))):(s+=x.css(e,"padding"+St[o],!0,i),"padding"!==n&&(s+=x.css(e,"border"+St[o]+"Width",!0,i)));return s}function Ft(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Lt(e),s=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=yt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Tt.test(i))return i;r=s&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+Ot(e,t,n||(s?"border":"content"),r,o)+"px"}function Pt(e){var t=o,n=kt[e];return n||(n=Rt(e,t),"none"!==n&&n||(vt=(vt||x("").appendTo("body"); +_697.attr("src",window.ActiveXObject?"javascript:false":"about:blank"); +_697.css({position:"absolute",top:-1000,left:-1000}); +_697.bind("load",cb); +_698(_695); +function _698(_699){ +var form=$(_694); +if(opts.url){ +form.attr("action",opts.url); +} +var t=form.attr("target"),a=form.attr("action"); +form.attr("target",_696); +var _69a=$(); +try{ +for(var n in _699){ +var _69b=$("").val(_699[n]).appendTo(form); +_69a=_69a.add(_69b); +} +_69c(); +form[0].submit(); +} +finally{ +form.attr("action",a); +t?form.attr("target",t):form.removeAttr("target"); +_69a.remove(); +} +}; +function _69c(){ +var f=$("#"+_696); +if(!f.length){ +return; +} +try{ +var s=f.contents()[0].readyState; +if(s&&s.toLowerCase()=="uninitialized"){ +setTimeout(_69c,100); +} +} +catch(e){ +cb(); +} +}; +var _69d=10; +function cb(){ +var f=$("#"+_696); +if(!f.length){ +return; +} +f.unbind(); +var data=""; +try{ +var body=f.contents().find("body"); +data=body.html(); +if(data==""){ +if(--_69d){ +setTimeout(cb,100); +return; +} +} +var ta=body.find(">textarea"); +if(ta.length){ +data=ta.val(); +}else{ +var pre=body.find(">pre"); +if(pre.length){ +data=pre.html(); +} +} +} +catch(e){ +} +opts.success.call(_694,data); +setTimeout(function(){ +f.unbind(); +f.remove(); +},100); +}; +}; +function _693(_69e,_69f){ +var opts=$.data(_69e,"form").options; +var _6a0=new FormData($(_69e)[0]); +for(var name in _69f){ +_6a0.append(name,_69f[name]); +} +$.ajax({url:opts.url,type:"post",xhr:function(){ +var xhr=$.ajaxSettings.xhr(); +if(xhr.upload){ +xhr.upload.addEventListener("progress",function(e){ +if(e.lengthComputable){ +var _6a1=e.total; +var _6a2=e.loaded||e.position; +var _6a3=Math.ceil(_6a2*100/_6a1); +opts.onProgress.call(_69e,_6a3); +} +},false); +} +return xhr; +},data:_6a0,dataType:"html",cache:false,contentType:false,processData:false,complete:function(res){ +opts.success.call(_69e,res.responseText); +}}); +}; +function load(_6a4,data){ +var opts=$.data(_6a4,"form").options; +if(typeof data=="string"){ +var _6a5={}; +if(opts.onBeforeLoad.call(_6a4,_6a5)==false){ +return; +} +$.ajax({url:data,data:_6a5,dataType:"json",success:function(data){ +_6a6(data); +},error:function(){ +opts.onLoadError.apply(_6a4,arguments); +}}); +}else{ +_6a6(data); +} +function _6a6(data){ +var form=$(_6a4); +for(var name in data){ +var val=data[name]; +if(!_6a7(name,val)){ +if(!_6a8(name,val)){ +form.find("input[name=\""+name+"\"]").val(val); +form.find("textarea[name=\""+name+"\"]").val(val); +form.find("select[name=\""+name+"\"]").val(val); +} +} +} +opts.onLoadSuccess.call(_6a4,data); +form.form("validate"); +}; +function _6a7(name,val){ +var _6a9=["switchbutton","radiobutton","checkbox"]; +for(var i=0;i<_6a9.length;i++){ +var _6aa=_6a9[i]; +var cc=$(_6a4).find("["+_6aa+"Name=\""+name+"\"]"); +if(cc.length){ +cc[_6aa]("uncheck"); +cc.each(function(){ +if(_6ab($(this)[_6aa]("options").value,val)){ +$(this)[_6aa]("check"); +} +}); +return true; +} +} +var cc=$(_6a4).find("input[name=\""+name+"\"][type=radio], input[name=\""+name+"\"][type=checkbox]"); +if(cc.length){ +cc._propAttr("checked",false); +cc.each(function(){ +if(_6ab($(this).val(),val)){ +$(this)._propAttr("checked",true); +} +}); +return true; +} +return false; +}; +function _6ab(v,val){ +if(v==String(val)||$.inArray(v,$.isArray(val)?val:[val])>=0){ +return true; +}else{ +return false; +} +}; +function _6a8(name,val){ +var _6ac=$(_6a4).find("[textboxName=\""+name+"\"],[sliderName=\""+name+"\"]"); +if(_6ac.length){ +for(var i=0;i=0;i--){ +var type=opts.fieldTypes[i]; +var _6b4=form.find("."+type+"-f"); +if(_6b4.length&&_6b4[type]){ +_6b4[type]("reset"); +} +} +form.form("validate"); +}; +function _6b5(_6b6){ +var _6b7=$.data(_6b6,"form").options; +$(_6b6).unbind(".form"); +if(_6b7.ajax){ +$(_6b6).bind("submit.form",function(){ +setTimeout(function(){ +_68c(_6b6,_6b7); +},0); +return false; +}); +} +$(_6b6).bind("_change.form",function(e,t){ +if($.inArray(t,_6b7.dirtyFields)==-1){ +_6b7.dirtyFields.push(t); +} +_6b7.onChange.call(this,t); +}).bind("change.form",function(e){ +var t=e.target; +if(!$(t).hasClass("textbox-text")){ +if($.inArray(t,_6b7.dirtyFields)==-1){ +_6b7.dirtyFields.push(t); +} +_6b7.onChange.call(this,t); +} +}); +_6b8(_6b6,_6b7.novalidate); +}; +function _6b9(_6ba,_6bb){ +_6bb=_6bb||{}; +var _6bc=$.data(_6ba,"form"); +if(_6bc){ +$.extend(_6bc.options,_6bb); +}else{ +$.data(_6ba,"form",{options:$.extend({},$.fn.form.defaults,$.fn.form.parseOptions(_6ba),_6bb)}); +} +}; +function _6bd(_6be){ +if($.fn.validatebox){ +var opts=$.data(_6be,"form").options; +var t=$(_6be); +t.find(".validatebox-text:not(:disabled)").validatebox("validate"); +var _6bf=t.find(".validatebox-invalid"); +if(opts.focusOnValidate){ +_6bf.filter(":not(:disabled):first").focus(); +} +return _6bf.length==0; +} +return true; +}; +function _6b8(_6c0,_6c1){ +var opts=$.data(_6c0,"form").options; +opts.novalidate=_6c1; +$(_6c0).find(".validatebox-text:not(:disabled)").validatebox(_6c1?"disableValidation":"enableValidation"); +}; +$.fn.form=function(_6c2,_6c3){ +if(typeof _6c2=="string"){ +this.each(function(){ +_6b9(this); +}); +return $.fn.form.methods[_6c2](this,_6c3); +} +return this.each(function(){ +_6b9(this,_6c2); +_6b5(this); +}); +}; +$.fn.form.methods={options:function(jq){ +return $.data(jq[0],"form").options; +},submit:function(jq,_6c4){ +return jq.each(function(){ +_68c(this,_6c4); +}); +},load:function(jq,data){ +return jq.each(function(){ +load(this,data); +}); +},clear:function(jq){ +return jq.each(function(){ +_6ae(this); +}); +},reset:function(jq){ +return jq.each(function(){ +_6b2(this); +}); +},validate:function(jq){ +return _6bd(jq[0]); +},disableValidation:function(jq){ +return jq.each(function(){ +_6b8(this,true); +}); +},enableValidation:function(jq){ +return jq.each(function(){ +_6b8(this,false); +}); +},resetValidation:function(jq){ +return jq.each(function(){ +$(this).find(".validatebox-text:not(:disabled)").validatebox("resetValidation"); +}); +},resetDirty:function(jq){ +return jq.each(function(){ +$(this).form("options").dirtyFields=[]; +}); +}}; +$.fn.form.parseOptions=function(_6c5){ +var t=$(_6c5); +return $.extend({},$.parser.parseOptions(_6c5,[{ajax:"boolean",dirty:"boolean"}]),{url:(t.attr("action")?t.attr("action"):undefined)}); +}; +$.fn.form.defaults={fieldTypes:["tagbox","combobox","combotree","combogrid","combotreegrid","datetimebox","datebox","timepicker","combo","datetimespinner","timespinner","numberspinner","spinner","slider","searchbox","numberbox","passwordbox","filebox","textbox","switchbutton","radiobutton","checkbox"],novalidate:false,focusOnValidate:true,ajax:true,iframe:true,dirty:false,dirtyFields:[],url:null,queryParams:{},onSubmit:function(_6c6){ +return $(this).form("validate"); +},onProgress:function(_6c7){ +},success:function(data){ +},onBeforeLoad:function(_6c8){ +},onLoadSuccess:function(data){ +},onLoadError:function(){ +},onChange:function(_6c9){ +}}; +})(jQuery); +(function($){ +function _6ca(_6cb){ +var _6cc=$.data(_6cb,"numberbox"); +var opts=_6cc.options; +$(_6cb).addClass("numberbox-f").textbox(opts); +$(_6cb).textbox("textbox").css({imeMode:"disabled"}); +$(_6cb).attr("numberboxName",$(_6cb).attr("textboxName")); +_6cc.numberbox=$(_6cb).next(); +_6cc.numberbox.addClass("numberbox"); +var _6cd=opts.parser.call(_6cb,opts.value); +var _6ce=opts.formatter.call(_6cb,_6cd); +$(_6cb).numberbox("initValue",_6cd).numberbox("setText",_6ce); +}; +function _6cf(_6d0,_6d1){ +var _6d2=$.data(_6d0,"numberbox"); +var opts=_6d2.options; +opts.value=parseFloat(_6d1); +var _6d1=opts.parser.call(_6d0,_6d1); +var text=opts.formatter.call(_6d0,_6d1); +opts.value=_6d1; +$(_6d0).textbox("setText",text).textbox("setValue",_6d1); +text=opts.formatter.call(_6d0,$(_6d0).textbox("getValue")); +$(_6d0).textbox("setText",text); +}; +$.fn.numberbox=function(_6d3,_6d4){ +if(typeof _6d3=="string"){ +var _6d5=$.fn.numberbox.methods[_6d3]; +if(_6d5){ +return _6d5(this,_6d4); +}else{ +return this.textbox(_6d3,_6d4); +} +} +_6d3=_6d3||{}; +return this.each(function(){ +var _6d6=$.data(this,"numberbox"); +if(_6d6){ +$.extend(_6d6.options,_6d3); +}else{ +_6d6=$.data(this,"numberbox",{options:$.extend({},$.fn.numberbox.defaults,$.fn.numberbox.parseOptions(this),_6d3)}); +} +_6ca(this); +}); +}; +$.fn.numberbox.methods={options:function(jq){ +var opts=jq.data("textbox")?jq.textbox("options"):{}; +return $.extend($.data(jq[0],"numberbox").options,{width:opts.width,originalValue:opts.originalValue,disabled:opts.disabled,readonly:opts.readonly}); +},cloneFrom:function(jq,from){ +return jq.each(function(){ +$(this).textbox("cloneFrom",from); +$.data(this,"numberbox",{options:$.extend(true,{},$(from).numberbox("options"))}); +$(this).addClass("numberbox-f"); +}); +},fix:function(jq){ +return jq.each(function(){ +var opts=$(this).numberbox("options"); +opts.value=null; +var _6d7=opts.parser.call(this,$(this).numberbox("getText")); +$(this).numberbox("setValue",_6d7); +}); +},setValue:function(jq,_6d8){ +return jq.each(function(){ +_6cf(this,_6d8); +}); +},clear:function(jq){ +return jq.each(function(){ +$(this).textbox("clear"); +$(this).numberbox("options").value=""; +}); +},reset:function(jq){ +return jq.each(function(){ +$(this).textbox("reset"); +$(this).numberbox("setValue",$(this).numberbox("getValue")); +}); +}}; +$.fn.numberbox.parseOptions=function(_6d9){ +var t=$(_6d9); +return $.extend({},$.fn.textbox.parseOptions(_6d9),$.parser.parseOptions(_6d9,["decimalSeparator","groupSeparator","suffix",{min:"number",max:"number",precision:"number"}]),{prefix:(t.attr("prefix")?t.attr("prefix"):undefined)}); +}; +$.fn.numberbox.defaults=$.extend({},$.fn.textbox.defaults,{inputEvents:{keypress:function(e){ +var _6da=e.data.target; +var opts=$(_6da).numberbox("options"); +return opts.filter.call(_6da,e); +},blur:function(e){ +$(e.data.target).numberbox("fix"); +},keydown:function(e){ +if(e.keyCode==13){ +$(e.data.target).numberbox("fix"); +} +}},min:null,max:null,precision:0,decimalSeparator:".",groupSeparator:"",prefix:"",suffix:"",filter:function(e){ +var opts=$(this).numberbox("options"); +var s=$(this).numberbox("getText"); +if(e.metaKey||e.ctrlKey){ +return true; +} +if($.inArray(String(e.which),["46","8","13","0"])>=0){ +return true; +} +var tmp=$(""); +tmp.html(String.fromCharCode(e.which)); +var c=tmp.text(); +tmp.remove(); +if(!c){ +return true; +} +if(c=="-"&&opts.min!=null&&opts.min>=0){ +return false; +} +if(c=="-"||c==opts.decimalSeparator){ +return (s.indexOf(c)==-1)?true:false; +}else{ +if(c==opts.groupSeparator){ +return true; +}else{ +if("0123456789".indexOf(c)>=0){ +return true; +}else{ +return false; +} +} +} +},formatter:function(_6db){ +if(!_6db){ +return _6db; +} +_6db=_6db+""; +var opts=$(this).numberbox("options"); +var s1=_6db,s2=""; +var dpos=_6db.indexOf("."); +if(dpos>=0){ +s1=_6db.substring(0,dpos); +s2=_6db.substring(dpos+1,_6db.length); +} +if(opts.groupSeparator){ +var p=/(\d+)(\d{3})/; +while(p.test(s1)){ +s1=s1.replace(p,"$1"+opts.groupSeparator+"$2"); +} +} +if(s2){ +return opts.prefix+s1+opts.decimalSeparator+s2+opts.suffix; +}else{ +return opts.prefix+s1+opts.suffix; +} +},parser:function(s){ +s=s+""; +var opts=$(this).numberbox("options"); +if(opts.prefix){ +s=$.trim(s.replace(new RegExp("\\"+$.trim(opts.prefix),"g"),"")); +} +if(opts.suffix){ +s=$.trim(s.replace(new RegExp("\\"+$.trim(opts.suffix),"g"),"")); +} +if(parseFloat(s)!=opts.value){ +if(opts.groupSeparator){ +s=$.trim(s.replace(new RegExp("\\"+opts.groupSeparator,"g"),"")); +} +if(opts.decimalSeparator){ +s=$.trim(s.replace(new RegExp("\\"+opts.decimalSeparator,"g"),".")); +} +s=s.replace(/\s/g,""); +} +var val=parseFloat(s).toFixed(opts.precision); +if(isNaN(val)){ +val=""; +}else{ +if(typeof (opts.min)=="number"&&valopts.max){ +val=opts.max.toFixed(opts.precision); +} +} +} +return val; +}}); +})(jQuery); +(function($){ +function _6dc(_6dd,_6de){ +var opts=$.data(_6dd,"calendar").options; +var t=$(_6dd); +if(_6de){ +$.extend(opts,{width:_6de.width,height:_6de.height}); +} +t._size(opts,t.parent()); +t.find(".calendar-body")._outerHeight(t.height()-t.find(".calendar-header")._outerHeight()); +if(t.find(".calendar-menu").is(":visible")){ +_6df(_6dd); +} +}; +function init(_6e0){ +$(_6e0).addClass("calendar").html("
"+"
"+"
"+"
"+"
"+"
"+""+"
"+"
"+"
"+"
"+"
"+""+""+""+"
"+"
"+"
"+"
"+"
"); +$(_6e0)._bind("_resize",function(e,_6e1){ +if($(this).hasClass("easyui-fluid")||_6e1){ +_6dc(_6e0); +} +return false; +}); +}; +function _6e2(_6e3){ +var opts=$.data(_6e3,"calendar").options; +var menu=$(_6e3).find(".calendar-menu"); +menu.find(".calendar-menu-year")._unbind(".calendar")._bind("keypress.calendar",function(e){ +if(e.keyCode==13){ +_6e4(true); +} +}); +$(_6e3)._unbind(".calendar")._bind("mouseover.calendar",function(e){ +var t=_6e5(e.target); +if(t.hasClass("calendar-nav")||t.hasClass("calendar-text")||(t.hasClass("calendar-day")&&!t.hasClass("calendar-disabled"))){ +t.addClass("calendar-nav-hover"); +} +})._bind("mouseout.calendar",function(e){ +var t=_6e5(e.target); +if(t.hasClass("calendar-nav")||t.hasClass("calendar-text")||(t.hasClass("calendar-day")&&!t.hasClass("calendar-disabled"))){ +t.removeClass("calendar-nav-hover"); +} +})._bind("click.calendar",function(e){ +var t=_6e5(e.target); +if(t.hasClass("calendar-menu-next")||t.hasClass("calendar-nextyear")){ +_6e6(1); +}else{ +if(t.hasClass("calendar-menu-prev")||t.hasClass("calendar-prevyear")){ +_6e6(-1); +}else{ +if(t.hasClass("calendar-menu-month")){ +menu.find(".calendar-selected").removeClass("calendar-selected"); +t.addClass("calendar-selected"); +_6e4(true); +}else{ +if(t.hasClass("calendar-prevmonth")){ +_6e7(-1); +}else{ +if(t.hasClass("calendar-nextmonth")){ +_6e7(1); +}else{ +if(t.hasClass("calendar-text")){ +if(menu.is(":visible")){ +menu.hide(); +}else{ +_6df(_6e3); +} +}else{ +if(t.hasClass("calendar-day")){ +if(t.hasClass("calendar-disabled")){ +return; +} +var _6e8=opts.current; +t.closest("div.calendar-body").find(".calendar-selected").removeClass("calendar-selected"); +t.addClass("calendar-selected"); +var _6e9=t.attr("abbr").split(","); +var y=parseInt(_6e9[0]); +var m=parseInt(_6e9[1]); +var d=parseInt(_6e9[2]); +opts.current=new opts.Date(y,m-1,d); +opts.onSelect.call(_6e3,opts.current); +if(!_6e8||_6e8.getTime()!=opts.current.getTime()){ +opts.onChange.call(_6e3,opts.current,_6e8); +} +if(opts.year!=y||opts.month!=m){ +opts.year=y; +opts.month=m; +show(_6e3); +} +} +} +} +} +} +} +} +}); +function _6e5(t){ +var day=$(t).closest(".calendar-day"); +if(day.length){ +return day; +}else{ +return $(t); +} +}; +function _6e4(_6ea){ +var menu=$(_6e3).find(".calendar-menu"); +var year=menu.find(".calendar-menu-year").val(); +var _6eb=menu.find(".calendar-selected").attr("abbr"); +if(!isNaN(year)){ +opts.year=parseInt(year); +opts.month=parseInt(_6eb); +show(_6e3); +} +if(_6ea){ +menu.hide(); +} +}; +function _6e6(_6ec){ +opts.year+=_6ec; +show(_6e3); +menu.find(".calendar-menu-year").val(opts.year); +}; +function _6e7(_6ed){ +opts.month+=_6ed; +if(opts.month>12){ +opts.year++; +opts.month=1; +}else{ +if(opts.month<1){ +opts.year--; +opts.month=12; +} +} +show(_6e3); +menu.find("td.calendar-selected").removeClass("calendar-selected"); +menu.find("td:eq("+(opts.month-1)+")").addClass("calendar-selected"); +}; +}; +function _6df(_6ee){ +var opts=$.data(_6ee,"calendar").options; +$(_6ee).find(".calendar-menu").show(); +if($(_6ee).find(".calendar-menu-month-inner").is(":empty")){ +$(_6ee).find(".calendar-menu-month-inner").empty(); +var t=$("
").appendTo($(_6ee).find(".calendar-menu-month-inner")); +var idx=0; +for(var i=0;i<3;i++){ +var tr=$("").appendTo(t); +for(var j=0;j<4;j++){ +$("").html(opts.months[idx++]).attr("abbr",idx).appendTo(tr); +} +} +} +var body=$(_6ee).find(".calendar-body"); +var sele=$(_6ee).find(".calendar-menu"); +var _6ef=sele.find(".calendar-menu-year-inner"); +var _6f0=sele.find(".calendar-menu-month-inner"); +_6ef.find("input").val(opts.year).focus(); +_6f0.find("td.calendar-selected").removeClass("calendar-selected"); +_6f0.find("td:eq("+(opts.month-1)+")").addClass("calendar-selected"); +sele._outerWidth(body._outerWidth()); +sele._outerHeight(body._outerHeight()); +_6f0._outerHeight(sele.height()-_6ef._outerHeight()); +}; +function _6f1(_6f2,year,_6f3){ +var opts=$.data(_6f2,"calendar").options; +var _6f4=[]; +var _6f5=new opts.Date(year,_6f3,0).getDate(); +for(var i=1;i<=_6f5;i++){ +_6f4.push([year,_6f3,i]); +} +var _6f6=[],week=[]; +var _6f7=-1; +while(_6f4.length>0){ +var date=_6f4.shift(); +week.push(date); +var day=new opts.Date(date[0],date[1]-1,date[2]).getDay(); +if(_6f7==day){ +day=0; +}else{ +if(day==(opts.firstDay==0?7:opts.firstDay)-1){ +_6f6.push(week); +week=[]; +} +} +_6f7=day; +} +if(week.length){ +_6f6.push(week); +} +var _6f8=_6f6[0]; +if(_6f8.length<7){ +while(_6f8.length<7){ +var _6f9=_6f8[0]; +var date=new opts.Date(_6f9[0],_6f9[1]-1,_6f9[2]-1); +_6f8.unshift([date.getFullYear(),date.getMonth()+1,date.getDate()]); +} +}else{ +var _6f9=_6f8[0]; +var week=[]; +for(var i=1;i<=7;i++){ +var date=new opts.Date(_6f9[0],_6f9[1]-1,_6f9[2]-i); +week.unshift([date.getFullYear(),date.getMonth()+1,date.getDate()]); +} +_6f6.unshift(week); +} +var _6fa=_6f6[_6f6.length-1]; +while(_6fa.length<7){ +var _6fb=_6fa[_6fa.length-1]; +var date=new opts.Date(_6fb[0],_6fb[1]-1,_6fb[2]+1); +_6fa.push([date.getFullYear(),date.getMonth()+1,date.getDate()]); +} +if(_6f6.length<6){ +var _6fb=_6fa[_6fa.length-1]; +var week=[]; +for(var i=1;i<=7;i++){ +var date=new opts.Date(_6fb[0],_6fb[1]-1,_6fb[2]+i); +week.push([date.getFullYear(),date.getMonth()+1,date.getDate()]); +} +_6f6.push(week); +} +return _6f6; +}; +function show(_6fc){ +var opts=$.data(_6fc,"calendar").options; +if(opts.current&&!opts.validator.call(_6fc,opts.current)){ +opts.current=null; +} +var now=new opts.Date(); +var _6fd=now.getFullYear()+","+(now.getMonth()+1)+","+now.getDate(); +var _6fe=opts.current?(opts.current.getFullYear()+","+(opts.current.getMonth()+1)+","+opts.current.getDate()):""; +var _6ff=6-opts.firstDay; +var _700=_6ff+1; +if(_6ff>=7){ +_6ff-=7; +} +if(_700>=7){ +_700-=7; +} +$(_6fc).find(".calendar-title span").html(opts.months[opts.month-1]+" "+opts.year); +var body=$(_6fc).find("div.calendar-body"); +body.children("table").remove(); +var data=[""]; +data.push(""); +if(opts.showWeek){ +data.push(""); +} +for(var i=opts.firstDay;i"+opts.weeks[i]+""); +} +for(var i=0;i"+opts.weeks[i]+""); +} +data.push(""); +data.push(""); +var _701=_6f1(_6fc,opts.year,opts.month); +for(var i=0;i<_701.length;i++){ +var week=_701[i]; +var cls=""; +if(i==0){ +cls="calendar-first"; +}else{ +if(i==_701.length-1){ +cls="calendar-last"; +} +} +data.push(""); +if(opts.showWeek){ +var _702=opts.getWeekNumber(new opts.Date(week[0][0],parseInt(week[0][1])-1,week[0][2])); +data.push(""); +} +for(var j=0;j"+d+""); +} +data.push(""); +} +data.push(""); +data.push("
"+opts.weekNumberHeader+"
"+_702+"
"); +body.append(data.join("")); +body.children("table.calendar-dtable").prependTo(body); +opts.onNavigate.call(_6fc,opts.year,opts.month); +}; +$.fn.calendar=function(_706,_707){ +if(typeof _706=="string"){ +return $.fn.calendar.methods[_706](this,_707); +} +_706=_706||{}; +return this.each(function(){ +var _708=$.data(this,"calendar"); +if(_708){ +$.extend(_708.options,_706); +}else{ +_708=$.data(this,"calendar",{options:$.extend({},$.fn.calendar.defaults,$.fn.calendar.parseOptions(this),_706)}); +init(this); +} +if(_708.options.border==false){ +$(this).addClass("calendar-noborder"); +} +_6dc(this); +_6e2(this); +show(this); +$(this).find("div.calendar-menu").hide(); +}); +}; +$.fn.calendar.methods={options:function(jq){ +return $.data(jq[0],"calendar").options; +},resize:function(jq,_709){ +return jq.each(function(){ +_6dc(this,_709); +}); +},moveTo:function(jq,date){ +return jq.each(function(){ +var opts=$(this).calendar("options"); +if(!date){ +var now=new opts.Date(); +$(this).calendar({year:now.getFullYear(),month:now.getMonth()+1,current:date}); +return; +} +if(opts.validator.call(this,date)){ +var _70a=opts.current; +$(this).calendar({year:date.getFullYear(),month:date.getMonth()+1,current:date}); +if(!_70a||_70a.getTime()!=date.getTime()){ +opts.onChange.call(this,opts.current,_70a); +} +} +}); +}}; +$.fn.calendar.parseOptions=function(_70b){ +var t=$(_70b); +return $.extend({},$.parser.parseOptions(_70b,["weekNumberHeader",{firstDay:"number",fit:"boolean",border:"boolean",showWeek:"boolean"}])); +}; +$.fn.calendar.defaults={Date:Date,width:180,height:180,fit:false,border:true,showWeek:false,firstDay:0,weeks:["S","M","T","W","T","F","S"],months:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],year:new Date().getFullYear(),month:new Date().getMonth()+1,current:(function(){ +var d=new Date(); +return new Date(d.getFullYear(),d.getMonth(),d.getDate()); +})(),weekNumberHeader:"",getWeekNumber:function(date){ +var _70c=new Date(date.getTime()); +_70c.setDate(_70c.getDate()+4-(_70c.getDay()||7)); +var time=_70c.getTime(); +_70c.setMonth(0); +_70c.setDate(1); +return Math.floor(Math.round((time-_70c)/86400000)/7)+1; +},formatter:function(date){ +return date.getDate(); +},styler:function(date){ +return ""; +},validator:function(date){ +return true; +},onSelect:function(date){ +},onChange:function(_70d,_70e){ +},onNavigate:function(year,_70f){ +}}; +})(jQuery); +(function($){ +function _710(_711){ +var _712=$.data(_711,"spinner"); +var opts=_712.options; +var _713=$.extend(true,[],opts.icons); +if(opts.spinAlign=="left"||opts.spinAlign=="right"){ +opts.spinArrow=true; +opts.iconAlign=opts.spinAlign; +var _714={iconCls:"spinner-button-updown",handler:function(e){ +var spin=$(e.target).closest(".spinner-button-top,.spinner-button-bottom"); +_71e(e.data.target,spin.hasClass("spinner-button-bottom")); +}}; +if(opts.spinAlign=="left"){ +_713.unshift(_714); +}else{ +_713.push(_714); +} +}else{ +opts.spinArrow=false; +if(opts.spinAlign=="vertical"){ +if(opts.buttonAlign!="top"){ +opts.buttonAlign="bottom"; +} +opts.clsLeft="textbox-button-bottom"; +opts.clsRight="textbox-button-top"; +}else{ +opts.clsLeft="textbox-button-left"; +opts.clsRight="textbox-button-right"; +} +} +$(_711).addClass("spinner-f").textbox($.extend({},opts,{icons:_713,doSize:false,onResize:function(_715,_716){ +if(!opts.spinArrow){ +var span=$(this).next(); +var btn=span.find(".textbox-button:not(.spinner-button)"); +if(btn.length){ +var _717=btn.outerWidth(); +var _718=btn.outerHeight(); +var _719=span.find(".spinner-button."+opts.clsLeft); +var _71a=span.find(".spinner-button."+opts.clsRight); +if(opts.buttonAlign=="right"){ +_71a.css("marginRight",_717+"px"); +}else{ +if(opts.buttonAlign=="left"){ +_719.css("marginLeft",_717+"px"); +}else{ +if(opts.buttonAlign=="top"){ +_71a.css("marginTop",_718+"px"); +}else{ +_719.css("marginBottom",_718+"px"); +} +} +} +} +} +opts.onResize.call(this,_715,_716); +}})); +$(_711).attr("spinnerName",$(_711).attr("textboxName")); +_712.spinner=$(_711).next(); +_712.spinner.addClass("spinner"); +if(opts.spinArrow){ +var _71b=_712.spinner.find(".spinner-button-updown"); +_71b.append(""+""+""+""+""+""); +}else{ +var _71c=$("").addClass(opts.clsLeft).appendTo(_712.spinner); +var _71d=$("").addClass(opts.clsRight).appendTo(_712.spinner); +_71c.linkbutton({iconCls:opts.reversed?"spinner-button-up":"spinner-button-down",onClick:function(){ +_71e(_711,!opts.reversed); +}}); +_71d.linkbutton({iconCls:opts.reversed?"spinner-button-down":"spinner-button-up",onClick:function(){ +_71e(_711,opts.reversed); +}}); +if(opts.disabled){ +$(_711).spinner("disable"); +} +if(opts.readonly){ +$(_711).spinner("readonly"); +} +} +$(_711).spinner("resize"); +}; +function _71e(_71f,down){ +var opts=$(_71f).spinner("options"); +opts.spin.call(_71f,down); +opts[down?"onSpinDown":"onSpinUp"].call(_71f); +$(_71f).spinner("validate"); +}; +$.fn.spinner=function(_720,_721){ +if(typeof _720=="string"){ +var _722=$.fn.spinner.methods[_720]; +if(_722){ +return _722(this,_721); +}else{ +return this.textbox(_720,_721); +} +} +_720=_720||{}; +return this.each(function(){ +var _723=$.data(this,"spinner"); +if(_723){ +$.extend(_723.options,_720); +}else{ +_723=$.data(this,"spinner",{options:$.extend({},$.fn.spinner.defaults,$.fn.spinner.parseOptions(this),_720)}); +} +_710(this); +}); +}; +$.fn.spinner.methods={options:function(jq){ +var opts=jq.textbox("options"); +return $.extend($.data(jq[0],"spinner").options,{width:opts.width,value:opts.value,originalValue:opts.originalValue,disabled:opts.disabled,readonly:opts.readonly}); +}}; +$.fn.spinner.parseOptions=function(_724){ +return $.extend({},$.fn.textbox.parseOptions(_724),$.parser.parseOptions(_724,["min","max","spinAlign",{increment:"number",reversed:"boolean"}])); +}; +$.fn.spinner.defaults=$.extend({},$.fn.textbox.defaults,{min:null,max:null,increment:1,spinAlign:"right",reversed:false,spin:function(down){ +},onSpinUp:function(){ +},onSpinDown:function(){ +}}); +})(jQuery); +(function($){ +function _725(_726){ +$(_726).addClass("numberspinner-f"); +var opts=$.data(_726,"numberspinner").options; +$(_726).numberbox($.extend({},opts,{doSize:false})).spinner(opts); +$(_726).numberbox("setValue",opts.value); +}; +function _727(_728,down){ +var opts=$.data(_728,"numberspinner").options; +var v=parseFloat($(_728).numberbox("getValue")||opts.value)||0; +if(down){ +v-=opts.increment; +}else{ +v+=opts.increment; +} +$(_728).numberbox("setValue",v); +}; +$.fn.numberspinner=function(_729,_72a){ +if(typeof _729=="string"){ +var _72b=$.fn.numberspinner.methods[_729]; +if(_72b){ +return _72b(this,_72a); +}else{ +return this.numberbox(_729,_72a); +} +} +_729=_729||{}; +return this.each(function(){ +var _72c=$.data(this,"numberspinner"); +if(_72c){ +$.extend(_72c.options,_729); +}else{ +$.data(this,"numberspinner",{options:$.extend({},$.fn.numberspinner.defaults,$.fn.numberspinner.parseOptions(this),_729)}); +} +_725(this); +}); +}; +$.fn.numberspinner.methods={options:function(jq){ +var opts=jq.numberbox("options"); +return $.extend($.data(jq[0],"numberspinner").options,{width:opts.width,value:opts.value,originalValue:opts.originalValue,disabled:opts.disabled,readonly:opts.readonly}); +}}; +$.fn.numberspinner.parseOptions=function(_72d){ +return $.extend({},$.fn.spinner.parseOptions(_72d),$.fn.numberbox.parseOptions(_72d),{}); +}; +$.fn.numberspinner.defaults=$.extend({},$.fn.spinner.defaults,$.fn.numberbox.defaults,{spin:function(down){ +_727(this,down); +}}); +})(jQuery); +(function($){ +function _72e(_72f){ +var opts=$.data(_72f,"timespinner").options; +$(_72f).addClass("timespinner-f").spinner(opts); +var _730=opts.formatter.call(_72f,opts.parser.call(_72f,opts.value)); +$(_72f).timespinner("initValue",_730); +}; +function _731(e){ +var _732=e.data.target; +var opts=$.data(_732,"timespinner").options; +var _733=$(_732).timespinner("getSelectionStart"); +for(var i=0;i=_734[0]&&_733<=_734[1]){ +_735(_732,i); +return; +} +} +}; +function _735(_736,_737){ +var opts=$.data(_736,"timespinner").options; +if(_737!=undefined){ +opts.highlight=_737; +} +var _738=opts.selections[opts.highlight]; +if(_738){ +var tb=$(_736).timespinner("textbox"); +$(_736).timespinner("setSelectionRange",{start:_738[0],end:_738[1]}); +tb.focus(); +} +}; +function _739(_73a,_73b){ +var opts=$.data(_73a,"timespinner").options; +var _73b=opts.parser.call(_73a,_73b); +var text=opts.formatter.call(_73a,_73b); +$(_73a).spinner("setValue",text); +}; +function _73c(_73d,down){ +var opts=$.data(_73d,"timespinner").options; +var s=$(_73d).timespinner("getValue"); +var _73e=opts.selections[opts.highlight]; +var s1=s.substring(0,_73e[0]); +var s2=s.substring(_73e[0],_73e[1]); +var s3=s.substring(_73e[1]); +if(s2==opts.ampm[0]){ +s2=opts.ampm[1]; +}else{ +if(s2==opts.ampm[1]){ +s2=opts.ampm[0]; +}else{ +s2=parseInt(s2,10)||0; +if(opts.selections.length-4==opts.highlight&&opts.hour12){ +if(s2==12){ +s2=0; +}else{ +if(s2==11&&!down){ +var tmp=s3.replace(opts.ampm[0],opts.ampm[1]); +if(s3!=tmp){ +s3=tmp; +}else{ +s3=s3.replace(opts.ampm[1],opts.ampm[0]); +} +} +} +} +s2=s2+opts.increment*(down?-1:1); +} +} +var v=s1+s2+s3; +$(_73d).timespinner("setValue",v); +_735(_73d); +}; +$.fn.timespinner=function(_73f,_740){ +if(typeof _73f=="string"){ +var _741=$.fn.timespinner.methods[_73f]; +if(_741){ +return _741(this,_740); +}else{ +return this.spinner(_73f,_740); +} +} +_73f=_73f||{}; +return this.each(function(){ +var _742=$.data(this,"timespinner"); +if(_742){ +$.extend(_742.options,_73f); +}else{ +$.data(this,"timespinner",{options:$.extend({},$.fn.timespinner.defaults,$.fn.timespinner.parseOptions(this),_73f)}); +} +_72e(this); +}); +}; +$.fn.timespinner.methods={options:function(jq){ +var opts=jq.data("spinner")?jq.spinner("options"):{}; +return $.extend($.data(jq[0],"timespinner").options,{width:opts.width,value:opts.value,originalValue:opts.originalValue,disabled:opts.disabled,readonly:opts.readonly}); +},setValue:function(jq,_743){ +return jq.each(function(){ +_739(this,_743); +}); +},getHours:function(jq){ +var opts=$.data(jq[0],"timespinner").options; +var date=opts.parser.call(jq[0],jq.timespinner("getValue")); +return date?date.getHours():null; +},getMinutes:function(jq){ +var opts=$.data(jq[0],"timespinner").options; +var date=opts.parser.call(jq[0],jq.timespinner("getValue")); +return date?date.getMinutes():null; +},getSeconds:function(jq){ +var opts=$.data(jq[0],"timespinner").options; +var date=opts.parser.call(jq[0],jq.timespinner("getValue")); +return date?date.getSeconds():null; +}}; +$.fn.timespinner.parseOptions=function(_744){ +return $.extend({},$.fn.spinner.parseOptions(_744),$.parser.parseOptions(_744,["separator",{hour12:"boolean",showSeconds:"boolean",highlight:"number"}])); +}; +$.fn.timespinner.defaults=$.extend({},$.fn.spinner.defaults,{inputEvents:$.extend({},$.fn.spinner.defaults.inputEvents,{click:function(e){ +_731.call(this,e); +},blur:function(e){ +var t=$(e.data.target); +t.timespinner("setValue",t.timespinner("getText")); +},keydown:function(e){ +if(e.keyCode==13){ +var t=$(e.data.target); +t.timespinner("setValue",t.timespinner("getText")); +} +}}),formatter:function(date){ +if(!date){ +return ""; +} +var opts=$(this).timespinner("options"); +var hour=date.getHours(); +var _745=date.getMinutes(); +var _746=date.getSeconds(); +var ampm=""; +if(opts.hour12){ +ampm=hour>=12?opts.ampm[1]:opts.ampm[0]; +hour=hour%12; +if(hour==0){ +hour=12; +} +} +var tt=[_747(hour),_747(_745)]; +if(opts.showSeconds){ +tt.push(_747(_746)); +} +var s=tt.join(opts.separator)+" "+ampm; +return $.trim(s); +function _747(_748){ +return (_748<10?"0":"")+_748; +}; +},parser:function(s){ +var opts=$(this).timespinner("options"); +var date=_749(s); +if(date){ +var min=_749(opts.min); +var max=_749(opts.max); +if(min&&min>date){ +date=min; +} +if(max&&max"]; +for(var i=0;i<_760.length;i++){ +_75f.cache[_760[i][0]]={width:_760[i][1]}; +} +var _761=0; +for(var s in _75f.cache){ +var item=_75f.cache[s]; +item.index=_761++; +ss.push(s+"{width:"+item.width+"}"); +} +ss.push(""); +$(ss.join("\n")).appendTo(cc); +cc.children("style[easyui]:not(:last)").remove(); +},getRule:function(_762){ +var _763=cc.children("style[easyui]:last")[0]; +var _764=_763.styleSheet?_763.styleSheet:(_763.sheet||document.styleSheets[document.styleSheets.length-1]); +var _765=_764.cssRules||_764.rules; +return _765[_762]; +},set:function(_766,_767){ +var item=_75f.cache[_766]; +if(item){ +item.width=_767; +var rule=this.getRule(item.index); +if(rule){ +rule.style["width"]=_767; +} +} +},remove:function(_768){ +var tmp=[]; +for(var s in _75f.cache){ +if(s.indexOf(_768)==-1){ +tmp.push([s,_75f.cache[s].width]); +} +} +_75f.cache={}; +this.add(tmp); +},dirty:function(_769){ +if(_769){ +_75f.dirty.push(_769); +} +},clean:function(){ +for(var i=0;i<_75f.dirty.length;i++){ +this.remove(_75f.dirty[i]); +} +_75f.dirty=[]; +}}; +}; +function _76a(_76b,_76c){ +var _76d=$.data(_76b,"datagrid"); +var opts=_76d.options; +var _76e=_76d.panel; +if(_76c){ +$.extend(opts,_76c); +} +if(opts.fit==true){ +var p=_76e.panel("panel").parent(); +opts.width=p.width(); +opts.height=p.height(); +} +_76e.panel("resize",opts); +}; +function _76f(_770){ +var _771=$.data(_770,"datagrid"); +var opts=_771.options; +var dc=_771.dc; +var wrap=_771.panel; +if(!wrap.is(":visible")){ +return; +} +var _772=wrap.width(); +var _773=wrap.height(); +var view=dc.view; +var _774=dc.view1; +var _775=dc.view2; +var _776=_774.children("div.datagrid-header"); +var _777=_775.children("div.datagrid-header"); +var _778=_776.find("table"); +var _779=_777.find("table"); +view.width(_772); +var _77a=_776.children("div.datagrid-header-inner").show(); +_774.width(_77a.find("table").width()); +if(!opts.showHeader){ +_77a.hide(); +} +_775.width(_772-_774._outerWidth()); +_774.children()._outerWidth(_774.width()); +_775.children()._outerWidth(_775.width()); +var all=_776.add(_777).add(_778).add(_779); +all.css("height",""); +var hh=Math.max(_778.height(),_779.height()); +all._outerHeight(hh); +view.children(".datagrid-empty").css("top",hh+"px"); +dc.body1.add(dc.body2).children("table.datagrid-btable-frozen").css({position:"absolute",top:dc.header2._outerHeight()}); +var _77b=dc.body2.children("table.datagrid-btable-frozen")._outerHeight(); +var _77c=_77b+_777._outerHeight()+_775.children(".datagrid-footer")._outerHeight(); +wrap.children(":not(.datagrid-view,.datagrid-mask,.datagrid-mask-msg)").each(function(){ +_77c+=$(this)._outerHeight(); +}); +var _77d=wrap.outerHeight()-wrap.height(); +var _77e=wrap._size("minHeight")||""; +var _77f=wrap._size("maxHeight")||""; +_774.add(_775).children("div.datagrid-body").css({marginTop:_77b,height:(isNaN(parseInt(opts.height))?"":(_773-_77c)),minHeight:(_77e?_77e-_77d-_77c:""),maxHeight:(_77f?_77f-_77d-_77c:"")}); +view.height(_775.height()); +}; +function _780(_781,_782,_783){ +var rows=$.data(_781,"datagrid").data.rows; +var opts=$.data(_781,"datagrid").options; +var dc=$.data(_781,"datagrid").dc; +var tmp=$("").appendTo("body"); +var _784=tmp.outerHeight(); +tmp.remove(); +if(!dc.body1.is(":empty")&&(!opts.nowrap||opts.autoRowHeight||_783)){ +if(_782!=undefined){ +var tr1=opts.finder.getTr(_781,_782,"body",1); +var tr2=opts.finder.getTr(_781,_782,"body",2); +_785(tr1,tr2); +}else{ +var tr1=opts.finder.getTr(_781,0,"allbody",1); +var tr2=opts.finder.getTr(_781,0,"allbody",2); +_785(tr1,tr2); +if(opts.showFooter){ +var tr1=opts.finder.getTr(_781,0,"allfooter",1); +var tr2=opts.finder.getTr(_781,0,"allfooter",2); +_785(tr1,tr2); +} +} +} +_76f(_781); +if(opts.height=="auto"){ +var _786=dc.body1.parent(); +var _787=dc.body2; +var _788=_789(_787); +var _78a=_788.height; +if(_788.width>_787.width()){ +_78a+=18; +} +_78a-=parseInt(_787.css("marginTop"))||0; +_786.height(_78a); +_787.height(_78a); +dc.view.height(dc.view2.height()); +} +dc.body2.triggerHandler("scroll"); +function _785(trs1,trs2){ +for(var i=0;i"); +} +_792(true); +_792(false); +_76f(_78f); +function _792(_793){ +var _794=_793?1:2; +var tr=opts.finder.getTr(_78f,_790,"body",_794); +(_793?dc.body1:dc.body2).children("table.datagrid-btable-frozen").append(tr); +}; +}; +function _795(_796,_797){ +function _798(){ +var _799=[]; +var _79a=[]; +$(_796).children("thead").each(function(){ +var opt=$.parser.parseOptions(this,[{frozen:"boolean"}]); +$(this).find("tr").each(function(){ +var cols=[]; +$(this).find("th").each(function(){ +var th=$(this); +var col=$.extend({},$.parser.parseOptions(this,["id","field","align","halign","order","width",{sortable:"boolean",checkbox:"boolean",resizable:"boolean",fixed:"boolean"},{rowspan:"number",colspan:"number"}]),{title:(th.html()||undefined),hidden:(th.attr("hidden")?true:undefined),hformatter:(th.attr("hformatter")?eval(th.attr("hformatter")):undefined),hstyler:(th.attr("hstyler")?eval(th.attr("hstyler")):undefined),formatter:(th.attr("formatter")?eval(th.attr("formatter")):undefined),styler:(th.attr("styler")?eval(th.attr("styler")):undefined),sorter:(th.attr("sorter")?eval(th.attr("sorter")):undefined)}); +if(col.width&&String(col.width).indexOf("%")==-1){ +col.width=parseInt(col.width); +} +if(th.attr("editor")){ +var s=$.trim(th.attr("editor")); +if(s.substr(0,1)=="{"){ +col.editor=eval("("+s+")"); +}else{ +col.editor=s; +} +} +cols.push(col); +}); +opt.frozen?_799.push(cols):_79a.push(cols); +}); +}); +return [_799,_79a]; +}; +var _79b=$("
"+"
"+"
"+"
"+"
"+"
"+"
"+"
"+"
"+"
"+""+"
"+"
"+"
"+"
"+"
"+"
"+"
"+"
"+""+"
"+"
"+"
"+"
").insertAfter(_796); +_79b.panel({doSize:false,cls:"datagrid"}); +$(_796).addClass("datagrid-f").hide().appendTo(_79b.children("div.datagrid-view")); +var cc=_798(); +var view=_79b.children("div.datagrid-view"); +var _79c=view.children("div.datagrid-view1"); +var _79d=view.children("div.datagrid-view2"); +return {panel:_79b,frozenColumns:cc[0],columns:cc[1],dc:{view:view,view1:_79c,view2:_79d,header1:_79c.children("div.datagrid-header").children("div.datagrid-header-inner"),header2:_79d.children("div.datagrid-header").children("div.datagrid-header-inner"),body1:_79c.children("div.datagrid-body").children("div.datagrid-body-inner"),body2:_79d.children("div.datagrid-body"),footer1:_79c.children("div.datagrid-footer").children("div.datagrid-footer-inner"),footer2:_79d.children("div.datagrid-footer").children("div.datagrid-footer-inner")}}; +}; +function _79e(_79f){ +var _7a0=$.data(_79f,"datagrid"); +var opts=_7a0.options; +var dc=_7a0.dc; +var _7a1=_7a0.panel; +_7a0.ss=$(_79f).datagrid("createStyleSheet"); +_7a1.panel($.extend({},opts,{id:null,doSize:false,onResize:function(_7a2,_7a3){ +if($.data(_79f,"datagrid")){ +_76f(_79f); +$(_79f).datagrid("fitColumns"); +opts.onResize.call(_7a1,_7a2,_7a3); +} +},onExpand:function(){ +if($.data(_79f,"datagrid")){ +$(_79f).datagrid("fixRowHeight").datagrid("fitColumns"); +opts.onExpand.call(_7a1); +} +}})); +var _7a4=$(_79f).attr("id")||""; +if(_7a4){ +_7a4+="_"; +} +_7a0.rowIdPrefix=_7a4+"datagrid-row-r"+(++_755); +_7a0.cellClassPrefix=_7a4+"datagrid-cell-c"+_755; +_7a5(dc.header1,opts.frozenColumns,true); +_7a5(dc.header2,opts.columns,false); +_7a6(); +dc.header1.add(dc.header2).css("display",opts.showHeader?"block":"none"); +dc.footer1.add(dc.footer2).css("display",opts.showFooter?"block":"none"); +if(opts.toolbar){ +if($.isArray(opts.toolbar)){ +$("div.datagrid-toolbar",_7a1).remove(); +var tb=$("
").prependTo(_7a1); +var tr=tb.find("tr"); +for(var i=0;i
").appendTo(tr); +}else{ +var td=$("").appendTo(tr); +btn.type=btn.type||"linkbutton"; +btn.plain=btn.plain||true; +var tool=$("").appendTo(td); +tool[0].onclick=eval(btn.handler||function(){ +}); +tool[btn.type](btn); +if(btn.onInit){ +btn.onInit.call(tool[0]); +} +} +} +}else{ +$(opts.toolbar).addClass("datagrid-toolbar").prependTo(_7a1); +$(opts.toolbar).show(); +} +}else{ +$("div.datagrid-toolbar",_7a1).remove(); +} +$("div.datagrid-pager",_7a1).remove(); +if(opts.pagination){ +var _7a7=$("
"); +if(opts.pagePosition=="bottom"){ +_7a7.appendTo(_7a1); +}else{ +if(opts.pagePosition=="top"){ +_7a7.addClass("datagrid-pager-top").prependTo(_7a1); +}else{ +var ptop=$("
").prependTo(_7a1); +_7a7.appendTo(_7a1); +_7a7=_7a7.add(ptop); +} +} +_7a7.pagination({total:0,pageNumber:opts.pageNumber,pageSize:opts.pageSize,pageList:opts.pageList,onSelectPage:function(_7a8,_7a9){ +opts.pageNumber=_7a8||1; +opts.pageSize=_7a9; +_7a7.pagination("refresh",{pageNumber:_7a8,pageSize:_7a9}); +_7f3(_79f); +}}); +opts.pageSize=_7a7.pagination("options").pageSize; +} +function _7a5(_7aa,_7ab,_7ac){ +if(!_7ab){ +return; +} +$(_7aa).show(); +$(_7aa).empty(); +var tmp=$("
").appendTo("body"); +tmp._outerWidth(99); +var _7ad=100-parseInt(tmp[0].style.width); +tmp.remove(); +var _7ae=[]; +var _7af=[]; +var _7b0=[]; +if(opts.sortName){ +_7ae=opts.sortName.split(","); +_7af=opts.sortOrder.split(","); +} +var t=$("
").appendTo(_7aa); +for(var i=0;i<_7ab.length;i++){ +var tr=$("").appendTo($("tbody",t)); +var cols=_7ab[i]; +for(var j=0;j").appendTo(tr); +if(col.checkbox){ +td.attr("field",col.field); +$("
").html("").appendTo(td); +}else{ +if(col.field){ +td.attr("field",col.field); +td.append("
"); +td.find("span:first").html(col.hformatter?col.hformatter(col.title,col):col.title); +var cell=td.find("div.datagrid-cell"); +var pos=_756(_7ae,col.field); +if(pos>=0){ +cell.addClass("datagrid-sort-"+_7af[pos]); +} +if(col.sortable){ +cell.addClass("datagrid-sort"); +} +if(col.resizable==false){ +cell.attr("resizable","false"); +} +if(col.width){ +var _7b3=$.parser.parseValue("width",col.width,dc.view,opts.scrollbarSize+(opts.rownumbers?opts.rownumberWidth:0)); +col.deltaWidth=_7ad; +col.boxWidth=_7b3-_7ad; +}else{ +col.auto=true; +} +cell.css("text-align",(col.halign||col.align||"")); +col.cellClass=_7a0.cellClassPrefix+"-"+col.field.replace(/[\.|\s]/g,"-"); +cell.addClass(col.cellClass); +}else{ +$("
").html(col.hformatter?col.hformatter(col.title,col):col.title).appendTo(td); +} +} +if(col.hidden){ +td.hide(); +_7b0.push(col.field); +} +} +} +if(_7ac&&opts.rownumbers){ +var td=$("
"); +if($("tr",t).length==0){ +td.wrap("").parent().appendTo($("tbody",t)); +}else{ +td.prependTo($("tr:first",t)); +} +} +for(var i=0;i<_7b0.length;i++){ +_7f5(_79f,_7b0[i],-1); +} +}; +function _7a6(){ +var _7b4=[[".datagrid-header-rownumber",(opts.rownumberWidth-1)+"px"],[".datagrid-cell-rownumber",(opts.rownumberWidth-1)+"px"]]; +var _7b5=_7b6(_79f,true).concat(_7b6(_79f)); +for(var i=0;i<_7b5.length;i++){ +var col=_7b7(_79f,_7b5[i]); +if(col&&!col.checkbox){ +_7b4.push(["."+col.cellClass,col.boxWidth?col.boxWidth+"px":"auto"]); +} +} +_7a0.ss.add(_7b4); +_7a0.ss.dirty(_7a0.cellSelectorPrefix); +_7a0.cellSelectorPrefix="."+_7a0.cellClassPrefix; +}; +}; +function _7b8(_7b9){ +var _7ba=$.data(_7b9,"datagrid"); +var _7bb=_7ba.panel; +var opts=_7ba.options; +var dc=_7ba.dc; +var _7bc=dc.header1.add(dc.header2); +_7bc._unbind(".datagrid"); +for(var _7bd in opts.headerEvents){ +_7bc._bind(_7bd+".datagrid",opts.headerEvents[_7bd]); +} +var _7be=_7bc.find("div.datagrid-cell"); +var _7bf=opts.resizeHandle=="right"?"e":(opts.resizeHandle=="left"?"w":"e,w"); +_7be.each(function(){ +$(this).resizable({handles:_7bf,edge:opts.resizeEdge,disabled:($(this).attr("resizable")?$(this).attr("resizable")=="false":false),minWidth:25,onStartResize:function(e){ +_7ba.resizing=true; +_7bc.css("cursor",$("body").css("cursor")); +if(!_7ba.proxy){ +_7ba.proxy=$("
").appendTo(dc.view); +} +if(e.data.dir=="e"){ +e.data.deltaEdge=$(this)._outerWidth()-(e.pageX-$(this).offset().left); +}else{ +e.data.deltaEdge=$(this).offset().left-e.pageX-1; +} +_7ba.proxy.css({left:e.pageX-$(_7bb).offset().left-1+e.data.deltaEdge,display:"none"}); +setTimeout(function(){ +if(_7ba.proxy){ +_7ba.proxy.show(); +} +},500); +},onResize:function(e){ +_7ba.proxy.css({left:e.pageX-$(_7bb).offset().left-1+e.data.deltaEdge,display:"block"}); +return false; +},onStopResize:function(e){ +_7bc.css("cursor",""); +$(this).css("height",""); +var _7c0=$(this).parent().attr("field"); +var col=_7b7(_7b9,_7c0); +col.width=$(this)._outerWidth()+1; +col.boxWidth=col.width-col.deltaWidth; +col.auto=undefined; +$(this).css("width",""); +$(_7b9).datagrid("fixColumnSize",_7c0); +_7ba.proxy.remove(); +_7ba.proxy=null; +if($(this).parents("div:first.datagrid-header").parent().hasClass("datagrid-view1")){ +_76f(_7b9); +} +$(_7b9).datagrid("fitColumns"); +opts.onResizeColumn.call(_7b9,_7c0,col.width); +setTimeout(function(){ +_7ba.resizing=false; +},0); +}}); +}); +var bb=dc.body1.add(dc.body2); +bb._unbind(); +for(var _7bd in opts.rowEvents){ +bb._bind(_7bd,opts.rowEvents[_7bd]); +} +dc.body1._bind("mousewheel DOMMouseScroll MozMousePixelScroll",function(e){ +e.preventDefault(); +var e1=e.originalEvent||window.event; +var _7c1=e1.wheelDelta||e1.detail*(-1); +if("deltaY" in e1){ +_7c1=e1.deltaY*-1; +} +var dg=$(e.target).closest("div.datagrid-view").children(".datagrid-f"); +var dc=dg.data("datagrid").dc; +dc.body2.scrollTop(dc.body2.scrollTop()-_7c1); +}); +dc.body2._bind("scroll",function(){ +var b1=dc.view1.children("div.datagrid-body"); +var stv=$(this).scrollTop(); +$(this).scrollTop(stv); +b1.scrollTop(stv); +var c1=dc.body1.children(":first"); +var c2=dc.body2.children(":first"); +if(c1.length&&c2.length){ +var top1=c1.offset().top; +var top2=c2.offset().top; +if(top1!=top2){ +b1.scrollTop(b1.scrollTop()+top1-top2); +} +} +dc.view2.children("div.datagrid-header,div.datagrid-footer")._scrollLeft($(this)._scrollLeft()); +dc.body2.children("table.datagrid-btable-frozen").css("left",-$(this)._scrollLeft()); +}); +}; +function _7c2(_7c3){ +return function(e){ +var td=$(e.target).closest("td[field]"); +if(td.length){ +var _7c4=_7c5(td); +if(!$(_7c4).data("datagrid").resizing&&_7c3){ +td.addClass("datagrid-header-over"); +}else{ +td.removeClass("datagrid-header-over"); +} +} +}; +}; +function _7c6(e){ +var _7c7=_7c5(e.target); +var opts=$(_7c7).datagrid("options"); +var ck=$(e.target).closest("input[type=checkbox]"); +if(ck.length){ +if(opts.singleSelect&&opts.selectOnCheck){ +return false; +} +if(ck.is(":checked")){ +_7c8(_7c7); +}else{ +_7c9(_7c7); +} +e.stopPropagation(); +}else{ +var cell=$(e.target).closest(".datagrid-cell"); +if(cell.length){ +var p1=cell.offset().left+5; +var p2=cell.offset().left+cell._outerWidth()-5; +if(e.pageXp1){ +_7ca(_7c7,cell.parent().attr("field")); +} +} +} +}; +function _7cb(e){ +var _7cc=_7c5(e.target); +var opts=$(_7cc).datagrid("options"); +var cell=$(e.target).closest(".datagrid-cell"); +if(cell.length){ +var p1=cell.offset().left+5; +var p2=cell.offset().left+cell._outerWidth()-5; +var cond=opts.resizeHandle=="right"?(e.pageX>p2):(opts.resizeHandle=="left"?(e.pageXp2)); +if(cond){ +var _7cd=cell.parent().attr("field"); +var col=_7b7(_7cc,_7cd); +if(col.resizable==false){ +return; +} +$(_7cc).datagrid("autoSizeColumn",_7cd); +col.auto=false; +} +} +}; +function _7ce(e){ +var _7cf=_7c5(e.target); +var opts=$(_7cf).datagrid("options"); +var td=$(e.target).closest("td[field]"); +opts.onHeaderContextMenu.call(_7cf,e,td.attr("field")); +}; +function _7d0(_7d1){ +return function(e){ +var tr=_7d2(e.target); +if(!tr){ +return; +} +var _7d3=_7c5(tr); +if($.data(_7d3,"datagrid").resizing){ +return; +} +var _7d4=_7d5(tr); +if(_7d1){ +_7d6(_7d3,_7d4); +}else{ +var opts=$.data(_7d3,"datagrid").options; +opts.finder.getTr(_7d3,_7d4).removeClass("datagrid-row-over"); +} +}; +}; +function _7d7(e){ +var tr=_7d2(e.target); +if(!tr){ +return; +} +var _7d8=_7c5(tr); +var opts=$.data(_7d8,"datagrid").options; +var _7d9=_7d5(tr); +var tt=$(e.target); +if(tt.parent().hasClass("datagrid-cell-check")){ +if(opts.singleSelect&&opts.selectOnCheck){ +tt._propAttr("checked",!tt.is(":checked")); +_7da(_7d8,_7d9); +}else{ +if(tt.is(":checked")){ +tt._propAttr("checked",false); +_7da(_7d8,_7d9); +}else{ +tt._propAttr("checked",true); +_7db(_7d8,_7d9); +} +} +}else{ +var row=opts.finder.getRow(_7d8,_7d9); +var td=tt.closest("td[field]",tr); +if(td.length){ +var _7dc=td.attr("field"); +opts.onClickCell.call(_7d8,_7d9,_7dc,row[_7dc]); +} +if(opts.singleSelect==true){ +_7dd(_7d8,_7d9); +}else{ +if(opts.ctrlSelect){ +if(e.metaKey||e.ctrlKey){ +if(tr.hasClass("datagrid-row-selected")){ +_7de(_7d8,_7d9); +}else{ +_7dd(_7d8,_7d9); +} +}else{ +if(e.shiftKey){ +$(_7d8).datagrid("clearSelections"); +var _7df=Math.min(opts.lastSelectedIndex||0,_7d9); +var _7e0=Math.max(opts.lastSelectedIndex||0,_7d9); +for(var i=_7df;i<=_7e0;i++){ +_7dd(_7d8,i); +} +}else{ +$(_7d8).datagrid("clearSelections"); +_7dd(_7d8,_7d9); +opts.lastSelectedIndex=_7d9; +} +} +}else{ +if(tr.hasClass("datagrid-row-selected")){ +_7de(_7d8,_7d9); +}else{ +_7dd(_7d8,_7d9); +} +} +} +opts.onClickRow.apply(_7d8,_759(_7d8,[_7d9,row])); +} +}; +function _7e1(e){ +var tr=_7d2(e.target); +if(!tr){ +return; +} +var _7e2=_7c5(tr); +var opts=$.data(_7e2,"datagrid").options; +var _7e3=_7d5(tr); +var row=opts.finder.getRow(_7e2,_7e3); +var td=$(e.target).closest("td[field]",tr); +if(td.length){ +var _7e4=td.attr("field"); +opts.onDblClickCell.call(_7e2,_7e3,_7e4,row[_7e4]); +} +opts.onDblClickRow.apply(_7e2,_759(_7e2,[_7e3,row])); +}; +function _7e5(e){ +var tr=_7d2(e.target); +if(tr){ +var _7e6=_7c5(tr); +var opts=$.data(_7e6,"datagrid").options; +var _7e7=_7d5(tr); +var row=opts.finder.getRow(_7e6,_7e7); +opts.onRowContextMenu.call(_7e6,e,_7e7,row); +}else{ +var body=_7d2(e.target,".datagrid-body"); +if(body){ +var _7e6=_7c5(body); +var opts=$.data(_7e6,"datagrid").options; +opts.onRowContextMenu.call(_7e6,e,-1,null); +} +} +}; +function _7c5(t){ +return $(t).closest("div.datagrid-view").children(".datagrid-f")[0]; +}; +function _7d2(t,_7e8){ +var tr=$(t).closest(_7e8||"tr.datagrid-row"); +if(tr.length&&tr.parent().length){ +return tr; +}else{ +return undefined; +} +}; +function _7d5(tr){ +if(tr.attr("datagrid-row-index")){ +return parseInt(tr.attr("datagrid-row-index")); +}else{ +return tr.attr("node-id"); +} +}; +function _7ca(_7e9,_7ea){ +var _7eb=$.data(_7e9,"datagrid"); +var opts=_7eb.options; +_7ea=_7ea||{}; +var _7ec={sortName:opts.sortName,sortOrder:opts.sortOrder}; +if(typeof _7ea=="object"){ +$.extend(_7ec,_7ea); +} +var _7ed=[]; +var _7ee=[]; +if(_7ec.sortName){ +_7ed=_7ec.sortName.split(","); +_7ee=_7ec.sortOrder.split(","); +} +if(typeof _7ea=="string"){ +var _7ef=_7ea; +var col=_7b7(_7e9,_7ef); +if(!col.sortable||_7eb.resizing){ +return; +} +var _7f0=col.order||"asc"; +var pos=_756(_7ed,_7ef); +if(pos>=0){ +var _7f1=_7ee[pos]=="asc"?"desc":"asc"; +if(opts.multiSort&&_7f1==_7f0){ +_7ed.splice(pos,1); +_7ee.splice(pos,1); +}else{ +_7ee[pos]=_7f1; +} +}else{ +if(opts.multiSort){ +_7ed.push(_7ef); +_7ee.push(_7f0); +}else{ +_7ed=[_7ef]; +_7ee=[_7f0]; +} +} +_7ec.sortName=_7ed.join(","); +_7ec.sortOrder=_7ee.join(","); +} +if(opts.onBeforeSortColumn.call(_7e9,_7ec.sortName,_7ec.sortOrder)==false){ +return; +} +$.extend(opts,_7ec); +var dc=_7eb.dc; +var _7f2=dc.header1.add(dc.header2); +_7f2.find("div.datagrid-cell").removeClass("datagrid-sort-asc datagrid-sort-desc"); +for(var i=0;i<_7ed.length;i++){ +var col=_7b7(_7e9,_7ed[i]); +_7f2.find("div."+col.cellClass).addClass("datagrid-sort-"+_7ee[i]); +} +if(opts.remoteSort){ +_7f3(_7e9); +}else{ +_7f4(_7e9,$(_7e9).datagrid("getData")); +} +opts.onSortColumn.call(_7e9,opts.sortName,opts.sortOrder); +}; +function _7f5(_7f6,_7f7,_7f8){ +_7f9(true); +_7f9(false); +function _7f9(_7fa){ +var aa=_7fb(_7f6,_7fa); +if(aa.length){ +var _7fc=aa[aa.length-1]; +var _7fd=_756(_7fc,_7f7); +if(_7fd>=0){ +for(var _7fe=0;_7fe=_803.find("table").width()){ +dc.body2.css("overflow-x","hidden"); +} +if(!opts.showHeader){ +_804.hide(); +} +function _807(){ +if(!opts.fitColumns){ +return; +} +if(!_802.leftWidth){ +_802.leftWidth=0; +} +var _808=0; +var cc=[]; +var _809=_7b6(_801,false); +for(var i=0;i<_809.length;i++){ +var col=_7b7(_801,_809[i]); +if(_80a(col)){ +_808+=col.width; +cc.push({field:col.field,col:col,addingWidth:0}); +} +} +if(!_808){ +return; +} +cc[cc.length-1].addingWidth-=_802.leftWidth; +_804.show(); +var _80b=_803.width()-_803.find("table").width()-opts.scrollbarSize+_802.leftWidth; +var rate=_80b/_808; +if(!opts.showHeader){ +_804.hide(); +} +for(var i=0;i0){ +c.col.boxWidth+=c.addingWidth; +c.col.width+=c.addingWidth; +} +} +_802.leftWidth=_80b; +$(_801).datagrid("fixColumnSize"); +}; +function _806(){ +var _80d=false; +var _80e=_7b6(_801,true).concat(_7b6(_801,false)); +$.map(_80e,function(_80f){ +var col=_7b7(_801,_80f); +if(String(col.width||"").indexOf("%")>=0){ +var _810=$.parser.parseValue("width",col.width,dc.view,opts.scrollbarSize+(opts.rownumbers?opts.rownumberWidth:0))-col.deltaWidth; +if(_810>0){ +col.boxWidth=_810; +_80d=true; +} +} +}); +if(_80d){ +$(_801).datagrid("fixColumnSize"); +} +}; +function _805(fit){ +var _811=dc.header1.add(dc.header2).find(".datagrid-cell-group"); +if(_811.length){ +_811.each(function(){ +$(this)._outerWidth(fit?$(this).parent().width():10); +}); +if(fit){ +_76f(_801); +} +} +}; +function _80a(col){ +if(String(col.width||"").indexOf("%")>=0){ +return false; +} +if(!col.hidden&&!col.checkbox&&!col.auto&&!col.fixed){ +return true; +} +}; +}; +function _812(_813,_814){ +var _815=$.data(_813,"datagrid"); +var opts=_815.options; +var dc=_815.dc; +var tmp=$("
").appendTo("body"); +if(_814){ +_76a(_814); +$(_813).datagrid("fitColumns"); +}else{ +var _816=false; +var _817=_7b6(_813,true).concat(_7b6(_813,false)); +for(var i=0;i<_817.length;i++){ +var _814=_817[i]; +var col=_7b7(_813,_814); +if(col.auto){ +_76a(_814); +_816=true; +} +} +if(_816){ +$(_813).datagrid("fitColumns"); +} +} +tmp.remove(); +function _76a(_818){ +var _819=dc.view.find("div.datagrid-header td[field=\""+_818+"\"] div.datagrid-cell"); +_819.css("width",""); +var col=$(_813).datagrid("getColumnOption",_818); +col.width=undefined; +col.boxWidth=undefined; +col.auto=true; +$(_813).datagrid("fixColumnSize",_818); +var _81a=Math.max(_81b("header"),_81b("allbody"),_81b("allfooter"))+1; +_819._outerWidth(_81a-1); +col.width=_81a; +col.boxWidth=parseInt(_819[0].style.width); +col.deltaWidth=_81a-col.boxWidth; +_819.css("width",""); +$(_813).datagrid("fixColumnSize",_818); +opts.onResizeColumn.call(_813,_818,col.width); +function _81b(type){ +var _81c=0; +if(type=="header"){ +_81c=_81d(_819); +}else{ +opts.finder.getTr(_813,0,type).find("td[field=\""+_818+"\"] div.datagrid-cell").each(function(){ +var w=_81d($(this)); +if(_81c1){ +var col=_7b7(_826,td.attr("field")); +var _828=col.boxWidth+col.deltaWidth-1; +for(var i=1;i<_827;i++){ +td=td.next(); +col=_7b7(_826,td.attr("field")); +_828+=col.boxWidth+col.deltaWidth; +} +$(this).children("div.datagrid-cell")._outerWidth(_828); +} +}); +}; +function _824(_829){ +var dc=$.data(_829,"datagrid").dc; +dc.view.find("div.datagrid-editable").each(function(){ +var cell=$(this); +var _82a=cell.parent().attr("field"); +var col=$(_829).datagrid("getColumnOption",_82a); +cell._outerWidth(col.boxWidth+col.deltaWidth-1); +var ed=$.data(this,"datagrid.editor"); +if(ed.actions.resize){ +ed.actions.resize(ed.target,cell.width()); +} +}); +}; +function _7b7(_82b,_82c){ +function find(_82d){ +if(_82d){ +for(var i=0;i<_82d.length;i++){ +var cc=_82d[i]; +for(var j=0;j=0){ +var _836=col.field||col.id||""; +for(var c=0;c<(col.colspan||1);c++){ +for(var r=0;r<(col.rowspan||1);r++){ +aa[_833+r][_834]=_836; +} +_834++; +} +} +}); +} +return aa; +function _832(){ +var _837=0; +$.map(_830[0]||[],function(col){ +_837+=col.colspan||1; +}); +return _837; +}; +function _835(a){ +for(var i=0;ib?1:-1); +}; +r=_83e(r1[sn],r2[sn],r1,r2)*(so=="asc"?1:-1); +if(r!=0){ +return r; +} +} +return r; +}); +} +if(opts.view.onBeforeRender){ +opts.view.onBeforeRender.call(opts.view,_83a,data.rows); +} +opts.view.render.call(opts.view,_83a,dc.body2,false); +opts.view.render.call(opts.view,_83a,dc.body1,true); +if(opts.showFooter){ +opts.view.renderFooter.call(opts.view,_83a,dc.footer2,false); +opts.view.renderFooter.call(opts.view,_83a,dc.footer1,true); +} +if(opts.view.onAfterRender){ +opts.view.onAfterRender.call(opts.view,_83a); +} +_83b.ss.clean(); +var _83f=$(_83a).datagrid("getPager"); +if(_83f.length){ +var _840=_83f.pagination("options"); +if(_840.total!=data.total){ +_83f.pagination("refresh",{pageNumber:opts.pageNumber,total:data.total}); +if(opts.pageNumber!=_840.pageNumber&&_840.pageNumber>0){ +opts.pageNumber=_840.pageNumber; +_7f3(_83a); +} +} +} +_780(_83a); +dc.body2.triggerHandler("scroll"); +$(_83a).datagrid("setSelectionState"); +$(_83a).datagrid("autoSizeColumn"); +opts.onLoadSuccess.call(_83a,data); +}; +function _841(_842){ +var _843=$.data(_842,"datagrid"); +var opts=_843.options; +var dc=_843.dc; +dc.header1.add(dc.header2).find("input[type=checkbox]")._propAttr("checked",false); +if(opts.idField){ +var _844=$.data(_842,"treegrid")?true:false; +var _845=opts.onSelect; +var _846=opts.onCheck; +opts.onSelect=opts.onCheck=function(){ +}; +var rows=opts.finder.getRows(_842); +for(var i=0;i_857.height()-_858){ +_857.scrollTop(_857.scrollTop()+top+tr._outerHeight()-_857.height()+_858); +} +} +} +}; +function _7d6(_85a,_85b){ +var _85c=$.data(_85a,"datagrid"); +var opts=_85c.options; +opts.finder.getTr(_85a,_85c.highlightIndex).removeClass("datagrid-row-over"); +opts.finder.getTr(_85a,_85b).addClass("datagrid-row-over"); +_85c.highlightIndex=_85b; +}; +function _7dd(_85d,_85e,_85f,_860){ +var _861=$.data(_85d,"datagrid"); +var opts=_861.options; +var row=opts.finder.getRow(_85d,_85e); +if(!row){ +return; +} +var tr=opts.finder.getTr(_85d,_85e); +if(tr.hasClass("datagrid-row-selected")){ +return; +} +if(opts.onBeforeSelect.apply(_85d,_759(_85d,[_85e,row]))==false){ +return; +} +if(opts.singleSelect){ +_862(_85d,true); +_861.selectedRows=[]; +} +if(!_85f&&opts.checkOnSelect){ +_7da(_85d,_85e,true); +} +if(opts.idField){ +_758(_861.selectedRows,opts.idField,row); +} +tr.addClass("datagrid-row-selected"); +if(_861.selectingData){ +_861.selectingData.push(row); +} +opts.onSelect.apply(_85d,_759(_85d,[_85e,row])); +if(!_860&&opts.scrollOnSelect){ +_852(_85d,_85e); +} +}; +function _7de(_863,_864,_865){ +var _866=$.data(_863,"datagrid"); +var dc=_866.dc; +var opts=_866.options; +var row=opts.finder.getRow(_863,_864); +if(!row){ +return; +} +var tr=opts.finder.getTr(_863,_864); +if(!tr.hasClass("datagrid-row-selected")){ +return; +} +if(opts.onBeforeUnselect.apply(_863,_759(_863,[_864,row]))==false){ +return; +} +if(!_865&&opts.checkOnSelect){ +_7db(_863,_864,true); +} +tr.removeClass("datagrid-row-selected"); +if(opts.idField){ +_757(_866.selectedRows,opts.idField,row[opts.idField]); +} +if(_866.selectingData){ +_866.selectingData.push(row); +} +opts.onUnselect.apply(_863,_759(_863,[_864,row])); +}; +function _867(_868,_869){ +var _86a=$.data(_868,"datagrid"); +var opts=_86a.options; +var _86b=$.data(_868,"treegrid")?true:false; +var _86c=opts.scrollOnSelect; +opts.scrollOnSelect=false; +_86a.selectingData=[]; +if(!_869&&opts.checkOnSelect){ +_7c8(_868,true); +} +var rows=opts.finder.getRows(_868); +for(var i=0;i"); +cell.children("table")._bind("click dblclick contextmenu",function(e){ +e.stopPropagation(); +}); +$.data(cell[0],"datagrid.editor",{actions:_8b1,target:_8b1.init(cell.find("td"),$.extend({height:opts.editorHeight},_8b0)),field:_8ae,type:_8af,oldHtml:_8b2}); +} +} +}); +_780(_8ac,_8ad,true); +}; +function _8a3(_8b4,_8b5){ +var opts=$.data(_8b4,"datagrid").options; +var tr=opts.finder.getTr(_8b4,_8b5); +tr.children("td").each(function(){ +var cell=$(this).find("div.datagrid-editable"); +if(cell.length){ +var ed=$.data(cell[0],"datagrid.editor"); +if(ed.actions.destroy){ +ed.actions.destroy(ed.target); +} +cell.html(ed.oldHtml); +$.removeData(cell[0],"datagrid.editor"); +cell.removeClass("datagrid-editable"); +cell.css("width",""); +} +}); +}; +function _896(_8b6,_8b7){ +var tr=$.data(_8b6,"datagrid").options.finder.getTr(_8b6,_8b7); +if(!tr.hasClass("datagrid-row-editing")){ +return true; +} +var vbox=tr.find(".validatebox-text"); +vbox.validatebox("validate"); +vbox.trigger("mouseleave"); +var _8b8=tr.find(".validatebox-invalid"); +return _8b8.length==0; +}; +function _8b9(_8ba,_8bb){ +var _8bc=$.data(_8ba,"datagrid").insertedRows; +var _8bd=$.data(_8ba,"datagrid").deletedRows; +var _8be=$.data(_8ba,"datagrid").updatedRows; +if(!_8bb){ +var rows=[]; +rows=rows.concat(_8bc); +rows=rows.concat(_8bd); +rows=rows.concat(_8be); +return rows; +}else{ +if(_8bb=="inserted"){ +return _8bc; +}else{ +if(_8bb=="deleted"){ +return _8bd; +}else{ +if(_8bb=="updated"){ +return _8be; +} +} +} +} +return []; +}; +function _8bf(_8c0,_8c1){ +var _8c2=$.data(_8c0,"datagrid"); +var opts=_8c2.options; +var data=_8c2.data; +var _8c3=_8c2.insertedRows; +var _8c4=_8c2.deletedRows; +$(_8c0).datagrid("cancelEdit",_8c1); +var row=opts.finder.getRow(_8c0,_8c1); +if(_756(_8c3,row)>=0){ +_757(_8c3,row); +}else{ +_8c4.push(row); +} +_757(_8c2.selectedRows,opts.idField,row[opts.idField]); +_757(_8c2.checkedRows,opts.idField,row[opts.idField]); +opts.view.deleteRow.call(opts.view,_8c0,_8c1); +if(opts.height=="auto"){ +_780(_8c0); +} +$(_8c0).datagrid("getPager").pagination("refresh",{total:data.total}); +}; +function _8c5(_8c6,_8c7){ +var data=$.data(_8c6,"datagrid").data; +var view=$.data(_8c6,"datagrid").options.view; +var _8c8=$.data(_8c6,"datagrid").insertedRows; +view.insertRow.call(view,_8c6,_8c7.index,_8c7.row); +_8c8.push(_8c7.row); +$(_8c6).datagrid("getPager").pagination("refresh",{total:data.total}); +}; +function _8c9(_8ca,row){ +var data=$.data(_8ca,"datagrid").data; +var view=$.data(_8ca,"datagrid").options.view; +var _8cb=$.data(_8ca,"datagrid").insertedRows; +view.insertRow.call(view,_8ca,null,row); +_8cb.push(row); +$(_8ca).datagrid("getPager").pagination("refresh",{total:data.total}); +}; +function _8cc(_8cd,_8ce){ +var _8cf=$.data(_8cd,"datagrid"); +var opts=_8cf.options; +var row=opts.finder.getRow(_8cd,_8ce.index); +var _8d0=false; +_8ce.row=_8ce.row||{}; +for(var _8d1 in _8ce.row){ +if(row[_8d1]!==_8ce.row[_8d1]){ +_8d0=true; +break; +} +} +if(_8d0){ +if(_756(_8cf.insertedRows,row)==-1){ +if(_756(_8cf.updatedRows,row)==-1){ +_8cf.updatedRows.push(row); +} +} +opts.view.updateRow.call(opts.view,_8cd,_8ce.index,_8ce.row); +} +}; +function _8d2(_8d3){ +var _8d4=$.data(_8d3,"datagrid"); +var data=_8d4.data; +var rows=data.rows; +var _8d5=[]; +for(var i=0;i=0){ +(_8e2=="s"?_7dd:_7da)(_8d9,_8e3,true); +} +} +}; +for(var i=0;i0){ +$(this).datagrid("loadData",data); +}else{ +$(this).datagrid("autoSizeColumn"); +} +} +_7f3(this); +}); +}; +function _8f3(_8f4){ +var _8f5={}; +$.map(_8f4,function(name){ +_8f5[name]=_8f6(name); +}); +return _8f5; +function _8f6(name){ +function isA(_8f7){ +return $.data($(_8f7)[0],name)!=undefined; +}; +return {init:function(_8f8,_8f9){ +var _8fa=$("").appendTo(_8f8); +if(_8fa[name]&&name!="text"){ +return _8fa[name](_8f9); +}else{ +return _8fa; +} +},destroy:function(_8fb){ +if(isA(_8fb,name)){ +$(_8fb)[name]("destroy"); +} +},getValue:function(_8fc){ +if(isA(_8fc,name)){ +var opts=$(_8fc)[name]("options"); +if(opts.multiple){ +return $(_8fc)[name]("getValues").join(opts.separator); +}else{ +return $(_8fc)[name]("getValue"); +} +}else{ +return $(_8fc).val(); +} +},setValue:function(_8fd,_8fe){ +if(isA(_8fd,name)){ +var opts=$(_8fd)[name]("options"); +if(opts.multiple){ +if(_8fe){ +$(_8fd)[name]("setValues",_8fe.split(opts.separator)); +}else{ +$(_8fd)[name]("clear"); +} +}else{ +$(_8fd)[name]("setValue",_8fe); +} +}else{ +$(_8fd).val(_8fe); +} +},resize:function(_8ff,_900){ +if(isA(_8ff,name)){ +$(_8ff)[name]("resize",_900); +}else{ +$(_8ff)._size({width:_900,height:$.fn.datagrid.defaults.editorHeight}); +} +}}; +}; +}; +var _901=$.extend({},_8f3(["text","textbox","passwordbox","filebox","numberbox","numberspinner","combobox","combotree","combogrid","combotreegrid","datebox","datetimebox","timespinner","datetimespinner"]),{textarea:{init:function(_902,_903){ +var _904=$("").appendTo(_902); +_904.css("vertical-align","middle")._outerHeight(_903.height); +return _904; +},getValue:function(_905){ +return $(_905).val(); +},setValue:function(_906,_907){ +$(_906).val(_907); +},resize:function(_908,_909){ +$(_908)._outerWidth(_909); +}},checkbox:{init:function(_90a,_90b){ +var _90c=$("").appendTo(_90a); +_90c.val(_90b.on); +_90c.attr("offval",_90b.off); +return _90c; +},getValue:function(_90d){ +if($(_90d).is(":checked")){ +return $(_90d).val(); +}else{ +return $(_90d).attr("offval"); +} +},setValue:function(_90e,_90f){ +var _910=false; +if($(_90e).val()==_90f){ +_910=true; +} +$(_90e)._propAttr("checked",_910); +}},validatebox:{init:function(_911,_912){ +var _913=$("").appendTo(_911); +_913.validatebox(_912); +return _913; +},destroy:function(_914){ +$(_914).validatebox("destroy"); +},getValue:function(_915){ +return $(_915).val(); +},setValue:function(_916,_917){ +$(_916).val(_917); +},resize:function(_918,_919){ +$(_918)._outerWidth(_919)._outerHeight($.fn.datagrid.defaults.editorHeight); +}}}); +$.fn.datagrid.methods={options:function(jq){ +var _91a=$.data(jq[0],"datagrid").options; +var _91b=$.data(jq[0],"datagrid").panel.panel("options"); +var opts=$.extend(_91a,{width:_91b.width,height:_91b.height,closed:_91b.closed,collapsed:_91b.collapsed,minimized:_91b.minimized,maximized:_91b.maximized}); +return opts; +},setSelectionState:function(jq){ +return jq.each(function(){ +_841(this); +}); +},createStyleSheet:function(jq){ +return _75b(jq[0]); +},getPanel:function(jq){ +return $.data(jq[0],"datagrid").panel; +},getPager:function(jq){ +return $.data(jq[0],"datagrid").panel.children("div.datagrid-pager"); +},getColumnFields:function(jq,_91c){ +return _7b6(jq[0],_91c); +},getColumnOption:function(jq,_91d){ +return _7b7(jq[0],_91d); +},resize:function(jq,_91e){ +return jq.each(function(){ +_76a(this,_91e); +}); +},load:function(jq,_91f){ +return jq.each(function(){ +var opts=$(this).datagrid("options"); +if(typeof _91f=="string"){ +opts.url=_91f; +_91f=null; +} +opts.pageNumber=1; +var _920=$(this).datagrid("getPager"); +_920.pagination("refresh",{pageNumber:1}); +_7f3(this,_91f); +}); +},reload:function(jq,_921){ +return jq.each(function(){ +var opts=$(this).datagrid("options"); +if(typeof _921=="string"){ +opts.url=_921; +_921=null; +} +_7f3(this,_921); +}); +},reloadFooter:function(jq,_922){ +return jq.each(function(){ +var opts=$.data(this,"datagrid").options; +var dc=$.data(this,"datagrid").dc; +if(_922){ +$.data(this,"datagrid").footer=_922; +} +if(opts.showFooter){ +opts.view.renderFooter.call(opts.view,this,dc.footer2,false); +opts.view.renderFooter.call(opts.view,this,dc.footer1,true); +if(opts.view.onAfterRender){ +opts.view.onAfterRender.call(opts.view,this); +} +$(this).datagrid("fixRowHeight"); +} +}); +},loading:function(jq){ +return jq.each(function(){ +var opts=$.data(this,"datagrid").options; +$(this).datagrid("getPager").pagination("loading"); +if(opts.loadMsg){ +var _923=$(this).datagrid("getPanel"); +if(!_923.children("div.datagrid-mask").length){ +$("
").appendTo(_923); +var msg=$("
").html(opts.loadMsg).appendTo(_923); +msg._outerHeight(40); +msg.css({marginLeft:(-msg.outerWidth()/2),lineHeight:(msg.height()+"px")}); +} +} +}); +},loaded:function(jq){ +return jq.each(function(){ +$(this).datagrid("getPager").pagination("loaded"); +var _924=$(this).datagrid("getPanel"); +_924.children("div.datagrid-mask-msg").remove(); +_924.children("div.datagrid-mask").remove(); +}); +},fitColumns:function(jq){ +return jq.each(function(){ +_800(this); +}); +},fixColumnSize:function(jq,_925){ +return jq.each(function(){ +_81e(this,_925); +}); +},fixRowHeight:function(jq,_926){ +return jq.each(function(){ +_780(this,_926); +}); +},freezeRow:function(jq,_927){ +return jq.each(function(){ +_78e(this,_927); +}); +},autoSizeColumn:function(jq,_928){ +return jq.each(function(){ +_812(this,_928); +}); +},loadData:function(jq,data){ +return jq.each(function(){ +_7f4(this,data); +_8d2(this); +}); +},getData:function(jq){ +return $.data(jq[0],"datagrid").data; +},getRows:function(jq){ +return $.data(jq[0],"datagrid").data.rows; +},getFooterRows:function(jq){ +return $.data(jq[0],"datagrid").footer; +},getRowIndex:function(jq,id){ +return _849(jq[0],id); +},getChecked:function(jq){ +return _84f(jq[0]); +},getSelected:function(jq){ +var rows=_84c(jq[0]); +return rows.length>0?rows[0]:null; +},getSelections:function(jq){ +return _84c(jq[0]); +},clearSelections:function(jq){ +return jq.each(function(){ +var _929=$.data(this,"datagrid"); +var _92a=_929.selectedRows; +var _92b=_929.checkedRows; +_92a.splice(0,_92a.length); +_862(this); +if(_929.options.checkOnSelect){ +_92b.splice(0,_92b.length); +} +}); +},clearChecked:function(jq){ +return jq.each(function(){ +var _92c=$.data(this,"datagrid"); +var _92d=_92c.selectedRows; +var _92e=_92c.checkedRows; +_92e.splice(0,_92e.length); +_7c9(this); +if(_92c.options.selectOnCheck){ +_92d.splice(0,_92d.length); +} +}); +},scrollTo:function(jq,_92f){ +return jq.each(function(){ +_852(this,_92f); +}); +},highlightRow:function(jq,_930){ +return jq.each(function(){ +_7d6(this,_930); +_852(this,_930); +}); +},selectAll:function(jq){ +return jq.each(function(){ +_867(this); +}); +},unselectAll:function(jq){ +return jq.each(function(){ +_862(this); +}); +},selectRow:function(jq,_931){ +return jq.each(function(){ +_7dd(this,_931); +}); +},selectRecord:function(jq,id){ +return jq.each(function(){ +var opts=$.data(this,"datagrid").options; +if(opts.idField){ +var _932=_849(this,id); +if(_932>=0){ +$(this).datagrid("selectRow",_932); +} +} +}); +},unselectRow:function(jq,_933){ +return jq.each(function(){ +_7de(this,_933); +}); +},checkRow:function(jq,_934){ +return jq.each(function(){ +_7da(this,_934); +}); +},uncheckRow:function(jq,_935){ +return jq.each(function(){ +_7db(this,_935); +}); +},checkAll:function(jq){ +return jq.each(function(){ +_7c8(this); +}); +},uncheckAll:function(jq){ +return jq.each(function(){ +_7c9(this); +}); +},beginEdit:function(jq,_936){ +return jq.each(function(){ +_891(this,_936); +}); +},endEdit:function(jq,_937){ +return jq.each(function(){ +_897(this,_937,false); +}); +},cancelEdit:function(jq,_938){ +return jq.each(function(){ +_897(this,_938,true); +}); +},getEditors:function(jq,_939){ +return _8a4(jq[0],_939); +},getEditor:function(jq,_93a){ +return _8a8(jq[0],_93a); +},refreshRow:function(jq,_93b){ +return jq.each(function(){ +var opts=$.data(this,"datagrid").options; +opts.view.refreshRow.call(opts.view,this,_93b); +}); +},validateRow:function(jq,_93c){ +return _896(jq[0],_93c); +},updateRow:function(jq,_93d){ +return jq.each(function(){ +_8cc(this,_93d); +}); +},appendRow:function(jq,row){ +return jq.each(function(){ +_8c9(this,row); +}); +},insertRow:function(jq,_93e){ +return jq.each(function(){ +_8c5(this,_93e); +}); +},deleteRow:function(jq,_93f){ +return jq.each(function(){ +_8bf(this,_93f); +}); +},getChanges:function(jq,_940){ +return _8b9(jq[0],_940); +},acceptChanges:function(jq){ +return jq.each(function(){ +_8d6(this); +}); +},rejectChanges:function(jq){ +return jq.each(function(){ +_8d8(this); +}); +},mergeCells:function(jq,_941){ +return jq.each(function(){ +_8ea(this,_941); +}); +},showColumn:function(jq,_942){ +return jq.each(function(){ +var col=$(this).datagrid("getColumnOption",_942); +if(col.hidden){ +col.hidden=false; +$(this).datagrid("getPanel").find("td[field=\""+_942+"\"]").show(); +_7f5(this,_942,1); +$(this).datagrid("fitColumns"); +} +}); +},hideColumn:function(jq,_943){ +return jq.each(function(){ +var col=$(this).datagrid("getColumnOption",_943); +if(!col.hidden){ +col.hidden=true; +$(this).datagrid("getPanel").find("td[field=\""+_943+"\"]").hide(); +_7f5(this,_943,-1); +$(this).datagrid("fitColumns"); +} +}); +},sort:function(jq,_944){ +return jq.each(function(){ +_7ca(this,_944); +}); +},gotoPage:function(jq,_945){ +return jq.each(function(){ +var _946=this; +var page,cb; +if(typeof _945=="object"){ +page=_945.page; +cb=_945.callback; +}else{ +page=_945; +} +$(_946).datagrid("options").pageNumber=page; +$(_946).datagrid("getPager").pagination("refresh",{pageNumber:page}); +_7f3(_946,null,function(){ +if(cb){ +cb.call(_946,page); +} +}); +}); +}}; +$.fn.datagrid.parseOptions=function(_947){ +var t=$(_947); +return $.extend({},$.fn.panel.parseOptions(_947),$.parser.parseOptions(_947,["url","toolbar","idField","sortName","sortOrder","pagePosition","resizeHandle",{sharedStyleSheet:"boolean",fitColumns:"boolean",autoRowHeight:"boolean",striped:"boolean",nowrap:"boolean"},{rownumbers:"boolean",singleSelect:"boolean",ctrlSelect:"boolean",checkOnSelect:"boolean",selectOnCheck:"boolean"},{pagination:"boolean",pageSize:"number",pageNumber:"number"},{multiSort:"boolean",remoteSort:"boolean",showHeader:"boolean",showFooter:"boolean"},{scrollbarSize:"number",scrollOnSelect:"boolean"}]),{pageList:(t.attr("pageList")?eval(t.attr("pageList")):undefined),loadMsg:(t.attr("loadMsg")!=undefined?t.attr("loadMsg"):undefined),rowStyler:(t.attr("rowStyler")?eval(t.attr("rowStyler")):undefined)}); +}; +$.fn.datagrid.parseData=function(_948){ +var t=$(_948); +var data={total:0,rows:[]}; +var _949=t.datagrid("getColumnFields",true).concat(t.datagrid("getColumnFields",false)); +t.find("tbody tr").each(function(){ +data.total++; +var row={}; +$.extend(row,$.parser.parseOptions(this,["iconCls","state"])); +for(var i=0;i<_949.length;i++){ +row[_949[i]]=$(this).find("td:eq("+i+")").html(); +} +data.rows.push(row); +}); +return data; +}; +var _94a={render:function(_94b,_94c,_94d){ +var rows=$(_94b).datagrid("getRows"); +$(_94c).empty().html(this.renderTable(_94b,0,rows,_94d)); +},renderFooter:function(_94e,_94f,_950){ +var opts=$.data(_94e,"datagrid").options; +var rows=$.data(_94e,"datagrid").footer||[]; +var _951=$(_94e).datagrid("getColumnFields",_950); +var _952=[""]; +for(var i=0;i"); +_952.push(this.renderRow.call(this,_94e,_951,_950,i,rows[i])); +_952.push(""); +} +_952.push("
"); +$(_94f).html(_952.join("")); +},renderTable:function(_953,_954,rows,_955){ +var _956=$.data(_953,"datagrid"); +var opts=_956.options; +if(_955){ +if(!(opts.rownumbers||(opts.frozenColumns&&opts.frozenColumns.length))){ +return ""; +} +} +var _957=$(_953).datagrid("getColumnFields",_955); +var _958=[""]; +for(var i=0;i"); +_958.push(this.renderRow.call(this,_953,_957,_955,_954,row)); +_958.push(""); +_954++; +} +_958.push("
"); +return _958.join(""); +},renderRow:function(_95b,_95c,_95d,_95e,_95f){ +var opts=$.data(_95b,"datagrid").options; +var cc=[]; +if(_95d&&opts.rownumbers){ +var _960=_95e+1; +if(opts.pagination){ +_960+=(opts.pageNumber-1)*opts.pageSize; +} +cc.push("
"+_960+"
"); +} +for(var i=0;i<_95c.length;i++){ +var _961=_95c[i]; +var col=$(_95b).datagrid("getColumnOption",_961); +if(col){ +var _962=_95f[_961]; +var css=col.styler?(col.styler.call(_95b,_962,_95f,_95e)||""):""; +var cs=this.getStyleValue(css); +var cls=cs.c?"class=\""+cs.c+"\"":""; +var _963=col.hidden?"style=\"display:none;"+cs.s+"\"":(cs.s?"style=\""+cs.s+"\"":""); +cc.push(""); +var _963=""; +if(!col.checkbox){ +if(col.align){ +_963+="text-align:"+col.align+";"; +} +if(!opts.nowrap){ +_963+="white-space:normal;height:auto;"; +}else{ +if(opts.autoRowHeight){ +_963+="height:auto;"; +} +} +} +cc.push("
"); +if(col.checkbox){ +cc.push(""); +}else{ +if(col.formatter){ +cc.push(col.formatter(_962,_95f,_95e)); +}else{ +cc.push(_962); +} +} +cc.push("
"); +cc.push(""); +} +} +return cc.join(""); +},getStyleValue:function(css){ +var _964=""; +var _965=""; +if(typeof css=="string"){ +_965=css; +}else{ +if(css){ +_964=css["class"]||""; +_965=css["style"]||""; +} +} +return {c:_964,s:_965}; +},refreshRow:function(_966,_967){ +this.updateRow.call(this,_966,_967,{}); +},updateRow:function(_968,_969,row){ +var opts=$.data(_968,"datagrid").options; +var _96a=opts.finder.getRow(_968,_969); +$.extend(_96a,row); +var cs=_96b.call(this,_969); +var _96c=cs.s; +var cls="datagrid-row "+(_969%2&&opts.striped?"datagrid-row-alt ":" ")+cs.c; +function _96b(_96d){ +var css=opts.rowStyler?opts.rowStyler.call(_968,_96d,_96a):""; +return this.getStyleValue(css); +}; +function _96e(_96f){ +var tr=opts.finder.getTr(_968,_969,"body",(_96f?1:2)); +if(!tr.length){ +return; +} +var _970=$(_968).datagrid("getColumnFields",_96f); +var _971=tr.find("div.datagrid-cell-check input[type=checkbox]").is(":checked"); +tr.html(this.renderRow.call(this,_968,_970,_96f,_969,_96a)); +var _972=(tr.hasClass("datagrid-row-checked")?" datagrid-row-checked":"")+(tr.hasClass("datagrid-row-selected")?" datagrid-row-selected":""); +tr.attr("style",_96c).attr("class",cls+_972); +if(_971){ +tr.find("div.datagrid-cell-check input[type=checkbox]")._propAttr("checked",true); +} +}; +_96e.call(this,true); +_96e.call(this,false); +$(_968).datagrid("fixRowHeight",_969); +},insertRow:function(_973,_974,row){ +var _975=$.data(_973,"datagrid"); +var opts=_975.options; +var dc=_975.dc; +var data=_975.data; +if(_974==undefined||_974==null){ +_974=data.rows.length; +} +if(_974>data.rows.length){ +_974=data.rows.length; +} +function _976(_977){ +var _978=_977?1:2; +for(var i=data.rows.length-1;i>=_974;i--){ +var tr=opts.finder.getTr(_973,i,"body",_978); +tr.attr("datagrid-row-index",i+1); +tr.attr("id",_975.rowIdPrefix+"-"+_978+"-"+(i+1)); +if(_977&&opts.rownumbers){ +var _979=i+2; +if(opts.pagination){ +_979+=(opts.pageNumber-1)*opts.pageSize; +} +tr.find("div.datagrid-cell-rownumber").html(_979); +} +if(opts.striped){ +tr.removeClass("datagrid-row-alt").addClass((i+1)%2?"datagrid-row-alt":""); +} +} +}; +function _97a(_97b){ +var _97c=_97b?1:2; +var _97d=$(_973).datagrid("getColumnFields",_97b); +var _97e=_975.rowIdPrefix+"-"+_97c+"-"+_974; +var tr=""; +if(_974>=data.rows.length){ +if(data.rows.length){ +opts.finder.getTr(_973,"","last",_97c).after(tr); +}else{ +var cc=_97b?dc.body1:dc.body2; +cc.html(""+tr+"
"); +} +}else{ +opts.finder.getTr(_973,_974+1,"body",_97c).before(tr); +} +}; +_976.call(this,true); +_976.call(this,false); +_97a.call(this,true); +_97a.call(this,false); +data.total+=1; +data.rows.splice(_974,0,row); +this.setEmptyMsg(_973); +this.refreshRow.call(this,_973,_974); +},deleteRow:function(_97f,_980){ +var _981=$.data(_97f,"datagrid"); +var opts=_981.options; +var data=_981.data; +function _982(_983){ +var _984=_983?1:2; +for(var i=_980+1;i").appendTo(_98b.dc.view); +d.html(opts.emptyMsg).css("top",h+"px"); +} +} +},renderEmptyRow:function(_98d){ +var opts=$(_98d).datagrid("options"); +var cols=$.map($(_98d).datagrid("getColumnFields"),function(_98e){ +return $(_98d).datagrid("getColumnOption",_98e); +}); +$.map(cols,function(col){ +col.formatter1=col.formatter; +col.styler1=col.styler; +col.formatter=col.styler=undefined; +}); +var _98f=opts.rowStyler; +opts.rowStyler=function(){ +}; +var _990=$.data(_98d,"datagrid").dc.body2; +_990.html(this.renderTable(_98d,0,[{}],false)); +_990.find("tbody *").css({height:1,borderColor:"transparent",background:"transparent"}); +var tr=_990.find(".datagrid-row"); +tr.removeClass("datagrid-row").removeAttr("datagrid-row-index"); +tr.find(".datagrid-cell,.datagrid-cell-check").empty(); +$.map(cols,function(col){ +col.formatter=col.formatter1; +col.styler=col.styler1; +col.formatter1=col.styler1=undefined; +}); +opts.rowStyler=_98f; +}}; +$.fn.datagrid.defaults=$.extend({},$.fn.panel.defaults,{sharedStyleSheet:false,frozenColumns:undefined,columns:undefined,fitColumns:false,resizeHandle:"right",resizeEdge:5,autoRowHeight:true,toolbar:null,striped:false,method:"post",nowrap:true,idField:null,url:null,data:null,loadMsg:"Processing, please wait ...",emptyMsg:"",rownumbers:false,singleSelect:false,ctrlSelect:false,selectOnCheck:true,checkOnSelect:true,pagination:false,pagePosition:"bottom",pageNumber:1,pageSize:10,pageList:[10,20,30,40,50],queryParams:{},sortName:null,sortOrder:"asc",multiSort:false,remoteSort:true,showHeader:true,showFooter:false,scrollOnSelect:true,scrollbarSize:18,rownumberWidth:30,editorHeight:31,headerEvents:{mouseover:_7c2(true),mouseout:_7c2(false),click:_7c6,dblclick:_7cb,contextmenu:_7ce},rowEvents:{mouseover:_7d0(true),mouseout:_7d0(false),click:_7d7,dblclick:_7e1,contextmenu:_7e5},rowStyler:function(_991,_992){ +},loader:function(_993,_994,_995){ +var opts=$(this).datagrid("options"); +if(!opts.url){ +return false; +} +$.ajax({type:opts.method,url:opts.url,data:_993,dataType:"json",success:function(data){ +_994(data); +},error:function(){ +_995.apply(this,arguments); +}}); +},loadFilter:function(data){ +return data; +},editors:_901,finder:{getTr:function(_996,_997,type,_998){ +type=type||"body"; +_998=_998||0; +var _999=$.data(_996,"datagrid"); +var dc=_999.dc; +var opts=_999.options; +if(_998==0){ +var tr1=opts.finder.getTr(_996,_997,type,1); +var tr2=opts.finder.getTr(_996,_997,type,2); +return tr1.add(tr2); +}else{ +if(type=="body"){ +var tr=$("#"+_999.rowIdPrefix+"-"+_998+"-"+_997); +if(!tr.length){ +tr=(_998==1?dc.body1:dc.body2).find(">table>tbody>tr[datagrid-row-index="+_997+"]"); +} +return tr; +}else{ +if(type=="footer"){ +return (_998==1?dc.footer1:dc.footer2).find(">table>tbody>tr[datagrid-row-index="+_997+"]"); +}else{ +if(type=="selected"){ +return (_998==1?dc.body1:dc.body2).find(">table>tbody>tr.datagrid-row-selected"); +}else{ +if(type=="highlight"){ +return (_998==1?dc.body1:dc.body2).find(">table>tbody>tr.datagrid-row-over"); +}else{ +if(type=="checked"){ +return (_998==1?dc.body1:dc.body2).find(">table>tbody>tr.datagrid-row-checked"); +}else{ +if(type=="editing"){ +return (_998==1?dc.body1:dc.body2).find(">table>tbody>tr.datagrid-row-editing"); +}else{ +if(type=="last"){ +return (_998==1?dc.body1:dc.body2).find(">table>tbody>tr[datagrid-row-index]:last"); +}else{ +if(type=="allbody"){ +return (_998==1?dc.body1:dc.body2).find(">table>tbody>tr[datagrid-row-index]"); +}else{ +if(type=="allfooter"){ +return (_998==1?dc.footer1:dc.footer2).find(">table>tbody>tr[datagrid-row-index]"); +} +} +} +} +} +} +} +} +} +} +},getRow:function(_99a,p){ +var _99b=(typeof p=="object")?p.attr("datagrid-row-index"):p; +return $.data(_99a,"datagrid").data.rows[parseInt(_99b)]; +},getRows:function(_99c){ +return $(_99c).datagrid("getRows"); +}},view:_94a,onBeforeLoad:function(_99d){ +},onLoadSuccess:function(){ +},onLoadError:function(){ +},onClickRow:function(_99e,_99f){ +},onDblClickRow:function(_9a0,_9a1){ +},onClickCell:function(_9a2,_9a3,_9a4){ +},onDblClickCell:function(_9a5,_9a6,_9a7){ +},onBeforeSortColumn:function(sort,_9a8){ +},onSortColumn:function(sort,_9a9){ +},onResizeColumn:function(_9aa,_9ab){ +},onBeforeSelect:function(_9ac,_9ad){ +},onSelect:function(_9ae,_9af){ +},onBeforeUnselect:function(_9b0,_9b1){ +},onUnselect:function(_9b2,_9b3){ +},onSelectAll:function(rows){ +},onUnselectAll:function(rows){ +},onBeforeCheck:function(_9b4,_9b5){ +},onCheck:function(_9b6,_9b7){ +},onBeforeUncheck:function(_9b8,_9b9){ +},onUncheck:function(_9ba,_9bb){ +},onCheckAll:function(rows){ +},onUncheckAll:function(rows){ +},onBeforeEdit:function(_9bc,_9bd){ +},onBeginEdit:function(_9be,_9bf){ +},onEndEdit:function(_9c0,_9c1,_9c2){ +},onAfterEdit:function(_9c3,_9c4,_9c5){ +},onCancelEdit:function(_9c6,_9c7){ +},onHeaderContextMenu:function(e,_9c8){ +},onRowContextMenu:function(e,_9c9,_9ca){ +}}); +})(jQuery); +(function($){ +var _9cb; +$(document)._unbind(".propertygrid")._bind("mousedown.propertygrid",function(e){ +var p=$(e.target).closest("div.datagrid-view,div.combo-panel"); +if(p.length){ +return; +} +_9cc(_9cb); +_9cb=undefined; +}); +function _9cd(_9ce){ +var _9cf=$.data(_9ce,"propertygrid"); +var opts=$.data(_9ce,"propertygrid").options; +$(_9ce).datagrid($.extend({},opts,{cls:"propertygrid",view:(opts.showGroup?opts.groupView:opts.view),onBeforeEdit:function(_9d0,row){ +if(opts.onBeforeEdit.call(_9ce,_9d0,row)==false){ +return false; +} +var dg=$(this); +var row=dg.datagrid("getRows")[_9d0]; +var col=dg.datagrid("getColumnOption","value"); +col.editor=row.editor; +},onClickCell:function(_9d1,_9d2,_9d3){ +if(_9cb!=this){ +_9cc(_9cb); +_9cb=this; +} +if(opts.editIndex!=_9d1){ +_9cc(_9cb); +$(this).datagrid("beginEdit",_9d1); +var ed=$(this).datagrid("getEditor",{index:_9d1,field:_9d2}); +if(!ed){ +ed=$(this).datagrid("getEditor",{index:_9d1,field:"value"}); +} +if(ed){ +var t=$(ed.target); +var _9d4=t.data("textbox")?t.textbox("textbox"):t; +_9d4.focus(); +opts.editIndex=_9d1; +} +} +opts.onClickCell.call(_9ce,_9d1,_9d2,_9d3); +},loadFilter:function(data){ +_9cc(this); +return opts.loadFilter.call(this,data); +}})); +}; +function _9cc(_9d5){ +var t=$(_9d5); +if(!t.length){ +return; +} +var opts=$.data(_9d5,"propertygrid").options; +opts.finder.getTr(_9d5,null,"editing").each(function(){ +var _9d6=parseInt($(this).attr("datagrid-row-index")); +if(t.datagrid("validateRow",_9d6)){ +t.datagrid("endEdit",_9d6); +}else{ +t.datagrid("cancelEdit",_9d6); +} +}); +opts.editIndex=undefined; +}; +$.fn.propertygrid=function(_9d7,_9d8){ +if(typeof _9d7=="string"){ +var _9d9=$.fn.propertygrid.methods[_9d7]; +if(_9d9){ +return _9d9(this,_9d8); +}else{ +return this.datagrid(_9d7,_9d8); +} +} +_9d7=_9d7||{}; +return this.each(function(){ +var _9da=$.data(this,"propertygrid"); +if(_9da){ +$.extend(_9da.options,_9d7); +}else{ +var opts=$.extend({},$.fn.propertygrid.defaults,$.fn.propertygrid.parseOptions(this),_9d7); +opts.frozenColumns=$.extend(true,[],opts.frozenColumns); +opts.columns=$.extend(true,[],opts.columns); +$.data(this,"propertygrid",{options:opts}); +} +_9cd(this); +}); +}; +$.fn.propertygrid.methods={options:function(jq){ +return $.data(jq[0],"propertygrid").options; +}}; +$.fn.propertygrid.parseOptions=function(_9db){ +return $.extend({},$.fn.datagrid.parseOptions(_9db),$.parser.parseOptions(_9db,[{showGroup:"boolean"}])); +}; +var _9dc=$.extend({},$.fn.datagrid.defaults.view,{render:function(_9dd,_9de,_9df){ +var _9e0=[]; +var _9e1=this.groups; +for(var i=0;i<_9e1.length;i++){ +_9e0.push(this.renderGroup.call(this,_9dd,i,_9e1[i],_9df)); +} +$(_9de).html(_9e0.join("")); +},renderGroup:function(_9e2,_9e3,_9e4,_9e5){ +var _9e6=$.data(_9e2,"datagrid"); +var opts=_9e6.options; +var _9e7=$(_9e2).datagrid("getColumnFields",_9e5); +var _9e8=opts.frozenColumns&&opts.frozenColumns.length; +if(_9e5){ +if(!(opts.rownumbers||_9e8)){ +return ""; +} +} +var _9e9=[]; +var css=opts.groupStyler.call(_9e2,_9e4.value,_9e4.rows); +var cs=_9ea(css,"datagrid-group"); +_9e9.push("
"); +if((_9e5&&(opts.rownumbers||opts.frozenColumns.length))||(!_9e5&&!(opts.rownumbers||opts.frozenColumns.length))){ +_9e9.push(""); +_9e9.push(" "); +_9e9.push(""); +} +if((_9e5&&_9e8)||(!_9e5)){ +_9e9.push(""); +_9e9.push(opts.groupFormatter.call(_9e2,_9e4.value,_9e4.rows)); +_9e9.push(""); +} +_9e9.push("
"); +_9e9.push(""); +var _9eb=_9e4.startIndex; +for(var j=0;j<_9e4.rows.length;j++){ +var css=opts.rowStyler?opts.rowStyler.call(_9e2,_9eb,_9e4.rows[j]):""; +var _9ec=""; +var _9ed=""; +if(typeof css=="string"){ +_9ed=css; +}else{ +if(css){ +_9ec=css["class"]||""; +_9ed=css["style"]||""; +} +} +var cls="class=\"datagrid-row "+(_9eb%2&&opts.striped?"datagrid-row-alt ":" ")+_9ec+"\""; +var _9ee=_9ed?"style=\""+_9ed+"\"":""; +var _9ef=_9e6.rowIdPrefix+"-"+(_9e5?1:2)+"-"+_9eb; +_9e9.push(""); +_9e9.push(this.renderRow.call(this,_9e2,_9e7,_9e5,_9eb,_9e4.rows[j])); +_9e9.push(""); +_9eb++; +} +_9e9.push("
"); +return _9e9.join(""); +function _9ea(css,cls){ +var _9f0=""; +var _9f1=""; +if(typeof css=="string"){ +_9f1=css; +}else{ +if(css){ +_9f0=css["class"]||""; +_9f1=css["style"]||""; +} +} +return "class=\""+cls+(_9f0?" "+_9f0:"")+"\" "+"style=\""+_9f1+"\""; +}; +},bindEvents:function(_9f2){ +var _9f3=$.data(_9f2,"datagrid"); +var dc=_9f3.dc; +var body=dc.body1.add(dc.body2); +var _9f4=($.data(body[0],"events")||$._data(body[0],"events")).click[0].handler; +body._unbind("click")._bind("click",function(e){ +var tt=$(e.target); +var _9f5=tt.closest("span.datagrid-row-expander"); +if(_9f5.length){ +var _9f6=_9f5.closest("div.datagrid-group").attr("group-index"); +if(_9f5.hasClass("datagrid-row-collapse")){ +$(_9f2).datagrid("collapseGroup",_9f6); +}else{ +$(_9f2).datagrid("expandGroup",_9f6); +} +}else{ +_9f4(e); +} +e.stopPropagation(); +}); +},onBeforeRender:function(_9f7,rows){ +var _9f8=$.data(_9f7,"datagrid"); +var opts=_9f8.options; +_9f9(); +var _9fa=[]; +for(var i=0;i"+".datagrid-group{height:"+opts.groupHeight+"px;overflow:hidden;font-weight:bold;border-bottom:1px solid #ccc;white-space:nowrap;word-break:normal;}"+".datagrid-group-title,.datagrid-group-expander{display:inline-block;vertical-align:bottom;height:100%;line-height:"+opts.groupHeight+"px;padding:0 4px;}"+".datagrid-group-title{position:relative;}"+".datagrid-group-expander{width:"+opts.expanderWidth+"px;text-align:center;padding:0}"+".datagrid-group-expander .datagrid-row-expander{margin:"+Math.floor((opts.groupHeight-16)/2)+"px 0;display:inline-block;width:16px;height:16px;cursor:pointer}"+""); +} +}; +},onAfterRender:function(_a01){ +$.fn.datagrid.defaults.view.onAfterRender.call(this,_a01); +var view=this; +var _a02=$.data(_a01,"datagrid"); +var opts=_a02.options; +if(!_a02.onResizeColumn){ +_a02.onResizeColumn=opts.onResizeColumn; +} +if(!_a02.onResize){ +_a02.onResize=opts.onResize; +} +opts.onResizeColumn=function(_a03,_a04){ +view.resizeGroup(_a01); +_a02.onResizeColumn.call(_a01,_a03,_a04); +}; +opts.onResize=function(_a05,_a06){ +view.resizeGroup(_a01); +_a02.onResize.call($(_a01).datagrid("getPanel")[0],_a05,_a06); +}; +view.resizeGroup(_a01); +}}); +$.extend($.fn.datagrid.methods,{groups:function(jq){ +return jq.datagrid("options").view.groups; +},expandGroup:function(jq,_a07){ +return jq.each(function(){ +var opts=$(this).datagrid("options"); +var view=$.data(this,"datagrid").dc.view; +var _a08=view.find(_a07!=undefined?"div.datagrid-group[group-index=\""+_a07+"\"]":"div.datagrid-group"); +var _a09=_a08.find("span.datagrid-row-expander"); +if(_a09.hasClass("datagrid-row-expand")){ +_a09.removeClass("datagrid-row-expand").addClass("datagrid-row-collapse"); +_a08.next("table").show(); +} +$(this).datagrid("fixRowHeight"); +if(opts.onExpandGroup){ +opts.onExpandGroup.call(this,_a07); +} +}); +},collapseGroup:function(jq,_a0a){ +return jq.each(function(){ +var opts=$(this).datagrid("options"); +var view=$.data(this,"datagrid").dc.view; +var _a0b=view.find(_a0a!=undefined?"div.datagrid-group[group-index=\""+_a0a+"\"]":"div.datagrid-group"); +var _a0c=_a0b.find("span.datagrid-row-expander"); +if(_a0c.hasClass("datagrid-row-collapse")){ +_a0c.removeClass("datagrid-row-collapse").addClass("datagrid-row-expand"); +_a0b.next("table").hide(); +} +$(this).datagrid("fixRowHeight"); +if(opts.onCollapseGroup){ +opts.onCollapseGroup.call(this,_a0a); +} +}); +},scrollToGroup:function(jq,_a0d){ +return jq.each(function(){ +var _a0e=$.data(this,"datagrid"); +var dc=_a0e.dc; +var grow=dc.body2.children("div.datagrid-group[group-index=\""+_a0d+"\"]"); +if(grow.length){ +var _a0f=grow.outerHeight(); +var _a10=dc.view2.children("div.datagrid-header")._outerHeight(); +var _a11=dc.body2.outerHeight(true)-dc.body2.outerHeight(); +var top=grow.position().top-_a10-_a11; +if(top<0){ +dc.body2.scrollTop(dc.body2.scrollTop()+top); +}else{ +if(top+_a0f>dc.body2.height()-18){ +dc.body2.scrollTop(dc.body2.scrollTop()+top+_a0f-dc.body2.height()+18); +} +} +} +}); +}}); +$.extend(_9dc,{refreshGroupTitle:function(_a12,_a13){ +var _a14=$.data(_a12,"datagrid"); +var opts=_a14.options; +var dc=_a14.dc; +var _a15=this.groups[_a13]; +var span=dc.body1.add(dc.body2).children("div.datagrid-group[group-index="+_a13+"]").find("span.datagrid-group-title"); +span.html(opts.groupFormatter.call(_a12,_a15.value,_a15.rows)); +},resizeGroup:function(_a16,_a17){ +var _a18=$.data(_a16,"datagrid"); +var dc=_a18.dc; +var ht=dc.header2.find("table"); +var fr=ht.find("tr.datagrid-filter-row").hide(); +var ww=dc.body2.children("table.datagrid-btable:first").width(); +if(_a17==undefined){ +var _a19=dc.body2.children("div.datagrid-group"); +}else{ +var _a19=dc.body2.children("div.datagrid-group[group-index="+_a17+"]"); +} +_a19._outerWidth(ww); +var opts=_a18.options; +if(opts.frozenColumns&&opts.frozenColumns.length){ +var _a1a=dc.view1.width()-opts.expanderWidth; +var _a1b=dc.view1.css("direction").toLowerCase()=="rtl"; +_a19.find(".datagrid-group-title").css(_a1b?"right":"left",-_a1a+"px"); +} +if(fr.length){ +if(opts.showFilterBar){ +fr.show(); +} +} +},insertRow:function(_a1c,_a1d,row){ +var _a1e=$.data(_a1c,"datagrid"); +var opts=_a1e.options; +var dc=_a1e.dc; +var _a1f=null; +var _a20; +if(!_a1e.data.rows.length){ +$(_a1c).datagrid("loadData",[row]); +return; +} +for(var i=0;i_a1f.startIndex+_a1f.rows.length){ +_a1d=_a1f.startIndex+_a1f.rows.length; +} +} +$.fn.datagrid.defaults.view.insertRow.call(this,_a1c,_a1d,row); +if(_a1d>=_a1f.startIndex+_a1f.rows.length){ +_a21(_a1d,true); +_a21(_a1d,false); +} +_a1f.rows.splice(_a1d-_a1f.startIndex,0,row); +}else{ +_a1f={value:row[opts.groupField],rows:[row],startIndex:_a1e.data.rows.length}; +_a20=this.groups.length; +dc.body1.append(this.renderGroup.call(this,_a1c,_a20,_a1f,true)); +dc.body2.append(this.renderGroup.call(this,_a1c,_a20,_a1f,false)); +this.groups.push(_a1f); +_a1e.data.rows.push(row); +} +this.setGroupIndex(_a1c); +this.refreshGroupTitle(_a1c,_a20); +this.resizeGroup(_a1c); +function _a21(_a22,_a23){ +var _a24=_a23?1:2; +var _a25=opts.finder.getTr(_a1c,_a22-1,"body",_a24); +var tr=opts.finder.getTr(_a1c,_a22,"body",_a24); +tr.insertAfter(_a25); +}; +},updateRow:function(_a26,_a27,row){ +var opts=$.data(_a26,"datagrid").options; +$.fn.datagrid.defaults.view.updateRow.call(this,_a26,_a27,row); +var tb=opts.finder.getTr(_a26,_a27,"body",2).closest("table.datagrid-btable"); +var _a28=parseInt(tb.prev().attr("group-index")); +this.refreshGroupTitle(_a26,_a28); +},deleteRow:function(_a29,_a2a){ +var _a2b=$.data(_a29,"datagrid"); +var opts=_a2b.options; +var dc=_a2b.dc; +var body=dc.body1.add(dc.body2); +var tb=opts.finder.getTr(_a29,_a2a,"body",2).closest("table.datagrid-btable"); +var _a2c=parseInt(tb.prev().attr("group-index")); +$.fn.datagrid.defaults.view.deleteRow.call(this,_a29,_a2a); +var _a2d=this.groups[_a2c]; +if(_a2d.rows.length>1){ +_a2d.rows.splice(_a2a-_a2d.startIndex,1); +this.refreshGroupTitle(_a29,_a2c); +}else{ +body.children("div.datagrid-group[group-index="+_a2c+"]").remove(); +for(var i=_a2c+1;i").insertBefore(tr.find(".tree-title")); +} +if(row.checkState=="checked"){ +_a55(_a69,_a6a,true,true); +}else{ +if(row.checkState=="unchecked"){ +_a55(_a69,_a6a,false,true); +}else{ +var flag=_a67(row); +if(flag===0){ +_a55(_a69,_a6a,false,true); +}else{ +if(flag===1){ +_a55(_a69,_a6a,true,true); +} +} +} +} +}else{ +ck.remove(); +row.checkState=undefined; +row.checked=undefined; +_a5e(_a69,row); +} +}; +function _a6b(_a6c,_a6d){ +var opts=$.data(_a6c,"treegrid").options; +var tr1=opts.finder.getTr(_a6c,_a6d,"body",1); +var tr2=opts.finder.getTr(_a6c,_a6d,"body",2); +var _a6e=$(_a6c).datagrid("getColumnFields",true).length+(opts.rownumbers?1:0); +var _a6f=$(_a6c).datagrid("getColumnFields",false).length; +_a70(tr1,_a6e); +_a70(tr2,_a6f); +function _a70(tr,_a71){ +$(""+""+"
"+""+"").insertAfter(tr); +}; +}; +function _a72(_a73,_a74,data,_a75,_a76){ +var _a77=$.data(_a73,"treegrid"); +var opts=_a77.options; +var dc=_a77.dc; +data=opts.loadFilter.call(_a73,data,_a74); +var node=find(_a73,_a74); +if(node){ +var _a78=opts.finder.getTr(_a73,_a74,"body",1); +var _a79=opts.finder.getTr(_a73,_a74,"body",2); +var cc1=_a78.next("tr.treegrid-tr-tree").children("td").children("div"); +var cc2=_a79.next("tr.treegrid-tr-tree").children("td").children("div"); +if(!_a75){ +node.children=[]; +} +}else{ +var cc1=dc.body1; +var cc2=dc.body2; +if(!_a75){ +_a77.data=[]; +} +} +if(!_a75){ +cc1.empty(); +cc2.empty(); +} +if(opts.view.onBeforeRender){ +opts.view.onBeforeRender.call(opts.view,_a73,_a74,data); +} +opts.view.render.call(opts.view,_a73,cc1,true); +opts.view.render.call(opts.view,_a73,cc2,false); +if(opts.showFooter){ +opts.view.renderFooter.call(opts.view,_a73,dc.footer1,true); +opts.view.renderFooter.call(opts.view,_a73,dc.footer2,false); +} +if(opts.view.onAfterRender){ +opts.view.onAfterRender.call(opts.view,_a73); +} +if(!_a74&&opts.pagination){ +var _a7a=$.data(_a73,"treegrid").total; +var _a7b=$(_a73).datagrid("getPager"); +var _a7c=_a7b.pagination("options"); +if(_a7c.total!=data.total){ +_a7b.pagination("refresh",{pageNumber:opts.pageNumber,total:data.total}); +if(opts.pageNumber!=_a7c.pageNumber&&_a7c.pageNumber>0){ +opts.pageNumber=_a7c.pageNumber; +_a43(_a73); +} +} +} +_a44(_a73); +_a4c(_a73); +$(_a73).treegrid("showLines"); +$(_a73).treegrid("setSelectionState"); +$(_a73).treegrid("autoSizeColumn"); +if(!_a76){ +opts.onLoadSuccess.call(_a73,node,data); +} +}; +function _a43(_a7d,_a7e,_a7f,_a80,_a81){ +var opts=$.data(_a7d,"treegrid").options; +var body=$(_a7d).datagrid("getPanel").find("div.datagrid-body"); +if(_a7e==undefined&&opts.queryParams){ +opts.queryParams.id=undefined; +} +if(_a7f){ +opts.queryParams=_a7f; +} +var _a82=$.extend({},opts.queryParams); +if(opts.pagination){ +$.extend(_a82,{page:opts.pageNumber,rows:opts.pageSize}); +} +if(opts.sortName){ +$.extend(_a82,{sort:opts.sortName,order:opts.sortOrder}); +} +var row=find(_a7d,_a7e); +if(opts.onBeforeLoad.call(_a7d,row,_a82)==false){ +return; +} +var _a83=body.find("tr[node-id=\""+_a7e+"\"] span.tree-folder"); +_a83.addClass("tree-loading"); +$(_a7d).treegrid("loading"); +var _a84=opts.loader.call(_a7d,_a82,function(data){ +_a83.removeClass("tree-loading"); +$(_a7d).treegrid("loaded"); +_a72(_a7d,_a7e,data,_a80); +if(_a81){ +_a81(); +} +},function(){ +_a83.removeClass("tree-loading"); +$(_a7d).treegrid("loaded"); +opts.onLoadError.apply(_a7d,arguments); +if(_a81){ +_a81(); +} +}); +if(_a84==false){ +_a83.removeClass("tree-loading"); +$(_a7d).treegrid("loaded"); +} +}; +function _a85(_a86){ +var _a87=_a88(_a86); +return _a87.length?_a87[0]:null; +}; +function _a88(_a89){ +return $.data(_a89,"treegrid").data; +}; +function _a66(_a8a,_a8b){ +var row=find(_a8a,_a8b); +if(row._parentId){ +return find(_a8a,row._parentId); +}else{ +return null; +} +}; +function _a48(_a8c,_a8d){ +var data=$.data(_a8c,"treegrid").data; +if(_a8d){ +var _a8e=find(_a8c,_a8d); +data=_a8e?(_a8e.children||[]):[]; +} +var _a8f=[]; +$.easyui.forEach(data,true,function(node){ +_a8f.push(node); +}); +return _a8f; +}; +function _a90(_a91,_a92){ +var opts=$.data(_a91,"treegrid").options; +var tr=opts.finder.getTr(_a91,_a92); +var node=tr.children("td[field=\""+opts.treeField+"\"]"); +return node.find("span.tree-indent,span.tree-hit").length; +}; +function find(_a93,_a94){ +var _a95=$.data(_a93,"treegrid"); +var opts=_a95.options; +var _a96=null; +$.easyui.forEach(_a95.data,true,function(node){ +if(node[opts.idField]==_a94){ +_a96=node; +return false; +} +}); +return _a96; +}; +function _a97(_a98,_a99){ +var opts=$.data(_a98,"treegrid").options; +var row=find(_a98,_a99); +var tr=opts.finder.getTr(_a98,_a99); +var hit=tr.find("span.tree-hit"); +if(hit.length==0){ +return; +} +if(hit.hasClass("tree-collapsed")){ +return; +} +if(opts.onBeforeCollapse.call(_a98,row)==false){ +return; +} +hit.removeClass("tree-expanded tree-expanded-hover").addClass("tree-collapsed"); +hit.next().removeClass("tree-folder-open"); +row.state="closed"; +tr=tr.next("tr.treegrid-tr-tree"); +var cc=tr.children("td").children("div"); +if(opts.animate){ +cc.slideUp("normal",function(){ +$(_a98).treegrid("autoSizeColumn"); +_a44(_a98,_a99); +opts.onCollapse.call(_a98,row); +}); +}else{ +cc.hide(); +$(_a98).treegrid("autoSizeColumn"); +_a44(_a98,_a99); +opts.onCollapse.call(_a98,row); +} +}; +function _a9a(_a9b,_a9c){ +var opts=$.data(_a9b,"treegrid").options; +var tr=opts.finder.getTr(_a9b,_a9c); +var hit=tr.find("span.tree-hit"); +var row=find(_a9b,_a9c); +if(hit.length==0){ +return; +} +if(hit.hasClass("tree-expanded")){ +return; +} +if(opts.onBeforeExpand.call(_a9b,row)==false){ +return; +} +hit.removeClass("tree-collapsed tree-collapsed-hover").addClass("tree-expanded"); +hit.next().addClass("tree-folder-open"); +var _a9d=tr.next("tr.treegrid-tr-tree"); +if(_a9d.length){ +var cc=_a9d.children("td").children("div"); +_a9e(cc); +}else{ +_a6b(_a9b,row[opts.idField]); +var _a9d=tr.next("tr.treegrid-tr-tree"); +var cc=_a9d.children("td").children("div"); +cc.hide(); +var _a9f=$.extend({},opts.queryParams||{}); +_a9f.id=row[opts.idField]; +_a43(_a9b,row[opts.idField],_a9f,true,function(){ +if(cc.is(":empty")){ +_a9d.remove(); +}else{ +_a9e(cc); +} +}); +} +function _a9e(cc){ +row.state="open"; +if(opts.animate){ +cc.slideDown("normal",function(){ +$(_a9b).treegrid("autoSizeColumn"); +_a44(_a9b,_a9c); +opts.onExpand.call(_a9b,row); +}); +}else{ +cc.show(); +$(_a9b).treegrid("autoSizeColumn"); +_a44(_a9b,_a9c); +opts.onExpand.call(_a9b,row); +} +}; +}; +function _a54(_aa0,_aa1){ +var opts=$.data(_aa0,"treegrid").options; +var tr=opts.finder.getTr(_aa0,_aa1); +var hit=tr.find("span.tree-hit"); +if(hit.hasClass("tree-expanded")){ +_a97(_aa0,_aa1); +}else{ +_a9a(_aa0,_aa1); +} +}; +function _aa2(_aa3,_aa4){ +var opts=$.data(_aa3,"treegrid").options; +var _aa5=_a48(_aa3,_aa4); +if(_aa4){ +_aa5.unshift(find(_aa3,_aa4)); +} +for(var i=0;i<_aa5.length;i++){ +_a97(_aa3,_aa5[i][opts.idField]); +} +}; +function _aa6(_aa7,_aa8){ +var opts=$.data(_aa7,"treegrid").options; +var _aa9=_a48(_aa7,_aa8); +if(_aa8){ +_aa9.unshift(find(_aa7,_aa8)); +} +for(var i=0;i<_aa9.length;i++){ +_a9a(_aa7,_aa9[i][opts.idField]); +} +}; +function _aaa(_aab,_aac){ +var opts=$.data(_aab,"treegrid").options; +var ids=[]; +var p=_a66(_aab,_aac); +while(p){ +var id=p[opts.idField]; +ids.unshift(id); +p=_a66(_aab,id); +} +for(var i=0;i").insertBefore(_ab1); +if(hit.prev().length){ +hit.prev().remove(); +} +} +} +_a72(_aae,_aaf.parent,_aaf.data,_ab0.data.length>0,true); +}; +function _ab2(_ab3,_ab4){ +var ref=_ab4.before||_ab4.after; +var opts=$.data(_ab3,"treegrid").options; +var _ab5=_a66(_ab3,ref); +_aad(_ab3,{parent:(_ab5?_ab5[opts.idField]:null),data:[_ab4.data]}); +var _ab6=_ab5?_ab5.children:$(_ab3).treegrid("getRoots"); +for(var i=0;i<_ab6.length;i++){ +if(_ab6[i][opts.idField]==ref){ +var _ab7=_ab6[_ab6.length-1]; +_ab6.splice(_ab4.before?i:(i+1),0,_ab7); +_ab6.splice(_ab6.length-1,1); +break; +} +} +_ab8(true); +_ab8(false); +_a4c(_ab3); +$(_ab3).treegrid("showLines"); +function _ab8(_ab9){ +var _aba=_ab9?1:2; +var tr=opts.finder.getTr(_ab3,_ab4.data[opts.idField],"body",_aba); +var _abb=tr.closest("table.datagrid-btable"); +tr=tr.parent().children(); +var dest=opts.finder.getTr(_ab3,ref,"body",_aba); +if(_ab4.before){ +tr.insertBefore(dest); +}else{ +var sub=dest.next("tr.treegrid-tr-tree"); +tr.insertAfter(sub.length?sub:dest); +} +_abb.remove(); +}; +}; +function _abc(_abd,_abe){ +var _abf=$.data(_abd,"treegrid"); +var opts=_abf.options; +var prow=_a66(_abd,_abe); +$(_abd).datagrid("deleteRow",_abe); +$.easyui.removeArrayItem(_abf.checkedRows,opts.idField,_abe); +_a4c(_abd); +if(prow){ +_a68(_abd,prow[opts.idField]); +} +_abf.total-=1; +$(_abd).datagrid("getPager").pagination("refresh",{total:_abf.total}); +$(_abd).treegrid("showLines"); +}; +function _ac0(_ac1){ +var t=$(_ac1); +var opts=t.treegrid("options"); +if(opts.lines){ +t.treegrid("getPanel").addClass("tree-lines"); +}else{ +t.treegrid("getPanel").removeClass("tree-lines"); +return; +} +t.treegrid("getPanel").find("span.tree-indent").removeClass("tree-line tree-join tree-joinbottom"); +t.treegrid("getPanel").find("div.datagrid-cell").removeClass("tree-node-last tree-root-first tree-root-one"); +var _ac2=t.treegrid("getRoots"); +if(_ac2.length>1){ +_ac3(_ac2[0]).addClass("tree-root-first"); +}else{ +if(_ac2.length==1){ +_ac3(_ac2[0]).addClass("tree-root-one"); +} +} +_ac4(_ac2); +_ac5(_ac2); +function _ac4(_ac6){ +$.map(_ac6,function(node){ +if(node.children&&node.children.length){ +_ac4(node.children); +}else{ +var cell=_ac3(node); +cell.find(".tree-icon").prev().addClass("tree-join"); +} +}); +if(_ac6.length){ +var cell=_ac3(_ac6[_ac6.length-1]); +cell.addClass("tree-node-last"); +cell.find(".tree-join").removeClass("tree-join").addClass("tree-joinbottom"); +} +}; +function _ac5(_ac7){ +$.map(_ac7,function(node){ +if(node.children&&node.children.length){ +_ac5(node.children); +} +}); +for(var i=0;i<_ac7.length-1;i++){ +var node=_ac7[i]; +var _ac8=t.treegrid("getLevel",node[opts.idField]); +var tr=opts.finder.getTr(_ac1,node[opts.idField]); +var cc=tr.next().find("tr.datagrid-row td[field=\""+opts.treeField+"\"] div.datagrid-cell"); +cc.find("span:eq("+(_ac8-1)+")").addClass("tree-line"); +} +}; +function _ac3(node){ +var tr=opts.finder.getTr(_ac1,node[opts.idField]); +var cell=tr.find("td[field=\""+opts.treeField+"\"] div.datagrid-cell"); +return cell; +}; +}; +$.fn.treegrid=function(_ac9,_aca){ +if(typeof _ac9=="string"){ +var _acb=$.fn.treegrid.methods[_ac9]; +if(_acb){ +return _acb(this,_aca); +}else{ +return this.datagrid(_ac9,_aca); +} +} +_ac9=_ac9||{}; +return this.each(function(){ +var _acc=$.data(this,"treegrid"); +if(_acc){ +$.extend(_acc.options,_ac9); +}else{ +_acc=$.data(this,"treegrid",{options:$.extend({},$.fn.treegrid.defaults,$.fn.treegrid.parseOptions(this),_ac9),data:[],checkedRows:[],tmpIds:[]}); +} +_a33(this); +if(_acc.options.data){ +$(this).treegrid("loadData",_acc.options.data); +} +_a43(this); +}); +}; +$.fn.treegrid.methods={options:function(jq){ +return $.data(jq[0],"treegrid").options; +},resize:function(jq,_acd){ +return jq.each(function(){ +$(this).datagrid("resize",_acd); +}); +},fixRowHeight:function(jq,_ace){ +return jq.each(function(){ +_a44(this,_ace); +}); +},loadData:function(jq,data){ +return jq.each(function(){ +_a72(this,data.parent,data); +}); +},load:function(jq,_acf){ +return jq.each(function(){ +$(this).treegrid("options").pageNumber=1; +$(this).treegrid("getPager").pagination({pageNumber:1}); +$(this).treegrid("reload",_acf); +}); +},reload:function(jq,id){ +return jq.each(function(){ +var opts=$(this).treegrid("options"); +var _ad0={}; +if(typeof id=="object"){ +_ad0=id; +}else{ +_ad0=$.extend({},opts.queryParams); +_ad0.id=id; +} +if(_ad0.id){ +var node=$(this).treegrid("find",_ad0.id); +if(node.children){ +node.children.splice(0,node.children.length); +} +opts.queryParams=_ad0; +var tr=opts.finder.getTr(this,_ad0.id); +tr.next("tr.treegrid-tr-tree").remove(); +tr.find("span.tree-hit").removeClass("tree-expanded tree-expanded-hover").addClass("tree-collapsed"); +_a9a(this,_ad0.id); +}else{ +_a43(this,null,_ad0); +} +}); +},reloadFooter:function(jq,_ad1){ +return jq.each(function(){ +var opts=$.data(this,"treegrid").options; +var dc=$.data(this,"datagrid").dc; +if(_ad1){ +$.data(this,"treegrid").footer=_ad1; +} +if(opts.showFooter){ +opts.view.renderFooter.call(opts.view,this,dc.footer1,true); +opts.view.renderFooter.call(opts.view,this,dc.footer2,false); +if(opts.view.onAfterRender){ +opts.view.onAfterRender.call(opts.view,this); +} +$(this).treegrid("fixRowHeight"); +} +}); +},getData:function(jq){ +return $.data(jq[0],"treegrid").data; +},getFooterRows:function(jq){ +return $.data(jq[0],"treegrid").footer; +},getRoot:function(jq){ +return _a85(jq[0]); +},getRoots:function(jq){ +return _a88(jq[0]); +},getParent:function(jq,id){ +return _a66(jq[0],id); +},getChildren:function(jq,id){ +return _a48(jq[0],id); +},getLevel:function(jq,id){ +return _a90(jq[0],id); +},find:function(jq,id){ +return find(jq[0],id); +},isLeaf:function(jq,id){ +var opts=$.data(jq[0],"treegrid").options; +var tr=opts.finder.getTr(jq[0],id); +var hit=tr.find("span.tree-hit"); +return hit.length==0; +},select:function(jq,id){ +return jq.each(function(){ +$(this).datagrid("selectRow",id); +}); +},unselect:function(jq,id){ +return jq.each(function(){ +$(this).datagrid("unselectRow",id); +}); +},collapse:function(jq,id){ +return jq.each(function(){ +_a97(this,id); +}); +},expand:function(jq,id){ +return jq.each(function(){ +_a9a(this,id); +}); +},toggle:function(jq,id){ +return jq.each(function(){ +_a54(this,id); +}); +},collapseAll:function(jq,id){ +return jq.each(function(){ +_aa2(this,id); +}); +},expandAll:function(jq,id){ +return jq.each(function(){ +_aa6(this,id); +}); +},expandTo:function(jq,id){ +return jq.each(function(){ +_aaa(this,id); +}); +},append:function(jq,_ad2){ +return jq.each(function(){ +_aad(this,_ad2); +}); +},insert:function(jq,_ad3){ +return jq.each(function(){ +_ab2(this,_ad3); +}); +},remove:function(jq,id){ +return jq.each(function(){ +_abc(this,id); +}); +},pop:function(jq,id){ +var row=jq.treegrid("find",id); +jq.treegrid("remove",id); +return row; +},refresh:function(jq,id){ +return jq.each(function(){ +var opts=$.data(this,"treegrid").options; +opts.view.refreshRow.call(opts.view,this,id); +}); +},update:function(jq,_ad4){ +return jq.each(function(){ +var opts=$.data(this,"treegrid").options; +var row=_ad4.row; +opts.view.updateRow.call(opts.view,this,_ad4.id,row); +if(row.checked!=undefined){ +row=find(this,_ad4.id); +$.extend(row,{checkState:row.checked?"checked":(row.checked===false?"unchecked":undefined)}); +_a68(this,_ad4.id); +} +}); +},beginEdit:function(jq,id){ +return jq.each(function(){ +$(this).datagrid("beginEdit",id); +$(this).treegrid("fixRowHeight",id); +}); +},endEdit:function(jq,id){ +return jq.each(function(){ +$(this).datagrid("endEdit",id); +}); +},cancelEdit:function(jq,id){ +return jq.each(function(){ +$(this).datagrid("cancelEdit",id); +}); +},showLines:function(jq){ +return jq.each(function(){ +_ac0(this); +}); +},setSelectionState:function(jq){ +return jq.each(function(){ +$(this).datagrid("setSelectionState"); +var _ad5=$(this).data("treegrid"); +for(var i=0;i<_ad5.tmpIds.length;i++){ +_a55(this,_ad5.tmpIds[i],true,true); +} +_ad5.tmpIds=[]; +}); +},getCheckedNodes:function(jq,_ad6){ +_ad6=_ad6||"checked"; +var rows=[]; +$.easyui.forEach(jq.data("treegrid").checkedRows,false,function(row){ +if(row.checkState==_ad6){ +rows.push(row); +} +}); +return rows; +},checkNode:function(jq,id){ +return jq.each(function(){ +_a55(this,id,true); +}); +},uncheckNode:function(jq,id){ +return jq.each(function(){ +_a55(this,id,false); +}); +},clearChecked:function(jq){ +return jq.each(function(){ +var _ad7=this; +var opts=$(_ad7).treegrid("options"); +$(_ad7).datagrid("clearChecked"); +$.map($(_ad7).treegrid("getCheckedNodes"),function(row){ +_a55(_ad7,row[opts.idField],false,true); +}); +}); +}}; +$.fn.treegrid.parseOptions=function(_ad8){ +return $.extend({},$.fn.datagrid.parseOptions(_ad8),$.parser.parseOptions(_ad8,["treeField",{checkbox:"boolean",cascadeCheck:"boolean",onlyLeafCheck:"boolean"},{animate:"boolean"}])); +}; +var _ad9=$.extend({},$.fn.datagrid.defaults.view,{render:function(_ada,_adb,_adc){ +var opts=$.data(_ada,"treegrid").options; +var _add=$(_ada).datagrid("getColumnFields",_adc); +var _ade=$.data(_ada,"datagrid").rowIdPrefix; +if(_adc){ +if(!(opts.rownumbers||(opts.frozenColumns&&opts.frozenColumns.length))){ +return; +} +} +var view=this; +if(this.treeNodes&&this.treeNodes.length){ +var _adf=_ae0.call(this,_adc,this.treeLevel,this.treeNodes); +$(_adb).append(_adf.join("")); +} +function _ae0(_ae1,_ae2,_ae3){ +var _ae4=$(_ada).treegrid("getParent",_ae3[0][opts.idField]); +var _ae5=(_ae4?_ae4.children.length:$(_ada).treegrid("getRoots").length)-_ae3.length; +var _ae6=[""]; +for(var i=0;i<_ae3.length;i++){ +var row=_ae3[i]; +if(row.state!="open"&&row.state!="closed"){ +row.state="open"; +} +var css=opts.rowStyler?opts.rowStyler.call(_ada,row):""; +var cs=this.getStyleValue(css); +var cls="class=\"datagrid-row "+(_ae5++%2&&opts.striped?"datagrid-row-alt ":" ")+cs.c+"\""; +var _ae7=cs.s?"style=\""+cs.s+"\"":""; +var _ae8=_ade+"-"+(_ae1?1:2)+"-"+row[opts.idField]; +_ae6.push(""); +_ae6=_ae6.concat(view.renderRow.call(view,_ada,_add,_ae1,_ae2,row)); +_ae6.push(""); +if(row.children&&row.children.length){ +var tt=_ae0.call(this,_ae1,_ae2+1,row.children); +var v=row.state=="closed"?"none":"block"; +_ae6.push(""); +} +} +_ae6.push("
"); +_ae6=_ae6.concat(tt); +_ae6.push("
"); +return _ae6; +}; +},renderFooter:function(_ae9,_aea,_aeb){ +var opts=$.data(_ae9,"treegrid").options; +var rows=$.data(_ae9,"treegrid").footer||[]; +var _aec=$(_ae9).datagrid("getColumnFields",_aeb); +var _aed=[""]; +for(var i=0;i"); +_aed.push(this.renderRow.call(this,_ae9,_aec,_aeb,0,row)); +_aed.push(""); +} +_aed.push("
"); +$(_aea).html(_aed.join("")); +},renderRow:function(_aee,_aef,_af0,_af1,row){ +var _af2=$.data(_aee,"treegrid"); +var opts=_af2.options; +var cc=[]; +if(_af0&&opts.rownumbers){ +cc.push("
0
"); +} +for(var i=0;i<_aef.length;i++){ +var _af3=_aef[i]; +var col=$(_aee).datagrid("getColumnOption",_af3); +if(col){ +var css=col.styler?(col.styler(row[_af3],row)||""):""; +var cs=this.getStyleValue(css); +var cls=cs.c?"class=\""+cs.c+"\"":""; +var _af4=col.hidden?"style=\"display:none;"+cs.s+"\"":(cs.s?"style=\""+cs.s+"\"":""); +cc.push(""); +var _af4=""; +if(!col.checkbox){ +if(col.align){ +_af4+="text-align:"+col.align+";"; +} +if(!opts.nowrap){ +_af4+="white-space:normal;height:auto;"; +}else{ +if(opts.autoRowHeight){ +_af4+="height:auto;"; +} +} +} +cc.push("
"); +if(col.checkbox){ +if(row.checked){ +cc.push(""); +}else{ +var val=null; +if(col.formatter){ +val=col.formatter(row[_af3],row); +}else{ +val=row[_af3]; +} +if(_af3==opts.treeField){ +for(var j=0;j<_af1;j++){ +cc.push(""); +} +if(row.state=="closed"){ +cc.push(""); +cc.push(""); +}else{ +if(row.children&&row.children.length){ +cc.push(""); +cc.push(""); +}else{ +cc.push(""); +cc.push(""); +} +} +if(this.hasCheckbox(_aee,row)){ +var flag=0; +var crow=$.easyui.getArrayItem(_af2.checkedRows,opts.idField,row[opts.idField]); +if(crow){ +flag=crow.checkState=="checked"?1:2; +row.checkState=crow.checkState; +row.checked=crow.checked; +$.easyui.addArrayItem(_af2.checkedRows,opts.idField,row); +}else{ +var prow=$.easyui.getArrayItem(_af2.checkedRows,opts.idField,row._parentId); +if(prow&&prow.checkState=="checked"&&opts.cascadeCheck){ +flag=1; +row.checked=true; +$.easyui.addArrayItem(_af2.checkedRows,opts.idField,row); +}else{ +if(row.checked){ +$.easyui.addArrayItem(_af2.tmpIds,row[opts.idField]); +} +} +row.checkState=flag?"checked":"unchecked"; +} +cc.push(""); +}else{ +row.checkState=undefined; +row.checked=undefined; +} +cc.push(""+val+""); +}else{ +cc.push(val); +} +} +cc.push("
"); +cc.push(""); +} +} +return cc.join(""); +},hasCheckbox:function(_af5,row){ +var opts=$.data(_af5,"treegrid").options; +if(opts.checkbox){ +if($.isFunction(opts.checkbox)){ +if(opts.checkbox.call(_af5,row)){ +return true; +}else{ +return false; +} +}else{ +if(opts.onlyLeafCheck){ +if(row.state=="open"&&!(row.children&&row.children.length)){ +return true; +} +}else{ +return true; +} +} +} +return false; +},refreshRow:function(_af6,id){ +this.updateRow.call(this,_af6,id,{}); +},updateRow:function(_af7,id,row){ +var opts=$.data(_af7,"treegrid").options; +var _af8=$(_af7).treegrid("find",id); +$.extend(_af8,row); +var _af9=$(_af7).treegrid("getLevel",id)-1; +var _afa=opts.rowStyler?opts.rowStyler.call(_af7,_af8):""; +var _afb=$.data(_af7,"datagrid").rowIdPrefix; +var _afc=_af8[opts.idField]; +function _afd(_afe){ +var _aff=$(_af7).treegrid("getColumnFields",_afe); +var tr=opts.finder.getTr(_af7,id,"body",(_afe?1:2)); +var _b00=tr.find("div.datagrid-cell-rownumber").html(); +var _b01=tr.find("div.datagrid-cell-check input[type=checkbox]").is(":checked"); +tr.html(this.renderRow(_af7,_aff,_afe,_af9,_af8)); +tr.attr("style",_afa||""); +tr.find("div.datagrid-cell-rownumber").html(_b00); +if(_b01){ +tr.find("div.datagrid-cell-check input[type=checkbox]")._propAttr("checked",true); +} +if(_afc!=id){ +tr.attr("id",_afb+"-"+(_afe?1:2)+"-"+_afc); +tr.attr("node-id",_afc); +} +}; +_afd.call(this,true); +_afd.call(this,false); +$(_af7).treegrid("fixRowHeight",id); +},deleteRow:function(_b02,id){ +var opts=$.data(_b02,"treegrid").options; +var tr=opts.finder.getTr(_b02,id); +tr.next("tr.treegrid-tr-tree").remove(); +tr.remove(); +var _b03=del(id); +if(_b03){ +if(_b03.children.length==0){ +tr=opts.finder.getTr(_b02,_b03[opts.idField]); +tr.next("tr.treegrid-tr-tree").remove(); +var cell=tr.children("td[field=\""+opts.treeField+"\"]").children("div.datagrid-cell"); +cell.find(".tree-icon").removeClass("tree-folder").addClass("tree-file"); +cell.find(".tree-hit").remove(); +$("").prependTo(cell); +} +} +this.setEmptyMsg(_b02); +function del(id){ +var cc; +var _b04=$(_b02).treegrid("getParent",id); +if(_b04){ +cc=_b04.children; +}else{ +cc=$(_b02).treegrid("getData"); +} +for(var i=0;ib?1:-1); +}; +r=_b0f(r1[sn],r2[sn])*(so=="asc"?1:-1); +if(r!=0){ +return r; +} +} +return r; +}); +for(var i=0;i"); +if(!_b32){ +_b35.push(""); +_b35.push(opts.groupFormatter.call(_b2f,_b31.value,_b31.rows)); +_b35.push(""); +} +_b35.push(""); +_b35.push(this.renderTable(_b2f,_b31.startIndex,_b31.rows,_b32)); +return _b35.join(""); +},groupRows:function(_b36,rows){ +var _b37=$.data(_b36,"datagrid"); +var opts=_b37.options; +var _b38=[]; +for(var i=0;idiv.combo-p>div.combo-panel:visible").panel("close"); +}); +}); +function _b48(_b49){ +var _b4a=$.data(_b49,"combo"); +var opts=_b4a.options; +if(!_b4a.panel){ +_b4a.panel=$("
").appendTo("html>body"); +_b4a.panel.panel({minWidth:opts.panelMinWidth,maxWidth:opts.panelMaxWidth,minHeight:opts.panelMinHeight,maxHeight:opts.panelMaxHeight,doSize:false,closed:true,cls:"combo-p",style:{position:"absolute",zIndex:10},onOpen:function(){ +var _b4b=$(this).panel("options").comboTarget; +var _b4c=$.data(_b4b,"combo"); +if(_b4c){ +_b4c.options.onShowPanel.call(_b4b); +} +},onBeforeClose:function(){ +_b47($(this).parent()); +},onClose:function(){ +var _b4d=$(this).panel("options").comboTarget; +var _b4e=$(_b4d).data("combo"); +if(_b4e){ +_b4e.options.onHidePanel.call(_b4d); +} +}}); +} +var _b4f=$.extend(true,[],opts.icons); +if(opts.hasDownArrow){ +_b4f.push({iconCls:"combo-arrow",handler:function(e){ +_b54(e.data.target); +}}); +} +$(_b49).addClass("combo-f").textbox($.extend({},opts,{icons:_b4f,onChange:function(){ +}})); +$(_b49).attr("comboName",$(_b49).attr("textboxName")); +_b4a.combo=$(_b49).next(); +_b4a.combo.addClass("combo"); +_b4a.panel._unbind(".combo"); +for(var _b50 in opts.panelEvents){ +_b4a.panel._bind(_b50+".combo",{target:_b49},opts.panelEvents[_b50]); +} +}; +function _b51(_b52){ +var _b53=$.data(_b52,"combo"); +var opts=_b53.options; +var p=_b53.panel; +if(p.is(":visible")){ +p.panel("close"); +} +if(!opts.cloned){ +p.panel("destroy"); +} +$(_b52).textbox("destroy"); +}; +function _b54(_b55){ +var _b56=$.data(_b55,"combo").panel; +if(_b56.is(":visible")){ +var _b57=_b56.combo("combo"); +_b58(_b57); +if(_b57!=_b55){ +$(_b55).combo("showPanel"); +} +}else{ +var p=$(_b55).closest("div.combo-p").children(".combo-panel"); +$("div.combo-panel:visible").not(_b56).not(p).panel("close"); +$(_b55).combo("showPanel"); +} +$(_b55).combo("textbox").focus(); +}; +function _b47(_b59){ +$(_b59).find(".combo-f").each(function(){ +var p=$(this).combo("panel"); +if(p.is(":visible")){ +p.panel("close"); +} +}); +}; +function _b5a(e){ +var _b5b=e.data.target; +var _b5c=$.data(_b5b,"combo"); +var opts=_b5c.options; +if(!opts.editable){ +_b54(_b5b); +}else{ +var p=$(_b5b).closest("div.combo-p").children(".combo-panel"); +$("div.combo-panel:visible").not(p).each(function(){ +var _b5d=$(this).combo("combo"); +if(_b5d!=_b5b){ +_b58(_b5d); +} +}); +} +}; +function _b5e(e){ +var _b5f=e.data.target; +var t=$(_b5f); +var _b60=t.data("combo"); +var opts=t.combo("options"); +_b60.panel.panel("options").comboTarget=_b5f; +switch(e.keyCode){ +case 38: +opts.keyHandler.up.call(_b5f,e); +break; +case 40: +opts.keyHandler.down.call(_b5f,e); +break; +case 37: +opts.keyHandler.left.call(_b5f,e); +break; +case 39: +opts.keyHandler.right.call(_b5f,e); +break; +case 13: +e.preventDefault(); +opts.keyHandler.enter.call(_b5f,e); +return false; +case 9: +case 27: +_b58(_b5f); +break; +default: +if(opts.editable){ +if(_b60.timer){ +clearTimeout(_b60.timer); +} +_b60.timer=setTimeout(function(){ +var q=t.combo("getText"); +if(_b60.previousText!=q){ +_b60.previousText=q; +t.combo("showPanel"); +opts.keyHandler.query.call(_b5f,q,e); +t.combo("validate"); +} +},opts.delay); +} +} +}; +function _b61(e){ +var _b62=e.data.target; +var _b63=$(_b62).data("combo"); +if(_b63.timer){ +clearTimeout(_b63.timer); +} +}; +function _b64(_b65){ +var _b66=$.data(_b65,"combo"); +var _b67=_b66.combo; +var _b68=_b66.panel; +var opts=$(_b65).combo("options"); +var _b69=_b68.panel("options"); +_b69.comboTarget=_b65; +if(_b69.closed){ +_b68.panel("panel").show().css({zIndex:($.fn.menu?$.fn.menu.defaults.zIndex++:($.fn.window?$.fn.window.defaults.zIndex++:99)),left:-999999}); +_b68.panel("resize",{width:(opts.panelWidth?opts.panelWidth:_b67._outerWidth()),height:opts.panelHeight}); +_b68.panel("panel").hide(); +_b68.panel("open"); +} +(function f(){ +if(_b69.comboTarget==_b65&&_b68.is(":visible")){ +_b68.panel("move",{left:_b6a(),top:_b6b()}); +setTimeout(f,200); +} +})(); +function _b6a(){ +var left=_b67.offset().left; +if(opts.panelAlign=="right"){ +left+=_b67._outerWidth()-_b68._outerWidth(); +} +if(left+_b68._outerWidth()>$(window)._outerWidth()+$(document).scrollLeft()){ +left=$(window)._outerWidth()+$(document).scrollLeft()-_b68._outerWidth(); +} +if(left<0){ +left=0; +} +return left; +}; +function _b6b(){ +if(opts.panelValign=="top"){ +var top=_b67.offset().top-_b68._outerHeight(); +}else{ +if(opts.panelValign=="bottom"){ +var top=_b67.offset().top+_b67._outerHeight(); +}else{ +var top=_b67.offset().top+_b67._outerHeight(); +if(top+_b68._outerHeight()>$(window)._outerHeight()+$(document).scrollTop()){ +top=_b67.offset().top-_b68._outerHeight(); +} +if(top<$(document).scrollTop()){ +top=_b67.offset().top+_b67._outerHeight(); +} +} +} +return top; +}; +}; +function _b58(_b6c){ +var _b6d=$.data(_b6c,"combo").panel; +_b6d.panel("close"); +}; +function _b6e(_b6f,text){ +var _b70=$.data(_b6f,"combo"); +var _b71=$(_b6f).textbox("getText"); +if(_b71!=text){ +$(_b6f).textbox("setText",text); +} +_b70.previousText=text; +}; +function _b72(_b73){ +var _b74=$.data(_b73,"combo"); +var opts=_b74.options; +var _b75=$(_b73).next(); +var _b76=[]; +_b75.find(".textbox-value").each(function(){ +_b76.push($(this).val()); +}); +if(opts.multivalue){ +return _b76; +}else{ +return _b76.length?_b76[0].split(opts.separator):_b76; +} +}; +function _b77(_b78,_b79){ +var _b7a=$.data(_b78,"combo"); +var _b7b=_b7a.combo; +var opts=$(_b78).combo("options"); +if(!$.isArray(_b79)){ +_b79=_b79.split(opts.separator); +} +var _b7c=_b72(_b78); +_b7b.find(".textbox-value").remove(); +if(_b79.length){ +if(opts.multivalue){ +for(var i=0;i<_b79.length;i++){ +_b7d(_b79[i]); +} +}else{ +_b7d(_b79.join(opts.separator)); +} +} +function _b7d(_b7e){ +var name=$(_b78).attr("textboxName")||""; +var _b7f=$("").appendTo(_b7b); +_b7f.attr("name",name); +if(opts.disabled){ +_b7f.attr("disabled","disabled"); +} +_b7f.val(_b7e); +}; +var _b80=(function(){ +if(opts.onChange==$.parser.emptyFn){ +return false; +} +if(_b7c.length!=_b79.length){ +return true; +} +for(var i=0;i<_b79.length;i++){ +if(_b79[i]!=_b7c[i]){ +return true; +} +} +return false; +})(); +if(_b80){ +$(_b78).val(_b79.join(opts.separator)); +if(opts.multiple){ +opts.onChange.call(_b78,_b79,_b7c); +}else{ +opts.onChange.call(_b78,_b79[0],_b7c[0]); +} +$(_b78).closest("form").trigger("_change",[_b78]); +} +}; +function _b81(_b82){ +var _b83=_b72(_b82); +return _b83[0]; +}; +function _b84(_b85,_b86){ +_b77(_b85,[_b86]); +}; +function _b87(_b88){ +var opts=$.data(_b88,"combo").options; +var _b89=opts.onChange; +opts.onChange=$.parser.emptyFn; +if(opts.multiple){ +_b77(_b88,opts.value?opts.value:[]); +}else{ +_b84(_b88,opts.value); +} +opts.onChange=_b89; +}; +$.fn.combo=function(_b8a,_b8b){ +if(typeof _b8a=="string"){ +var _b8c=$.fn.combo.methods[_b8a]; +if(_b8c){ +return _b8c(this,_b8b); +}else{ +return this.textbox(_b8a,_b8b); +} +} +_b8a=_b8a||{}; +return this.each(function(){ +var _b8d=$.data(this,"combo"); +if(_b8d){ +$.extend(_b8d.options,_b8a); +if(_b8a.value!=undefined){ +_b8d.options.originalValue=_b8a.value; +} +}else{ +_b8d=$.data(this,"combo",{options:$.extend({},$.fn.combo.defaults,$.fn.combo.parseOptions(this),_b8a),previousText:""}); +if(_b8d.options.multiple&&_b8d.options.value==""){ +_b8d.options.originalValue=[]; +}else{ +_b8d.options.originalValue=_b8d.options.value; +} +} +_b48(this); +_b87(this); +}); +}; +$.fn.combo.methods={options:function(jq){ +var opts=jq.textbox("options"); +return $.extend($.data(jq[0],"combo").options,{width:opts.width,height:opts.height,disabled:opts.disabled,readonly:opts.readonly,editable:opts.editable}); +},cloneFrom:function(jq,from){ +return jq.each(function(){ +$(this).textbox("cloneFrom",from); +$.data(this,"combo",{options:$.extend(true,{cloned:true},$(from).combo("options")),combo:$(this).next(),panel:$(from).combo("panel")}); +$(this).addClass("combo-f").attr("comboName",$(this).attr("textboxName")); +}); +},combo:function(jq){ +return jq.closest(".combo-panel").panel("options").comboTarget; +},panel:function(jq){ +return $.data(jq[0],"combo").panel; +},destroy:function(jq){ +return jq.each(function(){ +_b51(this); +}); +},showPanel:function(jq){ +return jq.each(function(){ +_b64(this); +}); +},hidePanel:function(jq){ +return jq.each(function(){ +_b58(this); +}); +},clear:function(jq){ +return jq.each(function(){ +$(this).textbox("setText",""); +var opts=$.data(this,"combo").options; +if(opts.multiple){ +$(this).combo("setValues",[]); +}else{ +$(this).combo("setValue",""); +} +}); +},reset:function(jq){ +return jq.each(function(){ +var opts=$.data(this,"combo").options; +if(opts.multiple){ +$(this).combo("setValues",opts.originalValue); +}else{ +$(this).combo("setValue",opts.originalValue); +} +}); +},setText:function(jq,text){ +return jq.each(function(){ +_b6e(this,text); +}); +},getValues:function(jq){ +return _b72(jq[0]); +},setValues:function(jq,_b8e){ +return jq.each(function(){ +_b77(this,_b8e); +}); +},getValue:function(jq){ +return _b81(jq[0]); +},setValue:function(jq,_b8f){ +return jq.each(function(){ +_b84(this,_b8f); +}); +}}; +$.fn.combo.parseOptions=function(_b90){ +var t=$(_b90); +return $.extend({},$.fn.textbox.parseOptions(_b90),$.parser.parseOptions(_b90,["separator","panelAlign",{panelWidth:"number",hasDownArrow:"boolean",delay:"number",reversed:"boolean",multivalue:"boolean",selectOnNavigation:"boolean"},{panelMinWidth:"number",panelMaxWidth:"number",panelMinHeight:"number",panelMaxHeight:"number"}]),{panelHeight:(t.attr("panelHeight")=="auto"?"auto":parseInt(t.attr("panelHeight"))||undefined),multiple:(t.attr("multiple")?true:undefined)}); +}; +$.fn.combo.defaults=$.extend({},$.fn.textbox.defaults,{inputEvents:{click:_b5a,keydown:_b5e,paste:_b5e,drop:_b5e,blur:_b61},panelEvents:{mousedown:function(e){ +e.preventDefault(); +e.stopPropagation(); +}},panelWidth:null,panelHeight:300,panelMinWidth:null,panelMaxWidth:null,panelMinHeight:null,panelMaxHeight:null,panelAlign:"left",panelValign:"auto",reversed:false,multiple:false,multivalue:true,selectOnNavigation:true,separator:",",hasDownArrow:true,delay:200,keyHandler:{up:function(e){ +},down:function(e){ +},left:function(e){ +},right:function(e){ +},enter:function(e){ +},query:function(q,e){ +}},onShowPanel:function(){ +},onHidePanel:function(){ +},onChange:function(_b91,_b92){ +}}); +})(jQuery); +(function($){ +function _b93(_b94,_b95){ +var _b96=$.data(_b94,"combobox"); +return $.easyui.indexOfArray(_b96.data,_b96.options.valueField,_b95); +}; +function _b97(_b98,_b99){ +var opts=$.data(_b98,"combobox").options; +var _b9a=$(_b98).combo("panel"); +var item=opts.finder.getEl(_b98,_b99); +if(item.length){ +if(item.position().top<=0){ +var h=_b9a.scrollTop()+item.position().top; +_b9a.scrollTop(h); +}else{ +if(item.position().top+item.outerHeight()>_b9a.height()){ +var h=_b9a.scrollTop()+item.position().top+item.outerHeight()-_b9a.height(); +_b9a.scrollTop(h); +} +} +} +_b9a.triggerHandler("scroll"); +}; +function nav(_b9b,dir){ +var opts=$.data(_b9b,"combobox").options; +var _b9c=$(_b9b).combobox("panel"); +var item=_b9c.children("div.combobox-item-hover"); +if(!item.length){ +item=_b9c.children("div.combobox-item-selected"); +} +item.removeClass("combobox-item-hover"); +var _b9d="div.combobox-item:visible:not(.combobox-item-disabled):first"; +var _b9e="div.combobox-item:visible:not(.combobox-item-disabled):last"; +if(!item.length){ +item=_b9c.children(dir=="next"?_b9d:_b9e); +}else{ +if(dir=="next"){ +item=item.nextAll(_b9d); +if(!item.length){ +item=_b9c.children(_b9d); +} +}else{ +item=item.prevAll(_b9d); +if(!item.length){ +item=_b9c.children(_b9e); +} +} +} +if(item.length){ +item.addClass("combobox-item-hover"); +var row=opts.finder.getRow(_b9b,item); +if(row){ +$(_b9b).combobox("scrollTo",row[opts.valueField]); +if(opts.selectOnNavigation){ +_b9f(_b9b,row[opts.valueField]); +} +} +} +}; +function _b9f(_ba0,_ba1,_ba2){ +var opts=$.data(_ba0,"combobox").options; +var _ba3=$(_ba0).combo("getValues"); +if($.inArray(_ba1+"",_ba3)==-1){ +if(opts.multiple){ +_ba3.push(_ba1); +}else{ +_ba3=[_ba1]; +} +_ba4(_ba0,_ba3,_ba2); +} +}; +function _ba5(_ba6,_ba7){ +var opts=$.data(_ba6,"combobox").options; +var _ba8=$(_ba6).combo("getValues"); +var _ba9=$.inArray(_ba7+"",_ba8); +if(_ba9>=0){ +_ba8.splice(_ba9,1); +_ba4(_ba6,_ba8); +} +}; +function _ba4(_baa,_bab,_bac){ +var opts=$.data(_baa,"combobox").options; +var _bad=$(_baa).combo("panel"); +if(!$.isArray(_bab)){ +_bab=_bab.split(opts.separator); +} +if(!opts.multiple){ +_bab=_bab.length?[_bab[0]]:[""]; +} +var _bae=$(_baa).combo("getValues"); +if(_bad.is(":visible")){ +_bad.find(".combobox-item-selected").each(function(){ +var row=opts.finder.getRow(_baa,$(this)); +if(row){ +if($.easyui.indexOfArray(_bae,row[opts.valueField])==-1){ +$(this).removeClass("combobox-item-selected"); +} +} +}); +} +$.map(_bae,function(v){ +if($.easyui.indexOfArray(_bab,v)==-1){ +var el=opts.finder.getEl(_baa,v); +if(el.hasClass("combobox-item-selected")){ +el.removeClass("combobox-item-selected"); +opts.onUnselect.call(_baa,opts.finder.getRow(_baa,v)); +} +} +}); +var _baf=null; +var vv=[],ss=[]; +for(var i=0;i<_bab.length;i++){ +var v=_bab[i]; +var s=v; +var row=opts.finder.getRow(_baa,v); +if(row){ +s=row[opts.textField]; +_baf=row; +var el=opts.finder.getEl(_baa,v); +if(!el.hasClass("combobox-item-selected")){ +el.addClass("combobox-item-selected"); +opts.onSelect.call(_baa,row); +} +}else{ +s=_bb0(v,opts.mappingRows)||v; +} +vv.push(v); +ss.push(s); +} +if(!_bac){ +$(_baa).combo("setText",ss.join(opts.separator)); +} +if(opts.showItemIcon){ +var tb=$(_baa).combobox("textbox"); +tb.removeClass("textbox-bgicon "+opts.textboxIconCls); +if(_baf&&_baf.iconCls){ +tb.addClass("textbox-bgicon "+_baf.iconCls); +opts.textboxIconCls=_baf.iconCls; +} +} +$(_baa).combo("setValues",vv); +_bad.triggerHandler("scroll"); +function _bb0(_bb1,a){ +var item=$.easyui.getArrayItem(a,opts.valueField,_bb1); +return item?item[opts.textField]:undefined; +}; +}; +function _bb2(_bb3,data,_bb4){ +var _bb5=$.data(_bb3,"combobox"); +var opts=_bb5.options; +_bb5.data=opts.loadFilter.call(_bb3,data); +opts.view.render.call(opts.view,_bb3,$(_bb3).combo("panel"),_bb5.data); +var vv=$(_bb3).combobox("getValues"); +$.easyui.forEach(_bb5.data,false,function(row){ +if(row["selected"]){ +$.easyui.addArrayItem(vv,row[opts.valueField]+""); +} +}); +if(opts.multiple){ +_ba4(_bb3,vv,_bb4); +}else{ +_ba4(_bb3,vv.length?[vv[vv.length-1]]:[],_bb4); +} +opts.onLoadSuccess.call(_bb3,data); +}; +function _bb6(_bb7,url,_bb8,_bb9){ +var opts=$.data(_bb7,"combobox").options; +if(url){ +opts.url=url; +} +_bb8=$.extend({},opts.queryParams,_bb8||{}); +if(opts.onBeforeLoad.call(_bb7,_bb8)==false){ +return; +} +opts.loader.call(_bb7,_bb8,function(data){ +_bb2(_bb7,data,_bb9); +},function(){ +opts.onLoadError.apply(this,arguments); +}); +}; +function _bba(_bbb,q){ +var _bbc=$.data(_bbb,"combobox"); +var opts=_bbc.options; +var _bbd=$(); +var qq=opts.multiple?q.split(opts.separator):[q]; +if(opts.mode=="remote"){ +_bbe(qq); +_bb6(_bbb,null,{q:q},true); +}else{ +var _bbf=$(_bbb).combo("panel"); +_bbf.find(".combobox-item-hover").removeClass("combobox-item-hover"); +_bbf.find(".combobox-item,.combobox-group").hide(); +var data=_bbc.data; +var vv=[]; +$.map(qq,function(q){ +q=$.trim(q); +var _bc0=q; +var _bc1=undefined; +_bbd=$(); +for(var i=0;i=0){ +vv.push(v); +} +}); +t.combobox("setValues",vv); +if(!opts.multiple){ +t.combobox("hidePanel"); +} +}; +function _bc6(_bc7){ +var _bc8=$.data(_bc7,"combobox"); +var opts=_bc8.options; +$(_bc7).addClass("combobox-f"); +$(_bc7).combo($.extend({},opts,{onShowPanel:function(){ +$(this).combo("panel").find("div.combobox-item:hidden,div.combobox-group:hidden").show(); +_ba4(this,$(this).combobox("getValues"),true); +$(this).combobox("scrollTo",$(this).combobox("getValue")); +opts.onShowPanel.call(this); +}})); +}; +function _bc9(e){ +$(this).children("div.combobox-item-hover").removeClass("combobox-item-hover"); +var item=$(e.target).closest("div.combobox-item"); +if(!item.hasClass("combobox-item-disabled")){ +item.addClass("combobox-item-hover"); +} +e.stopPropagation(); +}; +function _bca(e){ +$(e.target).closest("div.combobox-item").removeClass("combobox-item-hover"); +e.stopPropagation(); +}; +function _bcb(e){ +var _bcc=$(this).panel("options").comboTarget; +if(!_bcc){ +return; +} +var opts=$(_bcc).combobox("options"); +var item=$(e.target).closest("div.combobox-item"); +if(!item.length||item.hasClass("combobox-item-disabled")){ +return; +} +var row=opts.finder.getRow(_bcc,item); +if(!row){ +return; +} +if(opts.blurTimer){ +clearTimeout(opts.blurTimer); +opts.blurTimer=null; +} +opts.onClick.call(_bcc,row); +var _bcd=row[opts.valueField]; +if(opts.multiple){ +if(item.hasClass("combobox-item-selected")){ +_ba5(_bcc,_bcd); +}else{ +_b9f(_bcc,_bcd); +} +}else{ +$(_bcc).combobox("setValue",_bcd).combobox("hidePanel"); +} +e.stopPropagation(); +}; +function _bce(e){ +var _bcf=$(this).panel("options").comboTarget; +if(!_bcf){ +return; +} +var opts=$(_bcf).combobox("options"); +if(opts.groupPosition=="sticky"){ +var _bd0=$(this).children(".combobox-stick"); +if(!_bd0.length){ +_bd0=$("
").appendTo(this); +} +_bd0.hide(); +var _bd1=$(_bcf).data("combobox"); +$(this).children(".combobox-group:visible").each(function(){ +var g=$(this); +var _bd2=opts.finder.getGroup(_bcf,g); +var _bd3=_bd1.data[_bd2.startIndex+_bd2.count-1]; +var last=opts.finder.getEl(_bcf,_bd3[opts.valueField]); +if(g.position().top<0&&last.position().top>0){ +_bd0.show().html(g.html()); +return false; +} +}); +} +}; +$.fn.combobox=function(_bd4,_bd5){ +if(typeof _bd4=="string"){ +var _bd6=$.fn.combobox.methods[_bd4]; +if(_bd6){ +return _bd6(this,_bd5); +}else{ +return this.combo(_bd4,_bd5); +} +} +_bd4=_bd4||{}; +return this.each(function(){ +var _bd7=$.data(this,"combobox"); +if(_bd7){ +$.extend(_bd7.options,_bd4); +}else{ +_bd7=$.data(this,"combobox",{options:$.extend({},$.fn.combobox.defaults,$.fn.combobox.parseOptions(this),_bd4),data:[]}); +} +_bc6(this); +if(_bd7.options.data){ +_bb2(this,_bd7.options.data); +}else{ +var data=$.fn.combobox.parseData(this); +if(data.length){ +_bb2(this,data); +} +} +_bb6(this); +}); +}; +$.fn.combobox.methods={options:function(jq){ +var _bd8=jq.combo("options"); +return $.extend($.data(jq[0],"combobox").options,{width:_bd8.width,height:_bd8.height,originalValue:_bd8.originalValue,disabled:_bd8.disabled,readonly:_bd8.readonly,editable:_bd8.editable}); +},cloneFrom:function(jq,from){ +return jq.each(function(){ +$(this).combo("cloneFrom",from); +$.data(this,"combobox",$(from).data("combobox")); +$(this).addClass("combobox-f").attr("comboboxName",$(this).attr("textboxName")); +}); +},getData:function(jq){ +return $.data(jq[0],"combobox").data; +},setValues:function(jq,_bd9){ +return jq.each(function(){ +var opts=$(this).combobox("options"); +if($.isArray(_bd9)){ +_bd9=$.map(_bd9,function(_bda){ +if(_bda&&typeof _bda=="object"){ +$.easyui.addArrayItem(opts.mappingRows,opts.valueField,_bda); +return _bda[opts.valueField]; +}else{ +return _bda; +} +}); +} +_ba4(this,_bd9); +}); +},setValue:function(jq,_bdb){ +return jq.each(function(){ +$(this).combobox("setValues",$.isArray(_bdb)?_bdb:[_bdb]); +}); +},clear:function(jq){ +return jq.each(function(){ +_ba4(this,[]); +}); +},reset:function(jq){ +return jq.each(function(){ +var opts=$(this).combobox("options"); +if(opts.multiple){ +$(this).combobox("setValues",opts.originalValue); +}else{ +$(this).combobox("setValue",opts.originalValue); +} +}); +},loadData:function(jq,data){ +return jq.each(function(){ +_bb2(this,data); +}); +},reload:function(jq,url){ +return jq.each(function(){ +if(typeof url=="string"){ +_bb6(this,url); +}else{ +if(url){ +var opts=$(this).combobox("options"); +opts.queryParams=url; +} +_bb6(this); +} +}); +},select:function(jq,_bdc){ +return jq.each(function(){ +_b9f(this,_bdc); +}); +},unselect:function(jq,_bdd){ +return jq.each(function(){ +_ba5(this,_bdd); +}); +},scrollTo:function(jq,_bde){ +return jq.each(function(){ +_b97(this,_bde); +}); +}}; +$.fn.combobox.parseOptions=function(_bdf){ +var t=$(_bdf); +return $.extend({},$.fn.combo.parseOptions(_bdf),$.parser.parseOptions(_bdf,["valueField","textField","groupField","groupPosition","mode","method","url",{showItemIcon:"boolean",limitToList:"boolean"}])); +}; +$.fn.combobox.parseData=function(_be0){ +var data=[]; +var opts=$(_be0).combobox("options"); +$(_be0).children().each(function(){ +if(this.tagName.toLowerCase()=="optgroup"){ +var _be1=$(this).attr("label"); +$(this).children().each(function(){ +_be2(this,_be1); +}); +}else{ +_be2(this); +} +}); +return data; +function _be2(el,_be3){ +var t=$(el); +var row={}; +row[opts.valueField]=t.attr("value")!=undefined?t.attr("value"):t.text(); +row[opts.textField]=t.text(); +row["iconCls"]=$.parser.parseOptions(el,["iconCls"]).iconCls; +row["selected"]=t.is(":selected"); +row["disabled"]=t.is(":disabled"); +if(_be3){ +opts.groupField=opts.groupField||"group"; +row[opts.groupField]=_be3; +} +data.push(row); +}; +}; +var _be4=0; +var _be5={render:function(_be6,_be7,data){ +var _be8=$.data(_be6,"combobox"); +var opts=_be8.options; +var _be9=$(_be6).attr("id")||""; +_be4++; +_be8.itemIdPrefix=_be9+"_easyui_combobox_i"+_be4; +_be8.groupIdPrefix=_be9+"_easyui_combobox_g"+_be4; +_be8.groups=[]; +var dd=[]; +var _bea=undefined; +for(var i=0;i"); +dd.push(opts.groupFormatter?opts.groupFormatter.call(_be6,g):g); +dd.push(""); +}else{ +_be8.groups[_be8.groups.length-1].count++; +} +}else{ +_bea=undefined; +} +var cls="combobox-item"+(row.disabled?" combobox-item-disabled":"")+(g?" combobox-gitem":""); +dd.push("
"); +if(opts.showItemIcon&&row.iconCls){ +dd.push(""); +} +dd.push(opts.formatter?opts.formatter.call(_be6,row):s); +dd.push("
"); +} +$(_be7).html(dd.join("")); +}}; +$.fn.combobox.defaults=$.extend({},$.fn.combo.defaults,{valueField:"value",textField:"text",groupPosition:"static",groupField:null,groupFormatter:function(_beb){ +return _beb; +},mode:"local",method:"post",url:null,data:null,queryParams:{},showItemIcon:false,limitToList:false,unselectedValues:[],mappingRows:[],view:_be5,keyHandler:{up:function(e){ +nav(this,"prev"); +e.preventDefault(); +},down:function(e){ +nav(this,"next"); +e.preventDefault(); +},left:function(e){ +},right:function(e){ +},enter:function(e){ +_bc2(this); +},query:function(q,e){ +_bba(this,q); +}},inputEvents:$.extend({},$.fn.combo.defaults.inputEvents,{blur:function(e){ +$.fn.combo.defaults.inputEvents.blur(e); +var _bec=e.data.target; +var opts=$(_bec).combobox("options"); +if(opts.reversed||opts.limitToList){ +if(opts.blurTimer){ +clearTimeout(opts.blurTimer); +} +opts.blurTimer=setTimeout(function(){ +var _bed=$(_bec).parent().length; +if(_bed){ +if(opts.reversed){ +$(_bec).combobox("setValues",$(_bec).combobox("getValues")); +}else{ +if(opts.limitToList){ +var vv=[]; +$.map($(_bec).combobox("getValues"),function(v){ +var _bee=$.easyui.indexOfArray($(_bec).combobox("getData"),opts.valueField,v); +if(_bee>=0){ +vv.push(v); +} +}); +$(_bec).combobox("setValues",vv); +} +} +opts.blurTimer=null; +} +},50); +} +}}),panelEvents:{mouseover:_bc9,mouseout:_bca,mousedown:function(e){ +e.preventDefault(); +e.stopPropagation(); +},click:_bcb,scroll:_bce},filter:function(q,row){ +var opts=$(this).combobox("options"); +return row[opts.textField].toLowerCase().indexOf(q.toLowerCase())>=0; +},formatter:function(row){ +var opts=$(this).combobox("options"); +return row[opts.textField]; +},loader:function(_bef,_bf0,_bf1){ +var opts=$(this).combobox("options"); +if(!opts.url){ +return false; +} +$.ajax({type:opts.method,url:opts.url,data:_bef,dataType:"json",success:function(data){ +_bf0(data); +},error:function(){ +_bf1.apply(this,arguments); +}}); +},loadFilter:function(data){ +return data; +},finder:{getEl:function(_bf2,_bf3){ +var _bf4=_b93(_bf2,_bf3); +var id=$.data(_bf2,"combobox").itemIdPrefix+"_"+_bf4; +return $("#"+id); +},getGroupEl:function(_bf5,_bf6){ +var _bf7=$.data(_bf5,"combobox"); +var _bf8=$.easyui.indexOfArray(_bf7.groups,"value",_bf6); +var id=_bf7.groupIdPrefix+"_"+_bf8; +return $("#"+id); +},getGroup:function(_bf9,p){ +var _bfa=$.data(_bf9,"combobox"); +var _bfb=p.attr("id").substr(_bfa.groupIdPrefix.length+1); +return _bfa.groups[parseInt(_bfb)]; +},getRow:function(_bfc,p){ +var _bfd=$.data(_bfc,"combobox"); +var _bfe=(p instanceof $)?p.attr("id").substr(_bfd.itemIdPrefix.length+1):_b93(_bfc,p); +return _bfd.data[parseInt(_bfe)]; +}},onBeforeLoad:function(_bff){ +},onLoadSuccess:function(data){ +},onLoadError:function(){ +},onSelect:function(_c00){ +},onUnselect:function(_c01){ +},onClick:function(_c02){ +}}); +})(jQuery); +(function($){ +function _c03(_c04){ +var _c05=$.data(_c04,"combotree"); +var opts=_c05.options; +var tree=_c05.tree; +$(_c04).addClass("combotree-f"); +$(_c04).combo($.extend({},opts,{onShowPanel:function(){ +if(opts.editable){ +tree.tree("doFilter",""); +} +opts.onShowPanel.call(this); +}})); +var _c06=$(_c04).combo("panel"); +if(!tree){ +tree=$("
    ").appendTo(_c06); +_c05.tree=tree; +} +tree.tree($.extend({},opts,{checkbox:opts.multiple,onLoadSuccess:function(node,data){ +var _c07=$(_c04).combotree("getValues"); +if(opts.multiple){ +$.map(tree.tree("getChecked"),function(node){ +$.easyui.addArrayItem(_c07,node.id); +}); +} +_c0c(_c04,_c07,_c05.remainText); +opts.onLoadSuccess.call(this,node,data); +},onClick:function(node){ +if(opts.multiple){ +$(this).tree(node.checked?"uncheck":"check",node.target); +}else{ +$(_c04).combo("hidePanel"); +} +_c05.remainText=false; +_c09(_c04); +opts.onClick.call(this,node); +},onCheck:function(node,_c08){ +_c05.remainText=false; +_c09(_c04); +opts.onCheck.call(this,node,_c08); +}})); +}; +function _c09(_c0a){ +var _c0b=$.data(_c0a,"combotree"); +var opts=_c0b.options; +var tree=_c0b.tree; +var vv=[]; +if(opts.multiple){ +vv=$.map(tree.tree("getChecked"),function(node){ +return node.id; +}); +}else{ +var node=tree.tree("getSelected"); +if(node){ +vv.push(node.id); +} +} +vv=vv.concat(opts.unselectedValues); +_c0c(_c0a,vv,_c0b.remainText); +}; +function _c0c(_c0d,_c0e,_c0f){ +var _c10=$.data(_c0d,"combotree"); +var opts=_c10.options; +var tree=_c10.tree; +var _c11=tree.tree("options"); +var _c12=_c11.onBeforeCheck; +var _c13=_c11.onCheck; +var _c14=_c11.onBeforeSelect; +var _c15=_c11.onSelect; +_c11.onBeforeCheck=_c11.onCheck=_c11.onBeforeSelect=_c11.onSelect=function(){ +}; +if(!$.isArray(_c0e)){ +_c0e=_c0e.split(opts.separator); +} +if(!opts.multiple){ +_c0e=_c0e.length?[_c0e[0]]:[""]; +} +var vv=$.map(_c0e,function(_c16){ +return String(_c16); +}); +tree.find("div.tree-node-selected").removeClass("tree-node-selected"); +$.map(tree.tree("getChecked"),function(node){ +if($.inArray(String(node.id),vv)==-1){ +tree.tree("uncheck",node.target); +} +}); +var ss=[]; +opts.unselectedValues=[]; +$.map(vv,function(v){ +var node=tree.tree("find",v); +if(node){ +tree.tree("check",node.target).tree("select",node.target); +ss.push(_c17(node)); +}else{ +ss.push(_c18(v,opts.mappingRows)||v); +opts.unselectedValues.push(v); +} +}); +if(opts.multiple){ +$.map(tree.tree("getChecked"),function(node){ +var id=String(node.id); +if($.inArray(id,vv)==-1){ +vv.push(id); +ss.push(_c17(node)); +} +}); +} +_c11.onBeforeCheck=_c12; +_c11.onCheck=_c13; +_c11.onBeforeSelect=_c14; +_c11.onSelect=_c15; +if(!_c0f){ +var s=ss.join(opts.separator); +if($(_c0d).combo("getText")!=s){ +$(_c0d).combo("setText",s); +} +} +$(_c0d).combo("setValues",vv); +function _c18(_c19,a){ +var item=$.easyui.getArrayItem(a,"id",_c19); +return item?_c17(item):undefined; +}; +function _c17(node){ +return node[opts.textField||""]||node.text; +}; +}; +function _c1a(_c1b,q){ +var _c1c=$.data(_c1b,"combotree"); +var opts=_c1c.options; +var tree=_c1c.tree; +_c1c.remainText=true; +tree.tree("doFilter",opts.multiple?q.split(opts.separator):q); +}; +function _c1d(_c1e){ +var _c1f=$.data(_c1e,"combotree"); +_c1f.remainText=false; +$(_c1e).combotree("setValues",$(_c1e).combotree("getValues")); +$(_c1e).combotree("hidePanel"); +}; +$.fn.combotree=function(_c20,_c21){ +if(typeof _c20=="string"){ +var _c22=$.fn.combotree.methods[_c20]; +if(_c22){ +return _c22(this,_c21); +}else{ +return this.combo(_c20,_c21); +} +} +_c20=_c20||{}; +return this.each(function(){ +var _c23=$.data(this,"combotree"); +if(_c23){ +$.extend(_c23.options,_c20); +}else{ +$.data(this,"combotree",{options:$.extend({},$.fn.combotree.defaults,$.fn.combotree.parseOptions(this),_c20)}); +} +_c03(this); +}); +}; +$.fn.combotree.methods={options:function(jq){ +var _c24=jq.combo("options"); +return $.extend($.data(jq[0],"combotree").options,{width:_c24.width,height:_c24.height,originalValue:_c24.originalValue,disabled:_c24.disabled,readonly:_c24.readonly,editable:_c24.editable}); +},clone:function(jq,_c25){ +var t=jq.combo("clone",_c25); +t.data("combotree",{options:$.extend(true,{},jq.combotree("options")),tree:jq.combotree("tree")}); +return t; +},tree:function(jq){ +return $.data(jq[0],"combotree").tree; +},loadData:function(jq,data){ +return jq.each(function(){ +var opts=$.data(this,"combotree").options; +opts.data=data; +var tree=$.data(this,"combotree").tree; +tree.tree("loadData",data); +}); +},reload:function(jq,url){ +return jq.each(function(){ +var opts=$.data(this,"combotree").options; +var tree=$.data(this,"combotree").tree; +if(url){ +opts.url=url; +} +tree.tree({url:opts.url}); +}); +},setValues:function(jq,_c26){ +return jq.each(function(){ +var opts=$(this).combotree("options"); +if($.isArray(_c26)){ +_c26=$.map(_c26,function(_c27){ +if(_c27&&typeof _c27=="object"){ +$.easyui.addArrayItem(opts.mappingRows,"id",_c27); +return _c27.id; +}else{ +return _c27; +} +}); +} +_c0c(this,_c26); +}); +},setValue:function(jq,_c28){ +return jq.each(function(){ +$(this).combotree("setValues",$.isArray(_c28)?_c28:[_c28]); +}); +},clear:function(jq){ +return jq.each(function(){ +$(this).combotree("setValues",[]); +}); +},reset:function(jq){ +return jq.each(function(){ +var opts=$(this).combotree("options"); +if(opts.multiple){ +$(this).combotree("setValues",opts.originalValue); +}else{ +$(this).combotree("setValue",opts.originalValue); +} +}); +}}; +$.fn.combotree.parseOptions=function(_c29){ +return $.extend({},$.fn.combo.parseOptions(_c29),$.fn.tree.parseOptions(_c29)); +}; +$.fn.combotree.defaults=$.extend({},$.fn.combo.defaults,$.fn.tree.defaults,{editable:false,textField:null,unselectedValues:[],mappingRows:[],keyHandler:{up:function(e){ +},down:function(e){ +},left:function(e){ +},right:function(e){ +},enter:function(e){ +_c1d(this); +},query:function(q,e){ +_c1a(this,q); +}}}); +})(jQuery); +(function($){ +function _c2a(_c2b){ +var _c2c=$.data(_c2b,"combogrid"); +var opts=_c2c.options; +var grid=_c2c.grid; +$(_c2b).addClass("combogrid-f").combo($.extend({},opts,{onShowPanel:function(){ +_c43(this,$(this).combogrid("getValues"),true); +var p=$(this).combogrid("panel"); +var _c2d=p.outerHeight()-p.height(); +var _c2e=p._size("minHeight"); +var _c2f=p._size("maxHeight"); +var dg=$(this).combogrid("grid"); +dg.datagrid("resize",{width:"100%",height:(isNaN(parseInt(opts.panelHeight))?"auto":"100%"),minHeight:(_c2e?_c2e-_c2d:""),maxHeight:(_c2f?_c2f-_c2d:"")}); +var row=dg.datagrid("getSelected"); +if(row){ +dg.datagrid("scrollTo",dg.datagrid("getRowIndex",row)); +} +opts.onShowPanel.call(this); +}})); +var _c30=$(_c2b).combo("panel"); +if(!grid){ +grid=$("
    ").appendTo(_c30); +_c2c.grid=grid; +} +grid.datagrid($.extend({},opts,{border:false,singleSelect:(!opts.multiple),onLoadSuccess:_c31,onClickRow:_c32,onSelect:_c33("onSelect"),onUnselect:_c33("onUnselect"),onSelectAll:_c33("onSelectAll"),onUnselectAll:_c33("onUnselectAll")})); +function _c34(dg){ +return $(dg).closest(".combo-panel").panel("options").comboTarget||_c2b; +}; +function _c31(data){ +var _c35=_c34(this); +var _c36=$(_c35).data("combogrid"); +var opts=_c36.options; +var _c37=$(_c35).combo("getValues"); +_c43(_c35,_c37,_c36.remainText); +opts.onLoadSuccess.call(this,data); +}; +function _c32(_c38,row){ +var _c39=_c34(this); +var _c3a=$(_c39).data("combogrid"); +var opts=_c3a.options; +_c3a.remainText=false; +_c3b.call(this); +if(!opts.multiple){ +$(_c39).combo("hidePanel"); +} +opts.onClickRow.call(this,_c38,row); +}; +function _c33(_c3c){ +return function(_c3d,row){ +var _c3e=_c34(this); +var opts=$(_c3e).combogrid("options"); +if(_c3c=="onUnselectAll"){ +if(opts.multiple){ +_c3b.call(this); +} +}else{ +_c3b.call(this); +} +opts[_c3c].call(this,_c3d,row); +}; +}; +function _c3b(){ +var dg=$(this); +var _c3f=_c34(dg); +var _c40=$(_c3f).data("combogrid"); +var opts=_c40.options; +var vv=$.map(dg.datagrid("getSelections"),function(row){ +return row[opts.idField]; +}); +vv=vv.concat(opts.unselectedValues); +var _c41=dg.data("datagrid").dc.body2; +var _c42=_c41.scrollTop(); +_c43(_c3f,vv,_c40.remainText); +_c41.scrollTop(_c42); +}; +}; +function nav(_c44,dir){ +var _c45=$.data(_c44,"combogrid"); +var opts=_c45.options; +var grid=_c45.grid; +var _c46=grid.datagrid("getRows").length; +if(!_c46){ +return; +} +var tr=opts.finder.getTr(grid[0],null,"highlight"); +if(!tr.length){ +tr=opts.finder.getTr(grid[0],null,"selected"); +} +var _c47; +if(!tr.length){ +_c47=(dir=="next"?0:_c46-1); +}else{ +var _c47=parseInt(tr.attr("datagrid-row-index")); +_c47+=(dir=="next"?1:-1); +if(_c47<0){ +_c47=_c46-1; +} +if(_c47>=_c46){ +_c47=0; +} +} +grid.datagrid("highlightRow",_c47); +if(opts.selectOnNavigation){ +_c45.remainText=false; +grid.datagrid("selectRow",_c47); +} +}; +function _c43(_c48,_c49,_c4a){ +var _c4b=$.data(_c48,"combogrid"); +var opts=_c4b.options; +var grid=_c4b.grid; +var _c4c=$(_c48).combo("getValues"); +var _c4d=$(_c48).combo("options"); +var _c4e=_c4d.onChange; +_c4d.onChange=function(){ +}; +var _c4f=grid.datagrid("options"); +var _c50=_c4f.onSelect; +var _c51=_c4f.onUnselect; +var _c52=_c4f.onUnselectAll; +_c4f.onSelect=_c4f.onUnselect=_c4f.onUnselectAll=function(){ +}; +if(!$.isArray(_c49)){ +_c49=_c49.split(opts.separator); +} +if(!opts.multiple){ +_c49=_c49.length?[_c49[0]]:[""]; +} +var vv=$.map(_c49,function(_c53){ +return String(_c53); +}); +vv=$.grep(vv,function(v,_c54){ +return _c54===$.inArray(v,vv); +}); +var _c55=$.grep(grid.datagrid("getSelections"),function(row,_c56){ +return $.inArray(String(row[opts.idField]),vv)>=0; +}); +grid.datagrid("clearSelections"); +grid.data("datagrid").selectedRows=_c55; +var ss=[]; +opts.unselectedValues=[]; +$.map(vv,function(v){ +var _c57=grid.datagrid("getRowIndex",v); +if(_c57>=0){ +grid.datagrid("selectRow",_c57); +}else{ +if($.easyui.indexOfArray(_c55,opts.idField,v)==-1){ +opts.unselectedValues.push(v); +} +} +ss.push(_c58(v,grid.datagrid("getRows"))||_c58(v,_c55)||_c58(v,opts.mappingRows)||v); +}); +$(_c48).combo("setValues",_c4c); +_c4d.onChange=_c4e; +_c4f.onSelect=_c50; +_c4f.onUnselect=_c51; +_c4f.onUnselectAll=_c52; +if(!_c4a){ +var s=ss.join(opts.separator); +if($(_c48).combo("getText")!=s){ +$(_c48).combo("setText",s); +} +} +$(_c48).combo("setValues",_c49); +function _c58(_c59,a){ +var item=$.easyui.getArrayItem(a,opts.idField,_c59); +return item?item[opts.textField]:undefined; +}; +}; +function _c5a(_c5b,q){ +var _c5c=$.data(_c5b,"combogrid"); +var opts=_c5c.options; +var grid=_c5c.grid; +_c5c.remainText=true; +var qq=opts.multiple?q.split(opts.separator):[q]; +qq=$.grep(qq,function(q){ +return $.trim(q)!=""; +}); +if(opts.mode=="remote"){ +_c5d(qq); +grid.datagrid("load",$.extend({},opts.queryParams,{q:q})); +}else{ +grid.datagrid("highlightRow",-1); +var rows=grid.datagrid("getRows"); +var vv=[]; +$.map(qq,function(q){ +q=$.trim(q); +var _c5e=q; +_c5f(opts.mappingRows,q); +_c5f(grid.datagrid("getSelections"),q); +var _c60=_c5f(rows,q); +if(_c60>=0){ +if(opts.reversed){ +grid.datagrid("highlightRow",_c60); +} +}else{ +$.map(rows,function(row,i){ +if(opts.filter.call(_c5b,q,row)){ +grid.datagrid("highlightRow",i); +} +}); +} +}); +_c5d(vv); +} +function _c5f(rows,q){ +for(var i=0;i=0){ +$.easyui.addArrayItem(vv,v); +} +}); +$(_c62).combogrid("setValues",vv); +if(!opts.multiple){ +$(_c62).combogrid("hidePanel"); +} +}; +$.fn.combogrid=function(_c65,_c66){ +if(typeof _c65=="string"){ +var _c67=$.fn.combogrid.methods[_c65]; +if(_c67){ +return _c67(this,_c66); +}else{ +return this.combo(_c65,_c66); +} +} +_c65=_c65||{}; +return this.each(function(){ +var _c68=$.data(this,"combogrid"); +if(_c68){ +$.extend(_c68.options,_c65); +}else{ +_c68=$.data(this,"combogrid",{options:$.extend({},$.fn.combogrid.defaults,$.fn.combogrid.parseOptions(this),_c65)}); +} +_c2a(this); +}); +}; +$.fn.combogrid.methods={options:function(jq){ +var _c69=jq.combo("options"); +return $.extend($.data(jq[0],"combogrid").options,{width:_c69.width,height:_c69.height,originalValue:_c69.originalValue,disabled:_c69.disabled,readonly:_c69.readonly,editable:_c69.editable}); +},cloneFrom:function(jq,from){ +return jq.each(function(){ +$(this).combo("cloneFrom",from); +$.data(this,"combogrid",{options:$.extend(true,{cloned:true},$(from).combogrid("options")),combo:$(this).next(),panel:$(from).combo("panel"),grid:$(from).combogrid("grid")}); +}); +},grid:function(jq){ +return $.data(jq[0],"combogrid").grid; +},setValues:function(jq,_c6a){ +return jq.each(function(){ +var opts=$(this).combogrid("options"); +if($.isArray(_c6a)){ +_c6a=$.map(_c6a,function(_c6b){ +if(_c6b&&typeof _c6b=="object"){ +$.easyui.addArrayItem(opts.mappingRows,opts.idField,_c6b); +return _c6b[opts.idField]; +}else{ +return _c6b; +} +}); +} +_c43(this,_c6a); +}); +},setValue:function(jq,_c6c){ +return jq.each(function(){ +$(this).combogrid("setValues",$.isArray(_c6c)?_c6c:[_c6c]); +}); +},clear:function(jq){ +return jq.each(function(){ +$(this).combogrid("setValues",[]); +}); +},reset:function(jq){ +return jq.each(function(){ +var opts=$(this).combogrid("options"); +if(opts.multiple){ +$(this).combogrid("setValues",opts.originalValue); +}else{ +$(this).combogrid("setValue",opts.originalValue); +} +}); +}}; +$.fn.combogrid.parseOptions=function(_c6d){ +var t=$(_c6d); +return $.extend({},$.fn.combo.parseOptions(_c6d),$.fn.datagrid.parseOptions(_c6d),$.parser.parseOptions(_c6d,["idField","textField","mode"])); +}; +$.fn.combogrid.defaults=$.extend({},$.fn.combo.defaults,$.fn.datagrid.defaults,{loadMsg:null,idField:null,textField:null,unselectedValues:[],mappingRows:[],mode:"local",keyHandler:{up:function(e){ +nav(this,"prev"); +e.preventDefault(); +},down:function(e){ +nav(this,"next"); +e.preventDefault(); +},left:function(e){ +},right:function(e){ +},enter:function(e){ +_c61(this); +},query:function(q,e){ +_c5a(this,q); +}},inputEvents:$.extend({},$.fn.combo.defaults.inputEvents,{blur:function(e){ +$.fn.combo.defaults.inputEvents.blur(e); +var _c6e=e.data.target; +var opts=$(_c6e).combogrid("options"); +if(opts.reversed){ +$(_c6e).combogrid("setValues",$(_c6e).combogrid("getValues")); +} +}}),panelEvents:{mousedown:function(e){ +}},filter:function(q,row){ +var opts=$(this).combogrid("options"); +return (row[opts.textField]||"").toLowerCase().indexOf(q.toLowerCase())>=0; +}}); +})(jQuery); +(function($){ +function _c6f(_c70){ +var _c71=$.data(_c70,"combotreegrid"); +var opts=_c71.options; +$(_c70).addClass("combotreegrid-f").combo($.extend({},opts,{onShowPanel:function(){ +var p=$(this).combotreegrid("panel"); +var _c72=p.outerHeight()-p.height(); +var _c73=p._size("minHeight"); +var _c74=p._size("maxHeight"); +var dg=$(this).combotreegrid("grid"); +dg.treegrid("resize",{width:"100%",height:(isNaN(parseInt(opts.panelHeight))?"auto":"100%"),minHeight:(_c73?_c73-_c72:""),maxHeight:(_c74?_c74-_c72:"")}); +var row=dg.treegrid("getSelected"); +if(row){ +dg.treegrid("scrollTo",row[opts.idField]); +} +opts.onShowPanel.call(this); +}})); +if(!_c71.grid){ +var _c75=$(_c70).combo("panel"); +_c71.grid=$("
    ").appendTo(_c75); +} +_c71.grid.treegrid($.extend({},opts,{border:false,checkbox:opts.multiple,onLoadSuccess:function(row,data){ +var _c76=$(_c70).combotreegrid("getValues"); +if(opts.multiple){ +$.map($(this).treegrid("getCheckedNodes"),function(row){ +$.easyui.addArrayItem(_c76,row[opts.idField]); +}); +} +_c7b(_c70,_c76); +opts.onLoadSuccess.call(this,row,data); +_c71.remainText=false; +},onClickRow:function(row){ +if(opts.multiple){ +$(this).treegrid(row.checked?"uncheckNode":"checkNode",row[opts.idField]); +$(this).treegrid("unselect",row[opts.idField]); +}else{ +$(_c70).combo("hidePanel"); +} +_c78(_c70); +opts.onClickRow.call(this,row); +},onCheckNode:function(row,_c77){ +_c78(_c70); +opts.onCheckNode.call(this,row,_c77); +}})); +}; +function _c78(_c79){ +var _c7a=$.data(_c79,"combotreegrid"); +var opts=_c7a.options; +var grid=_c7a.grid; +var vv=[]; +if(opts.multiple){ +vv=$.map(grid.treegrid("getCheckedNodes"),function(row){ +return row[opts.idField]; +}); +}else{ +var row=grid.treegrid("getSelected"); +if(row){ +vv.push(row[opts.idField]); +} +} +vv=vv.concat(opts.unselectedValues); +_c7b(_c79,vv); +}; +function _c7b(_c7c,_c7d){ +var _c7e=$.data(_c7c,"combotreegrid"); +var opts=_c7e.options; +var grid=_c7e.grid; +var _c7f=grid.datagrid("options"); +var _c80=_c7f.onBeforeCheck; +var _c81=_c7f.onCheck; +var _c82=_c7f.onBeforeSelect; +var _c83=_c7f.onSelect; +_c7f.onBeforeCheck=_c7f.onCheck=_c7f.onBeforeSelect=_c7f.onSelect=function(){ +}; +if(!$.isArray(_c7d)){ +_c7d=_c7d.split(opts.separator); +} +if(!opts.multiple){ +_c7d=_c7d.length?[_c7d[0]]:[""]; +} +var vv=$.map(_c7d,function(_c84){ +return String(_c84); +}); +vv=$.grep(vv,function(v,_c85){ +return _c85===$.inArray(v,vv); +}); +var _c86=grid.treegrid("getSelected"); +if(_c86){ +grid.treegrid("unselect",_c86[opts.idField]); +} +$.map(grid.treegrid("getCheckedNodes"),function(row){ +if($.inArray(String(row[opts.idField]),vv)==-1){ +grid.treegrid("uncheckNode",row[opts.idField]); +} +}); +var ss=[]; +opts.unselectedValues=[]; +$.map(vv,function(v){ +var row=grid.treegrid("find",v); +if(row){ +if(opts.multiple){ +grid.treegrid("checkNode",v); +}else{ +grid.treegrid("select",v); +} +ss.push(_c87(row)); +}else{ +ss.push(_c88(v,opts.mappingRows)||v); +opts.unselectedValues.push(v); +} +}); +if(opts.multiple){ +$.map(grid.treegrid("getCheckedNodes"),function(row){ +var id=String(row[opts.idField]); +if($.inArray(id,vv)==-1){ +vv.push(id); +ss.push(_c87(row)); +} +}); +} +_c7f.onBeforeCheck=_c80; +_c7f.onCheck=_c81; +_c7f.onBeforeSelect=_c82; +_c7f.onSelect=_c83; +if(!_c7e.remainText){ +var s=ss.join(opts.separator); +if($(_c7c).combo("getText")!=s){ +$(_c7c).combo("setText",s); +} +} +$(_c7c).combo("setValues",vv); +function _c88(_c89,a){ +var item=$.easyui.getArrayItem(a,opts.idField,_c89); +return item?_c87(item):undefined; +}; +function _c87(row){ +return row[opts.textField||""]||row[opts.treeField]; +}; +}; +function _c8a(_c8b,q){ +var _c8c=$.data(_c8b,"combotreegrid"); +var opts=_c8c.options; +var grid=_c8c.grid; +_c8c.remainText=true; +var qq=opts.multiple?q.split(opts.separator):[q]; +qq=$.grep(qq,function(q){ +return $.trim(q)!=""; +}); +grid.treegrid("clearSelections").treegrid("clearChecked").treegrid("highlightRow",-1); +if(opts.mode=="remote"){ +_c8d(qq); +grid.treegrid("load",$.extend({},opts.queryParams,{q:q})); +}else{ +if(q){ +var data=grid.treegrid("getData"); +var vv=[]; +$.map(qq,function(q){ +q=$.trim(q); +if(q){ +var v=undefined; +$.easyui.forEach(data,true,function(row){ +if(q.toLowerCase()==String(row[opts.treeField]).toLowerCase()){ +v=row[opts.idField]; +return false; +}else{ +if(opts.filter.call(_c8b,q,row)){ +grid.treegrid("expandTo",row[opts.idField]); +grid.treegrid("highlightRow",row[opts.idField]); +return false; +} +} +}); +if(v==undefined){ +$.easyui.forEach(opts.mappingRows,false,function(row){ +if(q.toLowerCase()==String(row[opts.treeField])){ +v=row[opts.idField]; +return false; +} +}); +} +if(v!=undefined){ +vv.push(v); +}else{ +vv.push(q); +} +} +}); +_c8d(vv); +_c8c.remainText=false; +} +} +function _c8d(vv){ +if(!opts.reversed){ +$(_c8b).combotreegrid("setValues",vv); +} +}; +}; +function _c8e(_c8f){ +var _c90=$.data(_c8f,"combotreegrid"); +var opts=_c90.options; +var grid=_c90.grid; +var tr=opts.finder.getTr(grid[0],null,"highlight"); +_c90.remainText=false; +if(tr.length){ +var id=tr.attr("node-id"); +if(opts.multiple){ +if(tr.hasClass("datagrid-row-selected")){ +grid.treegrid("uncheckNode",id); +}else{ +grid.treegrid("checkNode",id); +} +}else{ +grid.treegrid("selectRow",id); +} +} +var vv=[]; +if(opts.multiple){ +$.map(grid.treegrid("getCheckedNodes"),function(row){ +vv.push(row[opts.idField]); +}); +}else{ +var row=grid.treegrid("getSelected"); +if(row){ +vv.push(row[opts.idField]); +} +} +$.map(opts.unselectedValues,function(v){ +if($.easyui.indexOfArray(opts.mappingRows,opts.idField,v)>=0){ +$.easyui.addArrayItem(vv,v); +} +}); +$(_c8f).combotreegrid("setValues",vv); +if(!opts.multiple){ +$(_c8f).combotreegrid("hidePanel"); +} +}; +$.fn.combotreegrid=function(_c91,_c92){ +if(typeof _c91=="string"){ +var _c93=$.fn.combotreegrid.methods[_c91]; +if(_c93){ +return _c93(this,_c92); +}else{ +return this.combo(_c91,_c92); +} +} +_c91=_c91||{}; +return this.each(function(){ +var _c94=$.data(this,"combotreegrid"); +if(_c94){ +$.extend(_c94.options,_c91); +}else{ +_c94=$.data(this,"combotreegrid",{options:$.extend({},$.fn.combotreegrid.defaults,$.fn.combotreegrid.parseOptions(this),_c91)}); +} +_c6f(this); +}); +}; +$.fn.combotreegrid.methods={options:function(jq){ +var _c95=jq.combo("options"); +return $.extend($.data(jq[0],"combotreegrid").options,{width:_c95.width,height:_c95.height,originalValue:_c95.originalValue,disabled:_c95.disabled,readonly:_c95.readonly,editable:_c95.editable}); +},grid:function(jq){ +return $.data(jq[0],"combotreegrid").grid; +},setValues:function(jq,_c96){ +return jq.each(function(){ +var opts=$(this).combotreegrid("options"); +if($.isArray(_c96)){ +_c96=$.map(_c96,function(_c97){ +if(_c97&&typeof _c97=="object"){ +$.easyui.addArrayItem(opts.mappingRows,opts.idField,_c97); +return _c97[opts.idField]; +}else{ +return _c97; +} +}); +} +_c7b(this,_c96); +}); +},setValue:function(jq,_c98){ +return jq.each(function(){ +$(this).combotreegrid("setValues",$.isArray(_c98)?_c98:[_c98]); +}); +},clear:function(jq){ +return jq.each(function(){ +$(this).combotreegrid("setValues",[]); +}); +},reset:function(jq){ +return jq.each(function(){ +var opts=$(this).combotreegrid("options"); +if(opts.multiple){ +$(this).combotreegrid("setValues",opts.originalValue); +}else{ +$(this).combotreegrid("setValue",opts.originalValue); +} +}); +}}; +$.fn.combotreegrid.parseOptions=function(_c99){ +var t=$(_c99); +return $.extend({},$.fn.combo.parseOptions(_c99),$.fn.treegrid.parseOptions(_c99),$.parser.parseOptions(_c99,["mode",{limitToGrid:"boolean"}])); +}; +$.fn.combotreegrid.defaults=$.extend({},$.fn.combo.defaults,$.fn.treegrid.defaults,{editable:false,singleSelect:true,limitToGrid:false,unselectedValues:[],mappingRows:[],mode:"local",textField:null,keyHandler:{up:function(e){ +},down:function(e){ +},left:function(e){ +},right:function(e){ +},enter:function(e){ +_c8e(this); +},query:function(q,e){ +_c8a(this,q); +}},inputEvents:$.extend({},$.fn.combo.defaults.inputEvents,{blur:function(e){ +$.fn.combo.defaults.inputEvents.blur(e); +var _c9a=e.data.target; +var opts=$(_c9a).combotreegrid("options"); +if(opts.limitToGrid){ +_c8e(_c9a); +} +}}),filter:function(q,row){ +var opts=$(this).combotreegrid("options"); +return (row[opts.treeField]||"").toLowerCase().indexOf(q.toLowerCase())>=0; +}}); +})(jQuery); +(function($){ +function _c9b(_c9c){ +var _c9d=$.data(_c9c,"tagbox"); +var opts=_c9d.options; +$(_c9c).addClass("tagbox-f").combobox($.extend({},opts,{cls:"tagbox",reversed:true,onChange:function(_c9e,_c9f){ +_ca0(); +$(this).combobox("hidePanel"); +opts.onChange.call(_c9c,_c9e,_c9f); +},onResizing:function(_ca1,_ca2){ +var _ca3=$(this).combobox("textbox"); +var tb=$(this).data("textbox").textbox; +var _ca4=tb.outerWidth(); +tb.css({height:"",paddingLeft:_ca3.css("marginLeft"),paddingRight:_ca3.css("marginRight")}); +_ca3.css("margin",0); +tb._outerWidth(_ca4); +_cb7(_c9c); +_ca9(this); +opts.onResizing.call(_c9c,_ca1,_ca2); +},onLoadSuccess:function(data){ +_ca0(); +opts.onLoadSuccess.call(_c9c,data); +}})); +_ca0(); +_cb7(_c9c); +function _ca0(){ +$(_c9c).next().find(".tagbox-label").remove(); +var _ca5=$(_c9c).tagbox("textbox"); +var ss=[]; +$.map($(_c9c).tagbox("getValues"),function(_ca6,_ca7){ +var row=opts.finder.getRow(_c9c,_ca6); +var text=opts.tagFormatter.call(_c9c,_ca6,row); +var cs={}; +var css=opts.tagStyler.call(_c9c,_ca6,row)||""; +if(typeof css=="string"){ +cs={s:css}; +}else{ +cs={c:css["class"]||"",s:css["style"]||""}; +} +var _ca8=$("").insertBefore(_ca5).html(text); +_ca8.attr("tagbox-index",_ca7); +_ca8.attr("style",cs.s).addClass(cs.c); +$("").appendTo(_ca8); +}); +_ca9(_c9c); +$(_c9c).combobox("setText",""); +}; +}; +function _ca9(_caa,_cab){ +var span=$(_caa).next(); +var _cac=_cab?$(_cab):span.find(".tagbox-label"); +if(_cac.length){ +var _cad=$(_caa).tagbox("textbox"); +var _cae=$(_cac[0]); +var _caf=_cae.outerHeight(true)-_cae.outerHeight(); +var _cb0=_cad.outerHeight()-_caf*2; +_cac.css({height:_cb0+"px",lineHeight:_cb0+"px"}); +var _cb1=span.find(".textbox-addon").css("height","100%"); +_cb1.find(".textbox-icon").css("height","100%"); +span.find(".textbox-button").linkbutton("resize",{height:"100%"}); +} +}; +function _cb2(_cb3){ +var span=$(_cb3).next(); +span._unbind(".tagbox")._bind("click.tagbox",function(e){ +var opts=$(_cb3).tagbox("options"); +if(opts.disabled||opts.readonly){ +return; +} +if($(e.target).hasClass("tagbox-remove")){ +var _cb4=parseInt($(e.target).parent().attr("tagbox-index")); +var _cb5=$(_cb3).tagbox("getValues"); +if(opts.onBeforeRemoveTag.call(_cb3,_cb5[_cb4])==false){ +return; +} +opts.onRemoveTag.call(_cb3,_cb5[_cb4]); +_cb5.splice(_cb4,1); +$(_cb3).tagbox("setValues",_cb5); +}else{ +var _cb6=$(e.target).closest(".tagbox-label"); +if(_cb6.length){ +var _cb4=parseInt(_cb6.attr("tagbox-index")); +var _cb5=$(_cb3).tagbox("getValues"); +opts.onClickTag.call(_cb3,_cb5[_cb4]); +} +} +$(this).find(".textbox-text").focus(); +})._bind("keyup.tagbox",function(e){ +_cb7(_cb3); +})._bind("mouseover.tagbox",function(e){ +if($(e.target).closest(".textbox-button,.textbox-addon,.tagbox-label").length){ +$(this).triggerHandler("mouseleave"); +}else{ +$(this).find(".textbox-text").triggerHandler("mouseenter"); +} +})._bind("mouseleave.tagbox",function(e){ +$(this).find(".textbox-text").triggerHandler("mouseleave"); +}); +}; +function _cb7(_cb8){ +var opts=$(_cb8).tagbox("options"); +var _cb9=$(_cb8).tagbox("textbox"); +var span=$(_cb8).next(); +var tmp=$("").appendTo("body"); +tmp.attr("style",_cb9.attr("style")); +tmp.css({position:"absolute",top:-9999,left:-9999,width:"auto",fontFamily:_cb9.css("fontFamily"),fontSize:_cb9.css("fontSize"),fontWeight:_cb9.css("fontWeight"),whiteSpace:"nowrap"}); +var _cba=_cbb(_cb9.val()); +var _cbc=_cbb(opts.prompt||""); +tmp.remove(); +var _cbd=Math.min(Math.max(_cba,_cbc)+20,span.width()); +_cb9._outerWidth(_cbd); +span.find(".textbox-button").linkbutton("resize",{height:"100%"}); +function _cbb(val){ +var s=val.replace(/&/g,"&").replace(/\s/g," ").replace(//g,">"); +tmp.html(s); +return tmp.outerWidth(); +}; +}; +function _cbe(_cbf){ +var t=$(_cbf); +var opts=t.tagbox("options"); +if(opts.limitToList){ +var _cc0=t.tagbox("panel"); +var item=_cc0.children("div.combobox-item-hover"); +if(item.length){ +item.removeClass("combobox-item-hover"); +var row=opts.finder.getRow(_cbf,item); +var _cc1=row[opts.valueField]; +$(_cbf).tagbox(item.hasClass("combobox-item-selected")?"unselect":"select",_cc1); +} +$(_cbf).tagbox("hidePanel"); +}else{ +var v=$.trim($(_cbf).tagbox("getText")); +if(v!==""){ +var _cc2=$(_cbf).tagbox("getValues"); +_cc2.push(v); +$(_cbf).tagbox("setValues",_cc2); +} +} +}; +function _cc3(_cc4,_cc5){ +$(_cc4).combobox("setText",""); +_cb7(_cc4); +$(_cc4).combobox("setValues",_cc5); +$(_cc4).combobox("setText",""); +$(_cc4).tagbox("validate"); +}; +$.fn.tagbox=function(_cc6,_cc7){ +if(typeof _cc6=="string"){ +var _cc8=$.fn.tagbox.methods[_cc6]; +if(_cc8){ +return _cc8(this,_cc7); +}else{ +return this.combobox(_cc6,_cc7); +} +} +_cc6=_cc6||{}; +return this.each(function(){ +var _cc9=$.data(this,"tagbox"); +if(_cc9){ +$.extend(_cc9.options,_cc6); +}else{ +$.data(this,"tagbox",{options:$.extend({},$.fn.tagbox.defaults,$.fn.tagbox.parseOptions(this),_cc6)}); +} +_c9b(this); +_cb2(this); +}); +}; +$.fn.tagbox.methods={options:function(jq){ +var _cca=jq.combobox("options"); +return $.extend($.data(jq[0],"tagbox").options,{width:_cca.width,height:_cca.height,originalValue:_cca.originalValue,disabled:_cca.disabled,readonly:_cca.readonly}); +},setValues:function(jq,_ccb){ +return jq.each(function(){ +_cc3(this,_ccb); +}); +},reset:function(jq){ +return jq.each(function(){ +$(this).combobox("reset").combobox("setText",""); +}); +}}; +$.fn.tagbox.parseOptions=function(_ccc){ +return $.extend({},$.fn.combobox.parseOptions(_ccc),$.parser.parseOptions(_ccc,[])); +}; +$.fn.tagbox.defaults=$.extend({},$.fn.combobox.defaults,{hasDownArrow:false,multiple:true,reversed:true,selectOnNavigation:false,tipOptions:$.extend({},$.fn.textbox.defaults.tipOptions,{showDelay:200}),val:function(_ccd){ +var vv=$(_ccd).parent().prev().tagbox("getValues"); +if($(_ccd).is(":focus")){ +vv.push($(_ccd).val()); +} +return vv.join(","); +},inputEvents:$.extend({},$.fn.combo.defaults.inputEvents,{blur:function(e){ +var _cce=e.data.target; +var opts=$(_cce).tagbox("options"); +if(opts.limitToList){ +_cbe(_cce); +} +}}),keyHandler:$.extend({},$.fn.combobox.defaults.keyHandler,{enter:function(e){ +_cbe(this); +},query:function(q,e){ +var opts=$(this).tagbox("options"); +if(opts.limitToList){ +$.fn.combobox.defaults.keyHandler.query.call(this,q,e); +}else{ +$(this).combobox("hidePanel"); +} +}}),tagFormatter:function(_ccf,row){ +var opts=$(this).tagbox("options"); +return row?row[opts.textField]:_ccf; +},tagStyler:function(_cd0,row){ +return ""; +},onClickTag:function(_cd1){ +},onBeforeRemoveTag:function(_cd2){ +},onRemoveTag:function(_cd3){ +}}); +})(jQuery); +(function($){ +function _cd4(_cd5){ +var _cd6=$.data(_cd5,"datebox"); +var opts=_cd6.options; +$(_cd5).addClass("datebox-f").combo($.extend({},opts,{onShowPanel:function(){ +_cd7(this); +_cd8(this); +_cd9(this); +_ce7(this,$(this).datebox("getText"),true); +opts.onShowPanel.call(this); +}})); +if(!_cd6.calendar){ +var _cda=$(_cd5).combo("panel").css("overflow","hidden"); +_cda.panel("options").onBeforeDestroy=function(){ +var c=$(this).find(".calendar-shared"); +if(c.length){ +c.insertBefore(c[0].pholder); +} +}; +var cc=$("
    ").prependTo(_cda); +if(opts.sharedCalendar){ +var c=$(opts.sharedCalendar); +if(!c[0].pholder){ +c[0].pholder=$("
    ").insertAfter(c); +} +c.addClass("calendar-shared").appendTo(cc); +if(!c.hasClass("calendar")){ +c.calendar(); +} +_cd6.calendar=c; +}else{ +_cd6.calendar=$("
    ").appendTo(cc).calendar(); +} +$.extend(_cd6.calendar.calendar("options"),{fit:true,border:false,onSelect:function(date){ +var _cdb=this.target; +var opts=$(_cdb).datebox("options"); +opts.onSelect.call(_cdb,date); +_ce7(_cdb,opts.formatter.call(_cdb,date)); +$(_cdb).combo("hidePanel"); +}}); +} +$(_cd5).combo("textbox").parent().addClass("datebox"); +$(_cd5).datebox("initValue",opts.value); +function _cd7(_cdc){ +var opts=$(_cdc).datebox("options"); +var _cdd=$(_cdc).combo("panel"); +_cdd._unbind(".datebox")._bind("click.datebox",function(e){ +if($(e.target).hasClass("datebox-button-a")){ +var _cde=parseInt($(e.target).attr("datebox-button-index")); +opts.buttons[_cde].handler.call(e.target,_cdc); +} +}); +}; +function _cd8(_cdf){ +var _ce0=$(_cdf).combo("panel"); +if(_ce0.children("div.datebox-button").length){ +return; +} +var _ce1=$("
    ").appendTo(_ce0); +var tr=_ce1.find("tr"); +for(var i=0;i").appendTo(tr); +var btn=opts.buttons[i]; +var t=$("").html($.isFunction(btn.text)?btn.text(_cdf):btn.text).appendTo(td); +t.attr("datebox-button-index",i); +} +tr.find("td").css("width",(100/opts.buttons.length)+"%"); +}; +function _cd9(_ce2){ +var _ce3=$(_ce2).combo("panel"); +var cc=_ce3.children("div.datebox-calendar-inner"); +_ce3.children()._outerWidth(_ce3.width()); +_cd6.calendar.appendTo(cc); +_cd6.calendar[0].target=_ce2; +if(opts.panelHeight!="auto"){ +var _ce4=_ce3.height(); +_ce3.children().not(cc).each(function(){ +_ce4-=$(this).outerHeight(); +}); +cc._outerHeight(_ce4); +} +_cd6.calendar.calendar("resize"); +}; +}; +function _ce5(_ce6,q){ +_ce7(_ce6,q,true); +}; +function _ce8(_ce9){ +var _cea=$.data(_ce9,"datebox"); +var opts=_cea.options; +var _ceb=_cea.calendar.calendar("options").current; +if(_ceb){ +_ce7(_ce9,opts.formatter.call(_ce9,_ceb)); +$(_ce9).combo("hidePanel"); +} +}; +function _ce7(_cec,_ced,_cee){ +var _cef=$.data(_cec,"datebox"); +var opts=_cef.options; +var _cf0=_cef.calendar; +_cf0.calendar("moveTo",opts.parser.call(_cec,_ced)); +if(_cee){ +$(_cec).combo("setValue",_ced); +}else{ +if(_ced){ +_ced=opts.formatter.call(_cec,_cf0.calendar("options").current); +} +$(_cec).combo("setText",_ced).combo("setValue",_ced); +} +}; +$.fn.datebox=function(_cf1,_cf2){ +if(typeof _cf1=="string"){ +var _cf3=$.fn.datebox.methods[_cf1]; +if(_cf3){ +return _cf3(this,_cf2); +}else{ +return this.combo(_cf1,_cf2); +} +} +_cf1=_cf1||{}; +return this.each(function(){ +var _cf4=$.data(this,"datebox"); +if(_cf4){ +$.extend(_cf4.options,_cf1); +}else{ +$.data(this,"datebox",{options:$.extend({},$.fn.datebox.defaults,$.fn.datebox.parseOptions(this),_cf1)}); +} +_cd4(this); +}); +}; +$.fn.datebox.methods={options:function(jq){ +var _cf5=jq.combo("options"); +return $.extend($.data(jq[0],"datebox").options,{width:_cf5.width,height:_cf5.height,originalValue:_cf5.originalValue,disabled:_cf5.disabled,readonly:_cf5.readonly}); +},cloneFrom:function(jq,from){ +return jq.each(function(){ +$(this).combo("cloneFrom",from); +$.data(this,"datebox",{options:$.extend(true,{},$(from).datebox("options")),calendar:$(from).datebox("calendar")}); +$(this).addClass("datebox-f"); +}); +},calendar:function(jq){ +return $.data(jq[0],"datebox").calendar; +},initValue:function(jq,_cf6){ +return jq.each(function(){ +var opts=$(this).datebox("options"); +if(_cf6){ +var date=opts.parser.call(this,_cf6); +_cf6=opts.formatter.call(this,date); +$(this).datebox("calendar").calendar("moveTo",date); +} +$(this).combo("initValue",_cf6).combo("setText",_cf6); +}); +},setValue:function(jq,_cf7){ +return jq.each(function(){ +_ce7(this,_cf7); +}); +},reset:function(jq){ +return jq.each(function(){ +var opts=$(this).datebox("options"); +$(this).datebox("setValue",opts.originalValue); +}); +},setDate:function(jq,date){ +return jq.each(function(){ +var opts=$(this).datebox("options"); +$(this).datebox("calendar").calendar("moveTo",date); +_ce7(this,date?opts.formatter.call(this,date):""); +}); +},getDate:function(jq){ +if(jq.datebox("getValue")){ +return jq.datebox("calendar").calendar("options").current; +}else{ +return null; +} +}}; +$.fn.datebox.parseOptions=function(_cf8){ +return $.extend({},$.fn.combo.parseOptions(_cf8),$.parser.parseOptions(_cf8,["sharedCalendar"])); +}; +$.fn.datebox.defaults=$.extend({},$.fn.combo.defaults,{panelWidth:250,panelHeight:"auto",sharedCalendar:null,keyHandler:{up:function(e){ +},down:function(e){ +},left:function(e){ +},right:function(e){ +},enter:function(e){ +_ce8(this); +},query:function(q,e){ +_ce5(this,q); +}},currentText:"Today",closeText:"Close",okText:"Ok",buttons:[{text:function(_cf9){ +return $(_cf9).datebox("options").currentText; +},handler:function(_cfa){ +var opts=$(_cfa).datebox("options"); +var now=new Date(); +var _cfb=new Date(now.getFullYear(),now.getMonth(),now.getDate()); +$(_cfa).datebox("calendar").calendar({year:_cfb.getFullYear(),month:_cfb.getMonth()+1,current:_cfb}); +opts.onSelect.call(_cfa,_cfb); +_ce8(_cfa); +}},{text:function(_cfc){ +return $(_cfc).datebox("options").closeText; +},handler:function(_cfd){ +$(this).closest("div.combo-panel").panel("close"); +}}],formatter:function(date){ +var y=date.getFullYear(); +var m=date.getMonth()+1; +var d=date.getDate(); +return (m<10?("0"+m):m)+"/"+(d<10?("0"+d):d)+"/"+y; +},parser:function(s){ +var _cfe=$.fn.calendar.defaults.Date; +if($(this).data("datebox")){ +_cfe=$(this).datebox("calendar").calendar("options").Date; +} +if(!s){ +return new _cfe(); +} +var ss=s.split("/"); +var m=parseInt(ss[0],10); +var d=parseInt(ss[1],10); +var y=parseInt(ss[2],10); +if(!isNaN(y)&&!isNaN(m)&&!isNaN(d)){ +return new _cfe(y,m-1,d); +}else{ +return new _cfe(); +} +},onSelect:function(date){ +}}); +})(jQuery); +(function($){ +function _cff(_d00){ +var _d01=$.data(_d00,"datetimebox"); +var opts=_d01.options; +$(_d00).datebox($.extend({},opts,{onShowPanel:function(){ +var _d02=$(this).datetimebox("getValue"); +_d08(this,_d02,true); +opts.onShowPanel.call(this); +},formatter:$.fn.datebox.defaults.formatter,parser:$.fn.datebox.defaults.parser})); +$(_d00).removeClass("datebox-f").addClass("datetimebox-f"); +$(_d00).datebox("calendar").calendar({onSelect:function(date){ +opts.onSelect.call(this.target,date); +}}); +if(!_d01.spinner){ +var _d03=$(_d00).datebox("panel"); +var p=$("
    ").insertAfter(_d03.children("div.datebox-calendar-inner")); +_d01.spinner=p.children("input"); +} +_d01.spinner.timespinner({width:opts.spinnerWidth,showSeconds:opts.showSeconds,separator:opts.timeSeparator,hour12:opts.hour12}); +$(_d00).datetimebox("initValue",opts.value); +}; +function _d04(_d05){ +var c=$(_d05).datetimebox("calendar"); +var t=$(_d05).datetimebox("spinner"); +var date=c.calendar("options").current; +return new Date(date.getFullYear(),date.getMonth(),date.getDate(),t.timespinner("getHours"),t.timespinner("getMinutes"),t.timespinner("getSeconds")); +}; +function _d06(_d07,q){ +_d08(_d07,q,true); +}; +function _d09(_d0a){ +var opts=$.data(_d0a,"datetimebox").options; +var date=_d04(_d0a); +_d08(_d0a,opts.formatter.call(_d0a,date)); +$(_d0a).combo("hidePanel"); +}; +function _d08(_d0b,_d0c,_d0d){ +var opts=$.data(_d0b,"datetimebox").options; +$(_d0b).combo("setValue",_d0c); +if(!_d0d){ +if(_d0c){ +var date=opts.parser.call(_d0b,_d0c); +$(_d0b).combo("setText",opts.formatter.call(_d0b,date)); +$(_d0b).combo("setValue",opts.formatter.call(_d0b,date)); +}else{ +$(_d0b).combo("setText",_d0c); +} +} +var date=opts.parser.call(_d0b,_d0c); +$(_d0b).datetimebox("calendar").calendar("moveTo",date); +$(_d0b).datetimebox("spinner").timespinner("setValue",_d0e(date)); +function _d0e(date){ +function _d0f(_d10){ +return (_d10<10?"0":"")+_d10; +}; +var tt=[_d0f(date.getHours()),_d0f(date.getMinutes())]; +if(opts.showSeconds){ +tt.push(_d0f(date.getSeconds())); +} +return tt.join($(_d0b).datetimebox("spinner").timespinner("options").separator); +}; +}; +$.fn.datetimebox=function(_d11,_d12){ +if(typeof _d11=="string"){ +var _d13=$.fn.datetimebox.methods[_d11]; +if(_d13){ +return _d13(this,_d12); +}else{ +return this.datebox(_d11,_d12); +} +} +_d11=_d11||{}; +return this.each(function(){ +var _d14=$.data(this,"datetimebox"); +if(_d14){ +$.extend(_d14.options,_d11); +}else{ +$.data(this,"datetimebox",{options:$.extend({},$.fn.datetimebox.defaults,$.fn.datetimebox.parseOptions(this),_d11)}); +} +_cff(this); +}); +}; +$.fn.datetimebox.methods={options:function(jq){ +var _d15=jq.datebox("options"); +return $.extend($.data(jq[0],"datetimebox").options,{originalValue:_d15.originalValue,disabled:_d15.disabled,readonly:_d15.readonly}); +},cloneFrom:function(jq,from){ +return jq.each(function(){ +$(this).datebox("cloneFrom",from); +$.data(this,"datetimebox",{options:$.extend(true,{},$(from).datetimebox("options")),spinner:$(from).datetimebox("spinner")}); +$(this).removeClass("datebox-f").addClass("datetimebox-f"); +}); +},spinner:function(jq){ +return $.data(jq[0],"datetimebox").spinner; +},initValue:function(jq,_d16){ +return jq.each(function(){ +var opts=$(this).datetimebox("options"); +var _d17=opts.value; +if(_d17){ +var date=opts.parser.call(this,_d17); +_d17=opts.formatter.call(this,date); +$(this).datetimebox("calendar").calendar("moveTo",date); +} +$(this).combo("initValue",_d17).combo("setText",_d17); +}); +},setValue:function(jq,_d18){ +return jq.each(function(){ +_d08(this,_d18); +}); +},reset:function(jq){ +return jq.each(function(){ +var opts=$(this).datetimebox("options"); +$(this).datetimebox("setValue",opts.originalValue); +}); +},setDate:function(jq,date){ +return jq.each(function(){ +var opts=$(this).datetimebox("options"); +$(this).datetimebox("calendar").calendar("moveTo",date); +_d08(this,date?opts.formatter.call(this,date):""); +}); +},getDate:function(jq){ +if(jq.datetimebox("getValue")){ +return jq.datetimebox("calendar").calendar("options").current; +}else{ +return null; +} +}}; +$.fn.datetimebox.parseOptions=function(_d19){ +var t=$(_d19); +return $.extend({},$.fn.datebox.parseOptions(_d19),$.parser.parseOptions(_d19,["timeSeparator","spinnerWidth",{showSeconds:"boolean"}])); +}; +$.fn.datetimebox.defaults=$.extend({},$.fn.datebox.defaults,{spinnerWidth:"100%",showSeconds:true,timeSeparator:":",hour12:false,panelEvents:{mousedown:function(e){ +}},keyHandler:{up:function(e){ +},down:function(e){ +},left:function(e){ +},right:function(e){ +},enter:function(e){ +_d09(this); +},query:function(q,e){ +_d06(this,q); +}},buttons:[{text:function(_d1a){ +return $(_d1a).datetimebox("options").currentText; +},handler:function(_d1b){ +var opts=$(_d1b).datetimebox("options"); +_d08(_d1b,opts.formatter.call(_d1b,new Date())); +$(_d1b).datetimebox("hidePanel"); +}},{text:function(_d1c){ +return $(_d1c).datetimebox("options").okText; +},handler:function(_d1d){ +_d09(_d1d); +}},{text:function(_d1e){ +return $(_d1e).datetimebox("options").closeText; +},handler:function(_d1f){ +$(_d1f).datetimebox("hidePanel"); +}}],formatter:function(date){ +if(!date){ +return ""; +} +return $.fn.datebox.defaults.formatter.call(this,date)+" "+$.fn.timespinner.defaults.formatter.call($(this).datetimebox("spinner")[0],date); +},parser:function(s){ +s=$.trim(s); +if(!s){ +return new Date(); +} +var dt=s.split(" "); +var _d20=$.fn.datebox.defaults.parser.call(this,dt[0]); +if(dt.length<2){ +return _d20; +} +var _d21=$.fn.timespinner.defaults.parser.call($(this).datetimebox("spinner")[0],dt[1]+(dt[2]?" "+dt[2]:"")); +return new Date(_d20.getFullYear(),_d20.getMonth(),_d20.getDate(),_d21.getHours(),_d21.getMinutes(),_d21.getSeconds()); +}}); +})(jQuery); +(function($){ +function _d22(_d23){ +var _d24=$.data(_d23,"timepicker"); +var opts=_d24.options; +$(_d23).addClass("timepicker-f").combo($.extend({},opts,{onShowPanel:function(){ +_d25(this); +_d26(_d23); +_d30(_d23,$(_d23).timepicker("getValue")); +}})); +$(_d23).timepicker("initValue",opts.value); +function _d25(_d27){ +var opts=$(_d27).timepicker("options"); +var _d28=$(_d27).combo("panel"); +_d28._unbind(".timepicker")._bind("click.timepicker",function(e){ +if($(e.target).hasClass("datebox-button-a")){ +var _d29=parseInt($(e.target).attr("datebox-button-index")); +opts.buttons[_d29].handler.call(e.target,_d27); +} +}); +}; +function _d26(_d2a){ +var _d2b=$(_d2a).combo("panel"); +if(_d2b.children("div.datebox-button").length){ +return; +} +var _d2c=$("
    ").appendTo(_d2b); +var tr=_d2c.find("tr"); +for(var i=0;i").appendTo(tr); +var btn=opts.buttons[i]; +var t=$("").html($.isFunction(btn.text)?btn.text(_d2a):btn.text).appendTo(td); +t.attr("datebox-button-index",i); +} +tr.find("td").css("width",(100/opts.buttons.length)+"%"); +}; +}; +function _d2d(_d2e,_d2f){ +var opts=$(_d2e).data("timepicker").options; +_d30(_d2e,_d2f); +opts.value=_d31(_d2e); +$(_d2e).combo("setValue",opts.value).combo("setText",opts.value); +}; +function _d30(_d32,_d33){ +var opts=$(_d32).data("timepicker").options; +if(_d33){ +var _d34=_d33.split(" "); +var hm=_d34[0].split(":"); +opts.selectingHour=parseInt(hm[0],10); +opts.selectingMinute=parseInt(hm[1],10); +opts.selectingAmpm=_d34[1]; +}else{ +opts.selectingHour=12; +opts.selectingMinute=0; +opts.selectingAmpm=opts.ampm[0]; +} +_d35(_d32); +}; +function _d31(_d36){ +var opts=$(_d36).data("timepicker").options; +var h=opts.selectingHour; +var m=opts.selectingMinute; +var ampm=opts.selectingAmpm; +if(!ampm){ +ampm=opts.ampm[0]; +} +var v=(h<10?"0"+h:h)+":"+(m<10?"0"+m:m); +if(!opts.hour24){ +v+=" "+ampm; +} +return v; +}; +function _d35(_d37){ +var opts=$(_d37).data("timepicker").options; +var _d38=$(_d37).combo("panel"); +var _d39=_d38.children(".timepicker-panel"); +if(!_d39.length){ +var _d39=$("
    ").prependTo(_d38); +} +_d39.empty(); +if(opts.panelHeight!="auto"){ +var _d3a=_d38.height()-_d38.find(".datebox-button").outerHeight(); +_d39._outerHeight(_d3a); +} +_d3b(_d37); +_d3c(_d37); +_d39.off(".timepicker"); +_d39.on("click.timepicker",".title-hour",function(e){ +opts.selectingType="hour"; +_d35(_d37); +}).on("click.timepicker",".title-minute",function(e){ +opts.selectingType="minute"; +_d35(_d37); +}).on("click.timepicker",".title-am",function(e){ +opts.selectingAmpm=opts.ampm[0]; +_d35(_d37); +}).on("click.timepicker",".title-pm",function(e){ +opts.selectingAmpm=opts.ampm[1]; +_d35(_d37); +}).on("click.timepicker",".item",function(e){ +var _d3d=parseInt($(this).text(),10); +if(opts.selectingType=="hour"){ +opts.selectingHour=_d3d; +}else{ +opts.selectingMinute=_d3d; +} +_d35(_d37); +}); +}; +function _d3b(_d3e){ +var opts=$(_d3e).data("timepicker").options; +var _d3f=$(_d3e).combo("panel"); +var _d40=_d3f.find(".timepicker-panel"); +var hour=opts.selectingHour; +var _d41=opts.selectingMinute; +$("
    "+"
    "+(hour<10?"0"+hour:hour)+"
    "+"
    :
    "+"
    "+(_d41<10?"0"+_d41:_d41)+"
    "+"
    "+"
    "+opts.ampm[0]+"
    "+"
    "+opts.ampm[1]+"
    "+"
    "+"
    ").appendTo(_d40); +var _d42=_d40.find(".panel-header"); +if(opts.selectingType=="hour"){ +_d42.find(".title-hour").addClass("title-selected"); +}else{ +_d42.find(".title-minute").addClass("title-selected"); +} +if(opts.selectingAmpm==opts.ampm[0]){ +_d42.find(".title-am").addClass("title-selected"); +} +if(opts.selectingAmpm==opts.ampm[1]){ +_d42.find(".title-pm").addClass("title-selected"); +} +if(opts.hour24){ +_d42.find(".ampm").hide(); +} +}; +function _d3c(_d43){ +var opts=$(_d43).data("timepicker").options; +var _d44=$(_d43).combo("panel"); +var _d45=_d44.find(".timepicker-panel"); +var _d46=$("
    "+"
    ").appendTo(_d45); +var _d47=_d46.outerWidth(); +var _d48=_d46.outerHeight(); +var size=Math.min(_d47,_d48)-20; +var _d49=size/2; +_d47=size; +_d48=size; +var _d4a=opts.selectingType=="hour"?opts.selectingHour:opts.selectingMinute; +var _d4b=_d4a/(opts.selectingType=="hour"?12:60)*360; +_d4b=parseFloat(_d4b).toFixed(4); +var _d4c={transform:"rotate("+_d4b+"deg)",}; +if(opts.hour24&&opts.selectingType=="hour"){ +if(_d4a==0){ +_d4c.top=opts.hourDistance[0]+"px"; +}else{ +if(_d4a<=12){ +_d4c.top=opts.hourDistance[1]+"px"; +} +} +} +var _d4d={width:_d47+"px",height:_d48+"px",marginLeft:-_d47/2+"px",marginTop:-_d48/2+"px"}; +var _d4e=[]; +_d4e.push("
    "); +_d4e.push("
    "); +_d4e.push("
    "); +_d4e.push("
    "); +_d4e.push("
    "); +var data=_d4f(); +if(opts.hour24&&opts.selectingType=="hour"){ +for(var i=0;i"+(_d50)+"
    "); +} +_d49-=opts.hourDistance[1]-opts.hourDistance[0]; +} +for(var i=0;i"+_d50+""); +} +_d4e.push(""); +_d46.html(_d4e.join("")); +_d46.find(".clock").css(_d4d); +_d46.find(".hand").css(_d4c); +function _d4f(){ +var data=[]; +if(opts.selectingType=="hour"){ +for(var i=0;i<12;i++){ +data.push(String(i)); +} +data[0]="12"; +}else{ +for(var i=0;i<60;i+=5){ +data.push(i<10?"0"+i:String(i)); +} +data[0]="00"; +} +return data; +}; +}; +$.fn.timepicker=function(_d52,_d53){ +if(typeof _d52=="string"){ +var _d54=$.fn.timepicker.methods[_d52]; +if(_d54){ +return _d54(this,_d53); +}else{ +return this.combo(_d52,_d53); +} +} +_d52=_d52||{}; +return this.each(function(){ +var _d55=$.data(this,"timepicker"); +if(_d55){ +$.extend(_d55.options,_d52); +}else{ +$.data(this,"timepicker",{options:$.extend({},$.fn.timepicker.defaults,$.fn.timepicker.parseOptions(this),_d52)}); +} +_d22(this); +}); +}; +$.fn.timepicker.methods={options:function(jq){ +var _d56=jq.combo("options"); +return $.extend($.data(jq[0],"timepicker").options,{width:_d56.width,height:_d56.height,originalValue:_d56.originalValue,disabled:_d56.disabled,readonly:_d56.readonly}); +},initValue:function(jq,_d57){ +return jq.each(function(){ +var opts=$(this).timepicker("options"); +opts.value=_d57; +_d30(this,_d57); +if(_d57){ +opts.value=_d31(this); +$(this).combo("initValue",opts.value).combo("setText",opts.value); +} +}); +},setValue:function(jq,_d58){ +return jq.each(function(){ +_d2d(this,_d58); +}); +},reset:function(jq){ +return jq.each(function(){ +var opts=$(this).timepicker("options"); +$(this).timepicker("setValue",opts.originalValue); +}); +}}; +$.fn.timepicker.parseOptions=function(_d59){ +return $.extend({},$.fn.combo.parseOptions(_d59),$.parser.parseOptions(_d59,[{hour24:"boolean"}])); +}; +$.fn.timepicker.defaults=$.extend({},$.fn.combo.defaults,{closeText:"Close",okText:"Ok",buttons:[{text:function(_d5a){ +return $(_d5a).timepicker("options").okText; +},handler:function(_d5b){ +$(_d5b).timepicker("setValue",_d31(_d5b)); +$(this).closest("div.combo-panel").panel("close"); +}},{text:function(_d5c){ +return $(_d5c).timepicker("options").closeText; +},handler:function(_d5d){ +$(this).closest("div.combo-panel").panel("close"); +}}],editable:false,ampm:["am","pm"],value:"",selectingHour:12,selectingMinute:0,selectingType:"hour",hour24:false,hourDistance:[20,50]}); +})(jQuery); +(function($){ +function init(_d5e){ +var _d5f=$("
    "+"
    "+""+""+"
    "+"
    "+"
    "+"
    "+""+"
    ").insertAfter(_d5e); +var t=$(_d5e); +t.addClass("slider-f").hide(); +var name=t.attr("name"); +if(name){ +_d5f.find("input.slider-value").attr("name",name); +t.removeAttr("name").attr("sliderName",name); +} +_d5f._bind("_resize",function(e,_d60){ +if($(this).hasClass("easyui-fluid")||_d60){ +_d61(_d5e); +} +return false; +}); +return _d5f; +}; +function _d61(_d62,_d63){ +var _d64=$.data(_d62,"slider"); +var opts=_d64.options; +var _d65=_d64.slider; +if(_d63){ +if(_d63.width){ +opts.width=_d63.width; +} +if(_d63.height){ +opts.height=_d63.height; +} +} +_d65._size(opts); +if(opts.mode=="h"){ +_d65.css("height",""); +_d65.children("div").css("height",""); +}else{ +_d65.css("width",""); +_d65.children("div").css("width",""); +_d65.children("div.slider-rule,div.slider-rulelabel,div.slider-inner")._outerHeight(_d65._outerHeight()); +} +_d66(_d62); +}; +function _d67(_d68){ +var _d69=$.data(_d68,"slider"); +var opts=_d69.options; +var _d6a=_d69.slider; +var aa=opts.mode=="h"?opts.rule:opts.rule.slice(0).reverse(); +if(opts.reversed){ +aa=aa.slice(0).reverse(); +} +_d6b(aa); +function _d6b(aa){ +var rule=_d6a.find("div.slider-rule"); +var _d6c=_d6a.find("div.slider-rulelabel"); +rule.empty(); +_d6c.empty(); +for(var i=0;i").appendTo(rule); +span.css((opts.mode=="h"?"left":"top"),_d6d); +if(aa[i]!="|"){ +span=$("").appendTo(_d6c); +span.html(aa[i]); +if(opts.mode=="h"){ +span.css({left:_d6d,marginLeft:-Math.round(span.outerWidth()/2)}); +}else{ +span.css({top:_d6d,marginTop:-Math.round(span.outerHeight()/2)}); +} +} +} +}; +}; +function _d6e(_d6f){ +var _d70=$.data(_d6f,"slider"); +var opts=_d70.options; +var _d71=_d70.slider; +_d71.removeClass("slider-h slider-v slider-disabled"); +_d71.addClass(opts.mode=="h"?"slider-h":"slider-v"); +_d71.addClass(opts.disabled?"slider-disabled":""); +var _d72=_d71.find(".slider-inner"); +_d72.html(""+""); +if(opts.range){ +_d72.append(""+""); +} +_d71.find("a.slider-handle").draggable({axis:opts.mode,cursor:"pointer",disabled:opts.disabled,onDrag:function(e){ +var left=e.data.left; +var _d73=_d71.width(); +if(opts.mode!="h"){ +left=e.data.top; +_d73=_d71.height(); +} +if(left<0||left>_d73){ +return false; +}else{ +_d74(left,this); +return false; +} +},onStartDrag:function(){ +_d70.isDragging=true; +opts.onSlideStart.call(_d6f,opts.value); +},onStopDrag:function(e){ +_d74(opts.mode=="h"?e.data.left:e.data.top,this); +opts.onSlideEnd.call(_d6f,opts.value); +opts.onComplete.call(_d6f,opts.value); +_d70.isDragging=false; +}}); +_d71.find("div.slider-inner")._unbind(".slider")._bind("mousedown.slider",function(e){ +if(_d70.isDragging||opts.disabled){ +return; +} +var pos=$(this).offset(); +_d74(opts.mode=="h"?(e.pageX-pos.left):(e.pageY-pos.top)); +opts.onComplete.call(_d6f,opts.value); +}); +function _d75(_d76){ +var dd=String(opts.step).split("."); +var dlen=dd.length>1?dd[1].length:0; +return parseFloat(_d76.toFixed(dlen)); +}; +function _d74(pos,_d77){ +var _d78=_d79(_d6f,pos); +var s=Math.abs(_d78%opts.step); +if(_d78>=0){ +if(s0; +if(_d78<=v2&&_d7a){ +v1=_d78; +}else{ +if(_d78>=v1&&(!_d7a)){ +v2=_d78; +} +} +}else{ +if(_d78v2){ +v2=_d78; +}else{ +_d78opts.max){ +_d82=opts.max; +} +var _d83=$("").appendTo(_d7f); +_d83.attr("name",name); +_d83.val(_d82); +_d81.push(_d82); +var _d84=_d7f.find(".slider-handle:eq("+i+")"); +var tip=_d84.next(); +var pos=_d85(_d7c,_d82); +if(opts.showTip){ +tip.show(); +tip.html(opts.tipFormatter.call(_d7c,_d82)); +}else{ +tip.hide(); +} +if(opts.mode=="h"){ +var _d86="left:"+pos+"px;"; +_d84.attr("style",_d86); +tip.attr("style",_d86+"margin-left:"+(-Math.round(tip.outerWidth()/2))+"px"); +}else{ +var _d86="top:"+pos+"px;"; +_d84.attr("style",_d86); +tip.attr("style",_d86+"margin-left:"+(-Math.round(tip.outerWidth()))+"px"); +} +} +opts.value=opts.range?_d81:_d81[0]; +$(_d7c).val(opts.range?_d81.join(opts.separator):_d81[0]); +if(_d80.join(",")!=_d81.join(",")){ +opts.onChange.call(_d7c,opts.value,(opts.range?_d80:_d80[0])); +} +}; +function _d66(_d87){ +var opts=$.data(_d87,"slider").options; +var fn=opts.onChange; +opts.onChange=function(){ +}; +_d7b(_d87,opts.value); +opts.onChange=fn; +}; +function _d85(_d88,_d89){ +var _d8a=$.data(_d88,"slider"); +var opts=_d8a.options; +var _d8b=_d8a.slider; +var size=opts.mode=="h"?_d8b.width():_d8b.height(); +var pos=opts.converter.toPosition.call(_d88,_d89,size); +if(opts.mode=="v"){ +pos=_d8b.height()-pos; +} +if(opts.reversed){ +pos=size-pos; +} +return pos; +}; +function _d79(_d8c,pos){ +var _d8d=$.data(_d8c,"slider"); +var opts=_d8d.options; +var _d8e=_d8d.slider; +var size=opts.mode=="h"?_d8e.width():_d8e.height(); +var pos=opts.mode=="h"?(opts.reversed?(size-pos):pos):(opts.reversed?pos:(size-pos)); +var _d8f=opts.converter.toValue.call(_d8c,pos,size); +return _d8f; +}; +$.fn.slider=function(_d90,_d91){ +if(typeof _d90=="string"){ +return $.fn.slider.methods[_d90](this,_d91); +} +_d90=_d90||{}; +return this.each(function(){ +var _d92=$.data(this,"slider"); +if(_d92){ +$.extend(_d92.options,_d90); +}else{ +_d92=$.data(this,"slider",{options:$.extend({},$.fn.slider.defaults,$.fn.slider.parseOptions(this),_d90),slider:init(this)}); +$(this)._propAttr("disabled",false); +} +var opts=_d92.options; +opts.min=parseFloat(opts.min); +opts.max=parseFloat(opts.max); +if(opts.range){ +if(!$.isArray(opts.value)){ +opts.value=$.map(String(opts.value).split(opts.separator),function(v){ +return parseFloat(v); +}); +} +if(opts.value.length<2){ +opts.value.push(opts.max); +} +}else{ +opts.value=parseFloat(opts.value); +} +opts.step=parseFloat(opts.step); +opts.originalValue=opts.value; +_d6e(this); +_d67(this); +_d61(this); +}); +}; +$.fn.slider.methods={options:function(jq){ +return $.data(jq[0],"slider").options; +},destroy:function(jq){ +return jq.each(function(){ +$.data(this,"slider").slider.remove(); +$(this).remove(); +}); +},resize:function(jq,_d93){ +return jq.each(function(){ +_d61(this,_d93); +}); +},getValue:function(jq){ +return jq.slider("options").value; +},getValues:function(jq){ +return jq.slider("options").value; +},setValue:function(jq,_d94){ +return jq.each(function(){ +_d7b(this,[_d94]); +}); +},setValues:function(jq,_d95){ +return jq.each(function(){ +_d7b(this,_d95); +}); +},clear:function(jq){ +return jq.each(function(){ +var opts=$(this).slider("options"); +_d7b(this,opts.range?[opts.min,opts.max]:[opts.min]); +}); +},reset:function(jq){ +return jq.each(function(){ +var opts=$(this).slider("options"); +$(this).slider(opts.range?"setValues":"setValue",opts.originalValue); +}); +},enable:function(jq){ +return jq.each(function(){ +$.data(this,"slider").options.disabled=false; +_d6e(this); +}); +},disable:function(jq){ +return jq.each(function(){ +$.data(this,"slider").options.disabled=true; +_d6e(this); +}); +}}; +$.fn.slider.parseOptions=function(_d96){ +var t=$(_d96); +return $.extend({},$.parser.parseOptions(_d96,["width","height","mode",{reversed:"boolean",showTip:"boolean",range:"boolean",min:"number",max:"number",step:"number"}]),{value:(t.val()||undefined),disabled:(t.attr("disabled")?true:undefined),rule:(t.attr("rule")?eval(t.attr("rule")):undefined)}); +}; +$.fn.slider.defaults={width:"auto",height:"auto",mode:"h",reversed:false,showTip:false,disabled:false,range:false,value:0,separator:",",min:0,max:100,step:1,rule:[],tipFormatter:function(_d97){ +return _d97; +},converter:{toPosition:function(_d98,size){ +var opts=$(this).slider("options"); +var p=(_d98-opts.min)/(opts.max-opts.min)*size; +return p; +},toValue:function(pos,size){ +var opts=$(this).slider("options"); +var v=opts.min+(opts.max-opts.min)*(pos/size); +return v; +}},onChange:function(_d99,_d9a){ +},onSlideStart:function(_d9b){ +},onSlideEnd:function(_d9c){ +},onComplete:function(_d9d){ +}}; +})(jQuery); + diff --git a/system-framework/system-plugin-example-web/src/main/resources/templates/index.html b/system-framework/system-plugin-example-web/src/main/resources/templates/index.html new file mode 100644 index 0000000..b546331 --- /dev/null +++ b/system-framework/system-plugin-example-web/src/main/resources/templates/index.html @@ -0,0 +1,116 @@ + + + + + index + + + + + +
    + Plugin Management +
    +
    + + + +
    +
    +
    +
    +
    + Business Feature +
    + +
    +
    + + + \ No newline at end of file