diff --git a/.gitignore b/.gitignore index b1f0472b69d1fd12bee195c50a245ff94133a8e0..0d23ed38aa67880693bf97ef5e6192bb05f17438 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,54 @@ +### gradle ### +.gradle +/build/ +!gradle/wrapper/gradle-wrapper.jar + +### STS ### +.settings/ +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +bin/ + +### IntelliJ IDEA ### /.idea/ /private/ /storage/ /litemall.iml -.idea \ No newline at end of file +.checkstyle +.idea +*.iws +*.iml +*.ipr +rebel.xml +### maven ### +target/ +*.war +*.ear +*.zip +*.tar +*.tar.gz + +### logs #### +/logs/ +*.log + +### temp ignore ### +*.cache +*.diff +*.patch +*.tmp +*.java~ +*.properties~ +*.xml~ + +### system ignore ### +.DS_Store +Thumbs.db +Servers +.metadata +upload +gen_code diff --git a/CHANGELOG.md b/CHANGELOG.md index b2cdffaaf575010b93ca6b635d39e1de4d7dfeac..01dcd70daf734e5c0c802a8e5d22d8d47a2be092 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,127 @@ # 更新日志 +## V 1.7.0 + +*2020-02-15* 支持docker部署、售后管理、通知管理、数据库七天备份。 + +#### Bug 修复 + + * `小商城`后端接口部分存在水平越权漏洞 + * `小商城`验证码未超时仍会发送验证码 + * `小商城`显示管理员评论回复(#340 by sunyinggang) + * `管理后台`管理员评论回复(#340 by sunyinggang) + * `轻商城`添加收货地址返回不正确(#320 by kevinleeex) + +#### 优化 + + * `管理后台`专题支持排序、批量删除 + * `基础系统`数据库四个表索引(#328 #330 #332 #334 by wtune) + +#### 新特性 + + * `基础系统`支持docker部署(参考实现 #321 by yuana1) + * `基础系统`自动备份7天数据到backup文件夹 + * `管理后台`通知中心和通知管理 + * `管理后台`登录页面增加版权内容 + * `管理后台`售后管理 + * `小商城`订单售后列表、售后详情、申请售后 + * `轻商城`实现账号注册功能(#324 by yelongbao) + +## V 1.6.1 + +*2020-01-01* + +#### Bug 修复 + + * `基础系统`删除不必要的Bean注解,导致不必要的实例化。 + +## V 1.6.0 + +*2019-12-31*,优化团购实现,删除微信模板实现。 + +#### Bug 修复 + + * `管理后台`修复查看商品详情时, 图片溢出(#305 by zaoangod) + +#### 优化 + + * `小商城`重构团购实现 + * `小商城`删除微信模板实现 + * `管理后台`优化查询区域速度(# by) + * `管理后台`更新vue-element-admin框架版本4.2.1 + +#### 新特性 + + * `小商城`尽可能替换vant图标 + * `小商城`首页banner添加产品跳转(#299 by staneychan) + * `小商城`发送邮件通知使用ssl方式(#307 by jessonxiang) + +## V 1.5.0 + +*2019-11-15*, 持续优化轻商城模块,以及推荐项目Flutter_Mall + +#### Bug 修复 + + * `小商城`优惠券绑定绑定优惠券ID(#157 by @beaver383) + * `小商城`评论列表不能正确显示 + * `轻商城`修正取消订单接口 (#256 by @1037621594) + +#### 优化 + + * `小商城`采用延迟队列实现支付超时取消订单功能(参考#275 by @alexzhu0592) + * `小商城`分享按钮可选配置 (#239 by @galenzhao) + +#### 新特性 + + * `基础系统`支持阿里云短信 + * `轻商城`接入微信支付H5支付 (#291 by @beaver383) + * `小商城`团购拼团超期取消 (#284 by @beaver383) + * `管理后台`订单详情新增打印 (#274 by @fanchenggang ) + * README文档推荐项目Flutter_Mall + +## V 1.4.0 + +*2019-05-16*,支持移动端轻商城 + +#### Bug 修复 + + * `小商场`购物车和订单的商品数量必须正整数 + * `小商城`微信支付回调校验失败通知信息 + * `小商城`收货地址采用userId和id联合查询 + * `管理后台`管理员不能删除自己账号 + +#### 优化 + + * `文档`支持API文档 + * `基础系统`更新第三方插件mybatis-generator-plugin到1.3.2 + * `管理后台`不允许管理员通过编辑接口修改管理员密码 + +#### 新特性 + + * `小商城`帮助中心页面 + * `小商城`后端登录验证方式采用JWT (#167 by @Bigger-Ma) + * `轻商场`基本结构完成 (#157 by @pkwenda) + * `管理后台`支持操作日志管理 + +## V 1.3.0 + +*2019-03-11*,支持配置管理 + + * `管理后台`商品类目页面和行政区域页面采用树形结构显示; + * `管理后台`取消国际化和主题; + * `管理后台`支持配置管理; + + 注意:虽然配置管理中可以设置订单超时时间,但是由于目前采用定时查询方式会产生延时, + 因此最终订单超时时间需要额外加上这些延迟才能更新状态。以后会解决这个问题(例如使用redis)。 + +## V 1.2.0 + +*2019-03-03*,支持权限管理 + + * `管理后台`支持权限管理; + * `小商城`取消编程式事务管理,采用注解式事务管理; + * `小商城`采用多线程进行数据库查询操作; + ## V 1.1.0 *2018-12-23*,支持优惠券 diff --git a/README.md b/README.md index b188a9382a15cde04d2a45a0fa3a7b991e1782b7..7110566704a60856672283321921625f98dec33b 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,48 @@ 又一个小商场系统。 -litemall = Spring Boot后端 + Vue管理员前端 + 微信小程序用户前端 +litemall = Spring Boot后端 + Vue管理员前端 + 微信小程序用户前端 + Vue用户移动端 * [文档](https://linlinjava.gitbook.io/litemall) * [贡献](https://linlinjava.gitbook.io/litemall/contribute) -* [FAQ](https://linlinjava.gitbook.io/litemall/7) +* [FAQ](https://linlinjava.gitbook.io/litemall/faq) +* [API](https://linlinjava.gitbook.io/litemall/api) -## 上架实例 +## 项目实例 -![](./doc/pic/demo.png) +### 小商场实例 + +* renard-wx模块实例 + +![](./doc/pics/readme/renard_wx_demo.png) + +> 注意:此实例是真实小商场,开发者可以购买商品和付款,但请不要尝试退款操作。 + +* litemall-wx模块实例 + +![](./doc/pics/readme/litemall_wx_demo.png) + +> 注意:此实例是测试小商场,开发者请不要尝试购买商品、付款、退款操作。 + +### 轻商场实例 + +请手机扫描以下二维码访问: + +![](./doc/pics/readme/mobmall.png) + +或者浏览器采用手机模式访问以下网址: [http://122.51.199.160:8080/vue/index.html#/](http://122.51.199.160:8080/vue/index.html#/) + +注意: +> 1. 由于第一次加载数据量较大,建议wifi网络访问,且耐心等待数秒。 +> 2. 此实例是测试轻商场,不支持支付,而且处于开发中还不完善。 + +### 管理后台实例 + +![](./doc/pics/readme/admin-dashboard.png) + +1. 浏览器打开,输入以下网址: [http://122.51.199.160:8080/#/login](http://122.51.199.160:8080/#/login) +2. 管理员用户名`admin123`,管理员密码`admin123` +> 注意:此实例只是测试管理后台,不是前两个小商城的管理后台。 ## 项目代码 @@ -18,7 +51,7 @@ litemall = Spring Boot后端 + Vue管理员前端 + 微信小程序用户前端 * [GitHub](https://github.com/linlinjava/litemall) ## 项目架构 -![](./doc/pic/1.png) +![](./doc/pics/readme/project-structure.png) ## 技术栈 @@ -26,7 +59,7 @@ litemall = Spring Boot后端 + Vue管理员前端 + 微信小程序用户前端 > 2. Vue > 3. 微信小程序 -![](doc/pic/2.png) +![](doc/pics/readme/technology-stack.png) ## 功能 @@ -43,7 +76,7 @@ litemall = Spring Boot后端 + Vue管理员前端 + 微信小程序用户前端 * 商品详情、商品评价、商品分享 * 购物车 * 下单 -* 订单列表、订单详情 +* 订单列表、订单详情、订单售后 * 地址、收藏、足迹、意见反馈 * 客服 @@ -54,28 +87,8 @@ litemall = Spring Boot后端 + Vue管理员前端 + 微信小程序用户前端 * 商品管理 * 推广管理 * 系统管理 - -## 云演示 - -### 小商城演示访问 - -由于没有上线,只能在微信开发工具中测试运行: - -1. 微信开发工具导入litemall-wx项目; -2. 项目配置,启用“不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书” -3. 点击“编译”,即可在微信开发工具预览效果; -4. 也可以点击“预览”,然后手机扫描登录。 - 注意,手机需要打开调试功能。 - -![](./doc/pic/3.png) - - -### 管理平台演示访问 - -1. 浏览器打开,输入以下网址[http://122.152.206.172:8080/#/login](http://122.152.206.172:8080/#/login) -2. 管理员名称`admin123`,管理员密码`admin123` - -![](doc/pic/4.png) +* 配置管理 +* 统计报表 ## 快速启动 @@ -97,9 +110,8 @@ litemall = Spring Boot后端 + Vue管理员前端 + 微信小程序用户前端 ```bash cd litemall mvn install - mvn package - cd ./litemall-all - mvn spring-boot:run + mvn clean package + java -Dfile.encoding=UTF-8 -jar litemall-all/target/litemall-all-0.1.0-exec.jar ``` 4. 启动管理后台前端 @@ -115,16 +127,34 @@ litemall = Spring Boot后端 + Vue管理员前端 + 微信小程序用户前端 5. 启动小商城前端 - 打开微信开发者工具,导入litemall-wx模块,点击`编译`即可,此时可以预览小商场效果。 - - 这里存在两套小商场前端litemall-wx和renard-wx,开发者可以分别导入和测试。 + 这里存在两套小商场前端litemall-wx和renard-wx,开发者可以分别导入和测试: -注意: -> 这里只是最简启动方式,而且小商场的微信登录会失败,更详细方案请参考文档。 + 1. 微信开发工具导入litemall-wx项目; + 2. 项目配置,启用“不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书” + 3. 点击“编译”,即可在微信开发工具预览效果; + 4. 也可以点击“预览”,然后手机扫描登录(但是手机需开启调试功能)。 + + 注意: + > 这里只是最简启动方式,而小商场的微信登录、微信支付等功能需开发者设置才能运行, + > 更详细方案请参考[文档](https://linlinjava.gitbook.io/litemall/project)。 + +6. 启动轻商城前端 + + 打开命令行,输入以下命令 + ```bash + npm install -g cnpm --registry=https://registry.npm.taobao.org + cd litemall/litemall-vue + cnpm install + cnpm run dev + ``` + 此时,浏览器(建议采用chrome 手机模式)打开,输入网址`http://localhost:6255`, 此时进入轻商场。 + 注意: + > 现在功能很不稳定,处在开发阶段。 + ## 开发计划 -当前版本[v1.1.0](./CHANGELOG.md) +当前版本[v1.7.0](https://linlinjava.gitbook.io/litemall/changelog) 目前项目开发中,存在诸多不足,以下是目前规划的开发计划。 @@ -137,7 +167,9 @@ V 1.0.0 完成以下目标: V 2.0.0 完成以下目标: 1. 小商城和管理后台完成所有基本业务; -2. 管理后台实现统计功能、日志功能、权限功能 +2. 管理后台实现统计功能、日志功能、权限功能; +3. 业务代码和细节代码进行调整优化; +4. 轻商城的开发; V 3.0.0 完成以下目标: @@ -148,8 +180,8 @@ V 3.0.0 完成以下目标: ## 警告 > 1. 本项目仅用于学习练习 -> 2. 项目目前还不完善,仍处在开发中,不承担任何使用后果 -> 3. 项目代码开源[MIT](./LICENSE),项目文档采用 [署名-禁止演绎 4.0 国际协议许可](https://creativecommons.org/licenses/by-nd/4.0/deed.zh) +> 2. 本项目还不完善,仍处在开发中,不承担任何使用后果 +> 3. 本项目代码开源[MIT](./LICENSE),项目文档采用 [署名-禁止演绎 4.0 国际协议许可](https://creativecommons.org/licenses/by-nd/4.0/deed.zh) ## 致谢 @@ -160,35 +192,53 @@ V 3.0.0 完成以下目标: 项目介绍:基于Node.js+MySQL开发的开源微信小程序商城(微信小程序) 项目参考: - (1)litemall项目数据库基于nideshop-mini-program项目数据库; - (2)litemall项目的litemall-wx模块基于nideshop-mini-program开发。 + + 1. litemall项目数据库基于nideshop-mini-program项目数据库; + 2. litemall项目的litemall-wx模块基于nideshop-mini-program开发。 2. [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) 项目介绍: 一个基于Vue和Element的后台集成方案 - 项目参考:litemall项目的litemall-admin模块的前端框架基于该项目修改扩展。 + 项目参考:litemall项目的litemall-admin模块的前端框架基于vue-element-admin项目修改扩展。 3. [mall-admin-web](https://github.com/macrozheng/mall-admin-web) 项目介绍:mall-admin-web是一个电商后台管理系统的前端项目,基于Vue+Element实现。 - 项目参考:litemall项目的litemall-admin模块的一些页面布局样式参考了该项目。 + 项目参考:litemall项目的litemall-admin模块的一些页面布局样式参考了mall-admin-web项目。 4. [biu](https://github.com/CaiBaoHong/biu) 项目介绍:管理后台项目开发脚手架,基于vue-element-admin和springboot搭建,前后端分离方式开发和部署。 - 项目参考:litemall项目的权限管理功能参考了该项目。 + 项目参考:litemall项目的权限管理功能参考了biu项目。 + +5. [vant--mobile-mall](https://github.com/qianzhaoy/vant--mobile-mall) + + 项目介绍:基于有赞 vant 组件库的移动商城。 + + 项目参考:litemall项目的litemall-vue模块基于vant--mobile-mall项目开发。 + +## 推荐 + +1. [Flutter_Mall](https://github.com/youxinLu/mall) + + 项目介绍:Flutter_Mall是一款Flutter开源在线商城应用程序。 + +2. [Taro_Mall](https://github.com/jiechud/taro-mall) + + 项目介绍:Taro_Mall是一款多端开源在线商城应用程序,后台是基于litemall基础上进行开发,前端采用Taro框架编写。 + ## 问题 -![](doc/pic/qq.png) +![](doc/pics/readme/qq3.png) * 开发者有问题或者好的建议可以用Issues反馈交流,请给出详细信息 * 在开发交流群中应讨论开发、业务和合作问题 * 如果真的需要QQ群里提问,请在提问前先完成以下过程: - * 请仔细阅读本项目文档,特别是是[**FAQ**](./doc/FAQ.md),查看能否解决; + * 请仔细阅读本项目文档,特别是是[**FAQ**](https://linlinjava.gitbook.io/litemall/faq),查看能否解决; * 请阅读[提问的智慧](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/master/README-zh_CN.md); * 请百度或谷歌相关技术; * 请查看相关技术的官方文档,例如微信小程序的官方文档; diff --git a/deploy/README.md b/deploy/README.md index 903619a27ea553127eaf2a0f66f755996e261964..ff337084be64ca0ed5b98ce67d64659227f841ca 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -2,7 +2,7 @@ ### 项目打包 -1. 在主机或者开发机打包项目到deploy; +1. 在服务器或者开发机打包项目到deploy; ``` cd litemall cat ./litemall-db/sql/litemall_schema.sql > ./deploy/db/litemall.sql @@ -25,13 +25,13 @@ 2. 修改litemall文件夹下面的*.yml外部配置文件,当litemall-all模块启动时会 加载外部配置文件,而覆盖默认jar包内部的配置文件。 - 例如,配置文件中一些地方需要设置成远程主机的IP地址 + 例如,配置文件中一些地方需要设置成远程服务器的IP地址 此时deploy部署包结构如下: * bin -存放远程主机运行的脚本,包括deploy.sh脚本和reset.sh脚本 +存放远程服务器运行的脚本,包括deploy.sh脚本和reset.sh脚本 * db @@ -39,15 +39,15 @@ * litemall -存放远程主机运行的代码,包括litemall-all二进制可执行包和litemall外部配置文件 +存放远程服务器运行的代码,包括litemall-all二进制可执行包和litemall外部配置文件 * util -存放开发主机运行的脚本,包括package.sh脚本和lazy.sh脚本。 -由于是本地开发主机运行,因此开发者可以不用上传到远程主机。 +存放开发服务器运行的脚本,包括package.sh脚本和lazy.sh脚本。 +由于是本地开发服务器运行,因此开发者可以不用上传到远程服务器。 ### 项目部署 -1. 远程主机环境(MySQL和JDK1.8)已经安装好,请确保云主机的安全组已经允许相应的端口。 +1. 远程服务器环境(MySQL和JDK1.8)已经安装好,请确保云服务器的安全组已经允许相应的端口。 2. 导入db/litemall.sql ```bash cd /home/ubuntu/deploy/db @@ -59,7 +59,7 @@ sudo ln -f -s /home/ubuntu/deploy/litemall/litemall.jar /etc/init.d/litemall sudo service litemall start ``` -4. 测试是否部署成功(xxx.xxx.xxx.xxx是云主机IP): +4. 测试是否部署成功(xxx.xxx.xxx.xxx是云服务器IP): ``` http://xxx.xxx.xxx.xxx:8080/wx/index/index http://xxx.xxx.xxx.xxx:8080/admin/index/index @@ -73,26 +73,26 @@ * util/packet.sh -在开发主机运行可以自动项目打包 +在开发服务器运行可以自动项目打包 * util/lazy.sh -在开发主机运行可以自动项目打包、项目上传远程主机、自动登录系统执行项目部署脚本。 +在开发服务器运行可以自动项目打包、项目上传远程服务器、自动登录系统执行项目部署脚本。 注意: -> 1. 开发者需要在util/lazy.sh中设置相应的远程主机登录账号和密钥文件路径。 -> 2. 开发者需要在bin/reset.sh设置远程主机的MySQL的root登录账户。 +> 1. 开发者需要在util/lazy.sh中设置相应的远程服务器登录账号和密钥文件路径。 +> 2. 开发者需要在bin/reset.sh设置远程服务器的MySQL的root登录账户。 * bin/deploy.sh -在远程主机运行可以自动部署服务 +在远程服务器运行可以自动部署服务 * bin/reset.sh -在远程主机运行可以自动项目导入数据、删除本地上传图片、再执行bin/deploy.sh部署服务。 +在远程服务器运行可以自动项目导入数据、删除本地上传图片、再执行bin/deploy.sh部署服务。 注意: -> 开发者需要在bin/reset.sh设置远程主机的MySQL的root登录账户。 +> 开发者需要在bin/reset.sh设置远程服务器的MySQL的root登录账户。 总结,当开发者设置好配置信息以后,可以在本地运行lazy.sh脚本自动一键部署: ```bash diff --git a/deploy/bin/deploy.sh b/deploy/bin/deploy.sh index cf3623b6ed5b91abd5f2b61492ceb2cbec32ea2b..4105e33cf450b7803f292732626d9aa02278c702 100644 --- a/deploy/bin/deploy.sh +++ b/deploy/bin/deploy.sh @@ -3,4 +3,6 @@ # 本脚本的作用是停止当前Spring Boot应用,然后再次部署 sudo service litemall stop sudo ln -f -s /home/ubuntu/deploy/litemall/litemall.jar /etc/init.d/litemall +sudo update-rc.d litemall defaults +sudo update-rc.d litemall enable sudo service litemall start \ No newline at end of file diff --git a/deploy/bin/reset.sh b/deploy/bin/reset.sh index e6bc8e21813cfe7dd36fde9c8cb4b08ec074d1fd..4ac1a1a27c36a15a1fdd17a78599cf6010838803 100644 --- a/deploy/bin/reset.sh +++ b/deploy/bin/reset.sh @@ -14,18 +14,18 @@ PASSWORD= if test -z "$PASSWORD" then - echo "请设置云主机MySQL的root账号密码" - exit -1 + echo "请设置云服务器MySQL的root账号密码" + exit 1 fi # 导入数据 -cd /home/ubuntu/deploy/db +cd /home/ubuntu/deploy/db || exit 2 mysql -h localhost -u $ROOT -p$PASSWORD < litemall.sql # 删除storage文件夹内文件 -cd /home/ubuntu/deploy/litemall/storage +cd /home/ubuntu/deploy/litemall/storage || exit 2 rm -f ./** # 重新部署服务 -cd /home/ubuntu/deploy/bin +cd /home/ubuntu/deploy/bin || exit 2 sudo ./deploy.sh \ No newline at end of file diff --git a/deploy/litemall/application-admin.yml b/deploy/db/.gitkeep similarity index 100% rename from deploy/litemall/application-admin.yml rename to deploy/db/.gitkeep diff --git a/deploy/litemall/application-db.yml b/deploy/litemall/application-db.yml deleted file mode 100644 index ef526211806da7e17ca1ac697076325aff1155a9..0000000000000000000000000000000000000000 --- a/deploy/litemall/application-db.yml +++ /dev/null @@ -1,26 +0,0 @@ -pagehelper: - helperDialect: mysql - reasonable: true - supportMethodsArguments: true - params: count=countSql - -spring: - datasource: - druid: - url: jdbc:mysql://localhost:3306/litemall?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&allowPublicKeyRetrieval=true&verifyServerCertificate=false&useSSL=false - driver-class-name: com.mysql.jdbc.Driver - username: litemall - password: litemall123456 - initial-size: 10 - max-active: 50 - min-idle: 10 - max-wait: 60000 - pool-prepared-statements: true - max-pool-prepared-statement-per-connection-size: 20 - validation-query: SELECT 1 FROM DUAL - test-on-borrow: false - test-on-return: false - test-while-idle: true - time-between-eviction-runs-millis: 60000 - filters: stat,wall - diff --git a/deploy/litemall/application.yml b/deploy/litemall/application.yml index 3802b74f1f80d60ac3b53274845d922a0bab3358..a3b9d922db0e60c5809ef1ea920e4391126e9c99 100644 --- a/deploy/litemall/application.yml +++ b/deploy/litemall/application.yml @@ -1,19 +1,148 @@ spring: profiles: - active: db, core, admin, wx + active: none message: encoding: UTF-8 + datasource: + druid: + url: jdbc:mysql://localhost:3306/litemall?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&allowPublicKeyRetrieval=true&verifyServerCertificate=false&useSSL=false + driver-class-name: com.mysql.jdbc.Driver + username: litemall + password: litemall123456 + initial-size: 10 + max-active: 50 + min-idle: 10 + max-wait: 60000 + pool-prepared-statements: true + max-pool-prepared-statement-per-connection-size: 20 + validation-query: SELECT 1 FROM DUAL + test-on-borrow: false + test-on-return: false + test-while-idle: true + time-between-eviction-runs-millis: 60000 + filters: stat,wall server: port: 8080 logging: - level: - root: ERROR - org.springframework: ERROR - org.mybatis: ERROR - org.linlinjava.litemall.core: ERROR - org.linlinjava.litemall.db: ERROR - org.linlinjava.litemall.admin: ERROR - org.linlinjava.litemall.wx: ERROR - org.linlinjava.litemall: ERROR \ No newline at end of file + config: classpath:logback-spring.xml + +pagehelper: + helperDialect: mysql + reasonable: true + supportMethodsArguments: true + params: count=countSql + +litemall: + # 开发者应该设置成自己的wx相关信息 + wx: + app-id: wxa5b486c6b918ecfb + app-secret: e04004829d4c383b4db7769d88dfbca1 + mch-id: 111111 + mch-key: xxxxxx + notify-url: http://122.51.199.160:8080/wx/order/pay-notify + # 商户证书文件路径 + # 请参考“商户证书”一节 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=4_3 + key-path: xxxxx + + #通知相关配置 + notify: + mail: + # 邮件通知配置,邮箱一般用于接收业务通知例如收到新的订单,sendto 定义邮件接收者,通常为商城运营人员 + enable: false + host: smtp.exmail.qq.com + username: ex@ex.com.cn + password: XXXXXXXXXXXXX + sendfrom: ex@ex.com.cn + sendto: ex@qq.com + port: 465 + + # 短消息模版通知配置 + # 短信息用于通知客户,例如发货短信通知,注意配置格式;template-name,template-templateId 请参考 NotifyType 枚举值 + sms: + enable: false + # 如果是腾讯云短信,则设置active的值tencent + # 如果是阿里云短信,则设置active的值aliyun + active: tencent + sign: litemall + template: + - name: paySucceed + templateId: 156349 + - name: captcha + templateId: 156433 + - name: ship + templateId: 158002 + - name: refund + templateId: 159447 + tencent: + appid: 111111111 + appkey: xxxxxxxxxxxxxx + aliyun: + regionId: xxx + accessKeyId: xxx + accessKeySecret: xxx + + # 快鸟物流查询配置 + express: + enable: false + appId: "XXXXXXXXX" + appKey: "XXXXXXXXXXXXXXXXXXXXXXXXX" + vendors: + - code: "ZTO" + name: "中通快递" + - code: "YTO" + name: "圆通速递" + - code: "YD" + name: "韵达速递" + - code: "YZPY" + name: "邮政快递包裹" + - code: "EMS" + name: "EMS" + - code: "DBL" + name: "德邦快递" + - code: "FAST" + name: "快捷快递" + - code: "ZJS" + name: "宅急送" + - code: "TNT" + name: "TNT快递" + - code: "UPS" + name: "UPS" + - code: "DHL" + name: "DHL" + - code: "FEDEX" + name: "FEDEX联邦(国内件)" + - code: "FEDEX_GJ" + name: "FEDEX联邦(国际件)" + + # 对象存储配置 + storage: + # 当前工作的对象存储模式,分别是local、aliyun、tencent + active: local + # 本地对象存储配置信息 + local: + storagePath: storage + address: http://122.51.199.160:8080/wx/storage/fetch/ + # 阿里云对象存储配置信息 + aliyun: + endpoint: oss-cn-shenzhen.aliyuncs.com + accessKeyId: 111111 + accessKeySecret: xxxxxx + bucketName: xxxxxx + # 腾讯对象存储配置信息 + # 请参考 https://cloud.tencent.com/document/product/436/6249 + tencent: + secretId: 111111 + secretKey: xxxxxx + region: xxxxxx + bucketName: xxxxxx + # 七牛云对象存储配置信息 + qiniu: + endpoint: http://pd5cb6ulu.bkt.clouddn.com + accessKey: 111111 + secretKey: xxxxxx + bucketName: litemall + +swagger: + production: true \ No newline at end of file diff --git a/deploy/util/lazy.sh b/deploy/util/lazy.sh index 33dbc35cc0489277bcae2c8b834d15b24923c077..04646e6368a772f577145e741281f26b02d5562e 100644 --- a/deploy/util/lazy.sh +++ b/deploy/util/lazy.sh @@ -2,11 +2,11 @@ # 本脚本的作用是 # 1. 项目打包 -# 2. 上传云主机 -# 3. 远程登录云主机并执行reset脚本 +# 2. 上传云服务器 +# 3. 远程登录云服务器并执行reset脚本 -# 请设置云主机的IP地址和账户 -# 例如 ubuntu@122.152.206.172 +# 请设置云服务器的IP地址和账户 +# 例如 ubuntu@122.51.199.160 REMOTE= # 请设置本地SSH私钥文件id_rsa路径 # 例如 /home/litemall/id_rsa @@ -14,32 +14,39 @@ ID_RSA= if test -z "$REMOTE" then - echo "请设置云主机登录IP地址和账户" - exit -1 + echo "请设置云服务器登录IP地址和账户" + exit 1 fi if test -z "$ID_RSA" then - echo "请设置云主机登录IP地址和账户" - exit -1 + echo "请设置云服务器登录IP地址和账户" + exit 1 fi DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" -cd $DIR/../.. +cd $DIR/../.. || exit 2 LITEMALL_HOME=$PWD echo "LITEMALL_HOME $LITEMALL_HOME" # 项目打包 -cd $LITEMALL_HOME +cd $LITEMALL_HOME || exit 2 ./deploy/util/package.sh -# 上传云主机 -cd $LITEMALL_HOME +# 上传云服务器 +cd $LITEMALL_HOME || exit 2 scp -i $ID_RSA -r ./deploy $REMOTE:/home/ubuntu/ -# 远程登录云主机并执行reset脚本 +# 远程登录云服务器并执行reset脚本 +# 这里使用tr命令,因为有可能deploy.sh和reset.sh的换行格式是CRLF,而LINUX环境应该是LF ssh $REMOTE -i $ID_RSA << eeooff -cd /home/ubuntu -sudo ./deploy/bin/reset.sh +cd /home/ubuntu/deploy/bin +cat deploy.sh | tr -d '\r' > deploy2.sh +mv deploy2.sh deploy.sh +chmod +x deploy.sh +cat reset.sh | tr -d '\r' > reset2.sh +mv reset2.sh reset.sh +chmod +x reset.sh +sudo ./reset.sh exit eeooff \ No newline at end of file diff --git a/deploy/util/package.sh b/deploy/util/package.sh index 9df8a1fc7a97de06f75c190969fc602e84ea6508..5c47b4b8fc41afb7d6fe95840c905fc2aceb384b 100644 --- a/deploy/util/package.sh +++ b/deploy/util/package.sh @@ -16,10 +16,16 @@ cat $LITEMALL_HOME/litemall-db/sql/litemall_schema.sql > $LITEMALL_HOME/deploy/d cat $LITEMALL_HOME/litemall-db/sql/litemall_table.sql >> $LITEMALL_HOME/deploy/db/litemall.sql cat $LITEMALL_HOME/litemall-db/sql/litemall_data.sql >> $LITEMALL_HOME/deploy/db/litemall.sql -cd $LITEMALL_HOME/litemall-admin # 安装阿里node镜像工具 npm install -g cnpm --registry=https://registry.npm.taobao.org -# 安装node项目依赖环境 + +# 打包litemall-admin +cd $LITEMALL_HOME/litemall-admin +cnpm install +cnpm run build:dep + +# 打包litemall-vue +cd $LITEMALL_HOME/litemall-vue cnpm install cnpm run build:dep diff --git a/doc/FAQ.md b/doc/FAQ.md index 897b32d405e3c3251842f22d3a89b53ad19ec2fc..b3cb98d8b7ee34f9cb6894ba54f4c86d706ffaa2 100644 --- a/doc/FAQ.md +++ b/doc/FAQ.md @@ -12,12 +12,12 @@ 原因: -目前账号的appid是本人申请,同时小程序未上线,因此开发者如果微信登录肯定会失败。 +小程序未上线之前,开发者必须设置自己申请的appid,否则微信登录肯定会失败。 解决: 1. 如果只是体验商品购买流程,开发者可以采用账号注册登录方式。 -2. 开发者在litemall-wx和litemall-wx-api模块的appid等信息设置成自己申请的信息。 +2. 开发者在litemall-wx、renard-wx和litemall-core模块的appid信息设置成自己申请的信息。 ### 1.2 appid已经修改,微信登陆仍然失败 @@ -55,7 +55,7 @@ 现象: -本人手机测试正常,而第三者手机测试不正常。 +开发者自己手机测试正常,而第三者手机测试不正常。 解决: @@ -133,6 +133,44 @@ litemall.wx.notify-url= 3. 最后,如果设置正确,用chrome的开发者工具查看登录页面向后端请求返回数据信息; 如果设置不正确,请启动相应的后端服务。 +### 2.2 安装失败/启动不成功 + +现象: + +执行`cnpm install`失败 + +原因: + +可能下载依赖失败。 + +解决: + +清空node_modules,重新执行`cnpm install`命令,或者自行百度、Google。 + +### 2.3 分页数据返回不正常 + +现象: + +如果管理后台点击很大的分页页数(实际已超过当时数据最大页数),后端仍然能够返回数据。 + +原因: + +这个不是BUG,而是开发者对于查询页面超过实际页面后应该产生何种效果的不同理解。 +* 返回最后一页数据可能是合理的; +* 返回空数据可能也是合理的。 + +解决: + +litemall-db模块的application-db.yaml资源文件中reasonable是true + + pagehelper: + helperDialect: mysql + reasonable: true + supportMethodsArguments: true + params: count=countSql + +开发者可以尝试设置reasonable为false,然后检查是否能够解决问题。 + ## 3. 基础系统 这里主要是指litemall-db、litemall-core和litemall-all模块三个模块的相关问题。 @@ -173,7 +211,7 @@ SQL: select id, key_name, key_value, add_time, update_time, deleted from litemal Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'add_time' in 'field list' ``` -原因是: +原因: 系统处在开发中,所以数据库表根据业务会不断调整,因此如果开发者更新代码以后直接运行,有可能导致当前代码 操作数据库失败,因为开发者当前的数据库表已经过时。 @@ -182,6 +220,39 @@ Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 如果出现数据库方面的报错,建议开发者重新导入数据库。 +### 3.3 数据库导入失败 + +现象: + +开发者直接(或使用Navicat)运行litemall_schema.sql时运行失败。 + +原因: + +可能是`drop user if exists`在MySQL低版本不支持,也可能是Navicat不支持。 + +解决: + +首先,请开发者请直接打开litemall_schema.sql文件,可以看到 +``` +drop database if exists litemall; +drop user if exists 'litemall'@'localhost'; +create database litemall default character set utf8mb4 collate utf8mb4_unicode_ci; +use litemall; +create user 'litemall'@'localhost' identified by 'litemall123456'; +grant all privileges on litemall.* to 'litemall'@'localhost'; +flush privileges; +``` + +可以看到,这里主要是完成三个工作 +* 创建数据库 +* 创建数据库用户 +* 分配该用户所有操作权限 + +因此,如果开发者运行litemall_schema.sql失败,开发者可以自行使用 +相关SQL命令或者使用SQL工具创建数据库、用户和分配权限工作。 + +此外实际上,**开发者也不应该在部署或者上线阶段运行litemall_schema.sql** + ## 4. 项目 这里主要是指其他项目开发相关问题。 @@ -194,16 +265,14 @@ IDEA导入项目时,非常耗时间,或者卡断,或者一直疯狂运行 原因: -应该是litemall-admin模块的node_modules文件夹导致的。 -node_modules是litemall-admin所依赖的项目库,可能有近200M的文件。 +应该是litemall-admin模块和litemall-vue模块的node_modules文件夹导致的。 +node_modules是litemall-admin和litemall-vue模块所依赖的项目库,可能有近200M的文件。 而IDEA如果没有设置,则可能尝试对该文件夹进行解析索引,从而导致卡断。 解决方案: -先关闭IDEA,然后删除node_modules文件夹,然后重新打开IDEA,设置node_modules文件夹Excluded状态。 - -![](./pic/excluded.png) - -### 4.2 项目war打包 +1. 先关闭IDEA,然后删除litemall-admin和litemall-vue模块内的node_modules文件夹; +2. 然后分别创建空的node_modules文件夹; +3. 重新打开IDEA,分别设置litemall-admin模块和litemall-vue模块的node_modules文件夹Excluded状态。 -目前不支持,请开发者自行实践。 +![](./pics/faq/excluded.png) \ No newline at end of file diff --git a/doc/README.md b/doc/README.md index 63898eb1355fc2495f464dff6f4e8dc384cc460d..9d47cbb3bcba8cb79de871e3817c0027fe81bdca 100644 --- a/doc/README.md +++ b/doc/README.md @@ -4,7 +4,10 @@ * [更新日志](../CHANGELOG.md) * [贡献指南](../CONTRIBUTE.md) * [FAQ](./FAQ.md) +* [API](./api.md) +* [数据库](./database.md) * [1. 系统架构](./project.md) * [2. 基础系统](./platform.md) * [3. 小商场](./wxmall.md) -* [4. 管理后台](./admin.md) \ No newline at end of file +* [4. 管理后台](./admin.md) +* [5. 轻商城](./mobmall.md) \ No newline at end of file diff --git a/doc/admin.md b/doc/admin.md index c83182927e7bb354c0ea2f62ffbd28145e1228c8..04efe1b0e674e2bea66be04da09d7c4907b6354a 100644 --- a/doc/admin.md +++ b/doc/admin.md @@ -8,7 +8,7 @@ * vue-router * axios * element - * vue-element-admin 3.9.3 + * vue-element-admin 4.2.1 * 其他,见package.json * 管理后台后端, 即litemall-admin-api模块 * Spring Boot 2.x @@ -21,11 +21,6 @@ * `改善`管理员登录页面打开慢,优化速度 * `改善`地址优化,目前每一次点击都会请求后台,应该缓存已有的数据 * `改善`vue和vue-element-admin等及时更新 -* `功能`系统角色和权限 -* `功能`系统日志功能 -* `功能`系统数据字典功能 -* `功能`系统栏目管理功能 -* `功能`支持数据库备份 ## 4.1 litemall-admin-api @@ -90,9 +85,187 @@ 而如果用户采用了账号和密码的形式登录,那么后端需要把用户密码加盐。 -#### 4.1.8.3 权限管理 +#### 4.1.8.3 权限控制 -### 4.1.9 定时任务 +后端实现权限管理功能来支持一定的安全控制。 +具体细节见4.1.9。 + +### 4.1.9 权限管理 + +权限管理只完成了操作权限的功能,而数据权限的功能未完成。 + +#### 4.1.9.1 权限设计 + +权限控制在数据库层面涉及到三个表`litemall_admin`, `litemall_role`和`litemall_permission`: +* litemall_admin表中存在roleId字段,保存角色ID数组; +* litemall_role表记录角色名称和角色介绍; +* litemall_permission表记录角色所用于的权限值。 + +权限控制在后端层面通过这三个表可以构建出管理员所属的角色以及所拥有的操作权限。 +当管理员登录以后,访问一些受权限保护的后端地址时,后端会验证当前管理员的操作权限和后端地址需要的操作权限; +如果不匹配则会抛出异常,然后前端就会收到无操作权限的提示信息。 + +权限控制在前端层面可以简单地把无操作权限显示给用户即可。 +但是前端可以进一步优化完成菜单权限特性和按钮权限特性: +* 菜单权限,即管理员登录以后,前端的菜单是自动生成的; +* 按钮权限,即管理员点击菜单跳转到页面,而页面中只出现当前管理员可操作的按钮。 + +以上内容参考了网上资料和开源项目,但是实现细节方面会存在一些出入。 + +##### 4.1.9.1.1 三个表,而不是五个表 + +通常是五个表,user, role, user_role, permission, role_permission。 + +但是本项目只需要三个表: +* user_role表的关联关系,可以通过user表的roles字段完成,因此可以省略; +* permission表省略,这里可能是非常奇怪的做法,但是实际上是可行的。 + +很多开源项目的permission表是记录当前系统的所有权限,最终呈现给管理后台的使用者; +但是数据来源则是开发者或者系统用户来进行数据输入的,但是这样真的合理吗? +* 开发者开发完系统以后,需要额外在数据库中写入权限相关内容,这样存在独立两个步骤 + 可能不是很好;此外,如果系统升级,完成新的权限,那么如何添加这些权限到数据库也 + 不是很好。 +* 如果开发者设计权限页面,支持系统管理员手动添加新的权限,那样其实也不合适, + 因为系统管理员可能对系统的权限并不了解,例如url地址所需要的权限。 + 这里很多开源项目都是采取这种方法,但是实际上管理员可能根本不会理解或者使用。 + +这里本项目参考了[biu](https://github.com/CaiBaoHong/biu)项目中注解的方式, +系统的所有的权限不是通过数据库中权限表数据获取,而是通过注解自动解析生产当前系统 +所有的权限。而且因为是注解,所以开发者在开发新的权限时,只需要在代码内直接书写, +不需要再次在数据库中写入。 + +当然,这里并意味说三个表就好或者五个表就不好,开发者可以按照自己的理解来做。 + +本项目具体如何实现见下文细节。 + +##### 4.1.9.1.2 权限只有一种类型,而不是三种类型 + +在[biu](https://github.com/CaiBaoHong/biu)项目中明确了存在三种权限类型,分别是 +菜单权限元数据、按钮权限元数据、接口权限元数据。 +很明显地,前两种权限对应了前端权限,而接口权限元数据就是后端权限。 + +本项目没有采用这种理念,其原因是因为对于最终管理员而言,或者说有可能是不懂IT的 +普通管理员,前端权限页面存在三个权限给用户勾选,可能反而会造成困惑。实际上, +前端页面建议还是应该出现一种权限效果。 + +本项目对管理员而言只有一种权限,但是这个权限本身对应了菜单权限元数据、按钮权限元数据、接口权限元数据。 +因此,管理员勾选一个权限以后,后台权限即授权成功,同时前端的菜单权限和按钮权限也自动调整。 +具体实现细节见下文。 + +后端权限基于shiro来实现,相关代码见litemall-admin-api模块。 + +##### 4.1.9.2 基本配置 + +1. config子包的`ShiroConfig`引入了Shiro并配置了shirFilter、realm和sessionManager; +2. shiroFilter配置只允许少量url可以匿名访问,其他url都需要登录才能访问; +3. realm设置的是shiro子包的`AdminAuthorizingRealm`类,该类作用是认证和授权的功能; +4. sessionManager设置的是shiro子包的`AdminWebSessionManager`类,该类作用是重置会话管理器。 + 默认会话管理器是基于cookie实现会话保持,而这里是基于自定义头部实现会话保持。 + +经过以上步骤,shiro就配置正常。 +* 当管理员登录时,先认证; +* 认证成功,则授权,在后端内保存roles和permissions;同时,在响应头部写入自定义头部和sessionId; +* 认证失败,则抛出认证异常; +* 管理员再次访问页面时,shiro通过自定义头自动认证成功。 + +##### 4.1.9.3 权限校验 + +但是以上只完成授权功能,而没有完成权限校验功能。 +这里开发者应该在Controller类的方法中使用`RequiresPermissions`注解。 +例如: +``` +@RestController +@RequestMapping("/admin/ad") +public class AdminAdController { + + @RequiresPermissions("admin:ad:list") + @RequiresPermissionsDesc(menu={"推广管理" , "广告管理"}, button="查询") + @GetMapping("/list") + public Object list(String name, String content, + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer limit, + @Sort @RequestParam(defaultValue = "add_time") String sort, + @Order @RequestParam(defaultValue = "desc") String order) { + List adList = adService.querySelective(name, content, page, limit, sort, order); + int total = adService.countSelective(name, content, page, limit, sort, order); + Map data = new HashMap<>(); + data.put("total", total); + data.put("items", adList); + + return ResponseUtil.ok(data); + } +} +``` + +而具体的权限校验逻辑则由shiro自动完成: +1. 登录成功或者会话登录,shiro已经有当前用户的权限列表; +2. 访问权限保护的方法时,shiro通过`RequiresPermissions`注解得到所需操作权限列表; +3. 测试已分配的权限和操作所需权限是否一致,如果一致则可以调用方法,否则抛出无权限的异常。 + +##### 4.1.9.4 权限描述 + +在annotation子包中存在一个自定义的`RequiresPermissionsDesc`注解 +``` +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface RequiresPermissionsDesc { + String[] menu(); + String button(); +} +``` + +这里讨论一下为什么要自定义权限描述注解? +> 当后端设计好权限以后,需要向前端发送系统的权限情况,管理员可以查看系统当前所有权限以及允许为一个角色配置权限。 +> 这里就带来了实现方面的问题: +> 一些开源项目在数据库设计一个permission表,记录系统所有权限,但是4.1.9.1.1节讨论了这种方式的局限性。 +> 因此本项目采用权限注解方式,要求开发者在使用权限注解(RequiresPermissions)的地方应该同时使用权限文档注解(RequiresPermissionsDesc), +> 这样系统能够通过权限注解自动生成权限列表,向前端返回可读的信息。 + +例如: +``` +@RestController +@RequestMapping("/admin/ad") +public class AdminAdController { + + @RequiresPermissions("admin:ad:list") + @RequiresPermissionsDesc(menu={"推广管理" , "广告管理"}, button="查询") + @GetMapping("/list") + public Object list(String name, String content, + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer limit, + @Sort @RequestParam(defaultValue = "add_time") String sort, + @Order @RequestParam(defaultValue = "desc") String order) { + List adList = adService.querySelective(name, content, page, limit, sort, order); + int total = adService.countSelective(name, content, page, limit, sort, order); + Map data = new HashMap<>(); + data.put("total", total); + data.put("items", adList); + + return ResponseUtil.ok(data); + } +} +``` + + +本项目通过搜索`RequiresPermissions`注解和`RequiresPermissionsDesc`注解(见util子包的PermissionUtil类), +在内部生产了`Permission`类的集合: +``` +public class Permission { + private RequiresPermissions requiresPermissions; + private RequiresPermissionsDesc requiresPermissionsDesc; + private String api; +} +``` + +也就是说,对于一个权限,既有权限值(requiresPermissions的value),也有权限对应的操作API, +也有权限的描述内容(requiresPermissionsDesc的menu和button)。 + +前端权限所需要的一些权限内容可以从这里来间接产生,具体方式见4.2.1节。 + +当然,需要指出的是,这里利用注解方式可以不需要在数据库中保存权限信息, +但是在灵活性方面可能也会有问题。 + +### 4.1.10 定时任务 job子包存在以下定时任务: * OrderJob类 @@ -112,6 +285,118 @@ job子包存在以下定时任务: litemall-admin模块的代码基于[vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) + +#### 4.2.1 前端权限 + +需要再次明确的是,前端权限的作用仅仅是隐藏菜单或者页面按钮,优化用户使用体验, +而实际上没有真正的权限保护功能。 + +这里前端校验的思路可以参考vue-element-admin的文档: +* [路由和侧边栏](https://panjiachen.github.io/vue-element-admin-site/zh/guide/essentials/router-and-nav.html) +* [权限验证](https://panjiachen.github.io/vue-element-admin-site/zh/guide/essentials/permission.html) + +##### 4.2.1.2 角色页面 + +管理员的角色页面用于创建角色和分配权限。 + +其中点击授权按钮,则会出现系统所有的权限和当前已经分配的权限,具体代码可以见AdminRoleController的getPermissions。 +* 系统所有的权限,可以通过后端自动解析权限描述注解来获取; +* 当前已分配权限,可以通过数据库访问来获取。 + +#### 4.2.1.3 权限API + +vue-element-admin中权限校验是基于role完成的权限校验; +而其他一些开源项目中权限校验是基于permission权限值完成的权限校验; +而本项目进行了调整,基于permission URL完成权限校验。 + +首先讨论为什么基于role的权限校验不是很好: + +vue-element-admin的权限校验要求在菜单或者页面按钮中写上role信息, +这里原理是可以的。但是存在一个问题,就是角色是管理员动态创建的, +因此菜单或者页面按钮直接写死访问所需要的role,不利于后期维护升级。 + +接着讨论为什么基于permission权限值的权限校验不是很好: + +在前后端分离项目中,前端和后端应该是不同的开发者开发。如果采用权限值的方式, +可能导致前后端耦合紧密的问题。 +例如,后端开发者开发了一个API是`/admin/ad/list`,后端访问所需的权限值是`admin:ad:list`, +此时前端也需要写菜单或者页面按钮的地方写上`admin:ad:list"`。 +当然,这里原理上和操作上都是可行的。但是,这里要求前端开发者必须知道自己访问后端 +API所对应的操作权限值。因此如何以一种好的前后端分离的方式来做可能是一个问题。 + +本项目则基于permission URL的权限校验方式: + +后端权限校验逻辑仍然是权限值,而前端权限校验逻辑则是URL。 +例如,后端开发者开发了一个API是`/admin/ad/list`,后端访问所需的权限值是`admin:ad:list`, +此时前端则不需要考虑权限值,而是仅仅需要知道自己访问API的方式,也就是在 +菜单或者页面按钮的地方写上`GET /admin/ad/list`,而不是`admin:ad:list"`。 +之所以是可行的,是因为后端权限是一个(权限值,权限描述,权限访问API)构成的Permission类。 + +当然,这里也存在一定的局限性。 + +#### 4.2.1.4 菜单权限 + +见`src/route/index.js`代码 + +例如 +``` + { + path: '/promotion', + component: Layout, + redirect: 'noredirect', + alwaysShow: true, + name: 'promotionManage', + meta: { + title: '推广管理', + icon: 'chart' + }, + children: [ + { + path: 'ad', + component: () => import('@/views/promotion/ad'), + name: 'ad', + meta: { + perms: ['GET /admin/ad/list', 'POST /admin/ad/create', 'GET /admin/ad/read', 'POST /admin/ad/update', 'POST /admin/ad/delete'], + title: '广告管理', + noCache: true + } + } + } + ] + }, +``` + +在菜单的meta中的perms属性声明了当前菜单所需要的操作权限。 +如果管理员登录以后,所分配的操作权限存在至少一个,那么该菜单就会显示; +否则该菜单就会隐藏。 + + +##### 4.2.1.4 按钮权限 + +在每个页面中一些组件可以采用指令`v-permission`或者函数`checkPermission`来实现按钮权限。 + +例如,`src/views/promation/ad`页面中 +``` +
+ + + 查找 + 添加 + 导出 +
+``` + +element的`el-button`组件声明了操作权限是`GET /admin/ad/list'`。 +如果管理员登录以后,所分配的操作权限匹配,那么该按钮就会显示; +否则该按钮就会隐藏。 + +##### 4.1.1.5 权限局限性 + +前端权限这里的代码调整旨在解决一些认为不合理的地方,但是实际上也同时带来了 +一定的局限性或者限制。 + +这里列出一些可能的问题,开发者可以自己审阅或者重新设计代码。 + ## 4.3 开发新组件 本章节介绍如何开发新的管理后台功能。 diff --git a/doc/api.md b/doc/api.md new file mode 100644 index 0000000000000000000000000000000000000000..fdaa4a23a8be6ed1e532dc7cd6a964abeb49c5b4 --- /dev/null +++ b/doc/api.md @@ -0,0 +1,3405 @@ +# 前后端接口文档 + +本项目前后端接口规范和接口文档。 + +注意: +> 1. 以下API部分基于nideshop开源项目的API设计; +> 2. 以下API是参考API,可能不是很合理,欢迎开发者交流。 +> 3. 接口文档处于开发中,如果发现接口描述和接口实际不对应,欢迎PR或者报告。 + +## 1 前后端接口规范 + +### 1.1 请求格式 + +这里没有采用RESTful风格的接口,而是定义具体语义的接口。 +目前只使用`GET`和`POST`来表示请求内容和更新内容两种语义。 + +#### 1.1.1 GET请求 + + GET API_URL?params + +例如 + + GET /home/index + +或者 + + GET /goods/list?page=1&limit=10 + +#### 1.1.2 POST更新 + + POST API_URL + { + body + } + +例如 + + POST /cart/clear + +或者 + + POST /goods/star + { + id: 1 + } + +#### 1.1.3 分页请求参数 + +当GET请求后端获取数组数据时,需要传递分页参数。 + +例如 + + GET /goods/list?page=1&limit=10&sort=add_time&order=desc + +本项目的通用分页请求参数统一传递四个: + + page: 请求页码 + limit: 每一页数量 + sort: 排序字段 + order: 升序降序 + +* page, 和通常计算机概念中数组下标从0开始不同,这里的page参数应该从1开始,1即代表第一页数据; +* limit, 分页大小; +* sort, 例如"add_time"或者"id"; +* order, 只能是"desc"或者"asc"。 + +此外,这里四个参数是可选的,后端应该设置默认参数,因此即使前端不设置, +后端也会自动返回合适的对象数组响应数据。 + +注意: +> 这里的参数是需要后端支持的,在一些场景下,例如数组对象是组装而成, +> 有可能sort和order不支持。 + +讨论: +> 有些请求后端是所有数据,这里page和limit可能设置是无意义的。但是 +> 仍然建议加上两个参数,例如page=1, limit=1000。 + +也就是说,请求后端数组数据时,同一传递四个分页参数,可能是比较良好的做法。 + +### 1.2 响应格式 + + Content-Type: application/json;charset=UTF-8 + + { + body + } + + +而body是存在一定格式的json内容: + + { + errno: xxx, + errmsg: xxx, + data: {} + } + +#### 1.2.1 失败异常 + + { + errno: xxx, + errmsg: xxx + } + +* errno是错误码,具体语义见1.3节。 +* errmsg是错误信息。 + +#### 1.2.2 操作成功 + + { + errno: 0, + errmsg: "成功", + } + +#### 1.2.3 普通对象 + + { + errno: 0, + errmsg: "成功", + data: {} + } + +#### 1.2.4 数组对象 + + { + errno: 0, + errmsg: "成功", + data: { + list: [], + total: XX, + page: XX, + limit: XX, + pages: XX + } + } + +list是对象数组,total是总的数量。 + +### 1.3 错误码 + +#### 1.3.1 系统通用错误码 + +系统通用错误码包括4XX和5XX + +* 4xx,前端错误,说明前端开发者需要重新了解后端接口使用规范: + * 401,参数错误,即前端没有传递后端需要的参数; + * 402,参数值错误,即前端传递的参数值不符合后端接收范围。 + +* 5xx,后端系统错误,除501外,说明后端开发者应该继续优化代码,尽量避免返回后端系统错误码: + * 501,验证失败,即后端要求用户登录; + * 502,系统内部错误,即没有合适命名的后端内部错误; + * 503,业务不支持,即后端虽然定义了接口,但是还没有实现功能; + * 504,更新数据失效,即后端采用了乐观锁更新,而并发更新时存在数据更新失效; + * 505,更新数据失败,即后端数据库更新失败(正常情况应该更新成功)。 + +#### 1.3.2 商场业务错误码 + +* AUTH_INVALID_ACCOUNT = 700 +* AUTH_CAPTCHA_UNSUPPORT = 701 +* AUTH_CAPTCHA_FREQUENCY = 702 +* AUTH_CAPTCHA_UNMATCH = 703 +* AUTH_NAME_REGISTERED = 704 +* AUTH_MOBILE_REGISTERED = 705 +* AUTH_MOBILE_UNREGISTERED = 706 +* AUTH_INVALID_MOBILE = 707 +* AUTH_OPENID_UNACCESS = 708 +* AUTH_OPENID_BINDED = 709 +* GOODS_UNSHELVE = 710 +* GOODS_NO_STOCK = 711 +* GOODS_UNKNOWN = 712 +* GOODS_INVALID = 713 +* ORDER_UNKNOWN = 720 +* ORDER_INVALID = 721 +* ORDER_CHECKOUT_FAIL = 722 +* ORDER_CANCEL_FAIL = 723 +* ORDER_PAY_FAIL = 724 +* ORDER_INVALID_OPERATION = 725 +* ORDER_COMMENTED = 726 +* ORDER_COMMENT_EXPIRED = 727 +* GROUPON_EXPIRED = 730 +* COUPON_EXCEED_LIMIT = 740 +* COUPON_RECEIVE_FAIL= 741 +* COUPON_CODE_INVALID= 742 + +#### 1.3.3 管理后台业务错误码 + +* ADMIN_INVALID_NAME = 601 +* ADMIN_INVALID_PASSWORD = 602 +* ADMIN_NAME_EXIST = 602 +* ADMIN_ALTER_NOT_ALLOWED = 603 +* ADMIN_DELETE_NOT_ALLOWED = 604 +* ADMIN_INVALID_ACCOUNT = 605 +* GOODS_UPDATE_NOT_ALLOWED = 610 +* GOODS_NAME_EXIST = 611 +* ORDER_CONFIRM_NOT_ALLOWED = 620 +* ORDER_REFUND_FAILED = 621 +* ORDER_REPLY_EXIST = 622 +* USER_INVALID_NAME = 630 +* USER_INVALID_PASSWORD = 631 +* USER_INVALID_MOBILE = 632 +* USER_NAME_EXIST = 633 +* USER_MOBILE_EXIST = 634 +* ROLE_NAME_EXIST = 640 +* ROLE_SUPER_SUPERMISSION = 641 +* ROLE_USER_EXIST = 642 + +### 1.4 Token + +前后端采用token来验证访问权限。 + +#### 1.4.1 Header&Token + +前后端Token交换流程如下: + +1. 前端访问商场登录API或者管理后台登录API; + +2. 成功以后,前端会接收后端响应的一个token,保存在本地; + +3. 请求受保护API则,则采用自定义头部携带此token + +4. 后端检验Token,成功则返回受保护的数据。 + +#### 1.4.2 商场自定义Header + +访问受保护商场API采用自定义`X-Litemall-Token`头部 + +1. 小商城(或轻商场)前端访问小商城后端登录API`/wx/auth/login` + + POST /wx/auth/login + + { + "username": "user123", + "password": "user123" + } + +2. 成功以后,前端会接收后端响应的一个token, + + { + "errno": 0, + "data": { + "userInfo": { + "nickName": "user123", + "avatarUrl": "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif" + }, + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0aGlzIGlzIGxpdGVtYWxsIHRva2VuIiwiYXVkIjoiTUlOSUFQUCIsImlzcyI6IkxJVEVNQUxMIiwiZXhwIjoxNTU3MzI2ODUwLCJ1c2VySWQiOjEsImlhdCI6MTU1NzMxOTY1MH0.XP0TuhupV_ttQsCr1KTaPZVlTbVzVOcnq_K0kXdbri0" + }, + "errmsg": "成功" + } + +3. 请求受保护API则,则采用自定义头部携带此token + + GET http://localhost:8080/wx/address/list + X-Litemall-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0aGlzIGlzIGxpdGVtYWxsIHRva2VuIiwiYXVkIjoiTUlOSUFQUCIsImlzcyI6IkxJVEVNQUxMIiwiZXhwIjoxNTU3MzM2ODU0LCJ1c2VySWQiOjIsImlhdCI6MTU1NzMyOTY1NH0.JY1-cqOnmi-CVjFohZMqK2iAdAH4O6CKj0Cqd5tMF3M + +#### 1.4.3 管理后台自定义Header + +访问受保护管理后台API则是自定义`X-Litemall-Admin-Token`头部。 + +1. 管理后台前端访问管理后台后端登录API`/admin/auth/login` + + POST /admin/auth/login + + { + "username": "admin123", + "password": "admin123" + } + +2. 成功以后,管理后台前端会接收后端响应的一个token, + + { + "errno": 0, + "data": { + "adminInfo": { + "nickName": "admin123", + "avatar": "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif" + }, + "token": "f2dbcae8-6e25-4f8e-bc58-aa81d512c952" + }, + "errmsg": "成功" + } + +3. 请求受保护API时,则采用自定义头部携带此token + + GET http://localhost:8080/wx/address/list + X-Litemall-Admin-Token: f2dbcae8-6e25-4f8e-bc58-aa81d512c952 + +### 1.5 版本控制 + +API应该存在版本控制,以保证兼容性。 + +由于仍处于开发中,因此目前未引入版本控制。 + +### 1.6 API格式 + +这里定义一个API的格式: + +应用场景 + + xxx + +接口链接 + + xxx + +请求参数 + + xxx + +响应内容 + + xxx + +错误码 + + xxx + +### 1.7 API预览 + +接下来会分别从用户层面和管理员层面构建商场API服务和管理后台API服务。 + +商场API服务涉及 + +* 安全服务 +* 首页服务 +* 类目服务 +* 商品服务 +* 购物车服务 +* 订单服务 +* 会员服务 +* 收货地址服务 +* 品牌商服务 +* 收藏服务 +* 评论服务 +* 优惠券服务 +* 反馈服务 +* 足迹服务 +* 团购服务 +* 帮助服务 +* 搜索服务 +* 专题服务 +* 对象存储服务 + + +管理后台API服务涉及: +* 略 + +### 1.8 API测试 + +本节以GET、POST两种方式以及是否需要登录举例说明如何测试和使用本项目API。 + +开发者可以使用各种API测试命令或者工具,这里以Postman作为工具。 + +#### 1.8.1 GET 示例 + +如果一个API是GET方法,那么请求参数需要在访问链接后面: + +例如测试2.4.2节商品详情API + +![](./pics/admin/get.png) + +#### 1.8.2 GET & Token 示例 + +如果需要登录才能访问数据,则需要先向后端请求登录,得到token,然后请求时携带token。 + +例如测试2.8.1节收货地址列表API + +如果没有登录,则返回未登录信息 + +![](./pics/admin/get_no_token.png) + +因此测试这些API,需要先登录 + +![](./pics/admin/login.png) + +然后,采用自定义`X-Litemall-Token`来携带token访问商场API + +![](./pics/admin/get_with_token.png) + +注意: +> 访问受保护商场API是采用自定义`X-Litemall-Token`头部; +> 而访问受保护管理后台API则是自定义`X-Litemall-Admin-Token`头部。 + +#### 1.8.3 POST 示例 + +通常POST请求后端时,都需要先登录才能有权限上传数据,因此这里不举例说明。 + +#### 1.8.4 POST & Token 示例 + +如果需要登录才能提交数据,则需要先向后端请求登录,得到token,然后请求时携带token。 + +![](./pics/admin/post_no_token.png) + +因此测试这些API,需要先登录 + +![](./pics/admin/login.png) + +然后,采用自定义`X-Litemall-Token`来携带token访问商场API + +![](./pics/admin/get_with_token.png) + +注意: +> 访问受保护商场API是采用自定义`X-Litemall-Token`头部; +> 而访问受保护管理后台API则是自定义`X-Litemall-Admin-Token`头部。 + +### 1.9 API保护 + +为了保护API不被滥用,通常API需要引入保护机制,例如OAuth2。 + +本项目暂时无保护机制,因此实际上一旦开发者知道服务器,就很容易访问API。 + +### 1.10 API局限性 + +当前API还存在一些问题,后面需要继续优化和完善。 + +* 无意义的通用字段 + +* 团购API完善 + +### 1.11 Not Like Swagger + +本项目不是很接受Swagger,基于以下考虑: + +* 前后端中立 + +在前后端分离项目中,依赖后端的Swagger来生成项目API似乎不是很理想, +这实际上把项目API设计工作过多地压在后端,同时前端也被迫依赖后端, +因为后端如果没有写好文档注解,前端不可能了解API的输入输出。 + +可能一种合理的做法应该这样: +项目初期前后端一起完成一个完整基本的API文档,定义好交互规范和具体API的行为,然后双方同时开始开发工作; +某个开发阶段,前端需要更多的数据或者新的API支持,此时也不需要立即联系后端(除非API产生破坏性变更), +而是暂时基于mock和自定义mock数据独立开发;之后,在合适阶段(可以按照项目规定,例如三天或者周五), +前后端再次沟通API的变更,后端了解需求后则可以接受、拒绝或者调整,当然变更必须要在API文档中体现和更新; +下一个开发阶段,前端和后端能够再次基于最新的API文档来调整自己代码。 +最后项目测试时,只要前端对照API文档,后端也是对照API文档。 + +* 后端代码简洁 + +如果使用Swagger,为了得到完整的文档,需要在每一个方法前面加上多个文档注解,文档越是详尽,则注解越多, +造成代码不是很简洁。特别是具备代码属性的注解和Swagger文档注解混杂在一起,可能不是很好。 + +当然,本项目也简单地配置了Swagger(见`WxSwagger2Configuration`和`AdminSwagger2Configuration`), +* 在线Swagger文档链接:http://122.51.199.160:8080/swagger-ui.html +* 本地Swagger文档链接:http://localhost:8080/swagger-ui.html + +此外,也使用了swagger-bootstrap-ui对Swagger进一步增强了使用效果。 +* 在线swagger-bootstrap-ui文档链接:http://122.51.199.160:8080/doc.html +* 本地swagger-bootstrap-ui文档链接:http://localhost:8080/doc.html + +当然正如上文讨论,本项目不是很接受Swagger的理念,所以后端没有使用Swagger的相关文档注解, +这也导致了Swagger接口文档的不具可读性。如果开发者需要,可以自行在后端补充Swagger注解。 + +需要注意的是: +> 这里接口默认是公开的,因此项目一旦需要上线,请及时删除swagger和swagger-bootstrap-ui依赖和配置, +> 或者采取其他手段,防止接口对外暴露造成**安全隐患**。 + +例如 +``` +swagger: + production: false +``` + +## 2 商城API服务 + +### 2.1 安全服务 + +#### 2.1.1 小程序微信登录 + +应用场景 + + 小程序环境下微信登录。 + +接口链接 + + xxx + +请求参数 + + xxx + +响应内容 + + xxx + +错误码 + + xxx + +#### 2.1.2 账号登录 + +应用场景 + + 基于用户名和密码的账号登录 + +接口链接 + + POST /wx/auth/login + +请求参数 + + { + "username": "user123", + "password": "user123" + } + +响应内容 + + { + "errno": 0, + "data": { + "userInfo": { + "nickName": "user123", + "avatarUrl": "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif" + }, + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0aGlzIGlzIGxpdGVtYWxsIHRva2VuIiwiYXVkIjoiTUlOSUFQUCIsImlzcyI6IkxJVEVNQUxMIiwiZXhwIjoxNTU3MzI2ODUwLCJ1c2VySWQiOjEsImlhdCI6MTU1NzMxOTY1MH0.XP0TuhupV_ttQsCr1KTaPZVlTbVzVOcnq_K0kXdbri0" + }, + "errmsg": "成功" + } + +错误码 + + 略 + +#### 2.1.3 注册 + +应用场景 + + xxx + +接口链接 + + xxx + +请求参数 + + xxx + +响应内容 + + xxx + +错误码 + + xxx + +#### 2.1.4 退出 + +应用场景 + + 账号退出 + +接口链接 + + POST /wx/auth/logout + +请求参数 + + { + "username": "user123", + "password": "user123" + } + +响应内容 + + { + "errno": 0, + "errmsg": "成功" + } + +错误码 + + 略 + +#### 2.1.5 注册验证码 + +应用场景 + + 用户未登录情况下,请求后端发送注册验证码用于注册。 + +接口链接 + + xxx + +请求参数 + + xxx + +响应内容 + + xxx + +错误码 + + xxx + +#### 2.1.6 操作验证码 + +应用场景 + + 用户已登录情况下,请求后端发送操作验证码用于相关操作。 + +接口链接 + + xxx + +请求参数 + + xxx + +响应内容 + + xxx + +错误码 + + xxx + +#### 2.1.7 账号密码修改 + +应用场景 + + 账号密码修改 + +接口链接 + + xxx + +请求参数 + + xxx + +响应内容 + + xxx + +错误码 + + xxx + +#### 2.1.8 微信手机号码绑定 + +应用场景 + + 微信手机号码绑定,仅用于小程序环境。 + +接口链接 + + xxx + +请求参数 + + xxx + +响应内容 + + xxx + +错误码 + + xxx + +#### 2.1.9 手机号码修改 + +应用场景 + + 手机号码修改 + +接口链接 + + xxx + +请求参数 + + xxx + +响应内容 + + xxx + +错误码 + + xxx + +#### 2.1.10 账号信息 + +应用场景 + + 账号信息 + +接口链接 + + GET /wx/auth/info + +请求参数 + + 无 + +响应内容 + + { + "errno": 0, + "data": { + "gender": 1, + "nickName": "user123", + "mobile": "", + "avatar": "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif" + }, + "errmsg": "成功" + } + +错误码 + + 略 + +#### 2.1.11 账号信息更新 + +应用场景 + + 账号信息更新。 + +接口链接 + + POST /wx/auth/profile + +请求参数 + + { + "gender": 1, + "nickName": "user123", + "avatar": "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif" + } + +响应内容 + + { + "errno": 0, + "errmsg": "成功" + } + +错误码 + + 略 + +### 2.2 首页服务 + +#### 2.2.1 首页数据 + +应用场景 + + 首页数据 + +接口链接 + + GET /wx/home/index + +请求参数 + + 无 + +响应内容 + + { + "errno": 0, + "data": { + "newGoodsList": [ + { + "id": 1181000, + "name": "母亲节礼物-舒适安睡组合", + "brief": "安心舒适是最好的礼物", + "picUrl": "http://yanxuan.nosdn.127.net/1f67b1970ee20fd572b7202da0ff705d.png", + "isNew": true, + "isHot": false, + "counterPrice": 2618.00, + "retailPrice": 2598.00 + }, + { + "id": 1116011, + "name": "蔓越莓曲奇 200克", + "brief": "酥脆奶香,甜酸回味", + "picUrl": "http://yanxuan.nosdn.127.net/767b370d07f3973500db54900bcbd2a7.png", + "isNew": true, + "isHot": true, + "counterPrice": 56.00, + "retailPrice": 36.00 + }, + { + "id": 1127047, + "name": "趣味粉彩系列笔记本", + "brief": "粉彩色泽,记录生活", + "picUrl": "http://yanxuan.nosdn.127.net/6c03ca93d8fe404faa266ea86f3f1e43.png", + "isNew": true, + "isHot": false, + "counterPrice": 49.00, + "retailPrice": 29.00 + }, + { + "id": 1135002, + "name": "宫廷奢华真丝四件套", + "brief": "100%桑蚕丝,丝滑润肤", + "picUrl": "http://yanxuan.nosdn.127.net/45548f26cfd0c7c41e0afc3709d48286.png", + "isNew": true, + "isHot": false, + "counterPrice": 2619.00, + "retailPrice": 2599.00 + }, + { + "id": 1152161, + "name": "竹语丝麻印花四件套", + "brief": "3重透气,清爽柔滑", + "picUrl": "http://yanxuan.nosdn.127.net/977401e75113f7c8334c4fb5b4bf6215.png", + "isNew": true, + "isHot": false, + "counterPrice": 479.00, + "retailPrice": 459.00 + }, + { + "id": 1166008, + "name": "Carat钻石 不粘厨具组合", + "brief": "钻石涂层,不粘锅锅具组", + "picUrl": "http://yanxuan.nosdn.127.net/615a16e899e01efb780c488df4233f48.png", + "isNew": true, + "isHot": false, + "counterPrice": 479.00, + "retailPrice": 459.00 + } + ], + "couponList": [ + { + "id": 2, + "name": "限时满减券", + "desc": "全场通用", + "tag": "无限制", + "discount": 10.00, + "min": 99.00, + "days": 10 + } + ], + "channel": [ + { + "id": 1005000, + "name": "居家", + "iconUrl": "http://yanxuan.nosdn.127.net/a45c2c262a476fea0b9fc684fed91ef5.png" + }, + { + "id": 1005001, + "name": "餐厨", + "iconUrl": "http://yanxuan.nosdn.127.net/ad8b00d084cb7d0958998edb5fee9c0a.png" + }, + { + "id": 1005002, + "name": "饮食", + "iconUrl": "http://yanxuan.nosdn.127.net/c9280327a3fd2374c000f6bf52dff6eb.png" + }, + { + "id": 1008000, + "name": "配件", + "iconUrl": "http://yanxuan.nosdn.127.net/11abb11c4cfdee59abfb6d16caca4c6a.png" + }, + { + "id": 1010000, + "name": "服装", + "iconUrl": "http://yanxuan.nosdn.127.net/28a685c96f91584e7e4876f1397767db.png" + }, + { + "id": 1011000, + "name": "婴童", + "iconUrl": "http://yanxuan.nosdn.127.net/1ba9967b8de1ac50fad21774a4494f5d.png" + }, + { + "id": 1012000, + "name": "杂货", + "iconUrl": "http://yanxuan.nosdn.127.net/c2a3d6349e72c35931fe3b5bcd0966be.png" + }, + { + "id": 1013001, + "name": "洗护", + "iconUrl": "http://yanxuan.nosdn.127.net/9fe068776b6b1fca13053d68e9c0a83f.png" + }, + { + "id": 1019000, + "name": "志趣", + "iconUrl": "http://yanxuan.nosdn.127.net/7093cfecb9dde1dd3eaf459623df4071.png" + } + ], + "grouponList": [ + { + "id": 1109008, + "name": "云端沙发组合", + "brief": "MUJI供应商携手打造", + "picUrl": "http://yanxuan.nosdn.127.net/c5be2604c0e4186a4e7079feeb742cee.png", + "counterPrice": 4019.00, + "retailPrice": 3999.00, + "grouponPrice": 3949.00, + "grouponDiscount": 50, + "grouponMember": 5 + }, + { + "id": 1039051, + "name": "多功能午睡枕", + "brief": "放松自在的午后时光", + "picUrl": "http://yanxuan.nosdn.127.net/c8ca0600fa7ba11ca8be6a3173dd38c9.png", + "counterPrice": 99.00, + "retailPrice": 79.00, + "grouponPrice": 59.00, + "grouponDiscount": 20, + "grouponMember": 20 + } + ], + "banner": [ + { + "id": 1, + "name": "合作 谁是你的菜", + "link": "", + "url": "http://yanxuan.nosdn.127.net/65091eebc48899298171c2eb6696fe27.jpg", + "position": 1, + "content": "合作 谁是你的菜", + "enabled": true, + "addTime": "2018-02-01 00:00:00", + "updateTime": "2018-02-01 00:00:00", + "deleted": false + }, + { + "id": 2, + "name": "活动 美食节", + "link": "", + "url": "http://yanxuan.nosdn.127.net/bff2e49136fcef1fd829f5036e07f116.jpg", + "position": 1, + "content": "活动 美食节", + "enabled": true, + "addTime": "2018-02-01 00:00:00", + "updateTime": "2018-02-01 00:00:00", + "deleted": false + }, + { + "id": 3, + "name": "活动 母亲节", + "link": "", + "url": "http://yanxuan.nosdn.127.net/8e50c65fda145e6dd1bf4fb7ee0fcecc.jpg", + "position": 1, + "content": "活动 母亲节5", + "enabled": true, + "addTime": "2018-02-01 00:00:00", + "updateTime": "2018-02-01 00:00:00", + "deleted": false + } + ], + "brandList": [ + { + "id": 1001000, + "name": "MUJI制造商", + "desc": "严选精选了MUJI制造商和生产原料,\n用几乎零利润的价格,剔除品牌溢价,\n让用户享受原品牌的品质生活。", + "picUrl": "http://yanxuan.nosdn.127.net/1541445967645114dd75f6b0edc4762d.png", + "floorPrice": 12.90 + }, + { + "id": 1001002, + "name": "内野制造商", + "desc": "严选从世界各地挑选毛巾,最终选择了为日本内野代工的工厂,追求毛巾的柔软度与功能性。品质比肩商场几百元的毛巾。", + "picUrl": "http://yanxuan.nosdn.127.net/8ca3ce091504f8aa1fba3fdbb7a6e351.png", + "floorPrice": 29.00 + }, + { + "id": 1001003, + "name": "Adidas制造商", + "desc": "严选找到为Adidas等品牌制造商,\n选取优质原材料,与厂方一起设计,\n为你提供好的理想的运动装备。", + "picUrl": "http://yanxuan.nosdn.127.net/335334d0deaff6dc3376334822ab3a2f.png", + "floorPrice": 49.00 + }, + { + "id": 1001007, + "name": "优衣库制造商", + "desc": "严选找到日本知名服装UNIQLO的制造商,\n选取优质长绒棉和精梳工艺,\n与厂方一起设计,为你提供理想的棉袜。", + "picUrl": "http://yanxuan.nosdn.127.net/0d72832e37e7e3ea391b519abbbc95a3.png", + "floorPrice": 29.00 + } + ], + "hotGoodsList": [ + { + "id": 1152008, + "name": "魔兽世界 部落 护腕 一只", + "brief": "吸汗、舒适、弹性、防护、耐用", + "picUrl": "http://yanxuan.nosdn.127.net/203cb83d93606865e3ddde57b69b9e9a.png", + "isNew": false, + "isHot": true, + "counterPrice": 49.00, + "retailPrice": 29.00 + }, + { + "id": 1152009, + "name": "魔兽世界 联盟 护腕 一只", + "brief": "吸汗、舒适、弹性、防护、耐用", + "picUrl": "http://yanxuan.nosdn.127.net/ae6d41117717387b82dcaf1dfce0cd97.png", + "isNew": false, + "isHot": true, + "counterPrice": 49.00, + "retailPrice": 29.00 + }, + { + "id": 1152031, + "name": "魔兽世界-伊利丹颈枕眼罩套装", + "brief": "差旅好伴侣", + "picUrl": "http://yanxuan.nosdn.127.net/fd6e78a397bd9e9804116a36f0270b0a.png", + "isNew": false, + "isHot": true, + "counterPrice": 119.00, + "retailPrice": 99.00 + }, + { + "id": 1022000, + "name": "意式毛线绣球四件套", + "brief": "浪漫毛线绣球,简约而不简单", + "picUrl": "http://yanxuan.nosdn.127.net/5350e35e6f22165f38928f3c2c52ac57.png", + "isNew": false, + "isHot": true, + "counterPrice": 319.00, + "retailPrice": 299.00 + }, + { + "id": 1011004, + "name": "色织精梳AB纱格纹空调被", + "brief": "加大加厚,双色精彩", + "picUrl": "http://yanxuan.nosdn.127.net/0984c9388a2c3fd2335779da904be393.png", + "isNew": false, + "isHot": true, + "counterPrice": 219.00, + "retailPrice": 199.00 + }, + { + "id": 1084003, + "name": "纯棉美式绞花针织盖毯", + "brief": "美式提花,温暖舒适", + "picUrl": "http://yanxuan.nosdn.127.net/cf40c167e7054fe184d49f19121f63c7.png", + "isNew": false, + "isHot": true, + "counterPrice": 219.00, + "retailPrice": 199.00 + } + ], + "topicList": [ + { + "id": 264, + "title": "设计师们推荐的应季好物", + "subtitle": "原创设计春款系列上新", + "price": 29.90, + "readCount": "77.7k", + "picUrl": "https://yanxuan.nosdn.127.net/14918201901050274.jpg" + }, + { + "id": 266, + "title": "一条丝巾就能提升时髦度", + "subtitle": "不知道大家对去年G20时,严选与国礼制造商一起推出的《凤凰于飞》等几款丝巾是否还...", + "price": 0.00, + "readCount": "35.0k", + "picUrl": "https://yanxuan.nosdn.127.net/14919007135160213.jpg" + }, + { + "id": 268, + "title": "米饭好吃的秘诀:会呼吸的锅", + "subtitle": "今年1月份,我们联系到了日本伊贺地区的长谷园,那里有着180年伊贺烧历史的窑厂。...", + "price": 0.00, + "readCount": "33.3k", + "picUrl": "https://yanxuan.nosdn.127.net/14920623353130483.jpg" + }, + { + "id": 271, + "title": "选式新懒人", + "subtitle": "懒出格调,懒出好生活。", + "price": 15.00, + "readCount": "57.7k", + "picUrl": "https://yanxuan.nosdn.127.net/14924199099661697.jpg" + } + ], + "floorGoodsList": [ + { + "name": "居家", + "goodsList": [ + { + "id": 1110016, + "name": "天然硅胶宠物除毛按摩刷", + "brief": "顺滑平面,猫狗通用,去除死毛", + "picUrl": "http://yanxuan.nosdn.127.net/3bd73b7279a83d1cbb50c0e45778e6d6.png", + "isNew": false, + "isHot": false, + "counterPrice": 59.00, + "retailPrice": 39.00 + }, + { + "id": 1110017, + "name": "耐用材料猫咪护理清洁套装", + "brief": "精致钢材,美容清洁", + "picUrl": "http://yanxuan.nosdn.127.net/534231583f82572398ec84bad425cdaf.png", + "isNew": false, + "isHot": false, + "counterPrice": 99.00, + "retailPrice": 79.00 + }, + { + "id": 1110018, + "name": "耐用狗狗清洁美容护理套装", + "brief": "精致钢材,耐咬美容", + "picUrl": "http://yanxuan.nosdn.127.net/d93aa5d6e7a296101cf4cb72613aeda6.png", + "isNew": false, + "isHot": false, + "counterPrice": 99.00, + "retailPrice": 79.00 + }, + { + "id": 1110019, + "name": "宠物合金钢安全除菌指甲护理组合", + "brief": "猫狗皆可用,保护家具", + "picUrl": "http://yanxuan.nosdn.127.net/1e7e392b6fc9da99dc112197b7444eec.png", + "isNew": false, + "isHot": false, + "counterPrice": 89.00, + "retailPrice": 69.00 + } + ], + "id": 1005000 + }, + { + "name": "餐厨", + "goodsList": [ + { + "id": 1023003, + "name": "100年传世珐琅锅 全家系列", + "brief": "特质铸铁,大容量全家共享", + "picUrl": "http://yanxuan.nosdn.127.net/c39d54c06a71b4b61b6092a0d31f2335.png", + "isNew": false, + "isHot": false, + "counterPrice": 418.00, + "retailPrice": 398.00 + }, + { + "id": 1073008, + "name": "铸铁珐琅牛排煎锅", + "brief": "沥油隔水,煎出外焦里嫩", + "picUrl": "http://yanxuan.nosdn.127.net/619e46411ccd62e5c0f16692ee1a85a0.png", + "isNew": false, + "isHot": false, + "counterPrice": 169.00, + "retailPrice": 149.00 + }, + { + "id": 1051000, + "name": "Carat钻石炒锅30cm", + "brief": "安全涂层,轻便无烟", + "picUrl": "http://yanxuan.nosdn.127.net/e564410546a11ddceb5a82bfce8da43d.png", + "isNew": false, + "isHot": false, + "counterPrice": 200.00, + "retailPrice": 180.00 + }, + { + "id": 1051001, + "name": "Carat钻石煎锅28cm", + "brief": "耐磨涂层,导热迅速", + "picUrl": "http://yanxuan.nosdn.127.net/f53ed57d9e23fda7e24dfd0e0a50c5d1.png", + "isNew": false, + "isHot": false, + "counterPrice": 179.00, + "retailPrice": 159.00 + } + ], + "id": 1005001 + }, + { + "name": "饮食", + "goodsList": [ + { + "id": 1045000, + "name": "绿茶蛋黄酥 200克/4枚入", + "brief": "香甜茶食,果腹优选", + "picUrl": "http://yanxuan.nosdn.127.net/b2adc3fd9b84a289a1be03e8ee400e61.png", + "isNew": false, + "isHot": false, + "counterPrice": 48.00, + "retailPrice": 28.00 + }, + { + "id": 1116011, + "name": "蔓越莓曲奇 200克", + "brief": "酥脆奶香,甜酸回味", + "picUrl": "http://yanxuan.nosdn.127.net/767b370d07f3973500db54900bcbd2a7.png", + "isNew": true, + "isHot": true, + "counterPrice": 56.00, + "retailPrice": 36.00 + }, + { + "id": 1070000, + "name": "星云酥 180克/3颗", + "brief": "酥饼界的小仙女", + "picUrl": "http://yanxuan.nosdn.127.net/8392725765cdd57fdae3f173877f4bda.png", + "isNew": false, + "isHot": false, + "counterPrice": 46.00, + "retailPrice": 26.00 + }, + { + "id": 1155015, + "name": "绿豆糕 80克(4枚入)", + "brief": "细腻松软,入口绵柔", + "picUrl": "http://yanxuan.nosdn.127.net/66b9f1638c0517d179262f14ed1345f9.png", + "isNew": true, + "isHot": false, + "counterPrice": 32.90, + "retailPrice": 12.90 + } + ], + "id": 1005002 + }, + { + "name": "配件", + "goodsList": [ + { + "id": 1085019, + "name": "20寸 纯PC“铝框”(非全铝)登机箱", + "brief": "铝质包角,牢固抗摔", + "picUrl": "http://yanxuan.nosdn.127.net/65c955a7a98e84d44ca30bb88a591eac.png", + "isNew": false, + "isHot": false, + "counterPrice": 369.00, + "retailPrice": 349.00 + }, + { + "id": 1086052, + "name": "20寸 铝镁合金登机箱", + "brief": "时尚金属箱,奢品质感", + "picUrl": "http://yanxuan.nosdn.127.net/93171a281c4ed272c007a050816e6f6c.png", + "isNew": false, + "isHot": false, + "counterPrice": 879.00, + "retailPrice": 859.00 + }, + { + "id": 1152101, + "name": "魔兽世界 部落 奥格瑞玛 拉杆箱 可登机", + "brief": "18寸,可携带登机", + "picUrl": "http://yanxuan.nosdn.127.net/c1c62211a17b71a634fa0c705d11fb42.png", + "isNew": false, + "isHot": true, + "counterPrice": 908.00, + "retailPrice": 888.00 + }, + { + "id": 1114011, + "name": "104升 纯PC拉链斜纹拉杆箱", + "brief": "104升的体积,90升的价格", + "picUrl": "http://yanxuan.nosdn.127.net/196b5ce11930b4eadaec563cb0406634.png", + "isNew": false, + "isHot": false, + "counterPrice": 319.00, + "retailPrice": 299.00 + } + ], + "id": 1008000 + } + ] + }, + "errmsg": "成功" + } + +错误码 + + 无 + +### 2.3 类目服务 + +### 2.4 商品服务 + +#### 2.4.1 商品列表 + +应用场景 + + 商品列表 + +接口链接 + + GET /wx/goods/list + +请求参数 + + isNew: 是否新品,true或者false + isHot: 是否热卖商品,true或者false + keyword: 关键字,如果设置则查询是否匹配关键字 + brandId: 品牌商ID,如果设置则查询品牌商所属商品 + categoryId: 商品分类ID,如果设置则查询分类所属商品 + page: 请求页码 + limit: 每一页数量 + sort: 排序字段 + order: 升序降序 + +响应内容 + + { + "errno": 0, + "data": { + "total": 11, + "pages": 6, + "limit": 2, + "page": 1, + "list": [ + { + "id": 1181000, + "name": "母亲节礼物-舒适安睡组合", + "brief": "安心舒适是最好的礼物", + "picUrl": "http://yanxuan.nosdn.127.net/1f67b1970ee20fd572b7202da0ff705d.png", + "isNew": true, + "isHot": false, + "counterPrice": 2618.00, + "retailPrice": 2598.00 + }, + { + "id": 1116011, + "name": "蔓越莓曲奇 200克", + "brief": "酥脆奶香,甜酸回味", + "picUrl": "http://yanxuan.nosdn.127.net/767b370d07f3973500db54900bcbd2a7.png", + "isNew": true, + "isHot": true, + "counterPrice": 56.00, + "retailPrice": 36.00 + } + ], + "filterCategoryList": [ + { + "id": 1005007, + "name": "锅具", + "keywords": "", + "desc": "一口好锅,炖煮生活一日三餐", + "pid": 1005001, + "iconUrl": "http://yanxuan.nosdn.127.net/4aab4598017b5749e3b63309d25e9f6b.png", + "picUrl": "http://yanxuan.nosdn.127.net/d2db0d1d0622c621a8aa5a7c06b0fc6d.png", + "level": "L2", + "sortOrder": 1, + "addTime": "2018-02-01 00:00:00", + "updateTime": "2018-02-01 00:00:00", + "deleted": false + }, + { + "id": 1008002, + "name": "布艺软装", + "keywords": "", + "desc": "各种风格软装装点你的家", + "pid": 1005000, + "iconUrl": "http://yanxuan.nosdn.127.net/8bbcd7de60a678846664af998f57e71c.png", + "picUrl": "http://yanxuan.nosdn.127.net/2e2fb4f2856a021bbcd1b4c8400f2b06.png", + "level": "L2", + "sortOrder": 6, + "addTime": "2018-02-01 00:00:00", + "updateTime": "2018-02-01 00:00:00", + "deleted": false + }, + { + "id": 1008008, + "name": "被枕", + "keywords": "", + "desc": "守护你的睡眠时光", + "pid": 1005000, + "iconUrl": "http://yanxuan.nosdn.127.net/927bc33f7ae2895dd6c11cf91f5e3228.png", + "picUrl": "http://yanxuan.nosdn.127.net/b43ef7cececebe6292d2f7f590522e05.png", + "level": "L2", + "sortOrder": 2, + "addTime": "2018-02-01 00:00:00", + "updateTime": "2018-02-01 00:00:00", + "deleted": false + }, + { + "id": 1008009, + "name": "床品件套", + "keywords": "", + "desc": "MUJI等品牌制造商出品", + "pid": 1005000, + "iconUrl": "http://yanxuan.nosdn.127.net/243e5bf327a87217ad1f54592f0176ec.png", + "picUrl": "http://yanxuan.nosdn.127.net/81f671bd36bce05d5f57827e5c88dd1b.png", + "level": "L2", + "sortOrder": 4, + "addTime": "2018-02-01 00:00:00", + "updateTime": "2018-02-01 00:00:00", + "deleted": false + }, + { + "id": 1008015, + "name": "糕点", + "keywords": "", + "desc": "四季糕点,用心烘焙", + "pid": 1005002, + "iconUrl": "http://yanxuan.nosdn.127.net/93168242df456b5f7bf3c89653b3db76.png", + "picUrl": "http://yanxuan.nosdn.127.net/66ea1d6ad602a8e441af7cada93bdc7a.png", + "level": "L2", + "sortOrder": 1, + "addTime": "2018-02-01 00:00:00", + "updateTime": "2018-02-01 00:00:00", + "deleted": false + }, + { + "id": 1012003, + "name": "文具", + "keywords": "", + "desc": "找回书写的力量", + "pid": 1012000, + "iconUrl": "http://yanxuan.nosdn.127.net/e1743239e41ca9af76875aedc73be7f0.png", + "picUrl": "http://yanxuan.nosdn.127.net/e074795f61a83292d0f20eb7d124e2ac.png", + "level": "L2", + "sortOrder": 1, + "addTime": "2018-02-01 00:00:00", + "updateTime": "2018-02-01 00:00:00", + "deleted": false + }, + { + "id": 1020003, + "name": "服饰", + "keywords": "", + "desc": "萌宝穿搭,柔软舒适触感", + "pid": 1011000, + "iconUrl": "http://yanxuan.nosdn.127.net/4e50f3c4e4d0a64cd0ad14cfc0b6bd17.png", + "picUrl": "http://yanxuan.nosdn.127.net/004f5f96df4aeb0645abbd70c0637239.png", + "level": "L2", + "sortOrder": 1, + "addTime": "2018-02-01 00:00:00", + "updateTime": "2018-02-01 00:00:00", + "deleted": false + } + ] + }, + "errmsg": "成功" + } + +错误码 + + 略 + + +#### 2.4.2 商品详情 + +应用场景 + + 商品详情 + +接口链接 + + GET /wx/goods/detail + +请求参数 + + id: 商品ID,例如id=1152008 + +响应内容 + + { + "errno": 0, + "data": { + "specificationList": [ + { + "name": "规格", + "valueList": [ + { + "id": 231, + "goodsId": 1152008, + "specification": "规格", + "value": "标准", + "picUrl": "", + "addTime": "2018-02-01 00:00:00", + "updateTime": "2018-02-01 00:00:00", + "deleted": false + } + ] + } + ], + "groupon": [], + "issue": [ + { + "id": 1, + "question": "购买运费如何收取?", + "answer": "单笔订单金额(不含运费)满88元免邮费;不满88元,每单收取10元运费。\n(港澳台地区需满", + "addTime": "2018-02-01 00:00:00", + "updateTime": "2018-02-01 00:00:00", + "deleted": false + }, + { + "id": 2, + "question": "使用什么快递发货?", + "answer": "严选默认使用顺丰快递发货(个别商品使用其他快递),配送范围覆盖全国大部分地区(港澳台地区除", + "addTime": "2018-02-01 00:00:00", + "updateTime": "2018-02-01 00:00:00", + "deleted": false + }, + { + "id": 3, + "question": "如何申请退货?", + "answer": "1.自收到商品之日起30日内,顾客可申请无忧退货,退款将原路返还,不同的银行处理时间不同,", + "addTime": "2018-02-01 00:00:00", + "updateTime": "2018-02-01 00:00:00", + "deleted": false + }, + { + "id": 4, + "question": "如何开具发票?", + "answer": "1.如需开具普通发票,请在下单时选择“我要开发票”并填写相关信息(APP仅限2.4.0及以", + "addTime": "2018-02-01 00:00:00", + "updateTime": "2018-02-01 00:00:00", + "deleted": false + } + ], + "userHasCollect": 0, + "shareImage": "", + "comment": { + "data": [], + "count": 0 + }, + "attribute": [], + "brand": {}, + "productList": [ + { + "id": 232, + "goodsId": 1152008, + "specifications": [ + "标准" + ], + "price": 29.00, + "number": 100, + "url": "http://yanxuan.nosdn.127.net/203cb83d93606865e3ddde57b69b9e9a.png", + "addTime": "2018-02-01 00:00:00", + "updateTime": "2018-02-01 00:00:00", + "deleted": false + } + ], + "info": { + "id": 1152008, + "goodsSn": "1152008", + "name": "魔兽世界 部落 护腕 一只", + "categoryId": 1032000, + "brandId": 0, + "gallery": [ + "http://yanxuan.nosdn.127.net/46bcddbc57e70bf5f36bdff9c9195c65.png", + "http://yanxuan.nosdn.127.net/46bcddbc57e70bf5f36bdff9c9195c65.png", + "http://yanxuan.nosdn.127.net/46bcddbc57e70bf5f36bdff9c9195c65.png", + "http://yanxuan.nosdn.127.net/46bcddbc57e70bf5f36bdff9c9195c65.png", + "http://yanxuan.nosdn.127.net/46bcddbc57e70bf5f36bdff9c9195c65.png" + ], + "keywords": "", + "brief": "吸汗、舒适、弹性、防护、耐用", + "isOnSale": true, + "sortOrder": 7, + "picUrl": "http://yanxuan.nosdn.127.net/203cb83d93606865e3ddde57b69b9e9a.png", + "shareUrl": "", + "isNew": false, + "isHot": true, + "unit": "件", + "counterPrice": 49.00, + "retailPrice": 29.00, + "addTime": "2018-02-01 00:00:00", + "updateTime": "2018-02-01 00:00:00", + "deleted": false, + "detail": "" + } + }, + "errmsg": "成功" + } + +错误码 + + 略 + + +#### 2.4.3 商品推荐 + +应用场景 + + 针对某个商品推荐其他商品 + +接口链接 + + GET /wx/goods/related + +请求参数 + + id: 商品ID,例如id=1152008 + page: 请求页码 + limit: 每一页数量 + sort: 排序字段 + order: 升序降序 + +响应内容 + + { + "errno": 0, + "data": { + "total": 8, + "pages": 2, + "limit": 6, + "page": 1, + "list": [ + { + "id": 1152004, + "name": "魔兽世界 蛋盾包 双肩包", + "brief": "伊利丹掉落,挤地铁神器", + "picUrl": "http://yanxuan.nosdn.127.net/8c93cef435d888bd79833777df1cd0c2.png", + "isNew": false, + "isHot": false, + "counterPrice": 419.00, + "retailPrice": 399.00 + }, + { + "id": 1152008, + "name": "魔兽世界 部落 护腕 一只", + "brief": "吸汗、舒适、弹性、防护、耐用", + "picUrl": "http://yanxuan.nosdn.127.net/203cb83d93606865e3ddde57b69b9e9a.png", + "isNew": false, + "isHot": true, + "counterPrice": 49.00, + "retailPrice": 29.00 + }, + { + "id": 1152009, + "name": "魔兽世界 联盟 护腕 一只", + "brief": "吸汗、舒适、弹性、防护、耐用", + "picUrl": "http://yanxuan.nosdn.127.net/ae6d41117717387b82dcaf1dfce0cd97.png", + "isNew": false, + "isHot": true, + "counterPrice": 49.00, + "retailPrice": 29.00 + }, + { + "id": 1152031, + "name": "魔兽世界-伊利丹颈枕眼罩套装", + "brief": "差旅好伴侣", + "picUrl": "http://yanxuan.nosdn.127.net/fd6e78a397bd9e9804116a36f0270b0a.png", + "isNew": false, + "isHot": true, + "counterPrice": 119.00, + "retailPrice": 99.00 + }, + { + "id": 1152095, + "name": "魔兽世界 联盟·暴风城 堡垒收纳盒", + "brief": "桌面整理神器", + "picUrl": "http://yanxuan.nosdn.127.net/c86b49f635fa141decebabbd0966a6ef.png", + "isNew": false, + "isHot": false, + "counterPrice": 519.00, + "retailPrice": 499.00 + }, + { + "id": 1152097, + "name": "魔兽世界 雷霆之怒逐风者的祝福之剑 雨伞", + "brief": "炫酷装备,可以背的雨伞", + "picUrl": "http://yanxuan.nosdn.127.net/532836444ae5eaec40b5810ca4f9b1e6.png", + "isNew": false, + "isHot": false, + "counterPrice": 419.00, + "retailPrice": 399.00 + } + ] + }, + "errmsg": "成功" + } + +错误码 + + 略 + +#### 2.4.4 商品分类 + +应用场景 + + 针对某个商品推荐其他商品 + +接口链接 + + GET /wx/goods/related + +请求参数 + + +响应内容 + +错误码 + +#### 2.4.5 在售商品总数 + +应用场景 + + 在售商品总数 + +接口链接 + + GET /wx/goods/count + +请求参数 + + 无 + +响应内容 + + { + "errno": 0, + "data": 238, + "errmsg": "成功" + } + +错误码 + + 无 + +### 2.5 购物车服务 + +#### 2.5.1 用户购物车 + +应用场景 + + 用户购物车 + +接口链接 + + +请求参数 + + 无 + +响应内容 + + +错误码 + + 略 + +### 2.6 订单服务 + +#### 2.6.1 订单列表 + +应用场景 + + 订单列表 + +接口链接 + + GET /wx/order/list + +请求参数 + + showType: 订单类型,0则全部,1则待付款,2则待发货,3则待收货,4则代评价 + page: 请求页码 + limit: 每一页数量 + sort: 排序字段 + order: 升序降序 + +响应结果 + + { + "errno": 0, + "data": { + "total": 1, + "pages": 1, + "limit": 10, + "page": 1, + "list": [ + { + "orderStatusText": "未付款", + "isGroupin": false, + "orderSn": "20190509607545", + "actualPrice": 3989.00, + "goodsList": [ + { + "number": 1, + "picUrl": "http://yanxuan.nosdn.127.net/c5be2604c0e4186a4e7079feeb742cee.png", + "id": 3, + "goodsName": "云端沙发组合", + "specifications": [ + "标准" + ] + } + ], + "id": 3, + "handleOption": { + "cancel": true, + "delete": false, + "pay": true, + "comment": false, + "confirm": false, + "refund": false, + "rebuy": false + } + } + ] + }, + "errmsg": "成功" + } + +错误码 + + 略 + +#### 2.6.2 订单详情 + +应用场景 + + 订单详情 + +接口链接 + + GET /wx/order/detail + +请求参数 + + orderId: 订单ID + +响应结果 + + { + "errno": 0, + "data": { + "orderInfo": { + "consignee": "d", + "address": "北京市市辖区东城区 ddd", + "addTime": "2019-05-09 15:30:29", + "orderSn": "20190509607545", + "actualPrice": 3989.00, + "mobile": "13811111111", + "orderStatusText": "未付款", + "goodsPrice": 3999.00, + "couponPrice": 10.00, + "id": 3, + "freightPrice": 0.00, + "handleOption": { + "cancel": true, + "delete": false, + "pay": true, + "comment": false, + "confirm": false, + "refund": false, + "rebuy": false + } + }, + "orderGoods": [ + { + "id": 3, + "orderId": 3, + "goodsId": 1109008, + "goodsName": "云端沙发组合", + "goodsSn": "1109008", + "productId": 140, + "number": 1, + "price": 3999.00, + "specifications": [ + "标准" + ], + "picUrl": "http://yanxuan.nosdn.127.net/c5be2604c0e4186a4e7079feeb742cee.png", + "comment": 0, + "addTime": "2019-05-09 15:30:29", + "updateTime": "2019-05-09 15:30:29", + "deleted": false + } + ] + }, + "errmsg": "成功" + } + +错误码 + + 略 + +#### 2.6.3 创建订单 + +应用场景 + + 创建新订单 + +接口链接 + + POST /wx/order/submit + +请求参数 + + { + "cartId": 0, + "addressId": 3, + "couponId": -1, + "message": "", + "grouponRulesId": 0, + "grouponLinkId": 0 + } + +响应结果 + + { + "errno": 0, + "data": { + "orderId": 4 + }, + "errmsg": "成功" + } + +错误码 + + 略 + +#### 2.6.4 取消订单 + +应用场景 + + 取消订单 + +接口链接 + + POST /wx/order/cancel + +请求参数 + + orderId: 订单ID + +响应结果 + + { + "errno": 0, + "errmsg": "成功" + } + +错误码 + + 略 + +#### 2.6.5 微信预支付交易单 + +应用场景 + + 订单的微信预支付交易单 + +接口链接 + + POST /wx/order/prepay + +说明 + + 具体微信支付交互流程和预支付使用方式,见官方文档: https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_3&index=1 + +请求参数 + + orderId: 订单ID + +响应结果 + + { + errno: 0, + errmsg: "成功", + data: { + appId: 'xxx', + timeStamp: 'xxx', + nonceStr: 'xxx', + packageValue: 'xxx', + signType: 'xxx', + paySign: 'xxx' + } + } + +错误码 + + 略 + +#### 2.6.6 确认收货 + +应用场景 + + 订单确认收货 + +接口链接 + + POST /wx/order/confirm + +请求参数 + + orderId: 订单ID + +响应结果 + + { + "errno": 0, + "errmsg": "成功" + } + +错误码 + + 略 + +#### 2.6.7 订单删除 + +应用场景 + + 删除订单记录 + +接口链接 + + POST /wx/order/delete + +请求参数 + + orderId: 订单ID + +响应结果 + + { + "errno": 0, + "errmsg": "成功" + } + +错误码 + + 略 + +#### 2.6.8 订单退款 + +应用场景 + + 订单已经支付但是商家未发货,用户可以点击退款按钮申请退款取消订单。 + +说明 + + 退款请求发送以后,不会自动退款,仅仅是后端设置退款请求记录。 + 管理员在管理后台看到退款请求以后会手动退款或者拒绝退款。 + +接口链接 + + POST /wx/order/refund + +请求参数 + + orderId: 订单ID + +响应结果 + + { + "errno": 0, + "errmsg": "成功" + } + +错误码 + + 略 + + +#### 2.6.9 待评价商品 + +应用场景 + + 用户确认收货以后,可以待评价的订单商品。 + +接口链接 + + GET /wx/order/goods + +请求参数 + + orderId: 订单ID + goodsId: 商品ID + +响应结果 + + { + "errno": 0, + "data": { + "id": 4, + "orderId": 4, + "goodsId": 1109008, + "goodsName": "云端沙发组合", + "goodsSn": "1109008", + "productId": 140, + "number": 1, + "price": 3999.00, + "specifications": [ + "标准" + ], + "picUrl": "http://yanxuan.nosdn.127.net/c5be2604c0e4186a4e7079feeb742cee.png", + "comment": 0, + "addTime": "2019-05-09 17:06:54", + "updateTime": "2019-05-09 17:06:54", + "deleted": false + }, + "errmsg": "成功" + } + +错误码 + + 略 + + +#### 2.6.10 订单评价 + +应用场景 + + 订单评价 + +接口链接 + + POST /wx/order/comment + +请求参数 + + orderGoodsId: 订单商品ID + content: 评价内容 + star: 评分,1分至5分 + hasPicture: 是否有评价图片 + picUrls: 评价图片列表 + +例如 + + { + "orderGoodsId": 4, + "content": "不错", + "star": 5, + "hasPicture": true, + "picUrls": [] + } + +响应结果 + + { + "errno": 0, + "errmsg": "成功" + } + +错误码 + + 略 + +### 2.7 会员服务 + +### 2.8 收货地址服务 + +#### 2.8.1 收货地址列表 + +应用场景 + + 用户收货地址列表 + +接口链接 + + GET /wx/address/list + +请求参数 + + page: 请求页码 + limit: 每一页数量 + sort: 排序字段 + order: 升序降序 + +响应结果 + + { + "errno": 0, + "data": { + "total": 1, + "pages": 1, + "limit": 1, + "page": 1, + "list": [ + { + "id": 3, + "name": "d", + "userId": 2, + "province": "北京市", + "city": "市辖区", + "county": "东城区", + "addressDetail": "ddd", + "areaCode": "110101", + "tel": "13811111111", + "isDefault": true, + "addTime": "2019-05-06 14:17:32", + "updateTime": "2019-05-06 14:17:32", + "deleted": false + } + ] + }, + "errmsg": "成功" + } + +错误码 + + 略 + +#### 2.8.2 收货地址详情 + +应用场景 + + 请求用户的收货地址详情 + +接口链接 + + GET /wx/address/detail + +请求参数 + + id: 收货地址ID + +响应结果 + + { + errno: 0, + errmsg: "成功",, + data: { + id: 收货地址ID, + name: 收货人, + tel: 手机号 + province: 省级行政区域, + city: 市级行政区域, + county: 区级行政区域, + addressDetail: 具体地址, + areaCode: 地址编码, + postalCode: 邮政编码 + isDefault: 是否默认 + } + } + +错误码 + + 略 + + +#### 2.8.3 保存收货地址 + +应用场景 + + 添加或者更新用户收货地址 + +接口链接 + + POST /wx/address/save + +请求参数 + + id: 收货地址ID,如果是0则是添加,否则是更新 + name: 收货人, + tel: 手机号 + province: 省级行政区域, + city: 市级行政区域, + county: 区级行政区域, + addressDetail: 具体地址, + areaCode: 地址编码, + postalCode: 邮政编码 + isDefault: 是否默认 + +例如 + + { + "id": 0, + "name": "xxx", + "tel": "13811111111", + "province": "北京市", + "city": "市辖区", + "county": "东城区", + "areaCode": "110101", + "addressDetail": "dddd", + "isDefault": true + } + +响应结果 + + { + errno: 0, + errmsg: "成功",, + data: 3 + } + +错误码 + + 略 + + +#### 2.8.4 删除收货地址 + +应用场景 + + 删除用户的某个收货地址 + +接口链接 + + POST /wx/address/delete + +请求参数 + + id: 收货地址ID + +响应结果 + + { + errno: 0, + errmsg: "成功" + } + +错误码 + + 略 + +### 2.9 品牌商服务 + +#### 2.9.1 品牌商列表 + +应用场景 + + 访问品牌商列表信息 + +接口链接 + + GET /wx/brand/list + +请求参数 + + page: 请求页码 + limit: 每一页数量 + sort: 排序字段 + order: 升序降序 + +响应内容 + + { + "errno": 0, + "data": { + "total": 49, + "pages": 5, + "limit": 10, + "page": 1, + "list": [ + { + "id": 1024000, + "name": "WMF制造商", + "desc": "严选找寻德国百年高端厨具WMF的制造商,\n选择拥有14年经验的不锈钢生产工厂,\n为你甄选事半功倍的优质厨具。", + "picUrl": "http://yanxuan.nosdn.127.net/2018e9ac91ec37d9aaf437a1fd5d7070.png", + "floorPrice": 9.90 + }, + { + "id": 1024001, + "name": "OBH制造商", + "desc": "严选寻找OBH品牌的制造商,打造精致厨具,\n韩国独资工厂制造,严格质检,品质雕琢\n力求为消费者带来全新的烹饪体验。", + "picUrl": "http://yanxuan.nosdn.127.net/bf3499ac17a11ffb9bb7caa47ebef2dd.png", + "floorPrice": 39.00 + }, + { + "id": 1024003, + "name": "Stoneline制造商", + "desc": "严选找寻德国经典品牌Stoneline的制造商,\n追踪工艺,考量细节,亲自试用,\n为你甄选出最合心意的锅具和陶瓷刀,下厨如神。", + "picUrl": "http://yanxuan.nosdn.127.net/3a44ae7db86f3f9b6e542720c54cc349.png", + "floorPrice": 9.90 + }, + { + "id": 1024006, + "name": "KitchenAid制造商", + "desc": "严选寻访KitchenAid品牌的制造商,\n采用德国LFGB认证食品级专用不锈钢,\n欧式简约设计,可靠安心,尽享下厨乐趣。", + "picUrl": "http://yanxuan.nosdn.127.net/e11385bf29d1b3949435b80fcd000948.png", + "floorPrice": 98.00 + }, + { + "id": 1034001, + "name": "Alexander McQueen制造商", + "desc": "为制造精致实用的高品质包包,\n严选团队选择Alexander McQueen制造商,\n严格筛选,带来轻奢优雅体验。", + "picUrl": "http://yanxuan.nosdn.127.net/db7ee9667d84cbce573688297586699c.jpg", + "floorPrice": 69.00 + }, + { + "id": 1023000, + "name": "PetitBateau小帆船制造商", + "desc": "为打造适合宝宝的婴童服装,\n严选团队寻找PetitBateau小帆船的品牌制造商,\n无荧光剂,国家A类标准,让宝宝穿的放心。", + "picUrl": "http://yanxuan.nosdn.127.net/1a11438598f1bb52b1741e123b523cb5.jpg", + "floorPrice": 36.00 + }, + { + "id": 1001000, + "name": "MUJI制造商", + "desc": "严选精选了MUJI制造商和生产原料,\n用几乎零利润的价格,剔除品牌溢价,\n让用户享受原品牌的品质生活。", + "picUrl": "http://yanxuan.nosdn.127.net/1541445967645114dd75f6b0edc4762d.png", + "floorPrice": 12.90 + }, + { + "id": 1001002, + "name": "内野制造商", + "desc": "严选从世界各地挑选毛巾,最终选择了为日本内野代工的工厂,追求毛巾的柔软度与功能性。品质比肩商场几百元的毛巾。", + "picUrl": "http://yanxuan.nosdn.127.net/8ca3ce091504f8aa1fba3fdbb7a6e351.png", + "floorPrice": 29.00 + }, + { + "id": 1001003, + "name": "Adidas制造商", + "desc": "严选找到为Adidas等品牌制造商,\n选取优质原材料,与厂方一起设计,\n为你提供好的理想的运动装备。", + "picUrl": "http://yanxuan.nosdn.127.net/335334d0deaff6dc3376334822ab3a2f.png", + "floorPrice": 49.00 + }, + { + "id": 1033003, + "name": "Armani制造商", + "desc": "严选团队携手国际标准化专业生产厂家,\n厂家长期为Armani、Alexander wang等知名品牌代工,\n专业进口设备,精密质量把控,精于品质居家体验。", + "picUrl": "http://yanxuan.nosdn.127.net/981e06f0f46f5f1f041d7de3dd3202e6.jpg", + "floorPrice": 199.00 + } + ] + }, + "errmsg": "成功" + } + +错误码 + + 略 + +#### 2.9.2 品牌商详情 + +应用场景 + + 访问单个品牌商详情信息 + +接口链接 + + GET /wx/brand/detail + +请求参数 + + id: 品牌商ID,例如1001020 + +响应内容 + + { + "errno": 0, + "data": { + "id": 1001020, + "name": "Ralph Lauren制造商", + "desc": "我们与Ralph Lauren Home的制造商成功接洽,掌握先进的生产设备,传承品牌工艺和工序。追求生活品质的你,值得拥有。", + "picUrl": "http://yanxuan.nosdn.127.net/9df78eb751eae2546bd3ee7e61c9b854.png", + "sortOrder": 20, + "floorPrice": 29.00, + "addTime": "2018-02-01 00:00:00", + "updateTime": "2018-02-01 00:00:00", + "deleted": false + }, + "errmsg": "成功" + } + +错误码 + + 略 + +### 2.10 收藏服务 + +#### 2.10.1 收藏列表 + +应用场景 + + 收藏列表 + +接口链接 + + GET /wx/collect/list + +请求参数 + + type: 收藏类型,如果是0则是商品收藏,如果是1则是专题收藏 + page: 请求页码 + limit: 每一页数量 + sort: 排序字段 + order: 升序降序 + +响应内容 + + { + "errno": 0, + "data": { + "total": 2, + "pages": 1, + "limit": 10, + "page": 1, + "list": [ + { + "brief": "酥脆奶香,甜酸回味", + "picUrl": "http://yanxuan.nosdn.127.net/767b370d07f3973500db54900bcbd2a7.png", + "valueId": 1116011, + "name": "蔓越莓曲奇 200克", + "id": 3, + "type": 0, + "retailPrice": 36.00 + }, + { + "brief": "MUJI供应商携手打造", + "picUrl": "http://yanxuan.nosdn.127.net/c5be2604c0e4186a4e7079feeb742cee.png", + "valueId": 1109008, + "name": "云端沙发组合", + "id": 2, + "type": 0, + "retailPrice": 3999.00 + } + ] + }, + "errmsg": "成功" + } + +错误码 + + 略 + + +#### 2.10.2 收藏添加或删除 + +应用场景 + + 用户收藏添加或删除 + +说明 + + 如果用户已经收藏,则请求API会删除已收藏商品或专题; + 如果用户未收藏,则请求API会添加新的商品或专题收藏记录。 + +接口链接 + + POST /wx/collect/addordelete + +请求参数 + + type: 收藏类型,如果是0则是商品收藏,如果是1则是专题收藏 + valueId: 收藏对象ID,如果type=0则设置商品ID,如果type=1则设置专题ID + +例如 + + { + "type": 0, + "valueId": 1116011 + } + + +响应内容 + + +错误码 + + 略 + +### 2.11 评论服务 + +#### 2.11.1 评论数量 + +应用场景 + + 某个商品或者专题的评论数量,包括总的评论数量和包含图片的评论数量 + +接口链接 + + GET /wx/comment/count + +请求参数 + + type: 评论类型,如果是0则是商品评论,如果是1则是专题评论 + valueId: 评论对象ID,如果type=0,则设置商品ID,如果type=0,则设置专题ID + +响应内容 + + { + "errno": 0, + "data": { + "hasPicCount": 34, + "allCount": 96 + }, + "errmsg": "成功" + } + +错误码 + + 无 + + +#### 2.11.2 评论列表 + +应用场景 + + 某个商品或者专题的评论列表 + +接口链接 + + GET /wx/comment/list + +请求参数 + + valueId=1181000&type=0&limit=20&page=1&showType=0 + type: 评论类型,如果是0则是商品评论,如果是1则是专题评论 + valueId: 评论对象ID,如果type=0,则设置商品ID,如果type=0,则设置专题ID + showType: 评论显示类型,如果是0则是所有评论,如果是1则是包含图片的评论 + page: 请求页码 + limit: 每一页数量 + sort: 排序字段 + order: 升序降序 + +响应内容 + + { + "errno": 0, + "data": { + "total": 96, + "pages": 20, + "limit": 5, + "page": 1, + "list": [ + { + "userInfo": { + "nickName": "user123", + "avatarUrl": "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif" + }, + "addTime": "2018-02-01 00:00:00", + "picList": [ + "https://yanxuan.nosdn.127.net/218783173f303ec6d8766810951d0790.jpg" + ], + "content": "布料很厚实,触感不错,洗过之后不缩水不掉色" + }, + { + "userInfo": { + "nickName": "user123", + "avatarUrl": "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif" + }, + "addTime": "2018-02-01 00:00:00", + "picList": [ + "https://yanxuan.nosdn.127.net/33978a0d6f56d94c45e4fc594b4b8606.jpg" + ], + "content": "料子很舒服,凉凉的,配合蚕丝被,夏天很凉快~" + }, + { + "userInfo": { + "nickName": "user123", + "avatarUrl": "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif" + }, + "addTime": "2018-02-01 00:00:00", + "picList": [ + "https://yanxuan.nosdn.127.net/d3975d1b6d88e9f9d762cd9a879d1a14.jpg" + ], + "content": "一直喜欢粗布的床上用品。冬暖夏凉。这套看起来非常漂亮。实际感觉有点粗布的感觉。很好!" + }, + { + "userInfo": { + "nickName": "user123", + "avatarUrl": "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif" + }, + "addTime": "2018-02-01 00:00:00", + "picList": [ + "https://yanxuan.nosdn.127.net/5fe1121396458cfe0dc1b25ec86f7ff9.jpg", + "https://yanxuan.nosdn.127.net/d5a55abd6ced5c811d775b04929aaabc.jpg", + "https://yanxuan.nosdn.127.net/f1764d820ba6ddaf51d297e3cf3826cd.jpg" + ], + "content": "太好了,舒服的不得了,腰,腿,脊柱,头,颈椎!\n无一处不舒服,真没想到这么优惠!\n搬了新家还要买!" + }, + { + "userInfo": { + "nickName": "user123", + "avatarUrl": "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif" + }, + "addTime": "2018-02-01 00:00:00", + "picList": [ + "https://yanxuan.nosdn.127.net/f753f91430dfb56f574c737d4b2fde46.jpg" + ], + "content": "抱着试试的态度 先买了小的 果然感觉很舒服 深陷其中 把自己全身心都给了它 第二个床垫已经在路上" + } + ] + }, + "errmsg": "成功" + } + +错误码 + + 无 + + +#### 2.11.3 发表评论 + +应用场景 + + 针对某个商品或者专题的发表评论 + +接口链接 + + +请求参数 + + +响应内容 + + +错误码 + + 略 + +### 2.12 优惠券服务 + + +#### 2.12.1 优惠券列表 + +应用场景 + + 优惠券列表 + +接口链接 + + GET /wx/coupon/list + +请求参数 + + page: 请求页码 + limit: 每一页数量 + sort: 排序字段 + order: 升序降序 + +响应内容 + + { + "errno": 0, + "data": { + "total": 2, + "pages": 1, + "limit": 10, + "page": 1, + "list": [ + { + "id": 1, + "name": "限时满减券", + "desc": "全场通用", + "tag": "无限制", + "discount": 5.00, + "min": 99.00, + "days": 10 + }, + { + "id": 2, + "name": "限时满减券", + "desc": "全场通用", + "tag": "无限制", + "discount": 10.00, + "min": 99.00, + "days": 10 + } + ] + }, + "errmsg": "成功" + } + +错误码 + + 略 + + +#### 2.12.2 用户优惠券列表 + +应用场景 + + 用户优惠券列表 + +接口链接 + + GET /wx/coupon/mylist + +请求参数 + + status: 优惠券状态,如果0则未使用,如果1则已使用,如果2则已过期 + page: 请求页码 + limit: 每一页数量 + sort: 排序字段 + order: 升序降序 + +响应内容 + + { + "errno": 0, + "data": { + "total": 2, + "pages": 1, + "limit": 10, + "page": 1, + "list": [ + { + "id": 1, + "name": "限时满减券", + "desc": "全场通用", + "tag": "无限制", + "min": "99.00", + "discount": "5.00", + "startTime": "2019-05-06 16:21:38", + "endTime": "2019-05-16 16:21:38" + }, + { + "id": 3, + "name": "新用户优惠券", + "desc": "全场通用", + "tag": "无限制", + "min": "99.00", + "discount": "10.00", + "startTime": "2019-05-06 12:30:06", + "endTime": "2019-05-16 12:30:06" + } + ] + }, + "errmsg": "成功" + } + +错误码 + + 略 + + +#### 2.12.3 下单可用优惠券 + +应用场景 + + 当前购物车下单商品订单可用优惠券 + +接口链接 + + GET /wx/coupon/selectlist + +请求参数 + + cartId: 购物车ID,如果0则是购物车商品,如果非0则是立即单一商品 + grouponRulesId: 团购规则ID,如果是团购商品则需要设置具体团购规则ID + +响应内容 + + { + "errno": 0, + "data": { + "total": 1, + "pages": 1, + "limit": 1, + "page": 1, + "list": [ + { + "id": 2, + "name": "限时满减券", + "desc": "全场通用", + "tag": "无限制", + "min": "99.00", + "discount": "10.00", + "startTime": "2019-05-09 15:27:29", + "endTime": "2019-05-19 15:27:29" + } + ] + }, + "errmsg": "成功" + } + +错误码 + + 略 + +#### 2.12.4 优惠券领取 + +应用场景 + + 领取优惠券 + +接口链接 + + POST /wx/coupon/receive + +请求参数 + + couponId: 可领取优惠券ID + +例如 + + { + "couponId": 2 + } + +响应内容 + + { + "errno": 0, + "errmsg": "成功" + } + +错误码 + + 略 + +#### 2.12.5 优惠券兑换 + +应用场景 + + 通过兑换码兑换优惠券 + +接口链接 + + POST /wx/coupon/exchange + +请求参数 + + code: 优惠券兑换码 + +响应内容 + + { + "errno": 0, + "errmsg": "成功" + } + +错误码 + + 略 + +### 2.13 反馈服务 + +### 2.14 足迹服务 + +#### 2.14.1 用户足迹列表 + +应用场景 + + 用户足迹列表 + +接口链接 + + GET /wx/footprint/list + +请求参数 + + page: 请求页码 + limit: 每一页数量 + +响应内容 + + { + "errno": 0, + "data": { + "total": 22, + "pages": 6, + "limit": 4, + "page": 1, + "list": [ + { + "brief": "酥脆奶香,甜酸回味", + "picUrl": "http://yanxuan.nosdn.127.net/767b370d07f3973500db54900bcbd2a7.png", + "addTime": "2019-05-09 10:10:01", + "goodsId": 1116011, + "name": "蔓越莓曲奇 200克", + "id": 22, + "retailPrice": 36.00 + }, + { + "brief": "MUJI供应商携手打造", + "picUrl": "http://yanxuan.nosdn.127.net/c5be2604c0e4186a4e7079feeb742cee.png", + "addTime": "2019-05-09 10:09:49", + "goodsId": 1109008, + "name": "云端沙发组合", + "id": 21, + "retailPrice": 3999.00 + }, + { + "brief": "酥脆奶香,甜酸回味", + "picUrl": "http://yanxuan.nosdn.127.net/767b370d07f3973500db54900bcbd2a7.png", + "addTime": "2019-05-08 22:40:55", + "goodsId": 1116011, + "name": "蔓越莓曲奇 200克", + "id": 20, + "retailPrice": 36.00 + }, + { + "brief": "MUJI供应商携手打造", + "picUrl": "http://yanxuan.nosdn.127.net/c5be2604c0e4186a4e7079feeb742cee.png", + "addTime": "2019-05-07 14:35:41", + "goodsId": 1109008, + "name": "云端沙发组合", + "id": 19, + "retailPrice": 3999.00 + } + ] + }, + "errmsg": "成功" + } + +错误码 + + 略 + +#### 2.14.2 用户足迹删除 + +应用场景 + + 用户足迹删除 + +接口链接 + + POST /wx/footprint/delete + +请求参数 + + id: 用户足迹ID + +响应内容 + + { + "errno": 0, + "errmsg": "成功" + } + +错误码 + + 略 + +### 2.15 团购服务 + +注意 +> 团购业务还不完善 + + +#### 2.15.1 团购商品列表 + +应用场景 + + 参加团购的商品列表信息 + +接口链接 + + +请求参数 + + +响应内容 + + +错误码 + + 略 + + +#### 2.15.2 团购活动详情 + +应用场景 + + 团购活动详情 + +接口链接 + + +请求参数 + + +响应内容 + + +错误码 + + 略 + +#### 2.15.3 参加团购 + +应用场景 + + 参加团购的商品列表信息 + +接口链接 + + +请求参数 + + +响应内容 + + +错误码 + + 略 + + +#### 2.15.4 用户参团列表 + +应用场景 + + 用户参团列表 + +接口链接 + + +请求参数 + + +响应内容 + + +错误码 + + 略 + +### 2.16 帮助服务 + +#### 2.16.1 帮助列表 + +应用场景 + + 帮助列表 + +接口链接 + + GET /wx/issue/list + +请求参数 + + page: 请求页码 + limit: 每一页数量 + sort: 排序字段 + order: 升序降序 + +响应内容 + + { + "errno": 0, + "data": { + "total": 4, + "pages": 1, + "limit": 10, + "page": 1, + "list": [ + { + "id": 1, + "question": "购买运费如何收取?", + "answer": "单笔订单金额(不含运费)满88元免邮费;不满88元,每单收取10元运费。\n(港澳台地区需满", + "addTime": "2018-02-01 00:00:00", + "updateTime": "2018-02-01 00:00:00", + "deleted": false + }, + { + "id": 2, + "question": "使用什么快递发货?", + "answer": "严选默认使用顺丰快递发货(个别商品使用其他快递),配送范围覆盖全国大部分地区(港澳台地区除", + "addTime": "2018-02-01 00:00:00", + "updateTime": "2018-02-01 00:00:00", + "deleted": false + }, + { + "id": 3, + "question": "如何申请退货?", + "answer": "1.自收到商品之日起30日内,顾客可申请无忧退货,退款将原路返还,不同的银行处理时间不同,", + "addTime": "2018-02-01 00:00:00", + "updateTime": "2018-02-01 00:00:00", + "deleted": false + }, + { + "id": 4, + "question": "如何开具发票?", + "answer": "1.如需开具普通发票,请在下单时选择“我要开发票”并填写相关信息(APP仅限2.4.0及以", + "addTime": "2018-02-01 00:00:00", + "updateTime": "2018-02-01 00:00:00", + "deleted": false + } + ] + }, + "errmsg": "成功" + } + +错误码 + + 无 + +### 2.17 搜索服务 + +### 2.18 专题服务 + +#### 2.18.1 专题列表 + +应用场景 + + 访问专题列表信息 + +接口链接 + + GET /wx/topic/list + +请求参数 + + page: 请求页码 + limit: 每一页数量 + sort: 排序字段 + order: 升序降序 + +响应内容 + + { + "errno": 0, + "data": { + "total": 20, + "pages": 2, + "limit": 10, + "page": 1, + "list": [ + { + "id": 264, + "title": "设计师们推荐的应季好物", + "subtitle": "原创设计春款系列上新", + "price": 29.90, + "readCount": "77.7k", + "picUrl": "https://yanxuan.nosdn.127.net/14918201901050274.jpg" + }, + { + "id": 266, + "title": "一条丝巾就能提升时髦度", + "subtitle": "不知道大家对去年G20时,严选与国礼制造商一起推出的《凤凰于飞》等几款丝巾是否还...", + "price": 0.00, + "readCount": "35.0k", + "picUrl": "https://yanxuan.nosdn.127.net/14919007135160213.jpg" + }, + { + "id": 268, + "title": "米饭好吃的秘诀:会呼吸的锅", + "subtitle": "今年1月份,我们联系到了日本伊贺地区的长谷园,那里有着180年伊贺烧历史的窑厂。...", + "price": 0.00, + "readCount": "33.3k", + "picUrl": "https://yanxuan.nosdn.127.net/14920623353130483.jpg" + }, + { + "id": 271, + "title": "选式新懒人", + "subtitle": "懒出格调,懒出好生活。", + "price": 15.00, + "readCount": "57.7k", + "picUrl": "https://yanxuan.nosdn.127.net/14924199099661697.jpg" + }, + { + "id": 272, + "title": "料理也要精细简单", + "subtitle": "享受天然的味道,日子每天都好新鲜", + "price": 69.00, + "readCount": "125.6k", + "picUrl": "https://yanxuan.nosdn.127.net/14925200530030186.jpg" + }, + { + "id": 274, + "title": "没有软木拖,怎么过夏天", + "subtitle": "刚入四月,杭州的气温就已升高至30度。店庆时买了软木拖的用户,陆续发回评价说,很...", + "price": 0.00, + "readCount": "46.4k", + "picUrl": "https://yanxuan.nosdn.127.net/14925822213780237.jpg" + }, + { + "id": 277, + "title": "治愈生活的满怀柔软", + "subtitle": "太鼓抱枕的上架历程,是从失踪开始的。由于表面的绒感,最初它被安排在秋冬季上架。某...", + "price": 0.00, + "readCount": "19.6k", + "picUrl": "https://yanxuan.nosdn.127.net/14926737925770587.jpg" + }, + { + "id": 281, + "title": "条纹新风尚", + "subtitle": "经典百搭,时尚线条", + "price": 29.00, + "readCount": "76.5k", + "picUrl": "https://yanxuan.nosdn.127.net/14926859849200826.jpg" + }, + { + "id": 282, + "title": "成就一室笋香", + "subtitle": "三石哥办公室常备小食推荐", + "price": 12.00, + "readCount": "40.9k", + "picUrl": "https://yanxuan.nosdn.127.net/14927695046601069.jpg" + }, + { + "id": 283, + "title": "孩子成长中少不了的一双鞋", + "subtitle": "说起毛毛虫鞋,好处实在太多了,作为一个2岁孩子的宝妈选品员,按捺不住想告诉大家,...", + "price": 0.00, + "readCount": "42.5k", + "picUrl": "https://yanxuan.nosdn.127.net/14927748974441080.jpg" + } + ] + }, + "errmsg": "成功" + } + +错误码 + + 略 + +#### 2.18.2 专题详情 + +应用场景 + + 单个专题详情信息 + +接口链接 + + GET /wx/topic/detail + +请求参数 + + id: 专题ID,例如 id=264 + +响应内容 + + { + "errno": 0, + "data": { + "topic": { + "id": 264, + "title": "设计师们推荐的应季好物", + "subtitle": "原创设计春款系列上新", + "price": 29.90, + "readCount": "77.7k", + "picUrl": "https://yanxuan.nosdn.127.net/14918201901050274.jpg", + "sortOrder": 0, + "goods": [], + "addTime": "2018-02-01 00:00:00", + "updateTime": "2018-02-01 00:00:00", + "deleted": false, + "content": "" + }, + "goods": [] + }, + "errmsg": "成功" + } + +错误码 + + 略 + +#### 2.18.3 专题推荐 + +应用场景 + + 基于某个专题推荐其他专题 + +接口链接 + + GET /wx/topic/related + +请求参数 + + id: 专题ID,例如 id=264 + +响应内容 + + { + "errno": 0, + "data": { + "total": 19, + "pages": 5, + "limit": 4, + "page": 1, + "list": [ + { + "id": 266, + "title": "一条丝巾就能提升时髦度", + "subtitle": "不知道大家对去年G20时,严选与国礼制造商一起推出的《凤凰于飞》等几款丝巾是否还...", + "price": 0.00, + "readCount": "35.0k", + "picUrl": "https://yanxuan.nosdn.127.net/14919007135160213.jpg", + "sortOrder": 0, + "goods": [], + "addTime": "2018-02-01 00:00:00", + "updateTime": "2018-02-01 00:00:00", + "deleted": false, + "content": "\u003cimg src\u003d\"//yanxuan.nosdn.127.net/75c55a13fde5eb2bc2dd6813b4c565cc.jpg\"\u003e\n \u003cimg src\u003d\"//yanxuan.nosdn.127.net/e27e1de2b271a28a21c10213b9df7e95.jpg\"\u003e\n \u003cimg src\u003d\"//yanxuan.nosdn.127.net/9d413d1d28f753cb19096b533d53418d.jpg\"\u003e\n \u003cimg src\u003d\"//yanxuan.nosdn.127.net/64b0f2f350969e9818a3b6c43c217325.jpg\"\u003e\n \u003cimg src\u003d\"//yanxuan.nosdn.127.net/a668e6ae7f1fa45565c1eac221787570.jpg\"\u003e\n \u003cimg src\u003d\"//yanxuan.nosdn.127.net/0d4004e19728f2707f08f4be79bbc774.jpg\"\u003e\n \u003cimg src\u003d\"//yanxuan.nosdn.127.net/79ee021bbe97de7ecda691de6787241f.jpg\"\u003e" + }, + { + "id": 268, + "title": "米饭好吃的秘诀:会呼吸的锅", + "subtitle": "今年1月份,我们联系到了日本伊贺地区的长谷园,那里有着180年伊贺烧历史的窑厂。...", + "price": 0.00, + "readCount": "33.3k", + "picUrl": "https://yanxuan.nosdn.127.net/14920623353130483.jpg", + "sortOrder": 0, + "goods": [], + "addTime": "2018-02-01 00:00:00", + "updateTime": "2018-02-01 00:00:00", + "deleted": false, + "content": "\u003cimg src\u003d\"//yanxuan.nosdn.127.net/75c55a13fde5eb2bc2dd6813b4c565cc.jpg\"\u003e\n \u003cimg src\u003d\"//yanxuan.nosdn.127.net/e27e1de2b271a28a21c10213b9df7e95.jpg\"\u003e\n \u003cimg src\u003d\"//yanxuan.nosdn.127.net/9d413d1d28f753cb19096b533d53418d.jpg\"\u003e\n \u003cimg src\u003d\"//yanxuan.nosdn.127.net/64b0f2f350969e9818a3b6c43c217325.jpg\"\u003e\n \u003cimg src\u003d\"//yanxuan.nosdn.127.net/a668e6ae7f1fa45565c1eac221787570.jpg\"\u003e\n \u003cimg src\u003d\"//yanxuan.nosdn.127.net/0d4004e19728f2707f08f4be79bbc774.jpg\"\u003e\n \u003cimg src\u003d\"//yanxuan.nosdn.127.net/79ee021bbe97de7ecda691de6787241f.jpg\"\u003e" + }, + { + "id": 271, + "title": "选式新懒人", + "subtitle": "懒出格调,懒出好生活。", + "price": 15.00, + "readCount": "57.7k", + "picUrl": "https://yanxuan.nosdn.127.net/14924199099661697.jpg", + "sortOrder": 0, + "goods": [], + "addTime": "2018-02-01 00:00:00", + "updateTime": "2018-02-01 00:00:00", + "deleted": false, + "content": "\u003cimg src\u003d\"//yanxuan.nosdn.127.net/75c55a13fde5eb2bc2dd6813b4c565cc.jpg\"\u003e\n \u003cimg src\u003d\"//yanxuan.nosdn.127.net/e27e1de2b271a28a21c10213b9df7e95.jpg\"\u003e\n \u003cimg src\u003d\"//yanxuan.nosdn.127.net/9d413d1d28f753cb19096b533d53418d.jpg\"\u003e\n \u003cimg src\u003d\"//yanxuan.nosdn.127.net/64b0f2f350969e9818a3b6c43c217325.jpg\"\u003e\n \u003cimg src\u003d\"//yanxuan.nosdn.127.net/a668e6ae7f1fa45565c1eac221787570.jpg\"\u003e\n \u003cimg src\u003d\"//yanxuan.nosdn.127.net/0d4004e19728f2707f08f4be79bbc774.jpg\"\u003e\n \u003cimg src\u003d\"//yanxuan.nosdn.127.net/79ee021bbe97de7ecda691de6787241f.jpg\"\u003e" + }, + { + "id": 272, + "title": "料理也要精细简单", + "subtitle": "享受天然的味道,日子每天都好新鲜", + "price": 69.00, + "readCount": "125.6k", + "picUrl": "https://yanxuan.nosdn.127.net/14925200530030186.jpg", + "sortOrder": 0, + "goods": [], + "addTime": "2018-02-01 00:00:00", + "updateTime": "2018-02-01 00:00:00", + "deleted": false, + "content": "\u003cimg src\u003d\"//yanxuan.nosdn.127.net/75c55a13fde5eb2bc2dd6813b4c565cc.jpg\"\u003e\n \u003cimg src\u003d\"//yanxuan.nosdn.127.net/e27e1de2b271a28a21c10213b9df7e95.jpg\"\u003e\n \u003cimg src\u003d\"//yanxuan.nosdn.127.net/9d413d1d28f753cb19096b533d53418d.jpg\"\u003e\n \u003cimg src\u003d\"//yanxuan.nosdn.127.net/64b0f2f350969e9818a3b6c43c217325.jpg\"\u003e\n \u003cimg src\u003d\"//yanxuan.nosdn.127.net/a668e6ae7f1fa45565c1eac221787570.jpg\"\u003e\n \u003cimg src\u003d\"//yanxuan.nosdn.127.net/0d4004e19728f2707f08f4be79bbc774.jpg\"\u003e\n \u003cimg src\u003d\"//yanxuan.nosdn.127.net/79ee021bbe97de7ecda691de6787241f.jpg\"\u003e" + } + ] + }, + "errmsg": "成功" + } + +### 2.19 对象存储服务 + +### 2.20 其他服务 + + +## 3 管理后台API服务 + +略 + +## 4 更新日志 + +略 diff --git a/doc/pic/nginx.conf b/doc/conf/nginx.conf similarity index 100% rename from doc/pic/nginx.conf rename to doc/conf/nginx.conf diff --git a/doc/database.md b/doc/database.md new file mode 100644 index 0000000000000000000000000000000000000000..e7eeae120838788c6f848f8746581363360db09c --- /dev/null +++ b/doc/database.md @@ -0,0 +1,572 @@ +# 数据库 + +litemall数据库基于nideshop中的[nideshop.sql](https://github.com/tumobi/nideshop/blob/master/nideshop.sql)数据库, +然后在实际开发过程中进行了调整和修改: + +* 删除了一些目前不必要的表; +* 删除了表中一些目前不必要的字段; +* 行政区域数据litemall_region没有采用原nideshop中的数据,而是采用了[Administrative-divisions-of-China](https://github.com/modood/Administrative-divisions-of-China); +* 表中的某些字段采用JSON存储; +* 表中的日期或时间字段采用DATE、DATETIME; +* 字段的数据类型粗粒度化,例如避免MEDIUMINT,而是INT; +* 表的数据做了清理、调整和补充(假数据)。 + +litemall数据库由三个sql文件组成,在litemall-db文件夹下面的sql文件夹中: + +1. litemall_schema.sql + + 作用是创建空数据库、创建用户、设置访问权限。 + + 开发者开发测试阶段可以使用,但是部署生产阶段一定要注意修改这里的默认用户名和密码。 + + 注意,这里的sql文件不一定需要运行,开发者可以自己手动或命令行或IDE进行对应的操作即可。 + +2. litemall_table.sql + + 作用是创建数据库表,但是没有创建任何数据。 + + 因此,开发者可以在部署生产阶段直接使用。 + +3. litemall_data.sql + + 作用是创建测试数据。 + + 开发者开发测试阶段可以使用,但是部署开发阶段应该使用自己的数据。 + +综上,这里litemall真正必须运行的sql文件是litemall_table.sql,其他两个sql文件开发者自行决定如何是否使用。 + +## 1 数据表结构 + +### 1.1 用户相关 + +![](./pics/database/user.png) + +### 1.2 商品相关 + +![](./pics/database/goods.png) + +### 1.3 订单相关 + +![](./pics/database/order.png) + +### 1.4 其他 + +![](./pics/database/others.png) + +## 2 数据表设计 + +接下来讨论一些数据表的关键细节。 + +### 2.1 商品和货品设计 + +这里商品存在商品表(litemall_goods),商品属性表(litemall_goods_attribute),商品规格表(litemall_goods_specification),商品货品表(litemall_goods_product)四种表 + +商品表是一种商品的基本信息,主要包括商品介绍,商品图片,商品所属类目,商品品牌商等; + +商品参数表其实也是商品的基本信息,但是由于是一对多关系,因此不能直接保存在商品表中(虽然采用JSON也可以但是不合理), +因此采用独立的商品参数表,通常是商品的一些公共基本商品参数; + +商品规格表是商品进一步区分货品的标识,例如同样一款衣服,基本信息一致,基本属性一致,但是在尺寸这个属性上可以 +把衣服区分成多个货品,而且造成对应的数量和价格不一致。商品规格可以看着是商品属性,但具有特殊特征。 + +商品规格和规格值存在以下几种关系: + +* 单一规格和单一规格值,最常见的,即当前商品存在一种货品; +* 单一规格和多个规格值,较常见,即当前商品基于某个规格存在多种货品,通常价格都是相同的,当然也可能不相同; +* 多个规格和单一规格值,可以简化成第一种情况,或者采用第四种情况,通常实际情况下不常见; +* 多个规格和多个规格值,通常是两种规格或者三种规格较为常见,而且对应的价格不完全相同。 + +商品货品表则是最终实现商品库存管理、购买业务的实体对象,存在多个规格值、数量和价格。 +例如,同样的衣服品牌,可能因为不能尺寸和颜色而存在最终的货品,这里每个货品的价格可以一样,也可以不一样。 + +总结一下,一个普通商品,实际上在数据库中,存在一个商品表项,存在(至少0个)多个商品属性表项目,存在(至少一个)多个商品规格表项, +存在(至少一个)多个货品表项。 + +举例如下: + +* 一个商品“2018春季衣服商品编号1111111”, +* 存在两个商品参数, + * 属性名称“面向人群”,属性值“男士” + * 属性名称“面料”,属性值“100%棉” +* 存在两种规格(分别五个规格值和三个规格值)共八个商品规格项, + * 规格名称“尺寸”,规则值“S” + * 规格名称“尺寸”,规则值“M” + * 规格名称“尺寸”,规则值“L” + * 规格名称“尺寸”,规则值“XL” + * 规格名称“尺寸”,规则值“XXL” + * 规格名称“颜色”,规格值“蓝色” + * 规格名称“颜色”,规格值“灰色” + * 规格名称“颜色”,规格值“黑色” +* 存在15个货品(尺寸*颜色=15个货品) + * 货品“S蓝”,数量 100, 价格 100 + * 货品“M蓝”,数量 100, 价格 100 + * 货品“L蓝”,数量 100, 价格 100 + * 货品“XL蓝”,数量 100, 价格 100 + * 货品“XXL蓝”,数量 100, 价格 100 + * 货品“S灰”,数量 100, 价格 100 + * 货品“M灰”,数量 100, 价格 100 + * 货品“L灰”,数量 100, 价格 100 + * 货品“XL灰”,数量 100, 价格 100 + * 货品“XXL灰”,数量 100, 价格 100 + * 货品“S黑”,数量 100, 价格 100 + * 货品“M黑”,数量 100, 价格 100 + * 货品“L黑”,数量 100, 价格 100 + * 货品“XL黑”,数量 0, 价格 100 + * 货品“XXL黑”,数量 0, 价格 100 + +以下是一些细节的讨论: + +* 商品表中可能存在数量和价格属性,而货品中也存在数量和价格属性,目前设计这样: + * 商品表的价格应该和某个货品的价格一样,通常应该是所有货品价格的最小值,或者基本款式的价格; + * 商品表中的数量和价格应该仅用于展示,而不能用于最终的订单价格计算; + * 商品表的数量应该设置成所有货品数量的总和; + * 在管理后台添加商品时,如果管理员不填写商品表的数量和价格属性,则自动填写合适的值;如果填写,则使用显示。 + * 当小商城中,用户查看商品详情时,初始显示商品表的价格,而如果用户选择具体规格后,则商品 + 详情里面的价格需要自动切换到该规格的价格。 +* 商品规格可以存在规格图片,效果是规格名称前放置规格图片 +* 货品也可以存在货品图片,效果是所有规格选定以后对应的货品有货,则在货品价格前放置货品图片 +* 如果商品是两种规格,分别是M个和N个规格值,那么通常应该是`M*N`个货品,但是有些货品可能天然不存在。 + 目前这里要求所有货品信息都应该存在,如果实际中货品不存在,也要设置商品数量为0. + +注意: + +> 这里的设计可能与实际项目设计不一致,但是目前是可行的。 +> 商品的中文用语“商品”和英语用语“goods”,货品的中文用语“货品”和英语用语“product”可能是不正确的。 + +### 2.2 用户和微信用户设计 + +目前准备支持用户普通账号登录和微信登录两种方式,两种登录方式仅仅采用一个litemall-user表可能不是很合适。 + +外,如果进一步支持其他多种第三方登录,那么这里需要重新设计。 + +### 2.3 行政区域设计 + +litemall_region表保存了行政区域信息,包括省级、市级、县级三个等级, + +原nideship.sql中存在region数据,但是litemall.sql的region数据则来自 +[Administrative-divisions-of-China](https://github.com/modood/Administrative-divisions-of-China)项目。 + +### 2.4 订单设计 + +订单信息主要由基本信息、商品信息、地址信息、费用信息、快递信息、支付信息和其他信息组成, +由litemall_order表和litemall_order_goods表保存。 + +* 基本信息 + +订单创建时的一些基本信息,例如用户、订单状态和订单留言等。 +其中订单状态是最重要的信息。 + +* 商品信息 + +由于订单可以存在多个商品,因此订单的商品信息是由独立的订单商品表记录(可能更应该称为货品)。 + +* 费用信息 + +订单一些费用情况,例如商品总价、优惠减免和实际付费等。 + +* 收货信息 + +用户下单时选择的收货地址以及联系人信息。 + +* 快递信息 + +目前快递信息仅仅记录快递公司、快递单号、快递发出时间。 +而如果快递过程中如果存在一些异常,例如物品丢失,则目前系统难以处理。 + +* 支付信息 + +支付时间和支付订单ID。 + +* 评论信息 + +订单商品的评论情况。 + +* 其他信息 + +#### 2.4.1 订单状态 + +![](pics/database/order-status.png) + +订单分成几种基本的状态: + +* 101 + + 状态码101,此时订单生成,记录订单编号、收货地址信息、订单商品信息和订单相关费用信息; + +* 201 + + 状态码201,此时用户微信支付付款,系统记录微信支付订单号、支付时间、支付状态; + +* 301 + + 状态码301,此时商场已经发货,系统记录快递公司、快递单号、快递发送时间。 + 当快递公司反馈用户签收后,系统记录快递到达时间。 + +* 401 + + 状态码401,当用户收到货以后点击确认收货,系统记录确认时间。 + +以上是一个订单成功完成的基本流程,但实际中还存在其他情况。 + +* 102 + + 状态码102,用户下单后未付款之前,点击取消按钮,系统记录结束时间 + +* 103 + + 状态码103,用户下单后半小时未付款则系统自动取消,系统记录结束时间 + +* 202 + + 状态码202,用户付款以后未发货前,点击退款按钮,系统进行设置退款状态,等待管理员退款操作 + +* 203 + + 状态码203,管理员在管理后台看到用户的退款申请,点击退款按钮进行退款操作。 + +* 402 + + 状态码402,用户已签收却不点击确认收货,超期7天以后,则系统自动确认收货。 + 用户不能再点击确认收货按钮,但是可以评价订单商品。 + +此外,当订单状态码是102、103、203、401和402时,订单可以执行删除操作。 +目前的设计是不执行物理删除,而是逻辑删除,因此用户查看自己订单时将看不到这些“已删除”的订单。 + +注意: +> 在上图中可以看到`101`到`101`的状态变化,这里只是小商场用户的操作,不会影响订单状态码。 +> 如果用户点击付款时,后端服务会生成预支付会话id,但是不会影响订单状态。 +> 如果而用户支付过程中,放弃支付,则也不会影响订单状态。 + +#### 2.4.2 状态变化 + +* 初始 -> 101 + +小商场用户在小商场点击`下单`按钮,此时小商城后端服务会生产商户订单。 + +所对应的后台服务方法是litemall-wx-api模块的`WxOrderController.submit`方法。 + +* 101 -> 101 + +这里开发者可能会奇怪,这里存在101->101的变化,这里表明后台没有响应 +小程序端的请求,但是这里的响应没有导致订单状态实际的变化。这里所指的 +响应小程序端请求是指下单成功以后小程序端自动请求付款或者用户在订单页面中 +点击`付款`所导致的对后台服务的预支付请求。 + +关于微信支付流程,可以参看官方文档的[小程序支付业务流程](https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_4&index=3) +也就是说这里小商城后台服务会返回付支付信息。 + +所对应的后台服务方法是litemall-wx-api模块的`WxOrderController.prepay` + +小商城接收返回的预支付信息后,会在小程序端出现支付页面。 +如果用户放弃支付,则不会出现任何效果,不会向小商场后台服务发送任何信息。 +如果用户支付,则会导致微信商户平台向小商场后台服务推送支付结果。 + +* 101 -> 102 + +如果用户没有支付,那么此时用户可以点击`取消订单`按钮来放弃当前订单。 + +所对应的后台服务方法是litemall-wx-api模块的`WxOrderController.cancel` + +* 101 -> 103 + +如果用户没有支付,也没有点击`取消订单`按钮,那么系统会定时查询数据库的订单信息。 +如果发现存在订单未支付状态超时半小时,此时系统会自动取消订单,来释放商品资源。 + +对应的应该是litemall-admin-api模块的系统定时任务的`OrderJob.checkOrderUnpaid` + +* 101 -> 201 + +如果用户支付,微信商户平台会向小商场后台服务推送支付结果。 +而响应结果表示支付成功,则订单状态信息设置201,表示支付成功。 + +所对应的后台服务方法是litemall-wx-api模块的`WxOrderController.payNotify` + +* 201 -> 202 + +当用户支付以后,管理员未发货前,用户可以点击`退款`申请退款取消订单。 +通常用户点击退款以后系统可以基于微信商户平台的退款接口实现自动退款, +但是这里考虑到安全原因,不支持系统自动退款操作。 +相应地,这里小商场后台服务只是设置订单状态,表示退款申请中。 + +所对应的后台服务方法是litemall-wx-api模块的`WxOrderController.refund` + +* 202 -> 203 + +这里退款操作是由管理员在微信商户平台手动退款,然后在本项目的 +管理平台里面点击`退款确认`按钮,此时订单状态会设置成203,表明 +退款已经成功,同时系统会自动恢复订单商品数量。 + +所对应的后台服务方法是litemall-admin-api模块的`AdminOrderController.refundConfirm` + +* 201 -> 301 + +当订单支付以后,管理员进行订单发货,然后在管理平台点击`发货`,填写快递信息, +设置订单状态是301,表示管理员已发货状态。 + +所对应的后台服务方法是litemall-admin-api模块的`AdminOrderController.ship` + +* 301 -> 401 + +当用户收到商品以后,用户点击`收货确认`按钮,设置订单状态401,表示用户成功收货。 + +所对应的后台服务方法是litemall-wx-api模块的`WxOrderController.confirm` + +* 301 -> 402 + +当管理员发货以后,用户一直没有确认收货,系统定时检测订单状态,如果发现发货以后 +七天用户都没有收货,此时系统自动确认用户收货,设置订单状态402。 + +应该改为 litemall-admin-api模块的系统定时任务`OrderJob.checkOrderUnconfirm` + +注意: +> 上述订单状态变化中具体的逻辑处理可以参考相应模块文档和模块代码。 + +#### 2.4.2 用户操作 + +订单状态码标识了订单的状态,但是对于用户而言,真正关心的只是他们能够进行的操作, +也就是在小商场的小程序端用户可以进行点击的按钮操作,目前支持: + +* `支付`,如果下单后未立即支付,则订单详情页面会出现`支付`按钮; +* `取消`,如果用户未支付,则订单详情页面会出现`取消`按钮; +* `退款`,如果用户支付后但是管理员未发货,则订单详情页面会出现`退款`按钮; +* `确认收货`,如果管理员已发货,则订单详情页面会出现`确认收货`按钮; +* `申请退货`,如果用户已经确认收货同时未超过一段时间,则订单详情页面会出现`申请退货`按钮; + 注意,这里如果是系统超时自动确认收货,则不会出现; +* `去评价`,如果用户确认收货以后,则订单详情页面会出现`去评价`按钮; +* `再次购买`,如果用户确认收货以后,则订单详情页面会出现`再次购买`按钮; +* `删除`,如果当前订单状态码是102、103、203、401和402时,则订单详情页面会出现`删除订单`按钮; + 注意,这里的删除操作是逻辑删除,即设置订单的删除状态`deleted`。 + +因此订单状态码和小商场用户操作之间存在映射关系: + +* 101 + + 用户可以`支付`、`取消` + +* 102 + + 用户可以`删除` + +* 103 + + 用户可以`删除` + +* 201 + + 用户可以`退款` + +* 203 + + 用户可以`删除` + +* 301 + + 用户可以`确认收货` + +* 401 + + 用户可以`删除`、`去评价`、`申请售后`、`再次购买` + +* 402 + + 用户可以`删除`、`去评价`、`申请售后`、`再次购买` + +#### 2.4.3 申请售后 + +当用户确认收货或者系统自动确认收货以后,订单可以申请售后。 +目前仅支持订单整体售后,而不支持订单商品独立售后。 +这是因为:订单存在商品售价、优惠券减免、团购减免以及物流运费属性, +如果要支持单个商品退款,那么存在一个需要解决的问题就是单个商品的 +退款金额如何计算。如果开发者这里考虑清楚,也可以参考当前代码实现 +订单商品独立售后 + +litemall_order表中存在`aftersale_status`字段,记录订单售后状态。 +而具体的售后记录则是litemall_aftersale表记录。 + +这里`type`字段表示当前售后类型,目前存在三种类型: + +* 如果type=0,即“未收货退款”,通常是系统超时自动确认收货,而实际上用户没有收货,因此可以选择这个; +* 如果type=1,即“无需退货退款”,通常是用户确认收货后申请售后,而管理员同意可以不需要退货,直接退款给用户; +* 如果type=2,即“退货退款”,通常是用户确认收货后申请售后,管理员同意用户退货,当管理员收到货以后再退款给用户。 + +需要注意的是:当前实现中,如果是“退货退款”类型,那么管理员在进行退款以后,系统会自动恢复货品数量。 +这是因为管理员完成“退货退款”售后,说明管理员已经收到用户的退货。 +开发者可以改变这里的实现逻辑,例如采用独立的退货入库流程。 + +`status`字段表示当前售后状态,分别是: + +* 如果status=0,未申请售后; +* 如果status=1,用户申请售后,等待管理员审核; +* 如果status=2,管理员审核通过,等待管理员退款; +* 如果status=3,管理员已退款,售后完成; +* 如果status=4,管理员审核不通过,售后完成; +* 如果status=5,用户已取消售后,当用户在申请售后以后可以在管理员审核前申请取消。 + +这里需要补充的是:订单litemall_order表的`aftersale_status`字段,和订单售后litemall_aftersale +表的`status`字段是完全一致的,方便前端分别查询订单状态和订单售后状态。 + +`amount`字段表示当前售后退款金额,正如前面所述当前仅支持订单整体售后,因此目前设计的退款金额是 +订单实际付款-订单运费。 + +#### 2.4.4 商品评价 + +在litemall_order表中存在`comments`字段,表示有几个订单商品没有评价; +而在litemall_order_goods表中存在`comment`字段,表示当前订单商品的评论ID。 + +* 当用户确认收货以后,`comments`设置当前订单中未评价的商品数量。而`comment`设置0; +* 当用户评价一个订单商品,`comments`会减一,而`comment`指向新创建的评论; +* 如果用户不评论超期,`comments`会设置0,而`comment`设置-1; + +### 2.5 评论设计 + +评论表litemall_comment保存评论相关的信息,其中最关键的是`type`字段和`value_id`字段。 + +这里`type`字段表示当前评论类型,目前存在两种类型: + +* 如果type=0,则当前评论是订单商品评论,value_id是订单商品ID; +* 如果type=1,则当前评论是专题评论,value_id是专题ID; + +`admin_content`字段则拥有记录管理后台管理员对用户评论的回复。 + +### 2.6 团购设计 + +团购是由团购规则表litemall_groupon_rules和团购活动表litemall_groupon组成。 + +管理员在管理后台对一些商品配置团购规则,保存在litemall_groupon_rules表中。 + +用户在小商场中则看到团购规则给出的优惠信息。 +接下来用户存在两种操作: +第一种是,用户开团,保存在litemall_groupon中,用户主动分享商品团购页面给朋友; +第二张是,用户参团,也保存在litemall_groupon中。 + +只有开团人数符合团购规则条件,创建的订单才会有效,否则管理员需要退款取消当前团购。 + +### 2.7 优惠券设计 + +优惠券由litemall_coupon表和litemall_coupon_user表组成: +* litemall_coupon表,是优惠券基本信息及使用规则。 +* litemall_coupon_user表,是用户优惠券领取和使用的记录。 + +#### 2.7.1 type + +type字段,标识优惠券发送的方式,目前支持: +* 通用券,即在首页或者优惠券列表页,用户可以看到优惠券信息并且点击领取; +* 注册券,即用户注册成功以后即系统自动发送给用户,无需领取; +* 兑换券,即用户在个人优惠券页面输入兑换码来兑换一张优惠券。 + +#### 2.7.2 goods_type + +goods_type字段,标识优惠券所能使用的商品范围: +* 全场通用,即所有商品都能使用; +* 类目限制,**目前不支持**,即某个类目的商品才能使用; +* 商品限制,**目前不支持**,即部分商品才能使用优惠券。 + +#### 2.7.3 time_type + +time_type字段,标识优惠券有效期; +* 用户领券日期的相对天数,即用户领券以后开始几天内有效; +* 管理员设置的绝对时间,即优惠券的开始使用时间和截至使用时间。 + +#### 2.7.4 status + +status字段,标识优惠券的当前状态。 + +这里需要指出的是,litemall_coupon表和litemall_coupon_user表都有status字段。 + +litemall_coupon表的status字段,包含以后三种状态: +* 正常可用, +* 已过期, +* 已下架, + +litemall_coupon_user表的status字段,包含以后三种状态: +* 未使用, +* 已使用, +* 已过期, +* 已下架, + +### 2.8 系统配置设计 + +系统配置表litemall_system保存系统的配置信息。 + +这里需要注意的是,在Java代码层系统配置表只能执行更新操作, +不能执行创建和删除操作。也就是说,系统配置数据都应该是开发者 +基于系统的配置需求在数据库中手动创建。 + +### 2.9 存储对象设计 + +存储对象表litemall_storage保存上传文件信息。 + +当用户或者管理员上传图像时,图像文件会保存到本地或者第三方云存储服务器中, +同时在存储对象表中记录一下。 + +### 2.10 操作日志设计 + +业务日志表litemall_log记录管理员的关键性操作。 + +需要讨论的是,很多项目的业务日志模块采用注解方式,即加上方法注解,因此可以自动捕获 +用户的操作行为。虽然这样做很方便且不会影响业务代码,但是实际上最终是粗颗粒地记录,反而记录意义不大。 + +因此本项目采用在方法内手写业务日志代码方式记录业务操作行为及结果。 +虽然比较繁琐,但是可以保证记录是细颗粒的。而且,如果管理员最终关心的操作较少,那么 +实际上需要写的代码不是很多。 + +考虑到语义,操作业务应该是“谁做了什么操作,结果成功还是失败,失败原因是什么,补充信息是什么”, +因此这里设计的业务日志表关键字段如下: +* 管理员 +* IP地址 +* 操作分类 +* 操作动作 +* 操作状态 +* 操作结果 +* 补充信息 + +#### 2.10.1 操作类别 + +这里的日志类型设计成四种(当然开发者需要可以扩展) +* 一般日志:用户觉得需要查看的一般操作日志,建议是默认的日志级别 +* 安全日志:用户安全相关的操作日志,例如登录、删除管理员 +* 订单日志:用户交易相关的操作日志,例如订单发货、退款 +* 其他日志:如果以上三种不合适,可以选择其他日志,建议是优先级最低的日志级别 + +当然建议开发者应该和最终用户讨论交流,记录真正关键性的业务操作,例如登录相关或订单相关等。 + +#### 2.10.2 操作结果 + +如果操作成功,可以使用操作结果字段记录被操作的对象。 +当然,有些操作没有具体对象,那么可以省略。 + +如果操作失败,也可以使用操作结果字段记录失败的原因。 + +#### 2.10.3 操作失败 + +虽然这里有操作状态字段和操作结果字段,可以记录操作失败的状态。 +但是通常失败操作不会对系统或者数据库带来影响,因此实际上开发者其实不需要 +记录太多操作失败的日志,而是记录操作成功的日志,告诉系统管理员当前状态的变化。 + +当然,是否记录操作失败取决于开发者或者最终用户是否需要。 +例如,登录这里应该记录用户登录失败的日志,因为保存的IP地址可以帮助管理员了解 +系统被访问的情况。 + +### 2.11 通用设计 + +除了以上表,数据库还存在其他一些业务表,例如专题表litemall_topic, +但是都很直观,不需要多讨论。 + +以下是一些表设计中无具体业务意义可通用的字段。 + +#### 2.11.1 deleted + +除极少数表,其他所有表都存在`deleted`字段,支持逻辑删除。 +因此目前删除数据时,不会直接删除数据,而是修改`deleted`字段。 +当然,数据库管理员可以连接到数据库直接删除数据,或者开发者 +可以修改这里的逻辑采用物理删除。 + +#### 2.11.2 add_time + +除极少数表,其他所有表都存在`add_time`字段,记录数据创建时间。 + +#### 2.11.3 update_time + +除极少数表,其他所有表都存在`update_time`字段,记录数据修改时间。 + +此外,此外开发者可以利用update_time来实现乐观锁更新机制。 \ No newline at end of file diff --git a/doc/mobmall.md b/doc/mobmall.md new file mode 100644 index 0000000000000000000000000000000000000000..c44ef399c11ffe3254c1dfe183e1ae38026fb1d2 --- /dev/null +++ b/doc/mobmall.md @@ -0,0 +1,29 @@ +# 5 litemall轻商城 + +litemall轻商城,是商城移动版本。 + +技术: + +* 轻商城前端,即litemall-vue模块 + * vue-cli3 + * Vue + Vue-router + Vant + Sass + * axios + * fastclick + * babel-polyfill + * @xkeshi/vue-countdown + * Vant +* 轻商城后端,即litemall-wx-api模块,也就是和小商城后端是一样的。 + * Spring Boot 2.x + * Spring MVC + * [weixin-java-tools](https://gitee.com/binary/weixin-java-tools) + + +## 5.1 litemall-wx-api + +可以阅读3.1 + +## 5.2 litemall-vue + +这里的代码基于[vant--mobile-mall](https://github.com/qianzhaoy/vant--mobile-mall) + +文档未完成。 diff --git a/doc/performance.md b/doc/performance.md new file mode 100644 index 0000000000000000000000000000000000000000..f45a723d40d5eedd75268a4021ca591d0f4e9557 --- /dev/null +++ b/doc/performance.md @@ -0,0 +1,22 @@ +# 性能 + +## 1 小程序性能 + +## 2 VUE性能 + +## 3 Spring Boot性能 + +### 3.1 gzip压缩 + +在litemall-all模块中配置gzip压缩 +``` +server: + compression: + enabled: true + min-response-size: 2048 + mime-types: application/javascript,text/css,application/json,application/xml,text/html,text/xml,text/plain +``` + +## 4 数据库性能 + +## 5 其他 \ No newline at end of file diff --git a/doc/pic/1.png b/doc/pic/1.png deleted file mode 100644 index 278333b56af99992d9586b9e38ec51617952ba4b..0000000000000000000000000000000000000000 Binary files a/doc/pic/1.png and /dev/null differ diff --git a/doc/pic/2.png b/doc/pic/2.png deleted file mode 100644 index 00b3fa1b8bee89b5e965a85d7f350aae2b52dad2..0000000000000000000000000000000000000000 Binary files a/doc/pic/2.png and /dev/null differ diff --git a/doc/pic/4.png b/doc/pic/4.png deleted file mode 100644 index 652048b7339588887318333e37cb1a7b1e172b87..0000000000000000000000000000000000000000 Binary files a/doc/pic/4.png and /dev/null differ diff --git a/doc/pic1/1-1.png b/doc/pic1/1-1.png deleted file mode 100644 index 0a7f70c0f103345b250355ac11c31676d29c0570..0000000000000000000000000000000000000000 Binary files a/doc/pic1/1-1.png and /dev/null differ diff --git a/doc/pic1/1-11.png b/doc/pic1/1-11.png deleted file mode 100644 index 1e42df55ae1a68d6386e8d178f940da1deba88b1..0000000000000000000000000000000000000000 Binary files a/doc/pic1/1-11.png and /dev/null differ diff --git a/doc/pic1/1-12.png b/doc/pic1/1-12.png deleted file mode 100644 index fe9907c4374412c3868f0149545db208c9956374..0000000000000000000000000000000000000000 Binary files a/doc/pic1/1-12.png and /dev/null differ diff --git a/doc/pic1/1-2.png b/doc/pic1/1-2.png deleted file mode 100644 index 06a1cabbbd78d51e2b01b700df5ac515480df7b6..0000000000000000000000000000000000000000 Binary files a/doc/pic1/1-2.png and /dev/null differ diff --git a/doc/pic1/1-3.png b/doc/pic1/1-3.png deleted file mode 100644 index 8532f66e5d8a07525174122c9a70b20fb63fcce6..0000000000000000000000000000000000000000 Binary files a/doc/pic1/1-3.png and /dev/null differ diff --git a/doc/pic1/1-5.png b/doc/pic1/1-5.png deleted file mode 100644 index 18fd2bf3e4396e1203d94366c65a0d9cda64136a..0000000000000000000000000000000000000000 Binary files a/doc/pic1/1-5.png and /dev/null differ diff --git a/doc/pic1/1-6.png b/doc/pic1/1-6.png deleted file mode 100644 index ac8bb87646167876e28c505be6fc008fd0ae9adb..0000000000000000000000000000000000000000 Binary files a/doc/pic1/1-6.png and /dev/null differ diff --git a/doc/pic1/1-7.png b/doc/pic1/1-7.png deleted file mode 100644 index ef2b2414f70167fdfaa534177c1e85bdb3686854..0000000000000000000000000000000000000000 Binary files a/doc/pic1/1-7.png and /dev/null differ diff --git a/doc/pics/admin/get.png b/doc/pics/admin/get.png new file mode 100644 index 0000000000000000000000000000000000000000..968694bbf76572a5ab440caa536471ee62c2207d Binary files /dev/null and b/doc/pics/admin/get.png differ diff --git a/doc/pics/admin/get_no_token.png b/doc/pics/admin/get_no_token.png new file mode 100644 index 0000000000000000000000000000000000000000..35f6700d34acf974c5fb2b3a512236bbcaaba810 Binary files /dev/null and b/doc/pics/admin/get_no_token.png differ diff --git a/doc/pics/admin/get_with_token.png b/doc/pics/admin/get_with_token.png new file mode 100644 index 0000000000000000000000000000000000000000..634a7df2d84ec7919500caaed31dbdff0e9e560e Binary files /dev/null and b/doc/pics/admin/get_with_token.png differ diff --git a/doc/pics/admin/login.png b/doc/pics/admin/login.png new file mode 100644 index 0000000000000000000000000000000000000000..60d6148c9d02b3ac854dddd175e8bc7859bc1a65 Binary files /dev/null and b/doc/pics/admin/login.png differ diff --git a/doc/pics/admin/post_no_token.png b/doc/pics/admin/post_no_token.png new file mode 100644 index 0000000000000000000000000000000000000000..7c81b7b3ce8dde0d73f500b286c77d7fdbeb0689 Binary files /dev/null and b/doc/pics/admin/post_no_token.png differ diff --git a/doc/pics/admin/post_with_token.png b/doc/pics/admin/post_with_token.png new file mode 100644 index 0000000000000000000000000000000000000000..13c1f1a4c7cc7bb92201f0782a6de5442dcddeff Binary files /dev/null and b/doc/pics/admin/post_with_token.png differ diff --git a/doc/pics/database/admin.png b/doc/pics/database/admin.png new file mode 100644 index 0000000000000000000000000000000000000000..d71300f74a4f8ec99b7694442304a2fbf827c7d3 Binary files /dev/null and b/doc/pics/database/admin.png differ diff --git a/doc/pics/database/goods.png b/doc/pics/database/goods.png new file mode 100644 index 0000000000000000000000000000000000000000..2a7d37ad47db5a938fbd03bd5e8df6aac2e6cece Binary files /dev/null and b/doc/pics/database/goods.png differ diff --git a/doc/pic2/2-1.png b/doc/pics/database/order-status.png similarity index 100% rename from doc/pic2/2-1.png rename to doc/pics/database/order-status.png diff --git a/doc/pics/database/order.png b/doc/pics/database/order.png new file mode 100644 index 0000000000000000000000000000000000000000..d4780416eef57bede742ecc270158970f5f28022 Binary files /dev/null and b/doc/pics/database/order.png differ diff --git a/doc/pics/database/others.png b/doc/pics/database/others.png new file mode 100644 index 0000000000000000000000000000000000000000..feaf3f4172ac4df3448ab0f74c06055b81f00b31 Binary files /dev/null and b/doc/pics/database/others.png differ diff --git a/doc/pics/database/user.png b/doc/pics/database/user.png new file mode 100644 index 0000000000000000000000000000000000000000..db4c37a04cceadd497d26239b2374ccbf253c506 Binary files /dev/null and b/doc/pics/database/user.png differ diff --git a/doc/pic/excluded.png b/doc/pics/faq/excluded.png similarity index 100% rename from doc/pic/excluded.png rename to doc/pics/faq/excluded.png diff --git a/doc/pic2/2-2.png b/doc/pics/platform/db-main.png similarity index 100% rename from doc/pic2/2-2.png rename to doc/pics/platform/db-main.png diff --git a/doc/pic2/2-3.png b/doc/pics/platform/mybatis-generator.png similarity index 100% rename from doc/pic2/2-3.png rename to doc/pics/platform/mybatis-generator.png diff --git a/doc/pics/project/deploy-single.png b/doc/pics/project/deploy-single.png new file mode 100644 index 0000000000000000000000000000000000000000..35ddfafb09f1279da7da4a383b421ccf12a5e666 Binary files /dev/null and b/doc/pics/project/deploy-single.png differ diff --git a/doc/pics/project/develop-stage.png b/doc/pics/project/develop-stage.png new file mode 100644 index 0000000000000000000000000000000000000000..901aac4e1c0c4e5deb32ef43467e2d936e37d37a Binary files /dev/null and b/doc/pics/project/develop-stage.png differ diff --git a/doc/pic1/1-8.png b/doc/pics/project/idea-maven-insatll.png similarity index 100% rename from doc/pic1/1-8.png rename to doc/pics/project/idea-maven-insatll.png diff --git a/doc/pic1/1-9.png b/doc/pics/project/idea-run-all.png similarity index 100% rename from doc/pic1/1-9.png rename to doc/pics/project/idea-run-all.png diff --git a/doc/pic1/1-13.png b/doc/pics/project/maven-profile.png similarity index 100% rename from doc/pic1/1-13.png rename to doc/pics/project/maven-profile.png diff --git a/doc/pics/project/online-deploy.png b/doc/pics/project/online-deploy.png new file mode 100644 index 0000000000000000000000000000000000000000..ee829c788554783fa924aac959802bc9dc3a75ce Binary files /dev/null and b/doc/pics/project/online-deploy.png differ diff --git a/doc/pics/project/project-structure.png b/doc/pics/project/project-structure.png new file mode 100644 index 0000000000000000000000000000000000000000..769c557e2141d9c9a3b4748bb1373f59d3282083 Binary files /dev/null and b/doc/pics/project/project-structure.png differ diff --git a/doc/pic1/1-4.png b/doc/pics/project/security-group.png similarity index 100% rename from doc/pic1/1-4.png rename to doc/pics/project/security-group.png diff --git a/doc/pic1/1-14.png b/doc/pics/project/spring-profile.png similarity index 100% rename from doc/pic1/1-14.png rename to doc/pics/project/spring-profile.png diff --git a/doc/pic1/1-10.png b/doc/pics/project/stage.png similarity index 100% rename from doc/pic1/1-10.png rename to doc/pics/project/stage.png diff --git a/doc/pic1/1-15.png b/doc/pics/project/yml-resource.png similarity index 100% rename from doc/pic1/1-15.png rename to doc/pics/project/yml-resource.png diff --git a/doc/pics/readme/admin-dashboard.png b/doc/pics/readme/admin-dashboard.png new file mode 100644 index 0000000000000000000000000000000000000000..4f3cc89d7b7bc5cb31134ffc5e9538a6e2348244 Binary files /dev/null and b/doc/pics/readme/admin-dashboard.png differ diff --git a/doc/pics/readme/litemall_wx_demo.png b/doc/pics/readme/litemall_wx_demo.png new file mode 100644 index 0000000000000000000000000000000000000000..163003d7c7e35f58233a15b17119a2eb92658b73 Binary files /dev/null and b/doc/pics/readme/litemall_wx_demo.png differ diff --git a/doc/pics/readme/mobmall.png b/doc/pics/readme/mobmall.png new file mode 100644 index 0000000000000000000000000000000000000000..1acc5fa810a605ee01e330be4f1844bd5b908b2e Binary files /dev/null and b/doc/pics/readme/mobmall.png differ diff --git a/doc/pics/readme/project-structure.png b/doc/pics/readme/project-structure.png new file mode 100644 index 0000000000000000000000000000000000000000..09b84b27c49de15bdd4b52601f0923be5680ba18 Binary files /dev/null and b/doc/pics/readme/project-structure.png differ diff --git a/doc/pics/readme/qq3.png b/doc/pics/readme/qq3.png new file mode 100644 index 0000000000000000000000000000000000000000..e1cfbb6df50bbd940be266f1a9af77297625a09b Binary files /dev/null and b/doc/pics/readme/qq3.png differ diff --git a/doc/pic/demo.png b/doc/pics/readme/renard_wx_demo.png similarity index 100% rename from doc/pic/demo.png rename to doc/pics/readme/renard_wx_demo.png diff --git a/doc/pics/readme/technology-stack.png b/doc/pics/readme/technology-stack.png new file mode 100644 index 0000000000000000000000000000000000000000..28d117792884fa8288720e24f16fdb53ae518192 Binary files /dev/null and b/doc/pics/readme/technology-stack.png differ diff --git a/doc/platform.md b/doc/platform.md index 29b5aef1081b698bdb7420b576b5bc144897d274..937259133b025fdbffe2a59dd6657d9c48f4a5c6 100644 --- a/doc/platform.md +++ b/doc/platform.md @@ -2,10 +2,10 @@ 目前litemall基础系统由以下部分组成: -* litemall数据库 * litemall-core模块 * litemall-db模块 * litemall-all模块 +* litemall-all-war模块 litemall-db模块提供数据库访问服务。 @@ -14,480 +14,7 @@ litemall-core模块提供通用服务。 litemall-all模块则只是一个包裹模块,几乎没有任何代码。该模块的作用是融合两个spring boot模块 和litemall-admin模块静态文件到一个单独Spring Boot可执行jar包中。 -## 2.1 litemall数据库 - -litemall数据库基于nideshop中的[nideshop.sql](https://github.com/tumobi/nideshop/blob/master/nideshop.sql)数据库,然后在实际开发过程中进行了调整和修改: - -* 删除了一些目前不必要的表; -* 删除了表中一些目前不必要的字段; -* 行政区域数据litemall_region没有采用原nideshop中的数据,而是采用了[Administrative-divisions-of-China](https://github.com/modood/Administrative-divisions-of-China); -* 表中的某些字段采用JSON存储; -* 表中的日期或时间字段采用DATE、DATETIME; -* 字段的数据类型粗粒度化,例如避免MEDIUMINT,而是INT; -* 表的数据做了清理、调整和补充(假数据)。 - -litemall数据库由三个sql文件组成,在litemall-db文件夹下面的sql文件夹中: - -1. litemall_schema.sql - - 作用是创建空数据库、创建用户、设置访问权限。 - - 开发者开发测试阶段可以使用,但是部署生产阶段一定要注意修改这里的默认用户名和密码。 - - 注意,这里的sql文件不一定需要运行,开发者可以自己手动或命令行或IDE进行对应的操作即可。 - -2. litemall_table.sql - - 作用是创建数据库表,但是没有创建任何数据。 - - 因此,开发者可以在部署生产阶段直接使用。 - -3. litemall_data.sql - - 作用是创建测试数据。 - - 开发者开发测试阶段可以使用,但是部署开发阶段应该使用自己的数据。 - -综上,这里litemall真正必须运行的sql文件是litemall_table.sql,其他两个sql文件开发者自行决定如何是否使用。 - -以下讨论一些关键性设计 - -注意: -> 以下设计基于个人理解,很可能存在不合理或者与实际系统不符合的地方。 - -### 2.1.1 商品和货品设计 - -这里商品存在商品表(litemall_goods),商品属性表(litemall_goods_attribute),商品规格表(litemall_goods_specification),商品货品表(litemall_goods_product)四种表 - -商品表是一种商品的基本信息,主要包括商品介绍,商品图片,商品所属类目,商品品牌商等; - -商品参数表其实也是商品的基本信息,但是由于是一对多关系,因此不能直接保存在商品表中(虽然采用JSON也可以但是不合理), -因此采用独立的商品参数表,通常是商品的一些公共基本商品参数; - -商品规格表是商品进一步区分货品的标识,例如同样一款衣服,基本信息一致,基本属性一致,但是在尺寸这个属性上可以 -把衣服区分成多个货品,而且造成对应的数量和价格不一致。商品规格可以看着是商品属性,但具有特殊特征。 - -商品规格和规格值存在以下几种关系: - -* 单一规格和单一规格值,最常见的,即当前商品存在一种货品; -* 单一规格和多个规格值,较常见,即当前商品基于某个规格存在多种货品,通常价格都是相同的,当然也可能不相同; -* 多个规格和单一规格值,可以简化成第一种情况,或者采用第四种情况,通常实际情况下不常见; -* 多个规格和多个规格值,通常是两种规格或者三种规格较为常见,而且对应的价格不完全相同。 - -商品货品表则是最终实现商品库存管理、购买业务的实体对象,存在多个规格值、数量和价格。 -例如,同样的衣服品牌,可能因为不能尺寸和颜色而存在最终的货品,这里每个货品的价格可以一样,也可以不一样。 - -总结一下,一个普通商品,实际上在数据库中,存在一个商品表项,存在(至少0个)多个商品属性表项目,存在(至少一个)多个商品规格表项, -存在(至少一个)多个货品表项。 - -举例如下: - -* 一个商品“2018春季衣服商品编号1111111”, -* 存在两个商品参数, - * 属性名称“面向人群”,属性值“男士” - * 属性名称“面料”,属性值“100%棉” -* 存在两种规格(分别五个规格值和三个规格值)共八个商品规格项, - * 规格名称“尺寸”,规则值“S” - * 规格名称“尺寸”,规则值“M” - * 规格名称“尺寸”,规则值“L” - * 规格名称“尺寸”,规则值“XL” - * 规格名称“尺寸”,规则值“XXL” - * 规格名称“颜色”,规格值“蓝色” - * 规格名称“颜色”,规格值“灰色” - * 规格名称“颜色”,规格值“黑色” -* 存在15个货品(尺寸*颜色=15个货品) - * 货品“S蓝”,数量 100, 价格 100 - * 货品“M蓝”,数量 100, 价格 100 - * 货品“L蓝”,数量 100, 价格 100 - * 货品“XL蓝”,数量 100, 价格 100 - * 货品“XXL蓝”,数量 100, 价格 100 - * 货品“S灰”,数量 100, 价格 100 - * 货品“M灰”,数量 100, 价格 100 - * 货品“L灰”,数量 100, 价格 100 - * 货品“XL灰”,数量 100, 价格 100 - * 货品“XXL灰”,数量 100, 价格 100 - * 货品“S黑”,数量 100, 价格 100 - * 货品“M黑”,数量 100, 价格 100 - * 货品“L黑”,数量 100, 价格 100 - * 货品“XL黑”,数量 0, 价格 100 - * 货品“XXL黑”,数量 0, 价格 100 - -以下是一些细节的讨论: - -* 商品表中可能存在数量和价格属性,而货品中也存在数量和价格属性,目前设计这样: - * 商品表的价格应该和某个货品的价格一样,通常应该是所有货品价格的最小值,或者基本款式的价格; - * 商品表中的数量和价格应该仅用于展示,而不能用于最终的订单价格计算; - * 商品表的数量应该设置成所有货品数量的总和; - * 在管理后台添加商品时,如果管理员不填写商品表的数量和价格属性,则自动填写合适的值;如果填写,则使用显示。 - * 当小商城中,用户查看商品详情时,初始显示商品表的价格,而如果用户选择具体规格后,则商品 - 详情里面的价格需要自动切换到该规格的价格。 -* 商品规格可以存在规格图片,效果是规格名称前放置规格图片 -* 货品也可以存在货品图片,效果是所有规格选定以后对应的货品有货,则在货品价格前放置货品图片 -* 如果商品是两种规格,分别是M个和N个规格值,那么通常应该是`M*N`个货品,但是有些货品可能天然不存在。 - 目前这里要求所有货品信息都应该存在,如果实际中货品不存在,也要设置商品数量为0. - -注意: - -> 这里的设计可能与实际项目设计不一致,但是目前是可行的。 -> 商品的中文用语“商品”和英语用语“goods”,货品的中文用语“货品”和英语用语“product”可能是不正确的。 - -### 2.1.2 用户和微信用户设计 - -目前准备支持用户普通账号登录和微信登录两种方式,两种登录方式仅仅采用一个litemall-user表可能不是很合适。 - -外,如果进一步支持其他多种第三方登录,那么这里需要重新设计。 - -### 2.1.3 行政区域设计 - -litemall_region表保存了行政区域信息,包括省级、市级、县级三个等级, - -原nideship.sql中存在region数据,但是litemall.sql的region数据则来自 -[Administrative-divisions-of-China](https://github.com/modood/Administrative-divisions-of-China)项目。 - -### 2.1.4 订单设计 - -订单信息主要由基本信息、商品信息、地址信息、费用信息、快递信息、支付信息和其他信息组成, -由litemall_order表和litemall_order_goods表保存。 - -* 基本信息 - -订单创建时的一些基本信息,例如用户、订单状态和订单留言等。 -其中订单状态是最重要的信息。 - -* 商品信息 - -由于订单可以存在多个商品,因此订单的商品信息是由独立的订单商品表记录(可能更应该称为货品)。 - -* 费用信息 - -订单一些费用情况,例如商品总价、优惠减免和实际付费等。 - -* 收货信息 - -用户下单时选择的收货地址以及联系人信息。 - -* 快递信息 - -目前快递信息仅仅记录快递公司、快递单号、快递发出时间。 -而如果快递过程中如果存在一些异常,例如物品丢失,则目前系统难以处理。 - -* 支付信息 - -支付时间和支付订单ID。 - -* 评论信息 - -订单商品的评论情况。 - -* 其他信息 - -#### 2.1.4.1 订单状态 - -![](pic2/2-1.png) - -订单分成几种基本的状态: - -* 101 - - 状态码101,此时订单生成,记录订单编号、收货地址信息、订单商品信息和订单相关费用信息; - -* 201 - - 状态码201,此时用户微信支付付款,系统记录微信支付订单号、支付时间、支付状态; - -* 301 - - 状态码301,此时商场已经发货,系统记录快递公司、快递单号、快递发送时间。 - 当快递公司反馈用户签收后,系统记录快递到达时间。 - -* 401 - - 状态码401,当用户收到货以后点击确认收货,系统记录确认时间。 - -以上是一个订单成功完成的基本流程,但实际中还存在其他情况。 - -* 102 - - 状态码102,用户下单后未付款之前,点击取消按钮,系统记录结束时间 - -* 103 - - 状态码103,用户下单后半小时未付款则系统自动取消,系统记录结束时间 - -* 202 - - 状态码202,用户付款以后未发货前,点击退款按钮,系统进行设置退款状态,等待管理员退款操作 - -* 203 - - 状态码203,管理员在管理后台看到用户的退款申请,点击退款按钮进行退款操作。 - -* 402 - - 状态码402,用户已签收却不点击确认收货,超期7天以后,则系统自动确认收货。 - 用户不能再点击确认收货按钮,但是可以评价订单商品。 - -此外,当订单状态码是102、103、203、401和402时,订单可以执行删除操作。 -目前的设计是不执行物理删除,而是逻辑删除,因此用户查看自己订单时将看不到这些“已删除”的订单。 - -注意: -> 在上图中可以看到`101`到`101`的状态变化,这里只是小商场用户的操作,不会影响订单状态码。 -> 如果用户点击付款时,后端服务会生成预支付会话id,但是不会影响订单状态。 -> 如果而用户支付过程中,放弃支付,则也不会影响订单状态。 - -#### 2.1.4.2 状态变化 - -* 初始 -> 101 - -小商场用户在小商场点击`下单`按钮,此时小商城后端服务会生产商户订单。 - -所对应的后台服务方法是litemall-wx-api模块的`WxOrderController.submit`方法。 - -* 101 -> 101 - -这里开发者可能会奇怪,这里存在101->101的变化,这里表明后台没有响应 -小程序端的请求,但是这里的响应没有导致订单状态实际的变化。这里所指的 -响应小程序端请求是指下单成功以后小程序端自动请求付款或者用户在订单页面中 -点击`付款`所导致的对后台服务的预支付请求。 - -关于微信支付流程,可以参看官方文档的[小程序支付业务流程](https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_4&index=3) -也就是说这里小商城后台服务会返回付支付信息。 - -所对应的后台服务方法是litemall-wx-api模块的`WxOrderController.prepay` - -小商城接收返回的预支付信息后,会在小程序端出现支付页面。 -如果用户放弃支付,则不会出现任何效果,不会向小商场后台服务发送任何信息。 -如果用户支付,则会导致微信商户平台向小商场后台服务推送支付结果。 - -* 101 -> 102 - -如果用户没有支付,那么此时用户可以点击`取消订单`按钮来放弃当前订单。 - -所对应的后台服务方法是litemall-wx-api模块的`WxOrderController.cancel` - -* 101 -> 103 - -如果用户没有支付,也没有点击`取消订单`按钮,那么系统会定时查询数据库的订单信息。 -如果发现存在订单未支付状态超时半小时,此时系统会自动取消订单,来释放商品资源。 - -所对应的后台服务方法是litemall-admin-api模块的`AdminOrderController.checkOrderUnpaid` - -* 101 -> 201 - -如果用户支付,微信商户平台会向小商场后台服务推送支付结果。 -而响应结果表示支付成功,则订单状态信息设置201,表示支付成功。 - -所对应的后台服务方法是litemall-wx-api模块的`WxOrderController.payNotify` - -* 201 -> 202 - -当用户支付以后,管理员未发货前,用户可以点击`退款`申请退款取消订单。 -通常用户点击退款以后系统可以基于微信商户平台的退款接口实现自动退款, -但是这里考虑到安全原因,不支持系统自动退款操作。 -相应地,这里小商场后台服务只是设置订单状态,表示退款申请中。 - -所对应的后台服务方法是litemall-wx-api模块的`WxOrderController.refund` - -* 202 -> 203 - -这里退款操作是由管理员在微信商户平台手动退款,然后在本项目的 -管理平台里面点击`退款确认`按钮,此时订单状态会设置成203,表明 -退款已经成功,同时系统会自动恢复订单商品数量。 - -所对应的后台服务方法是litemall-admin-api模块的`AdminOrderController.refundConfirm` - -* 201 -> 301 - -当订单支付以后,管理员进行订单发货,然后在管理平台点击`发货`,填写快递信息, -设置订单状态是301,表示管理员已发货状态。 - -所对应的后台服务方法是litemall-admin-api模块的`AdminOrderController.ship` - -* 301 -> 401 - -当用户收到商品以后,用户点击`收货确认`按钮,设置订单状态401,表示用户成功收货。 - -所对应的后台服务方法是litemall-wx-api模块的`WxOrderController.confirm` - -* 301 -> 402 - -当管理员发货以后,用户一直没有确认收货,系统定时检测订单状态,如果发现发货以后 -七天用户都没有收货,此时系统自动确认用户收货,设置订单状态402。 - -所对应的后台服务方法是litemall-admin-api模块的`AdminOrderController.checkOrderUnpaid` - -注意: -> 上述订单状态变化中具体的逻辑处理可以参考相应模块文档和模块代码。 - -#### 2.1.4.2 用户操作 - -订单状态码标识了订单的状态,但是对于用户而言,真正关心的只是他们能够进行的操作, -也就是在小商场的小程序端用户可以进行点击的按钮操作,目前支持: - -* `支付`,如果下单后未立即支付,则订单详情页面会出现`支付`按钮; -* `取消`,如果用户未支付,则订单详情页面会出现`取消`按钮; -* `退款`,如果用户支付后但是管理员未发货,则订单详情页面会出现`退款`按钮; -* `确认收货`,如果管理员已发货,则订单详情页面会出现`确认收货`按钮; -* `申请退货`,如果用户已经确认收货同时未超过一段时间,则订单详情页面会出现`申请退货`按钮; - 注意,这里如果是系统超时自动确认收货,则不会出现; -* `去评价`,如果用户确认收货以后,则订单详情页面会出现`去评价`按钮; -* `再次购买`,如果用户确认收货以后,则订单详情页面会出现`再次购买`按钮; -* `删除`,如果当前订单状态码是102、103、203、401和402时,则订单详情页面会出现`删除订单`按钮; - 注意,这里的删除操作是逻辑删除,即设置订单的删除状态`deleted`。 - -因此订单状态码和小商场用户操作之间存在映射关系: - -* 101 - - 用户可以`支付`、`取消` - -* 102 - - 用户可以`删除` - -* 103 - - 用户可以`删除` - -* 201 - - 用户可以`退款` - -* 203 - - 用户可以`删除` - -* 301 - - 用户可以`确认收货` - -* 401 - - 用户可以`删除`、`去评价`、`再次购买` - -* 402 - - 用户可以`删除`、`去评价`、`再次购买` - -#### 2.1.4.3 售后处理 - -目前不支持退货售后相关业务。 - -#### 2.1.4.4 商品评价 - -在litemall_order表中存在`comments`字段,表示有几个订单商品没有评价; -而在litemall_order_goods表中存在`comment`字段,表示当前订单商品的评论ID。 - -* 当用户确认收货以后,`comments`设置当前订单中未评价的商品数量。而`comment`设置0; -* 当用户评价一个订单商品,`comments`会减一,而`comment`指向新创建的评论; -* 如果用户不评论超期,`comments`会设置0,而`comment`设置-1; - -### 2.1.5 评论设计 - -评论表litemall_comment保存评论相关的信息,其中最关键的是`type`字段和`value_id`字段。 - -这里`type`字段表示当前评论类型,目前存在三种类型: - -* 如果type=0,则当前评论是订单商品评论,value_id是订单商品ID; -* 如果type=1,则当前评论是专题评论,value_id是专题ID; -* 如果type=2,则当前评论是订单商品评论的回复,value_id是订单商品的评论ID。 - -### 2.1.6 团购设计 - -团购是由团购规则表litemall_groupon_rules和团购活动表litemall_groupon组成。 - -管理员在管理后台对一些商品配置团购规则,保存在litemall_groupon_rules表中。 - -用户在小商场中则看到团购规则给出的优惠信息。 -接下来用户存在两种操作: -第一种是,用户开团,保存在litemall_groupon中,用户主动分享商品团购页面给朋友; -第二张是,用户参团,也保存在litemall_groupon中。 - -只有开团人数符合团购规则条件,创建的订单才会有效,否则管理员需要退款取消当前团购。 - -### 2.1.7 优惠券设计 - -优惠券由litemall_coupon表和litemall_coupon_user表组成: -* litemall_coupon表,是优惠券基本信息及使用规则。 -* litemall_coupon_user表,是用户优惠券领取和使用的记录。 - -#### 2.1.7.1 type - -type字段,标识优惠券发送的方式,目前支持: -* 通用券,即在首页或者优惠券列表页,用户可以看到优惠券信息并且点击领取; -* 注册券,即用户注册成功以后即系统自动发送给用户,无需领取; -* 兑换券,即用户在个人优惠券页面输入兑换码来兑换一张优惠券。 - -#### 2.1.7.2 goods_type - -goods_type字段,标识优惠券所能使用的商品范围: -* 全场通用,即所有商品都能使用; -* 类目限制,**目前不支持**,即某个类目的商品才能使用; -* 商品限制,**目前不支持**,即部分商品才能使用优惠券。 - -#### 2.1.7.3 time_type - -time_type字段,标识优惠券有效期; -* 用户领券日期的相对天数,即用户领券以后开始几天内有效; -* 管理员设置的绝对时间,即优惠券的开始使用时间和截至使用时间。 - -#### 2.1.7.4 status - -status字段,标识优惠券的当前状态。 - -这里需要指出的是,litemall_coupon表和litemall_coupon_user表都有status字段。 - -litemall_coupon表的status字段,包含以后三种状态: -* 正常可用, -* 已过期, -* 已下架, - -litemall_coupon_user表的status字段,包含以后三种状态: -* 未使用, -* 已使用, -* 已过期, -* 已下架, - -### 2.1.8 系统配置设计 - -系统配置表litemall_system保存系统的配置信息。 - -这里需要注意的是,在Java代码层系统配置表只能执行更新操作, -不能执行创建和删除操作。也就是说,系统配置数据都应该是开发者 -基于系统的配置需求在数据库中手动创建。 - -### 2.1.9 存储对象设计 - -存储对象表litemall_storage保存上传文件信息。 - -当用户或者管理员上传图像时,图像文件会保存到本地或者第三方云存储服务器中, -同时在存储对象表中记录一下。 - -### 2.1.10 通用设计 - -除了以上表,数据库还存在其他一些业务表,例如专题表litemall_topic, -但是都很直观,不需要多讨论。 - -以下是一些表设计中无具体业务意义可通用的字段。 - -#### 2.1.10.1 deleted - -除极少数表,其他所有表都存在`deleted`字段,支持逻辑删除。 -因此目前删除数据时,不会直接删除数据,而是修改`deleted`字段。 -当然,数据库管理员可以连接到数据库直接删除数据,或者开发者 -可以修改这里的逻辑采用物理删除。 - -#### 2.1.10.2 add_time - -除极少数表,其他所有表都存在`add_time`字段,记录数据创建时间。 - -#### 2.1.10.3 update_time - -除极少数表,其他所有表都存在`update_time`字段,记录数据修改时间。 - -此外,此外开发者可以利用update_time来实现乐观锁更新机制。 - -具体使用方法可以参考`2.2.6 并发访问` +litemall-all-war模块和litemall-all模块是一样的作用,只是采用war打包方式。 ## 2.2 litemall-db @@ -505,7 +32,7 @@ litemall-db模块是一个普通的Spring Boot应用,基于mybatis框架实现 * Mybatis Generator * Mybatis Generator非官方插件mybatis-generator-plugin -![](./pic2/2-2.png) +![](./pics/platform/db-main.png) 这里litemall-db模块可以分成以下几种代码: @@ -528,7 +55,7 @@ mybatis数据库访问代码是指dao接口代码、dao数据库XML文件和doma #### 2.2.1.1 自动生成代码 -![](./pic2/2-3.png) +![](./pics/platform/mybatis-generator.png) 如上图所示,双击`mybatis-generator:generate`,则mybatis generator插件会: @@ -1042,7 +569,7 @@ public interface Storage { public class WxTopicController { @GetMapping("list") public Object list(@RequestParam(defaultValue = "1") Integer page, - @RequestParam(defaultValue = "10") Integer size, + @RequestParam(defaultValue = "10") Integer limit, @Sort @RequestParam(defaultValue = "add_time") String sort, @Order @RequestParam(defaultValue = "desc") String order) { ... @@ -1096,7 +623,7 @@ public interface Storage { public class WxTopicController { @GetMapping("list") public Object list(@RequestParam(defaultValue = "1") Integer page, - @RequestParam(defaultValue = "10") Integer size, + @RequestParam(defaultValue = "10") Integer limit, @Sort @RequestParam(defaultValue = "add_time") String sort, @Order @RequestParam(defaultValue = "desc") String order) { ... @@ -1105,7 +632,7 @@ public interface Storage { ## 2.4 litemall-all -在章节1.5中讨论的部署方案中设计了一种单主机单服务方案, +在章节1.5中讨论的部署方案中设计了一种单服务器单服务方案, 也就是说两个后台服务和静态文件都部署在一个Spring Boot可执行jar包中。 查看litemall-all模块,代码仅仅只有一个Application类。 @@ -1122,3 +649,9 @@ public interface Storage { 注意: > 这个插件只是简单的拷贝操作;因此开发者应该在打包litemall-all > 之前确保先编译litemall-admin模块得到最终静态文件。 + + +## 2.5 litemall-all-war + +litemall-all-war模块就是对litemall-all模块进行少量调整, +最后打包时会在target目录下面生成litemall.war,用于tomcat部署。 \ No newline at end of file diff --git a/doc/project.md b/doc/project.md index 9de430219d09fe4ee4090007bdf7766516216358..fc9db2b8d8a802cb93a05d75d451df84519a7f24 100644 --- a/doc/project.md +++ b/doc/project.md @@ -2,30 +2,31 @@ ## 1.1 简介 -litemall是一个简单的商场系统,基于现有的开源项目,重新实现一个完整的前后端项目,包含小程序客户端和网页管理端。 +litemall是一个简单的商场系统,基于现有的开源项目,重新实现一个完整的前后端项目,包含小程序客户端、移动客户端和网页管理端。 -![](./pic1/1-1.png) +![](./pics/project/project-structure.png) -项目的架构是三个系统和六个模块: +项目的架构是四个系统和九个模块: * 基础系统子系统(platform) 由数据库、litemall-core模块、litemall-db模块和litemall-all模块组成; -* 小商场子系统(wxmall) +* 小商场子系统(wxmall,即weixin mall) 由litemall-wx-api模块、litemall-wx模块和renard-wx模块组成; +* 轻商城子系统(mobmall,即mobile mall) + + 由litemall-wx-api模块和litemall-vue模块组成。 + 注意,目前这里移动商城子系统的后端和小商场子系统是一样的。 + * 管理后台子系统(admin) 由litemall-admin-api模块和litemall-admin模块组成。 -* 简单商城系统(mall) - - 这里仅列出,目前没有开发计划。 - -而六个模块的开发设计到三种技术栈: +而九个模块的开发设计到三种技术栈: * Spring Boot技术栈 @@ -38,19 +39,63 @@ litemall是一个简单的商场系统,基于现有的开源项目,重新实 * Vue技术栈 - 采用VSC开发工具,开发litemall-admin模块。 + 采用VSC开发工具,开发litemall-admin模块和litemall-vue模块。 + +### 1.1.1 项目特点 + +项目存在以下特点: + +* 数据库方面,只是简单的表,表和表之间的依赖关系没有采用外键设计,而是依赖Java代码在service层面或者业务层面保证。这样做的好处是数据库频繁改动很方便,不会因为外键而导致数据库难以修改; +* 涉及三种技术栈,但是每种技术栈仅涉及最基础的技术; + * 后端技术栈,仅涉及 Spring,Spring Boot, Spring MVC和Mybatis技术,其他后端技术暂时不采用; + * 小程序技术栈,仅涉及miniprogram官方文档; + * 前端技术栈,仅涉及vue, vuex, vue-route和element技术; +* 安全方面,仅采用最基本的代码,提供简单基本的安全服务; +* 性能方面,没有涉及内存数据库缓存功能,而是完全依赖MySQL; +* 对象存储服务方面,支持本地存储和第三方云存储方案。 +* 消息通知方面,支持邮件通知、第三方云短信通知和微信模板通知; +* 部署方便,支持多服务部署和一键部署脚本; +* 文档全面,虽然还在开发中,但是规划中文档和代码注释一定会完成,帮助开发者理解项目。 + +总之,目前的系统只是为了学习技术和业务而开发的一个简单商场原型系统。虽然缺失很多企业级功能,但是是完整和合理的原型系统。 + +注意: +> 以上特点并不一定是优点。 ## 1.2 系统功能 -从业务功能上,目前由五个业务模块组成: +从业务功能上,目前由六个业务模块组成: * 会员业务模块 * 商场业务模块 * 商品业务模块 * 推广业务模块 * 系统业务模块 +* 配置业务模块 + +### 1.2.1 小商城功能 + +* 首页 +* 专题列表、专题详情 +* 分类列表、分类详情 +* 品牌列表、品牌详情 +* 新品首发、人气推荐 +* 团购 +* 搜索 +* 商品详情 +* 商品评价列表、商品评价 +* 购物车 +* 下单 +* 个人 +* 订单列表、订单详情、订单售后 +* 地址列表、地址添加、地址删除 +* 收藏、足迹、关于 + +### 1.2.2 轻商城功能 + +**目前还在开发中,不稳定** -### 1.2.1 小程序端功能 +以下是准备完成的功能: * 首页 * 专题列表、专题详情 @@ -68,7 +113,7 @@ litemall是一个简单的商场系统,基于现有的开源项目,重新实 * 地址列表、地址添加、地址删除 * 收藏、足迹、关于 -### 1.2.2 管理平台功能 +### 1.2.3 管理平台功能 * 会员管理 * 会员管理 @@ -97,40 +142,101 @@ litemall是一个简单的商场系统,基于现有的开源项目,重新实 * 团购活动 * 系统管理 * 管理员 + * 通知管理 * 对象存储 - * 权限管理(待定) + * 权限管理 * 定时任务(待定) - * 参数管理(待定) - * 系统日志(待定) -* 统计管理 + * 操作日志 +* 配置管理 + * 商场配置 + * 小程序配置 + * 运费配置 + * 订单配置 +* 统计报表 * 用户统计 * 订单统计 * 商品统计 +* 个人 + * 通知中心 + * 密码修改 -## 1.3 项目特点 +## 1.3 项目技术 -存在以下特点: +### 1.3.1 技术参考 -* 数据库方面,只是简单的表,表和表之间的依赖关系没有采用外键设计,而是依赖Java代码在service层面或者业务层面保证。这样做的好处是数据库频繁改动很方便,不会因为外键而导致数据库难以修改; -* 涉及三种技术栈,但是每种技术栈仅涉及最基础的技术; - * 后端技术栈,仅涉及 Spring,Spring Boot, Spring MVC和Mybatis技术,其他后端技术暂时不采用; - * 小程序技术栈,仅涉及miniprogram官方文档; - * 前端技术栈,仅涉及vue, vuex, vue-route和element技术; -* 安全方面,仅采用最基本的代码,提供简单基本的安全服务; -* 性能方面,没有涉及内存数据库缓存功能,而是完全依赖MySQL; -* 对象存储服务方面,支持本地存储和第三方云存储方案。 -* 消息通知方面,支持邮件通知、第三方云短信通知和微信模板通知; -* 部署方便,支持多服务部署和一键部署脚本; -* 文档全面,虽然还在开发中,但是规划中文档和代码注释一定会完成,帮助开发者理解项目。 +#### 1.3.1.1 Spring Boot技术 -总之,目前的系统只是为了学习技术和业务而开发的一个简单商场原型系统。虽然缺失很多企业级功能,但是是完整和合理的原型系统。 +Spring Boot技术栈参考以下文档或者项目: -注意: -> 以上特点并不一定是优点。 +1. MySQL + + 了解创建数据库和表、添加、查询、更新和删除即可。 + +2. Spring Boot 2.x + + * https://docs.spring.io/spring-boot/docs/2.1.5.RELEASE/reference/htmlsingle/#getting-started-introducing-spring-boot + * https://docs.spring.io/spring-boot/docs/2.1.5.RELEASE/reference/htmlsingle/#using-boot-maven + + 这里需要了解RestController, Service等注解,以及如何使用自动化配置。 + Spring Boot支持很多功能,开发者使用时查阅。 + +3. Mybatis + + * http://www.mybatis.org/mybatis-3/ + * http://www.mybatis.org/mybatis-3/java-api.html + * http://www.mybatis.org/mybatis-3/sqlmap-xml.html + + 这里可以简单了解,而使用Mybatis Generator来生成Java代码使用即可。 + +4. Mybatis Generator + + * http://www.mybatis.org/generator/running/runningWithMaven.html + * http://www.mybatis.org/generator/generatedobjects/results.html + * http://www.mybatis.org/generator/generatedobjects/exampleClassUsage.html + +5. Mybatis PageHelper + + * https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/en/HowToUse.md + +#### 1.3.1.2 小程序技术 + +1. 小程序 + + * https://developers.weixin.qq.com/miniprogram/dev/index.html + * https://developers.weixin.qq.com/miniprogram/dev/component/ + * https://developers.weixin.qq.com/miniprogram/dev/api/ + * https://developers.weixin.qq.com/community/develop + + 建议小程序方面遇到问题,可以到官方社区查找。 + +2. 微信支付 + + * https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_3&index=1 + +#### 1.3.1.3 Vue技术 + +1. Vue + + * https://cn.vuejs.org/index.html + +2. Vant + + * https://youzan.github.io/vant/#/zh-CN/intro + +3. Element + + * https://element.eleme.cn/#/zh-CN/component/installation + +4. vue-element-admin + + * https://github.com/PanJiaChen/vue-element-admin + * https://panjiachen.github.io/vue-element-admin-site/zh/ + +### 1.3.2 项目阶段 接下来,从项目的开发、部署(测试)和上线三个阶段介绍litemall。 -![](pic1/1-10.png) +![](./pics/project/stage.png) 首先需要明确的是三个不同阶段: @@ -141,7 +247,7 @@ litemall是一个简单的商场系统,基于现有的开源项目,重新实 * dep -即deploy或者deployment,这里指部署(测试阶段),通常代码已经编译打包运行在远程主机中, +即deploy或者deployment,这里指部署(测试阶段),通常代码已经编译打包运行在远程服务器中, 可以对外服务。此外,这里服务访问地址通常是IP地址。如果IP是公网IP,那么 部署以后就可以对外服务;如果是内网地址,那么只能内网访问。这里的“用户”主要是 指开发者本身、测试者;当然,如果是局域网或者不介意IP访问的,那么这里的“用户” @@ -149,7 +255,7 @@ litemall是一个简单的商场系统,基于现有的开源项目,重新实 * prod -即product或者production,这里指上线阶段,通常也是代码编译打包运行在远处主机中可以对外服务。 +即product或者production,这里指上线阶段,通常也是代码编译打包运行在远处服务器中可以对外服务。 此外,这里服务访问地址通常是域名地址,同时端口是80web端口。上线以后直接面向的是最终用户。 虽然服务的代码本身和dep是完全一样的,但是考虑到场景的不同,上线阶段可能在运行环境方面需要做 调整,例如采用反向代理屏蔽内部实际项目结构。此外,最大的不同应该是上线环境下要使用域名和80端口, @@ -167,9 +273,11 @@ litemall是一个简单的商场系统,基于现有的开源项目,重新实 先不开发和测试这样业务功能,等其他功能开发完毕和部署测试成功以后,再来开发这些线上环境才能 运行的功能,此时会有一个好的基础。 +接下来,分别从开发阶段、部署阶段和上线阶段三种阶段,分别介绍不同的方案实践要点。 + ## 1.4 开发方案 -![](pic1/1-2.png) +![](./pics/project/develop-stage.png) 如图所示,当前开发阶段的方案: @@ -184,18 +292,34 @@ litemall是一个简单的商场系统,基于现有的开源项目,重新实 数据库环境设置过程如下: -1. 安装MySQL +1. 安装MySQL; -2. 创建数据库、用户权限、数据库表和测试数据 +2. 创建数据库、用户权限、数据库表和测试数据; + 数据库文件存放在litemall-db/sql文件夹中,请开发者在MySQL中 + 按照顺序运行以下脚本: + * litemall_schema.sql,用于创建数据库、用户和权限; + * litemall_table.sql,用于创建表; + * litemall_data.sql,用于导入测试数据。 - 数据库文件存放在litemall-db/sql文件夹中,其中litemall_schema.sql创建数据库和用户权限, - litemall_table.sql则创建表,litemall_data.sql则是测试数据。 +注意: +> 建议采用命令行或者MySQL Workbench。如果采用Navicat可能导入失败。 - 注意:建议采用命令行或者MySQL Workbench。如果采用navicat可能导入失败。 +如果开发者运行litemall_schema.sql失败,可以打开该文件: +``` +drop database if exists litemall; +drop user if exists 'litemall'@'%'; +create database litemall default character set utf8mb4 collate utf8mb4_unicode_ci; +use litemall; +create user 'litemall'@'%' identified by 'litemall123456'; +grant all privileges on litemall.* to 'litemall'@'%'; +flush privilege +``` +可以看到几个命令,用于创建数据库、用户和访问权限,因此开发者可以利用 +命令或者工具完成这里的功能即可。 ### 1.4.2 Spring Boot开发环境 -1. 安装JDK8 +1. 安装JDK8(可以是Oracle JDK或者OpenJDK) 2. 安装Maven 3. 安装Git(可选) 4. 安装IDEA Community,建议安装Maven插件和Git插件。 @@ -212,7 +336,7 @@ litemall是一个简单的商场系统,基于现有的开源项目,重新实 或者采用IDEA的Maven插件安装本项目依赖库,点击`install` - ![](pic1/1-8.png) + ![](./pics/project/idea-maven-insatll.png) 7. 采用Maven命令编译本项目 @@ -239,7 +363,7 @@ litemall是一个简单的商场系统,基于现有的开源项目,重新实 如果采用IDEA,则litemall-all模块的Application类 右键` Run Application.main()`方式运行该模块, - ![](pic1/1-9.png) + ![](./pics/project/idea-run-all.png) 打开浏览器,输入 ``` @@ -249,13 +373,12 @@ litemall是一个简单的商场系统,基于现有的开源项目,重新实 如果出现JSON数据,则litemall-all模块运行正常。 注意: -> 1. 在上述开发步骤中,既介绍了Maven命令方式,也介绍了IDEA方式, -> 但是仍然建议开发者开发阶段采用IDEA。 -> 2. 以上是本人开发过程中采用的一些步骤,开发者可不拘泥于这些步骤。 -> 如果开发者没有按照本人步骤开发而出现相关问题,也请**不要**咨询, -> 本人**没有**相关解决经验。 -> 3. 开发者使用IDEA导入项目或者运行项目的时候会出现软件卡顿的现象,这通常是litemall-admin的 -> node_modules文件夹内自动下载了大量的litemall-admin的依赖库,当IDEA尝试索引该文件夹内的大量文件时 +> 1. 上述步骤中,既介绍了Maven命令方式,也介绍了IDEA方式, +> 但是建议开发者开发阶段采用IDEA。 +> 2. 上述步骤只是一种实践方式,开发者可不拘泥于这些步骤,多实践。 +> 当然,如果开发者不采用这里步骤而出现问题,请自行解决。 +> 3. 开发者使用IDEA导入项目或者运行项目时可能会出现**软件卡顿**的现象,这通常是litemall-admin或者litemall-vue的 +> node_modules文件夹内自动下载了大量的依赖库,当IDEA尝试索引该文件夹内的大量文件时 > 则出现IDEA卡顿的现象,具体解决方式可以参见[FAQ](./FAQ.md) ### 1.4.3 微信小程序开发环境 @@ -290,7 +413,7 @@ litemall是一个简单的商场系统,基于现有的开源项目,重新实 4. 请确定litemall-all模块已经运行,然后点击`登录`,如果能够成功登录,则表明管理后台的前端和后端对接成功,运行正常。 -目前本人采用VSC(Visual Studio Code)来开发litemall-admin项目,开发者也可以采用其他熟悉的IDE。 +本项目采用VSC(Visual Studio Code)开发litemall-admin模块,开发者也可以采用其他熟悉的IDE。 ### 1.4.5 项目配置 @@ -299,12 +422,12 @@ litemall是一个简单的商场系统,基于现有的开源项目,重新实 **项目配置结构** -1. 管理后台前端,即litemall-admin模块,配置文件在litemall-admin/config中,存在三个配置文件`dev.env.js`,`dep.env.js` -和`dep.env.js`。这里面配置信息都是一样,最主要的配置是`BASE_API`,即管理后台的服务根地址。 +1. 管理后台前端,即litemall-admin模块,配置文件在litemall-admin中,存在三个配置文件`env.development`,`env.deployment` +和`.env.production`。这里面配置信息都是一样,最主要的配置是`VUE_APP_BASE_API`,即管理后台的服务根地址。 - * 开发阶段,开发者运行命令`cnpm run dev`,这里就会采用`dev.env.js`配置文件; - * 部署阶段,当开发者运行命令`cnpm run build:dep`,这里就会采用`dep.env.js`配置文件; - * 上线阶段,当开发者运行命令`cnpm run build:prod`,这里就会采用`prod.env.js`配置文件。 + * 开发阶段,开发者运行命令`cnpm run dev`,这里就会采用`env.development`配置文件; + * 部署阶段,当开发者运行命令`cnpm run build:dep`,这里就会采用`env.deployment`配置文件; + * 上线阶段,当开发者运行命令`cnpm run build:prod`,这里就会采用`.env.production`配置文件。 2. 小商场前端,即litemall-wx模块,配置文件是`litemall-wx/project.config.json`和`litemall-wx/api.js`。 这里面最主要的配置信息是`project.config.json`中的`appid`,开发者需要设置自己申请的appid; @@ -316,7 +439,7 @@ litemall是一个简单的商场系统,基于现有的开源项目,重新实 // 局域网测试使用 // var WxApiRoot = 'http://192.168.0.101:8080/wx/'; // 云平台部署时使用 - // var WxApiRoot = 'http://122.152.206.172:8080/wx/'; + // var WxApiRoot = 'http://122.51.199.160:8080/wx/'; // 云平台上线时使用 // var WxApiRoot = 'https://www.menethil.com.cn/wx/'; @@ -341,24 +464,21 @@ litemall是一个简单的商场系统,基于现有的开源项目,重新实 #### 1.4.5.1 日志配置 -如果开发者启动litemall-all模块,则需要配置该模块的`application.yml`文件 +如果开发者启动litemall-all模块,则需要配置该模块的`logback-spring.xml`文件 ``` -logging: - level: - root: ERROR - org.springframework: ERROR - org.mybatis: ERROR - org.linlinjava.litemall.core: ERROR - org.linlinjava.litemall.db: ERROR - org.linlinjava.litemall.admin: ERROR - org.linlinjava.litemall.wx: ERROR - org.linlinjava.litemall: ERROR + + + + + + ``` -具体如何配置,请自行学习Spring Boot的日志配置。 +具体如何配置,请自行学习Spring Boot的日志配置和logback日志配置。 `org.linlinjava.litemall.core`定义litemall-core模块的日志级别 -`org.linlinjava.litemall.db`定义litemall-core模块的日志级别 +`org.linlinjava.litemall.db`定义litemall-db模块的日志级别 `org.linlinjava.litemall.wx`定义litemall-wx-api模块的日志级别 `org.linlinjava.litemall.admin`定义litemall-admin-api模块的日志级别 `org.linlinjava.litemall`而定义litemall所有后端模块的日志级别 @@ -368,7 +488,7 @@ logging: 注意: > 如果开发者独立启动litemall-wx-api模块,那么则需要配置litemall-wx-api模块的 -> `application.yml`文件来设置日志 +> 日志配置方式。 #### 1.4.5.2 数据库连接配置 @@ -473,24 +593,33 @@ litemall: # 短信息用于通知客户,例如发货短信通知,注意配置格式;template-name,template-templateId 请参考 NotifyType 枚举值 sms: enable: false - appid: 111111111 - appkey: xxxxxxxxxxxxxx + # 如果是腾讯云短信,则设置active的值tencent + # 如果是阿里云短信,则设置active的值aliyun + active: tencent + sign: litemall template: - - name: paySucceed - templateId: 156349 - - name: captcha - templateId: 156433 - - name: ship - templateId: 158002 - - name: refund - templateId: 159447 + - name: paySucceed + templateId: 156349 + - name: captcha + templateId: 156433 + - name: ship + templateId: 158002 + - name: refund + templateId: 159447 + tencent: + appid: 111111111 + appkey: xxxxxxxxxxxxxx + aliyun: + regionId: xxx + accessKeyId: xxx + accessKeySecret: xxx ``` 配置方式: -1. 腾讯云短信平台申请,然后设置四个场景的短信模板; -2. 开发者在配置文件设置`enable`的值`true`,然后其他信息设置 -腾讯云短信平台申请的appid等值。 -这里只测试过腾讯云短信平台,开发者需要自行测试其他短信云平台。 +1. 腾讯云短信平台或者阿里云短信平台申请,然后设置四个场景的短信模板; +2. 开发者在配置文件设置`enable`的值`true`,设置`active`的值`tencent`或`aliyun` +3. 然后配置其他信息,例如腾讯云短信平台申请的appid等值。 +这里只测试过腾讯云短信平台和阿里云短信平台,开发者需要自行测试其他短信云平台。 应用场景: 目前短信通知场景只支持支付成功、验证码、订单发送、退款成功四种情况。 @@ -500,41 +629,16 @@ litemall: 当配置好信息以后,开发者可以litemall-core模块的`SmsTest`测试类中设置手机号和 模板所需要的参数值,独立启动`SmsTest`测试类发送短信,然后查看手机是否成功接收短信。 -#### 1.4.5.7 微信通知配置 - -微信通知是微信上收到的服务通知。 - -在litemall-core模块的`application-core.yml`文件中配置微信通知服务: -``` -litemall: - notify: - # 微信模版通知配置 - # 微信模版用于通知客户或者运营者,注意配置格式;template-name,template-templateId 请参考 NotifyType 枚举值 - wx: - enable: false - template: - - name: paySucceed - templateId: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - - name: captcha - templateId: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - - name: ship - templateId: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - - name: refund - templateId: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -``` - -配置方式: -1. 微信公众平台申请,然后在`模板消息`中设置四个场景的微信模板; -2. 开发者在配置文件设置`enable`的值`true`,然后其他信息设置 -微信公众平台中所设置模板ID。 - -应用场景: -目前微信通知场景只支持支付成功、验证码、订单发送、退款成功四种情况。 -以后可能需要继续优化扩展。 - -验证配置成功: -这里没有实现测试类,因为微信通知需要小程序前端的formId作为参数,因此需要 -小商城前端配合。开发者可以在实际场景中DBEUG看看。 +短信模板参数命名: +这里存在一个问题,即腾讯云短信的官方平台中申请短信模板格式的模板参数是数组, +例如“你好,验证码是{0},时间是{1}"; +而阿里云短信的官方平台中申请短信模板的模板参数是JSON, +例如“你好,验证码是{param1},时间是{param2}"。 +为了保持当前代码的通用性,本项目采用数组传递参数,而对阿里云申请模板的参数做了一定的假设: +1. 腾讯云模块参数,申请模板时按照官方设置即可,例如“你好,验证码是{0},时间是{1}"; +2. 阿里云模板参数,本项目假定开发者在官方申请的参数格式应该采用"{ code: xxx, code1: xxx, code2: xxx }", +例如“你好,验证码是{code},时间是{code1}"。开发者可以查看`AliyunSmsSender`类的`sendWithTemplate`方法的 +源代码即可理解。如果觉得不合理,可以自行调整相关代码。 #### 1.4.5.8 物流配置 @@ -590,6 +694,10 @@ litemall: 当配置好信息以后,开发者可以litemall-core模块的`ExpressTest`测试类中设置快递公司编码和 真实测试快递单号,独立启动`ExpressTest`测试类查询物流信息。 +注意: +> 一部分快递公司(例如顺丰速运、申通快递等)的轨迹查询在开发环境下不支持, +> 具体支持情况或者使用限制请阅读[官方资料](http://www.kdniao.com/UserCenter/v2/UserHome.aspx) + #### 1.4.5.9 对象存储配置 对象存储,即存储和下载文件。 @@ -597,7 +705,7 @@ litemall: 在litemall-core模块的`application-core.yml`文件中配置对象存储服务: * 本地对象存储配置 -如果开发者采用当前主机保存上传的文件,则需要配置: +如果开发者采用当前服务器保存上传的文件,则需要配置: ``` litemall: storage: @@ -690,92 +798,100 @@ litemall: * litemall-wx模块部署在微信开发者工具中,此外数据API地址指向litemall-wx-api所在服务qi地址 * litemall-admin编译出的静态文件放在web服务器或者tomcat服务器,此外服务器地址设置指向3中litemall-admin-api所在地址 -最后,**如果项目部署云主机,则根据开发者的部署环境在以下文件中或代码中修改相应的配置。** +最后,**如果项目部署云服务器,则根据开发者的部署环境在以下文件中或代码中修改相应的配置。** 1. MySQL数据库设置合适的用户名和密码信息; 2. 后端服务模块设置合适的配置信息; 3. 小商场前端litemall-wx模块`config/api.js`的`WxApiRoot`设置小商场后端服务的服务地址; -4. 管理后台前端litemall-admin模块`config/dep.env.js`中的`BASE_API`设置管理后台后端服务的服务地址。 +4. 管理后台前端litemall-admin模块`.env.deployment`中的`VUE_APP_BASE_API`设置管理后台后端服务的服务地址。 实际上,最终的部署方案是灵活的: -* 可以是同一云主机中安装一个Spring Boot服务,同时提供litemall-admin、litemall-admin-api和litemall-wx-api三种服务 -* 可以单一云主机中仅安装一个tomcat/nginx服务器部署litemall-admin静态页面分发服务, +* 可以是同一云服务器中安装一个Spring Boot服务,同时提供litemall-admin、litemall-admin-api和litemall-wx-api三种服务 +* 可以单一云服务器中仅安装一个tomcat/nginx服务器部署litemall-admin静态页面分发服务, 然后部署两个Spring Boot的后端服务; * 也可以把litemall-admin静态页面托管第三方cdn,然后开发者部署两个后端服务 * 当然,甚至多个服务器,采用集群式并发提供服务。 注意 > 1. `本机`指的是是当前的开发机 -> 2. `云主机`指的是开发者购买并部署的远程主机 +> 2. `云服务器`指的是开发者购买并部署的远程服务器 以下简单列举几种方案。 ### 1.5.1 单机单服务部署方案 -本节介绍基于腾讯云的单机单服务部署方案,面向的是服务器数据和应用部署在云主机单机中用于演示的场景。 +本节介绍基于腾讯云的单机单服务部署方案,面向的是服务器数据和应用部署在云服务器单机中用于演示的场景。 其他云应该也是可行的。 -主要流程是:创建云主机,安装ubuntu操作系统,按照JDK和MySQL应用运行环境,部署单一Spring Boot服务。 - -![](pic1/1-11.png) +主要流程是:创建云服务器,安装ubuntu操作系统,按照JDK和MySQL应用运行环境,部署单一Spring Boot服务。 -#### 1.5.1.1 主机 +![](./pics/project/deploy-single.png) -请参考腾讯云官方文档进行相关操作。 +#### 1.5.1.1 云服务器 -1. 创建云主机虚拟机 +1. 创建云服务器 + 请参考腾讯云、阿里云或者其他云平台的官方文档进行相关操作。 + 建议最低配置是**1核2G**。 + 2. 安装操作系统 本项目采用ubuntu 16.04.1,但是并不限制其他操作系统。 3. 创建安全组 - ![](pic1/1-4.png) + ![](./pics/project/security-group.png) - 目前允许的端口:8081,8082,8083,8080,80,443,22,3306 + 目前允许的端口:8080,80,443,22,3306 注意: - 这里其实只需要8080端口,允许其他端口只是方面开发阶段的测试和调试。 + 这里其实只需要8080端口,允许其他端口只是方便开发阶段的测试和调试。 + 特别是3306端口,作为MySQL的远程访问端口,请在上线阶段关闭。 4. 设置SSH密钥(可选) - 建议开发者设置SSH密钥,可以免密码登录云主机,以及用于脚本自动上传应用。 + 建议开发者设置SSH密钥,可以免密码登录云服务器,以及用于脚本自动上传应用。 -5. 使用PuTTY远程登录云主机 +5. 使用PuTTY远程登录云服务器 如果开发者设置SSH密钥,可以采用免密码登录;否则采用账号和密码登录。 -#### 1.5.1.2 JDK8 +#### 1.5.1.2 OpenJDK8 -https://www.digitalocean.com/community/tutorials/how-to-install-java-with-apt-get-on-ubuntu-16-04 +这里可以安装openjdk-8-jre -http://www.webupd8.org/2012/09/install-oracle-java-8-in-ubuntu-via-ppa.html +```bash +sudo apt-get update +sudo apt-get install openjdk-8-jre +``` + +如果希望采用jdk,而不是jre,则可以运行 ```bash -sudo add-apt-repository ppa:webupd8team/java sudo apt-get update -sudo apt-get install oracle-java8-installer -sudo apt-get install oracle-java8-set-default +sudo apt-get install openjdk-8-jdk ``` -警告 -> "ppa:webupd8team/java" 不是Oracle官方PPA,可能存在安全隐患。 +注意 +> 如果用户想采用Oracle JDK8或者其他JDK环境,请查阅相关资料安装。 #### 1.5.1.3 MySQL -https://www.digitalocean.com/community/tutorials/how-to-install-mysql-on-ubuntu-16-04 - ``` sudo apt-get update sudo apt-get install mysql-server sudo apt-get install mysql-client ``` - + +如果配置MySQL,可以运行命令 +``` +sudo mysql_secure_installation +``` + #### 1.5.1.4 项目打包 -1. 在主机或者开发机打包项目到deploy; +1. 在服务器或者开发机打包项目到deploy; ``` cd litemall cat ./litemall-db/sql/litemall_schema.sql > ./deploy/db/litemall.sql @@ -800,26 +916,26 @@ sudo apt-get install mysql-client 2. 修改litemall文件夹下面的*.yml外部配置文件,当litemall-all模块启动时会 加载外部配置文件,而覆盖默认jar包内部的配置文件。 - 例如,配置文件中一些地方需要设置成远程主机的IP地址 + 例如,配置文件中一些地方需要设置成远程服务器的IP地址 此时deploy部署包结构如下: * bin -存放远程主机运行的脚本,包括deploy.sh脚本和reset.sh脚本 +存放远程服务器运行的脚本,包括deploy.sh脚本和reset.sh脚本 * db 存放litemall数据库文件 * litemall -存放远程主机运行的代码,包括litemall-all二进制可执行包和litemall外部配置文件 +存放远程服务器运行的代码,包括litemall-all二进制可执行包和litemall外部配置文件 * util -存放开发主机运行的脚本,包括package.sh脚本和lazy.sh脚本。 -由于是本地开发主机运行,因此开发者可以不用上传到远程主机。 +存放开发服务器运行的脚本,包括package.sh脚本和lazy.sh脚本。 +由于是本地开发服务器运行,因此开发者可以不用上传到远程服务器。 #### 1.5.1.5 项目部署 -1. 远程主机环境(MySQL和JDK1.8)已经安装好,请确保云主机的安全组已经允许相应的端口。 +1. 远程服务器环境(MySQL和JDK1.8)已经安装好,请确保云服务器的安全组已经允许相应的端口。 2. 导入db/litemall.sql ```bash cd /home/ubuntu/deploy/db @@ -831,7 +947,7 @@ sudo apt-get install mysql-client sudo ln -f -s /home/ubuntu/deploy/litemall/litemall.jar /etc/init.d/litemall sudo service litemall start ``` -4. 测试是否部署成功(xxx.xxx.xxx.xxx是云主机IP): +4. 测试是否部署成功(xxx.xxx.xxx.xxx是云服务器IP): ``` http://xxx.xxx.xxx.xxx:8080/wx/index/index http://xxx.xxx.xxx.xxx:8080/admin/index/index @@ -843,33 +959,33 @@ sudo apt-get install mysql-client > 这里很可能是开发者litemall-admin模块的`config/dep.env.js`或者`condig/prod.env.js` > 没有设置正确的管理后台后端地址,例如这里的`http://xxx.xxx.xxx.xxx:8080/admin` -#### 1.5.1.6 项目辅助脚本 +#### 1.5.1.6 deploy部署脚本 在前面的项目打包和项目部署中都是采用手动命令来部署。 这里可以写一些脚本简化: * util/packet.sh -在开发主机运行可以自动项目打包 +在开发服务器运行可以自动项目打包 * util/lazy.sh -在开发主机运行可以自动项目打包、项目上传远程主机、自动登录系统执行项目部署脚本。 +在开发服务器运行可以自动项目打包、项目上传远程服务器、自动登录系统执行项目部署脚本。 注意: -> 1. 开发者需要在util/lazy.sh中设置相应的远程主机登录账号和密钥文件路径。 -> 2. 开发者需要在bin/reset.sh设置远程主机的MySQL的root登录账户。 +> 1. 开发者需要在util/lazy.sh中设置相应的远程服务器登录账号和密钥文件路径。 +> 2. 开发者需要在bin/reset.sh设置远程服务器的MySQL的root登录账户。 * bin/deploy.sh -在远程主机运行可以自动部署服务 +在远程服务器运行可以自动部署服务 * bin/reset.sh -在远程主机运行可以自动项目导入数据、删除本地上传图片、再执行bin/deploy.sh部署服务。 +在远程服务器运行可以自动项目导入数据、删除本地上传图片、再执行bin/deploy.sh部署服务。 注意: -> 开发者需要在bin/reset.sh设置远程主机的MySQL的root登录账户。 +> 开发者需要在bin/reset.sh设置远程服务器的MySQL的root登录账户。 总结,当开发者设置好配置信息以后,可以在本地运行lazy.sh脚本自动一键部署: ```bash @@ -880,6 +996,10 @@ cd litemall 不过由于需要设置的信息会包含敏感安全信息,强烈建议开发者参考这里的deploy文件夹, 然后实现自己的deploy文件夹,妥善处置外部配置文件和脚本中的敏感安全信息!!! +#### 1.5.1.7 docker部署脚本 + +本项目也简单实现了docker部署方案,具体可以看docker文件夹。 + ### 1.5.2 单机多服务部署方案 @@ -890,8 +1010,8 @@ cd litemall 1. 专门的云数据库部署数据 2. 专门的云存储方案 3. 专门的CDN分发管理后台的静态文件 -4. 一台云主机部署管理后台的后端服务 -5. 一台或多台云主机部署小商场的后端服务 +4. 一台云服务器部署管理后台的后端服务 +5. 一台或多台云服务器部署小商场的后端服务 虽然由于环境原因没有正式测试过,但是这种简单的集群式场景应该是可行的。 在1.5.2节中所演示的三个服务是独立的,因此延伸到这里分布式是非常容易的。 @@ -917,7 +1037,7 @@ cd litemall * 提供管理后台前端所需要的数据; * 提供小商城前端所需要的数据。 -![](pic1/1-12.png) +![](./pics/project/online-deploy.png) 开发者可以基于自身业务采用其他上线方案。 @@ -940,7 +1060,7 @@ sudo apt-get update sudo apt-get install nginx ``` -有的文档会指出需要防火墙设置,但是腾讯云主机防火墙默认没有开启。 +有的文档会指出需要防火墙设置,但是腾讯云服务器防火墙默认没有开启。 开发者这里自己可以开启设置,或者直接不开启。 打开浏览器,输入以下地址: @@ -1040,11 +1160,10 @@ http://www.example.com 总结,经过以上不同方面的配置,nginx这里最终的配置是如下: 1. 证书`1_www.example.com_bundle.crt`和`2_www.example.com.key`放置在 `/etc/nginx/`文件夹内。 -2. 把`/etc/nginx/nginx.conf`文件进行修改,具体可以参考[本项目的nginx.conf](./pic/nginx.conf) +2. 把`/etc/nginx/nginx.conf`文件进行修改,具体可以参考[本项目的nginx.conf](./conf/nginx.conf) 3. 重启nginx 注意: -> 本人对nginx也不了解,仅仅依靠网络知识配置了简单的效果。 > 更多配置方法和功能,请开发者自行学习。 ### 1.6.3 小商场上线 @@ -1085,9 +1204,9 @@ http://www.example.com 1. MySQL数据库设置合适的用户名和密码信息; 2. 管理后台后端服务模块设置合适的配置信息,建议开发者参考deploy/litemall的外部配置文件, 这样可以避免开发者对模块内部的开发配置文件造成修改; -3. 管理后台前端litemall-admin模块`config/prod.env.js`中的`BASE_API`设置管理后台后端服务的服务地址。 +3. 管理后台前端litemall-admin模块`.env.production`中的`VUE_APP_BASE_API`设置管理后台后端服务的服务地址。 -### 1.6.5 项目评估和调整 +### 1.6.5 项目评估 本项目只是参考项目,项目代码质量和功能不可能符合开发者的最终需求, 因此开发者**请务必仔细评估项目代码**。 @@ -1124,8 +1243,70 @@ litemall-admin编译得到的前端文件在第一次加载时相当耗时,这 #### 1.6.6.4 nginx优化 -本人对nginx不是很熟悉,而nginx还存在很多可以调整优化的部分,这里建议开发者 -根据自己业务或架构情况优化。 +建议开发者根据自己业务或架构情况优化。 + +### 1.6.7 项目安全 + +项目一旦正式上线,即对外正式服务。但是服务同时,可能会存在安全隐患甚至黑客攻击。 + +本节仅列举一些注意事项,欢迎开发者补充和完善。 + +#### 1.6.7.1 账户安全 + +这里的账号安全,既包括商城端用户账户,也包括管理后台端管理员账户。 + +目前账号安全还缺乏一点的保护措施,例如 + +* 用户密码失败超过阈值,则显示验证码; +* 用户密码失败超过阈值,则取消登录; +* 用户密码失败超过阈值,则需要手机验证码; + +#### 1.6.7.2 关键业务记录 + +有关订单或者金钱相关的操作,建议开发者尽可能记录在数据库中,以便以后回溯。 + +#### 1.6.7.3 API统一调整 + +本项目公布了参考API接口,如果出现BUG可能会被黑客作为入口。 +建议开发者上线之前可以统一调整接口,以减少安全隐患。 + +#### 1.6.7.4 对账 + +本项目管理后台没有对账功能,建议开发者可以开发对账比对商场的状态是否正常。 + +#### 1.6.7.5 取消或者限制退款 + +本项目不支持自动退款功能,而是在管理后台通过管理员点击退款按钮来人工退款。 +但是仍然可能存在隐患,例如黑客通过漏洞进入管理后台从而进行不合理的退款操作。 + +因此建议开发者可以取消管理后台的退款按钮,而仅仅保持退款信息,管理员可以登录 +微信官方支付平台进行退款操作。 + +或者建议开发者基于一定的业务逻辑或场景限制管理后台的退款功能。例如,设置当天 +退款限额从而保证不会产生无限退款操作。 + +#### 1.6.7.6 资源限制访问 + +一些API操作涉及到后端服务器资源,因此需要做一定的限制,防止有限资源被恶意消耗。 + +有限资源可能包括: + +* 验证码 +* 图片上传 + +一些限制措施可能包括: + +* 限制单个IP的访问频率 +* 限制用户上传图片数量 + +#### 1.6.7.n 跟踪本项目进展 + +一旦有开发者反馈BUG,本项目会优先解决并及时上传补丁。 +因此建议开发者跟踪本项目进展,留意每次BUG修复的commit。 + +同时也希望开发者发现任何BUG都及时反馈。 + +目前还不存在LTS版本,未来业务稳定后可能会发布。 ## 1.7 项目管理 @@ -1143,16 +1324,18 @@ litemall-admin编译得到的前端文件在第一次加载时相当耗时,这 * litemall-all/.gitignore * .gitignore +开发者可以采用单一.gitignore文件。 + ### 1.7.2 项目自动部署 #### 1.7.2.1 deploy部署 -当前项目存在deploy部署文件夹,这个是上述1.5.1节部署腾讯云主机所采取的一些脚本。 +当前项目存在deploy部署文件夹,这个是上述1.5.1节部署腾讯云服务器所采取的一些脚本。 流程如下: -1. util脚本是当前开发主机运行,用来打包项目和上传腾讯云主机; +1. util脚本是当前开发服务器运行,用来打包项目和上传腾讯云服务器; 2. 打包项目时,会编译打包项目相关模块到litemall和db文件夹中; -3. bin脚本是云主机运行,用来安装数据库、导入数据、启动项目服务。 +3. bin脚本是云服务器运行,用来安装数据库、导入数据、启动项目服务。 这里deploy部署方式比较简单不灵活,开发者可以参考开发自己的项目脚本。 @@ -1240,9 +1423,9 @@ application配置文件中,但是问题就是数据库信息一旦改变则其 3. 上线阶段,同样地,在litemall.jar包同级目录创建上线配置文件。 此外,这里还可以采用另外一种思路,如下图: -![](pic1/1-13.png) -![](pic1/1-14.png) -![](pic1/1-15.png) +![](./pics/project/maven-profile.png) +![](./pics/project/spring-profile.png) +![](./pics/project/yml-resource.png) 其实原理也很简单,就是配置文件采用application-{module}-{profile}.yml来支持不同模块不同阶段的配置需求。 @@ -1282,8 +1465,8 @@ application配置文件中,但是问题就是数据库信息一旦改变则其 * 503,业务不支持,即后端虽然定义了接口,但是还没有实现功能; * 504,更新数据失效,即后端采用了乐观锁更新,而并发更新时存在数据更新失效; * 505,更新数据失败,即后端数据库更新失败(正常情况应该更新成功)。 -* 6xx,小商城后端业务错误码,具体见litemall-admin-api模块的`AdminResponseCode`类。 -* 7xx,管理后台后端业务错误码,具体见litemall-wx-api模块的`WxResponseCode`类。 +* 6xx,管理后台后端业务错误码,具体见litemall-admin-api模块的`AdminResponseCode`类。 +* 7xx,小商城后端业务错误码,具体见litemall-wx-api模块的`WxResponseCode`类。 需要指出的是,小商场后端可能返回4xx、5xx和6xx错误码;管理后台后端则可能返回4xx、5xx和7xx错误码。 这样设计原因是方便小商场前端和管理后台前端区别对待。 @@ -1305,6 +1488,9 @@ application配置文件中,但是问题就是数据库信息一旦改变则其 和小商场前端类似,管理后台前端处理后端响应错误码也存在三种类似的处理方式。 +注意: +> 这里的4xx和5xx错误码,和HTTP中的4xx和5xx状态码不是一个概念。 + ### 1.7.7 TODO 本项目存在一些TODO,**强烈建议**开发者上线前仔细审阅是否存在问题和做相应调整。 diff --git a/doc/pic/3.png b/doc/unused/3.png similarity index 100% rename from doc/pic/3.png rename to doc/unused/3.png diff --git a/doc/unused/5.gif b/doc/unused/5.gif new file mode 100644 index 0000000000000000000000000000000000000000..e4429646b929234d4f1546f99af2d204f5e23d08 Binary files /dev/null and b/doc/unused/5.gif differ diff --git a/doc/pic/litemall.ico b/doc/unused/litemall.ico similarity index 100% rename from doc/pic/litemall.ico rename to doc/unused/litemall.ico diff --git a/doc/pic/litemall.png b/doc/unused/litemall.png similarity index 100% rename from doc/pic/litemall.png rename to doc/unused/litemall.png diff --git a/doc/pic/qq.png b/doc/unused/qq.png similarity index 100% rename from doc/pic/qq.png rename to doc/unused/qq.png diff --git a/doc/unused/qq2.png b/doc/unused/qq2.png new file mode 100644 index 0000000000000000000000000000000000000000..69ac5e4b2b680728e8e754f79d07cda0334b79df Binary files /dev/null and b/doc/unused/qq2.png differ diff --git a/doc/wxmall.md b/doc/wxmall.md index 4c71d1aac0e5c188d4853fc7d848cc7f447f5139..dddde2149fbe45f4191aa2ad9d2c76b252689ffa 100644 --- a/doc/wxmall.md +++ b/doc/wxmall.md @@ -12,8 +12,6 @@ 目前发现存在的一些问题: -* `缺失`优惠券功能 -* `缺失`商品评价中管理员回复功能,进一步地用户之间相互评价回复 * `缺失`后台服务返回的token存在有效期,小商场应该自动刷新 * `缺失`账号多次登录失败,应该小商城出现图片验证码限制,或者后台账号锁定 * `改善`商品搜索中采用更好的搜索机制 @@ -25,13 +23,8 @@ * `改善`商品好评计算与显示,例如90%好评 * `改善`商品的评论列表中显示评价的评论分数、商品规格 * `改善`商品的评论列表中的图片点击可放大,同时用户评价的多个图片可以选择左右滑动查看。 -* `改善`当一些页面查询数据没有更新时,底部显示相应的提醒,例如“没有更多数据”。 -* `改善`个人页面中实现订单部件,显示用户订单数量,同时点击以后跳转到订单的相应页面。 * `改善`商品的订单中支持订单号搜索功能 * `改善`在一些内容比较多的页面中支持“顶部”功能 -* `功能`目前已经有账号登录页面,可以再支持手机短信登录方式。 -* `功能`个人页面支持帮助中心 -* `功能`推荐功能,基于用户的一些信息,在合适的页面给出推荐商品 ## 3.0 小商场环境 @@ -87,9 +80,9 @@ 2. 启动后台服务 -3. 部署后台服务到云主机 +3. 部署后台服务到云服务器 -4. litemall-wx的api.js设置云主机的域名。 +4. litemall-wx的api.js设置云服务器的域名。 编译运行,尝试微信支付。 ### 3.0.3 微信退款配置 @@ -240,14 +233,14 @@ var WxApiRoot = 'http://localhost:8082/wx/'; // 局域网测试使用 // var WxApiRoot = 'http://192.168.0.101:8082/wx/'; // 云平台部署时使用 -// var WxApiRoot = 'http://122.152.206.172:8082/wx/'; +// var WxApiRoot = 'http://122.51.199.160:8082/wx/'; ``` 也就是说这里存在三种类型的API服务地址,这里是考虑到开发存在三种情况: 1. 本机开发时,localhost是当前开发机的地址; 2. 手机预览时,192.168.0.101是开发机的IP地址; -3. 当后台部署在云主机中时,122.152.206.172是云主机的IP地址; +3. 当后台部署在云服务器中时,122.51.199.160是云服务器的IP地址; 4. 此外,更最重要的是,如果小程序正式部署时,这里的地址必须是域名, 而不能是IP地址。 diff --git a/docker/.gitignore b/docker/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..29a712de1956638bc819cc5163992cef92734844 --- /dev/null +++ b/docker/.gitignore @@ -0,0 +1,3 @@ + +/litemall/litemall.jar +/db/init-sql/litemall.sql diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000000000000000000000000000000000000..ebc8f1dd3f28bd9bf1482d80dbd2499c58c72e68 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,106 @@ +## docker + +### 项目打包 + +1. 在服务器或者开发机打包项目到docker; + ``` + cd litemall + cat ./litemall-db/sql/litemall_schema.sql > ./docker/db/init-sql/litemall.sql + cat ./litemall-db/sql/litemall_table.sql >> ./docker/db/init-sql/litemall.sql + cat ./litemall-db/sql/litemall_data.sql >> ./docker/db/init-sql/litemall.sql + + cd ./litemall-admin + cnpm install + cnpm run build:dep + + cd .. + mvn clean package + cp -f ./litemall-all/target/litemall-all-*-exec.jar ./docker/litemall/litemall.jar + ``` + 这里的工作是: + 1. 把数据库文件拷贝到docker/db文件夹 + 2. 编译litemall-admin项目 + 3. 编译litemall-all模块,同时把litemall-admin编译得到的静态文件拷贝到 + litemall-all模块的static目录 + +2. 修改litemall文件夹下面的*.yml外部配置文件,当litemall-all模块启动时会 + 加载外部配置文件,而覆盖默认jar包内部的配置文件。 + 例如,配置文件中一些地方需要设置成远程服务器的IP地址 + +此时docker部署包结构如下: + +* bin + +存放远程服务器运行的脚本,包括deploy.sh脚本和reset.sh脚本 + +* db + +存放litemall数据库文件 + +* litemall + +存放远程服务器运行的代码,包括litemall-all二进制可执行包和litemall外部配置文件 + +* util + +存放开发服务器运行的脚本,包括package.sh脚本和lazy.sh脚本。 +由于是本地开发服务器运行,因此开发者可以不用上传到远程服务器。 + +* docker-compose.yml + +docker-compose配置脚本,运行docker-compose命令会 + +### 项目部署 + +1. 云服务器环境安装docker和docker-compose(MySQL和JDK1.8无需安装,因为使用docker自动安装)。 + 此外请确保云服务器的安全组已经允许相应的端口。 + +2. 运行docker-compose + ```bash + cd /home/ubuntu/docker + sudo docker-compose + ``` + +3. 测试是否部署成功(xxx.xxx.xxx.xxx是云服务器IP): + ``` + http://xxx.xxx.xxx.xxx:8080/wx/index/index + http://xxx.xxx.xxx.xxx:8080/admin/index/index + http://xxx.xxx.xxx.xxx:8080/#/login + ``` + +### 项目辅助脚本 + +在前面的项目打包和项目部署中都是采用手动命令来部署。 +这里可以写一些脚本简化: + +* util/packet.sh + +在开发服务器运行可以自动项目打包 + +* util/lazy.sh + +在开发服务器运行可以自动项目打包、项目上传远程服务器、自动登录系统执行项目部署脚本。 + +注意: +> 1. 开发者需要在util/lazy.sh中设置相应的远程服务器登录账号和密钥文件路径。 +> 2. 开发者需要在bin/reset.sh设置远程服务器的MySQL的root登录账户。 + +* bin/deploy.sh + +在远程服务器运行可以自动部署服务 + +* bin/reset.sh + +在远程服务器运行可以自动项目导入数据、删除本地上传图片、再执行bin/deploy.sh部署服务。 + +注意: +> 开发者需要在bin/reset.sh设置远程服务器的MySQL的root登录账户。 + +总结,当开发者设置好配置信息以后,可以在本地运行lazy.sh脚本自动一键部署: +```bash +cd litemall +./docker/util/lazy.sh +``` + +不过由于需要设置的信息会包含敏感安全信息,强烈建议开发者参考这里的docker文件夹, +然后实现自己的docker文件夹,妥善处置外部配置文件和脚本中的敏感安全信息!!! \ No newline at end of file diff --git a/docker/bin/deploy.sh b/docker/bin/deploy.sh new file mode 100644 index 0000000000000000000000000000000000000000..9182a2b5598d585d1e431d1430aa0fa3395c6f45 --- /dev/null +++ b/docker/bin/deploy.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +cd /home/ubuntu/docker +sudo docker-compose down +sudo docker-compose build +sudo docker image prune -f +sudo docker-compose up -d diff --git a/docker/bin/reset.sh b/docker/bin/reset.sh new file mode 100644 index 0000000000000000000000000000000000000000..e9cfaa016d07f76e77c371f8ce874ef0ccb6bd44 --- /dev/null +++ b/docker/bin/reset.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +# 本脚本的作用是重置部署环境 +# 1.重置数据库 +# 2.删除storage文件夹内文件 +# 3.调用deploy.sh启动服务 +# 注意:由于1和2的原因,请仅在开发测试阶段使用本脚本! + +# 重置数据库 +# i. 请在`XXXXXX`处设置相应的root密码 +# ii. 同时请注意root密码放在脚本是非常危险的,因此这里仅仅是用于开发测试阶段。 +ROOT=root +PASSWORD=litemall123456 + +if test -z "$PASSWORD" +then + echo "请设置云服务器MySQL的root账号密码" + exit 1 +fi + +# 删除storage文件夹内文件 +cd /home/ubuntu/docker/litemall/storage || exit 2 +sudo rm -f ./** + +cd /home/ubuntu/docker || exit 3 +sudo docker-compose down +sudo docker-compose build +sudo docker image prune -f + +# 删除db/data文件夹内文件重置数据 +# 这样docker启动时会自动运行db/init-sql脚本,导入新的数据 +cd /home/ubuntu/docker/db/data || exit 1 +sudo rm -rf ./** + +cd /home/ubuntu/docker || exit 3 +sudo docker-compose up -d diff --git a/docker/db/conf.d/my.cnf b/docker/db/conf.d/my.cnf new file mode 100644 index 0000000000000000000000000000000000000000..352752c073f96a159ca1f0f5bbfecae6009ffac9 --- /dev/null +++ b/docker/db/conf.d/my.cnf @@ -0,0 +1,6 @@ +[mysqld] +wait_timeout=1814400 +max_allowed_packet = 100M +default-time_zone = '+8:00' +character-set-server=utf8mb4 +collation-server=utf8mb4_unicode_ci \ No newline at end of file diff --git a/deploy/litemall/application-wx.yml b/docker/db/data/.gitkeep similarity index 100% rename from deploy/litemall/application-wx.yml rename to docker/db/data/.gitkeep diff --git a/docker/db/init-sql/.gitkeep b/docker/db/init-sql/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..cb707c43b1e24c36596bb87b0b844e0c46f82482 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,35 @@ +version: '3' +services: + mysql57: + image: mysql:5.7 + container_name: mysql + ports: + - "3306:3306" + command: + --character-set-server=utf8 + --collation-server=utf8_general_ci + --default-authentication-plugin=mysql_native_password + volumes: + - ./db/conf.d:/etc/mysql/conf.d + - ./db/data:/var/lib/mysql + - ./db/init-sql:/docker-entrypoint-initdb.d + environment: + MYSQL_ROOT_PASSWORD: litemall123456 + restart: always + litemall: + build: + context: litemall + dockerfile: Dockerfile + container_name: litemall + ports: + - "8080:8080" + volumes: + - ./litemall/storage:/storage + - ./litemall/logs:/logs + - ./litemall/backup:/backup + - /etc/localtime:/etc/localtime + environment: + - TZ=Asia/Shanghai + depends_on: + - mysql57 + restart: always \ No newline at end of file diff --git a/docker/litemall/.gitkeep b/docker/litemall/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/docker/litemall/Dockerfile b/docker/litemall/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..a882824edd002d132a33bf299ffcbdcd9998415a --- /dev/null +++ b/docker/litemall/Dockerfile @@ -0,0 +1,4 @@ +FROM openjdk:8-jre +COPY application.yml application.yml +COPY litemall.jar litemall.jar +ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","litemall.jar"] \ No newline at end of file diff --git a/deploy/litemall/application-core.yml b/docker/litemall/application.yml similarity index 39% rename from deploy/litemall/application-core.yml rename to docker/litemall/application.yml index ba4875a34f49b4e540635005b089ec5515e7e7e8..a28dd8b5bc47ca0c794b9684b5262541bbc3fd23 100644 --- a/deploy/litemall/application-core.yml +++ b/docker/litemall/application.yml @@ -1,3 +1,39 @@ +spring: + profiles: + active: none + message: + encoding: UTF-8 + datasource: + druid: + url: jdbc:mysql://mysql:3306/litemall?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&allowPublicKeyRetrieval=true&verifyServerCertificate=false&useSSL=false + driver-class-name: com.mysql.jdbc.Driver + username: litemall + password: litemall123456 + initial-size: 10 + max-active: 50 + min-idle: 10 + max-wait: 60000 + pool-prepared-statements: true + max-pool-prepared-statement-per-connection-size: 20 + validation-query: SELECT 1 FROM DUAL + test-on-borrow: false + test-on-return: false + test-while-idle: true + time-between-eviction-runs-millis: 60000 + filters: stat,wall + +server: + port: 8080 + +logging: + config: classpath:logback-spring.xml + +pagehelper: + helperDialect: mysql + reasonable: true + supportMethodsArguments: true + params: count=countSql + litemall: # 开发者应该设置成自己的wx相关信息 wx: @@ -5,7 +41,7 @@ litemall: app-secret: e04004829d4c383b4db7769d88dfbca1 mch-id: 111111 mch-key: xxxxxx - notify-url: http://122.152.206.172:8080/wx/order/pay-notify + notify-url: http://122.51.199.160:8080/wx/order/pay-notify # 商户证书文件路径 # 请参考“商户证书”一节 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=4_3 key-path: xxxxx @@ -20,36 +56,32 @@ litemall: password: XXXXXXXXXXXXX sendfrom: ex@ex.com.cn sendto: ex@qq.com + port: 465 # 短消息模版通知配置 # 短信息用于通知客户,例如发货短信通知,注意配置格式;template-name,template-templateId 请参考 NotifyType 枚举值 sms: enable: false - appid: 111111111 - appkey: xxxxxxxxxxxxxx - template: - - name: paySucceed - templateId: 156349 - - name: captcha - templateId: 156433 - - name: ship - templateId: 158002 - - name: refund - templateId: 159447 - - # 微信模版通知配置 - # 微信模版用于通知客户或者运营者,注意配置格式;template-name,template-templateId 请参考 NotifyType 枚举值 - wx: - enable: false + # 如果是腾讯云短信,则设置active的值tencent + # 如果是阿里云短信,则设置active的值aliyun + active: tencent + sign: litemall template: - - name: paySucceed - templateId: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - - name: captcha - templateId: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - - name: ship - templateId: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - - name: refund - templateId: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + - name: paySucceed + templateId: 156349 + - name: captcha + templateId: 156433 + - name: ship + templateId: 158002 + - name: refund + templateId: 159447 + tencent: + appid: 111111111 + appkey: xxxxxxxxxxxxxx + aliyun: + regionId: xxx + accessKeyId: xxx + accessKeySecret: xxx # 快鸟物流查询配置 express: @@ -57,32 +89,32 @@ litemall: appId: "XXXXXXXXX" appKey: "XXXXXXXXXXXXXXXXXXXXXXXXX" vendors: - - code: "ZTO" - name: "中通快递" - - code: "YTO" - name: "圆通速递" - - code: "YD" - name: "韵达速递" - - code: "YZPY" - name: "邮政快递包裹" - - code: "EMS" - name: "EMS" - - code: "DBL" - name: "德邦快递" - - code: "FAST" - name: "快捷快递" - - code: "ZJS" - name: "宅急送" - - code: "TNT" - name: "TNT快递" - - code: "UPS" - name: "UPS" - - code: "DHL" - name: "DHL" - - code: "FEDEX" - name: "FEDEX联邦(国内件)" - - code: "FEDEX_GJ" - name: "FEDEX联邦(国际件)" + - code: "ZTO" + name: "中通快递" + - code: "YTO" + name: "圆通速递" + - code: "YD" + name: "韵达速递" + - code: "YZPY" + name: "邮政快递包裹" + - code: "EMS" + name: "EMS" + - code: "DBL" + name: "德邦快递" + - code: "FAST" + name: "快捷快递" + - code: "ZJS" + name: "宅急送" + - code: "TNT" + name: "TNT快递" + - code: "UPS" + name: "UPS" + - code: "DHL" + name: "DHL" + - code: "FEDEX" + name: "FEDEX联邦(国内件)" + - code: "FEDEX_GJ" + name: "FEDEX联邦(国际件)" # 对象存储配置 storage: @@ -91,7 +123,7 @@ litemall: # 本地对象存储配置信息 local: storagePath: storage - address: http://122.152.206.172:8080/wx/storage/fetch/ + address: http://122.51.199.160:8080/wx/storage/fetch/ # 阿里云对象存储配置信息 aliyun: endpoint: oss-cn-shenzhen.aliyuncs.com @@ -104,4 +136,13 @@ litemall: secretId: 111111 secretKey: xxxxxx region: xxxxxx - bucketName: xxxxxx \ No newline at end of file + bucketName: xxxxxx + # 七牛云对象存储配置信息 + qiniu: + endpoint: http://pd5cb6ulu.bkt.clouddn.com + accessKey: 111111 + secretKey: xxxxxx + bucketName: litemall + +swagger: + production: true \ No newline at end of file diff --git a/docker/util/lazy.sh b/docker/util/lazy.sh new file mode 100644 index 0000000000000000000000000000000000000000..2ac2bd3ca4fec5042bdb2f26ae314bad644aead7 --- /dev/null +++ b/docker/util/lazy.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +# 本脚本的作用是 +# 1. 项目打包 +# 2. 上传云服务器 +# 3. 远程登录云服务器并执行reset脚本 + +# 请设置云服务器的IP地址和账户 +# 例如 ubuntu@122.51.199.160 +REMOTE=ubuntu@122.51.199.160 +# 请设置本地SSH私钥文件id_rsa路径 +# 例如 /home/litemall/id_rsa +ID_RSA=/d/00/cloud/litemall.txt + +if test -z "$REMOTE" +then + echo "请设置云服务器登录IP地址和账户" + exit 1 +fi + +if test -z "$ID_RSA" +then + echo "请设置云服务器登录IP地址和账户" + exit 1 +fi + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +cd $DIR/../.. || exit 2 +LITEMALL_HOME=$PWD +echo "LITEMALL_HOME $LITEMALL_HOME" + +# 项目打包 +cd $LITEMALL_HOME || exit 2 +./docker/util/package.sh + +# 上传云服务器 +cd $LITEMALL_HOME || exit 2 +scp -i $ID_RSA -r ./docker $REMOTE:/home/ubuntu/ + +# 远程登录云服务器并执行reset脚本 +# 这里使用tr命令,因为有可能deploy.sh和reset.sh的换行格式是CRLF,而LINUX环境应该是LF +ssh $REMOTE -i $ID_RSA << eeooff +cd /home/ubuntu/docker/bin +cat deploy.sh | tr -d '\r' > deploy2.sh +mv deploy2.sh deploy.sh +chmod +x deploy.sh +cat reset.sh | tr -d '\r' > reset2.sh +mv reset2.sh reset.sh +chmod +x reset.sh +sudo ./reset.sh +exit +eeooff \ No newline at end of file diff --git a/docker/util/package.sh b/docker/util/package.sh new file mode 100644 index 0000000000000000000000000000000000000000..86eecab8ee1d52c8ce9dd90a6b731c86c024a4c4 --- /dev/null +++ b/docker/util/package.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# 请注意 +# 本脚本的作用是把本项目编译的结果保存到deploy文件夹中 +# 1. 把项目数据库文件拷贝到docker/db/init-sql +# 2. 编译litemall-admin +# 3. 编译litemall-all模块,然后拷贝到docker/litemall + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +cd $DIR/../.. +LITEMALL_HOME=$PWD +echo "LITEMALL_HOME $LITEMALL_HOME" + +# 复制数据库 +cat $LITEMALL_HOME/litemall-db/sql/litemall_schema.sql > $LITEMALL_HOME/docker/db/init-sql/litemall.sql +cat $LITEMALL_HOME/litemall-db/sql/litemall_table.sql >> $LITEMALL_HOME/docker/db/init-sql/litemall.sql +cat $LITEMALL_HOME/litemall-db/sql/litemall_data.sql >> $LITEMALL_HOME/docker/db/init-sql/litemall.sql + +# 安装阿里node镜像工具 +npm install -g cnpm --registry=https://registry.npm.taobao.org + +# 打包litemall-admin +cd $LITEMALL_HOME/litemall-admin +cnpm install +cnpm run build:dep + +# 打包litemall-vue +cd $LITEMALL_HOME/litemall-vue +cnpm install +cnpm run build:dep + +cd $LITEMALL_HOME +mvn clean package +cp -f $LITEMALL_HOME/litemall-all/target/litemall-all-*-exec.jar $LITEMALL_HOME/docker/litemall/litemall.jar \ No newline at end of file diff --git a/litemall-admin-api/pom.xml b/litemall-admin-api/pom.xml index 636564f8aa484b28d527022658160a79547ae1da..5afd40cc950ab2fb036f18768fa5005bc60f2d86 100644 --- a/litemall-admin-api/pom.xml +++ b/litemall-admin-api/pom.xml @@ -9,7 +9,10 @@ litemall 0.1.0 - + + UTF-8 + UTF-8 + @@ -21,16 +24,27 @@ org.linlinjava litemall-db - com.github.binarywang weixin-java-miniapp - + + io.springfox + springfox-swagger-ui + + + io.springfox + springfox-swagger2 + org.apache.shiro shiro-spring-boot-web-starter + + com.github.xiaoymin + swagger-bootstrap-ui + 1.9.6 + @@ -38,15 +52,11 @@ org.springframework.boot spring-boot-maven-plugin - - true - - - repackage - + repackage + true exec @@ -54,6 +64,4 @@ - - \ No newline at end of file diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/Application.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/Application.java index 2cb270e5eaaab6cf12df16c5a53cd4829131df7e..9283a5c0f4189c35e620371fd64c6a9b6cbd98ac 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/Application.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/Application.java @@ -6,7 +6,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.transaction.annotation.EnableTransactionManagement; -@SpringBootApplication(scanBasePackages = {"org.linlinjava.litemall.db", "org.linlinjava.litemall.core", "org.linlinjava.litemall.admin"}) +@SpringBootApplication(scanBasePackages = {"org.linlinjava.litemall.db", "org.linlinjava.litemall.core", + "org.linlinjava.litemall.admin"}) @MapperScan("org.linlinjava.litemall.db.dao") @EnableTransactionManagement @EnableScheduling diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/annotation/RequiresPermissionsDesc.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/annotation/RequiresPermissionsDesc.java index a9c23e286987c52bbe31febbd216df9a6cf7d255..eb49267773310ab16606128634c7fa48c3dc8e0c 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/annotation/RequiresPermissionsDesc.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/annotation/RequiresPermissionsDesc.java @@ -1,7 +1,5 @@ package org.linlinjava.litemall.admin.annotation; -import org.apache.shiro.authz.annotation.RequiresPermissions; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -11,5 +9,6 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) public @interface RequiresPermissionsDesc { String[] menu(); + String button(); } diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/config/AdminSwagger2Configuration.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/config/AdminSwagger2Configuration.java new file mode 100644 index 0000000000000000000000000000000000000000..e16ba1bab60da9d854f84120069a17c83c7c3893 --- /dev/null +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/config/AdminSwagger2Configuration.java @@ -0,0 +1,48 @@ +package org.linlinjava.litemall.admin.config; + +import com.github.xiaoymin.swaggerbootstrapui.annotations.EnableSwaggerBootstrapUI; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +/** + * swagger在线文档配置
+ * 项目启动后可通过地址:http://host:ip/swagger-ui.html 查看在线文档 + * + * @author enilu + * @version 2018-07-24 + */ + +@Configuration +@EnableSwagger2 +@EnableSwaggerBootstrapUI +public class AdminSwagger2Configuration { + @Bean + public Docket adminDocket() { + + return new Docket(DocumentationType.SWAGGER_2) + .groupName("admin") + .apiInfo(adminApiInfo()) + .select() + .apis(RequestHandlerSelectors.basePackage("org.linlinjava.litemall.admin.web")) + .paths(PathSelectors.any()) + .build(); + } + + private ApiInfo adminApiInfo() { + return new ApiInfoBuilder() + .title("litemall-admin API") + .description("litemall管理后台API") + .termsOfServiceUrl("https://github.com/linlinjava/litemall") + .contact("https://github.com/linlinjava/litemall") + .version("1.0") + .build(); + } +} diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/config/ShiroConfig.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/config/ShiroConfig.java index 96b335c932f4d4819b104803165a17e221ed0b88..18b9b257629e20cf4b7eb4a31ed4aa275ee04a57 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/config/ShiroConfig.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/config/ShiroConfig.java @@ -1,10 +1,10 @@ package org.linlinjava.litemall.admin.config; +import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.realm.Realm; import org.apache.shiro.session.mgt.SessionManager; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; -import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.linlinjava.litemall.admin.shiro.AdminAuthorizingRealm; import org.linlinjava.litemall.admin.shiro.AdminWebSessionManager; @@ -24,17 +24,8 @@ public class ShiroConfig { return new AdminAuthorizingRealm(); } -// @Bean -// public ShiroFilterChainDefinition shiroFilterChainDefinition() { -// DefaultShiroFilterChainDefinition chain = new DefaultShiroFilterChainDefinition(); -// chain.addPathDefinition("/admin/login/login", "anon"); -// chain.addPathDefinition("/admin/login/unauth", "anon"); -// chain.addPathDefinition("/admin/**", "authc"); -// return chain; -// } - @Bean - public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { + public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); Map filterChainDefinitionMap = new LinkedHashMap(); @@ -42,6 +33,7 @@ public class ShiroConfig { filterChainDefinitionMap.put("/admin/auth/401", "anon"); filterChainDefinitionMap.put("/admin/auth/index", "anon"); filterChainDefinitionMap.put("/admin/auth/403", "anon"); + filterChainDefinitionMap.put("/admin/index/*", "anon"); filterChainDefinitionMap.put("/admin/**", "authc"); shiroFilterFactoryBean.setLoginUrl("/admin/auth/401"); @@ -53,12 +45,12 @@ public class ShiroConfig { @Bean public SessionManager sessionManager() { - AdminWebSessionManager mySessionManager = new AdminWebSessionManager(); - return mySessionManager; + + return new AdminWebSessionManager(); } @Bean - public DefaultWebSecurityManager securityManager() { + public DefaultWebSecurityManager defaultWebSecurityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(realm()); securityManager.setSessionManager(sessionManager()); @@ -67,7 +59,8 @@ public class ShiroConfig { @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { - AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); + AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = + new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/config/ShiroExceptionHandler.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/config/ShiroExceptionHandler.java index b8fbfd26045e7a1d45b6e06f15dc6f86a127faa1..95381460de4372ddbefebdd279508c6e79d394a2 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/config/ShiroExceptionHandler.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/config/ShiroExceptionHandler.java @@ -1,5 +1,7 @@ package org.linlinjava.litemall.admin.config; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authz.AuthorizationException; import org.linlinjava.litemall.core.util.ResponseUtil; @@ -10,20 +12,22 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; @ControllerAdvice -@Order( value = Ordered.HIGHEST_PRECEDENCE ) +@Order(value = Ordered.HIGHEST_PRECEDENCE) public class ShiroExceptionHandler { + private final Log logger = LogFactory.getLog(ShiroExceptionHandler.class); + @ExceptionHandler(AuthenticationException.class) @ResponseBody public Object unauthenticatedHandler(AuthenticationException e) { - e.printStackTrace(); + logger.warn(e.getMessage(), e); return ResponseUtil.unlogin(); } @ExceptionHandler(AuthorizationException.class) @ResponseBody public Object unauthorizedHandler(AuthorizationException e) { - e.printStackTrace(); + logger.warn(e.getMessage(), e); return ResponseUtil.unauthz(); } diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/dao/Product.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/dao/Product.java deleted file mode 100644 index b2636a103773198e424b1ff1954a9b1ebf64f360..0000000000000000000000000000000000000000 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/dao/Product.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.linlinjava.litemall.admin.dao; - -import java.math.BigDecimal; - -public class Product { - String[] specifications; - BigDecimal price; - Integer number; - String url; - - public String[] getSpecifications() { - return specifications; - } - - public void setSpecifications(String[] specifications) { - this.specifications = specifications; - } - - public BigDecimal getPrice() { - return price; - } - - public void setPrice(BigDecimal price) { - this.price = price; - } - - public Integer getNumber() { - return number; - } - - public void setNumber(Integer number) { - this.number = number; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } -} diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/dao/GoodsAllinone.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/dto/GoodsAllinone.java similarity index 92% rename from litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/dao/GoodsAllinone.java rename to litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/dto/GoodsAllinone.java index 84c037fa07852cad967a4139fa7d6f7204ae68ac..5bd93c5081e5d819ecf47ee84037c1358b97e110 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/dao/GoodsAllinone.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/dto/GoodsAllinone.java @@ -1,4 +1,4 @@ -package org.linlinjava.litemall.admin.dao; +package org.linlinjava.litemall.admin.dto; import org.linlinjava.litemall.db.domain.LitemallGoods; import org.linlinjava.litemall.db.domain.LitemallGoodsAttribute; @@ -9,7 +9,6 @@ public class GoodsAllinone { LitemallGoods goods; LitemallGoodsSpecification[] specifications; LitemallGoodsAttribute[] attributes; - // 这里采用 Product 再转换到 LitemallGoodsProduct LitemallGoodsProduct[] products; public LitemallGoods getGoods() { diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/job/CouponJob.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/job/CouponJob.java index bc7c4f7fb94d8489d39402ae8881c9762fa80fc5..4e09ca53b50b8538d8eff2d9c794eeee34fa4e42 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/job/CouponJob.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/job/CouponJob.java @@ -11,6 +11,7 @@ import org.linlinjava.litemall.db.util.CouponUserConstant; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; + import java.util.List; /** @@ -27,22 +28,26 @@ public class CouponJob { /** * 每隔一个小时检查 + * TODO + * 注意,因为是相隔一个小时检查,因此导致优惠券真正超时时间可能比设定时间延迟1个小时 */ @Scheduled(fixedDelay = 60 * 60 * 1000) public void checkCouponExpired() { logger.info("系统开启任务检查优惠券是否已经过期"); List couponList = couponService.queryExpired(); - for(LitemallCoupon coupon : couponList){ + for (LitemallCoupon coupon : couponList) { coupon.setStatus(CouponConstant.STATUS_EXPIRED); couponService.updateById(coupon); } List couponUserList = couponUserService.queryExpired(); - for(LitemallCouponUser couponUser : couponUserList){ + for (LitemallCouponUser couponUser : couponUserList) { couponUser.setStatus(CouponUserConstant.STATUS_EXPIRED); couponUserService.update(couponUser); } + + logger.info("系统结束任务检查优惠券是否已经过期"); } } diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/job/DbJob.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/job/DbJob.java new file mode 100644 index 0000000000000000000000000000000000000000..351be5397777dda4afd7a76aff69f23d24532824 --- /dev/null +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/job/DbJob.java @@ -0,0 +1,56 @@ +package org.linlinjava.litemall.admin.job; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.linlinjava.litemall.db.util.DbUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.io.IOException; +import java.time.LocalDate; + +/** + * 数据库定时备份任务 + * 在backup文件夹中备份最近七日的数据库文件 + */ +@Component +public class DbJob { + private final Log logger = LogFactory.getLog(DbJob.class); + + @Autowired + private Environment environment; + + /* + * 定时时间是每天凌晨5点。 + */ + @Scheduled(cron = "0 0 5 * * ?") + public void backup() throws IOException { + logger.info("系统开启定时任务数据库备份"); + + String user = environment.getProperty("spring.datasource.druid.username"); + String password = environment.getProperty("spring.datasource.druid.password"); + String url = environment.getProperty("spring.datasource.druid.url"); + int index1 = url.indexOf("3306/"); + int index2 = url.indexOf("?"); + String db = url.substring(index1+5, index2); + + LocalDate localDate = LocalDate.now(); + String fileName = localDate.toString(); + File file = new File("backup", fileName); + file.getParentFile().mkdirs(); + file.createNewFile(); + + // 备份今天数据库 + DbUtil.backup(file, user, password, db); + // 删除七天前数据库备份文件 + LocalDate before = localDate.minusDays(7); + File fileBefore = new File("backup", fileName); + fileBefore.deleteOnExit(); + + logger.info("系统结束定时任务数据库备份"); + } + +} diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/job/OrderJob.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/job/OrderJob.java index 16a354aee5b0f23808f23fa5d3bf186b570a1c13..e9c0b52930d705b394916ff3bafcbc16a4cd53b3 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/job/OrderJob.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/job/OrderJob.java @@ -2,18 +2,14 @@ package org.linlinjava.litemall.admin.job; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.linlinjava.litemall.db.domain.LitemallGoodsProduct; -import org.linlinjava.litemall.db.domain.LitemallOrder; -import org.linlinjava.litemall.db.domain.LitemallOrderGoods; +import org.linlinjava.litemall.core.system.SystemConfig; +import org.linlinjava.litemall.db.domain.*; import org.linlinjava.litemall.db.service.*; import org.linlinjava.litemall.db.util.OrderUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionDefinition; -import org.springframework.transaction.TransactionStatus; -import org.springframework.transaction.support.DefaultTransactionDefinition; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.List; @@ -25,126 +21,61 @@ import java.util.List; public class OrderJob { private final Log logger = LogFactory.getLog(OrderJob.class); - @Autowired - private PlatformTransactionManager txManager; - @Autowired private LitemallOrderGoodsService orderGoodsService; @Autowired private LitemallOrderService orderService; @Autowired private LitemallGoodsProductService productService; - - /** - * 自动取消订单 - *

- * 定时检查订单未付款情况,如果超时半个小时则自动取消订单 - * 定时时间是每次相隔半个小时。 - *

- * 注意,因为是相隔半小时检查,因此导致有订单是超时一个小时以后才设置取消状态。 - * TODO - * 这里可以进一步地配合用户订单查询时订单未付款检查,如果订单超时半小时则取消。 - */ - @Scheduled(fixedDelay = 30 * 60 * 1000) - public void checkOrderUnpaid() { - logger.info("系统开启任务检查订单是否已经超期自动取消订单"); - - List orderList = orderService.queryUnpaid(); - for (LitemallOrder order : orderList) { - LocalDateTime add = order.getAddTime(); - LocalDateTime now = LocalDateTime.now(); - LocalDateTime expired = add.plusMinutes(30); - if (expired.isAfter(now)) { - continue; - } - - // 开启事务管理 - DefaultTransactionDefinition def = new DefaultTransactionDefinition(); - def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); - TransactionStatus status = txManager.getTransaction(def); - try { - // 设置订单已取消状态 - order.setOrderStatus(OrderUtil.STATUS_AUTO_CANCEL); - order.setEndTime(LocalDateTime.now()); - if (orderService.updateWithOptimisticLocker(order) == 0) { - throw new Exception("更新数据已失效"); - } - - // 商品货品数量增加 - Integer orderId = order.getId(); - List orderGoodsList = orderGoodsService.queryByOid(orderId); - for (LitemallOrderGoods orderGoods : orderGoodsList) { - Integer productId = orderGoods.getProductId(); - LitemallGoodsProduct product = productService.findById(productId); - Short number = orderGoods.getNumber(); - if (productService.addStock(productId, number) == 0) { - throw new Exception("商品货品库存增加失败"); - } - } - } catch (Exception ex) { - txManager.rollback(status); - logger.info("订单 ID=" + order.getId() + " 数据更新失败,放弃自动确认收货"); - return; - } - txManager.commit(status); - logger.info("订单 ID=" + order.getId() + " 已经超期自动取消订单"); - } - } + @Autowired + private LitemallGrouponService grouponService; + @Autowired + private LitemallGrouponRulesService rulesService; /** * 自动确认订单 *

- * 定时检查订单未确认情况,如果超时七天则自动确认订单 + * 定时检查订单未确认情况,如果超时 LITEMALL_ORDER_UNCONFIRM 天则自动确认订单 * 定时时间是每天凌晨3点。 *

- * 注意,因为是相隔一天检查,因此导致有订单是超时八天以后才设置自动确认。 - * 这里可以进一步地配合用户订单查询时订单未确认检查,如果订单超时7天则自动确认。 - * 但是,这里可能不是非常必要。相比订单未付款检查中存在商品资源有限所以应该 - * 早点清理未付款情况,这里八天再确认是可以的。。 + * TODO + * 注意,因为是相隔一天检查,因此导致订单真正超时时间是 [LITEMALL_ORDER_UNCONFIRM, 1 + LITEMALL_ORDER_UNCONFIRM] */ @Scheduled(cron = "0 0 3 * * ?") public void checkOrderUnconfirm() { - logger.info("系统开启任务检查订单是否已经超期自动确认收货"); + logger.info("系统开启定时任务检查订单是否已经超期自动确认收货"); - List orderList = orderService.queryUnconfirm(); + List orderList = orderService.queryUnconfirm(SystemConfig.getOrderUnconfirm()); for (LitemallOrder order : orderList) { - LocalDateTime ship = order.getShipTime(); - LocalDateTime now = LocalDateTime.now(); - LocalDateTime expired = ship.plusDays(7); - if (expired.isAfter(now)) { - continue; - } // 设置订单已取消状态 order.setOrderStatus(OrderUtil.STATUS_AUTO_CONFIRM); - order.setConfirmTime(now); + order.setConfirmTime(LocalDateTime.now()); if (orderService.updateWithOptimisticLocker(order) == 0) { logger.info("订单 ID=" + order.getId() + " 数据已经更新,放弃自动确认收货"); } else { logger.info("订单 ID=" + order.getId() + " 已经超期自动确认收货"); } } + + logger.info("系统结束定时任务检查订单是否已经超期自动确认收货"); } /** * 可评价订单商品超期 *

- * 定时检查订单商品评价情况,如果确认商品超时七天则取消可评价状态 + * 定时检查订单商品评价情况,如果确认商品超时 LITEMALL_ORDER_COMMENT 天则取消可评价状态 * 定时时间是每天凌晨4点。 + *

+ * TODO + * 注意,因为是相隔一天检查,因此导致订单真正超时时间是 [LITEMALL_ORDER_COMMENT, 1 + LITEMALL_ORDER_COMMENT] */ @Scheduled(cron = "0 0 4 * * ?") public void checkOrderComment() { logger.info("系统开启任务检查订单是否已经超期未评价"); - LocalDateTime now = LocalDateTime.now(); - List orderList = orderService.queryComment(); + List orderList = orderService.queryComment(SystemConfig.getOrderComment()); for (LitemallOrder order : orderList) { - LocalDateTime confirm = order.getConfirmTime(); - LocalDateTime expired = confirm.plusDays(7); - if (expired.isAfter(now)) { - continue; - } - order.setComments((short) 0); orderService.updateWithOptimisticLocker(order); @@ -154,5 +85,7 @@ public class OrderJob { orderGoodsService.updateById(orderGoods); } } + + logger.info("系统结束任务检查订单是否已经超期未评价"); } } diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/service/AdminGoodsService.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/service/AdminGoodsService.java new file mode 100644 index 0000000000000000000000000000000000000000..a049e925e003a5f257b126facba7d01c1e3de1dc --- /dev/null +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/service/AdminGoodsService.java @@ -0,0 +1,355 @@ +package org.linlinjava.litemall.admin.service; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.linlinjava.litemall.admin.dto.GoodsAllinone; +import org.linlinjava.litemall.admin.vo.CatVo; +import org.linlinjava.litemall.core.qcode.QCodeService; +import org.linlinjava.litemall.core.util.ResponseUtil; +import org.linlinjava.litemall.db.domain.*; +import org.linlinjava.litemall.db.service.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.linlinjava.litemall.admin.util.AdminResponseCode.GOODS_NAME_EXIST; + +@Service +public class AdminGoodsService { + private final Log logger = LogFactory.getLog(AdminGoodsService.class); + + @Autowired + private LitemallGoodsService goodsService; + @Autowired + private LitemallGoodsSpecificationService specificationService; + @Autowired + private LitemallGoodsAttributeService attributeService; + @Autowired + private LitemallGoodsProductService productService; + @Autowired + private LitemallCategoryService categoryService; + @Autowired + private LitemallBrandService brandService; + @Autowired + private LitemallCartService cartService; + @Autowired + private QCodeService qCodeService; + + public Object list(Integer goodsId, String goodsSn, String name, + Integer page, Integer limit, String sort, String order) { + List goodsList = goodsService.querySelective(goodsId, goodsSn, name, page, limit, sort, order); + return ResponseUtil.okList(goodsList); + } + + private Object validate(GoodsAllinone goodsAllinone) { + LitemallGoods goods = goodsAllinone.getGoods(); + String name = goods.getName(); + if (StringUtils.isEmpty(name)) { + return ResponseUtil.badArgument(); + } + String goodsSn = goods.getGoodsSn(); + if (StringUtils.isEmpty(goodsSn)) { + return ResponseUtil.badArgument(); + } + // 品牌商可以不设置,如果设置则需要验证品牌商存在 + Integer brandId = goods.getBrandId(); + if (brandId != null && brandId != 0) { + if (brandService.findById(brandId) == null) { + return ResponseUtil.badArgumentValue(); + } + } + // 分类可以不设置,如果设置则需要验证分类存在 + Integer categoryId = goods.getCategoryId(); + if (categoryId != null && categoryId != 0) { + if (categoryService.findById(categoryId) == null) { + return ResponseUtil.badArgumentValue(); + } + } + + LitemallGoodsAttribute[] attributes = goodsAllinone.getAttributes(); + for (LitemallGoodsAttribute attribute : attributes) { + String attr = attribute.getAttribute(); + if (StringUtils.isEmpty(attr)) { + return ResponseUtil.badArgument(); + } + String value = attribute.getValue(); + if (StringUtils.isEmpty(value)) { + return ResponseUtil.badArgument(); + } + } + + LitemallGoodsSpecification[] specifications = goodsAllinone.getSpecifications(); + for (LitemallGoodsSpecification specification : specifications) { + String spec = specification.getSpecification(); + if (StringUtils.isEmpty(spec)) { + return ResponseUtil.badArgument(); + } + String value = specification.getValue(); + if (StringUtils.isEmpty(value)) { + return ResponseUtil.badArgument(); + } + } + + LitemallGoodsProduct[] products = goodsAllinone.getProducts(); + for (LitemallGoodsProduct product : products) { + Integer number = product.getNumber(); + if (number == null || number < 0) { + return ResponseUtil.badArgument(); + } + + BigDecimal price = product.getPrice(); + if (price == null) { + return ResponseUtil.badArgument(); + } + + String[] productSpecifications = product.getSpecifications(); + if (productSpecifications.length == 0) { + return ResponseUtil.badArgument(); + } + } + + return null; + } + + /** + * 编辑商品 + * + * NOTE: + * 由于商品涉及到四个表,特别是litemall_goods_product表依赖litemall_goods_specification表, + * 这导致允许所有字段都是可编辑会带来一些问题,因此这里商品编辑功能是受限制: + * (1)litemall_goods表可以编辑字段; + * (2)litemall_goods_specification表只能编辑pic_url字段,其他操作不支持; + * (3)litemall_goods_product表只能编辑price, number和url字段,其他操作不支持; + * (4)litemall_goods_attribute表支持编辑、添加和删除操作。 + * + * NOTE2: + * 前后端这里使用了一个小技巧: + * 如果前端传来的update_time字段是空,则说明前端已经更新了某个记录,则这个记录会更新; + * 否则说明这个记录没有编辑过,无需更新该记录。 + * + * NOTE3: + * (1)购物车缓存了一些商品信息,因此需要及时更新。 + * 目前这些字段是goods_sn, goods_name, price, pic_url。 + * (2)但是订单里面的商品信息则是不会更新。 + * 如果订单是未支付订单,此时仍然以旧的价格支付。 + */ + @Transactional + public Object update(GoodsAllinone goodsAllinone) { + Object error = validate(goodsAllinone); + if (error != null) { + return error; + } + + LitemallGoods goods = goodsAllinone.getGoods(); + LitemallGoodsAttribute[] attributes = goodsAllinone.getAttributes(); + LitemallGoodsSpecification[] specifications = goodsAllinone.getSpecifications(); + LitemallGoodsProduct[] products = goodsAllinone.getProducts(); + + //将生成的分享图片地址写入数据库 + String url = qCodeService.createGoodShareImage(goods.getId().toString(), goods.getPicUrl(), goods.getName()); + goods.setShareUrl(url); + + // 商品表里面有一个字段retailPrice记录当前商品的最低价 + BigDecimal retailPrice = new BigDecimal(Integer.MAX_VALUE); + for (LitemallGoodsProduct product : products) { + BigDecimal productPrice = product.getPrice(); + if(retailPrice.compareTo(productPrice) == 1){ + retailPrice = productPrice; + } + } + goods.setRetailPrice(retailPrice); + + // 商品基本信息表litemall_goods + if (goodsService.updateById(goods) == 0) { + throw new RuntimeException("更新数据失败"); + } + + Integer gid = goods.getId(); + + // 商品规格表litemall_goods_specification + for (LitemallGoodsSpecification specification : specifications) { + // 目前只支持更新规格表的图片字段 + if(specification.getUpdateTime() == null){ + specification.setSpecification(null); + specification.setValue(null); + specificationService.updateById(specification); + } + } + + // 商品货品表litemall_product + for (LitemallGoodsProduct product : products) { + if(product.getUpdateTime() == null) { + productService.updateById(product); + } + } + + // 商品参数表litemall_goods_attribute + for (LitemallGoodsAttribute attribute : attributes) { + if (attribute.getId() == null || attribute.getId().equals(0)){ + attribute.setGoodsId(goods.getId()); + attributeService.add(attribute); + } + else if(attribute.getDeleted()){ + attributeService.deleteById(attribute.getId()); + } + else if(attribute.getUpdateTime() == null){ + attributeService.updateById(attribute); + } + } + + // 这里需要注意的是购物车litemall_cart有些字段是拷贝商品的一些字段,因此需要及时更新 + // 目前这些字段是goods_sn, goods_name, price, pic_url + for (LitemallGoodsProduct product : products) { + cartService.updateProduct(product.getId(), goods.getGoodsSn(), goods.getName(), product.getPrice(), product.getUrl()); + } + + return ResponseUtil.ok(); + } + + @Transactional + public Object delete(LitemallGoods goods) { + Integer id = goods.getId(); + if (id == null) { + return ResponseUtil.badArgument(); + } + + Integer gid = goods.getId(); + goodsService.deleteById(gid); + specificationService.deleteByGid(gid); + attributeService.deleteByGid(gid); + productService.deleteByGid(gid); + return ResponseUtil.ok(); + } + + @Transactional + public Object create(GoodsAllinone goodsAllinone) { + Object error = validate(goodsAllinone); + if (error != null) { + return error; + } + + LitemallGoods goods = goodsAllinone.getGoods(); + LitemallGoodsAttribute[] attributes = goodsAllinone.getAttributes(); + LitemallGoodsSpecification[] specifications = goodsAllinone.getSpecifications(); + LitemallGoodsProduct[] products = goodsAllinone.getProducts(); + + String name = goods.getName(); + if (goodsService.checkExistByName(name)) { + return ResponseUtil.fail(GOODS_NAME_EXIST, "商品名已经存在"); + } + + // 商品表里面有一个字段retailPrice记录当前商品的最低价 + BigDecimal retailPrice = new BigDecimal(Integer.MAX_VALUE); + for (LitemallGoodsProduct product : products) { + BigDecimal productPrice = product.getPrice(); + if(retailPrice.compareTo(productPrice) == 1){ + retailPrice = productPrice; + } + } + goods.setRetailPrice(retailPrice); + + // 商品基本信息表litemall_goods + goodsService.add(goods); + + //将生成的分享图片地址写入数据库 + String url = qCodeService.createGoodShareImage(goods.getId().toString(), goods.getPicUrl(), goods.getName()); + if (!StringUtils.isEmpty(url)) { + goods.setShareUrl(url); + if (goodsService.updateById(goods) == 0) { + throw new RuntimeException("更新数据失败"); + } + } + + // 商品规格表litemall_goods_specification + for (LitemallGoodsSpecification specification : specifications) { + specification.setGoodsId(goods.getId()); + specificationService.add(specification); + } + + // 商品参数表litemall_goods_attribute + for (LitemallGoodsAttribute attribute : attributes) { + attribute.setGoodsId(goods.getId()); + attributeService.add(attribute); + } + + // 商品货品表litemall_product + for (LitemallGoodsProduct product : products) { + product.setGoodsId(goods.getId()); + productService.add(product); + } + return ResponseUtil.ok(); + } + + public Object list2() { + // http://element-cn.eleme.io/#/zh-CN/component/cascader + // 管理员设置“所属分类” + List l1CatList = categoryService.queryL1(); + List categoryList = new ArrayList<>(l1CatList.size()); + + for (LitemallCategory l1 : l1CatList) { + CatVo l1CatVo = new CatVo(); + l1CatVo.setValue(l1.getId()); + l1CatVo.setLabel(l1.getName()); + + List l2CatList = categoryService.queryByPid(l1.getId()); + List children = new ArrayList<>(l2CatList.size()); + for (LitemallCategory l2 : l2CatList) { + CatVo l2CatVo = new CatVo(); + l2CatVo.setValue(l2.getId()); + l2CatVo.setLabel(l2.getName()); + children.add(l2CatVo); + } + l1CatVo.setChildren(children); + + categoryList.add(l1CatVo); + } + + // http://element-cn.eleme.io/#/zh-CN/component/select + // 管理员设置“所属品牌商” + List list = brandService.all(); + List> brandList = new ArrayList<>(l1CatList.size()); + for (LitemallBrand brand : list) { + Map b = new HashMap<>(2); + b.put("value", brand.getId()); + b.put("label", brand.getName()); + brandList.add(b); + } + + Map data = new HashMap<>(); + data.put("categoryList", categoryList); + data.put("brandList", brandList); + return ResponseUtil.ok(data); + } + + public Object detail(Integer id) { + LitemallGoods goods = goodsService.findById(id); + List products = productService.queryByGid(id); + List specifications = specificationService.queryByGid(id); + List attributes = attributeService.queryByGid(id); + + Integer categoryId = goods.getCategoryId(); + LitemallCategory category = categoryService.findById(categoryId); + Integer[] categoryIds = new Integer[]{}; + if (category != null) { + Integer parentCategoryId = category.getPid(); + categoryIds = new Integer[]{parentCategoryId, categoryId}; + } + + Map data = new HashMap<>(); + data.put("goods", goods); + data.put("specifications", specifications); + data.put("products", products); + data.put("attributes", attributes); + data.put("categoryIds", categoryIds); + + return ResponseUtil.ok(data); + } + +} diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/service/AdminOrderService.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/service/AdminOrderService.java new file mode 100644 index 0000000000000000000000000000000000000000..daae6ee23d44b77950009ea7feaada7be94f138a --- /dev/null +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/service/AdminOrderService.java @@ -0,0 +1,290 @@ +package org.linlinjava.litemall.admin.service; + +import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest; +import com.github.binarywang.wxpay.bean.result.WxPayRefundResult; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.github.binarywang.wxpay.service.WxPayService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.linlinjava.litemall.core.notify.NotifyService; +import org.linlinjava.litemall.core.notify.NotifyType; +import org.linlinjava.litemall.core.util.JacksonUtil; +import org.linlinjava.litemall.core.util.ResponseUtil; +import org.linlinjava.litemall.db.domain.*; +import org.linlinjava.litemall.db.service.*; +import org.linlinjava.litemall.db.util.CouponUserConstant; +import org.linlinjava.litemall.db.util.OrderUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.linlinjava.litemall.admin.util.AdminResponseCode.*; + +@Service + +public class AdminOrderService { + private final Log logger = LogFactory.getLog(AdminOrderService.class); + + @Autowired + private LitemallOrderGoodsService orderGoodsService; + @Autowired + private LitemallOrderService orderService; + @Autowired + private LitemallGoodsProductService productService; + @Autowired + private LitemallUserService userService; + @Autowired + private LitemallCommentService commentService; + @Autowired + private WxPayService wxPayService; + @Autowired + private NotifyService notifyService; + @Autowired + private LogHelper logHelper; + @Autowired + private LitemallCouponUserService couponUserService; + + public Object list(Integer userId, String orderSn, LocalDateTime start, LocalDateTime end, List orderStatusArray, + Integer page, Integer limit, String sort, String order) { + List orderList = orderService.querySelective(userId, orderSn, start, end, orderStatusArray, page, limit, + sort, order); + return ResponseUtil.okList(orderList); + } + + public Object detail(Integer id) { + LitemallOrder order = orderService.findById(id); + List orderGoods = orderGoodsService.queryByOid(id); + UserVo user = userService.findUserVoById(order.getUserId()); + Map data = new HashMap<>(); + data.put("order", order); + data.put("orderGoods", orderGoods); + data.put("user", user); + + return ResponseUtil.ok(data); + } + + /** + * 订单退款 + *

+ * 1. 检测当前订单是否能够退款; + * 2. 微信退款操作; + * 3. 设置订单退款确认状态; + * 4. 订单商品库存回库。 + *

+ * TODO + * 虽然接入了微信退款API,但是从安全角度考虑,建议开发者删除这里微信退款代码,采用以下两步走步骤: + * 1. 管理员登录微信官方支付平台点击退款操作进行退款 + * 2. 管理员登录litemall管理后台点击退款操作进行订单状态修改和商品库存回库 + * + * @param body 订单信息,{ orderId:xxx } + * @return 订单退款操作结果 + */ + @Transactional + public Object refund(String body) { + Integer orderId = JacksonUtil.parseInteger(body, "orderId"); + String refundMoney = JacksonUtil.parseString(body, "refundMoney"); + if (orderId == null) { + return ResponseUtil.badArgument(); + } + if (StringUtils.isEmpty(refundMoney)) { + return ResponseUtil.badArgument(); + } + + LitemallOrder order = orderService.findById(orderId); + if (order == null) { + return ResponseUtil.badArgument(); + } + + if (order.getActualPrice().compareTo(new BigDecimal(refundMoney)) != 0) { + return ResponseUtil.badArgumentValue(); + } + + // 如果订单不是退款状态,则不能退款 + if (!order.getOrderStatus().equals(OrderUtil.STATUS_REFUND)) { + return ResponseUtil.fail(ORDER_CONFIRM_NOT_ALLOWED, "订单不能确认收货"); + } + + // 微信退款 + WxPayRefundRequest wxPayRefundRequest = new WxPayRefundRequest(); + wxPayRefundRequest.setOutTradeNo(order.getOrderSn()); + wxPayRefundRequest.setOutRefundNo("refund_" + order.getOrderSn()); + // 元转成分 + Integer totalFee = order.getActualPrice().multiply(new BigDecimal(100)).intValue(); + wxPayRefundRequest.setTotalFee(totalFee); + wxPayRefundRequest.setRefundFee(totalFee); + + WxPayRefundResult wxPayRefundResult; + try { + wxPayRefundResult = wxPayService.refund(wxPayRefundRequest); + } catch (WxPayException e) { + logger.error(e.getMessage(), e); + return ResponseUtil.fail(ORDER_REFUND_FAILED, "订单退款失败"); + } + if (!wxPayRefundResult.getReturnCode().equals("SUCCESS")) { + logger.warn("refund fail: " + wxPayRefundResult.getReturnMsg()); + return ResponseUtil.fail(ORDER_REFUND_FAILED, "订单退款失败"); + } + if (!wxPayRefundResult.getResultCode().equals("SUCCESS")) { + logger.warn("refund fail: " + wxPayRefundResult.getReturnMsg()); + return ResponseUtil.fail(ORDER_REFUND_FAILED, "订单退款失败"); + } + + LocalDateTime now = LocalDateTime.now(); + // 设置订单取消状态 + order.setOrderStatus(OrderUtil.STATUS_REFUND_CONFIRM); + order.setEndTime(now); + // 记录订单退款相关信息 + order.setRefundAmount(order.getActualPrice()); + order.setRefundType("微信退款接口"); + order.setRefundContent(wxPayRefundResult.getRefundId()); + order.setRefundTime(now); + if (orderService.updateWithOptimisticLocker(order) == 0) { + throw new RuntimeException("更新数据已失效"); + } + + // 商品货品数量增加 + List orderGoodsList = orderGoodsService.queryByOid(orderId); + for (LitemallOrderGoods orderGoods : orderGoodsList) { + Integer productId = orderGoods.getProductId(); + Short number = orderGoods.getNumber(); + if (productService.addStock(productId, number) == 0) { + throw new RuntimeException("商品货品库存增加失败"); + } + } + + // 返还优惠券 + List couponUsers = couponUserService.findByOid(orderId); + for (LitemallCouponUser couponUser: couponUsers) { + // 优惠券状态设置为可使用 + couponUser.setStatus(CouponUserConstant.STATUS_USABLE); + couponUser.setUpdateTime(LocalDateTime.now()); + couponUserService.update(couponUser); + } + + //TODO 发送邮件和短信通知,这里采用异步发送 + // 退款成功通知用户, 例如“您申请的订单退款 [ 单号:{1} ] 已成功,请耐心等待到账。” + // 注意订单号只发后6位 + notifyService.notifySmsTemplate(order.getMobile(), NotifyType.REFUND, + new String[]{order.getOrderSn().substring(8, 14)}); + + logHelper.logOrderSucceed("退款", "订单编号 " + order.getOrderSn()); + return ResponseUtil.ok(); + } + + /** + * 发货 + * 1. 检测当前订单是否能够发货 + * 2. 设置订单发货状态 + * + * @param body 订单信息,{ orderId:xxx, shipSn: xxx, shipChannel: xxx } + * @return 订单操作结果 + * 成功则 { errno: 0, errmsg: '成功' } + * 失败则 { errno: XXX, errmsg: XXX } + */ + public Object ship(String body) { + Integer orderId = JacksonUtil.parseInteger(body, "orderId"); + String shipSn = JacksonUtil.parseString(body, "shipSn"); + String shipChannel = JacksonUtil.parseString(body, "shipChannel"); + if (orderId == null || shipSn == null || shipChannel == null) { + return ResponseUtil.badArgument(); + } + + LitemallOrder order = orderService.findById(orderId); + if (order == null) { + return ResponseUtil.badArgument(); + } + + // 如果订单不是已付款状态,则不能发货 + if (!order.getOrderStatus().equals(OrderUtil.STATUS_PAY)) { + return ResponseUtil.fail(ORDER_CONFIRM_NOT_ALLOWED, "订单不能确认收货"); + } + + order.setOrderStatus(OrderUtil.STATUS_SHIP); + order.setShipSn(shipSn); + order.setShipChannel(shipChannel); + order.setShipTime(LocalDateTime.now()); + if (orderService.updateWithOptimisticLocker(order) == 0) { + return ResponseUtil.updatedDateExpired(); + } + + //TODO 发送邮件和短信通知,这里采用异步发送 + // 发货会发送通知短信给用户: * + // "您的订单已经发货,快递公司 {1},快递单 {2} ,请注意查收" + notifyService.notifySmsTemplate(order.getMobile(), NotifyType.SHIP, new String[]{shipChannel, shipSn}); + + logHelper.logOrderSucceed("发货", "订单编号 " + order.getOrderSn()); + return ResponseUtil.ok(); + } + + /** + * 删除订单 + * 1. 检测当前订单是否能够删除 + * 2. 删除订单 + * + * @param body 订单信息,{ orderId:xxx } + * @return 订单操作结果 + * 成功则 { errno: 0, errmsg: '成功' } + * 失败则 { errno: XXX, errmsg: XXX } + */ + public Object delete(String body) { + Integer orderId = JacksonUtil.parseInteger(body, "orderId"); + LitemallOrder order = orderService.findById(orderId); + if (order == null) { + return ResponseUtil.badArgument(); + } + + // 如果订单不是关闭状态(已取消、系统取消、已退款、用户已确认、系统已确认),则不能删除 + Short status = order.getOrderStatus(); + if (!status.equals(OrderUtil.STATUS_CANCEL) && !status.equals(OrderUtil.STATUS_AUTO_CANCEL) && + !status.equals(OrderUtil.STATUS_CONFIRM) &&!status.equals(OrderUtil.STATUS_AUTO_CONFIRM) && + !status.equals(OrderUtil.STATUS_REFUND_CONFIRM)) { + return ResponseUtil.fail(ORDER_DELETE_FAILED, "订单不能删除"); + } + // 删除订单 + orderService.deleteById(orderId); + // 删除订单商品 + orderGoodsService.deleteByOrderId(orderId); + logHelper.logOrderSucceed("删除", "订单编号 " + order.getOrderSn()); + return ResponseUtil.ok(); + } + + /** + * 回复订单商品 + * + * @param body 订单信息,{ orderId:xxx } + * @return 订单操作结果 + * 成功则 { errno: 0, errmsg: '成功' } + * 失败则 { errno: XXX, errmsg: XXX } + */ + public Object reply(String body) { + Integer commentId = JacksonUtil.parseInteger(body, "commentId"); + if (commentId == null || commentId == 0) { + return ResponseUtil.badArgument(); + } + // 目前只支持回复一次 + LitemallComment comment = commentService.findById(commentId); + if(comment == null){ + return ResponseUtil.badArgument(); + } + if (!StringUtils.isEmpty(comment.getAdminContent())) { + return ResponseUtil.fail(ORDER_REPLY_EXIST, "订单商品已回复!"); + } + String content = JacksonUtil.parseString(body, "content"); + if (StringUtils.isEmpty(content)) { + return ResponseUtil.badArgument(); + } + // 更新评价回复 + comment.setAdminContent(content); + commentService.updateById(comment); + + return ResponseUtil.ok(); + } + +} diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/service/LogHelper.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/service/LogHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..70e5d58cb0be0b0f598fb812a27761d73009d74f --- /dev/null +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/service/LogHelper.java @@ -0,0 +1,113 @@ +package org.linlinjava.litemall.admin.service; + +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.subject.Subject; +import org.linlinjava.litemall.core.util.IpUtil; +import org.linlinjava.litemall.db.domain.LitemallAdmin; +import org.linlinjava.litemall.db.domain.LitemallLog; +import org.linlinjava.litemall.db.service.LitemallLogService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; + +/** + * 这里的日志类型设计成四种(当然开发者需要可以自己扩展) + * 一般日志:用户觉得需要查看的一般操作日志,建议是默认的日志级别 + * 安全日志:用户安全相关的操作日志,例如登录、删除管理员 + * 订单日志:用户交易相关的操作日志,例如订单发货、退款 + * 其他日志:如果以上三种不合适,可以选择其他日志,建议是优先级最低的日志级别 + *

+ * 当然可能很多操作是不需要记录到数据库的,例如编辑商品、编辑广告品之类。 + */ +@Component +public class LogHelper { + public static final Integer LOG_TYPE_GENERAL = 0; + public static final Integer LOG_TYPE_AUTH = 1; + public static final Integer LOG_TYPE_ORDER = 2; + public static final Integer LOG_TYPE_OTHER = 3; + + @Autowired + private LitemallLogService logService; + + public void logGeneralSucceed(String action) { + logAdmin(LOG_TYPE_GENERAL, action, true, "", ""); + } + + public void logGeneralSucceed(String action, String result) { + logAdmin(LOG_TYPE_GENERAL, action, true, result, ""); + } + + public void logGeneralFail(String action, String error) { + logAdmin(LOG_TYPE_GENERAL, action, false, error, ""); + } + + public void logAuthSucceed(String action) { + logAdmin(LOG_TYPE_AUTH, action, true, "", ""); + } + + public void logAuthSucceed(String action, String result) { + logAdmin(LOG_TYPE_AUTH, action, true, result, ""); + } + + public void logAuthFail(String action, String error) { + logAdmin(LOG_TYPE_AUTH, action, false, error, ""); + } + + public void logOrderSucceed(String action) { + logAdmin(LOG_TYPE_ORDER, action, true, "", ""); + } + + public void logOrderSucceed(String action, String result) { + logAdmin(LOG_TYPE_ORDER, action, true, result, ""); + } + + public void logOrderFail(String action, String error) { + logAdmin(LOG_TYPE_ORDER, action, false, error, ""); + } + + public void logOtherSucceed(String action) { + logAdmin(LOG_TYPE_OTHER, action, true, "", ""); + } + + public void logOtherSucceed(String action, String result) { + logAdmin(LOG_TYPE_OTHER, action, true, result, ""); + } + + + public void logOtherFail(String action, String error) { + logAdmin(LOG_TYPE_OTHER, action, false, error, ""); + } + + public void logAdmin(Integer type, String action, Boolean succeed, String result, String comment) { + LitemallLog log = new LitemallLog(); + + Subject currentUser = SecurityUtils.getSubject(); + if (currentUser != null) { + LitemallAdmin admin = (LitemallAdmin) currentUser.getPrincipal(); + if (admin != null) { + log.setAdmin(admin.getUsername()); + } else { + log.setAdmin("匿名用户"); + } + } else { + log.setAdmin("匿名用户"); + } + + HttpServletRequest request = + ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + if (request != null) { + log.setIp(IpUtil.getIpAddr(request)); + } + + log.setType(type); + log.setAction(action); + log.setStatus(succeed); + log.setResult(result); + log.setComment(comment); + logService.add(log); + } + +} diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/shiro/AdminAuthorizingRealm.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/shiro/AdminAuthorizingRealm.java index 0c1654bdc844456d15b9d851a1ba26b9ec21786d..505f3ec453e52ef56f840d9a9cf383107a551567 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/shiro/AdminAuthorizingRealm.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/shiro/AdminAuthorizingRealm.java @@ -12,8 +12,6 @@ import org.linlinjava.litemall.db.domain.LitemallAdmin; import org.linlinjava.litemall.db.service.LitemallAdminService; import org.linlinjava.litemall.db.service.LitemallPermissionService; import org.linlinjava.litemall.db.service.LitemallRoleService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -23,7 +21,6 @@ import java.util.Set; public class AdminAuthorizingRealm extends AuthorizingRealm { - private static final Logger log = LoggerFactory.getLogger(AdminAuthorizingRealm.class); @Autowired private LitemallAdminService adminService; @Autowired @@ -52,7 +49,7 @@ public class AdminAuthorizingRealm extends AuthorizingRealm { UsernamePasswordToken upToken = (UsernamePasswordToken) token; String username = upToken.getUsername(); - String password=new String(upToken.getPassword()); + String password = new String(upToken.getPassword()); if (StringUtils.isEmpty(username)) { throw new AccountException("用户名不能为空"); @@ -64,16 +61,16 @@ public class AdminAuthorizingRealm extends AuthorizingRealm { List adminList = adminService.findAdmin(username); Assert.state(adminList.size() < 2, "同一个用户名存在两个账户"); if (adminList.size() == 0) { - throw new UnknownAccountException("找不到用户("+username+")的帐号信息"); + throw new UnknownAccountException("找不到用户(" + username + ")的帐号信息"); } LitemallAdmin admin = adminList.get(0); BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); if (!encoder.matches(password, admin.getPassword())) { - throw new UnknownAccountException("找不到用户("+username+")的帐号信息"); + throw new UnknownAccountException("找不到用户(" + username + ")的帐号信息"); } - return new SimpleAuthenticationInfo(admin,password,getName()); + return new SimpleAuthenticationInfo(admin, password, getName()); } } diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/shiro/AdminWebSessionManager.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/shiro/AdminWebSessionManager.java index 46cfdcbb0b13d1484ffa092ab5cc8432135728ef..e2795bbb0ea25a155c6d84bee97fe8fb9bf0549b 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/shiro/AdminWebSessionManager.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/shiro/AdminWebSessionManager.java @@ -17,13 +17,13 @@ public class AdminWebSessionManager extends DefaultWebSessionManager { @Override protected Serializable getSessionId(ServletRequest request, ServletResponse response) { String id = WebUtils.toHttp(request).getHeader(LOGIN_TOKEN_KEY); - if (!StringUtils.isEmpty(id)) { + if (!StringUtils.isEmpty(id)) { request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE); request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id); request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE); return id; - } else { - return super.getSessionId(request, response); - } + } else { + return super.getSessionId(request, response); + } } } diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/task/AdminTaskStartupRunner.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/task/AdminTaskStartupRunner.java new file mode 100644 index 0000000000000000000000000000000000000000..2a0339424fb8f15dce0805c433bf9705cf4d9106 --- /dev/null +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/task/AdminTaskStartupRunner.java @@ -0,0 +1,41 @@ +package org.linlinjava.litemall.admin.task; + +import org.linlinjava.litemall.core.task.TaskService; +import org.linlinjava.litemall.db.domain.LitemallGrouponRules; +import org.linlinjava.litemall.db.service.LitemallGrouponRulesService; +import org.linlinjava.litemall.db.util.GrouponConstant; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; + +@Component +public class AdminTaskStartupRunner implements ApplicationRunner { + + @Autowired + private LitemallGrouponRulesService rulesService; + @Autowired + private TaskService taskService; + + @Override + public void run(ApplicationArguments args) throws Exception { + List grouponRulesList = rulesService.queryByStatus(GrouponConstant.RULE_STATUS_ON); + for(LitemallGrouponRules grouponRules : grouponRulesList){ + LocalDateTime now = LocalDateTime.now(); + LocalDateTime expire = grouponRules.getExpireTime(); + if(expire.isBefore(now)) { + // 已经过期,则加入延迟队列 + taskService.addTask(new GrouponRuleExpiredTask(grouponRules.getId(), 0)); + } + else{ + // 还没过期,则加入延迟队列 + long delay = ChronoUnit.MILLIS.between(now, expire); + taskService.addTask(new GrouponRuleExpiredTask(grouponRules.getId(), delay)); + } + } + } +} \ No newline at end of file diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/task/GrouponRuleExpiredTask.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/task/GrouponRuleExpiredTask.java new file mode 100644 index 0000000000000000000000000000000000000000..8d0f72f662850804deda152d535424de79e6fcda --- /dev/null +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/task/GrouponRuleExpiredTask.java @@ -0,0 +1,68 @@ +package org.linlinjava.litemall.admin.task; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.linlinjava.litemall.core.task.Task; +import org.linlinjava.litemall.core.util.BeanUtil; +import org.linlinjava.litemall.db.domain.LitemallGroupon; +import org.linlinjava.litemall.db.domain.LitemallGrouponRules; +import org.linlinjava.litemall.db.domain.LitemallOrder; +import org.linlinjava.litemall.db.service.*; +import org.linlinjava.litemall.db.util.GrouponConstant; +import org.linlinjava.litemall.db.util.OrderUtil; + +import java.util.List; + +public class GrouponRuleExpiredTask extends Task { + private final Log logger = LogFactory.getLog(GrouponRuleExpiredTask.class); + private int grouponRuleId = -1; + + public GrouponRuleExpiredTask(Integer grouponRuleId, long delayInMilliseconds){ + super("GrouponRuleExpiredTask-" + grouponRuleId, delayInMilliseconds); + this.grouponRuleId = grouponRuleId; + } + + @Override + public void run() { + logger.info("系统开始处理延时任务---团购规则过期---" + this.grouponRuleId); + + LitemallOrderService orderService = BeanUtil.getBean(LitemallOrderService.class); + LitemallGrouponService grouponService = BeanUtil.getBean(LitemallGrouponService.class); + LitemallGrouponRulesService grouponRulesService = BeanUtil.getBean(LitemallGrouponRulesService.class); + + LitemallGrouponRules grouponRules = grouponRulesService.findById(grouponRuleId); + if(grouponRules == null){ + return; + } + if(!grouponRules.getStatus().equals(GrouponConstant.RULE_STATUS_ON)){ + return; + } + + // 团购活动取消 + grouponRules.setStatus(GrouponConstant.RULE_STATUS_DOWN_EXPIRE); + grouponRulesService.updateById(grouponRules); + + List grouponList = grouponService.queryByRuleId(grouponRuleId); + // 用户团购处理 + for(LitemallGroupon groupon : grouponList){ + Short status = groupon.getStatus(); + LitemallOrder order = orderService.findById(groupon.getOrderId()); + if(status.equals(GrouponConstant.STATUS_NONE)){ + groupon.setStatus(GrouponConstant.STATUS_FAIL); + grouponService.updateById(groupon); + } + else if(status.equals(GrouponConstant.STATUS_ON)){ + // 如果团购进行中 + // (1) 团购设置团购失败等待退款状态 + groupon.setStatus(GrouponConstant.STATUS_FAIL); + grouponService.updateById(groupon); + // (2) 团购订单申请退款 + if(OrderUtil.isPayStatus(order)) { + order.setOrderStatus(OrderUtil.STATUS_REFUND); + orderService.updateWithOptimisticLocker(order); + } + } + } + logger.info("系统结束处理延时任务---团购规则过期---" + this.grouponRuleId); + } +} diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/util/AdminResponseCode.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/util/AdminResponseCode.java index 93f5eacc19d4c73d245244bd23dbe84aa561f857..3b926b30e6205f7ce9abe23ee6a0233be73556a9 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/util/AdminResponseCode.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/util/AdminResponseCode.java @@ -12,6 +12,7 @@ public class AdminResponseCode { public static final Integer ORDER_CONFIRM_NOT_ALLOWED = 620; public static final Integer ORDER_REFUND_FAILED = 621; public static final Integer ORDER_REPLY_EXIST = 622; + public static final Integer ORDER_DELETE_FAILED = 623; public static final Integer USER_INVALID_NAME = 630; public static final Integer USER_INVALID_PASSWORD = 631; public static final Integer USER_INVALID_MOBILE = 632; @@ -19,4 +20,11 @@ public class AdminResponseCode { public static final Integer USER_MOBILE_EXIST = 634; public static final Integer ROLE_NAME_EXIST = 640; public static final Integer ROLE_SUPER_SUPERMISSION = 641; + public static final Integer ROLE_USER_EXIST = 642; + public static final Integer GROUPON_GOODS_UNKNOWN = 650; + public static final Integer GROUPON_GOODS_EXISTED = 651; + public static final Integer GROUPON_GOODS_OFFLINE = 652; + public static final Integer NOTICE_UPDATE_NOT_ALLOWED = 660; + public static final Integer AFTERSALE_NOT_ALLOWED = 670; + } diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/util/Permission.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/util/Permission.java new file mode 100644 index 0000000000000000000000000000000000000000..8960c10be6246d57c3c0cff2d40cad7d51b8165f --- /dev/null +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/util/Permission.java @@ -0,0 +1,34 @@ +package org.linlinjava.litemall.admin.util; + +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.linlinjava.litemall.admin.annotation.RequiresPermissionsDesc; + +public class Permission { + private RequiresPermissions requiresPermissions; + private RequiresPermissionsDesc requiresPermissionsDesc; + private String api; + + public RequiresPermissions getRequiresPermissions() { + return requiresPermissions; + } + + public RequiresPermissionsDesc getRequiresPermissionsDesc() { + return requiresPermissionsDesc; + } + + public void setRequiresPermissions(RequiresPermissions requiresPermissions) { + this.requiresPermissions = requiresPermissions; + } + + public void setRequiresPermissionsDesc(RequiresPermissionsDesc requiresPermissionsDesc) { + this.requiresPermissionsDesc = requiresPermissionsDesc; + } + + public String getApi() { + return api; + } + + public void setApi(String api) { + this.api = api; + } +} diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/util/PermissionUtil.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/util/PermissionUtil.java index ab0b84c66d83fd865ac0a366eb718020cd5eb843..7b6a0c2aaf03a5c4b83f3c264ab6a01c6c6b6961 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/util/PermissionUtil.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/util/PermissionUtil.java @@ -4,61 +4,40 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.reflect.MethodUtils; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.linlinjava.litemall.admin.annotation.RequiresPermissionsDesc; +import org.linlinjava.litemall.admin.vo.PermVo; import org.springframework.context.ApplicationContext; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.stereotype.Controller; import org.springframework.util.ClassUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; import java.lang.reflect.Method; import java.util.*; -import java.util.stream.Collectors; public class PermissionUtil { - public static Map findPermissions(ApplicationContext context, String basicPackage) { - Map map = context.getBeansWithAnnotation(Controller.class); - Map permissions = new HashMap<>(); - for(Map.Entry entry : map.entrySet()){ - Object bean = entry.getValue(); - if(!StringUtils.contains(ClassUtils.getPackageName(bean.getClass()), basicPackage)){ - continue; - } - - Class clz = bean.getClass(); - Class controllerClz = clz.getSuperclass(); - List methods = MethodUtils.getMethodsListWithAnnotation(controllerClz, RequiresPermissions.class); - for(Method method : methods){ - RequiresPermissions requiresPermissions = AnnotationUtils.getAnnotation(method, RequiresPermissions.class); - RequiresPermissionsDesc requiresPermissionsDesc = AnnotationUtils.getAnnotation(method, RequiresPermissionsDesc.class); - if(requiresPermissions == null || requiresPermissionsDesc == null){ - continue; - } - permissions.put(requiresPermissions, requiresPermissionsDesc); - } - } - return permissions; - } - - public static List listPermissions(ApplicationContext context, String basicPackage) { + public static List listPermVo(List permissions) { List root = new ArrayList<>(); - Map map = findPermissions(context, basicPackage); - for(Map.Entry entry : map.entrySet()) { - RequiresPermissions requiresPermissions = entry.getKey(); - RequiresPermissionsDesc requiresPermissionsDesc = entry.getValue(); + for (Permission permission : permissions) { + RequiresPermissions requiresPermissions = permission.getRequiresPermissions(); + RequiresPermissionsDesc requiresPermissionsDesc = permission.getRequiresPermissionsDesc(); + String api = permission.getApi(); String[] menus = requiresPermissionsDesc.menu(); - if(menus.length != 2){ + if (menus.length != 2) { throw new RuntimeException("目前只支持两级菜单"); } String menu1 = menus[0]; PermVo perm1 = null; - for(PermVo permVo : root){ - if(permVo.getLabel().equals(menu1)){ + for (PermVo permVo : root) { + if (permVo.getLabel().equals(menu1)) { perm1 = permVo; break; } } - if(perm1 == null){ + if (perm1 == null) { perm1 = new PermVo(); perm1.setId(menu1); perm1.setLabel(menu1); @@ -67,13 +46,13 @@ public class PermissionUtil { } String menu2 = menus[1]; PermVo perm2 = null; - for(PermVo permVo : perm1.getChildren()){ - if(permVo.getLabel().equals(menu2)){ + for (PermVo permVo : perm1.getChildren()) { + if (permVo.getLabel().equals(menu2)) { perm2 = permVo; break; } } - if(perm2 == null){ + if (perm2 == null) { perm2 = new PermVo(); perm2.setId(menu2); perm2.setLabel(menu2); @@ -81,12 +60,93 @@ public class PermissionUtil { perm1.getChildren().add(perm2); } - PermVo leftPerm = new PermVo(); - leftPerm.setId(requiresPermissions.value()[0]); - leftPerm.setLabel(requiresPermissionsDesc.button()); + String button = requiresPermissionsDesc.button(); + PermVo leftPerm = null; + for (PermVo permVo : perm2.getChildren()) { + if (permVo.getLabel().equals(button)) { + leftPerm = permVo; + break; + } + } + if (leftPerm == null) { + leftPerm = new PermVo(); + leftPerm.setId(requiresPermissions.value()[0]); + leftPerm.setLabel(requiresPermissionsDesc.button()); + leftPerm.setApi(api); + perm2.getChildren().add(leftPerm); + } else { + // TODO + // 目前限制Controller里面每个方法的RequiresPermissionsDesc注解是唯一的 + // 如果允许相同,可能会造成内部权限不一致。 + throw new RuntimeException("权限已经存在,不能添加新权限"); + } - perm2.getChildren().add(leftPerm); } return root; } + + public static List listPermission(ApplicationContext context, String basicPackage) { + Map map = context.getBeansWithAnnotation(Controller.class); + List permissions = new ArrayList<>(); + for (Map.Entry entry : map.entrySet()) { + Object bean = entry.getValue(); + if (!StringUtils.contains(ClassUtils.getPackageName(bean.getClass()), basicPackage)) { + continue; + } + + Class clz = bean.getClass(); + Class controllerClz = clz.getSuperclass(); + RequestMapping clazzRequestMapping = AnnotationUtils.findAnnotation(controllerClz, RequestMapping.class); + List methods = MethodUtils.getMethodsListWithAnnotation(controllerClz, RequiresPermissions.class); + for (Method method : methods) { + RequiresPermissions requiresPermissions = AnnotationUtils.getAnnotation(method, + RequiresPermissions.class); + RequiresPermissionsDesc requiresPermissionsDesc = AnnotationUtils.getAnnotation(method, + RequiresPermissionsDesc.class); + + if (requiresPermissions == null || requiresPermissionsDesc == null) { + continue; + } + + String api = ""; + if (clazzRequestMapping != null) { + api = clazzRequestMapping.value()[0]; + } + + PostMapping postMapping = AnnotationUtils.getAnnotation(method, PostMapping.class); + if (postMapping != null) { + api = "POST " + api + postMapping.value()[0]; + + Permission permission = new Permission(); + permission.setRequiresPermissions(requiresPermissions); + permission.setRequiresPermissionsDesc(requiresPermissionsDesc); + permission.setApi(api); + permissions.add(permission); + continue; + } + GetMapping getMapping = AnnotationUtils.getAnnotation(method, GetMapping.class); + if (getMapping != null) { + api = "GET " + api + getMapping.value()[0]; + Permission permission = new Permission(); + permission.setRequiresPermissions(requiresPermissions); + permission.setRequiresPermissionsDesc(requiresPermissionsDesc); + permission.setApi(api); + permissions.add(permission); + continue; + } + // TODO + // 这里只支持GetMapping注解或者PostMapping注解,应该进一步提供灵活性 + throw new RuntimeException("目前权限管理应该在method的前面使用GetMapping注解或者PostMapping注解"); + } + } + return permissions; + } + + public static Set listPermissionString(List permissions) { + Set permissionsString = new HashSet<>(); + for (Permission permission : permissions) { + permissionsString.add(permission.getRequiresPermissions().value()[0]); + } + return permissionsString; + } } diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/util/CatVo.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/vo/CatVo.java similarity index 92% rename from litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/util/CatVo.java rename to litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/vo/CatVo.java index 033bb944054e892be2a8129031bc4717e0104133..05a04df2165e2afd1704741520197606d4402c26 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/util/CatVo.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/vo/CatVo.java @@ -1,4 +1,4 @@ -package org.linlinjava.litemall.admin.util; +package org.linlinjava.litemall.admin.vo; import java.util.List; diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/vo/CategoryVo.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/vo/CategoryVo.java new file mode 100644 index 0000000000000000000000000000000000000000..27fe89efc8d606faeeffc5165df46d8373128e6f --- /dev/null +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/vo/CategoryVo.java @@ -0,0 +1,78 @@ +package org.linlinjava.litemall.admin.vo; + +import java.util.List; + +public class CategoryVo { + private Integer id; + private String name; + private String keywords; + private String desc; + private String iconUrl; + private String picUrl; + private String level; + private List children; + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getKeywords() { + return keywords; + } + + public void setKeywords(String keywords) { + this.keywords = keywords; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + public String getIconUrl() { + return iconUrl; + } + + public void setIconUrl(String iconUrl) { + this.iconUrl = iconUrl; + } + + public String getPicUrl() { + return picUrl; + } + + public void setPicUrl(String picUrl) { + this.picUrl = picUrl; + } + + public String getLevel() { + return level; + } + + public void setLevel(String level) { + this.level = level; + } +} diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/util/PermVo.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/vo/PermVo.java similarity index 74% rename from litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/util/PermVo.java rename to litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/vo/PermVo.java index 30ff416c9f1bd5e1d8bf83359f5aac5936aeeb90..6bd3958ac33379fbd46736b15852e6a7958c3d83 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/util/PermVo.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/vo/PermVo.java @@ -1,10 +1,11 @@ -package org.linlinjava.litemall.admin.util; +package org.linlinjava.litemall.admin.vo; import java.util.List; public class PermVo { private String id; private String label; + private String api; private List children; public String getId() { @@ -23,6 +24,14 @@ public class PermVo { this.label = label; } + public void setApi(String api) { + this.api = api; + } + + public String getApi() { + return api; + } + public List getChildren() { return children; } diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/vo/RegionVo.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/vo/RegionVo.java new file mode 100644 index 0000000000000000000000000000000000000000..1ca43e2fabea7cdc963d83a1053aa8e93a9beb42 --- /dev/null +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/vo/RegionVo.java @@ -0,0 +1,52 @@ +package org.linlinjava.litemall.admin.vo; + +import java.util.List; + +public class RegionVo { + private Integer id; + private String name; + private Byte type; + private Integer code; + + private List children; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public List getChildren() { + return children; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Byte getType() { + return type; + } + + public void setType(Byte type) { + this.type = type; + } + + public Integer getCode() { + return code; + } + + public void setCode(Integer code) { + this.code = code; + } + + public void setChildren(List children) { + this.children = children; + } +} diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/util/StatVo.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/vo/StatVo.java similarity index 92% rename from litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/util/StatVo.java rename to litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/vo/StatVo.java index 04005d25e9aa24deae84e386335e5db0ce79ccf7..dcd9c76b1f6d619319206e96cadc3c5b4c98f694 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/util/StatVo.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/vo/StatVo.java @@ -1,4 +1,4 @@ -package org.linlinjava.litemall.admin.util; +package org.linlinjava.litemall.admin.vo; import java.util.ArrayList; import java.util.Arrays; diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminAdController.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminAdController.java index b68bc058f0316fe3418033a67ce7c5780cfee2b9..eaad1c4a6a084bfbc78db872e8830123d5bcd0a1 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminAdController.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminAdController.java @@ -15,9 +15,7 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.validation.constraints.NotNull; -import java.util.HashMap; import java.util.List; -import java.util.Map; @RestController @RequestMapping("/admin/ad") @@ -29,20 +27,15 @@ public class AdminAdController { private LitemallAdService adService; @RequiresPermissions("admin:ad:list") - @RequiresPermissionsDesc(menu={"推广管理" , "广告管理"}, button="查询") - @RequestMapping("/list") + @RequiresPermissionsDesc(menu = {"推广管理", "广告管理"}, button = "查询") + @GetMapping("/list") public Object list(String name, String content, @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer limit, @Sort @RequestParam(defaultValue = "add_time") String sort, @Order @RequestParam(defaultValue = "desc") String order) { List adList = adService.querySelective(name, content, page, limit, sort, order); - int total = adService.countSelective(name, content, page, limit, sort, order); - Map data = new HashMap<>(); - data.put("total", total); - data.put("items", adList); - - return ResponseUtil.ok(data); + return ResponseUtil.okList(adList); } private Object validate(LitemallAd ad) { @@ -58,7 +51,7 @@ public class AdminAdController { } @RequiresPermissions("admin:ad:create") - @RequiresPermissionsDesc(menu={"推广管理" , "广告管理"}, button="添加") + @RequiresPermissionsDesc(menu = {"推广管理", "广告管理"}, button = "添加") @PostMapping("/create") public Object create(@RequestBody LitemallAd ad) { Object error = validate(ad); @@ -70,15 +63,15 @@ public class AdminAdController { } @RequiresPermissions("admin:ad:read") - @RequiresPermissionsDesc(menu={"推广管理" , "广告管理"}, button="详情") + @RequiresPermissionsDesc(menu = {"推广管理", "广告管理"}, button = "详情") @GetMapping("/read") public Object read(@NotNull Integer id) { - LitemallAd brand = adService.findById(id); - return ResponseUtil.ok(brand); + LitemallAd ad = adService.findById(id); + return ResponseUtil.ok(ad); } @RequiresPermissions("admin:ad:update") - @RequiresPermissionsDesc(menu={"推广管理" , "广告管理"}, button="编辑") + @RequiresPermissionsDesc(menu = {"推广管理", "广告管理"}, button = "编辑") @PostMapping("/update") public Object update(@RequestBody LitemallAd ad) { Object error = validate(ad); @@ -93,7 +86,7 @@ public class AdminAdController { } @RequiresPermissions("admin:ad:delete") - @RequiresPermissionsDesc(menu={"推广管理" , "广告管理"}, button="删除") + @RequiresPermissionsDesc(menu = {"推广管理", "广告管理"}, button = "删除") @PostMapping("/delete") public Object delete(@RequestBody LitemallAd ad) { Integer id = ad.getId(); diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminAddressController.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminAddressController.java index 8bc17613f2a59d597fc2ffd7f703c8723a58e532..2a487c40e101b0585f9358db9f0d61a767c18214 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminAddressController.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminAddressController.java @@ -3,6 +3,7 @@ package org.linlinjava.litemall.admin.web; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.linlinjava.litemall.admin.annotation.RequiresPermissionsDesc; import org.linlinjava.litemall.core.util.ResponseUtil; import org.linlinjava.litemall.core.validator.Order; import org.linlinjava.litemall.core.validator.Sort; @@ -16,10 +17,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; @RestController @RequestMapping("/admin/address") @@ -32,27 +30,8 @@ public class AdminAddressController { @Autowired private LitemallRegionService regionService; - private Map toVo(LitemallAddress address) { - Map addressVo = new HashMap<>(); - addressVo.put("id", address.getId()); - addressVo.put("userId", address.getUserId()); - addressVo.put("name", address.getName()); - addressVo.put("mobile", address.getMobile()); - addressVo.put("isDefault", address.getIsDefault()); - addressVo.put("provinceId", address.getProvinceId()); - addressVo.put("cityId", address.getCityId()); - addressVo.put("areaId", address.getAreaId()); - addressVo.put("address", address.getAddress()); - String province = regionService.findById(address.getProvinceId()).getName(); - String city = regionService.findById(address.getCityId()).getName(); - String area = regionService.findById(address.getAreaId()).getName(); - addressVo.put("province", province); - addressVo.put("city", city); - addressVo.put("area", area); - return addressVo; - } - @RequiresPermissions("admin:address:list") + @RequiresPermissionsDesc(menu = {"用户管理", "收货地址"}, button = "查询") @GetMapping("/list") public Object list(Integer userId, String name, @RequestParam(defaultValue = "1") Integer page, @@ -61,18 +40,6 @@ public class AdminAddressController { @Order @RequestParam(defaultValue = "desc") String order) { List addressList = addressService.querySelective(userId, name, page, limit, sort, order); - int total = addressService.countSelective(userId, name, page, limit, sort, order); - - List> addressVoList = new ArrayList<>(addressList.size()); - for (LitemallAddress address : addressList) { - Map addressVo = toVo(address); - addressVoList.add(addressVo); - } - - Map data = new HashMap<>(); - data.put("total", total); - data.put("items", addressVoList); - - return ResponseUtil.ok(data); + return ResponseUtil.okList(addressList); } } diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminAdminController.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminAdminController.java index d41881e0246e836946d87685b64aee785b4691de..a8bc1b2b4d7fc1259125f729b034c1895ec6bd57 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminAdminController.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminAdminController.java @@ -2,8 +2,11 @@ package org.linlinjava.litemall.admin.web; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.shiro.SecurityUtils; import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.apache.shiro.subject.Subject; import org.linlinjava.litemall.admin.annotation.RequiresPermissionsDesc; +import org.linlinjava.litemall.admin.service.LogHelper; import org.linlinjava.litemall.core.util.RegexUtil; import org.linlinjava.litemall.core.util.ResponseUtil; import org.linlinjava.litemall.core.util.bcrypt.BCryptPasswordEncoder; @@ -17,9 +20,7 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.validation.constraints.NotNull; -import java.util.HashMap; import java.util.List; -import java.util.Map; import static org.linlinjava.litemall.admin.util.AdminResponseCode.*; @@ -31,9 +32,11 @@ public class AdminAdminController { @Autowired private LitemallAdminService adminService; + @Autowired + private LogHelper logHelper; @RequiresPermissions("admin:admin:list") - @RequiresPermissionsDesc(menu={"系统管理" , "管理员管理"}, button="查询") + @RequiresPermissionsDesc(menu = {"系统管理", "管理员管理"}, button = "查询") @GetMapping("/list") public Object list(String username, @RequestParam(defaultValue = "1") Integer page, @@ -41,12 +44,7 @@ public class AdminAdminController { @Sort @RequestParam(defaultValue = "add_time") String sort, @Order @RequestParam(defaultValue = "desc") String order) { List adminList = adminService.querySelective(username, page, limit, sort, order); - int total = adminService.countSelective(username, page, limit, sort, order); - Map data = new HashMap<>(); - data.put("total", total); - data.put("items", adminList); - - return ResponseUtil.ok(data); + return ResponseUtil.okList(adminList); } private Object validate(LitemallAdmin admin) { @@ -65,7 +63,7 @@ public class AdminAdminController { } @RequiresPermissions("admin:admin:create") - @RequiresPermissionsDesc(menu={"系统管理" , "管理员管理"}, button="添加") + @RequiresPermissionsDesc(menu = {"系统管理", "管理员管理"}, button = "添加") @PostMapping("/create") public Object create(@RequestBody LitemallAdmin admin) { Object error = validate(admin); @@ -84,11 +82,12 @@ public class AdminAdminController { String encodedPassword = encoder.encode(rawPassword); admin.setPassword(encodedPassword); adminService.add(admin); + logHelper.logAuthSucceed("添加管理员", username); return ResponseUtil.ok(admin); } @RequiresPermissions("admin:admin:read") - @RequiresPermissionsDesc(menu={"系统管理" , "管理员管理"}, button="详情") + @RequiresPermissionsDesc(menu = {"系统管理", "管理员管理"}, button = "详情") @GetMapping("/read") public Object read(@NotNull Integer id) { LitemallAdmin admin = adminService.findById(id); @@ -96,7 +95,7 @@ public class AdminAdminController { } @RequiresPermissions("admin:admin:update") - @RequiresPermissionsDesc(menu={"系统管理" , "管理员管理"}, button="编辑") + @RequiresPermissionsDesc(menu = {"系统管理", "管理员管理"}, button = "编辑") @PostMapping("/update") public Object update(@RequestBody LitemallAdmin admin) { Object error = validate(admin); @@ -109,20 +108,19 @@ public class AdminAdminController { return ResponseUtil.badArgument(); } - String rawPassword = admin.getPassword(); - BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); - String encodedPassword = encoder.encode(rawPassword); - admin.setPassword(encodedPassword); + // 不允许管理员通过编辑接口修改密码 + admin.setPassword(null); if (adminService.updateById(admin) == 0) { return ResponseUtil.updatedDataFailed(); } + logHelper.logAuthSucceed("编辑管理员", admin.getUsername()); return ResponseUtil.ok(admin); } @RequiresPermissions("admin:admin:delete") - @RequiresPermissionsDesc(menu={"系统管理" , "管理员管理"}, button="删除") + @RequiresPermissionsDesc(menu = {"系统管理", "管理员管理"}, button = "删除") @PostMapping("/delete") public Object delete(@RequestBody LitemallAdmin admin) { Integer anotherAdminId = admin.getId(); @@ -130,7 +128,15 @@ public class AdminAdminController { return ResponseUtil.badArgument(); } + // 管理员不能删除自身账号 + Subject currentUser = SecurityUtils.getSubject(); + LitemallAdmin currentAdmin = (LitemallAdmin) currentUser.getPrincipal(); + if (currentAdmin.getId().equals(anotherAdminId)) { + return ResponseUtil.fail(ADMIN_DELETE_NOT_ALLOWED, "管理员不能删除自己账号"); + } + adminService.deleteById(anotherAdminId); + logHelper.logAuthSucceed("删除管理员", admin.getUsername()); return ResponseUtil.ok(); } } diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminAftersaleController.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminAftersaleController.java new file mode 100644 index 0000000000000000000000000000000000000000..8694625fc63d5a2e03508586d2a0465b27600e8d --- /dev/null +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminAftersaleController.java @@ -0,0 +1,232 @@ +package org.linlinjava.litemall.admin.web; + +import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest; +import com.github.binarywang.wxpay.bean.result.WxPayRefundResult; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.github.binarywang.wxpay.service.WxPayService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.linlinjava.litemall.admin.annotation.RequiresPermissionsDesc; +import org.linlinjava.litemall.admin.service.LogHelper; +import org.linlinjava.litemall.admin.util.AdminResponseCode; +import org.linlinjava.litemall.core.notify.NotifyService; +import org.linlinjava.litemall.core.notify.NotifyType; +import org.linlinjava.litemall.core.util.JacksonUtil; +import org.linlinjava.litemall.core.util.ResponseUtil; +import org.linlinjava.litemall.core.validator.Order; +import org.linlinjava.litemall.core.validator.Sort; +import org.linlinjava.litemall.db.domain.LitemallAftersale; +import org.linlinjava.litemall.db.domain.LitemallGoodsProduct; +import org.linlinjava.litemall.db.domain.LitemallOrder; +import org.linlinjava.litemall.db.domain.LitemallOrderGoods; +import org.linlinjava.litemall.db.service.*; +import org.linlinjava.litemall.db.util.AftersaleConstant; +import org.linlinjava.litemall.db.util.OrderUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +import static org.linlinjava.litemall.admin.util.AdminResponseCode.ORDER_REFUND_FAILED; + +@RestController +@RequestMapping("/admin/aftersale") +@Validated +public class AdminAftersaleController { + private final Log logger = LogFactory.getLog(AdminAftersaleController.class); + + @Autowired + private LitemallAftersaleService aftersaleService; + @Autowired + private LitemallOrderService orderService; + @Autowired + private LitemallOrderGoodsService orderGoodsService; + @Autowired + private LitemallGoodsProductService goodsProductService; + @Autowired + private LogHelper logHelper; + @Autowired + private WxPayService wxPayService; + @Autowired + private NotifyService notifyService; + + @RequiresPermissions("admin:aftersale:list") + @RequiresPermissionsDesc(menu = {"商城管理", "售后管理"}, button = "查询") + @GetMapping("/list") + public Object list(Integer orderId, String aftersaleSn, Short status, + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer limit, + @Sort @RequestParam(defaultValue = "add_time") String sort, + @Order @RequestParam(defaultValue = "desc") String order) { + List aftersaleList = aftersaleService.querySelective(orderId, aftersaleSn, status, page, limit, sort, order); + return ResponseUtil.okList(aftersaleList); + } + + @RequiresPermissions("admin:aftersale:recept") + @RequiresPermissionsDesc(menu = {"商城管理", "售后管理"}, button = "审核通过") + @PostMapping("/recept") + public Object recept(@RequestBody LitemallAftersale aftersale) { + Integer id = aftersale.getId(); + LitemallAftersale aftersaleOne = aftersaleService.findById(id); + if(aftersaleOne == null){ + return ResponseUtil.fail(AdminResponseCode.AFTERSALE_NOT_ALLOWED, "售后不存在"); + } + Short status = aftersaleOne.getStatus(); + if(!status.equals(AftersaleConstant.STATUS_REQUEST)){ + return ResponseUtil.fail(AdminResponseCode.AFTERSALE_NOT_ALLOWED, "售后不能进行审核通过操作"); + } + aftersaleOne.setStatus(AftersaleConstant.STATUS_RECEPT); + aftersaleOne.setHandleTime(LocalDateTime.now()); + aftersaleService.updateById(aftersaleOne); + + // 订单也要更新售后状态 + orderService.updateAftersaleStatus(aftersaleOne.getOrderId(), AftersaleConstant.STATUS_RECEPT); + return ResponseUtil.ok(); + } + + @RequiresPermissions("admin:aftersale:batch-recept") + @RequiresPermissionsDesc(menu = {"商城管理", "售后管理"}, button = "批量通过") + @PostMapping("/batch-recept") + public Object batchRecept(@RequestBody String body) { + List ids = JacksonUtil.parseIntegerList(body, "ids"); + // NOTE + // 批量操作中,如果一部分数据项失败,应该如何处理 + // 这里采用忽略失败,继续处理其他项。 + // 当然开发者可以采取其他处理方式,具体情况具体分析,例如利用事务回滚所有操作然后返回用户失败信息 + for(Integer id : ids) { + LitemallAftersale aftersale = aftersaleService.findById(id); + if(aftersale == null){ + continue; + } + Short status = aftersale.getStatus(); + if(!status.equals(AftersaleConstant.STATUS_REQUEST)){ + continue; + } + aftersale.setStatus(AftersaleConstant.STATUS_RECEPT); + aftersale.setHandleTime(LocalDateTime.now()); + aftersaleService.updateById(aftersale); + + // 订单也要更新售后状态 + orderService.updateAftersaleStatus(aftersale.getOrderId(), AftersaleConstant.STATUS_RECEPT); + } + return ResponseUtil.ok(); + } + + @RequiresPermissions("admin:aftersale:reject") + @RequiresPermissionsDesc(menu = {"商城管理", "售后管理"}, button = "审核拒绝") + @PostMapping("/reject") + public Object reject(@RequestBody LitemallAftersale aftersale) { + Integer id = aftersale.getId(); + LitemallAftersale aftersaleOne = aftersaleService.findById(id); + if(aftersaleOne == null){ + return ResponseUtil.badArgumentValue(); + } + Short status = aftersaleOne.getStatus(); + if(!status.equals(AftersaleConstant.STATUS_REQUEST)){ + return ResponseUtil.fail(AdminResponseCode.AFTERSALE_NOT_ALLOWED, "售后不能进行审核拒绝操作"); + } + aftersaleOne.setStatus(AftersaleConstant.STATUS_REJECT); + aftersaleOne.setHandleTime(LocalDateTime.now()); + aftersaleService.updateById(aftersaleOne); + + // 订单也要更新售后状态 + orderService.updateAftersaleStatus(aftersaleOne.getOrderId(), AftersaleConstant.STATUS_REJECT); + return ResponseUtil.ok(); + } + + @RequiresPermissions("admin:aftersale:batch-reject") + @RequiresPermissionsDesc(menu = {"商城管理", "售后管理"}, button = "批量拒绝") + @PostMapping("/batch-reject") + public Object batchReject(@RequestBody String body) { + List ids = JacksonUtil.parseIntegerList(body, "ids"); + for(Integer id : ids) { + LitemallAftersale aftersale = aftersaleService.findById(id); + if(aftersale == null){ + continue; + } + Short status = aftersale.getStatus(); + if(!status.equals(AftersaleConstant.STATUS_REQUEST)){ + continue; + } + aftersale.setStatus(AftersaleConstant.STATUS_REJECT); + aftersale.setHandleTime(LocalDateTime.now()); + aftersaleService.updateById(aftersale); + + // 订单也要更新售后状态 + orderService.updateAftersaleStatus(aftersale.getOrderId(), AftersaleConstant.STATUS_REJECT); + } + return ResponseUtil.ok(); + } + + @RequiresPermissions("admin:aftersale:refund") + @RequiresPermissionsDesc(menu = {"商城管理", "售后管理"}, button = "退款") + @PostMapping("/refund") + public Object refund(@RequestBody LitemallAftersale aftersale) { + Integer id = aftersale.getId(); + LitemallAftersale aftersaleOne = aftersaleService.findById(id); + if(aftersaleOne == null){ + return ResponseUtil.badArgumentValue(); + } + if(!aftersaleOne.getStatus().equals(AftersaleConstant.STATUS_RECEPT)){ + return ResponseUtil.fail(AdminResponseCode.AFTERSALE_NOT_ALLOWED, "售后不能进行退款操作"); + } + Integer orderId = aftersaleOne.getOrderId(); + LitemallOrder order = orderService.findById(orderId); + + // 微信退款 + WxPayRefundRequest wxPayRefundRequest = new WxPayRefundRequest(); + wxPayRefundRequest.setOutTradeNo(order.getOrderSn()); + wxPayRefundRequest.setOutRefundNo("refund_" + order.getOrderSn()); + // 元转成分 + Integer totalFee = aftersaleOne.getAmount().multiply(new BigDecimal(100)).intValue(); + wxPayRefundRequest.setTotalFee(totalFee); + wxPayRefundRequest.setRefundFee(totalFee); + + WxPayRefundResult wxPayRefundResult; + try { + wxPayRefundResult = wxPayService.refund(wxPayRefundRequest); + } catch (WxPayException e) { + logger.error(e.getMessage(), e); + return ResponseUtil.fail(ORDER_REFUND_FAILED, "订单退款失败"); + } + if (!wxPayRefundResult.getReturnCode().equals("SUCCESS")) { + logger.warn("refund fail: " + wxPayRefundResult.getReturnMsg()); + return ResponseUtil.fail(ORDER_REFUND_FAILED, "订单退款失败"); + } + if (!wxPayRefundResult.getResultCode().equals("SUCCESS")) { + logger.warn("refund fail: " + wxPayRefundResult.getReturnMsg()); + return ResponseUtil.fail(ORDER_REFUND_FAILED, "订单退款失败"); + } + + aftersaleOne.setStatus(AftersaleConstant.STATUS_REFUND); + aftersaleOne.setHandleTime(LocalDateTime.now()); + aftersaleService.updateById(aftersaleOne); + + orderService.updateAftersaleStatus(orderId, AftersaleConstant.STATUS_REFUND); + + // NOTE + // 如果是“退货退款”类型的售后,这里退款说明用户的货已经退回,则需要商品货品数量增加 + // 开发者也可以删除一下代码,在其他地方增加商品货品入库操作 + if(aftersale.getType().equals(AftersaleConstant.TYPE_GOODS_REQUIRED)) { + List orderGoodsList = orderGoodsService.queryByOid(orderId); + for (LitemallOrderGoods orderGoods : orderGoodsList) { + Integer productId = orderGoods.getProductId(); + Short number = orderGoods.getNumber(); + goodsProductService.addStock(productId, number); + } + } + + // 发送短信通知,这里采用异步发送 + // 退款成功通知用户, 例如“您申请的订单退款 [ 单号:{1} ] 已成功,请耐心等待到账。” + // TODO 注意订单号只发后6位 + notifyService.notifySmsTemplate(order.getMobile(), NotifyType.REFUND, + new String[]{order.getOrderSn().substring(8, 14)}); + + logHelper.logOrderSucceed("退款", "订单编号 " + order.getOrderSn() + " 售后编号 " + aftersale.getAftersaleSn()); + return ResponseUtil.ok(); + } +} diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminAuthController.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminAuthController.java index 666f24981d18c7834d8885f66cff1b1378b88536..59becab5442224598f68ce7a7a02723bb062d425 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminAuthController.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminAuthController.java @@ -9,6 +9,10 @@ import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.annotation.RequiresAuthentication; import org.apache.shiro.subject.Subject; +import org.linlinjava.litemall.admin.service.LogHelper; +import org.linlinjava.litemall.admin.util.Permission; +import org.linlinjava.litemall.admin.util.PermissionUtil; +import org.linlinjava.litemall.core.util.IpUtil; import org.linlinjava.litemall.core.util.JacksonUtil; import org.linlinjava.litemall.core.util.ResponseUtil; import org.linlinjava.litemall.db.domain.LitemallAdmin; @@ -16,10 +20,13 @@ import org.linlinjava.litemall.db.service.LitemallAdminService; import org.linlinjava.litemall.db.service.LitemallPermissionService; import org.linlinjava.litemall.db.service.LitemallRoleService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; import org.springframework.util.StringUtils; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import javax.servlet.http.HttpServletRequest; +import java.time.LocalDateTime; import java.util.*; import static org.linlinjava.litemall.admin.util.AdminResponseCode.ADMIN_INVALID_ACCOUNT; @@ -36,12 +43,14 @@ public class AdminAuthController { private LitemallRoleService roleService; @Autowired private LitemallPermissionService permissionService; + @Autowired + private LogHelper logHelper; /* * { username : value, password : value } */ @PostMapping("/login") - public Object login(@RequestBody String body) { + public Object login(@RequestBody String body, HttpServletRequest request) { String username = JacksonUtil.parseString(body, "username"); String password = JacksonUtil.parseString(body, "password"); @@ -53,14 +62,34 @@ public class AdminAuthController { try { currentUser.login(new UsernamePasswordToken(username, password)); } catch (UnknownAccountException uae) { + logHelper.logAuthFail("登录", "用户帐号或密码不正确"); return ResponseUtil.fail(ADMIN_INVALID_ACCOUNT, "用户帐号或密码不正确"); } catch (LockedAccountException lae) { + logHelper.logAuthFail("登录", "用户帐号已锁定不可用"); return ResponseUtil.fail(ADMIN_INVALID_ACCOUNT, "用户帐号已锁定不可用"); } catch (AuthenticationException ae) { - return ResponseUtil.fail(ADMIN_INVALID_ACCOUNT, ae.getMessage()); + logHelper.logAuthFail("登录", "认证失败"); + return ResponseUtil.fail(ADMIN_INVALID_ACCOUNT, "认证失败"); } - return ResponseUtil.ok(currentUser.getSession().getId()); + + currentUser = SecurityUtils.getSubject(); + LitemallAdmin admin = (LitemallAdmin) currentUser.getPrincipal(); + admin.setLastLoginIp(IpUtil.getIpAddr(request)); + admin.setLastLoginTime(LocalDateTime.now()); + adminService.updateById(admin); + + logHelper.logAuthSucceed("登录"); + + // userInfo + Map adminInfo = new HashMap(); + adminInfo.put("nickName", admin.getUsername()); + adminInfo.put("avatar", admin.getAvatar()); + + Map result = new HashMap(); + result.put("token", currentUser.getSession().getId()); + result.put("adminInfo", adminInfo); + return ResponseUtil.ok(result); } /* @@ -68,8 +97,10 @@ public class AdminAuthController { */ @RequiresAuthentication @PostMapping("/logout") - public Object login() { + public Object logout() { Subject currentUser = SecurityUtils.getSubject(); + + logHelper.logAuthSucceed("退出"); currentUser.logout(); return ResponseUtil.ok(); } @@ -89,10 +120,44 @@ public class AdminAuthController { Set roles = roleService.queryByIds(roleIds); Set permissions = permissionService.queryByRoleIds(roleIds); data.put("roles", roles); - data.put("perms", permissions); + // NOTE + // 这里需要转换perms结构,因为对于前端而已API形式的权限更容易理解 + data.put("perms", toApi(permissions)); return ResponseUtil.ok(data); } + @Autowired + private ApplicationContext context; + private HashMap systemPermissionsMap = null; + + private Collection toApi(Set permissions) { + if (systemPermissionsMap == null) { + systemPermissionsMap = new HashMap<>(); + final String basicPackage = "org.linlinjava.litemall.admin"; + List systemPermissions = PermissionUtil.listPermission(context, basicPackage); + for (Permission permission : systemPermissions) { + String perm = permission.getRequiresPermissions().value()[0]; + String api = permission.getApi(); + systemPermissionsMap.put(perm, api); + } + } + + Collection apis = new HashSet<>(); + for (String perm : permissions) { + String api = systemPermissionsMap.get(perm); + apis.add(api); + + if (perm.equals("*")) { + apis.clear(); + apis.add("*"); + return apis; + // return systemPermissionsMap.values(); + + } + } + return apis; + } + @GetMapping("/401") public Object page401() { return ResponseUtil.unlogin(); diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminBrandController.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminBrandController.java index 937fc8e731e8af6aa798f5e00a8049afb997095f..d8cff59160c795b7873850f5702bccd1c28edc72 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminBrandController.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminBrandController.java @@ -16,9 +16,7 @@ import org.springframework.web.bind.annotation.*; import javax.validation.constraints.NotNull; import java.math.BigDecimal; -import java.util.HashMap; import java.util.List; -import java.util.Map; @RestController @RequestMapping("/admin/brand") @@ -30,7 +28,7 @@ public class AdminBrandController { private LitemallBrandService brandService; @RequiresPermissions("admin:brand:list") - @RequiresPermissionsDesc(menu={"商场管理" , "品牌管理"}, button="查询") + @RequiresPermissionsDesc(menu = {"商场管理", "品牌管理"}, button = "查询") @GetMapping("/list") public Object list(String id, String name, @RequestParam(defaultValue = "1") Integer page, @@ -38,12 +36,7 @@ public class AdminBrandController { @Sort @RequestParam(defaultValue = "add_time") String sort, @Order @RequestParam(defaultValue = "desc") String order) { List brandList = brandService.querySelective(id, name, page, limit, sort, order); - int total = brandService.countSelective(id, name, page, limit, sort, order); - Map data = new HashMap<>(); - data.put("total", total); - data.put("items", brandList); - - return ResponseUtil.ok(data); + return ResponseUtil.okList(brandList); } private Object validate(LitemallBrand brand) { @@ -65,7 +58,7 @@ public class AdminBrandController { } @RequiresPermissions("admin:brand:create") - @RequiresPermissionsDesc(menu={"商场管理" , "品牌管理"}, button="添加") + @RequiresPermissionsDesc(menu = {"商场管理", "品牌管理"}, button = "添加") @PostMapping("/create") public Object create(@RequestBody LitemallBrand brand) { Object error = validate(brand); @@ -77,7 +70,7 @@ public class AdminBrandController { } @RequiresPermissions("admin:brand:read") - @RequiresPermissionsDesc(menu={"商场管理" , "品牌管理"}, button="详情") + @RequiresPermissionsDesc(menu = {"商场管理", "品牌管理"}, button = "详情") @GetMapping("/read") public Object read(@NotNull Integer id) { LitemallBrand brand = brandService.findById(id); @@ -85,7 +78,7 @@ public class AdminBrandController { } @RequiresPermissions("admin:brand:update") - @RequiresPermissionsDesc(menu={"商场管理" , "品牌管理"}, button="编辑") + @RequiresPermissionsDesc(menu = {"商场管理", "品牌管理"}, button = "编辑") @PostMapping("/update") public Object update(@RequestBody LitemallBrand brand) { Object error = validate(brand); @@ -99,7 +92,7 @@ public class AdminBrandController { } @RequiresPermissions("admin:brand:delete") - @RequiresPermissionsDesc(menu={"商场管理" , "品牌管理"}, button="删除") + @RequiresPermissionsDesc(menu = {"商场管理", "品牌管理"}, button = "删除") @PostMapping("/delete") public Object delete(@RequestBody LitemallBrand brand) { Integer id = brand.getId(); diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminCategoryController.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminCategoryController.java index 4a0374f07d8a2f83dae39fe128ac7ef66cc07bc5..f22cb5c2cff73e1f1ca1012b04121e49bcde7b26 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminCategoryController.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminCategoryController.java @@ -4,9 +4,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.linlinjava.litemall.admin.annotation.RequiresPermissionsDesc; +import org.linlinjava.litemall.admin.vo.CategoryVo; import org.linlinjava.litemall.core.util.ResponseUtil; -import org.linlinjava.litemall.core.validator.Order; -import org.linlinjava.litemall.core.validator.Sort; import org.linlinjava.litemall.db.domain.LitemallCategory; import org.linlinjava.litemall.db.service.LitemallCategoryService; import org.springframework.beans.factory.annotation.Autowired; @@ -30,20 +29,42 @@ public class AdminCategoryController { private LitemallCategoryService categoryService; @RequiresPermissions("admin:category:list") - @RequiresPermissionsDesc(menu={"商场管理" , "类目管理"}, button="查询") + @RequiresPermissionsDesc(menu = {"商场管理", "类目管理"}, button = "查询") @GetMapping("/list") - public Object list(String id, String name, - @RequestParam(defaultValue = "1") Integer page, - @RequestParam(defaultValue = "10") Integer limit, - @Sort @RequestParam(defaultValue = "add_time") String sort, - @Order @RequestParam(defaultValue = "desc") String order) { - List collectList = categoryService.querySelective(id, name, page, limit, sort, order); - int total = categoryService.countSelective(id, name, page, limit, sort, order); - Map data = new HashMap<>(); - data.put("total", total); - data.put("items", collectList); - - return ResponseUtil.ok(data); + public Object list() { + List categoryVoList = new ArrayList<>(); + + List categoryList = categoryService.queryByPid(0); + for (LitemallCategory category : categoryList) { + CategoryVo categoryVO = new CategoryVo(); + categoryVO.setId(category.getId()); + categoryVO.setDesc(category.getDesc()); + categoryVO.setIconUrl(category.getIconUrl()); + categoryVO.setPicUrl(category.getPicUrl()); + categoryVO.setKeywords(category.getKeywords()); + categoryVO.setName(category.getName()); + categoryVO.setLevel(category.getLevel()); + + List children = new ArrayList<>(); + List subCategoryList = categoryService.queryByPid(category.getId()); + for (LitemallCategory subCategory : subCategoryList) { + CategoryVo subCategoryVo = new CategoryVo(); + subCategoryVo.setId(subCategory.getId()); + subCategoryVo.setDesc(subCategory.getDesc()); + subCategoryVo.setIconUrl(subCategory.getIconUrl()); + subCategoryVo.setPicUrl(subCategory.getPicUrl()); + subCategoryVo.setKeywords(subCategory.getKeywords()); + subCategoryVo.setName(subCategory.getName()); + subCategoryVo.setLevel(subCategory.getLevel()); + + children.add(subCategoryVo); + } + + categoryVO.setChildren(children); + categoryVoList.add(categoryVO); + } + + return ResponseUtil.okList(categoryVoList); } private Object validate(LitemallCategory category) { @@ -69,7 +90,7 @@ public class AdminCategoryController { } @RequiresPermissions("admin:category:create") - @RequiresPermissionsDesc(menu={"商场管理" , "类目管理"}, button="添加") + @RequiresPermissionsDesc(menu = {"商场管理", "类目管理"}, button = "添加") @PostMapping("/create") public Object create(@RequestBody LitemallCategory category) { Object error = validate(category); @@ -81,7 +102,7 @@ public class AdminCategoryController { } @RequiresPermissions("admin:category:read") - @RequiresPermissionsDesc(menu={"商场管理" , "类目管理"}, button="详情") + @RequiresPermissionsDesc(menu = {"商场管理", "类目管理"}, button = "详情") @GetMapping("/read") public Object read(@NotNull Integer id) { LitemallCategory category = categoryService.findById(id); @@ -89,7 +110,7 @@ public class AdminCategoryController { } @RequiresPermissions("admin:category:update") - @RequiresPermissionsDesc(menu={"商场管理" , "类目管理"}, button="编辑") + @RequiresPermissionsDesc(menu = {"商场管理", "类目管理"}, button = "编辑") @PostMapping("/update") public Object update(@RequestBody LitemallCategory category) { Object error = validate(category); @@ -104,7 +125,7 @@ public class AdminCategoryController { } @RequiresPermissions("admin:category:delete") - @RequiresPermissionsDesc(menu={"商场管理" , "类目管理"}, button="删除") + @RequiresPermissionsDesc(menu = {"商场管理", "类目管理"}, button = "删除") @PostMapping("/delete") public Object delete(@RequestBody LitemallCategory category) { Integer id = category.getId(); @@ -127,6 +148,6 @@ public class AdminCategoryController { d.put("label", category.getName()); data.add(d); } - return ResponseUtil.ok(data); + return ResponseUtil.okList(data); } } diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminCollectController.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminCollectController.java index 6bd69cd0140a51241c73ad702050155a1f42d958..3b8d4c6575b418fd45e85acaab2016359211c826 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminCollectController.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminCollectController.java @@ -16,9 +16,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import java.util.HashMap; import java.util.List; -import java.util.Map; @RestController @RequestMapping("/admin/collect") @@ -31,7 +29,7 @@ public class AdminCollectController { @RequiresPermissions("admin:collect:list") - @RequiresPermissionsDesc(menu={"用户管理" , "用户收藏"}, button="查询") + @RequiresPermissionsDesc(menu = {"用户管理", "用户收藏"}, button = "查询") @GetMapping("/list") public Object list(String userId, String valueId, @RequestParam(defaultValue = "1") Integer page, @@ -39,11 +37,6 @@ public class AdminCollectController { @Sort @RequestParam(defaultValue = "add_time") String sort, @Order @RequestParam(defaultValue = "desc") String order) { List collectList = collectService.querySelective(userId, valueId, page, limit, sort, order); - int total = collectService.countSelective(userId, valueId, page, limit, sort, order); - Map data = new HashMap<>(); - data.put("total", total); - data.put("items", collectList); - - return ResponseUtil.ok(data); + return ResponseUtil.okList(collectList); } } diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminCommentController.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminCommentController.java index 92f5f956d6ecd9cc2bc021fa10ca871cb1f765c6..3cc3ae3066b4f15bbe6174658093aa6b56fb80c2 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminCommentController.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminCommentController.java @@ -13,9 +13,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.util.HashMap; import java.util.List; -import java.util.Map; @RestController @RequestMapping("/admin/comment") @@ -27,24 +25,19 @@ public class AdminCommentController { private LitemallCommentService commentService; @RequiresPermissions("admin:comment:list") - @RequiresPermissionsDesc(menu={"商品管理" , "评论管理"}, button="查询") + @RequiresPermissionsDesc(menu = {"商品管理", "评论管理"}, button = "查询") @GetMapping("/list") public Object list(String userId, String valueId, @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer limit, @Sort @RequestParam(defaultValue = "add_time") String sort, @Order @RequestParam(defaultValue = "desc") String order) { - List brandList = commentService.querySelective(userId, valueId, page, limit, sort, order); - int total = commentService.countSelective(userId, valueId, page, limit, sort, order); - Map data = new HashMap<>(); - data.put("total", total); - data.put("items", brandList); - - return ResponseUtil.ok(data); + List commentList = commentService.querySelective(userId, valueId, page, limit, sort, order); + return ResponseUtil.okList(commentList); } @RequiresPermissions("admin:comment:delete") - @RequiresPermissionsDesc(menu={"商品管理" , "评论管理"}, button="删除") + @RequiresPermissionsDesc(menu = {"商品管理", "评论管理"}, button = "删除") @PostMapping("/delete") public Object delete(@RequestBody LitemallComment comment) { Integer id = comment.getId(); diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminConfigController.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminConfigController.java new file mode 100644 index 0000000000000000000000000000000000000000..f9232014d981c006feed4a9317481909036f5035 --- /dev/null +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminConfigController.java @@ -0,0 +1,96 @@ +package org.linlinjava.litemall.admin.web; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.linlinjava.litemall.admin.annotation.RequiresPermissionsDesc; +import org.linlinjava.litemall.core.system.SystemConfig; +import org.linlinjava.litemall.core.util.JacksonUtil; +import org.linlinjava.litemall.core.util.ResponseUtil; +import org.linlinjava.litemall.db.service.LitemallSystemConfigService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +@RestController +@RequestMapping("/admin/config") +@Validated +public class AdminConfigController { + private final Log logger = LogFactory.getLog(AdminConfigController.class); + + @Autowired + private LitemallSystemConfigService systemConfigService; + + @RequiresPermissions("admin:config:mall:list") + @RequiresPermissionsDesc(menu = {"配置管理", "商场配置"}, button = "详情") + @GetMapping("/mall") + public Object listMall() { + Map data = systemConfigService.listMail(); + return ResponseUtil.ok(data); + } + + @RequiresPermissions("admin:config:mall:updateConfigs") + @RequiresPermissionsDesc(menu = {"配置管理", "商场配置"}, button = "编辑") + @PostMapping("/mall") + public Object updateMall(@RequestBody String body) { + Map data = JacksonUtil.toMap(body); + systemConfigService.updateConfig(data); + SystemConfig.updateConfigs(data); + return ResponseUtil.ok(); + } + + @RequiresPermissions("admin:config:express:list") + @RequiresPermissionsDesc(menu = {"配置管理", "运费配置"}, button = "详情") + @GetMapping("/express") + public Object listExpress() { + Map data = systemConfigService.listExpress(); + return ResponseUtil.ok(data); + } + + @RequiresPermissions("admin:config:express:updateConfigs") + @RequiresPermissionsDesc(menu = {"配置管理", "运费配置"}, button = "编辑") + @PostMapping("/express") + public Object updateExpress(@RequestBody String body) { + Map data = JacksonUtil.toMap(body); + systemConfigService.updateConfig(data); + SystemConfig.updateConfigs(data); + return ResponseUtil.ok(); + } + + @RequiresPermissions("admin:config:order:list") + @RequiresPermissionsDesc(menu = {"配置管理", "订单配置"}, button = "详情") + @GetMapping("/order") + public Object lisOrder() { + Map data = systemConfigService.listOrder(); + return ResponseUtil.ok(data); + } + + @RequiresPermissions("admin:config:order:updateConfigs") + @RequiresPermissionsDesc(menu = {"配置管理", "订单配置"}, button = "编辑") + @PostMapping("/order") + public Object updateOrder(@RequestBody String body) { + Map data = JacksonUtil.toMap(body); + systemConfigService.updateConfig(data); + return ResponseUtil.ok(); + } + + @RequiresPermissions("admin:config:wx:list") + @RequiresPermissionsDesc(menu = {"配置管理", "小程序配置"}, button = "详情") + @GetMapping("/wx") + public Object listWx() { + Map data = systemConfigService.listWx(); + return ResponseUtil.ok(data); + } + + @RequiresPermissions("admin:config:wx:updateConfigs") + @RequiresPermissionsDesc(menu = {"配置管理", "小程序配置"}, button = "编辑") + @PostMapping("/wx") + public Object updateWx(@RequestBody String body) { + Map data = JacksonUtil.toMap(body); + systemConfigService.updateConfig(data); + SystemConfig.updateConfigs(data); + return ResponseUtil.ok(); + } +} diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminCouponController.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminCouponController.java index d38bef200fa6108d4ba2bd1a387f22450f940003..ea7cf706c3aec103132748f53032568e42611b48 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminCouponController.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminCouponController.java @@ -18,9 +18,7 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.validation.constraints.NotNull; -import java.util.HashMap; import java.util.List; -import java.util.Map; @RestController @RequestMapping("/admin/coupon") @@ -34,7 +32,7 @@ public class AdminCouponController { private LitemallCouponUserService couponUserService; @RequiresPermissions("admin:coupon:list") - @RequiresPermissionsDesc(menu={"推广管理" , "优惠券管理"}, button="查询") + @RequiresPermissionsDesc(menu = {"推广管理", "优惠券管理"}, button = "查询") @GetMapping("/list") public Object list(String name, Short type, Short status, @RequestParam(defaultValue = "1") Integer page, @@ -42,41 +40,32 @@ public class AdminCouponController { @Sort @RequestParam(defaultValue = "add_time") String sort, @Order @RequestParam(defaultValue = "desc") String order) { List couponList = couponService.querySelective(name, type, status, page, limit, sort, order); - int total = couponService.countSelective(name, type, status, page, limit, sort, order); - Map data = new HashMap<>(); - data.put("total", total); - data.put("items", couponList); - - return ResponseUtil.ok(data); + return ResponseUtil.okList(couponList); } - @RequiresPermissions("admin:coupon:list") - @RequiresPermissionsDesc(menu={"推广管理" , "优惠券管理"}, button="查询") + @RequiresPermissions("admin:coupon:listuser") + @RequiresPermissionsDesc(menu = {"推广管理", "优惠券管理"}, button = "查询用户") @GetMapping("/listuser") public Object listuser(Integer userId, Integer couponId, Short status, - @RequestParam(defaultValue = "1") Integer page, - @RequestParam(defaultValue = "10") Integer limit, - @Sort @RequestParam(defaultValue = "add_time") String sort, - @Order @RequestParam(defaultValue = "desc") String order) { - List couponList = couponUserService.queryList(userId, couponId, status, page, limit, sort, order); - int total = couponUserService.countList(userId, couponId, status, page, limit, sort, order); - Map data = new HashMap<>(); - data.put("total", total); - data.put("items", couponList); - - return ResponseUtil.ok(data); + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer limit, + @Sort @RequestParam(defaultValue = "add_time") String sort, + @Order @RequestParam(defaultValue = "desc") String order) { + List couponList = couponUserService.queryList(userId, couponId, status, page, + limit, sort, order); + return ResponseUtil.okList(couponList); } private Object validate(LitemallCoupon coupon) { String name = coupon.getName(); - if(StringUtils.isEmpty(name)){ + if (StringUtils.isEmpty(name)) { return ResponseUtil.badArgument(); } return null; } @RequiresPermissions("admin:coupon:create") - @RequiresPermissionsDesc(menu={"推广管理" , "优惠券管理"}, button="添加") + @RequiresPermissionsDesc(menu = {"推广管理", "优惠券管理"}, button = "添加") @PostMapping("/create") public Object create(@RequestBody LitemallCoupon coupon) { Object error = validate(coupon); @@ -85,7 +74,7 @@ public class AdminCouponController { } // 如果是兑换码类型,则这里需要生存一个兑换码 - if (coupon.getType().equals(CouponConstant.TYPE_CODE)){ + if (coupon.getType().equals(CouponConstant.TYPE_CODE)) { String code = couponService.generateCode(); coupon.setCode(code); } @@ -95,7 +84,7 @@ public class AdminCouponController { } @RequiresPermissions("admin:coupon:read") - @RequiresPermissionsDesc(menu={"推广管理" , "优惠券管理"}, button="详情") + @RequiresPermissionsDesc(menu = {"推广管理", "优惠券管理"}, button = "详情") @GetMapping("/read") public Object read(@NotNull Integer id) { LitemallCoupon coupon = couponService.findById(id); @@ -103,7 +92,7 @@ public class AdminCouponController { } @RequiresPermissions("admin:coupon:update") - @RequiresPermissionsDesc(menu={"推广管理" , "优惠券管理"}, button="编辑") + @RequiresPermissionsDesc(menu = {"推广管理", "优惠券管理"}, button = "编辑") @PostMapping("/update") public Object update(@RequestBody LitemallCoupon coupon) { Object error = validate(coupon); @@ -117,7 +106,7 @@ public class AdminCouponController { } @RequiresPermissions("admin:coupon:delete") - @RequiresPermissionsDesc(menu={"推广管理" , "优惠券管理"}, button="删除") + @RequiresPermissionsDesc(menu = {"推广管理", "优惠券管理"}, button = "删除") @PostMapping("/delete") public Object delete(@RequestBody LitemallCoupon coupon) { couponService.deleteById(coupon.getId()); diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminFeedbackController.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminFeedbackController.java index febbf26994fb93e29c56dab1195cb3f6c27ef9e5..603d2b7477c7c2921f33310597cab79db28c5b7e 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminFeedbackController.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminFeedbackController.java @@ -16,9 +16,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import java.util.HashMap; import java.util.List; -import java.util.Map; /** * @author Yogeek @@ -34,19 +32,15 @@ public class AdminFeedbackController { private LitemallFeedbackService feedbackService; @RequiresPermissions("admin:feedback:list") - @RequiresPermissionsDesc(menu={"用户管理" , "意见反馈"}, button="查询") + @RequiresPermissionsDesc(menu = {"用户管理", "意见反馈"}, button = "查询") @GetMapping("/list") public Object list(Integer userId, String username, @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer limit, @Sort @RequestParam(defaultValue = "add_time") String sort, @Order @RequestParam(defaultValue = "desc") String order) { - List feedbackList = feedbackService.querySelective(userId, username, page, limit, sort, order); - int total = feedbackService.countSelective(userId, username, page, limit, sort, order); - Map data = new HashMap<>(); - data.put("total", total); - data.put("items", feedbackList); - - return ResponseUtil.ok(data); + List feedbackList = feedbackService.querySelective(userId, username, page, limit, sort, + order); + return ResponseUtil.okList(feedbackList); } } diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminFootprintController.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminFootprintController.java index 50f55765f3b89e70ce6c1baa742da17565b5a180..d940c32a7da04d2076fccd7acbec0a8e1aa0d6a8 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminFootprintController.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminFootprintController.java @@ -16,9 +16,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import java.util.HashMap; import java.util.List; -import java.util.Map; @RestController @RequestMapping("/admin/footprint") @@ -30,19 +28,15 @@ public class AdminFootprintController { private LitemallFootprintService footprintService; @RequiresPermissions("admin:footprint:list") - @RequiresPermissionsDesc(menu={"用户管理" , "用户足迹"}, button="查询") + @RequiresPermissionsDesc(menu = {"用户管理", "用户足迹"}, button = "查询") @GetMapping("/list") public Object list(String userId, String goodsId, @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer limit, @Sort @RequestParam(defaultValue = "add_time") String sort, @Order @RequestParam(defaultValue = "desc") String order) { - List footprintList = footprintService.querySelective(userId, goodsId, page, limit, sort, order); - int total = footprintService.countSelective(userId, goodsId, page, limit, sort, order); - Map data = new HashMap<>(); - data.put("total", total); - data.put("items", footprintList); - - return ResponseUtil.ok(data); + List footprintList = footprintService.querySelective(userId, goodsId, page, limit, sort, + order); + return ResponseUtil.okList(footprintList); } } diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminGoodsController.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminGoodsController.java index a4416716c9b03a6a8a79aa58c88a5796c14f295e..c0fe6de024976342c2f90da2d45c637cf7decb94 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminGoodsController.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminGoodsController.java @@ -4,32 +4,16 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.linlinjava.litemall.admin.annotation.RequiresPermissionsDesc; -import org.linlinjava.litemall.admin.dao.GoodsAllinone; -import org.linlinjava.litemall.admin.util.CatVo; -import org.linlinjava.litemall.core.qcode.QCodeService; -import org.linlinjava.litemall.core.util.ResponseUtil; +import org.linlinjava.litemall.admin.dto.GoodsAllinone; +import org.linlinjava.litemall.admin.service.AdminGoodsService; import org.linlinjava.litemall.core.validator.Order; import org.linlinjava.litemall.core.validator.Sort; -import org.linlinjava.litemall.db.domain.*; -import org.linlinjava.litemall.db.service.*; +import org.linlinjava.litemall.db.domain.LitemallGoods; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionDefinition; -import org.springframework.transaction.TransactionStatus; -import org.springframework.transaction.support.DefaultTransactionDefinition; -import org.springframework.util.StringUtils; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.validation.constraints.NotNull; -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.linlinjava.litemall.admin.util.AdminResponseCode.GOODS_NAME_EXIST; -import static org.linlinjava.litemall.admin.util.AdminResponseCode.GOODS_UPDATE_NOT_ALLOWED; @RestController @RequestMapping("/admin/goods") @@ -38,366 +22,87 @@ public class AdminGoodsController { private final Log logger = LogFactory.getLog(AdminGoodsController.class); @Autowired - private PlatformTransactionManager txManager; - - @Autowired - private LitemallGoodsService goodsService; - @Autowired - private LitemallGoodsSpecificationService specificationService; - @Autowired - private LitemallGoodsAttributeService attributeService; - @Autowired - private LitemallGoodsProductService productService; - @Autowired - private LitemallCategoryService categoryService; - @Autowired - private LitemallBrandService brandService; - @Autowired - private LitemallCartService cartService; - @Autowired - private LitemallOrderGoodsService orderGoodsService; - - @Autowired - private QCodeService qCodeService; + private AdminGoodsService adminGoodsService; + /** + * 查询商品 + * + * @param goodsId + * @param goodsSn + * @param name + * @param page + * @param limit + * @param sort + * @param order + * @return + */ @RequiresPermissions("admin:goods:list") - @RequiresPermissionsDesc(menu={"商品管理" , "商品列表"}, button="查询") + @RequiresPermissionsDesc(menu = {"商品管理", "商品管理"}, button = "查询") @GetMapping("/list") - public Object list(String goodsSn, String name, + public Object list(Integer goodsId, String goodsSn, String name, @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer limit, @Sort @RequestParam(defaultValue = "add_time") String sort, @Order @RequestParam(defaultValue = "desc") String order) { - List goodsList = goodsService.querySelective(goodsSn, name, page, limit, sort, order); - int total = goodsService.countSelective(goodsSn, name, page, limit, sort, order); - Map data = new HashMap<>(); - data.put("total", total); - data.put("items", goodsList); - - return ResponseUtil.ok(data); + return adminGoodsService.list(goodsId, goodsSn, name, page, limit, sort, order); } - private Object validate(GoodsAllinone goodsAllinone) { - LitemallGoods goods = goodsAllinone.getGoods(); - String name = goods.getName(); - if (StringUtils.isEmpty(name)) { - return ResponseUtil.badArgument(); - } - String goodsSn = goods.getGoodsSn(); - if (StringUtils.isEmpty(goodsSn)) { - return ResponseUtil.badArgument(); - } - // 品牌商可以不设置,如果设置则需要验证品牌商存在 - Integer brandId = goods.getBrandId(); - if (brandId != null && brandId != 0) { - if (brandService.findById(brandId) == null) { - return ResponseUtil.badArgumentValue(); - } - } - // 分类可以不设置,如果设置则需要验证分类存在 - Integer categoryId = goods.getCategoryId(); - if (categoryId != null && categoryId != 0) { - if (categoryService.findById(categoryId) == null) { - return ResponseUtil.badArgumentValue(); - } - } - - LitemallGoodsAttribute[] attributes = goodsAllinone.getAttributes(); - for (LitemallGoodsAttribute attribute : attributes) { - String attr = attribute.getAttribute(); - if (StringUtils.isEmpty(attr)) { - return ResponseUtil.badArgument(); - } - String value = attribute.getValue(); - if (StringUtils.isEmpty(value)) { - return ResponseUtil.badArgument(); - } - } - - LitemallGoodsSpecification[] specifications = goodsAllinone.getSpecifications(); - for (LitemallGoodsSpecification specification : specifications) { - String spec = specification.getSpecification(); - if (StringUtils.isEmpty(spec)) { - return ResponseUtil.badArgument(); - } - String value = specification.getValue(); - if (StringUtils.isEmpty(value)) { - return ResponseUtil.badArgument(); - } - } - - LitemallGoodsProduct[] products = goodsAllinone.getProducts(); - for (LitemallGoodsProduct product : products) { - Integer number = product.getNumber(); - if (number == null || number < 0) { - return ResponseUtil.badArgument(); - } - - BigDecimal price = product.getPrice(); - if (price == null) { - return ResponseUtil.badArgument(); - } - - String[] productSpecifications = product.getSpecifications(); - if (productSpecifications.length == 0) { - return ResponseUtil.badArgument(); - } - } - - return null; + @GetMapping("/catAndBrand") + public Object list2() { + return adminGoodsService.list2(); } /** * 编辑商品 - *

- * TODO - * 目前商品修改的逻辑是 - * 1. 更新litemall_goods表 - * 2. 逻辑删除litemall_goods_specification、litemall_goods_attribute、litemall_goods_product - * 3. 添加litemall_goods_specification、litemall_goods_attribute、litemall_goods_product * - * 这里商品三个表的数据采用删除再添加的策略是因为 - * 商品编辑页面,支持管理员添加删除商品规格、添加删除商品属性,因此这里仅仅更新是不可能的, - * 只能删除三个表旧的数据,然后添加新的数据。 - * 但是这里又会引入新的问题,就是存在订单商品货品ID指向了失效的商品货品表。 - * 因此这里会拒绝管理员编辑商品,如果订单或购物车中存在商品。 - * 所以这里可能需要重新设计。 + * @param goodsAllinone + * @return */ @RequiresPermissions("admin:goods:update") - @RequiresPermissionsDesc(menu={"商品管理" , "商品列表"}, button="编辑") + @RequiresPermissionsDesc(menu = {"商品管理", "商品管理"}, button = "编辑") @PostMapping("/update") public Object update(@RequestBody GoodsAllinone goodsAllinone) { - Object error = validate(goodsAllinone); - if (error != null) { - return error; - } - - LitemallGoods goods = goodsAllinone.getGoods(); - LitemallGoodsAttribute[] attributes = goodsAllinone.getAttributes(); - LitemallGoodsSpecification[] specifications = goodsAllinone.getSpecifications(); - LitemallGoodsProduct[] products = goodsAllinone.getProducts(); - - Integer id = goods.getId(); - // 检查是否存在购物车商品或者订单商品 - // 如果存在则拒绝修改商品。 - if(orderGoodsService.checkExist(id)){ - return ResponseUtil.fail(GOODS_UPDATE_NOT_ALLOWED, "商品已经在订单中,不能修改"); - } - if(cartService.checkExist(id)){ - return ResponseUtil.fail(GOODS_UPDATE_NOT_ALLOWED, "商品已经在购物车中,不能修改"); - } - - // 开启事务管理 - DefaultTransactionDefinition def = new DefaultTransactionDefinition(); - def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); - TransactionStatus status = txManager.getTransaction(def); - try { - - //将生成的分享图片地址写入数据库 - String url = qCodeService.createGoodShareImage(goods.getId().toString(), goods.getPicUrl(), goods.getName()); - goods.setShareUrl(url); - - // 商品基本信息表litemall_goods - if (goodsService.updateById(goods) == 0) { - throw new Exception("更新数据失败"); - } - - Integer gid = goods.getId(); - specificationService.deleteByGid(gid); - attributeService.deleteByGid(gid); - productService.deleteByGid(gid); - - // 商品规格表litemall_goods_specification - for (LitemallGoodsSpecification specification : specifications) { - specification.setGoodsId(goods.getId()); - specificationService.add(specification); - } - - // 商品参数表litemall_goods_attribute - for (LitemallGoodsAttribute attribute : attributes) { - attribute.setGoodsId(goods.getId()); - attributeService.add(attribute); - } - - // 商品货品表litemall_product - for (LitemallGoodsProduct product : products) { - product.setGoodsId(goods.getId()); - productService.add(product); - } - } catch (Exception ex) { - txManager.rollback(status); - logger.error("系统内部错误", ex); - return ResponseUtil.fail(); - } - txManager.commit(status); - - qCodeService.createGoodShareImage(goods.getId().toString(), goods.getPicUrl(), goods.getName()); - - return ResponseUtil.ok(); + return adminGoodsService.update(goodsAllinone); } + /** + * 删除商品 + * + * @param goods + * @return + */ @RequiresPermissions("admin:goods:delete") - @RequiresPermissionsDesc(menu={"商品管理" , "商品列表"}, button="删除") + @RequiresPermissionsDesc(menu = {"商品管理", "商品管理"}, button = "删除") @PostMapping("/delete") public Object delete(@RequestBody LitemallGoods goods) { - Integer id = goods.getId(); - if (id == null) { - return ResponseUtil.badArgument(); - } - - // 开启事务管理 - DefaultTransactionDefinition def = new DefaultTransactionDefinition(); - def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); - TransactionStatus status = txManager.getTransaction(def); - try { - - Integer gid = goods.getId(); - goodsService.deleteById(gid); - specificationService.deleteByGid(gid); - attributeService.deleteByGid(gid); - productService.deleteByGid(gid); - } catch (Exception ex) { - txManager.rollback(status); - logger.error("系统内部错误", ex); - return ResponseUtil.fail(); - } - txManager.commit(status); - return ResponseUtil.ok(); + return adminGoodsService.delete(goods); } + /** + * 添加商品 + * + * @param goodsAllinone + * @return + */ @RequiresPermissions("admin:goods:create") - @RequiresPermissionsDesc(menu={"商品管理" , "商品上架"}, button="上架") + @RequiresPermissionsDesc(menu = {"商品管理", "商品管理"}, button = "上架") @PostMapping("/create") public Object create(@RequestBody GoodsAllinone goodsAllinone) { - Object error = validate(goodsAllinone); - if (error != null) { - return error; - } - - LitemallGoods goods = goodsAllinone.getGoods(); - LitemallGoodsAttribute[] attributes = goodsAllinone.getAttributes(); - LitemallGoodsSpecification[] specifications = goodsAllinone.getSpecifications(); - LitemallGoodsProduct[] products = goodsAllinone.getProducts(); - - String name = goods.getName(); - if (goodsService.checkExistByName(name)) { - return ResponseUtil.fail(GOODS_NAME_EXIST, "商品名已经存在"); - } - - // 开启事务管理 - DefaultTransactionDefinition def = new DefaultTransactionDefinition(); - def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); - TransactionStatus status = txManager.getTransaction(def); - try { - - // 商品基本信息表litemall_goods - goodsService.add(goods); - - //将生成的分享图片地址写入数据库 - String url = qCodeService.createGoodShareImage(goods.getId().toString(), goods.getPicUrl(), goods.getName()); - if (!StringUtils.isEmpty(url)) { - goods.setShareUrl(url); - if (goodsService.updateById(goods) == 0) { - throw new Exception("更新数据失败"); - } - } - - // 商品规格表litemall_goods_specification - for (LitemallGoodsSpecification specification : specifications) { - specification.setGoodsId(goods.getId()); - specificationService.add(specification); - } - - // 商品参数表litemall_goods_attribute - for (LitemallGoodsAttribute attribute : attributes) { - attribute.setGoodsId(goods.getId()); - attributeService.add(attribute); - } - - // 商品货品表litemall_product - for (LitemallGoodsProduct product : products) { - product.setGoodsId(goods.getId()); - productService.add(product); - } - } catch (Exception ex) { - txManager.rollback(status); - logger.error("系统内部错误", ex); - return ResponseUtil.fail(); - } - txManager.commit(status); - - return ResponseUtil.ok(); - } - - @RequiresPermissions("admin:goods:list") - @RequiresPermissionsDesc(menu={"商品管理" , "商品列表"}, button="查询") - @GetMapping("/catAndBrand") - public Object list2() { - // http://element-cn.eleme.io/#/zh-CN/component/cascader - // 管理员设置“所属分类” - List l1CatList = categoryService.queryL1(); - List categoryList = new ArrayList<>(l1CatList.size()); - - for (LitemallCategory l1 : l1CatList) { - CatVo l1CatVo = new CatVo(); - l1CatVo.setValue(l1.getId()); - l1CatVo.setLabel(l1.getName()); - - List l2CatList = categoryService.queryByPid(l1.getId()); - List children = new ArrayList<>(l2CatList.size()); - for (LitemallCategory l2 : l2CatList) { - CatVo l2CatVo = new CatVo(); - l2CatVo.setValue(l2.getId()); - l2CatVo.setLabel(l2.getName()); - children.add(l2CatVo); - } - l1CatVo.setChildren(children); - - categoryList.add(l1CatVo); - } - - // http://element-cn.eleme.io/#/zh-CN/component/select - // 管理员设置“所属品牌商” - List list = brandService.all(); - List> brandList = new ArrayList<>(l1CatList.size()); - for (LitemallBrand brand : list) { - Map b = new HashMap<>(2); - b.put("value", brand.getId()); - b.put("label", brand.getName()); - brandList.add(b); - } - - Map data = new HashMap<>(); - data.put("categoryList", categoryList); - data.put("brandList", brandList); - return ResponseUtil.ok(data); + return adminGoodsService.create(goodsAllinone); } + /** + * 商品详情 + * + * @param id + * @return + */ @RequiresPermissions("admin:goods:read") - @RequiresPermissionsDesc(menu={"商品管理" , "商品列表"}, button="编辑") + @RequiresPermissionsDesc(menu = {"商品管理", "商品管理"}, button = "详情") @GetMapping("/detail") public Object detail(@NotNull Integer id) { - LitemallGoods goods = goodsService.findById(id); - List products = productService.queryByGid(id); - List specifications = specificationService.queryByGid(id); - List attributes = attributeService.queryByGid(id); - - Integer categoryId = goods.getCategoryId(); - LitemallCategory category = categoryService.findById(categoryId); - Integer[] categoryIds = new Integer[]{}; - if (category != null) { - Integer parentCategoryId = category.getPid(); - categoryIds = new Integer[]{parentCategoryId, categoryId}; - } - - Map data = new HashMap<>(); - data.put("goods", goods); - data.put("specifications", specifications); - data.put("products", products); - data.put("attributes", attributes); - data.put("categoryIds", categoryIds); + return adminGoodsService.detail(id); - return ResponseUtil.ok(data); } } diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminGrouponController.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminGrouponController.java index db81996efd96c6ad1c2fbe4974449116a1afb3f4..9fb0066918a4fd9df1daa2111800dcbfcda71b6b 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminGrouponController.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminGrouponController.java @@ -4,6 +4,9 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.linlinjava.litemall.admin.annotation.RequiresPermissionsDesc; +import org.linlinjava.litemall.admin.task.GrouponRuleExpiredTask; +import org.linlinjava.litemall.admin.util.AdminResponseCode; +import org.linlinjava.litemall.core.task.TaskService; import org.linlinjava.litemall.core.util.ResponseUtil; import org.linlinjava.litemall.core.validator.Order; import org.linlinjava.litemall.core.validator.Sort; @@ -13,12 +16,15 @@ import org.linlinjava.litemall.db.domain.LitemallGrouponRules; import org.linlinjava.litemall.db.service.LitemallGoodsService; import org.linlinjava.litemall.db.service.LitemallGrouponRulesService; import org.linlinjava.litemall.db.service.LitemallGrouponService; +import org.linlinjava.litemall.db.util.GrouponConstant; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.math.BigDecimal; +import java.time.Duration; import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -36,46 +42,43 @@ public class AdminGrouponController { private LitemallGoodsService goodsService; @Autowired private LitemallGrouponService grouponService; + @Autowired + private TaskService taskService; @RequiresPermissions("admin:groupon:read") - @RequiresPermissionsDesc(menu={"推广管理" , "团购管理"}, button="查询") + @RequiresPermissionsDesc(menu = {"推广管理", "团购管理"}, button = "详情") @GetMapping("/listRecord") - public Object listRecord(String grouponId, + public Object listRecord(String grouponRuleId, @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer limit, @Sort @RequestParam(defaultValue = "add_time") String sort, @Order @RequestParam(defaultValue = "desc") String order) { - List grouponList = grouponService.querySelective(grouponId, page, limit, sort, order); - int total = grouponService.countSelective(grouponId, page, limit, sort, order); + List grouponList = grouponService.querySelective(grouponRuleId, page, limit, sort, order); - List> records = new ArrayList<>(); + List> groupons = new ArrayList<>(); for (LitemallGroupon groupon : grouponList) { try { - Map RecordData = new HashMap<>(); + Map recordData = new HashMap<>(); List subGrouponList = grouponService.queryJoinRecord(groupon.getId()); - LitemallGrouponRules rules = rulesService.queryById(groupon.getRulesId()); + LitemallGrouponRules rules = rulesService.findById(groupon.getRulesId()); LitemallGoods goods = goodsService.findById(rules.getGoodsId()); - RecordData.put("groupon", groupon); - RecordData.put("subGroupons", subGrouponList); - RecordData.put("rules", rules); - RecordData.put("goods", goods); + recordData.put("groupon", groupon); + recordData.put("subGroupons", subGrouponList); + recordData.put("rules", rules); + recordData.put("goods", goods); - records.add(RecordData); + groupons.add(recordData); } catch (Exception e) { - e.printStackTrace(); + logger.error(e.getMessage(), e); } } - Map data = new HashMap<>(); - data.put("total", total); - data.put("items", records); - - return ResponseUtil.ok(data); + return ResponseUtil.okList(groupons, grouponList); } @RequiresPermissions("admin:groupon:list") - @RequiresPermissionsDesc(menu={"推广管理" , "团购管理"}, button="查询") + @RequiresPermissionsDesc(menu = {"推广管理", "团购管理"}, button = "查询") @GetMapping("/list") public Object list(String goodsId, @RequestParam(defaultValue = "1") Integer page, @@ -83,12 +86,7 @@ public class AdminGrouponController { @Sort @RequestParam(defaultValue = "add_time") String sort, @Order @RequestParam(defaultValue = "desc") String order) { List rulesList = rulesService.querySelective(goodsId, page, limit, sort, order); - int total = rulesService.countSelective(goodsId, page, limit, sort, order); - Map data = new HashMap<>(); - data.put("total", total); - data.put("items", rulesList); - - return ResponseUtil.ok(data); + return ResponseUtil.okList(rulesList); } private Object validate(LitemallGrouponRules grouponRules) { @@ -113,7 +111,7 @@ public class AdminGrouponController { } @RequiresPermissions("admin:groupon:update") - @RequiresPermissionsDesc(menu={"推广管理" , "团购管理"}, button="编辑") + @RequiresPermissionsDesc(menu = {"推广管理", "团购管理"}, button = "编辑") @PostMapping("/update") public Object update(@RequestBody LitemallGrouponRules grouponRules) { Object error = validate(grouponRules); @@ -121,6 +119,14 @@ public class AdminGrouponController { return error; } + LitemallGrouponRules rules = rulesService.findById(grouponRules.getId()); + if(rules == null){ + return ResponseUtil.badArgumentValue(); + } + if(!rules.getStatus().equals(GrouponConstant.RULE_STATUS_ON)){ + return ResponseUtil.fail(AdminResponseCode.GROUPON_GOODS_OFFLINE, "团购已经下线"); + } + Integer goodsId = grouponRules.getGoodsId(); LitemallGoods goods = goodsService.findById(goodsId); if (goods == null) { @@ -138,7 +144,7 @@ public class AdminGrouponController { } @RequiresPermissions("admin:groupon:create") - @RequiresPermissionsDesc(menu={"推广管理" , "团购管理"}, button="添加") + @RequiresPermissionsDesc(menu = {"推广管理", "团购管理"}, button = "添加") @PostMapping("/create") public Object create(@RequestBody LitemallGrouponRules grouponRules) { Object error = validate(grouponRules); @@ -149,19 +155,28 @@ public class AdminGrouponController { Integer goodsId = grouponRules.getGoodsId(); LitemallGoods goods = goodsService.findById(goodsId); if (goods == null) { - return ResponseUtil.badArgumentValue(); + return ResponseUtil.fail(AdminResponseCode.GROUPON_GOODS_UNKNOWN, "团购商品不存在"); + } + if(rulesService.countByGoodsId(goodsId) > 0){ + return ResponseUtil.fail(AdminResponseCode.GROUPON_GOODS_EXISTED, "团购商品已经存在"); } grouponRules.setGoodsName(goods.getName()); grouponRules.setPicUrl(goods.getPicUrl()); - + grouponRules.setStatus(GrouponConstant.RULE_STATUS_ON); rulesService.createRules(grouponRules); + LocalDateTime now = LocalDateTime.now(); + LocalDateTime expire = grouponRules.getExpireTime(); + long delay = ChronoUnit.MILLIS.between(now, expire); + // 团购过期任务 + taskService.addTask(new GrouponRuleExpiredTask(grouponRules.getId(), delay)); + return ResponseUtil.ok(grouponRules); } @RequiresPermissions("admin:groupon:delete") - @RequiresPermissionsDesc(menu={"推广管理" , "团购管理"}, button="删除") + @RequiresPermissionsDesc(menu = {"推广管理", "团购管理"}, button = "删除") @PostMapping("/delete") public Object delete(@RequestBody LitemallGrouponRules grouponRules) { Integer id = grouponRules.getId(); diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminHistoryController.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminHistoryController.java index 940cf740551289c784e34a64f17bb68db307a338..29106d3723b3a553d2fea9d505cf1b78dda9d894 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminHistoryController.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminHistoryController.java @@ -15,9 +15,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import java.util.HashMap; import java.util.List; -import java.util.Map; @RestController @RequestMapping("/admin/history") @@ -28,19 +26,15 @@ public class AdminHistoryController { private LitemallSearchHistoryService searchHistoryService; @RequiresPermissions("admin:history:list") - @RequiresPermissionsDesc(menu={"用户管理" , "搜索历史"}, button="查询") + @RequiresPermissionsDesc(menu = {"用户管理", "搜索历史"}, button = "查询") @GetMapping("/list") public Object list(String userId, String keyword, @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer limit, @Sort @RequestParam(defaultValue = "add_time") String sort, @Order @RequestParam(defaultValue = "desc") String order) { - List footprintList = searchHistoryService.querySelective(userId, keyword, page, limit, sort, order); - int total = searchHistoryService.countSelective(userId, keyword, page, limit, sort, order); - Map data = new HashMap<>(); - data.put("total", total); - data.put("items", footprintList); - - return ResponseUtil.ok(data); + List historyList = searchHistoryService.querySelective(userId, keyword, page, limit, + sort, order); + return ResponseUtil.okList(historyList); } } diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminIndexController.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminIndexController.java index 863411cdd2ecf0c9612ec6aece125ff1d12e2aef..2bd7398a7434ef32b183052aa3ee018fe1e0e537 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminIndexController.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminIndexController.java @@ -5,6 +5,8 @@ import org.apache.commons.logging.LogFactory; import org.apache.shiro.authz.annotation.*; import org.linlinjava.litemall.admin.annotation.RequiresPermissionsDesc; import org.linlinjava.litemall.core.util.ResponseUtil; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -49,15 +51,15 @@ public class AdminIndexController { } @RequiresPermissions("index:permission:read") - @RequiresPermissionsDesc(menu={"其他" , "权限测试"}, button="权限读") - @RequestMapping("/read") + @RequiresPermissionsDesc(menu = {"其他", "权限测试"}, button = "权限读") + @GetMapping("/read") public Object read() { return ResponseUtil.ok("hello world, this is admin service"); } @RequiresPermissions("index:permission:write") - @RequiresPermissionsDesc(menu={"其他" , "权限测试"}, button="权限写") - @RequestMapping("/write") + @RequiresPermissionsDesc(menu = {"其他", "权限测试"}, button = "权限写") + @PostMapping("/write") public Object write() { return ResponseUtil.ok("hello world, this is admin service"); } diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminIssueController.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminIssueController.java index 54708b0b38026120758adb1d951b8b89f2d65bae..a71c37624cd1c177ccbea82901df907d1a3b7c03 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminIssueController.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminIssueController.java @@ -15,9 +15,7 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.validation.constraints.NotNull; -import java.util.HashMap; import java.util.List; -import java.util.Map; @RestController @RequestMapping("/admin/issue") @@ -29,7 +27,7 @@ public class AdminIssueController { private LitemallIssueService issueService; @RequiresPermissions("admin:issue:list") - @RequiresPermissionsDesc(menu={"商城管理" , "通用问题"}, button="查询") + @RequiresPermissionsDesc(menu = {"商场管理", "通用问题"}, button = "查询") @GetMapping("/list") public Object list(String question, @RequestParam(defaultValue = "1") Integer page, @@ -37,12 +35,7 @@ public class AdminIssueController { @Sort @RequestParam(defaultValue = "add_time") String sort, @Order @RequestParam(defaultValue = "desc") String order) { List issueList = issueService.querySelective(question, page, limit, sort, order); - int total = issueService.countSelective(question, page, limit, sort, order); - Map data = new HashMap<>(); - data.put("total", total); - data.put("items", issueList); - - return ResponseUtil.ok(data); + return ResponseUtil.okList(issueList); } private Object validate(LitemallIssue issue) { @@ -58,7 +51,7 @@ public class AdminIssueController { } @RequiresPermissions("admin:issue:create") - @RequiresPermissionsDesc(menu={"商城管理" , "通用问题"}, button="添加") + @RequiresPermissionsDesc(menu = {"商场管理", "通用问题"}, button = "添加") @PostMapping("/create") public Object create(@RequestBody LitemallIssue issue) { Object error = validate(issue); @@ -77,7 +70,7 @@ public class AdminIssueController { } @RequiresPermissions("admin:issue:update") - @RequiresPermissionsDesc(menu={"商城管理" , "通用问题"}, button="编辑") + @RequiresPermissionsDesc(menu = {"商场管理", "通用问题"}, button = "编辑") @PostMapping("/update") public Object update(@RequestBody LitemallIssue issue) { Object error = validate(issue); @@ -92,7 +85,7 @@ public class AdminIssueController { } @RequiresPermissions("admin:issue:delete") - @RequiresPermissionsDesc(menu={"商城管理" , "通用问题"}, button="删除") + @RequiresPermissionsDesc(menu = {"商场管理", "通用问题"}, button = "删除") @PostMapping("/delete") public Object delete(@RequestBody LitemallIssue issue) { Integer id = issue.getId(); diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminKeywordController.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminKeywordController.java index eca48483c39f212ac120b58c439c659155e83478..6643c864a1cf428ecbc71d6fccfcf38870ae05da 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminKeywordController.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminKeywordController.java @@ -15,9 +15,7 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.validation.constraints.NotNull; -import java.util.HashMap; import java.util.List; -import java.util.Map; @RestController @RequestMapping("/admin/keyword") @@ -29,20 +27,15 @@ public class AdminKeywordController { private LitemallKeywordService keywordService; @RequiresPermissions("admin:keyword:list") - @RequiresPermissionsDesc(menu={"商城管理" , "关键词"}, button="查询") + @RequiresPermissionsDesc(menu = {"商场管理", "关键词"}, button = "查询") @GetMapping("/list") public Object list(String keyword, String url, @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer limit, @Sort @RequestParam(defaultValue = "add_time") String sort, @Order @RequestParam(defaultValue = "desc") String order) { - List brandList = keywordService.querySelective(keyword, url, page, limit, sort, order); - int total = keywordService.countSelective(keyword, url, page, limit, sort, order); - Map data = new HashMap<>(); - data.put("total", total); - data.put("items", brandList); - - return ResponseUtil.ok(data); + List keywordList = keywordService.querySelective(keyword, url, page, limit, sort, order); + return ResponseUtil.okList(keywordList); } private Object validate(LitemallKeyword keywords) { @@ -50,49 +43,45 @@ public class AdminKeywordController { if (StringUtils.isEmpty(keyword)) { return ResponseUtil.badArgument(); } - String url = keywords.getUrl(); - if (StringUtils.isEmpty(url)) { - return ResponseUtil.badArgument(); - } return null; } @RequiresPermissions("admin:keyword:create") - @RequiresPermissionsDesc(menu={"商城管理" , "关键词"}, button="添加") + @RequiresPermissionsDesc(menu = {"商场管理", "关键词"}, button = "添加") @PostMapping("/create") - public Object create(@RequestBody LitemallKeyword keywords) { - Object error = validate(keywords); + public Object create(@RequestBody LitemallKeyword keyword) { + Object error = validate(keyword); if (error != null) { return error; } - keywordService.add(keywords); - return ResponseUtil.ok(keywords); + keywordService.add(keyword); + return ResponseUtil.ok(keyword); } @RequiresPermissions("admin:keyword:read") - @RequiresPermissionsDesc(menu={"商城管理" , "关键词"}, button="详情") + @RequiresPermissionsDesc(menu = {"商场管理", "关键词"}, button = "详情") @GetMapping("/read") public Object read(@NotNull Integer id) { - LitemallKeyword brand = keywordService.findById(id); - return ResponseUtil.ok(brand); + LitemallKeyword keyword = keywordService.findById(id); + return ResponseUtil.ok(keyword); } @RequiresPermissions("admin:keyword:update") - @RequiresPermissionsDesc(menu={"商城管理" , "关键词"}, button="编辑") + @RequiresPermissionsDesc(menu = {"商场管理", "关键词"}, button = "编辑") @PostMapping("/update") - public Object update(@RequestBody LitemallKeyword keywords) { - Object error = validate(keywords); + public Object update(@RequestBody LitemallKeyword keyword) { + Object error = validate(keyword); if (error != null) { return error; } - if (keywordService.updateById(keywords) == 0) { + if (keywordService.updateById(keyword) == 0) { return ResponseUtil.updatedDataFailed(); } - return ResponseUtil.ok(keywords); + return ResponseUtil.ok(keyword); } @RequiresPermissions("admin:keyword:delete") - @RequiresPermissionsDesc(menu={"商城管理" , "关键词"}, button="删除") + @RequiresPermissionsDesc(menu = {"商场管理", "关键词"}, button = "删除") @PostMapping("/delete") public Object delete(@RequestBody LitemallKeyword keyword) { Integer id = keyword.getId(); diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminLogController.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminLogController.java new file mode 100644 index 0000000000000000000000000000000000000000..0edb31cdfdd78190cbad938c2c591967fa0ca9bb --- /dev/null +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminLogController.java @@ -0,0 +1,41 @@ +package org.linlinjava.litemall.admin.web; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.linlinjava.litemall.admin.annotation.RequiresPermissionsDesc; +import org.linlinjava.litemall.core.util.ResponseUtil; +import org.linlinjava.litemall.core.validator.Order; +import org.linlinjava.litemall.core.validator.Sort; +import org.linlinjava.litemall.db.domain.LitemallLog; +import org.linlinjava.litemall.db.service.LitemallLogService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping("/admin/log") +@Validated +public class AdminLogController { + private final Log logger = LogFactory.getLog(AdminLogController.class); + + @Autowired + private LitemallLogService logService; + + @RequiresPermissions("admin:log:list") + @RequiresPermissionsDesc(menu = {"系统管理", "操作日志"}, button = "查询") + @GetMapping("/list") + public Object list(String name, + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer limit, + @Sort @RequestParam(defaultValue = "add_time") String sort, + @Order @RequestParam(defaultValue = "desc") String order) { + List logList = logService.querySelective(name, page, limit, sort, order); + return ResponseUtil.okList(logList); + } +} diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminNoticeController.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminNoticeController.java new file mode 100644 index 0000000000000000000000000000000000000000..04531ef5d4f904c95e51a097576015db666a5eab --- /dev/null +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminNoticeController.java @@ -0,0 +1,153 @@ +package org.linlinjava.litemall.admin.web; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.apache.shiro.subject.Subject; +import org.linlinjava.litemall.admin.annotation.RequiresPermissionsDesc; +import org.linlinjava.litemall.core.util.JacksonUtil; +import org.linlinjava.litemall.core.util.ResponseUtil; +import org.linlinjava.litemall.core.validator.Order; +import org.linlinjava.litemall.core.validator.Sort; +import org.linlinjava.litemall.db.domain.*; +import org.linlinjava.litemall.db.service.LitemallAdminService; +import org.linlinjava.litemall.db.service.LitemallNoticeAdminService; +import org.linlinjava.litemall.db.service.LitemallNoticeService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.StringUtils; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.validation.constraints.NotNull; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.linlinjava.litemall.admin.util.AdminResponseCode.NOTICE_UPDATE_NOT_ALLOWED; + +@RestController +@RequestMapping("/admin/notice") +@Validated +public class AdminNoticeController { + private final Log logger = LogFactory.getLog(AdminNoticeController.class); + + @Autowired + private LitemallNoticeService noticeService; + @Autowired + private LitemallAdminService adminService; + @Autowired + private LitemallNoticeAdminService noticeAdminService; + + @RequiresPermissions("admin:notice:list") + @RequiresPermissionsDesc(menu = {"系统管理", "通知管理"}, button = "查询") + @GetMapping("/list") + public Object list(String title, String content, + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer limit, + @Sort @RequestParam(defaultValue = "add_time") String sort, + @Order @RequestParam(defaultValue = "desc") String order) { + List noticeList = noticeService.querySelective(title, content, page, limit, sort, order); + return ResponseUtil.okList(noticeList); + } + + private Object validate(LitemallNotice notice) { + String title = notice.getTitle(); + if (StringUtils.isEmpty(title)) { + return ResponseUtil.badArgument(); + } + return null; + } + + private Integer getAdminId(){ + Subject currentUser = SecurityUtils.getSubject(); + LitemallAdmin admin = (LitemallAdmin) currentUser.getPrincipal(); + return admin.getId(); + } + + @RequiresPermissions("admin:notice:create") + @RequiresPermissionsDesc(menu = {"推广管理", "通知管理"}, button = "添加") + @PostMapping("/create") + public Object create(@RequestBody LitemallNotice notice) { + Object error = validate(notice); + if (error != null) { + return error; + } + // 1. 添加通知记录 + notice.setAdminId(getAdminId()); + noticeService.add(notice); + // 2. 添加管理员通知记录 + List adminList = adminService.all(); + LitemallNoticeAdmin noticeAdmin = new LitemallNoticeAdmin(); + noticeAdmin.setNoticeId(notice.getId()); + noticeAdmin.setNoticeTitle(notice.getTitle()); + for(LitemallAdmin admin : adminList){ + noticeAdmin.setAdminId(admin.getId()); + noticeAdminService.add(noticeAdmin); + } + return ResponseUtil.ok(notice); + } + + @RequiresPermissions("admin:notice:read") + @RequiresPermissionsDesc(menu = {"推广管理", "通知管理"}, button = "详情") + @GetMapping("/read") + public Object read(@NotNull Integer id) { + LitemallNotice notice = noticeService.findById(id); + List noticeAdminList = noticeAdminService.queryByNoticeId(id); + Map data = new HashMap<>(2); + data.put("notice", notice); + data.put("noticeAdminList", noticeAdminList); + return ResponseUtil.ok(data); + } + + @RequiresPermissions("admin:notice:update") + @RequiresPermissionsDesc(menu = {"推广管理", "通知管理"}, button = "编辑") + @PostMapping("/update") + public Object update(@RequestBody LitemallNotice notice) { + Object error = validate(notice); + if (error != null) { + return error; + } + LitemallNotice originalNotice = noticeService.findById(notice.getId()); + if (originalNotice == null) { + return ResponseUtil.badArgument(); + } + // 如果通知已经有人阅读过,则不支持编辑 + if(noticeAdminService.countReadByNoticeId(notice.getId()) > 0){ + return ResponseUtil.fail(NOTICE_UPDATE_NOT_ALLOWED, "通知已被阅读,不能重新编辑"); + } + // 1. 更新通知记录 + notice.setAdminId(getAdminId()); + noticeService.updateById(notice); + // 2. 更新管理员通知记录 + if(!originalNotice.getTitle().equals(notice.getTitle())){ + LitemallNoticeAdmin noticeAdmin = new LitemallNoticeAdmin(); + noticeAdmin.setNoticeTitle(notice.getTitle()); + noticeAdminService.updateByNoticeId(noticeAdmin, notice.getId()); + } + return ResponseUtil.ok(notice); + } + + @RequiresPermissions("admin:notice:delete") + @RequiresPermissionsDesc(menu = {"推广管理", "通知管理"}, button = "删除") + @PostMapping("/delete") + public Object delete(@RequestBody LitemallNotice notice) { + // 1. 删除通知管理员记录 + noticeAdminService.deleteByNoticeId(notice.getId()); + // 2. 删除通知记录 + noticeService.deleteById(notice.getId()); + return ResponseUtil.ok(); + } + + @RequiresPermissions("admin:notice:batch-delete") + @RequiresPermissionsDesc(menu = {"推广管理", "通知管理"}, button = "批量删除") + @PostMapping("/batch-delete") + public Object batchDelete(@RequestBody String body) { + List ids = JacksonUtil.parseIntegerList(body, "ids"); + // 1. 删除通知管理员记录 + noticeAdminService.deleteByNoticeIds(ids); + // 2. 删除通知记录 + noticeService.deleteByIds(ids); + return ResponseUtil.ok(); + } +} diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminOrderController.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminOrderController.java index 488cdf890783c1860b132719613bc8af6556be19..b5b4eea25c2ec5bd14a58064f94fd37fe698bd7d 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminOrderController.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminOrderController.java @@ -1,42 +1,23 @@ package org.linlinjava.litemall.admin.web; -import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest; -import com.github.binarywang.wxpay.bean.result.WxPayRefundResult; -import com.github.binarywang.wxpay.exception.WxPayException; -import com.github.binarywang.wxpay.service.WxPayService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.linlinjava.litemall.admin.annotation.RequiresPermissionsDesc; +import org.linlinjava.litemall.admin.service.AdminOrderService; +import org.linlinjava.litemall.core.express.ExpressService; import org.linlinjava.litemall.core.notify.NotifyService; -import org.linlinjava.litemall.core.notify.NotifyType; -import org.linlinjava.litemall.core.util.JacksonUtil; import org.linlinjava.litemall.core.util.ResponseUtil; import org.linlinjava.litemall.core.validator.Order; import org.linlinjava.litemall.core.validator.Sort; -import org.linlinjava.litemall.db.domain.LitemallComment; -import org.linlinjava.litemall.db.domain.LitemallOrder; -import org.linlinjava.litemall.db.domain.LitemallOrderGoods; -import org.linlinjava.litemall.db.domain.UserVo; -import org.linlinjava.litemall.db.service.*; -import org.linlinjava.litemall.db.util.OrderUtil; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionDefinition; -import org.springframework.transaction.TransactionStatus; -import org.springframework.transaction.support.DefaultTransactionDefinition; -import org.springframework.util.StringUtils; +import org.springframework.format.annotation.DateTimeFormat; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.validation.constraints.NotNull; -import java.math.BigDecimal; import java.time.LocalDateTime; -import java.util.HashMap; import java.util.List; -import java.util.Map; - -import static org.linlinjava.litemall.admin.util.AdminResponseCode.*; @RestController @RequestMapping("/admin/order") @@ -45,244 +26,109 @@ public class AdminOrderController { private final Log logger = LogFactory.getLog(AdminOrderController.class); @Autowired - private PlatformTransactionManager txManager; - - @Autowired - private LitemallOrderGoodsService orderGoodsService; - @Autowired - private LitemallOrderService orderService; - @Autowired - private LitemallGoodsProductService productService; + private AdminOrderService adminOrderService; @Autowired - private LitemallUserService userService; - @Autowired - private LitemallCommentService commentService; - @Autowired - private WxPayService wxPayService; - @Autowired - private NotifyService notifyService; + private ExpressService expressService; + /** + * 查询订单 + * + * @param userId + * @param orderSn + * @param orderStatusArray + * @param page + * @param limit + * @param sort + * @param order + * @return + */ @RequiresPermissions("admin:order:list") - @RequiresPermissionsDesc(menu={"商城管理" , "订单管理"}, button="查询") + @RequiresPermissionsDesc(menu = {"商场管理", "订单管理"}, button = "查询") @GetMapping("/list") public Object list(Integer userId, String orderSn, + @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime start, + @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime end, @RequestParam(required = false) List orderStatusArray, @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer limit, @Sort @RequestParam(defaultValue = "add_time") String sort, @Order @RequestParam(defaultValue = "desc") String order) { - List orderList = orderService.querySelective(userId, orderSn, orderStatusArray, page, limit, sort, order); - int total = orderService.countSelective(userId, orderSn, orderStatusArray, page, limit, sort, order); - - Map data = new HashMap<>(); - data.put("total", total); - data.put("items", orderList); + return adminOrderService.list(userId, orderSn, start, end, orderStatusArray, page, limit, sort, order); + } - return ResponseUtil.ok(data); + /** + * 查询物流公司 + * + * @return + */ + @GetMapping("/channel") + public Object channel() { + return ResponseUtil.ok(expressService.getVendors()); } + /** + * 订单详情 + * + * @param id + * @return + */ @RequiresPermissions("admin:order:read") - @RequiresPermissionsDesc(menu={"商城管理" , "订单管理"}, button="详情") + @RequiresPermissionsDesc(menu = {"商场管理", "订单管理"}, button = "详情") @GetMapping("/detail") public Object detail(@NotNull Integer id) { - LitemallOrder order = orderService.findById(id); - List orderGoods = orderGoodsService.queryByOid(id); - UserVo user = userService.findUserVoById(order.getUserId()); - Map data = new HashMap<>(); - data.put("order", order); - data.put("orderGoods", orderGoods); - data.put("user", user); - - return ResponseUtil.ok(data); + return adminOrderService.detail(id); } /** * 订单退款 - *

- * 1. 检测当前订单是否能够退款; - * 2. 微信退款操作; - * 3. 设置订单退款确认状态; - * 4. 订单商品库存回库。 - *

- * TODO - * 虽然接入了微信退款API,但是从安全角度考虑,建议开发者删除这里微信退款代码,采用以下两步走步骤: - * 1. 管理员登录微信官方支付平台点击退款操作进行退款 - * 2. 管理员登录litemall管理后台点击退款操作进行订单状态修改和商品库存回库 * - * @param body 订单信息,{ orderId:xxx } + * @param body 订单信息,{ orderId:xxx } * @return 订单退款操作结果 */ @RequiresPermissions("admin:order:refund") - @RequiresPermissionsDesc(menu={"商城管理" , "订单管理"}, button="订单退款") - @PostMapping("refund") + @RequiresPermissionsDesc(menu = {"商场管理", "订单管理"}, button = "订单退款") + @PostMapping("/refund") public Object refund(@RequestBody String body) { - Integer orderId = JacksonUtil.parseInteger(body, "orderId"); - String refundMoney = JacksonUtil.parseString(body, "refundMoney"); - if (orderId == null) { - return ResponseUtil.badArgument(); - } - if(StringUtils.isEmpty(refundMoney)){ - return ResponseUtil.badArgument(); - } - - LitemallOrder order = orderService.findById(orderId); - if (order == null) { - return ResponseUtil.badArgument(); - } - - if (order.getActualPrice().compareTo(new BigDecimal(refundMoney)) != 0) { - return ResponseUtil.badArgumentValue(); - } - - // 如果订单不是退款状态,则不能退款 - if (!order.getOrderStatus().equals(OrderUtil.STATUS_REFUND)) { - return ResponseUtil.fail(ORDER_CONFIRM_NOT_ALLOWED, "订单不能确认收货"); - } - - // 微信退款 - WxPayRefundRequest wxPayRefundRequest = new WxPayRefundRequest(); - wxPayRefundRequest.setOutTradeNo(order.getOrderSn()); - wxPayRefundRequest.setOutRefundNo("refund_" + order.getOrderSn()); - // 元转成分 - Integer totalFee = order.getActualPrice().multiply(new BigDecimal(100)).intValue(); - wxPayRefundRequest.setTotalFee(totalFee); - wxPayRefundRequest.setRefundFee(totalFee); - - WxPayRefundResult wxPayRefundResult = null; - try { - wxPayRefundResult = wxPayService.refund(wxPayRefundRequest); - } catch (WxPayException e) { - e.printStackTrace(); - return ResponseUtil.fail(ORDER_REFUND_FAILED, "订单退款失败"); - } - if(!wxPayRefundResult.getReturnCode().equals("SUCCESS")){ - logger.warn("refund fail: " + wxPayRefundResult.getReturnMsg()); - return ResponseUtil.fail(ORDER_REFUND_FAILED, "订单退款失败"); - } - if(!wxPayRefundResult.getResultCode().equals("SUCCESS")){ - logger.warn("refund fail: " + wxPayRefundResult.getReturnMsg()); - return ResponseUtil.fail(ORDER_REFUND_FAILED, "订单退款失败"); - } - - // 开启事务管理 - DefaultTransactionDefinition def = new DefaultTransactionDefinition(); - def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); - TransactionStatus status = txManager.getTransaction(def); - try { - // 设置订单取消状态 - order.setOrderStatus(OrderUtil.STATUS_REFUND_CONFIRM); - if (orderService.updateWithOptimisticLocker(order) == 0) { - throw new Exception("更新数据已失效"); - } - - // 商品货品数量增加 - List orderGoodsList = orderGoodsService.queryByOid(orderId); - for (LitemallOrderGoods orderGoods : orderGoodsList) { - Integer productId = orderGoods.getProductId(); - Short number = orderGoods.getNumber(); - if (productService.addStock(productId, number) == 0) { - throw new Exception("商品货品库存增加失败"); - } - } - } catch (Exception ex) { - txManager.rollback(status); - logger.error("系统内部错误", ex); - return ResponseUtil.fail(ORDER_REFUND_FAILED, "订单退款失败"); - } - txManager.commit(status); - - //TODO 发送邮件和短信通知,这里采用异步发送 - // 退款成功通知用户, 例如“您申请的订单退款 [ 单号:{1} ] 已成功,请耐心等待到账。” - // 注意订单号只发后6位 - notifyService.notifySmsTemplate(order.getMobile(), NotifyType.REFUND, new String[]{order.getOrderSn().substring(8, 14)}); - - return ResponseUtil.ok(); + return adminOrderService.refund(body); } /** * 发货 - * 1. 检测当前订单是否能够发货 - * 2. 设置订单发货状态 * - * @param body 订单信息,{ orderId:xxx, shipSn: xxx, shipChannel: xxx } + * @param body 订单信息,{ orderId:xxx, shipSn: xxx, shipChannel: xxx } * @return 订单操作结果 - * 成功则 { errno: 0, errmsg: '成功' } - * 失败则 { errno: XXX, errmsg: XXX } */ @RequiresPermissions("admin:order:ship") - @RequiresPermissionsDesc(menu={"商城管理" , "订单管理"}, button="订单发货") - @PostMapping("ship") + @RequiresPermissionsDesc(menu = {"商场管理", "订单管理"}, button = "订单发货") + @PostMapping("/ship") public Object ship(@RequestBody String body) { - Integer orderId = JacksonUtil.parseInteger(body, "orderId"); - String shipSn = JacksonUtil.parseString(body, "shipSn"); - String shipChannel = JacksonUtil.parseString(body, "shipChannel"); - if (orderId == null || shipSn == null || shipChannel == null) { - return ResponseUtil.badArgument(); - } - - LitemallOrder order = orderService.findById(orderId); - if (order == null) { - return ResponseUtil.badArgument(); - } - - // 如果订单不是已付款状态,则不能发货 - if (!order.getOrderStatus().equals(OrderUtil.STATUS_PAY)) { - return ResponseUtil.fail(ORDER_CONFIRM_NOT_ALLOWED, "订单不能确认收货"); - } - - order.setOrderStatus(OrderUtil.STATUS_SHIP); - order.setShipSn(shipSn); - order.setShipChannel(shipChannel); - order.setShipTime(LocalDateTime.now()); - if (orderService.updateWithOptimisticLocker(order) == 0) { - return ResponseUtil.updatedDateExpired(); - } + return adminOrderService.ship(body); + } - //TODO 发送邮件和短信通知,这里采用异步发送 - // 发货会发送通知短信给用户: * - // "您的订单已经发货,快递公司 {1},快递单 {2} ,请注意查收" - notifyService.notifySmsTemplate(order.getMobile(), NotifyType.SHIP, new String[]{shipChannel, shipSn}); - return ResponseUtil.ok(); + /** + * 删除订单 + * + * @param body 订单信息,{ orderId:xxx } + * @return 订单操作结果 + */ + @RequiresPermissions("admin:order:delete") + @RequiresPermissionsDesc(menu = {"商场管理", "订单管理"}, button = "订单删除") + @PostMapping("/delete") + public Object delete(@RequestBody String body) { + return adminOrderService.delete(body); } - /** * 回复订单商品 * - * @param body 订单信息,{ orderId:xxx } + * @param body 订单信息,{ orderId:xxx } * @return 订单操作结果 - * 成功则 { errno: 0, errmsg: '成功' } - * 失败则 { errno: XXX, errmsg: XXX } */ @RequiresPermissions("admin:order:reply") - @RequiresPermissionsDesc(menu={"商城管理" , "订单管理"}, button="订单商品回复") - @PostMapping("reply") + @RequiresPermissionsDesc(menu = {"商场管理", "订单管理"}, button = "订单商品回复") + @PostMapping("/reply") public Object reply(@RequestBody String body) { - Integer commentId = JacksonUtil.parseInteger(body, "commentId"); - if (commentId == null || commentId == 0) { - return ResponseUtil.badArgument(); - } - // 目前只支持回复一次 - if (commentService.findById(commentId) != null) { - return ResponseUtil.fail(ORDER_REPLY_EXIST, "订单商品已回复!"); - } - String content = JacksonUtil.parseString(body, "content"); - if (StringUtils.isEmpty(content)) { - return ResponseUtil.badArgument(); - } - // 创建评价回复 - LitemallComment comment = new LitemallComment(); - comment.setType((byte) 2); - comment.setValueId(commentId); - comment.setContent(content); - comment.setUserId(0); // 评价回复没有用 - comment.setStar((short) 0); // 评价回复没有用 - comment.setHasPicture(false); // 评价回复没有用 - comment.setPicUrls(new String[]{}); // 评价回复没有用 - commentService.save(comment); - - return ResponseUtil.ok(); + return adminOrderService.reply(body); } - } diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminProfileController.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminProfileController.java index d9f5f6ae9383b2992bdf63f4d92fc140f921decb..faa2dccbf89f9efa18274d3645859827441fe5bd 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminProfileController.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminProfileController.java @@ -1,5 +1,6 @@ package org.linlinjava.litemall.admin.web; +import io.swagger.models.auth.In; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.shiro.SecurityUtils; @@ -8,15 +9,25 @@ import org.apache.shiro.subject.Subject; import org.linlinjava.litemall.core.util.JacksonUtil; import org.linlinjava.litemall.core.util.ResponseUtil; import org.linlinjava.litemall.core.util.bcrypt.BCryptPasswordEncoder; +import org.linlinjava.litemall.core.validator.Order; +import org.linlinjava.litemall.core.validator.Sort; import org.linlinjava.litemall.db.domain.LitemallAdmin; +import org.linlinjava.litemall.db.domain.LitemallIssue; +import org.linlinjava.litemall.db.domain.LitemallNotice; +import org.linlinjava.litemall.db.domain.LitemallNoticeAdmin; import org.linlinjava.litemall.db.service.LitemallAdminService; +import org.linlinjava.litemall.db.service.LitemallNoticeAdminService; +import org.linlinjava.litemall.db.service.LitemallNoticeService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.StringUtils; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import static org.linlinjava.litemall.admin.util.AdminResponseCode.ADMIN_INVALID_ACCOUNT; @@ -28,6 +39,10 @@ public class AdminProfileController { @Autowired private LitemallAdminService adminService; + @Autowired + private LitemallNoticeService noticeService; + @Autowired + private LitemallNoticeAdminService noticeAdminService; @RequiresAuthentication @PostMapping("/password") @@ -56,4 +71,89 @@ public class AdminProfileController { return ResponseUtil.ok(); } + private Integer getAdminId(){ + Subject currentUser = SecurityUtils.getSubject(); + LitemallAdmin admin = (LitemallAdmin) currentUser.getPrincipal(); + return admin.getId(); + } + + @RequiresAuthentication + @GetMapping("/nnotice") + public Object nNotice() { + int count = noticeAdminService.countUnread(getAdminId()); + return ResponseUtil.ok(count); + } + + @RequiresAuthentication + @GetMapping("/lsnotice") + public Object lsNotice(String title, String type, + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer limit, + @Sort @RequestParam(defaultValue = "add_time") String sort, + @Order @RequestParam(defaultValue = "desc") String order) { + List noticeList = noticeAdminService.querySelective(title, type, getAdminId(), page, limit, sort, order); + return ResponseUtil.okList(noticeList); + } + + @RequiresAuthentication + @PostMapping("/catnotice") + public Object catNotice(@RequestBody String body) { + Integer noticeId = JacksonUtil.parseInteger(body, "noticeId"); + if(noticeId == null){ + return ResponseUtil.badArgument(); + } + + LitemallNoticeAdmin noticeAdmin = noticeAdminService.find(noticeId, getAdminId()); + if(noticeAdmin == null){ + return ResponseUtil.badArgumentValue(); + } + // 更新通知记录中的时间 + noticeAdmin.setReadTime(LocalDateTime.now()); + noticeAdminService.update(noticeAdmin); + + // 返回通知的相关信息 + Map data = new HashMap<>(); + LitemallNotice notice = noticeService.findById(noticeId); + data.put("title", notice.getTitle()); + data.put("content", notice.getContent()); + data.put("time", notice.getUpdateTime()); + Integer adminId = notice.getAdminId(); + if(adminId.equals(0)){ + data.put("admin", "系统"); + } + else{ + LitemallAdmin admin = adminService.findById(notice.getAdminId()); + data.put("admin", admin.getUsername()); + data.put("avatar", admin.getAvatar()); + } + return ResponseUtil.ok(data); + } + + @RequiresAuthentication + @PostMapping("/bcatnotice") + public Object bcatNotice(@RequestBody String body) { + List ids = JacksonUtil.parseIntegerList(body, "ids"); + noticeAdminService.markReadByIds(ids, getAdminId()); + return ResponseUtil.ok(); + } + + @RequiresAuthentication + @PostMapping("/rmnotice") + public Object rmNotice(@RequestBody String body) { + Integer id = JacksonUtil.parseInteger(body, "id"); + if(id == null){ + return ResponseUtil.badArgument(); + } + noticeAdminService.deleteById(id, getAdminId()); + return ResponseUtil.ok(); + } + + @RequiresAuthentication + @PostMapping("/brmnotice") + public Object brmNotice(@RequestBody String body) { + List ids = JacksonUtil.parseIntegerList(body, "ids"); + noticeAdminService.deleteByIds(ids, getAdminId()); + return ResponseUtil.ok(); + } + } diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminRegionController.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminRegionController.java index 25c3f3cb5f6761850b7189f429b156c0c8d2b5d8..6835f24e83cac38291f2106c38b9f62454a30290 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminRegionController.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminRegionController.java @@ -2,22 +2,21 @@ package org.linlinjava.litemall.admin.web; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.linlinjava.litemall.admin.vo.RegionVo; import org.linlinjava.litemall.core.util.ResponseUtil; -import org.linlinjava.litemall.core.validator.Order; -import org.linlinjava.litemall.core.validator.Sort; import org.linlinjava.litemall.db.domain.LitemallRegion; import org.linlinjava.litemall.db.service.LitemallRegionService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.validation.constraints.NotNull; -import java.util.HashMap; +import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; @RestController @RequestMapping("/admin/region") @@ -31,21 +30,58 @@ public class AdminRegionController { @GetMapping("/clist") public Object clist(@NotNull Integer id) { List regionList = regionService.queryByPid(id); - return ResponseUtil.ok(regionList); + return ResponseUtil.okList(regionList); } @GetMapping("/list") - public Object list(String name, Integer code, - @RequestParam(defaultValue = "1") Integer page, - @RequestParam(defaultValue = "10") Integer limit, - @Sort(accepts = {"id"}) @RequestParam(defaultValue = "id") String sort, - @Order @RequestParam(defaultValue = "desc") String order) { - List regionList = regionService.querySelective(name, code, page, limit, sort, order); - int total = regionService.countSelective(name, code, page, limit, sort, order); - Map data = new HashMap<>(); - data.put("total", total); - data.put("items", regionList); - - return ResponseUtil.ok(data); + public Object list() { + List regionVoList = new ArrayList<>(); + + List litemallRegions = regionService.getAll(); + Map> collect = litemallRegions.stream().collect(Collectors.groupingBy(LitemallRegion::getType)); + byte provinceType = 1; + List provinceList = collect.get(provinceType); + byte cityType = 2; + List city = collect.get(cityType); + Map> cityListMap = city.stream().collect(Collectors.groupingBy(LitemallRegion::getPid)); + byte areaType = 3; + List areas = collect.get(areaType); + Map> areaListMap = areas.stream().collect(Collectors.groupingBy(LitemallRegion::getPid)); + + for (LitemallRegion province : provinceList) { + RegionVo provinceVO = new RegionVo(); + provinceVO.setId(province.getId()); + provinceVO.setName(province.getName()); + provinceVO.setCode(province.getCode()); + provinceVO.setType(province.getType()); + + List cityList = cityListMap.get(province.getId()); + List cityVOList = new ArrayList<>(); + for (LitemallRegion cityVo : cityList) { + RegionVo cityVO = new RegionVo(); + cityVO.setId(cityVo.getId()); + cityVO.setName(cityVo.getName()); + cityVO.setCode(cityVo.getCode()); + cityVO.setType(cityVo.getType()); + + List areaList = areaListMap.get(cityVo.getId()); + List areaVOList = new ArrayList<>(); + for (LitemallRegion area : areaList) { + RegionVo areaVO = new RegionVo(); + areaVO.setId(area.getId()); + areaVO.setName(area.getName()); + areaVO.setCode(area.getCode()); + areaVO.setType(area.getType()); + areaVOList.add(areaVO); + } + + cityVO.setChildren(areaVOList); + cityVOList.add(cityVO); + } + provinceVO.setChildren(cityVOList); + regionVoList.add(provinceVO); + } + + return ResponseUtil.okList(regionVoList); } } diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminRoleController.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminRoleController.java index d882dcf59b3ba2490fac6b532e904f0d13a63c0c..dd0b3ffb1d71d17c9fa98873849adf3cf72e7a2d 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminRoleController.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminRoleController.java @@ -2,21 +2,20 @@ package org.linlinjava.litemall.admin.web; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.shiro.SecurityUtils; import org.apache.shiro.authz.annotation.RequiresPermissions; -import org.apache.shiro.subject.Subject; import org.linlinjava.litemall.admin.annotation.RequiresPermissionsDesc; import org.linlinjava.litemall.admin.util.AdminResponseCode; -import org.linlinjava.litemall.admin.util.PermVo; +import org.linlinjava.litemall.admin.util.Permission; import org.linlinjava.litemall.admin.util.PermissionUtil; +import org.linlinjava.litemall.admin.vo.PermVo; import org.linlinjava.litemall.core.util.JacksonUtil; import org.linlinjava.litemall.core.util.ResponseUtil; import org.linlinjava.litemall.core.validator.Order; import org.linlinjava.litemall.core.validator.Sort; import org.linlinjava.litemall.db.domain.LitemallAdmin; -import org.linlinjava.litemall.db.domain.LitemallBrand; import org.linlinjava.litemall.db.domain.LitemallPermission; import org.linlinjava.litemall.db.domain.LitemallRole; +import org.linlinjava.litemall.db.service.LitemallAdminService; import org.linlinjava.litemall.db.service.LitemallPermissionService; import org.linlinjava.litemall.db.service.LitemallRoleService; import org.springframework.beans.factory.annotation.Autowired; @@ -29,6 +28,7 @@ import javax.validation.constraints.NotNull; import java.util.*; import static org.linlinjava.litemall.admin.util.AdminResponseCode.ROLE_NAME_EXIST; +import static org.linlinjava.litemall.admin.util.AdminResponseCode.ROLE_USER_EXIST; @RestController @RequestMapping("/admin/role") @@ -40,9 +40,11 @@ public class AdminRoleController { private LitemallRoleService roleService; @Autowired private LitemallPermissionService permissionService; + @Autowired + private LitemallAdminService adminService; @RequiresPermissions("admin:role:list") - @RequiresPermissionsDesc(menu={"系统管理" , "角色管理"}, button="查询") + @RequiresPermissionsDesc(menu = {"系统管理", "角色管理"}, button = "角色查询") @GetMapping("/list") public Object list(String name, @RequestParam(defaultValue = "1") Integer page, @@ -50,18 +52,11 @@ public class AdminRoleController { @Sort @RequestParam(defaultValue = "add_time") String sort, @Order @RequestParam(defaultValue = "desc") String order) { List roleList = roleService.querySelective(name, page, limit, sort, order); - int total = roleService.countSelective(name, page, limit, sort, order); - Map data = new HashMap<>(); - data.put("total", total); - data.put("items", roleList); - - return ResponseUtil.ok(data); + return ResponseUtil.okList(roleList); } - @RequiresPermissions("admin:role:list") - @RequiresPermissionsDesc(menu={"系统管理" , "角色管理"}, button="查询") @GetMapping("/options") - public Object options(){ + public Object options() { List roleList = roleService.queryAll(); List> options = new ArrayList<>(roleList.size()); @@ -72,11 +67,11 @@ public class AdminRoleController { options.add(option); } - return ResponseUtil.ok(options); + return ResponseUtil.okList(options); } @RequiresPermissions("admin:role:read") - @RequiresPermissionsDesc(menu={"系统管理" , "角色管理"}, button="详情") + @RequiresPermissionsDesc(menu = {"系统管理", "角色管理"}, button = "角色详情") @GetMapping("/read") public Object read(@NotNull Integer id) { LitemallRole role = roleService.findById(id); @@ -94,7 +89,7 @@ public class AdminRoleController { } @RequiresPermissions("admin:role:create") - @RequiresPermissionsDesc(menu={"系统管理" , "角色管理"}, button="添加") + @RequiresPermissionsDesc(menu = {"系统管理", "角色管理"}, button = "角色添加") @PostMapping("/create") public Object create(@RequestBody LitemallRole role) { Object error = validate(role); @@ -102,7 +97,7 @@ public class AdminRoleController { return error; } - if (roleService.checkExist(role.getName())){ + if (roleService.checkExist(role.getName())) { return ResponseUtil.fail(ROLE_NAME_EXIST, "角色已经存在"); } @@ -112,7 +107,7 @@ public class AdminRoleController { } @RequiresPermissions("admin:role:update") - @RequiresPermissionsDesc(menu={"系统管理" , "角色管理"}, button="编辑") + @RequiresPermissionsDesc(menu = {"系统管理", "角色管理"}, button = "角色编辑") @PostMapping("/update") public Object update(@RequestBody LitemallRole role) { Object error = validate(role); @@ -125,13 +120,25 @@ public class AdminRoleController { } @RequiresPermissions("admin:role:delete") - @RequiresPermissionsDesc(menu={"系统管理" , "角色管理"}, button="删除") + @RequiresPermissionsDesc(menu = {"系统管理", "角色管理"}, button = "角色删除") @PostMapping("/delete") public Object delete(@RequestBody LitemallRole role) { Integer id = role.getId(); if (id == null) { return ResponseUtil.badArgument(); } + + // 如果当前角色所对应管理员仍存在,则拒绝删除角色。 + List adminList = adminService.all(); + for (LitemallAdmin admin : adminList) { + Integer[] roleIds = admin.getRoleIds(); + for (Integer roleId : roleIds) { + if (id.equals(roleId)) { + return ResponseUtil.fail(ROLE_USER_EXIST, "当前角色存在管理员,不能删除"); + } + } + } + roleService.deleteById(id); return ResponseUtil.ok(); } @@ -140,32 +147,26 @@ public class AdminRoleController { @Autowired private ApplicationContext context; private List systemPermissions = null; - private List getSystemPermissions(){ + private Set systemPermissionsString = null; + + private List getSystemPermissions() { final String basicPackage = "org.linlinjava.litemall.admin"; - if(systemPermissions == null){ - systemPermissions = PermissionUtil.listPermissions(context, basicPackage); + if (systemPermissions == null) { + List permissions = PermissionUtil.listPermission(context, basicPackage); + systemPermissions = PermissionUtil.listPermVo(permissions); + systemPermissionsString = PermissionUtil.listPermissionString(permissions); } return systemPermissions; } - private Set getSystemPermissionsString(){ - getSystemPermissions(); - - Set permissions = new HashSet(); - for(PermVo permVo : systemPermissions){ - permissions.add(permVo.getId()); - } - return permissions; - } - - private Set getAssignedPermissions(Integer roleId){ + private Set getAssignedPermissions(Integer roleId) { // 这里需要注意的是,如果存在超级权限*,那么这里需要转化成当前所有系统权限。 // 之所以这么做,是因为前端不能识别超级权限,所以这里需要转换一下。 Set assignedPermissions = null; - if(permissionService.checkSuperPermission(roleId)){ - assignedPermissions = getSystemPermissionsString(); - } - else{ + if (permissionService.checkSuperPermission(roleId)) { + getSystemPermissions(); + assignedPermissions = systemPermissionsString; + } else { assignedPermissions = permissionService.queryByRoleId(roleId); } @@ -177,8 +178,8 @@ public class AdminRoleController { * * @return 系统所有权限列表和管理员已分配权限 */ - @RequiresPermissions("admin:role:permission") - @RequiresPermissionsDesc(menu={"系统管理" , "角色管理"}, button="授权") + @RequiresPermissions("admin:role:permission:get") + @RequiresPermissionsDesc(menu = {"系统管理", "角色管理"}, button = "权限详情") @GetMapping("/permissions") public Object getPermissions(Integer roleId) { List systemPermissions = getSystemPermissions(); @@ -197,21 +198,24 @@ public class AdminRoleController { * @param body * @return */ - @RequiresPermissions("admin:role:permission") - @RequiresPermissionsDesc(menu={"系统管理" , "角色管理"}, button="授权") + @RequiresPermissions("admin:role:permission:update") + @RequiresPermissionsDesc(menu = {"系统管理", "角色管理"}, button = "权限变更") @PostMapping("/permissions") public Object updatePermissions(@RequestBody String body) { Integer roleId = JacksonUtil.parseInteger(body, "roleId"); List permissions = JacksonUtil.parseStringList(body, "permissions"); + if (roleId == null || permissions == null) { + return ResponseUtil.badArgument(); + } // 如果修改的角色是超级权限,则拒绝修改。 - if(permissionService.checkSuperPermission(roleId)){ + if (permissionService.checkSuperPermission(roleId)) { return ResponseUtil.fail(AdminResponseCode.ROLE_SUPER_SUPERMISSION, "当前角色的超级权限不能变更"); } // 先删除旧的权限,再更新新的权限 permissionService.deleteByRoleId(roleId); - for(String permission : permissions){ + for (String permission : permissions) { LitemallPermission litemallPermission = new LitemallPermission(); litemallPermission.setRoleId(roleId); litemallPermission.setPermission(permission); diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminStatController.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminStatController.java index a0832005c6022e91e4cdf3c54015a4e60238cca7..8ff94ff285ba2785edda346f9a8652e0a2a17c9f 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminStatController.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminStatController.java @@ -4,7 +4,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.linlinjava.litemall.admin.annotation.RequiresPermissionsDesc; -import org.linlinjava.litemall.admin.util.StatVo; +import org.linlinjava.litemall.admin.vo.StatVo; import org.linlinjava.litemall.core.util.ResponseUtil; import org.linlinjava.litemall.db.service.StatService; import org.springframework.beans.factory.annotation.Autowired; @@ -26,7 +26,7 @@ public class AdminStatController { private StatService statService; @RequiresPermissions("admin:stat:user") - @RequiresPermissionsDesc(menu={"统计管理" , "用户统计"}, button="查询") + @RequiresPermissionsDesc(menu = {"统计管理", "用户统计"}, button = "查询") @GetMapping("/user") public Object statUser() { List rows = statService.statUser(); @@ -38,7 +38,7 @@ public class AdminStatController { } @RequiresPermissions("admin:stat:order") - @RequiresPermissionsDesc(menu={"统计管理" , "订单统计"}, button="查询") + @RequiresPermissionsDesc(menu = {"统计管理", "订单统计"}, button = "查询") @GetMapping("/order") public Object statOrder() { List rows = statService.statOrder(); @@ -51,7 +51,7 @@ public class AdminStatController { } @RequiresPermissions("admin:stat:goods") - @RequiresPermissionsDesc(menu={"统计管理" , "商品统计"}, button="查询") + @RequiresPermissionsDesc(menu = {"统计管理", "商品统计"}, button = "查询") @GetMapping("/goods") public Object statGoods() { List rows = statService.statGoods(); diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminStorageController.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminStorageController.java index 303cd07c0660e61eb8b4c9209d774237559d9dfd..544fbb6c3fcf3dd8989f251265f4b0096fcdc0db 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminStorageController.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminStorageController.java @@ -18,9 +18,7 @@ import org.springframework.web.multipart.MultipartFile; import javax.validation.constraints.NotNull; import java.io.IOException; -import java.util.HashMap; import java.util.List; -import java.util.Map; @RestController @RequestMapping("/admin/storage") @@ -34,7 +32,7 @@ public class AdminStorageController { private LitemallStorageService litemallStorageService; @RequiresPermissions("admin:storage:list") - @RequiresPermissionsDesc(menu={"系统管理" , "对象存储"}, button="查询") + @RequiresPermissionsDesc(menu = {"系统管理", "对象存储"}, button = "查询") @GetMapping("/list") public Object list(String key, String name, @RequestParam(defaultValue = "1") Integer page, @@ -42,27 +40,21 @@ public class AdminStorageController { @Sort @RequestParam(defaultValue = "add_time") String sort, @Order @RequestParam(defaultValue = "desc") String order) { List storageList = litemallStorageService.querySelective(key, name, page, limit, sort, order); - int total = litemallStorageService.countSelective(key, name, page, limit, sort, order); - Map data = new HashMap<>(); - data.put("total", total); - data.put("items", storageList); - - return ResponseUtil.ok(data); + return ResponseUtil.okList(storageList); } @RequiresPermissions("admin:storage:create") - @RequiresPermissionsDesc(menu={"系统管理" , "对象存储"}, button="上传") + @RequiresPermissionsDesc(menu = {"系统管理", "对象存储"}, button = "上传") @PostMapping("/create") public Object create(@RequestParam("file") MultipartFile file) throws IOException { String originalFilename = file.getOriginalFilename(); - String url = storageService.store(file.getInputStream(), file.getSize(), file.getContentType(), originalFilename); - Map data = new HashMap<>(); - data.put("url", url); - return ResponseUtil.ok(data); + LitemallStorage litemallStorage = storageService.store(file.getInputStream(), file.getSize(), + file.getContentType(), originalFilename); + return ResponseUtil.ok(litemallStorage); } @RequiresPermissions("admin:storage:read") - @RequiresPermissionsDesc(menu={"系统管理" , "对象存储"}, button="详情") + @RequiresPermissionsDesc(menu = {"系统管理", "对象存储"}, button = "详情") @PostMapping("/read") public Object read(@NotNull Integer id) { LitemallStorage storageInfo = litemallStorageService.findById(id); @@ -73,7 +65,7 @@ public class AdminStorageController { } @RequiresPermissions("admin:storage:update") - @RequiresPermissionsDesc(menu={"系统管理" , "对象存储"}, button="编辑") + @RequiresPermissionsDesc(menu = {"系统管理", "对象存储"}, button = "编辑") @PostMapping("/update") public Object update(@RequestBody LitemallStorage litemallStorage) { if (litemallStorageService.update(litemallStorage) == 0) { @@ -83,7 +75,7 @@ public class AdminStorageController { } @RequiresPermissions("admin:storage:delete") - @RequiresPermissionsDesc(menu={"系统管理" , "对象存储"}, button="删除") + @RequiresPermissionsDesc(menu = {"系统管理", "对象存储"}, button = "删除") @PostMapping("/delete") public Object delete(@RequestBody LitemallStorage litemallStorage) { String key = litemallStorage.getKey(); diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminTopicController.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminTopicController.java index 31142c320d3f8ce318141ceaa33aba7a0c0f2d73..4894444d6eeab24890ac90dc5eb6c0b5b200a987 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminTopicController.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminTopicController.java @@ -1,13 +1,17 @@ package org.linlinjava.litemall.admin.web; +import io.swagger.models.auth.In; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.linlinjava.litemall.admin.annotation.RequiresPermissionsDesc; +import org.linlinjava.litemall.core.util.JacksonUtil; import org.linlinjava.litemall.core.util.ResponseUtil; import org.linlinjava.litemall.core.validator.Order; import org.linlinjava.litemall.core.validator.Sort; +import org.linlinjava.litemall.db.domain.LitemallGoods; import org.linlinjava.litemall.db.domain.LitemallTopic; +import org.linlinjava.litemall.db.service.LitemallGoodsService; import org.linlinjava.litemall.db.service.LitemallTopicService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.StringUtils; @@ -16,6 +20,7 @@ import org.springframework.web.bind.annotation.*; import javax.validation.constraints.NotNull; import java.math.BigDecimal; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -28,22 +33,19 @@ public class AdminTopicController { @Autowired private LitemallTopicService topicService; + @Autowired + private LitemallGoodsService goodsService; @RequiresPermissions("admin:topic:list") - @RequiresPermissionsDesc(menu={"推广管理" , "专题管理"}, button="查询") + @RequiresPermissionsDesc(menu = {"推广管理", "专题管理"}, button = "查询") @GetMapping("/list") public Object list(String title, String subtitle, @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer limit, - @Sort @RequestParam(defaultValue = "add_time") String sort, + @Sort(accepts = {"id", "add_time", "price"}) @RequestParam(defaultValue = "add_time") String sort, @Order @RequestParam(defaultValue = "desc") String order) { List topicList = topicService.querySelective(title, subtitle, page, limit, sort, order); - int total = topicService.countSelective(title, subtitle, page, limit, sort, order); - Map data = new HashMap<>(); - data.put("total", total); - data.put("items", topicList); - - return ResponseUtil.ok(data); + return ResponseUtil.okList(topicList); } private Object validate(LitemallTopic topic) { @@ -63,7 +65,7 @@ public class AdminTopicController { } @RequiresPermissions("admin:topic:create") - @RequiresPermissionsDesc(menu={"推广管理" , "专题管理"}, button="添加") + @RequiresPermissionsDesc(menu = {"推广管理", "专题管理"}, button = "添加") @PostMapping("/create") public Object create(@RequestBody LitemallTopic topic) { Object error = validate(topic); @@ -75,15 +77,25 @@ public class AdminTopicController { } @RequiresPermissions("admin:topic:read") - @RequiresPermissionsDesc(menu={"推广管理" , "专题管理"}, button="详情") + @RequiresPermissionsDesc(menu = {"推广管理", "专题管理"}, button = "详情") @GetMapping("/read") public Object read(@NotNull Integer id) { LitemallTopic topic = topicService.findById(id); - return ResponseUtil.ok(topic); + Integer[] goodsIds = topic.getGoods(); + List goodsList = null; + if (goodsIds == null || goodsIds.length == 0) { + goodsList = new ArrayList<>(); + } else { + goodsList = goodsService.queryByIds(goodsIds); + } + Map data = new HashMap<>(2); + data.put("topic", topic); + data.put("goodsList", goodsList); + return ResponseUtil.ok(data); } @RequiresPermissions("admin:topic:update") - @RequiresPermissionsDesc(menu={"推广管理" , "专题管理"}, button="编辑") + @RequiresPermissionsDesc(menu = {"推广管理", "专题管理"}, button = "编辑") @PostMapping("/update") public Object update(@RequestBody LitemallTopic topic) { Object error = validate(topic); @@ -97,11 +109,19 @@ public class AdminTopicController { } @RequiresPermissions("admin:topic:delete") - @RequiresPermissionsDesc(menu={"推广管理" , "专题管理"}, button="删除") + @RequiresPermissionsDesc(menu = {"推广管理", "专题管理"}, button = "删除") @PostMapping("/delete") public Object delete(@RequestBody LitemallTopic topic) { topicService.deleteById(topic.getId()); return ResponseUtil.ok(); } + @RequiresPermissions("admin:topic:batch-delete") + @RequiresPermissionsDesc(menu = {"推广管理", "专题管理"}, button = "批量删除") + @PostMapping("/batch-delete") + public Object batchDelete(@RequestBody String body) { + List ids = JacksonUtil.parseIntegerList(body, "ids"); + topicService.deleteByIds(ids); + return ResponseUtil.ok(); + } } diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminUserController.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminUserController.java index 2a0eeb196b5b0a5a71d0e62d43eeac89a9295904..2052d616c183e3d2a62424b0768f600774eb8ccd 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminUserController.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminUserController.java @@ -4,24 +4,19 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.linlinjava.litemall.admin.annotation.RequiresPermissionsDesc; -import org.linlinjava.litemall.core.util.RegexUtil; import org.linlinjava.litemall.core.util.ResponseUtil; -import org.linlinjava.litemall.core.util.bcrypt.BCryptPasswordEncoder; import org.linlinjava.litemall.core.validator.Order; import org.linlinjava.litemall.core.validator.Sort; import org.linlinjava.litemall.db.domain.LitemallUser; import org.linlinjava.litemall.db.service.LitemallUserService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.util.StringUtils; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; -import javax.validation.constraints.NotEmpty; -import java.util.HashMap; import java.util.List; -import java.util.Map; - -import static org.linlinjava.litemall.admin.util.AdminResponseCode.*; @RestController @RequestMapping("/admin/user") @@ -33,7 +28,7 @@ public class AdminUserController { private LitemallUserService userService; @RequiresPermissions("admin:user:list") - @RequiresPermissionsDesc(menu={"用户管理" , "会员管理"}, button="查询") + @RequiresPermissionsDesc(menu = {"用户管理", "会员管理"}, button = "查询") @GetMapping("/list") public Object list(String username, String mobile, @RequestParam(defaultValue = "1") Integer page, @@ -41,95 +36,6 @@ public class AdminUserController { @Sort @RequestParam(defaultValue = "add_time") String sort, @Order @RequestParam(defaultValue = "desc") String order) { List userList = userService.querySelective(username, mobile, page, limit, sort, order); - int total = userService.countSeletive(username, mobile, page, limit, sort, order); - Map data = new HashMap<>(); - data.put("total", total); - data.put("items", userList); - - return ResponseUtil.ok(data); - } - - @RequiresPermissions("admin:user:list") - @RequiresPermissionsDesc(menu={"用户管理" , "会员管理"}, button="查询") - @GetMapping("/username") - public Object username(@NotEmpty String username) { - int total = userService.countSeletive(username, null, null, null, null, null); - if (total == 0) { - return ResponseUtil.ok("不存在"); - } - return ResponseUtil.ok("已存在"); - } - - private Object validate(LitemallUser user) { - String username = user.getUsername(); - if (StringUtils.isEmpty(user)) { - return ResponseUtil.badArgument(); - } - if (!RegexUtil.isUsername(username)) { - return ResponseUtil.fail(USER_INVALID_NAME, "用户名不符合规定"); - } - String password = user.getPassword(); - if (StringUtils.isEmpty(password) || password.length() < 6) { - return ResponseUtil.fail(USER_INVALID_PASSWORD, "用户密码长度不能小于6"); - } - String mobile = user.getMobile(); - if (StringUtils.isEmpty(mobile)) { - return ResponseUtil.badArgument(); - } - if (!RegexUtil.isMobileExact(mobile)) { - return ResponseUtil.fail(USER_INVALID_MOBILE, "用户手机号码格式不正确"); - } - return null; - } - - @RequiresPermissions("admin:user:create") - @RequiresPermissionsDesc(menu={"用户管理" , "会员管理"}, button="添加") - @PostMapping("/create") - public Object create(@RequestBody LitemallUser user) { - Object error = validate(user); - if (error != null) { - return error; - } - String username = user.getUsername(); - String mobile = user.getMobile(); - List userList = userService.queryByUsername(username); - if (userList.size() > 0) { - return ResponseUtil.fail(USER_NAME_EXIST, "用户名已注册"); - } - userList = userService.queryByMobile(mobile); - if (userList.size() > 0) { - return ResponseUtil.fail(USER_MOBILE_EXIST, "手机号已注册"); - } - if (!RegexUtil.isMobileExact(mobile)) { - return ResponseUtil.fail(USER_INVALID_MOBILE, "手机号格式不正确"); - } - - String password = user.getPassword(); - BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); - String encodedPassword = encoder.encode(password); - user.setPassword(encodedPassword); - - userService.add(user); - return ResponseUtil.ok(user); - } - - @RequiresPermissions("admin:user:update") - @RequiresPermissionsDesc(menu={"用户管理" , "会员管理"}, button="编辑") - @PostMapping("/update") - public Object update(@RequestBody LitemallUser user) { - Object error = validate(user); - if (error != null) { - return error; - } - // 用户密码加密存储 - String password = user.getPassword(); - BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); - String encodedPassword = encoder.encode(password); - user.setPassword(encodedPassword); - - if (userService.updateById(user) == 0) { - return ResponseUtil.updatedDataFailed(); - } - return ResponseUtil.ok(user); + return ResponseUtil.okList(userList); } } diff --git a/litemall-admin-api/src/main/resources/application.yml b/litemall-admin-api/src/main/resources/application.yml index 1b40f0ba920f877308d83e9acfaf54a7d0c566c1..d3ca436723f374c0af2bce793909888d83c718d7 100644 --- a/litemall-admin-api/src/main/resources/application.yml +++ b/litemall-admin-api/src/main/resources/application.yml @@ -13,4 +13,7 @@ logging: org.springframework: ERROR org.mybatis: ERROR org.linlinjava.litemall.admin: DEBUG - org.linlinjava.litemall: ERROR \ No newline at end of file + org.linlinjava.litemall: ERROR + +swagger: + production: false \ No newline at end of file diff --git a/litemall-admin-api/src/test/java/org/linlinjava/litemall/admin/DbTest.java b/litemall-admin-api/src/test/java/org/linlinjava/litemall/admin/DbTest.java new file mode 100644 index 0000000000000000000000000000000000000000..2428305ad46c885ab97291498886cdc6557efe85 --- /dev/null +++ b/litemall-admin-api/src/test/java/org/linlinjava/litemall/admin/DbTest.java @@ -0,0 +1,55 @@ +package org.linlinjava.litemall.admin; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.core.env.Environment; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.context.web.WebAppConfiguration; + +import java.io.File; +import java.io.IOException; +import java.time.LocalDate; + +@WebAppConfiguration +@RunWith(SpringRunner.class) +@SpringBootTest +public class DbTest { + @Autowired + private Environment environment; + + @Test + public void test() { + String user = environment.getProperty("spring.datasource.druid.username"); + String password = environment.getProperty("spring.datasource.druid.password"); + String url = environment.getProperty("spring.datasource.druid.url"); + int index1 = url.indexOf("3306/"); + int index2 = url.indexOf("?"); + String db = url.substring(index1+5, index2); + System.out.println(user); + System.out.println(password); + System.out.println(db); + } + + @Test + public void testFileCreate() throws IOException { + LocalDate localDate = LocalDate.now(); + String fileName = localDate.toString() + ".sql"; + System.out.println(fileName); + + File file = new File("backup", fileName); + file.getParentFile().mkdirs(); + file.createNewFile(); + } + + @Test + public void testFileDelete() throws IOException { + LocalDate localDate = LocalDate.now(); + String fileName = localDate.toString() + ".sql"; + System.out.println(fileName); + + File file = new File("backup", fileName); + file.deleteOnExit(); + } +} diff --git a/litemall-admin-api/src/test/java/org/linlinjava/litemall/admin/PermissionTest.java b/litemall-admin-api/src/test/java/org/linlinjava/litemall/admin/PermissionTest.java index fe58d75d0c94da55291c8c20b0ccfea27a3a4286..5cc7d3a62afd3d9f196b58a8a4db1573daa0cb4c 100644 --- a/litemall-admin-api/src/test/java/org/linlinjava/litemall/admin/PermissionTest.java +++ b/litemall-admin-api/src/test/java/org/linlinjava/litemall/admin/PermissionTest.java @@ -2,8 +2,9 @@ package org.linlinjava.litemall.admin; import org.junit.Test; import org.junit.runner.RunWith; -import org.linlinjava.litemall.admin.util.PermVo; +import org.linlinjava.litemall.admin.util.Permission; import org.linlinjava.litemall.admin.util.PermissionUtil; +import org.linlinjava.litemall.admin.vo.PermVo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.ApplicationContext; @@ -23,7 +24,8 @@ public class PermissionTest { @Test public void test() { final String basicPackage = "org.linlinjava.litemall.admin"; - List permVoList = PermissionUtil.listPermissions(context, basicPackage); + List permissionList = PermissionUtil.listPermission(context, basicPackage); + List permVoList = PermissionUtil.listPermVo(permissionList); permVoList.stream().forEach(System.out::println); } } diff --git a/litemall-admin/.babelrc b/litemall-admin/.babelrc deleted file mode 100644 index 6c0b7f27e4385fd99181f25328c71b348742d659..0000000000000000000000000000000000000000 --- a/litemall-admin/.babelrc +++ /dev/null @@ -1,17 +0,0 @@ -{ - "presets": [ - ["env", { - "modules": false, - "targets": { - "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] - } - }], - "stage-2" - ], - "plugins": ["transform-vue-jsx", "transform-runtime"], - "env": { - "development":{ - "plugins": ["dynamic-import-node"] - } - } -} diff --git a/litemall-admin/.editorconfig b/litemall-admin/.editorconfig index ea6e20f5b2e79f76a0032c30d99b9fcd346232fd..3454886e3a5743c9c3ab8ec8ac3547d3c9343a47 100644 --- a/litemall-admin/.editorconfig +++ b/litemall-admin/.editorconfig @@ -1,4 +1,4 @@ -# http://editorconfig.org +# https://editorconfig.org root = true [*] diff --git a/litemall-admin/.env.deployment b/litemall-admin/.env.deployment new file mode 100644 index 0000000000000000000000000000000000000000..5e42a2e892059a4bf17dff5b337e41c23206d95a --- /dev/null +++ b/litemall-admin/.env.deployment @@ -0,0 +1,8 @@ +NODE_ENV = production + +# just a flag +ENV = 'deploymenet' + +# base api +VUE_APP_BASE_API = 'http://122.51.199.160:8080/admin' + diff --git a/litemall-admin/.env.development b/litemall-admin/.env.development new file mode 100644 index 0000000000000000000000000000000000000000..0ddbf114b798498f2a4c193f9f3e89a83b93a735 --- /dev/null +++ b/litemall-admin/.env.development @@ -0,0 +1,14 @@ +# just a flag +ENV = 'development' + +# base api +VUE_APP_BASE_API = 'http://localhost:8080/admin' + +# vue-cli uses the VUE_CLI_BABEL_TRANSPILE_MODULES environment variable, +# to control whether the babel-plugin-dynamic-import-node plugin is enabled. +# It only does one thing by converting all import() to require(). +# This configuration can significantly increase the speed of hot updates, +# when you have a large number of pages. +# Detail: https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/babel-preset-app/index.js + +VUE_CLI_BABEL_TRANSPILE_MODULES = true diff --git a/litemall-admin/.env.production b/litemall-admin/.env.production new file mode 100644 index 0000000000000000000000000000000000000000..4c05b967776c4189facc4988bdce4961d87ceee0 --- /dev/null +++ b/litemall-admin/.env.production @@ -0,0 +1,8 @@ +NODE_ENV = production + +# just a flag +ENV = 'production' + +# base api +VUE_APP_BASE_API = 'https://www.example.com/admin' + diff --git a/litemall-admin/.eslintignore b/litemall-admin/.eslintignore index e3a4037e479f58286a5de99b95107183d71ffcf9..e6529fc09c9bf8b29e9ea2999e5c4d717e99d9df 100644 --- a/litemall-admin/.eslintignore +++ b/litemall-admin/.eslintignore @@ -1,3 +1,4 @@ build/*.js -config/*.js src/assets +public +dist diff --git a/litemall-admin/.eslintrc.js b/litemall-admin/.eslintrc.js index 6f55c5a127f1f40408150036b225113bd84aad97..c977505478d92a4975213812eb1f58b981235b2d 100644 --- a/litemall-admin/.eslintrc.js +++ b/litemall-admin/.eslintrc.js @@ -21,7 +21,10 @@ module.exports = { "allowFirstLine": false } }], + "vue/singleline-html-element-content-newline": "off", + "vue/multiline-html-element-content-newline":"off", "vue/name-property-casing": ["error", "PascalCase"], + "vue/no-v-html": "off", 'accessor-pairs': 2, 'arrow-spacing': [2, { 'before': true, @@ -44,7 +47,7 @@ module.exports = { 'curly': [2, 'multi-line'], 'dot-location': [2, 'property'], 'eol-last': 2, - 'eqeqeq': [2, 'allow-null'], + 'eqeqeq': ["error", "always", {"null": "ignore"}], 'generator-star-spacing': [2, { 'before': true, 'after': true diff --git a/litemall-admin/.gitignore b/litemall-admin/.gitignore index 9322b8a68d26ea75436032b257e51b9f58c221c0..78a752d87e8427f84570300f62bdfb5b183a9931 100644 --- a/litemall-admin/.gitignore +++ b/litemall-admin/.gitignore @@ -6,8 +6,8 @@ yarn-debug.log* yarn-error.log* **/*.log -test/unit/coverage -test/e2e/reports +tests/**/coverage/ +tests/e2e/reports selenium-debug.log # Editor directories and files @@ -17,5 +17,7 @@ selenium-debug.log *.ntvs* *.njsproj *.sln +*.local package-lock.json +yarn.lock diff --git a/litemall-admin/.postcssrc.js b/litemall-admin/.postcssrc.js deleted file mode 100644 index eee3e92d7fa6cc132a69a8018b1eb0fa1fdbd56c..0000000000000000000000000000000000000000 --- a/litemall-admin/.postcssrc.js +++ /dev/null @@ -1,10 +0,0 @@ -// https://github.com/michael-ciniawsky/postcss-load-config - -module.exports = { - "plugins": { - "postcss-import": {}, - "postcss-url": {}, - // to edit target browsers: use "browserslist" field in package.json - "autoprefixer": {} - } -} diff --git a/litemall-admin/.travis.yml b/litemall-admin/.travis.yml deleted file mode 100644 index 16574d97ac81cd24ef1b43fdf3aa1c2618ddf70a..0000000000000000000000000000000000000000 --- a/litemall-admin/.travis.yml +++ /dev/null @@ -1,5 +0,0 @@ -language: node_js -node_js: stable -script: npm run test -notifications: - email: false diff --git a/litemall-admin/babel.config.js b/litemall-admin/babel.config.js new file mode 100644 index 0000000000000000000000000000000000000000..ba179669a123909a9728283fd9c004c65adb90c5 --- /dev/null +++ b/litemall-admin/babel.config.js @@ -0,0 +1,5 @@ +module.exports = { + presets: [ + '@vue/app' + ] +} diff --git a/litemall-admin/build/build.js b/litemall-admin/build/build.js deleted file mode 100644 index 34c71a55477f01994273ad393f1d4f82e0977a5d..0000000000000000000000000000000000000000 --- a/litemall-admin/build/build.js +++ /dev/null @@ -1,67 +0,0 @@ -'use strict' -require('./check-versions')() - -const ora = require('ora') -const rm = require('rimraf') -const path = require('path') -const chalk = require('chalk') -const webpack = require('webpack') -const config = require('../config') -const webpackConfig = require('./webpack.prod.conf') -var connect = require('connect') -var serveStatic = require('serve-static') - -const spinner = ora( - 'building for ' + process.env.env_config + ' environment...' -) -spinner.start() - -rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { - if (err) throw err - webpack(webpackConfig, (err, stats) => { - spinner.stop() - if (err) throw err - process.stdout.write( - stats.toString({ - colors: true, - modules: false, - children: false, - chunks: false, - chunkModules: false - }) + '\n\n' - ) - - if (stats.hasErrors()) { - console.log(chalk.red(' Build failed with errors.\n')) - process.exit(1) - } - - console.log(chalk.cyan(' Build complete.\n')) - console.log( - chalk.yellow( - ' Tip: built files are meant to be served over an HTTP server.\n' + - " Opening index.html over file:// won't work.\n" - ) - ) - - if (process.env.npm_config_preview) { - const port = 9526 - const host = 'http://localhost:' + port - const basePath = config.build.assetsPublicPath - const app = connect() - - app.use( - basePath, - serveStatic('./dist', { - index: ['index.html', '/'] - }) - ) - - app.listen(port, function() { - console.log( - chalk.green(`> Listening at http://localhost:${port}${basePath}`) - ) - }) - } - }) -}) diff --git a/litemall-admin/build/check-versions.js b/litemall-admin/build/check-versions.js deleted file mode 100644 index c5c29e90f46753429150bc3ed8a3fd2531f4eea9..0000000000000000000000000000000000000000 --- a/litemall-admin/build/check-versions.js +++ /dev/null @@ -1,64 +0,0 @@ -'use strict' -const chalk = require('chalk') -const semver = require('semver') -const packageConfig = require('../package.json') -const shell = require('shelljs') - -function exec(cmd) { - return require('child_process') - .execSync(cmd) - .toString() - .trim() -} - -const versionRequirements = [ - { - name: 'node', - currentVersion: semver.clean(process.version), - versionRequirement: packageConfig.engines.node - } -] - -if (shell.which('npm')) { - versionRequirements.push({ - name: 'npm', - currentVersion: exec('npm --version'), - versionRequirement: packageConfig.engines.npm - }) -} - -module.exports = function() { - const warnings = [] - - for (let i = 0; i < versionRequirements.length; i++) { - const mod = versionRequirements[i] - - if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { - warnings.push( - mod.name + - ': ' + - chalk.red(mod.currentVersion) + - ' should be ' + - chalk.green(mod.versionRequirement) - ) - } - } - - if (warnings.length) { - console.log('') - console.log( - chalk.yellow( - 'To use this template, you must update following to modules:' - ) - ) - console.log() - - for (let i = 0; i < warnings.length; i++) { - const warning = warnings[i] - console.log(' ' + warning) - } - - console.log() - process.exit(1) - } -} diff --git a/litemall-admin/build/index.js b/litemall-admin/build/index.js new file mode 100644 index 0000000000000000000000000000000000000000..ffa36a212a55b972d8036a14c0730efc89b7f270 --- /dev/null +++ b/litemall-admin/build/index.js @@ -0,0 +1,34 @@ +const { run } = require('runjs') +const chalk = require('chalk') +const rawArgv = process.argv.slice(2) +const args = rawArgv.join(' ') + +if (process.env.npm_config_preview || rawArgv.includes('--preview')) { + const report = rawArgv.includes('--report') + + run(`vue-cli-service build ${args}`) + + const port = 9526 + const publicPath = '/' + + var connect = require('connect') + var serveStatic = require('serve-static') + const app = connect() + + app.use( + publicPath, + serveStatic('./dist', { + index: ['index.html', '/'] + }) + ) + + app.listen(port, function () { + console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`)) + if (report) { + console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`)) + } + + }) +} else { + run(`vue-cli-service build ${args}`) +} \ No newline at end of file diff --git a/litemall-admin/build/logo.png b/litemall-admin/build/logo.png deleted file mode 100644 index f3d2503fc2a44b5053b0837ebea6e87a2d339a43..0000000000000000000000000000000000000000 Binary files a/litemall-admin/build/logo.png and /dev/null differ diff --git a/litemall-admin/build/utils.js b/litemall-admin/build/utils.js deleted file mode 100644 index c96d09364717172fc7b0a5c1f1ca43b595903ba3..0000000000000000000000000000000000000000 --- a/litemall-admin/build/utils.js +++ /dev/null @@ -1,108 +0,0 @@ -'use strict' -const path = require('path') -const config = require('../config') -const MiniCssExtractPlugin = require('mini-css-extract-plugin') -const packageConfig = require('../package.json') - -exports.assetsPath = function(_path) { - const assetsSubDirectory = - process.env.NODE_ENV === 'production' - ? config.build.assetsSubDirectory - : config.dev.assetsSubDirectory - - return path.posix.join(assetsSubDirectory, _path) -} - -exports.cssLoaders = function(options) { - options = options || {} - - const cssLoader = { - loader: 'css-loader', - options: { - sourceMap: options.sourceMap - } - } - - const postcssLoader = { - loader: 'postcss-loader', - options: { - sourceMap: options.sourceMap - } - } - - // generate loader string to be used with extract text plugin - function generateLoaders(loader, loaderOptions) { - const loaders = [] - - // Extract CSS when that option is specified - // (which is the case during production build) - if (options.extract) { - loaders.push(MiniCssExtractPlugin.loader) - } else { - loaders.push('vue-style-loader') - } - - loaders.push(cssLoader) - - if (options.usePostCSS) { - loaders.push(postcssLoader) - } - - if (loader) { - loaders.push({ - loader: loader + '-loader', - options: Object.assign({}, loaderOptions, { - sourceMap: options.sourceMap - }) - }) - } - - return loaders - } - // https://vue-loader.vuejs.org/en/configurations/extract-css.html - return { - css: generateLoaders(), - postcss: generateLoaders(), - less: generateLoaders('less'), - sass: generateLoaders('sass', { - indentedSyntax: true - }), - scss: generateLoaders('sass'), - stylus: generateLoaders('stylus'), - styl: generateLoaders('stylus') - } -} - -// Generate loaders for standalone style files (outside of .vue) -exports.styleLoaders = function(options) { - const output = [] - const loaders = exports.cssLoaders(options) - - for (const extension in loaders) { - const loader = loaders[extension] - output.push({ - test: new RegExp('\\.' + extension + '$'), - use: loader - }) - } - - return output -} - -exports.createNotifierCallback = () => { - const notifier = require('node-notifier') - - return (severity, errors) => { - if (severity !== 'error') return - - const error = errors[0] - const filename = error.file && error.file.split('!').pop() - - notifier.notify({ - title: packageConfig.name, - message: severity + ': ' + error.name, - subtitle: filename || '', - icon: path.join(__dirname, 'logo.png') - }) - } -} diff --git a/litemall-admin/build/vue-loader.conf.js b/litemall-admin/build/vue-loader.conf.js deleted file mode 100644 index 5496c9317fd97830944a169c5428374cf5bdaed3..0000000000000000000000000000000000000000 --- a/litemall-admin/build/vue-loader.conf.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict' - -module.exports = { - //You can set the vue-loader configuration by yourself. -} diff --git a/litemall-admin/build/webpack.base.conf.js b/litemall-admin/build/webpack.base.conf.js deleted file mode 100644 index 3b946b4b24294c2dd2a4793f94030d2172725115..0000000000000000000000000000000000000000 --- a/litemall-admin/build/webpack.base.conf.js +++ /dev/null @@ -1,107 +0,0 @@ -'use strict' -const path = require('path') -const utils = require('./utils') -const config = require('../config') -const { VueLoaderPlugin } = require('vue-loader') -const vueLoaderConfig = require('./vue-loader.conf') - -function resolve(dir) { - return path.join(__dirname, '..', dir) -} - -const createLintingRule = () => ({ - test: /\.(js|vue)$/, - loader: 'eslint-loader', - enforce: 'pre', - include: [resolve('src'), resolve('test')], - options: { - formatter: require('eslint-friendly-formatter'), - emitWarning: !config.dev.showEslintErrorsInOverlay - } -}) - -module.exports = { - context: path.resolve(__dirname, '../'), - entry: { - app: './src/main.js' - }, - output: { - path: config.build.assetsRoot, - filename: '[name].js', - publicPath: - process.env.NODE_ENV === 'production' - ? config.build.assetsPublicPath - : config.dev.assetsPublicPath - }, - resolve: { - extensions: ['.js', '.vue', '.json'], - alias: { - '@': resolve('src') - } - }, - module: { - rules: [ - ...(config.dev.useEslint ? [createLintingRule()] : []), - { - test: /\.vue$/, - loader: 'vue-loader', - options: vueLoaderConfig - }, - { - test: /\.js$/, - loader: 'babel-loader?cacheDirectory', - include: [ - resolve('src'), - resolve('test'), - resolve('node_modules/webpack-dev-server/client') - ] - }, - { - test: /\.svg$/, - loader: 'svg-sprite-loader', - include: [resolve('src/icons')], - options: { - symbolId: 'icon-[name]' - } - }, - { - test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, - loader: 'url-loader', - exclude: [resolve('src/icons')], - options: { - limit: 10000, - name: utils.assetsPath('img/[name].[hash:7].[ext]') - } - }, - { - test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, - loader: 'url-loader', - options: { - limit: 10000, - name: utils.assetsPath('media/[name].[hash:7].[ext]') - } - }, - { - test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, - loader: 'url-loader', - options: { - limit: 10000, - name: utils.assetsPath('fonts/[name].[hash:7].[ext]') - } - } - ] - }, - plugins: [new VueLoaderPlugin()], - node: { - // prevent webpack from injecting useless setImmediate polyfill because Vue - // source contains it (although only uses it if it's native). - setImmediate: false, - // prevent webpack from injecting mocks to Node native modules - // that does not make sense for the client - dgram: 'empty', - fs: 'empty', - net: 'empty', - tls: 'empty', - child_process: 'empty' - } -} diff --git a/litemall-admin/build/webpack.dev.conf.js b/litemall-admin/build/webpack.dev.conf.js deleted file mode 100644 index 26a5584a8683429584aedc7afda14102de4f4423..0000000000000000000000000000000000000000 --- a/litemall-admin/build/webpack.dev.conf.js +++ /dev/null @@ -1,98 +0,0 @@ -'use strict' -const path = require('path') -const utils = require('./utils') -const webpack = require('webpack') -const config = require('../config') -const merge = require('webpack-merge') -const baseWebpackConfig = require('./webpack.base.conf') -const HtmlWebpackPlugin = require('html-webpack-plugin') -const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') -const portfinder = require('portfinder') - -function resolve(dir) { - return path.join(__dirname, '..', dir) -} - -const HOST = process.env.HOST -const PORT = process.env.PORT && Number(process.env.PORT) - -const devWebpackConfig = merge(baseWebpackConfig, { - mode: 'development', - module: { - rules: utils.styleLoaders({ - sourceMap: config.dev.cssSourceMap, - usePostCSS: true - }) - }, - // cheap-module-eval-source-map is faster for development - devtool: config.dev.devtool, - - // these devServer options should be customized in /config/index.js - devServer: { - clientLogLevel: 'warning', - historyApiFallback: true, - hot: true, - compress: true, - host: HOST || config.dev.host, - port: PORT || config.dev.port, - open: config.dev.autoOpenBrowser, - overlay: config.dev.errorOverlay - ? { warnings: false, errors: true } - : false, - publicPath: config.dev.assetsPublicPath, - proxy: config.dev.proxyTable, - quiet: true, // necessary for FriendlyErrorsPlugin - watchOptions: { - poll: config.dev.poll - } - }, - plugins: [ - new webpack.DefinePlugin({ - 'process.env': require('../config/dev.env') - }), - new webpack.HotModuleReplacementPlugin(), - // https://github.com/ampedandwired/html-webpack-plugin - new HtmlWebpackPlugin({ - filename: 'index.html', - template: 'index.html', - inject: true, - favicon: resolve('favicon.ico'), - title: 'vue-element-admin', - templateParameters: { - BASE_URL: config.dev.assetsPublicPath + config.dev.assetsSubDirectory, - }, - }), - ] -}) - -module.exports = new Promise((resolve, reject) => { - portfinder.basePort = process.env.PORT || config.dev.port - portfinder.getPort((err, port) => { - if (err) { - reject(err) - } else { - // publish the new Port, necessary for e2e tests - process.env.PORT = port - // add port to devServer config - devWebpackConfig.devServer.port = port - - // Add FriendlyErrorsPlugin - devWebpackConfig.plugins.push( - new FriendlyErrorsPlugin({ - compilationSuccessInfo: { - messages: [ - `Your application is running here: http://${ - devWebpackConfig.devServer.host - }:${port}` - ] - }, - onErrors: config.dev.notifyOnErrors - ? utils.createNotifierCallback() - : undefined - }) - ) - - resolve(devWebpackConfig) - } - }) -}) diff --git a/litemall-admin/build/webpack.prod.conf.js b/litemall-admin/build/webpack.prod.conf.js deleted file mode 100644 index 946a134a55a897250b609b70a3531c646a5d4173..0000000000000000000000000000000000000000 --- a/litemall-admin/build/webpack.prod.conf.js +++ /dev/null @@ -1,188 +0,0 @@ -'use strict' -const path = require('path') -const utils = require('./utils') -const webpack = require('webpack') -const config = require('../config') -const merge = require('webpack-merge') -const baseWebpackConfig = require('./webpack.base.conf') -const CopyWebpackPlugin = require('copy-webpack-plugin') -const HtmlWebpackPlugin = require('html-webpack-plugin') -const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin') -const MiniCssExtractPlugin = require('mini-css-extract-plugin') -const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin') -const UglifyJsPlugin = require('uglifyjs-webpack-plugin') - -function resolve(dir) { - return path.join(__dirname, '..', dir) -} - -const env = require('../config/' + process.env.env_config + '.env') - -// For NamedChunksPlugin -const seen = new Set() -const nameLength = 4 - -const webpackConfig = merge(baseWebpackConfig, { - mode: 'production', - module: { - rules: utils.styleLoaders({ - sourceMap: config.build.productionSourceMap, - extract: true, - usePostCSS: true - }) - }, - devtool: config.build.productionSourceMap ? config.build.devtool : false, - output: { - path: config.build.assetsRoot, - filename: utils.assetsPath('js/[name].[chunkhash:8].js'), - chunkFilename: utils.assetsPath('js/[name].[chunkhash:8].js') - }, - plugins: [ - // http://vuejs.github.io/vue-loader/en/workflow/production.html - new webpack.DefinePlugin({ - 'process.env': env - }), - // extract css into its own file - new MiniCssExtractPlugin({ - filename: utils.assetsPath('css/[name].[contenthash:8].css'), - chunkFilename: utils.assetsPath('css/[name].[contenthash:8].css') - }), - // generate dist index.html with correct asset hash for caching. - // you can customize output by editing /index.html - // see https://github.com/ampedandwired/html-webpack-plugin - new HtmlWebpackPlugin({ - filename: config.build.index, - template: 'index.html', - inject: true, - favicon: resolve('favicon.ico'), - title: 'vue-element-admin', - templateParameters: { - BASE_URL: config.build.assetsPublicPath + config.build.assetsSubDirectory, - }, - minify: { - removeComments: true, - collapseWhitespace: true, - removeAttributeQuotes: true - // more options: - // https://github.com/kangax/html-minifier#options-quick-reference - } - // default sort mode uses toposort which cannot handle cyclic deps - // in certain cases, and in webpack 4, chunk order in HTML doesn't - // matter anyway - }), - new ScriptExtHtmlWebpackPlugin({ - //`runtime` must same as runtimeChunk name. default is `runtime` - inline: /runtime\..*\.js$/ - }), - // keep chunk.id stable when chunk has no name - new webpack.NamedChunksPlugin(chunk => { - if (chunk.name) { - return chunk.name - } - const modules = Array.from(chunk.modulesIterable) - if (modules.length > 1) { - const hash = require('hash-sum') - const joinedHash = hash(modules.map(m => m.id).join('_')) - let len = nameLength - while (seen.has(joinedHash.substr(0, len))) len++ - seen.add(joinedHash.substr(0, len)) - return `chunk-${joinedHash.substr(0, len)}` - } else { - return modules[0].id - } - }), - // keep module.id stable when vender modules does not change - new webpack.HashedModuleIdsPlugin(), - // copy custom static assets - new CopyWebpackPlugin([ - { - from: path.resolve(__dirname, '../static'), - to: config.build.assetsSubDirectory, - ignore: ['.*'] - } - ]) - ], - optimization: { - splitChunks: { - chunks: 'all', - cacheGroups: { - libs: { - name: 'chunk-libs', - test: /[\\/]node_modules[\\/]/, - priority: 10, - chunks: 'initial' // 只打包初始时依赖的第三方 - }, - elementUI: { - name: 'chunk-elementUI', // 单独将 elementUI 拆包 - priority: 20, // 权重要大于 libs 和 app 不然会被打包进 libs 或者 app - test: /[\\/]node_modules[\\/]element-ui[\\/]/ - }, - commons: { - name: 'chunk-commons', - test: resolve('src/components'), // 可自定义拓展你的规则 - minChunks: 3, // 最小公用次数 - priority: 5, - reuseExistingChunk: true - } - } - }, - runtimeChunk: 'single', - minimizer: [ - new UglifyJsPlugin({ - uglifyOptions: { - mangle: { - safari10: true - } - }, - sourceMap: config.build.productionSourceMap, - cache: true, - parallel: true - }), - // Compress extracted CSS. We are using this plugin so that possible - // duplicated CSS from different components can be deduped. - new OptimizeCSSAssetsPlugin() - ] - } -}) - -if (config.build.productionGzip) { - const CompressionWebpackPlugin = require('compression-webpack-plugin') - - webpackConfig.plugins.push( - new CompressionWebpackPlugin({ - asset: '[path].gz[query]', - algorithm: 'gzip', - test: new RegExp( - '\\.(' + config.build.productionGzipExtensions.join('|') + ')$' - ), - threshold: 10240, - minRatio: 0.8 - }) - ) -} - -if (config.build.generateAnalyzerReport || config.build.bundleAnalyzerReport) { - const BundleAnalyzerPlugin = require('webpack-bundle-analyzer') - .BundleAnalyzerPlugin - - if (config.build.bundleAnalyzerReport) { - webpackConfig.plugins.push( - new BundleAnalyzerPlugin({ - analyzerPort: 8080, - generateStatsFile: false - }) - ) - } - - if (config.build.generateAnalyzerReport) { - webpackConfig.plugins.push( - new BundleAnalyzerPlugin({ - analyzerMode: 'static', - reportFilename: 'bundle-report.html', - openAnalyzer: false - }) - ) - } -} - -module.exports = webpackConfig diff --git a/litemall-admin/config/dep.env.js b/litemall-admin/config/dep.env.js deleted file mode 100644 index 4f61bd0c2fab4c699f83d2d1de3871ffcbdf1d77..0000000000000000000000000000000000000000 --- a/litemall-admin/config/dep.env.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - NODE_ENV: '"production"', - ENV_CONFIG: '"dep"', - BASE_API: '"http://122.152.206.172:8080/admin"' -} diff --git a/litemall-admin/config/dev.env.js b/litemall-admin/config/dev.env.js deleted file mode 100644 index 94da14592ec681d0cd592b4800a1c5f6318231d3..0000000000000000000000000000000000000000 --- a/litemall-admin/config/dev.env.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - NODE_ENV: '"development"', - ENV_CONFIG: '"dev"', - BASE_API: '"http://localhost:8080/admin"' -} diff --git a/litemall-admin/config/index.js b/litemall-admin/config/index.js deleted file mode 100644 index 599e4a63471fd239ae242bb9fe3c2a704a155b3b..0000000000000000000000000000000000000000 --- a/litemall-admin/config/index.js +++ /dev/null @@ -1,88 +0,0 @@ -'use strict' -// Template version: 1.2.6 -// see http://vuejs-templates.github.io/webpack for documentation. - -const path = require('path') - -module.exports = { - dev: { - // Paths - assetsSubDirectory: 'static', - assetsPublicPath: '/', - proxyTable: {}, - - // Various Dev Server settings - - // can be overwritten by process.env.HOST - // if you want dev by ip, please set host: '0.0.0.0' - host: 'localhost', - port: 9527, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined - autoOpenBrowser: true, - errorOverlay: true, - notifyOnErrors: false, - poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- - - // Use Eslint Loader? - // If true, your code will be linted during bundling and - // linting errors and warnings will be shown in the console. - useEslint: true, - // If true, eslint errors and warnings will also be shown in the error overlay - // in the browser. - showEslintErrorsInOverlay: false, - - /** - * Source Maps - */ - - // https://webpack.js.org/configuration/devtool/#development - devtool: 'cheap-source-map', - - // CSS Sourcemaps off by default because relative paths are "buggy" - // with this option, according to the CSS-Loader README - // (https://github.com/webpack/css-loader#sourcemaps) - // In our experience, they generally work as expected, - // just be aware of this issue when enabling this option. - cssSourceMap: false - }, - - build: { - // Template for index.html - index: path.resolve(__dirname, '../dist/index.html'), - - // Paths - assetsRoot: path.resolve(__dirname, '../dist'), - assetsSubDirectory: 'static', - - /** - * You can set by youself according to actual condition - * You will need to set this if you plan to deploy your site under a sub path, - * for example GitHub pages. If you plan to deploy your site to https://foo.github.io/bar/, - * then assetsPublicPath should be set to "/bar/". - * In most cases please use '/' !!! - */ - assetsPublicPath: '/', - - /** - * Source Maps - */ - productionSourceMap: false, - // https://webpack.js.org/configuration/devtool/#production - devtool: 'source-map', - - // Gzip off by default as many popular static hosts such as - // Surge or Netlify already gzip all static assets for you. - // Before setting to `true`, make sure to: - // npm install --save-dev compression-webpack-plugin - productionGzip: false, - productionGzipExtensions: ['js', 'css'], - - // Run the build command with an extra argument to - // View the bundle analyzer report after build finishes: - // `npm run build:prod --report` - // Set to `true` or `false` to always turn it on or off - bundleAnalyzerReport: process.env.npm_config_report || false, - - // `npm run build:prod --generate_report` - generateAnalyzerReport: process.env.npm_config_generate_report || false - } -} diff --git a/litemall-admin/config/prod.env.js b/litemall-admin/config/prod.env.js deleted file mode 100644 index 7477e14b9894dc73acca9aed8fcd48b346a60da0..0000000000000000000000000000000000000000 --- a/litemall-admin/config/prod.env.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - NODE_ENV: '"production"', - ENV_CONFIG: '"prod"', - BASE_API: '"https://www.example.com/admin"' -} diff --git a/litemall-admin/jest.config.js b/litemall-admin/jest.config.js new file mode 100644 index 0000000000000000000000000000000000000000..143cdc868cbc529ca55eff89bbc0dc3f23fba65d --- /dev/null +++ b/litemall-admin/jest.config.js @@ -0,0 +1,24 @@ +module.exports = { + moduleFileExtensions: ['js', 'jsx', 'json', 'vue'], + transform: { + '^.+\\.vue$': 'vue-jest', + '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': + 'jest-transform-stub', + '^.+\\.jsx?$': 'babel-jest' + }, + moduleNameMapper: { + '^@/(.*)$': '/src/$1' + }, + snapshotSerializers: ['jest-serializer-vue'], + testMatch: [ + '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' + ], + collectCoverageFrom: ['src/utils/**/*.{js,vue}', '!src/utils/auth.js', '!src/utils/request.js', 'src/components/**/*.{js,vue}'], + coverageDirectory: '/tests/unit/coverage', + // 'collectCoverage': true, + 'coverageReporters': [ + 'lcov', + 'text-summary' + ], + testURL: 'http://localhost/' +} diff --git a/litemall-admin/jsconfig.json b/litemall-admin/jsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..958df0467f1fbdde303bf11f08e3ba9a4de3078a --- /dev/null +++ b/litemall-admin/jsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "baseUrl": "./", + "paths": { + "@/*": ["src/*"] + } + }, + "exclude": ["node_modules", "dist"] +} \ No newline at end of file diff --git a/litemall-admin/package.json b/litemall-admin/package.json index a3f4a1dedbdd3ff43288e28a7f8522a1258ff813..ba0a1f8444527b1cc1e55aabaf977154985fed40 100644 --- a/litemall-admin/package.json +++ b/litemall-admin/package.json @@ -1,111 +1,104 @@ { "name": "litemall-admin", - "version": "0.1.0", - "description": "litemall-admin basing on vue-element-admin 3.9.3", + "version": "1.0.0", + "description": "litemall-admin basing on vue-element-admin 4.2.1", "author": "linlinjava ", "license": "MIT", - "private": true, "scripts": { - "dev": "cross-env BABEL_ENV=development webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", - "build:dep": "cross-env NODE_ENV=production env_config=dep node build/build.js", - "build:prod": "cross-env NODE_ENV=production env_config=prod node build/build.js", + "dev": "vue-cli-service serve", + "build": "vue-cli-service build --mode production", + "build:prod": "vue-cli-service build --mode production", + "build:dep": "vue-cli-service build --mode deployment", + "preview": "node build/index.js --preview", "lint": "eslint --ext .js,.vue src", - "test": "npm run lint", - "precommit": "lint-staged", + "test:unit": "jest --clearCache && vue-cli-service test:unit", + "test:ci": "npm run lint && npm run test:unit", "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml" }, + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, "lint-staged": { "src/**/*.{js,vue}": [ "eslint --fix", "git add" ] }, + "keywords": [ + "vue", + "admin", + "dashboard", + "element-ui", + "boilerplate", + "admin-template", + "management-system" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/linlinjava/litemall.git" + }, + "bugs": { + "url": "https://github.com/linlinjava/litemall/issues" + }, "dependencies": { - "@tinymce/tinymce-vue": "1.1.0", - "@riophae/vue-treeselect": "0.0.37", + "@tinymce/tinymce-vue": "3.0.1", + "lodash": "^4.17.11", "v-charts": "1.19.0", - "axios": "0.18.0", - "clipboard": "1.7.1", + "axios": "0.18.1", + "clipboard": "2.0.4", "connect": "3.6.6", - "echarts": "4.1.0", - "element-ui": "2.4.6", + "echarts": "4.2.1", + "element-ui": "2.12.0", "file-saver": "1.3.8", - "font-awesome": "4.7.0", "js-cookie": "2.2.0", - "jszip": "3.1.5", "normalize.css": "7.0.0", "nprogress": "0.2.0", - "screenfull": "3.3.3", - "vue": "2.5.17", + "path-to-regexp": "2.4.0", + "screenfull": "4.2.0", + "vue": "2.6.10", "vue-count-to": "1.0.13", - "vue-i18n": "7.3.2", - "vue-router": "3.0.1", - "vuex": "3.0.1", - "xlsx": "^0.11.16" + "vue-router": "3.0.2", + "vuex": "3.1.0", + "xlsx": "0.14.1" }, "devDependencies": { - "autoprefixer": "8.5.0", - "babel-core": "6.26.3", - "babel-eslint": "8.2.6", - "babel-helper-vue-jsx-merge-props": "2.0.3", - "babel-loader": "7.1.5", - "babel-plugin-dynamic-import-node": "2.0.0", - "babel-plugin-syntax-jsx": "6.18.0", - "babel-plugin-transform-runtime": "6.23.0", - "babel-plugin-transform-vue-jsx": "3.7.0", - "babel-preset-env": "1.7.0", - "babel-preset-stage-2": "6.24.1", - "chalk": "2.4.1", - "copy-webpack-plugin": "4.5.2", - "cross-env": "5.2.0", - "css-loader": "1.0.0", - "eslint": "4.19.1", - "eslint-friendly-formatter": "4.0.1", - "eslint-loader": "2.0.0", - "eslint-plugin-vue": "4.7.1", - "file-loader": "1.1.11", - "friendly-errors-webpack-plugin": "1.7.0", - "hash-sum": "1.0.2", - "html-webpack-plugin": "4.0.0-alpha", - "husky": "0.14.3", - "lint-staged": "7.2.2", - "mini-css-extract-plugin": "0.4.1", - "node-notifier": "5.2.1", - "node-sass": "^4.7.2", - "optimize-css-assets-webpack-plugin": "5.0.0", - "ora": "3.0.0", - "path-to-regexp": "2.4.0", - "portfinder": "1.0.13", - "postcss-import": "11.1.0", - "postcss-loader": "2.1.6", - "postcss-url": "7.3.2", - "rimraf": "2.6.2", - "sass-loader": "7.0.3", - "script-ext-html-webpack-plugin": "2.0.1", + "@babel/core": "7.0.0", + "@babel/register": "7.0.0", + "@vue/cli-plugin-babel": "3.5.3", + "@vue/cli-plugin-eslint": "^3.9.1", + "@vue/cli-plugin-unit-jest": "3.5.3", + "@vue/cli-service": "3.5.3", + "@vue/test-utils": "1.0.0-beta.29", + "autoprefixer": "^9.5.1", + "babel-core": "7.0.0-bridge.0", + "babel-eslint": "10.0.1", + "babel-jest": "23.6.0", + "chalk": "2.4.2", + "chokidar": "2.1.5", + "connect": "3.6.6", + "eslint": "5.15.3", + "eslint-plugin-vue": "5.2.2", + "html-webpack-plugin": "3.2.0", + "husky": "1.3.1", + "lint-staged": "8.1.5", + "node-sass": "^4.9.0", + "runjs": "^4.3.2", + "sass-loader": "^7.1.0", + "script-ext-html-webpack-plugin": "2.1.3", "script-loader": "0.7.2", - "semver": "5.5.0", - "serve-static": "1.13.2", - "shelljs": "0.8.2", - "svg-sprite-loader": "3.8.0", - "svgo": "1.0.5", - "uglifyjs-webpack-plugin": "1.2.7", - "url-loader": "1.0.1", - "vue-loader": "15.3.0", - "vue-style-loader": "4.1.2", - "vue-template-compiler": "2.5.17", - "webpack": "4.16.5", - "webpack-bundle-analyzer": "2.13.1", - "webpack-cli": "3.1.0", - "webpack-dev-server": "3.1.14", - "webpack-merge": "4.1.4" + "serve-static": "^1.13.2", + "svg-sprite-loader": "4.1.3", + "svgo": "1.2.0", + "vue-template-compiler": "2.6.10" }, "engines": { - "node": ">= 6.0.0", + "node": ">=8.9", "npm": ">= 3.0.0" }, "browserslist": [ "> 1%", - "last 2 versions", - "not ie <= 8" + "last 2 versions" ] } diff --git a/litemall-admin/postcss.config.js b/litemall-admin/postcss.config.js new file mode 100644 index 0000000000000000000000000000000000000000..961986e2b11eeebe1d4ddabdf2e6c85e2a2562e0 --- /dev/null +++ b/litemall-admin/postcss.config.js @@ -0,0 +1,5 @@ +module.exports = { + plugins: { + autoprefixer: {} + } +} diff --git a/litemall-admin/favicon.ico b/litemall-admin/public/favicon.ico similarity index 100% rename from litemall-admin/favicon.ico rename to litemall-admin/public/favicon.ico diff --git a/litemall-admin/index.html b/litemall-admin/public/index.html similarity index 81% rename from litemall-admin/index.html rename to litemall-admin/public/index.html index b2af159180030dfa1b4d2d5434da0453091d03dc..82b1140ba06581e71a32b840e4415db019146017 100644 --- a/litemall-admin/index.html +++ b/litemall-admin/public/index.html @@ -5,7 +5,8 @@ - litemall-admin + + <%= webpackConfig.name %> diff --git a/litemall-admin/static/tinymce4.7.5/langs/zh_CN.js b/litemall-admin/public/tinymce4.7.5/langs/zh_CN.js similarity index 100% rename from litemall-admin/static/tinymce4.7.5/langs/zh_CN.js rename to litemall-admin/public/tinymce4.7.5/langs/zh_CN.js diff --git a/litemall-admin/static/tinymce4.7.5/plugins/codesample/css/prism.css b/litemall-admin/public/tinymce4.7.5/plugins/codesample/css/prism.css similarity index 100% rename from litemall-admin/static/tinymce4.7.5/plugins/codesample/css/prism.css rename to litemall-admin/public/tinymce4.7.5/plugins/codesample/css/prism.css diff --git a/litemall-admin/static/tinymce4.7.5/plugins/emoticons/img/smiley-cool.gif b/litemall-admin/public/tinymce4.7.5/plugins/emoticons/img/smiley-cool.gif similarity index 100% rename from litemall-admin/static/tinymce4.7.5/plugins/emoticons/img/smiley-cool.gif rename to litemall-admin/public/tinymce4.7.5/plugins/emoticons/img/smiley-cool.gif diff --git a/litemall-admin/static/tinymce4.7.5/plugins/emoticons/img/smiley-cry.gif b/litemall-admin/public/tinymce4.7.5/plugins/emoticons/img/smiley-cry.gif similarity index 100% rename from litemall-admin/static/tinymce4.7.5/plugins/emoticons/img/smiley-cry.gif rename to litemall-admin/public/tinymce4.7.5/plugins/emoticons/img/smiley-cry.gif diff --git a/litemall-admin/static/tinymce4.7.5/plugins/emoticons/img/smiley-embarassed.gif b/litemall-admin/public/tinymce4.7.5/plugins/emoticons/img/smiley-embarassed.gif similarity index 100% rename from litemall-admin/static/tinymce4.7.5/plugins/emoticons/img/smiley-embarassed.gif rename to litemall-admin/public/tinymce4.7.5/plugins/emoticons/img/smiley-embarassed.gif diff --git a/litemall-admin/static/tinymce4.7.5/plugins/emoticons/img/smiley-foot-in-mouth.gif b/litemall-admin/public/tinymce4.7.5/plugins/emoticons/img/smiley-foot-in-mouth.gif similarity index 100% rename from litemall-admin/static/tinymce4.7.5/plugins/emoticons/img/smiley-foot-in-mouth.gif rename to litemall-admin/public/tinymce4.7.5/plugins/emoticons/img/smiley-foot-in-mouth.gif diff --git a/litemall-admin/static/tinymce4.7.5/plugins/emoticons/img/smiley-frown.gif b/litemall-admin/public/tinymce4.7.5/plugins/emoticons/img/smiley-frown.gif similarity index 100% rename from litemall-admin/static/tinymce4.7.5/plugins/emoticons/img/smiley-frown.gif rename to litemall-admin/public/tinymce4.7.5/plugins/emoticons/img/smiley-frown.gif diff --git a/litemall-admin/static/tinymce4.7.5/plugins/emoticons/img/smiley-innocent.gif b/litemall-admin/public/tinymce4.7.5/plugins/emoticons/img/smiley-innocent.gif similarity index 100% rename from litemall-admin/static/tinymce4.7.5/plugins/emoticons/img/smiley-innocent.gif rename to litemall-admin/public/tinymce4.7.5/plugins/emoticons/img/smiley-innocent.gif diff --git a/litemall-admin/static/tinymce4.7.5/plugins/emoticons/img/smiley-kiss.gif b/litemall-admin/public/tinymce4.7.5/plugins/emoticons/img/smiley-kiss.gif similarity index 100% rename from litemall-admin/static/tinymce4.7.5/plugins/emoticons/img/smiley-kiss.gif rename to litemall-admin/public/tinymce4.7.5/plugins/emoticons/img/smiley-kiss.gif diff --git a/litemall-admin/static/tinymce4.7.5/plugins/emoticons/img/smiley-laughing.gif b/litemall-admin/public/tinymce4.7.5/plugins/emoticons/img/smiley-laughing.gif similarity index 100% rename from litemall-admin/static/tinymce4.7.5/plugins/emoticons/img/smiley-laughing.gif rename to litemall-admin/public/tinymce4.7.5/plugins/emoticons/img/smiley-laughing.gif diff --git a/litemall-admin/static/tinymce4.7.5/plugins/emoticons/img/smiley-money-mouth.gif b/litemall-admin/public/tinymce4.7.5/plugins/emoticons/img/smiley-money-mouth.gif similarity index 100% rename from litemall-admin/static/tinymce4.7.5/plugins/emoticons/img/smiley-money-mouth.gif rename to litemall-admin/public/tinymce4.7.5/plugins/emoticons/img/smiley-money-mouth.gif diff --git a/litemall-admin/static/tinymce4.7.5/plugins/emoticons/img/smiley-sealed.gif b/litemall-admin/public/tinymce4.7.5/plugins/emoticons/img/smiley-sealed.gif similarity index 100% rename from litemall-admin/static/tinymce4.7.5/plugins/emoticons/img/smiley-sealed.gif rename to litemall-admin/public/tinymce4.7.5/plugins/emoticons/img/smiley-sealed.gif diff --git a/litemall-admin/static/tinymce4.7.5/plugins/emoticons/img/smiley-smile.gif b/litemall-admin/public/tinymce4.7.5/plugins/emoticons/img/smiley-smile.gif similarity index 100% rename from litemall-admin/static/tinymce4.7.5/plugins/emoticons/img/smiley-smile.gif rename to litemall-admin/public/tinymce4.7.5/plugins/emoticons/img/smiley-smile.gif diff --git a/litemall-admin/static/tinymce4.7.5/plugins/emoticons/img/smiley-surprised.gif b/litemall-admin/public/tinymce4.7.5/plugins/emoticons/img/smiley-surprised.gif similarity index 100% rename from litemall-admin/static/tinymce4.7.5/plugins/emoticons/img/smiley-surprised.gif rename to litemall-admin/public/tinymce4.7.5/plugins/emoticons/img/smiley-surprised.gif diff --git a/litemall-admin/static/tinymce4.7.5/plugins/emoticons/img/smiley-tongue-out.gif b/litemall-admin/public/tinymce4.7.5/plugins/emoticons/img/smiley-tongue-out.gif similarity index 100% rename from litemall-admin/static/tinymce4.7.5/plugins/emoticons/img/smiley-tongue-out.gif rename to litemall-admin/public/tinymce4.7.5/plugins/emoticons/img/smiley-tongue-out.gif diff --git a/litemall-admin/static/tinymce4.7.5/plugins/emoticons/img/smiley-undecided.gif b/litemall-admin/public/tinymce4.7.5/plugins/emoticons/img/smiley-undecided.gif similarity index 100% rename from litemall-admin/static/tinymce4.7.5/plugins/emoticons/img/smiley-undecided.gif rename to litemall-admin/public/tinymce4.7.5/plugins/emoticons/img/smiley-undecided.gif diff --git a/litemall-admin/static/tinymce4.7.5/plugins/emoticons/img/smiley-wink.gif b/litemall-admin/public/tinymce4.7.5/plugins/emoticons/img/smiley-wink.gif similarity index 100% rename from litemall-admin/static/tinymce4.7.5/plugins/emoticons/img/smiley-wink.gif rename to litemall-admin/public/tinymce4.7.5/plugins/emoticons/img/smiley-wink.gif diff --git a/litemall-admin/static/tinymce4.7.5/plugins/emoticons/img/smiley-yell.gif b/litemall-admin/public/tinymce4.7.5/plugins/emoticons/img/smiley-yell.gif similarity index 100% rename from litemall-admin/static/tinymce4.7.5/plugins/emoticons/img/smiley-yell.gif rename to litemall-admin/public/tinymce4.7.5/plugins/emoticons/img/smiley-yell.gif diff --git a/litemall-admin/static/tinymce4.7.5/plugins/visualblocks/css/visualblocks.css b/litemall-admin/public/tinymce4.7.5/plugins/visualblocks/css/visualblocks.css similarity index 100% rename from litemall-admin/static/tinymce4.7.5/plugins/visualblocks/css/visualblocks.css rename to litemall-admin/public/tinymce4.7.5/plugins/visualblocks/css/visualblocks.css diff --git a/litemall-admin/static/tinymce4.7.5/skins/lightgray/content.inline.min.css b/litemall-admin/public/tinymce4.7.5/skins/lightgray/content.inline.min.css similarity index 100% rename from litemall-admin/static/tinymce4.7.5/skins/lightgray/content.inline.min.css rename to litemall-admin/public/tinymce4.7.5/skins/lightgray/content.inline.min.css diff --git a/litemall-admin/static/tinymce4.7.5/skins/lightgray/content.min.css b/litemall-admin/public/tinymce4.7.5/skins/lightgray/content.min.css similarity index 100% rename from litemall-admin/static/tinymce4.7.5/skins/lightgray/content.min.css rename to litemall-admin/public/tinymce4.7.5/skins/lightgray/content.min.css diff --git a/litemall-admin/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-mobile.woff b/litemall-admin/public/tinymce4.7.5/skins/lightgray/fonts/tinymce-mobile.woff similarity index 100% rename from litemall-admin/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-mobile.woff rename to litemall-admin/public/tinymce4.7.5/skins/lightgray/fonts/tinymce-mobile.woff diff --git a/litemall-admin/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.eot b/litemall-admin/public/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.eot similarity index 100% rename from litemall-admin/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.eot rename to litemall-admin/public/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.eot diff --git a/litemall-admin/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.svg b/litemall-admin/public/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.svg similarity index 100% rename from litemall-admin/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.svg rename to litemall-admin/public/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.svg diff --git a/litemall-admin/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.ttf b/litemall-admin/public/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.ttf similarity index 100% rename from litemall-admin/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.ttf rename to litemall-admin/public/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.ttf diff --git a/litemall-admin/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.woff b/litemall-admin/public/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.woff similarity index 100% rename from litemall-admin/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.woff rename to litemall-admin/public/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.woff diff --git a/litemall-admin/static/tinymce4.7.5/skins/lightgray/fonts/tinymce.eot b/litemall-admin/public/tinymce4.7.5/skins/lightgray/fonts/tinymce.eot similarity index 100% rename from litemall-admin/static/tinymce4.7.5/skins/lightgray/fonts/tinymce.eot rename to litemall-admin/public/tinymce4.7.5/skins/lightgray/fonts/tinymce.eot diff --git a/litemall-admin/static/tinymce4.7.5/skins/lightgray/fonts/tinymce.svg b/litemall-admin/public/tinymce4.7.5/skins/lightgray/fonts/tinymce.svg similarity index 100% rename from litemall-admin/static/tinymce4.7.5/skins/lightgray/fonts/tinymce.svg rename to litemall-admin/public/tinymce4.7.5/skins/lightgray/fonts/tinymce.svg diff --git a/litemall-admin/static/tinymce4.7.5/skins/lightgray/fonts/tinymce.ttf b/litemall-admin/public/tinymce4.7.5/skins/lightgray/fonts/tinymce.ttf similarity index 100% rename from litemall-admin/static/tinymce4.7.5/skins/lightgray/fonts/tinymce.ttf rename to litemall-admin/public/tinymce4.7.5/skins/lightgray/fonts/tinymce.ttf diff --git a/litemall-admin/static/tinymce4.7.5/skins/lightgray/fonts/tinymce.woff b/litemall-admin/public/tinymce4.7.5/skins/lightgray/fonts/tinymce.woff similarity index 100% rename from litemall-admin/static/tinymce4.7.5/skins/lightgray/fonts/tinymce.woff rename to litemall-admin/public/tinymce4.7.5/skins/lightgray/fonts/tinymce.woff diff --git a/litemall-admin/static/tinymce4.7.5/skins/lightgray/img/anchor.gif b/litemall-admin/public/tinymce4.7.5/skins/lightgray/img/anchor.gif similarity index 100% rename from litemall-admin/static/tinymce4.7.5/skins/lightgray/img/anchor.gif rename to litemall-admin/public/tinymce4.7.5/skins/lightgray/img/anchor.gif diff --git a/litemall-admin/static/tinymce4.7.5/skins/lightgray/img/loader.gif b/litemall-admin/public/tinymce4.7.5/skins/lightgray/img/loader.gif similarity index 100% rename from litemall-admin/static/tinymce4.7.5/skins/lightgray/img/loader.gif rename to litemall-admin/public/tinymce4.7.5/skins/lightgray/img/loader.gif diff --git a/litemall-admin/static/tinymce4.7.5/skins/lightgray/img/object.gif b/litemall-admin/public/tinymce4.7.5/skins/lightgray/img/object.gif similarity index 100% rename from litemall-admin/static/tinymce4.7.5/skins/lightgray/img/object.gif rename to litemall-admin/public/tinymce4.7.5/skins/lightgray/img/object.gif diff --git a/litemall-admin/static/tinymce4.7.5/skins/lightgray/img/trans.gif b/litemall-admin/public/tinymce4.7.5/skins/lightgray/img/trans.gif similarity index 100% rename from litemall-admin/static/tinymce4.7.5/skins/lightgray/img/trans.gif rename to litemall-admin/public/tinymce4.7.5/skins/lightgray/img/trans.gif diff --git a/litemall-admin/static/tinymce4.7.5/skins/lightgray/skin.min.css b/litemall-admin/public/tinymce4.7.5/skins/lightgray/skin.min.css similarity index 100% rename from litemall-admin/static/tinymce4.7.5/skins/lightgray/skin.min.css rename to litemall-admin/public/tinymce4.7.5/skins/lightgray/skin.min.css diff --git a/litemall-admin/static/tinymce4.7.5/skins/lightgray/skin.min.css.map b/litemall-admin/public/tinymce4.7.5/skins/lightgray/skin.min.css.map similarity index 100% rename from litemall-admin/static/tinymce4.7.5/skins/lightgray/skin.min.css.map rename to litemall-admin/public/tinymce4.7.5/skins/lightgray/skin.min.css.map diff --git a/litemall-admin/static/tinymce4.7.5/tinymce.min.js b/litemall-admin/public/tinymce4.7.5/tinymce.min.js similarity index 100% rename from litemall-admin/static/tinymce4.7.5/tinymce.min.js rename to litemall-admin/public/tinymce4.7.5/tinymce.min.js diff --git a/litemall-admin/src/api/aftersale.js b/litemall-admin/src/api/aftersale.js new file mode 100644 index 0000000000000000000000000000000000000000..11e4a200a78d05faa0265a22f8292f6a995d9f1b --- /dev/null +++ b/litemall-admin/src/api/aftersale.js @@ -0,0 +1,49 @@ +import request from '@/utils/request' + +export function listAftersale(query) { + return request({ + url: '/aftersale/list', + method: 'get', + params: query + }) +} + +export function receptAftersale(data) { + return request({ + url: '/aftersale/recept', + method: 'post', + data + }) +} + +export function batchReceptAftersale(data) { + return request({ + url: '/aftersale/batch-recept', + method: 'post', + data + }) +} + +export function rejectAftersale(data) { + return request({ + url: '/aftersale/reject', + method: 'post', + data + }) +} + +export function batchRejectAftersale(data) { + return request({ + url: '/aftersale/batch-reject', + method: 'post', + data + }) +} + +export function refundAftersale(data) { + return request({ + url: '/aftersale/refund', + method: 'post', + data + }) +} diff --git a/litemall-admin/src/api/config.js b/litemall-admin/src/api/config.js new file mode 100644 index 0000000000000000000000000000000000000000..2b129b84ec8ec5f9075f812db477cf3102301eb2 --- /dev/null +++ b/litemall-admin/src/api/config.js @@ -0,0 +1,61 @@ +import request from '@/utils/request' + +export function listMall() { + return request({ + url: '/config/mall', + method: 'get' + }) +} + +export function updateMall(data) { + return request({ + url: '/config/mall', + method: 'post', + data + }) +} + +export function listExpress() { + return request({ + url: '/config/express', + method: 'get' + }) +} + +export function updateExpress(data) { + return request({ + url: '/config/express', + method: 'post', + data + }) +} + +export function listOrder() { + return request({ + url: '/config/order', + method: 'get' + }) +} + +export function updateOrder(data) { + return request({ + url: '/config/order', + method: 'post', + data + }) +} + +export function listWx() { + return request({ + url: '/config/wx', + method: 'get' + }) +} + +export function updateWx(data) { + return request({ + url: '/config/wx', + method: 'post', + data + }) +} diff --git a/litemall-admin/src/api/qiniu.js b/litemall-admin/src/api/log.js similarity index 38% rename from litemall-admin/src/api/qiniu.js rename to litemall-admin/src/api/log.js index a0375844f37340f25a779b3e9dfbeefca5e655d4..0b69a8fb47783d024d8e7b115757a71cae316425 100644 --- a/litemall-admin/src/api/qiniu.js +++ b/litemall-admin/src/api/log.js @@ -1,8 +1,9 @@ import request from '@/utils/request' -export function getToken() { +export function listLog(query) { return request({ - url: '/qiniu/upload/token', // 假地址 自行替换 - method: 'get' + url: '/log/list', + method: 'get', + params: query }) } diff --git a/litemall-admin/src/api/notice.js b/litemall-admin/src/api/notice.js new file mode 100644 index 0000000000000000000000000000000000000000..a46669909296493e2df25b1a62479f5fa79c565c --- /dev/null +++ b/litemall-admin/src/api/notice.js @@ -0,0 +1,49 @@ +import request from '@/utils/request' + +export function listNotice(query) { + return request({ + url: '/notice/list', + method: 'get', + params: query + }) +} + +export function createNotice(data) { + return request({ + url: '/notice/create', + method: 'post', + data + }) +} + +export function readNotice(query) { + return request({ + url: '/notice/read', + method: 'get', + params: query + }) +} + +export function updateNotice(data) { + return request({ + url: '/notice/update', + method: 'post', + data + }) +} + +export function deleteNotice(data) { + return request({ + url: '/notice/delete', + method: 'post', + data + }) +} + +export function batchDeleteNotice(data) { + return request({ + url: '/notice/batch-delete', + method: 'post', + data + }) +} diff --git a/litemall-admin/src/api/order.js b/litemall-admin/src/api/order.js index 44d3abe9d368e716614f78411017787f37ffc6cd..122fdcbf5c9630dde5269d2326eff4c239e5244f 100644 --- a/litemall-admin/src/api/order.js +++ b/litemall-admin/src/api/order.js @@ -36,6 +36,14 @@ export function refundOrder(data) { }) } +export function deleteOrder(data) { + return request({ + url: '/order/delete', + method: 'post', + data + }) +} + export function replyComment(data) { return request({ url: '/order/reply', @@ -43,3 +51,10 @@ export function replyComment(data) { data }) } + +export function listChannel(id) { + return request({ + url: '/order/channel', + method: 'get' + }) +} diff --git a/litemall-admin/src/api/profile.js b/litemall-admin/src/api/profile.js index 1e94e4a75148ade2eec6bf24ed07a1a5a46f4acc..4ceb630619dc0b5b519b9b4ab8ff456650470304 100644 --- a/litemall-admin/src/api/profile.js +++ b/litemall-admin/src/api/profile.js @@ -7,3 +7,51 @@ export function changePassword(data) { data }) } + +export function nNotice() { + return request({ + url: '/profile/nnotice', + method: 'get' + }) +} + +export function listNotice(query) { + return request({ + url: '/profile/lsnotice', + method: 'get', + params: query + }) +} + +export function catNotice(data) { + return request({ + url: '/profile/catnotice', + method: 'post', + data + }) +} + +export function bcatNotice(data) { + return request({ + url: '/profile/bcatnotice', + method: 'post', + data + }) +} + +export function rmotice(data) { + return request({ + url: '/profile/rmnotice', + method: 'post', + data + }) +} + +export function brmNotice(data) { + return request({ + url: '/profile/brmnotice', + method: 'post', + data + }) +} + diff --git a/litemall-admin/src/api/region.js b/litemall-admin/src/api/region.js index dd705ee43cfe071478a6acfa30fb61f485d8a891..a114c4d7508157e731d1b6b6176fcb4520e0dcec 100644 --- a/litemall-admin/src/api/region.js +++ b/litemall-admin/src/api/region.js @@ -1,10 +1,9 @@ import request from '@/utils/request' -export function listRegion(query) { +export function listRegion() { return request({ url: '/region/list', - method: 'get', - params: query + method: 'get' }) } diff --git a/litemall-admin/src/api/storage.js b/litemall-admin/src/api/storage.js index be0307edfe69461d573e21b944c616aa911b4d6f..8a6a9a54952e176c284c3a0d6c6165dfd922362b 100644 --- a/litemall-admin/src/api/storage.js +++ b/litemall-admin/src/api/storage.js @@ -40,5 +40,5 @@ export function deleteStorage(data) { }) } -const uploadPath = process.env.BASE_API + '/storage/create' +const uploadPath = process.env.VUE_APP_BASE_API + '/storage/create' export { uploadPath } diff --git a/litemall-admin/src/api/topic.js b/litemall-admin/src/api/topic.js index 9b45f4f51a39fe119efabe0a4df8dd0088866891..697245dfb64ea2941f2022def1443fbc97222d25 100644 --- a/litemall-admin/src/api/topic.js +++ b/litemall-admin/src/api/topic.js @@ -16,11 +16,11 @@ export function createTopic(data) { }) } -export function readTopic(data) { +export function readTopic(query) { return request({ url: '/topic/read', method: 'get', - data + params: query }) } @@ -39,3 +39,11 @@ export function deleteTopic(data) { data }) } + +export function batchDeleteTopic(data) { + return request({ + url: '/topic/batch-delete', + method: 'post', + data + }) +} diff --git a/litemall-admin/src/api/user.js b/litemall-admin/src/api/user.js index cd953120baeed67e392e5c20c0db5f0a5eaf6bd9..a7b8b42e0ff0099ee8165cf8db4c28486ef1f6a6 100644 --- a/litemall-admin/src/api/user.js +++ b/litemall-admin/src/api/user.js @@ -8,30 +8,6 @@ export function fetchList(query) { }) } -export function createUser(data) { - return request({ - url: '/user/create', - method: 'post', - data - }) -} - -export function readUser(data) { - return request({ - url: '/user/detail', - method: 'get', - data - }) -} - -export function updateUser(data) { - return request({ - url: '/user/update', - method: 'post', - data - }) -} - export function listAddress(query) { return request({ url: '/address/list', diff --git a/litemall-admin/src/assets/custom-theme/fonts/element-icons.ttf b/litemall-admin/src/assets/custom-theme/fonts/element-icons.ttf deleted file mode 100644 index 570a3e11b2f002c146d7fdc20528951442f6fd93..0000000000000000000000000000000000000000 Binary files a/litemall-admin/src/assets/custom-theme/fonts/element-icons.ttf and /dev/null differ diff --git a/litemall-admin/src/assets/custom-theme/fonts/element-icons.woff b/litemall-admin/src/assets/custom-theme/fonts/element-icons.woff deleted file mode 100644 index c2bcc00509acd81fe152f08f39eef93984a0453a..0000000000000000000000000000000000000000 Binary files a/litemall-admin/src/assets/custom-theme/fonts/element-icons.woff and /dev/null differ diff --git a/litemall-admin/src/assets/custom-theme/index.css b/litemall-admin/src/assets/custom-theme/index.css deleted file mode 100644 index e8b4e08be87b205c0f59ee65b4291022081733a2..0000000000000000000000000000000000000000 --- a/litemall-admin/src/assets/custom-theme/index.css +++ /dev/null @@ -1 +0,0 @@ -@charset "UTF-8";.custom-theme .fade-in-linear-enter-active,.custom-theme .fade-in-linear-leave-active{-webkit-transition:opacity .2s linear;transition:opacity .2s linear}.custom-theme .fade-in-linear-enter,.custom-theme .fade-in-linear-leave,.custom-theme .fade-in-linear-leave-active{opacity:0}.custom-theme .el-fade-in-linear-enter-active,.custom-theme .el-fade-in-linear-leave-active{-webkit-transition:opacity .2s linear;transition:opacity .2s linear}.custom-theme .el-fade-in-linear-enter,.custom-theme .el-fade-in-linear-leave,.custom-theme .el-fade-in-linear-leave-active{opacity:0}.custom-theme .el-fade-in-enter-active,.custom-theme .el-fade-in-leave-active{-webkit-transition:all .3s cubic-bezier(.55,0,.1,1);transition:all .3s cubic-bezier(.55,0,.1,1)}.custom-theme .el-fade-in-enter,.custom-theme .el-fade-in-leave-active{opacity:0}.custom-theme .el-zoom-in-center-enter-active,.custom-theme .el-zoom-in-center-leave-active{-webkit-transition:all .3s cubic-bezier(.55,0,.1,1);transition:all .3s cubic-bezier(.55,0,.1,1)}.custom-theme .el-zoom-in-center-enter,.custom-theme .el-zoom-in-center-leave-active{opacity:0;-webkit-transform:scaleX(0);transform:scaleX(0)}.custom-theme .el-zoom-in-top-enter-active,.custom-theme .el-zoom-in-top-leave-active{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;-webkit-transform-origin:center top;transform-origin:center top}.custom-theme .el-zoom-in-top-enter,.custom-theme .el-zoom-in-top-leave-active{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.custom-theme .el-zoom-in-bottom-enter-active,.custom-theme .el-zoom-in-bottom-leave-active{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;-webkit-transform-origin:center bottom;transform-origin:center bottom}.custom-theme .el-zoom-in-bottom-enter,.custom-theme .el-zoom-in-bottom-leave-active{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.custom-theme .el-zoom-in-left-enter-active,.custom-theme .el-zoom-in-left-leave-active{opacity:1;-webkit-transform:scale(1,1);transform:scale(1,1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;-webkit-transform-origin:top left;transform-origin:top left}.custom-theme .el-zoom-in-left-enter,.custom-theme .el-zoom-in-left-leave-active{opacity:0;-webkit-transform:scale(.45,.45);transform:scale(.45,.45)}.custom-theme .collapse-transition{-webkit-transition:.3s height ease-in-out,.3s padding-top ease-in-out,.3s padding-bottom ease-in-out;transition:.3s height ease-in-out,.3s padding-top ease-in-out,.3s padding-bottom ease-in-out}.custom-theme .horizontal-collapse-transition{-webkit-transition:.3s width ease-in-out,.3s padding-left ease-in-out,.3s padding-right ease-in-out;transition:.3s width ease-in-out,.3s padding-left ease-in-out,.3s padding-right ease-in-out}.custom-theme .el-list-enter-active,.custom-theme .el-list-leave-active{-webkit-transition:all 1s;transition:all 1s}.custom-theme .el-list-enter,.custom-theme .el-list-leave-active{opacity:0;-webkit-transform:translateY(-30px);transform:translateY(-30px)}.custom-theme .el-opacity-transition{-webkit-transition:opacity .3s cubic-bezier(.55,0,.1,1);transition:opacity .3s cubic-bezier(.55,0,.1,1)}@font-face{font-family:element-icons;src:url(fonts/element-icons.woff?t=1508751886602) format("woff"),url(fonts/element-icons.ttf?t=1508751886602) format("truetype");font-weight:400;font-style:normal}.custom-theme [class*=" el-icon-"],.custom-theme [class^=el-icon-]{font-family:element-icons!important;speak:none;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;line-height:1;vertical-align:baseline;display:inline-block;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.custom-theme .el-icon-upload:before{content:"\e60d"}.custom-theme .el-icon-error:before{content:"\e62c"}.custom-theme .el-icon-success:before{content:"\e62d"}.custom-theme .el-icon-warning:before{content:"\e62e"}.custom-theme .el-icon-sort-down:before{content:"\e630"}.custom-theme .el-icon-sort-up:before{content:"\e631"}.custom-theme .el-icon-arrow-left:before{content:"\e600"}.custom-theme .el-icon-circle-plus:before{content:"\e601"}.custom-theme .el-icon-circle-plus-outline:before{content:"\e602"}.custom-theme .el-icon-arrow-down:before{content:"\e603"}.custom-theme .el-icon-arrow-right:before{content:"\e604"}.custom-theme .el-icon-arrow-up:before{content:"\e605"}.custom-theme .el-icon-back:before{content:"\e606"}.custom-theme .el-icon-circle-close:before{content:"\e607"}.custom-theme .el-icon-date:before{content:"\e608"}.custom-theme .el-icon-circle-close-outline:before{content:"\e609"}.custom-theme .el-icon-caret-left:before{content:"\e60a"}.custom-theme .el-icon-caret-bottom:before{content:"\e60b"}.custom-theme .el-icon-caret-top:before{content:"\e60c"}.custom-theme .el-icon-caret-right:before{content:"\e60e"}.custom-theme .el-icon-close:before{content:"\e60f"}.custom-theme .el-icon-d-arrow-left:before{content:"\e610"}.custom-theme .el-icon-check:before{content:"\e611"}.custom-theme .el-icon-delete:before{content:"\e612"}.custom-theme .el-icon-d-arrow-right:before{content:"\e613"}.custom-theme .el-icon-document:before{content:"\e614"}.custom-theme .el-icon-d-caret:before{content:"\e615"}.custom-theme .el-icon-edit-outline:before{content:"\e616"}.custom-theme .el-icon-download:before{content:"\e617"}.custom-theme .el-icon-goods:before{content:"\e618"}.custom-theme .el-icon-search:before{content:"\e619"}.custom-theme .el-icon-info:before{content:"\e61a"}.custom-theme .el-icon-message:before{content:"\e61b"}.custom-theme .el-icon-edit:before{content:"\e61c"}.custom-theme .el-icon-location:before{content:"\e61d"}.custom-theme .el-icon-loading:before{content:"\e61e"}.custom-theme .el-icon-location-outline:before{content:"\e61f"}.custom-theme .el-icon-menu:before{content:"\e620"}.custom-theme .el-icon-minus:before{content:"\e621"}.custom-theme .el-icon-bell:before{content:"\e622"}.custom-theme .el-icon-mobile-phone:before{content:"\e624"}.custom-theme .el-icon-news:before{content:"\e625"}.custom-theme .el-icon-more:before{content:"\e646"}.custom-theme .el-icon-more-outline:before{content:"\e626"}.custom-theme .el-icon-phone:before{content:"\e627"}.custom-theme .el-icon-phone-outline:before{content:"\e628"}.custom-theme .el-icon-picture:before{content:"\e629"}.custom-theme .el-icon-picture-outline:before{content:"\e62a"}.custom-theme .el-icon-plus:before{content:"\e62b"}.custom-theme .el-icon-printer:before{content:"\e62f"}.custom-theme .el-icon-rank:before{content:"\e632"}.custom-theme .el-icon-refresh:before{content:"\e633"}.custom-theme .el-icon-question:before{content:"\e634"}.custom-theme .el-icon-remove:before{content:"\e635"}.custom-theme .el-icon-share:before{content:"\e636"}.custom-theme .el-icon-star-on:before{content:"\e637"}.custom-theme .el-icon-setting:before{content:"\e638"}.custom-theme .el-icon-circle-check:before{content:"\e639"}.custom-theme .el-icon-service:before{content:"\e63a"}.custom-theme .el-icon-sold-out:before{content:"\e63b"}.custom-theme .el-icon-remove-outline:before{content:"\e63c"}.custom-theme .el-icon-star-off:before{content:"\e63d"}.custom-theme .el-icon-circle-check-outline:before{content:"\e63e"}.custom-theme .el-icon-tickets:before{content:"\e63f"}.custom-theme .el-icon-sort:before{content:"\e640"}.custom-theme .el-icon-zoom-in:before{content:"\e641"}.custom-theme .el-icon-time:before{content:"\e642"}.custom-theme .el-icon-view:before{content:"\e643"}.custom-theme .el-icon-upload2:before{content:"\e644"}.custom-theme .el-icon-zoom-out:before{content:"\e645"}.custom-theme .el-icon-loading{-webkit-animation:rotating 2s linear infinite;animation:rotating 2s linear infinite}.custom-theme .el-icon--right{margin-left:5px}.custom-theme .el-icon--left{margin-right:5px}@-webkit-keyframes rotating{0%{-webkit-transform:rotateZ(0);transform:rotateZ(0)}100%{-webkit-transform:rotateZ(360deg);transform:rotateZ(360deg)}}@keyframes rotating{0%{-webkit-transform:rotateZ(0);transform:rotateZ(0)}100%{-webkit-transform:rotateZ(360deg);transform:rotateZ(360deg)}}.custom-theme .el-popper .popper__arrow,.custom-theme .el-popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.custom-theme .el-popper .popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03));filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03))}.custom-theme .el-popper .popper__arrow::after{content:" ";border-width:6px}.custom-theme .el-popper[x-placement^=top]{margin-bottom:12px}.custom-theme .el-popper[x-placement^=top] .popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-top-color:#e6ebf5;border-bottom-width:0}.custom-theme .el-popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-6px;border-top-color:#fff;border-bottom-width:0}.custom-theme .el-popper[x-placement^=bottom]{margin-top:12px}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow{top:-6px;left:50%;margin-right:3px;border-top-width:0;border-bottom-color:#e6ebf5}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-6px;border-top-width:0;border-bottom-color:#fff}.custom-theme .el-popper[x-placement^=right]{margin-left:12px}.custom-theme .el-popper[x-placement^=right] .popper__arrow{top:50%;left:-6px;margin-bottom:3px;border-right-color:#e6ebf5;border-left-width:0}.custom-theme .el-popper[x-placement^=right] .popper__arrow::after{bottom:-6px;left:1px;border-right-color:#fff;border-left-width:0}.custom-theme .el-popper[x-placement^=left]{margin-right:12px}.custom-theme .el-popper[x-placement^=left] .popper__arrow{top:50%;right:-6px;margin-bottom:3px;border-right-width:0;border-left-color:#e6ebf5}.custom-theme .el-popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-6px;margin-left:-6px;border-right-width:0;border-left-color:#fff}.custom-theme .el-select-dropdown{position:absolute;z-index:1001;border:solid 1px #dfe4ed;border-radius:4px;background-color:#fff;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);-webkit-box-sizing:border-box;box-sizing:border-box;margin:5px 0}.custom-theme .el-select-dropdown.is-multiple .el-select-dropdown__item.selected{color:#262729;background-color:#fff}.custom-theme .el-select-dropdown.is-multiple .el-select-dropdown__item.selected.hover{background-color:#f5f7fa}.custom-theme .el-select-dropdown.is-multiple .el-select-dropdown__item.selected::after{position:absolute;right:20px;font-family:element-icons;content:"\E611";font-size:12px;font-weight:700;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.custom-theme .el-select-dropdown .el-scrollbar.is-empty .el-select-dropdown__list{padding:0}.custom-theme .el-select-dropdown .popper__arrow{-webkit-transform:translateX(-400%);transform:translateX(-400%)}.custom-theme .el-select-dropdown.is-arrow-fixed .popper__arrow{-webkit-transform:translateX(-200%);transform:translateX(-200%)}.custom-theme .el-select-dropdown__empty{padding:10px 0;margin:0;text-align:center;color:#999;font-size:14px}.custom-theme .el-select-dropdown__wrap{max-height:274px}.custom-theme .el-select-dropdown__list{list-style:none;padding:6px 0;margin:0;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-input{position:relative;font-size:14px;display:inline-block;width:100%}.custom-theme .el-input::-webkit-scrollbar{z-index:11;width:6px}.custom-theme .el-input::-webkit-scrollbar:horizontal{height:6px}.custom-theme .el-input::-webkit-scrollbar-thumb{border-radius:5px;width:6px;background:#b4bccc}.custom-theme .el-input::-webkit-scrollbar-corner{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track-piece{background:#fff;width:6px}.custom-theme .el-input__inner{-webkit-appearance:none;background-color:#fff;background-image:none;border-radius:4px;border:1px solid #d8dce5;-webkit-box-sizing:border-box;box-sizing:border-box;color:#5a5e66;display:inline-block;font-size:inherit;height:40px;line-height:1;outline:0;padding:0 15px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1);width:100%}.custom-theme .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input__inner:hover{border-color:#b4bccc}.custom-theme .el-input__inner:focus{outline:0;border-color:#262729}.custom-theme .el-input__suffix{position:absolute;height:100%;right:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s;pointer-events:none}.custom-theme .el-input__suffix-inner{pointer-events:all}.custom-theme .el-input__prefix{position:absolute;height:100%;left:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s}.custom-theme .el-input__icon{height:100%;width:25px;text-align:center;-webkit-transition:all .3s;transition:all .3s;line-height:40px}.custom-theme .el-input__icon:after{content:'';height:100%;width:0;display:inline-block;vertical-align:middle}.custom-theme .el-input__validateIcon{pointer-events:none}.custom-theme .el-input.is-active .el-input__inner{outline:0;border-color:#262729}.custom-theme .el-input.is-disabled .el-input__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-input.is-disabled .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__icon{cursor:not-allowed}.custom-theme .el-input--suffix .el-input__inner{padding-right:30px}.custom-theme .el-input--prefix .el-input__inner{padding-left:30px}.custom-theme .el-input--medium{font-size:14px}.custom-theme .el-input--medium .el-input__inner{height:36px}.custom-theme .el-input--medium .el-input__icon{line-height:36px}.custom-theme .el-input--small{font-size:13px}.custom-theme .el-input--small .el-input__inner{height:32px}.custom-theme .el-input--small .el-input__icon{line-height:32px}.custom-theme .el-input--mini{font-size:12px}.custom-theme .el-input--mini .el-input__inner{height:28px}.custom-theme .el-input--mini .el-input__icon{line-height:28px}.custom-theme .el-input-group{line-height:normal;display:inline-table;width:100%;border-collapse:separate}.custom-theme .el-input-group>.el-input__inner{vertical-align:middle;display:table-cell}.custom-theme .el-input-group__append,.custom-theme .el-input-group__prepend{background-color:#f5f7fa;color:#0a76a4;vertical-align:middle;display:table-cell;position:relative;border:1px solid #d8dce5;border-radius:4px;padding:0 20px;width:1px;white-space:nowrap}.custom-theme .el-input-group__append:focus,.custom-theme .el-input-group__prepend:focus{outline:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-select,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-select{display:inline-block;margin:-20px}.custom-theme .el-input-group__append button.el-button,.custom-theme .el-input-group__append div.el-select .el-input__inner,.custom-theme .el-input-group__append div.el-select:hover .el-input__inner,.custom-theme .el-input-group__prepend button.el-button,.custom-theme .el-input-group__prepend div.el-select .el-input__inner,.custom-theme .el-input-group__prepend div.el-select:hover .el-input__inner{border-color:transparent;background-color:transparent;color:inherit;border-top:0;border-bottom:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-input,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-input{font-size:inherit}.custom-theme .el-input-group__prepend{border-right:0;border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-input-group__append{border-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--prepend .el-input__inner{border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--append .el-input__inner{border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-textarea{display:inline-block;width:100%;vertical-align:bottom}.custom-theme .el-textarea__inner{display:block;resize:vertical;padding:5px 15px;line-height:1.5;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;font-size:14px;color:#5a5e66;background-color:#fff;background-image:none;border:1px solid #d8dce5;border-radius:4px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}.custom-theme .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:hover{border-color:#b4bccc}.custom-theme .el-textarea__inner:focus{outline:0;border-color:#262729}.custom-theme .el-textarea.is-disabled .el-textarea__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-tag{background-color:rgba(38,39,41,.1);display:inline-block;padding:0 10px;height:32px;line-height:30px;font-size:12px;color:#262729;border-radius:4px;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid rgba(38,39,41,.2);white-space:nowrap}.custom-theme .el-tag .el-icon-close{border-radius:50%;text-align:center;position:relative;cursor:pointer;font-size:12px;height:18px;width:18px;line-height:18px;vertical-align:middle;top:-1px;right:-5px;color:#262729}.custom-theme .el-tag .el-icon-close::before{display:block}.custom-theme .el-tag .el-icon-close:hover{background-color:#262729;color:#fff}.custom-theme .el-tag--info{background-color:rgba(10,118,164,.1);border-color:rgba(10,118,164,.2);color:#0a76a4}.custom-theme .el-tag--info.is-hit{border-color:#0a76a4}.custom-theme .el-tag--info .el-tag__close{color:#0a76a4}.custom-theme .el-tag--info .el-tag__close:hover{background-color:#0a76a4;color:#fff}.custom-theme .el-tag--success{background-color:rgba(64,145,103,.1);border-color:rgba(64,145,103,.2);color:#409167}.custom-theme .el-tag--success.is-hit{border-color:#409167}.custom-theme .el-tag--success .el-tag__close{color:#409167}.custom-theme .el-tag--success .el-tag__close:hover{background-color:#409167;color:#fff}.custom-theme .el-tag--warning{background-color:rgba(157,164,8,.1);border-color:rgba(157,164,8,.2);color:#9da408}.custom-theme .el-tag--warning.is-hit{border-color:#9da408}.custom-theme .el-tag--warning .el-tag__close{color:#9da408}.custom-theme .el-tag--warning .el-tag__close:hover{background-color:#9da408;color:#fff}.custom-theme .el-tag--danger{background-color:rgba(179,69,14,.1);border-color:rgba(179,69,14,.2);color:#b3450e}.custom-theme .el-tag--danger.is-hit{border-color:#b3450e}.custom-theme .el-tag--danger .el-tag__close{color:#b3450e}.custom-theme .el-tag--danger .el-tag__close:hover{background-color:#b3450e;color:#fff}.custom-theme .el-tag--medium{height:28px;line-height:26px}.custom-theme .el-tag--medium .el-icon-close{-webkit-transform:scale(.8);transform:scale(.8)}.custom-theme .el-tag--small{height:24px;padding:0 8px;line-height:22px}.custom-theme .el-tag--small .el-icon-close{-webkit-transform:scale(.8);transform:scale(.8)}.custom-theme .el-tag--mini{height:20px;padding:0 5px;line-height:19px}.custom-theme .el-tag--mini .el-icon-close{margin-left:-3px;-webkit-transform:scale(.7);transform:scale(.7)}.custom-theme .el-select-dropdown__item{font-size:14px;padding:0 20px;position:relative;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:#5a5e66;height:34px;line-height:34px;-webkit-box-sizing:border-box;box-sizing:border-box;cursor:pointer}.custom-theme .el-select-dropdown__item.is-disabled{color:#b4bccc;cursor:not-allowed}.custom-theme .el-select-dropdown__item.is-disabled:hover{background-color:#fff}.custom-theme .el-select-dropdown__item.hover,.custom-theme .el-select-dropdown__item:hover{background-color:#f5f7fa}.custom-theme .el-select-dropdown__item.selected{color:#262729;font-weight:700}.custom-theme .el-select-dropdown__item span{line-height:34px!important}.custom-theme .el-select-group{margin:0;padding:0}.custom-theme .el-select-group__wrap{position:relative;list-style:none;margin:0;padding:0}.custom-theme .el-select-group__wrap:not(:last-of-type){padding-bottom:24px}.custom-theme .el-select-group__wrap:not(:last-of-type)::after{content:'';position:absolute;display:block;left:20px;right:20px;bottom:12px;height:1px;background:#dfe4ed}.custom-theme .el-select-group__title{padding-left:20px;font-size:12px;color:#0a76a4;line-height:30px}.custom-theme .el-select-group .el-select-dropdown__item{padding-left:20px}.custom-theme .el-scrollbar{overflow:hidden;position:relative}.custom-theme .el-scrollbar:active>.el-scrollbar__bar,.custom-theme .el-scrollbar:focus>.el-scrollbar__bar,.custom-theme .el-scrollbar:hover>.el-scrollbar__bar{opacity:1;-webkit-transition:opacity 340ms ease-out;transition:opacity 340ms ease-out}.custom-theme .el-scrollbar__wrap{overflow:scroll;height:100%}.custom-theme .el-scrollbar__wrap--hidden-default::-webkit-scrollbar{width:0;height:0}.custom-theme .el-scrollbar__thumb{position:relative;display:block;width:0;height:0;cursor:pointer;border-radius:inherit;background-color:rgba(135,141,153,.3);-webkit-transition:.3s background-color;transition:.3s background-color}.custom-theme .el-scrollbar__thumb:hover{background-color:rgba(135,141,153,.5)}.custom-theme .el-scrollbar__bar{position:absolute;right:2px;bottom:2px;z-index:1;border-radius:4px;opacity:0;-webkit-transition:opacity 120ms ease-out;transition:opacity 120ms ease-out}.custom-theme .el-scrollbar__bar.is-vertical{width:6px;top:2px}.custom-theme .el-scrollbar__bar.is-vertical>div{width:100%}.custom-theme .el-scrollbar__bar.is-horizontal{height:6px;left:2px}.custom-theme .el-scrollbar__bar.is-horizontal>div{height:100%}.custom-theme .el-select{display:inline-block;position:relative}.custom-theme .el-select:hover .el-input__inner{border-color:#b4bccc}.custom-theme .el-select .el-input__inner{cursor:pointer;padding-right:35px}.custom-theme .el-select .el-input__inner:focus{border-color:#262729}.custom-theme .el-select .el-input .el-select__caret{color:#b4bccc;font-size:14px;-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;-webkit-transform:rotateZ(180deg);transform:rotateZ(180deg);line-height:16px;cursor:pointer}.custom-theme .el-select .el-input .el-select__caret.is-reverse{-webkit-transform:rotateZ(0);transform:rotateZ(0)}.custom-theme .el-select .el-input .el-select__caret.is-show-close{font-size:14px;text-align:center;-webkit-transform:rotateZ(180deg);transform:rotateZ(180deg);border-radius:100%;color:#b4bccc;-webkit-transition:color .2s cubic-bezier(.645,.045,.355,1);transition:color .2s cubic-bezier(.645,.045,.355,1)}.custom-theme .el-select .el-input .el-select__caret.is-show-close:hover{color:#878d99}.custom-theme .el-select .el-input.is-disabled .el-input__inner{cursor:not-allowed}.custom-theme .el-select .el-input.is-disabled .el-input__inner:hover{border-color:#dfe4ed}.custom-theme .el-select>.el-input{display:block}.custom-theme .el-select__input{border:none;outline:0;padding:0;margin-left:15px;color:#666;font-size:14px;vertical-align:baseline;-webkit-appearance:none;-moz-appearance:none;appearance:none;height:28px;background-color:transparent}.custom-theme .el-select__input.is-mini{height:14px}.custom-theme .el-select__close{cursor:pointer;position:absolute;top:8px;z-index:1000;right:25px;color:#b4bccc;line-height:18px;font-size:14px}.custom-theme .el-select__close:hover{color:#878d99}.custom-theme .el-select__tags{position:absolute;line-height:normal;white-space:normal;z-index:1;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.custom-theme .el-select .el-tag__close{margin-top:-2px}.custom-theme .el-select .el-tag{-webkit-box-sizing:border-box;box-sizing:border-box;border-color:transparent;margin:3px 0 3px 6px;background-color:#f0f2f5}.custom-theme .el-select .el-tag__close.el-icon-close{background-color:#b4bccc;right:-7px;color:#fff}.custom-theme .el-select .el-tag__close.el-icon-close:hover{background-color:#878d99}.custom-theme .el-select .el-tag__close.el-icon-close::before{display:block;-webkit-transform:translate(0,.5px);transform:translate(0,.5px)}.custom-theme .el-select__tag{display:inline-block;height:24px;line-height:24px;font-size:14px;border-radius:4px;color:#fff;background-color:#262729}.custom-theme .el-select__tag .el-icon-close{font-size:14px}.custom-theme .el-pagination{white-space:nowrap;padding:2px 5px;color:#2d2f33;font-weight:700}.custom-theme .el-pagination::after,.custom-theme .el-pagination::before{display:table;content:""}.custom-theme .el-pagination::after{clear:both}.custom-theme .el-pagination button,.custom-theme .el-pagination span:not([class*=suffix]){display:inline-block;font-size:13px;min-width:35.5px;height:28px;line-height:28px;vertical-align:top;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-pagination .el-input__inner{text-align:center}.custom-theme .el-pagination .el-input__suffix{right:0;-webkit-transform:scale(.8);transform:scale(.8)}.custom-theme .el-pagination .el-select .el-input{width:100px;margin:0 5px}.custom-theme .el-pagination .el-select .el-input .el-input__inner{padding-right:25px;border-radius:3px;height:28px}.custom-theme .el-pagination button{border:none;padding:0 6px;background:0 0}.custom-theme .el-pagination button:focus{outline:0}.custom-theme .el-pagination button:hover{color:#262729}.custom-theme .el-pagination button.disabled{color:#b4bccc;background-color:#fff;cursor:not-allowed}.custom-theme .el-pagination .btn-next,.custom-theme .el-pagination .btn-prev{background:center center no-repeat;background-size:16px;background-color:#fff;cursor:pointer;margin:0;color:#2d2f33}.custom-theme .el-pagination .btn-next .el-icon,.custom-theme .el-pagination .btn-prev .el-icon{display:block;font-size:12px}.custom-theme .el-pagination .btn-prev{padding-right:12px}.custom-theme .el-pagination .btn-next{padding-left:12px}.custom-theme .el-pagination--small .btn-next,.custom-theme .el-pagination--small .btn-prev,.custom-theme .el-pagination--small .el-pager li,.custom-theme .el-pagination--small .el-pager li:last-child{border-color:transparent;font-size:12px;line-height:22px;height:22px;min-width:22px}.custom-theme .el-pagination--small .arrow.disabled{visibility:hidden}.custom-theme .el-pagination__sizes{margin:0 10px 0 0;font-weight:400;color:#5a5e66}.custom-theme .el-pagination__sizes .el-input .el-input__inner{font-size:13px;padding-left:8px}.custom-theme .el-pagination__sizes .el-input .el-input__inner:hover{border-color:#262729}.custom-theme .el-pagination__total{margin-right:10px;font-weight:400;color:#5a5e66}.custom-theme .el-pagination__jump{margin-left:24px;font-weight:400;color:#5a5e66}.custom-theme .el-pagination__jump .el-input__inner{padding:0 3px}.custom-theme .el-pagination__rightwrapper{float:right}.custom-theme .el-pagination__editor{line-height:18px;padding:0 2px;height:28px;text-align:center;margin:0 2px;-webkit-box-sizing:border-box;box-sizing:border-box;border-radius:3px;-moz-appearance:textfield}.custom-theme .el-pagination__editor.el-input{width:50px}.custom-theme .el-pagination__editor.el-input .el-input__inner{height:28px}.custom-theme .el-pagination__editor .el-input__inner::-webkit-inner-spin-button,.custom-theme .el-pagination__editor .el-input__inner::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.custom-theme .el-pager{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;list-style:none;display:inline-block;vertical-align:top;font-size:0;padding:0;margin:0}.custom-theme .el-pager .el-icon-more::before{vertical-align:-4px}.custom-theme .el-pager li{padding:0 4px;background:#fff;vertical-align:top;display:inline-block;font-size:13px;min-width:35.5px;height:28px;line-height:28px;cursor:pointer;-webkit-box-sizing:border-box;box-sizing:border-box;text-align:center;margin:0}.custom-theme .el-pager li.btn-quicknext,.custom-theme .el-pager li.btn-quickprev{line-height:28px;color:#2d2f33}.custom-theme .el-pager li.btn-quickprev:hover{cursor:pointer}.custom-theme .el-pager li.btn-quicknext:hover{cursor:pointer}.custom-theme .el-pager li.active+li{border-left:0}.custom-theme .el-pager li:hover{color:#262729}.custom-theme .el-pager li.active{color:#262729;cursor:default}.custom-theme .v-modal-enter{-webkit-animation:v-modal-in .2s ease;animation:v-modal-in .2s ease}.custom-theme .v-modal-leave{-webkit-animation:v-modal-out .2s ease forwards;animation:v-modal-out .2s ease forwards}@-webkit-keyframes v-modal-in{0%{opacity:0}}@keyframes v-modal-in{0%{opacity:0}}@-webkit-keyframes v-modal-out{100%{opacity:0}}@keyframes v-modal-out{100%{opacity:0}}.custom-theme .v-modal{position:fixed;left:0;top:0;width:100%;height:100%;opacity:.5;background:#000}.custom-theme .el-dialog{position:relative;margin:0 auto 50px;background:#fff;border-radius:2px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,.3);box-shadow:0 1px 3px rgba(0,0,0,.3);-webkit-box-sizing:border-box;box-sizing:border-box;width:50%}.custom-theme .el-dialog.is-fullscreen{width:100%;margin-top:0;margin-bottom:0;height:100%;overflow:auto}.custom-theme .el-dialog__wrapper{position:fixed;top:0;right:0;bottom:0;left:0;overflow:auto;margin:0}.custom-theme .el-dialog__header{padding:15px;padding-bottom:10px}.custom-theme .el-dialog__headerbtn{position:absolute;top:15px;right:15px;padding:0;background:0 0;border:none;outline:0;cursor:pointer;font-size:16px}.custom-theme .el-dialog__headerbtn .el-dialog__close{color:#0a76a4}.custom-theme .el-dialog__headerbtn:focus .el-dialog__close,.custom-theme .el-dialog__headerbtn:hover .el-dialog__close{color:#262729}.custom-theme .el-dialog__title{line-height:24px;font-size:18px;color:#2d2f33}.custom-theme .el-dialog__body{padding:30px 20px;color:#5a5e66;line-height:24px;font-size:14px}.custom-theme .el-dialog__footer{padding:15px;padding-top:10px;text-align:right;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-dialog--center{text-align:center}.custom-theme .el-dialog--center .el-dialog__header{padding-top:30px}.custom-theme .el-dialog--center .el-dialog__body{text-align:initial;padding:25px 27px 30px}.custom-theme .el-dialog--center .el-dialog__footer{text-align:inherit;padding-bottom:30px}.custom-theme .dialog-fade-enter-active{-webkit-animation:dialog-fade-in .3s;animation:dialog-fade-in .3s}.custom-theme .dialog-fade-leave-active{-webkit-animation:dialog-fade-out .3s;animation:dialog-fade-out .3s}@-webkit-keyframes dialog-fade-in{0%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}}@keyframes dialog-fade-in{0%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}}@-webkit-keyframes dialog-fade-out{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}100%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}}@keyframes dialog-fade-out{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}100%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}}.custom-theme .el-input{position:relative;font-size:14px;display:inline-block;width:100%}.custom-theme .el-input::-webkit-scrollbar{z-index:11;width:6px}.custom-theme .el-input::-webkit-scrollbar:horizontal{height:6px}.custom-theme .el-input::-webkit-scrollbar-thumb{border-radius:5px;width:6px;background:#b4bccc}.custom-theme .el-input::-webkit-scrollbar-corner{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track-piece{background:#fff;width:6px}.custom-theme .el-input__inner{-webkit-appearance:none;background-color:#fff;background-image:none;border-radius:4px;border:1px solid #d8dce5;-webkit-box-sizing:border-box;box-sizing:border-box;color:#5a5e66;display:inline-block;font-size:inherit;height:40px;line-height:1;outline:0;padding:0 15px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1);width:100%}.custom-theme .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input__inner:hover{border-color:#b4bccc}.custom-theme .el-input__inner:focus{outline:0;border-color:#262729}.custom-theme .el-input__suffix{position:absolute;height:100%;right:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s;pointer-events:none}.custom-theme .el-input__suffix-inner{pointer-events:all}.custom-theme .el-input__prefix{position:absolute;height:100%;left:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s}.custom-theme .el-input__icon{height:100%;width:25px;text-align:center;-webkit-transition:all .3s;transition:all .3s;line-height:40px}.custom-theme .el-input__icon:after{content:'';height:100%;width:0;display:inline-block;vertical-align:middle}.custom-theme .el-input__validateIcon{pointer-events:none}.custom-theme .el-input.is-active .el-input__inner{outline:0;border-color:#262729}.custom-theme .el-input.is-disabled .el-input__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-input.is-disabled .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__icon{cursor:not-allowed}.custom-theme .el-input--suffix .el-input__inner{padding-right:30px}.custom-theme .el-input--prefix .el-input__inner{padding-left:30px}.custom-theme .el-input--medium{font-size:14px}.custom-theme .el-input--medium .el-input__inner{height:36px}.custom-theme .el-input--medium .el-input__icon{line-height:36px}.custom-theme .el-input--small{font-size:13px}.custom-theme .el-input--small .el-input__inner{height:32px}.custom-theme .el-input--small .el-input__icon{line-height:32px}.custom-theme .el-input--mini{font-size:12px}.custom-theme .el-input--mini .el-input__inner{height:28px}.custom-theme .el-input--mini .el-input__icon{line-height:28px}.custom-theme .el-input-group{line-height:normal;display:inline-table;width:100%;border-collapse:separate}.custom-theme .el-input-group>.el-input__inner{vertical-align:middle;display:table-cell}.custom-theme .el-input-group__append,.custom-theme .el-input-group__prepend{background-color:#f5f7fa;color:#0a76a4;vertical-align:middle;display:table-cell;position:relative;border:1px solid #d8dce5;border-radius:4px;padding:0 20px;width:1px;white-space:nowrap}.custom-theme .el-input-group__append:focus,.custom-theme .el-input-group__prepend:focus{outline:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-select,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-select{display:inline-block;margin:-20px}.custom-theme .el-input-group__append button.el-button,.custom-theme .el-input-group__append div.el-select .el-input__inner,.custom-theme .el-input-group__append div.el-select:hover .el-input__inner,.custom-theme .el-input-group__prepend button.el-button,.custom-theme .el-input-group__prepend div.el-select .el-input__inner,.custom-theme .el-input-group__prepend div.el-select:hover .el-input__inner{border-color:transparent;background-color:transparent;color:inherit;border-top:0;border-bottom:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-input,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-input{font-size:inherit}.custom-theme .el-input-group__prepend{border-right:0;border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-input-group__append{border-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--prepend .el-input__inner{border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--append .el-input__inner{border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-textarea{display:inline-block;width:100%;vertical-align:bottom}.custom-theme .el-textarea__inner{display:block;resize:vertical;padding:5px 15px;line-height:1.5;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;font-size:14px;color:#5a5e66;background-color:#fff;background-image:none;border:1px solid #d8dce5;border-radius:4px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}.custom-theme .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:hover{border-color:#b4bccc}.custom-theme .el-textarea__inner:focus{outline:0;border-color:#262729}.custom-theme .el-textarea.is-disabled .el-textarea__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-scrollbar{overflow:hidden;position:relative}.custom-theme .el-scrollbar:active>.el-scrollbar__bar,.custom-theme .el-scrollbar:focus>.el-scrollbar__bar,.custom-theme .el-scrollbar:hover>.el-scrollbar__bar{opacity:1;-webkit-transition:opacity 340ms ease-out;transition:opacity 340ms ease-out}.custom-theme .el-scrollbar__wrap{overflow:scroll;height:100%}.custom-theme .el-scrollbar__wrap--hidden-default::-webkit-scrollbar{width:0;height:0}.custom-theme .el-scrollbar__thumb{position:relative;display:block;width:0;height:0;cursor:pointer;border-radius:inherit;background-color:rgba(135,141,153,.3);-webkit-transition:.3s background-color;transition:.3s background-color}.custom-theme .el-scrollbar__thumb:hover{background-color:rgba(135,141,153,.5)}.custom-theme .el-scrollbar__bar{position:absolute;right:2px;bottom:2px;z-index:1;border-radius:4px;opacity:0;-webkit-transition:opacity 120ms ease-out;transition:opacity 120ms ease-out}.custom-theme .el-scrollbar__bar.is-vertical{width:6px;top:2px}.custom-theme .el-scrollbar__bar.is-vertical>div{width:100%}.custom-theme .el-scrollbar__bar.is-horizontal{height:6px;left:2px}.custom-theme .el-scrollbar__bar.is-horizontal>div{height:100%}.custom-theme .el-popper .popper__arrow,.custom-theme .el-popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.custom-theme .el-popper .popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03));filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03))}.custom-theme .el-popper .popper__arrow::after{content:" ";border-width:6px}.custom-theme .el-popper[x-placement^=top]{margin-bottom:12px}.custom-theme .el-popper[x-placement^=top] .popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-top-color:#e6ebf5;border-bottom-width:0}.custom-theme .el-popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-6px;border-top-color:#fff;border-bottom-width:0}.custom-theme .el-popper[x-placement^=bottom]{margin-top:12px}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow{top:-6px;left:50%;margin-right:3px;border-top-width:0;border-bottom-color:#e6ebf5}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-6px;border-top-width:0;border-bottom-color:#fff}.custom-theme .el-popper[x-placement^=right]{margin-left:12px}.custom-theme .el-popper[x-placement^=right] .popper__arrow{top:50%;left:-6px;margin-bottom:3px;border-right-color:#e6ebf5;border-left-width:0}.custom-theme .el-popper[x-placement^=right] .popper__arrow::after{bottom:-6px;left:1px;border-right-color:#fff;border-left-width:0}.custom-theme .el-popper[x-placement^=left]{margin-right:12px}.custom-theme .el-popper[x-placement^=left] .popper__arrow{top:50%;right:-6px;margin-bottom:3px;border-right-width:0;border-left-color:#e6ebf5}.custom-theme .el-popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-6px;margin-left:-6px;border-right-width:0;border-left-color:#fff}.custom-theme .el-autocomplete{position:relative;display:inline-block}.custom-theme .el-autocomplete-suggestion{margin:5px 0;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);border-radius:4px}.custom-theme .el-autocomplete-suggestion.el-popper .popper__arrow{left:24px!important}.custom-theme .el-autocomplete-suggestion__wrap{max-height:280px;padding:10px 0;-webkit-box-sizing:border-box;box-sizing:border-box;overflow:auto;background-color:#fff;border:1px solid #dfe4ed;border-radius:4px}.custom-theme .el-autocomplete-suggestion__list{margin:0;padding:0}.custom-theme .el-autocomplete-suggestion li{padding:0 20px;margin:0;line-height:34px;cursor:pointer;color:#5a5e66;font-size:14px;list-style:none;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.custom-theme .el-autocomplete-suggestion li:hover{background-color:#f5f7fa}.custom-theme .el-autocomplete-suggestion li.highlighted{background-color:#f5f7fa}.custom-theme .el-autocomplete-suggestion li.divider{margin-top:6px;border-top:1px solid #000}.custom-theme .el-autocomplete-suggestion li.divider:last-child{margin-bottom:-6px}.custom-theme .el-autocomplete-suggestion.is-loading li{text-align:center;height:100px;line-height:100px;font-size:20px;color:#999}.custom-theme .el-autocomplete-suggestion.is-loading li::after{display:inline-block;content:"";height:100%;vertical-align:middle}.custom-theme .el-autocomplete-suggestion.is-loading li:hover{background-color:#fff}.custom-theme .el-autocomplete-suggestion.is-loading .el-icon-loading{vertical-align:middle}.custom-theme .el-button{display:inline-block;line-height:1;white-space:nowrap;cursor:pointer;background:#fff;border:1px solid #d8dce5;border-color:#d8dce5;color:#5a5e66;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;-webkit-transition:.1s;transition:.1s;font-weight:500;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;padding:12px 20px;font-size:14px;border-radius:4px}.custom-theme .el-button+.el-button{margin-left:10px}.custom-theme .el-button.is-round{padding:12px 20px}.custom-theme .el-button:focus,.custom-theme .el-button:hover{color:#262729;border-color:#bebebf;background-color:#e9e9ea}.custom-theme .el-button:active{color:#222325;border-color:#222325;outline:0}.custom-theme .el-button::-moz-focus-inner{border:0}.custom-theme .el-button [class*=el-icon-]+span{margin-left:5px}.custom-theme .el-button.is-plain:focus,.custom-theme .el-button.is-plain:hover{background:#fff;border-color:#262729;color:#262729}.custom-theme .el-button.is-plain:active{background:#fff;border-color:#222325;color:#222325;outline:0}.custom-theme .el-button.is-active{color:#222325;border-color:#222325}.custom-theme .el-button.is-disabled,.custom-theme .el-button.is-disabled:focus,.custom-theme .el-button.is-disabled:hover{color:#b4bccc;cursor:not-allowed;background-image:none;background-color:#fff;border-color:#e6ebf5}.custom-theme .el-button.is-disabled.el-button--text{background-color:transparent}.custom-theme .el-button.is-disabled.is-plain,.custom-theme .el-button.is-disabled.is-plain:focus,.custom-theme .el-button.is-disabled.is-plain:hover{background-color:#fff;border-color:#e6ebf5;color:#b4bccc}.custom-theme .el-button.is-loading{position:relative;pointer-events:none}.custom-theme .el-button.is-loading:before{pointer-events:none;content:'';position:absolute;left:-1px;top:-1px;right:-1px;bottom:-1px;border-radius:inherit;background-color:rgba(255,255,255,.35)}.custom-theme .el-button.is-round{border-radius:20px;padding:12px 23px}.custom-theme .el-button--primary{color:#fff;background-color:#262729;border-color:#262729}.custom-theme .el-button--primary:focus,.custom-theme .el-button--primary:hover{background:#515254;border-color:#515254;color:#fff}.custom-theme .el-button--primary:active{background:#222325;border-color:#222325;color:#fff;outline:0}.custom-theme .el-button--primary.is-active{background:#222325;border-color:#222325;color:#fff}.custom-theme .el-button--primary.is-disabled,.custom-theme .el-button--primary.is-disabled:active,.custom-theme .el-button--primary.is-disabled:focus,.custom-theme .el-button--primary.is-disabled:hover{color:#fff;background-color:#939394;border-color:#939394}.custom-theme .el-button--primary.is-plain{color:#262729;background:#e9e9ea;border-color:#a8a9a9}.custom-theme .el-button--primary.is-plain:focus,.custom-theme .el-button--primary.is-plain:hover{background:#262729;border-color:#262729;color:#fff}.custom-theme .el-button--primary.is-plain:active{background:#222325;border-color:#222325;color:#fff;outline:0}.custom-theme .el-button--primary.is-plain.is-disabled,.custom-theme .el-button--primary.is-plain.is-disabled:active,.custom-theme .el-button--primary.is-plain.is-disabled:focus,.custom-theme .el-button--primary.is-plain.is-disabled:hover{color:#7d7d7f;background-color:#e9e9ea;border-color:#d4d4d4}.custom-theme .el-button--success{color:#fff;background-color:#409167;border-color:#409167}.custom-theme .el-button--success:focus,.custom-theme .el-button--success:hover{background:#66a785;border-color:#66a785;color:#fff}.custom-theme .el-button--success:active{background:#3a835d;border-color:#3a835d;color:#fff;outline:0}.custom-theme .el-button--success.is-active{background:#3a835d;border-color:#3a835d;color:#fff}.custom-theme .el-button--success.is-disabled,.custom-theme .el-button--success.is-disabled:active,.custom-theme .el-button--success.is-disabled:focus,.custom-theme .el-button--success.is-disabled:hover{color:#fff;background-color:#a0c8b3;border-color:#a0c8b3}.custom-theme .el-button--success.is-plain{color:#409167;background:#ecf4f0;border-color:#b3d3c2}.custom-theme .el-button--success.is-plain:focus,.custom-theme .el-button--success.is-plain:hover{background:#409167;border-color:#409167;color:#fff}.custom-theme .el-button--success.is-plain:active{background:#3a835d;border-color:#3a835d;color:#fff;outline:0}.custom-theme .el-button--success.is-plain.is-disabled,.custom-theme .el-button--success.is-plain.is-disabled:active,.custom-theme .el-button--success.is-plain.is-disabled:focus,.custom-theme .el-button--success.is-plain.is-disabled:hover{color:#8cbda4;background-color:#ecf4f0;border-color:#d9e9e1}.custom-theme .el-button--warning{color:#fff;background-color:#9da408;border-color:#9da408}.custom-theme .el-button--warning:focus,.custom-theme .el-button--warning:hover{background:#b1b639;border-color:#b1b639;color:#fff}.custom-theme .el-button--warning:active{background:#8d9407;border-color:#8d9407;color:#fff;outline:0}.custom-theme .el-button--warning.is-active{background:#8d9407;border-color:#8d9407;color:#fff}.custom-theme .el-button--warning.is-disabled,.custom-theme .el-button--warning.is-disabled:active,.custom-theme .el-button--warning.is-disabled:focus,.custom-theme .el-button--warning.is-disabled:hover{color:#fff;background-color:#ced284;border-color:#ced284}.custom-theme .el-button--warning.is-plain{color:#9da408;background:#f5f6e6;border-color:#d8db9c}.custom-theme .el-button--warning.is-plain:focus,.custom-theme .el-button--warning.is-plain:hover{background:#9da408;border-color:#9da408;color:#fff}.custom-theme .el-button--warning.is-plain:active{background:#8d9407;border-color:#8d9407;color:#fff;outline:0}.custom-theme .el-button--warning.is-plain.is-disabled,.custom-theme .el-button--warning.is-plain.is-disabled:active,.custom-theme .el-button--warning.is-plain.is-disabled:focus,.custom-theme .el-button--warning.is-plain.is-disabled:hover{color:#c4c86b;background-color:#f5f6e6;border-color:#ebedce}.custom-theme .el-button--danger{color:#fff;background-color:#b3450e;border-color:#b3450e}.custom-theme .el-button--danger:focus,.custom-theme .el-button--danger:hover{background:#c26a3e;border-color:#c26a3e;color:#fff}.custom-theme .el-button--danger:active{background:#a13e0d;border-color:#a13e0d;color:#fff;outline:0}.custom-theme .el-button--danger.is-active{background:#a13e0d;border-color:#a13e0d;color:#fff}.custom-theme .el-button--danger.is-disabled,.custom-theme .el-button--danger.is-disabled:active,.custom-theme .el-button--danger.is-disabled:focus,.custom-theme .el-button--danger.is-disabled:hover{color:#fff;background-color:#d9a287;border-color:#d9a287}.custom-theme .el-button--danger.is-plain{color:#b3450e;background:#f7ece7;border-color:#e1b59f}.custom-theme .el-button--danger.is-plain:focus,.custom-theme .el-button--danger.is-plain:hover{background:#b3450e;border-color:#b3450e;color:#fff}.custom-theme .el-button--danger.is-plain:active{background:#a13e0d;border-color:#a13e0d;color:#fff;outline:0}.custom-theme .el-button--danger.is-plain.is-disabled,.custom-theme .el-button--danger.is-plain.is-disabled:active,.custom-theme .el-button--danger.is-plain.is-disabled:focus,.custom-theme .el-button--danger.is-plain.is-disabled:hover{color:#d18f6e;background-color:#f7ece7;border-color:#f0dacf}.custom-theme .el-button--info{color:#fff;background-color:#0a76a4;border-color:#0a76a4}.custom-theme .el-button--info:focus,.custom-theme .el-button--info:hover{background:#3b91b6;border-color:#3b91b6;color:#fff}.custom-theme .el-button--info:active{background:#096a94;border-color:#096a94;color:#fff;outline:0}.custom-theme .el-button--info.is-active{background:#096a94;border-color:#096a94;color:#fff}.custom-theme .el-button--info.is-disabled,.custom-theme .el-button--info.is-disabled:active,.custom-theme .el-button--info.is-disabled:focus,.custom-theme .el-button--info.is-disabled:hover{color:#fff;background-color:#85bbd2;border-color:#85bbd2}.custom-theme .el-button--info.is-plain{color:#0a76a4;background:#e7f1f6;border-color:#9dc8db}.custom-theme .el-button--info.is-plain:focus,.custom-theme .el-button--info.is-plain:hover{background:#0a76a4;border-color:#0a76a4;color:#fff}.custom-theme .el-button--info.is-plain:active{background:#096a94;border-color:#096a94;color:#fff;outline:0}.custom-theme .el-button--info.is-plain.is-disabled,.custom-theme .el-button--info.is-plain.is-disabled:active,.custom-theme .el-button--info.is-plain.is-disabled:focus,.custom-theme .el-button--info.is-plain.is-disabled:hover{color:#6cadc8;background-color:#e7f1f6;border-color:#cee4ed}.custom-theme .el-button--medium{padding:10px 20px;font-size:14px;border-radius:4px}.custom-theme .el-button--medium.is-round{padding:10px 20px}.custom-theme .el-button--small{padding:9px 15px;font-size:12px;border-radius:3px}.custom-theme .el-button--small.is-round{padding:9px 15px}.custom-theme .el-button--mini{padding:7px 15px;font-size:12px;border-radius:3px}.custom-theme .el-button--mini.is-round{padding:7px 15px}.custom-theme .el-button--text{border:none;color:#262729;background:0 0;padding-left:0;padding-right:0}.custom-theme .el-button--text:focus,.custom-theme .el-button--text:hover{color:#515254;border-color:transparent;background-color:transparent}.custom-theme .el-button--text:active{color:#222325;border-color:transparent;background-color:transparent}.custom-theme .el-button-group{display:inline-block;vertical-align:middle}.custom-theme .el-button-group::after,.custom-theme .el-button-group::before{display:table;content:""}.custom-theme .el-button-group::after{clear:both}.custom-theme .el-button-group .el-button{float:left;position:relative}.custom-theme .el-button-group .el-button+.el-button{margin-left:0}.custom-theme .el-button-group .el-button:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-button-group .el-button:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-button-group .el-button:not(:first-child):not(:last-child){border-radius:0}.custom-theme .el-button-group .el-button:not(:last-child){margin-right:-1px}.custom-theme .el-button-group .el-button:active,.custom-theme .el-button-group .el-button:focus,.custom-theme .el-button-group .el-button:hover{z-index:1}.custom-theme .el-button-group .el-button.is-active{z-index:1}.custom-theme .el-button-group .el-button--primary:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--primary:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--primary:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--success:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--success:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--success:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--warning:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--warning:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--warning:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--danger:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--danger:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--danger:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--info:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--info:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--info:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-popper .popper__arrow,.custom-theme .el-popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.custom-theme .el-popper .popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03));filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03))}.custom-theme .el-popper .popper__arrow::after{content:" ";border-width:6px}.custom-theme .el-popper[x-placement^=top]{margin-bottom:12px}.custom-theme .el-popper[x-placement^=top] .popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-top-color:#e6ebf5;border-bottom-width:0}.custom-theme .el-popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-6px;border-top-color:#fff;border-bottom-width:0}.custom-theme .el-popper[x-placement^=bottom]{margin-top:12px}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow{top:-6px;left:50%;margin-right:3px;border-top-width:0;border-bottom-color:#e6ebf5}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-6px;border-top-width:0;border-bottom-color:#fff}.custom-theme .el-popper[x-placement^=right]{margin-left:12px}.custom-theme .el-popper[x-placement^=right] .popper__arrow{top:50%;left:-6px;margin-bottom:3px;border-right-color:#e6ebf5;border-left-width:0}.custom-theme .el-popper[x-placement^=right] .popper__arrow::after{bottom:-6px;left:1px;border-right-color:#fff;border-left-width:0}.custom-theme .el-popper[x-placement^=left]{margin-right:12px}.custom-theme .el-popper[x-placement^=left] .popper__arrow{top:50%;right:-6px;margin-bottom:3px;border-right-width:0;border-left-color:#e6ebf5}.custom-theme .el-popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-6px;margin-left:-6px;border-right-width:0;border-left-color:#fff}.custom-theme .el-dropdown{display:inline-block;position:relative;color:#5a5e66;font-size:14px}.custom-theme .el-dropdown .el-button-group{display:block}.custom-theme .el-dropdown .el-button-group .el-button{float:none}.custom-theme .el-dropdown .el-dropdown__caret-button{padding-left:5px;padding-right:5px;position:relative;border-left:none}.custom-theme .el-dropdown .el-dropdown__caret-button::before{content:'';position:absolute;display:block;width:1px;top:5px;bottom:5px;left:0;background:rgba(255,255,255,.5)}.custom-theme .el-dropdown .el-dropdown__caret-button:hover::before{top:0;bottom:0}.custom-theme .el-dropdown .el-dropdown__caret-button .el-dropdown__icon{padding-left:0}.custom-theme .el-dropdown__icon{font-size:12px;margin:0 3px}.custom-theme .el-dropdown-menu{position:absolute;top:0;left:0;z-index:10;padding:10px 0;margin:5px 0;background-color:#fff;border:1px solid #e6ebf5;border-radius:4px;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.custom-theme .el-dropdown-menu__item{list-style:none;line-height:36px;padding:0 20px;margin:0;font-size:14px;color:#5a5e66;cursor:pointer}.custom-theme .el-dropdown-menu__item:not(.is-disabled):hover{background-color:#e9e9ea;color:#515254}.custom-theme .el-dropdown-menu__item--divided{position:relative;margin-top:6px;border-top:1px solid #e6ebf5}.custom-theme .el-dropdown-menu__item--divided:before{content:'';height:6px;display:block;margin:0 -20px;background-color:#fff}.custom-theme .el-dropdown-menu__item.is-disabled{cursor:default;color:#bbb;pointer-events:none}.custom-theme .el-dropdown-menu--medium{padding:6px 0}.custom-theme .el-dropdown-menu--medium .el-dropdown-menu__item{line-height:30px;padding:0 17px;font-size:14px}.custom-theme .el-dropdown-menu--medium .el-dropdown-menu__item.el-dropdown-menu__item--divided{margin-top:6px}.custom-theme .el-dropdown-menu--medium .el-dropdown-menu__item.el-dropdown-menu__item--divided:before{height:6px;margin:0 -17px}.custom-theme .el-dropdown-menu--small{padding:6px 0}.custom-theme .el-dropdown-menu--small .el-dropdown-menu__item{line-height:27px;padding:0 15px;font-size:13px}.custom-theme .el-dropdown-menu--small .el-dropdown-menu__item.el-dropdown-menu__item--divided{margin-top:4px}.custom-theme .el-dropdown-menu--small .el-dropdown-menu__item.el-dropdown-menu__item--divided:before{height:4px;margin:0 -15px}.custom-theme .el-dropdown-menu--mini{padding:3px 0}.custom-theme .el-dropdown-menu--mini .el-dropdown-menu__item{line-height:24px;padding:0 10px;font-size:12px}.custom-theme .el-dropdown-menu--mini .el-dropdown-menu__item.el-dropdown-menu__item--divided{margin-top:3px}.custom-theme .el-dropdown-menu--mini .el-dropdown-menu__item.el-dropdown-menu__item--divided:before{height:3px;margin:0 -10px}.custom-theme .el-menu{border-right:solid 1px #e6e6e6;list-style:none;position:relative;margin:0;padding-left:0;background-color:#fff}.custom-theme .el-menu::after,.custom-theme .el-menu::before{display:table;content:""}.custom-theme .el-menu::after{clear:both}.custom-theme .el-menu li{list-style:none}.custom-theme .el-menu--horizontal{border-right:none;border-bottom:solid 1px #e6e6e6}.custom-theme .el-menu--horizontal .el-menu-item{float:left;height:60px;line-height:60px;margin:0;cursor:pointer;position:relative;-webkit-box-sizing:border-box;box-sizing:border-box;border-bottom:2px solid transparent;color:#878d99}.custom-theme .el-menu--horizontal .el-menu-item a,.custom-theme .el-menu--horizontal .el-menu-item a:hover{color:inherit}.custom-theme .el-menu--horizontal .el-menu-item:focus,.custom-theme .el-menu--horizontal .el-menu-item:hover{background-color:#fff}.custom-theme .el-menu--horizontal .el-submenu{float:left;position:relative}.custom-theme .el-menu--horizontal .el-submenu:focus{outline:0}.custom-theme .el-menu--horizontal .el-submenu:focus>.el-submenu__title{color:#2d2f33}.custom-theme .el-menu--horizontal .el-submenu>.el-menu{position:absolute;top:65px;left:0;border:none;padding:5px 0;background-color:#fff;z-index:100;min-width:100%;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);border-radius:2px}.custom-theme .el-menu--horizontal .el-submenu .el-submenu__title{height:60px;line-height:60px;border-bottom:2px solid transparent;color:#878d99}.custom-theme .el-menu--horizontal .el-submenu .el-submenu__title:hover{background-color:#fff}.custom-theme .el-menu--horizontal .el-submenu .el-menu-item{background-color:#fff;float:none;height:36px;line-height:36px;padding:0 10px}.custom-theme .el-menu--horizontal .el-submenu .el-submenu__icon-arrow{position:static;vertical-align:middle;margin-left:8px;margin-top:-3px}.custom-theme .el-menu--horizontal .el-menu-item:focus,.custom-theme .el-menu--horizontal .el-menu-item:hover,.custom-theme .el-menu--horizontal .el-submenu__title:hover{outline:0;color:#2d2f33}.custom-theme .el-menu--horizontal>.el-menu-item.is-active,.custom-theme .el-menu--horizontal>.el-submenu.is-active .el-submenu__title{border-bottom:2px solid #262729;color:#2d2f33}.custom-theme .el-menu--collapse{width:64px}.custom-theme .el-menu--collapse>.el-menu-item [class^=el-icon-],.custom-theme .el-menu--collapse>.el-submenu>.el-submenu__title [class^=el-icon-]{margin:0;vertical-align:middle;width:24px;text-align:center}.custom-theme .el-menu--collapse>.el-menu-item .el-submenu__icon-arrow,.custom-theme .el-menu--collapse>.el-submenu>.el-submenu__title .el-submenu__icon-arrow{display:none}.custom-theme .el-menu--collapse>.el-menu-item span,.custom-theme .el-menu--collapse>.el-submenu>.el-submenu__title span{height:0;width:0;overflow:hidden;visibility:hidden;display:inline-block}.custom-theme .el-menu--collapse>.el-menu-item.is-active i{color:inherit}.custom-theme .el-menu--collapse .el-menu .el-submenu{min-width:200px}.custom-theme .el-menu--collapse .el-submenu{position:relative}.custom-theme .el-menu--collapse .el-submenu .el-menu{position:absolute;margin-left:5px;top:0;left:100%;z-index:10;border:1px solid #dfe4ed;border-radius:2px;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.custom-theme .el-menu--collapse .el-submenu.is-opened>.el-submenu__title .el-submenu__icon-arrow{-webkit-transform:none;transform:none}.custom-theme .el-menu-item{height:56px;line-height:56px;font-size:14px;color:#2d2f33;padding:0 20px;cursor:pointer;position:relative;-webkit-transition:border-color .3s,background-color .3s,color .3s;transition:border-color .3s,background-color .3s,color .3s;-webkit-box-sizing:border-box;box-sizing:border-box;white-space:nowrap}.custom-theme .el-menu-item [class^=el-icon-]{margin-right:5px;width:24px;text-align:center;font-size:18px}.custom-theme .el-menu-item *{vertical-align:middle}.custom-theme .el-menu-item:first-child{margin-left:0}.custom-theme .el-menu-item:last-child{margin-right:0}.custom-theme .el-menu-item:focus,.custom-theme .el-menu-item:hover{outline:0;background-color:#e9e9ea}.custom-theme .el-menu-item i{color:#878d99}.custom-theme .el-menu-item.is-active{color:#262729}.custom-theme .el-menu-item.is-active i{color:inherit}.custom-theme .el-submenu__title{position:relative;height:56px;line-height:56px;font-size:14px;color:#2d2f33;padding:0 20px;cursor:pointer;position:relative;-webkit-transition:border-color .3s,background-color .3s,color .3s;transition:border-color .3s,background-color .3s,color .3s;-webkit-box-sizing:border-box;box-sizing:border-box;white-space:nowrap}.custom-theme .el-submenu__title *{vertical-align:middle}.custom-theme .el-submenu__title i{color:#878d99}.custom-theme .el-submenu__title:hover{background-color:#e9e9ea}.custom-theme .el-submenu .el-menu{border:none}.custom-theme .el-submenu .el-menu-item{height:50px;line-height:50px;padding:0 45px;min-width:200px}.custom-theme .el-submenu__icon-arrow{position:absolute;top:50%;right:20px;margin-top:-7px;-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;font-size:12px}.custom-theme .el-submenu.is-active .el-submenu__title{border-bottom-color:#262729}.custom-theme .el-submenu.is-opened>.el-submenu__title .el-submenu__icon-arrow{-webkit-transform:rotateZ(180deg);transform:rotateZ(180deg)}.custom-theme .el-submenu [class^=el-icon-]{vertical-align:middle;margin-right:5px;width:24px;text-align:center;font-size:18px}.custom-theme .el-menu-item-group>ul{padding:0}.custom-theme .el-menu-item-group__title{padding:7px 0 7px 20px;line-height:normal;font-size:12px;color:#878d99}.custom-theme .horizontal-collapse-transition .el-submenu__title .el-submenu__icon-arrow{-webkit-transition:.2s;transition:.2s;opacity:0}.custom-theme .el-input{position:relative;font-size:14px;display:inline-block;width:100%}.custom-theme .el-input::-webkit-scrollbar{z-index:11;width:6px}.custom-theme .el-input::-webkit-scrollbar:horizontal{height:6px}.custom-theme .el-input::-webkit-scrollbar-thumb{border-radius:5px;width:6px;background:#b4bccc}.custom-theme .el-input::-webkit-scrollbar-corner{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track-piece{background:#fff;width:6px}.custom-theme .el-input__inner{-webkit-appearance:none;background-color:#fff;background-image:none;border-radius:4px;border:1px solid #d8dce5;-webkit-box-sizing:border-box;box-sizing:border-box;color:#5a5e66;display:inline-block;font-size:inherit;height:40px;line-height:1;outline:0;padding:0 15px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1);width:100%}.custom-theme .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input__inner:hover{border-color:#b4bccc}.custom-theme .el-input__inner:focus{outline:0;border-color:#262729}.custom-theme .el-input__suffix{position:absolute;height:100%;right:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s;pointer-events:none}.custom-theme .el-input__suffix-inner{pointer-events:all}.custom-theme .el-input__prefix{position:absolute;height:100%;left:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s}.custom-theme .el-input__icon{height:100%;width:25px;text-align:center;-webkit-transition:all .3s;transition:all .3s;line-height:40px}.custom-theme .el-input__icon:after{content:'';height:100%;width:0;display:inline-block;vertical-align:middle}.custom-theme .el-input__validateIcon{pointer-events:none}.custom-theme .el-input.is-active .el-input__inner{outline:0;border-color:#262729}.custom-theme .el-input.is-disabled .el-input__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-input.is-disabled .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__icon{cursor:not-allowed}.custom-theme .el-input--suffix .el-input__inner{padding-right:30px}.custom-theme .el-input--prefix .el-input__inner{padding-left:30px}.custom-theme .el-input--medium{font-size:14px}.custom-theme .el-input--medium .el-input__inner{height:36px}.custom-theme .el-input--medium .el-input__icon{line-height:36px}.custom-theme .el-input--small{font-size:13px}.custom-theme .el-input--small .el-input__inner{height:32px}.custom-theme .el-input--small .el-input__icon{line-height:32px}.custom-theme .el-input--mini{font-size:12px}.custom-theme .el-input--mini .el-input__inner{height:28px}.custom-theme .el-input--mini .el-input__icon{line-height:28px}.custom-theme .el-input-group{line-height:normal;display:inline-table;width:100%;border-collapse:separate}.custom-theme .el-input-group>.el-input__inner{vertical-align:middle;display:table-cell}.custom-theme .el-input-group__append,.custom-theme .el-input-group__prepend{background-color:#f5f7fa;color:#0a76a4;vertical-align:middle;display:table-cell;position:relative;border:1px solid #d8dce5;border-radius:4px;padding:0 20px;width:1px;white-space:nowrap}.custom-theme .el-input-group__append:focus,.custom-theme .el-input-group__prepend:focus{outline:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-select,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-select{display:inline-block;margin:-20px}.custom-theme .el-input-group__append button.el-button,.custom-theme .el-input-group__append div.el-select .el-input__inner,.custom-theme .el-input-group__append div.el-select:hover .el-input__inner,.custom-theme .el-input-group__prepend button.el-button,.custom-theme .el-input-group__prepend div.el-select .el-input__inner,.custom-theme .el-input-group__prepend div.el-select:hover .el-input__inner{border-color:transparent;background-color:transparent;color:inherit;border-top:0;border-bottom:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-input,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-input{font-size:inherit}.custom-theme .el-input-group__prepend{border-right:0;border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-input-group__append{border-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--prepend .el-input__inner{border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--append .el-input__inner{border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-textarea{display:inline-block;width:100%;vertical-align:bottom}.custom-theme .el-textarea__inner{display:block;resize:vertical;padding:5px 15px;line-height:1.5;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;font-size:14px;color:#5a5e66;background-color:#fff;background-image:none;border:1px solid #d8dce5;border-radius:4px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}.custom-theme .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:hover{border-color:#b4bccc}.custom-theme .el-textarea__inner:focus{outline:0;border-color:#262729}.custom-theme .el-textarea.is-disabled .el-textarea__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-input{position:relative;font-size:14px;display:inline-block;width:100%}.custom-theme .el-input::-webkit-scrollbar{z-index:11;width:6px}.custom-theme .el-input::-webkit-scrollbar:horizontal{height:6px}.custom-theme .el-input::-webkit-scrollbar-thumb{border-radius:5px;width:6px;background:#b4bccc}.custom-theme .el-input::-webkit-scrollbar-corner{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track-piece{background:#fff;width:6px}.custom-theme .el-input__inner{-webkit-appearance:none;background-color:#fff;background-image:none;border-radius:4px;border:1px solid #d8dce5;-webkit-box-sizing:border-box;box-sizing:border-box;color:#5a5e66;display:inline-block;font-size:inherit;height:40px;line-height:1;outline:0;padding:0 15px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1);width:100%}.custom-theme .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input__inner:hover{border-color:#b4bccc}.custom-theme .el-input__inner:focus{outline:0;border-color:#262729}.custom-theme .el-input__suffix{position:absolute;height:100%;right:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s;pointer-events:none}.custom-theme .el-input__suffix-inner{pointer-events:all}.custom-theme .el-input__prefix{position:absolute;height:100%;left:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s}.custom-theme .el-input__icon{height:100%;width:25px;text-align:center;-webkit-transition:all .3s;transition:all .3s;line-height:40px}.custom-theme .el-input__icon:after{content:'';height:100%;width:0;display:inline-block;vertical-align:middle}.custom-theme .el-input__validateIcon{pointer-events:none}.custom-theme .el-input.is-active .el-input__inner{outline:0;border-color:#262729}.custom-theme .el-input.is-disabled .el-input__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-input.is-disabled .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__icon{cursor:not-allowed}.custom-theme .el-input--suffix .el-input__inner{padding-right:30px}.custom-theme .el-input--prefix .el-input__inner{padding-left:30px}.custom-theme .el-input--medium{font-size:14px}.custom-theme .el-input--medium .el-input__inner{height:36px}.custom-theme .el-input--medium .el-input__icon{line-height:36px}.custom-theme .el-input--small{font-size:13px}.custom-theme .el-input--small .el-input__inner{height:32px}.custom-theme .el-input--small .el-input__icon{line-height:32px}.custom-theme .el-input--mini{font-size:12px}.custom-theme .el-input--mini .el-input__inner{height:28px}.custom-theme .el-input--mini .el-input__icon{line-height:28px}.custom-theme .el-input-group{line-height:normal;display:inline-table;width:100%;border-collapse:separate}.custom-theme .el-input-group>.el-input__inner{vertical-align:middle;display:table-cell}.custom-theme .el-input-group__append,.custom-theme .el-input-group__prepend{background-color:#f5f7fa;color:#0a76a4;vertical-align:middle;display:table-cell;position:relative;border:1px solid #d8dce5;border-radius:4px;padding:0 20px;width:1px;white-space:nowrap}.custom-theme .el-input-group__append:focus,.custom-theme .el-input-group__prepend:focus{outline:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-select,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-select{display:inline-block;margin:-20px}.custom-theme .el-input-group__append button.el-button,.custom-theme .el-input-group__append div.el-select .el-input__inner,.custom-theme .el-input-group__append div.el-select:hover .el-input__inner,.custom-theme .el-input-group__prepend button.el-button,.custom-theme .el-input-group__prepend div.el-select .el-input__inner,.custom-theme .el-input-group__prepend div.el-select:hover .el-input__inner{border-color:transparent;background-color:transparent;color:inherit;border-top:0;border-bottom:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-input,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-input{font-size:inherit}.custom-theme .el-input-group__prepend{border-right:0;border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-input-group__append{border-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--prepend .el-input__inner{border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--append .el-input__inner{border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-textarea{display:inline-block;width:100%;vertical-align:bottom}.custom-theme .el-textarea__inner{display:block;resize:vertical;padding:5px 15px;line-height:1.5;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;font-size:14px;color:#5a5e66;background-color:#fff;background-image:none;border:1px solid #d8dce5;border-radius:4px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}.custom-theme .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:hover{border-color:#b4bccc}.custom-theme .el-textarea__inner:focus{outline:0;border-color:#262729}.custom-theme .el-textarea.is-disabled .el-textarea__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-input-number{position:relative;display:inline-block;width:180px;line-height:38px}.custom-theme .el-input-number .el-input{display:block}.custom-theme .el-input-number .el-input__inner{-webkit-appearance:none;padding-left:50px;padding-right:50px;text-align:center}.custom-theme .el-input-number__decrease,.custom-theme .el-input-number__increase{position:absolute;z-index:1;top:1px;width:40px;height:auto;text-align:center;background:#f5f7fa;color:#5a5e66;cursor:pointer;font-size:13px}.custom-theme .el-input-number__decrease:hover,.custom-theme .el-input-number__increase:hover{color:#262729}.custom-theme .el-input-number__decrease:hover:not(.is-disabled)~.el-input .el-input__inner:not(.is-disabled),.custom-theme .el-input-number__increase:hover:not(.is-disabled)~.el-input .el-input__inner:not(.is-disabled){border-color:#262729}.custom-theme .el-input-number__decrease.is-disabled,.custom-theme .el-input-number__increase.is-disabled{color:#b4bccc;cursor:not-allowed}.custom-theme .el-input-number__increase{right:1px;border-radius:0 4px 4px 0;border-left:1px solid #d8dce5}.custom-theme .el-input-number__decrease{left:1px;border-radius:4px 0 0 4px;border-right:1px solid #d8dce5}.custom-theme .el-input-number.is-disabled .el-input-number__decrease,.custom-theme .el-input-number.is-disabled .el-input-number__increase{border-color:#dfe4ed;color:#dfe4ed}.custom-theme .el-input-number.is-disabled .el-input-number__decrease:hover,.custom-theme .el-input-number.is-disabled .el-input-number__increase:hover{color:#dfe4ed;cursor:not-allowed}.custom-theme .el-input-number--medium{width:200px;line-height:34px}.custom-theme .el-input-number--medium .el-input-number__decrease,.custom-theme .el-input-number--medium .el-input-number__increase{width:36px;font-size:14px}.custom-theme .el-input-number--medium .el-input__inner{padding-left:43px;padding-right:43px}.custom-theme .el-input-number--small{width:130px;line-height:30px}.custom-theme .el-input-number--small .el-input-number__decrease,.custom-theme .el-input-number--small .el-input-number__increase{width:32px;font-size:13px}.custom-theme .el-input-number--small .el-input-number__decrease [class*=el-icon],.custom-theme .el-input-number--small .el-input-number__increase [class*=el-icon]{-webkit-transform:scale(.9);transform:scale(.9)}.custom-theme .el-input-number--small .el-input__inner{padding-left:39px;padding-right:39px}.custom-theme .el-input-number--mini{width:130px;line-height:26px}.custom-theme .el-input-number--mini .el-input-number__decrease,.custom-theme .el-input-number--mini .el-input-number__increase{width:28px;font-size:12px}.custom-theme .el-input-number--mini .el-input-number__decrease [class*=el-icon],.custom-theme .el-input-number--mini .el-input-number__increase [class*=el-icon]{-webkit-transform:scale(.8);transform:scale(.8)}.custom-theme .el-input-number--mini .el-input__inner{padding-left:35px;padding-right:35px}.custom-theme .el-input-number.is-without-controls .el-input__inner{padding-left:15px;padding-right:15px}.custom-theme .el-input-number.is-controls-right .el-input__inner{padding-left:15px;padding-right:50px}.custom-theme .el-input-number.is-controls-right .el-input-number__decrease,.custom-theme .el-input-number.is-controls-right .el-input-number__increase{height:auto;line-height:19px}.custom-theme .el-input-number.is-controls-right .el-input-number__decrease [class*=el-icon],.custom-theme .el-input-number.is-controls-right .el-input-number__increase [class*=el-icon]{-webkit-transform:scale(.8);transform:scale(.8)}.custom-theme .el-input-number.is-controls-right .el-input-number__increase{border-radius:0 4px 0 0;border-bottom:1px solid #d8dce5}.custom-theme .el-input-number.is-controls-right .el-input-number__decrease{right:1px;bottom:1px;top:auto;left:auto;border-right:none;border-left:1px solid #d8dce5;border-radius:0 0 4px 0}.custom-theme .el-input-number.is-controls-right[class*=medium] [class*=decrease],.custom-theme .el-input-number.is-controls-right[class*=medium] [class*=increase]{line-height:17px}.custom-theme .el-input-number.is-controls-right[class*=small] [class*=decrease],.custom-theme .el-input-number.is-controls-right[class*=small] [class*=increase]{line-height:15px}.custom-theme .el-input-number.is-controls-right[class*=mini] [class*=decrease],.custom-theme .el-input-number.is-controls-right[class*=mini] [class*=increase]{line-height:13px}.custom-theme .el-radio{color:#5a5e66;font-weight:500;line-height:1;position:relative;cursor:pointer;display:inline-block;white-space:nowrap;outline:0;font-size:14px;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.custom-theme .el-radio.is-bordered{padding:10px 20px 10px 10px;border-radius:4px;border:1px solid #d8dce5}.custom-theme .el-radio.is-bordered.is-checked{border-color:#262729}.custom-theme .el-radio.is-bordered.is-disabled{cursor:not-allowed;border-color:#e6ebf5}.custom-theme .el-radio.is-bordered+.el-radio.is-bordered{margin-left:10px}.custom-theme .el-radio--medium.is-bordered{padding:8px 20px 8px 10px;border-radius:4px}.custom-theme .el-radio--medium.is-bordered .el-radio__label{font-size:14px}.custom-theme .el-radio--medium.is-bordered .el-radio__inner{height:14px;width:14px}.custom-theme .el-radio--small.is-bordered{padding:6px 15px 6px 10px;border-radius:3px}.custom-theme .el-radio--small.is-bordered .el-radio__label{font-size:12px}.custom-theme .el-radio--small.is-bordered .el-radio__inner{height:12px;width:12px}.custom-theme .el-radio--mini.is-bordered{padding:4px 15px 4px 10px;border-radius:3px}.custom-theme .el-radio--mini.is-bordered .el-radio__label{font-size:12px}.custom-theme .el-radio--mini.is-bordered .el-radio__inner{height:12px;width:12px}.custom-theme .el-radio+.el-radio{margin-left:30px}.custom-theme .el-radio__input{white-space:nowrap;cursor:pointer;outline:0;display:inline-block;line-height:1;position:relative;vertical-align:middle}.custom-theme .el-radio__input.is-disabled .el-radio__inner{background-color:#f5f7fa;border-color:#dfe4ed;cursor:not-allowed}.custom-theme .el-radio__input.is-disabled .el-radio__inner::after{cursor:not-allowed;background-color:#f5f7fa}.custom-theme .el-radio__input.is-disabled .el-radio__inner+.el-radio__label{cursor:not-allowed}.custom-theme .el-radio__input.is-disabled.is-checked .el-radio__inner{background-color:#f5f7fa;border-color:#dfe4ed}.custom-theme .el-radio__input.is-disabled.is-checked .el-radio__inner::after{background-color:#b4bccc}.custom-theme .el-radio__input.is-disabled+span.el-radio__label{color:#b4bccc;cursor:not-allowed}.custom-theme .el-radio__input.is-checked .el-radio__inner{border-color:#262729;background:#262729}.custom-theme .el-radio__input.is-checked .el-radio__inner::after{-webkit-transform:translate(-50%,-50%) scale(1);transform:translate(-50%,-50%) scale(1)}.custom-theme .el-radio__input.is-checked+.el-radio__label{color:#262729}.custom-theme .el-radio__input.is-focus .el-radio__inner{border-color:#262729}.custom-theme .el-radio__inner{border:1px solid #d8dce5;border-radius:100%;width:14px;height:14px;background-color:#fff;position:relative;cursor:pointer;display:inline-block;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-radio__inner:hover{border-color:#262729}.custom-theme .el-radio__inner::after{width:4px;height:4px;border-radius:100%;background-color:#fff;content:"";position:absolute;left:50%;top:50%;-webkit-transform:translate(-50%,-50%) scale(0);transform:translate(-50%,-50%) scale(0);-webkit-transition:-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6);transition:-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6);transition:transform .15s cubic-bezier(.71,-.46,.88,.6);transition:transform .15s cubic-bezier(.71,-.46,.88,.6),-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6)}.custom-theme .el-radio__original{opacity:0;outline:0;position:absolute;z-index:-1;top:0;left:0;right:0;bottom:0;margin:0}.custom-theme .el-radio:focus:not(.is-focus):not(:active) .el-radio__inner{-webkit-box-shadow:0 0 2px 2px #262729;box-shadow:0 0 2px 2px #262729}.custom-theme .el-radio__label{font-size:14px;padding-left:10px}.custom-theme .el-radio-group{display:inline-block;line-height:1;vertical-align:middle;font-size:0}.custom-theme .el-radio-button{position:relative;display:inline-block;outline:0}.custom-theme .el-radio-button__inner{display:inline-block;line-height:1;white-space:nowrap;vertical-align:middle;background:#fff;border:1px solid #d8dce5;font-weight:500;border-left:0;color:#5a5e66;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;position:relative;cursor:pointer;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);padding:12px 20px;font-size:14px;border-radius:0}.custom-theme .el-radio-button__inner.is-round{padding:12px 20px}.custom-theme .el-radio-button__inner:hover{color:#262729}.custom-theme .el-radio-button__inner [class*=el-icon-]{line-height:.9}.custom-theme .el-radio-button__inner [class*=el-icon-]+span{margin-left:5px}.custom-theme .el-radio-button__orig-radio{opacity:0;outline:0;position:absolute;z-index:-1;left:-999px}.custom-theme .el-radio-button__orig-radio:checked+.el-radio-button__inner{color:#fff;background-color:#262729;border-color:#262729;-webkit-box-shadow:-1px 0 0 0 #262729;box-shadow:-1px 0 0 0 #262729}.custom-theme .el-radio-button__orig-radio:disabled+.el-radio-button__inner{color:#b4bccc;cursor:not-allowed;background-image:none;background-color:#fff;border-color:#e6ebf5;-webkit-box-shadow:none;box-shadow:none}.custom-theme .el-radio-button__orig-radio:disabled:checked+.el-radio-button__inner{background-color:#edf2fc}.custom-theme .el-radio-button:first-child .el-radio-button__inner{border-left:1px solid #d8dce5;border-radius:4px 0 0 4px;-webkit-box-shadow:none!important;box-shadow:none!important}.custom-theme .el-radio-button:last-child .el-radio-button__inner{border-radius:0 4px 4px 0}.custom-theme .el-radio-button:first-child:last-child .el-radio-button__inner{border-radius:4px}.custom-theme .el-radio-button--medium .el-radio-button__inner{padding:10px 20px;font-size:14px;border-radius:0}.custom-theme .el-radio-button--medium .el-radio-button__inner.is-round{padding:10px 20px}.custom-theme .el-radio-button--small .el-radio-button__inner{padding:9px 15px;font-size:12px;border-radius:0}.custom-theme .el-radio-button--small .el-radio-button__inner.is-round{padding:9px 15px}.custom-theme .el-radio-button--mini .el-radio-button__inner{padding:7px 15px;font-size:12px;border-radius:0}.custom-theme .el-radio-button--mini .el-radio-button__inner.is-round{padding:7px 15px}.custom-theme .el-radio-button:focus:not(.is-focus):not(:active){-webkit-box-shadow:0 0 2px 2px #262729;box-shadow:0 0 2px 2px #262729}.custom-theme .el-checkbox{color:#5a5e66;font-weight:500;font-size:14px;position:relative;cursor:pointer;display:inline-block;white-space:nowrap;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.custom-theme .el-checkbox.is-bordered{padding:9px 20px 9px 10px;border-radius:4px;border:1px solid #d8dce5}.custom-theme .el-checkbox.is-bordered.is-checked{border-color:#262729}.custom-theme .el-checkbox.is-bordered.is-disabled{border-color:#e6ebf5;cursor:not-allowed}.custom-theme .el-checkbox.is-bordered+.el-checkbox.is-bordered{margin-left:10px}.custom-theme .el-checkbox.is-bordered.el-checkbox--medium{padding:7px 20px 7px 10px;border-radius:4px}.custom-theme .el-checkbox.is-bordered.el-checkbox--medium .el-checkbox__label{line-height:17px;font-size:14px}.custom-theme .el-checkbox.is-bordered.el-checkbox--medium .el-checkbox__inner{height:14px;width:14px}.custom-theme .el-checkbox.is-bordered.el-checkbox--small{padding:3px 15px 7px 10px;border-radius:3px}.custom-theme .el-checkbox.is-bordered.el-checkbox--small .el-checkbox__label{line-height:15px;font-size:12px}.custom-theme .el-checkbox.is-bordered.el-checkbox--small .el-checkbox__inner{height:12px;width:12px}.custom-theme .el-checkbox.is-bordered.el-checkbox--small .el-checkbox__inner::after{height:6px;width:2px}.custom-theme .el-checkbox.is-bordered.el-checkbox--mini{padding:1px 15px 5px 10px;border-radius:3px}.custom-theme .el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__label{line-height:12px;font-size:12px}.custom-theme .el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__inner{height:12px;width:12px}.custom-theme .el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__inner::after{height:6px;width:2px}.custom-theme .el-checkbox__input{white-space:nowrap;cursor:pointer;outline:0;display:inline-block;line-height:1;position:relative;vertical-align:middle}.custom-theme .el-checkbox__input.is-disabled .el-checkbox__inner{background-color:#edf2fc;border-color:#d8dce5;cursor:not-allowed}.custom-theme .el-checkbox__input.is-disabled .el-checkbox__inner::after{cursor:not-allowed;border-color:#b4bccc}.custom-theme .el-checkbox__input.is-disabled .el-checkbox__inner+.el-checkbox__label{cursor:not-allowed}.custom-theme .el-checkbox__input.is-disabled.is-checked .el-checkbox__inner{background-color:#edf2fc;border-color:#d8dce5}.custom-theme .el-checkbox__input.is-disabled.is-checked .el-checkbox__inner::after{border-color:#b4bccc}.custom-theme .el-checkbox__input.is-disabled.is-indeterminate .el-checkbox__inner{background-color:#edf2fc;border-color:#d8dce5}.custom-theme .el-checkbox__input.is-disabled.is-indeterminate .el-checkbox__inner::before{background-color:#b4bccc;border-color:#b4bccc}.custom-theme .el-checkbox__input.is-disabled+span.el-checkbox__label{color:#b4bccc;cursor:not-allowed}.custom-theme .el-checkbox__input.is-checked .el-checkbox__inner{background-color:#262729;border-color:#262729}.custom-theme .el-checkbox__input.is-checked .el-checkbox__inner::after{-webkit-transform:rotate(45deg) scaleY(1);transform:rotate(45deg) scaleY(1)}.custom-theme .el-checkbox__input.is-checked+.el-checkbox__label{color:#262729}.custom-theme .el-checkbox__input.is-focus .el-checkbox__inner{border-color:#262729}.custom-theme .el-checkbox__input.is-indeterminate .el-checkbox__inner{background-color:#262729;border-color:#262729}.custom-theme .el-checkbox__input.is-indeterminate .el-checkbox__inner::before{content:'';position:absolute;display:block;background-color:#fff;height:2px;-webkit-transform:scale(.5);transform:scale(.5);left:0;right:0;top:5px}.custom-theme .el-checkbox__input.is-indeterminate .el-checkbox__inner::after{display:none}.custom-theme .el-checkbox__inner{display:inline-block;position:relative;border:1px solid #d8dce5;border-radius:2px;-webkit-box-sizing:border-box;box-sizing:border-box;width:14px;height:14px;background-color:#fff;z-index:1;-webkit-transition:border-color .25s cubic-bezier(.71,-.46,.29,1.46),background-color .25s cubic-bezier(.71,-.46,.29,1.46);transition:border-color .25s cubic-bezier(.71,-.46,.29,1.46),background-color .25s cubic-bezier(.71,-.46,.29,1.46)}.custom-theme .el-checkbox__inner:hover{border-color:#262729}.custom-theme .el-checkbox__inner::after{-webkit-box-sizing:content-box;box-sizing:content-box;content:"";border:1px solid #fff;border-left:0;border-top:0;height:7px;left:4px;position:absolute;top:1px;-webkit-transform:rotate(45deg) scaleY(0);transform:rotate(45deg) scaleY(0);width:3px;-webkit-transition:-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms;transition:-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms;transition:transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms;transition:transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms,-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms;-webkit-transform-origin:center;transform-origin:center}.custom-theme .el-checkbox__original{opacity:0;outline:0;position:absolute;margin:0;width:0;height:0;left:-999px}.custom-theme .el-checkbox__label{display:inline-block;padding-left:10px;line-height:19px;font-size:14px}.custom-theme .el-checkbox+.el-checkbox{margin-left:30px}.custom-theme .el-checkbox-button{position:relative;display:inline-block}.custom-theme .el-checkbox-button__inner{display:inline-block;line-height:1;font-weight:500;white-space:nowrap;vertical-align:middle;cursor:pointer;background:#fff;border:1px solid #d8dce5;border-left:0;color:#5a5e66;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;position:relative;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;padding:12px 20px;font-size:14px;border-radius:0}.custom-theme .el-checkbox-button__inner.is-round{padding:12px 20px}.custom-theme .el-checkbox-button__inner:hover{color:#262729}.custom-theme .el-checkbox-button__inner [class*=el-icon-]{line-height:.9}.custom-theme .el-checkbox-button__inner [class*=el-icon-]+span{margin-left:5px}.custom-theme .el-checkbox-button__original{opacity:0;outline:0;position:absolute;margin:0;left:-999px}.custom-theme .el-checkbox-button.is-checked .el-checkbox-button__inner{color:#fff;background-color:#262729;border-color:#262729;-webkit-box-shadow:-1px 0 0 0 #7d7d7f;box-shadow:-1px 0 0 0 #7d7d7f}.custom-theme .el-checkbox-button.is-disabled .el-checkbox-button__inner{color:#b4bccc;cursor:not-allowed;background-image:none;background-color:#fff;border-color:#e6ebf5;-webkit-box-shadow:none;box-shadow:none}.custom-theme .el-checkbox-button:first-child .el-checkbox-button__inner{border-left:1px solid #d8dce5;border-radius:4px 0 0 4px;-webkit-box-shadow:none!important;box-shadow:none!important}.custom-theme .el-checkbox-button.is-focus .el-checkbox-button__inner{border-color:#262729}.custom-theme .el-checkbox-button:last-child .el-checkbox-button__inner{border-radius:0 4px 4px 0}.custom-theme .el-checkbox-button--medium .el-checkbox-button__inner{padding:10px 20px;font-size:14px;border-radius:0}.custom-theme .el-checkbox-button--medium .el-checkbox-button__inner.is-round{padding:10px 20px}.custom-theme .el-checkbox-button--small .el-checkbox-button__inner{padding:9px 15px;font-size:12px;border-radius:0}.custom-theme .el-checkbox-button--small .el-checkbox-button__inner.is-round{padding:9px 15px}.custom-theme .el-checkbox-button--mini .el-checkbox-button__inner{padding:7px 15px;font-size:12px;border-radius:0}.custom-theme .el-checkbox-button--mini .el-checkbox-button__inner.is-round{padding:7px 15px}.custom-theme .el-checkbox-group{font-size:0}.custom-theme .el-switch{display:inline-block;position:relative;font-size:14px;line-height:20px;height:20px;vertical-align:middle}.custom-theme .el-switch.is-disabled .el-switch__core,.custom-theme .el-switch.is-disabled .el-switch__label{cursor:not-allowed}.custom-theme .el-switch__label{-webkit-transition:.2s;transition:.2s;height:20px;display:inline-block;font-size:14px;font-weight:500;cursor:pointer;vertical-align:middle;color:#2d2f33}.custom-theme .el-switch__label.is-active{color:#262729}.custom-theme .el-switch__label--left{margin-right:10px}.custom-theme .el-switch__label--right{margin-left:10px}.custom-theme .el-switch__label *{line-height:1;font-size:14px;display:inline-block}.custom-theme .el-switch__input{position:absolute;width:0;height:0;opacity:0;margin:0}.custom-theme .el-switch__input:focus~.el-switch__core{outline:1px solid #262729}.custom-theme .el-switch__core{margin:0;display:inline-block;position:relative;width:40px;height:20px;border:1px solid #d8dce5;outline:0;border-radius:10px;-webkit-box-sizing:border-box;box-sizing:border-box;background:#d8dce5;cursor:pointer;-webkit-transition:border-color .3s,background-color .3s;transition:border-color .3s,background-color .3s;vertical-align:middle}.custom-theme .el-switch__core .el-switch__button{position:absolute;top:1px;left:1px;border-radius:100%;-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;width:16px;height:16px;background-color:#fff}.custom-theme .el-switch.is-checked .el-switch__core{border-color:#262729;background-color:#262729}.custom-theme .el-switch.is-disabled{opacity:.6}.custom-theme .el-switch--wide .el-switch__label.el-switch__label--left span{left:10px}.custom-theme .el-switch--wide .el-switch__label.el-switch__label--right span{right:10px}.custom-theme .el-switch .label-fade-enter,.custom-theme .el-switch .label-fade-leave-active{opacity:0}.custom-theme .el-popper .popper__arrow,.custom-theme .el-popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.custom-theme .el-popper .popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03));filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03))}.custom-theme .el-popper .popper__arrow::after{content:" ";border-width:6px}.custom-theme .el-popper[x-placement^=top]{margin-bottom:12px}.custom-theme .el-popper[x-placement^=top] .popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-top-color:#e6ebf5;border-bottom-width:0}.custom-theme .el-popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-6px;border-top-color:#fff;border-bottom-width:0}.custom-theme .el-popper[x-placement^=bottom]{margin-top:12px}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow{top:-6px;left:50%;margin-right:3px;border-top-width:0;border-bottom-color:#e6ebf5}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-6px;border-top-width:0;border-bottom-color:#fff}.custom-theme .el-popper[x-placement^=right]{margin-left:12px}.custom-theme .el-popper[x-placement^=right] .popper__arrow{top:50%;left:-6px;margin-bottom:3px;border-right-color:#e6ebf5;border-left-width:0}.custom-theme .el-popper[x-placement^=right] .popper__arrow::after{bottom:-6px;left:1px;border-right-color:#fff;border-left-width:0}.custom-theme .el-popper[x-placement^=left]{margin-right:12px}.custom-theme .el-popper[x-placement^=left] .popper__arrow{top:50%;right:-6px;margin-bottom:3px;border-right-width:0;border-left-color:#e6ebf5}.custom-theme .el-popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-6px;margin-left:-6px;border-right-width:0;border-left-color:#fff}.custom-theme .el-select-dropdown{position:absolute;z-index:1001;border:solid 1px #dfe4ed;border-radius:4px;background-color:#fff;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);-webkit-box-sizing:border-box;box-sizing:border-box;margin:5px 0}.custom-theme .el-select-dropdown.is-multiple .el-select-dropdown__item.selected{color:#262729;background-color:#fff}.custom-theme .el-select-dropdown.is-multiple .el-select-dropdown__item.selected.hover{background-color:#f5f7fa}.custom-theme .el-select-dropdown.is-multiple .el-select-dropdown__item.selected::after{position:absolute;right:20px;font-family:element-icons;content:"\E611";font-size:12px;font-weight:700;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.custom-theme .el-select-dropdown .el-scrollbar.is-empty .el-select-dropdown__list{padding:0}.custom-theme .el-select-dropdown .popper__arrow{-webkit-transform:translateX(-400%);transform:translateX(-400%)}.custom-theme .el-select-dropdown.is-arrow-fixed .popper__arrow{-webkit-transform:translateX(-200%);transform:translateX(-200%)}.custom-theme .el-select-dropdown__empty{padding:10px 0;margin:0;text-align:center;color:#999;font-size:14px}.custom-theme .el-select-dropdown__wrap{max-height:274px}.custom-theme .el-select-dropdown__list{list-style:none;padding:6px 0;margin:0;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-input{position:relative;font-size:14px;display:inline-block;width:100%}.custom-theme .el-input::-webkit-scrollbar{z-index:11;width:6px}.custom-theme .el-input::-webkit-scrollbar:horizontal{height:6px}.custom-theme .el-input::-webkit-scrollbar-thumb{border-radius:5px;width:6px;background:#b4bccc}.custom-theme .el-input::-webkit-scrollbar-corner{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track-piece{background:#fff;width:6px}.custom-theme .el-input__inner{-webkit-appearance:none;background-color:#fff;background-image:none;border-radius:4px;border:1px solid #d8dce5;-webkit-box-sizing:border-box;box-sizing:border-box;color:#5a5e66;display:inline-block;font-size:inherit;height:40px;line-height:1;outline:0;padding:0 15px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1);width:100%}.custom-theme .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input__inner:hover{border-color:#b4bccc}.custom-theme .el-input__inner:focus{outline:0;border-color:#262729}.custom-theme .el-input__suffix{position:absolute;height:100%;right:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s;pointer-events:none}.custom-theme .el-input__suffix-inner{pointer-events:all}.custom-theme .el-input__prefix{position:absolute;height:100%;left:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s}.custom-theme .el-input__icon{height:100%;width:25px;text-align:center;-webkit-transition:all .3s;transition:all .3s;line-height:40px}.custom-theme .el-input__icon:after{content:'';height:100%;width:0;display:inline-block;vertical-align:middle}.custom-theme .el-input__validateIcon{pointer-events:none}.custom-theme .el-input.is-active .el-input__inner{outline:0;border-color:#262729}.custom-theme .el-input.is-disabled .el-input__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-input.is-disabled .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__icon{cursor:not-allowed}.custom-theme .el-input--suffix .el-input__inner{padding-right:30px}.custom-theme .el-input--prefix .el-input__inner{padding-left:30px}.custom-theme .el-input--medium{font-size:14px}.custom-theme .el-input--medium .el-input__inner{height:36px}.custom-theme .el-input--medium .el-input__icon{line-height:36px}.custom-theme .el-input--small{font-size:13px}.custom-theme .el-input--small .el-input__inner{height:32px}.custom-theme .el-input--small .el-input__icon{line-height:32px}.custom-theme .el-input--mini{font-size:12px}.custom-theme .el-input--mini .el-input__inner{height:28px}.custom-theme .el-input--mini .el-input__icon{line-height:28px}.custom-theme .el-input-group{line-height:normal;display:inline-table;width:100%;border-collapse:separate}.custom-theme .el-input-group>.el-input__inner{vertical-align:middle;display:table-cell}.custom-theme .el-input-group__append,.custom-theme .el-input-group__prepend{background-color:#f5f7fa;color:#0a76a4;vertical-align:middle;display:table-cell;position:relative;border:1px solid #d8dce5;border-radius:4px;padding:0 20px;width:1px;white-space:nowrap}.custom-theme .el-input-group__append:focus,.custom-theme .el-input-group__prepend:focus{outline:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-select,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-select{display:inline-block;margin:-20px}.custom-theme .el-input-group__append button.el-button,.custom-theme .el-input-group__append div.el-select .el-input__inner,.custom-theme .el-input-group__append div.el-select:hover .el-input__inner,.custom-theme .el-input-group__prepend button.el-button,.custom-theme .el-input-group__prepend div.el-select .el-input__inner,.custom-theme .el-input-group__prepend div.el-select:hover .el-input__inner{border-color:transparent;background-color:transparent;color:inherit;border-top:0;border-bottom:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-input,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-input{font-size:inherit}.custom-theme .el-input-group__prepend{border-right:0;border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-input-group__append{border-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--prepend .el-input__inner{border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--append .el-input__inner{border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-textarea{display:inline-block;width:100%;vertical-align:bottom}.custom-theme .el-textarea__inner{display:block;resize:vertical;padding:5px 15px;line-height:1.5;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;font-size:14px;color:#5a5e66;background-color:#fff;background-image:none;border:1px solid #d8dce5;border-radius:4px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}.custom-theme .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:hover{border-color:#b4bccc}.custom-theme .el-textarea__inner:focus{outline:0;border-color:#262729}.custom-theme .el-textarea.is-disabled .el-textarea__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-tag{background-color:rgba(38,39,41,.1);display:inline-block;padding:0 10px;height:32px;line-height:30px;font-size:12px;color:#262729;border-radius:4px;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid rgba(38,39,41,.2);white-space:nowrap}.custom-theme .el-tag .el-icon-close{border-radius:50%;text-align:center;position:relative;cursor:pointer;font-size:12px;height:18px;width:18px;line-height:18px;vertical-align:middle;top:-1px;right:-5px;color:#262729}.custom-theme .el-tag .el-icon-close::before{display:block}.custom-theme .el-tag .el-icon-close:hover{background-color:#262729;color:#fff}.custom-theme .el-tag--info{background-color:rgba(10,118,164,.1);border-color:rgba(10,118,164,.2);color:#0a76a4}.custom-theme .el-tag--info.is-hit{border-color:#0a76a4}.custom-theme .el-tag--info .el-tag__close{color:#0a76a4}.custom-theme .el-tag--info .el-tag__close:hover{background-color:#0a76a4;color:#fff}.custom-theme .el-tag--success{background-color:rgba(64,145,103,.1);border-color:rgba(64,145,103,.2);color:#409167}.custom-theme .el-tag--success.is-hit{border-color:#409167}.custom-theme .el-tag--success .el-tag__close{color:#409167}.custom-theme .el-tag--success .el-tag__close:hover{background-color:#409167;color:#fff}.custom-theme .el-tag--warning{background-color:rgba(157,164,8,.1);border-color:rgba(157,164,8,.2);color:#9da408}.custom-theme .el-tag--warning.is-hit{border-color:#9da408}.custom-theme .el-tag--warning .el-tag__close{color:#9da408}.custom-theme .el-tag--warning .el-tag__close:hover{background-color:#9da408;color:#fff}.custom-theme .el-tag--danger{background-color:rgba(179,69,14,.1);border-color:rgba(179,69,14,.2);color:#b3450e}.custom-theme .el-tag--danger.is-hit{border-color:#b3450e}.custom-theme .el-tag--danger .el-tag__close{color:#b3450e}.custom-theme .el-tag--danger .el-tag__close:hover{background-color:#b3450e;color:#fff}.custom-theme .el-tag--medium{height:28px;line-height:26px}.custom-theme .el-tag--medium .el-icon-close{-webkit-transform:scale(.8);transform:scale(.8)}.custom-theme .el-tag--small{height:24px;padding:0 8px;line-height:22px}.custom-theme .el-tag--small .el-icon-close{-webkit-transform:scale(.8);transform:scale(.8)}.custom-theme .el-tag--mini{height:20px;padding:0 5px;line-height:19px}.custom-theme .el-tag--mini .el-icon-close{margin-left:-3px;-webkit-transform:scale(.7);transform:scale(.7)}.custom-theme .el-select-dropdown__item{font-size:14px;padding:0 20px;position:relative;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:#5a5e66;height:34px;line-height:34px;-webkit-box-sizing:border-box;box-sizing:border-box;cursor:pointer}.custom-theme .el-select-dropdown__item.is-disabled{color:#b4bccc;cursor:not-allowed}.custom-theme .el-select-dropdown__item.is-disabled:hover{background-color:#fff}.custom-theme .el-select-dropdown__item.hover,.custom-theme .el-select-dropdown__item:hover{background-color:#f5f7fa}.custom-theme .el-select-dropdown__item.selected{color:#262729;font-weight:700}.custom-theme .el-select-dropdown__item span{line-height:34px!important}.custom-theme .el-select-group{margin:0;padding:0}.custom-theme .el-select-group__wrap{position:relative;list-style:none;margin:0;padding:0}.custom-theme .el-select-group__wrap:not(:last-of-type){padding-bottom:24px}.custom-theme .el-select-group__wrap:not(:last-of-type)::after{content:'';position:absolute;display:block;left:20px;right:20px;bottom:12px;height:1px;background:#dfe4ed}.custom-theme .el-select-group__title{padding-left:20px;font-size:12px;color:#0a76a4;line-height:30px}.custom-theme .el-select-group .el-select-dropdown__item{padding-left:20px}.custom-theme .el-scrollbar{overflow:hidden;position:relative}.custom-theme .el-scrollbar:active>.el-scrollbar__bar,.custom-theme .el-scrollbar:focus>.el-scrollbar__bar,.custom-theme .el-scrollbar:hover>.el-scrollbar__bar{opacity:1;-webkit-transition:opacity 340ms ease-out;transition:opacity 340ms ease-out}.custom-theme .el-scrollbar__wrap{overflow:scroll;height:100%}.custom-theme .el-scrollbar__wrap--hidden-default::-webkit-scrollbar{width:0;height:0}.custom-theme .el-scrollbar__thumb{position:relative;display:block;width:0;height:0;cursor:pointer;border-radius:inherit;background-color:rgba(135,141,153,.3);-webkit-transition:.3s background-color;transition:.3s background-color}.custom-theme .el-scrollbar__thumb:hover{background-color:rgba(135,141,153,.5)}.custom-theme .el-scrollbar__bar{position:absolute;right:2px;bottom:2px;z-index:1;border-radius:4px;opacity:0;-webkit-transition:opacity 120ms ease-out;transition:opacity 120ms ease-out}.custom-theme .el-scrollbar__bar.is-vertical{width:6px;top:2px}.custom-theme .el-scrollbar__bar.is-vertical>div{width:100%}.custom-theme .el-scrollbar__bar.is-horizontal{height:6px;left:2px}.custom-theme .el-scrollbar__bar.is-horizontal>div{height:100%}.custom-theme .el-select{display:inline-block;position:relative}.custom-theme .el-select:hover .el-input__inner{border-color:#b4bccc}.custom-theme .el-select .el-input__inner{cursor:pointer;padding-right:35px}.custom-theme .el-select .el-input__inner:focus{border-color:#262729}.custom-theme .el-select .el-input .el-select__caret{color:#b4bccc;font-size:14px;-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;-webkit-transform:rotateZ(180deg);transform:rotateZ(180deg);line-height:16px;cursor:pointer}.custom-theme .el-select .el-input .el-select__caret.is-reverse{-webkit-transform:rotateZ(0);transform:rotateZ(0)}.custom-theme .el-select .el-input .el-select__caret.is-show-close{font-size:14px;text-align:center;-webkit-transform:rotateZ(180deg);transform:rotateZ(180deg);border-radius:100%;color:#b4bccc;-webkit-transition:color .2s cubic-bezier(.645,.045,.355,1);transition:color .2s cubic-bezier(.645,.045,.355,1)}.custom-theme .el-select .el-input .el-select__caret.is-show-close:hover{color:#878d99}.custom-theme .el-select .el-input.is-disabled .el-input__inner{cursor:not-allowed}.custom-theme .el-select .el-input.is-disabled .el-input__inner:hover{border-color:#dfe4ed}.custom-theme .el-select>.el-input{display:block}.custom-theme .el-select__input{border:none;outline:0;padding:0;margin-left:15px;color:#666;font-size:14px;vertical-align:baseline;-webkit-appearance:none;-moz-appearance:none;appearance:none;height:28px;background-color:transparent}.custom-theme .el-select__input.is-mini{height:14px}.custom-theme .el-select__close{cursor:pointer;position:absolute;top:8px;z-index:1000;right:25px;color:#b4bccc;line-height:18px;font-size:14px}.custom-theme .el-select__close:hover{color:#878d99}.custom-theme .el-select__tags{position:absolute;line-height:normal;white-space:normal;z-index:1;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.custom-theme .el-select .el-tag__close{margin-top:-2px}.custom-theme .el-select .el-tag{-webkit-box-sizing:border-box;box-sizing:border-box;border-color:transparent;margin:3px 0 3px 6px;background-color:#f0f2f5}.custom-theme .el-select .el-tag__close.el-icon-close{background-color:#b4bccc;right:-7px;color:#fff}.custom-theme .el-select .el-tag__close.el-icon-close:hover{background-color:#878d99}.custom-theme .el-select .el-tag__close.el-icon-close::before{display:block;-webkit-transform:translate(0,.5px);transform:translate(0,.5px)}.custom-theme .el-select__tag{display:inline-block;height:24px;line-height:24px;font-size:14px;border-radius:4px;color:#fff;background-color:#262729}.custom-theme .el-select__tag .el-icon-close{font-size:14px}.custom-theme .el-button{display:inline-block;line-height:1;white-space:nowrap;cursor:pointer;background:#fff;border:1px solid #d8dce5;border-color:#d8dce5;color:#5a5e66;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;-webkit-transition:.1s;transition:.1s;font-weight:500;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;padding:12px 20px;font-size:14px;border-radius:4px}.custom-theme .el-button+.el-button{margin-left:10px}.custom-theme .el-button.is-round{padding:12px 20px}.custom-theme .el-button:focus,.custom-theme .el-button:hover{color:#262729;border-color:#bebebf;background-color:#e9e9ea}.custom-theme .el-button:active{color:#222325;border-color:#222325;outline:0}.custom-theme .el-button::-moz-focus-inner{border:0}.custom-theme .el-button [class*=el-icon-]+span{margin-left:5px}.custom-theme .el-button.is-plain:focus,.custom-theme .el-button.is-plain:hover{background:#fff;border-color:#262729;color:#262729}.custom-theme .el-button.is-plain:active{background:#fff;border-color:#222325;color:#222325;outline:0}.custom-theme .el-button.is-active{color:#222325;border-color:#222325}.custom-theme .el-button.is-disabled,.custom-theme .el-button.is-disabled:focus,.custom-theme .el-button.is-disabled:hover{color:#b4bccc;cursor:not-allowed;background-image:none;background-color:#fff;border-color:#e6ebf5}.custom-theme .el-button.is-disabled.el-button--text{background-color:transparent}.custom-theme .el-button.is-disabled.is-plain,.custom-theme .el-button.is-disabled.is-plain:focus,.custom-theme .el-button.is-disabled.is-plain:hover{background-color:#fff;border-color:#e6ebf5;color:#b4bccc}.custom-theme .el-button.is-loading{position:relative;pointer-events:none}.custom-theme .el-button.is-loading:before{pointer-events:none;content:'';position:absolute;left:-1px;top:-1px;right:-1px;bottom:-1px;border-radius:inherit;background-color:rgba(255,255,255,.35)}.custom-theme .el-button.is-round{border-radius:20px;padding:12px 23px}.custom-theme .el-button--primary{color:#fff;background-color:#262729;border-color:#262729}.custom-theme .el-button--primary:focus,.custom-theme .el-button--primary:hover{background:#515254;border-color:#515254;color:#fff}.custom-theme .el-button--primary:active{background:#222325;border-color:#222325;color:#fff;outline:0}.custom-theme .el-button--primary.is-active{background:#222325;border-color:#222325;color:#fff}.custom-theme .el-button--primary.is-disabled,.custom-theme .el-button--primary.is-disabled:active,.custom-theme .el-button--primary.is-disabled:focus,.custom-theme .el-button--primary.is-disabled:hover{color:#fff;background-color:#939394;border-color:#939394}.custom-theme .el-button--primary.is-plain{color:#262729;background:#e9e9ea;border-color:#a8a9a9}.custom-theme .el-button--primary.is-plain:focus,.custom-theme .el-button--primary.is-plain:hover{background:#262729;border-color:#262729;color:#fff}.custom-theme .el-button--primary.is-plain:active{background:#222325;border-color:#222325;color:#fff;outline:0}.custom-theme .el-button--primary.is-plain.is-disabled,.custom-theme .el-button--primary.is-plain.is-disabled:active,.custom-theme .el-button--primary.is-plain.is-disabled:focus,.custom-theme .el-button--primary.is-plain.is-disabled:hover{color:#7d7d7f;background-color:#e9e9ea;border-color:#d4d4d4}.custom-theme .el-button--success{color:#fff;background-color:#409167;border-color:#409167}.custom-theme .el-button--success:focus,.custom-theme .el-button--success:hover{background:#66a785;border-color:#66a785;color:#fff}.custom-theme .el-button--success:active{background:#3a835d;border-color:#3a835d;color:#fff;outline:0}.custom-theme .el-button--success.is-active{background:#3a835d;border-color:#3a835d;color:#fff}.custom-theme .el-button--success.is-disabled,.custom-theme .el-button--success.is-disabled:active,.custom-theme .el-button--success.is-disabled:focus,.custom-theme .el-button--success.is-disabled:hover{color:#fff;background-color:#a0c8b3;border-color:#a0c8b3}.custom-theme .el-button--success.is-plain{color:#409167;background:#ecf4f0;border-color:#b3d3c2}.custom-theme .el-button--success.is-plain:focus,.custom-theme .el-button--success.is-plain:hover{background:#409167;border-color:#409167;color:#fff}.custom-theme .el-button--success.is-plain:active{background:#3a835d;border-color:#3a835d;color:#fff;outline:0}.custom-theme .el-button--success.is-plain.is-disabled,.custom-theme .el-button--success.is-plain.is-disabled:active,.custom-theme .el-button--success.is-plain.is-disabled:focus,.custom-theme .el-button--success.is-plain.is-disabled:hover{color:#8cbda4;background-color:#ecf4f0;border-color:#d9e9e1}.custom-theme .el-button--warning{color:#fff;background-color:#9da408;border-color:#9da408}.custom-theme .el-button--warning:focus,.custom-theme .el-button--warning:hover{background:#b1b639;border-color:#b1b639;color:#fff}.custom-theme .el-button--warning:active{background:#8d9407;border-color:#8d9407;color:#fff;outline:0}.custom-theme .el-button--warning.is-active{background:#8d9407;border-color:#8d9407;color:#fff}.custom-theme .el-button--warning.is-disabled,.custom-theme .el-button--warning.is-disabled:active,.custom-theme .el-button--warning.is-disabled:focus,.custom-theme .el-button--warning.is-disabled:hover{color:#fff;background-color:#ced284;border-color:#ced284}.custom-theme .el-button--warning.is-plain{color:#9da408;background:#f5f6e6;border-color:#d8db9c}.custom-theme .el-button--warning.is-plain:focus,.custom-theme .el-button--warning.is-plain:hover{background:#9da408;border-color:#9da408;color:#fff}.custom-theme .el-button--warning.is-plain:active{background:#8d9407;border-color:#8d9407;color:#fff;outline:0}.custom-theme .el-button--warning.is-plain.is-disabled,.custom-theme .el-button--warning.is-plain.is-disabled:active,.custom-theme .el-button--warning.is-plain.is-disabled:focus,.custom-theme .el-button--warning.is-plain.is-disabled:hover{color:#c4c86b;background-color:#f5f6e6;border-color:#ebedce}.custom-theme .el-button--danger{color:#fff;background-color:#b3450e;border-color:#b3450e}.custom-theme .el-button--danger:focus,.custom-theme .el-button--danger:hover{background:#c26a3e;border-color:#c26a3e;color:#fff}.custom-theme .el-button--danger:active{background:#a13e0d;border-color:#a13e0d;color:#fff;outline:0}.custom-theme .el-button--danger.is-active{background:#a13e0d;border-color:#a13e0d;color:#fff}.custom-theme .el-button--danger.is-disabled,.custom-theme .el-button--danger.is-disabled:active,.custom-theme .el-button--danger.is-disabled:focus,.custom-theme .el-button--danger.is-disabled:hover{color:#fff;background-color:#d9a287;border-color:#d9a287}.custom-theme .el-button--danger.is-plain{color:#b3450e;background:#f7ece7;border-color:#e1b59f}.custom-theme .el-button--danger.is-plain:focus,.custom-theme .el-button--danger.is-plain:hover{background:#b3450e;border-color:#b3450e;color:#fff}.custom-theme .el-button--danger.is-plain:active{background:#a13e0d;border-color:#a13e0d;color:#fff;outline:0}.custom-theme .el-button--danger.is-plain.is-disabled,.custom-theme .el-button--danger.is-plain.is-disabled:active,.custom-theme .el-button--danger.is-plain.is-disabled:focus,.custom-theme .el-button--danger.is-plain.is-disabled:hover{color:#d18f6e;background-color:#f7ece7;border-color:#f0dacf}.custom-theme .el-button--info{color:#fff;background-color:#0a76a4;border-color:#0a76a4}.custom-theme .el-button--info:focus,.custom-theme .el-button--info:hover{background:#3b91b6;border-color:#3b91b6;color:#fff}.custom-theme .el-button--info:active{background:#096a94;border-color:#096a94;color:#fff;outline:0}.custom-theme .el-button--info.is-active{background:#096a94;border-color:#096a94;color:#fff}.custom-theme .el-button--info.is-disabled,.custom-theme .el-button--info.is-disabled:active,.custom-theme .el-button--info.is-disabled:focus,.custom-theme .el-button--info.is-disabled:hover{color:#fff;background-color:#85bbd2;border-color:#85bbd2}.custom-theme .el-button--info.is-plain{color:#0a76a4;background:#e7f1f6;border-color:#9dc8db}.custom-theme .el-button--info.is-plain:focus,.custom-theme .el-button--info.is-plain:hover{background:#0a76a4;border-color:#0a76a4;color:#fff}.custom-theme .el-button--info.is-plain:active{background:#096a94;border-color:#096a94;color:#fff;outline:0}.custom-theme .el-button--info.is-plain.is-disabled,.custom-theme .el-button--info.is-plain.is-disabled:active,.custom-theme .el-button--info.is-plain.is-disabled:focus,.custom-theme .el-button--info.is-plain.is-disabled:hover{color:#6cadc8;background-color:#e7f1f6;border-color:#cee4ed}.custom-theme .el-button--medium{padding:10px 20px;font-size:14px;border-radius:4px}.custom-theme .el-button--medium.is-round{padding:10px 20px}.custom-theme .el-button--small{padding:9px 15px;font-size:12px;border-radius:3px}.custom-theme .el-button--small.is-round{padding:9px 15px}.custom-theme .el-button--mini{padding:7px 15px;font-size:12px;border-radius:3px}.custom-theme .el-button--mini.is-round{padding:7px 15px}.custom-theme .el-button--text{border:none;color:#262729;background:0 0;padding-left:0;padding-right:0}.custom-theme .el-button--text:focus,.custom-theme .el-button--text:hover{color:#515254;border-color:transparent;background-color:transparent}.custom-theme .el-button--text:active{color:#222325;border-color:transparent;background-color:transparent}.custom-theme .el-button-group{display:inline-block;vertical-align:middle}.custom-theme .el-button-group::after,.custom-theme .el-button-group::before{display:table;content:""}.custom-theme .el-button-group::after{clear:both}.custom-theme .el-button-group .el-button{float:left;position:relative}.custom-theme .el-button-group .el-button+.el-button{margin-left:0}.custom-theme .el-button-group .el-button:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-button-group .el-button:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-button-group .el-button:not(:first-child):not(:last-child){border-radius:0}.custom-theme .el-button-group .el-button:not(:last-child){margin-right:-1px}.custom-theme .el-button-group .el-button:active,.custom-theme .el-button-group .el-button:focus,.custom-theme .el-button-group .el-button:hover{z-index:1}.custom-theme .el-button-group .el-button.is-active{z-index:1}.custom-theme .el-button-group .el-button--primary:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--primary:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--primary:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--success:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--success:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--success:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--warning:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--warning:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--warning:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--danger:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--danger:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--danger:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--info:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--info:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--info:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-checkbox{color:#5a5e66;font-weight:500;font-size:14px;position:relative;cursor:pointer;display:inline-block;white-space:nowrap;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.custom-theme .el-checkbox.is-bordered{padding:9px 20px 9px 10px;border-radius:4px;border:1px solid #d8dce5}.custom-theme .el-checkbox.is-bordered.is-checked{border-color:#262729}.custom-theme .el-checkbox.is-bordered.is-disabled{border-color:#e6ebf5;cursor:not-allowed}.custom-theme .el-checkbox.is-bordered+.el-checkbox.is-bordered{margin-left:10px}.custom-theme .el-checkbox.is-bordered.el-checkbox--medium{padding:7px 20px 7px 10px;border-radius:4px}.custom-theme .el-checkbox.is-bordered.el-checkbox--medium .el-checkbox__label{line-height:17px;font-size:14px}.custom-theme .el-checkbox.is-bordered.el-checkbox--medium .el-checkbox__inner{height:14px;width:14px}.custom-theme .el-checkbox.is-bordered.el-checkbox--small{padding:3px 15px 7px 10px;border-radius:3px}.custom-theme .el-checkbox.is-bordered.el-checkbox--small .el-checkbox__label{line-height:15px;font-size:12px}.custom-theme .el-checkbox.is-bordered.el-checkbox--small .el-checkbox__inner{height:12px;width:12px}.custom-theme .el-checkbox.is-bordered.el-checkbox--small .el-checkbox__inner::after{height:6px;width:2px}.custom-theme .el-checkbox.is-bordered.el-checkbox--mini{padding:1px 15px 5px 10px;border-radius:3px}.custom-theme .el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__label{line-height:12px;font-size:12px}.custom-theme .el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__inner{height:12px;width:12px}.custom-theme .el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__inner::after{height:6px;width:2px}.custom-theme .el-checkbox__input{white-space:nowrap;cursor:pointer;outline:0;display:inline-block;line-height:1;position:relative;vertical-align:middle}.custom-theme .el-checkbox__input.is-disabled .el-checkbox__inner{background-color:#edf2fc;border-color:#d8dce5;cursor:not-allowed}.custom-theme .el-checkbox__input.is-disabled .el-checkbox__inner::after{cursor:not-allowed;border-color:#b4bccc}.custom-theme .el-checkbox__input.is-disabled .el-checkbox__inner+.el-checkbox__label{cursor:not-allowed}.custom-theme .el-checkbox__input.is-disabled.is-checked .el-checkbox__inner{background-color:#edf2fc;border-color:#d8dce5}.custom-theme .el-checkbox__input.is-disabled.is-checked .el-checkbox__inner::after{border-color:#b4bccc}.custom-theme .el-checkbox__input.is-disabled.is-indeterminate .el-checkbox__inner{background-color:#edf2fc;border-color:#d8dce5}.custom-theme .el-checkbox__input.is-disabled.is-indeterminate .el-checkbox__inner::before{background-color:#b4bccc;border-color:#b4bccc}.custom-theme .el-checkbox__input.is-disabled+span.el-checkbox__label{color:#b4bccc;cursor:not-allowed}.custom-theme .el-checkbox__input.is-checked .el-checkbox__inner{background-color:#262729;border-color:#262729}.custom-theme .el-checkbox__input.is-checked .el-checkbox__inner::after{-webkit-transform:rotate(45deg) scaleY(1);transform:rotate(45deg) scaleY(1)}.custom-theme .el-checkbox__input.is-checked+.el-checkbox__label{color:#262729}.custom-theme .el-checkbox__input.is-focus .el-checkbox__inner{border-color:#262729}.custom-theme .el-checkbox__input.is-indeterminate .el-checkbox__inner{background-color:#262729;border-color:#262729}.custom-theme .el-checkbox__input.is-indeterminate .el-checkbox__inner::before{content:'';position:absolute;display:block;background-color:#fff;height:2px;-webkit-transform:scale(.5);transform:scale(.5);left:0;right:0;top:5px}.custom-theme .el-checkbox__input.is-indeterminate .el-checkbox__inner::after{display:none}.custom-theme .el-checkbox__inner{display:inline-block;position:relative;border:1px solid #d8dce5;border-radius:2px;-webkit-box-sizing:border-box;box-sizing:border-box;width:14px;height:14px;background-color:#fff;z-index:1;-webkit-transition:border-color .25s cubic-bezier(.71,-.46,.29,1.46),background-color .25s cubic-bezier(.71,-.46,.29,1.46);transition:border-color .25s cubic-bezier(.71,-.46,.29,1.46),background-color .25s cubic-bezier(.71,-.46,.29,1.46)}.custom-theme .el-checkbox__inner:hover{border-color:#262729}.custom-theme .el-checkbox__inner::after{-webkit-box-sizing:content-box;box-sizing:content-box;content:"";border:1px solid #fff;border-left:0;border-top:0;height:7px;left:4px;position:absolute;top:1px;-webkit-transform:rotate(45deg) scaleY(0);transform:rotate(45deg) scaleY(0);width:3px;-webkit-transition:-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms;transition:-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms;transition:transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms;transition:transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms,-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms;-webkit-transform-origin:center;transform-origin:center}.custom-theme .el-checkbox__original{opacity:0;outline:0;position:absolute;margin:0;width:0;height:0;left:-999px}.custom-theme .el-checkbox__label{display:inline-block;padding-left:10px;line-height:19px;font-size:14px}.custom-theme .el-checkbox+.el-checkbox{margin-left:30px}.custom-theme .el-checkbox-button{position:relative;display:inline-block}.custom-theme .el-checkbox-button__inner{display:inline-block;line-height:1;font-weight:500;white-space:nowrap;vertical-align:middle;cursor:pointer;background:#fff;border:1px solid #d8dce5;border-left:0;color:#5a5e66;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;position:relative;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;padding:12px 20px;font-size:14px;border-radius:0}.custom-theme .el-checkbox-button__inner.is-round{padding:12px 20px}.custom-theme .el-checkbox-button__inner:hover{color:#262729}.custom-theme .el-checkbox-button__inner [class*=el-icon-]{line-height:.9}.custom-theme .el-checkbox-button__inner [class*=el-icon-]+span{margin-left:5px}.custom-theme .el-checkbox-button__original{opacity:0;outline:0;position:absolute;margin:0;left:-999px}.custom-theme .el-checkbox-button.is-checked .el-checkbox-button__inner{color:#fff;background-color:#262729;border-color:#262729;-webkit-box-shadow:-1px 0 0 0 #7d7d7f;box-shadow:-1px 0 0 0 #7d7d7f}.custom-theme .el-checkbox-button.is-disabled .el-checkbox-button__inner{color:#b4bccc;cursor:not-allowed;background-image:none;background-color:#fff;border-color:#e6ebf5;-webkit-box-shadow:none;box-shadow:none}.custom-theme .el-checkbox-button:first-child .el-checkbox-button__inner{border-left:1px solid #d8dce5;border-radius:4px 0 0 4px;-webkit-box-shadow:none!important;box-shadow:none!important}.custom-theme .el-checkbox-button.is-focus .el-checkbox-button__inner{border-color:#262729}.custom-theme .el-checkbox-button:last-child .el-checkbox-button__inner{border-radius:0 4px 4px 0}.custom-theme .el-checkbox-button--medium .el-checkbox-button__inner{padding:10px 20px;font-size:14px;border-radius:0}.custom-theme .el-checkbox-button--medium .el-checkbox-button__inner.is-round{padding:10px 20px}.custom-theme .el-checkbox-button--small .el-checkbox-button__inner{padding:9px 15px;font-size:12px;border-radius:0}.custom-theme .el-checkbox-button--small .el-checkbox-button__inner.is-round{padding:9px 15px}.custom-theme .el-checkbox-button--mini .el-checkbox-button__inner{padding:7px 15px;font-size:12px;border-radius:0}.custom-theme .el-checkbox-button--mini .el-checkbox-button__inner.is-round{padding:7px 15px}.custom-theme .el-checkbox-group{font-size:0}.custom-theme .el-tag{background-color:rgba(38,39,41,.1);display:inline-block;padding:0 10px;height:32px;line-height:30px;font-size:12px;color:#262729;border-radius:4px;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid rgba(38,39,41,.2);white-space:nowrap}.custom-theme .el-tag .el-icon-close{border-radius:50%;text-align:center;position:relative;cursor:pointer;font-size:12px;height:18px;width:18px;line-height:18px;vertical-align:middle;top:-1px;right:-5px;color:#262729}.custom-theme .el-tag .el-icon-close::before{display:block}.custom-theme .el-tag .el-icon-close:hover{background-color:#262729;color:#fff}.custom-theme .el-tag--info{background-color:rgba(10,118,164,.1);border-color:rgba(10,118,164,.2);color:#0a76a4}.custom-theme .el-tag--info.is-hit{border-color:#0a76a4}.custom-theme .el-tag--info .el-tag__close{color:#0a76a4}.custom-theme .el-tag--info .el-tag__close:hover{background-color:#0a76a4;color:#fff}.custom-theme .el-tag--success{background-color:rgba(64,145,103,.1);border-color:rgba(64,145,103,.2);color:#409167}.custom-theme .el-tag--success.is-hit{border-color:#409167}.custom-theme .el-tag--success .el-tag__close{color:#409167}.custom-theme .el-tag--success .el-tag__close:hover{background-color:#409167;color:#fff}.custom-theme .el-tag--warning{background-color:rgba(157,164,8,.1);border-color:rgba(157,164,8,.2);color:#9da408}.custom-theme .el-tag--warning.is-hit{border-color:#9da408}.custom-theme .el-tag--warning .el-tag__close{color:#9da408}.custom-theme .el-tag--warning .el-tag__close:hover{background-color:#9da408;color:#fff}.custom-theme .el-tag--danger{background-color:rgba(179,69,14,.1);border-color:rgba(179,69,14,.2);color:#b3450e}.custom-theme .el-tag--danger.is-hit{border-color:#b3450e}.custom-theme .el-tag--danger .el-tag__close{color:#b3450e}.custom-theme .el-tag--danger .el-tag__close:hover{background-color:#b3450e;color:#fff}.custom-theme .el-tag--medium{height:28px;line-height:26px}.custom-theme .el-tag--medium .el-icon-close{-webkit-transform:scale(.8);transform:scale(.8)}.custom-theme .el-tag--small{height:24px;padding:0 8px;line-height:22px}.custom-theme .el-tag--small .el-icon-close{-webkit-transform:scale(.8);transform:scale(.8)}.custom-theme .el-tag--mini{height:20px;padding:0 5px;line-height:19px}.custom-theme .el-tag--mini .el-icon-close{margin-left:-3px;-webkit-transform:scale(.7);transform:scale(.7)}.custom-theme .el-table{position:relative;overflow:hidden;-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-box-flex:1;-ms-flex:1;flex:1;width:100%;max-width:100%;background-color:#fff;font-size:14px;color:#5a5e66}.custom-theme .el-table__empty-block{position:relative;min-height:60px;text-align:center;width:100%;height:100%}.custom-theme .el-table__empty-text{position:absolute;left:50%;top:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);color:color(#262729 s(16%) l(44%))}.custom-theme .el-table__expand-column .cell{padding:0;text-align:center}.custom-theme .el-table__expand-icon{position:relative;cursor:pointer;color:#666;font-size:12px;-webkit-transition:-webkit-transform .2s ease-in-out;transition:-webkit-transform .2s ease-in-out;transition:transform .2s ease-in-out;transition:transform .2s ease-in-out,-webkit-transform .2s ease-in-out;height:20px}.custom-theme .el-table__expand-icon--expanded{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.custom-theme .el-table__expand-icon>.el-icon{position:absolute;left:50%;top:50%;margin-left:-5px;margin-top:-5px}.custom-theme .el-table__expanded-cell{background-color:#fff}.custom-theme .el-table__expanded-cell[class*=cell]{padding:20px 50px}.custom-theme .el-table__expanded-cell:hover{background-color:#f5f7fa!important}.custom-theme .el-table--fit{border-right:0;border-bottom:0}.custom-theme .el-table--fit td.gutter,.custom-theme .el-table--fit th.gutter{border-right-width:1px}.custom-theme .el-table thead{color:#878d99;font-weight:500}.custom-theme .el-table thead.is-group th{background:#f5f7fa}.custom-theme .el-table td,.custom-theme .el-table th{padding:12px 0;min-width:0;-webkit-box-sizing:border-box;box-sizing:border-box;text-overflow:ellipsis;vertical-align:middle;position:relative}.custom-theme .el-table td.is-center,.custom-theme .el-table th.is-center{text-align:center}.custom-theme .el-table td.is-left,.custom-theme .el-table th.is-left{text-align:left}.custom-theme .el-table td.is-right,.custom-theme .el-table th.is-right{text-align:right}.custom-theme .el-table td.gutter,.custom-theme .el-table th.gutter{width:15px;border-right-width:0;border-bottom-width:0;padding:0}.custom-theme .el-table td.is-hidden>*,.custom-theme .el-table th.is-hidden>*{visibility:hidden}.custom-theme .el-table--medium td,.custom-theme .el-table--medium th{padding:10px 0}.custom-theme .el-table--small{font-size:12px}.custom-theme .el-table--small td,.custom-theme .el-table--small th{padding:8px 0}.custom-theme .el-table--mini{font-size:12px}.custom-theme .el-table--mini td,.custom-theme .el-table--mini th{padding:6px 0}.custom-theme .el-table tr{background-color:#fff}.custom-theme .el-table tr input[type=checkbox]{margin:0}.custom-theme .el-table td,.custom-theme .el-table th.is-leaf{border-bottom:1px solid #e6ebf5}.custom-theme .el-table th.is-sortable{cursor:pointer}.custom-theme .el-table th{white-space:nowrap;overflow:hidden;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;text-align:left}.custom-theme .el-table th div{display:inline-block;padding-left:10px;padding-right:10px;line-height:40px;-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.custom-theme .el-table th>.cell{position:relative;word-wrap:normal;text-overflow:ellipsis;display:inline-block;vertical-align:middle;width:100%;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-table th>.cell.highlight{color:#262729}.custom-theme .el-table th.required>div::before{display:inline-block;content:"";width:8px;height:8px;border-radius:50%;background:#ff4d51;margin-right:5px;vertical-align:middle}.custom-theme .el-table td div{-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-table td.gutter{width:0}.custom-theme .el-table .cell{-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden;text-overflow:ellipsis;white-space:normal;word-break:break-all;line-height:23px;padding-left:10px;padding-right:10px}.custom-theme .el-table .cell.el-tooltip{white-space:nowrap;min-width:50px}.custom-theme .el-table td:first-child .cell,.custom-theme .el-table th:first-child .cell{padding-left:0}.custom-theme .el-table--border,.custom-theme .el-table--group{border:1px solid #e6ebf5}.custom-theme .el-table--border::after,.custom-theme .el-table--group::after,.custom-theme .el-table::before{content:'';position:absolute;background-color:#e6ebf5;z-index:1}.custom-theme .el-table--border::after,.custom-theme .el-table--group::after{top:0;right:0;width:1px;height:100%}.custom-theme .el-table::before{left:0;bottom:0;width:100%;height:1px}.custom-theme .el-table--border{border-right:none;border-bottom:none}.custom-theme .el-table--border td,.custom-theme .el-table--border th{border-right:1px solid #e6ebf5}.custom-theme .el-table--border td:first-child .cell,.custom-theme .el-table--border th:first-child .cell{padding-left:10px}.custom-theme .el-table--border .has-gutter td:nth-last-of-type(2),.custom-theme .el-table--border .has-gutter th:nth-last-of-type(2){border-right:none}.custom-theme .el-table--border th.gutter:last-of-type{border-bottom:1px solid #e6ebf5;border-bottom-width:1px}.custom-theme .el-table--border th{border-bottom:1px solid #e6ebf5}.custom-theme .el-table--hidden{visibility:hidden}.custom-theme .el-table__fixed,.custom-theme .el-table__fixed-right{position:absolute;top:0;left:0;overflow-x:hidden;-webkit-box-shadow:0 0 10px rgba(0,0,0,.12);box-shadow:0 0 10px rgba(0,0,0,.12)}.custom-theme .el-table__fixed-right::before,.custom-theme .el-table__fixed::before{content:'';position:absolute;left:0;bottom:0;width:100%;height:1px;background-color:#e6ebf5;z-index:4}.custom-theme .el-table__fixed-right-patch{position:absolute;top:-1px;right:0;background-color:#fff;border-bottom:1px solid #e6ebf5}.custom-theme .el-table__fixed-right{top:0;left:auto;right:0}.custom-theme .el-table__fixed-right .el-table__fixed-body-wrapper,.custom-theme .el-table__fixed-right .el-table__fixed-footer-wrapper,.custom-theme .el-table__fixed-right .el-table__fixed-header-wrapper{left:auto;right:0}.custom-theme .el-table__fixed-header-wrapper{position:absolute;left:0;top:0;z-index:3}.custom-theme .el-table__fixed-footer-wrapper{position:absolute;left:0;bottom:0;z-index:3}.custom-theme .el-table__fixed-footer-wrapper tbody td{border-top:1px solid #e6ebf5;background-color:#f5f7fa;color:#5a5e66}.custom-theme .el-table__fixed-body-wrapper{position:absolute;left:0;top:37px;overflow:hidden;z-index:3}.custom-theme .el-table__body-wrapper,.custom-theme .el-table__footer-wrapper,.custom-theme .el-table__header-wrapper{width:100%}.custom-theme .el-table__footer-wrapper{margin-top:-1px}.custom-theme .el-table__footer-wrapper td{border-top:1px solid #e6ebf5}.custom-theme .el-table__body,.custom-theme .el-table__footer,.custom-theme .el-table__header{table-layout:fixed}.custom-theme .el-table__footer-wrapper,.custom-theme .el-table__header-wrapper{overflow:hidden}.custom-theme .el-table__footer-wrapper tbody td,.custom-theme .el-table__header-wrapper tbody td{background-color:#f5f7fa;color:#5a5e66}.custom-theme .el-table__body-wrapper{overflow:auto;position:relative}.custom-theme .el-table__body-wrapper.is-scroll-none~.el-table__fixed,.custom-theme .el-table__body-wrapper.is-scroll-none~.el-table__fixed-right{-webkit-box-shadow:none;box-shadow:none}.custom-theme .el-table__body-wrapper.is-scroll-left~.el-table__fixed{-webkit-box-shadow:none;box-shadow:none}.custom-theme .el-table__body-wrapper.is-scroll-right~.el-table__fixed-right{-webkit-box-shadow:none;box-shadow:none}.custom-theme .el-table__body-wrapper .el-table--border.is-scroll-right~.el-table__fixed-right{border-left:1px solid #e6ebf5}.custom-theme .el-table__body-wrapper .el-table--border.is-scroll-left~.el-table__fixed{border-right:1px solid #e6ebf5}.custom-theme .el-table .caret-wrapper{position:relative;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:13px;width:24px;cursor:pointer;overflow:initial}.custom-theme .el-table .sort-caret{color:#0a76a4;width:14px;overflow:hidden;font-size:13px}.custom-theme .el-table .ascending .sort-caret.ascending{color:#262729}.custom-theme .el-table .descending .sort-caret.descending{color:#262729}.custom-theme .el-table .hidden-columns{visibility:hidden;position:absolute;z-index:-1}.custom-theme .el-table--striped .el-table__body tr.el-table__row--striped td{background:#fafafa}.custom-theme .el-table--striped .el-table__body tr.el-table__row--striped.current-row td{background-color:#e9e9ea}.custom-theme .el-table__body tr.hover-row.current-row>td,.custom-theme .el-table__body tr.hover-row.el-table__row--striped.current-row>td,.custom-theme .el-table__body tr.hover-row.el-table__row--striped>td,.custom-theme .el-table__body tr.hover-row>td{background-color:#e9e9ea}.custom-theme .el-table__body tr.current-row>td{background-color:#e9e9ea}.custom-theme .el-table__column-resize-proxy{position:absolute;left:200px;top:0;bottom:0;width:0;border-left:1px solid #e6ebf5;z-index:10}.custom-theme .el-table__column-filter-trigger{display:inline-block;line-height:34px;cursor:pointer}.custom-theme .el-table__column-filter-trigger i{color:#0a76a4;font-size:12px;-webkit-transform:scale(.75);transform:scale(.75)}.custom-theme .el-table--enable-row-transition .el-table__body td{-webkit-transition:background-color .25s ease;transition:background-color .25s ease}.custom-theme .el-table--enable-row-hover .el-table__body tr:hover>td{background-color:#f5f7fa}.custom-theme .el-table--fluid-height .el-table__fixed,.custom-theme .el-table--fluid-height .el-table__fixed-right{bottom:0;overflow:hidden}.custom-theme .el-checkbox{color:#5a5e66;font-weight:500;font-size:14px;position:relative;cursor:pointer;display:inline-block;white-space:nowrap;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.custom-theme .el-checkbox.is-bordered{padding:9px 20px 9px 10px;border-radius:4px;border:1px solid #d8dce5}.custom-theme .el-checkbox.is-bordered.is-checked{border-color:#262729}.custom-theme .el-checkbox.is-bordered.is-disabled{border-color:#e6ebf5;cursor:not-allowed}.custom-theme .el-checkbox.is-bordered+.el-checkbox.is-bordered{margin-left:10px}.custom-theme .el-checkbox.is-bordered.el-checkbox--medium{padding:7px 20px 7px 10px;border-radius:4px}.custom-theme .el-checkbox.is-bordered.el-checkbox--medium .el-checkbox__label{line-height:17px;font-size:14px}.custom-theme .el-checkbox.is-bordered.el-checkbox--medium .el-checkbox__inner{height:14px;width:14px}.custom-theme .el-checkbox.is-bordered.el-checkbox--small{padding:3px 15px 7px 10px;border-radius:3px}.custom-theme .el-checkbox.is-bordered.el-checkbox--small .el-checkbox__label{line-height:15px;font-size:12px}.custom-theme .el-checkbox.is-bordered.el-checkbox--small .el-checkbox__inner{height:12px;width:12px}.custom-theme .el-checkbox.is-bordered.el-checkbox--small .el-checkbox__inner::after{height:6px;width:2px}.custom-theme .el-checkbox.is-bordered.el-checkbox--mini{padding:1px 15px 5px 10px;border-radius:3px}.custom-theme .el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__label{line-height:12px;font-size:12px}.custom-theme .el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__inner{height:12px;width:12px}.custom-theme .el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__inner::after{height:6px;width:2px}.custom-theme .el-checkbox__input{white-space:nowrap;cursor:pointer;outline:0;display:inline-block;line-height:1;position:relative;vertical-align:middle}.custom-theme .el-checkbox__input.is-disabled .el-checkbox__inner{background-color:#edf2fc;border-color:#d8dce5;cursor:not-allowed}.custom-theme .el-checkbox__input.is-disabled .el-checkbox__inner::after{cursor:not-allowed;border-color:#b4bccc}.custom-theme .el-checkbox__input.is-disabled .el-checkbox__inner+.el-checkbox__label{cursor:not-allowed}.custom-theme .el-checkbox__input.is-disabled.is-checked .el-checkbox__inner{background-color:#edf2fc;border-color:#d8dce5}.custom-theme .el-checkbox__input.is-disabled.is-checked .el-checkbox__inner::after{border-color:#b4bccc}.custom-theme .el-checkbox__input.is-disabled.is-indeterminate .el-checkbox__inner{background-color:#edf2fc;border-color:#d8dce5}.custom-theme .el-checkbox__input.is-disabled.is-indeterminate .el-checkbox__inner::before{background-color:#b4bccc;border-color:#b4bccc}.custom-theme .el-checkbox__input.is-disabled+span.el-checkbox__label{color:#b4bccc;cursor:not-allowed}.custom-theme .el-checkbox__input.is-checked .el-checkbox__inner{background-color:#262729;border-color:#262729}.custom-theme .el-checkbox__input.is-checked .el-checkbox__inner::after{-webkit-transform:rotate(45deg) scaleY(1);transform:rotate(45deg) scaleY(1)}.custom-theme .el-checkbox__input.is-checked+.el-checkbox__label{color:#262729}.custom-theme .el-checkbox__input.is-focus .el-checkbox__inner{border-color:#262729}.custom-theme .el-checkbox__input.is-indeterminate .el-checkbox__inner{background-color:#262729;border-color:#262729}.custom-theme .el-checkbox__input.is-indeterminate .el-checkbox__inner::before{content:'';position:absolute;display:block;background-color:#fff;height:2px;-webkit-transform:scale(.5);transform:scale(.5);left:0;right:0;top:5px}.custom-theme .el-checkbox__input.is-indeterminate .el-checkbox__inner::after{display:none}.custom-theme .el-checkbox__inner{display:inline-block;position:relative;border:1px solid #d8dce5;border-radius:2px;-webkit-box-sizing:border-box;box-sizing:border-box;width:14px;height:14px;background-color:#fff;z-index:1;-webkit-transition:border-color .25s cubic-bezier(.71,-.46,.29,1.46),background-color .25s cubic-bezier(.71,-.46,.29,1.46);transition:border-color .25s cubic-bezier(.71,-.46,.29,1.46),background-color .25s cubic-bezier(.71,-.46,.29,1.46)}.custom-theme .el-checkbox__inner:hover{border-color:#262729}.custom-theme .el-checkbox__inner::after{-webkit-box-sizing:content-box;box-sizing:content-box;content:"";border:1px solid #fff;border-left:0;border-top:0;height:7px;left:4px;position:absolute;top:1px;-webkit-transform:rotate(45deg) scaleY(0);transform:rotate(45deg) scaleY(0);width:3px;-webkit-transition:-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms;transition:-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms;transition:transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms;transition:transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms,-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms;-webkit-transform-origin:center;transform-origin:center}.custom-theme .el-checkbox__original{opacity:0;outline:0;position:absolute;margin:0;width:0;height:0;left:-999px}.custom-theme .el-checkbox__label{display:inline-block;padding-left:10px;line-height:19px;font-size:14px}.custom-theme .el-checkbox+.el-checkbox{margin-left:30px}.custom-theme .el-checkbox-button{position:relative;display:inline-block}.custom-theme .el-checkbox-button__inner{display:inline-block;line-height:1;font-weight:500;white-space:nowrap;vertical-align:middle;cursor:pointer;background:#fff;border:1px solid #d8dce5;border-left:0;color:#5a5e66;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;position:relative;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;padding:12px 20px;font-size:14px;border-radius:0}.custom-theme .el-checkbox-button__inner.is-round{padding:12px 20px}.custom-theme .el-checkbox-button__inner:hover{color:#262729}.custom-theme .el-checkbox-button__inner [class*=el-icon-]{line-height:.9}.custom-theme .el-checkbox-button__inner [class*=el-icon-]+span{margin-left:5px}.custom-theme .el-checkbox-button__original{opacity:0;outline:0;position:absolute;margin:0;left:-999px}.custom-theme .el-checkbox-button.is-checked .el-checkbox-button__inner{color:#fff;background-color:#262729;border-color:#262729;-webkit-box-shadow:-1px 0 0 0 #7d7d7f;box-shadow:-1px 0 0 0 #7d7d7f}.custom-theme .el-checkbox-button.is-disabled .el-checkbox-button__inner{color:#b4bccc;cursor:not-allowed;background-image:none;background-color:#fff;border-color:#e6ebf5;-webkit-box-shadow:none;box-shadow:none}.custom-theme .el-checkbox-button:first-child .el-checkbox-button__inner{border-left:1px solid #d8dce5;border-radius:4px 0 0 4px;-webkit-box-shadow:none!important;box-shadow:none!important}.custom-theme .el-checkbox-button.is-focus .el-checkbox-button__inner{border-color:#262729}.custom-theme .el-checkbox-button:last-child .el-checkbox-button__inner{border-radius:0 4px 4px 0}.custom-theme .el-checkbox-button--medium .el-checkbox-button__inner{padding:10px 20px;font-size:14px;border-radius:0}.custom-theme .el-checkbox-button--medium .el-checkbox-button__inner.is-round{padding:10px 20px}.custom-theme .el-checkbox-button--small .el-checkbox-button__inner{padding:9px 15px;font-size:12px;border-radius:0}.custom-theme .el-checkbox-button--small .el-checkbox-button__inner.is-round{padding:9px 15px}.custom-theme .el-checkbox-button--mini .el-checkbox-button__inner{padding:7px 15px;font-size:12px;border-radius:0}.custom-theme .el-checkbox-button--mini .el-checkbox-button__inner.is-round{padding:7px 15px}.custom-theme .el-checkbox-group{font-size:0}.custom-theme .el-tag{background-color:rgba(38,39,41,.1);display:inline-block;padding:0 10px;height:32px;line-height:30px;font-size:12px;color:#262729;border-radius:4px;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid rgba(38,39,41,.2);white-space:nowrap}.custom-theme .el-tag .el-icon-close{border-radius:50%;text-align:center;position:relative;cursor:pointer;font-size:12px;height:18px;width:18px;line-height:18px;vertical-align:middle;top:-1px;right:-5px;color:#262729}.custom-theme .el-tag .el-icon-close::before{display:block}.custom-theme .el-tag .el-icon-close:hover{background-color:#262729;color:#fff}.custom-theme .el-tag--info{background-color:rgba(10,118,164,.1);border-color:rgba(10,118,164,.2);color:#0a76a4}.custom-theme .el-tag--info.is-hit{border-color:#0a76a4}.custom-theme .el-tag--info .el-tag__close{color:#0a76a4}.custom-theme .el-tag--info .el-tag__close:hover{background-color:#0a76a4;color:#fff}.custom-theme .el-tag--success{background-color:rgba(64,145,103,.1);border-color:rgba(64,145,103,.2);color:#409167}.custom-theme .el-tag--success.is-hit{border-color:#409167}.custom-theme .el-tag--success .el-tag__close{color:#409167}.custom-theme .el-tag--success .el-tag__close:hover{background-color:#409167;color:#fff}.custom-theme .el-tag--warning{background-color:rgba(157,164,8,.1);border-color:rgba(157,164,8,.2);color:#9da408}.custom-theme .el-tag--warning.is-hit{border-color:#9da408}.custom-theme .el-tag--warning .el-tag__close{color:#9da408}.custom-theme .el-tag--warning .el-tag__close:hover{background-color:#9da408;color:#fff}.custom-theme .el-tag--danger{background-color:rgba(179,69,14,.1);border-color:rgba(179,69,14,.2);color:#b3450e}.custom-theme .el-tag--danger.is-hit{border-color:#b3450e}.custom-theme .el-tag--danger .el-tag__close{color:#b3450e}.custom-theme .el-tag--danger .el-tag__close:hover{background-color:#b3450e;color:#fff}.custom-theme .el-tag--medium{height:28px;line-height:26px}.custom-theme .el-tag--medium .el-icon-close{-webkit-transform:scale(.8);transform:scale(.8)}.custom-theme .el-tag--small{height:24px;padding:0 8px;line-height:22px}.custom-theme .el-tag--small .el-icon-close{-webkit-transform:scale(.8);transform:scale(.8)}.custom-theme .el-tag--mini{height:20px;padding:0 5px;line-height:19px}.custom-theme .el-tag--mini .el-icon-close{margin-left:-3px;-webkit-transform:scale(.7);transform:scale(.7)}.custom-theme .el-table-column--selection .cell{padding-left:14px;padding-right:14px}.custom-theme .el-table-filter{border:solid 1px #e6ebf5;border-radius:2px;background-color:#fff;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);-webkit-box-sizing:border-box;box-sizing:border-box;margin:2px 0}.custom-theme .el-table-filter__list{padding:5px 0;margin:0;list-style:none;min-width:100px}.custom-theme .el-table-filter__list-item{line-height:36px;padding:0 10px;cursor:pointer;font-size:14px}.custom-theme .el-table-filter__list-item:hover{background-color:#e9e9ea;color:#515254}.custom-theme .el-table-filter__list-item.is-active{background-color:#262729;color:#fff}.custom-theme .el-table-filter__content{min-width:100px}.custom-theme .el-table-filter__bottom{border-top:1px solid #e6ebf5;padding:8px}.custom-theme .el-table-filter__bottom button{background:0 0;border:none;color:#5a5e66;cursor:pointer;font-size:13px;padding:0 3px}.custom-theme .el-table-filter__bottom button:hover{color:#262729}.custom-theme .el-table-filter__bottom button:focus{outline:0}.custom-theme .el-table-filter__bottom button.is-disabled{color:#b4bccc;cursor:not-allowed}.custom-theme .el-table-filter__checkbox-group{padding:10px}.custom-theme .el-table-filter__checkbox-group label.el-checkbox{display:block;margin-bottom:8px;margin-left:5px}.custom-theme .el-table-filter__checkbox-group .el-checkbox:last-child{margin-bottom:0}.custom-theme .el-date-table{font-size:12px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.custom-theme .el-date-table.is-week-mode .el-date-table__row:hover div{background-color:#edf2fc}.custom-theme .el-date-table.is-week-mode .el-date-table__row:hover td.available:hover{color:#5a5e66}.custom-theme .el-date-table.is-week-mode .el-date-table__row:hover td:first-child div{margin-left:5px;border-top-left-radius:15px;border-bottom-left-radius:15px}.custom-theme .el-date-table.is-week-mode .el-date-table__row:hover td:last-child div{margin-right:5px;border-top-right-radius:15px;border-bottom-right-radius:15px}.custom-theme .el-date-table.is-week-mode .el-date-table__row.current div{background-color:#edf2fc}.custom-theme .el-date-table td{width:32px;height:30px;padding:4px 0;-webkit-box-sizing:border-box;box-sizing:border-box;text-align:center;cursor:pointer;position:relative}.custom-theme .el-date-table td div{height:30px;padding:3px 0;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-date-table td span{width:24px;height:24px;display:block;margin:0 auto;line-height:24px;position:absolute;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%);border-radius:50%}.custom-theme .el-date-table td.next-month,.custom-theme .el-date-table td.prev-month{color:#b4bccc}.custom-theme .el-date-table td.today{position:relative}.custom-theme .el-date-table td.today span{color:#262729}.custom-theme .el-date-table td.today.end-date span,.custom-theme .el-date-table td.today.start-date span{color:#fff}.custom-theme .el-date-table td.available:hover{color:#262729}.custom-theme .el-date-table td.in-range div{background-color:#edf2fc}.custom-theme .el-date-table td.in-range div:hover{background-color:#edf2fc}.custom-theme .el-date-table td.current:not(.disabled) span{color:#fff;background-color:#262729}.custom-theme .el-date-table td.end-date div,.custom-theme .el-date-table td.start-date div{color:#fff}.custom-theme .el-date-table td.end-date span,.custom-theme .el-date-table td.start-date span{background-color:#262729}.custom-theme .el-date-table td.start-date div{margin-left:5px;border-top-left-radius:15px;border-bottom-left-radius:15px}.custom-theme .el-date-table td.end-date div{margin-right:5px;border-top-right-radius:15px;border-bottom-right-radius:15px}.custom-theme .el-date-table td.disabled div{background-color:#f5f7fa;opacity:1;cursor:not-allowed;color:#b4bccc}.custom-theme .el-date-table td.week{font-size:80%;color:#5a5e66}.custom-theme .el-date-table th{padding:5px;color:#5a5e66;font-weight:400;border-bottom:solid 1px #e6ebf5}.custom-theme .el-month-table{font-size:12px;margin:-1px;border-collapse:collapse}.custom-theme .el-month-table td{text-align:center;padding:20px 3px;cursor:pointer}.custom-theme .el-month-table td.disabled .cell{background-color:#f5f7fa;cursor:not-allowed;color:#b4bccc}.custom-theme .el-month-table td.disabled .cell:hover{color:#b4bccc}.custom-theme .el-month-table td .cell{width:48px;height:32px;display:block;line-height:32px;color:#5a5e66;margin:0 auto}.custom-theme .el-month-table td .cell:hover{color:#262729}.custom-theme .el-month-table td.current:not(.disabled) .cell{color:#262729}.custom-theme .el-year-table{font-size:12px;margin:-1px;border-collapse:collapse}.custom-theme .el-year-table .el-icon{color:#2d2f33}.custom-theme .el-year-table td{text-align:center;padding:20px 3px;cursor:pointer}.custom-theme .el-year-table td.disabled .cell{background-color:#f5f7fa;cursor:not-allowed;color:#b4bccc}.custom-theme .el-year-table td.disabled .cell:hover{color:#b4bccc}.custom-theme .el-year-table td .cell{width:48px;height:32px;display:block;line-height:32px;color:#5a5e66;margin:0 auto}.custom-theme .el-year-table td .cell:hover{color:#262729}.custom-theme .el-year-table td.current:not(.disabled) .cell{color:#262729}.custom-theme .el-time-spinner.has-seconds .el-time-spinner__wrapper{width:33.3%}.custom-theme .el-time-spinner.has-seconds .el-time-spinner__wrapper:nth-child(2){margin-left:1%}.custom-theme .el-time-spinner__wrapper{max-height:190px;overflow:auto;display:inline-block;width:50%;vertical-align:top;position:relative}.custom-theme .el-time-spinner__wrapper .el-scrollbar__wrap:not(.el-scrollbar__wrap--hidden-default){padding-bottom:15px}.custom-theme .el-time-spinner__wrapper.is-arrow{-webkit-box-sizing:border-box;box-sizing:border-box;text-align:center;overflow:hidden}.custom-theme .el-time-spinner__wrapper.is-arrow .el-time-spinner__list{-webkit-transform:translateY(-32px);transform:translateY(-32px)}.custom-theme .el-time-spinner__wrapper.is-arrow .el-time-spinner__item:hover:not(.disabled):not(.active){background:#fff;cursor:default}.custom-theme .el-time-spinner__arrow{font-size:12px;color:#878d99;position:absolute;left:0;width:100%;z-index:1;text-align:center;height:30px;line-height:30px;cursor:pointer}.custom-theme .el-time-spinner__arrow:hover{color:#262729}.custom-theme .el-time-spinner__arrow.el-icon-arrow-up{top:10px}.custom-theme .el-time-spinner__arrow.el-icon-arrow-down{bottom:10px}.custom-theme .el-time-spinner__input.el-input{width:70%}.custom-theme .el-time-spinner__input.el-input .el-input__inner{padding:0;text-align:center}.custom-theme .el-time-spinner__list{padding:0;margin:0;list-style:none;text-align:center}.custom-theme .el-time-spinner__list::after,.custom-theme .el-time-spinner__list::before{content:'';display:block;width:100%;height:80px}.custom-theme .el-time-spinner__item{height:32px;line-height:32px;font-size:12px;color:#5a5e66}.custom-theme .el-time-spinner__item:hover:not(.disabled):not(.active){background:#f5f7fa;cursor:pointer}.custom-theme .el-time-spinner__item.active:not(.disabled){color:#2d2f33;font-weight:700}.custom-theme .el-time-spinner__item.disabled{color:#b4bccc;cursor:not-allowed}.custom-theme .fade-in-linear-enter-active,.custom-theme .fade-in-linear-leave-active{-webkit-transition:opacity .2s linear;transition:opacity .2s linear}.custom-theme .fade-in-linear-enter,.custom-theme .fade-in-linear-leave,.custom-theme .fade-in-linear-leave-active{opacity:0}.custom-theme .el-fade-in-linear-enter-active,.custom-theme .el-fade-in-linear-leave-active{-webkit-transition:opacity .2s linear;transition:opacity .2s linear}.custom-theme .el-fade-in-linear-enter,.custom-theme .el-fade-in-linear-leave,.custom-theme .el-fade-in-linear-leave-active{opacity:0}.custom-theme .el-fade-in-enter-active,.custom-theme .el-fade-in-leave-active{-webkit-transition:all .3s cubic-bezier(.55,0,.1,1);transition:all .3s cubic-bezier(.55,0,.1,1)}.custom-theme .el-fade-in-enter,.custom-theme .el-fade-in-leave-active{opacity:0}.custom-theme .el-zoom-in-center-enter-active,.custom-theme .el-zoom-in-center-leave-active{-webkit-transition:all .3s cubic-bezier(.55,0,.1,1);transition:all .3s cubic-bezier(.55,0,.1,1)}.custom-theme .el-zoom-in-center-enter,.custom-theme .el-zoom-in-center-leave-active{opacity:0;-webkit-transform:scaleX(0);transform:scaleX(0)}.custom-theme .el-zoom-in-top-enter-active,.custom-theme .el-zoom-in-top-leave-active{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;-webkit-transform-origin:center top;transform-origin:center top}.custom-theme .el-zoom-in-top-enter,.custom-theme .el-zoom-in-top-leave-active{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.custom-theme .el-zoom-in-bottom-enter-active,.custom-theme .el-zoom-in-bottom-leave-active{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;-webkit-transform-origin:center bottom;transform-origin:center bottom}.custom-theme .el-zoom-in-bottom-enter,.custom-theme .el-zoom-in-bottom-leave-active{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.custom-theme .el-zoom-in-left-enter-active,.custom-theme .el-zoom-in-left-leave-active{opacity:1;-webkit-transform:scale(1,1);transform:scale(1,1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;-webkit-transform-origin:top left;transform-origin:top left}.custom-theme .el-zoom-in-left-enter,.custom-theme .el-zoom-in-left-leave-active{opacity:0;-webkit-transform:scale(.45,.45);transform:scale(.45,.45)}.custom-theme .collapse-transition{-webkit-transition:.3s height ease-in-out,.3s padding-top ease-in-out,.3s padding-bottom ease-in-out;transition:.3s height ease-in-out,.3s padding-top ease-in-out,.3s padding-bottom ease-in-out}.custom-theme .horizontal-collapse-transition{-webkit-transition:.3s width ease-in-out,.3s padding-left ease-in-out,.3s padding-right ease-in-out;transition:.3s width ease-in-out,.3s padding-left ease-in-out,.3s padding-right ease-in-out}.custom-theme .el-list-enter-active,.custom-theme .el-list-leave-active{-webkit-transition:all 1s;transition:all 1s}.custom-theme .el-list-enter,.custom-theme .el-list-leave-active{opacity:0;-webkit-transform:translateY(-30px);transform:translateY(-30px)}.custom-theme .el-opacity-transition{-webkit-transition:opacity .3s cubic-bezier(.55,0,.1,1);transition:opacity .3s cubic-bezier(.55,0,.1,1)}.custom-theme .el-date-editor{position:relative;display:inline-block;text-align:left}.custom-theme .el-date-editor.el-input,.custom-theme .el-date-editor.el-input__inner{width:220px}.custom-theme .el-date-editor--daterange.el-input,.custom-theme .el-date-editor--daterange.el-input__inner,.custom-theme .el-date-editor--timerange.el-input,.custom-theme .el-date-editor--timerange.el-input__inner{width:350px}.custom-theme .el-date-editor--datetimerange.el-input,.custom-theme .el-date-editor--datetimerange.el-input__inner{width:400px}.custom-theme .el-date-editor .el-range__icon{font-size:14px;margin-left:-5px;color:#b4bccc;float:left;line-height:32px}.custom-theme .el-date-editor .el-range-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:none;outline:0;display:inline-block;height:100%;margin:0;padding:0;width:39%;text-align:center;font-size:14px;color:#5a5e66}.custom-theme .el-date-editor .el-range-input::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-date-editor .el-range-input:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-date-editor .el-range-input::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-date-editor .el-range-input::placeholder{color:#b4bccc}.custom-theme .el-date-editor .el-range-separator{display:inline-block;height:100%;padding:0 5px;margin:0;text-align:center;line-height:32px;font-size:14px;width:5%;color:#2d2f33}.custom-theme .el-date-editor .el-range__close-icon{font-size:14px;color:#b4bccc;width:25px;display:inline-block;float:right;line-height:32px}.custom-theme .el-range-editor.el-input__inner{padding:3px 10px}.custom-theme .el-range-editor.is-active{border-color:#262729}.custom-theme .el-range-editor.is-active:hover{border-color:#262729}.custom-theme .el-range-editor--medium.el-input__inner{height:36px}.custom-theme .el-range-editor--medium .el-range-separator{line-height:28px;font-size:14px}.custom-theme .el-range-editor--medium .el-range-input{font-size:14px}.custom-theme .el-range-editor--medium .el-range__close-icon,.custom-theme .el-range-editor--medium .el-range__icon{line-height:28px}.custom-theme .el-range-editor--small.el-input__inner{height:32px}.custom-theme .el-range-editor--small .el-range-separator{line-height:24px;font-size:13px}.custom-theme .el-range-editor--small .el-range-input{font-size:13px}.custom-theme .el-range-editor--small .el-range__close-icon,.custom-theme .el-range-editor--small .el-range__icon{line-height:24px}.custom-theme .el-range-editor--mini.el-input__inner{height:28px}.custom-theme .el-range-editor--mini .el-range-separator{line-height:20px;font-size:12px}.custom-theme .el-range-editor--mini .el-range-input{font-size:12px}.custom-theme .el-range-editor--mini .el-range__close-icon,.custom-theme .el-range-editor--mini .el-range__icon{line-height:20px}.custom-theme .el-range-editor.is-disabled{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-range-editor.is-disabled:focus,.custom-theme .el-range-editor.is-disabled:hover{border-color:#dfe4ed}.custom-theme .el-range-editor.is-disabled input{background-color:#f5f7fa;color:#b4bccc;cursor:not-allowed}.custom-theme .el-range-editor.is-disabled input::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-range-editor.is-disabled input:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-range-editor.is-disabled input::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-range-editor.is-disabled input::placeholder{color:#b4bccc}.custom-theme .el-range-editor.is-disabled .el-range-separator{color:#b4bccc}.custom-theme .el-picker-panel{color:#5a5e66;border:1px solid #dfe4ed;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);background:#fff;border-radius:4px;line-height:30px;margin:5px 0}.custom-theme .el-picker-panel__body-wrapper::after,.custom-theme .el-picker-panel__body::after{content:"";display:table;clear:both}.custom-theme .el-picker-panel__content{position:relative;margin:15px}.custom-theme .el-picker-panel__footer{border-top:1px solid #e4e4e4;padding:4px;text-align:right;background-color:#fff;position:relative;font-size:0}.custom-theme .el-picker-panel__shortcut{display:block;width:100%;border:0;background-color:transparent;line-height:28px;font-size:14px;color:#5a5e66;padding-left:12px;text-align:left;outline:0;cursor:pointer}.custom-theme .el-picker-panel__shortcut:hover{color:#262729}.custom-theme .el-picker-panel__shortcut.active{background-color:#e6f1fe;color:#262729}.custom-theme .el-picker-panel__btn{border:1px solid #dcdcdc;color:#333;line-height:24px;border-radius:2px;padding:0 20px;cursor:pointer;background-color:transparent;outline:0;font-size:12px}.custom-theme .el-picker-panel__btn[disabled]{color:#ccc;cursor:not-allowed}.custom-theme .el-picker-panel__icon-btn{font-size:12px;color:#2d2f33;border:0;background:0 0;cursor:pointer;outline:0;margin-top:8px}.custom-theme .el-picker-panel__icon-btn:hover{color:#262729}.custom-theme .el-picker-panel__icon-btn.is-disabled{color:#bbb}.custom-theme .el-picker-panel__icon-btn.is-disabled:hover{cursor:not-allowed}.custom-theme .el-picker-panel__link-btn{vertical-align:middle}.custom-theme .el-picker-panel .popper__arrow{-webkit-transform:translateX(-400%);transform:translateX(-400%)}.custom-theme .el-picker-panel [slot=sidebar],.custom-theme .el-picker-panel__sidebar{position:absolute;top:0;bottom:0;width:110px;border-right:1px solid #e4e4e4;-webkit-box-sizing:border-box;box-sizing:border-box;padding-top:6px;background-color:#fff;overflow:auto}.custom-theme .el-picker-panel [slot=sidebar]+.el-picker-panel__body,.custom-theme .el-picker-panel__sidebar+.el-picker-panel__body{margin-left:110px}.custom-theme .el-date-picker{width:322px}.custom-theme .el-date-picker.has-sidebar.has-time{width:434px}.custom-theme .el-date-picker.has-sidebar{width:438px}.custom-theme .el-date-picker.has-time .el-picker-panel__body-wrapper{position:relative}.custom-theme .el-date-picker .el-picker-panel__content{width:292px}.custom-theme .el-date-picker table{table-layout:fixed;width:100%}.custom-theme .el-date-picker__editor-wrap{position:relative;display:table-cell;padding:0 5px}.custom-theme .el-date-picker__time-header{position:relative;border-bottom:1px solid #e4e4e4;font-size:12px;padding:8px 5px 5px 5px;display:table;width:100%;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-date-picker__header{margin:12px;text-align:center}.custom-theme .el-date-picker__header--bordered{margin-bottom:0;padding-bottom:12px;border-bottom:solid 1px #e6ebf5}.custom-theme .el-date-picker__header--bordered+.el-picker-panel__content{margin-top:0}.custom-theme .el-date-picker__header-label{font-size:16px;font-weight:500;padding:0 5px;line-height:22px;text-align:center;cursor:pointer;color:#5a5e66}.custom-theme .el-date-picker__header-label:hover{color:#262729}.custom-theme .el-date-picker__header-label.active{color:#262729}.custom-theme .el-date-picker__prev-btn{float:left}.custom-theme .el-date-picker__next-btn{float:right}.custom-theme .el-date-picker__time-wrap{padding:10px;text-align:center}.custom-theme .el-date-picker__time-label{float:left;cursor:pointer;line-height:30px;margin-left:10px}.custom-theme .el-date-range-picker{width:646px}.custom-theme .el-date-range-picker.has-sidebar{width:756px}.custom-theme .el-date-range-picker table{table-layout:fixed;width:100%}.custom-theme .el-date-range-picker .el-picker-panel__body{min-width:513px}.custom-theme .el-date-range-picker .el-picker-panel__content{margin:0}.custom-theme .el-date-range-picker__header{position:relative;text-align:center;height:28px}.custom-theme .el-date-range-picker__header [class*=arrow-left]{float:left}.custom-theme .el-date-range-picker__header [class*=arrow-right]{float:right}.custom-theme .el-date-range-picker__header div{font-size:16px;font-weight:500;margin-right:50px}.custom-theme .el-date-range-picker__content{float:left;width:50%;-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:16px}.custom-theme .el-date-range-picker__content.is-left{border-right:1px solid #e4e4e4}.custom-theme .el-date-range-picker__content.is-right .el-date-range-picker__header div{margin-left:50px;margin-right:50px}.custom-theme .el-date-range-picker__editors-wrap{-webkit-box-sizing:border-box;box-sizing:border-box;display:table-cell}.custom-theme .el-date-range-picker__editors-wrap.is-right{text-align:right}.custom-theme .el-date-range-picker__time-header{position:relative;border-bottom:1px solid #e4e4e4;font-size:12px;padding:8px 5px 5px 5px;display:table;width:100%;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-date-range-picker__time-header>.el-icon-arrow-right{font-size:20px;vertical-align:middle;display:table-cell;color:#2d2f33}.custom-theme .el-date-range-picker__time-picker-wrap{position:relative;display:table-cell;padding:0 5px}.custom-theme .el-date-range-picker__time-picker-wrap .el-picker-panel{position:absolute;top:13px;right:0;z-index:1;background:#fff}.custom-theme .el-time-range-picker{width:354px;overflow:visible}.custom-theme .el-time-range-picker__content{position:relative;text-align:center;padding:10px}.custom-theme .el-time-range-picker__cell{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:4px 7px 7px;width:50%;display:inline-block}.custom-theme .el-time-range-picker__header{margin-bottom:5px;text-align:center;font-size:14px}.custom-theme .el-time-range-picker__body{border-radius:2px;border:1px solid #dfe4ed}.custom-theme .el-time-panel{margin:5px 0;border:solid 1px #dfe4ed;background-color:#fff;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);border-radius:2px;position:absolute;width:180px;left:0;z-index:1000;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.custom-theme .el-time-panel__content{font-size:0;position:relative;overflow:hidden}.custom-theme .el-time-panel__content::after,.custom-theme .el-time-panel__content::before{content:"";top:50%;position:absolute;margin-top:-15px;height:32px;z-index:-1;left:0;right:0;-webkit-box-sizing:border-box;box-sizing:border-box;padding-top:6px;text-align:left;border-top:1px solid #dfe4ed;border-bottom:1px solid #dfe4ed}.custom-theme .el-time-panel__content::after{left:50%;margin-left:12%;margin-right:12%}.custom-theme .el-time-panel__content::before{padding-left:50%;margin-right:12%;margin-left:12%}.custom-theme .el-time-panel__content.has-seconds::after{left:calc(100% / 3 * 2)}.custom-theme .el-time-panel__content.has-seconds::before{padding-left:calc(100% / 3)}.custom-theme .el-time-panel__footer{border-top:1px solid #e4e4e4;padding:4px;height:36px;line-height:25px;text-align:right;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-time-panel__btn{border:none;line-height:28px;padding:0 5px;margin:0 5px;cursor:pointer;background-color:transparent;outline:0;font-size:12px;color:#2d2f33}.custom-theme .el-time-panel__btn.confirm{font-weight:800;color:#262729}.custom-theme .el-time-panel .popper__arrow{-webkit-transform:translateX(-400%);transform:translateX(-400%)}.custom-theme .el-input{position:relative;font-size:14px;display:inline-block;width:100%}.custom-theme .el-input::-webkit-scrollbar{z-index:11;width:6px}.custom-theme .el-input::-webkit-scrollbar:horizontal{height:6px}.custom-theme .el-input::-webkit-scrollbar-thumb{border-radius:5px;width:6px;background:#b4bccc}.custom-theme .el-input::-webkit-scrollbar-corner{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track-piece{background:#fff;width:6px}.custom-theme .el-input__inner{-webkit-appearance:none;background-color:#fff;background-image:none;border-radius:4px;border:1px solid #d8dce5;-webkit-box-sizing:border-box;box-sizing:border-box;color:#5a5e66;display:inline-block;font-size:inherit;height:40px;line-height:1;outline:0;padding:0 15px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1);width:100%}.custom-theme .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input__inner:hover{border-color:#b4bccc}.custom-theme .el-input__inner:focus{outline:0;border-color:#262729}.custom-theme .el-input__suffix{position:absolute;height:100%;right:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s;pointer-events:none}.custom-theme .el-input__suffix-inner{pointer-events:all}.custom-theme .el-input__prefix{position:absolute;height:100%;left:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s}.custom-theme .el-input__icon{height:100%;width:25px;text-align:center;-webkit-transition:all .3s;transition:all .3s;line-height:40px}.custom-theme .el-input__icon:after{content:'';height:100%;width:0;display:inline-block;vertical-align:middle}.custom-theme .el-input__validateIcon{pointer-events:none}.custom-theme .el-input.is-active .el-input__inner{outline:0;border-color:#262729}.custom-theme .el-input.is-disabled .el-input__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-input.is-disabled .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__icon{cursor:not-allowed}.custom-theme .el-input--suffix .el-input__inner{padding-right:30px}.custom-theme .el-input--prefix .el-input__inner{padding-left:30px}.custom-theme .el-input--medium{font-size:14px}.custom-theme .el-input--medium .el-input__inner{height:36px}.custom-theme .el-input--medium .el-input__icon{line-height:36px}.custom-theme .el-input--small{font-size:13px}.custom-theme .el-input--small .el-input__inner{height:32px}.custom-theme .el-input--small .el-input__icon{line-height:32px}.custom-theme .el-input--mini{font-size:12px}.custom-theme .el-input--mini .el-input__inner{height:28px}.custom-theme .el-input--mini .el-input__icon{line-height:28px}.custom-theme .el-input-group{line-height:normal;display:inline-table;width:100%;border-collapse:separate}.custom-theme .el-input-group>.el-input__inner{vertical-align:middle;display:table-cell}.custom-theme .el-input-group__append,.custom-theme .el-input-group__prepend{background-color:#f5f7fa;color:#0a76a4;vertical-align:middle;display:table-cell;position:relative;border:1px solid #d8dce5;border-radius:4px;padding:0 20px;width:1px;white-space:nowrap}.custom-theme .el-input-group__append:focus,.custom-theme .el-input-group__prepend:focus{outline:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-select,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-select{display:inline-block;margin:-20px}.custom-theme .el-input-group__append button.el-button,.custom-theme .el-input-group__append div.el-select .el-input__inner,.custom-theme .el-input-group__append div.el-select:hover .el-input__inner,.custom-theme .el-input-group__prepend button.el-button,.custom-theme .el-input-group__prepend div.el-select .el-input__inner,.custom-theme .el-input-group__prepend div.el-select:hover .el-input__inner{border-color:transparent;background-color:transparent;color:inherit;border-top:0;border-bottom:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-input,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-input{font-size:inherit}.custom-theme .el-input-group__prepend{border-right:0;border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-input-group__append{border-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--prepend .el-input__inner{border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--append .el-input__inner{border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-textarea{display:inline-block;width:100%;vertical-align:bottom}.custom-theme .el-textarea__inner{display:block;resize:vertical;padding:5px 15px;line-height:1.5;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;font-size:14px;color:#5a5e66;background-color:#fff;background-image:none;border:1px solid #d8dce5;border-radius:4px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}.custom-theme .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:hover{border-color:#b4bccc}.custom-theme .el-textarea__inner:focus{outline:0;border-color:#262729}.custom-theme .el-textarea.is-disabled .el-textarea__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-scrollbar{overflow:hidden;position:relative}.custom-theme .el-scrollbar:active>.el-scrollbar__bar,.custom-theme .el-scrollbar:focus>.el-scrollbar__bar,.custom-theme .el-scrollbar:hover>.el-scrollbar__bar{opacity:1;-webkit-transition:opacity 340ms ease-out;transition:opacity 340ms ease-out}.custom-theme .el-scrollbar__wrap{overflow:scroll;height:100%}.custom-theme .el-scrollbar__wrap--hidden-default::-webkit-scrollbar{width:0;height:0}.custom-theme .el-scrollbar__thumb{position:relative;display:block;width:0;height:0;cursor:pointer;border-radius:inherit;background-color:rgba(135,141,153,.3);-webkit-transition:.3s background-color;transition:.3s background-color}.custom-theme .el-scrollbar__thumb:hover{background-color:rgba(135,141,153,.5)}.custom-theme .el-scrollbar__bar{position:absolute;right:2px;bottom:2px;z-index:1;border-radius:4px;opacity:0;-webkit-transition:opacity 120ms ease-out;transition:opacity 120ms ease-out}.custom-theme .el-scrollbar__bar.is-vertical{width:6px;top:2px}.custom-theme .el-scrollbar__bar.is-vertical>div{width:100%}.custom-theme .el-scrollbar__bar.is-horizontal{height:6px;left:2px}.custom-theme .el-scrollbar__bar.is-horizontal>div{height:100%}.custom-theme .el-popper .popper__arrow,.custom-theme .el-popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.custom-theme .el-popper .popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03));filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03))}.custom-theme .el-popper .popper__arrow::after{content:" ";border-width:6px}.custom-theme .el-popper[x-placement^=top]{margin-bottom:12px}.custom-theme .el-popper[x-placement^=top] .popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-top-color:#e6ebf5;border-bottom-width:0}.custom-theme .el-popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-6px;border-top-color:#fff;border-bottom-width:0}.custom-theme .el-popper[x-placement^=bottom]{margin-top:12px}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow{top:-6px;left:50%;margin-right:3px;border-top-width:0;border-bottom-color:#e6ebf5}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-6px;border-top-width:0;border-bottom-color:#fff}.custom-theme .el-popper[x-placement^=right]{margin-left:12px}.custom-theme .el-popper[x-placement^=right] .popper__arrow{top:50%;left:-6px;margin-bottom:3px;border-right-color:#e6ebf5;border-left-width:0}.custom-theme .el-popper[x-placement^=right] .popper__arrow::after{bottom:-6px;left:1px;border-right-color:#fff;border-left-width:0}.custom-theme .el-popper[x-placement^=left]{margin-right:12px}.custom-theme .el-popper[x-placement^=left] .popper__arrow{top:50%;right:-6px;margin-bottom:3px;border-right-width:0;border-left-color:#e6ebf5}.custom-theme .el-popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-6px;margin-left:-6px;border-right-width:0;border-left-color:#fff}.custom-theme .fade-in-linear-enter-active,.custom-theme .fade-in-linear-leave-active{-webkit-transition:opacity .2s linear;transition:opacity .2s linear}.custom-theme .fade-in-linear-enter,.custom-theme .fade-in-linear-leave,.custom-theme .fade-in-linear-leave-active{opacity:0}.custom-theme .el-fade-in-linear-enter-active,.custom-theme .el-fade-in-linear-leave-active{-webkit-transition:opacity .2s linear;transition:opacity .2s linear}.custom-theme .el-fade-in-linear-enter,.custom-theme .el-fade-in-linear-leave,.custom-theme .el-fade-in-linear-leave-active{opacity:0}.custom-theme .el-fade-in-enter-active,.custom-theme .el-fade-in-leave-active{-webkit-transition:all .3s cubic-bezier(.55,0,.1,1);transition:all .3s cubic-bezier(.55,0,.1,1)}.custom-theme .el-fade-in-enter,.custom-theme .el-fade-in-leave-active{opacity:0}.custom-theme .el-zoom-in-center-enter-active,.custom-theme .el-zoom-in-center-leave-active{-webkit-transition:all .3s cubic-bezier(.55,0,.1,1);transition:all .3s cubic-bezier(.55,0,.1,1)}.custom-theme .el-zoom-in-center-enter,.custom-theme .el-zoom-in-center-leave-active{opacity:0;-webkit-transform:scaleX(0);transform:scaleX(0)}.custom-theme .el-zoom-in-top-enter-active,.custom-theme .el-zoom-in-top-leave-active{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;-webkit-transform-origin:center top;transform-origin:center top}.custom-theme .el-zoom-in-top-enter,.custom-theme .el-zoom-in-top-leave-active{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.custom-theme .el-zoom-in-bottom-enter-active,.custom-theme .el-zoom-in-bottom-leave-active{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;-webkit-transform-origin:center bottom;transform-origin:center bottom}.custom-theme .el-zoom-in-bottom-enter,.custom-theme .el-zoom-in-bottom-leave-active{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.custom-theme .el-zoom-in-left-enter-active,.custom-theme .el-zoom-in-left-leave-active{opacity:1;-webkit-transform:scale(1,1);transform:scale(1,1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;-webkit-transform-origin:top left;transform-origin:top left}.custom-theme .el-zoom-in-left-enter,.custom-theme .el-zoom-in-left-leave-active{opacity:0;-webkit-transform:scale(.45,.45);transform:scale(.45,.45)}.custom-theme .collapse-transition{-webkit-transition:.3s height ease-in-out,.3s padding-top ease-in-out,.3s padding-bottom ease-in-out;transition:.3s height ease-in-out,.3s padding-top ease-in-out,.3s padding-bottom ease-in-out}.custom-theme .horizontal-collapse-transition{-webkit-transition:.3s width ease-in-out,.3s padding-left ease-in-out,.3s padding-right ease-in-out;transition:.3s width ease-in-out,.3s padding-left ease-in-out,.3s padding-right ease-in-out}.custom-theme .el-list-enter-active,.custom-theme .el-list-leave-active{-webkit-transition:all 1s;transition:all 1s}.custom-theme .el-list-enter,.custom-theme .el-list-leave-active{opacity:0;-webkit-transform:translateY(-30px);transform:translateY(-30px)}.custom-theme .el-opacity-transition{-webkit-transition:opacity .3s cubic-bezier(.55,0,.1,1);transition:opacity .3s cubic-bezier(.55,0,.1,1)}.custom-theme .el-date-editor{position:relative;display:inline-block;text-align:left}.custom-theme .el-date-editor.el-input,.custom-theme .el-date-editor.el-input__inner{width:220px}.custom-theme .el-date-editor--daterange.el-input,.custom-theme .el-date-editor--daterange.el-input__inner,.custom-theme .el-date-editor--timerange.el-input,.custom-theme .el-date-editor--timerange.el-input__inner{width:350px}.custom-theme .el-date-editor--datetimerange.el-input,.custom-theme .el-date-editor--datetimerange.el-input__inner{width:400px}.custom-theme .el-date-editor .el-range__icon{font-size:14px;margin-left:-5px;color:#b4bccc;float:left;line-height:32px}.custom-theme .el-date-editor .el-range-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:none;outline:0;display:inline-block;height:100%;margin:0;padding:0;width:39%;text-align:center;font-size:14px;color:#5a5e66}.custom-theme .el-date-editor .el-range-input::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-date-editor .el-range-input:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-date-editor .el-range-input::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-date-editor .el-range-input::placeholder{color:#b4bccc}.custom-theme .el-date-editor .el-range-separator{display:inline-block;height:100%;padding:0 5px;margin:0;text-align:center;line-height:32px;font-size:14px;width:5%;color:#2d2f33}.custom-theme .el-date-editor .el-range__close-icon{font-size:14px;color:#b4bccc;width:25px;display:inline-block;float:right;line-height:32px}.custom-theme .el-range-editor.el-input__inner{padding:3px 10px}.custom-theme .el-range-editor.is-active{border-color:#262729}.custom-theme .el-range-editor.is-active:hover{border-color:#262729}.custom-theme .el-range-editor--medium.el-input__inner{height:36px}.custom-theme .el-range-editor--medium .el-range-separator{line-height:28px;font-size:14px}.custom-theme .el-range-editor--medium .el-range-input{font-size:14px}.custom-theme .el-range-editor--medium .el-range__close-icon,.custom-theme .el-range-editor--medium .el-range__icon{line-height:28px}.custom-theme .el-range-editor--small.el-input__inner{height:32px}.custom-theme .el-range-editor--small .el-range-separator{line-height:24px;font-size:13px}.custom-theme .el-range-editor--small .el-range-input{font-size:13px}.custom-theme .el-range-editor--small .el-range__close-icon,.custom-theme .el-range-editor--small .el-range__icon{line-height:24px}.custom-theme .el-range-editor--mini.el-input__inner{height:28px}.custom-theme .el-range-editor--mini .el-range-separator{line-height:20px;font-size:12px}.custom-theme .el-range-editor--mini .el-range-input{font-size:12px}.custom-theme .el-range-editor--mini .el-range__close-icon,.custom-theme .el-range-editor--mini .el-range__icon{line-height:20px}.custom-theme .el-range-editor.is-disabled{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-range-editor.is-disabled:focus,.custom-theme .el-range-editor.is-disabled:hover{border-color:#dfe4ed}.custom-theme .el-range-editor.is-disabled input{background-color:#f5f7fa;color:#b4bccc;cursor:not-allowed}.custom-theme .el-range-editor.is-disabled input::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-range-editor.is-disabled input:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-range-editor.is-disabled input::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-range-editor.is-disabled input::placeholder{color:#b4bccc}.custom-theme .el-range-editor.is-disabled .el-range-separator{color:#b4bccc}.custom-theme .el-picker-panel{color:#5a5e66;border:1px solid #dfe4ed;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);background:#fff;border-radius:4px;line-height:30px;margin:5px 0}.custom-theme .el-picker-panel__body-wrapper::after,.custom-theme .el-picker-panel__body::after{content:"";display:table;clear:both}.custom-theme .el-picker-panel__content{position:relative;margin:15px}.custom-theme .el-picker-panel__footer{border-top:1px solid #e4e4e4;padding:4px;text-align:right;background-color:#fff;position:relative;font-size:0}.custom-theme .el-picker-panel__shortcut{display:block;width:100%;border:0;background-color:transparent;line-height:28px;font-size:14px;color:#5a5e66;padding-left:12px;text-align:left;outline:0;cursor:pointer}.custom-theme .el-picker-panel__shortcut:hover{color:#262729}.custom-theme .el-picker-panel__shortcut.active{background-color:#e6f1fe;color:#262729}.custom-theme .el-picker-panel__btn{border:1px solid #dcdcdc;color:#333;line-height:24px;border-radius:2px;padding:0 20px;cursor:pointer;background-color:transparent;outline:0;font-size:12px}.custom-theme .el-picker-panel__btn[disabled]{color:#ccc;cursor:not-allowed}.custom-theme .el-picker-panel__icon-btn{font-size:12px;color:#2d2f33;border:0;background:0 0;cursor:pointer;outline:0;margin-top:8px}.custom-theme .el-picker-panel__icon-btn:hover{color:#262729}.custom-theme .el-picker-panel__icon-btn.is-disabled{color:#bbb}.custom-theme .el-picker-panel__icon-btn.is-disabled:hover{cursor:not-allowed}.custom-theme .el-picker-panel__link-btn{vertical-align:middle}.custom-theme .el-picker-panel .popper__arrow{-webkit-transform:translateX(-400%);transform:translateX(-400%)}.custom-theme .el-picker-panel [slot=sidebar],.custom-theme .el-picker-panel__sidebar{position:absolute;top:0;bottom:0;width:110px;border-right:1px solid #e4e4e4;-webkit-box-sizing:border-box;box-sizing:border-box;padding-top:6px;background-color:#fff;overflow:auto}.custom-theme .el-picker-panel [slot=sidebar]+.el-picker-panel__body,.custom-theme .el-picker-panel__sidebar+.el-picker-panel__body{margin-left:110px}.custom-theme .el-date-picker{width:322px}.custom-theme .el-date-picker.has-sidebar.has-time{width:434px}.custom-theme .el-date-picker.has-sidebar{width:438px}.custom-theme .el-date-picker.has-time .el-picker-panel__body-wrapper{position:relative}.custom-theme .el-date-picker .el-picker-panel__content{width:292px}.custom-theme .el-date-picker table{table-layout:fixed;width:100%}.custom-theme .el-date-picker__editor-wrap{position:relative;display:table-cell;padding:0 5px}.custom-theme .el-date-picker__time-header{position:relative;border-bottom:1px solid #e4e4e4;font-size:12px;padding:8px 5px 5px 5px;display:table;width:100%;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-date-picker__header{margin:12px;text-align:center}.custom-theme .el-date-picker__header--bordered{margin-bottom:0;padding-bottom:12px;border-bottom:solid 1px #e6ebf5}.custom-theme .el-date-picker__header--bordered+.el-picker-panel__content{margin-top:0}.custom-theme .el-date-picker__header-label{font-size:16px;font-weight:500;padding:0 5px;line-height:22px;text-align:center;cursor:pointer;color:#5a5e66}.custom-theme .el-date-picker__header-label:hover{color:#262729}.custom-theme .el-date-picker__header-label.active{color:#262729}.custom-theme .el-date-picker__prev-btn{float:left}.custom-theme .el-date-picker__next-btn{float:right}.custom-theme .el-date-picker__time-wrap{padding:10px;text-align:center}.custom-theme .el-date-picker__time-label{float:left;cursor:pointer;line-height:30px;margin-left:10px}.custom-theme .el-scrollbar{overflow:hidden;position:relative}.custom-theme .el-scrollbar:active>.el-scrollbar__bar,.custom-theme .el-scrollbar:focus>.el-scrollbar__bar,.custom-theme .el-scrollbar:hover>.el-scrollbar__bar{opacity:1;-webkit-transition:opacity 340ms ease-out;transition:opacity 340ms ease-out}.custom-theme .el-scrollbar__wrap{overflow:scroll;height:100%}.custom-theme .el-scrollbar__wrap--hidden-default::-webkit-scrollbar{width:0;height:0}.custom-theme .el-scrollbar__thumb{position:relative;display:block;width:0;height:0;cursor:pointer;border-radius:inherit;background-color:rgba(135,141,153,.3);-webkit-transition:.3s background-color;transition:.3s background-color}.custom-theme .el-scrollbar__thumb:hover{background-color:rgba(135,141,153,.5)}.custom-theme .el-scrollbar__bar{position:absolute;right:2px;bottom:2px;z-index:1;border-radius:4px;opacity:0;-webkit-transition:opacity 120ms ease-out;transition:opacity 120ms ease-out}.custom-theme .el-scrollbar__bar.is-vertical{width:6px;top:2px}.custom-theme .el-scrollbar__bar.is-vertical>div{width:100%}.custom-theme .el-scrollbar__bar.is-horizontal{height:6px;left:2px}.custom-theme .el-scrollbar__bar.is-horizontal>div{height:100%}.custom-theme .el-popper .popper__arrow,.custom-theme .el-popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.custom-theme .el-popper .popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03));filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03))}.custom-theme .el-popper .popper__arrow::after{content:" ";border-width:6px}.custom-theme .el-popper[x-placement^=top]{margin-bottom:12px}.custom-theme .el-popper[x-placement^=top] .popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-top-color:#e6ebf5;border-bottom-width:0}.custom-theme .el-popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-6px;border-top-color:#fff;border-bottom-width:0}.custom-theme .el-popper[x-placement^=bottom]{margin-top:12px}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow{top:-6px;left:50%;margin-right:3px;border-top-width:0;border-bottom-color:#e6ebf5}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-6px;border-top-width:0;border-bottom-color:#fff}.custom-theme .el-popper[x-placement^=right]{margin-left:12px}.custom-theme .el-popper[x-placement^=right] .popper__arrow{top:50%;left:-6px;margin-bottom:3px;border-right-color:#e6ebf5;border-left-width:0}.custom-theme .el-popper[x-placement^=right] .popper__arrow::after{bottom:-6px;left:1px;border-right-color:#fff;border-left-width:0}.custom-theme .el-popper[x-placement^=left]{margin-right:12px}.custom-theme .el-popper[x-placement^=left] .popper__arrow{top:50%;right:-6px;margin-bottom:3px;border-right-width:0;border-left-color:#e6ebf5}.custom-theme .el-popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-6px;margin-left:-6px;border-right-width:0;border-left-color:#fff}.custom-theme .time-select{margin:5px 0;min-width:0}.custom-theme .time-select .el-picker-panel__content{max-height:200px;margin:0}.custom-theme .time-select-item{padding:8px 10px;font-size:14px;line-height:20px}.custom-theme .time-select-item.selected:not(.disabled){color:#262729;font-weight:700}.custom-theme .time-select-item.disabled{color:#dfe4ed;cursor:not-allowed}.custom-theme .time-select-item:hover{background-color:#f5f7fa;font-weight:700;cursor:pointer}.custom-theme .fade-in-linear-enter-active,.custom-theme .fade-in-linear-leave-active{-webkit-transition:opacity .2s linear;transition:opacity .2s linear}.custom-theme .fade-in-linear-enter,.custom-theme .fade-in-linear-leave,.custom-theme .fade-in-linear-leave-active{opacity:0}.custom-theme .el-fade-in-linear-enter-active,.custom-theme .el-fade-in-linear-leave-active{-webkit-transition:opacity .2s linear;transition:opacity .2s linear}.custom-theme .el-fade-in-linear-enter,.custom-theme .el-fade-in-linear-leave,.custom-theme .el-fade-in-linear-leave-active{opacity:0}.custom-theme .el-fade-in-enter-active,.custom-theme .el-fade-in-leave-active{-webkit-transition:all .3s cubic-bezier(.55,0,.1,1);transition:all .3s cubic-bezier(.55,0,.1,1)}.custom-theme .el-fade-in-enter,.custom-theme .el-fade-in-leave-active{opacity:0}.custom-theme .el-zoom-in-center-enter-active,.custom-theme .el-zoom-in-center-leave-active{-webkit-transition:all .3s cubic-bezier(.55,0,.1,1);transition:all .3s cubic-bezier(.55,0,.1,1)}.custom-theme .el-zoom-in-center-enter,.custom-theme .el-zoom-in-center-leave-active{opacity:0;-webkit-transform:scaleX(0);transform:scaleX(0)}.custom-theme .el-zoom-in-top-enter-active,.custom-theme .el-zoom-in-top-leave-active{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;-webkit-transform-origin:center top;transform-origin:center top}.custom-theme .el-zoom-in-top-enter,.custom-theme .el-zoom-in-top-leave-active{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.custom-theme .el-zoom-in-bottom-enter-active,.custom-theme .el-zoom-in-bottom-leave-active{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;-webkit-transform-origin:center bottom;transform-origin:center bottom}.custom-theme .el-zoom-in-bottom-enter,.custom-theme .el-zoom-in-bottom-leave-active{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.custom-theme .el-zoom-in-left-enter-active,.custom-theme .el-zoom-in-left-leave-active{opacity:1;-webkit-transform:scale(1,1);transform:scale(1,1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;-webkit-transform-origin:top left;transform-origin:top left}.custom-theme .el-zoom-in-left-enter,.custom-theme .el-zoom-in-left-leave-active{opacity:0;-webkit-transform:scale(.45,.45);transform:scale(.45,.45)}.custom-theme .collapse-transition{-webkit-transition:.3s height ease-in-out,.3s padding-top ease-in-out,.3s padding-bottom ease-in-out;transition:.3s height ease-in-out,.3s padding-top ease-in-out,.3s padding-bottom ease-in-out}.custom-theme .horizontal-collapse-transition{-webkit-transition:.3s width ease-in-out,.3s padding-left ease-in-out,.3s padding-right ease-in-out;transition:.3s width ease-in-out,.3s padding-left ease-in-out,.3s padding-right ease-in-out}.custom-theme .el-list-enter-active,.custom-theme .el-list-leave-active{-webkit-transition:all 1s;transition:all 1s}.custom-theme .el-list-enter,.custom-theme .el-list-leave-active{opacity:0;-webkit-transform:translateY(-30px);transform:translateY(-30px)}.custom-theme .el-opacity-transition{-webkit-transition:opacity .3s cubic-bezier(.55,0,.1,1);transition:opacity .3s cubic-bezier(.55,0,.1,1)}.custom-theme .el-date-editor{position:relative;display:inline-block;text-align:left}.custom-theme .el-date-editor.el-input,.custom-theme .el-date-editor.el-input__inner{width:220px}.custom-theme .el-date-editor--daterange.el-input,.custom-theme .el-date-editor--daterange.el-input__inner,.custom-theme .el-date-editor--timerange.el-input,.custom-theme .el-date-editor--timerange.el-input__inner{width:350px}.custom-theme .el-date-editor--datetimerange.el-input,.custom-theme .el-date-editor--datetimerange.el-input__inner{width:400px}.custom-theme .el-date-editor .el-range__icon{font-size:14px;margin-left:-5px;color:#b4bccc;float:left;line-height:32px}.custom-theme .el-date-editor .el-range-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:none;outline:0;display:inline-block;height:100%;margin:0;padding:0;width:39%;text-align:center;font-size:14px;color:#5a5e66}.custom-theme .el-date-editor .el-range-input::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-date-editor .el-range-input:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-date-editor .el-range-input::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-date-editor .el-range-input::placeholder{color:#b4bccc}.custom-theme .el-date-editor .el-range-separator{display:inline-block;height:100%;padding:0 5px;margin:0;text-align:center;line-height:32px;font-size:14px;width:5%;color:#2d2f33}.custom-theme .el-date-editor .el-range__close-icon{font-size:14px;color:#b4bccc;width:25px;display:inline-block;float:right;line-height:32px}.custom-theme .el-range-editor.el-input__inner{padding:3px 10px}.custom-theme .el-range-editor.is-active{border-color:#262729}.custom-theme .el-range-editor.is-active:hover{border-color:#262729}.custom-theme .el-range-editor--medium.el-input__inner{height:36px}.custom-theme .el-range-editor--medium .el-range-separator{line-height:28px;font-size:14px}.custom-theme .el-range-editor--medium .el-range-input{font-size:14px}.custom-theme .el-range-editor--medium .el-range__close-icon,.custom-theme .el-range-editor--medium .el-range__icon{line-height:28px}.custom-theme .el-range-editor--small.el-input__inner{height:32px}.custom-theme .el-range-editor--small .el-range-separator{line-height:24px;font-size:13px}.custom-theme .el-range-editor--small .el-range-input{font-size:13px}.custom-theme .el-range-editor--small .el-range__close-icon,.custom-theme .el-range-editor--small .el-range__icon{line-height:24px}.custom-theme .el-range-editor--mini.el-input__inner{height:28px}.custom-theme .el-range-editor--mini .el-range-separator{line-height:20px;font-size:12px}.custom-theme .el-range-editor--mini .el-range-input{font-size:12px}.custom-theme .el-range-editor--mini .el-range__close-icon,.custom-theme .el-range-editor--mini .el-range__icon{line-height:20px}.custom-theme .el-range-editor.is-disabled{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-range-editor.is-disabled:focus,.custom-theme .el-range-editor.is-disabled:hover{border-color:#dfe4ed}.custom-theme .el-range-editor.is-disabled input{background-color:#f5f7fa;color:#b4bccc;cursor:not-allowed}.custom-theme .el-range-editor.is-disabled input::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-range-editor.is-disabled input:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-range-editor.is-disabled input::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-range-editor.is-disabled input::placeholder{color:#b4bccc}.custom-theme .el-range-editor.is-disabled .el-range-separator{color:#b4bccc}.custom-theme .el-time-spinner.has-seconds .el-time-spinner__wrapper{width:33.3%}.custom-theme .el-time-spinner.has-seconds .el-time-spinner__wrapper:nth-child(2){margin-left:1%}.custom-theme .el-time-spinner__wrapper{max-height:190px;overflow:auto;display:inline-block;width:50%;vertical-align:top;position:relative}.custom-theme .el-time-spinner__wrapper .el-scrollbar__wrap:not(.el-scrollbar__wrap--hidden-default){padding-bottom:15px}.custom-theme .el-time-spinner__wrapper.is-arrow{-webkit-box-sizing:border-box;box-sizing:border-box;text-align:center;overflow:hidden}.custom-theme .el-time-spinner__wrapper.is-arrow .el-time-spinner__list{-webkit-transform:translateY(-32px);transform:translateY(-32px)}.custom-theme .el-time-spinner__wrapper.is-arrow .el-time-spinner__item:hover:not(.disabled):not(.active){background:#fff;cursor:default}.custom-theme .el-time-spinner__arrow{font-size:12px;color:#878d99;position:absolute;left:0;width:100%;z-index:1;text-align:center;height:30px;line-height:30px;cursor:pointer}.custom-theme .el-time-spinner__arrow:hover{color:#262729}.custom-theme .el-time-spinner__arrow.el-icon-arrow-up{top:10px}.custom-theme .el-time-spinner__arrow.el-icon-arrow-down{bottom:10px}.custom-theme .el-time-spinner__input.el-input{width:70%}.custom-theme .el-time-spinner__input.el-input .el-input__inner{padding:0;text-align:center}.custom-theme .el-time-spinner__list{padding:0;margin:0;list-style:none;text-align:center}.custom-theme .el-time-spinner__list::after,.custom-theme .el-time-spinner__list::before{content:'';display:block;width:100%;height:80px}.custom-theme .el-time-spinner__item{height:32px;line-height:32px;font-size:12px;color:#5a5e66}.custom-theme .el-time-spinner__item:hover:not(.disabled):not(.active){background:#f5f7fa;cursor:pointer}.custom-theme .el-time-spinner__item.active:not(.disabled){color:#2d2f33;font-weight:700}.custom-theme .el-time-spinner__item.disabled{color:#b4bccc;cursor:not-allowed}.custom-theme .el-time-panel{margin:5px 0;border:solid 1px #dfe4ed;background-color:#fff;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);border-radius:2px;position:absolute;width:180px;left:0;z-index:1000;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.custom-theme .el-time-panel__content{font-size:0;position:relative;overflow:hidden}.custom-theme .el-time-panel__content::after,.custom-theme .el-time-panel__content::before{content:"";top:50%;position:absolute;margin-top:-15px;height:32px;z-index:-1;left:0;right:0;-webkit-box-sizing:border-box;box-sizing:border-box;padding-top:6px;text-align:left;border-top:1px solid #dfe4ed;border-bottom:1px solid #dfe4ed}.custom-theme .el-time-panel__content::after{left:50%;margin-left:12%;margin-right:12%}.custom-theme .el-time-panel__content::before{padding-left:50%;margin-right:12%;margin-left:12%}.custom-theme .el-time-panel__content.has-seconds::after{left:calc(100% / 3 * 2)}.custom-theme .el-time-panel__content.has-seconds::before{padding-left:calc(100% / 3)}.custom-theme .el-time-panel__footer{border-top:1px solid #e4e4e4;padding:4px;height:36px;line-height:25px;text-align:right;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-time-panel__btn{border:none;line-height:28px;padding:0 5px;margin:0 5px;cursor:pointer;background-color:transparent;outline:0;font-size:12px;color:#2d2f33}.custom-theme .el-time-panel__btn.confirm{font-weight:800;color:#262729}.custom-theme .el-time-panel .popper__arrow{-webkit-transform:translateX(-400%);transform:translateX(-400%)}.custom-theme .el-input{position:relative;font-size:14px;display:inline-block;width:100%}.custom-theme .el-input::-webkit-scrollbar{z-index:11;width:6px}.custom-theme .el-input::-webkit-scrollbar:horizontal{height:6px}.custom-theme .el-input::-webkit-scrollbar-thumb{border-radius:5px;width:6px;background:#b4bccc}.custom-theme .el-input::-webkit-scrollbar-corner{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track-piece{background:#fff;width:6px}.custom-theme .el-input__inner{-webkit-appearance:none;background-color:#fff;background-image:none;border-radius:4px;border:1px solid #d8dce5;-webkit-box-sizing:border-box;box-sizing:border-box;color:#5a5e66;display:inline-block;font-size:inherit;height:40px;line-height:1;outline:0;padding:0 15px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1);width:100%}.custom-theme .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input__inner:hover{border-color:#b4bccc}.custom-theme .el-input__inner:focus{outline:0;border-color:#262729}.custom-theme .el-input__suffix{position:absolute;height:100%;right:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s;pointer-events:none}.custom-theme .el-input__suffix-inner{pointer-events:all}.custom-theme .el-input__prefix{position:absolute;height:100%;left:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s}.custom-theme .el-input__icon{height:100%;width:25px;text-align:center;-webkit-transition:all .3s;transition:all .3s;line-height:40px}.custom-theme .el-input__icon:after{content:'';height:100%;width:0;display:inline-block;vertical-align:middle}.custom-theme .el-input__validateIcon{pointer-events:none}.custom-theme .el-input.is-active .el-input__inner{outline:0;border-color:#262729}.custom-theme .el-input.is-disabled .el-input__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-input.is-disabled .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__icon{cursor:not-allowed}.custom-theme .el-input--suffix .el-input__inner{padding-right:30px}.custom-theme .el-input--prefix .el-input__inner{padding-left:30px}.custom-theme .el-input--medium{font-size:14px}.custom-theme .el-input--medium .el-input__inner{height:36px}.custom-theme .el-input--medium .el-input__icon{line-height:36px}.custom-theme .el-input--small{font-size:13px}.custom-theme .el-input--small .el-input__inner{height:32px}.custom-theme .el-input--small .el-input__icon{line-height:32px}.custom-theme .el-input--mini{font-size:12px}.custom-theme .el-input--mini .el-input__inner{height:28px}.custom-theme .el-input--mini .el-input__icon{line-height:28px}.custom-theme .el-input-group{line-height:normal;display:inline-table;width:100%;border-collapse:separate}.custom-theme .el-input-group>.el-input__inner{vertical-align:middle;display:table-cell}.custom-theme .el-input-group__append,.custom-theme .el-input-group__prepend{background-color:#f5f7fa;color:#0a76a4;vertical-align:middle;display:table-cell;position:relative;border:1px solid #d8dce5;border-radius:4px;padding:0 20px;width:1px;white-space:nowrap}.custom-theme .el-input-group__append:focus,.custom-theme .el-input-group__prepend:focus{outline:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-select,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-select{display:inline-block;margin:-20px}.custom-theme .el-input-group__append button.el-button,.custom-theme .el-input-group__append div.el-select .el-input__inner,.custom-theme .el-input-group__append div.el-select:hover .el-input__inner,.custom-theme .el-input-group__prepend button.el-button,.custom-theme .el-input-group__prepend div.el-select .el-input__inner,.custom-theme .el-input-group__prepend div.el-select:hover .el-input__inner{border-color:transparent;background-color:transparent;color:inherit;border-top:0;border-bottom:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-input,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-input{font-size:inherit}.custom-theme .el-input-group__prepend{border-right:0;border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-input-group__append{border-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--prepend .el-input__inner{border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--append .el-input__inner{border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-textarea{display:inline-block;width:100%;vertical-align:bottom}.custom-theme .el-textarea__inner{display:block;resize:vertical;padding:5px 15px;line-height:1.5;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;font-size:14px;color:#5a5e66;background-color:#fff;background-image:none;border:1px solid #d8dce5;border-radius:4px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}.custom-theme .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:hover{border-color:#b4bccc}.custom-theme .el-textarea__inner:focus{outline:0;border-color:#262729}.custom-theme .el-textarea.is-disabled .el-textarea__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-scrollbar{overflow:hidden;position:relative}.custom-theme .el-scrollbar:active>.el-scrollbar__bar,.custom-theme .el-scrollbar:focus>.el-scrollbar__bar,.custom-theme .el-scrollbar:hover>.el-scrollbar__bar{opacity:1;-webkit-transition:opacity 340ms ease-out;transition:opacity 340ms ease-out}.custom-theme .el-scrollbar__wrap{overflow:scroll;height:100%}.custom-theme .el-scrollbar__wrap--hidden-default::-webkit-scrollbar{width:0;height:0}.custom-theme .el-scrollbar__thumb{position:relative;display:block;width:0;height:0;cursor:pointer;border-radius:inherit;background-color:rgba(135,141,153,.3);-webkit-transition:.3s background-color;transition:.3s background-color}.custom-theme .el-scrollbar__thumb:hover{background-color:rgba(135,141,153,.5)}.custom-theme .el-scrollbar__bar{position:absolute;right:2px;bottom:2px;z-index:1;border-radius:4px;opacity:0;-webkit-transition:opacity 120ms ease-out;transition:opacity 120ms ease-out}.custom-theme .el-scrollbar__bar.is-vertical{width:6px;top:2px}.custom-theme .el-scrollbar__bar.is-vertical>div{width:100%}.custom-theme .el-scrollbar__bar.is-horizontal{height:6px;left:2px}.custom-theme .el-scrollbar__bar.is-horizontal>div{height:100%}.custom-theme .el-popper .popper__arrow,.custom-theme .el-popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.custom-theme .el-popper .popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03));filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03))}.custom-theme .el-popper .popper__arrow::after{content:" ";border-width:6px}.custom-theme .el-popper[x-placement^=top]{margin-bottom:12px}.custom-theme .el-popper[x-placement^=top] .popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-top-color:#e6ebf5;border-bottom-width:0}.custom-theme .el-popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-6px;border-top-color:#fff;border-bottom-width:0}.custom-theme .el-popper[x-placement^=bottom]{margin-top:12px}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow{top:-6px;left:50%;margin-right:3px;border-top-width:0;border-bottom-color:#e6ebf5}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-6px;border-top-width:0;border-bottom-color:#fff}.custom-theme .el-popper[x-placement^=right]{margin-left:12px}.custom-theme .el-popper[x-placement^=right] .popper__arrow{top:50%;left:-6px;margin-bottom:3px;border-right-color:#e6ebf5;border-left-width:0}.custom-theme .el-popper[x-placement^=right] .popper__arrow::after{bottom:-6px;left:1px;border-right-color:#fff;border-left-width:0}.custom-theme .el-popper[x-placement^=left]{margin-right:12px}.custom-theme .el-popper[x-placement^=left] .popper__arrow{top:50%;right:-6px;margin-bottom:3px;border-right-width:0;border-left-color:#e6ebf5}.custom-theme .el-popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-6px;margin-left:-6px;border-right-width:0;border-left-color:#fff}.custom-theme .el-popper .popper__arrow,.custom-theme .el-popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.custom-theme .el-popper .popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03));filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03))}.custom-theme .el-popper .popper__arrow::after{content:" ";border-width:6px}.custom-theme .el-popper[x-placement^=top]{margin-bottom:12px}.custom-theme .el-popper[x-placement^=top] .popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-top-color:#e6ebf5;border-bottom-width:0}.custom-theme .el-popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-6px;border-top-color:#fff;border-bottom-width:0}.custom-theme .el-popper[x-placement^=bottom]{margin-top:12px}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow{top:-6px;left:50%;margin-right:3px;border-top-width:0;border-bottom-color:#e6ebf5}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-6px;border-top-width:0;border-bottom-color:#fff}.custom-theme .el-popper[x-placement^=right]{margin-left:12px}.custom-theme .el-popper[x-placement^=right] .popper__arrow{top:50%;left:-6px;margin-bottom:3px;border-right-color:#e6ebf5;border-left-width:0}.custom-theme .el-popper[x-placement^=right] .popper__arrow::after{bottom:-6px;left:1px;border-right-color:#fff;border-left-width:0}.custom-theme .el-popper[x-placement^=left]{margin-right:12px}.custom-theme .el-popper[x-placement^=left] .popper__arrow{top:50%;right:-6px;margin-bottom:3px;border-right-width:0;border-left-color:#e6ebf5}.custom-theme .el-popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-6px;margin-left:-6px;border-right-width:0;border-left-color:#fff}.custom-theme .el-popover{position:absolute;background:#fff;min-width:150px;border-radius:4px;border:1px solid #e6ebf5;padding:12px;z-index:2000;color:#5a5e66;line-height:1.4;text-align:justify;word-break:break-all;font-size:14px;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.custom-theme .el-popover--plain{padding:18px 20px}.custom-theme .el-popover__title{color:#2d2f33;font-size:16px;line-height:1;margin-bottom:12px}.custom-theme .el-tooltip__popper{position:absolute;border-radius:4px;padding:10px;z-index:2000;font-size:12px;line-height:1.2}.custom-theme .el-tooltip__popper .popper__arrow,.custom-theme .el-tooltip__popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.custom-theme .el-tooltip__popper .popper__arrow{border-width:6px}.custom-theme .el-tooltip__popper .popper__arrow::after{content:" ";border-width:5px}.custom-theme .el-tooltip__popper[x-placement^=top]{margin-bottom:12px}.custom-theme .el-tooltip__popper[x-placement^=top] .popper__arrow{bottom:-6px;border-top-color:#2d2f33;border-bottom-width:0}.custom-theme .el-tooltip__popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-5px;border-top-color:#2d2f33;border-bottom-width:0}.custom-theme .el-tooltip__popper[x-placement^=bottom]{margin-top:12px}.custom-theme .el-tooltip__popper[x-placement^=bottom] .popper__arrow{top:-6px;border-top-width:0;border-bottom-color:#2d2f33}.custom-theme .el-tooltip__popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-5px;border-top-width:0;border-bottom-color:#2d2f33}.custom-theme .el-tooltip__popper[x-placement^=right]{margin-left:12px}.custom-theme .el-tooltip__popper[x-placement^=right] .popper__arrow{left:-6px;border-right-color:#2d2f33;border-left-width:0}.custom-theme .el-tooltip__popper[x-placement^=right] .popper__arrow::after{bottom:-5px;left:1px;border-right-color:#2d2f33;border-left-width:0}.custom-theme .el-tooltip__popper[x-placement^=left]{margin-right:12px}.custom-theme .el-tooltip__popper[x-placement^=left] .popper__arrow{right:-6px;border-right-width:0;border-left-color:#2d2f33}.custom-theme .el-tooltip__popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-5px;margin-left:-5px;border-right-width:0;border-left-color:#2d2f33}.custom-theme .el-tooltip__popper.is-dark{background:#2d2f33;color:#fff}.custom-theme .el-tooltip__popper.is-light{background:#fff;border:1px solid #2d2f33}.custom-theme .el-tooltip__popper.is-light[x-placement^=top] .popper__arrow{border-top-color:#2d2f33}.custom-theme .el-tooltip__popper.is-light[x-placement^=top] .popper__arrow::after{border-top-color:#fff}.custom-theme .el-tooltip__popper.is-light[x-placement^=bottom] .popper__arrow{border-bottom-color:#2d2f33}.custom-theme .el-tooltip__popper.is-light[x-placement^=bottom] .popper__arrow::after{border-bottom-color:#fff}.custom-theme .el-tooltip__popper.is-light[x-placement^=left] .popper__arrow{border-left-color:#2d2f33}.custom-theme .el-tooltip__popper.is-light[x-placement^=left] .popper__arrow::after{border-left-color:#fff}.custom-theme .el-tooltip__popper.is-light[x-placement^=right] .popper__arrow{border-right-color:#2d2f33}.custom-theme .el-tooltip__popper.is-light[x-placement^=right] .popper__arrow::after{border-right-color:#fff}.custom-theme .v-modal-enter{-webkit-animation:v-modal-in .2s ease;animation:v-modal-in .2s ease}.custom-theme .v-modal-leave{-webkit-animation:v-modal-out .2s ease forwards;animation:v-modal-out .2s ease forwards}@keyframes v-modal-in{0%{opacity:0}}@keyframes v-modal-out{100%{opacity:0}}.custom-theme .v-modal{position:fixed;left:0;top:0;width:100%;height:100%;opacity:.5;background:#000}.custom-theme .el-button{display:inline-block;line-height:1;white-space:nowrap;cursor:pointer;background:#fff;border:1px solid #d8dce5;border-color:#d8dce5;color:#5a5e66;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;-webkit-transition:.1s;transition:.1s;font-weight:500;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;padding:12px 20px;font-size:14px;border-radius:4px}.custom-theme .el-button+.el-button{margin-left:10px}.custom-theme .el-button.is-round{padding:12px 20px}.custom-theme .el-button:focus,.custom-theme .el-button:hover{color:#262729;border-color:#bebebf;background-color:#e9e9ea}.custom-theme .el-button:active{color:#222325;border-color:#222325;outline:0}.custom-theme .el-button::-moz-focus-inner{border:0}.custom-theme .el-button [class*=el-icon-]+span{margin-left:5px}.custom-theme .el-button.is-plain:focus,.custom-theme .el-button.is-plain:hover{background:#fff;border-color:#262729;color:#262729}.custom-theme .el-button.is-plain:active{background:#fff;border-color:#222325;color:#222325;outline:0}.custom-theme .el-button.is-active{color:#222325;border-color:#222325}.custom-theme .el-button.is-disabled,.custom-theme .el-button.is-disabled:focus,.custom-theme .el-button.is-disabled:hover{color:#b4bccc;cursor:not-allowed;background-image:none;background-color:#fff;border-color:#e6ebf5}.custom-theme .el-button.is-disabled.el-button--text{background-color:transparent}.custom-theme .el-button.is-disabled.is-plain,.custom-theme .el-button.is-disabled.is-plain:focus,.custom-theme .el-button.is-disabled.is-plain:hover{background-color:#fff;border-color:#e6ebf5;color:#b4bccc}.custom-theme .el-button.is-loading{position:relative;pointer-events:none}.custom-theme .el-button.is-loading:before{pointer-events:none;content:'';position:absolute;left:-1px;top:-1px;right:-1px;bottom:-1px;border-radius:inherit;background-color:rgba(255,255,255,.35)}.custom-theme .el-button.is-round{border-radius:20px;padding:12px 23px}.custom-theme .el-button--primary{color:#fff;background-color:#262729;border-color:#262729}.custom-theme .el-button--primary:focus,.custom-theme .el-button--primary:hover{background:#515254;border-color:#515254;color:#fff}.custom-theme .el-button--primary:active{background:#222325;border-color:#222325;color:#fff;outline:0}.custom-theme .el-button--primary.is-active{background:#222325;border-color:#222325;color:#fff}.custom-theme .el-button--primary.is-disabled,.custom-theme .el-button--primary.is-disabled:active,.custom-theme .el-button--primary.is-disabled:focus,.custom-theme .el-button--primary.is-disabled:hover{color:#fff;background-color:#939394;border-color:#939394}.custom-theme .el-button--primary.is-plain{color:#262729;background:#e9e9ea;border-color:#a8a9a9}.custom-theme .el-button--primary.is-plain:focus,.custom-theme .el-button--primary.is-plain:hover{background:#262729;border-color:#262729;color:#fff}.custom-theme .el-button--primary.is-plain:active{background:#222325;border-color:#222325;color:#fff;outline:0}.custom-theme .el-button--primary.is-plain.is-disabled,.custom-theme .el-button--primary.is-plain.is-disabled:active,.custom-theme .el-button--primary.is-plain.is-disabled:focus,.custom-theme .el-button--primary.is-plain.is-disabled:hover{color:#7d7d7f;background-color:#e9e9ea;border-color:#d4d4d4}.custom-theme .el-button--success{color:#fff;background-color:#409167;border-color:#409167}.custom-theme .el-button--success:focus,.custom-theme .el-button--success:hover{background:#66a785;border-color:#66a785;color:#fff}.custom-theme .el-button--success:active{background:#3a835d;border-color:#3a835d;color:#fff;outline:0}.custom-theme .el-button--success.is-active{background:#3a835d;border-color:#3a835d;color:#fff}.custom-theme .el-button--success.is-disabled,.custom-theme .el-button--success.is-disabled:active,.custom-theme .el-button--success.is-disabled:focus,.custom-theme .el-button--success.is-disabled:hover{color:#fff;background-color:#a0c8b3;border-color:#a0c8b3}.custom-theme .el-button--success.is-plain{color:#409167;background:#ecf4f0;border-color:#b3d3c2}.custom-theme .el-button--success.is-plain:focus,.custom-theme .el-button--success.is-plain:hover{background:#409167;border-color:#409167;color:#fff}.custom-theme .el-button--success.is-plain:active{background:#3a835d;border-color:#3a835d;color:#fff;outline:0}.custom-theme .el-button--success.is-plain.is-disabled,.custom-theme .el-button--success.is-plain.is-disabled:active,.custom-theme .el-button--success.is-plain.is-disabled:focus,.custom-theme .el-button--success.is-plain.is-disabled:hover{color:#8cbda4;background-color:#ecf4f0;border-color:#d9e9e1}.custom-theme .el-button--warning{color:#fff;background-color:#9da408;border-color:#9da408}.custom-theme .el-button--warning:focus,.custom-theme .el-button--warning:hover{background:#b1b639;border-color:#b1b639;color:#fff}.custom-theme .el-button--warning:active{background:#8d9407;border-color:#8d9407;color:#fff;outline:0}.custom-theme .el-button--warning.is-active{background:#8d9407;border-color:#8d9407;color:#fff}.custom-theme .el-button--warning.is-disabled,.custom-theme .el-button--warning.is-disabled:active,.custom-theme .el-button--warning.is-disabled:focus,.custom-theme .el-button--warning.is-disabled:hover{color:#fff;background-color:#ced284;border-color:#ced284}.custom-theme .el-button--warning.is-plain{color:#9da408;background:#f5f6e6;border-color:#d8db9c}.custom-theme .el-button--warning.is-plain:focus,.custom-theme .el-button--warning.is-plain:hover{background:#9da408;border-color:#9da408;color:#fff}.custom-theme .el-button--warning.is-plain:active{background:#8d9407;border-color:#8d9407;color:#fff;outline:0}.custom-theme .el-button--warning.is-plain.is-disabled,.custom-theme .el-button--warning.is-plain.is-disabled:active,.custom-theme .el-button--warning.is-plain.is-disabled:focus,.custom-theme .el-button--warning.is-plain.is-disabled:hover{color:#c4c86b;background-color:#f5f6e6;border-color:#ebedce}.custom-theme .el-button--danger{color:#fff;background-color:#b3450e;border-color:#b3450e}.custom-theme .el-button--danger:focus,.custom-theme .el-button--danger:hover{background:#c26a3e;border-color:#c26a3e;color:#fff}.custom-theme .el-button--danger:active{background:#a13e0d;border-color:#a13e0d;color:#fff;outline:0}.custom-theme .el-button--danger.is-active{background:#a13e0d;border-color:#a13e0d;color:#fff}.custom-theme .el-button--danger.is-disabled,.custom-theme .el-button--danger.is-disabled:active,.custom-theme .el-button--danger.is-disabled:focus,.custom-theme .el-button--danger.is-disabled:hover{color:#fff;background-color:#d9a287;border-color:#d9a287}.custom-theme .el-button--danger.is-plain{color:#b3450e;background:#f7ece7;border-color:#e1b59f}.custom-theme .el-button--danger.is-plain:focus,.custom-theme .el-button--danger.is-plain:hover{background:#b3450e;border-color:#b3450e;color:#fff}.custom-theme .el-button--danger.is-plain:active{background:#a13e0d;border-color:#a13e0d;color:#fff;outline:0}.custom-theme .el-button--danger.is-plain.is-disabled,.custom-theme .el-button--danger.is-plain.is-disabled:active,.custom-theme .el-button--danger.is-plain.is-disabled:focus,.custom-theme .el-button--danger.is-plain.is-disabled:hover{color:#d18f6e;background-color:#f7ece7;border-color:#f0dacf}.custom-theme .el-button--info{color:#fff;background-color:#0a76a4;border-color:#0a76a4}.custom-theme .el-button--info:focus,.custom-theme .el-button--info:hover{background:#3b91b6;border-color:#3b91b6;color:#fff}.custom-theme .el-button--info:active{background:#096a94;border-color:#096a94;color:#fff;outline:0}.custom-theme .el-button--info.is-active{background:#096a94;border-color:#096a94;color:#fff}.custom-theme .el-button--info.is-disabled,.custom-theme .el-button--info.is-disabled:active,.custom-theme .el-button--info.is-disabled:focus,.custom-theme .el-button--info.is-disabled:hover{color:#fff;background-color:#85bbd2;border-color:#85bbd2}.custom-theme .el-button--info.is-plain{color:#0a76a4;background:#e7f1f6;border-color:#9dc8db}.custom-theme .el-button--info.is-plain:focus,.custom-theme .el-button--info.is-plain:hover{background:#0a76a4;border-color:#0a76a4;color:#fff}.custom-theme .el-button--info.is-plain:active{background:#096a94;border-color:#096a94;color:#fff;outline:0}.custom-theme .el-button--info.is-plain.is-disabled,.custom-theme .el-button--info.is-plain.is-disabled:active,.custom-theme .el-button--info.is-plain.is-disabled:focus,.custom-theme .el-button--info.is-plain.is-disabled:hover{color:#6cadc8;background-color:#e7f1f6;border-color:#cee4ed}.custom-theme .el-button--medium{padding:10px 20px;font-size:14px;border-radius:4px}.custom-theme .el-button--medium.is-round{padding:10px 20px}.custom-theme .el-button--small{padding:9px 15px;font-size:12px;border-radius:3px}.custom-theme .el-button--small.is-round{padding:9px 15px}.custom-theme .el-button--mini{padding:7px 15px;font-size:12px;border-radius:3px}.custom-theme .el-button--mini.is-round{padding:7px 15px}.custom-theme .el-button--text{border:none;color:#262729;background:0 0;padding-left:0;padding-right:0}.custom-theme .el-button--text:focus,.custom-theme .el-button--text:hover{color:#515254;border-color:transparent;background-color:transparent}.custom-theme .el-button--text:active{color:#222325;border-color:transparent;background-color:transparent}.custom-theme .el-button-group{display:inline-block;vertical-align:middle}.custom-theme .el-button-group::after,.custom-theme .el-button-group::before{display:table;content:""}.custom-theme .el-button-group::after{clear:both}.custom-theme .el-button-group .el-button{float:left;position:relative}.custom-theme .el-button-group .el-button+.el-button{margin-left:0}.custom-theme .el-button-group .el-button:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-button-group .el-button:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-button-group .el-button:not(:first-child):not(:last-child){border-radius:0}.custom-theme .el-button-group .el-button:not(:last-child){margin-right:-1px}.custom-theme .el-button-group .el-button:active,.custom-theme .el-button-group .el-button:focus,.custom-theme .el-button-group .el-button:hover{z-index:1}.custom-theme .el-button-group .el-button.is-active{z-index:1}.custom-theme .el-button-group .el-button--primary:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--primary:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--primary:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--success:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--success:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--success:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--warning:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--warning:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--warning:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--danger:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--danger:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--danger:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--info:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--info:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--info:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-input{position:relative;font-size:14px;display:inline-block;width:100%}.custom-theme .el-input::-webkit-scrollbar{z-index:11;width:6px}.custom-theme .el-input::-webkit-scrollbar:horizontal{height:6px}.custom-theme .el-input::-webkit-scrollbar-thumb{border-radius:5px;width:6px;background:#b4bccc}.custom-theme .el-input::-webkit-scrollbar-corner{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track-piece{background:#fff;width:6px}.custom-theme .el-input__inner{-webkit-appearance:none;background-color:#fff;background-image:none;border-radius:4px;border:1px solid #d8dce5;-webkit-box-sizing:border-box;box-sizing:border-box;color:#5a5e66;display:inline-block;font-size:inherit;height:40px;line-height:1;outline:0;padding:0 15px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1);width:100%}.custom-theme .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input__inner:hover{border-color:#b4bccc}.custom-theme .el-input__inner:focus{outline:0;border-color:#262729}.custom-theme .el-input__suffix{position:absolute;height:100%;right:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s;pointer-events:none}.custom-theme .el-input__suffix-inner{pointer-events:all}.custom-theme .el-input__prefix{position:absolute;height:100%;left:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s}.custom-theme .el-input__icon{height:100%;width:25px;text-align:center;-webkit-transition:all .3s;transition:all .3s;line-height:40px}.custom-theme .el-input__icon:after{content:'';height:100%;width:0;display:inline-block;vertical-align:middle}.custom-theme .el-input__validateIcon{pointer-events:none}.custom-theme .el-input.is-active .el-input__inner{outline:0;border-color:#262729}.custom-theme .el-input.is-disabled .el-input__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-input.is-disabled .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__icon{cursor:not-allowed}.custom-theme .el-input--suffix .el-input__inner{padding-right:30px}.custom-theme .el-input--prefix .el-input__inner{padding-left:30px}.custom-theme .el-input--medium{font-size:14px}.custom-theme .el-input--medium .el-input__inner{height:36px}.custom-theme .el-input--medium .el-input__icon{line-height:36px}.custom-theme .el-input--small{font-size:13px}.custom-theme .el-input--small .el-input__inner{height:32px}.custom-theme .el-input--small .el-input__icon{line-height:32px}.custom-theme .el-input--mini{font-size:12px}.custom-theme .el-input--mini .el-input__inner{height:28px}.custom-theme .el-input--mini .el-input__icon{line-height:28px}.custom-theme .el-input-group{line-height:normal;display:inline-table;width:100%;border-collapse:separate}.custom-theme .el-input-group>.el-input__inner{vertical-align:middle;display:table-cell}.custom-theme .el-input-group__append,.custom-theme .el-input-group__prepend{background-color:#f5f7fa;color:#0a76a4;vertical-align:middle;display:table-cell;position:relative;border:1px solid #d8dce5;border-radius:4px;padding:0 20px;width:1px;white-space:nowrap}.custom-theme .el-input-group__append:focus,.custom-theme .el-input-group__prepend:focus{outline:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-select,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-select{display:inline-block;margin:-20px}.custom-theme .el-input-group__append button.el-button,.custom-theme .el-input-group__append div.el-select .el-input__inner,.custom-theme .el-input-group__append div.el-select:hover .el-input__inner,.custom-theme .el-input-group__prepend button.el-button,.custom-theme .el-input-group__prepend div.el-select .el-input__inner,.custom-theme .el-input-group__prepend div.el-select:hover .el-input__inner{border-color:transparent;background-color:transparent;color:inherit;border-top:0;border-bottom:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-input,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-input{font-size:inherit}.custom-theme .el-input-group__prepend{border-right:0;border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-input-group__append{border-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--prepend .el-input__inner{border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--append .el-input__inner{border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-textarea{display:inline-block;width:100%;vertical-align:bottom}.custom-theme .el-textarea__inner{display:block;resize:vertical;padding:5px 15px;line-height:1.5;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;font-size:14px;color:#5a5e66;background-color:#fff;background-image:none;border:1px solid #d8dce5;border-radius:4px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}.custom-theme .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:hover{border-color:#b4bccc}.custom-theme .el-textarea__inner:focus{outline:0;border-color:#262729}.custom-theme .el-textarea.is-disabled .el-textarea__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-message-box{display:inline-block;width:420px;padding-bottom:10px;vertical-align:middle;background-color:#fff;border-radius:4px;border:1px solid #e6ebf5;font-size:18px;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);text-align:left;overflow:hidden;-webkit-backface-visibility:hidden;backface-visibility:hidden}.custom-theme .el-message-box__wrapper{position:fixed;top:0;bottom:0;left:0;right:0;text-align:center}.custom-theme .el-message-box__wrapper::after{content:"";display:inline-block;height:100%;width:0;vertical-align:middle}.custom-theme .el-message-box__header{position:relative;padding:15px;padding-bottom:10px}.custom-theme .el-message-box__title{padding-left:0;margin-bottom:0;font-size:18px;line-height:1;color:#2d2f33}.custom-theme .el-message-box__headerbtn{position:absolute;top:15px;right:15px;padding:0;border:none;outline:0;background:0 0;font-size:16px;cursor:pointer}.custom-theme .el-message-box__headerbtn .el-message-box__close{color:#0a76a4}.custom-theme .el-message-box__headerbtn:focus .el-message-box__close,.custom-theme .el-message-box__headerbtn:hover .el-message-box__close{color:#262729}.custom-theme .el-message-box__content{position:relative;padding:10px 15px;color:#5a5e66;font-size:14px}.custom-theme .el-message-box__input{padding-top:15px}.custom-theme .el-message-box__input input.invalid{border-color:#b3450e}.custom-theme .el-message-box__input input.invalid:focus{border-color:#b3450e}.custom-theme .el-message-box__status{position:absolute;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);font-size:24px!important}.custom-theme .el-message-box__status::before{padding-left:1px}.custom-theme .el-message-box__status+.el-message-box__message{padding-left:36px;padding-right:12px}.custom-theme .el-message-box__status.el-icon-success{color:#409167}.custom-theme .el-message-box__status.el-icon-info{color:#0a76a4}.custom-theme .el-message-box__status.el-icon-warning{color:#9da408}.custom-theme .el-message-box__status.el-icon-error{color:#b3450e}.custom-theme .el-message-box__message{margin:0}.custom-theme .el-message-box__message p{margin:0;line-height:24px}.custom-theme .el-message-box__errormsg{color:#b3450e;font-size:12px;min-height:18px;margin-top:2px}.custom-theme .el-message-box__btns{padding:5px 15px 0;text-align:right}.custom-theme .el-message-box__btns button:nth-child(2){margin-left:10px}.custom-theme .el-message-box__btns-reverse{-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}.custom-theme .el-message-box--center{padding-bottom:30px}.custom-theme .el-message-box--center .el-message-box__header{padding-top:30px}.custom-theme .el-message-box--center .el-message-box__title{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.custom-theme .el-message-box--center .el-message-box__status{position:relative;top:auto;padding-right:5px;text-align:center;-webkit-transform:translateY(-1px);transform:translateY(-1px)}.custom-theme .el-message-box--center .el-message-box__message{margin-left:0}.custom-theme .el-message-box--center .el-message-box__btns,.custom-theme .el-message-box--center .el-message-box__content{text-align:center}.custom-theme .el-message-box--center .el-message-box__content{padding-left:27px;padding-right:27px}.custom-theme .msgbox-fade-enter-active{-webkit-animation:msgbox-fade-in .3s;animation:msgbox-fade-in .3s}.custom-theme .msgbox-fade-leave-active{-webkit-animation:msgbox-fade-out .3s;animation:msgbox-fade-out .3s}@-webkit-keyframes msgbox-fade-in{0%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}}@keyframes msgbox-fade-in{0%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}}@-webkit-keyframes msgbox-fade-out{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}100%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}}@keyframes msgbox-fade-out{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}100%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}}.custom-theme .el-breadcrumb{font-size:14px;line-height:1}.custom-theme .el-breadcrumb::after,.custom-theme .el-breadcrumb::before{display:table;content:""}.custom-theme .el-breadcrumb::after{clear:both}.custom-theme .el-breadcrumb__separator{margin:0 9px;font-weight:700;color:#b4bccc}.custom-theme .el-breadcrumb__separator[class*=icon]{margin:0 6px;font-weight:400}.custom-theme .el-breadcrumb__item{float:left}.custom-theme .el-breadcrumb__inner,.custom-theme .el-breadcrumb__inner a{font-weight:700;-webkit-transition:color .2s cubic-bezier(.645,.045,.355,1);transition:color .2s cubic-bezier(.645,.045,.355,1);color:#2d2f33}.custom-theme .el-breadcrumb__inner a:hover,.custom-theme .el-breadcrumb__inner:hover{color:#262729;cursor:pointer}.custom-theme .el-breadcrumb__item:last-child .el-breadcrumb__inner,.custom-theme .el-breadcrumb__item:last-child .el-breadcrumb__inner a,.custom-theme .el-breadcrumb__item:last-child .el-breadcrumb__inner a:hover,.custom-theme .el-breadcrumb__item:last-child .el-breadcrumb__inner:hover{font-weight:400;color:#5a5e66;cursor:text}.custom-theme .el-breadcrumb__item:last-child .el-breadcrumb__separator{display:none}.custom-theme .el-form--label-left .el-form-item__label{text-align:left}.custom-theme .el-form--label-top .el-form-item__label{float:none;display:inline-block;text-align:left;padding:0 0 10px 0}.custom-theme .el-form--inline .el-form-item{display:inline-block;margin-right:10px;vertical-align:top}.custom-theme .el-form--inline .el-form-item__label{float:none;display:inline-block}.custom-theme .el-form--inline .el-form-item__content{display:inline-block;vertical-align:top}.custom-theme .el-form--inline.el-form--label-top .el-form-item__content{display:block}.custom-theme .el-form-item{margin-bottom:22px}.custom-theme .el-form-item::after,.custom-theme .el-form-item::before{display:table;content:""}.custom-theme .el-form-item::after{clear:both}.custom-theme .el-form-item .el-form-item{margin-bottom:0}.custom-theme .el-form-item .el-input__validateIcon{display:none}.custom-theme .el-form-item--medium .el-form-item__label{line-height:36px}.custom-theme .el-form-item--medium .el-form-item__content{line-height:36px}.custom-theme .el-form-item--small .el-form-item__label{line-height:32px}.custom-theme .el-form-item--small .el-form-item__content{line-height:32px}.custom-theme .el-form-item--small.el-form-item{margin-bottom:18px}.custom-theme .el-form-item--small .el-form-item__error{padding-top:2px}.custom-theme .el-form-item--mini .el-form-item__label{line-height:28px}.custom-theme .el-form-item--mini .el-form-item__content{line-height:28px}.custom-theme .el-form-item--mini.el-form-item{margin-bottom:18px}.custom-theme .el-form-item--mini .el-form-item__error{padding-top:1px}.custom-theme .el-form-item__label{text-align:right;vertical-align:middle;float:left;font-size:14px;color:#5a5e66;line-height:40px;padding:0 12px 0 0;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-form-item__content{line-height:40px;position:relative;font-size:14px}.custom-theme .el-form-item__content::after,.custom-theme .el-form-item__content::before{display:table;content:""}.custom-theme .el-form-item__content::after{clear:both}.custom-theme .el-form-item__error{color:#b3450e;font-size:12px;line-height:1;padding-top:4px;position:absolute;top:100%;left:0}.custom-theme .el-form-item__error--inline{position:relative;top:auto;left:auto;display:inline-block;margin-left:10px}.custom-theme .el-form-item.is-required .el-form-item__label:before{content:'*';color:#b3450e;margin-right:4px}.custom-theme .el-form-item.is-error .el-input__inner,.custom-theme .el-form-item.is-error .el-input__inner:focus,.custom-theme .el-form-item.is-error .el-textarea__inner,.custom-theme .el-form-item.is-error .el-textarea__inner:focus{border-color:#b3450e}.custom-theme .el-form-item.is-error .el-input-group__append .el-input__inner,.custom-theme .el-form-item.is-error .el-input-group__prepend .el-input__inner{border-color:transparent}.custom-theme .el-form-item.is-error .el-input__validateIcon{color:#b3450e}.custom-theme .el-form-item.is-success .el-input__inner,.custom-theme .el-form-item.is-success .el-input__inner:focus,.custom-theme .el-form-item.is-success .el-textarea__inner,.custom-theme .el-form-item.is-success .el-textarea__inner:focus{border-color:#409167}.custom-theme .el-form-item.is-success .el-input-group__append .el-input__inner,.custom-theme .el-form-item.is-success .el-input-group__prepend .el-input__inner{border-color:transparent}.custom-theme .el-form-item.is-success .el-input__validateIcon{color:#409167}.custom-theme .el-form-item--feedback .el-input__validateIcon{display:inline-block}.custom-theme .el-tabs__header{padding:0;position:relative;margin:0 0 15px}.custom-theme .el-tabs__active-bar{position:absolute;bottom:0;left:0;height:2px;background-color:#262729;z-index:1;-webkit-transition:-webkit-transform .3s cubic-bezier(.645,.045,.355,1);transition:-webkit-transform .3s cubic-bezier(.645,.045,.355,1);transition:transform .3s cubic-bezier(.645,.045,.355,1);transition:transform .3s cubic-bezier(.645,.045,.355,1),-webkit-transform .3s cubic-bezier(.645,.045,.355,1);list-style:none}.custom-theme .el-tabs__new-tab{float:right;border:1px solid #d3dce6;height:18px;width:18px;line-height:18px;margin:12px 0 9px 10px;border-radius:3px;text-align:center;font-size:12px;color:#d3dce6;cursor:pointer;-webkit-transition:all .15s;transition:all .15s}.custom-theme .el-tabs__new-tab .el-icon-plus{-webkit-transform:scale(.8,.8);transform:scale(.8,.8)}.custom-theme .el-tabs__new-tab:hover{color:#262729}.custom-theme .el-tabs__nav-wrap{overflow:hidden;margin-bottom:-1px;position:relative}.custom-theme .el-tabs__nav-wrap::after{content:"";position:absolute;left:0;bottom:0;width:100%;height:2px;background-color:#dfe4ed;z-index:1}.custom-theme .el-tabs__nav-wrap.is-scrollable{padding:0 20px;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-tabs__nav-scroll{overflow:hidden}.custom-theme .el-tabs__nav-next,.custom-theme .el-tabs__nav-prev{position:absolute;cursor:pointer;line-height:44px;font-size:12px;color:#878d99}.custom-theme .el-tabs__nav-next{right:0}.custom-theme .el-tabs__nav-prev{left:0}.custom-theme .el-tabs__nav{white-space:nowrap;position:relative;-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;float:left;z-index:2}.custom-theme .el-tabs__item{padding:0 20px;height:40px;-webkit-box-sizing:border-box;box-sizing:border-box;line-height:40px;display:inline-block;list-style:none;font-size:14px;font-weight:500;color:#2d2f33;position:relative}.custom-theme .el-tabs__item:focus,.custom-theme .el-tabs__item:focus:active{outline:0}.custom-theme .el-tabs__item .el-icon-close{border-radius:50%;text-align:center;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);margin-left:5px}.custom-theme .el-tabs__item .el-icon-close:before{-webkit-transform:scale(.9);transform:scale(.9);display:inline-block}.custom-theme .el-tabs__item .el-icon-close:hover{background-color:#b4bccc;color:#fff}.custom-theme .el-tabs__item.is-active{color:#262729}.custom-theme .el-tabs__item:hover{color:#262729;cursor:pointer}.custom-theme .el-tabs__item.is-disabled{color:#b4bccc;cursor:default}.custom-theme .el-tabs__content{overflow:hidden;position:relative}.custom-theme .el-tabs--card>.el-tabs__header{border-bottom:1px solid #dfe4ed}.custom-theme .el-tabs--card>.el-tabs__header .el-tabs__nav-wrap::after{content:none}.custom-theme .el-tabs--card>.el-tabs__header .el-tabs__nav{border:1px solid #dfe4ed;border-bottom:none;border-radius:4px 4px 0 0}.custom-theme .el-tabs--card>.el-tabs__header .el-tabs__active-bar{display:none}.custom-theme .el-tabs--card>.el-tabs__header .el-tabs__item .el-icon-close{position:relative;font-size:12px;width:0;height:14px;vertical-align:middle;line-height:15px;overflow:hidden;top:-1px;right:-2px;-webkit-transform-origin:100% 50%;transform-origin:100% 50%}.custom-theme .el-tabs--card>.el-tabs__header .el-tabs__item{border-bottom:1px solid transparent;border-left:1px solid #dfe4ed;-webkit-transition:color .3s cubic-bezier(.645,.045,.355,1),padding .3s cubic-bezier(.645,.045,.355,1);transition:color .3s cubic-bezier(.645,.045,.355,1),padding .3s cubic-bezier(.645,.045,.355,1)}.custom-theme .el-tabs--card>.el-tabs__header .el-tabs__item:first-child{border-left:none}.custom-theme .el-tabs--card>.el-tabs__header .el-tabs__item.is-closable:hover{padding-left:13px;padding-right:13px}.custom-theme .el-tabs--card>.el-tabs__header .el-tabs__item.is-closable:hover .el-icon-close{width:14px}.custom-theme .el-tabs--card>.el-tabs__header .el-tabs__item.is-active{border-bottom-color:#fff}.custom-theme .el-tabs--card>.el-tabs__header .el-tabs__item.is-active.is-closable{padding-left:20px;padding-right:20px}.custom-theme .el-tabs--card>.el-tabs__header .el-tabs__item.is-active.is-closable .el-icon-close{width:14px}.custom-theme .el-tabs--border-card{background:#fff;border:1px solid #d8dce5;-webkit-box-shadow:0 2px 4px 0 rgba(0,0,0,.12),0 0 6px 0 rgba(0,0,0,.04);box-shadow:0 2px 4px 0 rgba(0,0,0,.12),0 0 6px 0 rgba(0,0,0,.04)}.custom-theme .el-tabs--border-card>.el-tabs__content{padding:15px}.custom-theme .el-tabs--border-card>.el-tabs__header{background-color:#f5f7fa;border-bottom:1px solid #dfe4ed;margin:0}.custom-theme .el-tabs--border-card>.el-tabs__header .el-tabs__nav-wrap::after{content:none}.custom-theme .el-tabs--border-card>.el-tabs__header .el-tabs__item{-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);border:1px solid transparent;margin:-1px -1px 0;color:#878d99}.custom-theme .el-tabs--border-card>.el-tabs__header .el-tabs__item.is-active{color:#262729;background-color:#fff;border-right-color:#d8dce5;border-left-color:#d8dce5}.custom-theme .el-tabs--border-card>.el-tabs__header .el-tabs__item:hover{color:#262729}.custom-theme .el-tabs--bottom:not(.el-tabs--border-card):not(.el-tabs--card) .el-tabs__item:nth-child(2),.custom-theme .el-tabs--top:not(.el-tabs--border-card):not(.el-tabs--card) .el-tabs__item:nth-child(2){padding-left:0}.custom-theme .el-tabs--bottom .el-tabs__header{margin-bottom:0;margin-top:10px}.custom-theme .el-tabs--bottom.el-tabs--border-card .el-tabs__header{border-bottom:0;border-top:1px solid #d8dce5}.custom-theme .el-tabs--bottom.el-tabs--border-card .el-tabs__nav-wrap{margin-top:-1px;margin-bottom:0}.custom-theme .el-tabs--bottom.el-tabs--border-card .el-tabs__item{border:1px solid transparent;margin:0 -1px -1px -1px}.custom-theme .el-tabs--left,.custom-theme .el-tabs--right{overflow:hidden}.custom-theme .el-tabs--left .el-tabs__header,.custom-theme .el-tabs--left .el-tabs__nav-scroll,.custom-theme .el-tabs--left .el-tabs__nav-wrap,.custom-theme .el-tabs--right .el-tabs__header,.custom-theme .el-tabs--right .el-tabs__nav-scroll,.custom-theme .el-tabs--right .el-tabs__nav-wrap{height:100%}.custom-theme .el-tabs--left .el-tabs__active-bar,.custom-theme .el-tabs--right .el-tabs__active-bar{top:0;bottom:auto;width:2px;height:auto}.custom-theme .el-tabs--left .el-tabs__nav-wrap,.custom-theme .el-tabs--right .el-tabs__nav-wrap{margin-bottom:0}.custom-theme .el-tabs--left .el-tabs__nav-wrap.is-scrollable,.custom-theme .el-tabs--right .el-tabs__nav-wrap.is-scrollable{padding:30px 0}.custom-theme .el-tabs--left .el-tabs__nav-wrap::after,.custom-theme .el-tabs--right .el-tabs__nav-wrap::after{height:100%;width:2px;bottom:auto;top:0}.custom-theme .el-tabs--left .el-tabs__nav,.custom-theme .el-tabs--right .el-tabs__nav{float:none}.custom-theme .el-tabs--left .el-tabs__item,.custom-theme .el-tabs--right .el-tabs__item{display:block}.custom-theme .el-tabs--left .el-tabs__nav-next,.custom-theme .el-tabs--left .el-tabs__nav-prev,.custom-theme .el-tabs--right .el-tabs__nav-next,.custom-theme .el-tabs--right .el-tabs__nav-prev{height:30px;line-height:30px;width:100%;text-align:center;cursor:pointer}.custom-theme .el-tabs--left .el-tabs__nav-next i,.custom-theme .el-tabs--left .el-tabs__nav-prev i,.custom-theme .el-tabs--right .el-tabs__nav-next i,.custom-theme .el-tabs--right .el-tabs__nav-prev i{-webkit-transform:rotateZ(90deg);transform:rotateZ(90deg)}.custom-theme .el-tabs--left .el-tabs__nav-prev,.custom-theme .el-tabs--right .el-tabs__nav-prev{left:auto;top:0}.custom-theme .el-tabs--left .el-tabs__nav-next,.custom-theme .el-tabs--right .el-tabs__nav-next{right:auto;bottom:0}.custom-theme .el-tabs--left .el-tabs__header{float:left;margin-bottom:0;margin-right:10px}.custom-theme .el-tabs--left .el-tabs__nav-wrap{margin-right:-1px}.custom-theme .el-tabs--left .el-tabs__nav-wrap::after{left:auto;right:0}.custom-theme .el-tabs--left .el-tabs__active-bar{right:0;left:auto}.custom-theme .el-tabs--left .el-tabs__item{text-align:right}.custom-theme .el-tabs--left.el-tabs--card .el-tabs__active-bar{display:none}.custom-theme .el-tabs--left.el-tabs--card .el-tabs__item{border-left:none;border-right:1px solid #dfe4ed;border-bottom:none;border-top:1px solid #dfe4ed}.custom-theme .el-tabs--left.el-tabs--card .el-tabs__item:first-child{border-right:1px solid #dfe4ed;border-top:none}.custom-theme .el-tabs--left.el-tabs--card .el-tabs__item.is-active{border:1px solid #dfe4ed;border-right-color:#fff;border-left:none;border-bottom:none}.custom-theme .el-tabs--left.el-tabs--card .el-tabs__item.is-active:first-child{border-top:none}.custom-theme .el-tabs--left.el-tabs--card .el-tabs__item.is-active:last-child{border-bottom:none}.custom-theme .el-tabs--left.el-tabs--card .el-tabs__nav{border-radius:4px 0 0 4px;border-bottom:1px solid #dfe4ed;border-right:none}.custom-theme .el-tabs--left.el-tabs--card .el-tabs__new-tab{float:none}.custom-theme .el-tabs--left.el-tabs--border-card .el-tabs__header{border-right:1px solid #dfe4ed}.custom-theme .el-tabs--left.el-tabs--border-card .el-tabs__item{border:1px solid transparent;margin:-1px 0 -1px -1px}.custom-theme .el-tabs--left.el-tabs--border-card .el-tabs__item.is-active{border-color:transparent;border-top-color:#d1dbe5;border-bottom-color:#d1dbe5}.custom-theme .el-tabs--right .el-tabs__header{float:right;margin-bottom:0;margin-left:10px}.custom-theme .el-tabs--right .el-tabs__nav-wrap{margin-left:-1px}.custom-theme .el-tabs--right .el-tabs__nav-wrap::after{left:0;right:auto}.custom-theme .el-tabs--right .el-tabs__active-bar{left:0}.custom-theme .el-tabs--right.el-tabs--card .el-tabs__active-bar{display:none}.custom-theme .el-tabs--right.el-tabs--card .el-tabs__item{border-bottom:none;border-top:1px solid #dfe4ed}.custom-theme .el-tabs--right.el-tabs--card .el-tabs__item:first-child{border-left:1px solid #dfe4ed;border-top:none}.custom-theme .el-tabs--right.el-tabs--card .el-tabs__item.is-active{border:1px solid #dfe4ed;border-left-color:#fff;border-right:none;border-bottom:none}.custom-theme .el-tabs--right.el-tabs--card .el-tabs__item.is-active:first-child{border-top:none}.custom-theme .el-tabs--right.el-tabs--card .el-tabs__item.is-active:last-child{border-bottom:none}.custom-theme .el-tabs--right.el-tabs--card .el-tabs__nav{border-radius:0 4px 4px 0;border-bottom:1px solid #dfe4ed;border-left:none}.custom-theme .el-tabs--right.el-tabs--border-card .el-tabs__header{border-left:1px solid #dfe4ed}.custom-theme .el-tabs--right.el-tabs--border-card .el-tabs__item{border:1px solid transparent;margin:-1px -1px -1px 0}.custom-theme .el-tabs--right.el-tabs--border-card .el-tabs__item.is-active{border-color:transparent;border-top-color:#d1dbe5;border-bottom-color:#d1dbe5}.custom-theme .slideInLeft-transition,.custom-theme .slideInRight-transition{display:inline-block}.custom-theme .slideInRight-enter{-webkit-animation:slideInRight-enter .3s;animation:slideInRight-enter .3s}.custom-theme .slideInRight-leave{position:absolute;left:0;right:0;-webkit-animation:slideInRight-leave .3s;animation:slideInRight-leave .3s}.custom-theme .slideInLeft-enter{-webkit-animation:slideInLeft-enter .3s;animation:slideInLeft-enter .3s}.custom-theme .slideInLeft-leave{position:absolute;left:0;right:0;-webkit-animation:slideInLeft-leave .3s;animation:slideInLeft-leave .3s}@-webkit-keyframes slideInRight-enter{0%{opacity:0;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(100%);transform:translateX(100%)}to{opacity:1;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes slideInRight-enter{0%{opacity:0;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(100%);transform:translateX(100%)}to{opacity:1;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0)}}@-webkit-keyframes slideInRight-leave{0%{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0);opacity:1}100%{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(100%);transform:translateX(100%);opacity:0}}@keyframes slideInRight-leave{0%{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0);opacity:1}100%{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(100%);transform:translateX(100%);opacity:0}}@-webkit-keyframes slideInLeft-enter{0%{opacity:0;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(-100%);transform:translateX(-100%)}to{opacity:1;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes slideInLeft-enter{0%{opacity:0;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(-100%);transform:translateX(-100%)}to{opacity:1;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0)}}@-webkit-keyframes slideInLeft-leave{0%{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0);opacity:1}100%{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(-100%);transform:translateX(-100%);opacity:0}}@keyframes slideInLeft-leave{0%{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0);opacity:1}100%{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(-100%);transform:translateX(-100%);opacity:0}}.custom-theme .el-tag{background-color:rgba(38,39,41,.1);display:inline-block;padding:0 10px;height:32px;line-height:30px;font-size:12px;color:#262729;border-radius:4px;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid rgba(38,39,41,.2);white-space:nowrap}.custom-theme .el-tag .el-icon-close{border-radius:50%;text-align:center;position:relative;cursor:pointer;font-size:12px;height:18px;width:18px;line-height:18px;vertical-align:middle;top:-1px;right:-5px;color:#262729}.custom-theme .el-tag .el-icon-close::before{display:block}.custom-theme .el-tag .el-icon-close:hover{background-color:#262729;color:#fff}.custom-theme .el-tag--info{background-color:rgba(10,118,164,.1);border-color:rgba(10,118,164,.2);color:#0a76a4}.custom-theme .el-tag--info.is-hit{border-color:#0a76a4}.custom-theme .el-tag--info .el-tag__close{color:#0a76a4}.custom-theme .el-tag--info .el-tag__close:hover{background-color:#0a76a4;color:#fff}.custom-theme .el-tag--success{background-color:rgba(64,145,103,.1);border-color:rgba(64,145,103,.2);color:#409167}.custom-theme .el-tag--success.is-hit{border-color:#409167}.custom-theme .el-tag--success .el-tag__close{color:#409167}.custom-theme .el-tag--success .el-tag__close:hover{background-color:#409167;color:#fff}.custom-theme .el-tag--warning{background-color:rgba(157,164,8,.1);border-color:rgba(157,164,8,.2);color:#9da408}.custom-theme .el-tag--warning.is-hit{border-color:#9da408}.custom-theme .el-tag--warning .el-tag__close{color:#9da408}.custom-theme .el-tag--warning .el-tag__close:hover{background-color:#9da408;color:#fff}.custom-theme .el-tag--danger{background-color:rgba(179,69,14,.1);border-color:rgba(179,69,14,.2);color:#b3450e}.custom-theme .el-tag--danger.is-hit{border-color:#b3450e}.custom-theme .el-tag--danger .el-tag__close{color:#b3450e}.custom-theme .el-tag--danger .el-tag__close:hover{background-color:#b3450e;color:#fff}.custom-theme .el-tag--medium{height:28px;line-height:26px}.custom-theme .el-tag--medium .el-icon-close{-webkit-transform:scale(.8);transform:scale(.8)}.custom-theme .el-tag--small{height:24px;padding:0 8px;line-height:22px}.custom-theme .el-tag--small .el-icon-close{-webkit-transform:scale(.8);transform:scale(.8)}.custom-theme .el-tag--mini{height:20px;padding:0 5px;line-height:19px}.custom-theme .el-tag--mini .el-icon-close{margin-left:-3px;-webkit-transform:scale(.7);transform:scale(.7)}.custom-theme .el-tree{cursor:default;background:#fff;color:#5a5e66}.custom-theme .el-tree__empty-block{position:relative;min-height:60px;text-align:center;width:100%;height:100%}.custom-theme .el-tree__empty-text{position:absolute;left:50%;top:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);color:#623615}.custom-theme .el-tree-node{white-space:nowrap}.custom-theme .el-tree-node__content{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:26px;cursor:pointer}.custom-theme .el-tree-node__content>.el-tree-node__expand-icon{padding:6px}.custom-theme .el-tree-node__content>.el-checkbox{margin-right:8px}.custom-theme .el-tree-node__content:hover{background-color:#f5f7fa}.custom-theme .el-tree-node__expand-icon{cursor:pointer;color:#b4bccc;font-size:12px;-webkit-transform:rotate(0);transform:rotate(0);-webkit-transition:-webkit-transform .3s ease-in-out;transition:-webkit-transform .3s ease-in-out;transition:transform .3s ease-in-out;transition:transform .3s ease-in-out,-webkit-transform .3s ease-in-out}.custom-theme .el-tree-node__expand-icon.expanded{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.custom-theme .el-tree-node__expand-icon.is-leaf{color:transparent;cursor:default}.custom-theme .el-tree-node__label{font-size:14px}.custom-theme .el-tree-node__loading-icon{margin-right:8px;font-size:14px;color:#b4bccc}.custom-theme .el-tree-node>.el-tree-node__children{overflow:hidden;background-color:transparent}.custom-theme .el-tree-node.is-expanded>.el-tree-node__children{display:block}.custom-theme .el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content{background-color:#eee}.custom-theme .el-alert{width:100%;padding:8px 16px;margin:0;-webkit-box-sizing:border-box;box-sizing:border-box;border-radius:4px;position:relative;background-color:#fff;overflow:hidden;opacity:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-transition:opacity .2s;transition:opacity .2s}.custom-theme .el-alert.is-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.custom-theme .el-alert--success{background-color:#ecf4f0;color:#409167}.custom-theme .el-alert--success .el-alert__description{color:#409167}.custom-theme .el-alert--info{background-color:#e7f1f6;color:#0a76a4}.custom-theme .el-alert--info .el-alert__description{color:#0a76a4}.custom-theme .el-alert--warning{background-color:#f5f6e6;color:#9da408}.custom-theme .el-alert--warning .el-alert__description{color:#9da408}.custom-theme .el-alert--error{background-color:#f7ece7;color:#b3450e}.custom-theme .el-alert--error .el-alert__description{color:#b3450e}.custom-theme .el-alert__content{display:table-cell;padding:0 8px}.custom-theme .el-alert__icon{font-size:16px;width:16px}.custom-theme .el-alert__icon.is-big{font-size:28px;width:28px}.custom-theme .el-alert__title{font-size:13px;line-height:18px}.custom-theme .el-alert__title.is-bold{font-weight:700}.custom-theme .el-alert .el-alert__description{font-size:12px;margin:5px 0 0 0}.custom-theme .el-alert__closebtn{font-size:12px;color:#b4bccc;opacity:1;position:absolute;top:12px;right:15px;cursor:pointer}.custom-theme .el-alert__closebtn.is-customed{font-style:normal;font-size:13px;top:9px}.custom-theme .el-alert-fade-enter,.custom-theme .el-alert-fade-leave-active{opacity:0}.custom-theme .el-notification{display:-webkit-box;display:-ms-flexbox;display:flex;width:330px;padding:14px 26px 14px 13px;border-radius:8px;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid #e6ebf5;position:fixed;background-color:#fff;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);-webkit-transition:opacity .3s,left .3s,right .3s,top .4s,bottom .3s,-webkit-transform .3s;transition:opacity .3s,left .3s,right .3s,top .4s,bottom .3s,-webkit-transform .3s;transition:opacity .3s,transform .3s,left .3s,right .3s,top .4s,bottom .3s;transition:opacity .3s,transform .3s,left .3s,right .3s,top .4s,bottom .3s,-webkit-transform .3s;overflow:hidden}.custom-theme .el-notification.right{right:16px}.custom-theme .el-notification.left{left:16px}.custom-theme .el-notification__group{margin-left:13px}.custom-theme .el-notification__title{font-weight:700;font-size:16px;color:#2d2f33;margin:0}.custom-theme .el-notification__content{font-size:14px;line-height:21px;margin:6px 0 0 0;color:#5a5e66;text-align:justify}.custom-theme .el-notification__content p{margin:0}.custom-theme .el-notification__icon{height:24px;width:24px;font-size:24px;-webkit-transform:translateY(4px);transform:translateY(4px)}.custom-theme .el-notification__closeBtn{position:absolute;top:15px;right:15px;cursor:pointer;color:#878d99;font-size:16px}.custom-theme .el-notification__closeBtn:hover{color:#5a5e66}.custom-theme .el-notification .el-icon-success{color:#409167}.custom-theme .el-notification .el-icon-error{color:#b3450e}.custom-theme .el-notification .el-icon-info{color:#0a76a4}.custom-theme .el-notification .el-icon-warning{color:#9da408}.custom-theme .el-notification-fade-enter.right{right:0;-webkit-transform:translateX(100%);transform:translateX(100%)}.custom-theme .el-notification-fade-enter.left{left:0;-webkit-transform:translateX(-100%);transform:translateX(-100%)}.custom-theme .el-notification-fade-leave-active{opacity:0}.custom-theme .el-input{position:relative;font-size:14px;display:inline-block;width:100%}.custom-theme .el-input::-webkit-scrollbar{z-index:11;width:6px}.custom-theme .el-input::-webkit-scrollbar:horizontal{height:6px}.custom-theme .el-input::-webkit-scrollbar-thumb{border-radius:5px;width:6px;background:#b4bccc}.custom-theme .el-input::-webkit-scrollbar-corner{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track-piece{background:#fff;width:6px}.custom-theme .el-input__inner{-webkit-appearance:none;background-color:#fff;background-image:none;border-radius:4px;border:1px solid #d8dce5;-webkit-box-sizing:border-box;box-sizing:border-box;color:#5a5e66;display:inline-block;font-size:inherit;height:40px;line-height:1;outline:0;padding:0 15px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1);width:100%}.custom-theme .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input__inner:hover{border-color:#b4bccc}.custom-theme .el-input__inner:focus{outline:0;border-color:#262729}.custom-theme .el-input__suffix{position:absolute;height:100%;right:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s;pointer-events:none}.custom-theme .el-input__suffix-inner{pointer-events:all}.custom-theme .el-input__prefix{position:absolute;height:100%;left:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s}.custom-theme .el-input__icon{height:100%;width:25px;text-align:center;-webkit-transition:all .3s;transition:all .3s;line-height:40px}.custom-theme .el-input__icon:after{content:'';height:100%;width:0;display:inline-block;vertical-align:middle}.custom-theme .el-input__validateIcon{pointer-events:none}.custom-theme .el-input.is-active .el-input__inner{outline:0;border-color:#262729}.custom-theme .el-input.is-disabled .el-input__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-input.is-disabled .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__icon{cursor:not-allowed}.custom-theme .el-input--suffix .el-input__inner{padding-right:30px}.custom-theme .el-input--prefix .el-input__inner{padding-left:30px}.custom-theme .el-input--medium{font-size:14px}.custom-theme .el-input--medium .el-input__inner{height:36px}.custom-theme .el-input--medium .el-input__icon{line-height:36px}.custom-theme .el-input--small{font-size:13px}.custom-theme .el-input--small .el-input__inner{height:32px}.custom-theme .el-input--small .el-input__icon{line-height:32px}.custom-theme .el-input--mini{font-size:12px}.custom-theme .el-input--mini .el-input__inner{height:28px}.custom-theme .el-input--mini .el-input__icon{line-height:28px}.custom-theme .el-input-group{line-height:normal;display:inline-table;width:100%;border-collapse:separate}.custom-theme .el-input-group>.el-input__inner{vertical-align:middle;display:table-cell}.custom-theme .el-input-group__append,.custom-theme .el-input-group__prepend{background-color:#f5f7fa;color:#0a76a4;vertical-align:middle;display:table-cell;position:relative;border:1px solid #d8dce5;border-radius:4px;padding:0 20px;width:1px;white-space:nowrap}.custom-theme .el-input-group__append:focus,.custom-theme .el-input-group__prepend:focus{outline:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-select,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-select{display:inline-block;margin:-20px}.custom-theme .el-input-group__append button.el-button,.custom-theme .el-input-group__append div.el-select .el-input__inner,.custom-theme .el-input-group__append div.el-select:hover .el-input__inner,.custom-theme .el-input-group__prepend button.el-button,.custom-theme .el-input-group__prepend div.el-select .el-input__inner,.custom-theme .el-input-group__prepend div.el-select:hover .el-input__inner{border-color:transparent;background-color:transparent;color:inherit;border-top:0;border-bottom:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-input,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-input{font-size:inherit}.custom-theme .el-input-group__prepend{border-right:0;border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-input-group__append{border-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--prepend .el-input__inner{border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--append .el-input__inner{border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-textarea{display:inline-block;width:100%;vertical-align:bottom}.custom-theme .el-textarea__inner{display:block;resize:vertical;padding:5px 15px;line-height:1.5;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;font-size:14px;color:#5a5e66;background-color:#fff;background-image:none;border:1px solid #d8dce5;border-radius:4px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}.custom-theme .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:hover{border-color:#b4bccc}.custom-theme .el-textarea__inner:focus{outline:0;border-color:#262729}.custom-theme .el-textarea.is-disabled .el-textarea__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-input-number{position:relative;display:inline-block;width:180px;line-height:38px}.custom-theme .el-input-number .el-input{display:block}.custom-theme .el-input-number .el-input__inner{-webkit-appearance:none;padding-left:50px;padding-right:50px;text-align:center}.custom-theme .el-input-number__decrease,.custom-theme .el-input-number__increase{position:absolute;z-index:1;top:1px;width:40px;height:auto;text-align:center;background:#f5f7fa;color:#5a5e66;cursor:pointer;font-size:13px}.custom-theme .el-input-number__decrease:hover,.custom-theme .el-input-number__increase:hover{color:#262729}.custom-theme .el-input-number__decrease:hover:not(.is-disabled)~.el-input .el-input__inner:not(.is-disabled),.custom-theme .el-input-number__increase:hover:not(.is-disabled)~.el-input .el-input__inner:not(.is-disabled){border-color:#262729}.custom-theme .el-input-number__decrease.is-disabled,.custom-theme .el-input-number__increase.is-disabled{color:#b4bccc;cursor:not-allowed}.custom-theme .el-input-number__increase{right:1px;border-radius:0 4px 4px 0;border-left:1px solid #d8dce5}.custom-theme .el-input-number__decrease{left:1px;border-radius:4px 0 0 4px;border-right:1px solid #d8dce5}.custom-theme .el-input-number.is-disabled .el-input-number__decrease,.custom-theme .el-input-number.is-disabled .el-input-number__increase{border-color:#dfe4ed;color:#dfe4ed}.custom-theme .el-input-number.is-disabled .el-input-number__decrease:hover,.custom-theme .el-input-number.is-disabled .el-input-number__increase:hover{color:#dfe4ed;cursor:not-allowed}.custom-theme .el-input-number--medium{width:200px;line-height:34px}.custom-theme .el-input-number--medium .el-input-number__decrease,.custom-theme .el-input-number--medium .el-input-number__increase{width:36px;font-size:14px}.custom-theme .el-input-number--medium .el-input__inner{padding-left:43px;padding-right:43px}.custom-theme .el-input-number--small{width:130px;line-height:30px}.custom-theme .el-input-number--small .el-input-number__decrease,.custom-theme .el-input-number--small .el-input-number__increase{width:32px;font-size:13px}.custom-theme .el-input-number--small .el-input-number__decrease [class*=el-icon],.custom-theme .el-input-number--small .el-input-number__increase [class*=el-icon]{-webkit-transform:scale(.9);transform:scale(.9)}.custom-theme .el-input-number--small .el-input__inner{padding-left:39px;padding-right:39px}.custom-theme .el-input-number--mini{width:130px;line-height:26px}.custom-theme .el-input-number--mini .el-input-number__decrease,.custom-theme .el-input-number--mini .el-input-number__increase{width:28px;font-size:12px}.custom-theme .el-input-number--mini .el-input-number__decrease [class*=el-icon],.custom-theme .el-input-number--mini .el-input-number__increase [class*=el-icon]{-webkit-transform:scale(.8);transform:scale(.8)}.custom-theme .el-input-number--mini .el-input__inner{padding-left:35px;padding-right:35px}.custom-theme .el-input-number.is-without-controls .el-input__inner{padding-left:15px;padding-right:15px}.custom-theme .el-input-number.is-controls-right .el-input__inner{padding-left:15px;padding-right:50px}.custom-theme .el-input-number.is-controls-right .el-input-number__decrease,.custom-theme .el-input-number.is-controls-right .el-input-number__increase{height:auto;line-height:19px}.custom-theme .el-input-number.is-controls-right .el-input-number__decrease [class*=el-icon],.custom-theme .el-input-number.is-controls-right .el-input-number__increase [class*=el-icon]{-webkit-transform:scale(.8);transform:scale(.8)}.custom-theme .el-input-number.is-controls-right .el-input-number__increase{border-radius:0 4px 0 0;border-bottom:1px solid #d8dce5}.custom-theme .el-input-number.is-controls-right .el-input-number__decrease{right:1px;bottom:1px;top:auto;left:auto;border-right:none;border-left:1px solid #d8dce5;border-radius:0 0 4px 0}.custom-theme .el-input-number.is-controls-right[class*=medium] [class*=decrease],.custom-theme .el-input-number.is-controls-right[class*=medium] [class*=increase]{line-height:17px}.custom-theme .el-input-number.is-controls-right[class*=small] [class*=decrease],.custom-theme .el-input-number.is-controls-right[class*=small] [class*=increase]{line-height:15px}.custom-theme .el-input-number.is-controls-right[class*=mini] [class*=decrease],.custom-theme .el-input-number.is-controls-right[class*=mini] [class*=increase]{line-height:13px}.custom-theme .el-tooltip__popper{position:absolute;border-radius:4px;padding:10px;z-index:2000;font-size:12px;line-height:1.2}.custom-theme .el-tooltip__popper .popper__arrow,.custom-theme .el-tooltip__popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.custom-theme .el-tooltip__popper .popper__arrow{border-width:6px}.custom-theme .el-tooltip__popper .popper__arrow::after{content:" ";border-width:5px}.custom-theme .el-tooltip__popper[x-placement^=top]{margin-bottom:12px}.custom-theme .el-tooltip__popper[x-placement^=top] .popper__arrow{bottom:-6px;border-top-color:#2d2f33;border-bottom-width:0}.custom-theme .el-tooltip__popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-5px;border-top-color:#2d2f33;border-bottom-width:0}.custom-theme .el-tooltip__popper[x-placement^=bottom]{margin-top:12px}.custom-theme .el-tooltip__popper[x-placement^=bottom] .popper__arrow{top:-6px;border-top-width:0;border-bottom-color:#2d2f33}.custom-theme .el-tooltip__popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-5px;border-top-width:0;border-bottom-color:#2d2f33}.custom-theme .el-tooltip__popper[x-placement^=right]{margin-left:12px}.custom-theme .el-tooltip__popper[x-placement^=right] .popper__arrow{left:-6px;border-right-color:#2d2f33;border-left-width:0}.custom-theme .el-tooltip__popper[x-placement^=right] .popper__arrow::after{bottom:-5px;left:1px;border-right-color:#2d2f33;border-left-width:0}.custom-theme .el-tooltip__popper[x-placement^=left]{margin-right:12px}.custom-theme .el-tooltip__popper[x-placement^=left] .popper__arrow{right:-6px;border-right-width:0;border-left-color:#2d2f33}.custom-theme .el-tooltip__popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-5px;margin-left:-5px;border-right-width:0;border-left-color:#2d2f33}.custom-theme .el-tooltip__popper.is-dark{background:#2d2f33;color:#fff}.custom-theme .el-tooltip__popper.is-light{background:#fff;border:1px solid #2d2f33}.custom-theme .el-tooltip__popper.is-light[x-placement^=top] .popper__arrow{border-top-color:#2d2f33}.custom-theme .el-tooltip__popper.is-light[x-placement^=top] .popper__arrow::after{border-top-color:#fff}.custom-theme .el-tooltip__popper.is-light[x-placement^=bottom] .popper__arrow{border-bottom-color:#2d2f33}.custom-theme .el-tooltip__popper.is-light[x-placement^=bottom] .popper__arrow::after{border-bottom-color:#fff}.custom-theme .el-tooltip__popper.is-light[x-placement^=left] .popper__arrow{border-left-color:#2d2f33}.custom-theme .el-tooltip__popper.is-light[x-placement^=left] .popper__arrow::after{border-left-color:#fff}.custom-theme .el-tooltip__popper.is-light[x-placement^=right] .popper__arrow{border-right-color:#2d2f33}.custom-theme .el-tooltip__popper.is-light[x-placement^=right] .popper__arrow::after{border-right-color:#fff}.custom-theme .el-slider::after,.custom-theme .el-slider::before{display:table;content:""}.custom-theme .el-slider::after{clear:both}.custom-theme .el-slider__runway{width:100%;height:6px;margin:16px 0;background-color:#dfe4ed;border-radius:3px;position:relative;cursor:pointer;vertical-align:middle}.custom-theme .el-slider__runway.show-input{margin-right:160px;width:auto}.custom-theme .el-slider__runway.disabled{cursor:default}.custom-theme .el-slider__runway.disabled .el-slider__bar{background-color:#b4bccc}.custom-theme .el-slider__runway.disabled .el-slider__button{border-color:#b4bccc}.custom-theme .el-slider__runway.disabled .el-slider__button-wrapper.hover,.custom-theme .el-slider__runway.disabled .el-slider__button-wrapper:hover{cursor:not-allowed}.custom-theme .el-slider__runway.disabled .el-slider__button-wrapper.dragging{cursor:not-allowed}.custom-theme .el-slider__runway.disabled .el-slider__button.dragging,.custom-theme .el-slider__runway.disabled .el-slider__button.hover,.custom-theme .el-slider__runway.disabled .el-slider__button:hover{-webkit-transform:scale(1);transform:scale(1)}.custom-theme .el-slider__runway.disabled .el-slider__button.hover,.custom-theme .el-slider__runway.disabled .el-slider__button:hover{cursor:not-allowed}.custom-theme .el-slider__runway.disabled .el-slider__button.dragging{cursor:not-allowed}.custom-theme .el-slider__input{float:right;margin-top:3px}.custom-theme .el-slider__bar{height:6px;background-color:#262729;border-top-left-radius:3px;border-bottom-left-radius:3px;position:absolute}.custom-theme .el-slider__button-wrapper{height:36px;width:36px;position:absolute;z-index:1001;top:-15px;-webkit-transform:translateX(-50%);transform:translateX(-50%);background-color:transparent;text-align:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.custom-theme .el-slider__button-wrapper::after{display:inline-block;content:"";height:100%;vertical-align:middle}.custom-theme .el-slider__button-wrapper .el-tooltip{vertical-align:middle;display:inline-block}.custom-theme .el-slider__button-wrapper.hover,.custom-theme .el-slider__button-wrapper:hover{cursor:-webkit-grab;cursor:grab}.custom-theme .el-slider__button-wrapper.dragging{cursor:-webkit-grabbing;cursor:grabbing}.custom-theme .el-slider__button{width:16px;height:16px;border:solid 2px #262729;background-color:#fff;border-radius:50%;-webkit-transition:.2s;transition:.2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.custom-theme .el-slider__button.dragging,.custom-theme .el-slider__button.hover,.custom-theme .el-slider__button:hover{-webkit-transform:scale(1.2);transform:scale(1.2)}.custom-theme .el-slider__button.hover,.custom-theme .el-slider__button:hover{cursor:-webkit-grab;cursor:grab}.custom-theme .el-slider__button.dragging{cursor:-webkit-grabbing;cursor:grabbing}.custom-theme .el-slider__stop{position:absolute;height:6px;width:6px;border-radius:100%;background-color:#fff;-webkit-transform:translateX(-50%);transform:translateX(-50%)}.custom-theme .el-slider.is-vertical{position:relative}.custom-theme .el-slider.is-vertical .el-slider__runway{width:4px;height:100%;margin:0 16px}.custom-theme .el-slider.is-vertical .el-slider__bar{width:4px;height:auto;border-radius:0 0 3px 3px}.custom-theme .el-slider.is-vertical .el-slider__button-wrapper{top:auto;left:-15px;-webkit-transform:translateY(50%);transform:translateY(50%)}.custom-theme .el-slider.is-vertical .el-slider__stop{-webkit-transform:translateY(50%);transform:translateY(50%)}.custom-theme .el-slider.is-vertical.el-slider--with-input{padding-bottom:58px}.custom-theme .el-slider.is-vertical.el-slider--with-input .el-slider__input{overflow:visible;float:none;position:absolute;bottom:22px;width:36px;margin-top:15px}.custom-theme .el-slider.is-vertical.el-slider--with-input .el-slider__input .el-input__inner{text-align:center;padding-left:5px;padding-right:5px}.custom-theme .el-slider.is-vertical.el-slider--with-input .el-slider__input .el-input-number__decrease,.custom-theme .el-slider.is-vertical.el-slider--with-input .el-slider__input .el-input-number__increase{top:32px;margin-top:-1px;border:1px solid #d8dce5;line-height:20px;-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}.custom-theme .el-slider.is-vertical.el-slider--with-input .el-slider__input .el-input-number__decrease{width:18px;right:18px;border-bottom-left-radius:4px}.custom-theme .el-slider.is-vertical.el-slider--with-input .el-slider__input .el-input-number__increase{width:19px;border-bottom-right-radius:4px}.custom-theme .el-slider.is-vertical.el-slider--with-input .el-slider__input .el-input-number__increase~.el-input .el-input__inner{border-bottom-left-radius:0;border-bottom-right-radius:0}.custom-theme .el-slider.is-vertical.el-slider--with-input .el-slider__input:hover .el-input-number__decrease,.custom-theme .el-slider.is-vertical.el-slider--with-input .el-slider__input:hover .el-input-number__increase{border-color:#b4bccc}.custom-theme .el-slider.is-vertical.el-slider--with-input .el-slider__input:active .el-input-number__decrease,.custom-theme .el-slider.is-vertical.el-slider--with-input .el-slider__input:active .el-input-number__increase{border-color:#262729}.custom-theme .el-loading-parent--relative{position:relative!important}.custom-theme .el-loading-parent--hidden{overflow:hidden!important}.custom-theme .el-loading-mask{position:absolute;z-index:10000;background-color:rgba(255,255,255,.9);margin:0;top:0;right:0;bottom:0;left:0;-webkit-transition:opacity .3s;transition:opacity .3s}.custom-theme .el-loading-mask.is-fullscreen{position:fixed}.custom-theme .el-loading-mask.is-fullscreen .el-loading-spinner{margin-top:-25px}.custom-theme .el-loading-mask.is-fullscreen .el-loading-spinner .circular{height:50px;width:50px}.custom-theme .el-loading-spinner{top:50%;margin-top:-21px;width:100%;text-align:center;position:absolute}.custom-theme .el-loading-spinner .el-loading-text{color:#262729;margin:3px 0;font-size:14px}.custom-theme .el-loading-spinner .circular{height:42px;width:42px;-webkit-animation:loading-rotate 2s linear infinite;animation:loading-rotate 2s linear infinite}.custom-theme .el-loading-spinner .path{-webkit-animation:loading-dash 1.5s ease-in-out infinite;animation:loading-dash 1.5s ease-in-out infinite;stroke-dasharray:90,150;stroke-dashoffset:0;stroke-width:2;stroke:#262729;stroke-linecap:round}.custom-theme .el-loading-spinner i{color:#262729}.custom-theme .el-loading-fade-enter,.custom-theme .el-loading-fade-leave-active{opacity:0}@-webkit-keyframes loading-rotate{100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes loading-rotate{100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes loading-dash{0%{stroke-dasharray:1,200;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-40px}100%{stroke-dasharray:90,150;stroke-dashoffset:-120px}}@keyframes loading-dash{0%{stroke-dasharray:1,200;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-40px}100%{stroke-dasharray:90,150;stroke-dashoffset:-120px}}.custom-theme .el-row{position:relative;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-row::after,.custom-theme .el-row::before{display:table;content:""}.custom-theme .el-row::after{clear:both}.custom-theme .el-row--flex{display:-webkit-box;display:-ms-flexbox;display:flex}.custom-theme .el-row--flex:after,.custom-theme .el-row--flex:before{display:none}.custom-theme .el-row--flex.is-justify-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.custom-theme .el-row--flex.is-justify-end{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.custom-theme .el-row--flex.is-justify-space-between{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.custom-theme .el-row--flex.is-justify-space-around{-ms-flex-pack:distribute;justify-content:space-around}.custom-theme .el-row--flex.is-align-middle{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.custom-theme .el-row--flex.is-align-bottom{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.custom-theme [class*=el-col-]{float:left;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-col-0{display:none}.custom-theme .el-col-1{width:4.16667%}.custom-theme .el-col-offset-1{margin-left:4.16667%}.custom-theme .el-col-pull-1{position:relative;right:4.16667%}.custom-theme .el-col-push-1{position:relative;left:4.16667%}.custom-theme .el-col-2{width:8.33333%}.custom-theme .el-col-offset-2{margin-left:8.33333%}.custom-theme .el-col-pull-2{position:relative;right:8.33333%}.custom-theme .el-col-push-2{position:relative;left:8.33333%}.custom-theme .el-col-3{width:12.5%}.custom-theme .el-col-offset-3{margin-left:12.5%}.custom-theme .el-col-pull-3{position:relative;right:12.5%}.custom-theme .el-col-push-3{position:relative;left:12.5%}.custom-theme .el-col-4{width:16.66667%}.custom-theme .el-col-offset-4{margin-left:16.66667%}.custom-theme .el-col-pull-4{position:relative;right:16.66667%}.custom-theme .el-col-push-4{position:relative;left:16.66667%}.custom-theme .el-col-5{width:20.83333%}.custom-theme .el-col-offset-5{margin-left:20.83333%}.custom-theme .el-col-pull-5{position:relative;right:20.83333%}.custom-theme .el-col-push-5{position:relative;left:20.83333%}.custom-theme .el-col-6{width:25%}.custom-theme .el-col-offset-6{margin-left:25%}.custom-theme .el-col-pull-6{position:relative;right:25%}.custom-theme .el-col-push-6{position:relative;left:25%}.custom-theme .el-col-7{width:29.16667%}.custom-theme .el-col-offset-7{margin-left:29.16667%}.custom-theme .el-col-pull-7{position:relative;right:29.16667%}.custom-theme .el-col-push-7{position:relative;left:29.16667%}.custom-theme .el-col-8{width:33.33333%}.custom-theme .el-col-offset-8{margin-left:33.33333%}.custom-theme .el-col-pull-8{position:relative;right:33.33333%}.custom-theme .el-col-push-8{position:relative;left:33.33333%}.custom-theme .el-col-9{width:37.5%}.custom-theme .el-col-offset-9{margin-left:37.5%}.custom-theme .el-col-pull-9{position:relative;right:37.5%}.custom-theme .el-col-push-9{position:relative;left:37.5%}.custom-theme .el-col-10{width:41.66667%}.custom-theme .el-col-offset-10{margin-left:41.66667%}.custom-theme .el-col-pull-10{position:relative;right:41.66667%}.custom-theme .el-col-push-10{position:relative;left:41.66667%}.custom-theme .el-col-11{width:45.83333%}.custom-theme .el-col-offset-11{margin-left:45.83333%}.custom-theme .el-col-pull-11{position:relative;right:45.83333%}.custom-theme .el-col-push-11{position:relative;left:45.83333%}.custom-theme .el-col-12{width:50%}.custom-theme .el-col-offset-12{margin-left:50%}.custom-theme .el-col-pull-12{position:relative;right:50%}.custom-theme .el-col-push-12{position:relative;left:50%}.custom-theme .el-col-13{width:54.16667%}.custom-theme .el-col-offset-13{margin-left:54.16667%}.custom-theme .el-col-pull-13{position:relative;right:54.16667%}.custom-theme .el-col-push-13{position:relative;left:54.16667%}.custom-theme .el-col-14{width:58.33333%}.custom-theme .el-col-offset-14{margin-left:58.33333%}.custom-theme .el-col-pull-14{position:relative;right:58.33333%}.custom-theme .el-col-push-14{position:relative;left:58.33333%}.custom-theme .el-col-15{width:62.5%}.custom-theme .el-col-offset-15{margin-left:62.5%}.custom-theme .el-col-pull-15{position:relative;right:62.5%}.custom-theme .el-col-push-15{position:relative;left:62.5%}.custom-theme .el-col-16{width:66.66667%}.custom-theme .el-col-offset-16{margin-left:66.66667%}.custom-theme .el-col-pull-16{position:relative;right:66.66667%}.custom-theme .el-col-push-16{position:relative;left:66.66667%}.custom-theme .el-col-17{width:70.83333%}.custom-theme .el-col-offset-17{margin-left:70.83333%}.custom-theme .el-col-pull-17{position:relative;right:70.83333%}.custom-theme .el-col-push-17{position:relative;left:70.83333%}.custom-theme .el-col-18{width:75%}.custom-theme .el-col-offset-18{margin-left:75%}.custom-theme .el-col-pull-18{position:relative;right:75%}.custom-theme .el-col-push-18{position:relative;left:75%}.custom-theme .el-col-19{width:79.16667%}.custom-theme .el-col-offset-19{margin-left:79.16667%}.custom-theme .el-col-pull-19{position:relative;right:79.16667%}.custom-theme .el-col-push-19{position:relative;left:79.16667%}.custom-theme .el-col-20{width:83.33333%}.custom-theme .el-col-offset-20{margin-left:83.33333%}.custom-theme .el-col-pull-20{position:relative;right:83.33333%}.custom-theme .el-col-push-20{position:relative;left:83.33333%}.custom-theme .el-col-21{width:87.5%}.custom-theme .el-col-offset-21{margin-left:87.5%}.custom-theme .el-col-pull-21{position:relative;right:87.5%}.custom-theme .el-col-push-21{position:relative;left:87.5%}.custom-theme .el-col-22{width:91.66667%}.custom-theme .el-col-offset-22{margin-left:91.66667%}.custom-theme .el-col-pull-22{position:relative;right:91.66667%}.custom-theme .el-col-push-22{position:relative;left:91.66667%}.custom-theme .el-col-23{width:95.83333%}.custom-theme .el-col-offset-23{margin-left:95.83333%}.custom-theme .el-col-pull-23{position:relative;right:95.83333%}.custom-theme .el-col-push-23{position:relative;left:95.83333%}.custom-theme .el-col-24{width:100%}.custom-theme .el-col-offset-24{margin-left:100%}.custom-theme .el-col-pull-24{position:relative;right:100%}.custom-theme .el-col-push-24{position:relative;left:100%}@media only screen and (max-width:768px){.custom-theme .el-col-xs-0{display:none}.custom-theme .el-col-xs-1{width:4.16667%}.custom-theme .el-col-xs-offset-1{margin-left:4.16667%}.custom-theme .el-col-xs-pull-1{position:relative;right:4.16667%}.custom-theme .el-col-xs-push-1{position:relative;left:4.16667%}.custom-theme .el-col-xs-2{width:8.33333%}.custom-theme .el-col-xs-offset-2{margin-left:8.33333%}.custom-theme .el-col-xs-pull-2{position:relative;right:8.33333%}.custom-theme .el-col-xs-push-2{position:relative;left:8.33333%}.custom-theme .el-col-xs-3{width:12.5%}.custom-theme .el-col-xs-offset-3{margin-left:12.5%}.custom-theme .el-col-xs-pull-3{position:relative;right:12.5%}.custom-theme .el-col-xs-push-3{position:relative;left:12.5%}.custom-theme .el-col-xs-4{width:16.66667%}.custom-theme .el-col-xs-offset-4{margin-left:16.66667%}.custom-theme .el-col-xs-pull-4{position:relative;right:16.66667%}.custom-theme .el-col-xs-push-4{position:relative;left:16.66667%}.custom-theme .el-col-xs-5{width:20.83333%}.custom-theme .el-col-xs-offset-5{margin-left:20.83333%}.custom-theme .el-col-xs-pull-5{position:relative;right:20.83333%}.custom-theme .el-col-xs-push-5{position:relative;left:20.83333%}.custom-theme .el-col-xs-6{width:25%}.custom-theme .el-col-xs-offset-6{margin-left:25%}.custom-theme .el-col-xs-pull-6{position:relative;right:25%}.custom-theme .el-col-xs-push-6{position:relative;left:25%}.custom-theme .el-col-xs-7{width:29.16667%}.custom-theme .el-col-xs-offset-7{margin-left:29.16667%}.custom-theme .el-col-xs-pull-7{position:relative;right:29.16667%}.custom-theme .el-col-xs-push-7{position:relative;left:29.16667%}.custom-theme .el-col-xs-8{width:33.33333%}.custom-theme .el-col-xs-offset-8{margin-left:33.33333%}.custom-theme .el-col-xs-pull-8{position:relative;right:33.33333%}.custom-theme .el-col-xs-push-8{position:relative;left:33.33333%}.custom-theme .el-col-xs-9{width:37.5%}.custom-theme .el-col-xs-offset-9{margin-left:37.5%}.custom-theme .el-col-xs-pull-9{position:relative;right:37.5%}.custom-theme .el-col-xs-push-9{position:relative;left:37.5%}.custom-theme .el-col-xs-10{width:41.66667%}.custom-theme .el-col-xs-offset-10{margin-left:41.66667%}.custom-theme .el-col-xs-pull-10{position:relative;right:41.66667%}.custom-theme .el-col-xs-push-10{position:relative;left:41.66667%}.custom-theme .el-col-xs-11{width:45.83333%}.custom-theme .el-col-xs-offset-11{margin-left:45.83333%}.custom-theme .el-col-xs-pull-11{position:relative;right:45.83333%}.custom-theme .el-col-xs-push-11{position:relative;left:45.83333%}.custom-theme .el-col-xs-12{width:50%}.custom-theme .el-col-xs-offset-12{margin-left:50%}.custom-theme .el-col-xs-pull-12{position:relative;right:50%}.custom-theme .el-col-xs-push-12{position:relative;left:50%}.custom-theme .el-col-xs-13{width:54.16667%}.custom-theme .el-col-xs-offset-13{margin-left:54.16667%}.custom-theme .el-col-xs-pull-13{position:relative;right:54.16667%}.custom-theme .el-col-xs-push-13{position:relative;left:54.16667%}.custom-theme .el-col-xs-14{width:58.33333%}.custom-theme .el-col-xs-offset-14{margin-left:58.33333%}.custom-theme .el-col-xs-pull-14{position:relative;right:58.33333%}.custom-theme .el-col-xs-push-14{position:relative;left:58.33333%}.custom-theme .el-col-xs-15{width:62.5%}.custom-theme .el-col-xs-offset-15{margin-left:62.5%}.custom-theme .el-col-xs-pull-15{position:relative;right:62.5%}.custom-theme .el-col-xs-push-15{position:relative;left:62.5%}.custom-theme .el-col-xs-16{width:66.66667%}.custom-theme .el-col-xs-offset-16{margin-left:66.66667%}.custom-theme .el-col-xs-pull-16{position:relative;right:66.66667%}.custom-theme .el-col-xs-push-16{position:relative;left:66.66667%}.custom-theme .el-col-xs-17{width:70.83333%}.custom-theme .el-col-xs-offset-17{margin-left:70.83333%}.custom-theme .el-col-xs-pull-17{position:relative;right:70.83333%}.custom-theme .el-col-xs-push-17{position:relative;left:70.83333%}.custom-theme .el-col-xs-18{width:75%}.custom-theme .el-col-xs-offset-18{margin-left:75%}.custom-theme .el-col-xs-pull-18{position:relative;right:75%}.custom-theme .el-col-xs-push-18{position:relative;left:75%}.custom-theme .el-col-xs-19{width:79.16667%}.custom-theme .el-col-xs-offset-19{margin-left:79.16667%}.custom-theme .el-col-xs-pull-19{position:relative;right:79.16667%}.custom-theme .el-col-xs-push-19{position:relative;left:79.16667%}.custom-theme .el-col-xs-20{width:83.33333%}.custom-theme .el-col-xs-offset-20{margin-left:83.33333%}.custom-theme .el-col-xs-pull-20{position:relative;right:83.33333%}.custom-theme .el-col-xs-push-20{position:relative;left:83.33333%}.custom-theme .el-col-xs-21{width:87.5%}.custom-theme .el-col-xs-offset-21{margin-left:87.5%}.custom-theme .el-col-xs-pull-21{position:relative;right:87.5%}.custom-theme .el-col-xs-push-21{position:relative;left:87.5%}.custom-theme .el-col-xs-22{width:91.66667%}.custom-theme .el-col-xs-offset-22{margin-left:91.66667%}.custom-theme .el-col-xs-pull-22{position:relative;right:91.66667%}.custom-theme .el-col-xs-push-22{position:relative;left:91.66667%}.custom-theme .el-col-xs-23{width:95.83333%}.custom-theme .el-col-xs-offset-23{margin-left:95.83333%}.custom-theme .el-col-xs-pull-23{position:relative;right:95.83333%}.custom-theme .el-col-xs-push-23{position:relative;left:95.83333%}.custom-theme .el-col-xs-24{width:100%}.custom-theme .el-col-xs-offset-24{margin-left:100%}.custom-theme .el-col-xs-pull-24{position:relative;right:100%}.custom-theme .el-col-xs-push-24{position:relative;left:100%}}@media only screen and (min-width:768px){.custom-theme .el-col-sm-0{display:none}.custom-theme .el-col-sm-1{width:4.16667%}.custom-theme .el-col-sm-offset-1{margin-left:4.16667%}.custom-theme .el-col-sm-pull-1{position:relative;right:4.16667%}.custom-theme .el-col-sm-push-1{position:relative;left:4.16667%}.custom-theme .el-col-sm-2{width:8.33333%}.custom-theme .el-col-sm-offset-2{margin-left:8.33333%}.custom-theme .el-col-sm-pull-2{position:relative;right:8.33333%}.custom-theme .el-col-sm-push-2{position:relative;left:8.33333%}.custom-theme .el-col-sm-3{width:12.5%}.custom-theme .el-col-sm-offset-3{margin-left:12.5%}.custom-theme .el-col-sm-pull-3{position:relative;right:12.5%}.custom-theme .el-col-sm-push-3{position:relative;left:12.5%}.custom-theme .el-col-sm-4{width:16.66667%}.custom-theme .el-col-sm-offset-4{margin-left:16.66667%}.custom-theme .el-col-sm-pull-4{position:relative;right:16.66667%}.custom-theme .el-col-sm-push-4{position:relative;left:16.66667%}.custom-theme .el-col-sm-5{width:20.83333%}.custom-theme .el-col-sm-offset-5{margin-left:20.83333%}.custom-theme .el-col-sm-pull-5{position:relative;right:20.83333%}.custom-theme .el-col-sm-push-5{position:relative;left:20.83333%}.custom-theme .el-col-sm-6{width:25%}.custom-theme .el-col-sm-offset-6{margin-left:25%}.custom-theme .el-col-sm-pull-6{position:relative;right:25%}.custom-theme .el-col-sm-push-6{position:relative;left:25%}.custom-theme .el-col-sm-7{width:29.16667%}.custom-theme .el-col-sm-offset-7{margin-left:29.16667%}.custom-theme .el-col-sm-pull-7{position:relative;right:29.16667%}.custom-theme .el-col-sm-push-7{position:relative;left:29.16667%}.custom-theme .el-col-sm-8{width:33.33333%}.custom-theme .el-col-sm-offset-8{margin-left:33.33333%}.custom-theme .el-col-sm-pull-8{position:relative;right:33.33333%}.custom-theme .el-col-sm-push-8{position:relative;left:33.33333%}.custom-theme .el-col-sm-9{width:37.5%}.custom-theme .el-col-sm-offset-9{margin-left:37.5%}.custom-theme .el-col-sm-pull-9{position:relative;right:37.5%}.custom-theme .el-col-sm-push-9{position:relative;left:37.5%}.custom-theme .el-col-sm-10{width:41.66667%}.custom-theme .el-col-sm-offset-10{margin-left:41.66667%}.custom-theme .el-col-sm-pull-10{position:relative;right:41.66667%}.custom-theme .el-col-sm-push-10{position:relative;left:41.66667%}.custom-theme .el-col-sm-11{width:45.83333%}.custom-theme .el-col-sm-offset-11{margin-left:45.83333%}.custom-theme .el-col-sm-pull-11{position:relative;right:45.83333%}.custom-theme .el-col-sm-push-11{position:relative;left:45.83333%}.custom-theme .el-col-sm-12{width:50%}.custom-theme .el-col-sm-offset-12{margin-left:50%}.custom-theme .el-col-sm-pull-12{position:relative;right:50%}.custom-theme .el-col-sm-push-12{position:relative;left:50%}.custom-theme .el-col-sm-13{width:54.16667%}.custom-theme .el-col-sm-offset-13{margin-left:54.16667%}.custom-theme .el-col-sm-pull-13{position:relative;right:54.16667%}.custom-theme .el-col-sm-push-13{position:relative;left:54.16667%}.custom-theme .el-col-sm-14{width:58.33333%}.custom-theme .el-col-sm-offset-14{margin-left:58.33333%}.custom-theme .el-col-sm-pull-14{position:relative;right:58.33333%}.custom-theme .el-col-sm-push-14{position:relative;left:58.33333%}.custom-theme .el-col-sm-15{width:62.5%}.custom-theme .el-col-sm-offset-15{margin-left:62.5%}.custom-theme .el-col-sm-pull-15{position:relative;right:62.5%}.custom-theme .el-col-sm-push-15{position:relative;left:62.5%}.custom-theme .el-col-sm-16{width:66.66667%}.custom-theme .el-col-sm-offset-16{margin-left:66.66667%}.custom-theme .el-col-sm-pull-16{position:relative;right:66.66667%}.custom-theme .el-col-sm-push-16{position:relative;left:66.66667%}.custom-theme .el-col-sm-17{width:70.83333%}.custom-theme .el-col-sm-offset-17{margin-left:70.83333%}.custom-theme .el-col-sm-pull-17{position:relative;right:70.83333%}.custom-theme .el-col-sm-push-17{position:relative;left:70.83333%}.custom-theme .el-col-sm-18{width:75%}.custom-theme .el-col-sm-offset-18{margin-left:75%}.custom-theme .el-col-sm-pull-18{position:relative;right:75%}.custom-theme .el-col-sm-push-18{position:relative;left:75%}.custom-theme .el-col-sm-19{width:79.16667%}.custom-theme .el-col-sm-offset-19{margin-left:79.16667%}.custom-theme .el-col-sm-pull-19{position:relative;right:79.16667%}.custom-theme .el-col-sm-push-19{position:relative;left:79.16667%}.custom-theme .el-col-sm-20{width:83.33333%}.custom-theme .el-col-sm-offset-20{margin-left:83.33333%}.custom-theme .el-col-sm-pull-20{position:relative;right:83.33333%}.custom-theme .el-col-sm-push-20{position:relative;left:83.33333%}.custom-theme .el-col-sm-21{width:87.5%}.custom-theme .el-col-sm-offset-21{margin-left:87.5%}.custom-theme .el-col-sm-pull-21{position:relative;right:87.5%}.custom-theme .el-col-sm-push-21{position:relative;left:87.5%}.custom-theme .el-col-sm-22{width:91.66667%}.custom-theme .el-col-sm-offset-22{margin-left:91.66667%}.custom-theme .el-col-sm-pull-22{position:relative;right:91.66667%}.custom-theme .el-col-sm-push-22{position:relative;left:91.66667%}.custom-theme .el-col-sm-23{width:95.83333%}.custom-theme .el-col-sm-offset-23{margin-left:95.83333%}.custom-theme .el-col-sm-pull-23{position:relative;right:95.83333%}.custom-theme .el-col-sm-push-23{position:relative;left:95.83333%}.custom-theme .el-col-sm-24{width:100%}.custom-theme .el-col-sm-offset-24{margin-left:100%}.custom-theme .el-col-sm-pull-24{position:relative;right:100%}.custom-theme .el-col-sm-push-24{position:relative;left:100%}}@media only screen and (min-width:992px){.custom-theme .el-col-md-0{display:none}.custom-theme .el-col-md-1{width:4.16667%}.custom-theme .el-col-md-offset-1{margin-left:4.16667%}.custom-theme .el-col-md-pull-1{position:relative;right:4.16667%}.custom-theme .el-col-md-push-1{position:relative;left:4.16667%}.custom-theme .el-col-md-2{width:8.33333%}.custom-theme .el-col-md-offset-2{margin-left:8.33333%}.custom-theme .el-col-md-pull-2{position:relative;right:8.33333%}.custom-theme .el-col-md-push-2{position:relative;left:8.33333%}.custom-theme .el-col-md-3{width:12.5%}.custom-theme .el-col-md-offset-3{margin-left:12.5%}.custom-theme .el-col-md-pull-3{position:relative;right:12.5%}.custom-theme .el-col-md-push-3{position:relative;left:12.5%}.custom-theme .el-col-md-4{width:16.66667%}.custom-theme .el-col-md-offset-4{margin-left:16.66667%}.custom-theme .el-col-md-pull-4{position:relative;right:16.66667%}.custom-theme .el-col-md-push-4{position:relative;left:16.66667%}.custom-theme .el-col-md-5{width:20.83333%}.custom-theme .el-col-md-offset-5{margin-left:20.83333%}.custom-theme .el-col-md-pull-5{position:relative;right:20.83333%}.custom-theme .el-col-md-push-5{position:relative;left:20.83333%}.custom-theme .el-col-md-6{width:25%}.custom-theme .el-col-md-offset-6{margin-left:25%}.custom-theme .el-col-md-pull-6{position:relative;right:25%}.custom-theme .el-col-md-push-6{position:relative;left:25%}.custom-theme .el-col-md-7{width:29.16667%}.custom-theme .el-col-md-offset-7{margin-left:29.16667%}.custom-theme .el-col-md-pull-7{position:relative;right:29.16667%}.custom-theme .el-col-md-push-7{position:relative;left:29.16667%}.custom-theme .el-col-md-8{width:33.33333%}.custom-theme .el-col-md-offset-8{margin-left:33.33333%}.custom-theme .el-col-md-pull-8{position:relative;right:33.33333%}.custom-theme .el-col-md-push-8{position:relative;left:33.33333%}.custom-theme .el-col-md-9{width:37.5%}.custom-theme .el-col-md-offset-9{margin-left:37.5%}.custom-theme .el-col-md-pull-9{position:relative;right:37.5%}.custom-theme .el-col-md-push-9{position:relative;left:37.5%}.custom-theme .el-col-md-10{width:41.66667%}.custom-theme .el-col-md-offset-10{margin-left:41.66667%}.custom-theme .el-col-md-pull-10{position:relative;right:41.66667%}.custom-theme .el-col-md-push-10{position:relative;left:41.66667%}.custom-theme .el-col-md-11{width:45.83333%}.custom-theme .el-col-md-offset-11{margin-left:45.83333%}.custom-theme .el-col-md-pull-11{position:relative;right:45.83333%}.custom-theme .el-col-md-push-11{position:relative;left:45.83333%}.custom-theme .el-col-md-12{width:50%}.custom-theme .el-col-md-offset-12{margin-left:50%}.custom-theme .el-col-md-pull-12{position:relative;right:50%}.custom-theme .el-col-md-push-12{position:relative;left:50%}.custom-theme .el-col-md-13{width:54.16667%}.custom-theme .el-col-md-offset-13{margin-left:54.16667%}.custom-theme .el-col-md-pull-13{position:relative;right:54.16667%}.custom-theme .el-col-md-push-13{position:relative;left:54.16667%}.custom-theme .el-col-md-14{width:58.33333%}.custom-theme .el-col-md-offset-14{margin-left:58.33333%}.custom-theme .el-col-md-pull-14{position:relative;right:58.33333%}.custom-theme .el-col-md-push-14{position:relative;left:58.33333%}.custom-theme .el-col-md-15{width:62.5%}.custom-theme .el-col-md-offset-15{margin-left:62.5%}.custom-theme .el-col-md-pull-15{position:relative;right:62.5%}.custom-theme .el-col-md-push-15{position:relative;left:62.5%}.custom-theme .el-col-md-16{width:66.66667%}.custom-theme .el-col-md-offset-16{margin-left:66.66667%}.custom-theme .el-col-md-pull-16{position:relative;right:66.66667%}.custom-theme .el-col-md-push-16{position:relative;left:66.66667%}.custom-theme .el-col-md-17{width:70.83333%}.custom-theme .el-col-md-offset-17{margin-left:70.83333%}.custom-theme .el-col-md-pull-17{position:relative;right:70.83333%}.custom-theme .el-col-md-push-17{position:relative;left:70.83333%}.custom-theme .el-col-md-18{width:75%}.custom-theme .el-col-md-offset-18{margin-left:75%}.custom-theme .el-col-md-pull-18{position:relative;right:75%}.custom-theme .el-col-md-push-18{position:relative;left:75%}.custom-theme .el-col-md-19{width:79.16667%}.custom-theme .el-col-md-offset-19{margin-left:79.16667%}.custom-theme .el-col-md-pull-19{position:relative;right:79.16667%}.custom-theme .el-col-md-push-19{position:relative;left:79.16667%}.custom-theme .el-col-md-20{width:83.33333%}.custom-theme .el-col-md-offset-20{margin-left:83.33333%}.custom-theme .el-col-md-pull-20{position:relative;right:83.33333%}.custom-theme .el-col-md-push-20{position:relative;left:83.33333%}.custom-theme .el-col-md-21{width:87.5%}.custom-theme .el-col-md-offset-21{margin-left:87.5%}.custom-theme .el-col-md-pull-21{position:relative;right:87.5%}.custom-theme .el-col-md-push-21{position:relative;left:87.5%}.custom-theme .el-col-md-22{width:91.66667%}.custom-theme .el-col-md-offset-22{margin-left:91.66667%}.custom-theme .el-col-md-pull-22{position:relative;right:91.66667%}.custom-theme .el-col-md-push-22{position:relative;left:91.66667%}.custom-theme .el-col-md-23{width:95.83333%}.custom-theme .el-col-md-offset-23{margin-left:95.83333%}.custom-theme .el-col-md-pull-23{position:relative;right:95.83333%}.custom-theme .el-col-md-push-23{position:relative;left:95.83333%}.custom-theme .el-col-md-24{width:100%}.custom-theme .el-col-md-offset-24{margin-left:100%}.custom-theme .el-col-md-pull-24{position:relative;right:100%}.custom-theme .el-col-md-push-24{position:relative;left:100%}}@media only screen and (min-width:1200px){.custom-theme .el-col-lg-0{display:none}.custom-theme .el-col-lg-1{width:4.16667%}.custom-theme .el-col-lg-offset-1{margin-left:4.16667%}.custom-theme .el-col-lg-pull-1{position:relative;right:4.16667%}.custom-theme .el-col-lg-push-1{position:relative;left:4.16667%}.custom-theme .el-col-lg-2{width:8.33333%}.custom-theme .el-col-lg-offset-2{margin-left:8.33333%}.custom-theme .el-col-lg-pull-2{position:relative;right:8.33333%}.custom-theme .el-col-lg-push-2{position:relative;left:8.33333%}.custom-theme .el-col-lg-3{width:12.5%}.custom-theme .el-col-lg-offset-3{margin-left:12.5%}.custom-theme .el-col-lg-pull-3{position:relative;right:12.5%}.custom-theme .el-col-lg-push-3{position:relative;left:12.5%}.custom-theme .el-col-lg-4{width:16.66667%}.custom-theme .el-col-lg-offset-4{margin-left:16.66667%}.custom-theme .el-col-lg-pull-4{position:relative;right:16.66667%}.custom-theme .el-col-lg-push-4{position:relative;left:16.66667%}.custom-theme .el-col-lg-5{width:20.83333%}.custom-theme .el-col-lg-offset-5{margin-left:20.83333%}.custom-theme .el-col-lg-pull-5{position:relative;right:20.83333%}.custom-theme .el-col-lg-push-5{position:relative;left:20.83333%}.custom-theme .el-col-lg-6{width:25%}.custom-theme .el-col-lg-offset-6{margin-left:25%}.custom-theme .el-col-lg-pull-6{position:relative;right:25%}.custom-theme .el-col-lg-push-6{position:relative;left:25%}.custom-theme .el-col-lg-7{width:29.16667%}.custom-theme .el-col-lg-offset-7{margin-left:29.16667%}.custom-theme .el-col-lg-pull-7{position:relative;right:29.16667%}.custom-theme .el-col-lg-push-7{position:relative;left:29.16667%}.custom-theme .el-col-lg-8{width:33.33333%}.custom-theme .el-col-lg-offset-8{margin-left:33.33333%}.custom-theme .el-col-lg-pull-8{position:relative;right:33.33333%}.custom-theme .el-col-lg-push-8{position:relative;left:33.33333%}.custom-theme .el-col-lg-9{width:37.5%}.custom-theme .el-col-lg-offset-9{margin-left:37.5%}.custom-theme .el-col-lg-pull-9{position:relative;right:37.5%}.custom-theme .el-col-lg-push-9{position:relative;left:37.5%}.custom-theme .el-col-lg-10{width:41.66667%}.custom-theme .el-col-lg-offset-10{margin-left:41.66667%}.custom-theme .el-col-lg-pull-10{position:relative;right:41.66667%}.custom-theme .el-col-lg-push-10{position:relative;left:41.66667%}.custom-theme .el-col-lg-11{width:45.83333%}.custom-theme .el-col-lg-offset-11{margin-left:45.83333%}.custom-theme .el-col-lg-pull-11{position:relative;right:45.83333%}.custom-theme .el-col-lg-push-11{position:relative;left:45.83333%}.custom-theme .el-col-lg-12{width:50%}.custom-theme .el-col-lg-offset-12{margin-left:50%}.custom-theme .el-col-lg-pull-12{position:relative;right:50%}.custom-theme .el-col-lg-push-12{position:relative;left:50%}.custom-theme .el-col-lg-13{width:54.16667%}.custom-theme .el-col-lg-offset-13{margin-left:54.16667%}.custom-theme .el-col-lg-pull-13{position:relative;right:54.16667%}.custom-theme .el-col-lg-push-13{position:relative;left:54.16667%}.custom-theme .el-col-lg-14{width:58.33333%}.custom-theme .el-col-lg-offset-14{margin-left:58.33333%}.custom-theme .el-col-lg-pull-14{position:relative;right:58.33333%}.custom-theme .el-col-lg-push-14{position:relative;left:58.33333%}.custom-theme .el-col-lg-15{width:62.5%}.custom-theme .el-col-lg-offset-15{margin-left:62.5%}.custom-theme .el-col-lg-pull-15{position:relative;right:62.5%}.custom-theme .el-col-lg-push-15{position:relative;left:62.5%}.custom-theme .el-col-lg-16{width:66.66667%}.custom-theme .el-col-lg-offset-16{margin-left:66.66667%}.custom-theme .el-col-lg-pull-16{position:relative;right:66.66667%}.custom-theme .el-col-lg-push-16{position:relative;left:66.66667%}.custom-theme .el-col-lg-17{width:70.83333%}.custom-theme .el-col-lg-offset-17{margin-left:70.83333%}.custom-theme .el-col-lg-pull-17{position:relative;right:70.83333%}.custom-theme .el-col-lg-push-17{position:relative;left:70.83333%}.custom-theme .el-col-lg-18{width:75%}.custom-theme .el-col-lg-offset-18{margin-left:75%}.custom-theme .el-col-lg-pull-18{position:relative;right:75%}.custom-theme .el-col-lg-push-18{position:relative;left:75%}.custom-theme .el-col-lg-19{width:79.16667%}.custom-theme .el-col-lg-offset-19{margin-left:79.16667%}.custom-theme .el-col-lg-pull-19{position:relative;right:79.16667%}.custom-theme .el-col-lg-push-19{position:relative;left:79.16667%}.custom-theme .el-col-lg-20{width:83.33333%}.custom-theme .el-col-lg-offset-20{margin-left:83.33333%}.custom-theme .el-col-lg-pull-20{position:relative;right:83.33333%}.custom-theme .el-col-lg-push-20{position:relative;left:83.33333%}.custom-theme .el-col-lg-21{width:87.5%}.custom-theme .el-col-lg-offset-21{margin-left:87.5%}.custom-theme .el-col-lg-pull-21{position:relative;right:87.5%}.custom-theme .el-col-lg-push-21{position:relative;left:87.5%}.custom-theme .el-col-lg-22{width:91.66667%}.custom-theme .el-col-lg-offset-22{margin-left:91.66667%}.custom-theme .el-col-lg-pull-22{position:relative;right:91.66667%}.custom-theme .el-col-lg-push-22{position:relative;left:91.66667%}.custom-theme .el-col-lg-23{width:95.83333%}.custom-theme .el-col-lg-offset-23{margin-left:95.83333%}.custom-theme .el-col-lg-pull-23{position:relative;right:95.83333%}.custom-theme .el-col-lg-push-23{position:relative;left:95.83333%}.custom-theme .el-col-lg-24{width:100%}.custom-theme .el-col-lg-offset-24{margin-left:100%}.custom-theme .el-col-lg-pull-24{position:relative;right:100%}.custom-theme .el-col-lg-push-24{position:relative;left:100%}}@media only screen and (min-width:1920px){.custom-theme .el-col-xl-0{display:none}.custom-theme .el-col-xl-1{width:4.16667%}.custom-theme .el-col-xl-offset-1{margin-left:4.16667%}.custom-theme .el-col-xl-pull-1{position:relative;right:4.16667%}.custom-theme .el-col-xl-push-1{position:relative;left:4.16667%}.custom-theme .el-col-xl-2{width:8.33333%}.custom-theme .el-col-xl-offset-2{margin-left:8.33333%}.custom-theme .el-col-xl-pull-2{position:relative;right:8.33333%}.custom-theme .el-col-xl-push-2{position:relative;left:8.33333%}.custom-theme .el-col-xl-3{width:12.5%}.custom-theme .el-col-xl-offset-3{margin-left:12.5%}.custom-theme .el-col-xl-pull-3{position:relative;right:12.5%}.custom-theme .el-col-xl-push-3{position:relative;left:12.5%}.custom-theme .el-col-xl-4{width:16.66667%}.custom-theme .el-col-xl-offset-4{margin-left:16.66667%}.custom-theme .el-col-xl-pull-4{position:relative;right:16.66667%}.custom-theme .el-col-xl-push-4{position:relative;left:16.66667%}.custom-theme .el-col-xl-5{width:20.83333%}.custom-theme .el-col-xl-offset-5{margin-left:20.83333%}.custom-theme .el-col-xl-pull-5{position:relative;right:20.83333%}.custom-theme .el-col-xl-push-5{position:relative;left:20.83333%}.custom-theme .el-col-xl-6{width:25%}.custom-theme .el-col-xl-offset-6{margin-left:25%}.custom-theme .el-col-xl-pull-6{position:relative;right:25%}.custom-theme .el-col-xl-push-6{position:relative;left:25%}.custom-theme .el-col-xl-7{width:29.16667%}.custom-theme .el-col-xl-offset-7{margin-left:29.16667%}.custom-theme .el-col-xl-pull-7{position:relative;right:29.16667%}.custom-theme .el-col-xl-push-7{position:relative;left:29.16667%}.custom-theme .el-col-xl-8{width:33.33333%}.custom-theme .el-col-xl-offset-8{margin-left:33.33333%}.custom-theme .el-col-xl-pull-8{position:relative;right:33.33333%}.custom-theme .el-col-xl-push-8{position:relative;left:33.33333%}.custom-theme .el-col-xl-9{width:37.5%}.custom-theme .el-col-xl-offset-9{margin-left:37.5%}.custom-theme .el-col-xl-pull-9{position:relative;right:37.5%}.custom-theme .el-col-xl-push-9{position:relative;left:37.5%}.custom-theme .el-col-xl-10{width:41.66667%}.custom-theme .el-col-xl-offset-10{margin-left:41.66667%}.custom-theme .el-col-xl-pull-10{position:relative;right:41.66667%}.custom-theme .el-col-xl-push-10{position:relative;left:41.66667%}.custom-theme .el-col-xl-11{width:45.83333%}.custom-theme .el-col-xl-offset-11{margin-left:45.83333%}.custom-theme .el-col-xl-pull-11{position:relative;right:45.83333%}.custom-theme .el-col-xl-push-11{position:relative;left:45.83333%}.custom-theme .el-col-xl-12{width:50%}.custom-theme .el-col-xl-offset-12{margin-left:50%}.custom-theme .el-col-xl-pull-12{position:relative;right:50%}.custom-theme .el-col-xl-push-12{position:relative;left:50%}.custom-theme .el-col-xl-13{width:54.16667%}.custom-theme .el-col-xl-offset-13{margin-left:54.16667%}.custom-theme .el-col-xl-pull-13{position:relative;right:54.16667%}.custom-theme .el-col-xl-push-13{position:relative;left:54.16667%}.custom-theme .el-col-xl-14{width:58.33333%}.custom-theme .el-col-xl-offset-14{margin-left:58.33333%}.custom-theme .el-col-xl-pull-14{position:relative;right:58.33333%}.custom-theme .el-col-xl-push-14{position:relative;left:58.33333%}.custom-theme .el-col-xl-15{width:62.5%}.custom-theme .el-col-xl-offset-15{margin-left:62.5%}.custom-theme .el-col-xl-pull-15{position:relative;right:62.5%}.custom-theme .el-col-xl-push-15{position:relative;left:62.5%}.custom-theme .el-col-xl-16{width:66.66667%}.custom-theme .el-col-xl-offset-16{margin-left:66.66667%}.custom-theme .el-col-xl-pull-16{position:relative;right:66.66667%}.custom-theme .el-col-xl-push-16{position:relative;left:66.66667%}.custom-theme .el-col-xl-17{width:70.83333%}.custom-theme .el-col-xl-offset-17{margin-left:70.83333%}.custom-theme .el-col-xl-pull-17{position:relative;right:70.83333%}.custom-theme .el-col-xl-push-17{position:relative;left:70.83333%}.custom-theme .el-col-xl-18{width:75%}.custom-theme .el-col-xl-offset-18{margin-left:75%}.custom-theme .el-col-xl-pull-18{position:relative;right:75%}.custom-theme .el-col-xl-push-18{position:relative;left:75%}.custom-theme .el-col-xl-19{width:79.16667%}.custom-theme .el-col-xl-offset-19{margin-left:79.16667%}.custom-theme .el-col-xl-pull-19{position:relative;right:79.16667%}.custom-theme .el-col-xl-push-19{position:relative;left:79.16667%}.custom-theme .el-col-xl-20{width:83.33333%}.custom-theme .el-col-xl-offset-20{margin-left:83.33333%}.custom-theme .el-col-xl-pull-20{position:relative;right:83.33333%}.custom-theme .el-col-xl-push-20{position:relative;left:83.33333%}.custom-theme .el-col-xl-21{width:87.5%}.custom-theme .el-col-xl-offset-21{margin-left:87.5%}.custom-theme .el-col-xl-pull-21{position:relative;right:87.5%}.custom-theme .el-col-xl-push-21{position:relative;left:87.5%}.custom-theme .el-col-xl-22{width:91.66667%}.custom-theme .el-col-xl-offset-22{margin-left:91.66667%}.custom-theme .el-col-xl-pull-22{position:relative;right:91.66667%}.custom-theme .el-col-xl-push-22{position:relative;left:91.66667%}.custom-theme .el-col-xl-23{width:95.83333%}.custom-theme .el-col-xl-offset-23{margin-left:95.83333%}.custom-theme .el-col-xl-pull-23{position:relative;right:95.83333%}.custom-theme .el-col-xl-push-23{position:relative;left:95.83333%}.custom-theme .el-col-xl-24{width:100%}.custom-theme .el-col-xl-offset-24{margin-left:100%}.custom-theme .el-col-xl-pull-24{position:relative;right:100%}.custom-theme .el-col-xl-push-24{position:relative;left:100%}}.custom-theme .el-progress{position:relative;line-height:1}.custom-theme .el-progress__text{font-size:14px;color:#5a5e66;display:inline-block;vertical-align:middle;margin-left:10px;line-height:1}.custom-theme .el-progress__text i{vertical-align:middle;display:block}.custom-theme .el-progress--circle{display:inline-block}.custom-theme .el-progress--circle .el-progress__text{position:absolute;top:50%;left:0;width:100%;text-align:center;margin:0;-webkit-transform:translate(0,-50%);transform:translate(0,-50%)}.custom-theme .el-progress--circle .el-progress__text i{vertical-align:middle;display:inline-block}.custom-theme .el-progress--without-text .el-progress__text{display:none}.custom-theme .el-progress--without-text .el-progress-bar{padding-right:0;margin-right:0;display:block}.custom-theme .el-progress--text-inside .el-progress-bar{padding-right:0;margin-right:0}.custom-theme .el-progress.is-success .el-progress-bar__inner{background-color:#409167}.custom-theme .el-progress.is-success .el-progress__text{color:#409167}.custom-theme .el-progress.is-exception .el-progress-bar__inner{background-color:#b3450e}.custom-theme .el-progress.is-exception .el-progress__text{color:#b3450e}.custom-theme .el-progress-bar{padding-right:50px;display:inline-block;vertical-align:middle;width:100%;margin-right:-55px;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-progress-bar__outer{height:6px;border-radius:100px;background-color:#e6ebf5;overflow:hidden;position:relative;vertical-align:middle}.custom-theme .el-progress-bar__inner{position:absolute;left:0;top:0;height:100%;background-color:#262729;text-align:right;border-radius:100px;line-height:1;white-space:nowrap}.custom-theme .el-progress-bar__inner::after{display:inline-block;content:"";height:100%;vertical-align:middle}.custom-theme .el-progress-bar__innerText{display:inline-block;vertical-align:middle;color:#fff;font-size:12px;margin:0 5px}@-webkit-keyframes progress{0%{background-position:0 0}100%{background-position:32px 0}}@keyframes progress{0%{background-position:0 0}100%{background-position:32px 0}}.custom-theme .el-upload{display:inline-block;text-align:center;cursor:pointer}.custom-theme .el-upload__input{display:none}.custom-theme .el-upload__tip{font-size:12px;color:#5a5e66;margin-top:7px}.custom-theme .el-upload iframe{position:absolute;z-index:-1;top:0;left:0;opacity:0}.custom-theme .el-upload--picture-card{background-color:#fbfdff;border:1px dashed #c0ccda;border-radius:6px;-webkit-box-sizing:border-box;box-sizing:border-box;width:148px;height:148px;cursor:pointer;line-height:146px;vertical-align:top}.custom-theme .el-upload--picture-card i{font-size:28px;color:#8c939d}.custom-theme .el-upload--picture-card:hover{border-color:#262729;color:#262729}.custom-theme .el-upload-dragger{background-color:#fff;border:1px dashed #d9d9d9;border-radius:6px;-webkit-box-sizing:border-box;box-sizing:border-box;width:360px;height:180px;text-align:center;cursor:pointer;position:relative;overflow:hidden}.custom-theme .el-upload-dragger .el-icon-upload{font-size:67px;color:#b4bccc;margin:40px 0 16px;line-height:50px}.custom-theme .el-upload-dragger+.el-upload__tip{text-align:center}.custom-theme .el-upload-dragger~.el-upload__files{border-top:1px solid #d8dce5;margin-top:7px;padding-top:5px}.custom-theme .el-upload-dragger .el-upload__text{color:#5a5e66;font-size:14px;text-align:center}.custom-theme .el-upload-dragger .el-upload__text em{color:#262729;font-style:normal}.custom-theme .el-upload-dragger:hover{border-color:#262729}.custom-theme .el-upload-dragger.is-dragover{background-color:rgba(32,159,255,.06);border:2px dashed #262729}.custom-theme .el-upload-list{margin:0;padding:0;list-style:none}.custom-theme .el-upload-list__item{-webkit-transition:all .5s cubic-bezier(.55,0,.1,1);transition:all .5s cubic-bezier(.55,0,.1,1);font-size:14px;color:#5a5e66;line-height:1.8;margin-top:5px;position:relative;-webkit-box-sizing:border-box;box-sizing:border-box;border-radius:4px;width:100%}.custom-theme .el-upload-list__item .el-progress{position:absolute;top:20px;width:100%}.custom-theme .el-upload-list__item .el-progress__text{position:absolute;right:0;top:-13px}.custom-theme .el-upload-list__item .el-progress-bar{margin-right:0;padding-right:0}.custom-theme .el-upload-list__item:first-child{margin-top:10px}.custom-theme .el-upload-list__item .el-icon-upload-success{color:#409167}.custom-theme .el-upload-list__item .el-icon-close{display:none;position:absolute;top:5px;right:5px;cursor:pointer;opacity:.75;color:#5a5e66}.custom-theme .el-upload-list__item .el-icon-close:hover{opacity:1}.custom-theme .el-upload-list__item .el-icon-close-tip{display:none;position:absolute;top:5px;right:0;cursor:pointer;opacity:1;color:#262729;-webkit-transform:translate(15%,0);transform:translate(15%,0)}.custom-theme .el-upload-list__item:hover{background-color:#f5f7fa}.custom-theme .el-upload-list__item:hover .el-icon-close{display:inline-block}.custom-theme .el-upload-list__item:hover .el-progress__text{display:none}.custom-theme .el-upload-list__item.is-success .el-upload-list__item-status-label{display:block}.custom-theme .el-upload-list__item.is-success .el-upload-list__item-name:focus,.custom-theme .el-upload-list__item.is-success .el-upload-list__item-name:hover{color:#262729;cursor:pointer}.custom-theme .el-upload-list__item.is-success:focus .el-icon-close-tip{display:inline-block}.custom-theme .el-upload-list__item.is-success:active,.custom-theme .el-upload-list__item.is-success:focus:not(.focusing){outline-width:0}.custom-theme .el-upload-list__item.is-success:active .el-icon-close-tip,.custom-theme .el-upload-list__item.is-success:focus:not(.focusing) .el-icon-close-tip{display:none}.custom-theme .el-upload-list__item.is-success:focus .el-upload-list__item-status-label,.custom-theme .el-upload-list__item.is-success:hover .el-upload-list__item-status-label{display:none}.custom-theme .el-upload-list.is-disabled .el-upload-list__item:hover .el-upload-list__item-status-label{display:block}.custom-theme .el-upload-list__item-name{color:#5a5e66;display:block;margin-right:40px;overflow:hidden;padding-left:4px;text-overflow:ellipsis;-webkit-transition:color .3s;transition:color .3s;white-space:nowrap}.custom-theme .el-upload-list__item-name [class^=el-icon]{height:100%;margin-right:7px;color:#878d99;line-height:inherit}.custom-theme .el-upload-list__item-status-label{position:absolute;right:5px;top:0;line-height:inherit;display:none}.custom-theme .el-upload-list__item-delete{position:absolute;right:10px;top:0;font-size:12px;color:#5a5e66;display:none}.custom-theme .el-upload-list__item-delete:hover{color:#262729}.custom-theme .el-upload-list--picture-card{margin:0;display:inline;vertical-align:top}.custom-theme .el-upload-list--picture-card .el-upload-list__item{overflow:hidden;background-color:#fff;border:1px solid #c0ccda;border-radius:6px;-webkit-box-sizing:border-box;box-sizing:border-box;width:148px;height:148px;margin:0 8px 8px 0;display:inline-block}.custom-theme .el-upload-list--picture-card .el-upload-list__item .el-icon-check,.custom-theme .el-upload-list--picture-card .el-upload-list__item .el-icon-circle-check{color:#fff}.custom-theme .el-upload-list--picture-card .el-upload-list__item .el-icon-close{display:none}.custom-theme .el-upload-list--picture-card .el-upload-list__item:hover .el-upload-list__item-status-label{display:none}.custom-theme .el-upload-list--picture-card .el-upload-list__item:hover .el-progress__text{display:block}.custom-theme .el-upload-list--picture-card .el-upload-list__item-name{display:none}.custom-theme .el-upload-list--picture-card .el-upload-list__item-thumbnail{width:100%;height:100%}.custom-theme .el-upload-list--picture-card .el-upload-list__item-status-label{position:absolute;right:-15px;top:-6px;width:40px;height:24px;background:#13ce66;text-align:center;-webkit-transform:rotate(45deg);transform:rotate(45deg);-webkit-box-shadow:0 0 1pc 1px rgba(0,0,0,.2);box-shadow:0 0 1pc 1px rgba(0,0,0,.2)}.custom-theme .el-upload-list--picture-card .el-upload-list__item-status-label i{font-size:12px;margin-top:11px;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}.custom-theme .el-upload-list--picture-card .el-upload-list__item-actions{position:absolute;width:100%;height:100%;left:0;top:0;cursor:default;text-align:center;color:#fff;opacity:0;font-size:20px;background-color:rgba(0,0,0,.5);-webkit-transition:opacity .3s;transition:opacity .3s}.custom-theme .el-upload-list--picture-card .el-upload-list__item-actions::after{display:inline-block;content:"";height:100%;vertical-align:middle}.custom-theme .el-upload-list--picture-card .el-upload-list__item-actions span{display:none;cursor:pointer}.custom-theme .el-upload-list--picture-card .el-upload-list__item-actions span+span{margin-left:15px}.custom-theme .el-upload-list--picture-card .el-upload-list__item-actions .el-upload-list__item-delete{position:static;font-size:inherit;color:inherit}.custom-theme .el-upload-list--picture-card .el-upload-list__item-actions:hover{opacity:1}.custom-theme .el-upload-list--picture-card .el-upload-list__item-actions:hover span{display:inline-block}.custom-theme .el-upload-list--picture-card .el-progress{top:50%;left:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);bottom:auto;width:126px}.custom-theme .el-upload-list--picture-card .el-progress .el-progress__text{top:50%}.custom-theme .el-upload-list--picture .el-upload-list__item{overflow:hidden;background-color:#fff;border:1px solid #c0ccda;border-radius:6px;-webkit-box-sizing:border-box;box-sizing:border-box;margin-top:10px;padding:10px 10px 10px 90px;height:92px}.custom-theme .el-upload-list--picture .el-upload-list__item .el-icon-check,.custom-theme .el-upload-list--picture .el-upload-list__item .el-icon-circle-check{color:#fff}.custom-theme .el-upload-list--picture .el-upload-list__item:hover .el-upload-list__item-status-label{background:0 0;-webkit-box-shadow:none;box-shadow:none;top:-2px;right:-12px}.custom-theme .el-upload-list--picture .el-upload-list__item:hover .el-progress__text{display:block}.custom-theme .el-upload-list--picture .el-upload-list__item.is-success .el-upload-list__item-name{line-height:70px;margin-top:0}.custom-theme .el-upload-list--picture .el-upload-list__item.is-success .el-upload-list__item-name i{display:none}.custom-theme .el-upload-list--picture .el-upload-list__item-thumbnail{vertical-align:middle;display:inline-block;width:70px;height:70px;float:left;position:relative;z-index:1;margin-left:-80px}.custom-theme .el-upload-list--picture .el-upload-list__item-name{display:block;margin-top:20px}.custom-theme .el-upload-list--picture .el-upload-list__item-name i{font-size:70px;line-height:1;position:absolute;left:9px;top:10px}.custom-theme .el-upload-list--picture .el-upload-list__item-status-label{position:absolute;right:-17px;top:-7px;width:46px;height:26px;background:#13ce66;text-align:center;-webkit-transform:rotate(45deg);transform:rotate(45deg);-webkit-box-shadow:0 1px 1px #ccc;box-shadow:0 1px 1px #ccc}.custom-theme .el-upload-list--picture .el-upload-list__item-status-label i{font-size:12px;margin-top:12px;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}.custom-theme .el-upload-list--picture .el-progress{position:relative;top:-7px}.custom-theme .el-upload-cover{position:absolute;left:0;top:0;width:100%;height:100%;overflow:hidden;z-index:10;cursor:default}.custom-theme .el-upload-cover::after{display:inline-block;content:"";height:100%;vertical-align:middle}.custom-theme .el-upload-cover img{display:block;width:100%;height:100%}.custom-theme .el-upload-cover__label{position:absolute;right:-15px;top:-6px;width:40px;height:24px;background:#13ce66;text-align:center;-webkit-transform:rotate(45deg);transform:rotate(45deg);-webkit-box-shadow:0 0 1pc 1px rgba(0,0,0,.2);box-shadow:0 0 1pc 1px rgba(0,0,0,.2)}.custom-theme .el-upload-cover__label i{font-size:12px;margin-top:11px;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);color:#fff}.custom-theme .el-upload-cover__progress{display:inline-block;vertical-align:middle;position:static;width:243px}.custom-theme .el-upload-cover__progress+.el-upload__inner{opacity:0}.custom-theme .el-upload-cover__content{position:absolute;top:0;left:0;width:100%;height:100%}.custom-theme .el-upload-cover__interact{position:absolute;bottom:0;left:0;width:100%;height:100%;background-color:rgba(0,0,0,.72);text-align:center}.custom-theme .el-upload-cover__interact .btn{display:inline-block;color:#fff;font-size:14px;cursor:pointer;vertical-align:middle;-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;margin-top:60px}.custom-theme .el-upload-cover__interact .btn i{margin-top:0}.custom-theme .el-upload-cover__interact .btn span{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.custom-theme .el-upload-cover__interact .btn:not(:first-child){margin-left:35px}.custom-theme .el-upload-cover__interact .btn:hover{-webkit-transform:translateY(-13px);transform:translateY(-13px)}.custom-theme .el-upload-cover__interact .btn:hover span{opacity:1}.custom-theme .el-upload-cover__interact .btn i{color:#fff;display:block;font-size:24px;line-height:inherit;margin:0 auto 5px}.custom-theme .el-upload-cover__title{position:absolute;bottom:0;left:0;background-color:#fff;height:36px;width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:400;text-align:left;padding:0 10px;margin:0;line-height:36px;font-size:14px;color:#2d2f33}.custom-theme .el-upload-cover+.el-upload__inner{opacity:0;position:relative;z-index:1}.custom-theme .el-progress{position:relative;line-height:1}.custom-theme .el-progress__text{font-size:14px;color:#5a5e66;display:inline-block;vertical-align:middle;margin-left:10px;line-height:1}.custom-theme .el-progress__text i{vertical-align:middle;display:block}.custom-theme .el-progress--circle{display:inline-block}.custom-theme .el-progress--circle .el-progress__text{position:absolute;top:50%;left:0;width:100%;text-align:center;margin:0;-webkit-transform:translate(0,-50%);transform:translate(0,-50%)}.custom-theme .el-progress--circle .el-progress__text i{vertical-align:middle;display:inline-block}.custom-theme .el-progress--without-text .el-progress__text{display:none}.custom-theme .el-progress--without-text .el-progress-bar{padding-right:0;margin-right:0;display:block}.custom-theme .el-progress--text-inside .el-progress-bar{padding-right:0;margin-right:0}.custom-theme .el-progress.is-success .el-progress-bar__inner{background-color:#409167}.custom-theme .el-progress.is-success .el-progress__text{color:#409167}.custom-theme .el-progress.is-exception .el-progress-bar__inner{background-color:#b3450e}.custom-theme .el-progress.is-exception .el-progress__text{color:#b3450e}.custom-theme .el-progress-bar{padding-right:50px;display:inline-block;vertical-align:middle;width:100%;margin-right:-55px;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-progress-bar__outer{height:6px;border-radius:100px;background-color:#e6ebf5;overflow:hidden;position:relative;vertical-align:middle}.custom-theme .el-progress-bar__inner{position:absolute;left:0;top:0;height:100%;background-color:#262729;text-align:right;border-radius:100px;line-height:1;white-space:nowrap}.custom-theme .el-progress-bar__inner::after{display:inline-block;content:"";height:100%;vertical-align:middle}.custom-theme .el-progress-bar__innerText{display:inline-block;vertical-align:middle;color:#fff;font-size:12px;margin:0 5px}@keyframes progress{0%{background-position:0 0}100%{background-position:32px 0}}.custom-theme .el-time-spinner{width:100%;white-space:nowrap}.custom-theme .el-spinner{display:inline-block;vertical-align:middle}.custom-theme .el-spinner-inner{-webkit-animation:rotate 2s linear infinite;animation:rotate 2s linear infinite;width:50px;height:50px}.custom-theme .el-spinner-inner .path{stroke:#ececec;stroke-linecap:round;-webkit-animation:dash 1.5s ease-in-out infinite;animation:dash 1.5s ease-in-out infinite}@-webkit-keyframes rotate{100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes rotate{100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes dash{0%{stroke-dasharray:1,150;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-35}100%{stroke-dasharray:90,150;stroke-dashoffset:-124}}@keyframes dash{0%{stroke-dasharray:1,150;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-35}100%{stroke-dasharray:90,150;stroke-dashoffset:-124}}.custom-theme .el-message{min-width:380px;-webkit-box-sizing:border-box;box-sizing:border-box;border-radius:4px;border-width:1px;border-style:solid;border-color:#e6ebf5;position:fixed;left:50%;top:20px;-webkit-transform:translateX(-50%);transform:translateX(-50%);background-color:#edf2fc;-webkit-transition:opacity .3s,-webkit-transform .4s;transition:opacity .3s,-webkit-transform .4s;transition:opacity .3s,transform .4s;transition:opacity .3s,transform .4s,-webkit-transform .4s;overflow:hidden;padding:15px 15px 15px 20px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.custom-theme .el-message.is-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.custom-theme .el-message p{margin:0}.custom-theme .el-message--info .el-message__content{color:#0a76a4}.custom-theme .el-message--success{background-color:#ecf4f0;border-color:#d9e9e1}.custom-theme .el-message--success .el-message__content{color:#409167}.custom-theme .el-message--warning{background-color:#f5f6e6;border-color:#ebedce}.custom-theme .el-message--warning .el-message__content{color:#9da408}.custom-theme .el-message--error{background-color:#f7ece7;border-color:#f0dacf}.custom-theme .el-message--error .el-message__content{color:#b3450e}.custom-theme .el-message__icon{margin-right:10px}.custom-theme .el-message__content{padding:0;font-size:14px;line-height:1}.custom-theme .el-message__content:focus{outline-width:0}.custom-theme .el-message__closeBtn{position:absolute;top:50%;right:15px;-webkit-transform:translateY(-50%);transform:translateY(-50%);cursor:pointer;color:#b4bccc;font-size:16px}.custom-theme .el-message__closeBtn:focus{outline-width:0}.custom-theme .el-message__closeBtn:hover{color:#878d99}.custom-theme .el-message .el-icon-success{color:#409167}.custom-theme .el-message .el-icon-error{color:#b3450e}.custom-theme .el-message .el-icon-info{color:#0a76a4}.custom-theme .el-message .el-icon-warning{color:#9da408}.custom-theme .el-message-fade-enter,.custom-theme .el-message-fade-leave-active{opacity:0;-webkit-transform:translate(-50%,-100%);transform:translate(-50%,-100%)}.custom-theme .el-badge{position:relative;vertical-align:middle;display:inline-block}.custom-theme .el-badge__content{background-color:#b3450e;border-radius:10px;color:#fff;display:inline-block;font-size:12px;height:18px;line-height:18px;padding:0 6px;text-align:center;white-space:nowrap;border:1px solid #fff}.custom-theme .el-badge__content.is-fixed{position:absolute;top:0;right:10px;-webkit-transform:translateY(-50%) translateX(100%);transform:translateY(-50%) translateX(100%)}.custom-theme .el-badge__content.is-fixed.is-dot{right:5px}.custom-theme .el-badge__content.is-dot{height:8px;width:8px;padding:0;right:0;border-radius:50%}.custom-theme .el-card{border-radius:4px;border:1px solid #e6ebf5;background-color:#fff;overflow:hidden;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);color:#2d2f33}.custom-theme .el-card__header{padding:18px 20px;border-bottom:1px solid #e6ebf5;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-card__body{padding:20px}.custom-theme .el-rate{height:20px;line-height:1}.custom-theme .el-rate:active,.custom-theme .el-rate:focus{outline-width:0}.custom-theme .el-rate__item{display:inline-block;position:relative;font-size:0;vertical-align:middle}.custom-theme .el-rate__icon{position:relative;display:inline-block;font-size:18px;margin-right:6px;color:#b4bccc;-webkit-transition:.3s;transition:.3s}.custom-theme .el-rate__icon.hover{-webkit-transform:scale(1.15);transform:scale(1.15)}.custom-theme .el-rate__icon .path2{position:absolute;left:0;top:0}.custom-theme .el-rate__decimal{position:absolute;top:0;left:0;display:inline-block;overflow:hidden}.custom-theme .el-rate__text{font-size:14px;vertical-align:middle}.custom-theme .el-steps{display:-webkit-box;display:-ms-flexbox;display:flex}.custom-theme .el-steps--simple{padding:13px 8%;border-radius:4px;background:#f5f7fa}.custom-theme .el-steps--horizontal{white-space:nowrap}.custom-theme .el-steps--vertical{height:100%;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-flow:column;flex-flow:column}.custom-theme .el-step{position:relative;-ms-flex-negative:1;flex-shrink:1}.custom-theme .el-step:last-of-type .el-step__line{display:none}.custom-theme .el-step:last-of-type.is-flex{-ms-flex-preferred-size:auto!important;flex-basis:auto!important;-ms-flex-negative:0;flex-shrink:0;-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0}.custom-theme .el-step:last-of-type .el-step__description,.custom-theme .el-step:last-of-type .el-step__main{padding-right:0}.custom-theme .el-step__head{position:relative;width:100%}.custom-theme .el-step__head.is-process{color:#2d2f33;border-color:#2d2f33}.custom-theme .el-step__head.is-wait{color:#b4bccc;border-color:#b4bccc}.custom-theme .el-step__head.is-success{color:#409167;border-color:#409167}.custom-theme .el-step__head.is-error{color:#b3450e;border-color:#b3450e}.custom-theme .el-step__head.is-finish{color:#262729;border-color:#262729}.custom-theme .el-step__icon{position:relative;z-index:1;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;width:24px;height:24px;font-size:14px;-webkit-box-sizing:border-box;box-sizing:border-box;background:#fff;-webkit-transition:.15s ease-out;transition:.15s ease-out}.custom-theme .el-step__icon.is-text{border-radius:50%;border:2px solid;border-color:inherit}.custom-theme .el-step__icon.is-icon{width:40px}.custom-theme .el-step__icon-inner{display:inline-block;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;text-align:center;font-weight:700;line-height:1;color:inherit}.custom-theme .el-step__icon-inner[class*=el-icon]:not(.is-status){font-size:25px;font-weight:400}.custom-theme .el-step__icon-inner.is-status{-webkit-transform:translateY(1px);transform:translateY(1px)}.custom-theme .el-step__line{position:absolute;border-color:inherit;background-color:#b4bccc}.custom-theme .el-step__line-inner{display:block;border-width:1px;border-style:solid;border-color:inherit;-webkit-transition:.15s ease-out;transition:.15s ease-out;-webkit-box-sizing:border-box;box-sizing:border-box;width:0;height:0}.custom-theme .el-step__main{white-space:normal;text-align:left}.custom-theme .el-step__title{font-size:16px;line-height:38px}.custom-theme .el-step__title.is-process{font-weight:700;color:#2d2f33}.custom-theme .el-step__title.is-wait{color:#b4bccc}.custom-theme .el-step__title.is-success{color:#409167}.custom-theme .el-step__title.is-error{color:#b3450e}.custom-theme .el-step__title.is-finish{color:#262729}.custom-theme .el-step__description{padding-right:10%;margin-top:-5px;font-size:12px;line-height:20px;font-weight:400}.custom-theme .el-step__description.is-process{color:#2d2f33}.custom-theme .el-step__description.is-wait{color:#b4bccc}.custom-theme .el-step__description.is-success{color:#409167}.custom-theme .el-step__description.is-error{color:#b3450e}.custom-theme .el-step__description.is-finish{color:#262729}.custom-theme .el-step.is-horizontal{display:inline-block}.custom-theme .el-step.is-horizontal .el-step__line{height:2px;top:11px;left:0;right:0}.custom-theme .el-step.is-vertical{display:-webkit-box;display:-ms-flexbox;display:flex}.custom-theme .el-step.is-vertical .el-step__head{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;width:24px}.custom-theme .el-step.is-vertical .el-step__main{padding-left:10px;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}.custom-theme .el-step.is-vertical .el-step__title{line-height:24px;padding-bottom:8px}.custom-theme .el-step.is-vertical .el-step__line{width:2px;top:0;bottom:0;left:11px}.custom-theme .el-step.is-vertical .el-step__icon.is-icon{width:24px}.custom-theme .el-step.is-center .el-step__head{text-align:center}.custom-theme .el-step.is-center .el-step__main{text-align:center}.custom-theme .el-step.is-center .el-step__description{padding-left:20%;padding-right:20%}.custom-theme .el-step.is-center .el-step__line{left:50%;right:-50%}.custom-theme .el-step.is-simple{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.custom-theme .el-step.is-simple .el-step__head{width:auto;font-size:0;padding-right:10px}.custom-theme .el-step.is-simple .el-step__icon{background:0 0;width:16px;height:16px;font-size:12px}.custom-theme .el-step.is-simple .el-step__icon-inner[class*=el-icon]:not(.is-status){font-size:18px}.custom-theme .el-step.is-simple .el-step__icon-inner.is-status{-webkit-transform:scale(.8) translateY(1px);transform:scale(.8) translateY(1px)}.custom-theme .el-step.is-simple .el-step__main{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}.custom-theme .el-step.is-simple .el-step__title{font-size:16px;line-height:20px}.custom-theme .el-step.is-simple:not(:last-of-type) .el-step__title{max-width:50%;word-break:break-all}.custom-theme .el-step.is-simple .el-step__arrow{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.custom-theme .el-step.is-simple .el-step__arrow::after,.custom-theme .el-step.is-simple .el-step__arrow::before{content:'';display:inline-block;position:absolute;height:15px;width:1px;background:#b4bccc}.custom-theme .el-step.is-simple .el-step__arrow::before{-webkit-transform:rotate(-45deg) translateY(-4px);transform:rotate(-45deg) translateY(-4px);-webkit-transform-origin:0 0;transform-origin:0 0}.custom-theme .el-step.is-simple .el-step__arrow::after{-webkit-transform:rotate(45deg) translateY(4px);transform:rotate(45deg) translateY(4px);-webkit-transform-origin:100% 100%;transform-origin:100% 100%}.custom-theme .el-step.is-simple:last-of-type .el-step__arrow{display:none}.custom-theme .el-carousel{overflow-x:hidden;position:relative}.custom-theme .el-carousel__container{position:relative;height:300px}.custom-theme .el-carousel__arrow{border:none;outline:0;padding:0;margin:0;height:36px;width:36px;cursor:pointer;-webkit-transition:.3s;transition:.3s;border-radius:50%;background-color:rgba(31,45,61,.11);color:#fff;position:absolute;top:50%;z-index:10;-webkit-transform:translateY(-50%);transform:translateY(-50%);text-align:center;font-size:12px}.custom-theme .el-carousel__arrow--left{left:16px}.custom-theme .el-carousel__arrow--right{right:16px}.custom-theme .el-carousel__arrow:hover{background-color:rgba(31,45,61,.23)}.custom-theme .el-carousel__arrow i{cursor:pointer}.custom-theme .el-carousel__indicators{position:absolute;list-style:none;bottom:0;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%);margin:0;padding:0;z-index:2}.custom-theme .el-carousel__indicators--outside{bottom:26px;text-align:center;position:static;-webkit-transform:none;transform:none}.custom-theme .el-carousel__indicators--outside .el-carousel__indicator:hover button{opacity:.64}.custom-theme .el-carousel__indicators--outside button{background-color:#b4bccc;opacity:.24}.custom-theme .el-carousel__indicators--labels{left:0;right:0;-webkit-transform:none;transform:none;text-align:center}.custom-theme .el-carousel__indicators--labels .el-carousel__button{height:auto;width:auto;padding:2px 18px;font-size:12px}.custom-theme .el-carousel__indicators--labels .el-carousel__indicator{padding:6px 4px}.custom-theme .el-carousel__indicator{display:inline-block;background-color:transparent;padding:12px 4px;cursor:pointer}.custom-theme .el-carousel__indicator:hover button{opacity:.72}.custom-theme .el-carousel__indicator.is-active button{opacity:1}.custom-theme .el-carousel__button{display:block;opacity:.48;width:30px;height:2px;background-color:#fff;border:none;outline:0;padding:0;margin:0;cursor:pointer;-webkit-transition:.3s;transition:.3s}.custom-theme .carousel-arrow-left-enter,.custom-theme .carousel-arrow-left-leave-active{-webkit-transform:translateY(-50%) translateX(-10px);transform:translateY(-50%) translateX(-10px);opacity:0}.custom-theme .carousel-arrow-right-enter,.custom-theme .carousel-arrow-right-leave-active{-webkit-transform:translateY(-50%) translateX(10px);transform:translateY(-50%) translateX(10px);opacity:0}.custom-theme .el-scrollbar{overflow:hidden;position:relative}.custom-theme .el-scrollbar:active>.el-scrollbar__bar,.custom-theme .el-scrollbar:focus>.el-scrollbar__bar,.custom-theme .el-scrollbar:hover>.el-scrollbar__bar{opacity:1;-webkit-transition:opacity 340ms ease-out;transition:opacity 340ms ease-out}.custom-theme .el-scrollbar__wrap{overflow:scroll;height:100%}.custom-theme .el-scrollbar__wrap--hidden-default::-webkit-scrollbar{width:0;height:0}.custom-theme .el-scrollbar__thumb{position:relative;display:block;width:0;height:0;cursor:pointer;border-radius:inherit;background-color:rgba(135,141,153,.3);-webkit-transition:.3s background-color;transition:.3s background-color}.custom-theme .el-scrollbar__thumb:hover{background-color:rgba(135,141,153,.5)}.custom-theme .el-scrollbar__bar{position:absolute;right:2px;bottom:2px;z-index:1;border-radius:4px;opacity:0;-webkit-transition:opacity 120ms ease-out;transition:opacity 120ms ease-out}.custom-theme .el-scrollbar__bar.is-vertical{width:6px;top:2px}.custom-theme .el-scrollbar__bar.is-vertical>div{width:100%}.custom-theme .el-scrollbar__bar.is-horizontal{height:6px;left:2px}.custom-theme .el-scrollbar__bar.is-horizontal>div{height:100%}.custom-theme .el-carousel__item{position:absolute;top:0;left:0;width:100%;height:100%;display:inline-block;overflow:hidden;z-index:0}.custom-theme .el-carousel__item.is-active{z-index:2}.custom-theme .el-carousel__item.is-animating{-webkit-transition:-webkit-transform .4s ease-in-out;transition:-webkit-transform .4s ease-in-out;transition:transform .4s ease-in-out;transition:transform .4s ease-in-out,-webkit-transform .4s ease-in-out}.custom-theme .el-carousel__item--card{width:50%;-webkit-transition:-webkit-transform .4s ease-in-out;transition:-webkit-transform .4s ease-in-out;transition:transform .4s ease-in-out;transition:transform .4s ease-in-out,-webkit-transform .4s ease-in-out}.custom-theme .el-carousel__item--card.is-in-stage{cursor:pointer;z-index:1}.custom-theme .el-carousel__item--card.is-in-stage.is-hover .el-carousel__mask,.custom-theme .el-carousel__item--card.is-in-stage:hover .el-carousel__mask{opacity:.12}.custom-theme .el-carousel__item--card.is-active{z-index:2}.custom-theme .el-carousel__mask{position:absolute;width:100%;height:100%;top:0;left:0;background-color:#fff;opacity:.24;-webkit-transition:.2s;transition:.2s}.custom-theme .el-collapse{border-top:1px solid #e6ebf5;border-bottom:1px solid #e6ebf5}.custom-theme .el-collapse-item__header{height:48px;line-height:48px;background-color:#fff;color:#2d2f33;cursor:pointer;border-bottom:1px solid #e6ebf5;font-size:13px;font-weight:500;-webkit-transition:border-bottom-color .3s;transition:border-bottom-color .3s}.custom-theme .el-collapse-item__header:active,.custom-theme .el-collapse-item__header:focus:not(.focusing){outline-width:0}.custom-theme .el-collapse-item__arrow{margin-right:8px;-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;float:right;line-height:48px;font-weight:300}.custom-theme .el-collapse-item__wrap{will-change:height;background-color:#fff;overflow:hidden;-webkit-box-sizing:border-box;box-sizing:border-box;border-bottom:1px solid #e6ebf5}.custom-theme .el-collapse-item__content{padding-bottom:25px;font-size:13px;color:#2d2f33;line-height:1.769230769230769}.custom-theme .el-collapse-item.is-active .el-collapse-item__header{border-bottom-color:transparent}.custom-theme .el-collapse-item.is-active .el-collapse-item__header .el-collapse-item__arrow{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.custom-theme .el-collapse-item:last-child{margin-bottom:-1px}.custom-theme .el-input{position:relative;font-size:14px;display:inline-block;width:100%}.custom-theme .el-input::-webkit-scrollbar{z-index:11;width:6px}.custom-theme .el-input::-webkit-scrollbar:horizontal{height:6px}.custom-theme .el-input::-webkit-scrollbar-thumb{border-radius:5px;width:6px;background:#b4bccc}.custom-theme .el-input::-webkit-scrollbar-corner{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track-piece{background:#fff;width:6px}.custom-theme .el-input__inner{-webkit-appearance:none;background-color:#fff;background-image:none;border-radius:4px;border:1px solid #d8dce5;-webkit-box-sizing:border-box;box-sizing:border-box;color:#5a5e66;display:inline-block;font-size:inherit;height:40px;line-height:1;outline:0;padding:0 15px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1);width:100%}.custom-theme .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input__inner:hover{border-color:#b4bccc}.custom-theme .el-input__inner:focus{outline:0;border-color:#262729}.custom-theme .el-input__suffix{position:absolute;height:100%;right:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s;pointer-events:none}.custom-theme .el-input__suffix-inner{pointer-events:all}.custom-theme .el-input__prefix{position:absolute;height:100%;left:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s}.custom-theme .el-input__icon{height:100%;width:25px;text-align:center;-webkit-transition:all .3s;transition:all .3s;line-height:40px}.custom-theme .el-input__icon:after{content:'';height:100%;width:0;display:inline-block;vertical-align:middle}.custom-theme .el-input__validateIcon{pointer-events:none}.custom-theme .el-input.is-active .el-input__inner{outline:0;border-color:#262729}.custom-theme .el-input.is-disabled .el-input__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-input.is-disabled .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__icon{cursor:not-allowed}.custom-theme .el-input--suffix .el-input__inner{padding-right:30px}.custom-theme .el-input--prefix .el-input__inner{padding-left:30px}.custom-theme .el-input--medium{font-size:14px}.custom-theme .el-input--medium .el-input__inner{height:36px}.custom-theme .el-input--medium .el-input__icon{line-height:36px}.custom-theme .el-input--small{font-size:13px}.custom-theme .el-input--small .el-input__inner{height:32px}.custom-theme .el-input--small .el-input__icon{line-height:32px}.custom-theme .el-input--mini{font-size:12px}.custom-theme .el-input--mini .el-input__inner{height:28px}.custom-theme .el-input--mini .el-input__icon{line-height:28px}.custom-theme .el-input-group{line-height:normal;display:inline-table;width:100%;border-collapse:separate}.custom-theme .el-input-group>.el-input__inner{vertical-align:middle;display:table-cell}.custom-theme .el-input-group__append,.custom-theme .el-input-group__prepend{background-color:#f5f7fa;color:#0a76a4;vertical-align:middle;display:table-cell;position:relative;border:1px solid #d8dce5;border-radius:4px;padding:0 20px;width:1px;white-space:nowrap}.custom-theme .el-input-group__append:focus,.custom-theme .el-input-group__prepend:focus{outline:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-select,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-select{display:inline-block;margin:-20px}.custom-theme .el-input-group__append button.el-button,.custom-theme .el-input-group__append div.el-select .el-input__inner,.custom-theme .el-input-group__append div.el-select:hover .el-input__inner,.custom-theme .el-input-group__prepend button.el-button,.custom-theme .el-input-group__prepend div.el-select .el-input__inner,.custom-theme .el-input-group__prepend div.el-select:hover .el-input__inner{border-color:transparent;background-color:transparent;color:inherit;border-top:0;border-bottom:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-input,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-input{font-size:inherit}.custom-theme .el-input-group__prepend{border-right:0;border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-input-group__append{border-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--prepend .el-input__inner{border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--append .el-input__inner{border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-textarea{display:inline-block;width:100%;vertical-align:bottom}.custom-theme .el-textarea__inner{display:block;resize:vertical;padding:5px 15px;line-height:1.5;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;font-size:14px;color:#5a5e66;background-color:#fff;background-image:none;border:1px solid #d8dce5;border-radius:4px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}.custom-theme .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:hover{border-color:#b4bccc}.custom-theme .el-textarea__inner:focus{outline:0;border-color:#262729}.custom-theme .el-textarea.is-disabled .el-textarea__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-popper .popper__arrow,.custom-theme .el-popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.custom-theme .el-popper .popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03));filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03))}.custom-theme .el-popper .popper__arrow::after{content:" ";border-width:6px}.custom-theme .el-popper[x-placement^=top]{margin-bottom:12px}.custom-theme .el-popper[x-placement^=top] .popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-top-color:#e6ebf5;border-bottom-width:0}.custom-theme .el-popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-6px;border-top-color:#fff;border-bottom-width:0}.custom-theme .el-popper[x-placement^=bottom]{margin-top:12px}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow{top:-6px;left:50%;margin-right:3px;border-top-width:0;border-bottom-color:#e6ebf5}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-6px;border-top-width:0;border-bottom-color:#fff}.custom-theme .el-popper[x-placement^=right]{margin-left:12px}.custom-theme .el-popper[x-placement^=right] .popper__arrow{top:50%;left:-6px;margin-bottom:3px;border-right-color:#e6ebf5;border-left-width:0}.custom-theme .el-popper[x-placement^=right] .popper__arrow::after{bottom:-6px;left:1px;border-right-color:#fff;border-left-width:0}.custom-theme .el-popper[x-placement^=left]{margin-right:12px}.custom-theme .el-popper[x-placement^=left] .popper__arrow{top:50%;right:-6px;margin-bottom:3px;border-right-width:0;border-left-color:#e6ebf5}.custom-theme .el-popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-6px;margin-left:-6px;border-right-width:0;border-left-color:#fff}.custom-theme .el-cascader{display:inline-block;position:relative;font-size:14px;line-height:40px}.custom-theme .el-cascader .el-input,.custom-theme .el-cascader .el-input__inner{cursor:pointer}.custom-theme .el-cascader .el-input__icon{-webkit-transition:none;transition:none}.custom-theme .el-cascader .el-icon-arrow-down{-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;font-size:14px}.custom-theme .el-cascader .el-icon-arrow-down.is-reverse{-webkit-transform:rotateZ(180deg);transform:rotateZ(180deg)}.custom-theme .el-cascader .el-icon-circle-close{z-index:2;-webkit-transition:color .2s cubic-bezier(.645,.045,.355,1);transition:color .2s cubic-bezier(.645,.045,.355,1)}.custom-theme .el-cascader .el-icon-circle-close:hover{color:#878d99}.custom-theme .el-cascader__clearIcon{z-index:2;position:relative}.custom-theme .el-cascader__label{position:absolute;left:0;top:0;height:100%;padding:0 25px 0 15px;color:#5a5e66;width:100%;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;-webkit-box-sizing:border-box;box-sizing:border-box;cursor:pointer;text-align:left;font-size:inherit}.custom-theme .el-cascader__label span{color:#000}.custom-theme .el-cascader--medium{font-size:14px;line-height:36px}.custom-theme .el-cascader--small{font-size:13px;line-height:32px}.custom-theme .el-cascader--mini{font-size:12px;line-height:28px}.custom-theme .el-cascader.is-disabled .el-cascader__label{z-index:2;color:#b4bccc}.custom-theme .el-cascader-menus{white-space:nowrap;background:#fff;position:absolute;margin:5px 0;z-index:2;border:solid 1px #dfe4ed;border-radius:2px;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.custom-theme .el-cascader-menus .popper__arrow{-webkit-transform:translateX(-400%);transform:translateX(-400%)}.custom-theme .el-cascader-menu{display:inline-block;vertical-align:top;height:204px;overflow:auto;border-right:solid 1px #dfe4ed;background-color:#fff;-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:6px 0;min-width:160px}.custom-theme .el-cascader-menu:last-child{border-right:0}.custom-theme .el-cascader-menu__item{font-size:14px;padding:8px 20px;position:relative;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:#5a5e66;height:34px;line-height:1.5;-webkit-box-sizing:border-box;box-sizing:border-box;cursor:pointer}.custom-theme .el-cascader-menu__item--extensible:after{font-family:element-icons;content:"\e604";font-size:14px;color:#bfcbd9;position:absolute;right:15px}.custom-theme .el-cascader-menu__item.is-disabled{color:#b4bccc;background-color:#fff;cursor:not-allowed}.custom-theme .el-cascader-menu__item.is-disabled:hover{background-color:#fff}.custom-theme .el-cascader-menu__item.is-active{color:#262729}.custom-theme .el-cascader-menu__item:hover{background-color:#f5f7fa}.custom-theme .el-cascader-menu__item.selected{color:#fff;background-color:#f5f7fa}.custom-theme .el-cascader-menu__item__keyword{font-weight:700}.custom-theme .el-cascader-menu--flexible{height:auto;max-height:180px;overflow:auto}.custom-theme .el-cascader-menu--flexible .el-cascader-menu__item{overflow:visible}.custom-theme .el-color-hue-slider{position:relative;-webkit-box-sizing:border-box;box-sizing:border-box;width:280px;height:12px;background-color:red;padding:0 2px}.custom-theme .el-color-hue-slider__bar{position:relative;background:-webkit-gradient(linear,left top,right top,from(red),color-stop(17%,#ff0),color-stop(33%,#0f0),color-stop(50%,#0ff),color-stop(67%,#00f),color-stop(83%,#f0f),to(red));background:linear-gradient(to right,red 0,#ff0 17%,#0f0 33%,#0ff 50%,#00f 67%,#f0f 83%,red 100%);height:100%}.custom-theme .el-color-hue-slider__thumb{position:absolute;cursor:pointer;-webkit-box-sizing:border-box;box-sizing:border-box;left:0;top:0;width:4px;height:100%;border-radius:1px;background:#fff;border:1px solid #f0f0f0;-webkit-box-shadow:0 0 2px rgba(0,0,0,.6);box-shadow:0 0 2px rgba(0,0,0,.6);z-index:1}.custom-theme .el-color-hue-slider.is-vertical{width:12px;height:180px;padding:2px 0}.custom-theme .el-color-hue-slider.is-vertical .el-color-hue-slider__bar{background:-webkit-gradient(linear,left top,left bottom,from(red),color-stop(17%,#ff0),color-stop(33%,#0f0),color-stop(50%,#0ff),color-stop(67%,#00f),color-stop(83%,#f0f),to(red));background:linear-gradient(to bottom,red 0,#ff0 17%,#0f0 33%,#0ff 50%,#00f 67%,#f0f 83%,red 100%)}.custom-theme .el-color-hue-slider.is-vertical .el-color-hue-slider__thumb{left:0;top:0;width:100%;height:4px}.custom-theme .el-color-svpanel{position:relative;width:280px;height:180px}.custom-theme .el-color-svpanel__black,.custom-theme .el-color-svpanel__white{position:absolute;top:0;left:0;right:0;bottom:0}.custom-theme .el-color-svpanel__white{background:-webkit-gradient(linear,left top,right top,from(#fff),to(rgba(255,255,255,0)));background:linear-gradient(to right,#fff,rgba(255,255,255,0))}.custom-theme .el-color-svpanel__black{background:-webkit-gradient(linear,left bottom,left top,from(#000),to(transparent));background:linear-gradient(to top,#000,transparent)}.custom-theme .el-color-svpanel__cursor{position:absolute}.custom-theme .el-color-svpanel__cursor>div{cursor:head;width:4px;height:4px;-webkit-box-shadow:0 0 0 1.5px #fff,inset 0 0 1px 1px rgba(0,0,0,.3),0 0 1px 2px rgba(0,0,0,.4);box-shadow:0 0 0 1.5px #fff,inset 0 0 1px 1px rgba(0,0,0,.3),0 0 1px 2px rgba(0,0,0,.4);border-radius:50%;-webkit-transform:translate(-2px,-2px);transform:translate(-2px,-2px)}.custom-theme .el-color-alpha-slider{position:relative;-webkit-box-sizing:border-box;box-sizing:border-box;width:280px;height:12px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==)}.custom-theme .el-color-alpha-slider__bar{position:relative;background:-webkit-gradient(linear,left top,right top,from(rgba(255,255,255,0)),to(white));background:linear-gradient(to right,rgba(255,255,255,0) 0,#fff 100%);height:100%}.custom-theme .el-color-alpha-slider__thumb{position:absolute;cursor:pointer;-webkit-box-sizing:border-box;box-sizing:border-box;left:0;top:0;width:4px;height:100%;border-radius:1px;background:#fff;border:1px solid #f0f0f0;-webkit-box-shadow:0 0 2px rgba(0,0,0,.6);box-shadow:0 0 2px rgba(0,0,0,.6);z-index:1}.custom-theme .el-color-alpha-slider.is-vertical{width:20px;height:180px}.custom-theme .el-color-alpha-slider.is-vertical .el-color-alpha-slider__bar{background:-webkit-gradient(linear,left top,left bottom,from(rgba(255,255,255,0)),to(white));background:linear-gradient(to bottom,rgba(255,255,255,0) 0,#fff 100%)}.custom-theme .el-color-alpha-slider.is-vertical .el-color-alpha-slider__thumb{left:0;top:0;width:100%;height:4px}.custom-theme .el-color-dropdown{width:300px}.custom-theme .el-color-dropdown__main-wrapper{margin-bottom:6px}.custom-theme .el-color-dropdown__main-wrapper::after{content:"";display:table;clear:both}.custom-theme .el-color-dropdown__btns{margin-top:6px;text-align:right}.custom-theme .el-color-dropdown__value{float:left;line-height:26px;font-size:12px;color:#000;width:160px}.custom-theme .el-color-dropdown__btn{border:1px solid #dcdcdc;color:#333;line-height:24px;border-radius:2px;padding:0 20px;cursor:pointer;background-color:transparent;outline:0;font-size:12px}.custom-theme .el-color-dropdown__btn[disabled]{color:#ccc;cursor:not-allowed}.custom-theme .el-color-dropdown__btn:hover{color:#262729;border-color:#262729}.custom-theme .el-color-dropdown__link-btn{cursor:pointer;color:#262729;text-decoration:none;padding:15px;font-size:12px}.custom-theme .el-color-dropdown__link-btn:hover{color:tint(#262729,20%)}.custom-theme .el-color-picker{display:inline-block;position:relative;line-height:normal;height:40px}.custom-theme .el-color-picker.is-disabled .el-color-picker__trigger{cursor:not-allowed}.custom-theme .el-color-picker--medium{height:36px}.custom-theme .el-color-picker--medium .el-color-picker__trigger{height:36px;width:36px}.custom-theme .el-color-picker--medium .el-color-picker__mask{height:34px;width:34px}.custom-theme .el-color-picker--small{height:32px}.custom-theme .el-color-picker--small .el-color-picker__trigger{height:32px;width:32px}.custom-theme .el-color-picker--small .el-color-picker__mask{height:30px;width:30px}.custom-theme .el-color-picker--small .el-color-picker__empty,.custom-theme .el-color-picker--small .el-color-picker__icon{-webkit-transform:translate3d(-50%,-50%,0) scale(.8);transform:translate3d(-50%,-50%,0) scale(.8)}.custom-theme .el-color-picker--mini{height:28px}.custom-theme .el-color-picker--mini .el-color-picker__trigger{height:28px;width:28px}.custom-theme .el-color-picker--mini .el-color-picker__mask{height:26px;width:26px}.custom-theme .el-color-picker--mini .el-color-picker__empty,.custom-theme .el-color-picker--mini .el-color-picker__icon{-webkit-transform:translate3d(-50%,-50%,0) scale(.8);transform:translate3d(-50%,-50%,0) scale(.8)}.custom-theme .el-color-picker__mask{height:38px;width:38px;border-radius:4px;position:absolute;top:1px;left:1px;z-index:1;cursor:not-allowed;background-color:rgba(255,255,255,.7)}.custom-theme .el-color-picker__trigger{display:inline-block;-webkit-box-sizing:border-box;box-sizing:border-box;height:40px;width:40px;padding:4px;border:1px solid #e6e6e6;border-radius:4px;font-size:0;position:relative;cursor:pointer}.custom-theme .el-color-picker__color{position:relative;display:block;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid #999;border-radius:2px;width:100%;height:100%;text-align:center}.custom-theme .el-color-picker__color.is-alpha{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==)}.custom-theme .el-color-picker__color-inner{position:absolute;left:0;top:0;right:0;bottom:0}.custom-theme .el-color-picker__empty{font-size:12px;color:#999;position:absolute;top:50%;left:50%;-webkit-transform:translate3d(-50%,-50%,0);transform:translate3d(-50%,-50%,0)}.custom-theme .el-color-picker__icon{display:inline-block;position:absolute;width:100%;top:50%;left:50%;-webkit-transform:translate3d(-50%,-50%,0);transform:translate3d(-50%,-50%,0);color:#fff;text-align:center;font-size:12px}.custom-theme .el-color-picker__panel{position:absolute;z-index:10;padding:6px;background-color:#fff;border:1px solid #e6ebf5;border-radius:4px;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.custom-theme .el-input{position:relative;font-size:14px;display:inline-block;width:100%}.custom-theme .el-input::-webkit-scrollbar{z-index:11;width:6px}.custom-theme .el-input::-webkit-scrollbar:horizontal{height:6px}.custom-theme .el-input::-webkit-scrollbar-thumb{border-radius:5px;width:6px;background:#b4bccc}.custom-theme .el-input::-webkit-scrollbar-corner{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track-piece{background:#fff;width:6px}.custom-theme .el-input__inner{-webkit-appearance:none;background-color:#fff;background-image:none;border-radius:4px;border:1px solid #d8dce5;-webkit-box-sizing:border-box;box-sizing:border-box;color:#5a5e66;display:inline-block;font-size:inherit;height:40px;line-height:1;outline:0;padding:0 15px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1);width:100%}.custom-theme .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input__inner:hover{border-color:#b4bccc}.custom-theme .el-input__inner:focus{outline:0;border-color:#262729}.custom-theme .el-input__suffix{position:absolute;height:100%;right:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s;pointer-events:none}.custom-theme .el-input__suffix-inner{pointer-events:all}.custom-theme .el-input__prefix{position:absolute;height:100%;left:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s}.custom-theme .el-input__icon{height:100%;width:25px;text-align:center;-webkit-transition:all .3s;transition:all .3s;line-height:40px}.custom-theme .el-input__icon:after{content:'';height:100%;width:0;display:inline-block;vertical-align:middle}.custom-theme .el-input__validateIcon{pointer-events:none}.custom-theme .el-input.is-active .el-input__inner{outline:0;border-color:#262729}.custom-theme .el-input.is-disabled .el-input__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-input.is-disabled .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__icon{cursor:not-allowed}.custom-theme .el-input--suffix .el-input__inner{padding-right:30px}.custom-theme .el-input--prefix .el-input__inner{padding-left:30px}.custom-theme .el-input--medium{font-size:14px}.custom-theme .el-input--medium .el-input__inner{height:36px}.custom-theme .el-input--medium .el-input__icon{line-height:36px}.custom-theme .el-input--small{font-size:13px}.custom-theme .el-input--small .el-input__inner{height:32px}.custom-theme .el-input--small .el-input__icon{line-height:32px}.custom-theme .el-input--mini{font-size:12px}.custom-theme .el-input--mini .el-input__inner{height:28px}.custom-theme .el-input--mini .el-input__icon{line-height:28px}.custom-theme .el-input-group{line-height:normal;display:inline-table;width:100%;border-collapse:separate}.custom-theme .el-input-group>.el-input__inner{vertical-align:middle;display:table-cell}.custom-theme .el-input-group__append,.custom-theme .el-input-group__prepend{background-color:#f5f7fa;color:#0a76a4;vertical-align:middle;display:table-cell;position:relative;border:1px solid #d8dce5;border-radius:4px;padding:0 20px;width:1px;white-space:nowrap}.custom-theme .el-input-group__append:focus,.custom-theme .el-input-group__prepend:focus{outline:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-select,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-select{display:inline-block;margin:-20px}.custom-theme .el-input-group__append button.el-button,.custom-theme .el-input-group__append div.el-select .el-input__inner,.custom-theme .el-input-group__append div.el-select:hover .el-input__inner,.custom-theme .el-input-group__prepend button.el-button,.custom-theme .el-input-group__prepend div.el-select .el-input__inner,.custom-theme .el-input-group__prepend div.el-select:hover .el-input__inner{border-color:transparent;background-color:transparent;color:inherit;border-top:0;border-bottom:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-input,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-input{font-size:inherit}.custom-theme .el-input-group__prepend{border-right:0;border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-input-group__append{border-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--prepend .el-input__inner{border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--append .el-input__inner{border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-textarea{display:inline-block;width:100%;vertical-align:bottom}.custom-theme .el-textarea__inner{display:block;resize:vertical;padding:5px 15px;line-height:1.5;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;font-size:14px;color:#5a5e66;background-color:#fff;background-image:none;border:1px solid #d8dce5;border-radius:4px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}.custom-theme .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:hover{border-color:#b4bccc}.custom-theme .el-textarea__inner:focus{outline:0;border-color:#262729}.custom-theme .el-textarea.is-disabled .el-textarea__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-button{display:inline-block;line-height:1;white-space:nowrap;cursor:pointer;background:#fff;border:1px solid #d8dce5;border-color:#d8dce5;color:#5a5e66;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;-webkit-transition:.1s;transition:.1s;font-weight:500;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;padding:12px 20px;font-size:14px;border-radius:4px}.custom-theme .el-button+.el-button{margin-left:10px}.custom-theme .el-button.is-round{padding:12px 20px}.custom-theme .el-button:focus,.custom-theme .el-button:hover{color:#262729;border-color:#bebebf;background-color:#e9e9ea}.custom-theme .el-button:active{color:#222325;border-color:#222325;outline:0}.custom-theme .el-button::-moz-focus-inner{border:0}.custom-theme .el-button [class*=el-icon-]+span{margin-left:5px}.custom-theme .el-button.is-plain:focus,.custom-theme .el-button.is-plain:hover{background:#fff;border-color:#262729;color:#262729}.custom-theme .el-button.is-plain:active{background:#fff;border-color:#222325;color:#222325;outline:0}.custom-theme .el-button.is-active{color:#222325;border-color:#222325}.custom-theme .el-button.is-disabled,.custom-theme .el-button.is-disabled:focus,.custom-theme .el-button.is-disabled:hover{color:#b4bccc;cursor:not-allowed;background-image:none;background-color:#fff;border-color:#e6ebf5}.custom-theme .el-button.is-disabled.el-button--text{background-color:transparent}.custom-theme .el-button.is-disabled.is-plain,.custom-theme .el-button.is-disabled.is-plain:focus,.custom-theme .el-button.is-disabled.is-plain:hover{background-color:#fff;border-color:#e6ebf5;color:#b4bccc}.custom-theme .el-button.is-loading{position:relative;pointer-events:none}.custom-theme .el-button.is-loading:before{pointer-events:none;content:'';position:absolute;left:-1px;top:-1px;right:-1px;bottom:-1px;border-radius:inherit;background-color:rgba(255,255,255,.35)}.custom-theme .el-button.is-round{border-radius:20px;padding:12px 23px}.custom-theme .el-button--primary{color:#fff;background-color:#262729;border-color:#262729}.custom-theme .el-button--primary:focus,.custom-theme .el-button--primary:hover{background:#515254;border-color:#515254;color:#fff}.custom-theme .el-button--primary:active{background:#222325;border-color:#222325;color:#fff;outline:0}.custom-theme .el-button--primary.is-active{background:#222325;border-color:#222325;color:#fff}.custom-theme .el-button--primary.is-disabled,.custom-theme .el-button--primary.is-disabled:active,.custom-theme .el-button--primary.is-disabled:focus,.custom-theme .el-button--primary.is-disabled:hover{color:#fff;background-color:#939394;border-color:#939394}.custom-theme .el-button--primary.is-plain{color:#262729;background:#e9e9ea;border-color:#a8a9a9}.custom-theme .el-button--primary.is-plain:focus,.custom-theme .el-button--primary.is-plain:hover{background:#262729;border-color:#262729;color:#fff}.custom-theme .el-button--primary.is-plain:active{background:#222325;border-color:#222325;color:#fff;outline:0}.custom-theme .el-button--primary.is-plain.is-disabled,.custom-theme .el-button--primary.is-plain.is-disabled:active,.custom-theme .el-button--primary.is-plain.is-disabled:focus,.custom-theme .el-button--primary.is-plain.is-disabled:hover{color:#7d7d7f;background-color:#e9e9ea;border-color:#d4d4d4}.custom-theme .el-button--success{color:#fff;background-color:#409167;border-color:#409167}.custom-theme .el-button--success:focus,.custom-theme .el-button--success:hover{background:#66a785;border-color:#66a785;color:#fff}.custom-theme .el-button--success:active{background:#3a835d;border-color:#3a835d;color:#fff;outline:0}.custom-theme .el-button--success.is-active{background:#3a835d;border-color:#3a835d;color:#fff}.custom-theme .el-button--success.is-disabled,.custom-theme .el-button--success.is-disabled:active,.custom-theme .el-button--success.is-disabled:focus,.custom-theme .el-button--success.is-disabled:hover{color:#fff;background-color:#a0c8b3;border-color:#a0c8b3}.custom-theme .el-button--success.is-plain{color:#409167;background:#ecf4f0;border-color:#b3d3c2}.custom-theme .el-button--success.is-plain:focus,.custom-theme .el-button--success.is-plain:hover{background:#409167;border-color:#409167;color:#fff}.custom-theme .el-button--success.is-plain:active{background:#3a835d;border-color:#3a835d;color:#fff;outline:0}.custom-theme .el-button--success.is-plain.is-disabled,.custom-theme .el-button--success.is-plain.is-disabled:active,.custom-theme .el-button--success.is-plain.is-disabled:focus,.custom-theme .el-button--success.is-plain.is-disabled:hover{color:#8cbda4;background-color:#ecf4f0;border-color:#d9e9e1}.custom-theme .el-button--warning{color:#fff;background-color:#9da408;border-color:#9da408}.custom-theme .el-button--warning:focus,.custom-theme .el-button--warning:hover{background:#b1b639;border-color:#b1b639;color:#fff}.custom-theme .el-button--warning:active{background:#8d9407;border-color:#8d9407;color:#fff;outline:0}.custom-theme .el-button--warning.is-active{background:#8d9407;border-color:#8d9407;color:#fff}.custom-theme .el-button--warning.is-disabled,.custom-theme .el-button--warning.is-disabled:active,.custom-theme .el-button--warning.is-disabled:focus,.custom-theme .el-button--warning.is-disabled:hover{color:#fff;background-color:#ced284;border-color:#ced284}.custom-theme .el-button--warning.is-plain{color:#9da408;background:#f5f6e6;border-color:#d8db9c}.custom-theme .el-button--warning.is-plain:focus,.custom-theme .el-button--warning.is-plain:hover{background:#9da408;border-color:#9da408;color:#fff}.custom-theme .el-button--warning.is-plain:active{background:#8d9407;border-color:#8d9407;color:#fff;outline:0}.custom-theme .el-button--warning.is-plain.is-disabled,.custom-theme .el-button--warning.is-plain.is-disabled:active,.custom-theme .el-button--warning.is-plain.is-disabled:focus,.custom-theme .el-button--warning.is-plain.is-disabled:hover{color:#c4c86b;background-color:#f5f6e6;border-color:#ebedce}.custom-theme .el-button--danger{color:#fff;background-color:#b3450e;border-color:#b3450e}.custom-theme .el-button--danger:focus,.custom-theme .el-button--danger:hover{background:#c26a3e;border-color:#c26a3e;color:#fff}.custom-theme .el-button--danger:active{background:#a13e0d;border-color:#a13e0d;color:#fff;outline:0}.custom-theme .el-button--danger.is-active{background:#a13e0d;border-color:#a13e0d;color:#fff}.custom-theme .el-button--danger.is-disabled,.custom-theme .el-button--danger.is-disabled:active,.custom-theme .el-button--danger.is-disabled:focus,.custom-theme .el-button--danger.is-disabled:hover{color:#fff;background-color:#d9a287;border-color:#d9a287}.custom-theme .el-button--danger.is-plain{color:#b3450e;background:#f7ece7;border-color:#e1b59f}.custom-theme .el-button--danger.is-plain:focus,.custom-theme .el-button--danger.is-plain:hover{background:#b3450e;border-color:#b3450e;color:#fff}.custom-theme .el-button--danger.is-plain:active{background:#a13e0d;border-color:#a13e0d;color:#fff;outline:0}.custom-theme .el-button--danger.is-plain.is-disabled,.custom-theme .el-button--danger.is-plain.is-disabled:active,.custom-theme .el-button--danger.is-plain.is-disabled:focus,.custom-theme .el-button--danger.is-plain.is-disabled:hover{color:#d18f6e;background-color:#f7ece7;border-color:#f0dacf}.custom-theme .el-button--info{color:#fff;background-color:#0a76a4;border-color:#0a76a4}.custom-theme .el-button--info:focus,.custom-theme .el-button--info:hover{background:#3b91b6;border-color:#3b91b6;color:#fff}.custom-theme .el-button--info:active{background:#096a94;border-color:#096a94;color:#fff;outline:0}.custom-theme .el-button--info.is-active{background:#096a94;border-color:#096a94;color:#fff}.custom-theme .el-button--info.is-disabled,.custom-theme .el-button--info.is-disabled:active,.custom-theme .el-button--info.is-disabled:focus,.custom-theme .el-button--info.is-disabled:hover{color:#fff;background-color:#85bbd2;border-color:#85bbd2}.custom-theme .el-button--info.is-plain{color:#0a76a4;background:#e7f1f6;border-color:#9dc8db}.custom-theme .el-button--info.is-plain:focus,.custom-theme .el-button--info.is-plain:hover{background:#0a76a4;border-color:#0a76a4;color:#fff}.custom-theme .el-button--info.is-plain:active{background:#096a94;border-color:#096a94;color:#fff;outline:0}.custom-theme .el-button--info.is-plain.is-disabled,.custom-theme .el-button--info.is-plain.is-disabled:active,.custom-theme .el-button--info.is-plain.is-disabled:focus,.custom-theme .el-button--info.is-plain.is-disabled:hover{color:#6cadc8;background-color:#e7f1f6;border-color:#cee4ed}.custom-theme .el-button--medium{padding:10px 20px;font-size:14px;border-radius:4px}.custom-theme .el-button--medium.is-round{padding:10px 20px}.custom-theme .el-button--small{padding:9px 15px;font-size:12px;border-radius:3px}.custom-theme .el-button--small.is-round{padding:9px 15px}.custom-theme .el-button--mini{padding:7px 15px;font-size:12px;border-radius:3px}.custom-theme .el-button--mini.is-round{padding:7px 15px}.custom-theme .el-button--text{border:none;color:#262729;background:0 0;padding-left:0;padding-right:0}.custom-theme .el-button--text:focus,.custom-theme .el-button--text:hover{color:#515254;border-color:transparent;background-color:transparent}.custom-theme .el-button--text:active{color:#222325;border-color:transparent;background-color:transparent}.custom-theme .el-button-group{display:inline-block;vertical-align:middle}.custom-theme .el-button-group::after,.custom-theme .el-button-group::before{display:table;content:""}.custom-theme .el-button-group::after{clear:both}.custom-theme .el-button-group .el-button{float:left;position:relative}.custom-theme .el-button-group .el-button+.el-button{margin-left:0}.custom-theme .el-button-group .el-button:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-button-group .el-button:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-button-group .el-button:not(:first-child):not(:last-child){border-radius:0}.custom-theme .el-button-group .el-button:not(:last-child){margin-right:-1px}.custom-theme .el-button-group .el-button:active,.custom-theme .el-button-group .el-button:focus,.custom-theme .el-button-group .el-button:hover{z-index:1}.custom-theme .el-button-group .el-button.is-active{z-index:1}.custom-theme .el-button-group .el-button--primary:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--primary:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--primary:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--success:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--success:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--success:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--warning:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--warning:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--warning:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--danger:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--danger:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--danger:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--info:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--info:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--info:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-checkbox{color:#5a5e66;font-weight:500;font-size:14px;position:relative;cursor:pointer;display:inline-block;white-space:nowrap;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.custom-theme .el-checkbox.is-bordered{padding:9px 20px 9px 10px;border-radius:4px;border:1px solid #d8dce5}.custom-theme .el-checkbox.is-bordered.is-checked{border-color:#262729}.custom-theme .el-checkbox.is-bordered.is-disabled{border-color:#e6ebf5;cursor:not-allowed}.custom-theme .el-checkbox.is-bordered+.el-checkbox.is-bordered{margin-left:10px}.custom-theme .el-checkbox.is-bordered.el-checkbox--medium{padding:7px 20px 7px 10px;border-radius:4px}.custom-theme .el-checkbox.is-bordered.el-checkbox--medium .el-checkbox__label{line-height:17px;font-size:14px}.custom-theme .el-checkbox.is-bordered.el-checkbox--medium .el-checkbox__inner{height:14px;width:14px}.custom-theme .el-checkbox.is-bordered.el-checkbox--small{padding:3px 15px 7px 10px;border-radius:3px}.custom-theme .el-checkbox.is-bordered.el-checkbox--small .el-checkbox__label{line-height:15px;font-size:12px}.custom-theme .el-checkbox.is-bordered.el-checkbox--small .el-checkbox__inner{height:12px;width:12px}.custom-theme .el-checkbox.is-bordered.el-checkbox--small .el-checkbox__inner::after{height:6px;width:2px}.custom-theme .el-checkbox.is-bordered.el-checkbox--mini{padding:1px 15px 5px 10px;border-radius:3px}.custom-theme .el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__label{line-height:12px;font-size:12px}.custom-theme .el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__inner{height:12px;width:12px}.custom-theme .el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__inner::after{height:6px;width:2px}.custom-theme .el-checkbox__input{white-space:nowrap;cursor:pointer;outline:0;display:inline-block;line-height:1;position:relative;vertical-align:middle}.custom-theme .el-checkbox__input.is-disabled .el-checkbox__inner{background-color:#edf2fc;border-color:#d8dce5;cursor:not-allowed}.custom-theme .el-checkbox__input.is-disabled .el-checkbox__inner::after{cursor:not-allowed;border-color:#b4bccc}.custom-theme .el-checkbox__input.is-disabled .el-checkbox__inner+.el-checkbox__label{cursor:not-allowed}.custom-theme .el-checkbox__input.is-disabled.is-checked .el-checkbox__inner{background-color:#edf2fc;border-color:#d8dce5}.custom-theme .el-checkbox__input.is-disabled.is-checked .el-checkbox__inner::after{border-color:#b4bccc}.custom-theme .el-checkbox__input.is-disabled.is-indeterminate .el-checkbox__inner{background-color:#edf2fc;border-color:#d8dce5}.custom-theme .el-checkbox__input.is-disabled.is-indeterminate .el-checkbox__inner::before{background-color:#b4bccc;border-color:#b4bccc}.custom-theme .el-checkbox__input.is-disabled+span.el-checkbox__label{color:#b4bccc;cursor:not-allowed}.custom-theme .el-checkbox__input.is-checked .el-checkbox__inner{background-color:#262729;border-color:#262729}.custom-theme .el-checkbox__input.is-checked .el-checkbox__inner::after{-webkit-transform:rotate(45deg) scaleY(1);transform:rotate(45deg) scaleY(1)}.custom-theme .el-checkbox__input.is-checked+.el-checkbox__label{color:#262729}.custom-theme .el-checkbox__input.is-focus .el-checkbox__inner{border-color:#262729}.custom-theme .el-checkbox__input.is-indeterminate .el-checkbox__inner{background-color:#262729;border-color:#262729}.custom-theme .el-checkbox__input.is-indeterminate .el-checkbox__inner::before{content:'';position:absolute;display:block;background-color:#fff;height:2px;-webkit-transform:scale(.5);transform:scale(.5);left:0;right:0;top:5px}.custom-theme .el-checkbox__input.is-indeterminate .el-checkbox__inner::after{display:none}.custom-theme .el-checkbox__inner{display:inline-block;position:relative;border:1px solid #d8dce5;border-radius:2px;-webkit-box-sizing:border-box;box-sizing:border-box;width:14px;height:14px;background-color:#fff;z-index:1;-webkit-transition:border-color .25s cubic-bezier(.71,-.46,.29,1.46),background-color .25s cubic-bezier(.71,-.46,.29,1.46);transition:border-color .25s cubic-bezier(.71,-.46,.29,1.46),background-color .25s cubic-bezier(.71,-.46,.29,1.46)}.custom-theme .el-checkbox__inner:hover{border-color:#262729}.custom-theme .el-checkbox__inner::after{-webkit-box-sizing:content-box;box-sizing:content-box;content:"";border:1px solid #fff;border-left:0;border-top:0;height:7px;left:4px;position:absolute;top:1px;-webkit-transform:rotate(45deg) scaleY(0);transform:rotate(45deg) scaleY(0);width:3px;-webkit-transition:-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms;transition:-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms;transition:transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms;transition:transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms,-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms;-webkit-transform-origin:center;transform-origin:center}.custom-theme .el-checkbox__original{opacity:0;outline:0;position:absolute;margin:0;width:0;height:0;left:-999px}.custom-theme .el-checkbox__label{display:inline-block;padding-left:10px;line-height:19px;font-size:14px}.custom-theme .el-checkbox+.el-checkbox{margin-left:30px}.custom-theme .el-checkbox-button{position:relative;display:inline-block}.custom-theme .el-checkbox-button__inner{display:inline-block;line-height:1;font-weight:500;white-space:nowrap;vertical-align:middle;cursor:pointer;background:#fff;border:1px solid #d8dce5;border-left:0;color:#5a5e66;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;position:relative;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;padding:12px 20px;font-size:14px;border-radius:0}.custom-theme .el-checkbox-button__inner.is-round{padding:12px 20px}.custom-theme .el-checkbox-button__inner:hover{color:#262729}.custom-theme .el-checkbox-button__inner [class*=el-icon-]{line-height:.9}.custom-theme .el-checkbox-button__inner [class*=el-icon-]+span{margin-left:5px}.custom-theme .el-checkbox-button__original{opacity:0;outline:0;position:absolute;margin:0;left:-999px}.custom-theme .el-checkbox-button.is-checked .el-checkbox-button__inner{color:#fff;background-color:#262729;border-color:#262729;-webkit-box-shadow:-1px 0 0 0 #7d7d7f;box-shadow:-1px 0 0 0 #7d7d7f}.custom-theme .el-checkbox-button.is-disabled .el-checkbox-button__inner{color:#b4bccc;cursor:not-allowed;background-image:none;background-color:#fff;border-color:#e6ebf5;-webkit-box-shadow:none;box-shadow:none}.custom-theme .el-checkbox-button:first-child .el-checkbox-button__inner{border-left:1px solid #d8dce5;border-radius:4px 0 0 4px;-webkit-box-shadow:none!important;box-shadow:none!important}.custom-theme .el-checkbox-button.is-focus .el-checkbox-button__inner{border-color:#262729}.custom-theme .el-checkbox-button:last-child .el-checkbox-button__inner{border-radius:0 4px 4px 0}.custom-theme .el-checkbox-button--medium .el-checkbox-button__inner{padding:10px 20px;font-size:14px;border-radius:0}.custom-theme .el-checkbox-button--medium .el-checkbox-button__inner.is-round{padding:10px 20px}.custom-theme .el-checkbox-button--small .el-checkbox-button__inner{padding:9px 15px;font-size:12px;border-radius:0}.custom-theme .el-checkbox-button--small .el-checkbox-button__inner.is-round{padding:9px 15px}.custom-theme .el-checkbox-button--mini .el-checkbox-button__inner{padding:7px 15px;font-size:12px;border-radius:0}.custom-theme .el-checkbox-button--mini .el-checkbox-button__inner.is-round{padding:7px 15px}.custom-theme .el-checkbox-group{font-size:0}.custom-theme .el-transfer{font-size:14px}.custom-theme .el-transfer__buttons{display:inline-block;vertical-align:middle;padding:0 30px}.custom-theme .el-transfer__button{display:block;margin:0 auto;padding:10px;border-radius:50%;color:#fff;background-color:#262729;font-size:0}.custom-theme .el-transfer__button.is-with-texts{border-radius:4px}.custom-theme .el-transfer__button.is-disabled{border:1px solid #d8dce5;background-color:#f5f7fa;color:#b4bccc}.custom-theme .el-transfer__button.is-disabled:hover{border:1px solid #d8dce5;background-color:#f5f7fa;color:#b4bccc}.custom-theme .el-transfer__button:first-child{margin-bottom:10px}.custom-theme .el-transfer__button:nth-child(2){margin:0}.custom-theme .el-transfer__button i,.custom-theme .el-transfer__button span{font-size:14px}.custom-theme .el-transfer__button [class*=el-icon-]+span{margin-left:0}.custom-theme .el-transfer-panel{border:1px solid #e6ebf5;border-radius:4px;overflow:hidden;background:#fff;display:inline-block;vertical-align:middle;width:200px;-webkit-box-sizing:border-box;box-sizing:border-box;position:relative}.custom-theme .el-transfer-panel__body{height:246px}.custom-theme .el-transfer-panel__body.is-with-footer{padding-bottom:40px}.custom-theme .el-transfer-panel__list{margin:0;padding:6px 0;list-style:none;height:246px;overflow:auto;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-transfer-panel__list.is-filterable{height:194px;padding-top:0}.custom-theme .el-transfer-panel__item{height:30px;line-height:30px;padding-left:15px;display:block}.custom-theme .el-transfer-panel__item+.el-transfer-panel__item{margin-left:0}.custom-theme .el-transfer-panel__item.el-checkbox{color:#5a5e66}.custom-theme .el-transfer-panel__item:hover{color:#262729}.custom-theme .el-transfer-panel__item.el-checkbox .el-checkbox__label{width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:block;-webkit-box-sizing:border-box;box-sizing:border-box;padding-left:24px;line-height:30px}.custom-theme .el-transfer-panel__item .el-checkbox__input{position:absolute;top:8px}.custom-theme .el-transfer-panel__filter{text-align:center;margin:15px;-webkit-box-sizing:border-box;box-sizing:border-box;display:block;width:auto}.custom-theme .el-transfer-panel__filter .el-input__inner{height:32px;width:100%;font-size:12px;display:inline-block;-webkit-box-sizing:border-box;box-sizing:border-box;border-radius:16px;padding-right:10px;padding-left:30px}.custom-theme .el-transfer-panel__filter .el-input__icon{margin-left:5px}.custom-theme .el-transfer-panel__filter .el-icon-circle-close{cursor:pointer}.custom-theme .el-transfer-panel .el-transfer-panel__header{height:40px;line-height:40px;background:#f5f7fa;margin:0;padding-left:15px;border-bottom:1px solid #e6ebf5;-webkit-box-sizing:border-box;box-sizing:border-box;color:#000}.custom-theme .el-transfer-panel .el-transfer-panel__header .el-checkbox{display:block;line-height:40px}.custom-theme .el-transfer-panel .el-transfer-panel__header .el-checkbox .el-checkbox__label{font-size:16px;color:#2d2f33;font-weight:400}.custom-theme .el-transfer-panel .el-transfer-panel__header .el-checkbox .el-checkbox__label span{position:absolute;right:15px;color:#878d99;font-size:12px;font-weight:400}.custom-theme .el-transfer-panel .el-transfer-panel__footer{height:40px;background:#fff;margin:0;padding:0;border-top:1px solid #e6ebf5;position:absolute;bottom:0;left:0;width:100%;z-index:1}.custom-theme .el-transfer-panel .el-transfer-panel__footer::after{display:inline-block;content:"";height:100%;vertical-align:middle}.custom-theme .el-transfer-panel .el-transfer-panel__footer .el-checkbox{padding-left:20px;color:#5a5e66}.custom-theme .el-transfer-panel .el-transfer-panel__empty{margin:0;height:30px;line-height:30px;padding:6px 15px 0;color:#878d99}.custom-theme .el-transfer-panel .el-checkbox__label{padding-left:8px}.custom-theme .el-transfer-panel .el-checkbox__inner{height:14px;width:14px;border-radius:3px}.custom-theme .el-transfer-panel .el-checkbox__inner::after{height:6px;width:3px;left:4px}.custom-theme .el-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-flex:1;-ms-flex:1;flex:1;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-container.is-vertical{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.custom-theme .el-header{padding:0 20px;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-aside{overflow:auto;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-main{-webkit-box-flex:1;-ms-flex:1;flex:1;overflow:auto;-webkit-box-sizing:border-box;box-sizing:border-box;padding:20px}.custom-theme .el-footer{padding:0 20px;-webkit-box-sizing:border-box;box-sizing:border-box} \ No newline at end of file diff --git a/litemall-admin/src/components/Breadcrumb/index.vue b/litemall-admin/src/components/Breadcrumb/index.vue index 32a8fb694d46c00a36503958ac13dbad0a8540d4..5dc482c0babb6a9d00f19fa770def70df0531ce9 100644 --- a/litemall-admin/src/components/Breadcrumb/index.vue +++ b/litemall-admin/src/components/Breadcrumb/index.vue @@ -1,16 +1,15 @@ diff --git a/litemall-admin/src/components/LangSelect/index.vue b/litemall-admin/src/components/LangSelect/index.vue deleted file mode 100644 index 31cc80d0fd664d2a0c36adc20c96f2e4163a8a79..0000000000000000000000000000000000000000 --- a/litemall-admin/src/components/LangSelect/index.vue +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - diff --git a/litemall-admin/src/components/Notice/index.vue b/litemall-admin/src/components/Notice/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..1b7499bede3d904f8fa8d1da9804f3e81c570b29 --- /dev/null +++ b/litemall-admin/src/components/Notice/index.vue @@ -0,0 +1,54 @@ + + + + + diff --git a/litemall-admin/src/components/Screenfull/index.vue b/litemall-admin/src/components/Screenfull/index.vue index 4cdcb568fd59514392c7534f83aa107ba87acf00..3a3a03db6589d8d813f975ae1275c6017520be31 100644 --- a/litemall-admin/src/components/Screenfull/index.vue +++ b/litemall-admin/src/components/Screenfull/index.vue @@ -1,29 +1,6 @@ @@ -55,7 +32,7 @@ export default { click() { if (!screenfull.enabled) { this.$message({ - message: 'you browser can not work', + message: '浏览器不支持全屏', type: 'warning' }) return false @@ -67,12 +44,12 @@ export default { diff --git a/litemall-admin/src/components/SizeSelect/index.vue b/litemall-admin/src/components/SizeSelect/index.vue index 0e428a23e2c21ee64ca7d65962699c74a7be44e1..2b4ab9f56a92a5cf5026c5ae8719c27a13b700a7 100644 --- a/litemall-admin/src/components/SizeSelect/index.vue +++ b/litemall-admin/src/components/SizeSelect/index.vue @@ -1,7 +1,7 @@