From 56035ff7d2bd6ad206702b7c718a2eaa013442ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=93=E5=B7=9D=E6=B1=9F?= <3179133204@qq.com> Date: Mon, 17 Apr 2023 16:07:39 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AC=AC=E4=B8=80=E6=AC=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/FUNDING.yml | 1 + .gitignore | 46 + LICENSE | 20 + README.md | 102 + bin/clean.bat | 12 + bin/package.bat | 12 + bin/run.bat | 14 + cs_test/pom.xml | 20 + .../ruoyi/cs/controller/CsTestController.java | 128 + .../main/java/com/ruoyi/cs/domain/CsTest.java | 66 + .../com/ruoyi/cs/mapper/CsTestMapper.java | 62 + .../com/ruoyi/cs/service/ICsTestService.java | 62 + .../cs/service/impl/CsTestServiceImpl.java | 95 + .../main/resources/mapper/CsTestMapper.xml | 62 + .../main/resources/templates/cs/test/add.html | 37 + .../resources/templates/cs/test/edit.html | 38 + .../resources/templates/cs/test/test.html | 94 + cs_test1/pom.xml | 20 + .../system/controller/CsTest1Controller.java | 127 + .../java/com/ruoyi/system/domain/CsTest1.java | 65 + .../ruoyi/system/mapper/CsTest1Mapper.java | 61 + .../ruoyi/system/service/ICsTest1Service.java | 61 + .../service/impl/CsTest1ServiceImpl.java | 94 + .../resources/mapper/system/CsTest1Mapper.xml | 62 + .../resources/templates/system/test1/add.html | 37 + .../templates/system/test1/edit.html | 38 + .../templates/system/test1/test1.html | 94 + doc/若依环境使用手册.docx | Bin 0 -> 426497 bytes pom.xml | 256 + ruoyi-admin/pom.xml | 144 + .../main/java/com/ruoyi/RuoYiApplication.java | 30 + .../com/ruoyi/RuoYiServletInitializer.java | 18 + .../controller/common/CommonController.java | 166 + .../demo/controller/DemoDialogController.java | 98 + .../demo/controller/DemoFormController.java | 399 + .../demo/controller/DemoIconController.java | 35 + .../controller/DemoOperateController.java | 326 + .../demo/controller/DemoReportController.java | 53 + .../demo/controller/DemoTableController.java | 846 ++ .../controller/demo/domain/CustomerModel.java | 116 + .../controller/demo/domain/GoodsModel.java | 99 + .../demo/domain/UserOperateModel.java | 149 + .../controller/monitor/CacheController.java | 90 + .../controller/monitor/DruidController.java | 26 + .../controller/monitor/ServerController.java | 31 + .../monitor/SysLogininforController.java | 94 + .../monitor/SysOperlogController.java | 90 + .../monitor/SysUserOnlineController.java | 88 + .../system/SysCaptchaController.java | 92 + .../system/SysConfigController.java | 157 + .../controller/system/SysDeptController.java | 187 + .../system/SysDictDataController.java | 121 + .../system/SysDictTypeController.java | 188 + .../controller/system/SysIndexController.java | 178 + .../controller/system/SysLoginController.java | 82 + .../controller/system/SysMenuController.java | 197 + .../system/SysNoticeController.java | 113 + .../controller/system/SysPostController.java | 162 + .../system/SysProfileController.java | 181 + .../system/SysRegisterController.java | 46 + .../controller/system/SysRoleController.java | 322 + .../controller/system/SysUserController.java | 334 + .../web/controller/tool/BuildController.java | 26 + .../controller/tool/SwaggerController.java | 24 + .../web/controller/tool/TestController.java | 183 + .../ruoyi/web/core/config/SwaggerConfig.java | 67 + .../src/main/resources/application-druid.yml | 61 + .../src/main/resources/application.yml | 142 + ruoyi-admin/src/main/resources/banner.txt | 24 + .../main/resources/ehcache/ehcache-shiro.xml | 91 + ruoyi-admin/src/main/resources/logback.xml | 93 + .../main/resources/mybatis/mybatis-config.xml | 20 + .../ajax/libs/beautifyhtml/beautifyhtml.js | 617 + .../ajax/libs/blockUI/jquery.blockUI.js | 620 + .../libs/bootstrap-fileinput/fileinput.css | 688 ++ .../libs/bootstrap-fileinput/fileinput.js | 6681 ++++++++++ .../bootstrap-fileinput/fileinput.min.css | 13 + .../libs/bootstrap-fileinput/fileinput.min.js | 11 + .../libs/bootstrap-fileinput/loading-sm.gif | Bin 0 -> 2670 bytes .../ajax/libs/bootstrap-fileinput/loading.gif | Bin 0 -> 847 bytes .../bootstrap-select/bootstrap-select.css | 459 + .../libs/bootstrap-select/bootstrap-select.js | 3247 +++++ .../bootstrap-select/bootstrap-select.min.css | 6 + .../bootstrap-select/bootstrap-select.min.js | 8 + .../bootstrap-table/bootstrap-table.min.css | 6 + .../bootstrap-table/bootstrap-table.min.js | 6 + .../bootstrap-table-auto-refresh.js | 95 + .../columns/bootstrap-table-fixed-columns.js | 5 + .../bootstrap-table-custom-view.js | 109 + .../editable/bootstrap-editable.css | 663 + .../editable/bootstrap-editable.min.js | 7 + .../editable/bootstrap-table-editable.js | 187 + .../extensions/editable/clear.png | Bin 0 -> 244 bytes .../extensions/editable/loading.gif | Bin 0 -> 1849 bytes .../export/bootstrap-table-export.js | 337 + .../extensions/export/tableExport.min.js | 92 + .../mobile/bootstrap-table-mobile.js | 124 + .../extensions/print/bootstrap-table-print.js | 285 + .../bootstrap-table-reorder-columns.js | 213 + .../reorder-columns/jquery.dragtable.js | 22 + .../bootstrap-table-reorder-rows.js | 118 + .../reorder-rows/jquery.tablednd.js | 603 + .../resizable/bootstrap-table-resizable.js | 69 + .../resizable/jquery.resizableColumns.min.js | 8 + .../extensions/tree/bootstrap-table-tree.js | 1060 ++ .../tree/bootstrap-table-tree.min.js | 5 + .../locale/bootstrap-table-zh-CN.js | 109 + .../locale/bootstrap-table-zh-CN.min.js | 1 + .../static/ajax/libs/cropper/cropper.css | 304 + .../static/ajax/libs/cropper/cropper.js | 3631 ++++++ .../static/ajax/libs/cropper/cropper.min.css | 9 + .../static/ajax/libs/cropper/cropper.min.js | 10 + .../ajax/libs/cxselect/jquery.cxselect.js | 406 + .../ajax/libs/cxselect/jquery.cxselect.min.js | 11 + .../datapicker/bootstrap-datetimepicker.css | 418 + .../datapicker/bootstrap-datetimepicker.js | 1978 +++ .../bootstrap-datetimepicker.min.css | 9 + .../bootstrap-datetimepicker.min.js | 1 + .../duallistbox/bootstrap-duallistbox.css | 86 + .../libs/duallistbox/bootstrap-duallistbox.js | 844 ++ .../duallistbox/bootstrap-duallistbox.min.css | 9 + .../duallistbox/bootstrap-duallistbox.min.js | 10 + .../static/ajax/libs/flot/curvedLines.js | 315 + .../static/ajax/libs/flot/jquery.flot.js | 2599 ++++ .../static/ajax/libs/flot/jquery.flot.pie.js | 750 ++ .../ajax/libs/flot/jquery.flot.resize.js | 60 + .../ajax/libs/flot/jquery.flot.spline.js | 212 + .../ajax/libs/flot/jquery.flot.symbol.js | 71 + .../ajax/libs/flot/jquery.flot.tooltip.min.js | 12 + .../ajax/libs/fullscreen/jquery.fullscreen.js | 182 + .../ajax/libs/highlight/default.min.css | 79 + .../ajax/libs/highlight/highlight.min.js | 1102 ++ .../static/ajax/libs/iCheck/custom.css | 72 + .../static/ajax/libs/iCheck/green-login.png | Bin 0 -> 3785 bytes .../static/ajax/libs/iCheck/green.png | Bin 0 -> 20818 bytes .../static/ajax/libs/iCheck/green@2x.png | Bin 0 -> 7708 bytes .../static/ajax/libs/iCheck/icheck.min.js | 11 + .../ajax/libs/jasny/jasny-bootstrap.css | 620 + .../static/ajax/libs/jasny/jasny-bootstrap.js | 1025 ++ .../ajax/libs/jasny/jasny-bootstrap.min.css | 7 + .../ajax/libs/jasny/jasny-bootstrap.min.js | 6 + .../jquery-layout/jquery.layout-latest.css | 1 + .../jquery-layout/jquery.layout-latest.js | 2 + .../3.5/css/default/img/diy/1_close.png | Bin 0 -> 601 bytes .../3.5/css/default/img/diy/1_open.png | Bin 0 -> 580 bytes .../3.5/css/default/img/diy/2.png | Bin 0 -> 570 bytes .../3.5/css/default/img/diy/3.png | Bin 0 -> 762 bytes .../3.5/css/default/img/diy/4.png | Bin 0 -> 399 bytes .../3.5/css/default/img/diy/5.png | Bin 0 -> 710 bytes .../3.5/css/default/img/diy/6.png | Bin 0 -> 432 bytes .../3.5/css/default/img/diy/7.png | Bin 0 -> 534 bytes .../3.5/css/default/img/diy/8.png | Bin 0 -> 529 bytes .../3.5/css/default/img/diy/9.png | Bin 0 -> 467 bytes .../3.5/css/default/img/line_conn.gif | Bin 0 -> 45 bytes .../3.5/css/default/img/loading.gif | Bin 0 -> 381 bytes .../3.5/css/default/img/zTreeStandard.gif | Bin 0 -> 5564 bytes .../3.5/css/default/img/zTreeStandard.png | Bin 0 -> 11173 bytes .../3.5/css/default/zTreeStyle.css | 102 + .../3.5/css/metro/img/line_conn.gif | Bin 0 -> 45 bytes .../3.5/css/metro/img/line_conn.png | Bin 0 -> 933 bytes .../3.5/css/metro/img/loading.gif | Bin 0 -> 381 bytes .../jquery-ztree/3.5/css/metro/img/metro.gif | Bin 0 -> 3981 bytes .../jquery-ztree/3.5/css/metro/img/metro.png | Bin 0 -> 7273 bytes .../jquery-ztree/3.5/css/metro/zTreeStyle.css | 107 + .../3.5/css/simple/img/left_menu.gif | Bin 0 -> 216 bytes .../3.5/css/simple/img/left_menu.png | Bin 0 -> 421 bytes .../3.5/css/simple/img/line_conn.gif | Bin 0 -> 45 bytes .../3.5/css/simple/img/loading.gif | Bin 0 -> 381 bytes .../3.5/css/simple/img/zTreeStandard.gif | Bin 0 -> 5564 bytes .../3.5/css/simple/img/zTreeStandard.png | Bin 0 -> 11173 bytes .../3.5/css/simple/zTreeStyle.css | 118 + .../3.5/js/jquery.ztree.all-3.5.js | 3820 ++++++ .../3.5/js/jquery.ztree.core-3.5.js | 1650 +++ .../3.5/js/jquery.ztree.excheck-3.5.js | 624 + .../3.5/js/jquery.ztree.exedit-3.5.js | 1178 ++ .../3.5/js/jquery.ztree.exhide-3.5.js | 366 + .../ajax/libs/jquery-ztree/3.5/log v3.x.txt | 170 + .../ajax/libs/jsonview/jquery.jsonview.css | 50 + .../ajax/libs/jsonview/jquery.jsonview.js | 250 + .../static/ajax/libs/layer/layer.min.js | 2 + .../libs/layer/theme/default/icon-ext.png | Bin 0 -> 5911 bytes .../ajax/libs/layer/theme/default/icon.png | Bin 0 -> 11493 bytes .../ajax/libs/layer/theme/default/layer.css | 184 + .../libs/layer/theme/default/loading-0.gif | Bin 0 -> 5793 bytes .../libs/layer/theme/default/loading-1.gif | Bin 0 -> 701 bytes .../libs/layer/theme/default/loading-2.gif | Bin 0 -> 1787 bytes .../ajax/libs/layer/theme/moon/default.png | Bin 0 -> 7563 bytes .../ajax/libs/layer/theme/moon/style.css | 138 + .../modules/laydate/default/font/iconfont.eot | Bin 0 -> 2456 bytes .../modules/laydate/default/font/iconfont.svg | 45 + .../modules/laydate/default/font/iconfont.ttf | Bin 0 -> 2272 bytes .../laydate/default/font/iconfont.woff | Bin 0 -> 1492 bytes .../css/modules/laydate/default/laydate.css | 2 + .../static/ajax/libs/layui/layui.min.js | 2 + .../static/ajax/libs/layui/modules/laydate.js | 2 + .../libs/report/echarts/echarts-all.min.js | 52 + .../libs/report/peity/jquery.peity.min.js | 13 + .../report/sparkline/jquery.sparkline.min.js | 5 + .../libs/select2/select2-bootstrap.min.css | 7 + .../static/ajax/libs/select2/select2.css | 481 + .../static/ajax/libs/select2/select2.js | 6108 +++++++++ .../static/ajax/libs/select2/select2.min.css | 1 + .../static/ajax/libs/select2/select2.min.js | 8 + .../smartwizard/jquery.smartWizard.min.js | 13 + .../libs/smartwizard/smart_wizard_all.min.css | 11 + .../ajax/libs/suggest/bootstrap-suggest.js | 1180 ++ .../libs/suggest/bootstrap-suggest.min.js | 9 + .../ajax/libs/summernote/font/summernote.eot | Bin 0 -> 12072 bytes .../ajax/libs/summernote/font/summernote.ttf | Bin 0 -> 11896 bytes .../ajax/libs/summernote/font/summernote.woff | Bin 0 -> 7428 bytes .../libs/summernote/font/summernote.woff2 | Bin 0 -> 6156 bytes .../ajax/libs/summernote/summernote-zh-CN.js | 155 + .../ajax/libs/summernote/summernote.css | 13 + .../static/ajax/libs/summernote/summernote.js | 10227 ++++++++++++++++ .../ajax/libs/summernote/summernote.min.js | 2 + .../libs/typeahead/bootstrap-typeahead.js | 774 ++ .../libs/typeahead/bootstrap-typeahead.min.js | 1 + .../libs/validate/additional-methods.min.js | 4 + .../libs/validate/jquery.validate.extend.js | 185 + .../ajax/libs/validate/jquery.validate.min.js | 4 + .../static/ajax/libs/validate/messages_zh.js | 25 + .../main/resources/static/css/animate.min.css | 12 + .../resources/static/css/bootstrap.min.css | 5 + .../resources/static/css/font-awesome.min.css | 4 + .../static/css/jquery.contextMenu.min.css | 15 + .../src/main/resources/static/css/login.css | 161 + .../main/resources/static/css/login.min.css | 1 + .../src/main/resources/static/css/skins.css | 1028 ++ .../src/main/resources/static/css/style.css | 7052 +++++++++++ .../main/resources/static/css/style.min.css | 1 + .../resources/static/css/zen-checkbox.css | 149 + .../src/main/resources/static/favicon.ico | Bin 0 -> 16958 bytes .../src/main/resources/static/file/rml.txt | 1 + .../resources/static/fonts/FontAwesome.otf | Bin 0 -> 134808 bytes .../static/fonts/Simple-Line-Icons.woff2 | Bin 0 -> 30064 bytes .../static/fonts/fontawesome-webfont.eot | Bin 0 -> 165742 bytes .../static/fonts/fontawesome-webfont.svg | 2671 ++++ .../static/fonts/fontawesome-webfont.ttf | Bin 0 -> 165548 bytes .../static/fonts/fontawesome-webfont.woff | Bin 0 -> 98024 bytes .../static/fonts/fontawesome-webfont.woff2 | Bin 0 -> 77160 bytes .../fonts/glyphicons-halflings-regular.eot | Bin 0 -> 20127 bytes .../fonts/glyphicons-halflings-regular.svg | 288 + .../fonts/glyphicons-halflings-regular.ttf | Bin 0 -> 45404 bytes .../fonts/glyphicons-halflings-regular.woff | Bin 0 -> 23424 bytes .../fonts/glyphicons-halflings-regular.woff2 | Bin 0 -> 18028 bytes .../main/resources/static/fonts/zenicon.woff | Bin 0 -> 84992 bytes .../src/main/resources/static/html/ie.html | 46 + .../resources/static/i18n/messages.properties | 37 + .../src/main/resources/static/img/blue.png | Bin 0 -> 4088 bytes .../resources/static/img/loading-upload.gif | Bin 0 -> 1688 bytes .../src/main/resources/static/img/loading.gif | Bin 0 -> 2538 bytes .../src/main/resources/static/img/locked.png | Bin 0 -> 1132 bytes .../resources/static/img/login-background.jpg | Bin 0 -> 142718 bytes .../src/main/resources/static/img/pay.png | Bin 0 -> 140720 bytes .../src/main/resources/static/img/profile.jpg | Bin 0 -> 81131 bytes .../main/resources/static/img/progress.png | Bin 0 -> 1269 bytes .../src/main/resources/static/img/qr_code.png | Bin 0 -> 8602 bytes .../src/main/resources/static/img/user.png | Bin 0 -> 1106 bytes .../main/resources/static/js/bootstrap.min.js | 6 + .../src/main/resources/static/js/cron.js | 926 ++ .../static/js/jquery-ui-1.10.4.min.js | 12 + .../static/js/jquery.contextMenu.min.js | 1 + .../static/js/jquery.i18n.properties.min.js | 13 + .../main/resources/static/js/jquery.min.js | 2 + .../main/resources/static/js/jquery.tmpl.js | 492 + .../js/plugins/metisMenu/jquery.metisMenu.js | 10 + .../slimscroll/jquery.slimscroll.min.js | 8 + .../main/resources/static/js/resize-tabs.js | 1 + .../src/main/resources/static/js/three.min.js | 767 ++ .../src/main/resources/static/ruoyi.png | Bin 0 -> 5645 bytes .../main/resources/static/ruoyi/css/ry-ui.css | 1187 ++ .../src/main/resources/static/ruoyi/index.js | 654 + .../main/resources/static/ruoyi/js/common.js | 580 + .../main/resources/static/ruoyi/js/ry-ui.js | 1766 +++ .../src/main/resources/static/ruoyi/login.js | 95 + .../main/resources/static/ruoyi/register.js | 82 + .../templates/demo/form/autocomplete.html | 323 + .../resources/templates/demo/form/basic.html | 593 + .../resources/templates/demo/form/button.html | 620 + .../resources/templates/demo/form/cards.html | 319 + .../templates/demo/form/cxselect.html | 161 + .../templates/demo/form/datetime.html | 236 + .../templates/demo/form/duallistbox.html | 65 + .../resources/templates/demo/form/grid.html | 432 + .../templates/demo/form/invoice.html | 122 + .../resources/templates/demo/form/jasny.html | 118 + .../templates/demo/form/labels_tips.html | 237 + .../templates/demo/form/localrefresh.html | 61 + .../templates/demo/form/progress_bars.html | 91 + .../resources/templates/demo/form/select.html | 148 + .../templates/demo/form/sortable.html | 198 + .../templates/demo/form/summernote.html | 93 + .../templates/demo/form/tabs_panels.html | 353 + .../templates/demo/form/timeline.html | 113 + .../resources/templates/demo/form/upload.html | 75 + .../templates/demo/form/validate.html | 193 + .../resources/templates/demo/form/wizard.html | 339 + .../templates/demo/icon/fontawesome.html | 1054 ++ .../templates/demo/icon/glyphicons.html | 1364 +++ .../templates/demo/modal/dialog.html | 215 + .../resources/templates/demo/modal/form.html | 102 + .../resources/templates/demo/modal/layer.html | 288 + .../resources/templates/demo/modal/table.html | 124 + .../templates/demo/modal/table/check.html | 86 + .../templates/demo/modal/table/frame1.html | 53 + .../templates/demo/modal/table/frame2.html | 24 + .../templates/demo/modal/table/parent.html | 102 + .../templates/demo/modal/table/radio.html | 86 + .../resources/templates/demo/operate/add.html | 78 + .../templates/demo/operate/detail.html | 71 + .../templates/demo/operate/edit.html | 79 + .../templates/demo/operate/other.html | 77 + .../templates/demo/operate/table.html | 125 + .../templates/demo/report/echarts.html | 1264 ++ .../templates/demo/report/metrics.html | 478 + .../templates/demo/report/peity.html | 206 + .../templates/demo/report/sparkline.html | 232 + .../templates/demo/table/asynTree.html | 85 + .../templates/demo/table/button.html | 92 + .../resources/templates/demo/table/child.html | 116 + .../resources/templates/demo/table/curd.html | 178 + .../templates/demo/table/customView.html | 122 + .../resources/templates/demo/table/data.html | 76 + .../templates/demo/table/detail.html | 86 + .../templates/demo/table/dynamicColumns.html | 123 + .../templates/demo/table/editable.html | 128 + .../resources/templates/demo/table/event.html | 133 + .../templates/demo/table/export.html | 85 + .../templates/demo/table/exportSelected.html | 120 + .../templates/demo/table/fixedColumns.html | 145 + .../templates/demo/table/footer.html | 95 + .../templates/demo/table/groupHeader.html | 80 + .../templates/demo/table/headerStyle.html | 91 + .../resources/templates/demo/table/image.html | 79 + .../resources/templates/demo/table/multi.html | 224 + .../resources/templates/demo/table/other.html | 106 + .../templates/demo/table/pageGo.html | 77 + .../templates/demo/table/params.html | 158 + .../resources/templates/demo/table/print.html | 131 + .../templates/demo/table/refresh.html | 79 + .../templates/demo/table/remember.html | 86 + .../templates/demo/table/reorderColumns.html | 84 + .../templates/demo/table/reorderRows.html | 91 + .../templates/demo/table/resizable.html | 78 + .../templates/demo/table/search.html | 202 + .../templates/demo/table/subdata.html | 242 + .../main/resources/templates/error/404.html | 27 + .../main/resources/templates/error/500.html | 28 + .../resources/templates/error/service.html | 20 + .../resources/templates/error/unauth.html | 28 + .../src/main/resources/templates/include.html | 222 + .../resources/templates/index-topnav.html | 445 + .../src/main/resources/templates/index.html | 379 + .../src/main/resources/templates/lock.html | 208 + .../src/main/resources/templates/login.html | 82 + .../src/main/resources/templates/main.html | 1626 +++ .../src/main/resources/templates/main_v1.html | 336 + .../templates/monitor/cache/cache.html | 184 + .../monitor/logininfor/logininfor.html | 146 + .../templates/monitor/online/online.html | 152 + .../templates/monitor/operlog/detail.html | 69 + .../templates/monitor/operlog/operlog.html | 185 + .../templates/monitor/server/server.html | 258 + .../main/resources/templates/register.html | 80 + .../src/main/resources/templates/skin.html | 165 + .../templates/system/config/add.html | 79 + .../templates/system/config/config.html | 144 + .../templates/system/config/edit.html | 83 + .../resources/templates/system/dept/add.html | 130 + .../resources/templates/system/dept/dept.html | 112 + .../resources/templates/system/dept/edit.html | 138 + .../resources/templates/system/dept/tree.html | 52 + .../templates/system/dict/data/add.html | 100 + .../templates/system/dict/data/data.html | 151 + .../templates/system/dict/data/edit.html | 101 + .../templates/system/dict/type/add.html | 75 + .../templates/system/dict/type/edit.html | 79 + .../templates/system/dict/type/tree.html | 42 + .../templates/system/dict/type/type.html | 148 + .../resources/templates/system/menu/add.html | 199 + .../resources/templates/system/menu/edit.html | 227 + .../resources/templates/system/menu/icon.html | 928 ++ .../resources/templates/system/menu/menu.html | 161 + .../resources/templates/system/menu/tree.html | 49 + .../templates/system/notice/add.html | 98 + .../templates/system/notice/edit.html | 103 + .../templates/system/notice/notice.html | 117 + .../resources/templates/system/post/add.html | 97 + .../resources/templates/system/post/edit.html | 104 + .../resources/templates/system/post/post.html | 120 + .../resources/templates/system/role/add.html | 174 + .../templates/system/role/authUser.html | 142 + .../templates/system/role/dataScope.html | 137 + .../resources/templates/system/role/edit.html | 183 + .../resources/templates/system/role/role.html | 169 + .../templates/system/role/selectUser.html | 113 + .../resources/templates/system/user/add.html | 258 + .../templates/system/user/authRole.html | 114 + .../templates/system/user/deptTree.html | 51 + .../resources/templates/system/user/edit.html | 225 + .../templates/system/user/profile/avatar.html | 261 + .../system/user/profile/profile.html | 298 + .../system/user/profile/resetPwd.html | 104 + .../templates/system/user/resetPwd.html | 45 + .../resources/templates/system/user/user.html | 292 + .../resources/templates/tool/build/build.html | 168 + ruoyi-common/pom.xml | 100 + .../ruoyi/common/annotation/DataScope.java | 33 + .../ruoyi/common/annotation/DataSource.java | 28 + .../com/ruoyi/common/annotation/Excel.java | 187 + .../com/ruoyi/common/annotation/Excels.java | 18 + .../java/com/ruoyi/common/annotation/Log.java | 50 + .../ruoyi/common/annotation/RepeatSubmit.java | 29 + .../com/ruoyi/common/config/RuoYiConfig.java | 124 + .../com/ruoyi/common/config/ServerConfig.java | 33 + .../DynamicDataSourceContextHolder.java | 45 + .../config/thread/ThreadPoolConfig.java | 63 + .../com/ruoyi/common/constant/Constants.java | 115 + .../ruoyi/common/constant/GenConstants.java | 114 + .../common/constant/PermissionConstants.java | 27 + .../common/constant/ScheduleConstants.java | 50 + .../ruoyi/common/constant/ShiroConstants.java | 79 + .../ruoyi/common/constant/UserConstants.java | 70 + .../core/context/PermissionContextHolder.java | 27 + .../core/controller/BaseController.java | 229 + .../ruoyi/common/core/domain/AjaxResult.java | 217 + .../ruoyi/common/core/domain/BaseEntity.java | 118 + .../ruoyi/common/core/domain/CxSelect.java | 69 + .../java/com/ruoyi/common/core/domain/R.java | 114 + .../ruoyi/common/core/domain/TreeEntity.java | 63 + .../com/ruoyi/common/core/domain/Ztree.java | 104 + .../common/core/domain/entity/SysDept.java | 203 + .../core/domain/entity/SysDictData.java | 176 + .../core/domain/entity/SysDictType.java | 94 + .../common/core/domain/entity/SysMenu.java | 214 + .../common/core/domain/entity/SysRole.java | 211 + .../common/core/domain/entity/SysUser.java | 385 + .../ruoyi/common/core/page/PageDomain.java | 89 + .../ruoyi/common/core/page/TableDataInfo.java | 85 + .../ruoyi/common/core/page/TableSupport.java | 56 + .../ruoyi/common/core/text/CharsetKit.java | 86 + .../com/ruoyi/common/core/text/Convert.java | 1000 ++ .../ruoyi/common/core/text/StrFormatter.java | 92 + .../ruoyi/common/enums/BusinessStatus.java | 19 + .../com/ruoyi/common/enums/BusinessType.java | 59 + .../ruoyi/common/enums/DataSourceType.java | 19 + .../com/ruoyi/common/enums/OnlineStatus.java | 24 + .../com/ruoyi/common/enums/OperatorType.java | 24 + .../com/ruoyi/common/enums/UserStatus.java | 30 + .../common/exception/DemoModeException.java | 15 + .../common/exception/GlobalException.java | 58 + .../common/exception/ServiceException.java | 58 + .../ruoyi/common/exception/UtilException.java | 26 + .../common/exception/base/BaseException.java | 97 + .../common/exception/file/FileException.java | 19 + .../FileNameLengthLimitExceededException.java | 16 + .../file/FileSizeLimitExceededException.java | 16 + .../exception/file/FileUploadException.java | 61 + .../file/InvalidExtensionException.java | 80 + .../common/exception/job/TaskException.java | 34 + .../exception/user/BlackListException.java | 16 + .../exception/user/CaptchaException.java | 16 + .../exception/user/RoleBlockedException.java | 16 + .../exception/user/UserBlockedException.java | 16 + .../exception/user/UserDeleteException.java | 16 + .../common/exception/user/UserException.java | 18 + .../user/UserNotExistsException.java | 16 + .../user/UserPasswordNotMatchException.java | 16 + .../UserPasswordRetryLimitCountException.java | 16 + ...UserPasswordRetryLimitExceedException.java | 16 + .../main/java/com/ruoyi/common/json/JSON.java | 187 + .../com/ruoyi/common/json/JSONObject.java | 749 ++ .../com/ruoyi/common/utils/AddressUtils.java | 54 + .../java/com/ruoyi/common/utils/Arith.java | 114 + .../com/ruoyi/common/utils/CacheUtils.java | 197 + .../com/ruoyi/common/utils/CookieUtils.java | 138 + .../com/ruoyi/common/utils/DateUtils.java | 191 + .../com/ruoyi/common/utils/DictUtils.java | 190 + .../com/ruoyi/common/utils/ExceptionUtil.java | 39 + .../java/com/ruoyi/common/utils/IpUtils.java | 370 + .../java/com/ruoyi/common/utils/LogUtils.java | 135 + .../com/ruoyi/common/utils/MapDataUtil.java | 54 + .../com/ruoyi/common/utils/MessageUtils.java | 26 + .../com/ruoyi/common/utils/PageUtils.java | 35 + .../com/ruoyi/common/utils/ServletUtils.java | 216 + .../com/ruoyi/common/utils/ShiroUtils.java | 86 + .../com/ruoyi/common/utils/StringUtils.java | 630 + .../java/com/ruoyi/common/utils/Threads.java | 99 + .../ruoyi/common/utils/bean/BeanUtils.java | 110 + .../common/utils/bean/BeanValidators.java | 24 + .../common/utils/file/FileTypeUtils.java | 76 + .../common/utils/file/FileUploadUtils.java | 232 + .../ruoyi/common/utils/file/FileUtils.java | 291 + .../ruoyi/common/utils/file/ImageUtils.java | 98 + .../common/utils/file/MimeTypeUtils.java | 59 + .../ruoyi/common/utils/html/EscapeUtil.java | 167 + .../ruoyi/common/utils/html/HTMLFilter.java | 570 + .../ruoyi/common/utils/http/HttpUtils.java | 274 + .../common/utils/poi/ExcelHandlerAdapter.java | 19 + .../com/ruoyi/common/utils/poi/ExcelUtil.java | 1735 +++ .../common/utils/reflect/ReflectUtils.java | 410 + .../common/utils/security/CipherUtils.java | 36 + .../ruoyi/common/utils/security/Md5Utils.java | 67 + .../utils/security/PermissionUtils.java | 118 + .../common/utils/spring/SpringUtils.java | 159 + .../com/ruoyi/common/utils/sql/SqlUtil.java | 61 + .../com/ruoyi/common/utils/uuid/IdUtils.java | 49 + .../java/com/ruoyi/common/utils/uuid/Seq.java | 86 + .../com/ruoyi/common/utils/uuid/UUID.java | 484 + .../main/java/com/ruoyi/common/xss/Xss.java | 27 + .../java/com/ruoyi/common/xss/XssFilter.java | 74 + .../xss/XssHttpServletRequestWrapper.java | 39 + .../com/ruoyi/common/xss/XssValidator.java | 34 + ruoyi-framework/pom.xml | 82 + .../framework/aspectj/DataScopeAspect.java | 172 + .../framework/aspectj/DataSourceAspect.java | 72 + .../ruoyi/framework/aspectj/LogAspect.java | 255 + .../framework/aspectj/PermissionsAspect.java | 30 + .../framework/config/ApplicationConfig.java | 20 + .../ruoyi/framework/config/CaptchaConfig.java | 83 + .../ruoyi/framework/config/DruidConfig.java | 128 + .../ruoyi/framework/config/FilterConfig.java | 44 + .../ruoyi/framework/config/I18nConfig.java | 43 + .../framework/config/KaptchaTextCreator.java | 76 + .../ruoyi/framework/config/MyBatisConfig.java | 132 + .../framework/config/ResourcesConfig.java | 58 + .../ruoyi/framework/config/ShiroConfig.java | 416 + .../config/properties/DruidProperties.java | 89 + .../datasource/DynamicDataSource.java | 27 + .../interceptor/RepeatSubmitInterceptor.java | 55 + .../impl/SameUrlDataInterceptor.java | 83 + .../ruoyi/framework/manager/AsyncManager.java | 55 + .../framework/manager/ShutdownManager.java | 87 + .../manager/factory/AsyncFactory.java | 136 + .../framework/shiro/realm/UserRealm.java | 158 + .../shiro/service/SysLoginService.java | 185 + .../shiro/service/SysPasswordService.java | 85 + .../shiro/service/SysRegisterService.java | 83 + .../shiro/service/SysShiroService.java | 62 + .../shiro/session/OnlineSession.java | 165 + .../shiro/session/OnlineSessionDAO.java | 117 + .../shiro/session/OnlineSessionFactory.java | 42 + .../shiro/util/AuthorizationUtils.java | 30 + .../web/CustomShiroFilterFactoryBean.java | 85 + .../shiro/web/filter/LogoutFilter.java | 90 + .../filter/captcha/CaptchaValidateFilter.java | 79 + .../filter/kickout/KickoutSessionFilter.java | 176 + .../filter/online/OnlineSessionFilter.java | 99 + .../filter/sync/SyncOnlineSessionFilter.java | 39 + .../web/session/OnlineWebSessionManager.java | 175 + .../SpringSessionValidationScheduler.java | 131 + .../ruoyi/framework/web/domain/Server.java | 241 + .../framework/web/domain/server/Cpu.java | 101 + .../framework/web/domain/server/Jvm.java | 130 + .../framework/web/domain/server/Mem.java | 61 + .../framework/web/domain/server/Sys.java | 84 + .../framework/web/domain/server/SysFile.java | 114 + .../web/exception/GlobalExceptionHandler.java | 116 + .../framework/web/service/CacheService.java | 83 + .../framework/web/service/ConfigService.java | 28 + .../framework/web/service/DictService.java | 46 + .../web/service/PermissionService.java | 262 + ruoyi-generator/pom.xml | 40 + .../com/ruoyi/generator/config/GenConfig.java | 73 + .../generator/controller/GenController.java | 304 + .../com/ruoyi/generator/domain/GenTable.java | 372 + .../generator/domain/GenTableColumn.java | 373 + .../mapper/GenTableColumnMapper.java | 60 + .../generator/mapper/GenTableMapper.java | 91 + .../service/IGenTableColumnService.java | 44 + .../generator/service/IGenTableService.java | 129 + .../impl/GenTableColumnServiceImpl.java | 69 + .../service/impl/GenTableServiceImpl.java | 534 + .../com/ruoyi/generator/util/GenUtils.java | 252 + .../generator/util/VelocityInitializer.java | 34 + .../ruoyi/generator/util/VelocityUtils.java | 384 + .../src/main/resources/generator.yml | 11 + .../mapper/generator/GenTableColumnMapper.xml | 127 + .../mapper/generator/GenTableMapper.xml | 194 + .../templates/tool/gen/createTable.html | 30 + .../resources/templates/tool/gen/edit.html | 608 + .../resources/templates/tool/gen/gen.html | 219 + .../templates/tool/gen/importTable.html | 95 + .../src/main/resources/vm/html/add.html.vm | 379 + .../src/main/resources/vm/html/edit.html.vm | 391 + .../main/resources/vm/html/list-tree.html.vm | 155 + .../src/main/resources/vm/html/list.html.vm | 154 + .../src/main/resources/vm/html/tree.html.vm | 51 + .../main/resources/vm/java/controller.java.vm | 202 + .../src/main/resources/vm/java/domain.java.vm | 105 + .../src/main/resources/vm/java/mapper.java.vm | 91 + .../main/resources/vm/java/service.java.vm | 73 + .../resources/vm/java/serviceImpl.java.vm | 213 + .../main/resources/vm/java/sub-domain.java.vm | 76 + .../src/main/resources/vm/sql/sql.vm | 22 + .../src/main/resources/vm/xml/mapper.xml.vm | 147 + ruoyi-quartz/pom.xml | 40 + .../ruoyi/quartz/config/ScheduleConfig.java | 57 + .../quartz/controller/SysJobController.java | 247 + .../controller/SysJobLogController.java | 103 + .../java/com/ruoyi/quartz/domain/SysJob.java | 169 + .../com/ruoyi/quartz/domain/SysJobLog.java | 155 + .../ruoyi/quartz/mapper/SysJobLogMapper.java | 64 + .../com/ruoyi/quartz/mapper/SysJobMapper.java | 67 + .../quartz/service/ISysJobLogService.java | 56 + .../ruoyi/quartz/service/ISysJobService.java | 102 + .../service/impl/SysJobLogServiceImpl.java | 88 + .../service/impl/SysJobServiceImpl.java | 263 + .../java/com/ruoyi/quartz/task/RyTask.java | 28 + .../ruoyi/quartz/util/AbstractQuartzJob.java | 107 + .../java/com/ruoyi/quartz/util/CronUtils.java | 94 + .../com/ruoyi/quartz/util/JobInvokeUtil.java | 182 + .../QuartzDisallowConcurrentExecution.java | 21 + .../ruoyi/quartz/util/QuartzJobExecution.java | 19 + .../com/ruoyi/quartz/util/ScheduleUtils.java | 141 + .../mapper/quartz/SysJobLogMapper.xml | 93 + .../resources/mapper/quartz/SysJobMapper.xml | 111 + .../resources/templates/monitor/job/add.html | 109 + .../resources/templates/monitor/job/cron.html | 1172 ++ .../templates/monitor/job/detail.html | 99 + .../resources/templates/monitor/job/edit.html | 111 + .../resources/templates/monitor/job/job.html | 198 + .../templates/monitor/job/jobLog.html | 138 + ruoyi-system/pom.xml | 28 + .../com/ruoyi/system/domain/SysConfig.java | 110 + .../ruoyi/system/domain/SysLogininfor.java | 159 + .../com/ruoyi/system/domain/SysNotice.java | 102 + .../com/ruoyi/system/domain/SysOperLog.java | 292 + .../java/com/ruoyi/system/domain/SysPost.java | 122 + .../com/ruoyi/system/domain/SysRoleDept.java | 46 + .../com/ruoyi/system/domain/SysRoleMenu.java | 46 + .../ruoyi/system/domain/SysUserOnline.java | 177 + .../com/ruoyi/system/domain/SysUserPost.java | 46 + .../com/ruoyi/system/domain/SysUserRole.java | 46 + .../ruoyi/system/mapper/SysConfigMapper.java | 76 + .../ruoyi/system/mapper/SysDeptMapper.java | 117 + .../system/mapper/SysDictDataMapper.java | 95 + .../system/mapper/SysDictTypeMapper.java | 83 + .../system/mapper/SysLogininforMapper.java | 42 + .../ruoyi/system/mapper/SysMenuMapper.java | 132 + .../ruoyi/system/mapper/SysNoticeMapper.java | 52 + .../ruoyi/system/mapper/SysOperLogMapper.java | 48 + .../ruoyi/system/mapper/SysPostMapper.java | 83 + .../system/mapper/SysRoleDeptMapper.java | 44 + .../ruoyi/system/mapper/SysRoleMapper.java | 84 + .../system/mapper/SysRoleMenuMapper.java | 44 + .../ruoyi/system/mapper/SysUserMapper.java | 124 + .../system/mapper/SysUserOnlineMapper.java | 52 + .../system/mapper/SysUserPostMapper.java | 44 + .../system/mapper/SysUserRoleMapper.java | 70 + .../system/service/ISysConfigService.java | 82 + .../ruoyi/system/service/ISysDeptService.java | 117 + .../system/service/ISysDictDataService.java | 60 + .../system/service/ISysDictTypeService.java | 107 + .../system/service/ISysLogininforService.java | 40 + .../ruoyi/system/service/ISysMenuService.java | 139 + .../system/service/ISysNoticeService.java | 52 + .../system/service/ISysOperLogService.java | 48 + .../ruoyi/system/service/ISysPostService.java | 91 + .../ruoyi/system/service/ISysRoleService.java | 166 + .../system/service/ISysUserOnlineService.java | 75 + .../ruoyi/system/service/ISysUserService.java | 214 + .../service/impl/SysConfigServiceImpl.java | 219 + .../service/impl/SysDeptServiceImpl.java | 328 + .../service/impl/SysDictDataServiceImpl.java | 113 + .../service/impl/SysDictTypeServiceImpl.java | 260 + .../impl/SysLogininforServiceImpl.java | 66 + .../service/impl/SysMenuServiceImpl.java | 410 + .../service/impl/SysNoticeServiceImpl.java | 82 + .../service/impl/SysOperLogServiceImpl.java | 77 + .../service/impl/SysPostServiceImpl.java | 181 + .../service/impl/SysRoleServiceImpl.java | 415 + .../impl/SysUserOnlineServiceImpl.java | 140 + .../service/impl/SysUserServiceImpl.java | 553 + .../mapper/system/SysConfigMapper.xml | 117 + .../resources/mapper/system/SysDeptMapper.xml | 158 + .../mapper/system/SysDictDataMapper.xml | 123 + .../mapper/system/SysDictTypeMapper.xml | 105 + .../mapper/system/SysLogininforMapper.xml | 56 + .../resources/mapper/system/SysMenuMapper.xml | 191 + .../mapper/system/SysNoticeMapper.xml | 85 + .../mapper/system/SysOperLogMapper.xml | 83 + .../resources/mapper/system/SysPostMapper.xml | 110 + .../mapper/system/SysRoleDeptMapper.xml | 34 + .../resources/mapper/system/SysRoleMapper.xml | 134 + .../mapper/system/SysRoleMenuMapper.xml | 34 + .../resources/mapper/system/SysUserMapper.xml | 231 + .../mapper/system/SysUserOnlineMapper.xml | 57 + .../mapper/system/SysUserPostMapper.xml | 34 + .../mapper/system/SysUserRoleMapper.xml | 48 + ry.bat | 67 + ry.sh | 86 + sql/quartz.sql | 174 + sql/ruoyi.html | 2890 +++++ sql/ruoyi.pdm | 4851 ++++++++ sql/ry_20230223.sql | 720 ++ 696 files changed, 157219 insertions(+) create mode 100644 .github/FUNDING.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 bin/clean.bat create mode 100644 bin/package.bat create mode 100644 bin/run.bat create mode 100644 cs_test/pom.xml create mode 100644 cs_test/src/main/java/com/ruoyi/cs/controller/CsTestController.java create mode 100644 cs_test/src/main/java/com/ruoyi/cs/domain/CsTest.java create mode 100644 cs_test/src/main/java/com/ruoyi/cs/mapper/CsTestMapper.java create mode 100644 cs_test/src/main/java/com/ruoyi/cs/service/ICsTestService.java create mode 100644 cs_test/src/main/java/com/ruoyi/cs/service/impl/CsTestServiceImpl.java create mode 100644 cs_test/src/main/resources/mapper/CsTestMapper.xml create mode 100644 cs_test/src/main/resources/templates/cs/test/add.html create mode 100644 cs_test/src/main/resources/templates/cs/test/edit.html create mode 100644 cs_test/src/main/resources/templates/cs/test/test.html create mode 100644 cs_test1/pom.xml create mode 100644 cs_test1/src/main/java/com/ruoyi/system/controller/CsTest1Controller.java create mode 100644 cs_test1/src/main/java/com/ruoyi/system/domain/CsTest1.java create mode 100644 cs_test1/src/main/java/com/ruoyi/system/mapper/CsTest1Mapper.java create mode 100644 cs_test1/src/main/java/com/ruoyi/system/service/ICsTest1Service.java create mode 100644 cs_test1/src/main/java/com/ruoyi/system/service/impl/CsTest1ServiceImpl.java create mode 100644 cs_test1/src/main/resources/mapper/system/CsTest1Mapper.xml create mode 100644 cs_test1/src/main/resources/templates/system/test1/add.html create mode 100644 cs_test1/src/main/resources/templates/system/test1/edit.html create mode 100644 cs_test1/src/main/resources/templates/system/test1/test1.html create mode 100644 doc/若依环境使用手册.docx create mode 100644 pom.xml create mode 100644 ruoyi-admin/pom.xml create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/RuoYiServletInitializer.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoDialogController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoFormController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoIconController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoOperateController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoReportController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoTableController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/domain/CustomerModel.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/domain/GoodsModel.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/domain/UserOperateModel.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/DruidController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysCaptchaController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysNoticeController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysPostController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/BuildController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/SwaggerController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/TestController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java create mode 100644 ruoyi-admin/src/main/resources/application-druid.yml create mode 100644 ruoyi-admin/src/main/resources/application.yml create mode 100644 ruoyi-admin/src/main/resources/banner.txt create mode 100644 ruoyi-admin/src/main/resources/ehcache/ehcache-shiro.xml create mode 100644 ruoyi-admin/src/main/resources/logback.xml create mode 100644 ruoyi-admin/src/main/resources/mybatis/mybatis-config.xml create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/beautifyhtml/beautifyhtml.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/blockUI/jquery.blockUI.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.css create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.min.css create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.min.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/loading-sm.gif create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/loading.gif create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.css create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.min.css create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.min.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/bootstrap-table.min.css create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/bootstrap-table.min.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/auto-refresh/bootstrap-table-auto-refresh.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/columns/bootstrap-table-fixed-columns.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/custom-view/bootstrap-table-custom-view.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/editable/bootstrap-editable.css create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/editable/bootstrap-editable.min.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/editable/bootstrap-table-editable.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/editable/clear.png create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/editable/loading.gif create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/export/bootstrap-table-export.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/export/tableExport.min.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/mobile/bootstrap-table-mobile.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/print/bootstrap-table-print.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/reorder-columns/bootstrap-table-reorder-columns.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/reorder-columns/jquery.dragtable.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/reorder-rows/bootstrap-table-reorder-rows.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/reorder-rows/jquery.tablednd.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/resizable/bootstrap-table-resizable.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/resizable/jquery.resizableColumns.min.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/tree/bootstrap-table-tree.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/tree/bootstrap-table-tree.min.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/locale/bootstrap-table-zh-CN.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/locale/bootstrap-table-zh-CN.min.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/cropper/cropper.css create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/cropper/cropper.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/cropper/cropper.min.css create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/cropper/cropper.min.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/cxselect/jquery.cxselect.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/cxselect/jquery.cxselect.min.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/datapicker/bootstrap-datetimepicker.css create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/datapicker/bootstrap-datetimepicker.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/datapicker/bootstrap-datetimepicker.min.css create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/datapicker/bootstrap-datetimepicker.min.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/duallistbox/bootstrap-duallistbox.css create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/duallistbox/bootstrap-duallistbox.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/duallistbox/bootstrap-duallistbox.min.css create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/duallistbox/bootstrap-duallistbox.min.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/flot/curvedLines.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/flot/jquery.flot.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/flot/jquery.flot.pie.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/flot/jquery.flot.resize.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/flot/jquery.flot.spline.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/flot/jquery.flot.symbol.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/flot/jquery.flot.tooltip.min.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/fullscreen/jquery.fullscreen.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/highlight/default.min.css create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/highlight/highlight.min.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/iCheck/custom.css create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/iCheck/green-login.png create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/iCheck/green.png create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/iCheck/green@2x.png create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/iCheck/icheck.min.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jasny/jasny-bootstrap.css create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jasny/jasny-bootstrap.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jasny/jasny-bootstrap.min.css create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jasny/jasny-bootstrap.min.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-layout/jquery.layout-latest.css create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-layout/jquery.layout-latest.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/default/img/diy/1_close.png create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/default/img/diy/1_open.png create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/default/img/diy/2.png create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/default/img/diy/3.png create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/default/img/diy/4.png create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/default/img/diy/5.png create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/default/img/diy/6.png create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/default/img/diy/7.png create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/default/img/diy/8.png create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/default/img/diy/9.png create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/default/img/line_conn.gif create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/default/img/loading.gif create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/default/img/zTreeStandard.gif create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/default/img/zTreeStandard.png create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/default/zTreeStyle.css create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/metro/img/line_conn.gif create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/metro/img/line_conn.png create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/metro/img/loading.gif create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/metro/img/metro.gif create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/metro/img/metro.png create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/metro/zTreeStyle.css create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/simple/img/left_menu.gif create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/simple/img/left_menu.png create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/simple/img/line_conn.gif create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/simple/img/loading.gif create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/simple/img/zTreeStandard.gif create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/simple/img/zTreeStandard.png create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/simple/zTreeStyle.css create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/js/jquery.ztree.all-3.5.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/js/jquery.ztree.core-3.5.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/js/jquery.ztree.excheck-3.5.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/js/jquery.ztree.exedit-3.5.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/js/jquery.ztree.exhide-3.5.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/log v3.x.txt create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jsonview/jquery.jsonview.css create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jsonview/jquery.jsonview.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/layer/layer.min.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/layer/theme/default/icon-ext.png create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/layer/theme/default/icon.png create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/layer/theme/default/layer.css create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/layer/theme/default/loading-0.gif create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/layer/theme/default/loading-1.gif create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/layer/theme/default/loading-2.gif create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/layer/theme/moon/default.png create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/layer/theme/moon/style.css create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/layui/css/modules/laydate/default/font/iconfont.eot create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/layui/css/modules/laydate/default/font/iconfont.svg create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/layui/css/modules/laydate/default/font/iconfont.ttf create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/layui/css/modules/laydate/default/font/iconfont.woff create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/layui/css/modules/laydate/default/laydate.css create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/layui/layui.min.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/layui/modules/laydate.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/report/echarts/echarts-all.min.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/report/peity/jquery.peity.min.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/report/sparkline/jquery.sparkline.min.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/select2/select2-bootstrap.min.css create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/select2/select2.css create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/select2/select2.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/select2/select2.min.css create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/select2/select2.min.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/smartwizard/jquery.smartWizard.min.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/smartwizard/smart_wizard_all.min.css create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/suggest/bootstrap-suggest.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/suggest/bootstrap-suggest.min.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/summernote/font/summernote.eot create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/summernote/font/summernote.ttf create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/summernote/font/summernote.woff create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/summernote/font/summernote.woff2 create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/summernote/summernote-zh-CN.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/summernote/summernote.css create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/summernote/summernote.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/summernote/summernote.min.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/typeahead/bootstrap-typeahead.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/typeahead/bootstrap-typeahead.min.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/validate/additional-methods.min.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/validate/jquery.validate.extend.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/validate/jquery.validate.min.js create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/validate/messages_zh.js create mode 100644 ruoyi-admin/src/main/resources/static/css/animate.min.css create mode 100644 ruoyi-admin/src/main/resources/static/css/bootstrap.min.css create mode 100644 ruoyi-admin/src/main/resources/static/css/font-awesome.min.css create mode 100644 ruoyi-admin/src/main/resources/static/css/jquery.contextMenu.min.css create mode 100644 ruoyi-admin/src/main/resources/static/css/login.css create mode 100644 ruoyi-admin/src/main/resources/static/css/login.min.css create mode 100644 ruoyi-admin/src/main/resources/static/css/skins.css create mode 100644 ruoyi-admin/src/main/resources/static/css/style.css create mode 100644 ruoyi-admin/src/main/resources/static/css/style.min.css create mode 100644 ruoyi-admin/src/main/resources/static/css/zen-checkbox.css create mode 100644 ruoyi-admin/src/main/resources/static/favicon.ico create mode 100644 ruoyi-admin/src/main/resources/static/file/rml.txt create mode 100644 ruoyi-admin/src/main/resources/static/fonts/FontAwesome.otf create mode 100644 ruoyi-admin/src/main/resources/static/fonts/Simple-Line-Icons.woff2 create mode 100644 ruoyi-admin/src/main/resources/static/fonts/fontawesome-webfont.eot create mode 100644 ruoyi-admin/src/main/resources/static/fonts/fontawesome-webfont.svg create mode 100644 ruoyi-admin/src/main/resources/static/fonts/fontawesome-webfont.ttf create mode 100644 ruoyi-admin/src/main/resources/static/fonts/fontawesome-webfont.woff create mode 100644 ruoyi-admin/src/main/resources/static/fonts/fontawesome-webfont.woff2 create mode 100644 ruoyi-admin/src/main/resources/static/fonts/glyphicons-halflings-regular.eot create mode 100644 ruoyi-admin/src/main/resources/static/fonts/glyphicons-halflings-regular.svg create mode 100644 ruoyi-admin/src/main/resources/static/fonts/glyphicons-halflings-regular.ttf create mode 100644 ruoyi-admin/src/main/resources/static/fonts/glyphicons-halflings-regular.woff create mode 100644 ruoyi-admin/src/main/resources/static/fonts/glyphicons-halflings-regular.woff2 create mode 100644 ruoyi-admin/src/main/resources/static/fonts/zenicon.woff create mode 100644 ruoyi-admin/src/main/resources/static/html/ie.html create mode 100644 ruoyi-admin/src/main/resources/static/i18n/messages.properties create mode 100644 ruoyi-admin/src/main/resources/static/img/blue.png create mode 100644 ruoyi-admin/src/main/resources/static/img/loading-upload.gif create mode 100644 ruoyi-admin/src/main/resources/static/img/loading.gif create mode 100644 ruoyi-admin/src/main/resources/static/img/locked.png create mode 100644 ruoyi-admin/src/main/resources/static/img/login-background.jpg create mode 100644 ruoyi-admin/src/main/resources/static/img/pay.png create mode 100644 ruoyi-admin/src/main/resources/static/img/profile.jpg create mode 100644 ruoyi-admin/src/main/resources/static/img/progress.png create mode 100644 ruoyi-admin/src/main/resources/static/img/qr_code.png create mode 100644 ruoyi-admin/src/main/resources/static/img/user.png create mode 100644 ruoyi-admin/src/main/resources/static/js/bootstrap.min.js create mode 100644 ruoyi-admin/src/main/resources/static/js/cron.js create mode 100644 ruoyi-admin/src/main/resources/static/js/jquery-ui-1.10.4.min.js create mode 100644 ruoyi-admin/src/main/resources/static/js/jquery.contextMenu.min.js create mode 100644 ruoyi-admin/src/main/resources/static/js/jquery.i18n.properties.min.js create mode 100644 ruoyi-admin/src/main/resources/static/js/jquery.min.js create mode 100644 ruoyi-admin/src/main/resources/static/js/jquery.tmpl.js create mode 100644 ruoyi-admin/src/main/resources/static/js/plugins/metisMenu/jquery.metisMenu.js create mode 100644 ruoyi-admin/src/main/resources/static/js/plugins/slimscroll/jquery.slimscroll.min.js create mode 100644 ruoyi-admin/src/main/resources/static/js/resize-tabs.js create mode 100644 ruoyi-admin/src/main/resources/static/js/three.min.js create mode 100644 ruoyi-admin/src/main/resources/static/ruoyi.png create mode 100644 ruoyi-admin/src/main/resources/static/ruoyi/css/ry-ui.css create mode 100644 ruoyi-admin/src/main/resources/static/ruoyi/index.js create mode 100644 ruoyi-admin/src/main/resources/static/ruoyi/js/common.js create mode 100644 ruoyi-admin/src/main/resources/static/ruoyi/js/ry-ui.js create mode 100644 ruoyi-admin/src/main/resources/static/ruoyi/login.js create mode 100644 ruoyi-admin/src/main/resources/static/ruoyi/register.js create mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/autocomplete.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/basic.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/button.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/cards.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/cxselect.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/datetime.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/duallistbox.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/grid.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/invoice.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/jasny.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/labels_tips.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/localrefresh.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/progress_bars.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/select.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/sortable.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/summernote.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/tabs_panels.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/timeline.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/upload.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/validate.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/wizard.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/icon/fontawesome.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/icon/glyphicons.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/modal/dialog.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/modal/form.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/modal/layer.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/modal/table.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/modal/table/check.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/modal/table/frame1.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/modal/table/frame2.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/modal/table/parent.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/modal/table/radio.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/operate/add.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/operate/detail.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/operate/edit.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/operate/other.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/operate/table.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/report/echarts.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/report/metrics.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/report/peity.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/report/sparkline.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/asynTree.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/button.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/child.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/curd.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/customView.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/data.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/detail.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/dynamicColumns.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/editable.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/event.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/export.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/exportSelected.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/fixedColumns.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/footer.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/groupHeader.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/headerStyle.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/image.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/multi.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/other.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/pageGo.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/params.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/print.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/refresh.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/remember.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/reorderColumns.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/reorderRows.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/resizable.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/search.html create mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/subdata.html create mode 100644 ruoyi-admin/src/main/resources/templates/error/404.html create mode 100644 ruoyi-admin/src/main/resources/templates/error/500.html create mode 100644 ruoyi-admin/src/main/resources/templates/error/service.html create mode 100644 ruoyi-admin/src/main/resources/templates/error/unauth.html create mode 100644 ruoyi-admin/src/main/resources/templates/include.html create mode 100644 ruoyi-admin/src/main/resources/templates/index-topnav.html create mode 100644 ruoyi-admin/src/main/resources/templates/index.html create mode 100644 ruoyi-admin/src/main/resources/templates/lock.html create mode 100644 ruoyi-admin/src/main/resources/templates/login.html create mode 100644 ruoyi-admin/src/main/resources/templates/main.html create mode 100644 ruoyi-admin/src/main/resources/templates/main_v1.html create mode 100644 ruoyi-admin/src/main/resources/templates/monitor/cache/cache.html create mode 100644 ruoyi-admin/src/main/resources/templates/monitor/logininfor/logininfor.html create mode 100644 ruoyi-admin/src/main/resources/templates/monitor/online/online.html create mode 100644 ruoyi-admin/src/main/resources/templates/monitor/operlog/detail.html create mode 100644 ruoyi-admin/src/main/resources/templates/monitor/operlog/operlog.html create mode 100644 ruoyi-admin/src/main/resources/templates/monitor/server/server.html create mode 100644 ruoyi-admin/src/main/resources/templates/register.html create mode 100644 ruoyi-admin/src/main/resources/templates/skin.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/config/add.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/config/config.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/config/edit.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/dept/add.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/dept/dept.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/dept/edit.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/dept/tree.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/dict/data/add.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/dict/data/data.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/dict/data/edit.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/dict/type/add.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/dict/type/edit.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/dict/type/tree.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/dict/type/type.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/menu/add.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/menu/edit.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/menu/icon.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/menu/menu.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/menu/tree.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/notice/add.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/notice/edit.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/notice/notice.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/post/add.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/post/edit.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/post/post.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/role/add.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/role/authUser.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/role/dataScope.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/role/edit.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/role/role.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/role/selectUser.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/user/add.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/user/authRole.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/user/deptTree.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/user/edit.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/user/profile/avatar.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/user/profile/profile.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/user/profile/resetPwd.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/user/resetPwd.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/user/user.html create mode 100644 ruoyi-admin/src/main/resources/templates/tool/build/build.html create mode 100644 ruoyi-common/pom.xml create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataScope.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSource.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excel.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excels.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/annotation/Log.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/config/ServerConfig.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/config/datasource/DynamicDataSourceContextHolder.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/config/thread/ThreadPoolConfig.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/constant/PermissionConstants.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/constant/ScheduleConstants.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/constant/ShiroConstants.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/core/context/PermissionContextHolder.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/core/controller/BaseController.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/AjaxResult.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/BaseEntity.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/CxSelect.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/R.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeEntity.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/Ztree.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictData.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictType.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysRole.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/core/page/PageDomain.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableDataInfo.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableSupport.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/core/text/CharsetKit.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/core/text/Convert.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/core/text/StrFormatter.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessStatus.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessType.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/enums/DataSourceType.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/enums/OnlineStatus.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/enums/OperatorType.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/enums/UserStatus.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/exception/DemoModeException.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/exception/GlobalException.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/exception/ServiceException.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/exception/UtilException.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/exception/base/BaseException.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileException.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileNameLengthLimitExceededException.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileSizeLimitExceededException.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileUploadException.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/exception/file/InvalidExtensionException.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/exception/job/TaskException.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/exception/user/BlackListException.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaException.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/exception/user/RoleBlockedException.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserBlockedException.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserDeleteException.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserException.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserNotExistsException.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordNotMatchException.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordRetryLimitCountException.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordRetryLimitExceedException.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/json/JSON.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/json/JSONObject.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/AddressUtils.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/Arith.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/CacheUtils.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/CookieUtils.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/DateUtils.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/ExceptionUtil.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/IpUtils.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/LogUtils.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/MapDataUtil.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/MessageUtils.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/PageUtils.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/ShiroUtils.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/Threads.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanUtils.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanValidators.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileTypeUtils.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/file/ImageUtils.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/html/EscapeUtil.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/html/HTMLFilter.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelHandlerAdapter.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/reflect/ReflectUtils.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/security/CipherUtils.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/security/Md5Utils.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/security/PermissionUtils.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/spring/SpringUtils.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/sql/SqlUtil.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/IdUtils.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/Seq.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/UUID.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/xss/Xss.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/xss/XssFilter.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/xss/XssHttpServletRequestWrapper.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/xss/XssValidator.java create mode 100644 ruoyi-framework/pom.xml create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataSourceAspect.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/PermissionsAspect.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/config/ApplicationConfig.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/config/CaptchaConfig.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/config/DruidConfig.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/config/I18nConfig.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/config/KaptchaTextCreator.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/config/MyBatisConfig.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/config/ShiroConfig.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/DruidProperties.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSource.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/manager/AsyncManager.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/manager/ShutdownManager.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/realm/UserRealm.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysLoginService.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysPasswordService.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysRegisterService.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysShiroService.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSession.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSessionDAO.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSessionFactory.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/util/AuthorizationUtils.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/CustomShiroFilterFactoryBean.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/LogoutFilter.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/captcha/CaptchaValidateFilter.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/kickout/KickoutSessionFilter.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/online/OnlineSessionFilter.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/sync/SyncOnlineSessionFilter.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/session/OnlineWebSessionManager.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/session/SpringSessionValidationScheduler.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/Server.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Cpu.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Jvm.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Mem.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Sys.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/SysFile.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/CacheService.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/ConfigService.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/DictService.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/PermissionService.java create mode 100644 ruoyi-generator/pom.xml create mode 100644 ruoyi-generator/src/main/java/com/ruoyi/generator/config/GenConfig.java create mode 100644 ruoyi-generator/src/main/java/com/ruoyi/generator/controller/GenController.java create mode 100644 ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTable.java create mode 100644 ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTableColumn.java create mode 100644 ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableColumnMapper.java create mode 100644 ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableMapper.java create mode 100644 ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableColumnService.java create mode 100644 ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableService.java create mode 100644 ruoyi-generator/src/main/java/com/ruoyi/generator/service/impl/GenTableColumnServiceImpl.java create mode 100644 ruoyi-generator/src/main/java/com/ruoyi/generator/service/impl/GenTableServiceImpl.java create mode 100644 ruoyi-generator/src/main/java/com/ruoyi/generator/util/GenUtils.java create mode 100644 ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityInitializer.java create mode 100644 ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java create mode 100644 ruoyi-generator/src/main/resources/generator.yml create mode 100644 ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml create mode 100644 ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml create mode 100644 ruoyi-generator/src/main/resources/templates/tool/gen/createTable.html create mode 100644 ruoyi-generator/src/main/resources/templates/tool/gen/edit.html create mode 100644 ruoyi-generator/src/main/resources/templates/tool/gen/gen.html create mode 100644 ruoyi-generator/src/main/resources/templates/tool/gen/importTable.html create mode 100644 ruoyi-generator/src/main/resources/vm/html/add.html.vm create mode 100644 ruoyi-generator/src/main/resources/vm/html/edit.html.vm create mode 100644 ruoyi-generator/src/main/resources/vm/html/list-tree.html.vm create mode 100644 ruoyi-generator/src/main/resources/vm/html/list.html.vm create mode 100644 ruoyi-generator/src/main/resources/vm/html/tree.html.vm create mode 100644 ruoyi-generator/src/main/resources/vm/java/controller.java.vm create mode 100644 ruoyi-generator/src/main/resources/vm/java/domain.java.vm create mode 100644 ruoyi-generator/src/main/resources/vm/java/mapper.java.vm create mode 100644 ruoyi-generator/src/main/resources/vm/java/service.java.vm create mode 100644 ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm create mode 100644 ruoyi-generator/src/main/resources/vm/java/sub-domain.java.vm create mode 100644 ruoyi-generator/src/main/resources/vm/sql/sql.vm create mode 100644 ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm create mode 100644 ruoyi-quartz/pom.xml create mode 100644 ruoyi-quartz/src/main/java/com/ruoyi/quartz/config/ScheduleConfig.java create mode 100644 ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobController.java create mode 100644 ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobLogController.java create mode 100644 ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/SysJob.java create mode 100644 ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/SysJobLog.java create mode 100644 ruoyi-quartz/src/main/java/com/ruoyi/quartz/mapper/SysJobLogMapper.java create mode 100644 ruoyi-quartz/src/main/java/com/ruoyi/quartz/mapper/SysJobMapper.java create mode 100644 ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/ISysJobLogService.java create mode 100644 ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/ISysJobService.java create mode 100644 ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobLogServiceImpl.java create mode 100644 ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobServiceImpl.java create mode 100644 ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java create mode 100644 ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/AbstractQuartzJob.java create mode 100644 ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/CronUtils.java create mode 100644 ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/JobInvokeUtil.java create mode 100644 ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/QuartzDisallowConcurrentExecution.java create mode 100644 ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/QuartzJobExecution.java create mode 100644 ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/ScheduleUtils.java create mode 100644 ruoyi-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml create mode 100644 ruoyi-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml create mode 100644 ruoyi-quartz/src/main/resources/templates/monitor/job/add.html create mode 100644 ruoyi-quartz/src/main/resources/templates/monitor/job/cron.html create mode 100644 ruoyi-quartz/src/main/resources/templates/monitor/job/detail.html create mode 100644 ruoyi-quartz/src/main/resources/templates/monitor/job/edit.html create mode 100644 ruoyi-quartz/src/main/resources/templates/monitor/job/job.html create mode 100644 ruoyi-quartz/src/main/resources/templates/monitor/job/jobLog.html create mode 100644 ruoyi-system/pom.xml create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/domain/SysConfig.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/domain/SysLogininfor.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/domain/SysNotice.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/domain/SysOperLog.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/domain/SysPost.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/domain/SysRoleDept.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/domain/SysRoleMenu.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserOnline.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserPost.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserRole.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysConfigMapper.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDeptMapper.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictDataMapper.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictTypeMapper.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysLogininforMapper.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysMenuMapper.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysNoticeMapper.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysOperLogMapper.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysPostMapper.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleDeptMapper.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMapper.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMenuMapper.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserOnlineMapper.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserPostMapper.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserRoleMapper.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/service/ISysConfigService.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDeptService.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictDataService.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictTypeService.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/service/ISysLogininforService.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/service/ISysMenuService.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/service/ISysNoticeService.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/service/ISysOperLogService.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/service/ISysPostService.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/service/ISysRoleService.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserOnlineService.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysConfigServiceImpl.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictDataServiceImpl.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictTypeServiceImpl.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysLogininforServiceImpl.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysNoticeServiceImpl.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysOperLogServiceImpl.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysPostServiceImpl.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserOnlineServiceImpl.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java create mode 100644 ruoyi-system/src/main/resources/mapper/system/SysConfigMapper.xml create mode 100644 ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml create mode 100644 ruoyi-system/src/main/resources/mapper/system/SysDictDataMapper.xml create mode 100644 ruoyi-system/src/main/resources/mapper/system/SysDictTypeMapper.xml create mode 100644 ruoyi-system/src/main/resources/mapper/system/SysLogininforMapper.xml create mode 100644 ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml create mode 100644 ruoyi-system/src/main/resources/mapper/system/SysNoticeMapper.xml create mode 100644 ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml create mode 100644 ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml create mode 100644 ruoyi-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml create mode 100644 ruoyi-system/src/main/resources/mapper/system/SysRoleMapper.xml create mode 100644 ruoyi-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml create mode 100644 ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml create mode 100644 ruoyi-system/src/main/resources/mapper/system/SysUserOnlineMapper.xml create mode 100644 ruoyi-system/src/main/resources/mapper/system/SysUserPostMapper.xml create mode 100644 ruoyi-system/src/main/resources/mapper/system/SysUserRoleMapper.xml create mode 100644 ry.bat create mode 100644 ry.sh create mode 100644 sql/quartz.sql create mode 100644 sql/ruoyi.html create mode 100644 sql/ruoyi.pdm create mode 100644 sql/ry_20230223.sql diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..5a0b447 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +custom: http://doc.ruoyi.vip/ruoyi/other/donate.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..09bdfea --- /dev/null +++ b/.gitignore @@ -0,0 +1,46 @@ +###################################################################### +# Build Tools + +.gradle +/build/ +!gradle/wrapper/gradle-wrapper.jar + +target/ +!.mvn/wrapper/maven-wrapper.jar + +###################################################################### +# IDE + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### JRebel ### +rebel.xml +### NetBeans ### +nbproject/private/ +build/* +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ + +###################################################################### +# Others +*.log +*.xml.versionsBackup +*.swp + +!*/build/*.java +!*/build/*.html +!*/build/*.xml \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8564f29 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2018 RuoYi + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d6cdb4d --- /dev/null +++ b/README.md @@ -0,0 +1,102 @@ +

+ logo +

+

RuoYi v4.7.6

+

基于SpringBoot开发的轻量级Java快速开发框架

+

+ + + +

+ +## 平台简介 + +一直想做一款后台管理系统,看了很多优秀的开源项目但是发现没有合适的。于是利用空闲休息时间开始自己写了一套后台系统。如此有了若依。她可以用于所有的Web应用程序,如网站管理后台,网站会员中心,CMS,CRM,OA。所有前端后台代码封装过后十分精简易上手,出错概率低。同时支持移动客户端访问。系统会陆续更新一些实用功能。 + +性别男,若依是给女儿取的名字(寓意:你若不离不弃,我必生死相依) + +若依是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。 + +* 前后端分离版本,请移步[RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue),微服务版本,请移步[RuoYi-Cloud](https://gitee.com/y_project/RuoYi-Cloud) +* 感谢 [hplus](https://gitee.com/hplus_admin/hplus) 后台主题 UI 框架。 +* 阿里云折扣场:[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场:[点我进入](http://txy.ruoyi.vip)   +* 阿里云优惠券:[点我领取](https://www.aliyun.com/minisite/goods?userCode=brki8iof&share_source=copy_link),腾讯云优惠券:[点我领取](https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console)   + +## 内置功能 + +1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。 +2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。 +3. 岗位管理:配置系统用户所属担任职务。 +4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。 +5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。 +6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。 +7. 参数管理:对系统动态配置常用参数。 +8. 通知公告:系统通知公告信息发布维护。 +9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。 +10. 登录日志:系统登录日志记录查询包含登录异常。 +11. 在线用户:当前系统中活跃用户状态监控。 +12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。 +13. 代码生成:前后端代码的生成(java、html、xml、sql)支持CRUD下载 。 +14. 系统接口:根据业务代码自动生成相关的api接口文档。 +15. 服务监控:监视当前系统CPU、内存、磁盘、堆栈等相关信息。 +16. 缓存监控:对系统的缓存查询,删除、清空等操作。 +17. 在线构建器:拖动表单元素生成相应的HTML代码。 +18. 连接池监视:监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈。 + +## 在线体验 + +- admin/admin123 +- 陆陆续续收到一些打赏,为了更好的体验已用于演示服务器升级。谢谢各位小伙伴。 + +演示地址:http://ruoyi.vip +文档地址:http://doc.ruoyi.vip + +## 演示图 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +## 若依交流群 + +QQ群: [![加入QQ群](https://img.shields.io/badge/已满-1389287-blue.svg)](https://jq.qq.com/?_wv=1027&k=5HBAaYN) [![加入QQ群](https://img.shields.io/badge/已满-1679294-blue.svg)](https://jq.qq.com/?_wv=1027&k=5cHeRVW) [![加入QQ群](https://img.shields.io/badge/已满-1529866-blue.svg)](https://jq.qq.com/?_wv=1027&k=53R0L5Z) [![加入QQ群](https://img.shields.io/badge/已满-1772718-blue.svg)](https://jq.qq.com/?_wv=1027&k=5g75dCU) [![加入QQ群](https://img.shields.io/badge/已满-1366522-blue.svg)](https://jq.qq.com/?_wv=1027&k=58cPoHA) [![加入QQ群](https://img.shields.io/badge/已满-1382251-blue.svg)](https://jq.qq.com/?_wv=1027&k=5Ofd4Pb) [![加入QQ群](https://img.shields.io/badge/已满-1145125-blue.svg)](https://jq.qq.com/?_wv=1027&k=5yugASz) [![加入QQ群](https://img.shields.io/badge/已满-86752435-blue.svg)](https://jq.qq.com/?_wv=1027&k=5Rf3d2P) [![加入QQ群](https://img.shields.io/badge/已满-134072510-blue.svg)](https://jq.qq.com/?_wv=1027&k=5ZIjaeP) [![加入QQ群](https://img.shields.io/badge/已满-210336300-blue.svg)](https://jq.qq.com/?_wv=1027&k=5CJw1jY) [![加入QQ群](https://img.shields.io/badge/已满-339522636-blue.svg)](https://jq.qq.com/?_wv=1027&k=5omzbKc) [![加入QQ群](https://img.shields.io/badge/已满-130035985-blue.svg)](https://jq.qq.com/?_wv=1027&k=qPIKBb7s) [![加入QQ群](https://img.shields.io/badge/已满-143151071-blue.svg)](https://jq.qq.com/?_wv=1027&k=4NsjKbtU) [![加入QQ群](https://img.shields.io/badge/已满-158781320-blue.svg)](https://jq.qq.com/?_wv=1027&k=VD2pkz2G) [![加入QQ群](https://img.shields.io/badge/已满-201531282-blue.svg)](https://jq.qq.com/?_wv=1027&k=HlshFwkJ) [![加入QQ群](https://img.shields.io/badge/已满-101526938-blue.svg)](https://jq.qq.com/?_wv=1027&k=0ARRrO9V) [![加入QQ群](https://img.shields.io/badge/已满-264355400-blue.svg)](https://jq.qq.com/?_wv=1027&k=up9k3ZXJ) [![加入QQ群](https://img.shields.io/badge/已满-298522656-blue.svg)](https://jq.qq.com/?_wv=1027&k=540WfdEr) [![加入QQ群](https://img.shields.io/badge/已满-139845794-blue.svg)](https://jq.qq.com/?_wv=1027&k=ss91fC4t) [![加入QQ群](https://img.shields.io/badge/已满-185760789-blue.svg)](https://jq.qq.com/?_wv=1027&k=Cqd66IKe) [![加入QQ群](https://img.shields.io/badge/175104288-blue.svg)](https://jq.qq.com/?_wv=1027&k=7FplYUnR) \ No newline at end of file diff --git a/bin/clean.bat b/bin/clean.bat new file mode 100644 index 0000000..24c0974 --- /dev/null +++ b/bin/clean.bat @@ -0,0 +1,12 @@ +@echo off +echo. +echo [Ϣ] target· +echo. + +%~d0 +cd %~dp0 + +cd .. +call mvn clean + +pause \ No newline at end of file diff --git a/bin/package.bat b/bin/package.bat new file mode 100644 index 0000000..c693ec0 --- /dev/null +++ b/bin/package.bat @@ -0,0 +1,12 @@ +@echo off +echo. +echo [Ϣ] Weḅwar/jarļ +echo. + +%~d0 +cd %~dp0 + +cd .. +call mvn clean package -Dmaven.test.skip=true + +pause \ No newline at end of file diff --git a/bin/run.bat b/bin/run.bat new file mode 100644 index 0000000..41efbd0 --- /dev/null +++ b/bin/run.bat @@ -0,0 +1,14 @@ +@echo off +echo. +echo [Ϣ] ʹJarWeb̡ +echo. + +cd %~dp0 +cd ../ruoyi-admin/target + +set JAVA_OPTS=-Xms256m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m + +java -jar %JAVA_OPTS% ruoyi-admin.jar + +cd bin +pause \ No newline at end of file diff --git a/cs_test/pom.xml b/cs_test/pom.xml new file mode 100644 index 0000000..24613ac --- /dev/null +++ b/cs_test/pom.xml @@ -0,0 +1,20 @@ + + + + ruoyi + com.ruoyi + 4.7.6 + + 4.0.0 + + cs_test + + + + com.ruoyi + ruoyi-common + + + \ No newline at end of file diff --git a/cs_test/src/main/java/com/ruoyi/cs/controller/CsTestController.java b/cs_test/src/main/java/com/ruoyi/cs/controller/CsTestController.java new file mode 100644 index 0000000..1bb6341 --- /dev/null +++ b/cs_test/src/main/java/com/ruoyi/cs/controller/CsTestController.java @@ -0,0 +1,128 @@ +package com.ruoyi.cs.controller; + +import java.util.List; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.cs.domain.CsTest; +import com.ruoyi.cs.service.ICsTestService; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.common.core.page.TableDataInfo; + +/** + * 【请填写功能名称】Controller + * + * @author ruoyi + * @date 2023-04-11 + */ +@Controller +@RequestMapping("/cs/test") +public class CsTestController extends BaseController +{ + private String prefix = "cs/test"; + + @Autowired + private ICsTestService csTestService; + + @RequiresPermissions("cs:test:view") + @GetMapping() + public String test() + { + return prefix + "/test"; + } + + /** + * 查询【请填写功能名称】列表 + */ + @RequiresPermissions("cs:test:list") + @PostMapping("/list") + @ResponseBody + public TableDataInfo list(CsTest csTest) + { + startPage(); + List list = csTestService.selectCsTestList(csTest); + return getDataTable(list); + } + + /** + * 导出【请填写功能名称】列表 + */ + @RequiresPermissions("cs:test:export") + @Log(title = "【请填写功能名称】", businessType = BusinessType.EXPORT) + @PostMapping("/export") + @ResponseBody + public AjaxResult export(CsTest csTest) + { + List list = csTestService.selectCsTestList(csTest); + ExcelUtil util = new ExcelUtil(CsTest.class); + return util.exportExcel(list, "【请填写功能名称】数据"); + } + + /** + * 新增【请填写功能名称】 + */ + @GetMapping("/add") + public String add() + { + return prefix + "/add"; + } + + /** + * 新增保存【请填写功能名称】 + */ + @RequiresPermissions("cs:test:add") + @Log(title = "【请填写功能名称】", businessType = BusinessType.INSERT) + @PostMapping("/add") + @ResponseBody + public AjaxResult addSave(CsTest csTest) + { + return toAjax(csTestService.insertCsTest(csTest)); + } + + /** + * 修改【请填写功能名称】 + */ + @RequiresPermissions("cs:test:edit") + @GetMapping("/edit/{id}") + public String edit(@PathVariable("id") Long id, ModelMap mmap) + { + CsTest csTest = csTestService.selectCsTestById(id); + mmap.put("csTest", csTest); + return prefix + "/edit"; + } + + /** + * 修改保存【请填写功能名称】 + */ + @RequiresPermissions("cs:test:edit") + @Log(title = "【请填写功能名称】", businessType = BusinessType.UPDATE) + @PostMapping("/edit") + @ResponseBody + public AjaxResult editSave(CsTest csTest) + { + return toAjax(csTestService.updateCsTest(csTest)); + } + + /** + * 删除【请填写功能名称】 + */ + @RequiresPermissions("cs:test:remove") + @Log(title = "【请填写功能名称】", businessType = BusinessType.DELETE) + @PostMapping( "/remove") + @ResponseBody + public AjaxResult remove(String ids) + { + return toAjax(csTestService.deleteCsTestByIds(ids)); + } +} + diff --git a/cs_test/src/main/java/com/ruoyi/cs/domain/CsTest.java b/cs_test/src/main/java/com/ruoyi/cs/domain/CsTest.java new file mode 100644 index 0000000..6854a24 --- /dev/null +++ b/cs_test/src/main/java/com/ruoyi/cs/domain/CsTest.java @@ -0,0 +1,66 @@ +package com.ruoyi.cs.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 【请填写功能名称】对象 cs_test + * + * @author ruoyi + * @date 2023-04-11 + */ +public class CsTest extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** $column.columnComment */ + private Long id; + + /** $column.columnComment */ + @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()") + private String name; + + /** $column.columnComment */ + @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()") + private String password; + + public void setId(Long id) + { + this.id = id; + } + + public Long getId() + { + return id; + } + public void setName(String name) + { + this.name = name; + } + + public String getName() + { + return name; + } + public void setPassword(String password) + { + this.password = password; + } + + public String getPassword() + { + return password; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("name", getName()) + .append("password", getPassword()) + .toString(); + } +} + diff --git a/cs_test/src/main/java/com/ruoyi/cs/mapper/CsTestMapper.java b/cs_test/src/main/java/com/ruoyi/cs/mapper/CsTestMapper.java new file mode 100644 index 0000000..e4caaf5 --- /dev/null +++ b/cs_test/src/main/java/com/ruoyi/cs/mapper/CsTestMapper.java @@ -0,0 +1,62 @@ +package com.ruoyi.cs.mapper; + + +import java.util.List; +import com.ruoyi.cs.domain.CsTest; + +/** + * 【请填写功能名称】Mapper接口 + * + * @author ruoyi + * @date 2023-04-11 + */ +public interface CsTestMapper +{ + /** + * 查询【请填写功能名称】 + * + * @param id 【请填写功能名称】主键 + * @return 【请填写功能名称】 + */ + public CsTest selectCsTestById(Long id); + + /** + * 查询【请填写功能名称】列表 + * + * @param csTest 【请填写功能名称】 + * @return 【请填写功能名称】集合 + */ + public List selectCsTestList(CsTest csTest); + + /** + * 新增【请填写功能名称】 + * + * @param csTest 【请填写功能名称】 + * @return 结果 + */ + public int insertCsTest(CsTest csTest); + + /** + * 修改【请填写功能名称】 + * + * @param csTest 【请填写功能名称】 + * @return 结果 + */ + public int updateCsTest(CsTest csTest); + + /** + * 删除【请填写功能名称】 + * + * @param id 【请填写功能名称】主键 + * @return 结果 + */ + public int deleteCsTestById(Long id); + + /** + * 批量删除【请填写功能名称】 + * + * @param ids 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteCsTestByIds(String[] ids); +} diff --git a/cs_test/src/main/java/com/ruoyi/cs/service/ICsTestService.java b/cs_test/src/main/java/com/ruoyi/cs/service/ICsTestService.java new file mode 100644 index 0000000..d6f2af8 --- /dev/null +++ b/cs_test/src/main/java/com/ruoyi/cs/service/ICsTestService.java @@ -0,0 +1,62 @@ +package com.ruoyi.cs.service; + +import java.util.List; +import com.ruoyi.cs.domain.CsTest; + +/** + * 【请填写功能名称】Service接口 + * + * @author ruoyi + * @date 2023-04-11 + */ +public interface ICsTestService +{ + /** + * 查询【请填写功能名称】 + * + * @param id 【请填写功能名称】主键 + * @return 【请填写功能名称】 + */ + public CsTest selectCsTestById(Long id); + + /** + * 查询【请填写功能名称】列表 + * + * @param csTest 【请填写功能名称】 + * @return 【请填写功能名称】集合 + */ + public List selectCsTestList(CsTest csTest); + + /** + * 新增【请填写功能名称】 + * + * @param csTest 【请填写功能名称】 + * @return 结果 + */ + public int insertCsTest(CsTest csTest); + + /** + * 修改【请填写功能名称】 + * + * @param csTest 【请填写功能名称】 + * @return 结果 + */ + public int updateCsTest(CsTest csTest); + + /** + * 批量删除【请填写功能名称】 + * + * @param ids 需要删除的【请填写功能名称】主键集合 + * @return 结果 + */ + public int deleteCsTestByIds(String ids); + + /** + * 删除【请填写功能名称】信息 + * + * @param id 【请填写功能名称】主键 + * @return 结果 + */ + public int deleteCsTestById(Long id); +} + diff --git a/cs_test/src/main/java/com/ruoyi/cs/service/impl/CsTestServiceImpl.java b/cs_test/src/main/java/com/ruoyi/cs/service/impl/CsTestServiceImpl.java new file mode 100644 index 0000000..3dd57df --- /dev/null +++ b/cs_test/src/main/java/com/ruoyi/cs/service/impl/CsTestServiceImpl.java @@ -0,0 +1,95 @@ +package com.ruoyi.cs.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.cs.mapper.CsTestMapper; +import com.ruoyi.cs.domain.CsTest; +import com.ruoyi.cs.service.ICsTestService; +import com.ruoyi.common.core.text.Convert; + +/** + * 【请填写功能名称】Service业务层处理 + * + * @author ruoyi + * @date 2023-04-11 + */ +@Service +public class CsTestServiceImpl implements ICsTestService +{ + @Autowired + private CsTestMapper csTestMapper; + + /** + * 查询【请填写功能名称】 + * + * @param id 【请填写功能名称】主键 + * @return 【请填写功能名称】 + */ + @Override + public CsTest selectCsTestById(Long id) + { + return csTestMapper.selectCsTestById(id); + } + + /** + * 查询【请填写功能名称】列表 + * + * @param csTest 【请填写功能名称】 + * @return 【请填写功能名称】 + */ + @Override + public List selectCsTestList(CsTest csTest) + { + return csTestMapper.selectCsTestList(csTest); + } + + /** + * 新增【请填写功能名称】 + * + * @param csTest 【请填写功能名称】 + * @return 结果 + */ + @Override + public int insertCsTest(CsTest csTest) + { + return csTestMapper.insertCsTest(csTest); + } + + /** + * 修改【请填写功能名称】 + * + * @param csTest 【请填写功能名称】 + * @return 结果 + */ + @Override + public int updateCsTest(CsTest csTest) + { + return csTestMapper.updateCsTest(csTest); + } + + /** + * 批量删除【请填写功能名称】 + * + * @param ids 需要删除的【请填写功能名称】主键 + * @return 结果 + */ + @Override + public int deleteCsTestByIds(String ids) + { + return csTestMapper.deleteCsTestByIds(Convert.toStrArray(ids)); + } + + /** + * 删除【请填写功能名称】信息 + * + * @param id 【请填写功能名称】主键 + * @return 结果 + */ + @Override + public int deleteCsTestById(Long id) + { + return csTestMapper.deleteCsTestById(id); + } +} + diff --git a/cs_test/src/main/resources/mapper/CsTestMapper.xml b/cs_test/src/main/resources/mapper/CsTestMapper.xml new file mode 100644 index 0000000..265c788 --- /dev/null +++ b/cs_test/src/main/resources/mapper/CsTestMapper.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + select id, name, password from cs_test + + + + + + + + insert into cs_test + + name, + password, + + + #{name}, + #{password}, + + + + + update cs_test + + name = #{name}, + password = #{password}, + + where id = #{id} + + + + delete from cs_test where id = #{id} + + + + delete from cs_test where id in + + #{id} + + + + \ No newline at end of file diff --git a/cs_test/src/main/resources/templates/cs/test/add.html b/cs_test/src/main/resources/templates/cs/test/add.html new file mode 100644 index 0000000..c4cffa6 --- /dev/null +++ b/cs_test/src/main/resources/templates/cs/test/add.html @@ -0,0 +1,37 @@ + + + + + + +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/cs_test/src/main/resources/templates/cs/test/edit.html b/cs_test/src/main/resources/templates/cs/test/edit.html new file mode 100644 index 0000000..fafd83b --- /dev/null +++ b/cs_test/src/main/resources/templates/cs/test/edit.html @@ -0,0 +1,38 @@ + + + + + + +
+
+ +
+ +
+ +
+
+
+ +
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/cs_test/src/main/resources/templates/cs/test/test.html b/cs_test/src/main/resources/templates/cs/test/test.html new file mode 100644 index 0000000..12946f8 --- /dev/null +++ b/cs_test/src/main/resources/templates/cs/test/test.html @@ -0,0 +1,94 @@ + + + + + + +
+
+
+
+
+ +
+
+
+ + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/cs_test1/pom.xml b/cs_test1/pom.xml new file mode 100644 index 0000000..7decae9 --- /dev/null +++ b/cs_test1/pom.xml @@ -0,0 +1,20 @@ + + + + ruoyi + com.ruoyi + 4.7.6 + + 4.0.0 + + cs_test1 + + + + com.ruoyi + ruoyi-common + + + \ No newline at end of file diff --git a/cs_test1/src/main/java/com/ruoyi/system/controller/CsTest1Controller.java b/cs_test1/src/main/java/com/ruoyi/system/controller/CsTest1Controller.java new file mode 100644 index 0000000..777b5e7 --- /dev/null +++ b/cs_test1/src/main/java/com/ruoyi/system/controller/CsTest1Controller.java @@ -0,0 +1,127 @@ +package com.ruoyi.system.controller; + +import java.util.List; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.system.domain.CsTest1; +import com.ruoyi.system.service.ICsTest1Service; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.common.core.page.TableDataInfo; + +/** + * 【请填写功能名称】Controller + * + * @author ruoyi + * @date 2023-04-11 + */ +@Controller +@RequestMapping("/system/test1") +public class CsTest1Controller extends BaseController +{ + private String prefix = "system/test1"; + + @Autowired + private ICsTest1Service csTest1Service; + + @RequiresPermissions("system:test1:view") + @GetMapping() + public String test1() + { + return prefix + "/test1"; + } + + /** + * 查询【请填写功能名称】列表 + */ + @RequiresPermissions("system:test1:list") + @PostMapping("/list") + @ResponseBody + public TableDataInfo list(CsTest1 csTest1) + { + startPage(); + List list = csTest1Service.selectCsTest1List(csTest1); + return getDataTable(list); + } + + /** + * 导出【请填写功能名称】列表 + */ + @RequiresPermissions("system:test1:export") + @Log(title = "【请填写功能名称】", businessType = BusinessType.EXPORT) + @PostMapping("/export") + @ResponseBody + public AjaxResult export(CsTest1 csTest1) + { + List list = csTest1Service.selectCsTest1List(csTest1); + ExcelUtil util = new ExcelUtil(CsTest1.class); + return util.exportExcel(list, "【请填写功能名称】数据"); + } + + /** + * 新增【请填写功能名称】 + */ + @GetMapping("/add") + public String add() + { + return prefix + "/add"; + } + + /** + * 新增保存【请填写功能名称】 + */ + @RequiresPermissions("system:test1:add") + @Log(title = "【请填写功能名称】", businessType = BusinessType.INSERT) + @PostMapping("/add") + @ResponseBody + public AjaxResult addSave(CsTest1 csTest1) + { + return toAjax(csTest1Service.insertCsTest1(csTest1)); + } + + /** + * 修改【请填写功能名称】 + */ + @RequiresPermissions("system:test1:edit") + @GetMapping("/edit/{id}") + public String edit(@PathVariable("id") Long id, ModelMap mmap) + { + CsTest1 csTest1 = csTest1Service.selectCsTest1ById(id); + mmap.put("csTest1", csTest1); + return prefix + "/edit"; + } + + /** + * 修改保存【请填写功能名称】 + */ + @RequiresPermissions("system:test1:edit") + @Log(title = "【请填写功能名称】", businessType = BusinessType.UPDATE) + @PostMapping("/edit") + @ResponseBody + public AjaxResult editSave(CsTest1 csTest1) + { + return toAjax(csTest1Service.updateCsTest1(csTest1)); + } + + /** + * 删除【请填写功能名称】 + */ + @RequiresPermissions("system:test1:remove") + @Log(title = "【请填写功能名称】", businessType = BusinessType.DELETE) + @PostMapping( "/remove") + @ResponseBody + public AjaxResult remove(String ids) + { + return toAjax(csTest1Service.deleteCsTest1ByIds(ids)); + } +} diff --git a/cs_test1/src/main/java/com/ruoyi/system/domain/CsTest1.java b/cs_test1/src/main/java/com/ruoyi/system/domain/CsTest1.java new file mode 100644 index 0000000..3f9881d --- /dev/null +++ b/cs_test1/src/main/java/com/ruoyi/system/domain/CsTest1.java @@ -0,0 +1,65 @@ +package com.ruoyi.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 【请填写功能名称】对象 cs_test1 + * + * @author ruoyi + * @date 2023-04-11 + */ +public class CsTest1 extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** id */ + private Long id; + + /** 姓名 */ + @Excel(name = "姓名") + private String name; + + /** 密码 */ + @Excel(name = "密码") + private String password; + + public void setId(Long id) + { + this.id = id; + } + + public Long getId() + { + return id; + } + public void setName(String name) + { + this.name = name; + } + + public String getName() + { + return name; + } + public void setPassword(String password) + { + this.password = password; + } + + public String getPassword() + { + return password; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("name", getName()) + .append("password", getPassword()) + .toString(); + } +} diff --git a/cs_test1/src/main/java/com/ruoyi/system/mapper/CsTest1Mapper.java b/cs_test1/src/main/java/com/ruoyi/system/mapper/CsTest1Mapper.java new file mode 100644 index 0000000..d25c3b2 --- /dev/null +++ b/cs_test1/src/main/java/com/ruoyi/system/mapper/CsTest1Mapper.java @@ -0,0 +1,61 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import com.ruoyi.system.domain.CsTest1; + +/** + * 【请填写功能名称】Mapper接口 + * + * @author ruoyi + * @date 2023-04-11 + */ +public interface CsTest1Mapper +{ + /** + * 查询【请填写功能名称】 + * + * @param id 【请填写功能名称】主键 + * @return 【请填写功能名称】 + */ + public CsTest1 selectCsTest1ById(Long id); + + /** + * 查询【请填写功能名称】列表 + * + * @param csTest1 【请填写功能名称】 + * @return 【请填写功能名称】集合 + */ + public List selectCsTest1List(CsTest1 csTest1); + + /** + * 新增【请填写功能名称】 + * + * @param csTest1 【请填写功能名称】 + * @return 结果 + */ + public int insertCsTest1(CsTest1 csTest1); + + /** + * 修改【请填写功能名称】 + * + * @param csTest1 【请填写功能名称】 + * @return 结果 + */ + public int updateCsTest1(CsTest1 csTest1); + + /** + * 删除【请填写功能名称】 + * + * @param id 【请填写功能名称】主键 + * @return 结果 + */ + public int deleteCsTest1ById(Long id); + + /** + * 批量删除【请填写功能名称】 + * + * @param ids 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteCsTest1ByIds(String[] ids); +} diff --git a/cs_test1/src/main/java/com/ruoyi/system/service/ICsTest1Service.java b/cs_test1/src/main/java/com/ruoyi/system/service/ICsTest1Service.java new file mode 100644 index 0000000..a41f833 --- /dev/null +++ b/cs_test1/src/main/java/com/ruoyi/system/service/ICsTest1Service.java @@ -0,0 +1,61 @@ +package com.ruoyi.system.service; + +import java.util.List; +import com.ruoyi.system.domain.CsTest1; + +/** + * 【请填写功能名称】Service接口 + * + * @author ruoyi + * @date 2023-04-11 + */ +public interface ICsTest1Service +{ + /** + * 查询【请填写功能名称】 + * + * @param id 【请填写功能名称】主键 + * @return 【请填写功能名称】 + */ + public CsTest1 selectCsTest1ById(Long id); + + /** + * 查询【请填写功能名称】列表 + * + * @param csTest1 【请填写功能名称】 + * @return 【请填写功能名称】集合 + */ + public List selectCsTest1List(CsTest1 csTest1); + + /** + * 新增【请填写功能名称】 + * + * @param csTest1 【请填写功能名称】 + * @return 结果 + */ + public int insertCsTest1(CsTest1 csTest1); + + /** + * 修改【请填写功能名称】 + * + * @param csTest1 【请填写功能名称】 + * @return 结果 + */ + public int updateCsTest1(CsTest1 csTest1); + + /** + * 批量删除【请填写功能名称】 + * + * @param ids 需要删除的【请填写功能名称】主键集合 + * @return 结果 + */ + public int deleteCsTest1ByIds(String ids); + + /** + * 删除【请填写功能名称】信息 + * + * @param id 【请填写功能名称】主键 + * @return 结果 + */ + public int deleteCsTest1ById(Long id); +} diff --git a/cs_test1/src/main/java/com/ruoyi/system/service/impl/CsTest1ServiceImpl.java b/cs_test1/src/main/java/com/ruoyi/system/service/impl/CsTest1ServiceImpl.java new file mode 100644 index 0000000..2faf068 --- /dev/null +++ b/cs_test1/src/main/java/com/ruoyi/system/service/impl/CsTest1ServiceImpl.java @@ -0,0 +1,94 @@ +package com.ruoyi.system.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.system.mapper.CsTest1Mapper; +import com.ruoyi.system.domain.CsTest1; +import com.ruoyi.system.service.ICsTest1Service; +import com.ruoyi.common.core.text.Convert; + +/** + * 【请填写功能名称】Service业务层处理 + * + * @author ruoyi + * @date 2023-04-11 + */ +@Service +public class CsTest1ServiceImpl implements ICsTest1Service +{ + @Autowired + private CsTest1Mapper csTest1Mapper; + + /** + * 查询【请填写功能名称】 + * + * @param id 【请填写功能名称】主键 + * @return 【请填写功能名称】 + */ + @Override + public CsTest1 selectCsTest1ById(Long id) + { + return csTest1Mapper.selectCsTest1ById(id); + } + + /** + * 查询【请填写功能名称】列表 + * + * @param csTest1 【请填写功能名称】 + * @return 【请填写功能名称】 + */ + @Override + public List selectCsTest1List(CsTest1 csTest1) + { + return csTest1Mapper.selectCsTest1List(csTest1); + } + + /** + * 新增【请填写功能名称】 + * + * @param csTest1 【请填写功能名称】 + * @return 结果 + */ + @Override + public int insertCsTest1(CsTest1 csTest1) + { + return csTest1Mapper.insertCsTest1(csTest1); + } + + /** + * 修改【请填写功能名称】 + * + * @param csTest1 【请填写功能名称】 + * @return 结果 + */ + @Override + public int updateCsTest1(CsTest1 csTest1) + { + return csTest1Mapper.updateCsTest1(csTest1); + } + + /** + * 批量删除【请填写功能名称】 + * + * @param ids 需要删除的【请填写功能名称】主键 + * @return 结果 + */ + @Override + public int deleteCsTest1ByIds(String ids) + { + return csTest1Mapper.deleteCsTest1ByIds(Convert.toStrArray(ids)); + } + + /** + * 删除【请填写功能名称】信息 + * + * @param id 【请填写功能名称】主键 + * @return 结果 + */ + @Override + public int deleteCsTest1ById(Long id) + { + return csTest1Mapper.deleteCsTest1ById(id); + } +} diff --git a/cs_test1/src/main/resources/mapper/system/CsTest1Mapper.xml b/cs_test1/src/main/resources/mapper/system/CsTest1Mapper.xml new file mode 100644 index 0000000..4060451 --- /dev/null +++ b/cs_test1/src/main/resources/mapper/system/CsTest1Mapper.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + select id, name, password from cs_test1 + + + + + + + + insert into cs_test1 + + name, + password, + + + #{name}, + #{password}, + + + + + update cs_test1 + + name = #{name}, + password = #{password}, + + where id = #{id} + + + + delete from cs_test1 where id = #{id} + + + + delete from cs_test1 where id in + + #{id} + + + + \ No newline at end of file diff --git a/cs_test1/src/main/resources/templates/system/test1/add.html b/cs_test1/src/main/resources/templates/system/test1/add.html new file mode 100644 index 0000000..e6dbc15 --- /dev/null +++ b/cs_test1/src/main/resources/templates/system/test1/add.html @@ -0,0 +1,37 @@ + + + + + + +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/cs_test1/src/main/resources/templates/system/test1/edit.html b/cs_test1/src/main/resources/templates/system/test1/edit.html new file mode 100644 index 0000000..dba9536 --- /dev/null +++ b/cs_test1/src/main/resources/templates/system/test1/edit.html @@ -0,0 +1,38 @@ + + + + + + +
+
+ +
+ +
+ +
+
+
+ +
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/cs_test1/src/main/resources/templates/system/test1/test1.html b/cs_test1/src/main/resources/templates/system/test1/test1.html new file mode 100644 index 0000000..2035b66 --- /dev/null +++ b/cs_test1/src/main/resources/templates/system/test1/test1.html @@ -0,0 +1,94 @@ + + + + + + +
+
+
+
+
+ +
+
+
+ + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/doc/若依环境使用手册.docx b/doc/若依环境使用手册.docx new file mode 100644 index 0000000000000000000000000000000000000000..fa5b62d7dcc9b9a31cc58db611ecac7b00cc385a GIT binary patch literal 426497 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

lksLB!Nmj-*B*fUrQfrUk(X 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%0Ak)Rl1D+4<~C+3nV#reCkhh! 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

ry>@!6&DKiX&7 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≀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

3mMKSNM*HIJBki30ER}0(^exCYM!!! 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 r0@aV!V?s9pwLgK-&%h8 + + 4.0.0 + + com.ruoyi + ruoyi + 4.7.6 + + ruoyi + http://www.ruoyi.vip + 若依管理系统 + + + 4.7.6 + UTF-8 + UTF-8 + 1.8 + 3.1.1 + 1.10.1 + 2.1.0 + 1.2.16 + 1.21 + 2.3.3 + 3.0.0 + 1.4.6 + 1.2.83 + 6.4.0 + 2.11.0 + 4.1.2 + 2.3 + + + + + + + + + org.springframework.boot + spring-boot-dependencies + 2.5.14 + pom + import + + + + + com.alibaba + druid-spring-boot-starter + ${druid.version} + + + + + pro.fessional + kaptcha + ${kaptcha.version} + + + + + org.apache.shiro + shiro-core + ${shiro.version} + + + + + org.apache.shiro + shiro-spring + ${shiro.version} + + + + + org.apache.shiro + shiro-ehcache + ${shiro.version} + + + + + com.github.theborakompanioni + thymeleaf-extras-shiro + ${thymeleaf.extras.shiro.version} + + + + + eu.bitwalker + UserAgentUtils + ${bitwalker.version} + + + + + com.github.pagehelper + pagehelper-spring-boot-starter + ${pagehelper.boot.version} + + + + + com.github.oshi + oshi-core + ${oshi.version} + + + + + io.springfox + springfox-boot-starter + ${swagger.version} + + + io.swagger + swagger-models + + + + + + + commons-io + commons-io + ${commons.io.version} + + + + + org.apache.poi + poi-ooxml + ${poi.version} + + + + + org.apache.velocity + velocity-engine-core + ${velocity.version} + + + + + com.alibaba + fastjson + ${fastjson.version} + + + + + com.ruoyi + ruoyi-quartz + ${ruoyi.version} + + + + + com.ruoyi + ruoyi-generator + ${ruoyi.version} + + + com.ruoyi + cs_test + ${ruoyi.version} + + + com.ruoyi + cs_test1 + ${ruoyi.version} + + + + + com.ruoyi + ruoyi-framework + ${ruoyi.version} + + + + + com.ruoyi + ruoyi-system + ${ruoyi.version} + + + + + com.ruoyi + ruoyi-common + ${ruoyi.version} + + + + + + + ruoyi-admin + ruoyi-framework + ruoyi-system + ruoyi-quartz + ruoyi-generator + ruoyi-common + cs_test + cs_test1 + + pom + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + ${java.version} + ${java.version} + ${project.build.sourceEncoding} + + + + + + + + public + aliyun nexus + https://maven.aliyun.com/repository/public + + true + + + + + + + public + aliyun nexus + https://maven.aliyun.com/repository/public + + true + + + false + + + + + \ No newline at end of file diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml new file mode 100644 index 0000000..8dcb0d2 --- /dev/null +++ b/ruoyi-admin/pom.xml @@ -0,0 +1,144 @@ + + + + ruoyi + com.ruoyi + 4.7.6 + + 4.0.0 + jar + ruoyi-admin + + + web服务入口 + + + + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + + org.springframework.boot + spring-boot-devtools + true + + + + + io.springfox + springfox-boot-starter + + + + + io.swagger + swagger-models + 1.6.2 + + + + + mysql + mysql-connector-java + + + + + com.ruoyi + ruoyi-framework + + + + + com.ruoyi + ruoyi-quartz + + + + + com.ruoyi + ruoyi-generator + + + + com.ruoyi + cs_test + + + + com.ruoyi + cs_test1 + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + 2.1.1.RELEASE + + true + + + + + repackage + + + + + + org.apache.maven.plugins + maven-war-plugin + 3.0.0 + + false + ${project.artifactId} + + + + + ${project.artifactId} + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java new file mode 100644 index 0000000..0e890c1 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java @@ -0,0 +1,30 @@ +package com.ruoyi; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; + +/** + * 启动程序 + * + * @author ruoyi + */ +@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }) +public class RuoYiApplication +{ + public static void main(String[] args) + { + // System.setProperty("spring.devtools.restart.enabled", "false"); + SpringApplication.run(RuoYiApplication.class, args); + System.out.println("(♥◠‿◠)ノ゙ 若依启动成功 ლ(´ڡ`ლ)゙ \n" + + " .-------. ____ __ \n" + + " | _ _ \\ \\ \\ / / \n" + + " | ( ' ) | \\ _. / ' \n" + + " |(_ o _) / _( )_ .' \n" + + " | (_,_).' __ ___(_ o _)' \n" + + " | |\\ \\ | || |(_,_)' \n" + + " | | \\ `' /| `-' / \n" + + " | | \\ / \\ / \n" + + " ''-' `'-' `-..-' "); + } +} \ No newline at end of file diff --git a/ruoyi-admin/src/main/java/com/ruoyi/RuoYiServletInitializer.java b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiServletInitializer.java new file mode 100644 index 0000000..6de67dc --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiServletInitializer.java @@ -0,0 +1,18 @@ +package com.ruoyi; + +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +/** + * web容器中进行部署 + * + * @author ruoyi + */ +public class RuoYiServletInitializer extends SpringBootServletInitializer +{ + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) + { + return application.sources(RuoYiApplication.class); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java new file mode 100644 index 0000000..afb1b52 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java @@ -0,0 +1,166 @@ +package com.ruoyi.web.controller.common; + +import java.util.ArrayList; +import java.util.List; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Controller; +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.ResponseBody; +import org.springframework.web.multipart.MultipartFile; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.config.ServerConfig; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.file.FileUploadUtils; +import com.ruoyi.common.utils.file.FileUtils; + +/** + * 通用请求处理 + * + * @author ruoyi + */ +@Controller +@RequestMapping("/common") +public class CommonController +{ + private static final Logger log = LoggerFactory.getLogger(CommonController.class); + + @Autowired + private ServerConfig serverConfig; + + private static final String FILE_DELIMETER = ","; + + /** + * 通用下载请求 + * + * @param fileName 文件名称 + * @param delete 是否删除 + */ + @GetMapping("/download") + public void fileDownload(String fileName, Boolean delete, HttpServletResponse response, HttpServletRequest request) + { + try + { + if (!FileUtils.checkAllowDownload(fileName)) + { + throw new Exception(StringUtils.format("文件名称({})非法,不允许下载。 ", fileName)); + } + String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1); + String filePath = RuoYiConfig.getDownloadPath() + fileName; + + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + FileUtils.setAttachmentResponseHeader(response, realFileName); + FileUtils.writeBytes(filePath, response.getOutputStream()); + if (delete) + { + FileUtils.deleteFile(filePath); + } + } + catch (Exception e) + { + log.error("下载文件失败", e); + } + } + + /** + * 通用上传请求(单个) + */ + @PostMapping("/upload") + @ResponseBody + public AjaxResult uploadFile(MultipartFile file) throws Exception + { + try + { + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, file); + String url = serverConfig.getUrl() + fileName; + AjaxResult ajax = AjaxResult.success(); + ajax.put("url", url); + ajax.put("fileName", fileName); + ajax.put("newFileName", FileUtils.getName(fileName)); + ajax.put("originalFilename", file.getOriginalFilename()); + return ajax; + } + catch (Exception e) + { + return AjaxResult.error(e.getMessage()); + } + } + + /** + * 通用上传请求(多个) + */ + @PostMapping("/uploads") + @ResponseBody + public AjaxResult uploadFiles(List files) throws Exception + { + try + { + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + List urls = new ArrayList(); + List fileNames = new ArrayList(); + List newFileNames = new ArrayList(); + List originalFilenames = new ArrayList(); + for (MultipartFile file : files) + { + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, file); + String url = serverConfig.getUrl() + fileName; + urls.add(url); + fileNames.add(fileName); + newFileNames.add(FileUtils.getName(fileName)); + originalFilenames.add(file.getOriginalFilename()); + } + AjaxResult ajax = AjaxResult.success(); + ajax.put("urls", StringUtils.join(urls, FILE_DELIMETER)); + ajax.put("fileNames", StringUtils.join(fileNames, FILE_DELIMETER)); + ajax.put("newFileNames", StringUtils.join(newFileNames, FILE_DELIMETER)); + ajax.put("originalFilenames", StringUtils.join(originalFilenames, FILE_DELIMETER)); + return ajax; + } + catch (Exception e) + { + return AjaxResult.error(e.getMessage()); + } + } + + /** + * 本地资源通用下载 + */ + @GetMapping("/download/resource") + public void resourceDownload(String resource, HttpServletRequest request, HttpServletResponse response) + throws Exception + { + try + { + if (!FileUtils.checkAllowDownload(resource)) + { + throw new Exception(StringUtils.format("资源文件({})非法,不允许下载。 ", resource)); + } + // 本地资源路径 + String localPath = RuoYiConfig.getProfile(); + // 数据库资源地址 + String downloadPath = localPath + StringUtils.substringAfter(resource, Constants.RESOURCE_PREFIX); + // 下载名称 + String downloadName = StringUtils.substringAfterLast(downloadPath, "/"); + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + FileUtils.setAttachmentResponseHeader(response, downloadName); + FileUtils.writeBytes(downloadPath, response.getOutputStream()); + } + catch (Exception e) + { + log.error("下载文件失败", e); + } + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoDialogController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoDialogController.java new file mode 100644 index 0000000..3030057 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoDialogController.java @@ -0,0 +1,98 @@ +package com.ruoyi.web.controller.demo.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +/** + * 模态窗口 + * + * @author ruoyi + */ +@Controller +@RequestMapping("/demo/modal") +public class DemoDialogController +{ + private String prefix = "demo/modal"; + + /** + * 模态窗口 + */ + @GetMapping("/dialog") + public String dialog() + { + return prefix + "/dialog"; + } + + /** + * 弹层组件 + */ + @GetMapping("/layer") + public String layer() + { + return prefix + "/layer"; + } + + /** + * 表单 + */ + @GetMapping("/form") + public String form() + { + return prefix + "/form"; + } + + /** + * 表格 + */ + @GetMapping("/table") + public String table() + { + return prefix + "/table"; + } + + /** + * 表格check + */ + @GetMapping("/check") + public String check() + { + return prefix + "/table/check"; + } + + /** + * 表格radio + */ + @GetMapping("/radio") + public String radio() + { + return prefix + "/table/radio"; + } + + /** + * 表格回传父窗体 + */ + @GetMapping("/parent") + public String parent() + { + return prefix + "/table/parent"; + } + + /** + * 多层窗口frame1 + */ + @GetMapping("/frame1") + public String frame1() + { + return prefix + "/table/frame1"; + } + + /** + * 多层窗口frame2 + */ + @GetMapping("/frame2") + public String frame2() + { + return prefix + "/table/frame2"; + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoFormController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoFormController.java new file mode 100644 index 0000000..7cc6faa --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoFormController.java @@ -0,0 +1,399 @@ +package com.ruoyi.web.controller.demo.controller; + +import java.util.ArrayList; +import java.util.List; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +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.ResponseBody; +import com.alibaba.fastjson.JSON; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.CxSelect; +import com.ruoyi.common.json.JSONObject; +import com.ruoyi.common.json.JSONObject.JSONArray; +import com.ruoyi.common.utils.StringUtils; + +/** + * 表单相关 + * + * @author ruoyi + */ +@Controller +@RequestMapping("/demo/form") +public class DemoFormController +{ + private String prefix = "demo/form"; + + private final static List users = new ArrayList(); + { + users.add(new UserFormModel(1, "1000001", "测试1", "15888888888")); + users.add(new UserFormModel(2, "1000002", "测试2", "15666666666")); + users.add(new UserFormModel(3, "1000003", "测试3", "15666666666")); + users.add(new UserFormModel(4, "1000004", "测试4", "15666666666")); + users.add(new UserFormModel(5, "1000005", "测试5", "15666666666")); + } + + /** + * 按钮页 + */ + @GetMapping("/button") + public String button() + { + return prefix + "/button"; + } + + /** + * 下拉框 + */ + @GetMapping("/select") + public String select() + { + return prefix + "/select"; + } + + /** + * 时间轴 + */ + @GetMapping("/timeline") + public String timeline() + { + return prefix + "/timeline"; + } + + /** + * 进度条 + */ + @GetMapping("/progress_bars") + public String progress_bars() + { + return prefix + "/progress_bars"; + } + + /** + * 表单校验 + */ + @GetMapping("/validate") + public String validate() + { + return prefix + "/validate"; + } + + /** + * 功能扩展(包含文件上传) + */ + @GetMapping("/jasny") + public String jasny() + { + return prefix + "/jasny"; + } + + /** + * 拖动排序 + */ + @GetMapping("/sortable") + public String sortable() + { + return prefix + "/sortable"; + } + + /** + * 单据打印 + */ + @GetMapping("/invoice") + public String invoice() + { + return prefix + "/invoice"; + } + + /** + * 标签 & 提示 + */ + @GetMapping("/labels_tips") + public String labels_tips() + { + return prefix + "/labels_tips"; + } + + /** + * 选项卡 & 面板 + */ + @GetMapping("/tabs_panels") + public String tabs_panels() + { + return prefix + "/tabs_panels"; + } + + /** + * 栅格 + */ + @GetMapping("/grid") + public String grid() + { + return prefix + "/grid"; + } + + /** + * 表单向导 + */ + @GetMapping("/wizard") + public String wizard() + { + return prefix + "/wizard"; + } + + /** + * 文件上传 + */ + @GetMapping("/upload") + public String upload() + { + return prefix + "/upload"; + } + + /** + * 日期和时间页 + */ + @GetMapping("/datetime") + public String datetime() + { + return prefix + "/datetime"; + } + + /** + * 左右互选组件 + */ + @GetMapping("/duallistbox") + public String duallistbox() + { + return prefix + "/duallistbox"; + } + + /** + * 基本表单 + */ + @GetMapping("/basic") + public String basic() + { + return prefix + "/basic"; + } + + /** + * 卡片列表 + */ + @GetMapping("/cards") + public String cards() + { + return prefix + "/cards"; + } + + /** + * summernote 富文本编辑器 + */ + @GetMapping("/summernote") + public String summernote() + { + return prefix + "/summernote"; + } + + /** + * 搜索自动补全 + */ + @GetMapping("/autocomplete") + public String autocomplete() + { + return prefix + "/autocomplete"; + } + + /** + * 多级联动下拉 + */ + @GetMapping("/cxselect") + public String cxselect(ModelMap mmap) + { + CxSelect cxSelectTB = new CxSelect(); + cxSelectTB.setN("淘宝"); + cxSelectTB.setV("taobao"); + CxSelect cxSelectTm = new CxSelect(); + cxSelectTm.setN("天猫"); + cxSelectTm.setV("tm"); + CxSelect cxSelectJhs = new CxSelect(); + cxSelectJhs.setN("聚划算"); + cxSelectJhs.setV("jhs"); + List tmList = new ArrayList(); + tmList.add(cxSelectTm); + tmList.add(cxSelectJhs); + cxSelectTB.setS(tmList); + + CxSelect cxSelectJD = new CxSelect(); + cxSelectJD.setN("京东"); + cxSelectJD.setV("jd"); + CxSelect cxSelectCs = new CxSelect(); + cxSelectCs.setN("京东超市"); + cxSelectCs.setV("jdcs"); + CxSelect cxSelectSx = new CxSelect(); + cxSelectSx.setN("京东生鲜"); + cxSelectSx.setV("jdsx"); + List jdList = new ArrayList(); + jdList.add(cxSelectCs); + jdList.add(cxSelectSx); + cxSelectJD.setS(jdList); + + List cxList = new ArrayList(); + cxList.add(cxSelectTB); + cxList.add(cxSelectJD); + + mmap.put("data", JSON.toJSON(cxList)); + return prefix + "/cxselect"; + } + + /** + * 局部刷新 + */ + @GetMapping("/localrefresh") + public String localRefresh(ModelMap mmap) + { + JSONArray list = new JSONArray(); + JSONObject item = new JSONObject(); + item.put("name", "这条任务数据是由ModelMap传递到页面的,点击添加按钮后会将这条数据替换为新数据"); + item.put("type", "默认"); + item.put("date", "2020.06.10"); + list.add(item); + mmap.put("tasks", list); + mmap.put("min", 2); + mmap.put("max", 10); + return prefix + "/localrefresh"; + } + + /** + * 局部刷新-添加任务 + * + * @param fragment 页面中的模板名称 + * @param taskName 任务名称 + */ + @PostMapping("/localrefresh/task") + public String localRefreshTask(String fragment, String taskName, ModelMap mmap) + { + JSONArray list = new JSONArray(); + JSONObject item = new JSONObject(); + item.put("name", StringUtils.defaultIfBlank(taskName, "通过电话销售过程中了解各盛市的设备仪器使用、采购情况及相关重要追踪人")); + item.put("type", "新增"); + item.put("date", "2018.06.10"); + list.add(item); + item = new JSONObject(); + item.put("name", "提高自己电话营销技巧,灵活专业地与客户进行电话交流"); + item.put("type", "新增"); + item.put("date", "2018.06.12"); + list.add(item); + mmap.put("tasks", list); + return prefix + "/localrefresh::" + fragment; + } + + /** + * 模拟数据 + */ + @GetMapping("/cityData") + @ResponseBody + public String cityData() + { + String data = "[{\"n\":\"湖南省\",\"s\":[{\"n\":\"长沙市\",\"s\":[{\"n\":\"芙蓉区\"},{\"n\":\"天心区\"},{\"n\":\"岳麓区\"},{\"n\":\"开福区\"},{\"n\":\"雨花区\"},{\"n\":\"望城区\"},{\"n\":\"长沙县\"},{\"n\":\"宁乡县\"},{\"n\":\"浏阳市\"}]},{\"n\":\"株洲市\",\"s\":[{\"n\":\"荷塘区\"},{\"n\":\"芦淞区\"},{\"n\":\"石峰区\"},{\"n\":\"天元区\"},{\"n\":\"株洲县\"},{\"n\":\"攸县\"},{\"n\":\"茶陵县\"},{\"n\":\"炎陵县\"},{\"n\":\"醴陵市\"}]},{\"n\":\"湘潭市\",\"s\":[{\"n\":\"雨湖区\"},{\"n\":\"岳塘区\"},{\"n\":\"湘潭县\"},{\"n\":\"湘乡市\"},{\"n\":\"韶山市\"}]},{\"n\":\"衡阳市\",\"s\":[{\"n\":\"珠晖区\"},{\"n\":\"雁峰区\"},{\"n\":\"石鼓区\"},{\"n\":\"蒸湘区\"},{\"n\":\"南岳区\"},{\"n\":\"衡阳县\"},{\"n\":\"衡南县\"},{\"n\":\"衡山县\"},{\"n\":\"衡东县\"},{\"n\":\"祁东县\"},{\"n\":\"耒阳市\"},{\"n\":\"常宁市\"}]},{\"n\":\"邵阳市\",\"s\":[{\"n\":\"双清区\"},{\"n\":\"大祥区\"},{\"n\":\"北塔区\"},{\"n\":\"邵东县\"},{\"n\":\"新邵县\"},{\"n\":\"邵阳县\"},{\"n\":\"隆回县\"},{\"n\":\"洞口县\"},{\"n\":\"绥宁县\"},{\"n\":\"新宁县\"},{\"n\":\"城步苗族自治县\"},{\"n\":\"武冈市\"}]},{\"n\":\"岳阳市\",\"s\":[{\"n\":\"岳阳楼区\"},{\"n\":\"云溪区\"},{\"n\":\"君山区\"},{\"n\":\"岳阳县\"},{\"n\":\"华容县\"},{\"n\":\"湘阴县\"},{\"n\":\"平江县\"},{\"n\":\"汨罗市\"},{\"n\":\"临湘市\"}]},{\"n\":\"常德市\",\"s\":[{\"n\":\"武陵区\"},{\"n\":\"鼎城区\"},{\"n\":\"安乡县\"},{\"n\":\"汉寿县\"},{\"n\":\"澧县\"},{\"n\":\"临澧县\"},{\"n\":\"桃源县\"},{\"n\":\"石门县\"},{\"n\":\"津市市\"}]},{\"n\":\"张家界市\",\"s\":[{\"n\":\"永定区\"},{\"n\":\"武陵源区\"},{\"n\":\"慈利县\"},{\"n\":\"桑植县\"}]},{\"n\":\"益阳市\",\"s\":[{\"n\":\"资阳区\"},{\"n\":\"赫山区\"},{\"n\":\"南县\"},{\"n\":\"桃江县\"},{\"n\":\"安化县\"},{\"n\":\"沅江市\"}]},{\"n\":\"郴州市\",\"s\":[{\"n\":\"北湖区\"},{\"n\":\"苏仙区\"},{\"n\":\"桂阳县\"},{\"n\":\"宜章县\"},{\"n\":\"永兴县\"},{\"n\":\"嘉禾县\"},{\"n\":\"临武县\"},{\"n\":\"汝城县\"},{\"n\":\"桂东县\"},{\"n\":\"安仁县\"},{\"n\":\"资兴市\"}]},{\"n\":\"永州市\",\"s\":[{\"n\":\"零陵区\"},{\"n\":\"冷水滩区\"},{\"n\":\"祁阳县\"},{\"n\":\"东安县\"},{\"n\":\"双牌县\"},{\"n\":\"道县\"},{\"n\":\"江永县\"},{\"n\":\"宁远县\"},{\"n\":\"蓝山县\"},{\"n\":\"新田县\"},{\"n\":\"江华瑶族自治县\"}]},{\"n\":\"怀化市\",\"s\":[{\"n\":\"鹤城区\"},{\"n\":\"中方县\"},{\"n\":\"沅陵县\"},{\"n\":\"辰溪县\"},{\"n\":\"溆浦县\"},{\"n\":\"会同县\"},{\"n\":\"麻阳苗族自治县\"},{\"n\":\"新晃侗族自治县\"},{\"n\":\"芷江侗族自治县\"},{\"n\":\"靖州苗族侗族自治县\"},{\"n\":\"通道侗族自治县\"},{\"n\":\"洪江市\"}]},{\"n\":\"娄底市\",\"s\":[{\"n\":\"娄星区\"},{\"n\":\"双峰县\"},{\"n\":\"新化县\"},{\"n\":\"冷水江市\"},{\"n\":\"涟源市\"}]},{\"n\":\"湘西土家族苗族自治州\",\"s\":[{\"n\":\"吉首市\"},{\"n\":\"泸溪县\"},{\"n\":\"凤凰县\"},{\"n\":\"花垣县\"},{\"n\":\"保靖县\"},{\"n\":\"古丈县\"},{\"n\":\"永顺县\"},{\"n\":\"龙山县\"}]}]},{\"n\":\"广东省\",\"s\":[{\"n\":\"广州市\",\"s\":[{\"n\":\"荔湾区\"},{\"n\":\"越秀区\"},{\"n\":\"海珠区\"},{\"n\":\"天河区\"},{\"n\":\"白云区\"},{\"n\":\"黄埔区\"},{\"n\":\"番禺区\"},{\"n\":\"花都区\"},{\"n\":\"南沙区\"},{\"n\":\"萝岗区\"},{\"n\":\"增城市\"},{\"n\":\"从化市\"}]},{\"n\":\"韶关市\",\"s\":[{\"n\":\"武江区\"},{\"n\":\"浈江区\"},{\"n\":\"曲江区\"},{\"n\":\"始兴县\"},{\"n\":\"仁化县\"},{\"n\":\"翁源县\"},{\"n\":\"乳源瑶族自治县\"},{\"n\":\"新丰县\"},{\"n\":\"乐昌市\"},{\"n\":\"南雄市\"}]},{\"n\":\"深圳市\",\"s\":[{\"n\":\"罗湖区\"},{\"n\":\"福田区\"},{\"n\":\"南山区\"},{\"n\":\"宝安区\"},{\"n\":\"龙岗区\"},{\"n\":\"盐田区\"}]},{\"n\":\"珠海市\",\"s\":[{\"n\":\"香洲区\"},{\"n\":\"斗门区\"},{\"n\":\"金湾区\"}]},{\"n\":\"汕头市\",\"s\":[{\"n\":\"龙湖区\"},{\"n\":\"金平区\"},{\"n\":\"濠江区\"},{\"n\":\"潮阳区\"},{\"n\":\"潮南区\"},{\"n\":\"澄海区\"},{\"n\":\"南澳县\"}]},{\"n\":\"佛山市\",\"s\":[{\"n\":\"禅城区\"},{\"n\":\"南海区\"},{\"n\":\"顺德区\"},{\"n\":\"三水区\"},{\"n\":\"高明区\"}]},{\"n\":\"江门市\",\"s\":[{\"n\":\"蓬江区\"},{\"n\":\"江海区\"},{\"n\":\"新会区\"},{\"n\":\"台山市\"},{\"n\":\"开平市\"},{\"n\":\"鹤山市\"},{\"n\":\"恩平市\"}]},{\"n\":\"湛江市\",\"s\":[{\"n\":\"赤坎区\"},{\"n\":\"霞山区\"},{\"n\":\"坡头区\"},{\"n\":\"麻章区\"},{\"n\":\"遂溪县\"},{\"n\":\"徐闻县\"},{\"n\":\"廉江市\"},{\"n\":\"雷州市\"},{\"n\":\"吴川市\"}]},{\"n\":\"茂名市\",\"s\":[{\"n\":\"茂南区\"},{\"n\":\"茂港区\"},{\"n\":\"电白县\"},{\"n\":\"高州市\"},{\"n\":\"化州市\"},{\"n\":\"信宜市\"}]},{\"n\":\"肇庆市\",\"s\":[{\"n\":\"端州区\"},{\"n\":\"鼎湖区\"},{\"n\":\"广宁县\"},{\"n\":\"怀集县\"},{\"n\":\"封开县\"},{\"n\":\"德庆县\"},{\"n\":\"高要市\"},{\"n\":\"四会市\"}]},{\"n\":\"惠州市\",\"s\":[{\"n\":\"惠城区\"},{\"n\":\"惠阳区\"},{\"n\":\"博罗县\"},{\"n\":\"惠东县\"},{\"n\":\"龙门县\"}]},{\"n\":\"梅州市\",\"s\":[{\"n\":\"梅江区\"},{\"n\":\"梅县\"},{\"n\":\"大埔县\"},{\"n\":\"丰顺县\"},{\"n\":\"五华县\"},{\"n\":\"平远县\"},{\"n\":\"蕉岭县\"},{\"n\":\"兴宁市\"}]},{\"n\":\"汕尾市\",\"s\":[{\"n\":\"城区\"},{\"n\":\"海丰县\"},{\"n\":\"陆河县\"},{\"n\":\"陆丰市\"}]},{\"n\":\"河源市\",\"s\":[{\"n\":\"源城区\"},{\"n\":\"紫金县\"},{\"n\":\"龙川县\"},{\"n\":\"连平县\"},{\"n\":\"和平县\"},{\"n\":\"东源县\"}]},{\"n\":\"阳江市\",\"s\":[{\"n\":\"江城区\"},{\"n\":\"阳西县\"},{\"n\":\"阳东县\"},{\"n\":\"阳春市\"}]},{\"n\":\"清远市\",\"s\":[{\"n\":\"清城区\"},{\"n\":\"清新区\"},{\"n\":\"佛冈县\"},{\"n\":\"阳山县\"},{\"n\":\"连山壮族瑶族自治县\"},{\"n\":\"连南瑶族自治县\"},{\"n\":\"英德市\"},{\"n\":\"连州市\"}]},{\"n\":\"东莞市\"},{\"n\":\"中山市\"},{\"n\":\"潮州市\",\"s\":[{\"n\":\"湘桥区\"},{\"n\":\"潮安区\"},{\"n\":\"饶平县\"}]},{\"n\":\"揭阳市\",\"s\":[{\"n\":\"榕城区\"},{\"n\":\"揭东区\"},{\"n\":\"揭西县\"},{\"n\":\"惠来县\"},{\"n\":\"普宁市\"}]},{\"n\":\"云浮市\",\"s\":[{\"n\":\"云城区\"},{\"n\":\"新兴县\"},{\"n\":\"郁南县\"},{\"n\":\"云安县\"},{\"n\":\"罗定市\"}]}]}]"; + return data; + } + + /** + * 获取用户数据 + */ + @GetMapping("/userModel") + @ResponseBody + public AjaxResult userModel() + { + AjaxResult ajax = new AjaxResult(); + + ajax.put("code", 200); + ajax.put("value", users); + return ajax; + } + + /** + * 获取数据集合 + */ + @GetMapping("/collection") + @ResponseBody + public AjaxResult collection() + { + String[] array = { "ruoyi 1", "ruoyi 2", "ruoyi 3", "ruoyi 4", "ruoyi 5" }; + AjaxResult ajax = new AjaxResult(); + ajax.put("value", array); + return ajax; + } +} + +class UserFormModel +{ + /** 用户ID */ + private int userId; + + /** 用户编号 */ + private String userCode; + + /** 用户姓名 */ + private String userName; + + /** 用户手机 */ + private String userPhone; + + public UserFormModel() + { + + } + + public UserFormModel(int userId, String userCode, String userName, String userPhone) + { + this.userId = userId; + this.userCode = userCode; + this.userName = userName; + this.userPhone = userPhone; + } + + public int getUserId() + { + return userId; + } + + public void setUserId(int userId) + { + this.userId = userId; + } + + public String getUserCode() + { + return userCode; + } + + public void setUserCode(String userCode) + { + this.userCode = userCode; + } + + public String getUserName() + { + return userName; + } + + public void setUserName(String userName) + { + this.userName = userName; + } + + public String getUserPhone() + { + return userPhone; + } + + public void setUserPhone(String userPhone) + { + this.userPhone = userPhone; + } + +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoIconController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoIconController.java new file mode 100644 index 0000000..b6884cc --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoIconController.java @@ -0,0 +1,35 @@ +package com.ruoyi.web.controller.demo.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +/** + * 图标相关 + * + * @author ruoyi + */ +@Controller +@RequestMapping("/demo/icon") +public class DemoIconController +{ + private String prefix = "demo/icon"; + + /** + * FontAwesome图标 + */ + @GetMapping("/fontawesome") + public String fontAwesome() + { + return prefix + "/fontawesome"; + } + + /** + * Glyphicons图标 + */ + @GetMapping("/glyphicons") + public String glyphicons() + { + return prefix + "/glyphicons"; + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoOperateController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoOperateController.java new file mode 100644 index 0000000..18cb90a --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoOperateController.java @@ -0,0 +1,326 @@ +package com.ruoyi.web.controller.demo.controller; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.multipart.MultipartFile; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.PageDomain; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.core.page.TableSupport; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.web.controller.demo.domain.CustomerModel; +import com.ruoyi.web.controller.demo.domain.UserOperateModel; + +/** + * 操作控制 + * + * @author ruoyi + */ +@Controller +@RequestMapping("/demo/operate") +public class DemoOperateController extends BaseController +{ + private String prefix = "demo/operate"; + + private final static Map users = new LinkedHashMap(); + { + users.put(1, new UserOperateModel(1, "1000001", "测试1", "0", "15888888888", "ry@qq.com", 150.0, "0")); + users.put(2, new UserOperateModel(2, "1000002", "测试2", "1", "15666666666", "ry@qq.com", 180.0, "1")); + users.put(3, new UserOperateModel(3, "1000003", "测试3", "0", "15666666666", "ry@qq.com", 110.0, "1")); + users.put(4, new UserOperateModel(4, "1000004", "测试4", "1", "15666666666", "ry@qq.com", 220.0, "1")); + users.put(5, new UserOperateModel(5, "1000005", "测试5", "0", "15666666666", "ry@qq.com", 140.0, "1")); + users.put(6, new UserOperateModel(6, "1000006", "测试6", "1", "15666666666", "ry@qq.com", 330.0, "1")); + users.put(7, new UserOperateModel(7, "1000007", "测试7", "0", "15666666666", "ry@qq.com", 160.0, "1")); + users.put(8, new UserOperateModel(8, "1000008", "测试8", "1", "15666666666", "ry@qq.com", 170.0, "1")); + users.put(9, new UserOperateModel(9, "1000009", "测试9", "0", "15666666666", "ry@qq.com", 180.0, "1")); + users.put(10, new UserOperateModel(10, "1000010", "测试10", "0", "15666666666", "ry@qq.com", 210.0, "1")); + users.put(11, new UserOperateModel(11, "1000011", "测试11", "1", "15666666666", "ry@qq.com", 110.0, "1")); + users.put(12, new UserOperateModel(12, "1000012", "测试12", "0", "15666666666", "ry@qq.com", 120.0, "1")); + users.put(13, new UserOperateModel(13, "1000013", "测试13", "1", "15666666666", "ry@qq.com", 380.0, "1")); + users.put(14, new UserOperateModel(14, "1000014", "测试14", "0", "15666666666", "ry@qq.com", 280.0, "1")); + users.put(15, new UserOperateModel(15, "1000015", "测试15", "0", "15666666666", "ry@qq.com", 570.0, "1")); + users.put(16, new UserOperateModel(16, "1000016", "测试16", "1", "15666666666", "ry@qq.com", 260.0, "1")); + users.put(17, new UserOperateModel(17, "1000017", "测试17", "1", "15666666666", "ry@qq.com", 210.0, "1")); + users.put(18, new UserOperateModel(18, "1000018", "测试18", "1", "15666666666", "ry@qq.com", 340.0, "1")); + users.put(19, new UserOperateModel(19, "1000019", "测试19", "1", "15666666666", "ry@qq.com", 160.0, "1")); + users.put(20, new UserOperateModel(20, "1000020", "测试20", "1", "15666666666", "ry@qq.com", 220.0, "1")); + users.put(21, new UserOperateModel(21, "1000021", "测试21", "1", "15666666666", "ry@qq.com", 120.0, "1")); + users.put(22, new UserOperateModel(22, "1000022", "测试22", "1", "15666666666", "ry@qq.com", 130.0, "1")); + users.put(23, new UserOperateModel(23, "1000023", "测试23", "1", "15666666666", "ry@qq.com", 490.0, "1")); + users.put(24, new UserOperateModel(24, "1000024", "测试24", "1", "15666666666", "ry@qq.com", 570.0, "1")); + users.put(25, new UserOperateModel(25, "1000025", "测试25", "1", "15666666666", "ry@qq.com", 250.0, "1")); + users.put(26, new UserOperateModel(26, "1000026", "测试26", "1", "15666666666", "ry@qq.com", 250.0, "1")); + } + + /** + * 表格 + */ + @GetMapping("/table") + public String table() + { + return prefix + "/table"; + } + + /** + * 其他 + */ + @GetMapping("/other") + public String other() + { + return prefix + "/other"; + } + + /** + * 查询数据 + */ + @PostMapping("/list") + @ResponseBody + public TableDataInfo list(UserOperateModel userModel) + { + TableDataInfo rspData = new TableDataInfo(); + List userList = new ArrayList(users.values()); + // 查询条件过滤 + if (StringUtils.isNotEmpty(userModel.getSearchValue())) + { + userList.clear(); + for (Map.Entry entry : users.entrySet()) + { + if (entry.getValue().getUserName().equals(userModel.getSearchValue())) + { + userList.add(entry.getValue()); + } + } + } + else if (StringUtils.isNotEmpty(userModel.getUserName())) + { + userList.clear(); + for (Map.Entry entry : users.entrySet()) + { + if (entry.getValue().getUserName().equals(userModel.getUserName())) + { + userList.add(entry.getValue()); + } + } + } + PageDomain pageDomain = TableSupport.buildPageRequest(); + if (null == pageDomain.getPageNum() || null == pageDomain.getPageSize()) + { + rspData.setRows(userList); + rspData.setTotal(userList.size()); + return rspData; + } + Integer pageNum = (pageDomain.getPageNum() - 1) * 10; + Integer pageSize = pageDomain.getPageNum() * 10; + if (pageSize > userList.size()) + { + pageSize = userList.size(); + } + rspData.setRows(userList.subList(pageNum, pageSize)); + rspData.setTotal(userList.size()); + return rspData; + } + + /** + * 新增用户 + */ + @GetMapping("/add") + public String add(ModelMap mmap) + { + return prefix + "/add"; + } + + /** + * 新增保存用户 + */ + @PostMapping("/add") + @ResponseBody + public AjaxResult addSave(UserOperateModel user) + { + Integer userId = users.size() + 1; + user.setUserId(userId); + return AjaxResult.success(users.put(userId, user)); + } + + /** + * 新增保存主子表信息 + */ + @PostMapping("/customer/add") + @ResponseBody + public AjaxResult addSave(CustomerModel customerModel) + { + System.out.println(customerModel.toString()); + return AjaxResult.success(); + } + + /** + * 修改用户 + */ + @GetMapping("/edit/{userId}") + public String edit(@PathVariable("userId") Integer userId, ModelMap mmap) + { + mmap.put("user", users.get(userId)); + return prefix + "/edit"; + } + + /** + * 修改保存用户 + */ + @PostMapping("/edit") + @ResponseBody + public AjaxResult editSave(UserOperateModel user) + { + return AjaxResult.success(users.put(user.getUserId(), user)); + } + + /** + * 导出 + */ + @PostMapping("/export") + @ResponseBody + public AjaxResult export(UserOperateModel user) + { + List list = new ArrayList(users.values()); + ExcelUtil util = new ExcelUtil(UserOperateModel.class); + return util.exportExcel(list, "用户数据"); + } + + /** + * 下载模板 + */ + @GetMapping("/importTemplate") + @ResponseBody + public AjaxResult importTemplate() + { + ExcelUtil util = new ExcelUtil(UserOperateModel.class); + return util.importTemplateExcel("用户数据"); + } + + /** + * 导入数据 + */ + @PostMapping("/importData") + @ResponseBody + public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception + { + ExcelUtil util = new ExcelUtil(UserOperateModel.class); + List userList = util.importExcel(file.getInputStream()); + String message = importUser(userList, updateSupport); + return AjaxResult.success(message); + } + + /** + * 删除用户 + */ + @PostMapping("/remove") + @ResponseBody + public AjaxResult remove(String ids) + { + Integer[] userIds = Convert.toIntArray(ids); + for (Integer userId : userIds) + { + users.remove(userId); + } + return AjaxResult.success(); + } + + /** + * 查看详细 + */ + @GetMapping("/detail/{userId}") + public String detail(@PathVariable("userId") Integer userId, ModelMap mmap) + { + mmap.put("user", users.get(userId)); + return prefix + "/detail"; + } + + @PostMapping("/clean") + @ResponseBody + public AjaxResult clean() + { + users.clear(); + return success(); + } + + /** + * 导入用户数据 + * + * @param userList 用户数据列表 + * @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据 + * @return 结果 + */ + public String importUser(List userList, Boolean isUpdateSupport) + { + if (StringUtils.isNull(userList) || userList.size() == 0) + { + throw new ServiceException("导入用户数据不能为空!"); + } + int successNum = 0; + int failureNum = 0; + StringBuilder successMsg = new StringBuilder(); + StringBuilder failureMsg = new StringBuilder(); + for (UserOperateModel user : userList) + { + try + { + // 验证是否存在这个用户 + boolean userFlag = false; + for (Map.Entry entry : users.entrySet()) + { + if (entry.getValue().getUserName().equals(user.getUserName())) + { + userFlag = true; + break; + } + } + if (!userFlag) + { + Integer userId = users.size() + 1; + user.setUserId(userId); + users.put(userId, user); + successNum++; + successMsg.append("
" + successNum + "、用户 " + user.getUserName() + " 导入成功"); + } + else if (isUpdateSupport) + { + users.put(user.getUserId(), user); + successNum++; + successMsg.append("
" + successNum + "、用户 " + user.getUserName() + " 更新成功"); + } + else + { + failureNum++; + failureMsg.append("
" + failureNum + "、用户 " + user.getUserName() + " 已存在"); + } + } + catch (Exception e) + { + failureNum++; + String msg = "
" + failureNum + "、账号 " + user.getUserName() + " 导入失败:"; + failureMsg.append(msg + e.getMessage()); + } + } + if (failureNum > 0) + { + failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:"); + throw new ServiceException(failureMsg.toString()); + } + else + { + successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:"); + } + return successMsg.toString(); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoReportController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoReportController.java new file mode 100644 index 0000000..6101008 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoReportController.java @@ -0,0 +1,53 @@ +package com.ruoyi.web.controller.demo.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +/** + * 报表 + * + * @author ruoyi + */ +@Controller +@RequestMapping("/demo/report") +public class DemoReportController +{ + private String prefix = "demo/report"; + + /** + * 百度ECharts + */ + @GetMapping("/echarts") + public String echarts() + { + return prefix + "/echarts"; + } + + /** + * 图表插件 + */ + @GetMapping("/peity") + public String peity() + { + return prefix + "/peity"; + } + + /** + * 线状图插件 + */ + @GetMapping("/sparkline") + public String sparkline() + { + return prefix + "/sparkline"; + } + + /** + * 图表组合 + */ + @GetMapping("/metrics") + public String metrics() + { + return prefix + "/metrics"; + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoTableController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoTableController.java new file mode 100644 index 0000000..680c29b --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoTableController.java @@ -0,0 +1,846 @@ +package com.ruoyi.web.controller.demo.controller; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +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.ResponseBody; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.annotation.Excel.ColumnType; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.PageDomain; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.core.page.TableSupport; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.poi.ExcelUtil; + +/** + * 表格相关 + * + * @author ruoyi + */ +@Controller +@RequestMapping("/demo/table") +public class DemoTableController extends BaseController +{ + private String prefix = "demo/table"; + + private final static List users = new ArrayList(); + { + users.add(new UserTableModel(1, "1000001", "测试1", "0", "15888888888", "ry@qq.com", 150.0, "0")); + users.add(new UserTableModel(2, "1000002", "测试2", "1", "15666666666", "ry@qq.com", 180.0, "1")); + users.add(new UserTableModel(3, "1000003", "测试3", "0", "15666666666", "ry@qq.com", 110.0, "1")); + users.add(new UserTableModel(4, "1000004", "测试4", "1", "15666666666", "ry@qq.com", 220.0, "1")); + users.add(new UserTableModel(5, "1000005", "测试5", "0", "15666666666", "ry@qq.com", 140.0, "1")); + users.add(new UserTableModel(6, "1000006", "测试6", "1", "15666666666", "ry@qq.com", 330.0, "1")); + users.add(new UserTableModel(7, "1000007", "测试7", "0", "15666666666", "ry@qq.com", 160.0, "1")); + users.add(new UserTableModel(8, "1000008", "测试8", "1", "15666666666", "ry@qq.com", 170.0, "1")); + users.add(new UserTableModel(9, "1000009", "测试9", "0", "15666666666", "ry@qq.com", 180.0, "1")); + users.add(new UserTableModel(10, "1000010", "测试10", "0", "15666666666", "ry@qq.com", 210.0, "1")); + users.add(new UserTableModel(11, "1000011", "测试11", "1", "15666666666", "ry@qq.com", 110.0, "1")); + users.add(new UserTableModel(12, "1000012", "测试12", "0", "15666666666", "ry@qq.com", 120.0, "1")); + users.add(new UserTableModel(13, "1000013", "测试13", "1", "15666666666", "ry@qq.com", 380.0, "1")); + users.add(new UserTableModel(14, "1000014", "测试14", "0", "15666666666", "ry@qq.com", 280.0, "1")); + users.add(new UserTableModel(15, "1000015", "测试15", "0", "15666666666", "ry@qq.com", 570.0, "1")); + users.add(new UserTableModel(16, "1000016", "测试16", "1", "15666666666", "ry@qq.com", 260.0, "1")); + users.add(new UserTableModel(17, "1000017", "测试17", "1", "15666666666", "ry@qq.com", 210.0, "1")); + users.add(new UserTableModel(18, "1000018", "测试18", "1", "15666666666", "ry@qq.com", 340.0, "1")); + users.add(new UserTableModel(19, "1000019", "测试19", "1", "15666666666", "ry@qq.com", 160.0, "1")); + users.add(new UserTableModel(20, "1000020", "测试20", "1", "15666666666", "ry@qq.com", 220.0, "1")); + users.add(new UserTableModel(21, "1000021", "测试21", "1", "15666666666", "ry@qq.com", 120.0, "1")); + users.add(new UserTableModel(22, "1000022", "测试22", "1", "15666666666", "ry@qq.com", 130.0, "1")); + users.add(new UserTableModel(23, "1000023", "测试23", "1", "15666666666", "ry@qq.com", 490.0, "1")); + users.add(new UserTableModel(24, "1000024", "测试24", "1", "15666666666", "ry@qq.com", 570.0, "1")); + users.add(new UserTableModel(25, "1000025", "测试25", "1", "15666666666", "ry@qq.com", 250.0, "1")); + users.add(new UserTableModel(26, "1000026", "测试26", "1", "15666666666", "ry@qq.com", 250.0, "1")); + } + + private final static List areas = new ArrayList(); + { + areas.add(new AreaModel(1, 0, "广东省", "440000", "GDS", "GuangDongSheng", 1)); + areas.add(new AreaModel(2, 0, "湖南省", "430000", "HNS", "HuNanSheng", 1)); + areas.add(new AreaModel(3, 0, "河南省", "410000", "HNS", "HeNanSheng", 0)); + areas.add(new AreaModel(4, 0, "湖北省", "420000", "HBS", "HuBeiSheng", 0)); + areas.add(new AreaModel(5, 0, "辽宁省", "210000", "LNS", "LiaoNingSheng", 0)); + areas.add(new AreaModel(6, 0, "山东省", "370000", "SDS", "ShanDongSheng", 0)); + areas.add(new AreaModel(7, 0, "陕西省", "610000", "SXS", "ShanXiSheng", 0)); + areas.add(new AreaModel(8, 0, "贵州省", "520000", "GZS", "GuiZhouSheng", 0)); + areas.add(new AreaModel(9, 0, "上海市", "310000", "SHS", "ShangHaiShi", 0)); + areas.add(new AreaModel(10, 0, "重庆市", "500000", "CQS", "ChongQingShi", 0)); + areas.add(new AreaModel(11, 0, "若依省", "666666", "YYS", "RuoYiSheng", 0)); + areas.add(new AreaModel(12, 0, "安徽省", "340000", "AHS", "AnHuiSheng", 0)); + areas.add(new AreaModel(13, 0, "福建省", "350000", "FJS", "FuJianSheng", 0)); + areas.add(new AreaModel(14, 0, "海南省", "460000", "HNS", "HaiNanSheng", 0)); + areas.add(new AreaModel(15, 0, "江苏省", "320000", "JSS", "JiangSuSheng", 0)); + areas.add(new AreaModel(16, 0, "青海省", "630000", "QHS", "QingHaiSheng", 0)); + areas.add(new AreaModel(17, 0, "广西壮族自治区", "450000", "GXZZZZQ", "GuangXiZhuangZuZiZhiQu", 0)); + areas.add(new AreaModel(18, 0, "宁夏回族自治区", "640000", "NXHZZZQ", "NingXiaHuiZuZiZhiQu", 0)); + areas.add(new AreaModel(19, 0, "内蒙古自治区", "150000", "NMGZZQ", "NeiMengGuZiZhiQu", 0)); + areas.add(new AreaModel(20, 0, "新疆维吾尔自治区", "650000", "XJWWEZZQ", "XinJiangWeiWuErZiZhiQu", 0)); + areas.add(new AreaModel(21, 0, "江西省", "360000", "JXS", "JiangXiSheng", 0)); + areas.add(new AreaModel(22, 0, "浙江省", "330000", "ZJS", "ZheJiangSheng", 0)); + areas.add(new AreaModel(23, 0, "河北省", "130000", "HBS", "HeBeiSheng", 0)); + areas.add(new AreaModel(24, 0, "天津市", "120000", "TJS", "TianJinShi", 0)); + areas.add(new AreaModel(25, 0, "山西省", "140000", "SXS", "ShanXiSheng", 0)); + areas.add(new AreaModel(26, 0, "台湾省", "710000", "TWS", "TaiWanSheng", 0)); + areas.add(new AreaModel(27, 0, "甘肃省", "620000", "GSS", "GanSuSheng", 0)); + areas.add(new AreaModel(28, 0, "四川省", "510000", "SCS", "SiChuanSheng", 0)); + areas.add(new AreaModel(29, 0, "云南省", "530000", "YNS", "YunNanSheng", 0)); + areas.add(new AreaModel(30, 0, "北京市", "110000", "BJS", "BeiJingShi", 0)); + areas.add(new AreaModel(31, 0, "香港特别行政区", "810000", "XGTBXZQ", "XiangGangTeBieXingZhengQu", 0)); + areas.add(new AreaModel(32, 0, "澳门特别行政区", "820000", "AMTBXZQ", "AoMenTeBieXingZhengQu", 0)); + + areas.add(new AreaModel(100, 1, "深圳市", "440300", "SZS", "ShenZhenShi", 1)); + areas.add(new AreaModel(101, 1, "广州市", "440100", "GZS", "GuangZhouShi", 0)); + areas.add(new AreaModel(102, 1, "东莞市", "441900", "DGS", "DongGuanShi", 0)); + areas.add(new AreaModel(103, 2, "长沙市", "410005", "CSS", "ChangShaShi", 1)); + areas.add(new AreaModel(104, 2, "岳阳市", "414000", "YYS", "YueYangShi", 0)); + + areas.add(new AreaModel(1000, 100, "龙岗区", "518172", "LGQ", "LongGangQu", 0)); + areas.add(new AreaModel(1001, 100, "南山区", "518051", "NSQ", "NanShanQu", 0)); + areas.add(new AreaModel(1002, 100, "宝安区", "518101", "BAQ", "BaoAnQu", 0)); + areas.add(new AreaModel(1003, 100, "福田区", "518081", "FTQ", "FuTianQu", 0)); + areas.add(new AreaModel(1004, 103, "天心区", "410004", "TXQ", "TianXinQu", 0)); + areas.add(new AreaModel(1005, 103, "开福区", "410008", "KFQ", "KaiFuQu", 0)); + areas.add(new AreaModel(1006, 103, "芙蓉区", "410011", "FRQ", "FuRongQu", 0)); + areas.add(new AreaModel(1007, 103, "雨花区", "410011", "YHQ", "YuHuaQu", 0)); + } + + private final static List columns = new ArrayList(); + { + columns.add(new UserTableColumn("用户ID", "userId")); + columns.add(new UserTableColumn("用户编号", "userCode")); + columns.add(new UserTableColumn("用户姓名", "userName")); + columns.add(new UserTableColumn("用户手机", "userPhone")); + columns.add(new UserTableColumn("用户邮箱", "userEmail")); + columns.add(new UserTableColumn("用户状态", "status")); + } + + /** + * 搜索相关 + */ + @GetMapping("/search") + public String search() + { + return prefix + "/search"; + } + + /** + * 数据汇总 + */ + @GetMapping("/footer") + public String footer() + { + return prefix + "/footer"; + } + + /** + * 组合表头 + */ + @GetMapping("/groupHeader") + public String groupHeader() + { + return prefix + "/groupHeader"; + } + + /** + * 表格导出 + */ + @GetMapping("/export") + public String export() + { + return prefix + "/export"; + } + + /** + * 表格导出选择列 + */ + @GetMapping("/exportSelected") + public String exportSelected() + { + return prefix + "/exportSelected"; + } + + /** + * 导出数据 + */ + @PostMapping("/exportData") + @ResponseBody + public AjaxResult exportSelected(UserTableModel userModel, String userIds) + { + List userList = new ArrayList(Arrays.asList(new UserTableModel[users.size()])); + Collections.copy(userList, users); + + // 条件过滤 + if (StringUtils.isNotEmpty(userIds)) + { + userList.clear(); + for (Long userId : Convert.toLongArray(userIds)) + { + for (UserTableModel user : users) + { + if (user.getUserId() == userId) + { + userList.add(user); + } + } + } + } + ExcelUtil util = new ExcelUtil(UserTableModel.class); + return util.exportExcel(userList, "用户数据"); + } + + /** + * 翻页记住选择 + */ + @GetMapping("/remember") + public String remember() + { + return prefix + "/remember"; + } + + /** + * 跳转至指定页 + */ + @GetMapping("/pageGo") + public String pageGo() + { + return prefix + "/pageGo"; + } + + /** + * 自定义查询参数 + */ + @GetMapping("/params") + public String params() + { + return prefix + "/params"; + } + + /** + * 多表格 + */ + @GetMapping("/multi") + public String multi() + { + return prefix + "/multi"; + } + + /** + * 点击按钮加载表格 + */ + @GetMapping("/button") + public String button() + { + return prefix + "/button"; + } + + /** + * 直接加载表格数据 + */ + @GetMapping("/data") + public String data(ModelMap mmap) + { + mmap.put("users", users); + return prefix + "/data"; + } + + /** + * 表格冻结列 + */ + @GetMapping("/fixedColumns") + public String fixedColumns() + { + return prefix + "/fixedColumns"; + } + + /** + * 自定义触发事件 + */ + @GetMapping("/event") + public String event() + { + return prefix + "/event"; + } + + /** + * 表格细节视图 + */ + @GetMapping("/detail") + public String detail() + { + return prefix + "/detail"; + } + + /** + * 表格父子视图 + */ + @GetMapping("/child") + public String child() + { + return prefix + "/child"; + } + + /** + * 表格图片预览 + */ + @GetMapping("/image") + public String image() + { + return prefix + "/image"; + } + + /** + * 动态增删改查 + */ + @GetMapping("/curd") + public String curd() + { + return prefix + "/curd"; + } + + /** + * 表格行拖拽操作 + */ + @GetMapping("/reorderRows") + public String reorderRows() + { + return prefix + "/reorderRows"; + } + + /** + * 表格列拖拽操作 + */ + @GetMapping("/reorderColumns") + public String reorderColumns() + { + return prefix + "/reorderColumns"; + } + + /** + * 表格列宽拖动 + */ + @GetMapping("/resizable") + public String resizable() + { + return prefix + "/resizable"; + } + + /** + * 表格行内编辑操作 + */ + @GetMapping("/editable") + public String editable() + { + return prefix + "/editable"; + } + + /** + * 主子表提交 + */ + @GetMapping("/subdata") + public String subdata() + { + return prefix + "/subdata"; + } + + /** + * 表格自动刷新 + */ + @GetMapping("/refresh") + public String refresh() + { + return prefix + "/refresh"; + } + + /** + * 表格打印配置 + */ + @GetMapping("/print") + public String print() + { + return prefix + "/print"; + } + + /** + * 表格标题格式化 + */ + @GetMapping("/headerStyle") + public String headerStyle() + { + return prefix + "/headerStyle"; + } + + /** + * 表格动态列 + */ + @GetMapping("/dynamicColumns") + public String dynamicColumns() + { + return prefix + "/dynamicColumns"; + } + + /** + * 自定义视图分页 + */ + @GetMapping("/customView") + public String customView() + { + return prefix + "/customView"; + } + + /** + * 异步加载表格树 + */ + @GetMapping("/asynTree") + public String asynTree() + { + return prefix + "/asynTree"; + } + + /** + * 表格其他操作 + */ + @GetMapping("/other") + public String other() + { + return prefix + "/other"; + } + + /** + * 动态获取列 + */ + @PostMapping("/ajaxColumns") + @ResponseBody + public AjaxResult ajaxColumns(UserTableColumn userColumn) + { + List columnList = new ArrayList(Arrays.asList(new UserTableColumn[columns.size()])); + Collections.copy(columnList, columns); + if (userColumn != null && "userBalance".equals(userColumn.getField())) + { + columnList.add(new UserTableColumn("用户余额", "userBalance")); + } + return AjaxResult.success(columnList); + } + + /** + * 查询数据 + */ + @PostMapping("/list") + @ResponseBody + public TableDataInfo list(UserTableModel userModel) + { + TableDataInfo rspData = new TableDataInfo(); + List userList = new ArrayList(Arrays.asList(new UserTableModel[users.size()])); + Collections.copy(userList, users); + // 查询条件过滤 + if (StringUtils.isNotEmpty(userModel.getUserName())) + { + userList.clear(); + for (UserTableModel user : users) + { + if (user.getUserName().equals(userModel.getUserName())) + { + userList.add(user); + } + } + } + PageDomain pageDomain = TableSupport.buildPageRequest(); + if (null == pageDomain.getPageNum() || null == pageDomain.getPageSize()) + { + rspData.setRows(userList); + rspData.setTotal(userList.size()); + return rspData; + } + Integer pageNum = (pageDomain.getPageNum() - 1) * 10; + Integer pageSize = pageDomain.getPageNum() * 10; + if (pageSize > userList.size()) + { + pageSize = userList.size(); + } + rspData.setRows(userList.subList(pageNum, pageSize)); + rspData.setTotal(userList.size()); + return rspData; + } + + /** + * 查询树表数据 + */ + @PostMapping("/tree/list") + @ResponseBody + public TableDataInfo treeList(AreaModel areaModel) + { + TableDataInfo rspData = new TableDataInfo(); + List areaList = new ArrayList(Arrays.asList(new AreaModel[areas.size()])); + // 默认查询条件 parentId 0 + Collections.copy(areaList, areas); + areaList.clear(); + if (StringUtils.isNotEmpty(areaModel.getAreaName())) + { + for (AreaModel area : areas) + { + if (area.getParentId() == 0 && area.getAreaName().equals(areaModel.getAreaName())) + { + areaList.add(area); + } + } + } + else + { + for (AreaModel area : areas) + { + if (area.getParentId() == 0) + { + areaList.add(area); + } + } + } + PageDomain pageDomain = TableSupport.buildPageRequest(); + Integer pageNum = (pageDomain.getPageNum() - 1) * pageDomain.getPageSize(); + Integer pageSize = pageDomain.getPageNum() * pageDomain.getPageSize(); + if (pageSize > areaList.size()) + { + pageSize = areaList.size(); + } + rspData.setRows(areaList.subList(pageNum, pageSize)); + rspData.setTotal(areaList.size()); + return rspData; + } + + /** + * 查询树表子节点数据 + */ + @PostMapping("/tree/listChild") + @ResponseBody + public List listChild(AreaModel areaModel) + { + List areaList = new ArrayList(Arrays.asList(new AreaModel[areas.size()])); + // 查询条件 parentId + Collections.copy(areaList, areas); + areaList.clear(); + if (StringUtils.isNotEmpty(areaModel.getAreaName())) + { + for (AreaModel area : areas) + { + if (area.getParentId().intValue() == areaModel.getParentId().intValue() && area.getAreaName().equals(areaModel.getAreaName())) + { + areaList.add(area); + } + } + } + else + { + for (AreaModel area : areas) + { + if (area.getParentId().intValue() == areaModel.getParentId().intValue()) + { + areaList.add(area); + } + } + } + return areaList; + } +} + +class UserTableColumn +{ + /** 表头 */ + private String title; + /** 字段 */ + private String field; + + public UserTableColumn() + { + + } + + public UserTableColumn(String title, String field) + { + this.title = title; + this.field = field; + } + + public String getTitle() + { + return title; + } + + public void setTitle(String title) + { + this.title = title; + } + + public String getField() + { + return field; + } + + public void setField(String field) + { + this.field = field; + } +} + +class UserTableModel +{ + /** 用户ID */ + private int userId; + + /** 用户编号 */ + @Excel(name = "用户编号", cellType = ColumnType.NUMERIC) + private String userCode; + + /** 用户姓名 */ + @Excel(name = "用户姓名") + private String userName; + + /** 用户性别 */ + private String userSex; + + /** 用户手机 */ + @Excel(name = "用户手机") + private String userPhone; + + /** 用户邮箱 */ + @Excel(name = "用户邮箱") + private String userEmail; + + /** 用户余额 */ + @Excel(name = "用户余额", cellType = ColumnType.NUMERIC) + private double userBalance; + + /** 用户状态(0正常 1停用) */ + private String status; + + /** 创建时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + public UserTableModel() + { + + } + + public UserTableModel(int userId, String userCode, String userName, String userSex, String userPhone, + String userEmail, double userBalance, String status) + { + this.userId = userId; + this.userCode = userCode; + this.userName = userName; + this.userSex = userSex; + this.userPhone = userPhone; + this.userEmail = userEmail; + this.userBalance = userBalance; + this.status = status; + this.createTime = DateUtils.getNowDate(); + } + + public int getUserId() + { + return userId; + } + + public void setUserId(int userId) + { + this.userId = userId; + } + + public String getUserCode() + { + return userCode; + } + + public void setUserCode(String userCode) + { + this.userCode = userCode; + } + + public String getUserName() + { + return userName; + } + + public void setUserName(String userName) + { + this.userName = userName; + } + + public String getUserSex() + { + return userSex; + } + + public void setUserSex(String userSex) + { + this.userSex = userSex; + } + + public String getUserPhone() + { + return userPhone; + } + + public void setUserPhone(String userPhone) + { + this.userPhone = userPhone; + } + + public String getUserEmail() + { + return userEmail; + } + + public void setUserEmail(String userEmail) + { + this.userEmail = userEmail; + } + + public double getUserBalance() + { + return userBalance; + } + + public void setUserBalance(double userBalance) + { + this.userBalance = userBalance; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public Date getCreateTime() + { + return createTime; + } + + public void setCreateTime(Date createTime) + { + this.createTime = createTime; + } +} +class AreaModel +{ + /** 编号 */ + private Long id; + + /** 父编号 */ + private Long parentId; + + /** 区域名称 */ + private String areaName; + + /** 区域代码 */ + private String areaCode; + + /** 名称首字母 */ + private String simplePy; + + /** 名称全拼 */ + private String pinYin; + + /** 是否有子节点(0无 1有) */ + private Integer isTreeLeaf = 1; + + public AreaModel() + { + + } + + public AreaModel(int id, int parentId, String areaName, String areaCode, String simplePy, String pinYin, Integer isTreeLeaf) + { + this.id = Long.valueOf(id); + this.parentId = Long.valueOf(parentId); + this.areaName = areaName; + this.areaCode = areaCode; + this.simplePy = simplePy; + this.pinYin = pinYin; + this.isTreeLeaf = isTreeLeaf; + } + + public Long getId() + { + return id; + } + + public void setId(Long id) + { + this.id = id; + } + + public Long getParentId() + { + return parentId; + } + + public void setParentId(Long parentId) + { + this.parentId = parentId; + } + + public String getAreaName() + { + return areaName; + } + + public void setAreaName(String areaName) + { + this.areaName = areaName; + } + + public String getAreaCode() + { + return areaCode; + } + + public void setAreaCode(String areaCode) + { + this.areaCode = areaCode; + } + + public String getSimplePy() + { + return simplePy; + } + + public void setSimplePy(String simplePy) + { + this.simplePy = simplePy; + } + + public String getPinYin() + { + return pinYin; + } + + public void setPinYin(String pinYin) + { + this.pinYin = pinYin; + } + + public Integer getIsTreeLeaf() + { + return isTreeLeaf; + } + + public void setIsTreeLeaf(Integer isTreeLeaf) + { + this.isTreeLeaf = isTreeLeaf; + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/domain/CustomerModel.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/domain/CustomerModel.java new file mode 100644 index 0000000..5e7d6e5 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/domain/CustomerModel.java @@ -0,0 +1,116 @@ +package com.ruoyi.web.controller.demo.domain; + +import java.util.List; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +/** + * 客户测试信息 + * + * @author ruoyi + */ +public class CustomerModel +{ + /** + * 客户姓名 + */ + private String name; + + /** + * 客户手机 + */ + private String phonenumber; + + /** + * 客户性别 + */ + private String sex; + + /** + * 客户生日 + */ + private String birthday; + + /** + * 客户描述 + */ + private String remark; + + /** + * 商品信息 + */ + private List goods; + + public String getName() + { + return name; + } + + public void setName(String name) + { + this.name = name; + } + + public String getPhonenumber() + { + return phonenumber; + } + + public void setPhonenumber(String phonenumber) + { + this.phonenumber = phonenumber; + } + + + public String getSex() + { + return sex; + } + + public void setSex(String sex) + { + this.sex = sex; + } + + public String getBirthday() + { + return birthday; + } + + public void setBirthday(String birthday) + { + this.birthday = birthday; + } + + public String getRemark() + { + return remark; + } + + public void setRemark(String remark) + { + this.remark = remark; + } + + public List getGoods() + { + return goods; + } + + public void setGoods(List goods) + { + this.goods = goods; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("name", getName()) + .append("phonenumber", getPhonenumber()) + .append("sex", getSex()) + .append("birthday", getBirthday()) + .append("goods", getGoods()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/domain/GoodsModel.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/domain/GoodsModel.java new file mode 100644 index 0000000..b25542a --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/domain/GoodsModel.java @@ -0,0 +1,99 @@ +package com.ruoyi.web.controller.demo.domain; + +import java.util.Date; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +/** + * 商品测试信息 + * + * @author ruoyi + */ +public class GoodsModel +{ + /** + * 商品名称 + */ + private String name; + + /** + * 商品重量 + */ + private Integer weight; + + /** + * 商品价格 + */ + private Double price; + + /** + * 商品日期 + */ + private Date date; + + /** + * 商品种类 + */ + private String type; + + public String getName() + { + return name; + } + + public void setName(String name) + { + this.name = name; + } + + public Integer getWeight() + { + return weight; + } + + public void setWeight(Integer weight) + { + this.weight = weight; + } + + public Double getPrice() + { + return price; + } + + public void setPrice(Double price) + { + this.price = price; + } + + public Date getDate() + { + return date; + } + + public void setDate(Date date) + { + this.date = date; + } + + public String getType() + { + return type; + } + + public void setType(String type) + { + this.type = type; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("name", getName()) + .append("weight", getWeight()) + .append("price", getPrice()) + .append("date", getDate()) + .append("type", getType()) + .toString(); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/domain/UserOperateModel.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/domain/UserOperateModel.java new file mode 100644 index 0000000..8b158aa --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/domain/UserOperateModel.java @@ -0,0 +1,149 @@ +package com.ruoyi.web.controller.demo.domain; + +import java.util.Date; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.annotation.Excel.Type; +import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.common.utils.DateUtils; + +public class UserOperateModel extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + private int userId; + + @Excel(name = "用户编号") + private String userCode; + + @Excel(name = "用户姓名") + private String userName; + + @Excel(name = "用户性别", readConverterExp = "0=男,1=女,2=未知") + private String userSex; + + @Excel(name = "用户手机") + private String userPhone; + + @Excel(name = "用户邮箱") + private String userEmail; + + @Excel(name = "用户余额") + private double userBalance; + + @Excel(name = "用户状态", readConverterExp = "0=正常,1=停用") + private String status; + + @Excel(name = "创建时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Type.EXPORT) + private Date createTime; + + public UserOperateModel() + { + + } + + public UserOperateModel(int userId, String userCode, String userName, String userSex, String userPhone, + String userEmail, double userBalance, String status) + { + this.userId = userId; + this.userCode = userCode; + this.userName = userName; + this.userSex = userSex; + this.userPhone = userPhone; + this.userEmail = userEmail; + this.userBalance = userBalance; + this.status = status; + this.createTime = DateUtils.getNowDate(); + } + + public int getUserId() + { + return userId; + } + + public void setUserId(int userId) + { + this.userId = userId; + } + + public String getUserCode() + { + return userCode; + } + + public void setUserCode(String userCode) + { + this.userCode = userCode; + } + + public String getUserName() + { + return userName; + } + + public void setUserName(String userName) + { + this.userName = userName; + } + + public String getUserSex() + { + return userSex; + } + + public void setUserSex(String userSex) + { + this.userSex = userSex; + } + + public String getUserPhone() + { + return userPhone; + } + + public void setUserPhone(String userPhone) + { + this.userPhone = userPhone; + } + + public String getUserEmail() + { + return userEmail; + } + + public void setUserEmail(String userEmail) + { + this.userEmail = userEmail; + } + + public double getUserBalance() + { + return userBalance; + } + + public void setUserBalance(double userBalance) + { + this.userBalance = userBalance; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + @Override + public Date getCreateTime() + { + return createTime; + } + + @Override + public void setCreateTime(Date createTime) + { + this.createTime = createTime; + } +} \ No newline at end of file diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java new file mode 100644 index 0000000..baeb2b8 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java @@ -0,0 +1,90 @@ +package com.ruoyi.web.controller.monitor; + +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +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.ResponseBody; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.framework.web.service.CacheService; + +/** + * 缓存监控 + * + * @author ruoyi + */ +@Controller +@RequestMapping("/monitor/cache") +public class CacheController extends BaseController +{ + private String prefix = "monitor/cache"; + + @Autowired + private CacheService cacheService; + + @RequiresPermissions("monitor:cache:view") + @GetMapping() + public String cache(ModelMap mmap) + { + mmap.put("cacheNames", cacheService.getCacheNames()); + return prefix + "/cache"; + } + + @RequiresPermissions("monitor:cache:view") + @PostMapping("/getNames") + public String getCacheNames(String fragment, ModelMap mmap) + { + mmap.put("cacheNames", cacheService.getCacheNames()); + return prefix + "/cache::" + fragment; + } + + @RequiresPermissions("monitor:cache:view") + @PostMapping("/getKeys") + public String getCacheKeys(String fragment, String cacheName, ModelMap mmap) + { + mmap.put("cacheName", cacheName); + mmap.put("cacheKeys", cacheService.getCacheKeys(cacheName)); + return prefix + "/cache::" + fragment; + } + + @RequiresPermissions("monitor:cache:view") + @PostMapping("/getValue") + public String getCacheValue(String fragment, String cacheName, String cacheKey, ModelMap mmap) + { + mmap.put("cacheName", cacheName); + mmap.put("cacheKey", cacheKey); + mmap.put("cacheValue", cacheService.getCacheValue(cacheName, cacheKey)); + return prefix + "/cache::" + fragment; + } + + @RequiresPermissions("monitor:cache:view") + @PostMapping("/clearCacheName") + @ResponseBody + public AjaxResult clearCacheName(String cacheName, ModelMap mmap) + { + cacheService.clearCacheName(cacheName); + return AjaxResult.success(); + } + + @RequiresPermissions("monitor:cache:view") + @PostMapping("/clearCacheKey") + @ResponseBody + public AjaxResult clearCacheKey(String cacheName, String cacheKey, ModelMap mmap) + { + cacheService.clearCacheKey(cacheName, cacheKey); + return AjaxResult.success(); + } + + @RequiresPermissions("monitor:cache:view") + @GetMapping("/clearAll") + @ResponseBody + public AjaxResult clearAll(ModelMap mmap) + { + cacheService.clearAll(); + return AjaxResult.success(); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/DruidController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/DruidController.java new file mode 100644 index 0000000..a51bf20 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/DruidController.java @@ -0,0 +1,26 @@ +package com.ruoyi.web.controller.monitor; + +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import com.ruoyi.common.core.controller.BaseController; + +/** + * druid 监控 + * + * @author ruoyi + */ +@Controller +@RequestMapping("/monitor/data") +public class DruidController extends BaseController +{ + private String prefix = "/druid"; + + @RequiresPermissions("monitor:data:view") + @GetMapping() + public String index() + { + return redirect(prefix + "/index.html"); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java new file mode 100644 index 0000000..386b5c7 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java @@ -0,0 +1,31 @@ +package com.ruoyi.web.controller.monitor; + +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.framework.web.domain.Server; + +/** + * 服务器监控 + * + * @author ruoyi + */ +@Controller +@RequestMapping("/monitor/server") +public class ServerController extends BaseController +{ + private String prefix = "monitor/server"; + + @RequiresPermissions("monitor:server:view") + @GetMapping() + public String server(ModelMap mmap) throws Exception + { + Server server = new Server(); + server.copyTo(); + mmap.put("server", server); + return prefix + "/server"; + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java new file mode 100644 index 0000000..0d30e42 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java @@ -0,0 +1,94 @@ +package com.ruoyi.web.controller.monitor; + +import java.util.List; +import com.ruoyi.framework.shiro.service.SysPasswordService; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +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.ResponseBody; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.system.domain.SysLogininfor; +import com.ruoyi.system.service.ISysLogininforService; + +/** + * 系统访问记录 + * + * @author ruoyi + */ +@Controller +@RequestMapping("/monitor/logininfor") +public class SysLogininforController extends BaseController +{ + private String prefix = "monitor/logininfor"; + + @Autowired + private ISysLogininforService logininforService; + + @Autowired + private SysPasswordService passwordService; + + @RequiresPermissions("monitor:logininfor:view") + @GetMapping() + public String logininfor() + { + return prefix + "/logininfor"; + } + + @RequiresPermissions("monitor:logininfor:list") + @PostMapping("/list") + @ResponseBody + public TableDataInfo list(SysLogininfor logininfor) + { + startPage(); + List list = logininforService.selectLogininforList(logininfor); + return getDataTable(list); + } + + @Log(title = "登录日志", businessType = BusinessType.EXPORT) + @RequiresPermissions("monitor:logininfor:export") + @PostMapping("/export") + @ResponseBody + public AjaxResult export(SysLogininfor logininfor) + { + List list = logininforService.selectLogininforList(logininfor); + ExcelUtil util = new ExcelUtil(SysLogininfor.class); + return util.exportExcel(list, "登录日志"); + } + + @RequiresPermissions("monitor:logininfor:remove") + @Log(title = "登录日志", businessType = BusinessType.DELETE) + @PostMapping("/remove") + @ResponseBody + public AjaxResult remove(String ids) + { + return toAjax(logininforService.deleteLogininforByIds(ids)); + } + + @RequiresPermissions("monitor:logininfor:remove") + @Log(title = "登录日志", businessType = BusinessType.CLEAN) + @PostMapping("/clean") + @ResponseBody + public AjaxResult clean() + { + logininforService.cleanLogininfor(); + return success(); + } + + @RequiresPermissions("monitor:logininfor:unlock") + @Log(title = "账户解锁", businessType = BusinessType.OTHER) + @PostMapping("/unlock") + @ResponseBody + public AjaxResult unlock(String loginName) + { + passwordService.clearLoginRecordCache(loginName); + return success(); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java new file mode 100644 index 0000000..cddd972 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java @@ -0,0 +1,90 @@ +package com.ruoyi.web.controller.monitor; + +import java.util.List; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.system.domain.SysOperLog; +import com.ruoyi.system.service.ISysOperLogService; + +/** + * 操作日志记录 + * + * @author ruoyi + */ +@Controller +@RequestMapping("/monitor/operlog") +public class SysOperlogController extends BaseController +{ + private String prefix = "monitor/operlog"; + + @Autowired + private ISysOperLogService operLogService; + + @RequiresPermissions("monitor:operlog:view") + @GetMapping() + public String operlog() + { + return prefix + "/operlog"; + } + + @RequiresPermissions("monitor:operlog:list") + @PostMapping("/list") + @ResponseBody + public TableDataInfo list(SysOperLog operLog) + { + startPage(); + List list = operLogService.selectOperLogList(operLog); + return getDataTable(list); + } + + @Log(title = "操作日志", businessType = BusinessType.EXPORT) + @RequiresPermissions("monitor:operlog:export") + @PostMapping("/export") + @ResponseBody + public AjaxResult export(SysOperLog operLog) + { + List list = operLogService.selectOperLogList(operLog); + ExcelUtil util = new ExcelUtil(SysOperLog.class); + return util.exportExcel(list, "操作日志"); + } + + @Log(title = "操作日志", businessType = BusinessType.DELETE) + @RequiresPermissions("monitor:operlog:remove") + @PostMapping("/remove") + @ResponseBody + public AjaxResult remove(String ids) + { + return toAjax(operLogService.deleteOperLogByIds(ids)); + } + + @RequiresPermissions("monitor:operlog:detail") + @GetMapping("/detail/{operId}") + public String detail(@PathVariable("operId") Long operId, ModelMap mmap) + { + mmap.put("operLog", operLogService.selectOperLogById(operId)); + return prefix + "/detail"; + } + + @Log(title = "操作日志", businessType = BusinessType.CLEAN) + @RequiresPermissions("monitor:operlog:remove") + @PostMapping("/clean") + @ResponseBody + public AjaxResult clean() + { + operLogService.cleanOperLog(); + return success(); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java new file mode 100644 index 0000000..19435e9 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java @@ -0,0 +1,88 @@ +package com.ruoyi.web.controller.monitor; + +import java.util.List; +import org.apache.shiro.authz.annotation.Logical; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +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.ResponseBody; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.enums.OnlineStatus; +import com.ruoyi.common.utils.ShiroUtils; +import com.ruoyi.framework.shiro.session.OnlineSession; +import com.ruoyi.framework.shiro.session.OnlineSessionDAO; +import com.ruoyi.system.domain.SysUserOnline; +import com.ruoyi.system.service.ISysUserOnlineService; + +/** + * 在线用户监控 + * + * @author ruoyi + */ +@Controller +@RequestMapping("/monitor/online") +public class SysUserOnlineController extends BaseController +{ + private String prefix = "monitor/online"; + + @Autowired + private ISysUserOnlineService userOnlineService; + + @Autowired + private OnlineSessionDAO onlineSessionDAO; + + @RequiresPermissions("monitor:online:view") + @GetMapping() + public String online() + { + return prefix + "/online"; + } + + @RequiresPermissions("monitor:online:list") + @PostMapping("/list") + @ResponseBody + public TableDataInfo list(SysUserOnline userOnline) + { + startPage(); + List list = userOnlineService.selectUserOnlineList(userOnline); + return getDataTable(list); + } + + @RequiresPermissions(value = { "monitor:online:batchForceLogout", "monitor:online:forceLogout" }, logical = Logical.OR) + @Log(title = "在线用户", businessType = BusinessType.FORCE) + @PostMapping("/batchForceLogout") + @ResponseBody + public AjaxResult batchForceLogout(String ids) + { + for (String sessionId : Convert.toStrArray(ids)) + { + SysUserOnline online = userOnlineService.selectOnlineById(sessionId); + if (online == null) + { + return error("用户已下线"); + } + OnlineSession onlineSession = (OnlineSession) onlineSessionDAO.readSession(online.getSessionId()); + if (onlineSession == null) + { + return error("用户已下线"); + } + if (sessionId.equals(ShiroUtils.getSessionId())) + { + return error("当前登录用户无法强退"); + } + onlineSessionDAO.delete(onlineSession); + online.setStatus(OnlineStatus.off_line); + userOnlineService.saveOnline(online); + userOnlineService.removeUserCache(online.getLoginName(), sessionId); + } + return success(); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysCaptchaController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysCaptchaController.java new file mode 100644 index 0000000..739c469 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysCaptchaController.java @@ -0,0 +1,92 @@ +package com.ruoyi.web.controller.system; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import javax.annotation.Resource; +import javax.imageio.ImageIO; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.ModelAndView; +import com.google.code.kaptcha.Constants; +import com.google.code.kaptcha.Producer; +import com.ruoyi.common.core.controller.BaseController; + +/** + * 图片验证码(支持算术形式) + * + * @author ruoyi + */ +@Controller +@RequestMapping("/captcha") +public class SysCaptchaController extends BaseController +{ + @Resource(name = "captchaProducer") + private Producer captchaProducer; + + @Resource(name = "captchaProducerMath") + private Producer captchaProducerMath; + + /** + * 验证码生成 + */ + @GetMapping(value = "/captchaImage") + public ModelAndView getKaptchaImage(HttpServletRequest request, HttpServletResponse response) + { + ServletOutputStream out = null; + try + { + HttpSession session = request.getSession(); + response.setDateHeader("Expires", 0); + response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate"); + response.addHeader("Cache-Control", "post-check=0, pre-check=0"); + response.setHeader("Pragma", "no-cache"); + response.setContentType("image/jpeg"); + + String type = request.getParameter("type"); + String capStr = null; + String code = null; + BufferedImage bi = null; + if ("math".equals(type)) + { + String capText = captchaProducerMath.createText(); + capStr = capText.substring(0, capText.lastIndexOf("@")); + code = capText.substring(capText.lastIndexOf("@") + 1); + bi = captchaProducerMath.createImage(capStr); + } + else if ("char".equals(type)) + { + capStr = code = captchaProducer.createText(); + bi = captchaProducer.createImage(capStr); + } + session.setAttribute(Constants.KAPTCHA_SESSION_KEY, code); + out = response.getOutputStream(); + ImageIO.write(bi, "jpg", out); + out.flush(); + + } + catch (Exception e) + { + e.printStackTrace(); + } + finally + { + try + { + if (out != null) + { + out.close(); + } + } + catch (IOException e) + { + e.printStackTrace(); + } + } + return null; + } +} \ No newline at end of file diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java new file mode 100644 index 0000000..2be9e63 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java @@ -0,0 +1,157 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.system.domain.SysConfig; +import com.ruoyi.system.service.ISysConfigService; + +/** + * 参数配置 信息操作处理 + * + * @author ruoyi + */ +@Controller +@RequestMapping("/system/config") +public class SysConfigController extends BaseController +{ + private String prefix = "system/config"; + + @Autowired + private ISysConfigService configService; + + @RequiresPermissions("system:config:view") + @GetMapping() + public String config() + { + return prefix + "/config"; + } + + /** + * 查询参数配置列表 + */ + @RequiresPermissions("system:config:list") + @PostMapping("/list") + @ResponseBody + public TableDataInfo list(SysConfig config) + { + startPage(); + List list = configService.selectConfigList(config); + return getDataTable(list); + } + + @Log(title = "参数管理", businessType = BusinessType.EXPORT) + @RequiresPermissions("system:config:export") + @PostMapping("/export") + @ResponseBody + public AjaxResult export(SysConfig config) + { + List list = configService.selectConfigList(config); + ExcelUtil util = new ExcelUtil(SysConfig.class); + return util.exportExcel(list, "参数数据"); + } + + /** + * 新增参数配置 + */ + @GetMapping("/add") + public String add() + { + return prefix + "/add"; + } + + /** + * 新增保存参数配置 + */ + @RequiresPermissions("system:config:add") + @Log(title = "参数管理", businessType = BusinessType.INSERT) + @PostMapping("/add") + @ResponseBody + public AjaxResult addSave(@Validated SysConfig config) + { + if (!configService.checkConfigKeyUnique(config)) + { + return error("新增参数'" + config.getConfigName() + "'失败,参数键名已存在"); + } + config.setCreateBy(getLoginName()); + return toAjax(configService.insertConfig(config)); + } + + /** + * 修改参数配置 + */ + @RequiresPermissions("system:config:edit") + @GetMapping("/edit/{configId}") + public String edit(@PathVariable("configId") Long configId, ModelMap mmap) + { + mmap.put("config", configService.selectConfigById(configId)); + return prefix + "/edit"; + } + + /** + * 修改保存参数配置 + */ + @RequiresPermissions("system:config:edit") + @Log(title = "参数管理", businessType = BusinessType.UPDATE) + @PostMapping("/edit") + @ResponseBody + public AjaxResult editSave(@Validated SysConfig config) + { + if (!configService.checkConfigKeyUnique(config)) + { + return error("修改参数'" + config.getConfigName() + "'失败,参数键名已存在"); + } + config.setUpdateBy(getLoginName()); + return toAjax(configService.updateConfig(config)); + } + + /** + * 删除参数配置 + */ + @RequiresPermissions("system:config:remove") + @Log(title = "参数管理", businessType = BusinessType.DELETE) + @PostMapping("/remove") + @ResponseBody + public AjaxResult remove(String ids) + { + configService.deleteConfigByIds(ids); + return success(); + } + + /** + * 刷新参数缓存 + */ + @RequiresPermissions("system:config:remove") + @Log(title = "参数管理", businessType = BusinessType.CLEAN) + @GetMapping("/refreshCache") + @ResponseBody + public AjaxResult refreshCache() + { + configService.resetConfigCache(); + return success(); + } + + /** + * 校验参数键名 + */ + @PostMapping("/checkConfigKeyUnique") + @ResponseBody + public boolean checkConfigKeyUnique(SysConfig config) + { + return configService.checkConfigKeyUnique(config); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java new file mode 100644 index 0000000..f7ab223 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java @@ -0,0 +1,187 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.Ztree; +import com.ruoyi.common.core.domain.entity.SysDept; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.service.ISysDeptService; + +/** + * 部门信息 + * + * @author ruoyi + */ +@Controller +@RequestMapping("/system/dept") +public class SysDeptController extends BaseController +{ + private String prefix = "system/dept"; + + @Autowired + private ISysDeptService deptService; + + @RequiresPermissions("system:dept:view") + @GetMapping() + public String dept() + { + return prefix + "/dept"; + } + + @RequiresPermissions("system:dept:list") + @PostMapping("/list") + @ResponseBody + public List list(SysDept dept) + { + List deptList = deptService.selectDeptList(dept); + return deptList; + } + + /** + * 新增部门 + */ + @GetMapping("/add/{parentId}") + public String add(@PathVariable("parentId") Long parentId, ModelMap mmap) + { + if (!getSysUser().isAdmin()) + { + parentId = getSysUser().getDeptId(); + } + mmap.put("dept", deptService.selectDeptById(parentId)); + return prefix + "/add"; + } + + /** + * 新增保存部门 + */ + @Log(title = "部门管理", businessType = BusinessType.INSERT) + @RequiresPermissions("system:dept:add") + @PostMapping("/add") + @ResponseBody + public AjaxResult addSave(@Validated SysDept dept) + { + if (!deptService.checkDeptNameUnique(dept)) + { + return error("新增部门'" + dept.getDeptName() + "'失败,部门名称已存在"); + } + dept.setCreateBy(getLoginName()); + return toAjax(deptService.insertDept(dept)); + } + + /** + * 修改部门 + */ + @RequiresPermissions("system:dept:edit") + @GetMapping("/edit/{deptId}") + public String edit(@PathVariable("deptId") Long deptId, ModelMap mmap) + { + deptService.checkDeptDataScope(deptId); + SysDept dept = deptService.selectDeptById(deptId); + if (StringUtils.isNotNull(dept) && 100L == deptId) + { + dept.setParentName("无"); + } + mmap.put("dept", dept); + return prefix + "/edit"; + } + + /** + * 修改保存部门 + */ + @Log(title = "部门管理", businessType = BusinessType.UPDATE) + @RequiresPermissions("system:dept:edit") + @PostMapping("/edit") + @ResponseBody + public AjaxResult editSave(@Validated SysDept dept) + { + Long deptId = dept.getDeptId(); + deptService.checkDeptDataScope(deptId); + if (!deptService.checkDeptNameUnique(dept)) + { + return error("修改部门'" + dept.getDeptName() + "'失败,部门名称已存在"); + } + else if (dept.getParentId().equals(deptId)) + { + return error("修改部门'" + dept.getDeptName() + "'失败,上级部门不能是自己"); + } + else if (StringUtils.equals(UserConstants.DEPT_DISABLE, dept.getStatus()) && deptService.selectNormalChildrenDeptById(deptId) > 0) + { + return AjaxResult.error("该部门包含未停用的子部门!"); + } + dept.setUpdateBy(getLoginName()); + return toAjax(deptService.updateDept(dept)); + } + + /** + * 删除 + */ + @Log(title = "部门管理", businessType = BusinessType.DELETE) + @RequiresPermissions("system:dept:remove") + @GetMapping("/remove/{deptId}") + @ResponseBody + public AjaxResult remove(@PathVariable("deptId") Long deptId) + { + if (deptService.selectDeptCount(deptId) > 0) + { + return AjaxResult.warn("存在下级部门,不允许删除"); + } + if (deptService.checkDeptExistUser(deptId)) + { + return AjaxResult.warn("部门存在用户,不允许删除"); + } + deptService.checkDeptDataScope(deptId); + return toAjax(deptService.deleteDeptById(deptId)); + } + + /** + * 校验部门名称 + */ + @PostMapping("/checkDeptNameUnique") + @ResponseBody + public boolean checkDeptNameUnique(SysDept dept) + { + return deptService.checkDeptNameUnique(dept); + } + + /** + * 选择部门树 + * + * @param deptId 部门ID + * @param excludeId 排除ID + */ + @GetMapping(value = { "/selectDeptTree/{deptId}", "/selectDeptTree/{deptId}/{excludeId}" }) + public String selectDeptTree(@PathVariable("deptId") Long deptId, + @PathVariable(value = "excludeId", required = false) Long excludeId, ModelMap mmap) + { + mmap.put("dept", deptService.selectDeptById(deptId)); + mmap.put("excludeId", excludeId); + return prefix + "/tree"; + } + + /** + * 加载部门列表树(排除下级) + */ + @GetMapping("/treeData/{excludeId}") + @ResponseBody + public List treeDataExcludeChild(@PathVariable(value = "excludeId", required = false) Long excludeId) + { + SysDept dept = new SysDept(); + dept.setExcludeId(excludeId); + List ztrees = deptService.selectDeptTreeExcludeChild(dept); + return ztrees; + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java new file mode 100644 index 0000000..610fd6b --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java @@ -0,0 +1,121 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysDictData; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.system.service.ISysDictDataService; + +/** + * 数据字典信息 + * + * @author ruoyi + */ +@Controller +@RequestMapping("/system/dict/data") +public class SysDictDataController extends BaseController +{ + private String prefix = "system/dict/data"; + + @Autowired + private ISysDictDataService dictDataService; + + @RequiresPermissions("system:dict:view") + @GetMapping() + public String dictData() + { + return prefix + "/data"; + } + + @PostMapping("/list") + @RequiresPermissions("system:dict:list") + @ResponseBody + public TableDataInfo list(SysDictData dictData) + { + startPage(); + List list = dictDataService.selectDictDataList(dictData); + return getDataTable(list); + } + + @Log(title = "字典数据", businessType = BusinessType.EXPORT) + @RequiresPermissions("system:dict:export") + @PostMapping("/export") + @ResponseBody + public AjaxResult export(SysDictData dictData) + { + List list = dictDataService.selectDictDataList(dictData); + ExcelUtil util = new ExcelUtil(SysDictData.class); + return util.exportExcel(list, "字典数据"); + } + + /** + * 新增字典类型 + */ + @GetMapping("/add/{dictType}") + public String add(@PathVariable("dictType") String dictType, ModelMap mmap) + { + mmap.put("dictType", dictType); + return prefix + "/add"; + } + + /** + * 新增保存字典类型 + */ + @Log(title = "字典数据", businessType = BusinessType.INSERT) + @RequiresPermissions("system:dict:add") + @PostMapping("/add") + @ResponseBody + public AjaxResult addSave(@Validated SysDictData dict) + { + dict.setCreateBy(getLoginName()); + return toAjax(dictDataService.insertDictData(dict)); + } + + /** + * 修改字典类型 + */ + @RequiresPermissions("system:dict:edit") + @GetMapping("/edit/{dictCode}") + public String edit(@PathVariable("dictCode") Long dictCode, ModelMap mmap) + { + mmap.put("dict", dictDataService.selectDictDataById(dictCode)); + return prefix + "/edit"; + } + + /** + * 修改保存字典类型 + */ + @Log(title = "字典数据", businessType = BusinessType.UPDATE) + @RequiresPermissions("system:dict:edit") + @PostMapping("/edit") + @ResponseBody + public AjaxResult editSave(@Validated SysDictData dict) + { + dict.setUpdateBy(getLoginName()); + return toAjax(dictDataService.updateDictData(dict)); + } + + @Log(title = "字典数据", businessType = BusinessType.DELETE) + @RequiresPermissions("system:dict:remove") + @PostMapping("/remove") + @ResponseBody + public AjaxResult remove(String ids) + { + dictDataService.deleteDictDataByIds(ids); + return success(); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java new file mode 100644 index 0000000..0a66af1 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java @@ -0,0 +1,188 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.Ztree; +import com.ruoyi.common.core.domain.entity.SysDictType; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.system.service.ISysDictTypeService; + +/** + * 数据字典信息 + * + * @author ruoyi + */ +@Controller +@RequestMapping("/system/dict") +public class SysDictTypeController extends BaseController +{ + private String prefix = "system/dict/type"; + + @Autowired + private ISysDictTypeService dictTypeService; + + @RequiresPermissions("system:dict:view") + @GetMapping() + public String dictType() + { + return prefix + "/type"; + } + + @PostMapping("/list") + @RequiresPermissions("system:dict:list") + @ResponseBody + public TableDataInfo list(SysDictType dictType) + { + startPage(); + List list = dictTypeService.selectDictTypeList(dictType); + return getDataTable(list); + } + + @Log(title = "字典类型", businessType = BusinessType.EXPORT) + @RequiresPermissions("system:dict:export") + @PostMapping("/export") + @ResponseBody + public AjaxResult export(SysDictType dictType) + { + + List list = dictTypeService.selectDictTypeList(dictType); + ExcelUtil util = new ExcelUtil(SysDictType.class); + return util.exportExcel(list, "字典类型"); + } + + /** + * 新增字典类型 + */ + @GetMapping("/add") + public String add() + { + return prefix + "/add"; + } + + /** + * 新增保存字典类型 + */ + @Log(title = "字典类型", businessType = BusinessType.INSERT) + @RequiresPermissions("system:dict:add") + @PostMapping("/add") + @ResponseBody + public AjaxResult addSave(@Validated SysDictType dict) + { + if (!dictTypeService.checkDictTypeUnique(dict)) + { + return error("新增字典'" + dict.getDictName() + "'失败,字典类型已存在"); + } + dict.setCreateBy(getLoginName()); + return toAjax(dictTypeService.insertDictType(dict)); + } + + /** + * 修改字典类型 + */ + @RequiresPermissions("system:dict:edit") + @GetMapping("/edit/{dictId}") + public String edit(@PathVariable("dictId") Long dictId, ModelMap mmap) + { + mmap.put("dict", dictTypeService.selectDictTypeById(dictId)); + return prefix + "/edit"; + } + + /** + * 修改保存字典类型 + */ + @Log(title = "字典类型", businessType = BusinessType.UPDATE) + @RequiresPermissions("system:dict:edit") + @PostMapping("/edit") + @ResponseBody + public AjaxResult editSave(@Validated SysDictType dict) + { + if (!dictTypeService.checkDictTypeUnique(dict)) + { + return error("修改字典'" + dict.getDictName() + "'失败,字典类型已存在"); + } + dict.setUpdateBy(getLoginName()); + return toAjax(dictTypeService.updateDictType(dict)); + } + + @Log(title = "字典类型", businessType = BusinessType.DELETE) + @RequiresPermissions("system:dict:remove") + @PostMapping("/remove") + @ResponseBody + public AjaxResult remove(String ids) + { + dictTypeService.deleteDictTypeByIds(ids); + return success(); + } + + /** + * 刷新字典缓存 + */ + @RequiresPermissions("system:dict:remove") + @Log(title = "字典类型", businessType = BusinessType.CLEAN) + @GetMapping("/refreshCache") + @ResponseBody + public AjaxResult refreshCache() + { + dictTypeService.resetDictCache(); + return success(); + } + + /** + * 查询字典详细 + */ + @RequiresPermissions("system:dict:list") + @GetMapping("/detail/{dictId}") + public String detail(@PathVariable("dictId") Long dictId, ModelMap mmap) + { + mmap.put("dict", dictTypeService.selectDictTypeById(dictId)); + mmap.put("dictList", dictTypeService.selectDictTypeAll()); + return "system/dict/data/data"; + } + + /** + * 校验字典类型 + */ + @PostMapping("/checkDictTypeUnique") + @ResponseBody + public boolean checkDictTypeUnique(SysDictType dictType) + { + return dictTypeService.checkDictTypeUnique(dictType); + } + + /** + * 选择字典树 + */ + @GetMapping("/selectDictTree/{columnId}/{dictType}") + public String selectDeptTree(@PathVariable("columnId") Long columnId, @PathVariable("dictType") String dictType, + ModelMap mmap) + { + mmap.put("columnId", columnId); + mmap.put("dict", dictTypeService.selectDictTypeByType(dictType)); + return prefix + "/tree"; + } + + /** + * 加载字典列表树 + */ + @GetMapping("/treeData") + @ResponseBody + public List treeData() + { + List ztrees = dictTypeService.selectDictTree(new SysDictType()); + return ztrees; + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java new file mode 100644 index 0000000..667fea5 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java @@ -0,0 +1,178 @@ +package com.ruoyi.web.controller.system; + +import java.util.Date; +import java.util.List; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.constant.ShiroConstants; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysMenu; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.utils.CookieUtils; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.shiro.service.SysPasswordService; +import com.ruoyi.system.service.ISysConfigService; +import com.ruoyi.system.service.ISysMenuService; + +/** + * 首页 业务处理 + * + * @author ruoyi + */ +@Controller +public class SysIndexController extends BaseController +{ + @Autowired + private ISysMenuService menuService; + + @Autowired + private ISysConfigService configService; + + @Autowired + private SysPasswordService passwordService; + + // 系统首页 + @GetMapping("/index") + public String index(ModelMap mmap) + { + // 取身份信息 + SysUser user = getSysUser(); + // 根据用户id取出菜单 + List menus = menuService.selectMenusByUser(user); + mmap.put("menus", menus); + mmap.put("user", user); + mmap.put("sideTheme", configService.selectConfigByKey("sys.index.sideTheme")); + mmap.put("skinName", configService.selectConfigByKey("sys.index.skinName")); + Boolean footer = Convert.toBool(configService.selectConfigByKey("sys.index.footer"), true); + Boolean tagsView = Convert.toBool(configService.selectConfigByKey("sys.index.tagsView"), true); + mmap.put("footer", footer); + mmap.put("tagsView", tagsView); + mmap.put("mainClass", contentMainClass(footer, tagsView)); + mmap.put("copyrightYear", RuoYiConfig.getCopyrightYear()); + mmap.put("demoEnabled", RuoYiConfig.isDemoEnabled()); + mmap.put("isDefaultModifyPwd", initPasswordIsModify(user.getPwdUpdateDate())); + mmap.put("isPasswordExpired", passwordIsExpiration(user.getPwdUpdateDate())); + mmap.put("isMobile", ServletUtils.checkAgentIsMobile(ServletUtils.getRequest().getHeader("User-Agent"))); + + // 菜单导航显示风格 + String menuStyle = configService.selectConfigByKey("sys.index.menuStyle"); + // 移动端,默认使左侧导航菜单,否则取默认配置 + String indexStyle = ServletUtils.checkAgentIsMobile(ServletUtils.getRequest().getHeader("User-Agent")) ? "index" : menuStyle; + + // 优先Cookie配置导航菜单 + Cookie[] cookies = ServletUtils.getRequest().getCookies(); + for (Cookie cookie : cookies) + { + if (StringUtils.isNotEmpty(cookie.getName()) && "nav-style".equalsIgnoreCase(cookie.getName())) + { + indexStyle = cookie.getValue(); + break; + } + } + String webIndex = "topnav".equalsIgnoreCase(indexStyle) ? "index-topnav" : "index"; + return webIndex; + } + + // 锁定屏幕 + @GetMapping("/lockscreen") + public String lockscreen(ModelMap mmap) + { + mmap.put("user", getSysUser()); + ServletUtils.getSession().setAttribute(ShiroConstants.LOCK_SCREEN, true); + return "lock"; + } + + // 解锁屏幕 + @PostMapping("/unlockscreen") + @ResponseBody + public AjaxResult unlockscreen(String password) + { + SysUser user = getSysUser(); + if (StringUtils.isNull(user)) + { + return AjaxResult.error("服务器超时,请重新登录"); + } + if (passwordService.matches(user, password)) + { + ServletUtils.getSession().removeAttribute(ShiroConstants.LOCK_SCREEN); + return AjaxResult.success(); + } + return AjaxResult.error("密码不正确,请重新输入。"); + } + + // 切换主题 + @GetMapping("/system/switchSkin") + public String switchSkin() + { + return "skin"; + } + + // 切换菜单 + @GetMapping("/system/menuStyle/{style}") + public void menuStyle(@PathVariable String style, HttpServletResponse response) + { + CookieUtils.setCookie(response, "nav-style", style); + } + + // 系统介绍 + @GetMapping("/system/main") + public String main(ModelMap mmap) + { + mmap.put("version", RuoYiConfig.getVersion()); + return "main"; + } + + // content-main class + public String contentMainClass(Boolean footer, Boolean tagsView) + { + if (!footer && !tagsView) + { + return "tagsview-footer-hide"; + } + else if (!footer) + { + return "footer-hide"; + } + else if (!tagsView) + { + return "tagsview-hide"; + } + return StringUtils.EMPTY; + } + + // 检查初始密码是否提醒修改 + public boolean initPasswordIsModify(Date pwdUpdateDate) + { + Integer initPasswordModify = Convert.toInt(configService.selectConfigByKey("sys.account.initPasswordModify")); + return initPasswordModify != null && initPasswordModify == 1 && pwdUpdateDate == null; + } + + // 检查密码是否过期 + public boolean passwordIsExpiration(Date pwdUpdateDate) + { + Integer passwordValidateDays = Convert.toInt(configService.selectConfigByKey("sys.account.passwordValidateDays")); + if (passwordValidateDays != null && passwordValidateDays > 0) + { + if (StringUtils.isNull(pwdUpdateDate)) + { + // 如果从未修改过初始密码,直接提醒过期 + return true; + } + Date nowDate = DateUtils.getNowDate(); + return DateUtils.differentDaysByMillisecond(nowDate, pwdUpdateDate) > passwordValidateDays; + } + return false; + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java new file mode 100644 index 0000000..55aecae --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java @@ -0,0 +1,82 @@ +package com.ruoyi.web.controller.system; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.UsernamePasswordToken; +import org.apache.shiro.subject.Subject; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.web.service.ConfigService; + +/** + * 登录验证 + * + * @author ruoyi + */ +@Controller +public class SysLoginController extends BaseController +{ + /** + * 是否开启记住我功能 + */ + @Value("${shiro.rememberMe.enabled: false}") + private boolean rememberMe; + + @Autowired + private ConfigService configService; + + @GetMapping("/login") + public String login(HttpServletRequest request, HttpServletResponse response, ModelMap mmap) + { + // 如果是Ajax请求,返回Json字符串。 + if (ServletUtils.isAjaxRequest(request)) + { + return ServletUtils.renderString(response, "{\"code\":\"1\",\"msg\":\"未登录或登录超时。请重新登录\"}"); + } + // 是否开启记住我 + mmap.put("isRemembered", rememberMe); + // 是否开启用户注册 + mmap.put("isAllowRegister", Convert.toBool(configService.getKey("sys.account.registerUser"), false)); + return "login"; + } + + @PostMapping("/login") + @ResponseBody + public AjaxResult ajaxLogin(String username, String password, Boolean rememberMe) + { + UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe); + Subject subject = SecurityUtils.getSubject(); + try + { + subject.login(token); + return success(); + } + catch (AuthenticationException e) + { + String msg = "用户或密码错误"; + if (StringUtils.isNotEmpty(e.getMessage())) + { + msg = e.getMessage(); + } + return error(msg); + } + } + + @GetMapping("/unauth") + public String unauth() + { + return "error/unauth"; + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java new file mode 100644 index 0000000..9acbffa --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java @@ -0,0 +1,197 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.Ztree; +import com.ruoyi.common.core.domain.entity.SysMenu; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.ShiroUtils; +import com.ruoyi.framework.shiro.util.AuthorizationUtils; +import com.ruoyi.system.service.ISysMenuService; + +/** + * 菜单信息 + * + * @author ruoyi + */ +@Controller +@RequestMapping("/system/menu") +public class SysMenuController extends BaseController +{ + private String prefix = "system/menu"; + + @Autowired + private ISysMenuService menuService; + + @RequiresPermissions("system:menu:view") + @GetMapping() + public String menu() + { + return prefix + "/menu"; + } + + @RequiresPermissions("system:menu:list") + @PostMapping("/list") + @ResponseBody + public List list(SysMenu menu) + { + Long userId = ShiroUtils.getUserId(); + List menuList = menuService.selectMenuList(menu, userId); + return menuList; + } + + /** + * 删除菜单 + */ + @Log(title = "菜单管理", businessType = BusinessType.DELETE) + @RequiresPermissions("system:menu:remove") + @GetMapping("/remove/{menuId}") + @ResponseBody + public AjaxResult remove(@PathVariable("menuId") Long menuId) + { + if (menuService.selectCountMenuByParentId(menuId) > 0) + { + return AjaxResult.warn("存在子菜单,不允许删除"); + } + if (menuService.selectCountRoleMenuByMenuId(menuId) > 0) + { + return AjaxResult.warn("菜单已分配,不允许删除"); + } + AuthorizationUtils.clearAllCachedAuthorizationInfo(); + return toAjax(menuService.deleteMenuById(menuId)); + } + + /** + * 新增 + */ + @GetMapping("/add/{parentId}") + public String add(@PathVariable("parentId") Long parentId, ModelMap mmap) + { + SysMenu menu = null; + if (0L != parentId) + { + menu = menuService.selectMenuById(parentId); + } + else + { + menu = new SysMenu(); + menu.setMenuId(0L); + menu.setMenuName("主目录"); + } + mmap.put("menu", menu); + return prefix + "/add"; + } + + /** + * 新增保存菜单 + */ + @Log(title = "菜单管理", businessType = BusinessType.INSERT) + @RequiresPermissions("system:menu:add") + @PostMapping("/add") + @ResponseBody + public AjaxResult addSave(@Validated SysMenu menu) + { + if (!menuService.checkMenuNameUnique(menu)) + { + return error("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); + } + menu.setCreateBy(getLoginName()); + AuthorizationUtils.clearAllCachedAuthorizationInfo(); + return toAjax(menuService.insertMenu(menu)); + } + + /** + * 修改菜单 + */ + @RequiresPermissions("system:menu:edit") + @GetMapping("/edit/{menuId}") + public String edit(@PathVariable("menuId") Long menuId, ModelMap mmap) + { + mmap.put("menu", menuService.selectMenuById(menuId)); + return prefix + "/edit"; + } + + /** + * 修改保存菜单 + */ + @Log(title = "菜单管理", businessType = BusinessType.UPDATE) + @RequiresPermissions("system:menu:edit") + @PostMapping("/edit") + @ResponseBody + public AjaxResult editSave(@Validated SysMenu menu) + { + if (!menuService.checkMenuNameUnique(menu)) + { + return error("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); + } + menu.setUpdateBy(getLoginName()); + AuthorizationUtils.clearAllCachedAuthorizationInfo(); + return toAjax(menuService.updateMenu(menu)); + } + + /** + * 选择菜单图标 + */ + @GetMapping("/icon") + public String icon() + { + return prefix + "/icon"; + } + + /** + * 校验菜单名称 + */ + @PostMapping("/checkMenuNameUnique") + @ResponseBody + public boolean checkMenuNameUnique(SysMenu menu) + { + return menuService.checkMenuNameUnique(menu); + } + + /** + * 加载角色菜单列表树 + */ + @GetMapping("/roleMenuTreeData") + @ResponseBody + public List roleMenuTreeData(SysRole role) + { + Long userId = ShiroUtils.getUserId(); + List ztrees = menuService.roleMenuTreeData(role, userId); + return ztrees; + } + + /** + * 加载所有菜单列表树 + */ + @GetMapping("/menuTreeData") + @ResponseBody + public List menuTreeData() + { + Long userId = ShiroUtils.getUserId(); + List ztrees = menuService.menuTreeData(userId); + return ztrees; + } + + /** + * 选择菜单树 + */ + @GetMapping("/selectMenuTree/{menuId}") + public String selectMenuTree(@PathVariable("menuId") Long menuId, ModelMap mmap) + { + mmap.put("menu", menuService.selectMenuById(menuId)); + return prefix + "/tree"; + } +} \ No newline at end of file diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysNoticeController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysNoticeController.java new file mode 100644 index 0000000..93e1be9 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysNoticeController.java @@ -0,0 +1,113 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.system.domain.SysNotice; +import com.ruoyi.system.service.ISysNoticeService; + +/** + * 公告 信息操作处理 + * + * @author ruoyi + */ +@Controller +@RequestMapping("/system/notice") +public class SysNoticeController extends BaseController +{ + private String prefix = "system/notice"; + + @Autowired + private ISysNoticeService noticeService; + + @RequiresPermissions("system:notice:view") + @GetMapping() + public String notice() + { + return prefix + "/notice"; + } + + /** + * 查询公告列表 + */ + @RequiresPermissions("system:notice:list") + @PostMapping("/list") + @ResponseBody + public TableDataInfo list(SysNotice notice) + { + startPage(); + List list = noticeService.selectNoticeList(notice); + return getDataTable(list); + } + + /** + * 新增公告 + */ + @GetMapping("/add") + public String add() + { + return prefix + "/add"; + } + + /** + * 新增保存公告 + */ + @RequiresPermissions("system:notice:add") + @Log(title = "通知公告", businessType = BusinessType.INSERT) + @PostMapping("/add") + @ResponseBody + public AjaxResult addSave(@Validated SysNotice notice) + { + notice.setCreateBy(getLoginName()); + return toAjax(noticeService.insertNotice(notice)); + } + + /** + * 修改公告 + */ + @RequiresPermissions("system:notice:edit") + @GetMapping("/edit/{noticeId}") + public String edit(@PathVariable("noticeId") Long noticeId, ModelMap mmap) + { + mmap.put("notice", noticeService.selectNoticeById(noticeId)); + return prefix + "/edit"; + } + + /** + * 修改保存公告 + */ + @RequiresPermissions("system:notice:edit") + @Log(title = "通知公告", businessType = BusinessType.UPDATE) + @PostMapping("/edit") + @ResponseBody + public AjaxResult editSave(@Validated SysNotice notice) + { + notice.setUpdateBy(getLoginName()); + return toAjax(noticeService.updateNotice(notice)); + } + + /** + * 删除公告 + */ + @RequiresPermissions("system:notice:remove") + @Log(title = "通知公告", businessType = BusinessType.DELETE) + @PostMapping("/remove") + @ResponseBody + public AjaxResult remove(String ids) + { + return toAjax(noticeService.deleteNoticeByIds(ids)); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysPostController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysPostController.java new file mode 100644 index 0000000..e12cc2c --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysPostController.java @@ -0,0 +1,162 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.system.domain.SysPost; +import com.ruoyi.system.service.ISysPostService; + +/** + * 岗位信息操作处理 + * + * @author ruoyi + */ +@Controller +@RequestMapping("/system/post") +public class SysPostController extends BaseController +{ + private String prefix = "system/post"; + + @Autowired + private ISysPostService postService; + + @RequiresPermissions("system:post:view") + @GetMapping() + public String operlog() + { + return prefix + "/post"; + } + + @RequiresPermissions("system:post:list") + @PostMapping("/list") + @ResponseBody + public TableDataInfo list(SysPost post) + { + startPage(); + List list = postService.selectPostList(post); + return getDataTable(list); + } + + @Log(title = "岗位管理", businessType = BusinessType.EXPORT) + @RequiresPermissions("system:post:export") + @PostMapping("/export") + @ResponseBody + public AjaxResult export(SysPost post) + { + List list = postService.selectPostList(post); + ExcelUtil util = new ExcelUtil(SysPost.class); + return util.exportExcel(list, "岗位数据"); + } + + @RequiresPermissions("system:post:remove") + @Log(title = "岗位管理", businessType = BusinessType.DELETE) + @PostMapping("/remove") + @ResponseBody + public AjaxResult remove(String ids) + { + try + { + return toAjax(postService.deletePostByIds(ids)); + } + catch (Exception e) + { + return error(e.getMessage()); + } + } + + /** + * 新增岗位 + */ + @GetMapping("/add") + public String add() + { + return prefix + "/add"; + } + + /** + * 新增保存岗位 + */ + @RequiresPermissions("system:post:add") + @Log(title = "岗位管理", businessType = BusinessType.INSERT) + @PostMapping("/add") + @ResponseBody + public AjaxResult addSave(@Validated SysPost post) + { + if (!postService.checkPostNameUnique(post)) + { + return error("新增岗位'" + post.getPostName() + "'失败,岗位名称已存在"); + } + else if (!postService.checkPostCodeUnique(post)) + { + return error("新增岗位'" + post.getPostName() + "'失败,岗位编码已存在"); + } + post.setCreateBy(getLoginName()); + return toAjax(postService.insertPost(post)); + } + + /** + * 修改岗位 + */ + @RequiresPermissions("system:post:edit") + @GetMapping("/edit/{postId}") + public String edit(@PathVariable("postId") Long postId, ModelMap mmap) + { + mmap.put("post", postService.selectPostById(postId)); + return prefix + "/edit"; + } + + /** + * 修改保存岗位 + */ + @RequiresPermissions("system:post:edit") + @Log(title = "岗位管理", businessType = BusinessType.UPDATE) + @PostMapping("/edit") + @ResponseBody + public AjaxResult editSave(@Validated SysPost post) + { + if (!postService.checkPostNameUnique(post)) + { + return error("修改岗位'" + post.getPostName() + "'失败,岗位名称已存在"); + } + else if (!postService.checkPostCodeUnique(post)) + { + return error("修改岗位'" + post.getPostName() + "'失败,岗位编码已存在"); + } + post.setUpdateBy(getLoginName()); + return toAjax(postService.updatePost(post)); + } + + /** + * 校验岗位名称 + */ + @PostMapping("/checkPostNameUnique") + @ResponseBody + public boolean checkPostNameUnique(SysPost post) + { + return postService.checkPostNameUnique(post); + } + + /** + * 校验岗位编码 + */ + @PostMapping("/checkPostCodeUnique") + @ResponseBody + public boolean checkPostCodeUnique(SysPost post) + { + return postService.checkPostCodeUnique(post); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java new file mode 100644 index 0000000..e95868a --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java @@ -0,0 +1,181 @@ +package com.ruoyi.web.controller.system; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +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.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.multipart.MultipartFile; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.ShiroUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.file.FileUploadUtils; +import com.ruoyi.common.utils.file.MimeTypeUtils; +import com.ruoyi.framework.shiro.service.SysPasswordService; +import com.ruoyi.system.service.ISysUserService; + +/** + * 个人信息 业务处理 + * + * @author ruoyi + */ +@Controller +@RequestMapping("/system/user/profile") +public class SysProfileController extends BaseController +{ + private static final Logger log = LoggerFactory.getLogger(SysProfileController.class); + + private String prefix = "system/user/profile"; + + @Autowired + private ISysUserService userService; + + @Autowired + private SysPasswordService passwordService; + + /** + * 个人信息 + */ + @GetMapping() + public String profile(ModelMap mmap) + { + SysUser user = getSysUser(); + mmap.put("user", user); + mmap.put("roleGroup", userService.selectUserRoleGroup(user.getUserId())); + mmap.put("postGroup", userService.selectUserPostGroup(user.getUserId())); + return prefix + "/profile"; + } + + @GetMapping("/checkPassword") + @ResponseBody + public boolean checkPassword(String password) + { + SysUser user = getSysUser(); + return passwordService.matches(user, password); + } + + @GetMapping("/resetPwd") + public String resetPwd(ModelMap mmap) + { + SysUser user = getSysUser(); + mmap.put("user", userService.selectUserById(user.getUserId())); + return prefix + "/resetPwd"; + } + + @Log(title = "重置密码", businessType = BusinessType.UPDATE) + @PostMapping("/resetPwd") + @ResponseBody + public AjaxResult resetPwd(String oldPassword, String newPassword) + { + SysUser user = getSysUser(); + if (!passwordService.matches(user, oldPassword)) + { + return error("修改密码失败,旧密码错误"); + } + if (passwordService.matches(user, newPassword)) + { + return error("新密码不能与旧密码相同"); + } + user.setSalt(ShiroUtils.randomSalt()); + user.setPassword(passwordService.encryptPassword(user.getLoginName(), newPassword, user.getSalt())); + user.setPwdUpdateDate(DateUtils.getNowDate()); + if (userService.resetUserPwd(user) > 0) + { + setSysUser(userService.selectUserById(user.getUserId())); + return success(); + } + return error("修改密码异常,请联系管理员"); + } + + /** + * 修改用户 + */ + @GetMapping("/edit") + public String edit(ModelMap mmap) + { + SysUser user = getSysUser(); + mmap.put("user", userService.selectUserById(user.getUserId())); + return prefix + "/edit"; + } + + /** + * 修改头像 + */ + @GetMapping("/avatar") + public String avatar(ModelMap mmap) + { + SysUser user = getSysUser(); + mmap.put("user", userService.selectUserById(user.getUserId())); + return prefix + "/avatar"; + } + + /** + * 修改用户 + */ + @Log(title = "个人信息", businessType = BusinessType.UPDATE) + @PostMapping("/update") + @ResponseBody + public AjaxResult update(SysUser user) + { + SysUser currentUser = getSysUser(); + currentUser.setUserName(user.getUserName()); + currentUser.setEmail(user.getEmail()); + currentUser.setPhonenumber(user.getPhonenumber()); + currentUser.setSex(user.getSex()); + if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(currentUser)) + { + return error("修改用户'" + currentUser.getLoginName() + "'失败,手机号码已存在"); + } + else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(currentUser)) + { + return error("修改用户'" + currentUser.getLoginName() + "'失败,邮箱账号已存在"); + } + if (userService.updateUserInfo(currentUser) > 0) + { + setSysUser(userService.selectUserById(currentUser.getUserId())); + return success(); + } + return error(); + } + + /** + * 保存头像 + */ + @Log(title = "个人信息", businessType = BusinessType.UPDATE) + @PostMapping("/updateAvatar") + @ResponseBody + public AjaxResult updateAvatar(@RequestParam("avatarfile") MultipartFile file) + { + SysUser currentUser = getSysUser(); + try + { + if (!file.isEmpty()) + { + String avatar = FileUploadUtils.upload(RuoYiConfig.getAvatarPath(), file, MimeTypeUtils.IMAGE_EXTENSION); + currentUser.setAvatar(avatar); + if (userService.updateUserInfo(currentUser) > 0) + { + setSysUser(userService.selectUserById(currentUser.getUserId())); + return success(); + } + } + return error(); + } + catch (Exception e) + { + log.error("修改头像失败!", e); + return error(e.getMessage()); + } + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java new file mode 100644 index 0000000..e8dc001 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java @@ -0,0 +1,46 @@ +package com.ruoyi.web.controller.system; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.shiro.service.SysRegisterService; +import com.ruoyi.system.service.ISysConfigService; + +/** + * 注册验证 + * + * @author ruoyi + */ +@Controller +public class SysRegisterController extends BaseController +{ + @Autowired + private SysRegisterService registerService; + + @Autowired + private ISysConfigService configService; + + @GetMapping("/register") + public String register() + { + return "register"; + } + + @PostMapping("/register") + @ResponseBody + public AjaxResult ajaxRegister(SysUser user) + { + if (!("true".equals(configService.selectConfigByKey("sys.account.registerUser")))) + { + return error("当前系统没有开启注册功能!"); + } + String msg = registerService.register(user); + return StringUtils.isEmpty(msg) ? success() : error(msg); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java new file mode 100644 index 0000000..228814b --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java @@ -0,0 +1,322 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.Ztree; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.framework.shiro.util.AuthorizationUtils; +import com.ruoyi.system.domain.SysUserRole; +import com.ruoyi.system.service.ISysDeptService; +import com.ruoyi.system.service.ISysRoleService; +import com.ruoyi.system.service.ISysUserService; + +/** + * 角色信息 + * + * @author ruoyi + */ +@Controller +@RequestMapping("/system/role") +public class SysRoleController extends BaseController +{ + private String prefix = "system/role"; + + @Autowired + private ISysRoleService roleService; + + @Autowired + private ISysUserService userService; + + @Autowired + private ISysDeptService deptService; + + @RequiresPermissions("system:role:view") + @GetMapping() + public String role() + { + return prefix + "/role"; + } + + @RequiresPermissions("system:role:list") + @PostMapping("/list") + @ResponseBody + public TableDataInfo list(SysRole role) + { + startPage(); + List list = roleService.selectRoleList(role); + return getDataTable(list); + } + + @Log(title = "角色管理", businessType = BusinessType.EXPORT) + @RequiresPermissions("system:role:export") + @PostMapping("/export") + @ResponseBody + public AjaxResult export(SysRole role) + { + List list = roleService.selectRoleList(role); + ExcelUtil util = new ExcelUtil(SysRole.class); + return util.exportExcel(list, "角色数据"); + } + + /** + * 新增角色 + */ + @GetMapping("/add") + public String add() + { + return prefix + "/add"; + } + + /** + * 新增保存角色 + */ + @RequiresPermissions("system:role:add") + @Log(title = "角色管理", businessType = BusinessType.INSERT) + @PostMapping("/add") + @ResponseBody + public AjaxResult addSave(@Validated SysRole role) + { + if (!roleService.checkRoleNameUnique(role)) + { + return error("新增角色'" + role.getRoleName() + "'失败,角色名称已存在"); + } + else if (!roleService.checkRoleKeyUnique(role)) + { + return error("新增角色'" + role.getRoleName() + "'失败,角色权限已存在"); + } + role.setCreateBy(getLoginName()); + AuthorizationUtils.clearAllCachedAuthorizationInfo(); + return toAjax(roleService.insertRole(role)); + + } + + /** + * 修改角色 + */ + @RequiresPermissions("system:role:edit") + @GetMapping("/edit/{roleId}") + public String edit(@PathVariable("roleId") Long roleId, ModelMap mmap) + { + roleService.checkRoleDataScope(roleId); + mmap.put("role", roleService.selectRoleById(roleId)); + return prefix + "/edit"; + } + + /** + * 修改保存角色 + */ + @RequiresPermissions("system:role:edit") + @Log(title = "角色管理", businessType = BusinessType.UPDATE) + @PostMapping("/edit") + @ResponseBody + public AjaxResult editSave(@Validated SysRole role) + { + roleService.checkRoleAllowed(role); + roleService.checkRoleDataScope(role.getRoleId()); + if (!roleService.checkRoleNameUnique(role)) + { + return error("修改角色'" + role.getRoleName() + "'失败,角色名称已存在"); + } + else if (!roleService.checkRoleKeyUnique(role)) + { + return error("修改角色'" + role.getRoleName() + "'失败,角色权限已存在"); + } + role.setUpdateBy(getLoginName()); + AuthorizationUtils.clearAllCachedAuthorizationInfo(); + return toAjax(roleService.updateRole(role)); + } + + /** + * 角色分配数据权限 + */ + @GetMapping("/authDataScope/{roleId}") + public String authDataScope(@PathVariable("roleId") Long roleId, ModelMap mmap) + { + mmap.put("role", roleService.selectRoleById(roleId)); + return prefix + "/dataScope"; + } + + /** + * 保存角色分配数据权限 + */ + @RequiresPermissions("system:role:edit") + @Log(title = "角色管理", businessType = BusinessType.UPDATE) + @PostMapping("/authDataScope") + @ResponseBody + public AjaxResult authDataScopeSave(SysRole role) + { + roleService.checkRoleAllowed(role); + roleService.checkRoleDataScope(role.getRoleId()); + role.setUpdateBy(getLoginName()); + if (roleService.authDataScope(role) > 0) + { + setSysUser(userService.selectUserById(getUserId())); + return success(); + } + return error(); + } + + @RequiresPermissions("system:role:remove") + @Log(title = "角色管理", businessType = BusinessType.DELETE) + @PostMapping("/remove") + @ResponseBody + public AjaxResult remove(String ids) + { + return toAjax(roleService.deleteRoleByIds(ids)); + } + + /** + * 校验角色名称 + */ + @PostMapping("/checkRoleNameUnique") + @ResponseBody + public boolean checkRoleNameUnique(SysRole role) + { + return roleService.checkRoleNameUnique(role); + } + + /** + * 校验角色权限 + */ + @PostMapping("/checkRoleKeyUnique") + @ResponseBody + public boolean checkRoleKeyUnique(SysRole role) + { + return roleService.checkRoleKeyUnique(role); + } + + /** + * 选择菜单树 + */ + @GetMapping("/selectMenuTree") + public String selectMenuTree() + { + return prefix + "/tree"; + } + + /** + * 角色状态修改 + */ + @Log(title = "角色管理", businessType = BusinessType.UPDATE) + @RequiresPermissions("system:role:edit") + @PostMapping("/changeStatus") + @ResponseBody + public AjaxResult changeStatus(SysRole role) + { + roleService.checkRoleAllowed(role); + roleService.checkRoleDataScope(role.getRoleId()); + return toAjax(roleService.changeStatus(role)); + } + + /** + * 分配用户 + */ + @RequiresPermissions("system:role:edit") + @GetMapping("/authUser/{roleId}") + public String authUser(@PathVariable("roleId") Long roleId, ModelMap mmap) + { + mmap.put("role", roleService.selectRoleById(roleId)); + return prefix + "/authUser"; + } + + /** + * 查询已分配用户角色列表 + */ + @RequiresPermissions("system:role:list") + @PostMapping("/authUser/allocatedList") + @ResponseBody + public TableDataInfo allocatedList(SysUser user) + { + startPage(); + List list = userService.selectAllocatedList(user); + return getDataTable(list); + } + + /** + * 取消授权 + */ + @RequiresPermissions("system:role:edit") + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PostMapping("/authUser/cancel") + @ResponseBody + public AjaxResult cancelAuthUser(SysUserRole userRole) + { + return toAjax(roleService.deleteAuthUser(userRole)); + } + + /** + * 批量取消授权 + */ + @RequiresPermissions("system:role:edit") + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PostMapping("/authUser/cancelAll") + @ResponseBody + public AjaxResult cancelAuthUserAll(Long roleId, String userIds) + { + return toAjax(roleService.deleteAuthUsers(roleId, userIds)); + } + + /** + * 选择用户 + */ + @GetMapping("/authUser/selectUser/{roleId}") + public String selectUser(@PathVariable("roleId") Long roleId, ModelMap mmap) + { + mmap.put("role", roleService.selectRoleById(roleId)); + return prefix + "/selectUser"; + } + + /** + * 查询未分配用户角色列表 + */ + @RequiresPermissions("system:role:list") + @PostMapping("/authUser/unallocatedList") + @ResponseBody + public TableDataInfo unallocatedList(SysUser user) + { + startPage(); + List list = userService.selectUnallocatedList(user); + return getDataTable(list); + } + + /** + * 批量选择用户授权 + */ + @RequiresPermissions("system:role:edit") + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PostMapping("/authUser/selectAll") + @ResponseBody + public AjaxResult selectAuthUserAll(Long roleId, String userIds) + { + roleService.checkRoleDataScope(roleId); + return toAjax(roleService.insertAuthUsers(roleId, userIds)); + } + + /** + * 加载角色部门(数据权限)列表树 + */ + @RequiresPermissions("system:role:edit") + @GetMapping("/deptTreeData") + @ResponseBody + public List deptTreeData(SysRole role) + { + List ztrees = deptService.roleDeptTreeData(role); + return ztrees; + } +} \ No newline at end of file diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java new file mode 100644 index 0000000..7090989 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java @@ -0,0 +1,334 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; +import java.util.stream.Collectors; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.multipart.MultipartFile; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.Ztree; +import com.ruoyi.common.core.domain.entity.SysDept; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.ShiroUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.framework.shiro.service.SysPasswordService; +import com.ruoyi.framework.shiro.util.AuthorizationUtils; +import com.ruoyi.system.service.ISysDeptService; +import com.ruoyi.system.service.ISysPostService; +import com.ruoyi.system.service.ISysRoleService; +import com.ruoyi.system.service.ISysUserService; + +/** + * 用户信息 + * + * @author ruoyi + */ +@Controller +@RequestMapping("/system/user") +public class SysUserController extends BaseController +{ + private String prefix = "system/user"; + + @Autowired + private ISysUserService userService; + + @Autowired + private ISysRoleService roleService; + + @Autowired + private ISysDeptService deptService; + + @Autowired + private ISysPostService postService; + + @Autowired + private SysPasswordService passwordService; + + @RequiresPermissions("system:user:view") + @GetMapping() + public String user() + { + return prefix + "/user"; + } + + @RequiresPermissions("system:user:list") + @PostMapping("/list") + @ResponseBody + public TableDataInfo list(SysUser user) + { + startPage(); + List list = userService.selectUserList(user); + return getDataTable(list); + } + + //导出 + @Log(title = "用户管理", businessType = BusinessType.EXPORT) + @RequiresPermissions("system:user:export") + @PostMapping("/export") + @ResponseBody + public AjaxResult export(SysUser user) + { + List list = userService.selectUserList(user); + ExcelUtil util = new ExcelUtil(SysUser.class); + return util.exportExcel(list, "用户数据"); + } + + //导入 + @Log(title = "用户管理", businessType = BusinessType.IMPORT) + @RequiresPermissions("system:user:import") + @PostMapping("/importData") + @ResponseBody + public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception + { + ExcelUtil util = new ExcelUtil(SysUser.class); + List userList = util.importExcel(file.getInputStream()); + String message = userService.importUser(userList, updateSupport, getLoginName()); + return AjaxResult.success(message); + } + + @RequiresPermissions("system:user:view") + @GetMapping("/importTemplate") + @ResponseBody + public AjaxResult importTemplate() + { + ExcelUtil util = new ExcelUtil(SysUser.class); + return util.importTemplateExcel("用户数据"); + } + + /** + * 新增用户 + */ + @GetMapping("/add") + public String add(ModelMap mmap) + { + mmap.put("roles", roleService.selectRoleAll().stream().filter(r -> !r.isAdmin()).collect(Collectors.toList())); + mmap.put("posts", postService.selectPostAll()); + return prefix + "/add"; + } + + /** + * 新增保存用户 + */ + @RequiresPermissions("system:user:add") + @Log(title = "用户管理", businessType = BusinessType.INSERT) + @PostMapping("/add") + @ResponseBody + public AjaxResult addSave(@Validated SysUser user) + { + if (!userService.checkLoginNameUnique(user)) + { + return error("新增用户'" + user.getLoginName() + "'失败,登录账号已存在"); + } + else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) + { + return error("新增用户'" + user.getLoginName() + "'失败,手机号码已存在"); + } + else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) + { + return error("新增用户'" + user.getLoginName() + "'失败,邮箱账号已存在"); + } + user.setSalt(ShiroUtils.randomSalt()); + user.setPassword(passwordService.encryptPassword(user.getLoginName(), user.getPassword(), user.getSalt())); + user.setCreateBy(getLoginName()); + return toAjax(userService.insertUser(user)); + } + + /** + * 修改用户 + */ + @RequiresPermissions("system:user:edit") + @GetMapping("/edit/{userId}") + public String edit(@PathVariable("userId") Long userId, ModelMap mmap) + { + userService.checkUserDataScope(userId); + List roles = roleService.selectRolesByUserId(userId); + mmap.put("user", userService.selectUserById(userId)); + mmap.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList())); + mmap.put("posts", postService.selectPostsByUserId(userId)); + return prefix + "/edit"; + } + + /** + * 修改保存用户 + */ + @RequiresPermissions("system:user:edit") + @Log(title = "用户管理", businessType = BusinessType.UPDATE) + @PostMapping("/edit") + @ResponseBody + public AjaxResult editSave(@Validated SysUser user) + { + userService.checkUserAllowed(user); + userService.checkUserDataScope(user.getUserId()); + if (!userService.checkLoginNameUnique(user)) + { + return error("修改用户'" + user.getLoginName() + "'失败,登录账号已存在"); + } + else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) + { + return error("修改用户'" + user.getLoginName() + "'失败,手机号码已存在"); + } + else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) + { + return error("修改用户'" + user.getLoginName() + "'失败,邮箱账号已存在"); + } + user.setUpdateBy(getLoginName()); + AuthorizationUtils.clearAllCachedAuthorizationInfo(); + return toAjax(userService.updateUser(user)); + } + + @RequiresPermissions("system:user:resetPwd") + @GetMapping("/resetPwd/{userId}") + public String resetPwd(@PathVariable("userId") Long userId, ModelMap mmap) + { + mmap.put("user", userService.selectUserById(userId)); + return prefix + "/resetPwd"; + } + + @RequiresPermissions("system:user:resetPwd") + @Log(title = "重置密码", businessType = BusinessType.UPDATE) + @PostMapping("/resetPwd") + @ResponseBody + public AjaxResult resetPwdSave(SysUser user) + { + userService.checkUserAllowed(user); + userService.checkUserDataScope(user.getUserId()); + user.setSalt(ShiroUtils.randomSalt()); + user.setPassword(passwordService.encryptPassword(user.getLoginName(), user.getPassword(), user.getSalt())); + if (userService.resetUserPwd(user) > 0) + { + if (ShiroUtils.getUserId().longValue() == user.getUserId().longValue()) + { + setSysUser(userService.selectUserById(user.getUserId())); + } + return success(); + } + return error(); + } + + /** + * 进入授权角色页 + */ + @GetMapping("/authRole/{userId}") + public String authRole(@PathVariable("userId") Long userId, ModelMap mmap) + { + SysUser user = userService.selectUserById(userId); + // 获取用户所属的角色列表 + List roles = roleService.selectRolesByUserId(userId); + mmap.put("user", user); + mmap.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList())); + return prefix + "/authRole"; + } + + /** + * 用户授权角色 + */ + @RequiresPermissions("system:user:edit") + @Log(title = "用户管理", businessType = BusinessType.GRANT) + @PostMapping("/authRole/insertAuthRole") + @ResponseBody + public AjaxResult insertAuthRole(Long userId, Long[] roleIds) + { + userService.checkUserDataScope(userId); + userService.insertUserAuth(userId, roleIds); + AuthorizationUtils.clearAllCachedAuthorizationInfo(); + return success(); + } + + @RequiresPermissions("system:user:remove") + @Log(title = "用户管理", businessType = BusinessType.DELETE) + @PostMapping("/remove") + @ResponseBody + public AjaxResult remove(String ids) + { + if (ArrayUtils.contains(Convert.toLongArray(ids), getUserId())) + { + return error("当前用户不能删除"); + } + return toAjax(userService.deleteUserByIds(ids)); + } + + /** + * 校验用户名 + */ + @PostMapping("/checkLoginNameUnique") + @ResponseBody + public boolean checkLoginNameUnique(SysUser user) + { + return userService.checkLoginNameUnique(user); + } + + /** + * 校验手机号码 + */ + @PostMapping("/checkPhoneUnique") + @ResponseBody + public boolean checkPhoneUnique(SysUser user) + { + return userService.checkPhoneUnique(user); + } + + /** + * 校验email邮箱 + */ + @PostMapping("/checkEmailUnique") + @ResponseBody + public boolean checkEmailUnique(SysUser user) + { + return userService.checkEmailUnique(user); + } + + /** + * 用户状态修改 + */ + @Log(title = "用户管理", businessType = BusinessType.UPDATE) + @RequiresPermissions("system:user:edit") + @PostMapping("/changeStatus") + @ResponseBody + public AjaxResult changeStatus(SysUser user) + { + userService.checkUserAllowed(user); + userService.checkUserDataScope(user.getUserId()); + return toAjax(userService.changeStatus(user)); + } + + /** + * 加载部门列表树 + */ + @RequiresPermissions("system:user:list") + @GetMapping("/deptTreeData") + @ResponseBody + public List deptTreeData() + { + List ztrees = deptService.selectDeptTree(new SysDept()); + return ztrees; + } + + /** + * 选择部门树 + * + * @param deptId 部门ID + */ + @RequiresPermissions("system:user:list") + @GetMapping("/selectDeptTree/{deptId}") + public String selectDeptTree(@PathVariable("deptId") Long deptId, ModelMap mmap) + { + mmap.put("dept", deptService.selectDeptById(deptId)); + return prefix + "/deptTree"; + } +} \ No newline at end of file diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/BuildController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/BuildController.java new file mode 100644 index 0000000..4d3c128 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/BuildController.java @@ -0,0 +1,26 @@ +package com.ruoyi.web.controller.tool; + +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import com.ruoyi.common.core.controller.BaseController; + +/** + * build 表单构建 + * + * @author ruoyi + */ +@Controller +@RequestMapping("/tool/build") +public class BuildController extends BaseController +{ + private String prefix = "tool/build"; + + @RequiresPermissions("tool:build:view") + @GetMapping() + public String build() + { + return prefix + "/build"; + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/SwaggerController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/SwaggerController.java new file mode 100644 index 0000000..de2ae6c --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/SwaggerController.java @@ -0,0 +1,24 @@ +package com.ruoyi.web.controller.tool; + +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import com.ruoyi.common.core.controller.BaseController; + +/** + * swagger 接口 + * + * @author ruoyi + */ +@Controller +@RequestMapping("/tool/swagger") +public class SwaggerController extends BaseController +{ + @RequiresPermissions("tool:swagger:view") + @GetMapping() + public String index() + { + return redirect("/swagger-ui/index.html"); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/TestController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/TestController.java new file mode 100644 index 0000000..b4f6bac --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/TestController.java @@ -0,0 +1,183 @@ +package com.ruoyi.web.controller.tool; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.R; +import com.ruoyi.common.utils.StringUtils; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import io.swagger.annotations.ApiOperation; + +/** + * swagger 用户测试方法 + * + * @author ruoyi + */ +@Api("用户信息管理") +@RestController +@RequestMapping("/test/user") +public class TestController extends BaseController +{ + private final static Map users = new LinkedHashMap(); + { + users.put(1, new UserEntity(1, "admin", "admin123", "15888888888")); + users.put(2, new UserEntity(2, "ry", "admin123", "15666666666")); + } + + @ApiOperation("获取用户列表") + @GetMapping("/list") + public R> userList() + { + List userList = new ArrayList(users.values()); + return R.ok(userList); + } + + @ApiOperation("获取用户详细") + @ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", dataTypeClass = Integer.class) + @GetMapping("/{userId}") + public R getUser(@PathVariable Integer userId) + { + if (!users.isEmpty() && users.containsKey(userId)) + { + return R.ok(users.get(userId)); + } + else + { + return R.fail("用户不存在"); + } + } + + @ApiOperation("新增用户") + @ApiImplicitParams({ + @ApiImplicitParam(name = "userId", value = "用户id", dataType = "Integer", dataTypeClass = Integer.class), + @ApiImplicitParam(name = "username", value = "用户名称", dataType = "String", dataTypeClass = String.class), + @ApiImplicitParam(name = "password", value = "用户密码", dataType = "String", dataTypeClass = String.class), + @ApiImplicitParam(name = "mobile", value = "用户手机", dataType = "String", dataTypeClass = String.class) + }) + @PostMapping("/save") + public R save(UserEntity user) + { + if (StringUtils.isNull(user) || StringUtils.isNull(user.getUserId())) + { + return R.fail("用户ID不能为空"); + } + users.put(user.getUserId(), user); + return R.ok(); + } + + @ApiOperation("更新用户") + @PutMapping("/update") + public R update(@RequestBody UserEntity user) + { + if (StringUtils.isNull(user) || StringUtils.isNull(user.getUserId())) + { + return R.fail("用户ID不能为空"); + } + if (users.isEmpty() || !users.containsKey(user.getUserId())) + { + return R.fail("用户不存在"); + } + users.remove(user.getUserId()); + users.put(user.getUserId(), user); + return R.ok(); + } + + @ApiOperation("删除用户信息") + @ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", dataTypeClass = Integer.class) + @DeleteMapping("/{userId}") + public R delete(@PathVariable Integer userId) + { + if (!users.isEmpty() && users.containsKey(userId)) + { + users.remove(userId); + return R.ok(); + } + else + { + return R.fail("用户不存在"); + } + } +} + +@ApiModel(value = "UserEntity", description = "用户实体") +class UserEntity +{ + @ApiModelProperty("用户ID") + private Integer userId; + + @ApiModelProperty("用户名称") + private String username; + + @ApiModelProperty("用户密码") + private String password; + + @ApiModelProperty("用户手机") + private String mobile; + + public UserEntity() + { + + } + + public UserEntity(Integer userId, String username, String password, String mobile) + { + this.userId = userId; + this.username = username; + this.password = password; + this.mobile = mobile; + } + + public Integer getUserId() + { + return userId; + } + + public void setUserId(Integer userId) + { + this.userId = userId; + } + + public String getUsername() + { + return username; + } + + public void setUsername(String username) + { + this.username = username; + } + + public String getPassword() + { + return password; + } + + public void setPassword(String password) + { + this.password = password; + } + + public String getMobile() + { + return mobile; + } + + public void setMobile(String mobile) + { + this.mobile = mobile; + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java b/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java new file mode 100644 index 0000000..e18d2cc --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java @@ -0,0 +1,67 @@ +package com.ruoyi.web.core.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import com.ruoyi.common.config.RuoYiConfig; +import io.swagger.annotations.ApiOperation; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.service.Contact; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; + +/** + * Swagger2的接口配置 + * + * @author ruoyi + */ +@Configuration +public class SwaggerConfig +{ + /** 是否开启swagger */ + @Value("${swagger.enabled}") + private boolean enabled; + + /** + * 创建API + */ + @Bean + public Docket createRestApi() + { + return new Docket(DocumentationType.OAS_30) + // 是否启用Swagger + .enable(enabled) + // 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息) + .apiInfo(apiInfo()) + // 设置哪些接口暴露给Swagger展示 + .select() + // 扫描所有有注解的api,用这种方式更灵活 + .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) + // 扫描指定包中的swagger注解 + //.apis(RequestHandlerSelectors.basePackage("com.ruoyi.project.tool.swagger")) + // 扫描所有 .apis(RequestHandlerSelectors.any()) + .paths(PathSelectors.any()) + .build(); + } + + /** + * 添加摘要信息 + */ + private ApiInfo apiInfo() + { + // 用ApiInfoBuilder进行定制 + return new ApiInfoBuilder() + // 设置标题 + .title("标题:若依管理系统_接口文档") + // 描述 + .description("描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...") + // 作者信息 + .contact(new Contact(RuoYiConfig.getName(), null, null)) + // 版本 + .version("版本号:" + RuoYiConfig.getVersion()) + .build(); + } +} diff --git a/ruoyi-admin/src/main/resources/application-druid.yml b/ruoyi-admin/src/main/resources/application-druid.yml new file mode 100644 index 0000000..d4b4877 --- /dev/null +++ b/ruoyi-admin/src/main/resources/application-druid.yml @@ -0,0 +1,61 @@ +# 数据源配置 +spring: + datasource: + type: com.alibaba.druid.pool.DruidDataSource + driverClassName: com.mysql.cj.jdbc.Driver + druid: + # 主库数据源 + master: + url: jdbc:mysql://localhost:3306/ry?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + username: root + password: dcj123456 + # 从库数据源 + slave: + # 从数据源开关/默认关闭 + enabled: false + url: + username: + password: + # 初始连接数 + initialSize: 5 + # 最小连接池数量 + minIdle: 10 + # 最大连接池数量 + maxActive: 20 + # 配置获取连接等待超时的时间 + maxWait: 60000 + # 配置连接超时时间 + connectTimeout: 30000 + # 配置网络超时时间 + socketTimeout: 60000 + # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 + timeBetweenEvictionRunsMillis: 60000 + # 配置一个连接在池中最小生存的时间,单位是毫秒 + minEvictableIdleTimeMillis: 300000 + # 配置一个连接在池中最大生存的时间,单位是毫秒 + maxEvictableIdleTimeMillis: 900000 + # 配置检测连接是否有效 + validationQuery: SELECT 1 FROM DUAL + testWhileIdle: true + testOnBorrow: false + testOnReturn: false + webStatFilter: + enabled: true + statViewServlet: + enabled: true + # 设置白名单,不填则允许所有访问 + allow: + url-pattern: /druid/* + # 控制台管理用户名和密码 + login-username: ruoyi + login-password: 123456 + filter: + stat: + enabled: true + # 慢SQL记录 + log-slow-sql: true + slow-sql-millis: 1000 + merge-sql: true + wall: + config: + multi-statement-allow: true \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml new file mode 100644 index 0000000..d9d4510 --- /dev/null +++ b/ruoyi-admin/src/main/resources/application.yml @@ -0,0 +1,142 @@ +# 项目相关配置 +ruoyi: + # 名称 + name: RuoYi + # 版本 + version: 4.7.6 + # 版权年份 + copyrightYear: 2023 + # 实例演示开关 + demoEnabled: true + # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath) + profile: D:/ruoyi/uploadPath + # 获取ip地址开关 + addressEnabled: false + +# 开发环境配置 +server: + # 服务器的HTTP端口,默认为80 + port: 80 + servlet: + # 应用的访问路径 + context-path: / + tomcat: + # tomcat的URI编码 + uri-encoding: UTF-8 + # 连接数满后的排队数,默认为100 + accept-count: 1000 + threads: + # tomcat最大线程数,默认为200 + max: 800 + # Tomcat启动初始化的线程数,默认值10 + min-spare: 100 + +# 日志配置 +logging: + level: + com.ruoyi: debug + org.springframework: warn + +# 用户配置 +user: + password: + # 密码错误{maxRetryCount}次锁定10分钟 + maxRetryCount: 5 + +# Spring配置 +spring: + # 模板引擎 + thymeleaf: + mode: HTML + encoding: utf-8 + # 禁用缓存 + cache: false + # 资源信息 + messages: + # 国际化资源文件路径 + basename: static/i18n/messages + jackson: + time-zone: GMT+8 + date-format: yyyy-MM-dd HH:mm:ss + profiles: + active: druid + # 文件上传 + servlet: + multipart: + # 单个文件大小 + max-file-size: 10MB + # 设置总上传的文件大小 + max-request-size: 20MB + # 服务模块 + devtools: + restart: + # 热部署开关 + enabled: true + +# MyBatis +mybatis: + # 搜索指定包别名 + typeAliasesPackage: com.ruoyi.**.domain + # 配置mapper的扫描,找到所有的mapper.xml映射文件 + mapperLocations: classpath*:mapper/**/*Mapper.xml + # 加载全局的配置文件 + configLocation: classpath:mybatis/mybatis-config.xml + +# PageHelper分页插件 +pagehelper: + helperDialect: mysql + supportMethodsArguments: true + params: count=countSql + +# Shiro +shiro: + user: + # 登录地址 + loginUrl: /login + # 权限认证失败地址 + unauthorizedUrl: /unauth + # 首页地址 + indexUrl: /index + # 验证码开关 + captchaEnabled: true + # 验证码类型 math 数组计算 char 字符 + captchaType: math + cookie: + # 设置Cookie的域名 默认空,即当前访问的域名 + domain: + # 设置cookie的有效访问路径 + path: / + # 设置HttpOnly属性 + httpOnly: true + # 设置Cookie的过期时间,天为单位 + maxAge: 30 + # 设置密钥,务必保持唯一性(生成方式,直接拷贝到main运行即可)Base64.encodeToString(CipherUtils.generateNewKey(128, "AES").getEncoded()) (默认启动生成随机秘钥,随机秘钥会导致之前客户端RememberMe Cookie无效,如设置固定秘钥RememberMe Cookie则有效) + cipherKey: + session: + # Session超时时间,-1代表永不过期(默认30分钟) + expireTime: 30 + # 同步session到数据库的周期(默认1分钟) + dbSyncPeriod: 1 + # 相隔多久检查一次session的有效性,默认就是10分钟 + validationInterval: 10 + # 同一个用户最大会话数,比如2的意思是同一个账号允许最多同时两个人登录(默认-1不限制) + maxSession: -1 + # 踢出之前登录的/之后登录的用户,默认踢出之前登录的用户 + kickoutAfter: false + rememberMe: + # 是否开启记住我 + enabled: true + +# 防止XSS攻击 +xss: + # 过滤开关 + enabled: true + # 排除链接(多个用逗号分隔) + excludes: /system/notice/* + # 匹配链接 + urlPatterns: /system/*,/monitor/*,/tool/* + +# Swagger配置 +swagger: + # 是否开启swagger + enabled: true diff --git a/ruoyi-admin/src/main/resources/banner.txt b/ruoyi-admin/src/main/resources/banner.txt new file mode 100644 index 0000000..0931cb8 --- /dev/null +++ b/ruoyi-admin/src/main/resources/banner.txt @@ -0,0 +1,24 @@ +Application Version: ${ruoyi.version} +Spring Boot Version: ${spring-boot.version} +//////////////////////////////////////////////////////////////////// +// _ooOoo_ // +// o8888888o // +// 88" . "88 // +// (| ^_^ |) // +// O\ = /O // +// ____/`---'\____ // +// .' \\| |// `. // +// / \\||| : |||// \ // +// / _||||| -:- |||||- \ // +// | | \\\ - /// | | // +// | \_| ''\---/'' | | // +// \ .-\__ `-` ___/-. / // +// ___`. .' /--.--\ `. . ___ // +// ."" '< `.___\_<|>_/___.' >'"". // +// | | : `- \`.;`\ _ /`;.`/ - ` : | | // +// \ \ `-. \_ __\ /__ _/ .-` / / // +// ========`-.____`-.___\_____/___.-`____.-'======== // +// `=---=' // +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // +// 佛祖保佑 永不宕机 永无BUG // +//////////////////////////////////////////////////////////////////// \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/ehcache/ehcache-shiro.xml b/ruoyi-admin/src/main/resources/ehcache/ehcache-shiro.xml new file mode 100644 index 0000000..7bf080f --- /dev/null +++ b/ruoyi-admin/src/main/resources/ehcache/ehcache-shiro.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/logback.xml b/ruoyi-admin/src/main/resources/logback.xml new file mode 100644 index 0000000..a360583 --- /dev/null +++ b/ruoyi-admin/src/main/resources/logback.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + ${log.pattern} + + + + + + ${log.path}/sys-info.log + + + + ${log.path}/sys-info.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + INFO + + ACCEPT + + DENY + + + + + ${log.path}/sys-error.log + + + + ${log.path}/sys-error.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + ERROR + + ACCEPT + + DENY + + + + + + ${log.path}/sys-user.log + + + ${log.path}/sys-user.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/mybatis/mybatis-config.xml b/ruoyi-admin/src/main/resources/mybatis/mybatis-config.xml new file mode 100644 index 0000000..ac47c03 --- /dev/null +++ b/ruoyi-admin/src/main/resources/mybatis/mybatis-config.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + diff --git a/ruoyi-admin/src/main/resources/static/ajax/libs/beautifyhtml/beautifyhtml.js b/ruoyi-admin/src/main/resources/static/ajax/libs/beautifyhtml/beautifyhtml.js new file mode 100644 index 0000000..ea69dec --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ajax/libs/beautifyhtml/beautifyhtml.js @@ -0,0 +1,617 @@ +/*jshint curly:true, eqeqeq:true, laxbreak:true, noempty:false */ +/* + + The MIT License (MIT) + + Copyright (c) 2007-2013 Einar Lielmanis and contributors. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + + Style HTML +--------------- + + Written by Nochum Sossonko, (nsossonko@hotmail.com) + + Based on code initially developed by: Einar Lielmanis, + http://jsbeautifier.org/ + + Usage: + style_html(html_source); + + style_html(html_source, options); + + The options are: + indent_size (default 4) — indentation size, + indent_char (default space) — character to indent with, + max_char (default 250) - maximum amount of characters per line (0 = disable) + brace_style (default "collapse") - "collapse" | "expand" | "end-expand" + put braces on the same line as control statements (default), or put braces on own line (Allman / ANSI style), or just put end braces on own line. + unformatted (defaults to inline tags) - list of tags, that shouldn't be reformatted + indent_scripts (default normal) - "keep"|"separate"|"normal" + + e.g. + + style_html(html_source, { + 'indent_size': 2, + 'indent_char': ' ', + 'max_char': 78, + 'brace_style': 'expand', + 'unformatted': ['a', 'sub', 'sup', 'b', 'i', 'u'] + }); +*/ + +(function() { + + function style_html(html_source, options, js_beautify, css_beautify) { + //Wrapper function to invoke all the necessary constructors and deal with the output. + + var multi_parser, + indent_size, + indent_character, + max_char, + brace_style, + unformatted; + + options = options || {}; + indent_size = options.indent_size || 4; + indent_character = options.indent_char || ' '; + brace_style = options.brace_style || 'collapse'; + max_char = options.max_char === 0 ? Infinity : options.max_char || 250; + unformatted = options.unformatted || ['a', 'span', 'bdo', 'em', 'strong', 'dfn', 'code', 'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'q', 'sub', 'sup', 'tt', 'i', 'b', 'big', 'small', 'u', 's', 'strike', 'font', 'ins', 'del', 'pre', 'address', 'dt', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']; + + function Parser() { + + this.pos = 0; //Parser position + this.token = ''; + this.current_mode = 'CONTENT'; //reflects the current Parser mode: TAG/CONTENT + this.tags = { //An object to hold tags, their position, and their parent-tags, initiated with default values + parent: 'parent1', + parentcount: 1, + parent1: '' + }; + this.tag_type = ''; + this.token_text = this.last_token = this.last_text = this.token_type = ''; + + this.Utils = { //Uilities made available to the various functions + whitespace: "\n\r\t ".split(''), + single_token: 'br,input,link,meta,!doctype,basefont,base,area,hr,wbr,param,img,isindex,?xml,embed,?php,?,?='.split(','), //all the single tags for HTML + extra_liners: 'head,body,/html'.split(','), //for tags that need a line of whitespace before them + in_array: function (what, arr) { + for (var i=0; i= this.input.length) { + return content.length?content.join(''):['', 'TK_EOF']; + } + + input_char = this.input.charAt(this.pos); + this.pos++; + this.line_char_count++; + + if (this.Utils.in_array(input_char, this.Utils.whitespace)) { + if (content.length) { + space = true; + } + this.line_char_count--; + continue; //don't want to insert unnecessary space + } + else if (space) { + if (this.line_char_count >= this.max_char) { //insert a line when the max_char is reached + content.push('\n'); + for (var i=0; i', 'igm'); + reg_match.lastIndex = this.pos; + var reg_array = reg_match.exec(this.input); + var end_script = reg_array?reg_array.index:this.input.length; //absolute end of script + if(this.pos < end_script) { //get everything in between the script tags + content = this.input.substring(this.pos, end_script); + this.pos = end_script; + } + return content; + }; + + this.record_tag = function (tag){ //function to record a tag and its parent in this.tags Object + if (this.tags[tag + 'count']) { //check for the existence of this tag type + this.tags[tag + 'count']++; + this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level + } + else { //otherwise initialize this tag type + this.tags[tag + 'count'] = 1; + this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level + } + this.tags[tag + this.tags[tag + 'count'] + 'parent'] = this.tags.parent; //set the parent (i.e. in the case of a div this.tags.div1parent) + this.tags.parent = tag + this.tags[tag + 'count']; //and make this the current parent (i.e. in the case of a div 'div1') + }; + + this.retrieve_tag = function (tag) { //function to retrieve the opening tag to the corresponding closer + if (this.tags[tag + 'count']) { //if the openener is not in the Object we ignore it + var temp_parent = this.tags.parent; //check to see if it's a closable tag. + while (temp_parent) { //till we reach '' (the initial value); + if (tag + this.tags[tag + 'count'] === temp_parent) { //if this is it use it + break; + } + temp_parent = this.tags[temp_parent + 'parent']; //otherwise keep on climbing up the DOM Tree + } + if (temp_parent) { //if we caught something + this.indent_level = this.tags[tag + this.tags[tag + 'count']]; //set the indent_level accordingly + this.tags.parent = this.tags[temp_parent + 'parent']; //and set the current parent + } + delete this.tags[tag + this.tags[tag + 'count'] + 'parent']; //delete the closed tags parent reference... + delete this.tags[tag + this.tags[tag + 'count']]; //...and the tag itself + if (this.tags[tag + 'count'] === 1) { + delete this.tags[tag + 'count']; + } + else { + this.tags[tag + 'count']--; + } + } + }; + + this.get_tag = function (peek) { //function to get a full tag and parse its type + var input_char = '', + content = [], + comment = '', + space = false, + tag_start, tag_end, + orig_pos = this.pos, + orig_line_char_count = this.line_char_count; + + peek = peek !== undefined ? peek : false; + + do { + if (this.pos >= this.input.length) { + if (peek) { + this.pos = orig_pos; + this.line_char_count = orig_line_char_count; + } + return content.length?content.join(''):['', 'TK_EOF']; + } + + input_char = this.input.charAt(this.pos); + this.pos++; + this.line_char_count++; + + if (this.Utils.in_array(input_char, this.Utils.whitespace)) { //don't want to insert unnecessary space + space = true; + this.line_char_count--; + continue; + } + + if (input_char === "'" || input_char === '"') { + if (!content[1] || content[1] !== '!') { //if we're in a comment strings don't get treated specially + input_char += this.get_unformatted(input_char); + space = true; + } + } + + if (input_char === '=') { //no space before = + space = false; + } + + if (content.length && content[content.length-1] !== '=' && input_char !== '>' && space) { + //no space after = or before > + if (this.line_char_count >= this.max_char) { + this.print_newline(false, content); + this.line_char_count = 0; + } + else { + content.push(' '); + this.line_char_count++; + } + space = false; + } + if (input_char === '<') { + tag_start = this.pos - 1; + } + content.push(input_char); //inserts character at-a-time (or string) + } while (input_char !== '>'); + + var tag_complete = content.join(''); + var tag_index; + if (tag_complete.indexOf(' ') !== -1) { //if there's whitespace, thats where the tag name ends + tag_index = tag_complete.indexOf(' '); + } + else { //otherwise go with the tag ending + tag_index = tag_complete.indexOf('>'); + } + var tag_check = tag_complete.substring(1, tag_index).toLowerCase(); + if (tag_complete.charAt(tag_complete.length-2) === '/' || + this.Utils.in_array(tag_check, this.Utils.single_token)) { //if this tag name is a single tag type (either in the list or has a closing /) + if ( ! peek) { + this.tag_type = 'SINGLE'; + } + } + else if (tag_check === 'script') { //for later script handling + if ( ! peek) { + this.record_tag(tag_check); + this.tag_type = 'SCRIPT'; + } + } + else if (tag_check === 'style') { //for future style handling (for now it justs uses get_content) + if ( ! peek) { + this.record_tag(tag_check); + this.tag_type = 'STYLE'; + } + } + else if (this.is_unformatted(tag_check, unformatted)) { // do not reformat the "unformatted" tags + comment = this.get_unformatted('', tag_complete); //...delegate to get_unformatted function + content.push(comment); + // Preserve collapsed whitespace either before or after this tag. + if (tag_start > 0 && this.Utils.in_array(this.input.charAt(tag_start - 1), this.Utils.whitespace)){ + content.splice(0, 0, this.input.charAt(tag_start - 1)); + } + tag_end = this.pos - 1; + if (this.Utils.in_array(this.input.charAt(tag_end + 1), this.Utils.whitespace)){ + content.push(this.input.charAt(tag_end + 1)); + } + this.tag_type = 'SINGLE'; + } + else if (tag_check.charAt(0) === '!') { //peek for so... + comment = this.get_unformatted('-->', tag_complete); //...delegate to get_unformatted + content.push(comment); + } + if ( ! peek) { + this.tag_type = 'START'; + } + } + else if (tag_check.indexOf('[endif') !== -1) {//peek for ', tag_complete); + content.push(comment); + this.tag_type = 'SINGLE'; + } + } + else if ( ! peek) { + if (tag_check.charAt(0) === '/') { //this tag is a double tag so check for tag-ending + this.retrieve_tag(tag_check.substring(1)); //remove it and all ancestors + this.tag_type = 'END'; + } + else { //otherwise it's a start-tag + this.record_tag(tag_check); //push it on the tag stack + this.tag_type = 'START'; + } + if (this.Utils.in_array(tag_check, this.Utils.extra_liners)) { //check if this double needs an extra line + this.print_newline(true, this.output); + } + } + + if (peek) { + this.pos = orig_pos; + this.line_char_count = orig_line_char_count; + } + + return content.join(''); //returns fully formatted tag + }; + + this.get_unformatted = function (delimiter, orig_tag) { //function to return unformatted content in its entirety + + if (orig_tag && orig_tag.toLowerCase().indexOf(delimiter) !== -1) { + return ''; + } + var input_char = ''; + var content = ''; + var space = true; + do { + + if (this.pos >= this.input.length) { + return content; + } + + input_char = this.input.charAt(this.pos); + this.pos++; + + if (this.Utils.in_array(input_char, this.Utils.whitespace)) { + if (!space) { + this.line_char_count--; + continue; + } + if (input_char === '\n' || input_char === '\r') { + content += '\n'; + /* Don't change tab indention for unformatted blocks. If using code for html editing, this will greatly affect

 tags if they are specified in the 'unformatted array'
+                for (var i=0; i]*>\s*$/);
+
+            // if next_tag comes back but is not an isolated tag, then
+            // let's treat the 'a' tag as having content
+            // and respect the unformatted option
+            if (!tag || this.Utils.in_array(tag, unformatted)){
+                return true;
+            } else {
+                return false;
+            }
+        };
+
+        this.printer = function (js_source, indent_character, indent_size, max_char, brace_style) { //handles input/output and some other printing functions
+
+          this.input = js_source || ''; //gets the input for the Parser
+          this.output = [];
+          this.indent_character = indent_character;
+          this.indent_string = '';
+          this.indent_size = indent_size;
+          this.brace_style = brace_style;
+          this.indent_level = 0;
+          this.max_char = max_char;
+          this.line_char_count = 0; //count to see if max_char was exceeded
+
+          for (var i=0; i 0) {
+              this.indent_level--;
+            }
+          };
+        };
+        return this;
+      }
+
+      /*_____________________--------------------_____________________*/
+
+      multi_parser = new Parser(); //wrapping functions Parser
+      multi_parser.printer(html_source, indent_character, indent_size, max_char, brace_style); //initialize starting values
+
+      while (true) {
+          var t = multi_parser.get_token();
+          multi_parser.token_text = t[0];
+          multi_parser.token_type = t[1];
+
+        if (multi_parser.token_type === 'TK_EOF') {
+          break;
+        }
+
+        switch (multi_parser.token_type) {
+          case 'TK_TAG_START':
+            multi_parser.print_newline(false, multi_parser.output);
+            multi_parser.print_token(multi_parser.token_text);
+            multi_parser.indent();
+            multi_parser.current_mode = 'CONTENT';
+            break;
+          case 'TK_TAG_STYLE':
+          case 'TK_TAG_SCRIPT':
+            multi_parser.print_newline(false, multi_parser.output);
+            multi_parser.print_token(multi_parser.token_text);
+            multi_parser.current_mode = 'CONTENT';
+            break;
+          case 'TK_TAG_END':
+            //Print new line only if the tag has no content and has child
+            if (multi_parser.last_token === 'TK_CONTENT' && multi_parser.last_text === '') {
+                var tag_name = multi_parser.token_text.match(/\w+/)[0];
+                var tag_extracted_from_last_output = multi_parser.output[multi_parser.output.length -1].match(/<\s*(\w+)/);
+                if (tag_extracted_from_last_output === null || tag_extracted_from_last_output[1] !== tag_name) {
+                    multi_parser.print_newline(true, multi_parser.output);
+                }
+            }
+            multi_parser.print_token(multi_parser.token_text);
+            multi_parser.current_mode = 'CONTENT';
+            break;
+          case 'TK_TAG_SINGLE':
+            // Don't add a newline before elements that should remain unformatted.
+            var tag_check = multi_parser.token_text.match(/^\s*<([a-z]+)/i);
+            if (!tag_check || !multi_parser.Utils.in_array(tag_check[1], unformatted)){
+                multi_parser.print_newline(false, multi_parser.output);
+            }
+            multi_parser.print_token(multi_parser.token_text);
+            multi_parser.current_mode = 'CONTENT';
+            break;
+          case 'TK_CONTENT':
+            if (multi_parser.token_text !== '') {
+              multi_parser.print_token(multi_parser.token_text);
+            }
+            multi_parser.current_mode = 'TAG';
+            break;
+          case 'TK_STYLE':
+          case 'TK_SCRIPT':
+            if (multi_parser.token_text !== '') {
+              multi_parser.output.push('\n');
+              var text = multi_parser.token_text,
+                  _beautifier,
+                  script_indent_level = 1;
+              if (multi_parser.token_type === 'TK_SCRIPT') {
+                _beautifier = typeof js_beautify === 'function' && js_beautify;
+              } else if (multi_parser.token_type === 'TK_STYLE') {
+                _beautifier = typeof css_beautify === 'function' && css_beautify;
+              }
+
+              if (options.indent_scripts === "keep") {
+                script_indent_level = 0;
+              } else if (options.indent_scripts === "separate") {
+                script_indent_level = -multi_parser.indent_level;
+              }
+
+              var indentation = multi_parser.get_full_indent(script_indent_level);
+              if (_beautifier) {
+                // call the Beautifier if avaliable
+                text = _beautifier(text.replace(/^\s*/, indentation), options);
+              } else {
+                // simply indent the string otherwise
+                var white = text.match(/^\s*/)[0];
+                var _level = white.match(/[^\n\r]*$/)[0].split(multi_parser.indent_string).length - 1;
+                var reindent = multi_parser.get_full_indent(script_indent_level -_level);
+                text = text.replace(/^\s*/, indentation)
+                       .replace(/\r\n|\r|\n/g, '\n' + reindent)
+                       .replace(/\s*$/, '');
+              }
+              if (text) {
+                multi_parser.print_token(text);
+                multi_parser.print_newline(true, multi_parser.output);
+              }
+            }
+            multi_parser.current_mode = 'TAG';
+            break;
+        }
+        multi_parser.last_token = multi_parser.token_type;
+        multi_parser.last_text = multi_parser.token_text;
+      }
+      return multi_parser.output.join('');
+    }
+
+    // If we're running a web page and don't have either of the above, add our one global
+    window.html_beautify = function(html_source, options) {
+        return style_html(html_source, options, window.js_beautify, window.css_beautify);
+    };
+
+}());
diff --git a/ruoyi-admin/src/main/resources/static/ajax/libs/blockUI/jquery.blockUI.js b/ruoyi-admin/src/main/resources/static/ajax/libs/blockUI/jquery.blockUI.js
new file mode 100644
index 0000000..552613d
--- /dev/null
+++ b/ruoyi-admin/src/main/resources/static/ajax/libs/blockUI/jquery.blockUI.js
@@ -0,0 +1,620 @@
+/*!
+ * jQuery blockUI plugin
+ * Version 2.70.0-2014.11.23
+ * Requires jQuery v1.7 or later
+ *
+ * Examples at: http://malsup.com/jquery/block/
+ * Copyright (c) 2007-2013 M. Alsup
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * Thanks to Amir-Hossein Sobhi for some excellent contributions!
+ */
+
+;(function() {
+/*jshint eqeqeq:false curly:false latedef:false */
+"use strict";
+
+	function setup($) {
+		$.fn._fadeIn = $.fn.fadeIn;
+
+		var noOp = $.noop || function() {};
+
+		// this bit is to ensure we don't call setExpression when we shouldn't (with extra muscle to handle
+		// confusing userAgent strings on Vista)
+		var msie = /MSIE/.test(navigator.userAgent);
+		var ie6  = /MSIE 6.0/.test(navigator.userAgent) && ! /MSIE 8.0/.test(navigator.userAgent);
+		var mode = document.documentMode || 0;
+		var setExpr = $.isFunction( document.createElement('div').style.setExpression );
+
+		// global $ methods for blocking/unblocking the entire page
+		$.blockUI   = function(opts) { install(window, opts); };
+		$.unblockUI = function(opts) { remove(window, opts); };
+
+		// convenience method for quick growl-like notifications  (http://www.google.com/search?q=growl)
+		$.growlUI = function(title, message, timeout, onClose) {
+			var $m = $('
'); + if (title) $m.append('

'+title+'

'); + if (message) $m.append('

'+message+'

'); + if (timeout === undefined) timeout = 3000; + + // Added by konapun: Set timeout to 30 seconds if this growl is moused over, like normal toast notifications + var callBlock = function(opts) { + opts = opts || {}; + + $.blockUI({ + message: $m, + fadeIn : typeof opts.fadeIn !== 'undefined' ? opts.fadeIn : 700, + fadeOut: typeof opts.fadeOut !== 'undefined' ? opts.fadeOut : 1000, + timeout: typeof opts.timeout !== 'undefined' ? opts.timeout : timeout, + centerY: false, + showOverlay: false, + onUnblock: onClose, + css: $.blockUI.defaults.growlCSS + }); + }; + + callBlock(); + var nonmousedOpacity = $m.css('opacity'); + $m.mouseover(function() { + callBlock({ + fadeIn: 0, + timeout: 30000 + }); + + var displayBlock = $('.blockMsg'); + displayBlock.stop(); // cancel fadeout if it has started + displayBlock.fadeTo(300, 1); // make it easier to read the message by removing transparency + }).mouseout(function() { + $('.blockMsg').fadeOut(1000); + }); + // End konapun additions + }; + + // plugin method for blocking element content + $.fn.block = function(opts) { + if ( this[0] === window ) { + $.blockUI( opts ); + return this; + } + var fullOpts = $.extend({}, $.blockUI.defaults, opts || {}); + this.each(function() { + var $el = $(this); + if (fullOpts.ignoreIfBlocked && $el.data('blockUI.isBlocked')) + return; + $el.unblock({ fadeOut: 0 }); + }); + + return this.each(function() { + if ($.css(this,'position') == 'static') { + this.style.position = 'relative'; + $(this).data('blockUI.static', true); + } + this.style.zoom = 1; // force 'hasLayout' in ie + install(this, opts); + }); + }; + + // plugin method for unblocking element content + $.fn.unblock = function(opts) { + if ( this[0] === window ) { + $.unblockUI( opts ); + return this; + } + return this.each(function() { + remove(this, opts); + }); + }; + + $.blockUI.version = 2.70; // 2nd generation blocking at no extra cost! + + // override these in your code to change the default behavior and style + $.blockUI.defaults = { + // message displayed when blocking (use null for no message) + message: '
加载中......
', + + title: null, // title string; only used when theme == true + draggable: true, // only used when theme == true (requires jquery-ui.js to be loaded) + + theme: false, // set to true to use with jQuery UI themes + + // styles for the message when blocking; if you wish to disable + // these and use an external stylesheet then do this in your code: + // $.blockUI.defaults.css = {}; + css: { + padding: 0, + margin: 0, + width: '30%', + top: '40%', + left: '35%', + textAlign: 'center', + color: '#000', + border: '0px', + backgroundColor:'transparent', + cursor: 'wait' + }, + + // minimal style set used when themes are used + themedCSS: { + width: '30%', + top: '40%', + left: '35%' + }, + + // styles for the overlay + overlayCSS: { + backgroundColor: '#000', + opacity: 0.6, + cursor: 'wait' + }, + + // style to replace wait cursor before unblocking to correct issue + // of lingering wait cursor + cursorReset: 'default', + + // styles applied when using $.growlUI + growlCSS: { + width: '350px', + top: '10px', + left: '', + right: '10px', + border: 'none', + padding: '5px', + opacity: 0.6, + cursor: 'default', + color: '#fff', + backgroundColor: '#000', + '-webkit-border-radius':'10px', + '-moz-border-radius': '10px', + 'border-radius': '10px' + }, + + // IE issues: 'about:blank' fails on HTTPS and javascript:false is s-l-o-w + // (hat tip to Jorge H. N. de Vasconcelos) + /*jshint scripturl:true */ + iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank', + + // force usage of iframe in non-IE browsers (handy for blocking applets) + forceIframe: false, + + // z-index for the blocking overlay + baseZ: 1000, + + // set these to true to have the message automatically centered + centerX: true, // <-- only effects element blocking (page block controlled via css above) + centerY: true, + + // allow body element to be stetched in ie6; this makes blocking look better + // on "short" pages. disable if you wish to prevent changes to the body height + allowBodyStretch: true, + + // enable if you want key and mouse events to be disabled for content that is blocked + bindEvents: true, + + // be default blockUI will supress tab navigation from leaving blocking content + // (if bindEvents is true) + constrainTabKey: true, + + // fadeIn time in millis; set to 0 to disable fadeIn on block + fadeIn: 200, + + // fadeOut time in millis; set to 0 to disable fadeOut on unblock + fadeOut: 400, + + // time in millis to wait before auto-unblocking; set to 0 to disable auto-unblock + timeout: 0, + + // disable if you don't want to show the overlay + showOverlay: true, + + // if true, focus will be placed in the first available input field when + // page blocking + focusInput: true, + + // elements that can receive focus + focusableElements: ':input:enabled:visible', + + // suppresses the use of overlay styles on FF/Linux (due to performance issues with opacity) + // no longer needed in 2012 + // applyPlatformOpacityRules: true, + + // callback method invoked when fadeIn has completed and blocking message is visible + onBlock: null, + + // callback method invoked when unblocking has completed; the callback is + // passed the element that has been unblocked (which is the window object for page + // blocks) and the options that were passed to the unblock call: + // onUnblock(element, options) + onUnblock: null, + + // callback method invoked when the overlay area is clicked. + // setting this will turn the cursor to a pointer, otherwise cursor defined in overlayCss will be used. + onOverlayClick: null, + + // don't ask; if you really must know: http://groups.google.com/group/jquery-en/browse_thread/thread/36640a8730503595/2f6a79a77a78e493#2f6a79a77a78e493 + quirksmodeOffsetHack: 4, + + // class name of the message block + blockMsgClass: 'blockMsg', + + // if it is already blocked, then ignore it (don't unblock and reblock) + ignoreIfBlocked: false + }; + + // private data and functions follow... + + var pageBlock = null; + var pageBlockEls = []; + + function install(el, opts) { + var css, themedCSS; + var full = (el == window); + var msg = (opts && opts.message !== undefined ? opts.message : undefined); + opts = $.extend({}, $.blockUI.defaults, opts || {}); + + if (opts.ignoreIfBlocked && $(el).data('blockUI.isBlocked')) + return; + + opts.overlayCSS = $.extend({}, $.blockUI.defaults.overlayCSS, opts.overlayCSS || {}); + css = $.extend({}, $.blockUI.defaults.css, opts.css || {}); + if (opts.onOverlayClick) + opts.overlayCSS.cursor = 'pointer'; + + themedCSS = $.extend({}, $.blockUI.defaults.themedCSS, opts.themedCSS || {}); + msg = msg === undefined ? opts.message : msg; + + // remove the current block (if there is one) + if (full && pageBlock) + remove(window, {fadeOut:0}); + + // if an existing element is being used as the blocking content then we capture + // its current place in the DOM (and current display style) so we can restore + // it when we unblock + if (msg && typeof msg != 'string' && (msg.parentNode || msg.jquery)) { + var node = msg.jquery ? msg[0] : msg; + var data = {}; + $(el).data('blockUI.history', data); + data.el = node; + data.parent = node.parentNode; + data.display = node.style.display; + data.position = node.style.position; + if (data.parent) + data.parent.removeChild(node); + } + + $(el).data('blockUI.onUnblock', opts.onUnblock); + var z = opts.baseZ; + + // blockUI uses 3 layers for blocking, for simplicity they are all used on every platform; + // layer1 is the iframe layer which is used to supress bleed through of underlying content + // layer2 is the overlay layer which has opacity and a wait cursor (by default) + // layer3 is the message content that is displayed while blocking + var lyr1, lyr2, lyr3, s; + if (msie || opts.forceIframe) + lyr1 = $(''); + else + lyr1 = $(''); + + if (opts.theme) + lyr2 = $(''); + else + lyr2 = $(''); + + if (opts.theme && full) { + s = ''; + } + else if (opts.theme) { + s = ''; + } + else if (full) { + s = ''; + } + else { + s = ''; + } + lyr3 = $(s); + + // if we have a message, style it + if (msg) { + if (opts.theme) { + lyr3.css(themedCSS); + lyr3.addClass('ui-widget-content'); + } + else + lyr3.css(css); + } + + // style the overlay + if (!opts.theme /*&& (!opts.applyPlatformOpacityRules)*/) + lyr2.css(opts.overlayCSS); + lyr2.css('position', full ? 'fixed' : 'absolute'); + + // make iframe layer transparent in IE + if (msie || opts.forceIframe) + lyr1.css('opacity',0.0); + + //$([lyr1[0],lyr2[0],lyr3[0]]).appendTo(full ? 'body' : el); + var layers = [lyr1,lyr2,lyr3], $par = full ? $('body') : $(el); + $.each(layers, function() { + this.appendTo($par); + }); + + if (opts.theme && opts.draggable && $.fn.draggable) { + lyr3.draggable({ + handle: '.ui-dialog-titlebar', + cancel: 'li' + }); + } + + // ie7 must use absolute positioning in quirks mode and to account for activex issues (when scrolling) + var expr = setExpr && (!$.support.boxModel || $('object,embed', full ? null : el).length > 0); + if (ie6 || expr) { + // give body 100% height + if (full && opts.allowBodyStretch && $.support.boxModel) + $('html,body').css('height','100%'); + + // fix ie6 issue when blocked element has a border width + if ((ie6 || !$.support.boxModel) && !full) { + var t = sz(el,'borderTopWidth'), l = sz(el,'borderLeftWidth'); + var fixT = t ? '(0 - '+t+')' : 0; + var fixL = l ? '(0 - '+l+')' : 0; + } + + // simulate fixed position + $.each(layers, function(i,o) { + var s = o[0].style; + s.position = 'absolute'; + if (i < 2) { + if (full) + s.setExpression('height','Math.max(document.body.scrollHeight, document.body.offsetHeight) - (jQuery.support.boxModel?0:'+opts.quirksmodeOffsetHack+') + "px"'); + else + s.setExpression('height','this.parentNode.offsetHeight + "px"'); + if (full) + s.setExpression('width','jQuery.support.boxModel && document.documentElement.clientWidth || document.body.clientWidth + "px"'); + else + s.setExpression('width','this.parentNode.offsetWidth + "px"'); + if (fixL) s.setExpression('left', fixL); + if (fixT) s.setExpression('top', fixT); + } + else if (opts.centerY) { + if (full) s.setExpression('top','(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"'); + s.marginTop = 0; + } + else if (!opts.centerY && full) { + var top = (opts.css && opts.css.top) ? parseInt(opts.css.top, 10) : 0; + var expression = '((document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + '+top+') + "px"'; + s.setExpression('top',expression); + } + }); + } + + // show the message + if (msg) { + if (opts.theme) + lyr3.find('.ui-widget-content').append(msg); + else + lyr3.append(msg); + if (msg.jquery || msg.nodeType) + $(msg).show(); + } + + if ((msie || opts.forceIframe) && opts.showOverlay) + lyr1.show(); // opacity is zero + if (opts.fadeIn) { + var cb = opts.onBlock ? opts.onBlock : noOp; + var cb1 = (opts.showOverlay && !msg) ? cb : noOp; + var cb2 = msg ? cb : noOp; + if (opts.showOverlay) + lyr2._fadeIn(opts.fadeIn, cb1); + if (msg) + lyr3._fadeIn(opts.fadeIn, cb2); + } + else { + if (opts.showOverlay) + lyr2.show(); + if (msg) + lyr3.show(); + if (opts.onBlock) + opts.onBlock.bind(lyr3)(); + } + + // bind key and mouse events + bind(1, el, opts); + + if (full) { + pageBlock = lyr3[0]; + pageBlockEls = $(opts.focusableElements,pageBlock); + if (opts.focusInput) + setTimeout(focus, 20); + } + else + center(lyr3[0], opts.centerX, opts.centerY); + + if (opts.timeout) { + // auto-unblock + var to = setTimeout(function() { + if (full) + $.unblockUI(opts); + else + $(el).unblock(opts); + }, opts.timeout); + $(el).data('blockUI.timeout', to); + } + } + + // remove the block + function remove(el, opts) { + var count; + var full = (el == window); + var $el = $(el); + var data = $el.data('blockUI.history'); + var to = $el.data('blockUI.timeout'); + if (to) { + clearTimeout(to); + $el.removeData('blockUI.timeout'); + } + opts = $.extend({}, $.blockUI.defaults, opts || {}); + bind(0, el, opts); // unbind events + + if (opts.onUnblock === null) { + opts.onUnblock = $el.data('blockUI.onUnblock'); + $el.removeData('blockUI.onUnblock'); + } + + var els; + if (full) // crazy selector to handle odd field errors in ie6/7 + els = $('body').children().filter('.blockUI').add('body > .blockUI'); + else + els = $el.find('>.blockUI'); + + // fix cursor issue + if ( opts.cursorReset ) { + if ( els.length > 1 ) + els[1].style.cursor = opts.cursorReset; + if ( els.length > 2 ) + els[2].style.cursor = opts.cursorReset; + } + + if (full) + pageBlock = pageBlockEls = null; + + if (opts.fadeOut) { + count = els.length; + els.stop().fadeOut(opts.fadeOut, function() { + if ( --count === 0) + reset(els,data,opts,el); + }); + } + else + reset(els, data, opts, el); + } + + // move blocking element back into the DOM where it started + function reset(els,data,opts,el) { + var $el = $(el); + if ( $el.data('blockUI.isBlocked') ) + return; + + els.each(function(i,o) { + // remove via DOM calls so we don't lose event handlers + if (this.parentNode) + this.parentNode.removeChild(this); + }); + + if (data && data.el) { + data.el.style.display = data.display; + data.el.style.position = data.position; + data.el.style.cursor = 'default'; // #59 + if (data.parent) + data.parent.appendChild(data.el); + $el.removeData('blockUI.history'); + } + + if ($el.data('blockUI.static')) { + $el.css('position', 'static'); // #22 + } + + if (typeof opts.onUnblock == 'function') + opts.onUnblock(el,opts); + + // fix issue in Safari 6 where block artifacts remain until reflow + var body = $(document.body), w = body.width(), cssW = body[0].style.width; + body.width(w-1).width(w); + body[0].style.width = cssW; + } + + // bind/unbind the handler + function bind(b, el, opts) { + var full = el == window, $el = $(el); + + // don't bother unbinding if there is nothing to unbind + if (!b && (full && !pageBlock || !full && !$el.data('blockUI.isBlocked'))) + return; + + $el.data('blockUI.isBlocked', b); + + // don't bind events when overlay is not in use or if bindEvents is false + if (!full || !opts.bindEvents || (b && !opts.showOverlay)) + return; + + // bind anchors and inputs for mouse and key events + var events = 'mousedown mouseup keydown keypress keyup touchstart touchend touchmove'; + if (b) + $(document).bind(events, opts, handler); + else + $(document).unbind(events, handler); + + // former impl... + // var $e = $('a,:input'); + // b ? $e.bind(events, opts, handler) : $e.unbind(events, handler); + } + + // event handler to suppress keyboard/mouse events when blocking + function handler(e) { + // allow tab navigation (conditionally) + if (e.type === 'keydown' && e.keyCode && e.keyCode == 9) { + if (pageBlock && e.data.constrainTabKey) { + var els = pageBlockEls; + var fwd = !e.shiftKey && e.target === els[els.length-1]; + var back = e.shiftKey && e.target === els[0]; + if (fwd || back) { + setTimeout(function(){focus(back);},10); + return false; + } + } + } + var opts = e.data; + var target = $(e.target); + if (target.hasClass('blockOverlay') && opts.onOverlayClick) + opts.onOverlayClick(e); + + // allow events within the message content + if (target.parents('div.' + opts.blockMsgClass).length > 0) + return true; + + // allow events for content that is not being blocked + return target.parents().children().filter('div.blockUI').length === 0; + } + + function focus(back) { + if (!pageBlockEls) + return; + var e = pageBlockEls[back===true ? pageBlockEls.length-1 : 0]; + if (e) + e.focus(); + } + + function center(el, x, y) { + var p = el.parentNode, s = el.style; + var l = ((p.offsetWidth - el.offsetWidth)/2) - sz(p,'borderLeftWidth'); + var t = ((p.offsetHeight - el.offsetHeight)/2) - sz(p,'borderTopWidth'); + if (x) s.left = l > 0 ? (l+'px') : '0'; + if (y) s.top = t > 0 ? (t+'px') : '0'; + } + + function sz(el, p) { + return parseInt($.css(el,p),10)||0; + } + + } + + + /*global define:true */ + if (typeof define === 'function' && define.amd && define.amd.jQuery) { + define(['jquery'], setup); + } else { + setup(jQuery); + } + +})(); \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.css b/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.css new file mode 100644 index 0000000..6334b65 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.css @@ -0,0 +1,688 @@ +/*! + * bootstrap-fileinput v5.5.2 + * http://plugins.krajee.com/file-input + * + * Krajee default styling for bootstrap-fileinput. + * + * Author: Kartik Visweswaran + * Copyright: 2014 - 2022, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD-3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */ + +.file-loading input[type=file], +input[type=file].file-loading { + width: 0; + height: 0; +} + +.file-no-browse { + position: absolute; + left: 50%; + bottom: 20%; + width: 1px; + height: 1px; + font-size: 0; + opacity: 0; + border: none; + background: none; + outline: none; + box-shadow: none; +} + +.kv-hidden, +.file-caption-icon, +.file-zoom-dialog .modal-header:before, +.file-zoom-dialog .modal-header:after, +.file-input-new .file-preview, +.file-input-new .close, +.file-input-new .glyphicon-file, +.file-input-new .fileinput-remove-button, +.file-input-new .fileinput-upload-button, +.file-input-new .no-browse .input-group-btn, +.file-input-ajax-new .fileinput-remove-button, +.file-input-ajax-new .fileinput-upload-button, +.file-input-ajax-new .no-browse .input-group-btn, +.hide-content .kv-file-content, +.is-locked .fileinput-upload-button, +.is-locked .fileinput-remove-button { + display: none; +} + +.file-caption .input-group { + align-items: center; +} + +.btn-file input[type=file], +.file-caption-icon, +.file-preview .fileinput-remove, +.krajee-default .file-thumb-progress, +.file-zoom-dialog .btn-navigate, +.file-zoom-dialog .floating-buttons { + position: absolute; +} + +.file-caption-icon .kv-caption-icon { + line-height: inherit; +} + +.file-input, +.file-loading:before, +.btn-file, +.file-caption, +.file-preview, +.krajee-default.file-preview-frame, +.krajee-default .file-thumbnail-footer, +.file-zoom-dialog .modal-dialog { + position: relative; +} + +.file-error-message pre, +.file-error-message ul, +.krajee-default .file-actions, +.krajee-default .file-other-error { + text-align: left; +} + +.file-error-message pre, +.file-error-message ul { + margin: 0; +} + +.krajee-default .file-drag-handle, +.krajee-default .file-upload-indicator { + float: left; + margin-top: 10px; + width: 16px; + height: 16px; +} + +.file-thumb-progress .progress, +.file-thumb-progress .progress-bar { + font-family: Verdana, Helvetica, sans-serif; + font-size: 0.7rem; +} + +.krajee-default .file-thumb-progress .progress, +.kv-upload-progress .progress { + background-color: #ccc; +} + +.krajee-default .file-caption-info, +.krajee-default .file-size-info { + display: block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + width: 160px; + height: 15px; + margin: auto; +} + +.file-zoom-content > .file-object.type-video, +.file-zoom-content > .file-object.type-flash, +.file-zoom-content > .file-object.type-image { + max-width: 100%; + max-height: 100%; + width: auto; +} + +.file-zoom-content > .file-object.type-video, +.file-zoom-content > .file-object.type-flash { + height: 100%; +} + +.file-zoom-content > .file-object.type-pdf, +.file-zoom-content > .file-object.type-html, +.file-zoom-content > .file-object.type-text, +.file-zoom-content > .file-object.type-default { + width: 100%; +} + +.file-loading:before { + content: " Loading..."; + display: inline-block; + padding-left: 20px; + line-height: 16px; + font-size: 13px; + font-variant: small-caps; + color: #999; + background: transparent url(loading.gif) top left no-repeat; +} + +.file-object { + margin: 0 0 -5px 0; + padding: 0; +} + +.btn-file { + overflow: hidden; +} + +.btn-file input[type=file] { + top: 0; + left: 0; + min-width: 100%; + min-height: 100%; + text-align: right; + opacity: 0; + background: none repeat scroll 0 0 transparent; + cursor: inherit; + display: block; +} + +.btn-file ::-ms-browse { + font-size: 10000px; + width: 100%; + height: 100%; +} + +.file-caption.icon-visible .file-caption-icon { + display: inline-block; +} + +.file-caption.icon-visible .file-caption-name { + padding-left: 25px; +} + +.file-caption.icon-visible > .input-group-lg .file-caption-name { + padding-left: 30px; +} + +.file-caption.icon-visible > .input-group-sm .file-caption-name { + padding-left: 22px; +} + +.file-caption-name:not(.file-caption-disabled) { + background-color: transparent; +} + +.file-caption-name.file-processing { + font-style: italic; + border-color: #bbb; + opacity: 0.5; +} + +.file-caption-icon { + padding: 7px 5px; + left: 4px; +} + +.input-group-lg .file-caption-icon { + font-size: 1.25rem; +} + +.input-group-sm .file-caption-icon { + font-size: 0.875rem; + padding: 0.25rem; +} + +.file-error-message { + color: #a94442; + background-color: #f2dede; + margin: 5px; + border: 1px solid #ebccd1; + border-radius: 4px; + padding: 15px; +} + +.file-error-message pre { + margin: 5px 0; +} + +.file-caption-disabled { + background-color: #eee; + cursor: not-allowed; + opacity: 1; +} + +.file-preview { + border-radius: 5px; + border: 1px solid #ddd; + padding: 8px; + width: 100%; + margin-bottom: 5px; +} + +.file-preview .btn-xs { + padding: 1px 5px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} + +.file-preview .fileinput-remove { + top: 1px; + right: 1px; + line-height: 10px; +} + +.file-preview .clickable { + cursor: pointer; +} + +.file-preview-image { + font: 40px Impact, Charcoal, sans-serif; + color: #008000; + width: auto; + height: auto; + max-width: 100%; + max-height: 100%; +} + +.krajee-default.file-preview-frame { + margin: 8px; + border: 1px solid rgba(0, 0, 0, 0.2); + box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.2); + padding: 6px; + float: left; + text-align: center; + +} + +.krajee-default.file-preview-frame .kv-file-content { + width: 213px; + height: 160px; +} + +.krajee-default.file-preview-frame .kv-file-content.kv-pdf-rendered { + width: 400px; +} + +.krajee-default.file-preview-frame[data-template="audio"] .kv-file-content { + width: 240px; + height: 55px; +} + +.krajee-default.file-preview-frame .file-thumbnail-footer { + height: 70px; +} + +.krajee-default.file-preview-frame:not(.file-preview-error):hover { + border: 1px solid rgba(0, 0, 0, 0.3); + box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.4); +} + +.krajee-default .file-preview-text { + color: #428bca; + border: 1px solid #ddd; + outline: none; + resize: none; +} + +.krajee-default .file-preview-html { + border: 1px solid #ddd; +} + +.krajee-default .file-other-icon { + font-size: 6em; + line-height: 1; +} + +.krajee-default .file-footer-buttons { + float: right; +} + +.krajee-default .file-footer-caption { + display: block; + text-align: center; + padding-top: 4px; + font-size: 11px; + color: #999; + margin-bottom: 30px; +} + +.file-upload-stats { + font-size: 10px; + text-align: center; + width: 100%; +} + +.kv-upload-progress .file-upload-stats { + font-size: 12px; + margin: -10px 0 5px; +} + +.krajee-default .file-preview-error { + opacity: 0.65; + box-shadow: none; +} + +.krajee-default .file-thumb-progress { + top: 37px; + left: 0; + right: 0; +} + +.krajee-default.kvsortable-ghost { + background: #e1edf7; + border: 2px solid #a1abff; +} + +.krajee-default .file-preview-other:hover { + opacity: 0.8; +} + +.krajee-default .file-preview-frame:not(.file-preview-error) .file-footer-caption:hover { + color: #000; +} + +.kv-upload-progress .progress { + height: 20px; + margin: 10px 0; + overflow: hidden; +} + +.kv-upload-progress .progress-bar { + height: 20px; + font-family: Verdana, Helvetica, sans-serif; +} + + +/*noinspection CssOverwrittenProperties*/ + +.file-zoom-dialog .file-other-icon { + font-size: 22em; + font-size: 50vmin; +} + +.file-zoom-dialog .modal-dialog { + width: auto; +} + +.file-zoom-dialog .modal-header { + display: flex; + align-items: center; + justify-content: space-between; +} + +.file-zoom-dialog .btn-navigate { + margin: 0 0.1rem; + padding: 0; + font-size: 1.2rem; + width: 2.4rem; + height: 2.4rem; + top: 50%; + border-radius: 50%; + text-align: center; +} + +.btn-navigate * { + width: auto; +} + +.file-zoom-dialog .floating-buttons { + top: 5px; + right: 10px; +} + +.file-zoom-dialog .btn-kv-prev { + left: 0; +} + +.file-zoom-dialog .btn-kv-next { + right: 0; +} + +.file-zoom-dialog .kv-zoom-header { + padding: 0.5rem; +} + +.file-zoom-dialog .kv-zoom-body { + padding: 0.25rem; +} + +.file-zoom-dialog .kv-zoom-description { + position: absolute; + opacity: 0.8; + font-size: 0.8rem; + background-color: #1a1a1a; + padding: 1rem; + text-align: center; + border-radius: 0.5rem; + color: #fff; + left: 15%; + right: 15%; + bottom: 15%; +} + +.file-zoom-dialog .kv-desc-hide { + float: right; + color: #fff; + padding: 0 0.1rem; + background: none; + border: none; +} + +.file-zoom-dialog .kv-desc-hide:hover { + opacity: 0.7; +} + +.file-zoom-dialog .kv-desc-hide:focus { + opacity: 0.9; +} + +.file-input-new .no-browse .form-control { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} + +.file-input-ajax-new .no-browse .form-control { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} + +.file-caption { + width: 100%; + position: relative; +} + +.file-thumb-loading { + background: transparent url(loading.gif) no-repeat scroll center center content-box !important; +} + +.file-drop-zone { + border: 1px dashed #aaa; + min-height: 260px; + border-radius: 4px; + text-align: center; + vertical-align: middle; + margin: 12px 15px 12px 12px; + padding: 5px; +} + +.file-drop-zone.clickable:hover { + border: 2px dashed #999; +} + +.file-drop-zone.clickable:focus { + border: 2px solid #5acde2; +} + +.file-drop-zone .file-preview-thumbnails { + cursor: default; +} + +.file-drop-zone-title { + color: #aaa; + font-size: 1.6em; + text-align: center; + padding: 85px 10px; + cursor: default; +} + +.file-highlighted { + border: 2px dashed #999 !important; + background-color: #eee; +} + +.file-uploading { + background: url(loading-sm.gif) no-repeat center bottom 10px; + opacity: 0.65; +} + +.file-zoom-fullscreen .modal-dialog { + min-width: 100%; + margin: 0; +} + +.file-zoom-fullscreen .modal-content { + border-radius: 0; + box-shadow: none; + min-height: 100vh; +} + +.file-zoom-fullscreen .kv-zoom-body { + overflow-y: auto; +} + +.floating-buttons { + z-index: 3000; +} + +.floating-buttons .btn-kv { + margin-left: 3px; + z-index: 3000; +} + +.kv-zoom-actions { + min-width: 140px; +} + +.kv-zoom-actions .btn-kv { + margin-left: 3px; +} + +.file-zoom-content { + text-align: center; + white-space: nowrap; + min-height: 300px; +} + +.file-zoom-content:hover { + background: transparent; +} + +.file-zoom-content .file-preview-image { + max-height: 100%; +} + +.file-zoom-content .file-preview-video { + max-height: 100%; +} + +.file-zoom-content > .file-object.type-image { + height: auto; + min-height: inherit; +} + +.file-zoom-content > .file-object.type-audio { + width: auto; + height: 30px; +} + +@media (min-width: 576px) { + .file-zoom-dialog .modal-dialog { + max-width: 500px; + } +} + +@media (min-width: 992px) { + .file-zoom-dialog .modal-lg { + max-width: 800px; + } +} + +@media (max-width: 767px) { + .file-preview-thumbnails { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + } + + .file-zoom-dialog .modal-header { + flex-direction: column; + } +} + +@media (max-width: 350px) { + .krajee-default.file-preview-frame:not([data-template="audio"]) .kv-file-content { + width: 160px; + } +} + +@media (max-width: 420px) { + .krajee-default.file-preview-frame .kv-file-content.kv-pdf-rendered { + width: 100%; + } +} + +.file-loading[dir=rtl]:before { + background: transparent url(loading.gif) top right no-repeat; + padding-left: 0; + padding-right: 20px; +} + +.clickable .file-drop-zone-title { + cursor: pointer; +} + +.file-sortable .file-drag-handle:hover { + opacity: 0.7; +} + +.file-sortable .file-drag-handle { + cursor: grab; + opacity: 1; +} + +.file-grabbing, +.file-grabbing * { + cursor: not-allowed !important; +} + +.file-grabbing .file-preview-thumbnails * { + cursor: grabbing !important; +} + +.file-preview-frame.sortable-chosen { + background-color: #d9edf7; + border-color: #17a2b8; + box-shadow: none !important; +} + +.file-preview .kv-zoom-cache { + display: none; +} + +.file-preview-other-frame, .file-preview-object, .kv-file-content, .kv-zoom-body { + display: flex; + align-items: center; + justify-content: center; +} + +.btn-kv-rotate, +.kv-file-rotate { + display: none; +} + +.rotatable:not(.hide-rotate) .btn-kv-rotate, +.rotatable:not(.hide-rotate) .kv-file-rotate { + display: inline-block; +} + +.rotatable .file-zoom-detail, +.rotatable .kv-file-content, +.rotatable .kv-file-content > :first-child { + transform-origin: center center; +} + +.rotate-animate { + transition: transform 0.3s ease; +} + +.kv-overflow-hidden { + overflow: hidden; +} \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.js b/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.js new file mode 100644 index 0000000..75466c0 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.js @@ -0,0 +1,6681 @@ +/*! + * bootstrap-fileinput v5.5.2 + * http://plugins.krajee.com/file-input + * + * Author: Kartik Visweswaran + * Copyright: 2014 - 2022, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD-3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */ +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + define(['jquery'], factory); + } else if (typeof module === 'object' && typeof module.exports === 'object') { + factory(require('jquery')); + } else { + factory(window.jQuery); + } +}(function ($) { + 'use strict'; + + $.fn.fileinputLocales = {}; + $.fn.fileinputThemes = {}; + + if (!$.fn.fileinputBsVersion) { + $.fn.fileinputBsVersion = (window.bootstrap && window.bootstrap.Alert && window.bootstrap.Alert.VERSION) || + (window.Alert && window.Alert.VERSION) || '3.x.x'; + } + + String.prototype.setTokens = function (replacePairs) { + var str = this.toString(), key, re; + for (key in replacePairs) { + if (replacePairs.hasOwnProperty(key)) { + re = new RegExp('\{' + key + '\}', 'g'); + str = str.replace(re, replacePairs[key]); + } + } + return str; + }; + + if (!Array.prototype.flatMap) { // polyfill flatMap + Array.prototype.flatMap = function (lambda) { + return [].concat(this.map(lambda)); + }; + } + + var $h, FileInput; + + // fileinput helper object for all global variables and internal helper methods + $h = { + FRAMES: '.kv-preview-thumb', + SORT_CSS: 'file-sortable', + INIT_FLAG: 'init-', + SCRIPT_SRC: document && document.currentScript && document.currentScript.src || null, + OBJECT_PARAMS: '\n' + + '\n' + + '\n' + + '\n' + + '\n' + + '\n', + DEFAULT_PREVIEW: '
\n' + + '{previewFileIcon}\n' + + '
', + MODAL_ID: 'kvFileinputModal', + MODAL_EVENTS: ['show', 'shown', 'hide', 'hidden', 'loaded'], + logMessages: { + ajaxError: '{status}: {error}. Error Details: {text}.', + badDroppedFiles: 'Error scanning dropped files!', + badExifParser: 'Error loading the piexif.js library. {details}', + badInputType: 'The input "type" must be set to "file" for initializing the "bootstrap-fileinput" plugin.', + exifWarning: 'To avoid this warning, either set "autoOrientImage" to "false" OR ensure you have loaded ' + + 'the "piexif.js" library correctly on your page before the "fileinput.js" script.', + invalidChunkSize: 'Invalid upload chunk size: "{chunkSize}". Resumable uploads are disabled.', + invalidThumb: 'Invalid thumb frame with id: "{id}".', + noResumableSupport: 'The browser does not support resumable or chunk uploads.', + noUploadUrl: 'The "uploadUrl" is not set. Ajax uploads and resumable uploads have been disabled.', + retryStatus: 'Retrying upload for chunk # {chunk} for {filename}... retry # {retry}.', + chunkQueueError: 'Could not push task to ajax pool for chunk index # {index}.', + resumableMaxRetriesReached: 'Maximum resumable ajax retries ({n}) reached.', + resumableRetryError: 'Could not retry the resumable request (try # {n})... aborting.', + resumableAborting: 'Aborting / cancelling the resumable request.', + resumableRequestError: 'Error processing resumable request. {msg}' + + }, + objUrl: window.URL || window.webkitURL, + getZoomPlaceholder: function () { // used to prevent 404 errors in URL parsing + var src = $h.SCRIPT_SRC, srcPath, zoomVar = '?kvTemp__2873389129__='; + if (!src) { + return zoomVar; + } + srcPath = src.substring(0, src.lastIndexOf("/")); + return srcPath.substring(0, srcPath.lastIndexOf("/") + 1) + 'img/loading.gif' + zoomVar; + }, + isBs: function (ver) { + var chk = $.trim(($.fn.fileinputBsVersion || '') + ''); + ver = parseInt(ver, 10); + if (!chk) { + return ver === 4; + } + return ver === parseInt(chk.charAt(0), 10); + + }, + defaultButtonCss: function (fill) { + return 'btn-default btn-' + (fill ? '' : 'outline-') + 'secondary'; + }, + now: function () { + return new Date().getTime(); + }, + round: function (num) { + num = parseFloat(num); + return isNaN(num) ? 0 : Math.floor(Math.round(num)); + }, + getArray: function (obj) { + var i, arr = [], len = obj && obj.length || 0; + for (i = 0; i < len; i++) { + arr.push(obj[i]); + } + return arr; + }, + getFileRelativePath: function (file) { + /** @namespace file.relativePath */ + /** @namespace file.webkitRelativePath */ + return String(file.newPath || file.relativePath || file.webkitRelativePath || $h.getFileName(file) || null); + + }, + getFileId: function (file, generateFileId) { + var relativePath = $h.getFileRelativePath(file); + if (typeof generateFileId === 'function') { + return generateFileId(file); + } + if (!file) { + return null; + } + if (!relativePath) { + return null; + } + return (file.size + '_' + encodeURIComponent(relativePath).replace(/%/g, '_')); + }, + getFrameSelector: function (id, selector) { + selector = selector || ''; + return '[id="' + id + '"]' + selector; + }, + getZoomSelector: function (id, selector) { + return $h.getFrameSelector('zoom-' + id, selector); + }, + getFrameElement: function ($element, id, selector) { + return $element.find($h.getFrameSelector(id, selector)); + }, + getZoomElement: function ($element, id, selector) { + return $element.find($h.getZoomSelector(id, selector)); + }, + getElapsed: function (seconds) { + var delta = seconds, out = '', result = {}, structure = { + year: 31536000, + month: 2592000, + week: 604800, // uncomment row to ignore + day: 86400, // feel free to add your own row + hour: 3600, + minute: 60, + second: 1 + }; + $h.getObjectKeys(structure).forEach(function (key) { + result[key] = Math.floor(delta / structure[key]); + delta -= result[key] * structure[key]; + }); + $.each(result, function (key, value) { + if (value > 0) { + out += (out ? ' ' : '') + value + key.substring(0, 1); + } + }); + return out; + }, + debounce: function (func, delay) { + var inDebounce; + return function () { + var args = arguments, context = this; + clearTimeout(inDebounce); + inDebounce = setTimeout(function () { + func.apply(context, args); + }, delay); + }; + }, + stopEvent: function (e) { + e.stopPropagation(); + e.preventDefault(); + }, + getFileName: function (file) { + /** @namespace file.fileName */ + return file ? (file.fileName || file.name || '') : ''; // some confusion in different versions of Firefox + }, + createObjectURL: function (data) { + if ($h.objUrl && $h.objUrl.createObjectURL && data) { + return $h.objUrl.createObjectURL(data); + } + return ''; + }, + revokeObjectURL: function (data) { + if ($h.objUrl && $h.objUrl.revokeObjectURL && data) { + $h.objUrl.revokeObjectURL(data); + } + }, + compare: function (input, str, exact) { + return input !== undefined && (exact ? input === str : input.match(str)); + }, + isIE: function (ver) { + var div, status; + // check for IE versions < 11 + if (navigator.appName !== 'Microsoft Internet Explorer') { + return false; + } + if (ver === 10) { + return new RegExp('msie\\s' + ver, 'i').test(navigator.userAgent); + } + div = document.createElement('div'); + div.innerHTML = ''; + status = div.getElementsByTagName('i').length; + document.body.appendChild(div); + div.parentNode.removeChild(div); + return status; + }, + canOrientImage: function ($el) { + var $img = $(document.createElement('img')).css({width: '1px', height: '1px'}).insertAfter($el), + flag = $img.css('image-orientation'); + $img.remove(); + return !!flag; + }, + canAssignFilesToInput: function () { + var input = document.createElement('input'); + try { + input.type = 'file'; + input.files = null; + return true; + } catch (err) { + return false; + } + }, + getDragDropFolders: function (items) { + var i, item, len = items ? items.length : 0, folders = 0; + if (len > 0 && items[0].webkitGetAsEntry()) { + for (i = 0; i < len; i++) { + item = items[i].webkitGetAsEntry(); + if (item && item.isDirectory) { + folders++; + } + } + } + return folders; + }, + initModal: function ($modal) { + var $body = $('body'); + if ($body.length) { + $modal.appendTo($body); + } + }, + isFunction: function (v) { + return typeof v === 'function'; + }, + isEmpty: function (value, trim) { + if (value === undefined || value === null || value === '') { + return true; + } + if ($h.isString(value) && trim) { + return $.trim(value) === ''; + } + if ($h.isArray(value)) { + return value.length === 0; + } + if ($.isPlainObject(value) && $.isEmptyObject(value)) { + return true; + } + return false; + }, + isArray: function (a) { + return Array.isArray(a) || Object.prototype.toString.call(a) === '[object Array]'; + }, + isString: function (a) { + return Object.prototype.toString.call(a) === '[object String]'; + }, + ifSet: function (needle, haystack, def) { + def = def || ''; + return (haystack && typeof haystack === 'object' && needle in haystack) ? haystack[needle] : def; + }, + cleanArray: function (arr) { + if (!(arr instanceof Array)) { + arr = []; + } + return arr.filter(function (e) { + return (e !== undefined && e !== null); + }); + }, + spliceArray: function (arr, index, reverseOrder) { + var i, j = 0, out = [], newArr; + if (!(arr instanceof Array)) { + return []; + } + newArr = $.extend(true, [], arr); + if (reverseOrder) { + newArr.reverse(); + } + for (i = 0; i < newArr.length; i++) { + if (i !== index) { + out[j] = newArr[i]; + j++; + } + } + if (reverseOrder) { + out.reverse(); + } + return out; + }, + getNum: function (num, def) { + def = def || 0; + if (typeof num === 'number') { + return num; + } + if (typeof num === 'string') { + num = parseFloat(num); + } + return isNaN(num) ? def : num; + }, + hasFileAPISupport: function () { + return !!(window.File && window.FileReader); + }, + hasDragDropSupport: function () { + var div = document.createElement('div'); + /** @namespace div.draggable */ + /** @namespace div.ondragstart */ + /** @namespace div.ondrop */ + return !$h.isIE(9) && + (div.draggable !== undefined || (div.ondragstart !== undefined && div.ondrop !== undefined)); + }, + hasFileUploadSupport: function () { + return $h.hasFileAPISupport() && window.FormData; + }, + hasBlobSupport: function () { + try { + return !!window.Blob && Boolean(new Blob()); + } catch (e) { + return false; + } + }, + hasArrayBufferViewSupport: function () { + try { + return new Blob([new Uint8Array(100)]).size === 100; + } catch (e) { + return false; + } + }, + hasResumableUploadSupport: function () { + /** @namespace Blob.prototype.webkitSlice */ + /** @namespace Blob.prototype.mozSlice */ + return $h.hasFileUploadSupport() && $h.hasBlobSupport() && $h.hasArrayBufferViewSupport() && + (!!Blob.prototype.webkitSlice || !!Blob.prototype.mozSlice || !!Blob.prototype.slice || false); + }, + dataURI2Blob: function (dataURI) { + var BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || + window.MSBlobBuilder, canBlob = $h.hasBlobSupport(), byteStr, arrayBuffer, intArray, i, mimeStr, bb, + canProceed = (canBlob || BlobBuilder) && window.atob && window.ArrayBuffer && window.Uint8Array; + if (!canProceed) { + return null; + } + if (dataURI.split(',')[0].indexOf('base64') >= 0) { + byteStr = atob(dataURI.split(',')[1]); + } else { + byteStr = decodeURIComponent(dataURI.split(',')[1]); + } + arrayBuffer = new ArrayBuffer(byteStr.length); + intArray = new Uint8Array(arrayBuffer); + for (i = 0; i < byteStr.length; i += 1) { + intArray[i] = byteStr.charCodeAt(i); + } + mimeStr = dataURI.split(',')[0].split(':')[1].split(';')[0]; + if (canBlob) { + return new Blob([$h.hasArrayBufferViewSupport() ? intArray : arrayBuffer], {type: mimeStr}); + } + bb = new BlobBuilder(); + bb.append(arrayBuffer); + return bb.getBlob(mimeStr); + }, + arrayBuffer2String: function (buffer) { + if (window.TextDecoder) { + return new TextDecoder('utf-8').decode(buffer); + } + var array = Array.prototype.slice.apply(new Uint8Array(buffer)), out = '', i = 0, len, c, char2, char3; + len = array.length; + while (i < len) { + c = array[i++]; + switch (c >> 4) { // jshint ignore:line + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + // 0xxxxxxx + out += String.fromCharCode(c); + break; + case 12: + case 13: + // 110x xxxx 10xx xxxx + char2 = array[i++]; + out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F)); // jshint ignore:line + break; + case 14: + // 1110 xxxx 10xx xxxx 10xx xxxx + char2 = array[i++]; + char3 = array[i++]; + out += String.fromCharCode(((c & 0x0F) << 12) | // jshint ignore:line + ((char2 & 0x3F) << 6) | // jshint ignore:line + ((char3 & 0x3F) << 0)); // jshint ignore:line + break; + } + } + return out; + }, + isHtml: function (str) { + var a = document.createElement('div'); + a.innerHTML = str; + for (var c = a.childNodes, i = c.length; i--;) { + if (c[i].nodeType === 1) { + return true; + } + } + return false; + }, + isPdf: function (str) { + if ($h.isEmpty(str)) { + return false; + } + str = str.toString().trim().replace(/\n/g, ' '); + if (str.length === 0) { + return false; + } + }, + isSvg: function (str) { + if ($h.isEmpty(str)) { + return false; + } + str = str.toString().trim().replace(/\n/g, ' '); + if (str.length === 0) { + return false; + } + return str.match(/^\s*<\?xml/i) && (str.match(/' + str + '')); + }, + uniqId: function () { + return (new Date().getTime() + Math.floor(Math.random() * Math.pow(10, 15))).toString(36); + }, + cspBuffer: { + CSP_ATTRIB: 'data-csp-01928735', // a randomly named temporary attribute to store the CSP elem id + domElementsStyles: {}, + stash: function (htmlString) { + var self = this, outerDom = $.parseHTML('
' + htmlString + '
'), $el = $(outerDom); + $el.find('[style]').each(function (key, elem) { + var $elem = $(elem), styleDeclaration = $elem[0].style, id = $h.uniqId(), styles = {}; + if (styleDeclaration && styleDeclaration.length) { + $(styleDeclaration).each(function () { + styles[this] = styleDeclaration[this]; + }); + self.domElementsStyles[id] = styles; + $elem.removeAttr('style').attr(self.CSP_ATTRIB, id); + } + }); + $el.filter('*').removeAttr('style'); // make sure all style attr are removed + var values = Object.values ? Object.values(outerDom) : Object.keys(outerDom).map(function (itm) { + return outerDom[itm]; + }); + return values.flatMap(function (elem) { + return elem.innerHTML; + }).join(''); + }, + apply: function (domElement) { + var self = this, $el = $(domElement); + $el.find('[' + self.CSP_ATTRIB + ']').each(function (key, elem) { + var $elem = $(elem), id = $elem.attr(self.CSP_ATTRIB), styles = self.domElementsStyles[id]; + if (styles) { + $elem.css(styles); + } + $elem.removeAttr(self.CSP_ATTRIB); + }); + self.domElementsStyles = {}; + } + }, + setHtml: function ($elem, htmlString) { + var buf = $h.cspBuffer; + $elem.html(buf.stash(htmlString)); + buf.apply($elem); + return $elem; + }, + htmlEncode: function (str, undefVal) { + if (str === undefined) { + return undefVal || null; + } + return str.replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + }, + replaceTags: function (str, tags) { + var out = str; + if (!tags) { + return out; + } + $.each(tags, function (key, value) { + if (typeof value === 'function') { + value = value(); + } + out = out.split(key).join(value); + }); + return out; + }, + cleanMemory: function ($thumb) { + var data = $thumb.is('img') ? $thumb.attr('src') : $thumb.find('source').attr('src'); + $h.revokeObjectURL(data); + }, + findFileName: function (filePath) { + var sepIndex = filePath.lastIndexOf('/'); + if (sepIndex === -1) { + sepIndex = filePath.lastIndexOf('\\'); + } + return filePath.split(filePath.substring(sepIndex, sepIndex + 1)).pop(); + }, + checkFullScreen: function () { + return document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || + document.msFullscreenElement; + }, + toggleFullScreen: function (maximize) { + var doc = document, de = doc.documentElement, isFullScreen = $h.checkFullScreen(); + if (de && maximize && !isFullScreen) { + if (de.requestFullscreen) { + de.requestFullscreen(); + } else { + if (de.msRequestFullscreen) { + de.msRequestFullscreen(); + } else { + if (de.mozRequestFullScreen) { + de.mozRequestFullScreen(); + } else { + if (de.webkitRequestFullscreen) { + de.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); + } + } + } + } + } else { + if (isFullScreen) { + if (doc.exitFullscreen) { + doc.exitFullscreen(); + } else { + if (doc.msExitFullscreen) { + doc.msExitFullscreen(); + } else { + if (doc.mozCancelFullScreen) { + doc.mozCancelFullScreen(); + } else { + if (doc.webkitExitFullscreen) { + doc.webkitExitFullscreen(); + } + } + } + } + } + } + }, + moveArray: function (arr, oldIndex, newIndex, reverseOrder) { + var newArr = $.extend(true, [], arr); + if (reverseOrder) { + newArr.reverse(); + } + if (newIndex >= newArr.length) { + var k = newIndex - newArr.length; + while ((k--) + 1) { + newArr.push(undefined); + } + } + newArr.splice(newIndex, 0, newArr.splice(oldIndex, 1)[0]); + if (reverseOrder) { + newArr.reverse(); + } + return newArr; + }, + closeButton: function (css) { + css = ($h.isBs(5) ? 'btn-close' : 'close') + (css ? ' ' + css : ''); + return ''; + }, + getRotation: function (value) { + switch (value) { + case 2: + return 'rotateY(180deg)'; + case 3: + return 'rotate(180deg)'; + case 4: + return 'rotate(180deg) rotateY(180deg)'; + case 5: + return 'rotate(270deg) rotateY(180deg)'; + case 6: + return 'rotate(90deg)'; + case 7: + return 'rotate(90deg) rotateY(180deg)'; + case 8: + return 'rotate(270deg)'; + default: + return ''; + } + }, + setTransform: function (el, val) { + if (!el) { + return; + } + el.style.transform = val; + el.style.webkitTransform = val; + el.style['-moz-transform'] = val; + el.style['-ms-transform'] = val; + el.style['-o-transform'] = val; + }, + getObjectKeys: function (obj) { + var keys = []; + if (obj) { + $.each(obj, function (key) { + keys.push(key); + }); + } + return keys; + }, + getObjectSize: function (obj) { + return $h.getObjectKeys(obj).length; + }, + /** + * Small dependency injection for the task manager + * https://gist.github.com/fearphage/4341799 + */ + whenAll: function (array) { + var s = [].slice, resolveValues = arguments.length === 1 && $h.isArray(array) ? array : s.call(arguments), + deferred = $.Deferred(), i, failed = 0, value, length = resolveValues.length, + remaining = length, rejectContexts, rejectValues, resolveContexts, updateFunc; + rejectContexts = rejectValues = resolveContexts = Array(length); + updateFunc = function (index, contexts, values) { + return function () { + if (values !== resolveValues) { + failed++; + } + deferred.notifyWith(contexts[index] = this, values[index] = s.call(arguments)); + if (!(--remaining)) { + deferred[(!failed ? 'resolve' : 'reject') + 'With'](contexts, values); + } + }; + }; + for (i = 0; i < length; i++) { + if ((value = resolveValues[i]) && $.isFunction(value.promise)) { + value.promise() + .done(updateFunc(i, resolveContexts, resolveValues)) + .fail(updateFunc(i, rejectContexts, rejectValues)); + } else { + deferred.notifyWith(this, value); + --remaining; + } + } + if (!remaining) { + deferred.resolveWith(resolveContexts, resolveValues); + } + return deferred.promise(); + } + }; + FileInput = function (element, options) { + var self = this; + self.$element = $(element); + self.$parent = self.$element.parent(); + if (!self._validate()) { + return; + } + self.isPreviewable = $h.hasFileAPISupport(); + self.isIE9 = $h.isIE(9); + self.isIE10 = $h.isIE(10); + if (self.isPreviewable || self.isIE9) { + self._init(options); + self._listen(); + } + self.$element.removeClass('file-loading'); + }; + + FileInput.prototype = { + constructor: FileInput, + _cleanup: function () { + var self = this; + self.reader = null; + self.clearFileStack(); + self.fileBatchCompleted = true; + self.isError = false; + self.isDuplicateError = false; + self.isPersistentError = false; + self.cancelling = false; + self.paused = false; + self.lastProgress = 0; + self._initAjax(); + }, + _isAborted: function () { + var self = this; + return self.cancelling || self.paused; + }, + _initAjax: function () { + var self = this, tm = self.taskManager = { + pool: {}, + addPool: function (id) { + return (tm.pool[id] = new tm.TasksPool(id)); + }, + getPool: function (id) { + return tm.pool[id]; + }, + addTask: function (id, logic) { // add standalone task directly from task manager + return new tm.Task(id, logic); + }, + TasksPool: function (id) { + var tp = this; + tp.id = id; + tp.cancelled = false; + tp.cancelledDeferrer = $.Deferred(); + tp.tasks = {}; + tp.addTask = function (id, logic) { + return (tp.tasks[id] = new tm.Task(id, logic)); + }; + tp.size = function () { + return $h.getObjectSize(tp.tasks); + }; + tp.run = function (maxThreads) { + var i = 0, failed = false, task, tasksList = $h.getObjectKeys(tp.tasks).map(function (key) { + return tp.tasks[key]; + }), tasksDone = [], deferred = $.Deferred(), enqueue, callback; + + if (tp.cancelled) { + tp.cancelledDeferrer.resolve(); + return deferred.reject(); + } + // if run all at once + if (!maxThreads) { + var tasksDeferredList = $h.getObjectKeys(tp.tasks).map(function (key) { + return tp.tasks[key].deferred; + }); + // when all are done + $h.whenAll(tasksDeferredList).done(function () { + var argv = $h.getArray(arguments); + if (!tp.cancelled) { + deferred.resolve.apply(null, argv); + tp.cancelledDeferrer.reject(); + } else { + deferred.reject.apply(null, argv); + tp.cancelledDeferrer.resolve(); + } + }).fail(function () { + var argv = $h.getArray(arguments); + deferred.reject.apply(null, argv); + if (!tp.cancelled) { + tp.cancelledDeferrer.reject(); + } else { + tp.cancelledDeferrer.resolve(); + } + }); + // run all tasks + $.each(tp.tasks, function (id) { + task = tp.tasks[id]; + task.run(); + }); + return deferred; + } + enqueue = function (task) { + $.when(task.deferred) + .fail(function () { + failed = true; + callback.apply(null, arguments); + }) + .always(callback); + }; + callback = function () { + var argv = $h.getArray(arguments); + // notify a task just ended + deferred.notify(argv); + tasksDone.push(argv); + if (tp.cancelled) { + deferred.reject.apply(null, tasksDone); + tp.cancelledDeferrer.resolve(); + return; + } + if (tasksDone.length === tp.size()) { + if (failed) { + deferred.reject.apply(null, tasksDone); + } else { + deferred.resolve.apply(null, tasksDone); + } + } + // if there are any tasks remaining + if (tasksList.length) { + task = tasksList.shift(); + enqueue(task); + task.run(); + } + }; + // run the first "maxThreads" tasks + while (tasksList.length && i++ < maxThreads) { + task = tasksList.shift(); + enqueue(task); + task.run(); + } + return deferred; + }; + tp.cancel = function () { + tp.cancelled = true; + return tp.cancelledDeferrer; + }; + }, + Task: function (id, logic) { + var tk = this; + tk.id = id; + tk.deferred = $.Deferred(); + tk.logic = logic; + tk.context = null; + tk.run = function () { + var argv = $h.getArray(arguments); + argv.unshift(tk.deferred); // add deferrer as first argument + logic.apply(tk.context, argv); // run task + return tk.deferred; // return deferrer + }; + tk.runWithContext = function (context) { + tk.context = context; + return tk.run(); + }; + } + }; + self.ajaxQueue = []; + self.ajaxRequests = []; + self.ajaxPool = null; + self.ajaxAborted = false; + }, + _init: function (options, refreshMode) { + var self = this, f, $el = self.$element, $cont, t, tmp; + self.options = options; + self.zoomPlaceholder = $h.getZoomPlaceholder(); + self.canOrientImage = $h.canOrientImage($el); + $.each(options, function (key, value) { + switch (key) { + case 'minFileCount': + case 'maxFileCount': + case 'maxTotalFileCount': + case 'minFileSize': + case 'maxFileSize': + case 'maxFilePreviewSize': + case 'resizeQuality': + case 'resizeIfSizeMoreThan': + case 'progressUploadThreshold': + case 'initialPreviewCount': + case 'zoomModalHeight': + case 'minImageHeight': + case 'maxImageHeight': + case 'minImageWidth': + case 'maxImageWidth': + case 'bytesToKB': + self[key] = $h.getNum(value); + break; + default: + self[key] = value; + break; + } + }); + if (!self.bytesToKB || self.bytesToKB <= 0) { + self.bytesToKB = 1024; + } + if (self.errorCloseButton === undefined) { + self.errorCloseButton = $h.closeButton('kv-error-close' + ($h.isBs(5) ? ' float-end' : '')); + } + if (self.maxTotalFileCount > 0 && self.maxTotalFileCount < self.maxFileCount) { + self.maxTotalFileCount = self.maxFileCount; + } + if (self.rtl) { // swap buttons for rtl + tmp = self.previewZoomButtonIcons.prev; + self.previewZoomButtonIcons.prev = self.previewZoomButtonIcons.next; + self.previewZoomButtonIcons.next = tmp; + } + // validate chunk threads to not exceed maxAjaxThreads + if (!isNaN(self.maxAjaxThreads) && self.maxAjaxThreads < self.resumableUploadOptions.maxThreads) { + self.resumableUploadOptions.maxThreads = self.maxAjaxThreads; + } + self._initFileManager(); + if (typeof self.autoOrientImage === 'function') { + self.autoOrientImage = self.autoOrientImage(); + } + if (typeof self.autoOrientImageInitial === 'function') { + self.autoOrientImageInitial = self.autoOrientImageInitial(); + } + if (!refreshMode) { + self._cleanup(); + } + self.duplicateErrors = []; + self.$form = $el.closest('form'); + self._initTemplateDefaults(); + self.uploadFileAttr = !$h.isEmpty($el.attr('name')) ? $el.attr('name') : 'file_data'; + t = self._getLayoutTemplate('progress'); + self.progressTemplate = t.replace('{class}', self.progressClass); + self.progressInfoTemplate = t.replace('{class}', self.progressInfoClass); + self.progressPauseTemplate = t.replace('{class}', self.progressPauseClass); + self.progressCompleteTemplate = t.replace('{class}', self.progressCompleteClass); + self.progressErrorTemplate = t.replace('{class}', self.progressErrorClass); + self.isDisabled = $el.attr('disabled') || $el.attr('readonly'); + if (self.isDisabled) { + $el.attr('disabled', true); + } + self.isClickable = self.browseOnZoneClick && self.showPreview && + (self.dropZoneEnabled || !$h.isEmpty(self.defaultPreviewContent)); + self.isAjaxUpload = $h.hasFileUploadSupport() && !$h.isEmpty(self.uploadUrl); + self.dropZoneEnabled = $h.hasDragDropSupport() && self.dropZoneEnabled; + if (!self.isAjaxUpload) { + self.dropZoneEnabled = self.dropZoneEnabled && $h.canAssignFilesToInput(); + } + self.slug = typeof options.slugCallback === 'function' ? options.slugCallback : self._slugDefault; + self.mainTemplate = self.showCaption ? self._getLayoutTemplate('main1') : self._getLayoutTemplate('main2'); + self.captionTemplate = self._getLayoutTemplate('caption'); + self.previewGenericTemplate = self._getPreviewTemplate('generic'); + if (!self.imageCanvas && self.resizeImage && (self.maxImageWidth || self.maxImageHeight)) { + self.imageCanvas = document.createElement('canvas'); + self.imageCanvasContext = self.imageCanvas.getContext('2d'); + } + if ($h.isEmpty($el.attr('id'))) { + $el.attr('id', $h.uniqId()); + } + self.namespace = '.fileinput_' + $el.attr('id').replace(/-/g, '_'); + if (self.$container === undefined) { + self.$container = self._createContainer(); + } else { + self._refreshContainer(); + } + $cont = self.$container; + self.$dropZone = $cont.find('.file-drop-zone'); + self.$progress = $cont.find('.kv-upload-progress'); + self.$btnUpload = $cont.find('.fileinput-upload'); + self.$captionContainer = $h.getElement(options, 'elCaptionContainer', $cont.find('.file-caption')); + self.$caption = $h.getElement(options, 'elCaptionText', $cont.find('.file-caption-name')); + if (!$h.isEmpty(self.msgPlaceholder)) { + f = $el.attr('multiple') ? self.filePlural : self.fileSingle; + self.$caption.attr('placeholder', self.msgPlaceholder.replace('{files}', f)); + } + self.$captionIcon = self.$captionContainer.find('.file-caption-icon'); + self.$previewContainer = $h.getElement(options, 'elPreviewContainer', $cont.find('.file-preview')); + self.$preview = $h.getElement(options, 'elPreviewImage', $cont.find('.file-preview-thumbnails')); + self.$previewStatus = $h.getElement(options, 'elPreviewStatus', $cont.find('.file-preview-status')); + self.$errorContainer = $h.getElement(options, 'elErrorContainer', + self.$previewContainer.find('.kv-fileinput-error')); + self._validateDisabled(); + if (!$h.isEmpty(self.msgErrorClass)) { + $h.addCss(self.$errorContainer, self.msgErrorClass); + } + if (!refreshMode) { + self._resetErrors(); + self.$errorContainer.hide(); + self.previewInitId = 'thumb-' + $el.attr('id'); + self._initPreviewCache(); + self._initPreview(true); + self._initPreviewActions(); + if (self.$parent.hasClass('file-loading')) { + self.$container.insertBefore(self.$parent); + self.$parent.remove(); + } + } else { + if (!self._errorsExist()) { + self.$errorContainer.hide(); + } + } + self._setFileDropZoneTitle(); + if ($el.attr('disabled')) { + self.disable(); + } + self._initZoom(); + if (self.hideThumbnailContent) { + $h.addCss(self.$preview, 'hide-content'); + } + }, + _initFileManager: function () { + var self = this; + self.uploadStartTime = $h.now(); + self.fileManager = { + stack: {}, + filesProcessed: [], + errors: [], + loadedImages: {}, + totalImages: 0, + totalFiles: null, + totalSize: null, + uploadedSize: 0, + stats: {}, + bpsLog: [], + bps: 0, + initStats: function (id) { + var data = {started: $h.now()}; + if (id) { + self.fileManager.stats[id] = data; + } else { + self.fileManager.stats = data; + } + }, + getUploadStats: function (id, loaded, total) { + var fm = self.fileManager, + started = id ? fm.stats[id] && fm.stats[id].started || $h.now() : self.uploadStartTime, + elapsed = ($h.now() - started) / 1000, bps = Math.ceil(elapsed ? loaded / elapsed : 0), + pendingBytes = total - loaded, out, delay = fm.bpsLog.length ? self.bitrateUpdateDelay : 0; + setTimeout(function () { + var i, j = 0, n = 0, len, beg; + fm.bpsLog.push(bps); + fm.bpsLog.sort(function (a, b) { + return a - b; + }); + len = fm.bpsLog.length; + beg = len > 10 ? len - 10 : Math.ceil(len / 2); + for (i = len; i > beg; i--) { + n = parseFloat(fm.bpsLog[i]); + j++; + } + fm.bps = (j > 0 ? n / j : 0) * 64; + }, delay); + out = { + fileId: id, + started: started, + elapsed: elapsed, + loaded: loaded, + total: total, + bps: fm.bps, + bitrate: self._getSize(fm.bps, false, self.bitRateUnits), + pendingBytes: pendingBytes + }; + if (id) { + fm.stats[id] = out; + } else { + fm.stats = out; + } + return out; + }, + exists: function (id) { + return $.inArray(id, self.fileManager.getIdList()) !== -1; + }, + count: function () { + return self.fileManager.getIdList().length; + }, + total: function () { + var fm = self.fileManager; + if (!fm.totalFiles) { + fm.totalFiles = fm.count(); + } + return fm.totalFiles; + }, + getTotalSize: function () { + var fm = self.fileManager; + if (fm.totalSize) { + return fm.totalSize; + } + fm.totalSize = 0; + $.each(self.getFileStack(), function (id, f) { + var size = parseFloat(f.size); + fm.totalSize += isNaN(size) ? 0 : size; + }); + return fm.totalSize; + }, + add: function (file, id) { + if (!id) { + id = self.fileManager.getId(file); + } + if (!id) { + return; + } + self.fileManager.stack[id] = { + file: file, + name: $h.getFileName(file), + relativePath: $h.getFileRelativePath(file), + size: file.size, + nameFmt: self._getFileName(file, ''), + sizeFmt: self._getSize(file.size) + }; + }, + remove: function ($thumb) { + var id = self._getThumbFileId($thumb); + self.fileManager.removeFile(id); + }, + removeFile: function (id) { + var fm = self.fileManager; + if (!id) { + return; + } + delete fm.stack[id]; + delete fm.loadedImages[id]; + }, + move: function (idFrom, idTo) { + var result = {}, stack = self.fileManager.stack; + if (!idFrom && !idTo || idFrom === idTo) { + return; + } + $.each(stack, function (k, v) { + if (k !== idFrom) { + result[k] = v; + } + if (k === idTo) { + result[idFrom] = stack[idFrom]; + } + }); + self.fileManager.stack = result; + }, + list: function () { + var files = []; + $.each(self.getFileStack(), function (k, v) { + if (v && v.file) { + files.push(v.file); + } + }); + return files; + }, + isPending: function (id) { + return $.inArray(id, self.fileManager.filesProcessed) === -1 && self.fileManager.exists(id); + }, + isProcessed: function () { + var filesProcessed = true, fm = self.fileManager; + $.each(self.getFileStack(), function (id) { + if (fm.isPending(id)) { + filesProcessed = false; + } + }); + return filesProcessed; + }, + clear: function () { + var fm = self.fileManager; + self.isDuplicateError = false; + self.isPersistentError = false; + fm.totalFiles = null; + fm.totalSize = null; + fm.uploadedSize = 0; + fm.stack = {}; + fm.errors = []; + fm.filesProcessed = []; + fm.stats = {}; + fm.bpsLog = []; + fm.bps = 0; + fm.clearImages(); + }, + clearImages: function () { + self.fileManager.loadedImages = {}; + self.fileManager.totalImages = 0; + }, + addImage: function (id, config) { + self.fileManager.loadedImages[id] = config; + }, + removeImage: function (id) { + delete self.fileManager.loadedImages[id]; + }, + getImageIdList: function () { + return $h.getObjectKeys(self.fileManager.loadedImages); + }, + getImageCount: function () { + return self.fileManager.getImageIdList().length; + }, + getId: function (file) { + return self._getFileId(file); + }, + getIndex: function (id) { + return self.fileManager.getIdList().indexOf(id); + }, + getThumb: function (id) { + var $thumb = null; + self._getThumbs().each(function () { + var $t = $(this); + if (self._getThumbFileId($t) === id) { + $thumb = $t; + } + }); + return $thumb; + }, + getThumbIndex: function ($thumb) { + var id = self._getThumbFileId($thumb); + return self.fileManager.getIndex(id); + }, + getIdList: function () { + return $h.getObjectKeys(self.fileManager.stack); + }, + getFile: function (id) { + return self.fileManager.stack[id] || null; + }, + getFileName: function (id, fmt) { + var file = self.fileManager.getFile(id); + if (!file) { + return ''; + } + return fmt ? (file.nameFmt || '') : file.name || ''; + }, + getFirstFile: function () { + var ids = self.fileManager.getIdList(), id = ids && ids.length ? ids[0] : null; + return self.fileManager.getFile(id); + }, + setFile: function (id, file) { + if (self.fileManager.getFile(id)) { + self.fileManager.stack[id].file = file; + } else { + self.fileManager.add(file, id); + } + }, + setProcessed: function (id) { + self.fileManager.filesProcessed.push(id); + }, + getProgress: function () { + var total = self.fileManager.total(), filesProcessed = self.fileManager.filesProcessed.length; + if (!total) { + return 0; + } + return Math.ceil(filesProcessed / total * 100); + + }, + setProgress: function (id, pct) { + var f = self.fileManager.getFile(id); + if (!isNaN(pct) && f) { + f.progress = pct; + } + } + }; + }, + _setUploadData: function (fd, config) { + var self = this; + $.each(config, function (key, value) { + var param = self.uploadParamNames[key] || key; + if ($h.isArray(value)) { + fd.append(param, value[0], value[1]); + } else { + fd.append(param, value); + } + }); + }, + _initResumableUpload: function () { + var self = this, opts = self.resumableUploadOptions, logs = $h.logMessages, rm, fm = self.fileManager; + if (!self.enableResumableUpload) { + return; + } + if (opts.fallback !== false && typeof opts.fallback !== 'function') { + opts.fallback = function (s) { + s._log(logs.noResumableSupport); + s.enableResumableUpload = false; + }; + } + if (!$h.hasResumableUploadSupport() && opts.fallback !== false) { + opts.fallback(self); + return; + } + if (!self.uploadUrl && self.enableResumableUpload) { + self._log(logs.noUploadUrl); + self.enableResumableUpload = false; + return; + + } + opts.chunkSize = parseFloat(opts.chunkSize); + if (opts.chunkSize <= 0 || isNaN(opts.chunkSize)) { + self._log(logs.invalidChunkSize, {chunkSize: opts.chunkSize}); + self.enableResumableUpload = false; + return; + } + rm = self.resumableManager = { + init: function (id, f, index) { + rm.logs = []; + rm.stack = []; + rm.error = ''; + rm.id = id; + rm.file = f.file; + rm.fileName = f.name; + rm.fileIndex = index; + rm.completed = false; + rm.lastProgress = 0; + if (self.showPreview) { + rm.$thumb = fm.getThumb(id) || null; + rm.$progress = rm.$btnDelete = null; + if (rm.$thumb && rm.$thumb.length) { + rm.$progress = rm.$thumb.find('.file-thumb-progress'); + rm.$btnDelete = rm.$thumb.find('.kv-file-remove'); + } + } + rm.chunkSize = opts.chunkSize * self.bytesToKB; + rm.chunkCount = rm.getTotalChunks(); + }, + setAjaxError: function (jqXHR, textStatus, errorThrown, isTest) { + if (jqXHR.responseJSON && jqXHR.responseJSON.error) { + errorThrown = jqXHR.responseJSON.error.toString(); + } + if (!isTest) { + rm.error = errorThrown; + } + if (opts.showErrorLog) { + self._log(logs.ajaxError, { + status: jqXHR.status, + error: errorThrown, + text: jqXHR.responseText || '' + }); + } + }, + reset: function () { + rm.stack = []; + rm.chunksProcessed = {}; + }, + setProcessed: function (status) { + var id = rm.id, msg, $thumb = rm.$thumb, $prog = rm.$progress, hasThumb = $thumb && $thumb.length, + params = {id: hasThumb ? $thumb.attr('id') : '', index: fm.getIndex(id), fileId: id}, tokens, + skipErrorsAndProceed = self.resumableUploadOptions.skipErrorsAndProceed; + rm.completed = true; + rm.lastProgress = 0; + if (hasThumb) { + $thumb.removeClass('file-uploading'); + } + if (status === 'success') { + fm.uploadedSize += rm.file.size; + if (self.showPreview) { + self._setProgress(101, $prog); + self._setThumbStatus($thumb, 'Success'); + self._initUploadSuccess(rm.chunksProcessed[id].data, $thumb); + } + fm.removeFile(id); + delete rm.chunksProcessed[id]; + self._raise('fileuploaded', [params.id, params.index, params.fileId]); + if (fm.isProcessed()) { + self._setProgress(101); + } + } else { + if (status !== 'cancel') { + if (self.showPreview) { + self._setThumbStatus($thumb, 'Error'); + self._setPreviewError($thumb, true); + self._setProgress(101, $prog, self.msgProgressError); + self._setProgress(101, self.$progress, self.msgProgressError); + self.cancelling = !skipErrorsAndProceed; + } + if (!self.$errorContainer.find('li[data-file-id="' + params.fileId + '"]').length) { + tokens = {file: rm.fileName, max: opts.maxRetries, error: rm.error}; + msg = self.msgResumableUploadRetriesExceeded.setTokens(tokens); + $.extend(params, tokens); + self._showFileError(msg, params, 'filemaxretries'); + if (skipErrorsAndProceed) { + fm.removeFile(id); + delete rm.chunksProcessed[id]; + if (fm.isProcessed()) { + self._setProgress(101); + } + } + } + } + } + if (fm.isProcessed()) { + rm.reset(); + } + }, + check: function () { + var status = true; + $.each(rm.logs, function (index, value) { + if (!value) { + status = false; + return false; + } + }); + }, + processedResumables: function () { + var logs = rm.logs, i, count = 0; + if (!logs || !logs.length) { + return 0; + } + for (i = 0; i < logs.length; i++) { + if (logs[i] === true) { + count++; + } + } + return count; + }, + getUploadedSize: function () { + var size = rm.processedResumables() * rm.chunkSize; + return size > rm.file.size ? rm.file.size : size; + }, + getTotalChunks: function () { + var chunkSize = parseFloat(rm.chunkSize); + if (!isNaN(chunkSize) && chunkSize > 0) { + return Math.ceil(rm.file.size / chunkSize); + } + return 0; + }, + getProgress: function () { + var chunksProcessed = rm.processedResumables(), total = rm.chunkCount; + if (total === 0) { + return 0; + } + return Math.ceil(chunksProcessed / total * 100); + }, + checkAborted: function (intervalId) { + if (self._isAborted()) { + clearInterval(intervalId); + self.unlock(); + } + }, + upload: function () { + var ids = fm.getIdList(), flag = 'new', intervalId; + intervalId = setInterval(function () { + var id; + rm.checkAborted(intervalId); + if (flag === 'new') { + self.lock(); + flag = 'processing'; + id = ids.shift(); + fm.initStats(id); + if (fm.stack[id]) { + rm.init(id, fm.stack[id], fm.getIndex(id)); + rm.processUpload(); + } + } + if (!fm.isPending(id) && rm.completed) { + flag = 'new'; + } + if (fm.isProcessed()) { + var $initThumbs = self.$preview.find('.file-preview-initial'); + if ($initThumbs.length) { + $h.addCss($initThumbs, $h.SORT_CSS); + self._initSortable(); + } + clearInterval(intervalId); + self._clearFileInput(); + self.unlock(); + setTimeout(function () { + var data = self.previewCache.data; + if (data) { + self.initialPreview = data.content; + self.initialPreviewConfig = data.config; + self.initialPreviewThumbTags = data.tags; + } + self._raise('filebatchuploadcomplete', [ + self.initialPreview, + self.initialPreviewConfig, + self.initialPreviewThumbTags, + self._getExtraData() + ]); + }, self.processDelay); + } + }, self.processDelay); + }, + uploadResumable: function () { + var i, pool, tm = self.taskManager, total = rm.chunkCount; + pool = tm.addPool(rm.id); + for (i = 0; i < total; i++) { + rm.logs[i] = !!(rm.chunksProcessed[rm.id] && rm.chunksProcessed[rm.id][i]); + if (!rm.logs[i]) { + rm.pushAjax(i, 0); + } + } + pool.run(opts.maxThreads) + .done(function () { + rm.setProcessed('success'); + }) + .fail(function () { + rm.setProcessed(pool.cancelled ? 'cancel' : 'error'); + }); + }, + processUpload: function () { + var fd, f, id = rm.id, fnBefore, fnSuccess, fnError, fnComplete, outData; + if (!opts.testUrl) { + rm.uploadResumable(); + return; + } + fd = new FormData(); + f = fm.stack[id]; + self._setUploadData(fd, { + fileId: id, + fileName: f.fileName, + fileSize: f.size, + fileRelativePath: f.relativePath, + chunkSize: rm.chunkSize, + chunkCount: rm.chunkCount + }); + fnBefore = function (jqXHR) { + outData = self._getOutData(fd, jqXHR); + self._raise('filetestbeforesend', [id, fm, rm, outData]); + }; + fnSuccess = function (data, textStatus, jqXHR) { + outData = self._getOutData(fd, jqXHR, data); + var pNames = self.uploadParamNames, chunksUploaded = pNames.chunksUploaded || 'chunksUploaded', + params = [id, fm, rm, outData]; + if (!data[chunksUploaded] || !$h.isArray(data[chunksUploaded])) { + self._raise('filetesterror', params); + } else { + if (!rm.chunksProcessed[id]) { + rm.chunksProcessed[id] = {}; + } + $.each(data[chunksUploaded], function (key, index) { + rm.logs[index] = true; + rm.chunksProcessed[id][index] = true; + }); + rm.chunksProcessed[id].data = data; + self._raise('filetestsuccess', params); + } + rm.uploadResumable(); + }; + fnError = function (jqXHR, textStatus, errorThrown) { + outData = self._getOutData(fd, jqXHR); + self._raise('filetestajaxerror', [id, fm, rm, outData]); + rm.setAjaxError(jqXHR, textStatus, errorThrown, true); + rm.uploadResumable(); + }; + fnComplete = function () { + self._raise('filetestcomplete', [id, fm, rm, self._getOutData(fd)]); + }; + self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError, fd, id, rm.fileIndex, opts.testUrl); + }, + pushAjax: function (index, retry) { + var tm = self.taskManager, pool = tm.getPool(rm.id); + pool.addTask(pool.size() + 1, function (deferrer) { + // use fifo chunk stack + var arr = rm.stack.shift(), index; + index = arr[0]; + if (!rm.chunksProcessed[rm.id] || !rm.chunksProcessed[rm.id][index]) { + rm.sendAjax(index, arr[1], deferrer); + } else { + self._log(logs.chunkQueueError, {index: index}); + } + }); + rm.stack.push([index, retry]); + }, + sendAjax: function (index, retry, deferrer) { + var f, chunkSize = rm.chunkSize, id = rm.id, file = rm.file, $thumb = rm.$thumb, + msgs = $h.logMessages, $btnDelete = rm.$btnDelete, logError = function (msg, tokens) { + if (tokens) { + msg = msg.setTokens(tokens); + } + msg = msgs.resumableRequestError.setTokens({msg: msg}); + self._log(msg); + deferrer.reject(msg); + }; + if (rm.chunksProcessed[id] && rm.chunksProcessed[id][index]) { + return; + } + if (retry > opts.maxRetries) { + logError(msgs.resumableMaxRetriesReached, {n: opts.maxRetries}); + rm.setProcessed('error'); + return; + } + var fd, outData, fnBefore, fnSuccess, fnError, fnComplete, slice = file.slice ? 'slice' : + (file.mozSlice ? 'mozSlice' : (file.webkitSlice ? 'webkitSlice' : 'slice')), + blob = file[slice](chunkSize * index, chunkSize * (index + 1)); + fd = new FormData(); + f = fm.stack[id]; + self._setUploadData(fd, { + chunkCount: rm.chunkCount, + chunkIndex: index, + chunkSize: chunkSize, + chunkSizeStart: chunkSize * index, + fileBlob: [blob, rm.fileName], + fileId: id, + fileName: rm.fileName, + fileRelativePath: f.relativePath, + fileSize: file.size, + retryCount: retry + }); + if (rm.$progress && rm.$progress.length) { + rm.$progress.show(); + } + fnBefore = function (jqXHR) { + outData = self._getOutData(fd, jqXHR); + if (self.showPreview) { + if (!$thumb.hasClass('file-preview-success')) { + self._setThumbStatus($thumb, 'Loading'); + $h.addCss($thumb, 'file-uploading'); + } + $btnDelete.attr('disabled', true); + } + self._raise('filechunkbeforesend', [id, index, retry, fm, rm, outData]); + }; + fnSuccess = function (data, textStatus, jqXHR) { + if (self._isAborted()) { + logError(msgs.resumableAborting); + return; + } + outData = self._getOutData(fd, jqXHR, data); + var paramNames = self.uploadParamNames, chunkIndex = paramNames.chunkIndex || 'chunkIndex', + params = [id, index, retry, fm, rm, outData]; + if (data.error) { + if (opts.showErrorLog) { + self._log(logs.retryStatus, { + retry: retry + 1, + filename: rm.fileName, + chunk: index + }); + } + self._raise('filechunkerror', params); + rm.pushAjax(index, retry + 1); + rm.error = data.error; + logError(data.error); + } else { + rm.logs[data[chunkIndex]] = true; + if (!rm.chunksProcessed[id]) { + rm.chunksProcessed[id] = {}; + } + rm.chunksProcessed[id][data[chunkIndex]] = true; + rm.chunksProcessed[id].data = data; + deferrer.resolve.call(null, data); + self._raise('filechunksuccess', params); + rm.check(); + } + }; + fnError = function (jqXHR, textStatus, errorThrown) { + if (self._isAborted()) { + logError(msgs.resumableAborting); + return; + } + outData = self._getOutData(fd, jqXHR); + rm.setAjaxError(jqXHR, textStatus, errorThrown); + self._raise('filechunkajaxerror', [id, index, retry, fm, rm, outData]); + rm.pushAjax(index, retry + 1); // push another task + logError(msgs.resumableRetryError, {n: retry - 1}); // resolve the current task + }; + fnComplete = function () { + if (!self._isAborted()) { + self._raise('filechunkcomplete', [id, index, retry, fm, rm, self._getOutData(fd)]); + } + }; + self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError, fd, id, rm.fileIndex); + } + }; + rm.reset(); + }, + _initTemplateDefaults: function () { + var self = this, tMain1, tMain2, tPreview, tFileIcon, tClose, tCaption, tBtnDefault, tBtnLink, tBtnBrowse, + tModalMain, tModal, tProgress, tSize, tFooter, tActions, tActionDelete, tActionUpload, tActionDownload, + tActionZoom, tActionDrag, tIndicator, tTagBef, tTagBef1, tTagBef2, tTagAft, tGeneric, tHtml, tImage, + tText, tOffice, tGdocs, tVideo, tAudio, tFlash, tObject, tPdf, tOther, tStyle, tZoomCache, vDefaultDim, + tActionRotate, tStats, tModalLabel, tDescClose, renderObject = function (type, mime) { + return '\n' + $h.DEFAULT_PREVIEW + '\n\n'; + }, defBtnCss1 = 'btn btn-sm btn-kv ' + $h.defaultButtonCss(); + tMain1 = '{preview}\n' + + '
\n' + + '
\n' + + '
\n' + + ' {caption}\n\n' + + ($h.isBs(5) ? '' : '
\n') + + ' {remove}\n' + + ' {cancel}\n' + + ' {pause}\n' + + ' {upload}\n' + + ' {browse}\n' + + ($h.isBs(5) ? '' : '
\n') + + '
'; + '
'; + tMain2 = '{preview}\n
\n
\n' + + '{remove}\n{cancel}\n{upload}\n{browse}\n'; + tPreview = '
\n' + + ' {close}' + + '
\n' + + '
\n' + + '
\n' + + '
\n' + + '
\n' + + '
\n' + + '
'; + tClose = $h.closeButton('fileinput-remove'); + tFileIcon = ''; + // noinspection HtmlUnknownAttribute + tCaption = '\n'; + //noinspection HtmlUnknownAttribute + tBtnDefault = ''; + //noinspection HtmlUnknownTarget,HtmlUnknownAttribute + tBtnLink = '
{icon} {label}'; + //noinspection HtmlUnknownAttribute + tBtnBrowse = '
{icon} {label}
'; + tModalLabel = $h.MODAL_ID + 'Label'; + tModalMain = ''; + tModal = '\n'; + tDescClose = ''; + tProgress = '
\n' + + '
\n' + + ' {status}\n' + + '
\n' + + '
{stats}'; + tStats = '
' + + '{pendingTime} ' + + '{uploadSpeed}' + + '
'; + tSize = ' ({sizeText})'; + tFooter = ''; + tActions = '
\n' + + ' \n' + + '
\n' + + '{drag}\n' + + '
'; + //noinspection HtmlUnknownAttribute + tActionDelete = '\n'; + tActionUpload = ''; + tActionRotate = ''; + tActionDownload = '{downloadIcon}'; + tActionZoom = ''; + tActionDrag = '{dragIcon}'; + tIndicator = '
{indicator}
'; + tTagBef = '
\n'; + tTagBef2 = tTagBef + ' title="{caption}">
\n'; + tTagAft = '
{footer}\n{zoomCache}
\n'; + tGeneric = '{content}\n'; + tStyle = ' {style}'; + tHtml = renderObject('html', 'text/html'); + tText = renderObject('text', 'text/plain;charset=UTF-8'); + tPdf = renderObject('pdf', 'application/pdf'); + tImage = '{alt}\n'; + tOffice = ''; + tGdocs = ''; + tVideo = '\n'; + tAudio = '\n'; + tFlash = '\n'; + tObject = '\n' + '\n' + + $h.OBJECT_PARAMS + ' ' + $h.DEFAULT_PREVIEW + '\n\n'; + tOther = '
\n' + $h.DEFAULT_PREVIEW + '\n
\n'; + tZoomCache = '
{zoomContent}
'; + vDefaultDim = {width: '100%', height: '100%', 'min-height': '480px'}; + if (self._isPdfRendered()) { + tPdf = self.pdfRendererTemplate.replace('{renderer}', self._encodeURI(self.pdfRendererUrl)); + } + self.defaults = { + layoutTemplates: { + main1: tMain1, + main2: tMain2, + preview: tPreview, + close: tClose, + fileIcon: tFileIcon, + caption: tCaption, + modalMain: tModalMain, + modal: tModal, + descriptionClose: tDescClose, + progress: tProgress, + stats: tStats, + size: tSize, + footer: tFooter, + indicator: tIndicator, + actions: tActions, + actionDelete: tActionDelete, + actionRotate: tActionRotate, + actionUpload: tActionUpload, + actionDownload: tActionDownload, + actionZoom: tActionZoom, + actionDrag: tActionDrag, + btnDefault: tBtnDefault, + btnLink: tBtnLink, + btnBrowse: tBtnBrowse, + zoomCache: tZoomCache + }, + previewMarkupTags: { + tagBefore1: tTagBef1, + tagBefore2: tTagBef2, + tagAfter: tTagAft + }, + previewContentTemplates: { + generic: tGeneric, + html: tHtml, + image: tImage, + text: tText, + office: tOffice, + gdocs: tGdocs, + video: tVideo, + audio: tAudio, + flash: tFlash, + object: tObject, + pdf: tPdf, + other: tOther + }, + allowedPreviewTypes: ['image', 'html', 'text', 'video', 'audio', 'flash', 'pdf', 'object'], + previewTemplates: {}, + previewSettings: { + image: {width: 'auto', height: 'auto', 'max-width': '100%', 'max-height': '100%'}, + html: {width: '213px', height: '160px'}, + text: {width: '213px', height: '160px'}, + office: {width: '213px', height: '160px'}, + gdocs: {width: '213px', height: '160px'}, + video: {width: '213px', height: '160px'}, + audio: {width: '100%', height: '30px'}, + flash: {width: '213px', height: '160px'}, + object: {width: '213px', height: '160px'}, + pdf: {width: '100%', height: '160px', 'position': 'relative'}, + other: {width: '213px', height: '160px'} + }, + previewSettingsSmall: { + image: {width: 'auto', height: 'auto', 'max-width': '100%', 'max-height': '100%'}, + html: {width: '100%', height: '160px'}, + text: {width: '100%', height: '160px'}, + office: {width: '100%', height: '160px'}, + gdocs: {width: '100%', height: '160px'}, + video: {width: '100%', height: 'auto'}, + audio: {width: '100%', height: '30px'}, + flash: {width: '100%', height: 'auto'}, + object: {width: '100%', height: 'auto'}, + pdf: {width: '100%', height: '160px'}, + other: {width: '100%', height: '160px'} + }, + previewZoomSettings: { + image: {width: 'auto', height: 'auto', 'max-width': '100%', 'max-height': '100%'}, + html: vDefaultDim, + text: vDefaultDim, + office: {width: '100%', height: '100%', 'max-width': '100%', 'min-height': '480px'}, + gdocs: {width: '100%', height: '100%', 'max-width': '100%', 'min-height': '480px'}, + video: {width: 'auto', height: '100%', 'max-width': '100%'}, + audio: {width: '100%', height: '30px'}, + flash: {width: 'auto', height: '480px'}, + object: {width: 'auto', height: '100%', 'max-width': '100%', 'min-height': '480px'}, + pdf: vDefaultDim, + other: {width: 'auto', height: '100%', 'min-height': '480px'} + }, + mimeTypeAliases: { + 'video/quicktime': 'video/mp4' + }, + fileTypeSettings: { + image: function (vType, vName) { + return ($h.compare(vType, 'image.*') && !$h.compare(vType, /(tiff?|wmf)$/i) || + $h.compare(vName, /\.(gif|png|jpe?g)$/i)); + }, + html: function (vType, vName) { + return $h.compare(vType, 'text/html') || $h.compare(vName, /\.(htm|html)$/i); + }, + office: function (vType, vName) { + return $h.compare(vType, /(word|excel|powerpoint|office)$/i) || + $h.compare(vName, /\.(docx?|xlsx?|pptx?|pps|potx?)$/i); + }, + gdocs: function (vType, vName) { + return $h.compare(vType, /(word|excel|powerpoint|office|iwork-pages|tiff?)$/i) || + $h.compare(vName, + /\.(docx?|xlsx?|pptx?|pps|potx?|rtf|ods|odt|pages|ai|dxf|ttf|tiff?|wmf|e?ps)$/i); + }, + text: function (vType, vName) { + return $h.compare(vType, 'text.*') || $h.compare(vName, /\.(xml|javascript)$/i) || + $h.compare(vName, /\.(txt|md|nfo|ini|json|php|js|css)$/i); + }, + video: function (vType, vName) { + return $h.compare(vType, 'video.*') && ($h.compare(vType, /(ogg|mp4|mp?g|mov|webm|3gp)$/i) || + $h.compare(vName, /\.(og?|mp4|webm|mp?g|mov|3gp)$/i)); + }, + audio: function (vType, vName) { + return $h.compare(vType, 'audio.*') && ($h.compare(vName, /(ogg|mp3|mp?g|wav)$/i) || + $h.compare(vName, /\.(og?|mp3|mp?g|wav)$/i)); + }, + flash: function (vType, vName) { + return $h.compare(vType, 'application/x-shockwave-flash', true) || $h.compare(vName, + /\.(swf)$/i); + }, + pdf: function (vType, vName) { + return $h.compare(vType, 'application/pdf', true) || $h.compare(vName, /\.(pdf)$/i); + }, + object: function () { + return true; + }, + other: function () { + return true; + } + }, + fileActionSettings: { + showRemove: true, + showUpload: true, + showDownload: true, + showZoom: true, + showDrag: true, + showRotate: false, + removeIcon: '', + removeClass: defBtnCss1, + removeErrorClass: 'btn btn-sm btn-kv btn-danger', + removeTitle: 'Remove file', + uploadIcon: '', + uploadClass: defBtnCss1, + uploadTitle: 'Upload file', + uploadRetryIcon: '', + uploadRetryTitle: 'Retry upload', + downloadIcon: '', + downloadClass: defBtnCss1, + downloadTitle: 'Download file', + rotateIcon: '', + rotateClass: defBtnCss1, + rotateTitle: 'Rotate 90 deg. clockwise', + zoomIcon: '', + zoomClass: defBtnCss1, + zoomTitle: 'View Details', + dragIcon: '', + dragClass: 'text-primary', + dragTitle: 'Move / Rearrange', + dragSettings: {}, + indicatorNew: '', + indicatorSuccess: '', + indicatorError: '', + indicatorLoading: '', + indicatorPaused: '', + indicatorNewTitle: 'Not uploaded yet', + indicatorSuccessTitle: 'Uploaded', + indicatorErrorTitle: 'Upload Error', + indicatorLoadingTitle: 'Uploading …', + indicatorPausedTitle: 'Upload Paused' + } + }; + $.each(self.defaults, function (key, setting) { + if (key === 'allowedPreviewTypes') { + if (self.allowedPreviewTypes === undefined) { + self.allowedPreviewTypes = setting; + } + return; + } + self[key] = $.extend(true, {}, setting, self[key]); + }); + self._initPreviewTemplates(); + }, + _initPreviewTemplates: function () { + var self = this, tags = self.previewMarkupTags, tagBef, tagAft = tags.tagAfter; + $.each(self.previewContentTemplates, function (key, value) { + if ($h.isEmpty(self.previewTemplates[key])) { + tagBef = tags.tagBefore2; + if (key === 'generic' || key === 'image') { + tagBef = tags.tagBefore1; + } + if (self._isPdfRendered() && key === 'pdf') { + tagBef = tagBef.replace('kv-file-content', 'kv-file-content kv-pdf-rendered'); + } + self.previewTemplates[key] = tagBef + value + tagAft; + } + }); + }, + _initPreviewCache: function () { + var self = this; + self.previewCache = { + data: {}, + init: function () { + var content = self.initialPreview; + if (content.length > 0 && !$h.isArray(content)) { + content = content.split(self.initialPreviewDelimiter); + } + self.previewCache.data = { + content: content, + config: self.initialPreviewConfig, + tags: self.initialPreviewThumbTags + }; + }, + count: function (skipNull) { + if (!self.previewCache.data || !self.previewCache.data.content) { + return 0; + } + if (skipNull) { + var chk = self.previewCache.data.content.filter(function (n) { + return n !== null; + }); + return chk.length; + } + return self.previewCache.data.content.length; + }, + get: function (i, isDisabled) { + var ind = $h.INIT_FLAG + i, data = self.previewCache.data, config = data.config[i], + content = data.content[i], out, $tmp, cat, ftr, + fname, ftype, frameClass, asData = $h.ifSet('previewAsData', config, self.initialPreviewAsData), + a = config ? {title: config.title || null, alt: config.alt || null} : {title: null, alt: null}, + parseTemplate = function (cat, dat, fname, ftype, ftr, ind, fclass, t) { + var fc = ' file-preview-initial ' + $h.SORT_CSS + (fclass ? ' ' + fclass : ''), + id = self.previewInitId + '-' + ind, + fileId = config && config.fileId || id; + /** @namespace config.zoomData */ + return self._generatePreviewTemplate(cat, dat, fname, ftype, id, fileId, false, null, null, fc, + ftr, ind, t, a, config && config.zoomData || dat); + }; + if (!content || !content.length) { + return ''; + } + isDisabled = isDisabled === undefined ? true : isDisabled; + cat = $h.ifSet('type', config, self.initialPreviewFileType || 'generic'); + fname = $h.ifSet('filename', config, $h.ifSet('caption', config)); + ftype = $h.ifSet('filetype', config, cat); + ftr = self.previewCache.footer(i, isDisabled, (config && config.size || null)); + frameClass = $h.ifSet('frameClass', config); + if (asData) { + out = parseTemplate(cat, content, fname, ftype, ftr, ind, frameClass); + } else { + out = parseTemplate('generic', content, fname, ftype, ftr, ind, frameClass, cat) + .setTokens({'content': data.content[i]}); + } + if (data.tags.length && data.tags[i]) { + out = $h.replaceTags(out, data.tags[i]); + } + /** @namespace config.frameAttr */ + if (!$h.isEmpty(config) && !$h.isEmpty(config.frameAttr)) { + $tmp = $h.createElement(out); + $tmp.find('.file-preview-initial').attr(config.frameAttr); + out = $tmp.html(); + $tmp.remove(); + } + return out; + }, + clean: function (data) { + data.content = $h.cleanArray(data.content); + data.config = $h.cleanArray(data.config); + data.tags = $h.cleanArray(data.tags); + self.previewCache.data = data; + }, + add: function (content, config, tags, append) { + var data = self.previewCache.data, index; + if (!content || !content.length) { + return 0; + } + index = content.length - 1; + if (!$h.isArray(content)) { + content = content.split(self.initialPreviewDelimiter); + } + if (append && data.content) { + index = data.content.push(content[0]) - 1; + data.config[index] = config; + data.tags[index] = tags; + } else { + data.content = content; + data.config = config; + data.tags = tags; + } + self.previewCache.clean(data); + return index; + }, + set: function (content, config, tags, append) { + var data = self.previewCache.data, i, chk; + if (!content || !content.length) { + return; + } + if (!$h.isArray(content)) { + content = content.split(self.initialPreviewDelimiter); + } + chk = content.filter(function (n) { + return n !== null; + }); + if (!chk.length) { + return; + } + if (data.content === undefined) { + data.content = []; + } + if (data.config === undefined) { + data.config = []; + } + if (data.tags === undefined) { + data.tags = []; + } + if (append) { + for (i = 0; i < content.length; i++) { + if (content[i]) { + data.content.push(content[i]); + } + } + for (i = 0; i < config.length; i++) { + if (config[i]) { + data.config.push(config[i]); + } + } + for (i = 0; i < tags.length; i++) { + if (tags[i]) { + data.tags.push(tags[i]); + } + } + } else { + data.content = content; + data.config = config; + data.tags = tags; + } + self.previewCache.clean(data); + }, + unset: function (index) { + var chk = self.previewCache.count(), rev = self.reversePreviewOrder; + if (!chk) { + return; + } + if (chk === 1) { + self.previewCache.data.content = []; + self.previewCache.data.config = []; + self.previewCache.data.tags = []; + self.initialPreview = []; + self.initialPreviewConfig = []; + self.initialPreviewThumbTags = []; + return; + } + self.previewCache.data.content = $h.spliceArray(self.previewCache.data.content, index, rev); + self.previewCache.data.config = $h.spliceArray(self.previewCache.data.config, index, rev); + self.previewCache.data.tags = $h.spliceArray(self.previewCache.data.tags, index, rev); + var data = $.extend(true, {}, self.previewCache.data); + self.previewCache.clean(data); + }, + out: function () { + var html = '', caption, len = self.previewCache.count(), i, content; + if (len === 0) { + return {content: '', caption: ''}; + } + for (i = 0; i < len; i++) { + content = self.previewCache.get(i); + html = self.reversePreviewOrder ? (content + html) : (html + content); + } + caption = self._getMsgSelected(len); + return {content: html, caption: caption}; + }, + footer: function (i, isDisabled, size) { + var data = self.previewCache.data || {}; + if ($h.isEmpty(data.content)) { + return ''; + } + if ($h.isEmpty(data.config) || $h.isEmpty(data.config[i])) { + data.config[i] = {}; + } + isDisabled = isDisabled === undefined ? true : isDisabled; + var config = data.config[i], caption = $h.ifSet('caption', config), a, + width = $h.ifSet('width', config, 'auto'), url = $h.ifSet('url', config, false), + key = $h.ifSet('key', config, null), fileId = $h.ifSet('fileId', config, null), + fs = self.fileActionSettings, initPreviewShowDel = self.initialPreviewShowDelete || false, + downloadInitialUrl = !self.initialPreviewDownloadUrl ? '' : + self.initialPreviewDownloadUrl + '?key=' + key + (fileId ? '&fileId=' + fileId : ''), + dUrl = config.downloadUrl || downloadInitialUrl, + dFil = config.filename || config.caption || '', + initPreviewShowDwl = !!(dUrl), + sDel = $h.ifSet('showRemove', config, initPreviewShowDel), + sRot = $h.ifSet('showRotate', config, $h.ifSet('showRotate', fs, true)), + sDwl = $h.ifSet('showDownload', config, $h.ifSet('showDownload', fs, initPreviewShowDwl)), + sZm = $h.ifSet('showZoom', config, $h.ifSet('showZoom', fs, true)), + sDrg = $h.ifSet('showDrag', config, $h.ifSet('showDrag', fs, true)), + dis = (url === false) && isDisabled; + sDwl = sDwl && config.downloadUrl !== false && !!dUrl; + a = self._renderFileActions(config, false, sDwl, sDel, sRot, sZm, sDrg, dis, url, key, true, dUrl, dFil); + return self._getLayoutTemplate('footer').setTokens({ + 'progress': self._renderThumbProgress(), + 'actions': a, + 'caption': caption, + 'size': self._getSize(size), + 'width': width, + 'indicator': '' + }); + } + }; + self.previewCache.init(); + }, + _isPdfRendered: function () { + var self = this, useLib = self.usePdfRenderer, + flag = typeof useLib === 'function' ? useLib() : !!useLib; + return flag && self.pdfRendererUrl; + }, + _handler: function ($el, event, callback) { + var self = this, ns = self.namespace, ev = event.split(' ').join(ns + ' ') + ns; + if (!$el || !$el.length) { + return; + } + $el.off(ev).on(ev, callback); + }, + _encodeURI: function (vUrl) { + var self = this; + return self.encodeUrl ? encodeURI(vUrl) : vUrl; + }, + _log: function (msg, tokens) { + var self = this, id = self.$element.attr('id'); + if (!self.showConsoleLogs) { + return; + } + if (id) { + msg = '"' + id + '": ' + msg; + } + msg = 'bootstrap-fileinput: ' + msg; + if (typeof tokens === 'object') { + msg = msg.setTokens(tokens); + } + if (window.console && typeof window.console.log !== 'undefined') { + window.console.log(msg); + } else { + window.alert(msg); + } + }, + _validate: function () { + var self = this, status = self.$element.attr('type') === 'file'; + if (!status) { + self._log($h.logMessages.badInputType); + } + return status; + }, + _errorsExist: function () { + var self = this, $err, $errList = self.$errorContainer.find('li'); + if ($errList.length) { + return true; + } + $err = $h.createElement(self.$errorContainer.html()); + $err.find('.kv-error-close').remove(); + $err.find('ul').remove(); + return !!$.trim($err.text()).length; + }, + _errorHandler: function (evt, caption) { + var self = this, err = evt.target.error, showError = function (msg) { + self._showError(msg.replace('{name}', caption)); + }; + /** @namespace err.NOT_FOUND_ERR */ + /** @namespace err.SECURITY_ERR */ + /** @namespace err.NOT_READABLE_ERR */ + if (err.code === err.NOT_FOUND_ERR) { + showError(self.msgFileNotFound); + } else { + if (err.code === err.SECURITY_ERR) { + showError(self.msgFileSecured); + } else { + if (err.code === err.NOT_READABLE_ERR) { + showError(self.msgFileNotReadable); + } else { + if (err.code === err.ABORT_ERR) { + showError(self.msgFilePreviewAborted); + } else { + showError(self.msgFilePreviewError); + } + } + } + } + }, + _addError: function (msg) { + var self = this, $error = self.$errorContainer; + if (msg && $error.length) { + $h.setHtml($error, self.errorCloseButton + msg); + self._handler($error.find('.kv-error-close'), 'click', function () { + setTimeout(function () { + if (self.showPreview && !self.getFrames().length) { + self.clear(); + } + $error.fadeOut('slow'); + }, self.processDelay); + }); + } + }, + _setValidationError: function (css) { + var self = this; + css = (css ? css + ' ' : '') + 'has-error'; + self.$container.removeClass(css).addClass('has-error'); + $h.addCss(self.$caption, 'is-invalid'); + }, + _resetErrors: function (fade) { + var self = this, $error = self.$errorContainer, history = self.resumableUploadOptions.retainErrorHistory; + if (self.isPersistentError || (self.enableResumableUpload && history && !self.clearInput)) { + return; + } + self.clearInput = false; + self.isError = false; + self.$container.removeClass('has-error'); + self.$caption.removeClass('is-invalid is-valid file-processing'); + $error.html(''); + if (fade) { + $error.fadeOut('slow'); + } else { + $error.hide(); + } + }, + _showFolderError: function (folders) { + var self = this, $error = self.$errorContainer, msg; + if (!folders) { + return; + } + if (!self.isAjaxUpload) { + self._clearFileInput(); + } + msg = self.msgFoldersNotAllowed.replace('{n}', folders); + self._addError(msg); + self._setValidationError(); + $error.fadeIn(self.fadeDelay); + self._raise('filefoldererror', [folders, msg]); + }, + showUserError: function (msg, params, retainErrorHistory) { + var self = this, fileName; + if (!self.uploadInitiated) { + return; + } + if (!params || !params.fileId) { + if (!retainErrorHistory) { + self.$errorContainer.html(''); + } + } else { + if (!retainErrorHistory) { + self.$errorContainer.find('[data-file-id="' + params.fileId + '"]').remove(); + } + fileName = self.fileManager.getFileName(params.fileId); + if (fileName) { + msg = '' + fileName + ': ' + msg; + } + } + self._showFileError(msg, params, 'fileusererror'); + }, + _showFileError: function (msg, params, event) { + var self = this, $error = self.$errorContainer, ev = event || 'fileuploaderror', + fId = params && params.fileId || '', e = params && params.id ? + '
  • ' + msg + '
  • ' : + '
  • ' + msg + '
  • '; + + if ($error.find('ul').length === 0) { + self._addError('
      ' + e + '
    '); + } else { + $error.find('ul').append(e); + } + $error.fadeIn(self.fadeDelay); + self._raise(ev, [params, msg]); + self._setValidationError('file-input-new'); + return true; + }, + _showError: function (msg, params, event) { + var self = this, $error = self.$errorContainer, ev = event || 'fileerror'; + params = params || {}; + params.reader = self.reader; + self._addError(msg); + $error.fadeIn(self.fadeDelay); + self._raise(ev, [params, msg]); + if (!self.isAjaxUpload) { + self._clearFileInput(); + } + self._setValidationError('file-input-new'); + self.$btnUpload.attr('disabled', true); + return true; + }, + _noFilesError: function (params) { + var self = this, label = self.minFileCount > 1 ? self.filePlural : self.fileSingle, + msg = self.msgFilesTooLess.replace('{n}', self.minFileCount).replace('{files}', label), + $error = self.$errorContainer; + msg = '
  • ' + msg + '
  • '; + if ($error.find('ul').length === 0) { + self._addError('
      ' + msg + '
    '); + } else { + $error.find('ul').append(msg); + } + self.isError = true; + self._updateFileDetails(0); + $error.fadeIn(self.fadeDelay); + self._raise('fileerror', [params, msg]); + self._clearFileInput(); + self._setValidationError(); + }, + _parseError: function (operation, jqXHR, errorThrown, fileName) { + /** @namespace jqXHR.responseJSON */ + var self = this, errMsg = $.trim(errorThrown + ''), textPre, errText, text; + errText = jqXHR.responseJSON && jqXHR.responseJSON.error ? jqXHR.responseJSON.error.toString() : ''; + text = errText ? errText : jqXHR.responseText; + if (self.cancelling && self.msgUploadAborted) { + errMsg = self.msgUploadAborted; + } + if (self.showAjaxErrorDetails && text) { + if (errText) { + errMsg = $.trim(errText + ''); + } else { + text = $.trim(text.replace(/\n\s*\n/g, '\n')); + textPre = text.length ? '
    ' + text + '
    ' : ''; + errMsg += errMsg ? textPre : text; + } + } + if (!errMsg) { + errMsg = self.msgAjaxError.replace('{operation}', operation); + } + self.cancelling = false; + return fileName ? '' + fileName + ': ' + errMsg : errMsg; + }, + _parseFileType: function (type, name) { + var self = this, isValid, vType, cat, i, types = self.allowedPreviewTypes || []; + if (type === 'application/text-plain') { + return 'text'; + } + for (i = 0; i < types.length; i++) { + cat = types[i]; + isValid = self.fileTypeSettings[cat]; + vType = isValid(type, name) ? cat : ''; + if (!$h.isEmpty(vType)) { + return vType; + } + } + return 'other'; + }, + _getPreviewIcon: function (fname) { + var self = this, ext, out = null; + if (fname && fname.indexOf('.') > -1) { + ext = fname.split('.').pop(); + if (self.previewFileIconSettings) { + out = self.previewFileIconSettings[ext] || self.previewFileIconSettings[ext.toLowerCase()] || null; + } + if (self.previewFileExtSettings) { + $.each(self.previewFileExtSettings, function (key, func) { + if (self.previewFileIconSettings[key] && func(ext)) { + out = self.previewFileIconSettings[key]; + //noinspection UnnecessaryReturnStatementJS + return; + } + }); + } + } + return out || self.previewFileIcon; + }, + _parseFilePreviewIcon: function (content, fname) { + var self = this, icn = self._getPreviewIcon(fname), out = content; + if (out.indexOf('{previewFileIcon}') > -1) { + out = out.setTokens({'previewFileIconClass': self.previewFileIconClass, 'previewFileIcon': icn}); + } + return out; + }, + _raise: function (event, params) { + var self = this, e = $.Event(event); + if (params !== undefined) { + self.$element.trigger(e, params); + } else { + self.$element.trigger(e); + } + var out = e.result, isAborted = out === false; + if (e.isDefaultPrevented() || isAborted) { + return false; + } + if (e.type === 'filebatchpreupload' && (out || isAborted)) { + self.ajaxAborted = out; + return false; + } + switch (event) { + // ignore these events + case 'filebatchuploadcomplete': + case 'filebatchuploadsuccess': + case 'fileuploaded': + case 'fileclear': + case 'filecleared': + case 'filereset': + case 'fileerror': + case 'filefoldererror': + case 'filecustomerror': + case 'filesuccessremove': + break; + // receive data response via `filecustomerror` event` + default: + if (!self.ajaxAborted) { + self.ajaxAborted = out; + } + break; + } + return true; + }, + _listenFullScreen: function (isFullScreen) { + var self = this, $modal = self.$modal, $btnFull, $btnBord; + if (!$modal || !$modal.length) { + return; + } + $btnFull = $modal && $modal.find('.btn-kv-fullscreen'); + $btnBord = $modal && $modal.find('.btn-kv-borderless'); + if (!$btnFull.length || !$btnBord.length) { + return; + } + $btnFull.removeClass('active').attr('aria-pressed', 'false'); + $btnBord.removeClass('active').attr('aria-pressed', 'false'); + if (isFullScreen) { + $btnFull.addClass('active').attr('aria-pressed', 'true'); + } else { + $btnBord.addClass('active').attr('aria-pressed', 'true'); + } + if ($modal.hasClass('file-zoom-fullscreen')) { + self._maximizeZoomDialog(); + } else { + if (isFullScreen) { + self._maximizeZoomDialog(); + } else { + $btnBord.removeClass('active').attr('aria-pressed', 'false'); + } + } + }, + _listen: function () { + var self = this, $el = self.$element, $form = self.$form, $cont = self.$container, fullScreenEv; + self._handler($el, 'click', function (e) { + self._initFileSelected(); + if ($el.hasClass('file-no-browse')) { + if ($el.data('zoneClicked')) { + $el.data('zoneClicked', false); + } else { + e.preventDefault(); + } + } + }); + self._handler($el, 'change', $.proxy(self._change, self)); + self._handler(self.$caption, 'paste', $.proxy(self.paste, self)); + if (self.showBrowse) { + self._handler(self.$btnFile, 'click', $.proxy(self._browse, self)); + self._handler(self.$btnFile, 'keypress', function (e) { + var keycode = e.keyCode || e.which; + if (keycode === 13) { + $el.trigger('click'); + self._browse(e); + } + }); + } + self._handler($cont.find('.fileinput-remove:not([disabled])'), 'click', $.proxy(self.clear, self)); + self._handler($cont.find('.fileinput-cancel'), 'click', $.proxy(self.cancel, self)); + self._handler($cont.find('.fileinput-pause'), 'click', $.proxy(self.pause, self)); + self._initDragDrop(); + self._handler($form, 'reset', $.proxy(self.clear, self)); + if (!self.isAjaxUpload) { + self._handler($form, 'submit', $.proxy(self._submitForm, self)); + } + self._handler(self.$container.find('.fileinput-upload'), 'click', $.proxy(self._uploadClick, self)); + self._handler($(window), 'resize', function () { + self._listenFullScreen(screen.width === window.innerWidth && screen.height === window.innerHeight); + }); + fullScreenEv = 'webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange'; + self._handler($(document), fullScreenEv, function () { + self._listenFullScreen($h.checkFullScreen()); + }); + self.$caption.on('focus', function () { + self.$captionContainer.focus(); + }); + self._autoFitContent(); + self._initClickable(); + self._refreshPreview(); + }, + _autoFitContent: function () { + var width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth, + self = this, config = width < 400 ? (self.previewSettingsSmall || self.defaults.previewSettingsSmall) : + (self.previewSettings || self.defaults.previewSettings), sel; + $.each(config, function (cat, settings) { + sel = '.file-preview-frame .file-preview-' + cat; + self.$preview.find(sel + '.kv-preview-data,' + sel + ' .kv-preview-data').css(settings); + }); + }, + _scanDroppedItems: function (item, files, path) { + path = path || ''; + var self = this, i, dirReader, readDir, errorHandler = function (e) { + self._log($h.logMessages.badDroppedFiles); + self._log(e); + }; + if (item.isFile) { + item.file(function (file) { + if (path) { + file.newPath = path + file.name; + } + files.push(file); + }, errorHandler); + } else { + if (item.isDirectory) { + dirReader = item.createReader(); + readDir = function () { + dirReader.readEntries(function (entries) { + if (entries && entries.length > 0) { + for (i = 0; i < entries.length; i++) { + self._scanDroppedItems(entries[i], files, path + item.name + '/'); + } + // recursively call readDir() again, since browser can only handle first 100 entries. + readDir(); + } + return null; + }, errorHandler); + }; + readDir(); + } + } + + }, + _initDragDrop: function () { + var self = this, $zone = self.$dropZone; + if (self.dropZoneEnabled && self.showPreview) { + self._handler($zone, 'dragenter dragover', $.proxy(self._zoneDragEnter, self)); + self._handler($zone, 'dragleave', $.proxy(self._zoneDragLeave, self)); + self._handler($zone, 'drop', $.proxy(self._zoneDrop, self)); + self._handler($(document), 'dragenter dragover drop', self._zoneDragDropInit); + } + }, + _zoneDragDropInit: function (e) { + e.stopPropagation(); + e.preventDefault(); + }, + _zoneDragEnter: function (e) { + var self = this, dt = e.originalEvent.dataTransfer, hasFiles = $.inArray('Files', dt.types) > -1; + self._zoneDragDropInit(e); + if (self.isDisabled || !hasFiles) { + dt.effectAllowed = 'none'; + dt.dropEffect = 'none'; + return; + } + dt.dropEffect = 'copy'; + if (self._raise('fileDragEnter', {'sourceEvent': e, 'files': dt.types.Files})) { + $h.addCss(self.$dropZone, 'file-highlighted'); + } + }, + _zoneDragLeave: function (e) { + var self = this; + self._zoneDragDropInit(e); + if (self.isDisabled) { + return; + } + if (self._raise('fileDragLeave', {'sourceEvent': e})) { + self.$dropZone.removeClass('file-highlighted'); + } + + }, + _dropFiles: function (e, files) { + var self = this, $el = self.$element; + if (!self.isAjaxUpload) { + self.changeTriggered = true; + $el.get(0).files = files; + setTimeout(function () { + self.changeTriggered = false; + $el.trigger('change' + self.namespace); + }, self.processDelay); + } else { + self._change(e, files); + } + self.$dropZone.removeClass('file-highlighted'); + }, + _zoneDrop: function (e) { + /** @namespace e.originalEvent.dataTransfer */ + var self = this, i, $el = self.$element, dt = e.originalEvent.dataTransfer, + files = dt.files, items = dt.items, folders = $h.getDragDropFolders(items); + e.preventDefault(); + if (self.isDisabled || $h.isEmpty(files)) { + return; + } + if (!self._raise('fileDragDrop', {'sourceEvent': e, 'files': files})) { + return; + } + if (folders > 0) { + if (!self.isAjaxUpload) { + self._showFolderError(folders); + return; + } + files = []; + for (i = 0; i < items.length; i++) { + var item = items[i].webkitGetAsEntry(); + if (item) { + self._scanDroppedItems(item, files); + } + } + setTimeout(function () { + self._dropFiles(e, files); + }, 500); + } else { + self._dropFiles(e, files); + } + }, + _uploadClick: function (e) { + var self = this, $btn = self.$container.find('.fileinput-upload'), $form, + isEnabled = !$btn.hasClass('disabled') && $h.isEmpty($btn.attr('disabled')); + if (e && e.isDefaultPrevented()) { + return; + } + if (!self.isAjaxUpload) { + if (isEnabled && $btn.attr('type') !== 'submit') { + e.preventDefault(); + $form = $btn.closest('form'); + // downgrade to normal form submit if possible + if ($form.length) { + $form.trigger('submit'); + } + } + return; + } + e.preventDefault(); + if (isEnabled) { + self.upload(); + } + }, + _submitForm: function () { + var self = this; + return self._isFileSelectionValid() && !self._abort({}); + }, + _clearPreview: function () { + var self = this, + $thumbs = self.showUploadedThumbs ? self.getFrames(':not(.file-preview-success)') : self.getFrames(); + $thumbs.each(function () { + var $thumb = $(this); + $thumb.remove(); + }); + if (!self.getFrames().length || !self.showPreview) { + self._resetUpload(); + } + self._validateDefaultPreview(); + }, + _initSortable: function () { + var self = this, $el = self.$preview, settings, selector = '.' + $h.SORT_CSS, $cont, $body = $('body'), + $html = $('html'), rev = self.reversePreviewOrder, Sortable = window.Sortable, beginGrab, endGrab; + if (!Sortable || $el.find(selector).length === 0) { + return; + } + $cont = $body.length ? $body : ($html.length ? $html : self.$container); + beginGrab = function () { + $cont.addClass('file-grabbing'); + }; + endGrab = function () { + $cont.removeClass('file-grabbing'); + }; + settings = { + handle: '.drag-handle-init', + dataIdAttr: 'data-fileid', + animation: 600, + draggable: selector, + scroll: false, + forceFallback: true, + onChoose: beginGrab, + onStart: beginGrab, + onUnchoose: endGrab, + onEnd: endGrab, + onSort: function (e) { + var oldIndex = e.oldIndex, newIndex = e.newIndex, i = 0, len = self.initialPreviewConfig.length, + exceedsLast = len > 0 && newIndex >= len, $item = $(e.item), $first; + if (exceedsLast) { + newIndex = len - 1; + } + self.initialPreview = $h.moveArray(self.initialPreview, oldIndex, newIndex, rev); + self.initialPreviewConfig = $h.moveArray(self.initialPreviewConfig, oldIndex, newIndex, rev); + self.previewCache.init(); + self.getFrames('.file-preview-initial').each(function () { + $(this).attr('data-fileindex', $h.INIT_FLAG + i); + i++; + }); + if (exceedsLast) { + $first = self.getFrames(':not(.file-preview-initial):first'); + if ($first.length) { + $item.slideUp(function () { + $item.insertBefore($first).slideDown(); + }); + } + } + self._raise('filesorted', { + previewId: $item.attr('id'), + 'oldIndex': oldIndex, + 'newIndex': newIndex, + stack: self.initialPreviewConfig + }); + }, + }; + $.extend(true, settings, self.fileActionSettings.dragSettings); + if (self.sortable) { + self.sortable.destroy(); + } + self.sortable = Sortable.create($el[0], settings); + }, + _setPreviewContent: function (content) { + var self = this; + $h.setHtml(self.$preview, content); + self._autoFitContent(); + }, + _initPreviewImageOrientations: function () { + var self = this, i = 0, canOrientImage = self.canOrientImage; + if (!self.autoOrientImageInitial && !canOrientImage) { + return; + } + self.getFrames('.file-preview-initial').each(function () { + var $thumb = $(this), $img, $zoomImg, id, config = self.initialPreviewConfig[i]; + /** @namespace config.exif */ + if (config && config.exif && config.exif.Orientation) { + id = $thumb.attr('id'); + $img = $thumb.find('>.kv-file-content img'); + $zoomImg = self._getZoom(id, ' >.kv-file-content img'); + if (canOrientImage) { + $img.css('image-orientation', (self.autoOrientImageInitial ? 'from-image' : 'none')); + } else { + self.setImageOrientation($img, $zoomImg, config.exif.Orientation, $thumb); + } + } + i++; + }); + }, + _initPreview: function (isInit) { + var self = this, cap = self.initialCaption || '', out; + if (!self.previewCache.count(true)) { + self._clearPreview(); + if (isInit) { + self._setCaption(cap); + } else { + self._initCaption(); + } + return; + } + out = self.previewCache.out(); + cap = isInit && self.initialCaption ? self.initialCaption : out.caption; + self._setPreviewContent(out.content); + self._setInitThumbAttr(); + self._setCaption(cap); + self._initSortable(); + if (!$h.isEmpty(out.content)) { + self.$container.removeClass('file-input-new'); + } + self._initPreviewImageOrientations(); + }, + _getZoomButton: function (type) { + var self = this, label = self.previewZoomButtonIcons[type], css = self.previewZoomButtonClasses[type], + title = ' title="' + (self.previewZoomButtonTitles[type] || '') + '" ', tag = $h.isBs(5) ? 'bs-' : '', + params = title + (type === 'close' ? ' data-' + tag + 'dismiss="modal" aria-hidden="true"' : ''); + if (type === 'fullscreen' || type === 'borderless' || type === 'toggleheader') { + params += ' data-toggle="button" aria-pressed="false" autocomplete="off"'; + } + return ''; + }, + _getModalContent: function () { + var self = this; + return self._getLayoutTemplate('modal').setTokens({ + 'rtl': self.rtl ? ' kv-rtl' : '', + 'zoomFrameClass': self.frameClass, + 'prev': self._getZoomButton('prev'), + 'next': self._getZoomButton('next'), + 'rotate': self._getZoomButton('rotate'), + 'toggleheader': self._getZoomButton('toggleheader'), + 'fullscreen': self._getZoomButton('fullscreen'), + 'borderless': self._getZoomButton('borderless'), + 'close': self._getZoomButton('close') + }); + }, + _listenModalEvent: function (event) { + var self = this, $modal = self.$modal, getParams = function (e) { + return { + sourceEvent: e, + previewId: $modal.data('previewId'), + modal: $modal + }; + }; + $modal.on(event + '.bs.modal', function (e) { + if (e.namespace !== 'bs.modal') { + return; + } + var $btnFull = $modal.find('.btn-fullscreen'), $btnBord = $modal.find('.btn-borderless'); + if ($modal.data('fileinputPluginId') === self.$element.attr('id')) { + self._raise('filezoom' + event, getParams(e)); + } + if (event === 'shown') { + self._handleRotation($modal, $modal.find('.file-zoom-detail'), $modal.data('angle')); + $btnBord.removeClass('active').attr('aria-pressed', 'false'); + $btnFull.removeClass('active').attr('aria-pressed', 'false'); + if ($modal.hasClass('file-zoom-fullscreen')) { + self._maximizeZoomDialog(); + if ($h.checkFullScreen()) { + $btnFull.addClass('active').attr('aria-pressed', 'true'); + } else { + $btnBord.addClass('active').attr('aria-pressed', 'true'); + } + } + } + }); + }, + _initZoom: function () { + var self = this, $dialog, modalMain = self._getLayoutTemplate('modalMain'), modalId = '#' + $h.MODAL_ID; + modalMain = self._setTabIndex('modal', modalMain); + if (!self.showPreview) { + return; + } + self.$modal = $(modalId); + if (!self.$modal || !self.$modal.length) { + $dialog = $h.createElement($h.cspBuffer.stash(modalMain)).insertAfter(self.$container); + self.$modal = $(modalId).insertBefore($dialog); + $h.cspBuffer.apply(self.$modal); + $dialog.remove(); + } + $h.initModal(self.$modal); + self.$modal.html($h.cspBuffer.stash(self._getModalContent())); + $h.cspBuffer.apply(self.$modal); + $.each($h.MODAL_EVENTS, function (key, event) { + self._listenModalEvent(event); + }); + }, + _initZoomButtons: function () { + var self = this, $modal = self.$modal, previewId = $modal.data('previewId') || '', $first, $last, + thumbs = self.getFrames().toArray(), len = thumbs.length, $prev = $modal.find('.btn-kv-prev'), + $next = $modal.find('.btn-kv-next'), $rotate = $modal.find('.btn-kv-rotate'); + if (thumbs.length < 2) { + $prev.hide(); + $next.hide(); + return; + } else { + $prev.show(); + $next.show(); + } + if (!len) { + return; + } + $first = $(thumbs[0]); + $last = $(thumbs[len - 1]); + $prev.removeAttr('disabled'); + $next.removeAttr('disabled'); + if (self.reversePreviewOrder) { + [$prev, $next] = [$next, $prev]; // swap + } + if ($first.length && $first.attr('id') === previewId) { + $prev.attr('disabled', true); + } + if ($last.length && $last.attr('id') === previewId) { + $next.attr('disabled', true); + } + }, + _maximizeZoomDialog: function () { + var self = this, $modal = self.$modal, $head = $modal.find('.modal-header:visible'), + $foot = $modal.find('.modal-footer:visible'), $body = $modal.find('.kv-zoom-body'), + h = $(window).height(), diff = 0; + $modal.addClass('file-zoom-fullscreen'); + if ($head && $head.length) { + h -= $head.outerHeight(true); + } + if ($foot && $foot.length) { + h -= $foot.outerHeight(true); + } + if ($body && $body.length) { + diff = $body.outerHeight(true) - $body.height(); + h -= diff; + } + $modal.find('.kv-zoom-body').height(h); + }, + _resizeZoomDialog: function (fullScreen) { + var self = this, $modal = self.$modal, $btnFull = $modal.find('.btn-kv-fullscreen'), + $btnBord = $modal.find('.btn-kv-borderless'); + if ($modal.hasClass('file-zoom-fullscreen')) { + $h.toggleFullScreen(false); + if (!fullScreen) { + if (!$btnFull.hasClass('active')) { + $modal.removeClass('file-zoom-fullscreen'); + self.$modal.find('.kv-zoom-body').css('height', self.zoomModalHeight); + } else { + $btnFull.removeClass('active').attr('aria-pressed', 'false'); + } + } else { + if (!$btnFull.hasClass('active')) { + $modal.removeClass('file-zoom-fullscreen'); + self._resizeZoomDialog(true); + if ($btnBord.hasClass('active')) { + $btnBord.removeClass('active').attr('aria-pressed', 'false'); + } + } + } + } else { + if (!fullScreen) { + self._maximizeZoomDialog(); + return; + } + $h.toggleFullScreen(true); + } + $modal.focus(); + }, + _setZoomContent: function ($frame, navigate) { + var self = this, $content, tmplt, body, title, $body, $dataEl, config, previewId = $frame.attr('id'), + $zoomPreview = self._getZoom(previewId), $modal = self.$modal, $tmp, desc, $desc, + $btnFull = $modal.find('.btn-kv-fullscreen'), $btnBord = $modal.find('.btn-kv-borderless'), cap, size, + $btnTogh = $modal.find('.btn-kv-toggleheader'), dir = navigate === 'prev' ? 'Left' : 'Right', + slideIn = 'slideIn' + dir, slideOut = 'slideOut' + dir, parsed, zoomData = $frame.data('zoom'); + if (zoomData) { + zoomData = decodeURIComponent(zoomData); + parsed = $zoomPreview.html().replace(self.zoomPlaceholder, '').setTokens({zoomData: zoomData}); + $zoomPreview.html(parsed); + $frame.data('zoom', ''); + $zoomPreview.attr('data-zoom', zoomData); + } + tmplt = $zoomPreview.attr('data-template') || 'generic'; + $content = $zoomPreview.find('.kv-file-content'); + body = $content.length ? $content.html() : ''; + cap = $frame.data('caption') || self.msgZoomModalHeading; + size = $frame.data('size') || ''; + desc = $frame.data('description') || ''; + $modal.find('.kv-zoom-caption').attr('title', cap).html(cap); + $modal.find('.kv-zoom-size').html(size); + $desc = $modal.find('.kv-zoom-description').hide(); + if (desc) { + if (self.showDescriptionClose) { + desc = self._getLayoutTemplate('descriptionClose').setTokens({ + closeIcon: self.previewZoomButtonIcons.close + }) + '' + desc; + } + $desc.show().html(desc); + if (self.showDescriptionClose) { + self._handler($modal.find('.kv-desc-hide'), 'click', function () { + $(this).parent().fadeOut('fast', function () { + $modal.focus(); + }); + }); + } + } + $body = $modal.find('.kv-zoom-body'); + $modal.removeClass('kv-single-content'); + if (navigate) { + $tmp = $body.addClass('file-thumb-loading').clone().insertAfter($body); + $h.setHtml($body, body).hide(); + $tmp.fadeOut('fast', function () { + $body.fadeIn('fast', function () { + $body.removeClass('file-thumb-loading'); + }); + $tmp.remove(); + }); + } else { + $h.setHtml($body, body); + } + config = self.previewZoomSettings[tmplt]; + if (config) { + $dataEl = $body.find('.kv-preview-data'); + $h.addCss($dataEl, 'file-zoom-detail'); + $.each(config, function (key, value) { + $dataEl.css(key, value); + if (($dataEl.attr('width') && key === 'width') || ($dataEl.attr('height') && key === 'height')) { + $dataEl.removeAttr(key); + } + }); + } + $modal.data('previewId', previewId); + self._handler($modal.find('.btn-kv-prev'), 'click', function () { + self._zoomSlideShow('prev', previewId); + }); + self._handler($modal.find('.btn-kv-next'), 'click', function () { + self._zoomSlideShow('next', previewId); + }); + self._handler($btnFull, 'click', function () { + self._resizeZoomDialog(true); + }); + self._handler($btnBord, 'click', function () { + self._resizeZoomDialog(false); + }); + self._handler($btnTogh, 'click', function () { + var $header = $modal.find('.modal-header'), $floatBar = $modal.find('.floating-buttons'), + ht, $actions = $header.find('.kv-zoom-actions'), resize = function (height) { + var $body = self.$modal.find('.kv-zoom-body'), h = self.zoomModalHeight; + if ($modal.hasClass('file-zoom-fullscreen')) { + h = $body.outerHeight(true); + if (!height) { + h = h - $header.outerHeight(true); + } + } + $body.css('height', height ? h + height : h); + }; + if ($header.is(':visible')) { + ht = $header.outerHeight(true); + $header.slideUp('slow', function () { + $actions.find('.btn').appendTo($floatBar); + resize(ht); + }); + } else { + $floatBar.find('.btn').appendTo($actions); + $header.slideDown('slow', function () { + resize(); + }); + } + $modal.focus(); + }); + self._handler($modal, 'keydown', function (e) { + var key = e.which || e.keyCode, delay = self.processDelay + 1, $prev = $(this).find('.btn-kv-prev'), + $next = $(this).find('.btn-kv-next'), vId = $(this).data('previewId'), vPrevKey, vNextKey; + [vPrevKey, vNextKey] = self.rtl ? [39, 37] : [37, 39]; + $.each({prev: [$prev, vPrevKey], next: [$next, vNextKey]}, function (direction, config) { + var $btn = config[0], vKey = config[1]; + if (key === vKey && $btn.length) { + $modal.focus(); + if (!$btn.attr('disabled')) { + $btn.blur(); + setTimeout(function () { + $btn.focus(); + self._zoomSlideShow(direction, vId); + setTimeout(function () { + if ($btn.attr('disabled')) { + $modal.focus(); + } + }, delay); + }, delay); + } + } + }); + }); + }, + _showModal: function ($frame) { + var self = this, $modal = self.$modal, $content, css, angle; + if (!$frame || !$frame.length) { + return; + } + $h.initModal($modal); + $h.setHtml($modal, self._getModalContent()); + self._setZoomContent($frame); + $modal.removeClass('rotatable'); + $modal.data({backdrop: false, fileinputPluginId: self.$element.attr('id')}); + $modal.find('.kv-zoom-body').css('height', self.zoomModalHeight); + $content = $frame.find('.kv-file-content > :first-child'); + if ($content.length) { + css = $content.css('transform'); + if (css) { + $modal.find('.file-zoom-detail').css('transform', css); + } + } + if ($frame.hasClass('rotatable')) { + $modal.addClass('rotatable'); + } + if ($frame.data('angle')) { + $modal.data('angle', $frame.data('angle')); + } + angle = ($frame.data('angle') || 0); + $modal.modal('show'); + self._initZoomButtons(); + self._initRotateZoom($frame, $content); + }, + _zoomPreview: function ($btn) { + var self = this, $frame; + if (!$btn.length) { + throw 'Cannot zoom to detailed preview!'; + } + $frame = $btn.closest($h.FRAMES); + self._showModal($frame); + }, + _zoomSlideShow: function (dir, previewId) { + var self = this, $modal = self.$modal, $btn = $modal.find('.kv-zoom-actions .btn-kv-' + dir), $targFrame, i, + $thumb, thumbsData = self.getFrames().toArray(), thumbs = [], len = thumbsData.length, out, angle, + $content; + if (self.reversePreviewOrder) { + dir = dir === 'prev' ? 'next' : 'prev'; + } + if ($btn.attr('disabled')) { + return; + } + for (i = 0; i < len; i++) { + $thumb = $(thumbsData[i]); + if ($thumb && $thumb.length && $thumb.find('.kv-file-zoom:visible').length) { + thumbs.push(thumbsData[i]); + } + } + len = thumbs.length; + for (i = 0; i < len; i++) { + if ($(thumbs[i]).attr('id') === previewId) { + out = dir === 'prev' ? i - 1 : i + 1; + break; + } + } + if (out < 0 || out >= len || !thumbs[out]) { + return; + } + $targFrame = $(thumbs[out]); + if ($targFrame.length) { + self._setZoomContent($targFrame, dir); + } + self._initZoomButtons(); + if ($targFrame.length && $targFrame.hasClass('rotatable')) { + angle = $targFrame.data('angle') || 0; + $modal.addClass('rotatable').data('angle', angle); + $content = $targFrame.find('.kv-file-content > :first-child'); + self._initRotateZoom($targFrame, $content); + } else { + $modal.removeClass('rotatable').removeData('angle'); + } + self._raise('filezoom' + dir, {'previewId': previewId, modal: self.$modal}); + }, + _initZoomButton: function () { + var self = this; + self.$preview.find('.kv-file-zoom').each(function () { + var $el = $(this); + self._handler($el, 'click', function () { + self._zoomPreview($el); + }); + }); + }, + _inputFileCount: function () { + return this.$element[0].files.length; + }, + _refreshPreview: function () { + var self = this, files; + if ((!self._inputFileCount() && !self.isAjaxUpload) || !self.showPreview || !self.isPreviewable) { + return; + } + if (self.isAjaxUpload) { + if (self.fileManager.count() > 0) { + files = $.extend(true, [], self.getFileList()); + self.fileManager.clear(); + self._clearFileInput(); + } else { + files = self.$element[0].files; + } + } else { + files = self.$element[0].files; + } + if (files && files.length) { + self.readFiles(files); + } + }, + _clearObjects: function ($el) { + $el.find('video audio').each(function () { + this.pause(); + $(this).remove(); + }); + $el.find('img object div').each(function () { + $(this).remove(); + }); + }, + _clearFileInput: function () { + var self = this, $el = self.$element, $srcFrm, $tmpFrm, $tmpEl; + if (!self._inputFileCount()) { + return; + } + $srcFrm = $el.closest('form'); + $tmpFrm = $(document.createElement('form')); + $tmpEl = $(document.createElement('div')); + $el.before($tmpEl); + if ($srcFrm.length) { + $srcFrm.after($tmpFrm); + } else { + $tmpEl.after($tmpFrm); + } + $tmpFrm.append($el).trigger('reset'); + $tmpEl.before($el).remove(); + $tmpFrm.remove(); + }, + _resetUpload: function () { + var self = this; + self.uploadInitiated = false; + self.uploadStartTime = $h.now(); + self.uploadCache = []; + self.$btnUpload.removeAttr('disabled'); + self._setProgress(0); + self._hideProgress(); + self._resetErrors(false); + self._initAjax(); + self.fileManager.clearImages(); + self._resetCanvas(); + if (self.overwriteInitial) { + self.initialPreview = []; + self.initialPreviewConfig = []; + self.initialPreviewThumbTags = []; + self.previewCache.data = { + content: [], + config: [], + tags: [] + }; + } + }, + _resetCanvas: function () { + var self = this; + if (self.imageCanvas && self.imageCanvasContext) { + self.imageCanvasContext.clearRect(0, 0, self.imageCanvas.width, self.imageCanvas.height); + } + }, + _hasInitialPreview: function () { + var self = this; + return !self.overwriteInitial && self.previewCache.count(true); + }, + _resetPreview: function () { + var self = this, out, cap, $div, hasSuc = self.showUploadedThumbs, hasErr = !self.removeFromPreviewOnError, + includeProcessed = (hasSuc || hasErr) && self.isDuplicateError; + if (self.previewCache.count(true)) { + out = self.previewCache.out(); + if (includeProcessed) { + $div = $h.createElement('').insertAfter(self.$container); + self.getFrames().each(function () { + var $thumb = $(this); + if ((hasSuc && $thumb.hasClass('file-preview-success')) || + (hasErr && $thumb.hasClass('file-preview-error'))) { + $div.append($thumb); + } + }); + } + self._setPreviewContent(out.content); + self._setInitThumbAttr(); + cap = self.initialCaption ? self.initialCaption : out.caption; + self._setCaption(cap); + if (includeProcessed) { + $div.contents().appendTo(self.$preview); + $div.remove(); + } + } else { + self._clearPreview(); + self._initCaption(); + } + if (self.showPreview) { + self._initZoom(); + self._initSortable(); + } + self.isDuplicateError = false; + }, + _clearDefaultPreview: function () { + var self = this; + self.$preview.find('.file-default-preview').remove(); + }, + _validateDefaultPreview: function () { + var self = this; + if (!self.showPreview || $h.isEmpty(self.defaultPreviewContent)) { + return; + } + self._setPreviewContent('
    ' + self.defaultPreviewContent + '
    '); + self.$container.removeClass('file-input-new'); + self._initClickable(); + }, + _resetPreviewThumbs: function (isAjax) { + var self = this, out; + if (isAjax) { + self._clearPreview(); + self.clearFileStack(); + return; + } + if (self._hasInitialPreview()) { + out = self.previewCache.out(); + self._setPreviewContent(out.content); + self._setInitThumbAttr(); + self._setCaption(out.caption); + self._initPreviewActions(); + } else { + self._clearPreview(); + } + }, + _getLayoutTemplate: function (t) { + var self = this, template = self.layoutTemplates[t]; + if ($h.isEmpty(self.customLayoutTags)) { + return template; + } + return $h.replaceTags(template, self.customLayoutTags); + }, + _getPreviewTemplate: function (t) { + var self = this, templates = self.previewTemplates, template = templates[t] || templates.other; + if ($h.isEmpty(self.customPreviewTags)) { + return template; + } + return $h.replaceTags(template, self.customPreviewTags); + }, + _getOutData: function (formdata, jqXHR, responseData, filesData) { + var self = this; + jqXHR = jqXHR || {}; + responseData = responseData || {}; + filesData = filesData || self.fileManager.list(); + return { + formdata: formdata, + files: filesData, + filenames: self.filenames, + filescount: self.getFilesCount(), + extra: self._getExtraData(), + response: responseData, + reader: self.reader, + jqXHR: jqXHR + }; + }, + _getMsgSelected: function (n, processing) { + var self = this, strFiles = n === 1 ? self.fileSingle : self.filePlural; + return n > 0 ? self.msgSelected.replace('{n}', n).replace('{files}', strFiles) : + (processing ? self.msgProcessing : self.msgNoFilesSelected); + }, + _getFrame: function (id, skipWarning) { + var self = this, $frame = $h.getFrameElement(self.$preview, id); + if (self.showPreview && !skipWarning && !$frame.length) { + self._log($h.logMessages.invalidThumb, {id: id}); + } + return $frame; + }, + _getZoom: function (id, selector) { + var self = this, $frame = $h.getZoomElement(self.$preview, id, selector); + if (self.showPreview && !$frame.length) { + self._log($h.logMessages.invalidThumb, {id: id}); + } + return $frame; + }, + _getThumbs: function (css) { + css = css || ''; + return this.getFrames(':not(.file-preview-initial)' + css); + }, + _getThumbId: function (fileId) { + var self = this; + return self.previewInitId + '-' + fileId; + }, + _getExtraData: function (fileId, index) { + var self = this, data = self.uploadExtraData; + if (typeof self.uploadExtraData === 'function') { + data = self.uploadExtraData(fileId, index); + } + return data; + }, + _initXhr: function (xhrobj, fileId) { + var self = this, fm = self.fileManager, func = function (event) { + var pct = 0, total = event.total, loaded = event.loaded || event.position, + stats = fm.getUploadStats(fileId, loaded, total); + /** @namespace event.lengthComputable */ + if (event.lengthComputable && !self.enableResumableUpload) { + pct = $h.round(loaded / total * 100); + } + if (fileId) { + self._setFileUploadStats(fileId, pct, stats); + } else { + self._setProgress(pct, null, null, self._getStats(stats)); + } + self._raise('fileajaxprogress', [stats]); + }; + if (xhrobj.upload) { + if (self.progressDelay) { + func = $h.debounce(func, self.progressDelay); + } + xhrobj.upload.addEventListener('progress', func, false); + } + return xhrobj; + }, + _initAjaxSettings: function () { + var self = this; + self._ajaxSettings = $.extend(true, {}, self.ajaxSettings); + self._ajaxDeleteSettings = $.extend(true, {}, self.ajaxDeleteSettings); + }, + _mergeAjaxCallback: function (funcName, srcFunc, type) { + var self = this, settings = self._ajaxSettings, flag = self.mergeAjaxCallbacks, targFunc; + if (type === 'delete') { + settings = self._ajaxDeleteSettings; + flag = self.mergeAjaxDeleteCallbacks; + } + targFunc = settings[funcName]; + if (flag && typeof targFunc === 'function') { + if (flag === 'before') { + settings[funcName] = function () { + targFunc.apply(this, arguments); + srcFunc.apply(this, arguments); + }; + } else { + settings[funcName] = function () { + srcFunc.apply(this, arguments); + targFunc.apply(this, arguments); + }; + } + } else { + settings[funcName] = srcFunc; + } + }, + _ajaxSubmit: function (fnBefore, fnSuccess, fnComplete, fnError, formdata, fileId, index, vUrl) { + var self = this, settings, defaults, data, tm = self.taskManager; + if (!self._raise('filepreajax', [formdata, fileId, index])) { + return; + } + formdata.append('initialPreview', JSON.stringify(self.initialPreview)); + formdata.append('initialPreviewConfig', JSON.stringify(self.initialPreviewConfig)); + formdata.append('initialPreviewThumbTags', JSON.stringify(self.initialPreviewThumbTags)); + self._initAjaxSettings(); + self._mergeAjaxCallback('beforeSend', fnBefore); + self._mergeAjaxCallback('success', fnSuccess); + self._mergeAjaxCallback('complete', fnComplete); + self._mergeAjaxCallback('error', fnError); + vUrl = vUrl || self.uploadUrlThumb || self.uploadUrl; + if (typeof vUrl === 'function') { + vUrl = vUrl(); + } + data = self._getExtraData(fileId, index) || {}; + if (typeof data === 'object') { + $.each(data, function (key, value) { + formdata.append(key, value); + }); + } + defaults = { + xhr: function () { + var xhrobj = $.ajaxSettings.xhr(); + return self._initXhr(xhrobj, fileId); + }, + url: self._encodeURI(vUrl), + type: 'POST', + dataType: 'json', + data: formdata, + cache: false, + processData: false, + contentType: false + }; + settings = $.extend(true, {}, defaults, self._ajaxSettings); + self.ajaxQueue.push(settings); + tm.addTask(fileId + '-' + index, function () { + var self = this.self, config, xhr; + config = self.ajaxQueue.shift(); + xhr = $.ajax(config); + self.ajaxRequests.push(xhr); + }).runWithContext({self: self}); + }, + _mergeArray: function (prop, content) { + var self = this, arr1 = $h.cleanArray(self[prop]), arr2 = $h.cleanArray(content); + self[prop] = arr1.concat(arr2); + }, + _initUploadSuccess: function (out, $thumb, allFiles) { + var self = this, append, data, index, $div, content, config, tags, id, i; + if (!self.showPreview || typeof out !== 'object' || $.isEmptyObject(out)) { + self._resetCaption(); + return; + } + if (out.initialPreview !== undefined && out.initialPreview.length > 0) { + self.hasInitData = true; + content = out.initialPreview || []; + config = out.initialPreviewConfig || []; + tags = out.initialPreviewThumbTags || []; + append = out.append === undefined || out.append; + if (content.length > 0 && !$h.isArray(content)) { + content = content.split(self.initialPreviewDelimiter); + } + if (content.length) { + self._mergeArray('initialPreview', content); + self._mergeArray('initialPreviewConfig', config); + self._mergeArray('initialPreviewThumbTags', tags); + } + if ($thumb !== undefined) { + if (!allFiles) { + index = self.previewCache.add(content[0], config[0], tags[0], append); + data = self.previewCache.get(index, false); + $div = $h.createElement(data).hide().appendTo($thumb); + $thumb.fadeOut('slow', function () { + var $newThumb = $div.find('> .file-preview-frame'); + if ($newThumb && $newThumb.length) { + $newThumb.insertBefore($thumb).fadeIn('slow').css('display:inline-block'); + } + self._initPreviewActions(); + self._clearFileInput(); + $thumb.remove(); + $div.remove(); + self._initSortable(); + }); + } else { + id = $thumb.attr('id'); + i = self._getUploadCacheIndex(id); + if (i !== null) { + self.uploadCache[i] = { + id: id, + content: content[0], + config: config[0] || [], + tags: tags[0] || [], + append: append + }; + } + } + } else { + self.previewCache.set(content, config, tags, append); + self._initPreview(); + self._initPreviewActions(); + } + } + self._resetCaption(); + }, + _getUploadCacheIndex: function (id) { + var self = this, i, len = self.uploadCache.length, config; + for (i = 0; i < len; i++) { + config = self.uploadCache[i]; + if (config.id === id) { + return i; + } + } + return null; + }, + _initSuccessThumbs: function () { + var self = this; + if (!self.showPreview) { + return; + } + setTimeout(function () { + self._getThumbs($h.FRAMES + '.file-preview-success').each(function () { + var $thumb = $(this), $remove = $thumb.find('.kv-file-remove'); + $remove.removeAttr('disabled'); + self._handler($remove, 'click', function () { + var id = $thumb.attr('id'), + out = self._raise('filesuccessremove', [id, $thumb.attr('data-fileindex')]); + $h.cleanMemory($thumb); + if (out === false) { + return; + } + self.$caption.attr('title', ''); + $thumb.fadeOut('slow', function () { + var fm = self.fileManager; + $thumb.remove(); + if (!self.getFrames().length) { + self.reset(); + } + }); + }); + }); + }, self.processDelay); + }, + _updateInitialPreview: function () { + var self = this, u = self.uploadCache; + if (self.showPreview) { + $.each(u, function (key, setting) { + self.previewCache.add(setting.content, setting.config, setting.tags, setting.append); + }); + if (self.hasInitData) { + self._initPreview(); + self._initPreviewActions(); + } + } + }, + _getThumbFileId: function ($thumb) { + var self = this; + if (self.showPreview && $thumb !== undefined) { + return $thumb.attr('data-fileid'); + } + return null; + }, + _getThumbFile: function ($thumb) { + var self = this, id = self._getThumbFileId($thumb); + return id ? self.fileManager.getFile(id) : null; + }, + _uploadSingle: function (i, id, isBatch, deferrer) { + var self = this, fm = self.fileManager, count = fm.count(), formdata = new FormData(), outData, + previewId = self._getThumbId(id), $thumb, chkComplete, $btnUpload, $btnDelete, + hasPostData = count > 0 || !$.isEmptyObject(self.uploadExtraData), uploadFailed, $prog, fnBefore, + errMsg, fnSuccess, fnComplete, fnError, updateUploadLog, op = self.ajaxOperations.uploadThumb, + fileObj = fm.getFile(id), params = {id: previewId, index: i, fileId: id}, + fileName = self.fileManager.getFileName(id, true), resolve = function () { + if (deferrer && deferrer.resolve) { + deferrer.resolve(); + } + }, reject = function () { + if (deferrer && deferrer.reject) { + deferrer.reject(); + } + }; + if (self.enableResumableUpload) { // not enabled for resumable uploads + return; + } + self.uploadInitiated = true; + if (self.showPreview) { + $thumb = fm.getThumb(id); + $prog = $thumb.find('.file-thumb-progress'); + $btnUpload = $thumb.find('.kv-file-upload'); + $btnDelete = $thumb.find('.kv-file-remove'); + $prog.show(); + } + if (count === 0 || !hasPostData || (self.showPreview && $btnUpload && $btnUpload.hasClass('disabled')) || + self._abort(params)) { + return; + } + updateUploadLog = function () { + if (!uploadFailed) { + fm.removeFile(id); + } else { + fm.errors.push(id); + } + fm.setProcessed(id); + if (fm.isProcessed()) { + self.fileBatchCompleted = true; + chkComplete(); + } + }; + chkComplete = function () { + var $initThumbs; + if (!self.fileBatchCompleted) { + return; + } + setTimeout(function () { + var triggerReset = fm.count() === 0, errCount = fm.errors.length; + self._updateInitialPreview(); + self.unlock(triggerReset); + if (triggerReset) { + self._clearFileInput(); + } + $initThumbs = self.$preview.find('.file-preview-initial'); + if (self.uploadAsync && $initThumbs.length) { + $h.addCss($initThumbs, $h.SORT_CSS); + self._initSortable(); + } + self._raise('filebatchuploadcomplete', [fm.stack, self._getExtraData()]); + if (!self.retryErrorUploads || errCount === 0) { + fm.clear(); + } + self._setProgress(101); + self.ajaxAborted = false; + self.uploadInitiated = false; + }, self.processDelay); + }; + fnBefore = function (jqXHR) { + outData = self._getOutData(formdata, jqXHR); + fm.initStats(id); + self.fileBatchCompleted = false; + if (!isBatch) { + self.ajaxAborted = false; + } + if (self.showPreview) { + if (!$thumb.hasClass('file-preview-success')) { + self._setThumbStatus($thumb, 'Loading'); + $h.addCss($thumb, 'file-uploading'); + } + $btnUpload.attr('disabled', true); + $btnDelete.attr('disabled', true); + } + if (!isBatch) { + self.lock(); + } + if (fm.errors.indexOf(id) !== -1) { + delete fm.errors[id]; + } + self._raise('filepreupload', [outData, previewId, i, self._getThumbFileId($thumb)]); + $.extend(true, params, outData); + if (self._abort(params)) { + jqXHR.abort(); + + if (!isBatch) { + self._setThumbStatus($thumb, 'New'); + $thumb.removeClass('file-uploading'); + $btnUpload.removeAttr('disabled'); + $btnDelete.removeAttr('disabled'); + } + self._setProgressCancelled(); + } + }; + fnSuccess = function (data, textStatus, jqXHR) { + var pid = self.showPreview && $thumb.attr('id') ? $thumb.attr('id') : previewId; + outData = self._getOutData(formdata, jqXHR, data); + $.extend(true, params, outData); + setTimeout(function () { + if ($h.isEmpty(data) || $h.isEmpty(data.error)) { + if (self.showPreview) { + self._setThumbStatus($thumb, 'Success'); + $btnUpload.hide(); + self._initUploadSuccess(data, $thumb, isBatch); + self._setProgress(101, $prog); + } + self._raise('fileuploaded', [outData, pid, i, self._getThumbFileId($thumb)]); + if (!isBatch) { + self.fileManager.remove($thumb); + } else { + updateUploadLog(); + resolve(); + } + } else { + uploadFailed = true; + errMsg = self._parseError(op, jqXHR, self.msgUploadError, self.fileManager.getFileName(id)); + self._showFileError(errMsg, params); + self._setPreviewError($thumb, true); + if (!self.retryErrorUploads) { + $btnUpload.hide(); + } + if (isBatch) { + updateUploadLog(); + resolve(); + } + self._setProgress(101, self._getFrame(pid).find('.file-thumb-progress'), + self.msgUploadError); + } + }, self.processDelay); + }; + fnComplete = function () { + if (self.showPreview) { + $btnUpload.removeAttr('disabled'); + $btnDelete.removeAttr('disabled'); + $thumb.removeClass('file-uploading'); + } + if (!isBatch) { + self.unlock(false); + self._clearFileInput(); + } else { + chkComplete(); + } + self._initSuccessThumbs(); + }; + fnError = function (jqXHR, textStatus, errorThrown) { + errMsg = self._parseError(op, jqXHR, errorThrown, self.fileManager.getFileName(id)); + uploadFailed = true; + setTimeout(function () { + var $prog; + if (isBatch) { + updateUploadLog(); + reject(); + } + self.fileManager.setProgress(id, 100); + self._setPreviewError($thumb, true); + if (!self.retryErrorUploads) { + $btnUpload.hide(); + } + $.extend(true, params, self._getOutData(formdata, jqXHR)); + self._setProgress(101, self.$progress, self.msgAjaxProgressError.replace('{operation}', op)); + $prog = self.showPreview && $thumb ? $thumb.find('.file-thumb-progress') : ''; + self._setProgress(101, $prog, self.msgUploadError); + self._showFileError(errMsg, params); + }, self.processDelay); + }; + self._setFileData(formdata, fileObj.file, fileName, id); + self._setUploadData(formdata, {fileId: id}); + self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError, formdata, id, i); + }, + _setFileData: function (formdata, file, fileName, fileId) { + var self = this, preProcess = self.preProcessUpload; + if (preProcess && typeof preProcess === 'function') { + formdata.append(self.uploadFileAttr, preProcess(fileId, file)); + } else { + formdata.append(self.uploadFileAttr, file, fileName); + } + }, + _checkBatchPreupload: function (outData, jqXHR) { + var self = this, out = self._raise('filebatchpreupload', [outData]); + if (out) { + return true; + } + self._abort(outData); + if (jqXHR) { + jqXHR.abort(); + } + self._getThumbs().each(function () { + var $thumb = $(this), $btnUpload = $thumb.find('.kv-file-upload'), + $btnDelete = $thumb.find('.kv-file-remove'); + if ($thumb.hasClass('file-preview-loading')) { + self._setThumbStatus($thumb, 'New'); + $thumb.removeClass('file-uploading'); + } + $btnUpload.removeAttr('disabled'); + $btnDelete.removeAttr('disabled'); + }); + self._setProgressCancelled(); + return false; + }, + _uploadBatch: function () { + var self = this, fm = self.fileManager, total = fm.total(), params = {}, fnBefore, fnSuccess, fnError, + fnComplete, hasPostData = total > 0 || !$.isEmptyObject(self.uploadExtraData), errMsg, + setAllUploaded, formdata = new FormData(), op = self.ajaxOperations.uploadBatch; + if (total === 0 || !hasPostData || self._abort(params)) { + return; + } + setAllUploaded = function () { + self.fileManager.clear(); + self._clearFileInput(); + }; + fnBefore = function (jqXHR) { + self.lock(); + fm.initStats(); + var outData = self._getOutData(formdata, jqXHR); + self.ajaxAborted = false; + if (self.showPreview) { + self._getThumbs().each(function () { + var $thumb = $(this), $btnUpload = $thumb.find('.kv-file-upload'), + $btnDelete = $thumb.find('.kv-file-remove'); + if (!$thumb.hasClass('file-preview-success')) { + self._setThumbStatus($thumb, 'Loading'); + $h.addCss($thumb, 'file-uploading'); + } + $btnUpload.attr('disabled', true); + $btnDelete.attr('disabled', true); + }); + } + self._checkBatchPreupload(outData, jqXHR); + }; + fnSuccess = function (data, textStatus, jqXHR) { + /** @namespace data.errorkeys */ + var outData = self._getOutData(formdata, jqXHR, data), key = 0, + $thumbs = self._getThumbs(':not(.file-preview-success)'), + keys = $h.isEmpty(data) || $h.isEmpty(data.errorkeys) ? [] : data.errorkeys; + + if ($h.isEmpty(data) || $h.isEmpty(data.error)) { + self._raise('filebatchuploadsuccess', [outData]); + setAllUploaded(); + if (self.showPreview) { + $thumbs.each(function () { + var $thumb = $(this); + self._setThumbStatus($thumb, 'Success'); + $thumb.removeClass('file-uploading'); + $thumb.find('.kv-file-upload').hide().removeAttr('disabled'); + }); + self._initUploadSuccess(data); + } else { + self.reset(); + } + self._setProgress(101); + } else { + if (self.showPreview) { + $thumbs.each(function () { + var $thumb = $(this); + $thumb.removeClass('file-uploading'); + $thumb.find('.kv-file-upload').removeAttr('disabled'); + $thumb.find('.kv-file-remove').removeAttr('disabled'); + if (keys.length === 0 || $.inArray(key, keys) !== -1) { + self._setPreviewError($thumb, true); + if (!self.retryErrorUploads) { + $thumb.find('.kv-file-upload').hide(); + self.fileManager.remove($thumb); + } + } else { + $thumb.find('.kv-file-upload').hide(); + self._setThumbStatus($thumb, 'Success'); + self.fileManager.remove($thumb); + } + if (!$thumb.hasClass('file-preview-error') || self.retryErrorUploads) { + key++; + } + }); + self._initUploadSuccess(data); + } + errMsg = self._parseError(op, jqXHR, self.msgUploadError); + self._showFileError(errMsg, outData, 'filebatchuploaderror'); + self._setProgress(101, self.$progress, self.msgUploadError); + } + }; + fnComplete = function () { + self.unlock(); + self._initSuccessThumbs(); + self._clearFileInput(); + self._raise('filebatchuploadcomplete', [self.fileManager.stack, self._getExtraData()]); + }; + fnError = function (jqXHR, textStatus, errorThrown) { + var outData = self._getOutData(formdata, jqXHR); + errMsg = self._parseError(op, jqXHR, errorThrown); + self._showFileError(errMsg, outData, 'filebatchuploaderror'); + self.uploadFileCount = total - 1; + if (!self.showPreview) { + return; + } + self._getThumbs().each(function () { + var $thumb = $(this); + $thumb.removeClass('file-uploading'); + if (self._getThumbFile($thumb)) { + self._setPreviewError($thumb); + } + }); + self._getThumbs().removeClass('file-uploading'); + self._getThumbs(' .kv-file-upload').removeAttr('disabled'); + self._getThumbs(' .kv-file-delete').removeAttr('disabled'); + self._setProgress(101, self.$progress, self.msgAjaxProgressError.replace('{operation}', op)); + }; + var ctr = 0; + $.each(self.fileManager.stack, function (key, data) { + if (!$h.isEmpty(data.file)) { + self._setFileData(formdata, data.file, (data.nameFmt || ('untitled_' + ctr)), key); + } + ctr++; + }); + self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError, formdata); + }, + _uploadExtraOnly: function () { + var self = this, params = {}, fnBefore, fnSuccess, fnComplete, fnError, formdata = new FormData(), errMsg, + op = self.ajaxOperations.uploadExtra; + fnBefore = function (jqXHR) { + self.lock(); + var outData = self._getOutData(formdata, jqXHR); + self._setProgress(50); + params.data = outData; + params.xhr = jqXHR; + self._checkBatchPreupload(outData, jqXHR); + }; + fnSuccess = function (data, textStatus, jqXHR) { + var outData = self._getOutData(formdata, jqXHR, data); + if ($h.isEmpty(data) || $h.isEmpty(data.error)) { + self._raise('filebatchuploadsuccess', [outData]); + self._clearFileInput(); + self._initUploadSuccess(data); + self._setProgress(101); + } else { + errMsg = self._parseError(op, jqXHR, self.msgUploadError); + self._showFileError(errMsg, outData, 'filebatchuploaderror'); + } + }; + fnComplete = function () { + self.unlock(); + self._clearFileInput(); + self._raise('filebatchuploadcomplete', [self.fileManager.stack, self._getExtraData()]); + }; + fnError = function (jqXHR, textStatus, errorThrown) { + var outData = self._getOutData(formdata, jqXHR); + errMsg = self._parseError(op, jqXHR, errorThrown); + params.data = outData; + self._showFileError(errMsg, outData, 'filebatchuploaderror'); + self._setProgress(101, self.$progress, self.msgAjaxProgressError.replace('{operation}', op)); + }; + self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError, formdata); + }, + _deleteFileIndex: function ($frame) { + var self = this, ind = $frame.attr('data-fileindex'), rev = self.reversePreviewOrder; + if (ind.substring(0, 5) === $h.INIT_FLAG) { + ind = parseInt(ind.replace($h.INIT_FLAG, '')); + self.initialPreview = $h.spliceArray(self.initialPreview, ind, rev); + self.initialPreviewConfig = $h.spliceArray(self.initialPreviewConfig, ind, rev); + self.initialPreviewThumbTags = $h.spliceArray(self.initialPreviewThumbTags, ind, rev); + self.getFrames().each(function () { + var $nFrame = $(this), nInd = $nFrame.attr('data-fileindex'); + if (nInd.substring(0, 5) === $h.INIT_FLAG) { + nInd = parseInt(nInd.replace($h.INIT_FLAG, '')); + if (nInd > ind) { + nInd--; + $nFrame.attr('data-fileindex', $h.INIT_FLAG + nInd); + } + } + }); + } + }, + _resetCaption: function () { + var self = this; + setTimeout(function () { + var cap = '', n, chk = self.previewCache.count(true), len = self.fileManager.count(), file, + incomplete = ':not(.file-preview-success):not(.file-preview-error)', cfg, + hasThumb = self.showPreview && self.getFrames(incomplete).length; + if (len === 0 && chk === 0 && !hasThumb) { + self.reset(); + } else { + n = chk + len; + if (n > 1) { + cap = self._getMsgSelected(n); + } else { + if (len === 0) { + cfg = self.initialPreviewConfig[0]; + cap = ''; + if (cfg) { + cap = cfg.caption || cfg.filename || ''; + } + if (!cap) { + cap = self._getMsgSelected(n); + } + } else { + file = self.fileManager.getFirstFile(); + cap = file ? file.nameFmt : '_'; + } + } + self._setCaption(cap); + } + }, self.processDelay); + }, + _handleRotation: function ($el, $content, angle) { + var self = this, css, newCss, addCss = '', scale = 1, elContent = $content[0], quadrant, transform, h, w, + wNew, $parent = $content.parent(), hParent, wParent, $body = $('body'), bodyExists = !!$body.length; + if (bodyExists) { + $body.addClass('kv-overflow-hidden'); + } + if (!$content.length || $el.hasClass('hide-rotate')) { + if (bodyExists) { + $body.removeClass('kv-overflow-hidden'); + } + return; + } + transform = $content.css('transform'); + if (transform) { + $content.css('transform', 'none'); + } + if (transform) { + $content.css('transform', transform); + } + angle = angle || 0; + quadrant = angle % 360; + css = 'rotate(' + angle + 'deg)'; + newCss = 'rotate(' + quadrant + 'deg)'; + addCss = ''; + if (quadrant === 90 || quadrant === 270) { + w = elContent.naturalWidth || $content.outerWidth() || 0; + h = elContent.naturalHeight || $content.outerHeight() || 0; + scale = w > h && w != 0 ? (h / w).toFixed(2) : 1; + if ($parent.length) { + hParent = $parent.height(); + wParent = $parent.width(); + wNew = Math.min(w, wParent); + if (hParent > scale * wNew) { + scale = wNew > hParent && wNew != 0 ? (hParent / wNew).toFixed(2) : 1; + } + } + if (scale !== 1) { + addCss = ' scale(' + scale + ')'; + } + } + $content.addClass('rotate-animate').css('transform', css + addCss); + setTimeout(function () { + $content.removeClass('rotate-animate').css('transform', newCss + addCss); + if (bodyExists) { + $body.removeClass('kv-overflow-hidden'); + } + $el.data('angle', quadrant); + }, self.fadeDelay); + }, + _initRotateButton: function () { + var self = this; + self.getFrames('.rotatable .kv-file-rotate').each(function () { + var $el = $(this), $frame = $el.closest($h.FRAMES), + $content = $frame.find('.kv-file-content > :first-child'); + self._handler($el, 'click', function () { + var angle = ($frame.data('angle') || 0) + 90; + self._handleRotation($frame, $content, angle); + }); + }); + }, + _initRotateZoom: function ($frame, $content) { + var self = this, $modal = self.$modal, $rotate = $modal.find('.btn-kv-rotate'), + angle = $frame.data('angle'); + $modal.data('angle', angle); + if ($rotate.length) { + $rotate.off('click'); + if ($modal.hasClass('rotatable')) { + $rotate.on('click', function () { + angle = ($modal.data('angle') || 0) + 90; + $modal.data('angle', angle); + self._handleRotation($modal, $modal.find('.file-zoom-detail'), angle); + self._handleRotation($frame, $content, angle); + if ($frame.hasClass('hide-rotate')) { + $frame.data('angle', angle); + } + }); + } + } + }, + _initFileActions: function () { + var self = this; + if (!self.showPreview) { + return; + } + self._initZoomButton(); + self._initRotateButton(); + self.getFrames(' .kv-file-remove').each(function () { + var $el = $(this), $frame = $el.closest($h.FRAMES), hasError, id = $frame.attr('id'), + ind = $frame.attr('data-fileindex'), status, fm = self.fileManager; + self._handler($el, 'click', function () { + status = self._raise('filepreremove', [id, ind]); + if (status === false || !self._validateMinCount()) { + return false; + } + hasError = $frame.hasClass('file-preview-error'); + $h.cleanMemory($frame); + $frame.fadeOut('slow', function () { + self.fileManager.remove($frame); + self._clearObjects($frame); + $frame.remove(); + if (id && hasError) { + self.$errorContainer.find('li[data-thumb-id="' + id + '"]').fadeOut('fast', function () { + $(this).remove(); + if (!self._errorsExist()) { + self._resetErrors(); + } + }); + } + self._clearFileInput(); + self._resetCaption(); + self._raise('fileremoved', [id, ind]); + }); + }); + }); + self.getFrames(' .kv-file-upload').each(function () { + var $el = $(this); + self._handler($el, 'click', function () { + var $frame = $el.closest($h.FRAMES), fileId = self._getThumbFileId($frame); + self._hideProgress(); + if ($frame.hasClass('file-preview-error') && !self.retryErrorUploads) { + return; + } + self._uploadSingle(self.fileManager.getIndex(fileId), fileId, false); + }); + }); + }, + _initPreviewActions: function () { + var self = this, $preview = self.$preview, deleteExtraData = self.deleteExtraData || {}, + btnRemove = $h.FRAMES + ' .kv-file-remove', settings = self.fileActionSettings, + origClass = settings.removeClass, errClass = settings.removeErrorClass, + resetProgress = function () { + var hasFiles = self.isAjaxUpload ? self.previewCache.count(true) : self._inputFileCount(); + if (!self.getFrames().length && !hasFiles) { + self._setCaption(''); + self.reset(); + self.initialCaption = ''; + } else { + self._resetCaption(); + } + }; + self._initZoomButton(); + self._initRotateButton(); + $preview.find(btnRemove).each(function () { + var $el = $(this), vUrl = $el.data('url') || self.deleteUrl, vKey = $el.data('key'), errMsg, fnBefore, + fnSuccess, fnError, op = self.ajaxOperations.deleteThumb; + if ($h.isEmpty(vUrl) || vKey === undefined) { + return; + } + if (typeof vUrl === 'function') { + vUrl = vUrl(); + } + var $frame = $el.closest($h.FRAMES), cache = self.previewCache.data, settings, params, config, + fileName, extraData, index = $frame.attr('data-fileindex'); + index = parseInt(index.replace($h.INIT_FLAG, '')); + config = $h.isEmpty(cache.config) && $h.isEmpty(cache.config[index]) ? null : cache.config[index]; + extraData = $h.isEmpty(config) || $h.isEmpty(config.extra) ? deleteExtraData : config.extra; + fileName = config && (config.filename || config.caption) || ''; + if (typeof extraData === 'function') { + extraData = extraData(); + } + params = {id: $el.attr('id'), key: vKey, extra: extraData}; + fnBefore = function (jqXHR) { + self.ajaxAborted = false; + self._raise('filepredelete', [vKey, jqXHR, extraData]); + if (self._abort()) { + jqXHR.abort(); + } else { + $el.removeClass(errClass); + $h.addCss($frame, 'file-uploading'); + $h.addCss($el, 'disabled ' + origClass); + } + }; + fnSuccess = function (data, textStatus, jqXHR) { + var n, cap; + if (!$h.isEmpty(data) && !$h.isEmpty(data.error)) { + params.jqXHR = jqXHR; + params.response = data; + errMsg = self._parseError(op, jqXHR, self.msgDeleteError, fileName); + self._showFileError(errMsg, params, 'filedeleteerror'); + $frame.removeClass('file-uploading'); + $el.removeClass('disabled ' + origClass).addClass(errClass); + resetProgress(); + return; + } + $frame.removeClass('file-uploading').addClass('file-deleted'); + $frame.fadeOut('slow', function () { + index = parseInt(($frame.attr('data-fileindex')).replace($h.INIT_FLAG, '')); + self.previewCache.unset(index); + self._deleteFileIndex($frame); + n = self.previewCache.count(true); + cap = n > 0 ? self._getMsgSelected(n) : ''; + self._setCaption(cap); + self._raise('filedeleted', [vKey, jqXHR, extraData]); + self._clearObjects($frame); + $frame.remove(); + resetProgress(); + }); + }; + fnError = function (jqXHR, textStatus, errorThrown) { + var errMsg = self._parseError(op, jqXHR, errorThrown, fileName); + params.jqXHR = jqXHR; + params.response = {}; + self._showFileError(errMsg, params, 'filedeleteerror'); + $frame.removeClass('file-uploading'); + $el.removeClass('disabled ' + origClass).addClass(errClass); + resetProgress(); + }; + self._initAjaxSettings(); + self._mergeAjaxCallback('beforeSend', fnBefore, 'delete'); + self._mergeAjaxCallback('success', fnSuccess, 'delete'); + self._mergeAjaxCallback('error', fnError, 'delete'); + settings = $.extend(true, {}, { + url: self._encodeURI(vUrl), + type: 'POST', + dataType: 'json', + data: $.extend(true, {}, {key: vKey}, extraData) + }, self._ajaxDeleteSettings); + self._handler($el, 'click', function () { + if (!self._validateMinCount()) { + return false; + } + self.ajaxAborted = false; + self._raise('filebeforedelete', [vKey, extraData]); + if (self.ajaxAborted instanceof Promise) { + self.ajaxAborted.then(function (result) { + if (!result) { + $.ajax(settings); + } + }); + } else { + if (!self.ajaxAborted) { + $.ajax(settings); + } + } + }); + }); + }, + _hideFileIcon: function () { + var self = this; + if (self.overwriteInitial) { + self.$captionContainer.removeClass('icon-visible'); + } + }, + _showFileIcon: function () { + var self = this; + $h.addCss(self.$captionContainer, 'icon-visible'); + }, + _getSize: function (bytes, skipTemplate, sizeUnits) { + var self = this, size = parseFloat(bytes), i = 0, factor = self.bytesToKB, func = self.fileSizeGetter, out, + sizeHuman = size, newSize; + if (!$.isNumeric(bytes) || !$.isNumeric(size)) { + return ''; + } + if (typeof func === 'function') { + out = func(size); + } else { + if (!sizeUnits) { + sizeUnits = self.sizeUnits; + } + if (size > 0) { + while (sizeHuman >= factor) { + sizeHuman /= factor; + ++i; + } + if (!sizeUnits[i]) { + sizeHuman = size; + i = 0; + } + } + newSize = sizeHuman.toFixed(2); + if (newSize == sizeHuman) { + newSize = sizeHuman; + } + out = newSize + ' ' + sizeUnits[i]; + } + return skipTemplate ? out : self._getLayoutTemplate('size').replace('{sizeText}', out); + }, + _getFileType: function (ftype) { + var self = this; + return self.mimeTypeAliases[ftype] || ftype; + }, + _generatePreviewTemplate: function ( + cat, + data, + fname, + ftype, + previewId, + fileId, + isError, + size, + fnameUpdated, + frameClass, + foot, + ind, + templ, + attrs, + zoomData + ) { + var self = this, caption = self.slug(fname), prevContent, zoomContent = '', styleAttribs = '', + filename = fnameUpdated || fname, isIconic, ext = filename.split('.').pop().toLowerCase(), + screenW = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth, + config, title = caption, alt = caption, typeCss = 'type-default', getContent, addFrameCss, + footer = foot || self._renderFileFooter(cat, caption, size, 'auto', isError), isRotatable, + alwaysPreview = $.inArray(ext, self.alwaysPreviewFileExtensions) !== -1, + forcePrevIcon = self.preferIconicPreview && !alwaysPreview, + forceZoomIcon = self.preferIconicZoomPreview && !alwaysPreview, newCat = forcePrevIcon ? 'other' : cat; + config = screenW < 400 ? (self.previewSettingsSmall[newCat] || self.defaults.previewSettingsSmall[newCat]) : + (self.previewSettings[newCat] || self.defaults.previewSettings[newCat]); + if (config) { + $.each(config, function (key, val) { + styleAttribs += key + ':' + val + ';'; + }); + } + getContent = function (vCat, vData, zoom, frameCss, vZoomData) { + var id = zoom ? 'zoom-' + previewId : previewId, tmplt = self._getPreviewTemplate(vCat), + css = (frameClass || '') + ' ' + frameCss, tokens; + if (self.frameClass) { + css = self.frameClass + ' ' + css; + } + if (zoom) { + css = css.replace(' ' + $h.SORT_CSS, ''); + } + tmplt = self._parseFilePreviewIcon(tmplt, fname); + if (cat === 'object' && !ftype) { + $.each(self.defaults.fileTypeSettings, function (key, func) { + if (key === 'object' || key === 'other') { + return; + } + if (func(fname, ftype)) { + typeCss = 'type-' + key; + } + }); + } + if (!$h.isEmpty(attrs)) { + if (attrs.title !== undefined && attrs.title !== null) { + title = attrs.title; + } + if (attrs.alt !== undefined && attrs.alt !== null) { + alt = title = attrs.alt; + } + } + tokens = { + 'previewId': id, + 'caption': caption, + 'title': title, + 'alt': alt, + 'frameClass': css, + 'type': self._getFileType(ftype), + 'fileindex': ind, + 'fileid': fileId || '', + 'filename': filename, + 'typeCss': typeCss, + 'footer': footer, + 'data': vData, +// 'data': zoom && vZoomData ? self.zoomPlaceholder + '{zoomData}' : vData, + 'template': templ || cat, + 'style': styleAttribs ? 'style="' + styleAttribs + '"' : '', + 'zoomData': vZoomData ? encodeURIComponent(vZoomData) : '' + }; + if (zoom) { + tokens.zoomCache = ''; + tokens.zoomData = '{zoomData}'; + } + return tmplt.setTokens(tokens); + }; + ind = ind || previewId.slice(previewId.lastIndexOf('-') + 1); + isRotatable = self.fileActionSettings.showRotate && $.inArray(ext, self.rotatableFileExtensions) !== -1; + if (self.fileActionSettings.showZoom) { + addFrameCss = 'kv-zoom-thumb'; + if (isRotatable) { + addFrameCss += ' rotatable' + (forceZoomIcon ? ' hide-rotate' : ''); + } + zoomContent = getContent((forceZoomIcon ? 'other' : cat), data, true, addFrameCss, zoomData); + } + zoomContent = '\n' + self._getLayoutTemplate('zoomCache').replace('{zoomContent}', zoomContent); + if (typeof self.sanitizeZoomCache === 'function') { + zoomContent = self.sanitizeZoomCache(zoomContent); + } + addFrameCss = 'kv-preview-thumb'; + if (isRotatable) { + isIconic = forcePrevIcon || self.hideThumbnailContent || !!self.previewFileIconSettings[ext]; + addFrameCss += ' rotatable' + (isIconic ? ' hide-rotate' : ''); + } + prevContent = getContent((forcePrevIcon ? 'other' : cat), data, false, addFrameCss, zoomData); + return prevContent.setTokens({zoomCache: zoomContent}); + }, + _addToPreview: function ($preview, content) { + var self = this, $el; + content = $h.cspBuffer.stash(content); + $el = self.reversePreviewOrder ? $preview.prepend(content) : $preview.append(content); + $h.cspBuffer.apply($preview); + return $el; + }, + _previewDefault: function (file, isDisabled) { + var self = this, $preview = self.$preview; + if (!self.showPreview) { + return; + } + var fname = $h.getFileName(file), ftype = file ? file.type : '', content, size = file.size || 0, + caption = self._getFileName(file, ''), isError = isDisabled === true && !self.isAjaxUpload, + data = $h.createObjectURL(file), fileId = self.fileManager.getId(file), + previewId = self._getThumbId(fileId); + self._clearDefaultPreview(); + content = self._generatePreviewTemplate('other', data, fname, ftype, previewId, fileId, isError, size); + self._addToPreview($preview, content); + self._setThumbAttr(previewId, caption, size); + if (isDisabled === true && self.isAjaxUpload) { + self._setThumbStatus(self._getFrame(previewId), 'Error'); + } + }, + _previewFile: function (i, file, theFile, data, fileInfo) { + if (!this.showPreview) { + return; + } + var self = this, fname = $h.getFileName(file), ftype = fileInfo.type, content, + caption = fileInfo.name, cat = self._parseFileType(ftype, fname), $preview = self.$preview, + fsize = file.size || 0, iData = cat === 'image' ? theFile.target.result : data, fm = self.fileManager, + fileId = fm.getId(file), previewId = self._getThumbId(fileId); + /** @namespace window.DOMPurify */ + content = self._generatePreviewTemplate(cat, iData, fname, ftype, previewId, fileId, false, fsize, fileInfo.filename); + self._clearDefaultPreview(); + self._addToPreview($preview, content); + var $thumb = self._getFrame(previewId); + self._validateImageOrientation($thumb.find('img'), file, previewId, fileId, caption, ftype, fsize, iData); + self._setThumbAttr(previewId, caption, fsize); + self._initSortable(); + }, + _setThumbAttr: function (id, caption, size, description) { + var self = this, $frame = self._getFrame(id); + if ($frame.length) { + size = size && size > 0 ? self._getSize(size) : ''; + $frame.data({'caption': caption, 'size': size, 'description': description || ''}); + } + }, + _setInitThumbAttr: function () { + var self = this, data = self.previewCache.data, len = self.previewCache.count(true), config, + caption, size, description, previewId; + if (len === 0) { + return; + } + for (var i = 0; i < len; i++) { + config = data.config[i]; + previewId = self.previewInitId + '-' + $h.INIT_FLAG + i; + caption = $h.ifSet('caption', config, $h.ifSet('filename', config)); + size = $h.ifSet('size', config); + description = $h.ifSet('description', config); + self._setThumbAttr(previewId, caption, size, description); + } + }, + _slugDefault: function (text) { + // noinspection RegExpRedundantEscape + return $h.isEmpty(text, true) ? '' : String(text).replace(/[\[\]\/\{}:;#%=\(\)\*\+\?\\\^\$\|<>&"']/g, '_'); + }, + _updateFileDetails: function (numFiles) { + var self = this, $el = self.$element, label, n, log, nFiles, file, + name = ($h.isIE(9) && $h.findFileName($el.val())) || ($el[0].files[0] && $el[0].files[0].name); + if (!name && self.fileManager.count() > 0) { + file = self.fileManager.getFirstFile(); + label = file.nameFmt; + } else { + label = name ? self.slug(name) : '_'; + } + n = self.isAjaxUpload ? self.fileManager.count() : numFiles; + nFiles = self.previewCache.count(true) + n; + log = n === 1 ? label : self._getMsgSelected(nFiles, !self.isAjaxUpload && !self.isError); + if (self.isError) { + self.$previewContainer.removeClass('file-thumb-loading'); + self._initCapStatus(); + self.$previewStatus.html(''); + self.$captionContainer.removeClass('icon-visible'); + } else { + self._showFileIcon(); + } + self._setCaption(log, self.isError); + self.$container.removeClass('file-input-new file-input-ajax-new'); + self._raise('fileselect', [numFiles, label]); + if (self.previewCache.count(true)) { + self._initPreviewActions(); + } + }, + _setThumbStatus: function ($thumb, status) { + var self = this; + if (!self.showPreview) { + return; + } + var icon = 'indicator' + status, msg = icon + 'Title', + css = 'file-preview-' + status.toLowerCase(), + $indicator = $thumb.find('.file-upload-indicator'), + config = self.fileActionSettings; + $thumb.removeClass('file-preview-success file-preview-error file-preview-paused file-preview-loading'); + if (status === 'Success') { + $thumb.find('.file-drag-handle').remove(); + } + $h.setHtml($indicator, config[icon]); + $indicator.attr('title', config[msg]); + $thumb.addClass(css); + if (status === 'Error' && !self.retryErrorUploads) { + $thumb.find('.kv-file-upload').attr('disabled', true); + } + }, + _setProgressCancelled: function () { + var self = this; + self._setProgress(101, self.$progress, self.msgCancelled); + }, + _setProgress: function (p, $el, error, stats) { + var self = this; + $el = $el || self.$progress; + if (!$el.length) { + return; + } + var pct = Math.min(p, 100), out, pctLimit = self.progressUploadThreshold, + t = p <= 100 ? self.progressTemplate : self.progressCompleteTemplate, + template = pct < 100 ? self.progressTemplate : + (error ? (self.paused ? self.progressPauseTemplate : self.progressErrorTemplate) : t); + if (p >= 100) { + stats = ''; + } + if (!$h.isEmpty(template)) { + if (pctLimit && pct > pctLimit && p <= 100) { + out = template.setTokens({'percent': pctLimit, 'status': self.msgUploadThreshold}); + } else { + out = template.setTokens({'percent': pct, 'status': (p > 100 ? self.msgUploadEnd : pct + '%')}); + } + stats = stats || ''; + out = out.setTokens({stats: stats}); + $h.setHtml($el, out); + if (error) { + $h.setHtml($el.find('[role="progressbar"]'), error); + } + } + }, + _hasFiles: function () { + var el = this.$element[0]; + return !!(el && el.files && el.files.length); + }, + _setFileDropZoneTitle: function () { + var self = this, $zone = self.$container.find('.file-drop-zone'), title = self.dropZoneTitle, strFiles; + if (self.isClickable) { + strFiles = $h.isEmpty(self.$element.attr('multiple')) ? self.fileSingle : self.filePlural; + title += self.dropZoneClickTitle.replace('{files}', strFiles); + } + $zone.find('.' + self.dropZoneTitleClass).remove(); + if (!self.showPreview || $zone.length === 0 || self.fileManager.count() > 0 || !self.dropZoneEnabled || + self.previewCache.count() > 0 || (!self.isAjaxUpload && self._hasFiles())) { + return; + } + if ($zone.find($h.FRAMES).length === 0 && $h.isEmpty(self.defaultPreviewContent)) { + $zone.prepend('
    ' + title + '
    '); + } + self.$container.removeClass('file-input-new'); + $h.addCss(self.$container, 'file-input-ajax-new'); + }, + _getStats: function (stats) { + var self = this, pendingTime, t; + if (!self.showUploadStats || !stats || !stats.bitrate) { + return ''; + } + t = self._getLayoutTemplate('stats'); + pendingTime = (!stats.elapsed || !stats.bps) ? self.msgCalculatingTime : + self.msgPendingTime.setTokens({time: $h.getElapsed(Math.ceil(stats.pendingBytes / stats.bps))}); + + return t.setTokens({ + uploadSpeed: stats.bitrate, + pendingTime: pendingTime + }); + }, + _setResumableProgress: function (pct, stats, $thumb) { + var self = this, rm = self.resumableManager, obj = $thumb ? rm : self, + $prog = $thumb ? $thumb.find('.file-thumb-progress') : null; + if (obj.lastProgress === 0) { + obj.lastProgress = pct; + } + if (pct < obj.lastProgress) { + pct = obj.lastProgress; + } + self._setProgress(pct, $prog, null, self._getStats(stats)); + obj.lastProgress = pct; + }, + _toggleResumableProgress: function (template, message) { + var self = this, $progress = self.$progress; + if ($progress && $progress.length) { + $h.setHtml($progress, template.setTokens({ + percent: 101, + status: message, + stats: '' + })); + } + }, + _setFileUploadStats: function (id, pct, stats) { + var self = this, $prog = self.$progress; + if (!self.showPreview && (!$prog || !$prog.length)) { + return; + } + var fm = self.fileManager, rm = self.resumableManager, $thumb = fm.getThumb(id), pctTot, + totUpSize = 0, totSize = fm.getTotalSize(), totStats = $.extend(true, {}, stats); + if (self.enableResumableUpload) { + var loaded = stats.loaded, currUplSize = rm.getUploadedSize(), currTotSize = rm.file.size, totLoaded; + loaded += currUplSize; + totLoaded = fm.uploadedSize + loaded; + pct = $h.round(100 * loaded / currTotSize); + stats.pendingBytes = currTotSize - currUplSize; + self._setResumableProgress(pct, stats, $thumb); + pctTot = Math.floor(100 * totLoaded / totSize); + totStats.pendingBytes = totSize - totLoaded; + self._setResumableProgress(pctTot, totStats); + } else { + fm.setProgress(id, pct); + $prog = $thumb && $thumb.length ? $thumb.find('.file-thumb-progress') : null; + self._setProgress(pct, $prog, null, self._getStats(stats)); + $.each(fm.stats, function (id, cfg) { + totUpSize += cfg.loaded; + }); + totStats.pendingBytes = totSize - totUpSize; + pctTot = $h.round(totUpSize / totSize * 100); + self._setProgress(pctTot, null, null, self._getStats(totStats)); + } + }, + _validateMinCount: function () { + var self = this, len = self.isAjaxUpload ? self.fileManager.count() : self._inputFileCount(); + if (self.validateInitialCount && self.minFileCount > 0 && self._getFileCount(len - 1) < self.minFileCount) { + self._noFilesError({}); + return false; + } + return true; + }, + _getFileCount: function (fileCount, includeInitial) { + var self = this, addCount = 0; + if (includeInitial === undefined) { + includeInitial = self.validateInitialCount && !self.overwriteInitial; + } + if (includeInitial) { + addCount = self.previewCache.count(true); + fileCount += addCount; + } + return fileCount; + }, + _getFileId: function (file) { + return $h.getFileId(file, this.generateFileId); + }, + _getFileName: function (file, defaultValue) { + var self = this, fileName = $h.getFileName(file); + return fileName ? self.slug(fileName) : defaultValue; + }, + _getFileNames: function (skipNull) { + var self = this; + return self.filenames.filter(function (n) { + return (skipNull ? n !== undefined : n !== undefined && n !== null); + }); + }, + _setPreviewError: function ($thumb, keepFile) { + var self = this, removeFrame = self.removeFromPreviewOnError && !self.retryErrorUploads; + if (!keepFile || removeFrame) { + self.fileManager.remove($thumb); + } + if (!self.showPreview) { + return; + } + if (removeFrame) { + $thumb.remove(); + return; + } else { + self._setThumbStatus($thumb, 'Error'); + } + self._refreshUploadButton($thumb); + }, + _refreshUploadButton: function ($thumb) { + var self = this, $btn = $thumb.find('.kv-file-upload'), cfg = self.fileActionSettings, + icon = cfg.uploadIcon, title = cfg.uploadTitle; + if (!$btn.length) { + return; + } + if (self.retryErrorUploads) { + icon = cfg.uploadRetryIcon; + title = cfg.uploadRetryTitle; + } + $btn.attr('title', title); + $h.setHtml($btn, icon); + }, + _isValidSize: function (size, type, $image, $thumb, filename, params) { + var self = this, msg, dim, $img, tag = size === 'Small' ? 'min' : 'max', limit = self[tag + 'Image' + type]; + if ($h.isEmpty(limit) || !$image.length) { + return true; + } + $img = $image[0]; + dim = (type === 'Width') ? $img.naturalWidth || $img.width : $img.naturalHeight || $img.height; + if (size === 'Small' ? dim >= limit : dim <= limit) { + return true; + } + msg = self['msgImage' + type + size] || 'Image "{name}" has a size validation error (limit "{size}").'; + self._showFileError(msg.setTokens({'name': filename, 'size': limit, 'dimension': dim}), params); + self._setPreviewError($thumb); + self.fileManager.remove($thumb); + self._clearFileInput(); + return false; + }, + _getExifObj: function (data) { + var self = this, exifObj, error = $h.logMessages.exifWarning; + if (data.slice(0, 23) !== 'data:image/jpeg;base64,' && data.slice(0, 22) !== 'data:image/jpg;base64,') { + exifObj = null; + return; + } + try { + exifObj = window.piexif ? window.piexif.load(data) : null; + } catch (err) { + exifObj = null; + error = err && err.message || ''; + } + if (!exifObj && self.showExifErrorLog) { + self._log($h.logMessages.badExifParser, {details: error}); + } + return exifObj; + }, + setImageOrientation: function ($img, $zoomImg, value, $thumb) { + var self = this, invalidImg = !$img || !$img.length, invalidZoomImg = !$zoomImg || !$zoomImg.length, $mark, + isHidden = false, $div, zoomOnly = invalidImg && $thumb && $thumb.attr('data-template') === 'image', ev; + if (invalidImg && invalidZoomImg) { + return; + } + ev = 'load.fileinputimageorient'; + if (zoomOnly) { + $img = $zoomImg; + $zoomImg = null; + $img.css(self.previewSettings.image); + $div = $(document.createElement('div')).appendTo($thumb.find('.kv-file-content')); + $mark = $(document.createElement('span')).insertBefore($img); + $img.css('visibility', 'hidden').removeClass('file-zoom-detail').appendTo($div); + } else { + isHidden = !$img.is(':visible'); + } + $img.off(ev).on(ev, function () { + if (isHidden) { + self.$preview.removeClass('hide-content'); + $thumb.find('.kv-file-content').css('visibility', 'hidden'); + } + var img = $img[0], zoomImg = $zoomImg && $zoomImg.length ? $zoomImg[0] : null, + h = img.offsetHeight, w = img.offsetWidth, r = $h.getRotation(value); + if (isHidden) { + $thumb.find('.kv-file-content').css('visibility', 'visible'); + self.$preview.addClass('hide-content'); + } + $img.data('orientation', value); + if (zoomImg) { + $zoomImg.data('orientation', value); + } + if (value < 5) { + $h.setTransform(img, r); + $h.setTransform(zoomImg, r); + return; + } + var offsetAngle = Math.atan(w / h), origFactor = Math.sqrt(Math.pow(h, 2) + Math.pow(w, 2)), + scale = !origFactor ? 1 : (h / Math.cos(Math.PI / 2 + offsetAngle)) / origFactor, + s = ' scale(' + Math.abs(scale) + ')'; + $h.setTransform(img, r + s); + $h.setTransform(zoomImg, r + s); + if (zoomOnly) { + $img.css('visibility', 'visible').insertAfter($mark).addClass('file-zoom-detail'); + $mark.remove(); + $div.remove(); + } + }); + }, + _validateImageOrientation: function ($img, file, previewId, fileId, caption, ftype, fsize, iData) { + var self = this, exifObj = null, value, autoOrientImage = self.autoOrientImage, selector; + exifObj = self._getExifObj(iData); + if (self.canOrientImage) { + $img.css('image-orientation', (autoOrientImage ? 'from-image' : 'none')); + self._validateImage(previewId, fileId, caption, ftype, fsize, iData, exifObj); + return; + } + selector = $h.getZoomSelector(previewId, ' img'); + value = exifObj ? exifObj['0th'][piexif.ImageIFD.Orientation] : null; // jshint ignore:line + if (!value) { + self._validateImage(previewId, fileId, caption, ftype, fsize, iData, exifObj); + return; + } + self.setImageOrientation($img, $(selector), value, self._getFrame(previewId)); + self._raise('fileimageoriented', {'$img': $img, 'file': file}); + self._validateImage(previewId, fileId, caption, ftype, fsize, iData, exifObj); + }, + _validateImage: function (previewId, fileId, fname, ftype, fsize, iData, exifObj) { + var self = this, $preview = self.$preview, params, w1, w2, $thumb = self._getFrame(previewId), + i = $thumb.attr('data-fileindex'), $img = $thumb.find('img'); + fname = fname || 'Untitled'; + $img.one('load', function () { + if ($img.data('validated')) { + return; + } + $img.data('validated', true); + w1 = $thumb.width(); + w2 = $preview.width(); + if (w1 > w2) { + $img.css('width', '100%'); + } + params = {ind: i, id: previewId, fileId: fileId}; + setTimeout(function () { + var isValidWidth, isValidHeight; + isValidWidth = self._isValidSize('Small', 'Width', $img, $thumb, fname, params); + isValidHeight = self._isValidSize('Small', 'Height', $img, $thumb, fname, params); + if (!self.resizeImage) { + isValidWidth = isValidWidth && self._isValidSize('Large', 'Width', $img, $thumb, fname, params); + isValidHeight = isValidHeight && self._isValidSize('Large', 'Height', $img, $thumb, fname, params); + } + self._raise('fileimageloaded', [previewId]); + $thumb.data('exif', exifObj); + if (isValidWidth && isValidHeight) { + self.fileManager.addImage(fileId, { + ind: i, + img: $img, + thumb: $thumb, + pid: previewId, + typ: ftype, + siz: fsize, + validated: false, + imgData: iData, + exifObj: exifObj + }); + self._validateAllImages(); + } + }, self.processDelay); + }).one('error', function () { + self._raise('fileimageloaderror', [previewId]); + }); + }, + _validateAllImages: function () { + var self = this, counter = {val: 0}, numImgs = self.fileManager.getImageCount(), fsize, + minSize = self.resizeIfSizeMoreThan; + if (numImgs !== self.fileManager.totalImages) { + return; + } + self._raise('fileimagesloaded'); + if (!self.resizeImage) { + return; + } + $.each(self.fileManager.loadedImages, function (id, config) { + if (!config.validated) { + fsize = config.siz; + if (fsize && fsize > minSize * self.bytesToKB) { + self._getResizedImage(id, config, counter, numImgs); + } + config.validated = true; + } + }); + }, + _getResizedImage: function (id, config, counter, numImgs) { + var self = this, img = $(config.img)[0], width = img.naturalWidth, height = img.naturalHeight, blob, + ratio = 1, maxWidth = self.maxImageWidth || width, maxHeight = self.maxImageHeight || height, + isValidImage = !!(width && height), chkWidth, chkHeight, canvas = self.imageCanvas, dataURI, + context = self.imageCanvasContext, type = config.typ, pid = config.pid, ind = config.ind, + $thumb = config.thumb, throwError, msg, exifObj = config.exifObj, exifStr, file, params, evParams; + throwError = function (msg, params, ev) { + if (self.isAjaxUpload) { + self._showFileError(msg, params, ev); + } else { + self._showError(msg, params, ev); + } + self._setPreviewError($thumb); + }; + file = self.fileManager.getFile(id); + params = {id: pid, 'index': ind, fileId: id}; + evParams = [id, pid, ind]; + if (!file || !isValidImage || (width <= maxWidth && height <= maxHeight)) { + if (isValidImage && file) { + self._raise('fileimageresized', evParams); + } + counter.val++; + if (counter.val === numImgs) { + self._raise('fileimagesresized'); + } + if (!isValidImage) { + throwError(self.msgImageResizeError, params, 'fileimageresizeerror'); + return; + } + } + type = type || self.resizeDefaultImageType; + chkWidth = width > maxWidth; + chkHeight = height > maxHeight; + if (self.resizePreference === 'width') { + ratio = chkWidth ? maxWidth / width : (chkHeight ? maxHeight / height : 1); + } else { + ratio = chkHeight ? maxHeight / height : (chkWidth ? maxWidth / width : 1); + } + self._resetCanvas(); + width *= ratio; + height *= ratio; + canvas.width = width; + canvas.height = height; + try { + context.drawImage(img, 0, 0, width, height); + dataURI = canvas.toDataURL(type, self.resizeQuality); + if (exifObj) { + exifStr = window.piexif.dump(exifObj); + dataURI = window.piexif.insert(exifStr, dataURI); + } + blob = $h.dataURI2Blob(dataURI); + self.fileManager.setFile(id, blob); + self._raise('fileimageresized', evParams); + counter.val++; + if (counter.val === numImgs) { + self._raise('fileimagesresized', [undefined, undefined]); + } + if (!(blob instanceof Blob)) { + throwError(self.msgImageResizeError, params, 'fileimageresizeerror'); + } + } catch (err) { + counter.val++; + if (counter.val === numImgs) { + self._raise('fileimagesresized', [undefined, undefined]); + } + msg = self.msgImageResizeException.replace('{errors}', err.message); + throwError(msg, params, 'fileimageresizeexception'); + } + }, + _showProgress: function () { + var self = this; + if (self.$progress && self.$progress.length) { + self.$progress.show(); + } + }, + _hideProgress: function () { + var self = this; + if (self.$progress && self.$progress.length) { + self.$progress.hide(); + } + }, + _initBrowse: function ($container) { + var self = this, $el = self.$element; + if (self.showBrowse) { + self.$btnFile = $container.find('.btn-file').append($el); + } else { + $el.appendTo($container).attr('tabindex', -1); + $h.addCss($el, 'file-no-browse'); + } + }, + _initClickable: function () { + var self = this, $zone, $tmpZone; + if (!self.isClickable) { + return; + } + $zone = self.$dropZone; + if (!self.isAjaxUpload) { + $tmpZone = self.$preview.find('.file-default-preview'); + if ($tmpZone.length) { + $zone = $tmpZone; + } + } + + $h.addCss($zone, 'clickable'); + $zone.attr('tabindex', -1); + self._handler($zone, 'click', function (e) { + var $tar = $(e.target); + if (!self.$errorContainer.is(':visible') && (!$tar.parents( + '.file-preview-thumbnails').length || $tar.parents( + '.file-default-preview').length)) { + self.$element.data('zoneClicked', true).trigger('click'); + $zone.blur(); + } + }); + }, + _initCaption: function () { + var self = this, cap = self.initialCaption || ''; + if (self.overwriteInitial || $h.isEmpty(cap)) { + self.$caption.val(''); + return false; + } + self._setCaption(cap); + return true; + }, + _setCaption: function (content, isError) { + var self = this, title, out, icon, n, cap, file; + if (!self.$caption.length) { + return; + } + self.$captionContainer.removeClass('icon-visible'); + if (isError) { + title = $('
    ' + self.msgValidationError + '
    ').text(); + n = self.fileManager.count(); + if (n) { + file = self.fileManager.getFirstFile(); + cap = n === 1 && file ? file.nameFmt : self._getMsgSelected(n); + } else { + cap = self._getMsgSelected(self.msgNo); + } + out = $h.isEmpty(content) ? cap : content; + icon = '' + self.msgValidationErrorIcon + ''; + } else { + if ($h.isEmpty(content)) { + self.$caption.attr('title', ''); + return; + } + title = $('
    ' + content + '
    ').text(); + out = title; + icon = self._getLayoutTemplate('fileIcon'); + } + self.$captionContainer.addClass('icon-visible'); + self.$caption.attr('title', title).val(out); + $h.setHtml(self.$captionIcon, icon); + }, + _createContainer: function () { + var self = this, attribs = {'class': 'file-input file-input-new' + (self.rtl ? ' kv-rtl' : '')}, + $container = $h.createElement($h.cspBuffer.stash(self._renderMain())); + $h.cspBuffer.apply($container); + $container.insertBefore(self.$element).attr(attribs); + self._initBrowse($container); + if (self.theme) { + $container.addClass('theme-' + self.theme); + } + return $container; + }, + _refreshContainer: function () { + var self = this, $container = self.$container, $el = self.$element; + $el.insertAfter($container); + $h.setHtml($container, self._renderMain()); + self._initBrowse($container); + self._validateDisabled(); + }, + _validateDisabled: function () { + var self = this; + self.$caption.attr({readonly: self.isDisabled}); + }, + _setTabIndex: function (type, html) { + var self = this, index = self.tabIndexConfig[type]; + return html.setTokens({ + tabIndexConfig: index === undefined || index === null ? '' : 'tabindex="' + index + '"' + }); + }, + _renderMain: function () { + var self = this, + dropCss = self.dropZoneEnabled ? ' file-drop-zone' : 'file-drop-disabled', + close = !self.showClose ? '' : self._getLayoutTemplate('close'), + preview = !self.showPreview ? '' : self._getLayoutTemplate('preview') + .setTokens({'class': self.previewClass, 'dropClass': dropCss}), + css = self.isDisabled ? self.captionClass + ' file-caption-disabled' : self.captionClass, + caption = self.captionTemplate.setTokens({'class': css + ' kv-fileinput-caption'}); + caption = self._setTabIndex('caption', caption); + return self.mainTemplate.setTokens({ + 'class': self.mainClass + (!self.showBrowse && self.showCaption ? ' no-browse' : ''), + 'inputGroupClass': self.inputGroupClass, + 'preview': preview, + 'close': close, + 'caption': caption, + 'upload': self._renderButton('upload'), + 'remove': self._renderButton('remove'), + 'cancel': self._renderButton('cancel'), + 'pause': self._renderButton('pause'), + 'browse': self._renderButton('browse') + }); + + }, + _renderButton: function (type) { + var self = this, tmplt = self._getLayoutTemplate('btnDefault'), css = self[type + 'Class'], + title = self[type + 'Title'], icon = self[type + 'Icon'], label = self[type + 'Label'], + status = self.isDisabled ? ' disabled' : '', btnType = 'button'; + switch (type) { + case 'remove': + if (!self.showRemove) { + return ''; + } + break; + case 'cancel': + if (!self.showCancel) { + return ''; + } + css += ' kv-hidden'; + break; + case 'pause': + if (!self.showPause) { + return ''; + } + css += ' kv-hidden'; + break; + case 'upload': + if (!self.showUpload) { + return ''; + } + if (self.isAjaxUpload && !self.isDisabled) { + tmplt = self._getLayoutTemplate('btnLink').replace('{href}', self.uploadUrl); + } else { + btnType = 'submit'; + } + break; + case 'browse': + if (!self.showBrowse) { + return ''; + } + tmplt = self._getLayoutTemplate('btnBrowse'); + break; + default: + return ''; + } + tmplt = self._setTabIndex(type, tmplt); + + css += type === 'browse' ? ' btn-file' : ' fileinput-' + type + ' fileinput-' + type + '-button'; + if (!$h.isEmpty(label)) { + label = ' ' + label + ''; + } + return tmplt.setTokens({ + 'type': btnType, 'css': css, 'title': title, 'status': status, 'icon': icon, 'label': label + }); + }, + _renderThumbProgress: function () { + var self = this; + return '
    ' + + self.progressInfoTemplate.setTokens({percent: 101, status: self.msgUploadBegin, stats: ''}) + + '
    '; + }, + _renderFileFooter: function (cat, caption, size, width, isError) { + var self = this, config = self.fileActionSettings, rem = config.showRemove, drg = config.showDrag, + upl = config.showUpload, rot = config.showRotate, zoom = config.showZoom, out, params, + template = self._getLayoutTemplate('footer'), tInd = self._getLayoutTemplate('indicator'), + ind = isError ? config.indicatorError : config.indicatorNew, + title = isError ? config.indicatorErrorTitle : config.indicatorNewTitle, + indicator = tInd.setTokens({'indicator': ind, 'indicatorTitle': title}); + size = self._getSize(size); + params = {type: cat, caption: caption, size: size, width: width, progress: '', indicator: indicator}; + if (self.isAjaxUpload) { + params.progress = self._renderThumbProgress(); + params.actions = self._renderFileActions(params, upl, false, rem, rot, zoom, drg, false, false, false); + } else { + params.actions = self._renderFileActions(params, false, false, false, false, zoom, drg, false, false, false); + } + out = template.setTokens(params); + out = $h.replaceTags(out, self.previewThumbTags); + return out; + }, + _renderFileActions: function ( + cfg, + showUpl, + showDwn, + showDel, + showRot, + showZoom, + showDrag, + disabled, + url, + key, + isInit, + dUrl, + dFile + ) { + var self = this; + if (!cfg.type && isInit) { + cfg.type = 'image'; + } + if (self.enableResumableUpload) { + showUpl = false; + } else { + if (typeof showUpl === 'function') { + showUpl = showUpl(cfg); + } + } + if (typeof showDwn === 'function') { + showDwn = showDwn(cfg); + } + if (typeof showDel === 'function') { + showDel = showDel(cfg); + } + if (typeof showZoom === 'function') { + showZoom = showZoom(cfg); + } + if (typeof showDrag === 'function') { + showDrag = showDrag(cfg); + } + if (typeof showRot === 'function') { + showRot = showRot(cfg); + } + if (!showUpl && !showDwn && !showDel && !showRot && !showZoom && !showDrag) { + return ''; + } + var vUrl = url === false ? '' : ' data-url="' + url + '"', btnZoom = '', btnDrag = '', btnRotate = '', css, + vKey = key === false ? '' : ' data-key="' + key + '"', btnDelete = '', btnUpload = '', btnDownload = '', + template = self._getLayoutTemplate('actions'), config = self.fileActionSettings, + otherButtons = self.otherActionButtons.setTokens({'dataKey': vKey, 'key': key}), + removeClass = disabled ? config.removeClass + ' disabled' : config.removeClass; + if (showDel) { + btnDelete = self._getLayoutTemplate('actionDelete').setTokens({ + 'removeClass': removeClass, + 'removeIcon': config.removeIcon, + 'removeTitle': config.removeTitle, + 'dataUrl': vUrl, + 'dataKey': vKey, + 'key': key + }); + } + if (showRot) { + btnRotate = self._getLayoutTemplate('actionRotate').setTokens({ + 'rotateClass': config.rotateClass, + 'rotateIcon': config.rotateIcon, + 'rotateTitle': config.rotateTitle + }); + } + if (showUpl) { + btnUpload = self._getLayoutTemplate('actionUpload').setTokens({ + 'uploadClass': config.uploadClass, + 'uploadIcon': config.uploadIcon, + 'uploadTitle': config.uploadTitle + }); + } + if (showDwn) { + btnDownload = self._getLayoutTemplate('actionDownload').setTokens({ + 'downloadClass': config.downloadClass, + 'downloadIcon': config.downloadIcon, + 'downloadTitle': config.downloadTitle, + 'downloadUrl': dUrl || self.initialPreviewDownloadUrl + }); + btnDownload = btnDownload.setTokens({'filename': dFile, 'key': key}); + } + if (showZoom) { + btnZoom = self._getLayoutTemplate('actionZoom').setTokens({ + 'zoomClass': config.zoomClass, + 'zoomIcon': config.zoomIcon, + 'zoomTitle': config.zoomTitle + }); + } + if (showDrag && isInit) { + css = 'drag-handle-init ' + config.dragClass; + btnDrag = self._getLayoutTemplate('actionDrag').setTokens({ + 'dragClass': css, + 'dragTitle': config.dragTitle, + 'dragIcon': config.dragIcon + }); + } + return template.setTokens({ + 'delete': btnDelete, + 'upload': btnUpload, + 'download': btnDownload, + 'rotate': btnRotate, + 'zoom': btnZoom, + 'drag': btnDrag, + 'other': otherButtons + }); + }, + _browse: function (e) { + var self = this; + if (e && e.isDefaultPrevented() || !self._raise('filebrowse')) { + return; + } + if (self.isError && !self.isAjaxUpload) { + self.clear(); + } + if (self.focusCaptionOnBrowse) { + self.$captionContainer.focus(); + } + }, + _change: function (e) { + var self = this; + $(document.body).off('focusin.fileinput focusout.fileinput'); + if (self.changeTriggered) { + self._toggleLoading('hide'); + return; + } + self._toggleLoading('show'); + var $el = self.$element, isDragDrop = arguments.length > 1, isAjaxUpload = self.isAjaxUpload, + tfiles, files = isDragDrop ? arguments[1] : $el[0].files, ctr = self.fileManager.count(), + total, initCount, len, isSingleUpl = $h.isEmpty($el.attr('multiple')), + maxCount = !isAjaxUpload && isSingleUpl ? 1 : self.maxFileCount, maxTotCount = self.maxTotalFileCount, + inclAll = maxTotCount > 0 && maxTotCount > maxCount, flagSingle = (isSingleUpl && ctr > 0), + throwError = function (mesg, file, previewId, index) { + var p1 = $.extend(true, {}, self._getOutData(null, {}, {}, files), {id: previewId, index: index}), + p2 = {id: previewId, index: index, file: file, files: files}; + self.isPersistentError = true; + self._toggleLoading('hide'); + return isAjaxUpload ? self._showFileError(mesg, p1) : self._showError(mesg, p2); + }, + maxCountCheck = function (n, m, all) { + var msg = all ? self.msgTotalFilesTooMany : self.msgFilesTooMany; + msg = msg.replace('{m}', m).replace('{n}', n); + self.isError = throwError(msg, null, null, null); + self.$captionContainer.removeClass('icon-visible'); + self._setCaption('', true); + self.$container.removeClass('file-input-new file-input-ajax-new'); + }; + self.reader = null; + self._resetUpload(); + self._hideFileIcon(); + if (self.dropZoneEnabled) { + self.$container.find('.file-drop-zone .' + self.dropZoneTitleClass).remove(); + } + if (!isAjaxUpload) { + if (e.target && e.target.files === undefined) { + files = e.target.value ? [{name: e.target.value.replace(/^.+\\/, '')}] : []; + } else { + files = e.target.files || {}; + } + } + tfiles = files; + if ($h.isEmpty(tfiles) || tfiles.length === 0) { + if (!isAjaxUpload) { + self.clear(); + } + self._raise('fileselectnone'); + return; + } + self._resetErrors(); + len = tfiles.length; + initCount = isAjaxUpload ? (self.fileManager.count() + len) : len; + total = self._getFileCount(initCount, inclAll ? false : undefined); + if (maxCount > 0 && total > maxCount) { + if (!self.autoReplace || len > maxCount) { + maxCountCheck((self.autoReplace && len > maxCount ? len : total), maxCount); + return; + } + if (total > maxCount) { + self._resetPreviewThumbs(isAjaxUpload); + } + + } else { + if (inclAll) { + total = self._getFileCount(initCount, true); + if (maxTotCount > 0 && total > maxTotCount) { + if (!self.autoReplace || len > maxCount) { + maxCountCheck((self.autoReplace && len > maxTotCount ? len : total), maxTotCount, true); + return; + } + if (total > maxCount) { + self._resetPreviewThumbs(isAjaxUpload); + } + } + } + if (!isAjaxUpload || flagSingle) { + self._resetPreviewThumbs(false); + if (flagSingle) { + self.clearFileStack(); + } + } else { + if (isAjaxUpload && ctr === 0 && (!self.previewCache.count(true) || self.overwriteInitial)) { + self._resetPreviewThumbs(true); + } + } + } + if (self.autoReplace) { + self._getThumbs().each(function () { + var $thumb = $(this); + if ($thumb.hasClass('file-preview-success') || $thumb.hasClass('file-preview-error')) { + $thumb.remove(); + } + }); + } + self.readFiles(tfiles); + self._toggleLoading('hide'); + }, + _abort: function (params) { + var self = this, data; + if (self.ajaxAborted && typeof self.ajaxAborted === 'object' && self.ajaxAborted.message !== undefined) { + data = $.extend(true, {}, self._getOutData(null), params); + data.abortData = self.ajaxAborted.data || {}; + data.abortMessage = self.ajaxAborted.message; + self._setProgress(101, self.$progress, self.msgCancelled); + self._showFileError(self.ajaxAborted.message, data, 'filecustomerror'); + self.cancel(); + self.unlock(); + return true; + } + return !!self.ajaxAborted; + }, + _resetFileStack: function () { + var self = this, i = 0; + self._getThumbs().each(function () { + var $thumb = $(this), ind = $thumb.attr('data-fileindex'), pid = $thumb.attr('id'); + if (ind === '-1' || ind === -1) { + return; + } + if (!self._getThumbFile($thumb)) { + $thumb.attr({'data-fileindex': i}); + i++; + } else { + $thumb.attr({'data-fileindex': '-1'}); + } + self._getZoom(pid).attr({ + 'data-fileindex': $thumb.attr('data-fileindex') + }); + }); + }, + _isFileSelectionValid: function (cnt) { + var self = this; + cnt = cnt || 0; + if (self.required && !self.getFilesCount()) { + self.$errorContainer.html(''); + self._showFileError(self.msgFileRequired); + return false; + } + if (self.minFileCount > 0 && self._getFileCount(cnt) < self.minFileCount) { + self._noFilesError({}); + return false; + } + return true; + }, + _canPreview: function (file) { + var self = this; + if (!file || !self.showPreview || !self.$preview || !self.$preview.length) { + return false; + } + var name = file.name || '', type = file.type || '', size = (file.size || 0) / self.bytesToKB, + cat = self._parseFileType(type, name), allowedTypes, allowedMimes, allowedExts, skipPreview, + types = self.allowedPreviewTypes, mimes = self.allowedPreviewMimeTypes, + exts = self.allowedPreviewExtensions || [], dTypes = self.disabledPreviewTypes, + dMimes = self.disabledPreviewMimeTypes, dExts = self.disabledPreviewExtensions || [], + maxSize = self.maxFilePreviewSize && parseFloat(self.maxFilePreviewSize) || 0, + expAllExt = new RegExp('\\.(' + exts.join('|') + ')$', 'i'), + expDisExt = new RegExp('\\.(' + dExts.join('|') + ')$', 'i'); + allowedTypes = !types || types.indexOf(cat) !== -1; + allowedMimes = !mimes || mimes.indexOf(type) !== -1; + allowedExts = !exts.length || $h.compare(name, expAllExt); + skipPreview = (dTypes && dTypes.indexOf(cat) !== -1) || (dMimes && dMimes.indexOf(type) !== -1) || + (dExts.length && $h.compare(name, expDisExt)) || (maxSize && !isNaN(maxSize) && size > maxSize); + return !skipPreview && (allowedTypes || allowedMimes || allowedExts); + }, + addToStack: function (file, id) { + var self = this; + self.stackIsUpdating = true; + self.fileManager.add(file, id); + self._refreshPreview(); + self.stackIsUpdating = false; + }, + clearFileStack: function () { + var self = this; + self.fileManager.clear(); + self._initResumableUpload(); + if (self.enableResumableUpload) { + if (self.showPause === null) { + self.showPause = true; + } + if (self.showCancel === null) { + self.showCancel = false; + } + } else { + self.showPause = false; + if (self.showCancel === null) { + self.showCancel = true; + } + } + return self.$element; + }, + getFileStack: function () { + return this.fileManager.stack; + }, + getFileList: function () { + return this.fileManager.list(); + }, + getFilesSize: function () { + return this.fileManager.getTotalSize(); + }, + getFilesCount: function (includeInitial) { + var self = this, len = self.isAjaxUpload ? self.fileManager.count() : self._inputFileCount(); + if (includeInitial) { + len += self.previewCache.count(true); + } + return self._getFileCount(len); + }, + _initCapStatus: function (status) { + var self = this, $cap = self.$caption; + $cap.removeClass('is-valid file-processing'); + if (!status) { + return; + } + if (status === 'processing') { + $cap.addClass('file-processing'); + } else { + $cap.addClass('is-valid'); + } + }, + _toggleLoading: function (type) { + var self = this; + self.$previewStatus.html(type === 'hide' ? '' : self.msgProcessing); + self.$container.removeClass('file-thumb-loading'); + self._initCapStatus(type === 'hide' ? '' : 'processing'); + if (type !== 'hide') { + if (self.dropZoneEnabled) { + self.$container.find('.file-drop-zone .' + self.dropZoneTitleClass).remove(); + } + self.$container.addClass('file-thumb-loading'); + } + }, + _initFileSelected: function () { + var self = this, $el = self.$element, $body = $(document.body), ev = 'focusin.fileinput focusout.fileinput'; + if ($body.length) { + $body.off(ev).on('focusout.fileinput', function () { + self._toggleLoading('show'); + }).on('focusin.fileinput', function () { + setTimeout(function () { + if (!$el.val()) { + self._setFileDropZoneTitle(); + } + $body.off(ev); + self._toggleLoading('hide'); + }, 2500); + }); + } else { + self._toggleLoading('hide'); + } + }, + readFiles: function (files) { + this.reader = new FileReader(); + var self = this, reader = self.reader, $container = self.$previewContainer, + $status = self.$previewStatus, msgLoading = self.msgLoading, msgProgress = self.msgProgress, + previewInitId = self.previewInitId, numFiles = files.length, settings = self.fileTypeSettings, + readFile, fileTypes = self.allowedFileTypes, typLen = fileTypes ? fileTypes.length : 0, + fileExt = self.allowedFileExtensions, strExt = $h.isEmpty(fileExt) ? '' : fileExt.join(', '), + throwError = function (msg, file, previewId, index, fileId) { + var $thumb, p1 = $.extend(true, {}, self._getOutData(null, {}, {}, files), + {id: previewId, index: index, fileId: fileId}), + p2 = {id: previewId, index: index, fileId: fileId, file: file, files: files}; + self._previewDefault(file, true); + $thumb = self._getFrame(previewId, true); + self._toggleLoading('hide'); + if (self.isAjaxUpload) { + setTimeout(function () { + readFile(index + 1); + }, self.processDelay); + } else { + self.unlock(); + numFiles = 0; + } + if (self.removeFromPreviewOnError && $thumb.length) { + $thumb.remove(); + } else { + self._initFileActions(); + $thumb.find('.kv-file-upload').remove(); + } + self.isPersistentError = true; + self.isError = self.isAjaxUpload ? self._showFileError(msg, p1) : self._showError(msg, p2); + self._updateFileDetails(numFiles); + }; + self.fileManager.clearImages(); + $.each(files, function (key, file) { + var func = self.fileTypeSettings.image; + if (func && func(file.type)) { + self.fileManager.totalImages++; + } + }); + readFile = function (i) { + var $error = self.$errorContainer, errors, fm = self.fileManager; + if (i >= numFiles) { + self.unlock(); + if (self.duplicateErrors.length) { + errors = '
  • ' + self.duplicateErrors.join('
  • ') + '
  • '; + if ($error.find('ul').length === 0) { + $h.setHtml($error, self.errorCloseButton + '
      ' + errors + '
    '); + } else { + $error.find('ul').append(errors); + } + $error.fadeIn(self.fadeDelay); + self._handler($error.find('.kv-error-close'), 'click', function () { + $error.fadeOut(self.fadeDelay); + }); + self.duplicateErrors = []; + } + if (self.isAjaxUpload) { + self._raise('filebatchselected', [fm.stack]); + if (fm.count() === 0 && !self.isError) { + self.reset(); + } + } else { + self._raise('filebatchselected', [files]); + } + $container.removeClass('file-thumb-loading'); + self._initCapStatus('valid'); + $status.html(''); + return; + } + self.lock(true); + var file = files[i], id, previewId, fileProcessed, + fSize = (file && file.size || 0), sizeHuman = self._getSize(fSize, true), j, msg, + fnImage = settings.image, chk, typ, typ1, typ2, caption, fileSize = fSize / self.bytesToKB, + fileExtExpr = '', previewData, fileCount = 0, strTypes = '', fileId, canLoad, + fileReaderAborted = false, func, knownTypes = 0, isImage, processFileLoaded, initFileData; + initFileData = function (dataSource) { + dataSource = dataSource || file; + id = fileId = self._getFileId(file); + previewId = previewInitId + '-' + id; + previewData = $h.createObjectURL(dataSource); + caption = self._getFileName(file, ''); + }; + processFileLoaded = function () { + var isImageResized = !!fm.loadedImages[id], msg = msgProgress.setTokens({ + 'index': i + 1, + 'files': numFiles, + 'percent': 50, + 'name': caption + }); + setTimeout(function () { + $status.html(msg); + self._updateFileDetails(numFiles); + if (self.getFilesCount(true) > 0 && self.getFrames(':visible')) { + self.$dropZone.find('.' + self.dropZoneTitleClass).remove(); + } + readFile(i + 1); + }, self.processDelay); + if (self._raise('fileloaded', [file, previewId, id, i, reader]) && self.isAjaxUpload) { + if (!isImageResized) { + fm.add(file); + } + } else { + if (isImageResized) { + fm.removeFile(id); + } + } + }; + if (!file) { + return; + } + initFileData(); + + if (typLen > 0) { + for (j = 0; j < typLen; j++) { + typ1 = fileTypes[j]; + typ2 = self.msgFileTypes[typ1] || typ1; + strTypes += j === 0 ? typ2 : ', ' + typ2; + } + } + if (caption === false) { + readFile(i + 1); + return; + } + if (caption.length === 0) { + msg = self.msgInvalidFileName.replace('{name}', $h.htmlEncode($h.getFileName(file), '[unknown]')); + throwError(msg, file, previewId, i, fileId); + return; + } + if (!$h.isEmpty(fileExt)) { + fileExtExpr = new RegExp('\\.(' + fileExt.join('|') + ')$', 'i'); + } + if (self.isAjaxUpload && fm.exists(fileId) || self._getFrame(previewId, true).length) { + var p2 = {id: previewId, index: i, fileId: fileId, file: file, files: files}; + msg = self.msgDuplicateFile.setTokens({name: caption, size: sizeHuman}); + if (self.isAjaxUpload) { + if (!self.stackIsUpdating) { + self.duplicateErrors.push(msg); + self.isDuplicateError = true; + self._raise('fileduplicateerror', [file, fileId, caption, sizeHuman, previewId, i]); + } + readFile(i + 1); + self._updateFileDetails(numFiles); + } else { + self._showError(msg, p2); + self.unlock(); + numFiles = 0; + self._clearFileInput(); + self.reset(); + self._updateFileDetails(numFiles); + } + return; + } + if (self.maxFileSize > 0 && fileSize > self.maxFileSize) { + msg = self.msgSizeTooLarge.setTokens({ + 'name': caption, + 'size': sizeHuman, + 'maxSize': self._getSize(self.maxFileSize * self.bytesToKB, true) + }); + throwError(msg, file, previewId, i, fileId); + return; + } + if (self.minFileSize !== null && fileSize <= $h.getNum(self.minFileSize)) { + msg = self.msgSizeTooSmall.setTokens({ + 'name': caption, + 'size': sizeHuman, + 'minSize': self._getSize(self.minFileSize * self.bytesToKB, true) + }); + throwError(msg, file, previewId, i, fileId); + return; + } + if (!$h.isEmpty(fileTypes) && $h.isArray(fileTypes)) { + for (j = 0; j < fileTypes.length; j += 1) { + typ = fileTypes[j]; + func = settings[typ]; + fileCount += !func || (typeof func !== 'function') ? 0 : (func(file.type, + $h.getFileName(file)) ? 1 : 0); + } + if (fileCount === 0) { + msg = self.msgInvalidFileType.setTokens({name: caption, types: strTypes}); + throwError(msg, file, previewId, i, fileId); + return; + } + } + if (fileCount === 0 && !$h.isEmpty(fileExt) && $h.isArray(fileExt) && !$h.isEmpty(fileExtExpr)) { + chk = $h.compare(caption, fileExtExpr); + fileCount += $h.isEmpty(chk) ? 0 : chk.length; + if (fileCount === 0) { + msg = self.msgInvalidFileExtension.setTokens({name: caption, extensions: strExt}); + throwError(msg, file, previewId, i, fileId); + return; + } + } + if (!self._canPreview(file)) { + canLoad = self._raise('filebeforeload', [file, i, reader]); + if (self.isAjaxUpload && canLoad) { + fm.add(file); + } + if (self.showPreview && canLoad) { + $container.addClass('file-thumb-loading'); + self._initCapStatus('processing'); + self._previewDefault(file); + self._initFileActions(); + } + setTimeout(function () { + if (canLoad) { + self._updateFileDetails(numFiles); + } + readFile(i + 1); + self._raise('fileloaded', [file, previewId, id, i]); + }, 10); + return; + } + isImage = fnImage(file.type, caption); + $status.html(msgLoading.replace('{index}', i + 1).replace('{files}', numFiles)); + $container.addClass('file-thumb-loading'); + self._initCapStatus('processing'); + reader.onerror = function (evt) { + self._errorHandler(evt, caption); + }; + reader.onload = function (theFile) { + var hex, fileInfo, fileData, byte, bytes = [], contents, mime, + processPreview = function (fType, ext) { + if ($h.isEmpty(fType)) { // look for ascii text content + contents = $h.arrayBuffer2String(reader.result); + fType = $h.isSvg(contents) ? 'image/svg+xml' : $h.getMimeType(hex, contents, file.type); + } + fileInfo = {'name': caption, 'type': fType || ''}; + if (ext && typeof File !== "undefined") { + try { + var fName = fileInfo.filename = caption + '.' + ext; + fileProcessed = new File([file], fName, {type: fileInfo.type}); + initFileData(fileProcessed); + } catch (err) { + } + } + isImage = fnImage(fType, ''); + if (isImage) { + var newReader = new FileReader(); + newReader.onerror = function (theFileNew) { + self._errorHandler(theFileNew, caption); + }; + newReader.onload = function (theFileNew) { + if (self.isAjaxUpload && !self._raise('filebeforeload', [file, i, reader])) { + fileReaderAborted = true; + self._resetCaption(); + reader.abort(); + $status.html(''); + $container.removeClass('file-thumb-loading'); + self._initCapStatus('valid'); + self.enable(); + return; + } + self._previewFile(i, file, theFileNew, previewData, fileInfo); + self._initFileActions(); + processFileLoaded(); + }; + newReader.readAsDataURL(file); + return; + } + if (self.isAjaxUpload && !self._raise('filebeforeload', [file, i, reader])) { + fileReaderAborted = true; + self._resetCaption(); + reader.abort(); + $status.html(''); + $container.removeClass('file-thumb-loading'); + self._initCapStatus('valid'); + self.enable(); + return; + } + self._previewFile(i, file, theFile, previewData, fileInfo); + self._initFileActions(); + processFileLoaded(); + }; + mime = file.type; + fileInfo = {'name': caption, 'type': mime}; + $.each(settings, function (k, f) { + if (k !== 'object' && k !== 'other' && typeof f === 'function' && f(mime, caption)) { + knownTypes++; + } + }); + if (typeof FileTypeParser !== "undefined") { + fileData = new Uint8Array(theFile.target.result); + new FileTypeParser().parse(fileData).then(function (result) { + processPreview(result && result.mime || mime, result && result.ext || ''); + }); + } else { + if (knownTypes === 0) { // auto detect mime types from content if no known file types detected + fileData = new Uint8Array(theFile.target.result); + for (j = 0; j < fileData.length; j++) { + byte = fileData[j].toString(16); + bytes.push(byte); + } + hex = bytes.join('').toLowerCase().substring(0, 8); + mime = $h.getMimeType(hex, '', ''); + } + processPreview(mime); + } + }; + reader.onprogress = function (data) { + if (data.lengthComputable) { + var fact = (data.loaded / data.total) * 100, progress = Math.ceil(fact); + msg = msgProgress.setTokens({ + 'index': i + 1, + 'files': numFiles, + 'percent': progress, + 'name': caption + }); + setTimeout(function () { + if (!fileReaderAborted) { + $status.html(msg); + } + }, self.processDelay); + } + }; + reader.readAsArrayBuffer(file); + }; + + readFile(0); + self._updateFileDetails(numFiles); + }, + lock: function (selectMode) { + var self = this, $container = self.$container; + self._resetErrors(); + self.disable(); + if (!selectMode && self.showCancel) { + $container.find('.fileinput-cancel').show(); + } + if (!selectMode && self.showPause) { + $container.find('.fileinput-pause').show(); + } + self._initCapStatus('processing'); + self._raise('filelock', [self.fileManager.stack, self._getExtraData()]); + return self.$element; + }, + unlock: function (reset) { + var self = this, $container = self.$container; + if (reset === undefined) { + reset = true; + } + self.enable(); + $container.removeClass('is-locked'); + if (self.showCancel) { + $container.find('.fileinput-cancel').hide(); + } + if (self.showPause) { + $container.find('.fileinput-pause').hide(); + } + if (reset) { + self._resetFileStack(); + } + self._initCapStatus(); + self._raise('fileunlock', [self.fileManager.stack, self._getExtraData()]); + return self.$element; + }, + resume: function () { + var self = this, fm = self.fileManager, flag = false, rm = self.resumableManager; + fm.bpsLog = []; + fm.bps = 0; + if (!self.enableResumableUpload) { + return self.$element; + } + if (self.paused) { + self._toggleResumableProgress(self.progressPauseTemplate, self.msgUploadResume); + } else { + flag = true; + } + self.paused = false; + if (flag) { + self._toggleResumableProgress(self.progressInfoTemplate, self.msgUploadBegin); + } + setTimeout(function () { + rm.upload(); + }, self.processDelay); + return self.$element; + }, + paste: function (e) { + var self = this, ev = e.originalEvent, files = ev.clipboardData && ev.clipboardData.files || null; + if (files) { + self._dropFiles(e, files); + } + return self.$element; + }, + pause: function () { + var self = this, rm = self.resumableManager, xhr = self.ajaxRequests, len = xhr.length, i, + pct = rm.getProgress(), actions = self.fileActionSettings, tm = self.taskManager, + pool = tm.getPool(rm.id); + if (!self.enableResumableUpload) { + return self.$element; + } else { + if (pool) { + pool.cancel(); + } + } + self._raise('fileuploadpaused', [self.fileManager, rm]); + if (len > 0) { + for (i = 0; i < len; i += 1) { + self.paused = true; + xhr[i].abort(); + } + } + if (self.showPreview) { + self._getThumbs().each(function () { + var $thumb = $(this), t = self._getLayoutTemplate('stats'), stats, + $indicator = $thumb.find('.file-upload-indicator'); + $thumb.removeClass('file-uploading'); + if ($indicator.attr('title') === actions.indicatorLoadingTitle) { + self._setThumbStatus($thumb, 'Paused'); + stats = t.setTokens({pendingTime: self.msgPaused, uploadSpeed: ''}); + self.paused = true; + self._setProgress(pct, $thumb.find('.file-thumb-progress'), pct + '%', stats); + } + if (!self._getThumbFile($thumb)) { + $thumb.find('.kv-file-remove').removeClass('disabled').removeAttr('disabled'); + } + }); + } + self._setProgress(101, self.$progress, self.msgPaused); + return self.$element; + }, + cancel: function () { + var self = this, xhr = self.ajaxRequests, + rm = self.resumableManager, tm = self.taskManager, + pool = rm ? tm.getPool(rm.id) : undefined, len = xhr.length, i; + if (self.enableResumableUpload && pool) { + pool.cancel().done(function () { + self._setProgressCancelled(); + }); + rm.reset(); + self._raise('fileuploadcancelled', [self.fileManager, rm]); + } else { + if (self.ajaxPool) { + self.ajaxPool.cancel(); + } + self._raise('fileuploadcancelled', [self.fileManager]); + } + self._initAjax(); + if (len > 0) { + for (i = 0; i < len; i += 1) { + self.cancelling = true; + xhr[i].abort(); + } + } + self._getThumbs().each(function () { + var $thumb = $(this), $prog = $thumb.find('.file-thumb-progress'); + $thumb.removeClass('file-uploading'); + self._setProgress(0, $prog); + $prog.hide(); + if (!self._getThumbFile($thumb)) { + $thumb.find('.kv-file-upload').removeClass('disabled').removeAttr('disabled'); + $thumb.find('.kv-file-remove').removeClass('disabled').removeAttr('disabled'); + } + self.unlock(); + }); + setTimeout(function () { + self._setProgressCancelled(); + }, self.processDelay); + return self.$element; + }, + clear: function () { + var self = this, cap; + if (!self._raise('fileclear')) { + return; + } + self.clearInput = true; + self.$btnUpload.removeAttr('disabled'); + self._getThumbs().find('video,audio,img').each(function () { + $h.cleanMemory($(this)); + }); + self._clearFileInput(); + self._resetUpload(); + self.clearFileStack(); + self.isDuplicateError = false; + self.isPersistentError = false; + self._resetErrors(true); + if (self._hasInitialPreview()) { + self._showFileIcon(); + self._resetPreview(); + self._initPreviewActions(); + self.$container.removeClass('file-input-new'); + } else { + self._getThumbs().each(function () { + self._clearObjects($(this)); + }); + if (self.isAjaxUpload) { + self.previewCache.data = {}; + } + self.$preview.html(''); + cap = (!self.overwriteInitial && self.initialCaption.length > 0) ? self.initialCaption : ''; + self.$caption.attr('title', '').val(cap); + $h.addCss(self.$container, 'file-input-new'); + self._validateDefaultPreview(); + } + if (self.$container.find($h.FRAMES).length === 0) { + if (!self._initCaption()) { + self.$captionContainer.removeClass('icon-visible'); + } + } + self._hideFileIcon(); + if (self.focusCaptionOnClear) { + self.$captionContainer.focus(); + } + self._setFileDropZoneTitle(); + self._raise('filecleared'); + return self.$element; + }, + reset: function () { + var self = this; + if (!self._raise('filereset')) { + return; + } + self.lastProgress = 0; + self._resetPreview(); + self.$container.find('.fileinput-filename').text(''); + $h.addCss(self.$container, 'file-input-new'); + if (self.getFrames().length) { + self.$container.removeClass('file-input-new'); + } + self.clearFileStack(); + self._setFileDropZoneTitle(); + return self.$element; + }, + disable: function () { + var self = this, $container = self.$container; + self.isDisabled = true; + self._raise('filedisabled'); + self.$element.attr('disabled', 'disabled'); + $container.addClass('is-locked'); + $h.addCss($container.find('.btn-file'), 'disabled'); + $container.find('.kv-fileinput-caption').addClass('file-caption-disabled'); + $container.find('.fileinput-remove, .fileinput-upload, .file-preview-frame button') + .attr('disabled', true); + self._initDragDrop(); + return self.$element; + }, + enable: function () { + var self = this, $container = self.$container; + self.isDisabled = false; + self._raise('fileenabled'); + self.$element.removeAttr('disabled'); + $container.removeClass('is-locked'); + $container.find('.kv-fileinput-caption').removeClass('file-caption-disabled'); + $container.find('.fileinput-remove, .fileinput-upload, .file-preview-frame button') + .removeAttr('disabled'); + $container.find('.btn-file').removeClass('disabled'); + self._initDragDrop(); + return self.$element; + }, + upload: function () { + var self = this, fm = self.fileManager, totLen = fm.count(), i, outData, tm = self.taskManager, + hasExtraData = !$.isEmptyObject(self._getExtraData()); + fm.bpsLog = []; + fm.bps = 0; + if (!self.isAjaxUpload || self.isDisabled || !self._isFileSelectionValid(totLen)) { + return; + } + self.lastProgress = 0; + self._resetUpload(); + if (totLen === 0 && !hasExtraData) { + self._showFileError(self.msgUploadEmpty); + return; + } + self.cancelling = false; + self.uploadInitiated = true; + self._showProgress(); + self.lock(); + if (totLen === 0 && hasExtraData) { + self._setProgress(2); + self._uploadExtraOnly(); + return; + } + if (self.enableResumableUpload) { + return self.resume(); + } + if (self.uploadAsync || self.enableResumableUpload) { + outData = self._getOutData(null); + if (!self._checkBatchPreupload(outData)) { + return; + } + self.fileBatchCompleted = false; + self.uploadCache = []; + $.each(self.getFileStack(), function (id) { + var previewId = self._getThumbId(id); + self.uploadCache.push({id: previewId, content: null, config: null, tags: null, append: true}); + }); + self.$preview.find('.file-preview-initial').removeClass($h.SORT_CSS); + self._initSortable(); + } + self._setProgress(2); + self.hasInitData = false; + if (self.uploadAsync) { + i = 0; + var pool = self.ajaxPool = tm.addPool($h.uniqId()); + $.each(self.getFileStack(), function (id) { + pool.addTask(id + i, function (deferrer) { + self._uploadSingle(i, id, true, deferrer); + }); + i++; + }); + + pool.run(self.maxAjaxThreads).done(function () { + self._log('Async upload batch completed successfully.'); + self._raise('filebatchuploadsuccess', [fm.stack, self._getExtraData()]); + }).fail(function () { + self._log('Async upload batch completed with errors.'); + self._raise('filebatchuploaderror', [fm.stack, self._getExtraData()]); + }); + return; + } + self._uploadBatch(); + return self.$element; + }, + destroy: function () { + var self = this, $form = self.$form, $cont = self.$container, $el = self.$element, ns = self.namespace; + $(document).off(ns); + $(window).off(ns); + if ($form && $form.length) { + $form.off(ns); + } + if (self.isAjaxUpload) { + self._clearFileInput(); + } + self._cleanup(); + self._initPreviewCache(); + $el.insertBefore($cont).off(ns).removeData(); + $cont.off().remove(); + return $el; + }, + refresh: function (options) { + var self = this, $el = self.$element; + if (typeof options !== 'object' || $h.isEmpty(options)) { + options = self.options; + } else { + options = $.extend(true, {}, self.options, options); + } + self._init(options, true); + self._listen(); + return $el; + }, + zoom: function (frameId) { + var self = this, $frame = self._getFrame(frameId); + self._showModal($frame); + }, + getExif: function (frameId) { + var self = this, $frame = self._getFrame(frameId); + return $frame && $frame.data('exif') || null; + }, + getFrames: function (cssFilter) { + var self = this, $frames; + cssFilter = cssFilter || ''; + $frames = self.$preview.find($h.FRAMES + cssFilter); + if (self.reversePreviewOrder) { + $frames = $($frames.get().reverse()); + } + return $frames; + }, + getPreview: function () { + var self = this; + return { + content: self.initialPreview, + config: self.initialPreviewConfig, + tags: self.initialPreviewThumbTags + }; + } + }; + + $.fn.fileinput = function (option) { + if (!$h.hasFileAPISupport() && !$h.isIE(9)) { + return; + } + var args = Array.apply(null, arguments), retvals = []; + args.shift(); + this.each(function () { + var self = $(this), data = self.data('fileinput'), options = typeof option === 'object' && option, + theme = options.theme || self.data('theme'), l = {}, t = {}, + lang = options.language || self.data('language') || $.fn.fileinput.defaults.language || 'en', opt; + if (!data) { + if (theme) { + t = $.fn.fileinputThemes[theme] || {}; + } + if (lang !== 'en' && !$h.isEmpty($.fn.fileinputLocales[lang])) { + l = $.fn.fileinputLocales[lang] || {}; + } + opt = $.extend(true, {}, $.fn.fileinput.defaults, t, $.fn.fileinputLocales.en, l, options, self.data()); + data = new FileInput(this, opt); + self.data('fileinput', data); + } + + if (typeof option === 'string') { + retvals.push(data[option].apply(data, args)); + } + }); + switch (retvals.length) { + case 0: + return this; + case 1: + return retvals[0]; + default: + return retvals; + } + }; + + var IFRAME_ATTRIBS = 'class="kv-preview-data file-preview-pdf" src="{renderer}?file={data}" {style}', + defBtnCss1 = 'btn btn-sm btn-kv ' + $h.defaultButtonCss(), defBtnCss2 = 'btn ' + $h.defaultButtonCss(); + + $.fn.fileinput.defaults = { + language: 'zh', + bytesToKB: 1024, + showCaption: true, + showBrowse: true, + showPreview: true, + showRemove: true, + showUpload: true, + showUploadStats: true, + showCancel: null, + showPause: null, + showClose: true, + showUploadedThumbs: true, + showConsoleLogs: false, + browseOnZoneClick: false, + autoReplace: false, + showDescriptionClose: true, + autoOrientImage: function () { // applicable for JPEG images only and non ios safari + var ua = window.navigator.userAgent, webkit = !!ua.match(/WebKit/i), + iOS = !!ua.match(/iP(od|ad|hone)/i), iOSSafari = iOS && webkit && !ua.match(/CriOS/i); + return !iOSSafari; + }, + autoOrientImageInitial: true, + showExifErrorLog: false, + required: false, + rtl: false, + hideThumbnailContent: false, + encodeUrl: true, + focusCaptionOnBrowse: true, + focusCaptionOnClear: true, + generateFileId: null, + previewClass: '', + captionClass: '', + frameClass: 'krajee-default', + mainClass: '', + inputGroupClass: '', + mainTemplate: null, + fileSizeGetter: null, + initialCaption: '', + initialPreview: [], + initialPreviewDelimiter: '*$$*', + initialPreviewAsData: false, + initialPreviewFileType: 'image', + initialPreviewConfig: [], + initialPreviewThumbTags: [], + previewThumbTags: {}, + initialPreviewShowDelete: true, + initialPreviewDownloadUrl: '', + removeFromPreviewOnError: false, + deleteUrl: '', + deleteExtraData: {}, + overwriteInitial: true, + sanitizeZoomCache: function (content) { + var $container = $h.createElement(content); + $container.find('input,textarea,select,datalist,form,.file-thumbnail-footer').remove(); + return $container.html(); + }, + previewZoomButtonIcons: { + prev: '', + next: '', + toggleheader: '', + fullscreen: '', + borderless: '', + close: '' + }, + previewZoomButtonClasses: { + prev: 'btn btn-default btn-outline-secondary btn-navigate', + next: 'btn btn-default btn-outline-secondary btn-navigate', + rotate: defBtnCss1, + toggleheader: defBtnCss1, + fullscreen: defBtnCss1, + borderless: defBtnCss1, + close: defBtnCss1 + }, + previewTemplates: {}, + previewContentTemplates: {}, + preferIconicPreview: false, + preferIconicZoomPreview: false, + alwaysPreviewFileExtensions: [], + rotatableFileExtensions: ['jpg', 'jpeg', 'png', 'gif'], + allowedFileTypes: null, + allowedFileExtensions: null, + allowedPreviewTypes: undefined, + allowedPreviewMimeTypes: null, + allowedPreviewExtensions: null, + disabledPreviewTypes: undefined, + disabledPreviewExtensions: ['msi', 'exe', 'com', 'zip', 'rar', 'app', 'vb', 'scr'], + disabledPreviewMimeTypes: null, + defaultPreviewContent: null, + customLayoutTags: {}, + customPreviewTags: {}, + previewFileIcon: '', + previewFileIconClass: 'file-other-icon', + previewFileIconSettings: {}, + previewFileExtSettings: {}, + buttonLabelClass: 'hidden-xs', + browseIcon: ' ', + browseClass: 'btn btn-primary', + removeIcon: '', + removeClass: defBtnCss2, + cancelIcon: '', + cancelClass: defBtnCss2, + pauseIcon: '', + pauseClass: defBtnCss2, + uploadIcon: '', + uploadClass: defBtnCss2, + uploadUrl: null, + uploadUrlThumb: null, + uploadAsync: true, + uploadParamNames: { + chunkCount: 'chunkCount', + chunkIndex: 'chunkIndex', + chunkSize: 'chunkSize', + chunkSizeStart: 'chunkSizeStart', + chunksUploaded: 'chunksUploaded', + fileBlob: 'fileBlob', + fileId: 'fileId', + fileName: 'fileName', + fileRelativePath: 'fileRelativePath', + fileSize: 'fileSize', + retryCount: 'retryCount' + }, + maxAjaxThreads: 5, + fadeDelay: 800, + processDelay: 100, + bitrateUpdateDelay: 500, + queueDelay: 10, // must be lesser than process delay + progressDelay: 0, // must be lesser than process delay + enableResumableUpload: false, + resumableUploadOptions: { + fallback: null, + testUrl: null, // used for checking status of chunks/ files previously / partially uploaded + chunkSize: 2048, // in KB + maxThreads: 4, + maxRetries: 3, + showErrorLog: true, + retainErrorHistory: false, // when set to true, display complete error history always unless user explicitly resets upload + skipErrorsAndProceed: false // when set to true, files with errors will be skipped and upload will continue with other files + }, + uploadExtraData: {}, + zoomModalHeight: 485, // 5px more than the default preview content heights set for text, html, pdf etc. + minImageWidth: null, + minImageHeight: null, + maxImageWidth: null, + maxImageHeight: null, + resizeImage: false, + resizePreference: 'width', + resizeQuality: 0.92, + resizeDefaultImageType: 'image/jpeg', + resizeIfSizeMoreThan: 0, // in KB + minFileSize: -1, + maxFileSize: 0, + maxFilePreviewSize: 25600, // 25 MB + minFileCount: 0, + maxFileCount: 0, + maxTotalFileCount: 0, + validateInitialCount: false, + msgValidationErrorClass: 'text-danger', + msgValidationErrorIcon: ' ', + msgErrorClass: 'file-error-message', + progressThumbClass: 'progress-bar progress-bar-striped active progress-bar-animated', + progressClass: 'progress-bar bg-success progress-bar-success progress-bar-striped active progress-bar-animated', + progressInfoClass: 'progress-bar bg-info progress-bar-info progress-bar-striped active progress-bar-animated', + progressCompleteClass: 'progress-bar bg-success progress-bar-success', + progressPauseClass: 'progress-bar bg-primary progress-bar-primary progress-bar-striped active progress-bar-animated', + progressErrorClass: 'progress-bar bg-danger progress-bar-danger', + progressUploadThreshold: 99, + previewFileType: 'image', + elCaptionContainer: null, + elCaptionText: null, + elPreviewContainer: null, + elPreviewImage: null, + elPreviewStatus: null, + elErrorContainer: null, + errorCloseButton: undefined, + slugCallback: null, + dropZoneEnabled: true, + dropZoneTitleClass: 'file-drop-zone-title', + fileActionSettings: {}, + otherActionButtons: '', + textEncoding: 'UTF-8', + preProcessUpload: null, + ajaxSettings: {}, + ajaxDeleteSettings: {}, + showAjaxErrorDetails: true, + mergeAjaxCallbacks: false, + mergeAjaxDeleteCallbacks: false, + retryErrorUploads: true, + reversePreviewOrder: false, + usePdfRenderer: function () { + var isIE11 = !!window.MSInputMethodContext && !!document.documentMode; + return !!navigator.userAgent.match(/(iPod|iPhone|iPad|Android)/i) || isIE11; + }, + pdfRendererUrl: '', + pdfRendererTemplate: '', + tabIndexConfig: { + browse: 500, + remove: 500, + upload: 500, + cancel: null, + pause: null, + modal: -1 + } + }; + + // noinspection HtmlUnknownAttribute + $.fn.fileinputLocales.en = { + sizeUnits: ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], + bitRateUnits: ['B/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s', 'PB/s', 'EB/s', 'ZB/s', 'YB/s'], + fileSingle: 'file', + filePlural: 'files', + browseLabel: 'Browse …', + removeLabel: 'Remove', + removeTitle: 'Clear all unprocessed files', + cancelLabel: 'Cancel', + cancelTitle: 'Abort ongoing upload', + pauseLabel: 'Pause', + pauseTitle: 'Pause ongoing upload', + uploadLabel: 'Upload', + uploadTitle: 'Upload selected files', + msgNo: 'No', + msgNoFilesSelected: 'No files selected', + msgCancelled: 'Cancelled', + msgPaused: 'Paused', + msgPlaceholder: 'Select {files} ...', + msgZoomModalHeading: 'Detailed Preview', + msgFileRequired: 'You must select a file to upload.', + msgSizeTooSmall: 'File "{name}" ({size}) is too small and must be larger than {minSize}.', + msgSizeTooLarge: 'File "{name}" ({size}) exceeds maximum allowed upload size of {maxSize}.', + msgFilesTooLess: 'You must select at least {n} {files} to upload.', + msgFilesTooMany: 'Number of files selected for upload ({n}) exceeds maximum allowed limit of {m}.', + msgTotalFilesTooMany: 'You can upload a maximum of {m} files ({n} files detected).', + msgFileNotFound: 'File "{name}" not found!', + msgFileSecured: 'Security restrictions prevent reading the file "{name}".', + msgFileNotReadable: 'File "{name}" is not readable.', + msgFilePreviewAborted: 'File preview aborted for "{name}".', + msgFilePreviewError: 'An error occurred while reading the file "{name}".', + msgInvalidFileName: 'Invalid or unsupported characters in file name "{name}".', + msgInvalidFileType: 'Invalid type for file "{name}". Only "{types}" files are supported.', + msgInvalidFileExtension: 'Invalid extension for file "{name}". Only "{extensions}" files are supported.', + msgFileTypes: { + 'image': 'image', + 'html': 'HTML', + 'text': 'text', + 'video': 'video', + 'audio': 'audio', + 'flash': 'flash', + 'pdf': 'PDF', + 'object': 'object' + }, + msgUploadAborted: 'The file upload was aborted', + msgUploadThreshold: 'Processing …', + msgUploadBegin: 'Initializing …', + msgUploadEnd: 'Done', + msgUploadResume: 'Resuming upload …', + msgUploadEmpty: 'No valid data available for upload.', + msgUploadError: 'Upload Error', + msgDeleteError: 'Delete Error', + msgProgressError: 'Error', + msgValidationError: 'Validation Error', + msgLoading: 'Loading file {index} of {files} …', + msgProgress: 'Loading file {index} of {files} - {name} - {percent}% completed.', + msgSelected: '{n} {files} selected', + msgProcessing: 'Processing ...', + msgFoldersNotAllowed: 'Drag & drop files only! {n} folder(s) dropped were skipped.', + msgImageWidthSmall: 'Width of image file "{name}" must be at least {size} px (detected {dimension} px).', + msgImageHeightSmall: 'Height of image file "{name}" must be at least {size} px (detected {dimension} px).', + msgImageWidthLarge: 'Width of image file "{name}" cannot exceed {size} px (detected {dimension} px).', + msgImageHeightLarge: 'Height of image file "{name}" cannot exceed {size} px (detected {dimension} px).', + msgImageResizeError: 'Could not get the image dimensions to resize.', + msgImageResizeException: 'Error while resizing the image.
    {errors}
    ', + msgAjaxError: 'Something went wrong with the {operation} operation. Please try again later!', + msgAjaxProgressError: '{operation} failed', + msgDuplicateFile: 'File "{name}" of same size "{size}" has already been selected earlier. Skipping duplicate selection.', + msgResumableUploadRetriesExceeded: 'Upload aborted beyond {max} retries for file {file}! Error Details:
    {error}
    ', + msgPendingTime: '{time} remaining', + msgCalculatingTime: 'calculating time remaining', + ajaxOperations: { + deleteThumb: 'file delete', + uploadThumb: 'file upload', + uploadBatch: 'batch file upload', + uploadExtra: 'form data upload' + }, + dropZoneTitle: 'Drag & drop files here …', + dropZoneClickTitle: '
    (or click to select {files})', + previewZoomButtonTitles: { + prev: 'View previous file', + next: 'View next file', + rotate: 'Rotate 90 deg. clockwise', + toggleheader: 'Toggle header', + fullscreen: 'Toggle full screen', + borderless: 'Toggle borderless mode', + close: 'Close detailed preview' + } + }; + + $.fn.fileinputLocales.zh = { + sizeUnits: ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], + bitRateUnits: ['B/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s', 'PB/s', 'EB/s', 'ZB/s', 'YB/s'], + fileSingle: '文件', + filePlural: '个文件', + browseLabel: '选择 …', + removeLabel: '移除', + removeTitle: '清除选中文件', + cancelLabel: '取消', + cancelTitle: '取消进行中的上传', + pauseLabel: '暂停', + pauseTitle: '暂停上传', + uploadLabel: '上传', + uploadTitle: '上传选中文件', + msgNo: '没有', + msgNoFilesSelected: '未选择文件', + msgPaused: '已暂停', + msgCancelled: '取消', + msgPlaceholder: '选择 {files} ...', + msgZoomModalHeading: '详细预览', + msgFileRequired: '必须选择一个文件上传.', + msgSizeTooSmall: '文件 "{name}" ({size}) 必须大于限定大小 {minSize}.', + msgSizeTooLarge: '文件 "{name}" ({size}) 超过了允许大小 {maxSize}.', + msgFilesTooLess: '你必须选择最少 {n} {files} 来上传. ', + msgFilesTooMany: '选择的上传文件个数 ({n}) 超出最大文件的限制个数 {m}.', + msgTotalFilesTooMany: '你最多可以上传 {m} 个文件 (当前有{n} 个文件).', + msgFileNotFound: '文件 "{name}" 未找到!', + msgFileSecured: '安全限制,为了防止读取文件 "{name}".', + msgFileNotReadable: '文件 "{name}" 不可读.', + msgFilePreviewAborted: '取消 "{name}" 的预览.', + msgFilePreviewError: '读取 "{name}" 时出现了一个错误.', + msgInvalidFileName: '文件名 "{name}" 包含非法字符.', + msgInvalidFileType: '不正确的类型 "{name}". 只支持 "{types}" 类型的文件.', + msgInvalidFileExtension: '不正确的文件扩展名 "{name}". 只支持 "{extensions}" 的文件扩展名.', + msgFileTypes: { + 'image': 'image', + 'html': 'HTML', + 'text': 'text', + 'video': 'video', + 'audio': 'audio', + 'flash': 'flash', + 'pdf': 'PDF', + 'object': 'object' + }, + msgUploadAborted: '该文件上传被中止', + msgUploadThreshold: '处理中 …', + msgUploadBegin: '正在初始化 …', + msgUploadEnd: '完成', + msgUploadResume: '继续上传 …', + msgUploadEmpty: '无效的文件上传.', + msgUploadError: '上传出错', + msgDeleteError: '删除出错', + msgProgressError: '上传出错', + msgValidationError: '验证错误', + msgLoading: '加载第 {index} 文件 共 {files} …', + msgProgress: '加载第 {index} 文件 共 {files} - {name} - {percent}% 完成.', + msgSelected: '{n} {files} 选中', + msgProcessing: '处理中 ...', + msgFoldersNotAllowed: '只支持拖拽文件! 跳过 {n} 拖拽的文件夹.', + msgImageWidthSmall: '图像文件的"{name}"的宽度必须是至少{size}像素.', + msgImageHeightSmall: '图像文件的"{name}"的高度必须至少为{size}像素.', + msgImageWidthLarge: '图像文件"{name}"的宽度不能超过{size}像素.', + msgImageHeightLarge: '图像文件"{name}"的高度不能超过{size}像素.', + msgImageResizeError: '无法获取的图像尺寸调整。', + msgImageResizeException: '调整图像大小时发生错误。
    {errors}
    ', + msgAjaxError: '{operation} 发生错误. 请重试!', + msgAjaxProgressError: '{operation} 失败', + msgDuplicateFile: '文件 "{name}",大小 "{size}" 已经被选中.忽略相同的文件.', + msgResumableUploadRetriesExceeded: '文件 {file} 上传失败超过 {max} 次重试 ! 错误详情:
    {error}
    ', + msgPendingTime: '{time} 剩余', + msgCalculatingTime: '计算剩余时间', + ajaxOperations: { + deleteThumb: '删除文件', + uploadThumb: '上传文件', + uploadBatch: '批量上传', + uploadExtra: '表单数据上传' + }, + dropZoneTitle: '拖拽文件到这里 …
    支持多文件同时上传', + dropZoneClickTitle: '
    (或点击{files}按钮选择文件)', + fileActionSettings: { + removeTitle: '删除文件', + uploadTitle: '上传文件', + downloadTitle: '下载文件', + uploadRetryTitle: '重试', + rotateTitle: '顺时针旋转90度', + zoomTitle: '查看详情', + dragTitle: '移动 / 重置', + indicatorNewTitle: '没有上传', + indicatorSuccessTitle: '上传', + indicatorErrorTitle: '上传错误', + indicatorPausedTitle: '上传已暂停', + indicatorLoadingTitle: '上传 …' + }, + previewZoomButtonTitles: { + prev: '预览上一个文件', + next: '预览下一个文件', + rotate: '顺时针旋转90度', + toggleheader: '缩放', + fullscreen: '全屏', + borderless: '无边界模式', + close: '关闭当前预览' + } + }; + + $.fn.fileinput.Constructor = FileInput; + + /** + * Convert automatically file inputs with class 'file' into a bootstrap fileinput control. + */ + $(document).ready(function () { + var $input = $('input.file[type=file]'); + if ($input.length) { + $input.fileinput(); + } + }); +})); \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.min.css b/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.min.css new file mode 100644 index 0000000..f8a3cd5 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.min.css @@ -0,0 +1,13 @@ +/*! + * bootstrap-fileinput v5.5.2 + * http://plugins.krajee.com/file-input + * + * Krajee default styling for bootstrap-fileinput. + * + * Author: Kartik Visweswaran + * Copyright: 2014 - 2022, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD-3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */ + .file-loading input[type=file],input[type=file].file-loading{width:0;height:0;}.file-no-browse{position:absolute;left:50%;bottom:20%;width:1px;height:1px;font-size:0;opacity:0;border:none;background:none;outline:none;box-shadow:none;}.kv-hidden,.file-caption-icon,.file-zoom-dialog .modal-header:before,.file-zoom-dialog .modal-header:after,.file-input-new .file-preview,.file-input-new .close,.file-input-new .glyphicon-file,.file-input-new .fileinput-remove-button,.file-input-new .fileinput-upload-button,.file-input-new .no-browse .input-group-btn,.file-input-ajax-new .fileinput-remove-button,.file-input-ajax-new .fileinput-upload-button,.file-input-ajax-new .no-browse .input-group-btn,.hide-content .kv-file-content,.is-locked .fileinput-upload-button,.is-locked .fileinput-remove-button{display:none;}.file-caption .input-group{align-items:center;}.btn-file input[type=file],.file-caption-icon,.file-preview .fileinput-remove,.krajee-default .file-thumb-progress,.file-zoom-dialog .btn-navigate,.file-zoom-dialog .floating-buttons{position:absolute;}.file-caption-icon .kv-caption-icon{line-height:inherit;}.file-input,.file-loading:before,.btn-file,.file-caption,.file-preview,.krajee-default.file-preview-frame,.krajee-default .file-thumbnail-footer,.file-zoom-dialog .modal-dialog{position:relative;}.file-error-message pre,.file-error-message ul,.krajee-default .file-actions,.krajee-default .file-other-error{text-align:left;}.file-error-message pre,.file-error-message ul{margin:0;}.krajee-default .file-drag-handle,.krajee-default .file-upload-indicator{float:left;margin-top:10px;width:16px;height:16px;}.file-thumb-progress .progress,.file-thumb-progress .progress-bar{font-family:Verdana,Helvetica,sans-serif;font-size:0.7rem;}.krajee-default .file-thumb-progress .progress,.kv-upload-progress .progress{background-color:#ccc;}.krajee-default .file-caption-info,.krajee-default .file-size-info{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;width:160px;height:15px;margin:auto;}.file-zoom-content > .file-object.type-video,.file-zoom-content > .file-object.type-flash,.file-zoom-content > .file-object.type-image{max-width:100%;max-height:100%;width:auto;}.file-zoom-content > .file-object.type-video,.file-zoom-content > .file-object.type-flash{height:100%;}.file-zoom-content > .file-object.type-pdf,.file-zoom-content > .file-object.type-html,.file-zoom-content > .file-object.type-text,.file-zoom-content > .file-object.type-default{width:100%;}.file-loading:before{content:" Loading...";display:inline-block;padding-left:20px;line-height:16px;font-size:13px;font-variant:small-caps;color:#999;background:transparent url(loading.gif) top left no-repeat;}.file-object{margin:0 0 -5px 0;padding:0;}.btn-file{overflow:hidden;}.btn-file input[type=file]{top:0;left:0;min-width:100%;min-height:100%;text-align:right;opacity:0;background:none repeat scroll 0 0 transparent;cursor:inherit;display:block;}.btn-file::-ms-browse{font-size:10000px;width:100%;height:100%;}.file-caption.icon-visible .file-caption-icon{display:inline-block;}.file-caption.icon-visible .file-caption-name{padding-left:25px;}.file-caption.icon-visible > .input-group-lg .file-caption-name{padding-left:30px;}.file-caption.icon-visible > .input-group-sm .file-caption-name{padding-left:22px;}.file-caption-name:not(.file-caption-disabled){background-color:transparent;}.file-caption-name.file-processing{font-style:italic;border-color:#bbb;opacity:0.5;}.file-caption-icon{padding:7px 5px;left:4px;}.input-group-lg .file-caption-icon{font-size:1.25rem;}.input-group-sm .file-caption-icon{font-size:0.875rem;padding:0.25rem;}.file-error-message{color:#a94442;background-color:#f2dede;margin:5px;border:1px solid #ebccd1;border-radius:4px;padding:15px;}.file-error-message pre{margin:5px 0;}.file-caption-disabled{background-color:#eee;cursor:not-allowed;opacity:1;}.file-preview{border-radius:5px;border:1px solid #ddd;padding:8px;width:100%;margin-bottom:5px;}.file-preview .btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px;}.file-preview .fileinput-remove{top:1px;right:1px;line-height:10px;}.file-preview .clickable{cursor:pointer;}.file-preview-image{font:40px Impact,Charcoal,sans-serif;color:#008000;width:auto;height:auto;max-width:100%;max-height:100%;}.krajee-default.file-preview-frame{margin:8px;border:1px solid rgba(0,0,0,0.2);box-shadow:0 0 10px 0 rgba(0,0,0,0.2);padding:6px;float:left;text-align:center;}.krajee-default.file-preview-frame .kv-file-content{width:213px;height:160px;}.krajee-default.file-preview-frame .kv-file-content.kv-pdf-rendered{width:400px;}.krajee-default.file-preview-frame[data-template="audio"] .kv-file-content{width:240px;height:55px;}.krajee-default.file-preview-frame .file-thumbnail-footer{height:70px;}.krajee-default.file-preview-frame:not(.file-preview-error):hover{border:1px solid rgba(0,0,0,0.3);box-shadow:0 0 10px 0 rgba(0,0,0,0.4);}.krajee-default .file-preview-text{color:#428bca;border:1px solid #ddd;outline:none;resize:none;}.krajee-default .file-preview-html{border:1px solid #ddd;}.krajee-default .file-other-icon{font-size:6em;line-height:1;}.krajee-default .file-footer-buttons{float:right;}.krajee-default .file-footer-caption{display:block;text-align:center;padding-top:4px;font-size:11px;color:#999;margin-bottom:30px;}.file-upload-stats{font-size:10px;text-align:center;width:100%;}.kv-upload-progress .file-upload-stats{font-size:12px;margin:-10px 0 5px;}.krajee-default .file-preview-error{opacity:0.65;box-shadow:none;}.krajee-default .file-thumb-progress{top:37px;left:0;right:0;}.krajee-default.kvsortable-ghost{background:#e1edf7;border:2px solid #a1abff;}.krajee-default .file-preview-other:hover{opacity:0.8;}.krajee-default .file-preview-frame:not(.file-preview-error) .file-footer-caption:hover{color:#000;}.kv-upload-progress .progress{height:20px;margin:10px 0;overflow:hidden;}.kv-upload-progress .progress-bar{height:20px;font-family:Verdana,Helvetica,sans-serif;}.file-zoom-dialog .file-other-icon{font-size:22em;font-size:50vmin;}.file-zoom-dialog .modal-dialog{width:auto;}.file-zoom-dialog .modal-header{display:flex;align-items:center;justify-content:space-between;}.file-zoom-dialog .btn-navigate{margin:0 0.1rem;padding:0;font-size:1.2rem;width:2.4rem;height:2.4rem;top:50%;border-radius:50%;text-align:center;}.btn-navigate *{width:auto;}.file-zoom-dialog .floating-buttons{top:5px;right:10px;}.file-zoom-dialog .btn-kv-prev{left:0;}.file-zoom-dialog .btn-kv-next{right:0;}.file-zoom-dialog .kv-zoom-header{padding:0.5rem;}.file-zoom-dialog .kv-zoom-body{padding:0.25rem;}.file-zoom-dialog .kv-zoom-description{position:absolute;opacity:0.8;font-size:0.8rem;background-color:#1a1a1a;padding:1rem;text-align:center;border-radius:0.5rem;color:#fff;left:15%;right:15%;bottom:15%;}.file-zoom-dialog .kv-desc-hide{float:right;color:#fff;padding:0 0.1rem;background:none;border:none;}.file-zoom-dialog .kv-desc-hide:hover{opacity:0.7;}.file-zoom-dialog .kv-desc-hide:focus{opacity:0.9;}.file-input-new .no-browse .form-control{border-top-right-radius:4px;border-bottom-right-radius:4px;}.file-input-ajax-new .no-browse .form-control{border-top-right-radius:4px;border-bottom-right-radius:4px;}.file-caption{width:100%;position:relative;}.file-thumb-loading{background:transparent url(loading.gif) no-repeat scroll center center content-box !important;}.file-drop-zone{border:1px dashed #aaa;min-height:260px;border-radius:4px;text-align:center;vertical-align:middle;margin:12px 15px 12px 12px;padding:5px;}.file-drop-zone.clickable:hover{border:2px dashed #999;}.file-drop-zone.clickable:focus{border:2px solid #5acde2;}.file-drop-zone .file-preview-thumbnails{cursor:default;}.file-drop-zone-title{color:#aaa;font-size:1.6em;text-align:center;padding:85px 10px;cursor:default;}.file-highlighted{border:2px dashed #999 !important;background-color:#eee;}.file-uploading{background:url(loading-sm.gif) no-repeat center bottom 10px;opacity:0.65;}.file-zoom-fullscreen .modal-dialog{min-width:100%;margin:0;}.file-zoom-fullscreen .modal-content{border-radius:0;box-shadow:none;min-height:100vh;}.file-zoom-fullscreen .kv-zoom-body{overflow-y:auto;}.floating-buttons{z-index:3000;}.floating-buttons .btn-kv{margin-left:3px;z-index:3000;}.kv-zoom-actions{min-width:140px;}.kv-zoom-actions .btn-kv{margin-left:3px;}.file-zoom-content{text-align:center;white-space:nowrap;min-height:300px;}.file-zoom-content:hover{background:transparent;}.file-zoom-content .file-preview-image{max-height:100%;}.file-zoom-content .file-preview-video{max-height:100%;}.file-zoom-content > .file-object.type-image{height:auto;min-height:inherit;}.file-zoom-content > .file-object.type-audio{width:auto;height:30px;}@media (min-width:576px){.file-zoom-dialog .modal-dialog{max-width:500px;}}@media (min-width:992px){.file-zoom-dialog .modal-lg{max-width:800px;}}@media (max-width:767px){.file-preview-thumbnails{display:flex;justify-content:center;align-items:center;flex-direction:column;}.file-zoom-dialog .modal-header{flex-direction:column;}}@media (max-width:350px){.krajee-default.file-preview-frame:not([data-template="audio"]) .kv-file-content{width:160px;}}@media (max-width:420px){.krajee-default.file-preview-frame .kv-file-content.kv-pdf-rendered{width:100%;}}.file-loading[dir=rtl]:before{background:transparent url(loading.gif) top right no-repeat;padding-left:0;padding-right:20px;}.clickable .file-drop-zone-title{cursor:pointer;}.file-sortable .file-drag-handle:hover{opacity:0.7;}.file-sortable .file-drag-handle{cursor:grab;opacity:1;}.file-grabbing,.file-grabbing *{cursor:not-allowed !important;}.file-grabbing .file-preview-thumbnails *{cursor:grabbing !important;}.file-preview-frame.sortable-chosen{background-color:#d9edf7;border-color:#17a2b8;box-shadow:none !important;}.file-preview .kv-zoom-cache{display:none;}.file-preview-other-frame,.file-preview-object,.kv-file-content,.kv-zoom-body{display:flex;align-items:center;justify-content:center;}.btn-kv-rotate,.kv-file-rotate{display:none;}.rotatable:not(.hide-rotate) .btn-kv-rotate,.rotatable:not(.hide-rotate) .kv-file-rotate{display:inline-block;}.rotatable .file-zoom-detail,.rotatable .kv-file-content,.rotatable .kv-file-content >:first-child{transform-origin:center center;}.rotate-animate{transition:transform 0.3s ease;}.kv-overflow-hidden{overflow:hidden;} \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.min.js b/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.min.js new file mode 100644 index 0000000..72bf6bf --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.min.js @@ -0,0 +1,11 @@ +/*! + * bootstrap-fileinput v5.5.2 + * http://plugins.krajee.com/file-input + * + * Author: Kartik Visweswaran + * Copyright: 2014 - 2022, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD-3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */ +(function(factory){'use strict';if(typeof define==='function'&&define.amd){define(['jquery'],factory)}else if(typeof module==='object'&&typeof module.exports==='object'){factory(require('jquery'))}else{factory(window.jQuery)}}(function($){'use strict';$.fn.fileinputLocales={};$.fn.fileinputThemes={};if(!$.fn.fileinputBsVersion){$.fn.fileinputBsVersion=(window.bootstrap&&window.bootstrap.Alert&&window.bootstrap.Alert.VERSION)||(window.Alert&&window.Alert.VERSION)||'3.x.x'}String.prototype.setTokens=function(replacePairs){var str=this.toString(),key,re;for(key in replacePairs){if(replacePairs.hasOwnProperty(key)){re=new RegExp('\{'+key+'\}','g');str=str.replace(re,replacePairs[key])}}return str};if(!Array.prototype.flatMap){Array.prototype.flatMap=function(lambda){return[].concat(this.map(lambda))}}var $h,FileInput;$h={FRAMES:'.kv-preview-thumb',SORT_CSS:'file-sortable',INIT_FLAG:'init-',SCRIPT_SRC:document&&document.currentScript&&document.currentScript.src||null,OBJECT_PARAMS:'\n\n\n\n\n\n',DEFAULT_PREVIEW:'
    \n{previewFileIcon}\n
    ',MODAL_ID:'kvFileinputModal',MODAL_EVENTS:['show','shown','hide','hidden','loaded'],logMessages:{ajaxError:'{status}: {error}. Error Details: {text}.',badDroppedFiles:'Error scanning dropped files!',badExifParser:'Error loading the piexif.js library. {details}',badInputType:'The input "type" must be set to "file" for initializing the "bootstrap-fileinput" plugin.',exifWarning:'To avoid this warning, either set "autoOrientImage" to "false" OR ensure you have loaded the "piexif.js" library correctly on your page before the "fileinput.js" script.',invalidChunkSize:'Invalid upload chunk size: "{chunkSize}". Resumable uploads are disabled.',invalidThumb:'Invalid thumb frame with id: "{id}".',noResumableSupport:'The browser does not support resumable or chunk uploads.',noUploadUrl:'The "uploadUrl" is not set. Ajax uploads and resumable uploads have been disabled.',retryStatus:'Retrying upload for chunk # {chunk} for {filename}... retry # {retry}.',chunkQueueError:'Could not push task to ajax pool for chunk index # {index}.',resumableMaxRetriesReached:'Maximum resumable ajax retries ({n}) reached.',resumableRetryError:'Could not retry the resumable request (try # {n})... aborting.',resumableAborting:'Aborting / cancelling the resumable request.',resumableRequestError:'Error processing resumable request. {msg}'},objUrl:window.URL||window.webkitURL,getZoomPlaceholder:function(){var src=$h.SCRIPT_SRC,srcPath,zoomVar='?kvTemp__2873389129__=';if(!src){return zoomVar}srcPath=src.substring(0,src.lastIndexOf("/"));return srcPath.substring(0,srcPath.lastIndexOf("/")+1)+'img/loading.gif'+zoomVar},isBs:function(ver){var chk=$.trim(($.fn.fileinputBsVersion||'')+'');ver=parseInt(ver,10);if(!chk){return ver===4}return ver===parseInt(chk.charAt(0),10)},defaultButtonCss:function(fill){return'btn-default btn-'+(fill?'':'outline-')+'secondary'},now:function(){return new Date().getTime()},round:function(num){num=parseFloat(num);return isNaN(num)?0:Math.floor(Math.round(num))},getArray:function(obj){var i,arr=[],len=obj&&obj.length||0;for(i=0;i0){out+=(out?' ':'')+value+key.substring(0,1)}});return out},debounce:function(func,delay){var inDebounce;return function(){var args=arguments,context=this;clearTimeout(inDebounce);inDebounce=setTimeout(function(){func.apply(context,args)},delay)}},stopEvent:function(e){e.stopPropagation();e.preventDefault()},getFileName:function(file){return file?(file.fileName||file.name||''):''},createObjectURL:function(data){if($h.objUrl&&$h.objUrl.createObjectURL&&data){return $h.objUrl.createObjectURL(data)}return''},revokeObjectURL:function(data){if($h.objUrl&&$h.objUrl.revokeObjectURL&&data){$h.objUrl.revokeObjectURL(data)}},compare:function(input,str,exact){return input!==undefined&&(exact?input===str:input.match(str))},isIE:function(ver){var div,status;if(navigator.appName!=='Microsoft Internet Explorer'){return false}if(ver===10){return new RegExp('msie\\s'+ver,'i').test(navigator.userAgent)}div=document.createElement('div');div.innerHTML='';status=div.getElementsByTagName('i').length;document.body.appendChild(div);div.parentNode.removeChild(div);return status},canOrientImage:function($el){var $img=$(document.createElement('img')).css({width:'1px',height:'1px'}).insertAfter($el),flag=$img.css('image-orientation');$img.remove();return!!flag},canAssignFilesToInput:function(){var input=document.createElement('input');try{input.type='file';input.files=null;return true}catch(err){return false}},getDragDropFolders:function(items){var i,item,len=items?items.length:0,folders=0;if(len>0&&items[0].webkitGetAsEntry()){for(i=0;i=0){byteStr=atob(dataURI.split(',')[1])}else{byteStr=decodeURIComponent(dataURI.split(',')[1])}arrayBuffer=new ArrayBuffer(byteStr.length);intArray=new Uint8Array(arrayBuffer);for(i=0;i>4){case 0:case 1:case 2:case 3:case 4:case 5:case 6:case 7:out+=String.fromCharCode(c);break;case 12:case 13:char2=array[i++];out+=String.fromCharCode(((c&0x1F)<<6)|(char2&0x3F));break;case 14:char2=array[i++];char3=array[i++];out+=String.fromCharCode(((c&0x0F)<<12)|((char2&0x3F)<<6)|((char3&0x3F)<<0));break}}return out},isHtml:function(str){var a=document.createElement('div');a.innerHTML=str;for(var c=a.childNodes,i=c.length;i--;){if(c[i].nodeType===1){return true}}return false},isPdf:function(str){if($h.isEmpty(str)){return false}str=str.toString().trim().replace(/\n/g,' ');if(str.length===0){return false}},isSvg:function(str){if($h.isEmpty(str)){return false}str=str.toString().trim().replace(/\n/g,' ');if(str.length===0){return false}return str.match(/^\s*<\?xml/i)&&(str.match(/'+str+''))},uniqId:function(){return(new Date().getTime()+Math.floor(Math.random()*Math.pow(10,15))).toString(36)},cspBuffer:{CSP_ATTRIB:'data-csp-01928735',domElementsStyles:{},stash:function(htmlString){var self=this,outerDom=$.parseHTML('
    '+htmlString+'
    '),$el=$(outerDom);$el.find('[style]').each(function(key,elem){var $elem=$(elem),styleDeclaration=$elem[0].style,id=$h.uniqId(),styles={};if(styleDeclaration&&styleDeclaration.length){$(styleDeclaration).each(function(){styles[this]=styleDeclaration[this]});self.domElementsStyles[id]=styles;$elem.removeAttr('style').attr(self.CSP_ATTRIB,id)}});$el.filter('*').removeAttr('style');var values=Object.values?Object.values(outerDom):Object.keys(outerDom).map(function(itm){return outerDom[itm]});return values.flatMap(function(elem){return elem.innerHTML}).join('')},apply:function(domElement){var self=this,$el=$(domElement);$el.find('['+self.CSP_ATTRIB+']').each(function(key,elem){var $elem=$(elem),id=$elem.attr(self.CSP_ATTRIB),styles=self.domElementsStyles[id];if(styles){$elem.css(styles)}$elem.removeAttr(self.CSP_ATTRIB)});self.domElementsStyles={}}},setHtml:function($elem,htmlString){var buf=$h.cspBuffer;$elem.html(buf.stash(htmlString));buf.apply($elem);return $elem},htmlEncode:function(str,undefVal){if(str===undefined){return undefVal||null}return str.replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"').replace(/'/g,''')},replaceTags:function(str,tags){var out=str;if(!tags){return out}$.each(tags,function(key,value){if(typeof value==='function'){value=value()}out=out.split(key).join(value)});return out},cleanMemory:function($thumb){var data=$thumb.is('img')?$thumb.attr('src'):$thumb.find('source').attr('src');$h.revokeObjectURL(data)},findFileName:function(filePath){var sepIndex=filePath.lastIndexOf('/');if(sepIndex===-1){sepIndex=filePath.lastIndexOf('\\')}return filePath.split(filePath.substring(sepIndex,sepIndex+1)).pop()},checkFullScreen:function(){return document.fullscreenElement||document.mozFullScreenElement||document.webkitFullscreenElement||document.msFullscreenElement},toggleFullScreen:function(maximize){var doc=document,de=doc.documentElement,isFullScreen=$h.checkFullScreen();if(de&&maximize&&!isFullScreen){if(de.requestFullscreen){de.requestFullscreen()}else{if(de.msRequestFullscreen){de.msRequestFullscreen()}else{if(de.mozRequestFullScreen){de.mozRequestFullScreen()}else{if(de.webkitRequestFullscreen){de.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT)}}}}}else{if(isFullScreen){if(doc.exitFullscreen){doc.exitFullscreen()}else{if(doc.msExitFullscreen){doc.msExitFullscreen()}else{if(doc.mozCancelFullScreen){doc.mozCancelFullScreen()}else{if(doc.webkitExitFullscreen){doc.webkitExitFullscreen()}}}}}}},moveArray:function(arr,oldIndex,newIndex,reverseOrder){var newArr=$.extend(true,[],arr);if(reverseOrder){newArr.reverse()}if(newIndex>=newArr.length){var k=newIndex-newArr.length;while((k--)+1){newArr.push(undefined)}}newArr.splice(newIndex,0,newArr.splice(oldIndex,1)[0]);if(reverseOrder){newArr.reverse()}return newArr},closeButton:function(css){css=($h.isBs(5)?'btn-close':'close')+(css?' '+css:'');return''},getRotation:function(value){switch(value){case 2:return'rotateY(180deg)';case 3:return'rotate(180deg)';case 4:return'rotate(180deg) rotateY(180deg)';case 5:return'rotate(270deg) rotateY(180deg)';case 6:return'rotate(90deg)';case 7:return'rotate(90deg) rotateY(180deg)';case 8:return'rotate(270deg)';default:return''}},setTransform:function(el,val){if(!el){return}el.style.transform=val;el.style.webkitTransform=val;el.style['-moz-transform']=val;el.style['-ms-transform']=val;el.style['-o-transform']=val},getObjectKeys:function(obj){var keys=[];if(obj){$.each(obj,function(key){keys.push(key)})}return keys},getObjectSize:function(obj){return $h.getObjectKeys(obj).length},whenAll:function(array){var s=[].slice,resolveValues=arguments.length===1&&$h.isArray(array)?array:s.call(arguments),deferred=$.Deferred(),i,failed=0,value,length=resolveValues.length,remaining=length,rejectContexts,rejectValues,resolveContexts,updateFunc;rejectContexts=rejectValues=resolveContexts=Array(length);updateFunc=function(index,contexts,values){return function(){if(values!==resolveValues){failed++}deferred.notifyWith(contexts[index]=this,values[index]=s.call(arguments));if(!(--remaining)){deferred[(!failed?'resolve':'reject')+'With'](contexts,values)}}};for(i=0;i0&&self.maxTotalFileCount10?len-10:Math.ceil(len/2);for(i=len;i>beg;i--){n=parseFloat(fm.bpsLog[i]);j++}fm.bps=(j>0?n/j:0)*64},delay);out={fileId:id,started:started,elapsed:elapsed,loaded:loaded,total:total,bps:fm.bps,bitrate:self._getSize(fm.bps,false,self.bitRateUnits),pendingBytes:pendingBytes};if(id){fm.stats[id]=out}else{fm.stats=out}return out},exists:function(id){return $.inArray(id,self.fileManager.getIdList())!==-1},count:function(){return self.fileManager.getIdList().length},total:function(){var fm=self.fileManager;if(!fm.totalFiles){fm.totalFiles=fm.count()}return fm.totalFiles},getTotalSize:function(){var fm=self.fileManager;if(fm.totalSize){return fm.totalSize}fm.totalSize=0;$.each(self.getFileStack(),function(id,f){var size=parseFloat(f.size);fm.totalSize+=isNaN(size)?0:size});return fm.totalSize},add:function(file,id){if(!id){id=self.fileManager.getId(file)}if(!id){return}self.fileManager.stack[id]={file:file,name:$h.getFileName(file),relativePath:$h.getFileRelativePath(file),size:file.size,nameFmt:self._getFileName(file,''),sizeFmt:self._getSize(file.size)}},remove:function($thumb){var id=self._getThumbFileId($thumb);self.fileManager.removeFile(id)},removeFile:function(id){var fm=self.fileManager;if(!id){return}delete fm.stack[id];delete fm.loadedImages[id]},move:function(idFrom,idTo){var result={},stack=self.fileManager.stack;if(!idFrom&&!idTo||idFrom===idTo){return}$.each(stack,function(k,v){if(k!==idFrom){result[k]=v}if(k===idTo){result[idFrom]=stack[idFrom]}});self.fileManager.stack=result},list:function(){var files=[];$.each(self.getFileStack(),function(k,v){if(v&&v.file){files.push(v.file)}});return files},isPending:function(id){return $.inArray(id,self.fileManager.filesProcessed)===-1&&self.fileManager.exists(id)},isProcessed:function(){var filesProcessed=true,fm=self.fileManager;$.each(self.getFileStack(),function(id){if(fm.isPending(id)){filesProcessed=false}});return filesProcessed},clear:function(){var fm=self.fileManager;self.isDuplicateError=false;self.isPersistentError=false;fm.totalFiles=null;fm.totalSize=null;fm.uploadedSize=0;fm.stack={};fm.errors=[];fm.filesProcessed=[];fm.stats={};fm.bpsLog=[];fm.bps=0;fm.clearImages()},clearImages:function(){self.fileManager.loadedImages={};self.fileManager.totalImages=0},addImage:function(id,config){self.fileManager.loadedImages[id]=config},removeImage:function(id){delete self.fileManager.loadedImages[id]},getImageIdList:function(){return $h.getObjectKeys(self.fileManager.loadedImages)},getImageCount:function(){return self.fileManager.getImageIdList().length},getId:function(file){return self._getFileId(file)},getIndex:function(id){return self.fileManager.getIdList().indexOf(id)},getThumb:function(id){var $thumb=null;self._getThumbs().each(function(){var $t=$(this);if(self._getThumbFileId($t)===id){$thumb=$t}});return $thumb},getThumbIndex:function($thumb){var id=self._getThumbFileId($thumb);return self.fileManager.getIndex(id)},getIdList:function(){return $h.getObjectKeys(self.fileManager.stack)},getFile:function(id){return self.fileManager.stack[id]||null},getFileName:function(id,fmt){var file=self.fileManager.getFile(id);if(!file){return''}return fmt?(file.nameFmt||''):file.name||''},getFirstFile:function(){var ids=self.fileManager.getIdList(),id=ids&&ids.length?ids[0]:null;return self.fileManager.getFile(id)},setFile:function(id,file){if(self.fileManager.getFile(id)){self.fileManager.stack[id].file=file}else{self.fileManager.add(file,id)}},setProcessed:function(id){self.fileManager.filesProcessed.push(id)},getProgress:function(){var total=self.fileManager.total(),filesProcessed=self.fileManager.filesProcessed.length;if(!total){return 0}return Math.ceil(filesProcessed/total*100)},setProgress:function(id,pct){var f=self.fileManager.getFile(id);if(!isNaN(pct)&&f){f.progress=pct}}}},_setUploadData:function(fd,config){var self=this;$.each(config,function(key,value){var param=self.uploadParamNames[key]||key;if($h.isArray(value)){fd.append(param,value[0],value[1])}else{fd.append(param,value)}})},_initResumableUpload:function(){var self=this,opts=self.resumableUploadOptions,logs=$h.logMessages,rm,fm=self.fileManager;if(!self.enableResumableUpload){return}if(opts.fallback!==false&&typeof opts.fallback!=='function'){opts.fallback=function(s){s._log(logs.noResumableSupport);s.enableResumableUpload=false}}if(!$h.hasResumableUploadSupport()&&opts.fallback!==false){opts.fallback(self);return}if(!self.uploadUrl&&self.enableResumableUpload){self._log(logs.noUploadUrl);self.enableResumableUpload=false;return}opts.chunkSize=parseFloat(opts.chunkSize);if(opts.chunkSize<=0||isNaN(opts.chunkSize)){self._log(logs.invalidChunkSize,{chunkSize:opts.chunkSize});self.enableResumableUpload=false;return}rm=self.resumableManager={init:function(id,f,index){rm.logs=[];rm.stack=[];rm.error='';rm.id=id;rm.file=f.file;rm.fileName=f.name;rm.fileIndex=index;rm.completed=false;rm.lastProgress=0;if(self.showPreview){rm.$thumb=fm.getThumb(id)||null;rm.$progress=rm.$btnDelete=null;if(rm.$thumb&&rm.$thumb.length){rm.$progress=rm.$thumb.find('.file-thumb-progress');rm.$btnDelete=rm.$thumb.find('.kv-file-remove')}}rm.chunkSize=opts.chunkSize*self.bytesToKB;rm.chunkCount=rm.getTotalChunks()},setAjaxError:function(jqXHR,textStatus,errorThrown,isTest){if(jqXHR.responseJSON&&jqXHR.responseJSON.error){errorThrown=jqXHR.responseJSON.error.toString()}if(!isTest){rm.error=errorThrown}if(opts.showErrorLog){self._log(logs.ajaxError,{status:jqXHR.status,error:errorThrown,text:jqXHR.responseText||''})}},reset:function(){rm.stack=[];rm.chunksProcessed={}},setProcessed:function(status){var id=rm.id,msg,$thumb=rm.$thumb,$prog=rm.$progress,hasThumb=$thumb&&$thumb.length,params={id:hasThumb?$thumb.attr('id'):'',index:fm.getIndex(id),fileId:id},tokens,skipErrorsAndProceed=self.resumableUploadOptions.skipErrorsAndProceed;rm.completed=true;rm.lastProgress=0;if(hasThumb){$thumb.removeClass('file-uploading')}if(status==='success'){fm.uploadedSize+=rm.file.size;if(self.showPreview){self._setProgress(101,$prog);self._setThumbStatus($thumb,'Success');self._initUploadSuccess(rm.chunksProcessed[id].data,$thumb)}fm.removeFile(id);delete rm.chunksProcessed[id];self._raise('fileuploaded',[params.id,params.index,params.fileId]);if(fm.isProcessed()){self._setProgress(101)}}else{if(status!=='cancel'){if(self.showPreview){self._setThumbStatus($thumb,'Error');self._setPreviewError($thumb,true);self._setProgress(101,$prog,self.msgProgressError);self._setProgress(101,self.$progress,self.msgProgressError);self.cancelling=!skipErrorsAndProceed}if(!self.$errorContainer.find('li[data-file-id="'+params.fileId+'"]').length){tokens={file:rm.fileName,max:opts.maxRetries,error:rm.error};msg=self.msgResumableUploadRetriesExceeded.setTokens(tokens);$.extend(params,tokens);self._showFileError(msg,params,'filemaxretries');if(skipErrorsAndProceed){fm.removeFile(id);delete rm.chunksProcessed[id];if(fm.isProcessed()){self._setProgress(101)}}}}}if(fm.isProcessed()){rm.reset()}},check:function(){var status=true;$.each(rm.logs,function(index,value){if(!value){status=false;return false}})},processedResumables:function(){var logs=rm.logs,i,count=0;if(!logs||!logs.length){return 0}for(i=0;irm.file.size?rm.file.size:size},getTotalChunks:function(){var chunkSize=parseFloat(rm.chunkSize);if(!isNaN(chunkSize)&&chunkSize>0){return Math.ceil(rm.file.size/chunkSize)}return 0},getProgress:function(){var chunksProcessed=rm.processedResumables(),total=rm.chunkCount;if(total===0){return 0}return Math.ceil(chunksProcessed/total*100)},checkAborted:function(intervalId){if(self._isAborted()){clearInterval(intervalId);self.unlock()}},upload:function(){var ids=fm.getIdList(),flag='new',intervalId;intervalId=setInterval(function(){var id;rm.checkAborted(intervalId);if(flag==='new'){self.lock();flag='processing';id=ids.shift();fm.initStats(id);if(fm.stack[id]){rm.init(id,fm.stack[id],fm.getIndex(id));rm.processUpload()}}if(!fm.isPending(id)&&rm.completed){flag='new'}if(fm.isProcessed()){var $initThumbs=self.$preview.find('.file-preview-initial');if($initThumbs.length){$h.addCss($initThumbs,$h.SORT_CSS);self._initSortable()}clearInterval(intervalId);self._clearFileInput();self.unlock();setTimeout(function(){var data=self.previewCache.data;if(data){self.initialPreview=data.content;self.initialPreviewConfig=data.config;self.initialPreviewThumbTags=data.tags}self._raise('filebatchuploadcomplete',[self.initialPreview,self.initialPreviewConfig,self.initialPreviewThumbTags,self._getExtraData()])},self.processDelay)}},self.processDelay)},uploadResumable:function(){var i,pool,tm=self.taskManager,total=rm.chunkCount;pool=tm.addPool(rm.id);for(i=0;iopts.maxRetries){logError(msgs.resumableMaxRetriesReached,{n:opts.maxRetries});rm.setProcessed('error');return}var fd,outData,fnBefore,fnSuccess,fnError,fnComplete,slice=file.slice?'slice':(file.mozSlice?'mozSlice':(file.webkitSlice?'webkitSlice':'slice')),blob=file[slice](chunkSize*index,chunkSize*(index+1));fd=new FormData();f=fm.stack[id];self._setUploadData(fd,{chunkCount:rm.chunkCount,chunkIndex:index,chunkSize:chunkSize,chunkSizeStart:chunkSize*index,fileBlob:[blob,rm.fileName],fileId:id,fileName:rm.fileName,fileRelativePath:f.relativePath,fileSize:file.size,retryCount:retry});if(rm.$progress&&rm.$progress.length){rm.$progress.show()}fnBefore=function(jqXHR){outData=self._getOutData(fd,jqXHR);if(self.showPreview){if(!$thumb.hasClass('file-preview-success')){self._setThumbStatus($thumb,'Loading');$h.addCss($thumb,'file-uploading')}$btnDelete.attr('disabled',true)}self._raise('filechunkbeforesend',[id,index,retry,fm,rm,outData])};fnSuccess=function(data,textStatus,jqXHR){if(self._isAborted()){logError(msgs.resumableAborting);return}outData=self._getOutData(fd,jqXHR,data);var paramNames=self.uploadParamNames,chunkIndex=paramNames.chunkIndex||'chunkIndex',params=[id,index,retry,fm,rm,outData];if(data.error){if(opts.showErrorLog){self._log(logs.retryStatus,{retry:retry+1,filename:rm.fileName,chunk:index})}self._raise('filechunkerror',params);rm.pushAjax(index,retry+1);rm.error=data.error;logError(data.error)}else{rm.logs[data[chunkIndex]]=true;if(!rm.chunksProcessed[id]){rm.chunksProcessed[id]={}}rm.chunksProcessed[id][data[chunkIndex]]=true;rm.chunksProcessed[id].data=data;deferrer.resolve.call(null,data);self._raise('filechunksuccess',params);rm.check()}};fnError=function(jqXHR,textStatus,errorThrown){if(self._isAborted()){logError(msgs.resumableAborting);return}outData=self._getOutData(fd,jqXHR);rm.setAjaxError(jqXHR,textStatus,errorThrown);self._raise('filechunkajaxerror',[id,index,retry,fm,rm,outData]);rm.pushAjax(index,retry+1);logError(msgs.resumableRetryError,{n:retry-1})};fnComplete=function(){if(!self._isAborted()){self._raise('filechunkcomplete',[id,index,retry,fm,rm,self._getOutData(fd)])}};self._ajaxSubmit(fnBefore,fnSuccess,fnComplete,fnError,fd,id,rm.fileIndex)}};rm.reset()},_initTemplateDefaults:function(){var self=this,tMain1,tMain2,tPreview,tFileIcon,tClose,tCaption,tBtnDefault,tBtnLink,tBtnBrowse,tModalMain,tModal,tProgress,tSize,tFooter,tActions,tActionDelete,tActionUpload,tActionDownload,tActionZoom,tActionDrag,tIndicator,tTagBef,tTagBef1,tTagBef2,tTagAft,tGeneric,tHtml,tImage,tText,tOffice,tGdocs,tVideo,tAudio,tFlash,tObject,tPdf,tOther,tStyle,tZoomCache,vDefaultDim,tActionRotate,tStats,tModalLabel,tDescClose,renderObject=function(type,mime){return'\n'+$h.DEFAULT_PREVIEW+'\n\n'},defBtnCss1='btn btn-sm btn-kv '+$h.defaultButtonCss();tMain1='{preview}\n
    \n
    \n
    \n {caption}\n\n'+($h.isBs(5)?'':'
    \n')+' {remove}\n {cancel}\n {pause}\n {upload}\n {browse}\n'+($h.isBs(5)?'':'
    \n')+'
    ';'
    ';tMain2='{preview}\n
    \n
    \n{remove}\n{cancel}\n{upload}\n{browse}\n';tPreview='
    \n {close}
    \n
    \n
    \n
    \n
    \n
    \n
    ';tClose=$h.closeButton('fileinput-remove');tFileIcon='';tCaption='\n';tBtnDefault='';tBtnLink='{icon} {label}';tBtnBrowse='
    {icon} {label}
    ';tModalLabel=$h.MODAL_ID+'Label';tModalMain='';tModal='\n';tDescClose='';tProgress='
    \n
    \n {status}\n
    \n
    {stats}';tStats='
    {pendingTime} {uploadSpeed}
    ';tSize=' ({sizeText})';tFooter='';tActions='
    \n \n
    \n{drag}\n
    ';tActionDelete='\n';tActionUpload='';tActionRotate='';tActionDownload='{downloadIcon}';tActionZoom='';tActionDrag='{dragIcon}';tIndicator='
    {indicator}
    ';tTagBef='
    \n';tTagBef2=tTagBef+' title="{caption}">
    \n';tTagAft='
    {footer}\n{zoomCache}
    \n';tGeneric='{content}\n';tStyle=' {style}';tHtml=renderObject('html','text/html');tText=renderObject('text','text/plain;charset=UTF-8');tPdf=renderObject('pdf','application/pdf');tImage='{alt}\n';tOffice='';tGdocs='';tVideo='\n';tAudio='\n';tFlash='\n';tObject='\n\n'+$h.OBJECT_PARAMS+' '+$h.DEFAULT_PREVIEW+'\n\n';tOther='
    \n'+$h.DEFAULT_PREVIEW+'\n
    \n';tZoomCache='
    {zoomContent}
    ';vDefaultDim={width:'100%',height:'100%','min-height':'480px'};if(self._isPdfRendered()){tPdf=self.pdfRendererTemplate.replace('{renderer}',self._encodeURI(self.pdfRendererUrl))}self.defaults={layoutTemplates:{main1:tMain1,main2:tMain2,preview:tPreview,close:tClose,fileIcon:tFileIcon,caption:tCaption,modalMain:tModalMain,modal:tModal,descriptionClose:tDescClose,progress:tProgress,stats:tStats,size:tSize,footer:tFooter,indicator:tIndicator,actions:tActions,actionDelete:tActionDelete,actionRotate:tActionRotate,actionUpload:tActionUpload,actionDownload:tActionDownload,actionZoom:tActionZoom,actionDrag:tActionDrag,btnDefault:tBtnDefault,btnLink:tBtnLink,btnBrowse:tBtnBrowse,zoomCache:tZoomCache},previewMarkupTags:{tagBefore1:tTagBef1,tagBefore2:tTagBef2,tagAfter:tTagAft},previewContentTemplates:{generic:tGeneric,html:tHtml,image:tImage,text:tText,office:tOffice,gdocs:tGdocs,video:tVideo,audio:tAudio,flash:tFlash,object:tObject,pdf:tPdf,other:tOther},allowedPreviewTypes:['image','html','text','video','audio','flash','pdf','object'],previewTemplates:{},previewSettings:{image:{width:'auto',height:'auto','max-width':'100%','max-height':'100%'},html:{width:'213px',height:'160px'},text:{width:'213px',height:'160px'},office:{width:'213px',height:'160px'},gdocs:{width:'213px',height:'160px'},video:{width:'213px',height:'160px'},audio:{width:'100%',height:'30px'},flash:{width:'213px',height:'160px'},object:{width:'213px',height:'160px'},pdf:{width:'100%',height:'160px','position':'relative'},other:{width:'213px',height:'160px'}},previewSettingsSmall:{image:{width:'auto',height:'auto','max-width':'100%','max-height':'100%'},html:{width:'100%',height:'160px'},text:{width:'100%',height:'160px'},office:{width:'100%',height:'160px'},gdocs:{width:'100%',height:'160px'},video:{width:'100%',height:'auto'},audio:{width:'100%',height:'30px'},flash:{width:'100%',height:'auto'},object:{width:'100%',height:'auto'},pdf:{width:'100%',height:'160px'},other:{width:'100%',height:'160px'}},previewZoomSettings:{image:{width:'auto',height:'auto','max-width':'100%','max-height':'100%'},html:vDefaultDim,text:vDefaultDim,office:{width:'100%',height:'100%','max-width':'100%','min-height':'480px'},gdocs:{width:'100%',height:'100%','max-width':'100%','min-height':'480px'},video:{width:'auto',height:'100%','max-width':'100%'},audio:{width:'100%',height:'30px'},flash:{width:'auto',height:'480px'},object:{width:'auto',height:'100%','max-width':'100%','min-height':'480px'},pdf:vDefaultDim,other:{width:'auto',height:'100%','min-height':'480px'}},mimeTypeAliases:{'video/quicktime':'video/mp4'},fileTypeSettings:{image:function(vType,vName){return($h.compare(vType,'image.*')&&!$h.compare(vType,/(tiff?|wmf)$/i)||$h.compare(vName,/\.(gif|png|jpe?g)$/i))},html:function(vType,vName){return $h.compare(vType,'text/html')||$h.compare(vName,/\.(htm|html)$/i)},office:function(vType,vName){return $h.compare(vType,/(word|excel|powerpoint|office)$/i)||$h.compare(vName,/\.(docx?|xlsx?|pptx?|pps|potx?)$/i)},gdocs:function(vType,vName){return $h.compare(vType,/(word|excel|powerpoint|office|iwork-pages|tiff?)$/i)||$h.compare(vName,/\.(docx?|xlsx?|pptx?|pps|potx?|rtf|ods|odt|pages|ai|dxf|ttf|tiff?|wmf|e?ps)$/i)},text:function(vType,vName){return $h.compare(vType,'text.*')||$h.compare(vName,/\.(xml|javascript)$/i)||$h.compare(vName,/\.(txt|md|nfo|ini|json|php|js|css)$/i)},video:function(vType,vName){return $h.compare(vType,'video.*')&&($h.compare(vType,/(ogg|mp4|mp?g|mov|webm|3gp)$/i)||$h.compare(vName,/\.(og?|mp4|webm|mp?g|mov|3gp)$/i))},audio:function(vType,vName){return $h.compare(vType,'audio.*')&&($h.compare(vName,/(ogg|mp3|mp?g|wav)$/i)||$h.compare(vName,/\.(og?|mp3|mp?g|wav)$/i))},flash:function(vType,vName){return $h.compare(vType,'application/x-shockwave-flash',true)||$h.compare(vName,/\.(swf)$/i)},pdf:function(vType,vName){return $h.compare(vType,'application/pdf',true)||$h.compare(vName,/\.(pdf)$/i)},object:function(){return true},other:function(){return true}},fileActionSettings:{showRemove:true,showUpload:true,showDownload:true,showZoom:true,showDrag:true,showRotate:false,removeIcon:'',removeClass:defBtnCss1,removeErrorClass:'btn btn-sm btn-kv btn-danger',removeTitle:'Remove file',uploadIcon:'',uploadClass:defBtnCss1,uploadTitle:'Upload file',uploadRetryIcon:'',uploadRetryTitle:'Retry upload',downloadIcon:'',downloadClass:defBtnCss1,downloadTitle:'Download file',rotateIcon:'',rotateClass:defBtnCss1,rotateTitle:'Rotate 90 deg. clockwise',zoomIcon:'',zoomClass:defBtnCss1,zoomTitle:'View Details',dragIcon:'',dragClass:'text-primary',dragTitle:'Move / Rearrange',dragSettings:{},indicatorNew:'',indicatorSuccess:'',indicatorError:'',indicatorLoading:'',indicatorPaused:'',indicatorNewTitle:'Not uploaded yet',indicatorSuccessTitle:'Uploaded',indicatorErrorTitle:'Upload Error',indicatorLoadingTitle:'Uploading …',indicatorPausedTitle:'Upload Paused'}};$.each(self.defaults,function(key,setting){if(key==='allowedPreviewTypes'){if(self.allowedPreviewTypes===undefined){self.allowedPreviewTypes=setting}return}self[key]=$.extend(true,{},setting,self[key])});self._initPreviewTemplates()},_initPreviewTemplates:function(){var self=this,tags=self.previewMarkupTags,tagBef,tagAft=tags.tagAfter;$.each(self.previewContentTemplates,function(key,value){if($h.isEmpty(self.previewTemplates[key])){tagBef=tags.tagBefore2;if(key==='generic'||key==='image'){tagBef=tags.tagBefore1}if(self._isPdfRendered()&&key==='pdf'){tagBef=tagBef.replace('kv-file-content','kv-file-content kv-pdf-rendered')}self.previewTemplates[key]=tagBef+value+tagAft}})},_initPreviewCache:function(){var self=this;self.previewCache={data:{},init:function(){var content=self.initialPreview;if(content.length>0&&!$h.isArray(content)){content=content.split(self.initialPreviewDelimiter)}self.previewCache.data={content:content,config:self.initialPreviewConfig,tags:self.initialPreviewThumbTags}},count:function(skipNull){if(!self.previewCache.data||!self.previewCache.data.content){return 0}if(skipNull){var chk=self.previewCache.data.content.filter(function(n){return n!==null});return chk.length}return self.previewCache.data.content.length},get:function(i,isDisabled){var ind=$h.INIT_FLAG+i,data=self.previewCache.data,config=data.config[i],content=data.content[i],out,$tmp,cat,ftr,fname,ftype,frameClass,asData=$h.ifSet('previewAsData',config,self.initialPreviewAsData),a=config?{title:config.title||null,alt:config.alt||null}:{title:null,alt:null},parseTemplate=function(cat,dat,fname,ftype,ftr,ind,fclass,t){var fc=' file-preview-initial '+$h.SORT_CSS+(fclass?' '+fclass:''),id=self.previewInitId+'-'+ind,fileId=config&&config.fileId||id;return self._generatePreviewTemplate(cat,dat,fname,ftype,id,fileId,false,null,null,fc,ftr,ind,t,a,config&&config.zoomData||dat)};if(!content||!content.length){return''}isDisabled=isDisabled===undefined?true:isDisabled;cat=$h.ifSet('type',config,self.initialPreviewFileType||'generic');fname=$h.ifSet('filename',config,$h.ifSet('caption',config));ftype=$h.ifSet('filetype',config,cat);ftr=self.previewCache.footer(i,isDisabled,(config&&config.size||null));frameClass=$h.ifSet('frameClass',config);if(asData){out=parseTemplate(cat,content,fname,ftype,ftr,ind,frameClass)}else{out=parseTemplate('generic',content,fname,ftype,ftr,ind,frameClass,cat).setTokens({'content':data.content[i]})}if(data.tags.length&&data.tags[i]){out=$h.replaceTags(out,data.tags[i])}if(!$h.isEmpty(config)&&!$h.isEmpty(config.frameAttr)){$tmp=$h.createElement(out);$tmp.find('.file-preview-initial').attr(config.frameAttr);out=$tmp.html();$tmp.remove()}return out},clean:function(data){data.content=$h.cleanArray(data.content);data.config=$h.cleanArray(data.config);data.tags=$h.cleanArray(data.tags);self.previewCache.data=data},add:function(content,config,tags,append){var data=self.previewCache.data,index;if(!content||!content.length){return 0}index=content.length-1;if(!$h.isArray(content)){content=content.split(self.initialPreviewDelimiter)}if(append&&data.content){index=data.content.push(content[0])-1;data.config[index]=config;data.tags[index]=tags}else{data.content=content;data.config=config;data.tags=tags}self.previewCache.clean(data);return index},set:function(content,config,tags,append){var data=self.previewCache.data,i,chk;if(!content||!content.length){return}if(!$h.isArray(content)){content=content.split(self.initialPreviewDelimiter)}chk=content.filter(function(n){return n!==null});if(!chk.length){return}if(data.content===undefined){data.content=[]}if(data.config===undefined){data.config=[]}if(data.tags===undefined){data.tags=[]}if(append){for(i=0;i'+fileName+':
    '+msg}}self._showFileError(msg,params,'fileusererror')},_showFileError:function(msg,params,event){var self=this,$error=self.$errorContainer,ev=event||'fileuploaderror',fId=params&¶ms.fileId||'',e=params&¶ms.id?'
  • '+msg+'
  • ':'
  • '+msg+'
  • ';if($error.find('ul').length===0){self._addError('
      '+e+'
    ')}else{$error.find('ul').append(e)}$error.fadeIn(self.fadeDelay);self._raise(ev,[params,msg]);self._setValidationError('file-input-new');return true},_showError:function(msg,params,event){var self=this,$error=self.$errorContainer,ev=event||'fileerror';params=params||{};params.reader=self.reader;self._addError(msg);$error.fadeIn(self.fadeDelay);self._raise(ev,[params,msg]);if(!self.isAjaxUpload){self._clearFileInput()}self._setValidationError('file-input-new');self.$btnUpload.attr('disabled',true);return true},_noFilesError:function(params){var self=this,label=self.minFileCount>1?self.filePlural:self.fileSingle,msg=self.msgFilesTooLess.replace('{n}',self.minFileCount).replace('{files}',label),$error=self.$errorContainer;msg='
  • '+msg+'
  • ';if($error.find('ul').length===0){self._addError('
      '+msg+'
    ')}else{$error.find('ul').append(msg)}self.isError=true;self._updateFileDetails(0);$error.fadeIn(self.fadeDelay);self._raise('fileerror',[params,msg]);self._clearFileInput();self._setValidationError()},_parseError:function(operation,jqXHR,errorThrown,fileName){var self=this,errMsg=$.trim(errorThrown+''),textPre,errText,text;errText=jqXHR.responseJSON&&jqXHR.responseJSON.error?jqXHR.responseJSON.error.toString():'';text=errText?errText:jqXHR.responseText;if(self.cancelling&&self.msgUploadAborted){errMsg=self.msgUploadAborted}if(self.showAjaxErrorDetails&&text){if(errText){errMsg=$.trim(errText+'')}else{text=$.trim(text.replace(/\n\s*\n/g,'\n'));textPre=text.length?'
    '+text+'
    ':'';errMsg+=errMsg?textPre:text}}if(!errMsg){errMsg=self.msgAjaxError.replace('{operation}',operation)}self.cancelling=false;return fileName?''+fileName+': '+errMsg:errMsg},_parseFileType:function(type,name){var self=this,isValid,vType,cat,i,types=self.allowedPreviewTypes||[];if(type==='application/text-plain'){return'text'}for(i=0;i-1){ext=fname.split('.').pop();if(self.previewFileIconSettings){out=self.previewFileIconSettings[ext]||self.previewFileIconSettings[ext.toLowerCase()]||null}if(self.previewFileExtSettings){$.each(self.previewFileExtSettings,function(key,func){if(self.previewFileIconSettings[key]&&func(ext)){out=self.previewFileIconSettings[key];return}})}}return out||self.previewFileIcon},_parseFilePreviewIcon:function(content,fname){var self=this,icn=self._getPreviewIcon(fname),out=content;if(out.indexOf('{previewFileIcon}')>-1){out=out.setTokens({'previewFileIconClass':self.previewFileIconClass,'previewFileIcon':icn})}return out},_raise:function(event,params){var self=this,e=$.Event(event);if(params!==undefined){self.$element.trigger(e,params)}else{self.$element.trigger(e)}var out=e.result,isAborted=out===false;if(e.isDefaultPrevented()||isAborted){return false}if(e.type==='filebatchpreupload'&&(out||isAborted)){self.ajaxAborted=out;return false}switch(event){case'filebatchuploadcomplete':case'filebatchuploadsuccess':case'fileuploaded':case'fileclear':case'filecleared':case'filereset':case'fileerror':case'filefoldererror':case'filecustomerror':case'filesuccessremove':break;default:if(!self.ajaxAborted){self.ajaxAborted=out}break}return true},_listenFullScreen:function(isFullScreen){var self=this,$modal=self.$modal,$btnFull,$btnBord;if(!$modal||!$modal.length){return}$btnFull=$modal&&$modal.find('.btn-kv-fullscreen');$btnBord=$modal&&$modal.find('.btn-kv-borderless');if(!$btnFull.length||!$btnBord.length){return}$btnFull.removeClass('active').attr('aria-pressed','false');$btnBord.removeClass('active').attr('aria-pressed','false');if(isFullScreen){$btnFull.addClass('active').attr('aria-pressed','true')}else{$btnBord.addClass('active').attr('aria-pressed','true')}if($modal.hasClass('file-zoom-fullscreen')){self._maximizeZoomDialog()}else{if(isFullScreen){self._maximizeZoomDialog()}else{$btnBord.removeClass('active').attr('aria-pressed','false')}}},_listen:function(){var self=this,$el=self.$element,$form=self.$form,$cont=self.$container,fullScreenEv;self._handler($el,'click',function(e){self._initFileSelected();if($el.hasClass('file-no-browse')){if($el.data('zoneClicked')){$el.data('zoneClicked',false)}else{e.preventDefault()}}});self._handler($el,'change',$.proxy(self._change,self));self._handler(self.$caption,'paste',$.proxy(self.paste,self));if(self.showBrowse){self._handler(self.$btnFile,'click',$.proxy(self._browse,self));self._handler(self.$btnFile,'keypress',function(e){var keycode=e.keyCode||e.which;if(keycode===13){$el.trigger('click');self._browse(e)}})}self._handler($cont.find('.fileinput-remove:not([disabled])'),'click',$.proxy(self.clear,self));self._handler($cont.find('.fileinput-cancel'),'click',$.proxy(self.cancel,self));self._handler($cont.find('.fileinput-pause'),'click',$.proxy(self.pause,self));self._initDragDrop();self._handler($form,'reset',$.proxy(self.clear,self));if(!self.isAjaxUpload){self._handler($form,'submit',$.proxy(self._submitForm,self))}self._handler(self.$container.find('.fileinput-upload'),'click',$.proxy(self._uploadClick,self));self._handler($(window),'resize',function(){self._listenFullScreen(screen.width===window.innerWidth&&screen.height===window.innerHeight)});fullScreenEv='webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange';self._handler($(document),fullScreenEv,function(){self._listenFullScreen($h.checkFullScreen())});self.$caption.on('focus',function(){self.$captionContainer.focus()});self._autoFitContent();self._initClickable();self._refreshPreview()},_autoFitContent:function(){var width=window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth,self=this,config=width<400?(self.previewSettingsSmall||self.defaults.previewSettingsSmall):(self.previewSettings||self.defaults.previewSettings),sel;$.each(config,function(cat,settings){sel='.file-preview-frame .file-preview-'+cat;self.$preview.find(sel+'.kv-preview-data,'+sel+' .kv-preview-data').css(settings)})},_scanDroppedItems:function(item,files,path){path=path||'';var self=this,i,dirReader,readDir,errorHandler=function(e){self._log($h.logMessages.badDroppedFiles);self._log(e)};if(item.isFile){item.file(function(file){if(path){file.newPath=path+file.name}files.push(file)},errorHandler)}else{if(item.isDirectory){dirReader=item.createReader();readDir=function(){dirReader.readEntries(function(entries){if(entries&&entries.length>0){for(i=0;i-1;self._zoneDragDropInit(e);if(self.isDisabled||!hasFiles){dt.effectAllowed='none';dt.dropEffect='none';return}dt.dropEffect='copy';if(self._raise('fileDragEnter',{'sourceEvent':e,'files':dt.types.Files})){$h.addCss(self.$dropZone,'file-highlighted')}},_zoneDragLeave:function(e){var self=this;self._zoneDragDropInit(e);if(self.isDisabled){return}if(self._raise('fileDragLeave',{'sourceEvent':e})){self.$dropZone.removeClass('file-highlighted')}},_dropFiles:function(e,files){var self=this,$el=self.$element;if(!self.isAjaxUpload){self.changeTriggered=true;$el.get(0).files=files;setTimeout(function(){self.changeTriggered=false;$el.trigger('change'+self.namespace)},self.processDelay)}else{self._change(e,files)}self.$dropZone.removeClass('file-highlighted')},_zoneDrop:function(e){var self=this,i,$el=self.$element,dt=e.originalEvent.dataTransfer,files=dt.files,items=dt.items,folders=$h.getDragDropFolders(items);e.preventDefault();if(self.isDisabled||$h.isEmpty(files)){return}if(!self._raise('fileDragDrop',{'sourceEvent':e,'files':files})){return}if(folders>0){if(!self.isAjaxUpload){self._showFolderError(folders);return}files=[];for(i=0;i0&&newIndex>=len,$item=$(e.item),$first;if(exceedsLast){newIndex=len-1}self.initialPreview=$h.moveArray(self.initialPreview,oldIndex,newIndex,rev);self.initialPreviewConfig=$h.moveArray(self.initialPreviewConfig,oldIndex,newIndex,rev);self.previewCache.init();self.getFrames('.file-preview-initial').each(function(){$(this).attr('data-fileindex',$h.INIT_FLAG+i);i++});if(exceedsLast){$first=self.getFrames(':not(.file-preview-initial):first');if($first.length){$item.slideUp(function(){$item.insertBefore($first).slideDown()})}}self._raise('filesorted',{previewId:$item.attr('id'),'oldIndex':oldIndex,'newIndex':newIndex,stack:self.initialPreviewConfig})},};$.extend(true,settings,self.fileActionSettings.dragSettings);if(self.sortable){self.sortable.destroy()}self.sortable=Sortable.create($el[0],settings)},_setPreviewContent:function(content){var self=this;$h.setHtml(self.$preview,content);self._autoFitContent()},_initPreviewImageOrientations:function(){var self=this,i=0,canOrientImage=self.canOrientImage;if(!self.autoOrientImageInitial&&!canOrientImage){return}self.getFrames('.file-preview-initial').each(function(){var $thumb=$(this),$img,$zoomImg,id,config=self.initialPreviewConfig[i];if(config&&config.exif&&config.exif.Orientation){id=$thumb.attr('id');$img=$thumb.find('>.kv-file-content img');$zoomImg=self._getZoom(id,' >.kv-file-content img');if(canOrientImage){$img.css('image-orientation',(self.autoOrientImageInitial?'from-image':'none'))}else{self.setImageOrientation($img,$zoomImg,config.exif.Orientation,$thumb)}}i++})},_initPreview:function(isInit){var self=this,cap=self.initialCaption||'',out;if(!self.previewCache.count(true)){self._clearPreview();if(isInit){self._setCaption(cap)}else{self._initCaption()}return}out=self.previewCache.out();cap=isInit&&self.initialCaption?self.initialCaption:out.caption;self._setPreviewContent(out.content);self._setInitThumbAttr();self._setCaption(cap);self._initSortable();if(!$h.isEmpty(out.content)){self.$container.removeClass('file-input-new')}self._initPreviewImageOrientations()},_getZoomButton:function(type){var self=this,label=self.previewZoomButtonIcons[type],css=self.previewZoomButtonClasses[type],title=' title="'+(self.previewZoomButtonTitles[type]||'')+'" ',tag=$h.isBs(5)?'bs-':'',params=title+(type==='close'?' data-'+tag+'dismiss="modal" aria-hidden="true"':'');if(type==='fullscreen'||type==='borderless'||type==='toggleheader'){params+=' data-toggle="button" aria-pressed="false" autocomplete="off"'}return''},_getModalContent:function(){var self=this;return self._getLayoutTemplate('modal').setTokens({'rtl':self.rtl?' kv-rtl':'','zoomFrameClass':self.frameClass,'prev':self._getZoomButton('prev'),'next':self._getZoomButton('next'),'rotate':self._getZoomButton('rotate'),'toggleheader':self._getZoomButton('toggleheader'),'fullscreen':self._getZoomButton('fullscreen'),'borderless':self._getZoomButton('borderless'),'close':self._getZoomButton('close')})},_listenModalEvent:function(event){var self=this,$modal=self.$modal,getParams=function(e){return{sourceEvent:e,previewId:$modal.data('previewId'),modal:$modal}};$modal.on(event+'.bs.modal',function(e){if(e.namespace!=='bs.modal'){return}var $btnFull=$modal.find('.btn-fullscreen'),$btnBord=$modal.find('.btn-borderless');if($modal.data('fileinputPluginId')===self.$element.attr('id')){self._raise('filezoom'+event,getParams(e))}if(event==='shown'){self._handleRotation($modal,$modal.find('.file-zoom-detail'),$modal.data('angle'));$btnBord.removeClass('active').attr('aria-pressed','false');$btnFull.removeClass('active').attr('aria-pressed','false');if($modal.hasClass('file-zoom-fullscreen')){self._maximizeZoomDialog();if($h.checkFullScreen()){$btnFull.addClass('active').attr('aria-pressed','true')}else{$btnBord.addClass('active').attr('aria-pressed','true')}}}})},_initZoom:function(){var self=this,$dialog,modalMain=self._getLayoutTemplate('modalMain'),modalId='#'+$h.MODAL_ID;modalMain=self._setTabIndex('modal',modalMain);if(!self.showPreview){return}self.$modal=$(modalId);if(!self.$modal||!self.$modal.length){$dialog=$h.createElement($h.cspBuffer.stash(modalMain)).insertAfter(self.$container);self.$modal=$(modalId).insertBefore($dialog);$h.cspBuffer.apply(self.$modal);$dialog.remove()}$h.initModal(self.$modal);self.$modal.html($h.cspBuffer.stash(self._getModalContent()));$h.cspBuffer.apply(self.$modal);$.each($h.MODAL_EVENTS,function(key,event){self._listenModalEvent(event)})},_initZoomButtons:function(){var self=this,$modal=self.$modal,previewId=$modal.data('previewId')||'',$first,$last,thumbs=self.getFrames().toArray(),len=thumbs.length,$prev=$modal.find('.btn-kv-prev'),$next=$modal.find('.btn-kv-next'),$rotate=$modal.find('.btn-kv-rotate');if(thumbs.length<2){$prev.hide();$next.hide();return}else{$prev.show();$next.show()}if(!len){return}$first=$(thumbs[0]);$last=$(thumbs[len-1]);$prev.removeAttr('disabled');$next.removeAttr('disabled');if(self.reversePreviewOrder){[$prev,$next]=[$next,$prev]}if($first.length&&$first.attr('id')===previewId){$prev.attr('disabled',true)}if($last.length&&$last.attr('id')===previewId){$next.attr('disabled',true)}},_maximizeZoomDialog:function(){var self=this,$modal=self.$modal,$head=$modal.find('.modal-header:visible'),$foot=$modal.find('.modal-footer:visible'),$body=$modal.find('.kv-zoom-body'),h=$(window).height(),diff=0;$modal.addClass('file-zoom-fullscreen');if($head&&$head.length){h-=$head.outerHeight(true)}if($foot&&$foot.length){h-=$foot.outerHeight(true)}if($body&&$body.length){diff=$body.outerHeight(true)-$body.height();h-=diff}$modal.find('.kv-zoom-body').height(h)},_resizeZoomDialog:function(fullScreen){var self=this,$modal=self.$modal,$btnFull=$modal.find('.btn-kv-fullscreen'),$btnBord=$modal.find('.btn-kv-borderless');if($modal.hasClass('file-zoom-fullscreen')){$h.toggleFullScreen(false);if(!fullScreen){if(!$btnFull.hasClass('active')){$modal.removeClass('file-zoom-fullscreen');self.$modal.find('.kv-zoom-body').css('height',self.zoomModalHeight)}else{$btnFull.removeClass('active').attr('aria-pressed','false')}}else{if(!$btnFull.hasClass('active')){$modal.removeClass('file-zoom-fullscreen');self._resizeZoomDialog(true);if($btnBord.hasClass('active')){$btnBord.removeClass('active').attr('aria-pressed','false')}}}}else{if(!fullScreen){self._maximizeZoomDialog();return}$h.toggleFullScreen(true)}$modal.focus()},_setZoomContent:function($frame,navigate){var self=this,$content,tmplt,body,title,$body,$dataEl,config,previewId=$frame.attr('id'),$zoomPreview=self._getZoom(previewId),$modal=self.$modal,$tmp,desc,$desc,$btnFull=$modal.find('.btn-kv-fullscreen'),$btnBord=$modal.find('.btn-kv-borderless'),cap,size,$btnTogh=$modal.find('.btn-kv-toggleheader'),dir=navigate==='prev'?'Left':'Right',slideIn='slideIn'+dir,slideOut='slideOut'+dir,parsed,zoomData=$frame.data('zoom');if(zoomData){zoomData=decodeURIComponent(zoomData);parsed=$zoomPreview.html().replace(self.zoomPlaceholder,'').setTokens({zoomData:zoomData});$zoomPreview.html(parsed);$frame.data('zoom','');$zoomPreview.attr('data-zoom',zoomData)}tmplt=$zoomPreview.attr('data-template')||'generic';$content=$zoomPreview.find('.kv-file-content');body=$content.length?$content.html():'';cap=$frame.data('caption')||self.msgZoomModalHeading;size=$frame.data('size')||'';desc=$frame.data('description')||'';$modal.find('.kv-zoom-caption').attr('title',cap).html(cap);$modal.find('.kv-zoom-size').html(size);$desc=$modal.find('.kv-zoom-description').hide();if(desc){if(self.showDescriptionClose){desc=self._getLayoutTemplate('descriptionClose').setTokens({closeIcon:self.previewZoomButtonIcons.close})+''+desc}$desc.show().html(desc);if(self.showDescriptionClose){self._handler($modal.find('.kv-desc-hide'),'click',function(){$(this).parent().fadeOut('fast',function(){$modal.focus()})})}}$body=$modal.find('.kv-zoom-body');$modal.removeClass('kv-single-content');if(navigate){$tmp=$body.addClass('file-thumb-loading').clone().insertAfter($body);$h.setHtml($body,body).hide();$tmp.fadeOut('fast',function(){$body.fadeIn('fast',function(){$body.removeClass('file-thumb-loading')});$tmp.remove()})}else{$h.setHtml($body,body)}config=self.previewZoomSettings[tmplt];if(config){$dataEl=$body.find('.kv-preview-data');$h.addCss($dataEl,'file-zoom-detail');$.each(config,function(key,value){$dataEl.css(key,value);if(($dataEl.attr('width')&&key==='width')||($dataEl.attr('height')&&key==='height')){$dataEl.removeAttr(key)}})}$modal.data('previewId',previewId);self._handler($modal.find('.btn-kv-prev'),'click',function(){self._zoomSlideShow('prev',previewId)});self._handler($modal.find('.btn-kv-next'),'click',function(){self._zoomSlideShow('next',previewId)});self._handler($btnFull,'click',function(){self._resizeZoomDialog(true)});self._handler($btnBord,'click',function(){self._resizeZoomDialog(false)});self._handler($btnTogh,'click',function(){var $header=$modal.find('.modal-header'),$floatBar=$modal.find('.floating-buttons'),ht,$actions=$header.find('.kv-zoom-actions'),resize=function(height){var $body=self.$modal.find('.kv-zoom-body'),h=self.zoomModalHeight;if($modal.hasClass('file-zoom-fullscreen')){h=$body.outerHeight(true);if(!height){h=h-$header.outerHeight(true)}}$body.css('height',height?h+height:h)};if($header.is(':visible')){ht=$header.outerHeight(true);$header.slideUp('slow',function(){$actions.find('.btn').appendTo($floatBar);resize(ht)})}else{$floatBar.find('.btn').appendTo($actions);$header.slideDown('slow',function(){resize()})}$modal.focus()});self._handler($modal,'keydown',function(e){var key=e.which||e.keyCode,delay=self.processDelay+1,$prev=$(this).find('.btn-kv-prev'),$next=$(this).find('.btn-kv-next'),vId=$(this).data('previewId'),vPrevKey,vNextKey;[vPrevKey,vNextKey]=self.rtl?[39,37]:[37,39];$.each({prev:[$prev,vPrevKey],next:[$next,vNextKey]},function(direction,config){var $btn=config[0],vKey=config[1];if(key===vKey&&$btn.length){$modal.focus();if(!$btn.attr('disabled')){$btn.blur();setTimeout(function(){$btn.focus();self._zoomSlideShow(direction,vId);setTimeout(function(){if($btn.attr('disabled')){$modal.focus()}},delay)},delay)}}})})},_showModal:function($frame){var self=this,$modal=self.$modal,$content,css,angle;if(!$frame||!$frame.length){return}$h.initModal($modal);$h.setHtml($modal,self._getModalContent());self._setZoomContent($frame);$modal.removeClass('rotatable');$modal.data({backdrop:false,fileinputPluginId:self.$element.attr('id')});$modal.find('.kv-zoom-body').css('height',self.zoomModalHeight);$content=$frame.find('.kv-file-content > :first-child');if($content.length){css=$content.css('transform');if(css){$modal.find('.file-zoom-detail').css('transform',css)}}if($frame.hasClass('rotatable')){$modal.addClass('rotatable')}if($frame.data('angle')){$modal.data('angle',$frame.data('angle'))}angle=($frame.data('angle')||0);$modal.modal('show');self._initZoomButtons();self._initRotateZoom($frame,$content)},_zoomPreview:function($btn){var self=this,$frame;if(!$btn.length){throw'Cannot zoom to detailed preview!';}$frame=$btn.closest($h.FRAMES);self._showModal($frame)},_zoomSlideShow:function(dir,previewId){var self=this,$modal=self.$modal,$btn=$modal.find('.kv-zoom-actions .btn-kv-'+dir),$targFrame,i,$thumb,thumbsData=self.getFrames().toArray(),thumbs=[],len=thumbsData.length,out,angle,$content;if(self.reversePreviewOrder){dir=dir==='prev'?'next':'prev'}if($btn.attr('disabled')){return}for(i=0;i=len||!thumbs[out]){return}$targFrame=$(thumbs[out]);if($targFrame.length){self._setZoomContent($targFrame,dir)}self._initZoomButtons();if($targFrame.length&&$targFrame.hasClass('rotatable')){angle=$targFrame.data('angle')||0;$modal.addClass('rotatable').data('angle',angle);$content=$targFrame.find('.kv-file-content > :first-child');self._initRotateZoom($targFrame,$content)}else{$modal.removeClass('rotatable').removeData('angle')}self._raise('filezoom'+dir,{'previewId':previewId,modal:self.$modal})},_initZoomButton:function(){var self=this;self.$preview.find('.kv-file-zoom').each(function(){var $el=$(this);self._handler($el,'click',function(){self._zoomPreview($el)})})},_inputFileCount:function(){return this.$element[0].files.length},_refreshPreview:function(){var self=this,files;if((!self._inputFileCount()&&!self.isAjaxUpload)||!self.showPreview||!self.isPreviewable){return}if(self.isAjaxUpload){if(self.fileManager.count()>0){files=$.extend(true,[],self.getFileList());self.fileManager.clear();self._clearFileInput()}else{files=self.$element[0].files}}else{files=self.$element[0].files}if(files&&files.length){self.readFiles(files)}},_clearObjects:function($el){$el.find('video audio').each(function(){this.pause();$(this).remove()});$el.find('img object div').each(function(){$(this).remove()})},_clearFileInput:function(){var self=this,$el=self.$element,$srcFrm,$tmpFrm,$tmpEl;if(!self._inputFileCount()){return}$srcFrm=$el.closest('form');$tmpFrm=$(document.createElement('form'));$tmpEl=$(document.createElement('div'));$el.before($tmpEl);if($srcFrm.length){$srcFrm.after($tmpFrm)}else{$tmpEl.after($tmpFrm)}$tmpFrm.append($el).trigger('reset');$tmpEl.before($el).remove();$tmpFrm.remove()},_resetUpload:function(){var self=this;self.uploadInitiated=false;self.uploadStartTime=$h.now();self.uploadCache=[];self.$btnUpload.removeAttr('disabled');self._setProgress(0);self._hideProgress();self._resetErrors(false);self._initAjax();self.fileManager.clearImages();self._resetCanvas();if(self.overwriteInitial){self.initialPreview=[];self.initialPreviewConfig=[];self.initialPreviewThumbTags=[];self.previewCache.data={content:[],config:[],tags:[]}}},_resetCanvas:function(){var self=this;if(self.imageCanvas&&self.imageCanvasContext){self.imageCanvasContext.clearRect(0,0,self.imageCanvas.width,self.imageCanvas.height)}},_hasInitialPreview:function(){var self=this;return!self.overwriteInitial&&self.previewCache.count(true)},_resetPreview:function(){var self=this,out,cap,$div,hasSuc=self.showUploadedThumbs,hasErr=!self.removeFromPreviewOnError,includeProcessed=(hasSuc||hasErr)&&self.isDuplicateError;if(self.previewCache.count(true)){out=self.previewCache.out();if(includeProcessed){$div=$h.createElement('').insertAfter(self.$container);self.getFrames().each(function(){var $thumb=$(this);if((hasSuc&&$thumb.hasClass('file-preview-success'))||(hasErr&&$thumb.hasClass('file-preview-error'))){$div.append($thumb)}})}self._setPreviewContent(out.content);self._setInitThumbAttr();cap=self.initialCaption?self.initialCaption:out.caption;self._setCaption(cap);if(includeProcessed){$div.contents().appendTo(self.$preview);$div.remove()}}else{self._clearPreview();self._initCaption()}if(self.showPreview){self._initZoom();self._initSortable()}self.isDuplicateError=false},_clearDefaultPreview:function(){var self=this;self.$preview.find('.file-default-preview').remove()},_validateDefaultPreview:function(){var self=this;if(!self.showPreview||$h.isEmpty(self.defaultPreviewContent)){return}self._setPreviewContent('
    '+self.defaultPreviewContent+'
    ');self.$container.removeClass('file-input-new');self._initClickable()},_resetPreviewThumbs:function(isAjax){var self=this,out;if(isAjax){self._clearPreview();self.clearFileStack();return}if(self._hasInitialPreview()){out=self.previewCache.out();self._setPreviewContent(out.content);self._setInitThumbAttr();self._setCaption(out.caption);self._initPreviewActions()}else{self._clearPreview()}},_getLayoutTemplate:function(t){var self=this,template=self.layoutTemplates[t];if($h.isEmpty(self.customLayoutTags)){return template}return $h.replaceTags(template,self.customLayoutTags)},_getPreviewTemplate:function(t){var self=this,templates=self.previewTemplates,template=templates[t]||templates.other;if($h.isEmpty(self.customPreviewTags)){return template}return $h.replaceTags(template,self.customPreviewTags)},_getOutData:function(formdata,jqXHR,responseData,filesData){var self=this;jqXHR=jqXHR||{};responseData=responseData||{};filesData=filesData||self.fileManager.list();return{formdata:formdata,files:filesData,filenames:self.filenames,filescount:self.getFilesCount(),extra:self._getExtraData(),response:responseData,reader:self.reader,jqXHR:jqXHR}},_getMsgSelected:function(n,processing){var self=this,strFiles=n===1?self.fileSingle:self.filePlural;return n>0?self.msgSelected.replace('{n}',n).replace('{files}',strFiles):(processing?self.msgProcessing:self.msgNoFilesSelected)},_getFrame:function(id,skipWarning){var self=this,$frame=$h.getFrameElement(self.$preview,id);if(self.showPreview&&!skipWarning&&!$frame.length){self._log($h.logMessages.invalidThumb,{id:id})}return $frame},_getZoom:function(id,selector){var self=this,$frame=$h.getZoomElement(self.$preview,id,selector);if(self.showPreview&&!$frame.length){self._log($h.logMessages.invalidThumb,{id:id})}return $frame},_getThumbs:function(css){css=css||'';return this.getFrames(':not(.file-preview-initial)'+css)},_getThumbId:function(fileId){var self=this;return self.previewInitId+'-'+fileId},_getExtraData:function(fileId,index){var self=this,data=self.uploadExtraData;if(typeof self.uploadExtraData==='function'){data=self.uploadExtraData(fileId,index)}return data},_initXhr:function(xhrobj,fileId){var self=this,fm=self.fileManager,func=function(event){var pct=0,total=event.total,loaded=event.loaded||event.position,stats=fm.getUploadStats(fileId,loaded,total);if(event.lengthComputable&&!self.enableResumableUpload){pct=$h.round(loaded/total*100)}if(fileId){self._setFileUploadStats(fileId,pct,stats)}else{self._setProgress(pct,null,null,self._getStats(stats))}self._raise('fileajaxprogress',[stats])};if(xhrobj.upload){if(self.progressDelay){func=$h.debounce(func,self.progressDelay)}xhrobj.upload.addEventListener('progress',func,false)}return xhrobj},_initAjaxSettings:function(){var self=this;self._ajaxSettings=$.extend(true,{},self.ajaxSettings);self._ajaxDeleteSettings=$.extend(true,{},self.ajaxDeleteSettings)},_mergeAjaxCallback:function(funcName,srcFunc,type){var self=this,settings=self._ajaxSettings,flag=self.mergeAjaxCallbacks,targFunc;if(type==='delete'){settings=self._ajaxDeleteSettings;flag=self.mergeAjaxDeleteCallbacks}targFunc=settings[funcName];if(flag&&typeof targFunc==='function'){if(flag==='before'){settings[funcName]=function(){targFunc.apply(this,arguments);srcFunc.apply(this,arguments)}}else{settings[funcName]=function(){srcFunc.apply(this,arguments);targFunc.apply(this,arguments)}}}else{settings[funcName]=srcFunc}},_ajaxSubmit:function(fnBefore,fnSuccess,fnComplete,fnError,formdata,fileId,index,vUrl){var self=this,settings,defaults,data,tm=self.taskManager;if(!self._raise('filepreajax',[formdata,fileId,index])){return}formdata.append('initialPreview',JSON.stringify(self.initialPreview));formdata.append('initialPreviewConfig',JSON.stringify(self.initialPreviewConfig));formdata.append('initialPreviewThumbTags',JSON.stringify(self.initialPreviewThumbTags));self._initAjaxSettings();self._mergeAjaxCallback('beforeSend',fnBefore);self._mergeAjaxCallback('success',fnSuccess);self._mergeAjaxCallback('complete',fnComplete);self._mergeAjaxCallback('error',fnError);vUrl=vUrl||self.uploadUrlThumb||self.uploadUrl;if(typeof vUrl==='function'){vUrl=vUrl()}data=self._getExtraData(fileId,index)||{};if(typeof data==='object'){$.each(data,function(key,value){formdata.append(key,value)})}defaults={xhr:function(){var xhrobj=$.ajaxSettings.xhr();return self._initXhr(xhrobj,fileId)},url:self._encodeURI(vUrl),type:'POST',dataType:'json',data:formdata,cache:false,processData:false,contentType:false};settings=$.extend(true,{},defaults,self._ajaxSettings);self.ajaxQueue.push(settings);tm.addTask(fileId+'-'+index,function(){var self=this.self,config,xhr;config=self.ajaxQueue.shift();xhr=$.ajax(config);self.ajaxRequests.push(xhr)}).runWithContext({self:self})},_mergeArray:function(prop,content){var self=this,arr1=$h.cleanArray(self[prop]),arr2=$h.cleanArray(content);self[prop]=arr1.concat(arr2)},_initUploadSuccess:function(out,$thumb,allFiles){var self=this,append,data,index,$div,content,config,tags,id,i;if(!self.showPreview||typeof out!=='object'||$.isEmptyObject(out)){self._resetCaption();return}if(out.initialPreview!==undefined&&out.initialPreview.length>0){self.hasInitData=true;content=out.initialPreview||[];config=out.initialPreviewConfig||[];tags=out.initialPreviewThumbTags||[];append=out.append===undefined||out.append;if(content.length>0&&!$h.isArray(content)){content=content.split(self.initialPreviewDelimiter)}if(content.length){self._mergeArray('initialPreview',content);self._mergeArray('initialPreviewConfig',config);self._mergeArray('initialPreviewThumbTags',tags)}if($thumb!==undefined){if(!allFiles){index=self.previewCache.add(content[0],config[0],tags[0],append);data=self.previewCache.get(index,false);$div=$h.createElement(data).hide().appendTo($thumb);$thumb.fadeOut('slow',function(){var $newThumb=$div.find('> .file-preview-frame');if($newThumb&&$newThumb.length){$newThumb.insertBefore($thumb).fadeIn('slow').css('display:inline-block')}self._initPreviewActions();self._clearFileInput();$thumb.remove();$div.remove();self._initSortable()})}else{id=$thumb.attr('id');i=self._getUploadCacheIndex(id);if(i!==null){self.uploadCache[i]={id:id,content:content[0],config:config[0]||[],tags:tags[0]||[],append:append}}}}else{self.previewCache.set(content,config,tags,append);self._initPreview();self._initPreviewActions()}}self._resetCaption()},_getUploadCacheIndex:function(id){var self=this,i,len=self.uploadCache.length,config;for(i=0;i0||!$.isEmptyObject(self.uploadExtraData),uploadFailed,$prog,fnBefore,errMsg,fnSuccess,fnComplete,fnError,updateUploadLog,op=self.ajaxOperations.uploadThumb,fileObj=fm.getFile(id),params={id:previewId,index:i,fileId:id},fileName=self.fileManager.getFileName(id,true),resolve=function(){if(deferrer&&deferrer.resolve){deferrer.resolve()}},reject=function(){if(deferrer&&deferrer.reject){deferrer.reject()}};if(self.enableResumableUpload){return}self.uploadInitiated=true;if(self.showPreview){$thumb=fm.getThumb(id);$prog=$thumb.find('.file-thumb-progress');$btnUpload=$thumb.find('.kv-file-upload');$btnDelete=$thumb.find('.kv-file-remove');$prog.show()}if(count===0||!hasPostData||(self.showPreview&&$btnUpload&&$btnUpload.hasClass('disabled'))||self._abort(params)){return}updateUploadLog=function(){if(!uploadFailed){fm.removeFile(id)}else{fm.errors.push(id)}fm.setProcessed(id);if(fm.isProcessed()){self.fileBatchCompleted=true;chkComplete()}};chkComplete=function(){var $initThumbs;if(!self.fileBatchCompleted){return}setTimeout(function(){var triggerReset=fm.count()===0,errCount=fm.errors.length;self._updateInitialPreview();self.unlock(triggerReset);if(triggerReset){self._clearFileInput()}$initThumbs=self.$preview.find('.file-preview-initial');if(self.uploadAsync&&$initThumbs.length){$h.addCss($initThumbs,$h.SORT_CSS);self._initSortable()}self._raise('filebatchuploadcomplete',[fm.stack,self._getExtraData()]);if(!self.retryErrorUploads||errCount===0){fm.clear()}self._setProgress(101);self.ajaxAborted=false;self.uploadInitiated=false},self.processDelay)};fnBefore=function(jqXHR){outData=self._getOutData(formdata,jqXHR);fm.initStats(id);self.fileBatchCompleted=false;if(!isBatch){self.ajaxAborted=false}if(self.showPreview){if(!$thumb.hasClass('file-preview-success')){self._setThumbStatus($thumb,'Loading');$h.addCss($thumb,'file-uploading')}$btnUpload.attr('disabled',true);$btnDelete.attr('disabled',true)}if(!isBatch){self.lock()}if(fm.errors.indexOf(id)!==-1){delete fm.errors[id]}self._raise('filepreupload',[outData,previewId,i,self._getThumbFileId($thumb)]);$.extend(true,params,outData);if(self._abort(params)){jqXHR.abort();if(!isBatch){self._setThumbStatus($thumb,'New');$thumb.removeClass('file-uploading');$btnUpload.removeAttr('disabled');$btnDelete.removeAttr('disabled')}self._setProgressCancelled()}};fnSuccess=function(data,textStatus,jqXHR){var pid=self.showPreview&&$thumb.attr('id')?$thumb.attr('id'):previewId;outData=self._getOutData(formdata,jqXHR,data);$.extend(true,params,outData);setTimeout(function(){if($h.isEmpty(data)||$h.isEmpty(data.error)){if(self.showPreview){self._setThumbStatus($thumb,'Success');$btnUpload.hide();self._initUploadSuccess(data,$thumb,isBatch);self._setProgress(101,$prog)}self._raise('fileuploaded',[outData,pid,i,self._getThumbFileId($thumb)]);if(!isBatch){self.fileManager.remove($thumb)}else{updateUploadLog();resolve()}}else{uploadFailed=true;errMsg=self._parseError(op,jqXHR,self.msgUploadError,self.fileManager.getFileName(id));self._showFileError(errMsg,params);self._setPreviewError($thumb,true);if(!self.retryErrorUploads){$btnUpload.hide()}if(isBatch){updateUploadLog();resolve()}self._setProgress(101,self._getFrame(pid).find('.file-thumb-progress'),self.msgUploadError)}},self.processDelay)};fnComplete=function(){if(self.showPreview){$btnUpload.removeAttr('disabled');$btnDelete.removeAttr('disabled');$thumb.removeClass('file-uploading')}if(!isBatch){self.unlock(false);self._clearFileInput()}else{chkComplete()}self._initSuccessThumbs()};fnError=function(jqXHR,textStatus,errorThrown){errMsg=self._parseError(op,jqXHR,errorThrown,self.fileManager.getFileName(id));uploadFailed=true;setTimeout(function(){var $prog;if(isBatch){updateUploadLog();reject()}self.fileManager.setProgress(id,100);self._setPreviewError($thumb,true);if(!self.retryErrorUploads){$btnUpload.hide()}$.extend(true,params,self._getOutData(formdata,jqXHR));self._setProgress(101,self.$progress,self.msgAjaxProgressError.replace('{operation}',op));$prog=self.showPreview&&$thumb?$thumb.find('.file-thumb-progress'):'';self._setProgress(101,$prog,self.msgUploadError);self._showFileError(errMsg,params)},self.processDelay)};self._setFileData(formdata,fileObj.file,fileName,id);self._setUploadData(formdata,{fileId:id});self._ajaxSubmit(fnBefore,fnSuccess,fnComplete,fnError,formdata,id,i)},_setFileData:function(formdata,file,fileName,fileId){var self=this,preProcess=self.preProcessUpload;if(preProcess&&typeof preProcess==='function'){formdata.append(self.uploadFileAttr,preProcess(fileId,file))}else{formdata.append(self.uploadFileAttr,file,fileName)}},_checkBatchPreupload:function(outData,jqXHR){var self=this,out=self._raise('filebatchpreupload',[outData]);if(out){return true}self._abort(outData);if(jqXHR){jqXHR.abort()}self._getThumbs().each(function(){var $thumb=$(this),$btnUpload=$thumb.find('.kv-file-upload'),$btnDelete=$thumb.find('.kv-file-remove');if($thumb.hasClass('file-preview-loading')){self._setThumbStatus($thumb,'New');$thumb.removeClass('file-uploading')}$btnUpload.removeAttr('disabled');$btnDelete.removeAttr('disabled')});self._setProgressCancelled();return false},_uploadBatch:function(){var self=this,fm=self.fileManager,total=fm.total(),params={},fnBefore,fnSuccess,fnError,fnComplete,hasPostData=total>0||!$.isEmptyObject(self.uploadExtraData),errMsg,setAllUploaded,formdata=new FormData(),op=self.ajaxOperations.uploadBatch;if(total===0||!hasPostData||self._abort(params)){return}setAllUploaded=function(){self.fileManager.clear();self._clearFileInput()};fnBefore=function(jqXHR){self.lock();fm.initStats();var outData=self._getOutData(formdata,jqXHR);self.ajaxAborted=false;if(self.showPreview){self._getThumbs().each(function(){var $thumb=$(this),$btnUpload=$thumb.find('.kv-file-upload'),$btnDelete=$thumb.find('.kv-file-remove');if(!$thumb.hasClass('file-preview-success')){self._setThumbStatus($thumb,'Loading');$h.addCss($thumb,'file-uploading')}$btnUpload.attr('disabled',true);$btnDelete.attr('disabled',true)})}self._checkBatchPreupload(outData,jqXHR)};fnSuccess=function(data,textStatus,jqXHR){var outData=self._getOutData(formdata,jqXHR,data),key=0,$thumbs=self._getThumbs(':not(.file-preview-success)'),keys=$h.isEmpty(data)||$h.isEmpty(data.errorkeys)?[]:data.errorkeys;if($h.isEmpty(data)||$h.isEmpty(data.error)){self._raise('filebatchuploadsuccess',[outData]);setAllUploaded();if(self.showPreview){$thumbs.each(function(){var $thumb=$(this);self._setThumbStatus($thumb,'Success');$thumb.removeClass('file-uploading');$thumb.find('.kv-file-upload').hide().removeAttr('disabled')});self._initUploadSuccess(data)}else{self.reset()}self._setProgress(101)}else{if(self.showPreview){$thumbs.each(function(){var $thumb=$(this);$thumb.removeClass('file-uploading');$thumb.find('.kv-file-upload').removeAttr('disabled');$thumb.find('.kv-file-remove').removeAttr('disabled');if(keys.length===0||$.inArray(key,keys)!==-1){self._setPreviewError($thumb,true);if(!self.retryErrorUploads){$thumb.find('.kv-file-upload').hide();self.fileManager.remove($thumb)}}else{$thumb.find('.kv-file-upload').hide();self._setThumbStatus($thumb,'Success');self.fileManager.remove($thumb)}if(!$thumb.hasClass('file-preview-error')||self.retryErrorUploads){key++}});self._initUploadSuccess(data)}errMsg=self._parseError(op,jqXHR,self.msgUploadError);self._showFileError(errMsg,outData,'filebatchuploaderror');self._setProgress(101,self.$progress,self.msgUploadError)}};fnComplete=function(){self.unlock();self._initSuccessThumbs();self._clearFileInput();self._raise('filebatchuploadcomplete',[self.fileManager.stack,self._getExtraData()])};fnError=function(jqXHR,textStatus,errorThrown){var outData=self._getOutData(formdata,jqXHR);errMsg=self._parseError(op,jqXHR,errorThrown);self._showFileError(errMsg,outData,'filebatchuploaderror');self.uploadFileCount=total-1;if(!self.showPreview){return}self._getThumbs().each(function(){var $thumb=$(this);$thumb.removeClass('file-uploading');if(self._getThumbFile($thumb)){self._setPreviewError($thumb)}});self._getThumbs().removeClass('file-uploading');self._getThumbs(' .kv-file-upload').removeAttr('disabled');self._getThumbs(' .kv-file-delete').removeAttr('disabled');self._setProgress(101,self.$progress,self.msgAjaxProgressError.replace('{operation}',op))};var ctr=0;$.each(self.fileManager.stack,function(key,data){if(!$h.isEmpty(data.file)){self._setFileData(formdata,data.file,(data.nameFmt||('untitled_'+ctr)),key)}ctr++});self._ajaxSubmit(fnBefore,fnSuccess,fnComplete,fnError,formdata)},_uploadExtraOnly:function(){var self=this,params={},fnBefore,fnSuccess,fnComplete,fnError,formdata=new FormData(),errMsg,op=self.ajaxOperations.uploadExtra;fnBefore=function(jqXHR){self.lock();var outData=self._getOutData(formdata,jqXHR);self._setProgress(50);params.data=outData;params.xhr=jqXHR;self._checkBatchPreupload(outData,jqXHR)};fnSuccess=function(data,textStatus,jqXHR){var outData=self._getOutData(formdata,jqXHR,data);if($h.isEmpty(data)||$h.isEmpty(data.error)){self._raise('filebatchuploadsuccess',[outData]);self._clearFileInput();self._initUploadSuccess(data);self._setProgress(101)}else{errMsg=self._parseError(op,jqXHR,self.msgUploadError);self._showFileError(errMsg,outData,'filebatchuploaderror')}};fnComplete=function(){self.unlock();self._clearFileInput();self._raise('filebatchuploadcomplete',[self.fileManager.stack,self._getExtraData()])};fnError=function(jqXHR,textStatus,errorThrown){var outData=self._getOutData(formdata,jqXHR);errMsg=self._parseError(op,jqXHR,errorThrown);params.data=outData;self._showFileError(errMsg,outData,'filebatchuploaderror');self._setProgress(101,self.$progress,self.msgAjaxProgressError.replace('{operation}',op))};self._ajaxSubmit(fnBefore,fnSuccess,fnComplete,fnError,formdata)},_deleteFileIndex:function($frame){var self=this,ind=$frame.attr('data-fileindex'),rev=self.reversePreviewOrder;if(ind.substring(0,5)===$h.INIT_FLAG){ind=parseInt(ind.replace($h.INIT_FLAG,''));self.initialPreview=$h.spliceArray(self.initialPreview,ind,rev);self.initialPreviewConfig=$h.spliceArray(self.initialPreviewConfig,ind,rev);self.initialPreviewThumbTags=$h.spliceArray(self.initialPreviewThumbTags,ind,rev);self.getFrames().each(function(){var $nFrame=$(this),nInd=$nFrame.attr('data-fileindex');if(nInd.substring(0,5)===$h.INIT_FLAG){nInd=parseInt(nInd.replace($h.INIT_FLAG,''));if(nInd>ind){nInd--;$nFrame.attr('data-fileindex',$h.INIT_FLAG+nInd)}}})}},_resetCaption:function(){var self=this;setTimeout(function(){var cap='',n,chk=self.previewCache.count(true),len=self.fileManager.count(),file,incomplete=':not(.file-preview-success):not(.file-preview-error)',cfg,hasThumb=self.showPreview&&self.getFrames(incomplete).length;if(len===0&&chk===0&&!hasThumb){self.reset()}else{n=chk+len;if(n>1){cap=self._getMsgSelected(n)}else{if(len===0){cfg=self.initialPreviewConfig[0];cap='';if(cfg){cap=cfg.caption||cfg.filename||''}if(!cap){cap=self._getMsgSelected(n)}}else{file=self.fileManager.getFirstFile();cap=file?file.nameFmt:'_'}}self._setCaption(cap)}},self.processDelay)},_handleRotation:function($el,$content,angle){var self=this,css,newCss,addCss='',scale=1,elContent=$content[0],quadrant,transform,h,w,wNew,$parent=$content.parent(),hParent,wParent,$body=$('body'),bodyExists=!!$body.length;if(bodyExists){$body.addClass('kv-overflow-hidden')}if(!$content.length||$el.hasClass('hide-rotate')){if(bodyExists){$body.removeClass('kv-overflow-hidden')}return}transform=$content.css('transform');if(transform){$content.css('transform','none')}if(transform){$content.css('transform',transform)}angle=angle||0;quadrant=angle%360;css='rotate('+angle+'deg)';newCss='rotate('+quadrant+'deg)';addCss='';if(quadrant===90||quadrant===270){w=elContent.naturalWidth||$content.outerWidth()||0;h=elContent.naturalHeight||$content.outerHeight()||0;scale=w>h&&w!=0?(h/w).toFixed(2):1;if($parent.length){hParent=$parent.height();wParent=$parent.width();wNew=Math.min(w,wParent);if(hParent>scale*wNew){scale=wNew>hParent&&wNew!=0?(hParent/wNew).toFixed(2):1}}if(scale!==1){addCss=' scale('+scale+')'}}$content.addClass('rotate-animate').css('transform',css+addCss);setTimeout(function(){$content.removeClass('rotate-animate').css('transform',newCss+addCss);if(bodyExists){$body.removeClass('kv-overflow-hidden')}$el.data('angle',quadrant)},self.fadeDelay)},_initRotateButton:function(){var self=this;self.getFrames('.rotatable .kv-file-rotate').each(function(){var $el=$(this),$frame=$el.closest($h.FRAMES),$content=$frame.find('.kv-file-content > :first-child');self._handler($el,'click',function(){var angle=($frame.data('angle')||0)+90;self._handleRotation($frame,$content,angle)})})},_initRotateZoom:function($frame,$content){var self=this,$modal=self.$modal,$rotate=$modal.find('.btn-kv-rotate'),angle=$frame.data('angle');$modal.data('angle',angle);if($rotate.length){$rotate.off('click');if($modal.hasClass('rotatable')){$rotate.on('click',function(){angle=($modal.data('angle')||0)+90;$modal.data('angle',angle);self._handleRotation($modal,$modal.find('.file-zoom-detail'),angle);self._handleRotation($frame,$content,angle);if($frame.hasClass('hide-rotate')){$frame.data('angle',angle)}})}}},_initFileActions:function(){var self=this;if(!self.showPreview){return}self._initZoomButton();self._initRotateButton();self.getFrames(' .kv-file-remove').each(function(){var $el=$(this),$frame=$el.closest($h.FRAMES),hasError,id=$frame.attr('id'),ind=$frame.attr('data-fileindex'),status,fm=self.fileManager;self._handler($el,'click',function(){status=self._raise('filepreremove',[id,ind]);if(status===false||!self._validateMinCount()){return false}hasError=$frame.hasClass('file-preview-error');$h.cleanMemory($frame);$frame.fadeOut('slow',function(){self.fileManager.remove($frame);self._clearObjects($frame);$frame.remove();if(id&&hasError){self.$errorContainer.find('li[data-thumb-id="'+id+'"]').fadeOut('fast',function(){$(this).remove();if(!self._errorsExist()){self._resetErrors()}})}self._clearFileInput();self._resetCaption();self._raise('fileremoved',[id,ind])})})});self.getFrames(' .kv-file-upload').each(function(){var $el=$(this);self._handler($el,'click',function(){var $frame=$el.closest($h.FRAMES),fileId=self._getThumbFileId($frame);self._hideProgress();if($frame.hasClass('file-preview-error')&&!self.retryErrorUploads){return}self._uploadSingle(self.fileManager.getIndex(fileId),fileId,false)})})},_initPreviewActions:function(){var self=this,$preview=self.$preview,deleteExtraData=self.deleteExtraData||{},btnRemove=$h.FRAMES+' .kv-file-remove',settings=self.fileActionSettings,origClass=settings.removeClass,errClass=settings.removeErrorClass,resetProgress=function(){var hasFiles=self.isAjaxUpload?self.previewCache.count(true):self._inputFileCount();if(!self.getFrames().length&&!hasFiles){self._setCaption('');self.reset();self.initialCaption=''}else{self._resetCaption()}};self._initZoomButton();self._initRotateButton();$preview.find(btnRemove).each(function(){var $el=$(this),vUrl=$el.data('url')||self.deleteUrl,vKey=$el.data('key'),errMsg,fnBefore,fnSuccess,fnError,op=self.ajaxOperations.deleteThumb;if($h.isEmpty(vUrl)||vKey===undefined){return}if(typeof vUrl==='function'){vUrl=vUrl()}var $frame=$el.closest($h.FRAMES),cache=self.previewCache.data,settings,params,config,fileName,extraData,index=$frame.attr('data-fileindex');index=parseInt(index.replace($h.INIT_FLAG,''));config=$h.isEmpty(cache.config)&&$h.isEmpty(cache.config[index])?null:cache.config[index];extraData=$h.isEmpty(config)||$h.isEmpty(config.extra)?deleteExtraData:config.extra;fileName=config&&(config.filename||config.caption)||'';if(typeof extraData==='function'){extraData=extraData()}params={id:$el.attr('id'),key:vKey,extra:extraData};fnBefore=function(jqXHR){self.ajaxAborted=false;self._raise('filepredelete',[vKey,jqXHR,extraData]);if(self._abort()){jqXHR.abort()}else{$el.removeClass(errClass);$h.addCss($frame,'file-uploading');$h.addCss($el,'disabled '+origClass)}};fnSuccess=function(data,textStatus,jqXHR){var n,cap;if(!$h.isEmpty(data)&&!$h.isEmpty(data.error)){params.jqXHR=jqXHR;params.response=data;errMsg=self._parseError(op,jqXHR,self.msgDeleteError,fileName);self._showFileError(errMsg,params,'filedeleteerror');$frame.removeClass('file-uploading');$el.removeClass('disabled '+origClass).addClass(errClass);resetProgress();return}$frame.removeClass('file-uploading').addClass('file-deleted');$frame.fadeOut('slow',function(){index=parseInt(($frame.attr('data-fileindex')).replace($h.INIT_FLAG,''));self.previewCache.unset(index);self._deleteFileIndex($frame);n=self.previewCache.count(true);cap=n>0?self._getMsgSelected(n):'';self._setCaption(cap);self._raise('filedeleted',[vKey,jqXHR,extraData]);self._clearObjects($frame);$frame.remove();resetProgress()})};fnError=function(jqXHR,textStatus,errorThrown){var errMsg=self._parseError(op,jqXHR,errorThrown,fileName);params.jqXHR=jqXHR;params.response={};self._showFileError(errMsg,params,'filedeleteerror');$frame.removeClass('file-uploading');$el.removeClass('disabled '+origClass).addClass(errClass);resetProgress()};self._initAjaxSettings();self._mergeAjaxCallback('beforeSend',fnBefore,'delete');self._mergeAjaxCallback('success',fnSuccess,'delete');self._mergeAjaxCallback('error',fnError,'delete');settings=$.extend(true,{},{url:self._encodeURI(vUrl),type:'POST',dataType:'json',data:$.extend(true,{},{key:vKey},extraData)},self._ajaxDeleteSettings);self._handler($el,'click',function(){if(!self._validateMinCount()){return false}self.ajaxAborted=false;self._raise('filebeforedelete',[vKey,extraData]);if(self.ajaxAborted instanceof Promise){self.ajaxAborted.then(function(result){if(!result){$.ajax(settings)}})}else{if(!self.ajaxAborted){$.ajax(settings)}}})})},_hideFileIcon:function(){var self=this;if(self.overwriteInitial){self.$captionContainer.removeClass('icon-visible')}},_showFileIcon:function(){var self=this;$h.addCss(self.$captionContainer,'icon-visible')},_getSize:function(bytes,skipTemplate,sizeUnits){var self=this,size=parseFloat(bytes),i=0,factor=self.bytesToKB,func=self.fileSizeGetter,out,sizeHuman=size,newSize;if(!$.isNumeric(bytes)||!$.isNumeric(size)){return''}if(typeof func==='function'){out=func(size)}else{if(!sizeUnits){sizeUnits=self.sizeUnits}if(size>0){while(sizeHuman>=factor){sizeHuman/=factor;++i}if(!sizeUnits[i]){sizeHuman=size;i=0}}newSize=sizeHuman.toFixed(2);if(newSize==sizeHuman){newSize=sizeHuman}out=newSize+' '+sizeUnits[i]}return skipTemplate?out:self._getLayoutTemplate('size').replace('{sizeText}',out)},_getFileType:function(ftype){var self=this;return self.mimeTypeAliases[ftype]||ftype},_generatePreviewTemplate:function(cat,data,fname,ftype,previewId,fileId,isError,size,fnameUpdated,frameClass,foot,ind,templ,attrs,zoomData){var self=this,caption=self.slug(fname),prevContent,zoomContent='',styleAttribs='',filename=fnameUpdated||fname,isIconic,ext=filename.split('.').pop().toLowerCase(),screenW=window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth,config,title=caption,alt=caption,typeCss='type-default',getContent,addFrameCss,footer=foot||self._renderFileFooter(cat,caption,size,'auto',isError),isRotatable,alwaysPreview=$.inArray(ext,self.alwaysPreviewFileExtensions)!==-1,forcePrevIcon=self.preferIconicPreview&&!alwaysPreview,forceZoomIcon=self.preferIconicZoomPreview&&!alwaysPreview,newCat=forcePrevIcon?'other':cat;config=screenW<400?(self.previewSettingsSmall[newCat]||self.defaults.previewSettingsSmall[newCat]):(self.previewSettings[newCat]||self.defaults.previewSettings[newCat]);if(config){$.each(config,function(key,val){styleAttribs+=key+':'+val+';'})}getContent=function(vCat,vData,zoom,frameCss,vZoomData){var id=zoom?'zoom-'+previewId:previewId,tmplt=self._getPreviewTemplate(vCat),css=(frameClass||'')+' '+frameCss,tokens;if(self.frameClass){css=self.frameClass+' '+css}if(zoom){css=css.replace(' '+$h.SORT_CSS,'')}tmplt=self._parseFilePreviewIcon(tmplt,fname);if(cat==='object'&&!ftype){$.each(self.defaults.fileTypeSettings,function(key,func){if(key==='object'||key==='other'){return}if(func(fname,ftype)){typeCss='type-'+key}})}if(!$h.isEmpty(attrs)){if(attrs.title!==undefined&&attrs.title!==null){title=attrs.title}if(attrs.alt!==undefined&&attrs.alt!==null){alt=title=attrs.alt}}tokens={'previewId':id,'caption':caption,'title':title,'alt':alt,'frameClass':css,'type':self._getFileType(ftype),'fileindex':ind,'fileid':fileId||'','filename':filename,'typeCss':typeCss,'footer':footer,'data':vData,'template':templ||cat,'style':styleAttribs?'style="'+styleAttribs+'"':'','zoomData':vZoomData?encodeURIComponent(vZoomData):''};if(zoom){tokens.zoomCache='';tokens.zoomData='{zoomData}'}return tmplt.setTokens(tokens)};ind=ind||previewId.slice(previewId.lastIndexOf('-')+1);isRotatable=self.fileActionSettings.showRotate&&$.inArray(ext,self.rotatableFileExtensions)!==-1;if(self.fileActionSettings.showZoom){addFrameCss='kv-zoom-thumb';if(isRotatable){addFrameCss+=' rotatable'+(forceZoomIcon?' hide-rotate':'')}zoomContent=getContent((forceZoomIcon?'other':cat),data,true,addFrameCss,zoomData)}zoomContent='\n'+self._getLayoutTemplate('zoomCache').replace('{zoomContent}',zoomContent);if(typeof self.sanitizeZoomCache==='function'){zoomContent=self.sanitizeZoomCache(zoomContent)}addFrameCss='kv-preview-thumb';if(isRotatable){isIconic=forcePrevIcon||self.hideThumbnailContent||!!self.previewFileIconSettings[ext];addFrameCss+=' rotatable'+(isIconic?' hide-rotate':'')}prevContent=getContent((forcePrevIcon?'other':cat),data,false,addFrameCss,zoomData);return prevContent.setTokens({zoomCache:zoomContent})},_addToPreview:function($preview,content){var self=this,$el;content=$h.cspBuffer.stash(content);$el=self.reversePreviewOrder?$preview.prepend(content):$preview.append(content);$h.cspBuffer.apply($preview);return $el},_previewDefault:function(file,isDisabled){var self=this,$preview=self.$preview;if(!self.showPreview){return}var fname=$h.getFileName(file),ftype=file?file.type:'',content,size=file.size||0,caption=self._getFileName(file,''),isError=isDisabled===true&&!self.isAjaxUpload,data=$h.createObjectURL(file),fileId=self.fileManager.getId(file),previewId=self._getThumbId(fileId);self._clearDefaultPreview();content=self._generatePreviewTemplate('other',data,fname,ftype,previewId,fileId,isError,size);self._addToPreview($preview,content);self._setThumbAttr(previewId,caption,size);if(isDisabled===true&&self.isAjaxUpload){self._setThumbStatus(self._getFrame(previewId),'Error')}},_previewFile:function(i,file,theFile,data,fileInfo){if(!this.showPreview){return}var self=this,fname=$h.getFileName(file),ftype=fileInfo.type,content,caption=fileInfo.name,cat=self._parseFileType(ftype,fname),$preview=self.$preview,fsize=file.size||0,iData=cat==='image'?theFile.target.result:data,fm=self.fileManager,fileId=fm.getId(file),previewId=self._getThumbId(fileId);content=self._generatePreviewTemplate(cat,iData,fname,ftype,previewId,fileId,false,fsize,fileInfo.filename);self._clearDefaultPreview();self._addToPreview($preview,content);var $thumb=self._getFrame(previewId);self._validateImageOrientation($thumb.find('img'),file,previewId,fileId,caption,ftype,fsize,iData);self._setThumbAttr(previewId,caption,fsize);self._initSortable()},_setThumbAttr:function(id,caption,size,description){var self=this,$frame=self._getFrame(id);if($frame.length){size=size&&size>0?self._getSize(size):'';$frame.data({'caption':caption,'size':size,'description':description||''})}},_setInitThumbAttr:function(){var self=this,data=self.previewCache.data,len=self.previewCache.count(true),config,caption,size,description,previewId;if(len===0){return}for(var i=0;i&"']/g,'_')},_updateFileDetails:function(numFiles){var self=this,$el=self.$element,label,n,log,nFiles,file,name=($h.isIE(9)&&$h.findFileName($el.val()))||($el[0].files[0]&&$el[0].files[0].name);if(!name&&self.fileManager.count()>0){file=self.fileManager.getFirstFile();label=file.nameFmt}else{label=name?self.slug(name):'_'}n=self.isAjaxUpload?self.fileManager.count():numFiles;nFiles=self.previewCache.count(true)+n;log=n===1?label:self._getMsgSelected(nFiles,!self.isAjaxUpload&&!self.isError);if(self.isError){self.$previewContainer.removeClass('file-thumb-loading');self._initCapStatus();self.$previewStatus.html('');self.$captionContainer.removeClass('icon-visible')}else{self._showFileIcon()}self._setCaption(log,self.isError);self.$container.removeClass('file-input-new file-input-ajax-new');self._raise('fileselect',[numFiles,label]);if(self.previewCache.count(true)){self._initPreviewActions()}},_setThumbStatus:function($thumb,status){var self=this;if(!self.showPreview){return}var icon='indicator'+status,msg=icon+'Title',css='file-preview-'+status.toLowerCase(),$indicator=$thumb.find('.file-upload-indicator'),config=self.fileActionSettings;$thumb.removeClass('file-preview-success file-preview-error file-preview-paused file-preview-loading');if(status==='Success'){$thumb.find('.file-drag-handle').remove()}$h.setHtml($indicator,config[icon]);$indicator.attr('title',config[msg]);$thumb.addClass(css);if(status==='Error'&&!self.retryErrorUploads){$thumb.find('.kv-file-upload').attr('disabled',true)}},_setProgressCancelled:function(){var self=this;self._setProgress(101,self.$progress,self.msgCancelled)},_setProgress:function(p,$el,error,stats){var self=this;$el=$el||self.$progress;if(!$el.length){return}var pct=Math.min(p,100),out,pctLimit=self.progressUploadThreshold,t=p<=100?self.progressTemplate:self.progressCompleteTemplate,template=pct<100?self.progressTemplate:(error?(self.paused?self.progressPauseTemplate:self.progressErrorTemplate):t);if(p>=100){stats=''}if(!$h.isEmpty(template)){if(pctLimit&&pct>pctLimit&&p<=100){out=template.setTokens({'percent':pctLimit,'status':self.msgUploadThreshold})}else{out=template.setTokens({'percent':pct,'status':(p>100?self.msgUploadEnd:pct+'%')})}stats=stats||'';out=out.setTokens({stats:stats});$h.setHtml($el,out);if(error){$h.setHtml($el.find('[role="progressbar"]'),error)}}},_hasFiles:function(){var el=this.$element[0];return!!(el&&el.files&&el.files.length)},_setFileDropZoneTitle:function(){var self=this,$zone=self.$container.find('.file-drop-zone'),title=self.dropZoneTitle,strFiles;if(self.isClickable){strFiles=$h.isEmpty(self.$element.attr('multiple'))?self.fileSingle:self.filePlural;title+=self.dropZoneClickTitle.replace('{files}',strFiles)}$zone.find('.'+self.dropZoneTitleClass).remove();if(!self.showPreview||$zone.length===0||self.fileManager.count()>0||!self.dropZoneEnabled||self.previewCache.count()>0||(!self.isAjaxUpload&&self._hasFiles())){return}if($zone.find($h.FRAMES).length===0&&$h.isEmpty(self.defaultPreviewContent)){$zone.prepend('
    '+title+'
    ')}self.$container.removeClass('file-input-new');$h.addCss(self.$container,'file-input-ajax-new')},_getStats:function(stats){var self=this,pendingTime,t;if(!self.showUploadStats||!stats||!stats.bitrate){return''}t=self._getLayoutTemplate('stats');pendingTime=(!stats.elapsed||!stats.bps)?self.msgCalculatingTime:self.msgPendingTime.setTokens({time:$h.getElapsed(Math.ceil(stats.pendingBytes/stats.bps))});return t.setTokens({uploadSpeed:stats.bitrate,pendingTime:pendingTime})},_setResumableProgress:function(pct,stats,$thumb){var self=this,rm=self.resumableManager,obj=$thumb?rm:self,$prog=$thumb?$thumb.find('.file-thumb-progress'):null;if(obj.lastProgress===0){obj.lastProgress=pct}if(pct0&&self._getFileCount(len-1)=limit:dim<=limit){return true}msg=self['msgImage'+type+size]||'Image "{name}" has a size validation error (limit "{size}").';self._showFileError(msg.setTokens({'name':filename,'size':limit,'dimension':dim}),params);self._setPreviewError($thumb);self.fileManager.remove($thumb);self._clearFileInput();return false},_getExifObj:function(data){var self=this,exifObj,error=$h.logMessages.exifWarning;if(data.slice(0,23)!=='data:image/jpeg;base64,'&&data.slice(0,22)!=='data:image/jpg;base64,'){exifObj=null;return}try{exifObj=window.piexif?window.piexif.load(data):null}catch(err){exifObj=null;error=err&&err.message||''}if(!exifObj&&self.showExifErrorLog){self._log($h.logMessages.badExifParser,{details:error})}return exifObj},setImageOrientation:function($img,$zoomImg,value,$thumb){var self=this,invalidImg=!$img||!$img.length,invalidZoomImg=!$zoomImg||!$zoomImg.length,$mark,isHidden=false,$div,zoomOnly=invalidImg&&$thumb&&$thumb.attr('data-template')==='image',ev;if(invalidImg&&invalidZoomImg){return}ev='load.fileinputimageorient';if(zoomOnly){$img=$zoomImg;$zoomImg=null;$img.css(self.previewSettings.image);$div=$(document.createElement('div')).appendTo($thumb.find('.kv-file-content'));$mark=$(document.createElement('span')).insertBefore($img);$img.css('visibility','hidden').removeClass('file-zoom-detail').appendTo($div)}else{isHidden=!$img.is(':visible')}$img.off(ev).on(ev,function(){if(isHidden){self.$preview.removeClass('hide-content');$thumb.find('.kv-file-content').css('visibility','hidden')}var img=$img[0],zoomImg=$zoomImg&&$zoomImg.length?$zoomImg[0]:null,h=img.offsetHeight,w=img.offsetWidth,r=$h.getRotation(value);if(isHidden){$thumb.find('.kv-file-content').css('visibility','visible');self.$preview.addClass('hide-content')}$img.data('orientation',value);if(zoomImg){$zoomImg.data('orientation',value)}if(value<5){$h.setTransform(img,r);$h.setTransform(zoomImg,r);return}var offsetAngle=Math.atan(w/h),origFactor=Math.sqrt(Math.pow(h,2)+Math.pow(w,2)),scale=!origFactor?1:(h/Math.cos(Math.PI/2+offsetAngle))/origFactor,s=' scale('+Math.abs(scale)+')';$h.setTransform(img,r+s);$h.setTransform(zoomImg,r+s);if(zoomOnly){$img.css('visibility','visible').insertAfter($mark).addClass('file-zoom-detail');$mark.remove();$div.remove()}})},_validateImageOrientation:function($img,file,previewId,fileId,caption,ftype,fsize,iData){var self=this,exifObj=null,value,autoOrientImage=self.autoOrientImage,selector;exifObj=self._getExifObj(iData);if(self.canOrientImage){$img.css('image-orientation',(autoOrientImage?'from-image':'none'));self._validateImage(previewId,fileId,caption,ftype,fsize,iData,exifObj);return}selector=$h.getZoomSelector(previewId,' img');value=exifObj?exifObj['0th'][piexif.ImageIFD.Orientation]:null;if(!value){self._validateImage(previewId,fileId,caption,ftype,fsize,iData,exifObj);return}self.setImageOrientation($img,$(selector),value,self._getFrame(previewId));self._raise('fileimageoriented',{'$img':$img,'file':file});self._validateImage(previewId,fileId,caption,ftype,fsize,iData,exifObj)},_validateImage:function(previewId,fileId,fname,ftype,fsize,iData,exifObj){var self=this,$preview=self.$preview,params,w1,w2,$thumb=self._getFrame(previewId),i=$thumb.attr('data-fileindex'),$img=$thumb.find('img');fname=fname||'Untitled';$img.one('load',function(){if($img.data('validated')){return}$img.data('validated',true);w1=$thumb.width();w2=$preview.width();if(w1>w2){$img.css('width','100%')}params={ind:i,id:previewId,fileId:fileId};setTimeout(function(){var isValidWidth,isValidHeight;isValidWidth=self._isValidSize('Small','Width',$img,$thumb,fname,params);isValidHeight=self._isValidSize('Small','Height',$img,$thumb,fname,params);if(!self.resizeImage){isValidWidth=isValidWidth&&self._isValidSize('Large','Width',$img,$thumb,fname,params);isValidHeight=isValidHeight&&self._isValidSize('Large','Height',$img,$thumb,fname,params)}self._raise('fileimageloaded',[previewId]);$thumb.data('exif',exifObj);if(isValidWidth&&isValidHeight){self.fileManager.addImage(fileId,{ind:i,img:$img,thumb:$thumb,pid:previewId,typ:ftype,siz:fsize,validated:false,imgData:iData,exifObj:exifObj});self._validateAllImages()}},self.processDelay)}).one('error',function(){self._raise('fileimageloaderror',[previewId])})},_validateAllImages:function(){var self=this,counter={val:0},numImgs=self.fileManager.getImageCount(),fsize,minSize=self.resizeIfSizeMoreThan;if(numImgs!==self.fileManager.totalImages){return}self._raise('fileimagesloaded');if(!self.resizeImage){return}$.each(self.fileManager.loadedImages,function(id,config){if(!config.validated){fsize=config.siz;if(fsize&&fsize>minSize*self.bytesToKB){self._getResizedImage(id,config,counter,numImgs)}config.validated=true}})},_getResizedImage:function(id,config,counter,numImgs){var self=this,img=$(config.img)[0],width=img.naturalWidth,height=img.naturalHeight,blob,ratio=1,maxWidth=self.maxImageWidth||width,maxHeight=self.maxImageHeight||height,isValidImage=!!(width&&height),chkWidth,chkHeight,canvas=self.imageCanvas,dataURI,context=self.imageCanvasContext,type=config.typ,pid=config.pid,ind=config.ind,$thumb=config.thumb,throwError,msg,exifObj=config.exifObj,exifStr,file,params,evParams;throwError=function(msg,params,ev){if(self.isAjaxUpload){self._showFileError(msg,params,ev);}else{self._showError(msg,params,ev)}self._setPreviewError($thumb)};file=self.fileManager.getFile(id);params={id:pid,'index':ind,fileId:id};evParams=[id,pid,ind];if(!file||!isValidImage||(width<=maxWidth&&height<=maxHeight)){if(isValidImage&&file){self._raise('fileimageresized',evParams)}counter.val++;if(counter.val===numImgs){self._raise('fileimagesresized')}if(!isValidImage){throwError(self.msgImageResizeError,params,'fileimageresizeerror');return}}type=type||self.resizeDefaultImageType;chkWidth=width>maxWidth;chkHeight=height>maxHeight;if(self.resizePreference==='width'){ratio=chkWidth?maxWidth/width:(chkHeight?maxHeight/height:1)}else{ratio=chkHeight?maxHeight/height:(chkWidth?maxWidth/width:1)}self._resetCanvas();width*=ratio;height*=ratio;canvas.width=width;canvas.height=height;try{context.drawImage(img,0,0,width,height);dataURI=canvas.toDataURL(type,self.resizeQuality);if(exifObj){exifStr=window.piexif.dump(exifObj);dataURI=window.piexif.insert(exifStr,dataURI)}blob=$h.dataURI2Blob(dataURI);self.fileManager.setFile(id,blob);self._raise('fileimageresized',evParams);counter.val++;if(counter.val===numImgs){self._raise('fileimagesresized',[undefined,undefined])}if(!(blob instanceof Blob)){throwError(self.msgImageResizeError,params,'fileimageresizeerror');}}catch(err){counter.val++;if(counter.val===numImgs){self._raise('fileimagesresized',[undefined,undefined])}msg=self.msgImageResizeException.replace('{errors}',err.message);throwError(msg,params,'fileimageresizeexception');}},_showProgress:function(){var self=this;if(self.$progress&&self.$progress.length){self.$progress.show()}},_hideProgress:function(){var self=this;if(self.$progress&&self.$progress.length){self.$progress.hide()}},_initBrowse:function($container){var self=this,$el=self.$element;if(self.showBrowse){self.$btnFile=$container.find('.btn-file').append($el)}else{$el.appendTo($container).attr('tabindex',-1);$h.addCss($el,'file-no-browse')}},_initClickable:function(){var self=this,$zone,$tmpZone;if(!self.isClickable){return}$zone=self.$dropZone;if(!self.isAjaxUpload){$tmpZone=self.$preview.find('.file-default-preview');if($tmpZone.length){$zone=$tmpZone}}$h.addCss($zone,'clickable');$zone.attr('tabindex',-1);self._handler($zone,'click',function(e){var $tar=$(e.target);if(!self.$errorContainer.is(':visible')&&(!$tar.parents('.file-preview-thumbnails').length||$tar.parents('.file-default-preview').length)){self.$element.data('zoneClicked',true).trigger('click');$zone.blur()}})},_initCaption:function(){var self=this,cap=self.initialCaption||'';if(self.overwriteInitial||$h.isEmpty(cap)){self.$caption.val('');return false}self._setCaption(cap);return true},_setCaption:function(content,isError){var self=this,title,out,icon,n,cap,file;if(!self.$caption.length){return}self.$captionContainer.removeClass('icon-visible');if(isError){title=$('
    '+self.msgValidationError+'
    ').text();n=self.fileManager.count();if(n){file=self.fileManager.getFirstFile();cap=n===1&&file?file.nameFmt:self._getMsgSelected(n)}else{cap=self._getMsgSelected(self.msgNo)}out=$h.isEmpty(content)?cap:content;icon=''+self.msgValidationErrorIcon+''}else{if($h.isEmpty(content)){self.$caption.attr('title','');return}title=$('
    '+content+'
    ').text();out=title;icon=self._getLayoutTemplate('fileIcon')}self.$captionContainer.addClass('icon-visible');self.$caption.attr('title',title).val(out);$h.setHtml(self.$captionIcon,icon)},_createContainer:function(){var self=this,attribs={'class':'file-input file-input-new'+(self.rtl?' kv-rtl':'')},$container=$h.createElement($h.cspBuffer.stash(self._renderMain()));$h.cspBuffer.apply($container);$container.insertBefore(self.$element).attr(attribs);self._initBrowse($container);if(self.theme){$container.addClass('theme-'+self.theme)}return $container},_refreshContainer:function(){var self=this,$container=self.$container,$el=self.$element;$el.insertAfter($container);$h.setHtml($container,self._renderMain());self._initBrowse($container);self._validateDisabled()},_validateDisabled:function(){var self=this;self.$caption.attr({readonly:self.isDisabled})},_setTabIndex:function(type,html){var self=this,index=self.tabIndexConfig[type];return html.setTokens({tabIndexConfig:index===undefined||index===null?'':'tabindex="'+index+'"'})},_renderMain:function(){var self=this,dropCss=self.dropZoneEnabled?' file-drop-zone':'file-drop-disabled',close=!self.showClose?'':self._getLayoutTemplate('close'),preview=!self.showPreview?'':self._getLayoutTemplate('preview').setTokens({'class':self.previewClass,'dropClass':dropCss}),css=self.isDisabled?self.captionClass+' file-caption-disabled':self.captionClass,caption=self.captionTemplate.setTokens({'class':css+' kv-fileinput-caption'});caption=self._setTabIndex('caption',caption);return self.mainTemplate.setTokens({'class':self.mainClass+(!self.showBrowse&&self.showCaption?' no-browse':''),'inputGroupClass':self.inputGroupClass,'preview':preview,'close':close,'caption':caption,'upload':self._renderButton('upload'),'remove':self._renderButton('remove'),'cancel':self._renderButton('cancel'),'pause':self._renderButton('pause'),'browse':self._renderButton('browse')})},_renderButton:function(type){var self=this,tmplt=self._getLayoutTemplate('btnDefault'),css=self[type+'Class'],title=self[type+'Title'],icon=self[type+'Icon'],label=self[type+'Label'],status=self.isDisabled?' disabled':'',btnType='button';switch(type){case'remove':if(!self.showRemove){return''}break;case'cancel':if(!self.showCancel){return''}css+=' kv-hidden';break;case'pause':if(!self.showPause){return''}css+=' kv-hidden';break;case'upload':if(!self.showUpload){return''}if(self.isAjaxUpload&&!self.isDisabled){tmplt=self._getLayoutTemplate('btnLink').replace('{href}',self.uploadUrl)}else{btnType='submit'}break;case'browse':if(!self.showBrowse){return''}tmplt=self._getLayoutTemplate('btnBrowse');break;default:return''}tmplt=self._setTabIndex(type,tmplt);css+=type==='browse'?' btn-file':' fileinput-'+type+' fileinput-'+type+'-button';if(!$h.isEmpty(label)){label=' '+label+''}return tmplt.setTokens({'type':btnType,'css':css,'title':title,'status':status,'icon':icon,'label':label})},_renderThumbProgress:function(){var self=this;return'
    '+self.progressInfoTemplate.setTokens({percent:101,status:self.msgUploadBegin,stats:''})+'
    '},_renderFileFooter:function(cat,caption,size,width,isError){var self=this,config=self.fileActionSettings,rem=config.showRemove,drg=config.showDrag,upl=config.showUpload,rot=config.showRotate,zoom=config.showZoom,out,params,template=self._getLayoutTemplate('footer'),tInd=self._getLayoutTemplate('indicator'),ind=isError?config.indicatorError:config.indicatorNew,title=isError?config.indicatorErrorTitle:config.indicatorNewTitle,indicator=tInd.setTokens({'indicator':ind,'indicatorTitle':title});size=self._getSize(size);params={type:cat,caption:caption,size:size,width:width,progress:'',indicator:indicator};if(self.isAjaxUpload){params.progress=self._renderThumbProgress();params.actions=self._renderFileActions(params,upl,false,rem,rot,zoom,drg,false,false,false)}else{params.actions=self._renderFileActions(params,false,false,false,false,zoom,drg,false,false,false)}out=template.setTokens(params);out=$h.replaceTags(out,self.previewThumbTags);return out},_renderFileActions:function(cfg,showUpl,showDwn,showDel,showRot,showZoom,showDrag,disabled,url,key,isInit,dUrl,dFile){var self=this;if(!cfg.type&&isInit){cfg.type='image'}if(self.enableResumableUpload){showUpl=false}else{if(typeof showUpl==='function'){showUpl=showUpl(cfg)}}if(typeof showDwn==='function'){showDwn=showDwn(cfg)}if(typeof showDel==='function'){showDel=showDel(cfg)}if(typeof showZoom==='function'){showZoom=showZoom(cfg)}if(typeof showDrag==='function'){showDrag=showDrag(cfg)}if(typeof showRot==='function'){showRot=showRot(cfg)}if(!showUpl&&!showDwn&&!showDel&&!showRot&&!showZoom&&!showDrag){return''}var vUrl=url===false?'':' data-url="'+url+'"',btnZoom='',btnDrag='',btnRotate='',css,vKey=key===false?'':' data-key="'+key+'"',btnDelete='',btnUpload='',btnDownload='',template=self._getLayoutTemplate('actions'),config=self.fileActionSettings,otherButtons=self.otherActionButtons.setTokens({'dataKey':vKey,'key':key}),removeClass=disabled?config.removeClass+' disabled':config.removeClass;if(showDel){btnDelete=self._getLayoutTemplate('actionDelete').setTokens({'removeClass':removeClass,'removeIcon':config.removeIcon,'removeTitle':config.removeTitle,'dataUrl':vUrl,'dataKey':vKey,'key':key})}if(showRot){btnRotate=self._getLayoutTemplate('actionRotate').setTokens({'rotateClass':config.rotateClass,'rotateIcon':config.rotateIcon,'rotateTitle':config.rotateTitle})}if(showUpl){btnUpload=self._getLayoutTemplate('actionUpload').setTokens({'uploadClass':config.uploadClass,'uploadIcon':config.uploadIcon,'uploadTitle':config.uploadTitle})}if(showDwn){btnDownload=self._getLayoutTemplate('actionDownload').setTokens({'downloadClass':config.downloadClass,'downloadIcon':config.downloadIcon,'downloadTitle':config.downloadTitle,'downloadUrl':dUrl||self.initialPreviewDownloadUrl});btnDownload=btnDownload.setTokens({'filename':dFile,'key':key})}if(showZoom){btnZoom=self._getLayoutTemplate('actionZoom').setTokens({'zoomClass':config.zoomClass,'zoomIcon':config.zoomIcon,'zoomTitle':config.zoomTitle})}if(showDrag&&isInit){css='drag-handle-init '+config.dragClass;btnDrag=self._getLayoutTemplate('actionDrag').setTokens({'dragClass':css,'dragTitle':config.dragTitle,'dragIcon':config.dragIcon})}return template.setTokens({'delete':btnDelete,'upload':btnUpload,'download':btnDownload,'rotate':btnRotate,'zoom':btnZoom,'drag':btnDrag,'other':otherButtons})},_browse:function(e){var self=this;if(e&&e.isDefaultPrevented()||!self._raise('filebrowse')){return}if(self.isError&&!self.isAjaxUpload){self.clear()}if(self.focusCaptionOnBrowse){self.$captionContainer.focus()}},_change:function(e){var self=this;$(document.body).off('focusin.fileinput focusout.fileinput');if(self.changeTriggered){self._toggleLoading('hide');return}self._toggleLoading('show');var $el=self.$element,isDragDrop=arguments.length>1,isAjaxUpload=self.isAjaxUpload,tfiles,files=isDragDrop?arguments[1]:$el[0].files,ctr=self.fileManager.count(),total,initCount,len,isSingleUpl=$h.isEmpty($el.attr('multiple')),maxCount=!isAjaxUpload&&isSingleUpl?1:self.maxFileCount,maxTotCount=self.maxTotalFileCount,inclAll=maxTotCount>0&&maxTotCount>maxCount,flagSingle=(isSingleUpl&&ctr>0),throwError=function(mesg,file,previewId,index){var p1=$.extend(true,{},self._getOutData(null,{},{},files),{id:previewId,index:index}),p2={id:previewId,index:index,file:file,files:files};self.isPersistentError=true;self._toggleLoading('hide');return isAjaxUpload?self._showFileError(mesg,p1):self._showError(mesg,p2)},maxCountCheck=function(n,m,all){var msg=all?self.msgTotalFilesTooMany:self.msgFilesTooMany;msg=msg.replace('{m}',m).replace('{n}',n);self.isError=throwError(msg,null,null,null);self.$captionContainer.removeClass('icon-visible');self._setCaption('',true);self.$container.removeClass('file-input-new file-input-ajax-new')};self.reader=null;self._resetUpload();self._hideFileIcon();if(self.dropZoneEnabled){self.$container.find('.file-drop-zone .'+self.dropZoneTitleClass).remove()}if(!isAjaxUpload){if(e.target&&e.target.files===undefined){files=e.target.value?[{name:e.target.value.replace(/^.+\\/,'')}]:[]}else{files=e.target.files||{}}}tfiles=files;if($h.isEmpty(tfiles)||tfiles.length===0){if(!isAjaxUpload){self.clear()}self._raise('fileselectnone');return}self._resetErrors();len=tfiles.length;initCount=isAjaxUpload?(self.fileManager.count()+len):len;total=self._getFileCount(initCount,inclAll?false:undefined);if(maxCount>0&&total>maxCount){if(!self.autoReplace||len>maxCount){maxCountCheck((self.autoReplace&&len>maxCount?len:total),maxCount);return}if(total>maxCount){self._resetPreviewThumbs(isAjaxUpload)}}else{if(inclAll){total=self._getFileCount(initCount,true);if(maxTotCount>0&&total>maxTotCount){if(!self.autoReplace||len>maxCount){maxCountCheck((self.autoReplace&&len>maxTotCount?len:total),maxTotCount,true);return}if(total>maxCount){self._resetPreviewThumbs(isAjaxUpload)}}}if(!isAjaxUpload||flagSingle){self._resetPreviewThumbs(false);if(flagSingle){self.clearFileStack()}}else{if(isAjaxUpload&&ctr===0&&(!self.previewCache.count(true)||self.overwriteInitial)){self._resetPreviewThumbs(true)}}}if(self.autoReplace){self._getThumbs().each(function(){var $thumb=$(this);if($thumb.hasClass('file-preview-success')||$thumb.hasClass('file-preview-error')){$thumb.remove()}})}self.readFiles(tfiles);self._toggleLoading('hide')},_abort:function(params){var self=this,data;if(self.ajaxAborted&&typeof self.ajaxAborted==='object'&&self.ajaxAborted.message!==undefined){data=$.extend(true,{},self._getOutData(null),params);data.abortData=self.ajaxAborted.data||{};data.abortMessage=self.ajaxAborted.message;self._setProgress(101,self.$progress,self.msgCancelled);self._showFileError(self.ajaxAborted.message,data,'filecustomerror');self.cancel();self.unlock();return true}return!!self.ajaxAborted},_resetFileStack:function(){var self=this,i=0;self._getThumbs().each(function(){var $thumb=$(this),ind=$thumb.attr('data-fileindex'),pid=$thumb.attr('id');if(ind==='-1'||ind===-1){return}if(!self._getThumbFile($thumb)){$thumb.attr({'data-fileindex':i});i++}else{$thumb.attr({'data-fileindex':'-1'})}self._getZoom(pid).attr({'data-fileindex':$thumb.attr('data-fileindex')})})},_isFileSelectionValid:function(cnt){var self=this;cnt=cnt||0;if(self.required&&!self.getFilesCount()){self.$errorContainer.html('');self._showFileError(self.msgFileRequired);return false}if(self.minFileCount>0&&self._getFileCount(cnt)maxSize);return!skipPreview&&(allowedTypes||allowedMimes||allowedExts)},addToStack:function(file,id){var self=this;self.stackIsUpdating=true;self.fileManager.add(file,id);self._refreshPreview();self.stackIsUpdating=false},clearFileStack:function(){var self=this;self.fileManager.clear();self._initResumableUpload();if(self.enableResumableUpload){if(self.showPause===null){self.showPause=true}if(self.showCancel===null){self.showCancel=false}}else{self.showPause=false;if(self.showCancel===null){self.showCancel=true}}return self.$element},getFileStack:function(){return this.fileManager.stack},getFileList:function(){return this.fileManager.list()},getFilesSize:function(){return this.fileManager.getTotalSize()},getFilesCount:function(includeInitial){var self=this,len=self.isAjaxUpload?self.fileManager.count():self._inputFileCount();if(includeInitial){len+=self.previewCache.count(true)}return self._getFileCount(len)},_initCapStatus:function(status){var self=this,$cap=self.$caption;$cap.removeClass('is-valid file-processing');if(!status){return}if(status==='processing'){$cap.addClass('file-processing')}else{$cap.addClass('is-valid')}},_toggleLoading:function(type){var self=this;self.$previewStatus.html(type==='hide'?'':self.msgProcessing);self.$container.removeClass('file-thumb-loading');self._initCapStatus(type==='hide'?'':'processing');if(type!=='hide'){if(self.dropZoneEnabled){self.$container.find('.file-drop-zone .'+self.dropZoneTitleClass).remove()}self.$container.addClass('file-thumb-loading')}},_initFileSelected:function(){var self=this,$el=self.$element,$body=$(document.body),ev='focusin.fileinput focusout.fileinput';if($body.length){$body.off(ev).on('focusout.fileinput',function(){self._toggleLoading('show')}).on('focusin.fileinput',function(){setTimeout(function(){if(!$el.val()){self._setFileDropZoneTitle()}$body.off(ev);self._toggleLoading('hide')},2500)})}else{self._toggleLoading('hide')}},readFiles:function(files){this.reader=new FileReader();var self=this,reader=self.reader,$container=self.$previewContainer,$status=self.$previewStatus,msgLoading=self.msgLoading,msgProgress=self.msgProgress,previewInitId=self.previewInitId,numFiles=files.length,settings=self.fileTypeSettings,readFile,fileTypes=self.allowedFileTypes,typLen=fileTypes?fileTypes.length:0,fileExt=self.allowedFileExtensions,strExt=$h.isEmpty(fileExt)?'':fileExt.join(', '),throwError=function(msg,file,previewId,index,fileId){var $thumb,p1=$.extend(true,{},self._getOutData(null,{},{},files),{id:previewId,index:index,fileId:fileId}),p2={id:previewId,index:index,fileId:fileId,file:file,files:files};self._previewDefault(file,true);$thumb=self._getFrame(previewId,true);self._toggleLoading('hide');if(self.isAjaxUpload){setTimeout(function(){readFile(index+1)},self.processDelay)}else{self.unlock();numFiles=0}if(self.removeFromPreviewOnError&&$thumb.length){$thumb.remove()}else{self._initFileActions();$thumb.find('.kv-file-upload').remove()}self.isPersistentError=true;self.isError=self.isAjaxUpload?self._showFileError(msg,p1):self._showError(msg,p2);self._updateFileDetails(numFiles)};self.fileManager.clearImages();$.each(files,function(key,file){var func=self.fileTypeSettings.image;if(func&&func(file.type)){self.fileManager.totalImages++}});readFile=function(i){var $error=self.$errorContainer,errors,fm=self.fileManager;if(i>=numFiles){self.unlock();if(self.duplicateErrors.length){errors='
  • '+self.duplicateErrors.join('
  • ')+'
  • ';if($error.find('ul').length===0){$h.setHtml($error,self.errorCloseButton+'
      '+errors+'
    ')}else{$error.find('ul').append(errors)}$error.fadeIn(self.fadeDelay);self._handler($error.find('.kv-error-close'),'click',function(){$error.fadeOut(self.fadeDelay)});self.duplicateErrors=[]}if(self.isAjaxUpload){self._raise('filebatchselected',[fm.stack]);if(fm.count()===0&&!self.isError){self.reset()}}else{self._raise('filebatchselected',[files])}$container.removeClass('file-thumb-loading');self._initCapStatus('valid');$status.html('');return}self.lock(true);var file=files[i],id,previewId,fileProcessed,fSize=(file&&file.size||0),sizeHuman=self._getSize(fSize,true),j,msg,fnImage=settings.image,chk,typ,typ1,typ2,caption,fileSize=fSize/self.bytesToKB,fileExtExpr='',previewData,fileCount=0,strTypes='',fileId,canLoad,fileReaderAborted=false,func,knownTypes=0,isImage,processFileLoaded,initFileData;initFileData=function(dataSource){dataSource=dataSource||file;id=fileId=self._getFileId(file);previewId=previewInitId+'-'+id;previewData=$h.createObjectURL(dataSource);caption=self._getFileName(file,'')};processFileLoaded=function(){var isImageResized=!!fm.loadedImages[id],msg=msgProgress.setTokens({'index':i+1,'files':numFiles,'percent':50,'name':caption});setTimeout(function(){$status.html(msg);self._updateFileDetails(numFiles);if(self.getFilesCount(true)>0&&self.getFrames(':visible')){self.$dropZone.find('.'+self.dropZoneTitleClass).remove()}readFile(i+1)},self.processDelay);if(self._raise('fileloaded',[file,previewId,id,i,reader])&&self.isAjaxUpload){if(!isImageResized){fm.add(file)}}else{if(isImageResized){fm.removeFile(id)}}};if(!file){return}initFileData();if(typLen>0){for(j=0;j0&&fileSize>self.maxFileSize){msg=self.msgSizeTooLarge.setTokens({'name':caption,'size':sizeHuman,'maxSize':self._getSize(self.maxFileSize*self.bytesToKB,true)});throwError(msg,file,previewId,i,fileId);return}if(self.minFileSize!==null&&fileSize<=$h.getNum(self.minFileSize)){msg=self.msgSizeTooSmall.setTokens({'name':caption,'size':sizeHuman,'minSize':self._getSize(self.minFileSize*self.bytesToKB,true)});throwError(msg,file,previewId,i,fileId);return}if(!$h.isEmpty(fileTypes)&&$h.isArray(fileTypes)){for(j=0;j0){for(i=0;i0){for(i=0;i0)?self.initialCaption:'';self.$caption.attr('title','').val(cap);$h.addCss(self.$container,'file-input-new');self._validateDefaultPreview()}if(self.$container.find($h.FRAMES).length===0){if(!self._initCaption()){self.$captionContainer.removeClass('icon-visible')}}self._hideFileIcon();if(self.focusCaptionOnClear){self.$captionContainer.focus()}self._setFileDropZoneTitle();self._raise('filecleared');return self.$element},reset:function(){var self=this;if(!self._raise('filereset')){return}self.lastProgress=0;self._resetPreview();self.$container.find('.fileinput-filename').text('');$h.addCss(self.$container,'file-input-new');if(self.getFrames().length){self.$container.removeClass('file-input-new')}self.clearFileStack();self._setFileDropZoneTitle();return self.$element},disable:function(){var self=this,$container=self.$container;self.isDisabled=true;self._raise('filedisabled');self.$element.attr('disabled','disabled');$container.addClass('is-locked');$h.addCss($container.find('.btn-file'),'disabled');$container.find('.kv-fileinput-caption').addClass('file-caption-disabled');$container.find('.fileinput-remove, .fileinput-upload, .file-preview-frame button').attr('disabled',true);self._initDragDrop();return self.$element},enable:function(){var self=this,$container=self.$container;self.isDisabled=false;self._raise('fileenabled');self.$element.removeAttr('disabled');$container.removeClass('is-locked');$container.find('.kv-fileinput-caption').removeClass('file-caption-disabled');$container.find('.fileinput-remove, .fileinput-upload, .file-preview-frame button').removeAttr('disabled');$container.find('.btn-file').removeClass('disabled');self._initDragDrop();return self.$element},upload:function(){var self=this,fm=self.fileManager,totLen=fm.count(),i,outData,tm=self.taskManager,hasExtraData=!$.isEmptyObject(self._getExtraData());fm.bpsLog=[];fm.bps=0;if(!self.isAjaxUpload||self.isDisabled||!self._isFileSelectionValid(totLen)){return}self.lastProgress=0;self._resetUpload();if(totLen===0&&!hasExtraData){self._showFileError(self.msgUploadEmpty);return}self.cancelling=false;self.uploadInitiated=true;self._showProgress();self.lock();if(totLen===0&&hasExtraData){self._setProgress(2);self._uploadExtraOnly();return}if(self.enableResumableUpload){return self.resume()}if(self.uploadAsync||self.enableResumableUpload){outData=self._getOutData(null);if(!self._checkBatchPreupload(outData)){return}self.fileBatchCompleted=false;self.uploadCache=[];$.each(self.getFileStack(),function(id){var previewId=self._getThumbId(id);self.uploadCache.push({id:previewId,content:null,config:null,tags:null,append:true})});self.$preview.find('.file-preview-initial').removeClass($h.SORT_CSS);self._initSortable()}self._setProgress(2);self.hasInitData=false;if(self.uploadAsync){i=0;var pool=self.ajaxPool=tm.addPool($h.uniqId());$.each(self.getFileStack(),function(id){pool.addTask(id+i,function(deferrer){self._uploadSingle(i,id,true,deferrer)});i++});pool.run(self.maxAjaxThreads).done(function(){self._log('Async upload batch completed successfully.');self._raise('filebatchuploadsuccess',[fm.stack,self._getExtraData()])}).fail(function(){self._log('Async upload batch completed with errors.');self._raise('filebatchuploaderror',[fm.stack,self._getExtraData()])});return}self._uploadBatch();return self.$element},destroy:function(){var self=this,$form=self.$form,$cont=self.$container,$el=self.$element,ns=self.namespace;$(document).off(ns);$(window).off(ns);if($form&&$form.length){$form.off(ns)}if(self.isAjaxUpload){self._clearFileInput()}self._cleanup();self._initPreviewCache();$el.insertBefore($cont).off(ns).removeData();$cont.off().remove();return $el},refresh:function(options){var self=this,$el=self.$element;if(typeof options!=='object'||$h.isEmpty(options)){options=self.options}else{options=$.extend(true,{},self.options,options)}self._init(options,true);self._listen();return $el},zoom:function(frameId){var self=this,$frame=self._getFrame(frameId);self._showModal($frame)},getExif:function(frameId){var self=this,$frame=self._getFrame(frameId);return $frame&&$frame.data('exif')||null},getFrames:function(cssFilter){var self=this,$frames;cssFilter=cssFilter||'';$frames=self.$preview.find($h.FRAMES+cssFilter);if(self.reversePreviewOrder){$frames=$($frames.get().reverse())}return $frames},getPreview:function(){var self=this;return{content:self.initialPreview,config:self.initialPreviewConfig,tags:self.initialPreviewThumbTags}}};$.fn.fileinput=function(option){if(!$h.hasFileAPISupport()&&!$h.isIE(9)){return}var args=Array.apply(null,arguments),retvals=[];args.shift();this.each(function(){var self=$(this),data=self.data('fileinput'),options=typeof option==='object'&&option,theme=options.theme||self.data('theme'),l={},t={},lang=options.language||self.data('language')||$.fn.fileinput.defaults.language||'en',opt;if(!data){if(theme){t=$.fn.fileinputThemes[theme]||{}}if(lang!=='en'&&!$h.isEmpty($.fn.fileinputLocales[lang])){l=$.fn.fileinputLocales[lang]||{}}opt=$.extend(true,{},$.fn.fileinput.defaults,t,$.fn.fileinputLocales.en,l,options,self.data());data=new FileInput(this,opt);self.data('fileinput',data)}if(typeof option==='string'){retvals.push(data[option].apply(data,args))}});switch(retvals.length){case 0:return this;case 1:return retvals[0];default:return retvals}};var IFRAME_ATTRIBS='class="kv-preview-data file-preview-pdf" src="{renderer}?file={data}" {style}',defBtnCss1='btn btn-sm btn-kv '+$h.defaultButtonCss(),defBtnCss2='btn '+$h.defaultButtonCss();$.fn.fileinput.defaults={language:'zh',bytesToKB:1024,showCaption:true,showBrowse:true,showPreview:true,showRemove:true,showUpload:true,showUploadStats:true,showCancel:null,showPause:null,showClose:true,showUploadedThumbs:true,showConsoleLogs:false,browseOnZoneClick:false,autoReplace:false,showDescriptionClose:true,autoOrientImage:function(){var ua=window.navigator.userAgent,webkit=!!ua.match(/WebKit/i),iOS=!!ua.match(/iP(od|ad|hone)/i),iOSSafari=iOS&&webkit&&!ua.match(/CriOS/i);return!iOSSafari},autoOrientImageInitial:true,showExifErrorLog:false,required:false,rtl:false,hideThumbnailContent:false,encodeUrl:true,focusCaptionOnBrowse:true,focusCaptionOnClear:true,generateFileId:null,previewClass:'',captionClass:'',frameClass:'krajee-default',mainClass:'',inputGroupClass:'',mainTemplate:null,fileSizeGetter:null,initialCaption:'',initialPreview:[],initialPreviewDelimiter:'*$$*',initialPreviewAsData:false,initialPreviewFileType:'image',initialPreviewConfig:[],initialPreviewThumbTags:[],previewThumbTags:{},initialPreviewShowDelete:true,initialPreviewDownloadUrl:'',removeFromPreviewOnError:false,deleteUrl:'',deleteExtraData:{},overwriteInitial:true,sanitizeZoomCache:function(content){var $container=$h.createElement(content);$container.find('input,textarea,select,datalist,form,.file-thumbnail-footer').remove();return $container.html()},previewZoomButtonIcons:{prev:'',next:'',toggleheader:'',fullscreen:'',borderless:'',close:''},previewZoomButtonClasses:{prev:'btn btn-default btn-outline-secondary btn-navigate',next:'btn btn-default btn-outline-secondary btn-navigate',rotate:defBtnCss1,toggleheader:defBtnCss1,fullscreen:defBtnCss1,borderless:defBtnCss1,close:defBtnCss1},previewTemplates:{},previewContentTemplates:{},preferIconicPreview:false,preferIconicZoomPreview:false,alwaysPreviewFileExtensions:[],rotatableFileExtensions:['jpg','jpeg','png','gif'],allowedFileTypes:null,allowedFileExtensions:null,allowedPreviewTypes:undefined,allowedPreviewMimeTypes:null,allowedPreviewExtensions:null,disabledPreviewTypes:undefined,disabledPreviewExtensions:['msi','exe','com','zip','rar','app','vb','scr'],disabledPreviewMimeTypes:null,defaultPreviewContent:null,customLayoutTags:{},customPreviewTags:{},previewFileIcon:'',previewFileIconClass:'file-other-icon',previewFileIconSettings:{},previewFileExtSettings:{},buttonLabelClass:'hidden-xs',browseIcon:' ',browseClass:'btn btn-primary',removeIcon:'',removeClass:defBtnCss2,cancelIcon:'',cancelClass:defBtnCss2,pauseIcon:'',pauseClass:defBtnCss2,uploadIcon:'',uploadClass:defBtnCss2,uploadUrl:null,uploadUrlThumb:null,uploadAsync:true,uploadParamNames:{chunkCount:'chunkCount',chunkIndex:'chunkIndex',chunkSize:'chunkSize',chunkSizeStart:'chunkSizeStart',chunksUploaded:'chunksUploaded',fileBlob:'fileBlob',fileId:'fileId',fileName:'fileName',fileRelativePath:'fileRelativePath',fileSize:'fileSize',retryCount:'retryCount'},maxAjaxThreads:5,fadeDelay:800,processDelay:100,bitrateUpdateDelay:500,queueDelay:10,progressDelay:0,enableResumableUpload:false,resumableUploadOptions:{fallback:null,testUrl:null,chunkSize:2048,maxThreads:4,maxRetries:3,showErrorLog:true,retainErrorHistory:false,skipErrorsAndProceed:false},uploadExtraData:{},zoomModalHeight:485,minImageWidth:null,minImageHeight:null,maxImageWidth:null,maxImageHeight:null,resizeImage:false,resizePreference:'width',resizeQuality:0.92,resizeDefaultImageType:'image/jpeg',resizeIfSizeMoreThan:0,minFileSize:-1,maxFileSize:0,maxFilePreviewSize:25600,minFileCount:0,maxFileCount:0,maxTotalFileCount:0,validateInitialCount:false,msgValidationErrorClass:'text-danger',msgValidationErrorIcon:' ',msgErrorClass:'file-error-message',progressThumbClass:'progress-bar progress-bar-striped active progress-bar-animated',progressClass:'progress-bar bg-success progress-bar-success progress-bar-striped active progress-bar-animated',progressInfoClass:'progress-bar bg-info progress-bar-info progress-bar-striped active progress-bar-animated',progressCompleteClass:'progress-bar bg-success progress-bar-success',progressPauseClass:'progress-bar bg-primary progress-bar-primary progress-bar-striped active progress-bar-animated',progressErrorClass:'progress-bar bg-danger progress-bar-danger',progressUploadThreshold:99,previewFileType:'image',elCaptionContainer:null,elCaptionText:null,elPreviewContainer:null,elPreviewImage:null,elPreviewStatus:null,elErrorContainer:null,errorCloseButton:undefined,slugCallback:null,dropZoneEnabled:true,dropZoneTitleClass:'file-drop-zone-title',fileActionSettings:{},otherActionButtons:'',textEncoding:'UTF-8',preProcessUpload:null,ajaxSettings:{},ajaxDeleteSettings:{},showAjaxErrorDetails:true,mergeAjaxCallbacks:false,mergeAjaxDeleteCallbacks:false,retryErrorUploads:true,reversePreviewOrder:false,usePdfRenderer:function(){var isIE11=!!window.MSInputMethodContext&&!!document.documentMode;return!!navigator.userAgent.match(/(iPod|iPhone|iPad|Android)/i)||isIE11},pdfRendererUrl:'',pdfRendererTemplate:'',tabIndexConfig:{browse:500,remove:500,upload:500,cancel:null,pause:null,modal:-1}};$.fn.fileinputLocales.en={sizeUnits:['B','KB','MB','GB','TB','PB','EB','ZB','YB'],bitRateUnits:['B/s','KB/s','MB/s','GB/s','TB/s','PB/s','EB/s','ZB/s','YB/s'],fileSingle:'file',filePlural:'files',browseLabel:'Browse …',removeLabel:'Remove',removeTitle:'Clear all unprocessed files',cancelLabel:'Cancel',cancelTitle:'Abort ongoing upload',pauseLabel:'Pause',pauseTitle:'Pause ongoing upload',uploadLabel:'Upload',uploadTitle:'Upload selected files',msgNo:'No',msgNoFilesSelected:'No files selected',msgCancelled:'Cancelled',msgPaused:'Paused',msgPlaceholder:'Select {files} ...',msgZoomModalHeading:'Detailed Preview',msgFileRequired:'You must select a file to upload.',msgSizeTooSmall:'File "{name}" ({size}) is too small and must be larger than {minSize}.',msgSizeTooLarge:'File "{name}" ({size}) exceeds maximum allowed upload size of {maxSize}.',msgFilesTooLess:'You must select at least {n} {files} to upload.',msgFilesTooMany:'Number of files selected for upload ({n}) exceeds maximum allowed limit of {m}.',msgTotalFilesTooMany:'You can upload a maximum of {m} files ({n} files detected).',msgFileNotFound:'File "{name}" not found!',msgFileSecured:'Security restrictions prevent reading the file "{name}".',msgFileNotReadable:'File "{name}" is not readable.',msgFilePreviewAborted:'File preview aborted for "{name}".',msgFilePreviewError:'An error occurred while reading the file "{name}".',msgInvalidFileName:'Invalid or unsupported characters in file name "{name}".',msgInvalidFileType:'Invalid type for file "{name}". Only "{types}" files are supported.',msgInvalidFileExtension:'Invalid extension for file "{name}". Only "{extensions}" files are supported.',msgFileTypes:{'image':'image','html':'HTML','text':'text','video':'video','audio':'audio','flash':'flash','pdf':'PDF','object':'object'},msgUploadAborted:'The file upload was aborted',msgUploadThreshold:'Processing …',msgUploadBegin:'Initializing …',msgUploadEnd:'Done',msgUploadResume:'Resuming upload …',msgUploadEmpty:'No valid data available for upload.',msgUploadError:'Upload Error',msgDeleteError:'Delete Error',msgProgressError:'Error',msgValidationError:'Validation Error',msgLoading:'Loading file {index} of {files} …',msgProgress:'Loading file {index} of {files} - {name} - {percent}% completed.',msgSelected:'{n} {files} selected',msgProcessing:'Processing ...',msgFoldersNotAllowed:'Drag & drop files only! {n} folder(s) dropped were skipped.',msgImageWidthSmall:'Width of image file "{name}" must be at least {size} px (detected {dimension} px).',msgImageHeightSmall:'Height of image file "{name}" must be at least {size} px (detected {dimension} px).',msgImageWidthLarge:'Width of image file "{name}" cannot exceed {size} px (detected {dimension} px).',msgImageHeightLarge:'Height of image file "{name}" cannot exceed {size} px (detected {dimension} px).',msgImageResizeError:'Could not get the image dimensions to resize.',msgImageResizeException:'Error while resizing the image.
    {errors}
    ',msgAjaxError:'Something went wrong with the {operation} operation. Please try again later!',msgAjaxProgressError:'{operation} failed',msgDuplicateFile:'File "{name}" of same size "{size}" has already been selected earlier. Skipping duplicate selection.',msgResumableUploadRetriesExceeded:'Upload aborted beyond {max} retries for file {file}! Error Details:
    {error}
    ',msgPendingTime:'{time} remaining',msgCalculatingTime:'calculating time remaining',ajaxOperations:{deleteThumb:'file delete',uploadThumb:'file upload',uploadBatch:'batch file upload',uploadExtra:'form data upload'},dropZoneTitle:'Drag & drop files here …',dropZoneClickTitle:'
    (or click to select {files})',previewZoomButtonTitles:{prev:'View previous file',next:'View next file',rotate:'Rotate 90 deg. clockwise',toggleheader:'Toggle header',fullscreen:'Toggle full screen',borderless:'Toggle borderless mode',close:'Close detailed preview'}};$.fn.fileinputLocales.zh={sizeUnits:['B','KB','MB','GB','TB','PB','EB','ZB','YB'],bitRateUnits:['B/s','KB/s','MB/s','GB/s','TB/s','PB/s','EB/s','ZB/s','YB/s'],fileSingle:'文件',filePlural:'个文件',browseLabel:'选择 …',removeLabel:'移除',removeTitle:'清除选中文件',cancelLabel:'取消',cancelTitle:'取消进行中的上传',pauseLabel:'暂停',pauseTitle:'暂停上传',uploadLabel:'上传',uploadTitle:'上传选中文件',msgNo:'没有',msgNoFilesSelected:'未选择文件',msgPaused:'已暂停',msgCancelled:'取消',msgPlaceholder:'选择 {files} ...',msgZoomModalHeading:'详细预览',msgFileRequired:'必须选择一个文件上传.',msgSizeTooSmall:'文件 "{name}" ({size}) 必须大于限定大小 {minSize}.',msgSizeTooLarge:'文件 "{name}" ({size}) 超过了允许大小 {maxSize}.',msgFilesTooLess:'你必须选择最少 {n} {files} 来上传. ',msgFilesTooMany:'选择的上传文件个数 ({n}) 超出最大文件的限制个数 {m}.',msgTotalFilesTooMany:'你最多可以上传 {m} 个文件 (当前有{n} 个文件).',msgFileNotFound:'文件 "{name}" 未找到!',msgFileSecured:'安全限制,为了防止读取文件 "{name}".',msgFileNotReadable:'文件 "{name}" 不可读.',msgFilePreviewAborted:'取消 "{name}" 的预览.',msgFilePreviewError:'读取 "{name}" 时出现了一个错误.',msgInvalidFileName:'文件名 "{name}" 包含非法字符.',msgInvalidFileType:'不正确的类型 "{name}". 只支持 "{types}" 类型的文件.',msgInvalidFileExtension:'不正确的文件扩展名 "{name}". 只支持 "{extensions}" 的文件扩展名.',msgFileTypes:{'image':'image','html':'HTML','text':'text','video':'video','audio':'audio','flash':'flash','pdf':'PDF','object':'object'},msgUploadAborted:'该文件上传被中止',msgUploadThreshold:'处理中 …',msgUploadBegin:'正在初始化 …',msgUploadEnd:'完成',msgUploadResume:'继续上传 …',msgUploadEmpty:'无效的文件上传.',msgUploadError:'上传出错',msgDeleteError:'删除出错',msgProgressError:'上传出错',msgValidationError:'验证错误',msgLoading:'加载第 {index} 文件 共 {files} …',msgProgress:'加载第 {index} 文件 共 {files} - {name} - {percent}% 完成.',msgSelected:'{n} {files} 选中',msgProcessing:'处理中 ...',msgFoldersNotAllowed:'只支持拖拽文件! 跳过 {n} 拖拽的文件夹.',msgImageWidthSmall:'图像文件的"{name}"的宽度必须是至少{size}像素.',msgImageHeightSmall:'图像文件的"{name}"的高度必须至少为{size}像素.',msgImageWidthLarge:'图像文件"{name}"的宽度不能超过{size}像素.',msgImageHeightLarge:'图像文件"{name}"的高度不能超过{size}像素.',msgImageResizeError:'无法获取的图像尺寸调整。',msgImageResizeException:'调整图像大小时发生错误。
    {errors}
    ',msgAjaxError:'{operation} 发生错误. 请重试!',msgAjaxProgressError:'{operation} 失败',msgDuplicateFile:'文件 "{name}",大小 "{size}" 已经被选中.忽略相同的文件.',msgResumableUploadRetriesExceeded:'文件 {file} 上传失败超过 {max} 次重试 ! 错误详情:
    {error}
    ',msgPendingTime:'{time} 剩余',msgCalculatingTime:'计算剩余时间',ajaxOperations:{deleteThumb:'删除文件',uploadThumb:'上传文件',uploadBatch:'批量上传',uploadExtra:'表单数据上传'},dropZoneTitle:'拖拽文件到这里 …
    支持多文件同时上传',dropZoneClickTitle:'
    (或点击{files}按钮选择文件)',fileActionSettings:{removeTitle:'删除文件',uploadTitle:'上传文件',downloadTitle:'下载文件',uploadRetryTitle:'重试',rotateTitle:'顺时针旋转90度',zoomTitle:'查看详情',dragTitle:'移动 / 重置',indicatorNewTitle:'没有上传',indicatorSuccessTitle:'上传',indicatorErrorTitle:'上传错误',indicatorPausedTitle:'上传已暂停',indicatorLoadingTitle:'上传 …'},previewZoomButtonTitles:{prev:'预览上一个文件',next:'预览下一个文件',rotate:'顺时针旋转90度',toggleheader:'缩放',fullscreen:'全屏',borderless:'无边界模式',close:'关闭当前预览'}};$.fn.fileinput.Constructor=FileInput;$(document).ready(function(){var $input=$('input.file[type=file]');if($input.length){$input.fileinput()}})})); \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/loading-sm.gif b/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/loading-sm.gif new file mode 100644 index 0000000000000000000000000000000000000000..44e3b7a0f702aa1d301468b1d6c1d74d45dfdfa4 GIT binary patch literal 2670 zcmb`Je@v5i9>LSr1(@@0hFth{;0&ijQR3npb})ArbBQ}PvxUrk9==i>-je&{`bYn0 zn)LJfykDR9({*{d^3^65#=>kEmYR~<(cUp}W8&ez9`;@Cv+TEYecW~T-rd+qu-ktq^cI?{N#Y-2bznz}@Wlou^?0COp;`YSDe?RQ|Tc54YcCq*3v~POu z=Q)qt<8AOhdGZ9Ce@UMNZ%`E#<|wqPl%!-T1wNGi6v$*^VoXMUfgA&)8H|&=avY{@ zJeN-^xjCUe&M)59EweXtoViM;a-1?pz|8ANLRhI=H=wp@r|+lJt@60`I1L>ikf9d z0~~r>SYAh`8ncKP#Y`em&991Ki+9$D<+Ah`h8ABLvp^+Tq&fmry95!A4*~{!r(W{r|twO<9E6hwX}% z)F~4zBCfrDD2L!G=moq=Ocm!kf(>!j#MWki`kE{G-BYoHbydNkr`YYfzkHJ}sdz&Z z8KyFYGObizP&mFNfL9$Pkl^BMW@H7Mj~&{~Y}&^@Em8az7i38?{{@hom-`@R{}EIl zuKr(+p!%I^SP+EozXRAQx&zv)Q;+jLkH^n1bF~-lxqi~zs6M>$2b`E?bDR1(oHROR zaB7GXUJ{zi)G5043^#qed;40J-ClCQog6(Hud5xphOP%&4#R>m7Bck%Y7iX-)s;GnT=AX8I6;x z*Ol?b-#BAm+c8m@;Zg6;uE=_2%jzUWr*x2ox8%paAn8qLY^bd(b;)ctlkU^fu=Vc3 zz;SN~k7AeP8RH2m;z z2CX1gkPsDhP~gugYVl&ptMm6Li~=Mu93%mmkyQ%qyHLgm8|ug3#{dSLkO}}pC4+2m z`Abd|+`DA@$4K%kR+rDRtj=pVseWU_SosF`XJ_(Mv(56PPTS5aAL87zteO7ON2zXW zXJVy1^(Mx0!G&0`_T@yc7N3_Iz0+VN*C^fuY4rdK8WyJhb56^W6%| zhv|@e@GJq}V9C%h00$|+q%bv<3=9s$WESlms9VW!FOl?MEbIgN3J@Vy2p1xL+O3%* z4%2yvY0~i3-n5pgHjU@SvC&L^4Rsa4mb2FHx-acsLminpiEGcb4^avWjB6bs?yxHv zFP>OoZm&@)|E$XG{cHFN;rjRP&k2G>fr0d>;EjE|0c(GfTT-1_oMa2pu0v=?C ziWR6(s{%i;LARh*1pqiF)TclTcn}}b?&(F5kHB?rd-LMljI3nO?V@)3zQ#dO-513t ztjcsEGqLW@n}vqVK-aY$rTULTyjJV(hyiVw^9O}%O%TO%-BuBoT7PVy_KKNUTkL36 z?~x1XsVjwnVnJz^Up>+9s|T|{G?2yf>OloQZy$sL`TErZnkDwpwn;{pv6^-mtgcw9 z_E@9A+pHJ039kOw(VR1!{Fzhc9qk-wO9+x3Qyrg@1UN60rpBE1jKy*kvzjY8GMB%rN zWSO@Pl7v*3SO+Q5wzobgsdpIf?Qr=duPeH-rgHWhu znosfxTsJ3QpXcCF1>~qGXs@ZJ zyw~IM73y!A^G>VZ8R)gNG&BU|KuYo)y+(NV^DdAFW5LeVJBBCMNfRtuR+y^ER&<(X zE7%B&e5#Z^`o7-@nEg_P~LI*N!rA^h{|;%@7gdIe0+9 zL6FPmK*NXRK=)=Qr!7yn1(?oIuvujqni9B8`-RfJ#pzQFR*0=zw6NbXTj0F1BLlZh zfVly?4JS*30beAWg%&SoyRsrS%M*Y;RQ^0c<0TpwHaFY zz6r{%O%_#7fe{VKyK@(naSJIeRCsx4aYAE48*ixFGRdMll6*@GUTc0%``>`o8j#V4 zg9*c-O~)LSJSTE&n6)s0!!ECt)zd(wL!wvC+k6wRhYtI}PTi7bt)^yIDG`AshjlL| qtmsHM!1PnVu#-b#!*qv!K4 select.bs-select-hidden, +select.selectpicker { + display: none !important; +} +.bootstrap-select { + width: 220px \0; + /*IE9 and below*/ + vertical-align: middle; +} +.bootstrap-select > .dropdown-toggle { + position: relative; + width: 100%; + text-align: right; + white-space: nowrap; + display: -webkit-inline-box; + display: -webkit-inline-flex; + display: -ms-inline-flexbox; + display: inline-flex; + -webkit-box-align: center; + -webkit-align-items: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; +} +.bootstrap-select > .dropdown-toggle:after { + margin-top: -1px; +} +.bootstrap-select > .dropdown-toggle.bs-placeholder, +.bootstrap-select > .dropdown-toggle.bs-placeholder:hover, +.bootstrap-select > .dropdown-toggle.bs-placeholder:focus, +.bootstrap-select > .dropdown-toggle.bs-placeholder:active { + color: #999; +} +.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-primary, +.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-secondary, +.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-success, +.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-danger, +.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-info, +.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-dark, +.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-primary:hover, +.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-secondary:hover, +.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-success:hover, +.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-danger:hover, +.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-info:hover, +.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-dark:hover, +.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-primary:focus, +.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-secondary:focus, +.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-success:focus, +.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-danger:focus, +.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-info:focus, +.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-dark:focus, +.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-primary:active, +.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-secondary:active, +.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-success:active, +.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-danger:active, +.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-info:active, +.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-dark:active { + color: rgba(255, 255, 255, 0.5); +} +.bootstrap-select > select { + position: absolute !important; + bottom: 0; + left: 50%; + display: block !important; + width: 0.5px !important; + height: 100% !important; + padding: 0 !important; + opacity: 0 !important; + border: none; + z-index: 0 !important; +} +.bootstrap-select > select.mobile-device { + top: 0; + left: 0; + display: block !important; + width: 100% !important; + z-index: 2 !important; +} +.has-error .bootstrap-select .dropdown-toggle, +.error .bootstrap-select .dropdown-toggle, +.bootstrap-select.is-invalid .dropdown-toggle, +.was-validated .bootstrap-select select:invalid + .dropdown-toggle { + border-color: #b94a48; +} +.bootstrap-select.is-valid .dropdown-toggle, +.was-validated .bootstrap-select select:valid + .dropdown-toggle { + border-color: #28a745; +} +.bootstrap-select.fit-width { + width: auto !important; +} +.bootstrap-select:not([class*="col-"]):not([class*="form-control"]):not(.input-group-btn) { + width: 220px; +} +.bootstrap-select > select.mobile-device:focus + .dropdown-toggle, +.bootstrap-select .dropdown-toggle:focus { + outline: thin dotted #333333 !important; + outline: 5px auto -webkit-focus-ring-color !important; + outline-offset: -2px; +} +.bootstrap-select.form-control { + margin-bottom: 0; + padding: 0; + border: none; + height: auto; +} +:not(.input-group) > .bootstrap-select.form-control:not([class*="col-"]) { + width: 100%; +} +.bootstrap-select.form-control.input-group-btn { + float: none; + z-index: auto; +} +.form-inline .bootstrap-select, +.form-inline .bootstrap-select.form-control:not([class*="col-"]) { + width: auto; +} +.bootstrap-select:not(.input-group-btn), +.bootstrap-select[class*="col-"] { + float: none; + display: inline-block; + margin-left: 0; +} +.bootstrap-select.dropdown-menu-right, +.bootstrap-select[class*="col-"].dropdown-menu-right, +.row .bootstrap-select[class*="col-"].dropdown-menu-right { + float: right; +} +.form-inline .bootstrap-select, +.form-horizontal .bootstrap-select, +.form-group .bootstrap-select { + margin-bottom: 0; +} +.form-group-lg .bootstrap-select.form-control, +.form-group-sm .bootstrap-select.form-control { + padding: 0; +} +.form-group-lg .bootstrap-select.form-control .dropdown-toggle, +.form-group-sm .bootstrap-select.form-control .dropdown-toggle { + height: 100%; + font-size: inherit; + line-height: inherit; + border-radius: inherit; +} +.bootstrap-select.form-control-sm .dropdown-toggle, +.bootstrap-select.form-control-lg .dropdown-toggle { + font-size: inherit; + line-height: inherit; + border-radius: inherit; +} +.bootstrap-select.form-control-sm .dropdown-toggle { + padding: 0.25rem 0.5rem; +} +.bootstrap-select.form-control-lg .dropdown-toggle { + padding: 0.5rem 1rem; +} +.form-inline .bootstrap-select .form-control { + width: 100%; +} +.bootstrap-select.disabled, +.bootstrap-select > .disabled { + cursor: not-allowed; +} +.bootstrap-select.disabled:focus, +.bootstrap-select > .disabled:focus { + outline: none !important; +} +.bootstrap-select.bs-container { + position: absolute; + top: 0; + left: 0; + height: 0 !important; + padding: 0 !important; +} +.bootstrap-select.bs-container .dropdown-menu { + z-index: 1060; +} +.bootstrap-select .dropdown-toggle .filter-option { + position: static; + top: 0; + left: 0; + float: left; + height: 100%; + width: 100%; + text-align: left; + overflow: hidden; + -webkit-box-flex: 0; + -webkit-flex: 0 1 auto; + -ms-flex: 0 1 auto; + flex: 0 1 auto; +} +.bs3.bootstrap-select .dropdown-toggle .filter-option { + padding-right: inherit; +} +.input-group .bs3-has-addon.bootstrap-select .dropdown-toggle .filter-option { + position: absolute; + padding-top: inherit; + padding-bottom: inherit; + padding-left: inherit; + float: none; +} +.input-group .bs3-has-addon.bootstrap-select .dropdown-toggle .filter-option .filter-option-inner { + padding-right: inherit; +} +.bootstrap-select .dropdown-toggle .filter-option-inner-inner { + overflow: hidden; +} +.bootstrap-select .dropdown-toggle .filter-expand { + width: 0 !important; + float: left; + opacity: 0 !important; + overflow: hidden; +} +.bootstrap-select .dropdown-toggle .caret { + position: absolute; + top: 50%; + right: 12px; + margin-top: -2px; + vertical-align: middle; +} +.input-group .bootstrap-select.form-control .dropdown-toggle { + border-radius: inherit; +} +.bootstrap-select[class*="col-"] .dropdown-toggle { + width: 100%; +} +.bootstrap-select .dropdown-menu { + min-width: 100%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +.bootstrap-select .dropdown-menu > .inner:focus { + outline: none !important; +} +.bootstrap-select .dropdown-menu.inner { + position: static; + float: none; + border: 0; + padding: 0; + margin: 0; + border-radius: 0; + -webkit-box-shadow: none; + box-shadow: none; +} +.bootstrap-select .dropdown-menu li { + position: relative; +} +.bootstrap-select .dropdown-menu li.active small { + color: rgba(255, 255, 255, 0.5) !important; +} +.bootstrap-select .dropdown-menu li.disabled a { + cursor: not-allowed; +} +.bootstrap-select .dropdown-menu li a { + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.bootstrap-select .dropdown-menu li a.opt { + position: relative; + padding-left: 2.25em; +} +.bootstrap-select .dropdown-menu li a span.check-mark { + display: none; +} +.bootstrap-select .dropdown-menu li a span.text { + display: inline-block; +} +.bootstrap-select .dropdown-menu li small { + padding-left: 0.5em; +} +.bootstrap-select .dropdown-menu .notify { + position: absolute; + bottom: 5px; + width: 96%; + margin: 0 2%; + min-height: 26px; + padding: 3px 5px; + background: #f5f5f5; + border: 1px solid #e3e3e3; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); + pointer-events: none; + opacity: 0.9; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +.bootstrap-select .dropdown-menu .notify.fadeOut { + -webkit-animation: 300ms linear 750ms forwards bs-notify-fadeOut; + -o-animation: 300ms linear 750ms forwards bs-notify-fadeOut; + animation: 300ms linear 750ms forwards bs-notify-fadeOut; +} +.bootstrap-select .no-results { + padding: 3px; + background: #f5f5f5; + margin: 0 5px; + white-space: nowrap; +} +.bootstrap-select.fit-width .dropdown-toggle .filter-option { + position: static; + display: inline; + padding: 0; +} +.bootstrap-select.fit-width .dropdown-toggle .filter-option-inner, +.bootstrap-select.fit-width .dropdown-toggle .filter-option-inner-inner { + display: inline; +} +.bootstrap-select.fit-width .dropdown-toggle .bs-caret:before { + content: '\00a0'; +} +.bootstrap-select.fit-width .dropdown-toggle .caret { + position: static; + top: auto; + margin-top: -1px; +} +.bootstrap-select.show-tick .dropdown-menu .selected span.check-mark { + position: absolute; + display: inline-block; + right: 15px; + top: 5px; +} +.bootstrap-select.show-tick .dropdown-menu li a span.text { + margin-right: 34px; +} +.bootstrap-select .bs-ok-default:after { + content: ''; + display: block; + width: 0.5em; + height: 1em; + border-style: solid; + border-width: 0 0.26em 0.26em 0; + -webkit-transform-style: preserve-3d; + transform-style: preserve-3d; + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); + -o-transform: rotate(45deg); + transform: rotate(45deg); +} +.bootstrap-select.show-menu-arrow.open > .dropdown-toggle, +.bootstrap-select.show-menu-arrow.show > .dropdown-toggle { + z-index: 1061; +} +.bootstrap-select.show-menu-arrow .dropdown-toggle .filter-option:before { + content: ''; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid rgba(204, 204, 204, 0.2); + position: absolute; + bottom: -4px; + left: 9px; + display: none; +} +.bootstrap-select.show-menu-arrow .dropdown-toggle .filter-option:after { + content: ''; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid white; + position: absolute; + bottom: -4px; + left: 10px; + display: none; +} +.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle .filter-option:before { + bottom: auto; + top: -4px; + border-top: 7px solid rgba(204, 204, 204, 0.2); + border-bottom: 0; +} +.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle .filter-option:after { + bottom: auto; + top: -4px; + border-top: 6px solid white; + border-bottom: 0; +} +.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle .filter-option:before { + right: 12px; + left: auto; +} +.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle .filter-option:after { + right: 13px; + left: auto; +} +.bootstrap-select.show-menu-arrow.open > .dropdown-toggle .filter-option:before, +.bootstrap-select.show-menu-arrow.show > .dropdown-toggle .filter-option:before, +.bootstrap-select.show-menu-arrow.open > .dropdown-toggle .filter-option:after, +.bootstrap-select.show-menu-arrow.show > .dropdown-toggle .filter-option:after { + display: block; +} +.bs-searchbox, +.bs-actionsbox, +.bs-donebutton { + padding: 4px 8px; +} +.bs-actionsbox { + width: 100%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +.bs-actionsbox .btn-group button { + width: 50%; +} +.bs-donebutton { + float: left; + width: 100%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +.bs-donebutton .btn-group button { + width: 100%; +} +.bs-searchbox + .bs-actionsbox { + padding: 0 8px 4px; +} +.bs-searchbox .form-control { + margin-bottom: 0; + width: 100%; + float: none; +} diff --git a/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.js b/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.js new file mode 100644 index 0000000..d25d751 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.js @@ -0,0 +1,3247 @@ +/*! + * Bootstrap-select v1.13.18 (https://developer.snapappointments.com/bootstrap-select) + * + * Copyright 2012-2020 SnapAppointments, LLC + * Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE) + */ + +(function (root, factory) { + if (root === undefined && window !== undefined) root = window; + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module unless amdModuleId is set + define(["jquery"], function (a0) { + return (factory(a0)); + }); + } else if (typeof module === 'object' && module.exports) { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like environments that support module.exports, + // like Node. + module.exports = factory(require("jquery")); + } else { + factory(root["jQuery"]); + } +}(this, function (jQuery) { + +(function ($) { + 'use strict'; + + var DISALLOWED_ATTRIBUTES = ['sanitize', 'whiteList', 'sanitizeFn']; + + var uriAttrs = [ + 'background', + 'cite', + 'href', + 'itemtype', + 'longdesc', + 'poster', + 'src', + 'xlink:href' + ]; + + var ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i; + + var DefaultWhitelist = { + // Global attributes allowed on any supplied element below. + '*': ['class', 'dir', 'id', 'lang', 'role', 'tabindex', 'style', ARIA_ATTRIBUTE_PATTERN], + a: ['target', 'href', 'title', 'rel'], + area: [], + b: [], + br: [], + col: [], + code: [], + div: [], + em: [], + hr: [], + h1: [], + h2: [], + h3: [], + h4: [], + h5: [], + h6: [], + i: [], + img: ['src', 'alt', 'title', 'width', 'height'], + li: [], + ol: [], + p: [], + pre: [], + s: [], + small: [], + span: [], + sub: [], + sup: [], + strong: [], + u: [], + ul: [] + } + + /** + * A pattern that recognizes a commonly useful subset of URLs that are safe. + * + * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts + */ + var SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file):|[^&:/?#]*(?:[/?#]|$))/gi; + + /** + * A pattern that matches safe data URLs. Only matches image, video and audio types. + * + * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts + */ + var DATA_URL_PATTERN = /^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+/]+=*$/i; + + function allowedAttribute (attr, allowedAttributeList) { + var attrName = attr.nodeName.toLowerCase() + + if ($.inArray(attrName, allowedAttributeList) !== -1) { + if ($.inArray(attrName, uriAttrs) !== -1) { + return Boolean(attr.nodeValue.match(SAFE_URL_PATTERN) || attr.nodeValue.match(DATA_URL_PATTERN)) + } + + return true + } + + var regExp = $(allowedAttributeList).filter(function (index, value) { + return value instanceof RegExp + }) + + // Check if a regular expression validates the attribute. + for (var i = 0, l = regExp.length; i < l; i++) { + if (attrName.match(regExp[i])) { + return true + } + } + + return false + } + + function sanitizeHtml (unsafeElements, whiteList, sanitizeFn) { + if (sanitizeFn && typeof sanitizeFn === 'function') { + return sanitizeFn(unsafeElements); + } + + var whitelistKeys = Object.keys(whiteList); + + for (var i = 0, len = unsafeElements.length; i < len; i++) { + var elements = unsafeElements[i].querySelectorAll('*'); + + for (var j = 0, len2 = elements.length; j < len2; j++) { + var el = elements[j]; + var elName = el.nodeName.toLowerCase(); + + if (whitelistKeys.indexOf(elName) === -1) { + el.parentNode.removeChild(el); + + continue; + } + + var attributeList = [].slice.call(el.attributes); + var whitelistedAttributes = [].concat(whiteList['*'] || [], whiteList[elName] || []); + + for (var k = 0, len3 = attributeList.length; k < len3; k++) { + var attr = attributeList[k]; + + if (!allowedAttribute(attr, whitelistedAttributes)) { + el.removeAttribute(attr.nodeName); + } + } + } + } + } + + // Polyfill for browsers with no classList support + // Remove in v2 + if (!('classList' in document.createElement('_'))) { + (function (view) { + if (!('Element' in view)) return; + + var classListProp = 'classList', + protoProp = 'prototype', + elemCtrProto = view.Element[protoProp], + objCtr = Object, + classListGetter = function () { + var $elem = $(this); + + return { + add: function (classes) { + classes = Array.prototype.slice.call(arguments).join(' '); + return $elem.addClass(classes); + }, + remove: function (classes) { + classes = Array.prototype.slice.call(arguments).join(' '); + return $elem.removeClass(classes); + }, + toggle: function (classes, force) { + return $elem.toggleClass(classes, force); + }, + contains: function (classes) { + return $elem.hasClass(classes); + } + } + }; + + if (objCtr.defineProperty) { + var classListPropDesc = { + get: classListGetter, + enumerable: true, + configurable: true + }; + try { + objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); + } catch (ex) { // IE 8 doesn't support enumerable:true + // adding undefined to fight this issue https://github.com/eligrey/classList.js/issues/36 + // modernie IE8-MSW7 machine has IE8 8.0.6001.18702 and is affected + if (ex.number === undefined || ex.number === -0x7FF5EC54) { + classListPropDesc.enumerable = false; + objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); + } + } + } else if (objCtr[protoProp].__defineGetter__) { + elemCtrProto.__defineGetter__(classListProp, classListGetter); + } + }(window)); + } + + var testElement = document.createElement('_'); + + testElement.classList.add('c1', 'c2'); + + if (!testElement.classList.contains('c2')) { + var _add = DOMTokenList.prototype.add, + _remove = DOMTokenList.prototype.remove; + + DOMTokenList.prototype.add = function () { + Array.prototype.forEach.call(arguments, _add.bind(this)); + } + + DOMTokenList.prototype.remove = function () { + Array.prototype.forEach.call(arguments, _remove.bind(this)); + } + } + + testElement.classList.toggle('c3', false); + + // Polyfill for IE 10 and Firefox <24, where classList.toggle does not + // support the second argument. + if (testElement.classList.contains('c3')) { + var _toggle = DOMTokenList.prototype.toggle; + + DOMTokenList.prototype.toggle = function (token, force) { + if (1 in arguments && !this.contains(token) === !force) { + return force; + } else { + return _toggle.call(this, token); + } + }; + } + + testElement = null; + + // shallow array comparison + function isEqual (array1, array2) { + return array1.length === array2.length && array1.every(function (element, index) { + return element === array2[index]; + }); + }; + + // + if (!String.prototype.startsWith) { + (function () { + 'use strict'; // needed to support `apply`/`call` with `undefined`/`null` + var defineProperty = (function () { + // IE 8 only supports `Object.defineProperty` on DOM elements + try { + var object = {}; + var $defineProperty = Object.defineProperty; + var result = $defineProperty(object, object, object) && $defineProperty; + } catch (error) { + } + return result; + }()); + var toString = {}.toString; + var startsWith = function (search) { + if (this == null) { + throw new TypeError(); + } + var string = String(this); + if (search && toString.call(search) == '[object RegExp]') { + throw new TypeError(); + } + var stringLength = string.length; + var searchString = String(search); + var searchLength = searchString.length; + var position = arguments.length > 1 ? arguments[1] : undefined; + // `ToInteger` + var pos = position ? Number(position) : 0; + if (pos != pos) { // better `isNaN` + pos = 0; + } + var start = Math.min(Math.max(pos, 0), stringLength); + // Avoid the `indexOf` call if no match is possible + if (searchLength + start > stringLength) { + return false; + } + var index = -1; + while (++index < searchLength) { + if (string.charCodeAt(start + index) != searchString.charCodeAt(index)) { + return false; + } + } + return true; + }; + if (defineProperty) { + defineProperty(String.prototype, 'startsWith', { + 'value': startsWith, + 'configurable': true, + 'writable': true + }); + } else { + String.prototype.startsWith = startsWith; + } + }()); + } + + if (!Object.keys) { + Object.keys = function ( + o, // object + k, // key + r // result array + ) { + // initialize object and result + r = []; + // iterate over object keys + for (k in o) { + // fill result array with non-prototypical keys + r.hasOwnProperty.call(o, k) && r.push(k); + } + // return result + return r; + }; + } + + if (HTMLSelectElement && !HTMLSelectElement.prototype.hasOwnProperty('selectedOptions')) { + Object.defineProperty(HTMLSelectElement.prototype, 'selectedOptions', { + get: function () { + return this.querySelectorAll(':checked'); + } + }); + } + + function getSelectedOptions (select, ignoreDisabled) { + var selectedOptions = select.selectedOptions, + options = [], + opt; + + if (ignoreDisabled) { + for (var i = 0, len = selectedOptions.length; i < len; i++) { + opt = selectedOptions[i]; + + if (!(opt.disabled || opt.parentNode.tagName === 'OPTGROUP' && opt.parentNode.disabled)) { + options.push(opt); + } + } + + return options; + } + + return selectedOptions; + } + + // much faster than $.val() + function getSelectValues (select, selectedOptions) { + var value = [], + options = selectedOptions || select.selectedOptions, + opt; + + for (var i = 0, len = options.length; i < len; i++) { + opt = options[i]; + + if (!(opt.disabled || opt.parentNode.tagName === 'OPTGROUP' && opt.parentNode.disabled)) { + value.push(opt.value); + } + } + + if (!select.multiple) { + return !value.length ? null : value[0]; + } + + return value; + } + + // set data-selected on select element if the value has been programmatically selected + // prior to initialization of bootstrap-select + // * consider removing or replacing an alternative method * + var valHooks = { + useDefault: false, + _set: $.valHooks.select.set + }; + + $.valHooks.select.set = function (elem, value) { + if (value && !valHooks.useDefault) $(elem).data('selected', true); + + return valHooks._set.apply(this, arguments); + }; + + var changedArguments = null; + + var EventIsSupported = (function () { + try { + new Event('change'); + return true; + } catch (e) { + return false; + } + })(); + + $.fn.triggerNative = function (eventName) { + var el = this[0], + event; + + if (el.dispatchEvent) { // for modern browsers & IE9+ + if (EventIsSupported) { + // For modern browsers + event = new Event(eventName, { + bubbles: true + }); + } else { + // For IE since it doesn't support Event constructor + event = document.createEvent('Event'); + event.initEvent(eventName, true, false); + } + + el.dispatchEvent(event); + } else if (el.fireEvent) { // for IE8 + event = document.createEventObject(); + event.eventType = eventName; + el.fireEvent('on' + eventName, event); + } else { + // fall back to jQuery.trigger + this.trigger(eventName); + } + }; + // + + function stringSearch (li, searchString, method, normalize) { + var stringTypes = [ + 'display', + 'subtext', + 'tokens' + ], + searchSuccess = false; + + for (var i = 0; i < stringTypes.length; i++) { + var stringType = stringTypes[i], + string = li[stringType]; + + if (string) { + string = string.toString(); + + // Strip HTML tags. This isn't perfect, but it's much faster than any other method + if (stringType === 'display') { + string = string.replace(/<[^>]+>/g, ''); + } + + if (normalize) string = normalizeToBase(string); + string = string.toUpperCase(); + + if (method === 'contains') { + searchSuccess = string.indexOf(searchString) >= 0; + } else { + searchSuccess = string.startsWith(searchString); + } + + if (searchSuccess) break; + } + } + + return searchSuccess; + } + + function toInteger (value) { + return parseInt(value, 10) || 0; + } + + // Borrowed from Lodash (_.deburr) + /** Used to map Latin Unicode letters to basic Latin letters. */ + var deburredLetters = { + // Latin-1 Supplement block. + '\xc0': 'A', '\xc1': 'A', '\xc2': 'A', '\xc3': 'A', '\xc4': 'A', '\xc5': 'A', + '\xe0': 'a', '\xe1': 'a', '\xe2': 'a', '\xe3': 'a', '\xe4': 'a', '\xe5': 'a', + '\xc7': 'C', '\xe7': 'c', + '\xd0': 'D', '\xf0': 'd', + '\xc8': 'E', '\xc9': 'E', '\xca': 'E', '\xcb': 'E', + '\xe8': 'e', '\xe9': 'e', '\xea': 'e', '\xeb': 'e', + '\xcc': 'I', '\xcd': 'I', '\xce': 'I', '\xcf': 'I', + '\xec': 'i', '\xed': 'i', '\xee': 'i', '\xef': 'i', + '\xd1': 'N', '\xf1': 'n', + '\xd2': 'O', '\xd3': 'O', '\xd4': 'O', '\xd5': 'O', '\xd6': 'O', '\xd8': 'O', + '\xf2': 'o', '\xf3': 'o', '\xf4': 'o', '\xf5': 'o', '\xf6': 'o', '\xf8': 'o', + '\xd9': 'U', '\xda': 'U', '\xdb': 'U', '\xdc': 'U', + '\xf9': 'u', '\xfa': 'u', '\xfb': 'u', '\xfc': 'u', + '\xdd': 'Y', '\xfd': 'y', '\xff': 'y', + '\xc6': 'Ae', '\xe6': 'ae', + '\xde': 'Th', '\xfe': 'th', + '\xdf': 'ss', + // Latin Extended-A block. + '\u0100': 'A', '\u0102': 'A', '\u0104': 'A', + '\u0101': 'a', '\u0103': 'a', '\u0105': 'a', + '\u0106': 'C', '\u0108': 'C', '\u010a': 'C', '\u010c': 'C', + '\u0107': 'c', '\u0109': 'c', '\u010b': 'c', '\u010d': 'c', + '\u010e': 'D', '\u0110': 'D', '\u010f': 'd', '\u0111': 'd', + '\u0112': 'E', '\u0114': 'E', '\u0116': 'E', '\u0118': 'E', '\u011a': 'E', + '\u0113': 'e', '\u0115': 'e', '\u0117': 'e', '\u0119': 'e', '\u011b': 'e', + '\u011c': 'G', '\u011e': 'G', '\u0120': 'G', '\u0122': 'G', + '\u011d': 'g', '\u011f': 'g', '\u0121': 'g', '\u0123': 'g', + '\u0124': 'H', '\u0126': 'H', '\u0125': 'h', '\u0127': 'h', + '\u0128': 'I', '\u012a': 'I', '\u012c': 'I', '\u012e': 'I', '\u0130': 'I', + '\u0129': 'i', '\u012b': 'i', '\u012d': 'i', '\u012f': 'i', '\u0131': 'i', + '\u0134': 'J', '\u0135': 'j', + '\u0136': 'K', '\u0137': 'k', '\u0138': 'k', + '\u0139': 'L', '\u013b': 'L', '\u013d': 'L', '\u013f': 'L', '\u0141': 'L', + '\u013a': 'l', '\u013c': 'l', '\u013e': 'l', '\u0140': 'l', '\u0142': 'l', + '\u0143': 'N', '\u0145': 'N', '\u0147': 'N', '\u014a': 'N', + '\u0144': 'n', '\u0146': 'n', '\u0148': 'n', '\u014b': 'n', + '\u014c': 'O', '\u014e': 'O', '\u0150': 'O', + '\u014d': 'o', '\u014f': 'o', '\u0151': 'o', + '\u0154': 'R', '\u0156': 'R', '\u0158': 'R', + '\u0155': 'r', '\u0157': 'r', '\u0159': 'r', + '\u015a': 'S', '\u015c': 'S', '\u015e': 'S', '\u0160': 'S', + '\u015b': 's', '\u015d': 's', '\u015f': 's', '\u0161': 's', + '\u0162': 'T', '\u0164': 'T', '\u0166': 'T', + '\u0163': 't', '\u0165': 't', '\u0167': 't', + '\u0168': 'U', '\u016a': 'U', '\u016c': 'U', '\u016e': 'U', '\u0170': 'U', '\u0172': 'U', + '\u0169': 'u', '\u016b': 'u', '\u016d': 'u', '\u016f': 'u', '\u0171': 'u', '\u0173': 'u', + '\u0174': 'W', '\u0175': 'w', + '\u0176': 'Y', '\u0177': 'y', '\u0178': 'Y', + '\u0179': 'Z', '\u017b': 'Z', '\u017d': 'Z', + '\u017a': 'z', '\u017c': 'z', '\u017e': 'z', + '\u0132': 'IJ', '\u0133': 'ij', + '\u0152': 'Oe', '\u0153': 'oe', + '\u0149': "'n", '\u017f': 's' + }; + + /** Used to match Latin Unicode letters (excluding mathematical operators). */ + var reLatin = /[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g; + + /** Used to compose unicode character classes. */ + var rsComboMarksRange = '\\u0300-\\u036f', + reComboHalfMarksRange = '\\ufe20-\\ufe2f', + rsComboSymbolsRange = '\\u20d0-\\u20ff', + rsComboMarksExtendedRange = '\\u1ab0-\\u1aff', + rsComboMarksSupplementRange = '\\u1dc0-\\u1dff', + rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange + rsComboMarksExtendedRange + rsComboMarksSupplementRange; + + /** Used to compose unicode capture groups. */ + var rsCombo = '[' + rsComboRange + ']'; + + /** + * Used to match [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks) and + * [combining diacritical marks for symbols](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks_for_Symbols). + */ + var reComboMark = RegExp(rsCombo, 'g'); + + function deburrLetter (key) { + return deburredLetters[key]; + }; + + function normalizeToBase (string) { + string = string.toString(); + return string && string.replace(reLatin, deburrLetter).replace(reComboMark, ''); + } + + // List of HTML entities for escaping. + var escapeMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '`': '`' + }; + + // Functions for escaping and unescaping strings to/from HTML interpolation. + var createEscaper = function (map) { + var escaper = function (match) { + return map[match]; + }; + // Regexes for identifying a key that needs to be escaped. + var source = '(?:' + Object.keys(map).join('|') + ')'; + var testRegexp = RegExp(source); + var replaceRegexp = RegExp(source, 'g'); + return function (string) { + string = string == null ? '' : '' + string; + return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string; + }; + }; + + var htmlEscape = createEscaper(escapeMap); + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + var keyCodeMap = { + 32: ' ', + 48: '0', + 49: '1', + 50: '2', + 51: '3', + 52: '4', + 53: '5', + 54: '6', + 55: '7', + 56: '8', + 57: '9', + 59: ';', + 65: 'A', + 66: 'B', + 67: 'C', + 68: 'D', + 69: 'E', + 70: 'F', + 71: 'G', + 72: 'H', + 73: 'I', + 74: 'J', + 75: 'K', + 76: 'L', + 77: 'M', + 78: 'N', + 79: 'O', + 80: 'P', + 81: 'Q', + 82: 'R', + 83: 'S', + 84: 'T', + 85: 'U', + 86: 'V', + 87: 'W', + 88: 'X', + 89: 'Y', + 90: 'Z', + 96: '0', + 97: '1', + 98: '2', + 99: '3', + 100: '4', + 101: '5', + 102: '6', + 103: '7', + 104: '8', + 105: '9' + }; + + var keyCodes = { + ESCAPE: 27, // KeyboardEvent.which value for Escape (Esc) key + ENTER: 13, // KeyboardEvent.which value for Enter key + SPACE: 32, // KeyboardEvent.which value for space key + TAB: 9, // KeyboardEvent.which value for tab key + ARROW_UP: 38, // KeyboardEvent.which value for up arrow key + ARROW_DOWN: 40 // KeyboardEvent.which value for down arrow key + } + + var version = { + success: false, + major: '3' + }; + + try { + version.full = ($.fn.dropdown.Constructor.VERSION || '').split(' ')[0].split('.'); + version.major = version.full[0]; + version.success = true; + } catch (err) { + // do nothing + } + + var selectId = 0; + + var EVENT_KEY = '.bs.select'; + + var classNames = { + DISABLED: 'disabled', + DIVIDER: 'divider', + SHOW: 'open', + DROPUP: 'dropup', + MENU: 'dropdown-menu', + MENURIGHT: 'dropdown-menu-right', + MENULEFT: 'dropdown-menu-left', + // to-do: replace with more advanced template/customization options + BUTTONCLASS: 'btn-default', + POPOVERHEADER: 'popover-title', + ICONBASE: 'glyphicon', + TICKICON: 'glyphicon-ok' + } + + var Selector = { + MENU: '.' + classNames.MENU + } + + var elementTemplates = { + div: document.createElement('div'), + span: document.createElement('span'), + i: document.createElement('i'), + subtext: document.createElement('small'), + a: document.createElement('a'), + li: document.createElement('li'), + whitespace: document.createTextNode('\u00A0'), + fragment: document.createDocumentFragment() + } + + elementTemplates.noResults = elementTemplates.li.cloneNode(false); + elementTemplates.noResults.className = 'no-results'; + + elementTemplates.a.setAttribute('role', 'option'); + elementTemplates.a.className = 'dropdown-item'; + + elementTemplates.subtext.className = 'text-muted'; + + elementTemplates.text = elementTemplates.span.cloneNode(false); + elementTemplates.text.className = 'text'; + + elementTemplates.checkMark = elementTemplates.span.cloneNode(false); + + var REGEXP_ARROW = new RegExp(keyCodes.ARROW_UP + '|' + keyCodes.ARROW_DOWN); + var REGEXP_TAB_OR_ESCAPE = new RegExp('^' + keyCodes.TAB + '$|' + keyCodes.ESCAPE); + + var generateOption = { + li: function (content, classes, optgroup) { + var li = elementTemplates.li.cloneNode(false); + + if (content) { + if (content.nodeType === 1 || content.nodeType === 11) { + li.appendChild(content); + } else { + li.innerHTML = content; + } + } + + if (typeof classes !== 'undefined' && classes !== '') li.className = classes; + if (typeof optgroup !== 'undefined' && optgroup !== null) li.classList.add('optgroup-' + optgroup); + + return li; + }, + + a: function (text, classes, inline) { + var a = elementTemplates.a.cloneNode(true); + + if (text) { + if (text.nodeType === 11) { + a.appendChild(text); + } else { + a.insertAdjacentHTML('beforeend', text); + } + } + + if (typeof classes !== 'undefined' && classes !== '') a.classList.add.apply(a.classList, classes.split(/\s+/)); + if (inline) a.setAttribute('style', inline); + + return a; + }, + + text: function (options, useFragment) { + var textElement = elementTemplates.text.cloneNode(false), + subtextElement, + iconElement; + + if (options.content) { + textElement.innerHTML = options.content; + } else { + textElement.textContent = options.text; + + if (options.icon) { + var whitespace = elementTemplates.whitespace.cloneNode(false); + + // need to use for icons in the button to prevent a breaking change + // note: switch to span in next major release + iconElement = (useFragment === true ? elementTemplates.i : elementTemplates.span).cloneNode(false); + iconElement.className = this.options.iconBase + ' ' + options.icon; + + elementTemplates.fragment.appendChild(iconElement); + elementTemplates.fragment.appendChild(whitespace); + } + + if (options.subtext) { + subtextElement = elementTemplates.subtext.cloneNode(false); + subtextElement.textContent = options.subtext; + textElement.appendChild(subtextElement); + } + } + + if (useFragment === true) { + while (textElement.childNodes.length > 0) { + elementTemplates.fragment.appendChild(textElement.childNodes[0]); + } + } else { + elementTemplates.fragment.appendChild(textElement); + } + + return elementTemplates.fragment; + }, + + label: function (options) { + var textElement = elementTemplates.text.cloneNode(false), + subtextElement, + iconElement; + + textElement.innerHTML = options.display; + + if (options.icon) { + var whitespace = elementTemplates.whitespace.cloneNode(false); + + iconElement = elementTemplates.span.cloneNode(false); + iconElement.className = this.options.iconBase + ' ' + options.icon; + + elementTemplates.fragment.appendChild(iconElement); + elementTemplates.fragment.appendChild(whitespace); + } + + if (options.subtext) { + subtextElement = elementTemplates.subtext.cloneNode(false); + subtextElement.textContent = options.subtext; + textElement.appendChild(subtextElement); + } + + elementTemplates.fragment.appendChild(textElement); + + return elementTemplates.fragment; + } + } + + function showNoResults (searchMatch, searchValue) { + if (!searchMatch.length) { + elementTemplates.noResults.innerHTML = this.options.noneResultsText.replace('{0}', '"' + htmlEscape(searchValue) + '"'); + this.$menuInner[0].firstChild.appendChild(elementTemplates.noResults); + } + } + + var Selectpicker = function (element, options) { + var that = this; + + // bootstrap-select has been initialized - revert valHooks.select.set back to its original function + if (!valHooks.useDefault) { + $.valHooks.select.set = valHooks._set; + valHooks.useDefault = true; + } + + this.$element = $(element); + this.$newElement = null; + this.$button = null; + this.$menu = null; + this.options = options; + this.selectpicker = { + main: {}, + search: {}, + current: {}, // current changes if a search is in progress + view: {}, + isSearching: false, + keydown: { + keyHistory: '', + resetKeyHistory: { + start: function () { + return setTimeout(function () { + that.selectpicker.keydown.keyHistory = ''; + }, 800); + } + } + } + }; + + this.sizeInfo = {}; + + // If we have no title yet, try to pull it from the html title attribute (jQuery doesnt' pick it up as it's not a + // data-attribute) + if (this.options.title === null) { + this.options.title = this.$element.attr('title'); + } + + // Format window padding + var winPad = this.options.windowPadding; + if (typeof winPad === 'number') { + this.options.windowPadding = [winPad, winPad, winPad, winPad]; + } + + // Expose public methods + this.val = Selectpicker.prototype.val; + this.render = Selectpicker.prototype.render; + this.refresh = Selectpicker.prototype.refresh; + this.setStyle = Selectpicker.prototype.setStyle; + this.selectAll = Selectpicker.prototype.selectAll; + this.deselectAll = Selectpicker.prototype.deselectAll; + this.destroy = Selectpicker.prototype.destroy; + this.remove = Selectpicker.prototype.remove; + this.show = Selectpicker.prototype.show; + this.hide = Selectpicker.prototype.hide; + + this.init(); + }; + + Selectpicker.VERSION = '1.13.18'; + + // part of this is duplicated in i18n/defaults-en_US.js. Make sure to update both. + Selectpicker.DEFAULTS = { + noneSelectedText: 'Nothing selected', + noneResultsText: 'No results matched {0}', + countSelectedText: function (numSelected, numTotal) { + return (numSelected == 1) ? '{0} item selected' : '{0} items selected'; + }, + maxOptionsText: function (numAll, numGroup) { + return [ + (numAll == 1) ? 'Limit reached ({n} item max)' : 'Limit reached ({n} items max)', + (numGroup == 1) ? 'Group limit reached ({n} item max)' : 'Group limit reached ({n} items max)' + ]; + }, + selectAllText: 'Select All', + deselectAllText: 'Deselect All', + doneButton: false, + doneButtonText: 'Close', + multipleSeparator: ', ', + styleBase: 'btn', + style: classNames.BUTTONCLASS, + size: 'auto', + title: null, + selectedTextFormat: 'values', + width: false, + container: false, + hideDisabled: false, + showSubtext: false, + showIcon: true, + showContent: true, + dropupAuto: true, + header: false, + liveSearch: false, + liveSearchPlaceholder: null, + liveSearchNormalize: false, + liveSearchStyle: 'contains', + actionsBox: false, + iconBase: classNames.ICONBASE, + tickIcon: classNames.TICKICON, + showTick: false, + template: { + caret: '' + }, + maxOptions: false, + mobile: false, + selectOnTab: false, + dropdownAlignRight: false, + windowPadding: 0, + virtualScroll: 600, + display: false, + sanitize: true, + sanitizeFn: null, + whiteList: DefaultWhitelist + }; + + Selectpicker.prototype = { + + constructor: Selectpicker, + + init: function () { + var that = this, + id = this.$element.attr('id'), + element = this.$element[0], + form = element.form; + + selectId++; + this.selectId = 'bs-select-' + selectId; + + element.classList.add('bs-select-hidden'); + + this.multiple = this.$element.prop('multiple'); + this.autofocus = this.$element.prop('autofocus'); + + if (element.classList.contains('show-tick')) { + this.options.showTick = true; + } + + this.$newElement = this.createDropdown(); + this.buildData(); + this.$element + .after(this.$newElement) + .prependTo(this.$newElement); + + // ensure select is associated with form element if it got unlinked after moving it inside newElement + if (form && element.form === null) { + if (!form.id) form.id = 'form-' + this.selectId; + element.setAttribute('form', form.id); + } + + this.$button = this.$newElement.children('button'); + this.$menu = this.$newElement.children(Selector.MENU); + this.$menuInner = this.$menu.children('.inner'); + this.$searchbox = this.$menu.find('input'); + + element.classList.remove('bs-select-hidden'); + + if (this.options.dropdownAlignRight === true) this.$menu[0].classList.add(classNames.MENURIGHT); + + if (typeof id !== 'undefined') { + this.$button.attr('data-id', id); + } + + this.checkDisabled(); + this.clickListener(); + + if (this.options.liveSearch) { + this.liveSearchListener(); + this.focusedParent = this.$searchbox[0]; + } else { + this.focusedParent = this.$menuInner[0]; + } + + this.setStyle(); + this.render(); + this.setWidth(); + if (this.options.container) { + this.selectPosition(); + } else { + this.$element.on('hide' + EVENT_KEY, function () { + if (that.isVirtual()) { + // empty menu on close + var menuInner = that.$menuInner[0], + emptyMenu = menuInner.firstChild.cloneNode(false); + + // replace the existing UL with an empty one - this is faster than $.empty() or innerHTML = '' + menuInner.replaceChild(emptyMenu, menuInner.firstChild); + menuInner.scrollTop = 0; + } + }); + } + this.$menu.data('this', this); + this.$newElement.data('this', this); + if (this.options.mobile) this.mobile(); + + this.$newElement.on({ + 'hide.bs.dropdown': function (e) { + that.$element.trigger('hide' + EVENT_KEY, e); + }, + 'hidden.bs.dropdown': function (e) { + that.$element.trigger('hidden' + EVENT_KEY, e); + }, + 'show.bs.dropdown': function (e) { + that.$element.trigger('show' + EVENT_KEY, e); + }, + 'shown.bs.dropdown': function (e) { + that.$element.trigger('shown' + EVENT_KEY, e); + } + }); + + if (element.hasAttribute('required')) { + this.$element.on('invalid' + EVENT_KEY, function () { + that.$button[0].classList.add('bs-invalid'); + + that.$element + .on('shown' + EVENT_KEY + '.invalid', function () { + that.$element + .val(that.$element.val()) // set the value to hide the validation message in Chrome when menu is opened + .off('shown' + EVENT_KEY + '.invalid'); + }) + .on('rendered' + EVENT_KEY, function () { + // if select is no longer invalid, remove the bs-invalid class + if (this.validity.valid) that.$button[0].classList.remove('bs-invalid'); + that.$element.off('rendered' + EVENT_KEY); + }); + + that.$button.on('blur' + EVENT_KEY, function () { + that.$element.trigger('focus').trigger('blur'); + that.$button.off('blur' + EVENT_KEY); + }); + }); + } + + setTimeout(function () { + that.buildList(); + that.$element.trigger('loaded' + EVENT_KEY); + }); + }, + + createDropdown: function () { + // Options + // If we are multiple or showTick option is set, then add the show-tick class + var showTick = (this.multiple || this.options.showTick) ? ' show-tick' : '', + multiselectable = this.multiple ? ' aria-multiselectable="true"' : '', + inputGroup = '', + autofocus = this.autofocus ? ' autofocus' : ''; + + if (version.major < 4 && this.$element.parent().hasClass('input-group')) { + inputGroup = ' input-group-btn'; + } + + // Elements + var drop, + header = '', + searchbox = '', + actionsbox = '', + donebutton = ''; + + if (this.options.header) { + header = + '
    ' + + '' + + this.options.header + + '
    '; + } + + if (this.options.liveSearch) { + searchbox = + ''; + } + + if (this.multiple && this.options.actionsBox) { + actionsbox = + '
    ' + + '
    ' + + '' + + '' + + '
    ' + + '
    '; + } + + if (this.multiple && this.options.doneButton) { + donebutton = + '
    ' + + '
    ' + + '' + + '
    ' + + '
    '; + } + + drop = + ''; + + return $(drop); + }, + + setPositionData: function () { + this.selectpicker.view.canHighlight = []; + this.selectpicker.view.size = 0; + this.selectpicker.view.firstHighlightIndex = false; + + for (var i = 0; i < this.selectpicker.current.data.length; i++) { + var li = this.selectpicker.current.data[i], + canHighlight = true; + + if (li.type === 'divider') { + canHighlight = false; + li.height = this.sizeInfo.dividerHeight; + } else if (li.type === 'optgroup-label') { + canHighlight = false; + li.height = this.sizeInfo.dropdownHeaderHeight; + } else { + li.height = this.sizeInfo.liHeight; + } + + if (li.disabled) canHighlight = false; + + this.selectpicker.view.canHighlight.push(canHighlight); + + if (canHighlight) { + this.selectpicker.view.size++; + li.posinset = this.selectpicker.view.size; + if (this.selectpicker.view.firstHighlightIndex === false) this.selectpicker.view.firstHighlightIndex = i; + } + + li.position = (i === 0 ? 0 : this.selectpicker.current.data[i - 1].position) + li.height; + } + }, + + isVirtual: function () { + return (this.options.virtualScroll !== false) && (this.selectpicker.main.elements.length >= this.options.virtualScroll) || this.options.virtualScroll === true; + }, + + createView: function (isSearching, setSize, refresh) { + var that = this, + scrollTop = 0, + active = [], + selected, + prevActive; + + this.selectpicker.isSearching = isSearching; + this.selectpicker.current = isSearching ? this.selectpicker.search : this.selectpicker.main; + + this.setPositionData(); + + if (setSize) { + if (refresh) { + scrollTop = this.$menuInner[0].scrollTop; + } else if (!that.multiple) { + var element = that.$element[0], + selectedIndex = (element.options[element.selectedIndex] || {}).liIndex; + + if (typeof selectedIndex === 'number' && that.options.size !== false) { + var selectedData = that.selectpicker.main.data[selectedIndex], + position = selectedData && selectedData.position; + + if (position) { + scrollTop = position - ((that.sizeInfo.menuInnerHeight + that.sizeInfo.liHeight) / 2); + } + } + } + } + + scroll(scrollTop, true); + + this.$menuInner.off('scroll.createView').on('scroll.createView', function (e, updateValue) { + if (!that.noScroll) scroll(this.scrollTop, updateValue); + that.noScroll = false; + }); + + function scroll (scrollTop, init) { + var size = that.selectpicker.current.elements.length, + chunks = [], + chunkSize, + chunkCount, + firstChunk, + lastChunk, + currentChunk, + prevPositions, + positionIsDifferent, + previousElements, + menuIsDifferent = true, + isVirtual = that.isVirtual(); + + that.selectpicker.view.scrollTop = scrollTop; + + chunkSize = Math.ceil(that.sizeInfo.menuInnerHeight / that.sizeInfo.liHeight * 1.5); // number of options in a chunk + chunkCount = Math.round(size / chunkSize) || 1; // number of chunks + + for (var i = 0; i < chunkCount; i++) { + var endOfChunk = (i + 1) * chunkSize; + + if (i === chunkCount - 1) { + endOfChunk = size; + } + + chunks[i] = [ + (i) * chunkSize + (!i ? 0 : 1), + endOfChunk + ]; + + if (!size) break; + + if (currentChunk === undefined && scrollTop - 1 <= that.selectpicker.current.data[endOfChunk - 1].position - that.sizeInfo.menuInnerHeight) { + currentChunk = i; + } + } + + if (currentChunk === undefined) currentChunk = 0; + + prevPositions = [that.selectpicker.view.position0, that.selectpicker.view.position1]; + + // always display previous, current, and next chunks + firstChunk = Math.max(0, currentChunk - 1); + lastChunk = Math.min(chunkCount - 1, currentChunk + 1); + + that.selectpicker.view.position0 = isVirtual === false ? 0 : (Math.max(0, chunks[firstChunk][0]) || 0); + that.selectpicker.view.position1 = isVirtual === false ? size : (Math.min(size, chunks[lastChunk][1]) || 0); + + positionIsDifferent = prevPositions[0] !== that.selectpicker.view.position0 || prevPositions[1] !== that.selectpicker.view.position1; + + if (that.activeIndex !== undefined) { + prevActive = that.selectpicker.main.elements[that.prevActiveIndex]; + active = that.selectpicker.main.elements[that.activeIndex]; + selected = that.selectpicker.main.elements[that.selectedIndex]; + + if (init) { + if (that.activeIndex !== that.selectedIndex) { + that.defocusItem(active); + } + that.activeIndex = undefined; + } + + if (that.activeIndex && that.activeIndex !== that.selectedIndex) { + that.defocusItem(selected); + } + } + + if (that.prevActiveIndex !== undefined && that.prevActiveIndex !== that.activeIndex && that.prevActiveIndex !== that.selectedIndex) { + that.defocusItem(prevActive); + } + + if (init || positionIsDifferent) { + previousElements = that.selectpicker.view.visibleElements ? that.selectpicker.view.visibleElements.slice() : []; + + if (isVirtual === false) { + that.selectpicker.view.visibleElements = that.selectpicker.current.elements; + } else { + that.selectpicker.view.visibleElements = that.selectpicker.current.elements.slice(that.selectpicker.view.position0, that.selectpicker.view.position1); + } + + that.setOptionStatus(); + + // if searching, check to make sure the list has actually been updated before updating DOM + // this prevents unnecessary repaints + if (isSearching || (isVirtual === false && init)) menuIsDifferent = !isEqual(previousElements, that.selectpicker.view.visibleElements); + + // if virtual scroll is disabled and not searching, + // menu should never need to be updated more than once + if ((init || isVirtual === true) && menuIsDifferent) { + var menuInner = that.$menuInner[0], + menuFragment = document.createDocumentFragment(), + emptyMenu = menuInner.firstChild.cloneNode(false), + marginTop, + marginBottom, + elements = that.selectpicker.view.visibleElements, + toSanitize = []; + + // replace the existing UL with an empty one - this is faster than $.empty() + menuInner.replaceChild(emptyMenu, menuInner.firstChild); + + for (var i = 0, visibleElementsLen = elements.length; i < visibleElementsLen; i++) { + var element = elements[i], + elText, + elementData; + + if (that.options.sanitize) { + elText = element.lastChild; + + if (elText) { + elementData = that.selectpicker.current.data[i + that.selectpicker.view.position0]; + + if (elementData && elementData.content && !elementData.sanitized) { + toSanitize.push(elText); + elementData.sanitized = true; + } + } + } + + menuFragment.appendChild(element); + } + + if (that.options.sanitize && toSanitize.length) { + sanitizeHtml(toSanitize, that.options.whiteList, that.options.sanitizeFn); + } + + if (isVirtual === true) { + marginTop = (that.selectpicker.view.position0 === 0 ? 0 : that.selectpicker.current.data[that.selectpicker.view.position0 - 1].position); + marginBottom = (that.selectpicker.view.position1 > size - 1 ? 0 : that.selectpicker.current.data[size - 1].position - that.selectpicker.current.data[that.selectpicker.view.position1 - 1].position); + + menuInner.firstChild.style.marginTop = marginTop + 'px'; + menuInner.firstChild.style.marginBottom = marginBottom + 'px'; + } else { + menuInner.firstChild.style.marginTop = 0; + menuInner.firstChild.style.marginBottom = 0; + } + + menuInner.firstChild.appendChild(menuFragment); + + // if an option is encountered that is wider than the current menu width, update the menu width accordingly + // switch to ResizeObserver with increased browser support + if (isVirtual === true && that.sizeInfo.hasScrollBar) { + var menuInnerInnerWidth = menuInner.firstChild.offsetWidth; + + if (init && menuInnerInnerWidth < that.sizeInfo.menuInnerInnerWidth && that.sizeInfo.totalMenuWidth > that.sizeInfo.selectWidth) { + menuInner.firstChild.style.minWidth = that.sizeInfo.menuInnerInnerWidth + 'px'; + } else if (menuInnerInnerWidth > that.sizeInfo.menuInnerInnerWidth) { + // set to 0 to get actual width of menu + that.$menu[0].style.minWidth = 0; + + var actualMenuWidth = menuInner.firstChild.offsetWidth; + + if (actualMenuWidth > that.sizeInfo.menuInnerInnerWidth) { + that.sizeInfo.menuInnerInnerWidth = actualMenuWidth; + menuInner.firstChild.style.minWidth = that.sizeInfo.menuInnerInnerWidth + 'px'; + } + + // reset to default CSS styling + that.$menu[0].style.minWidth = ''; + } + } + } + } + + that.prevActiveIndex = that.activeIndex; + + if (!that.options.liveSearch) { + that.$menuInner.trigger('focus'); + } else if (isSearching && init) { + var index = 0, + newActive; + + if (!that.selectpicker.view.canHighlight[index]) { + index = 1 + that.selectpicker.view.canHighlight.slice(1).indexOf(true); + } + + newActive = that.selectpicker.view.visibleElements[index]; + + that.defocusItem(that.selectpicker.view.currentActive); + + that.activeIndex = (that.selectpicker.current.data[index] || {}).index; + + that.focusItem(newActive); + } + } + + $(window) + .off('resize' + EVENT_KEY + '.' + this.selectId + '.createView') + .on('resize' + EVENT_KEY + '.' + this.selectId + '.createView', function () { + var isActive = that.$newElement.hasClass(classNames.SHOW); + + if (isActive) scroll(that.$menuInner[0].scrollTop); + }); + }, + + focusItem: function (li, liData, noStyle) { + if (li) { + liData = liData || this.selectpicker.main.data[this.activeIndex]; + var a = li.firstChild; + + if (a) { + a.setAttribute('aria-setsize', this.selectpicker.view.size); + a.setAttribute('aria-posinset', liData.posinset); + + if (noStyle !== true) { + this.focusedParent.setAttribute('aria-activedescendant', a.id); + li.classList.add('active'); + a.classList.add('active'); + } + } + } + }, + + defocusItem: function (li) { + if (li) { + li.classList.remove('active'); + if (li.firstChild) li.firstChild.classList.remove('active'); + } + }, + + setPlaceholder: function () { + var that = this, + updateIndex = false; + + if (this.options.title && !this.multiple) { + if (!this.selectpicker.view.titleOption) this.selectpicker.view.titleOption = document.createElement('option'); + + // this option doesn't create a new
  • element, but does add a new option at the start, + // so startIndex should increase to prevent having to check every option for the bs-title-option class + updateIndex = true; + + var element = this.$element[0], + selectTitleOption = false, + titleNotAppended = !this.selectpicker.view.titleOption.parentNode, + selectedIndex = element.selectedIndex, + selectedOption = element.options[selectedIndex], + navigation = window.performance && window.performance.getEntriesByType('navigation'), + // Safari doesn't support getEntriesByType('navigation') - fall back to performance.navigation + isNotBackForward = (navigation && navigation.length) ? navigation[0].type !== 'back_forward' : window.performance.navigation.type !== 2; + + if (titleNotAppended) { + // Use native JS to prepend option (faster) + this.selectpicker.view.titleOption.className = 'bs-title-option'; + this.selectpicker.view.titleOption.value = ''; + + // Check if selected or data-selected attribute is already set on an option. If not, select the titleOption option. + // the selected item may have been changed by user or programmatically before the bootstrap select plugin runs, + // if so, the select will have the data-selected attribute + selectTitleOption = !selectedOption || (selectedIndex === 0 && selectedOption.defaultSelected === false && this.$element.data('selected') === undefined); + } + + if (titleNotAppended || this.selectpicker.view.titleOption.index !== 0) { + element.insertBefore(this.selectpicker.view.titleOption, element.firstChild); + } + + // Set selected *after* appending to select, + // otherwise the option doesn't get selected in IE + // set using selectedIndex, as setting the selected attr to true here doesn't work in IE11 + if (selectTitleOption && isNotBackForward) { + element.selectedIndex = 0; + } else if (document.readyState !== 'complete') { + // if navigation type is back_forward, there's a chance the select will have its value set by BFCache + // wait for that value to be set, then run render again + window.addEventListener('pageshow', function () { + if (that.selectpicker.view.displayedValue !== element.value) that.render(); + }); + } + } + + return updateIndex; + }, + + buildData: function () { + var optionSelector = ':not([hidden]):not([data-hidden="true"])', + mainData = [], + optID = 0, + startIndex = this.setPlaceholder() ? 1 : 0; // append the titleOption if necessary and skip the first option in the loop + + if (this.options.hideDisabled) optionSelector += ':not(:disabled)'; + + var selectOptions = this.$element[0].querySelectorAll('select > *' + optionSelector); + + function addDivider (config) { + var previousData = mainData[mainData.length - 1]; + + // ensure optgroup doesn't create back-to-back dividers + if ( + previousData && + previousData.type === 'divider' && + (previousData.optID || config.optID) + ) { + return; + } + + config = config || {}; + config.type = 'divider'; + + mainData.push(config); + } + + function addOption (option, config) { + config = config || {}; + + config.divider = option.getAttribute('data-divider') === 'true'; + + if (config.divider) { + addDivider({ + optID: config.optID + }); + } else { + var liIndex = mainData.length, + cssText = option.style.cssText, + inlineStyle = cssText ? htmlEscape(cssText) : '', + optionClass = (option.className || '') + (config.optgroupClass || ''); + + if (config.optID) optionClass = 'opt ' + optionClass; + + config.optionClass = optionClass.trim(); + config.inlineStyle = inlineStyle; + config.text = option.textContent; + + config.content = option.getAttribute('data-content'); + config.tokens = option.getAttribute('data-tokens'); + config.subtext = option.getAttribute('data-subtext'); + config.icon = option.getAttribute('data-icon'); + + option.liIndex = liIndex; + + config.display = config.content || config.text; + config.type = 'option'; + config.index = liIndex; + config.option = option; + config.selected = !!option.selected; + config.disabled = config.disabled || !!option.disabled; + + mainData.push(config); + } + } + + function addOptgroup (index, selectOptions) { + var optgroup = selectOptions[index], + // skip placeholder option + previous = index - 1 < startIndex ? false : selectOptions[index - 1], + next = selectOptions[index + 1], + options = optgroup.querySelectorAll('option' + optionSelector); + + if (!options.length) return; + + var config = { + display: htmlEscape(optgroup.label), + subtext: optgroup.getAttribute('data-subtext'), + icon: optgroup.getAttribute('data-icon'), + type: 'optgroup-label', + optgroupClass: ' ' + (optgroup.className || '') + }, + headerIndex, + lastIndex; + + optID++; + + if (previous) { + addDivider({ optID: optID }); + } + + config.optID = optID; + + mainData.push(config); + + for (var j = 0, len = options.length; j < len; j++) { + var option = options[j]; + + if (j === 0) { + headerIndex = mainData.length - 1; + lastIndex = headerIndex + len; + } + + addOption(option, { + headerIndex: headerIndex, + lastIndex: lastIndex, + optID: config.optID, + optgroupClass: config.optgroupClass, + disabled: optgroup.disabled + }); + } + + if (next) { + addDivider({ optID: optID }); + } + } + + for (var len = selectOptions.length, i = startIndex; i < len; i++) { + var item = selectOptions[i]; + + if (item.tagName !== 'OPTGROUP') { + addOption(item, {}); + } else { + addOptgroup(i, selectOptions); + } + } + + this.selectpicker.main.data = this.selectpicker.current.data = mainData; + }, + + buildList: function () { + var that = this, + selectData = this.selectpicker.main.data, + mainElements = [], + widestOptionLength = 0; + + if ((that.options.showTick || that.multiple) && !elementTemplates.checkMark.parentNode) { + elementTemplates.checkMark.className = this.options.iconBase + ' ' + that.options.tickIcon + ' check-mark'; + elementTemplates.a.appendChild(elementTemplates.checkMark); + } + + function buildElement (item) { + var liElement, + combinedLength = 0; + + switch (item.type) { + case 'divider': + liElement = generateOption.li( + false, + classNames.DIVIDER, + (item.optID ? item.optID + 'div' : undefined) + ); + + break; + + case 'option': + liElement = generateOption.li( + generateOption.a( + generateOption.text.call(that, item), + item.optionClass, + item.inlineStyle + ), + '', + item.optID + ); + + if (liElement.firstChild) { + liElement.firstChild.id = that.selectId + '-' + item.index; + } + + break; + + case 'optgroup-label': + liElement = generateOption.li( + generateOption.label.call(that, item), + 'dropdown-header' + item.optgroupClass, + item.optID + ); + + break; + } + + item.element = liElement; + mainElements.push(liElement); + + // count the number of characters in the option - not perfect, but should work in most cases + if (item.display) combinedLength += item.display.length; + if (item.subtext) combinedLength += item.subtext.length; + // if there is an icon, ensure this option's width is checked + if (item.icon) combinedLength += 1; + + if (combinedLength > widestOptionLength) { + widestOptionLength = combinedLength; + + // guess which option is the widest + // use this when calculating menu width + // not perfect, but it's fast, and the width will be updating accordingly when scrolling + that.selectpicker.view.widestOption = mainElements[mainElements.length - 1]; + } + } + + for (var len = selectData.length, i = 0; i < len; i++) { + var item = selectData[i]; + + buildElement(item); + } + + this.selectpicker.main.elements = this.selectpicker.current.elements = mainElements; + }, + + findLis: function () { + return this.$menuInner.find('.inner > li'); + }, + + render: function () { + var that = this, + element = this.$element[0], + // ensure titleOption is appended and selected (if necessary) before getting selectedOptions + placeholderSelected = this.setPlaceholder() && element.selectedIndex === 0, + selectedOptions = getSelectedOptions(element, this.options.hideDisabled), + selectedCount = selectedOptions.length, + button = this.$button[0], + buttonInner = button.querySelector('.filter-option-inner-inner'), + multipleSeparator = document.createTextNode(this.options.multipleSeparator), + titleFragment = elementTemplates.fragment.cloneNode(false), + showCount, + countMax, + hasContent = false; + + button.classList.toggle('bs-placeholder', that.multiple ? !selectedCount : !getSelectValues(element, selectedOptions)); + + if (!that.multiple && selectedOptions.length === 1) { + that.selectpicker.view.displayedValue = getSelectValues(element, selectedOptions); + } + + if (this.options.selectedTextFormat === 'static') { + titleFragment = generateOption.text.call(this, { text: this.options.title }, true); + } else { + showCount = this.multiple && this.options.selectedTextFormat.indexOf('count') !== -1 && selectedCount > 1; + + // determine if the number of selected options will be shown (showCount === true) + if (showCount) { + countMax = this.options.selectedTextFormat.split('>'); + showCount = (countMax.length > 1 && selectedCount > countMax[1]) || (countMax.length === 1 && selectedCount >= 2); + } + + // only loop through all selected options if the count won't be shown + if (showCount === false) { + if (!placeholderSelected) { + for (var selectedIndex = 0; selectedIndex < selectedCount; selectedIndex++) { + if (selectedIndex < 50) { + var option = selectedOptions[selectedIndex], + thisData = this.selectpicker.main.data[option.liIndex], + titleOptions = {}; + + if (this.multiple && selectedIndex > 0) { + titleFragment.appendChild(multipleSeparator.cloneNode(false)); + } + + if (option.title) { + titleOptions.text = option.title; + } else if (thisData) { + if (thisData.content && that.options.showContent) { + titleOptions.content = thisData.content.toString(); + hasContent = true; + } else { + if (that.options.showIcon) { + titleOptions.icon = thisData.icon; + } + if (that.options.showSubtext && !that.multiple && thisData.subtext) titleOptions.subtext = ' ' + thisData.subtext; + titleOptions.text = option.textContent.trim(); + } + } + + titleFragment.appendChild(generateOption.text.call(this, titleOptions, true)); + } else { + break; + } + } + + // add ellipsis + if (selectedCount > 49) { + titleFragment.appendChild(document.createTextNode('...')); + } + } + } else { + var optionSelector = ':not([hidden]):not([data-hidden="true"]):not([data-divider="true"])'; + if (this.options.hideDisabled) optionSelector += ':not(:disabled)'; + + // If this is a multiselect, and selectedTextFormat is count, then show 1 of 2 selected, etc. + var totalCount = this.$element[0].querySelectorAll('select > option' + optionSelector + ', optgroup' + optionSelector + ' option' + optionSelector).length, + tr8nText = (typeof this.options.countSelectedText === 'function') ? this.options.countSelectedText(selectedCount, totalCount) : this.options.countSelectedText; + + titleFragment = generateOption.text.call(this, { + text: tr8nText.replace('{0}', selectedCount.toString()).replace('{1}', totalCount.toString()) + }, true); + } + } + + if (this.options.title == undefined) { + // use .attr to ensure undefined is returned if title attribute is not set + this.options.title = this.$element.attr('title'); + } + + // If the select doesn't have a title, then use the default, or if nothing is set at all, use noneSelectedText + if (!titleFragment.childNodes.length) { + titleFragment = generateOption.text.call(this, { + text: typeof this.options.title !== 'undefined' ? this.options.title : this.options.noneSelectedText + }, true); + } + + // strip all HTML tags and trim the result, then unescape any escaped tags + button.title = titleFragment.textContent.replace(/<[^>]*>?/g, '').trim(); + + if (this.options.sanitize && hasContent) { + sanitizeHtml([titleFragment], that.options.whiteList, that.options.sanitizeFn); + } + + buttonInner.innerHTML = ''; + buttonInner.appendChild(titleFragment); + + if (version.major < 4 && this.$newElement[0].classList.contains('bs3-has-addon')) { + var filterExpand = button.querySelector('.filter-expand'), + clone = buttonInner.cloneNode(true); + + clone.className = 'filter-expand'; + + if (filterExpand) { + button.replaceChild(clone, filterExpand); + } else { + button.appendChild(clone); + } + } + + this.$element.trigger('rendered' + EVENT_KEY); + }, + + /** + * @param [style] + * @param [status] + */ + setStyle: function (newStyle, status) { + var button = this.$button[0], + newElement = this.$newElement[0], + style = this.options.style.trim(), + buttonClass; + + if (this.$element.attr('class')) { + this.$newElement.addClass(this.$element.attr('class').replace(/selectpicker|mobile-device|bs-select-hidden|validate\[.*\]/gi, '')); + } + + if (version.major < 4) { + newElement.classList.add('bs3'); + + if (newElement.parentNode.classList && newElement.parentNode.classList.contains('input-group') && + (newElement.previousElementSibling || newElement.nextElementSibling) && + (newElement.previousElementSibling || newElement.nextElementSibling).classList.contains('input-group-addon') + ) { + newElement.classList.add('bs3-has-addon'); + } + } + + if (newStyle) { + buttonClass = newStyle.trim(); + } else { + buttonClass = style; + } + + if (status == 'add') { + if (buttonClass) button.classList.add.apply(button.classList, buttonClass.split(' ')); + } else if (status == 'remove') { + if (buttonClass) button.classList.remove.apply(button.classList, buttonClass.split(' ')); + } else { + if (style) button.classList.remove.apply(button.classList, style.split(' ')); + if (buttonClass) button.classList.add.apply(button.classList, buttonClass.split(' ')); + } + }, + + liHeight: function (refresh) { + if (!refresh && (this.options.size === false || Object.keys(this.sizeInfo).length)) return; + + var newElement = elementTemplates.div.cloneNode(false), + menu = elementTemplates.div.cloneNode(false), + menuInner = elementTemplates.div.cloneNode(false), + menuInnerInner = document.createElement('ul'), + divider = elementTemplates.li.cloneNode(false), + dropdownHeader = elementTemplates.li.cloneNode(false), + li, + a = elementTemplates.a.cloneNode(false), + text = elementTemplates.span.cloneNode(false), + header = this.options.header && this.$menu.find('.' + classNames.POPOVERHEADER).length > 0 ? this.$menu.find('.' + classNames.POPOVERHEADER)[0].cloneNode(true) : null, + search = this.options.liveSearch ? elementTemplates.div.cloneNode(false) : null, + actions = this.options.actionsBox && this.multiple && this.$menu.find('.bs-actionsbox').length > 0 ? this.$menu.find('.bs-actionsbox')[0].cloneNode(true) : null, + doneButton = this.options.doneButton && this.multiple && this.$menu.find('.bs-donebutton').length > 0 ? this.$menu.find('.bs-donebutton')[0].cloneNode(true) : null, + firstOption = this.$element.find('option')[0]; + + this.sizeInfo.selectWidth = this.$newElement[0].offsetWidth; + + text.className = 'text'; + a.className = 'dropdown-item ' + (firstOption ? firstOption.className : ''); + newElement.className = this.$menu[0].parentNode.className + ' ' + classNames.SHOW; + newElement.style.width = 0; // ensure button width doesn't affect natural width of menu when calculating + if (this.options.width === 'auto') menu.style.minWidth = 0; + menu.className = classNames.MENU + ' ' + classNames.SHOW; + menuInner.className = 'inner ' + classNames.SHOW; + menuInnerInner.className = classNames.MENU + ' inner ' + (version.major === '4' ? classNames.SHOW : ''); + divider.className = classNames.DIVIDER; + dropdownHeader.className = 'dropdown-header'; + + text.appendChild(document.createTextNode('\u200b')); + + if (this.selectpicker.current.data.length) { + for (var i = 0; i < this.selectpicker.current.data.length; i++) { + var data = this.selectpicker.current.data[i]; + if (data.type === 'option') { + li = data.element; + break; + } + } + } else { + li = elementTemplates.li.cloneNode(false); + a.appendChild(text); + li.appendChild(a); + } + + dropdownHeader.appendChild(text.cloneNode(true)); + + if (this.selectpicker.view.widestOption) { + menuInnerInner.appendChild(this.selectpicker.view.widestOption.cloneNode(true)); + } + + menuInnerInner.appendChild(li); + menuInnerInner.appendChild(divider); + menuInnerInner.appendChild(dropdownHeader); + if (header) menu.appendChild(header); + if (search) { + var input = document.createElement('input'); + search.className = 'bs-searchbox'; + input.className = 'form-control'; + search.appendChild(input); + menu.appendChild(search); + } + if (actions) menu.appendChild(actions); + menuInner.appendChild(menuInnerInner); + menu.appendChild(menuInner); + if (doneButton) menu.appendChild(doneButton); + newElement.appendChild(menu); + + document.body.appendChild(newElement); + + var liHeight = li.offsetHeight, + dropdownHeaderHeight = dropdownHeader ? dropdownHeader.offsetHeight : 0, + headerHeight = header ? header.offsetHeight : 0, + searchHeight = search ? search.offsetHeight : 0, + actionsHeight = actions ? actions.offsetHeight : 0, + doneButtonHeight = doneButton ? doneButton.offsetHeight : 0, + dividerHeight = $(divider).outerHeight(true), + // fall back to jQuery if getComputedStyle is not supported + menuStyle = window.getComputedStyle ? window.getComputedStyle(menu) : false, + menuWidth = menu.offsetWidth, + $menu = menuStyle ? null : $(menu), + menuPadding = { + vert: toInteger(menuStyle ? menuStyle.paddingTop : $menu.css('paddingTop')) + + toInteger(menuStyle ? menuStyle.paddingBottom : $menu.css('paddingBottom')) + + toInteger(menuStyle ? menuStyle.borderTopWidth : $menu.css('borderTopWidth')) + + toInteger(menuStyle ? menuStyle.borderBottomWidth : $menu.css('borderBottomWidth')), + horiz: toInteger(menuStyle ? menuStyle.paddingLeft : $menu.css('paddingLeft')) + + toInteger(menuStyle ? menuStyle.paddingRight : $menu.css('paddingRight')) + + toInteger(menuStyle ? menuStyle.borderLeftWidth : $menu.css('borderLeftWidth')) + + toInteger(menuStyle ? menuStyle.borderRightWidth : $menu.css('borderRightWidth')) + }, + menuExtras = { + vert: menuPadding.vert + + toInteger(menuStyle ? menuStyle.marginTop : $menu.css('marginTop')) + + toInteger(menuStyle ? menuStyle.marginBottom : $menu.css('marginBottom')) + 2, + horiz: menuPadding.horiz + + toInteger(menuStyle ? menuStyle.marginLeft : $menu.css('marginLeft')) + + toInteger(menuStyle ? menuStyle.marginRight : $menu.css('marginRight')) + 2 + }, + scrollBarWidth; + + menuInner.style.overflowY = 'scroll'; + + scrollBarWidth = menu.offsetWidth - menuWidth; + + document.body.removeChild(newElement); + + this.sizeInfo.liHeight = liHeight; + this.sizeInfo.dropdownHeaderHeight = dropdownHeaderHeight; + this.sizeInfo.headerHeight = headerHeight; + this.sizeInfo.searchHeight = searchHeight; + this.sizeInfo.actionsHeight = actionsHeight; + this.sizeInfo.doneButtonHeight = doneButtonHeight; + this.sizeInfo.dividerHeight = dividerHeight; + this.sizeInfo.menuPadding = menuPadding; + this.sizeInfo.menuExtras = menuExtras; + this.sizeInfo.menuWidth = menuWidth; + this.sizeInfo.menuInnerInnerWidth = menuWidth - menuPadding.horiz; + this.sizeInfo.totalMenuWidth = this.sizeInfo.menuWidth; + this.sizeInfo.scrollBarWidth = scrollBarWidth; + this.sizeInfo.selectHeight = this.$newElement[0].offsetHeight; + + this.setPositionData(); + }, + + getSelectPosition: function () { + var that = this, + $window = $(window), + pos = that.$newElement.offset(), + $container = $(that.options.container), + containerPos; + + if (that.options.container && $container.length && !$container.is('body')) { + containerPos = $container.offset(); + containerPos.top += parseInt($container.css('borderTopWidth')); + containerPos.left += parseInt($container.css('borderLeftWidth')); + } else { + containerPos = { top: 0, left: 0 }; + } + + var winPad = that.options.windowPadding; + + this.sizeInfo.selectOffsetTop = pos.top - containerPos.top - $window.scrollTop(); + this.sizeInfo.selectOffsetBot = $window.height() - this.sizeInfo.selectOffsetTop - this.sizeInfo.selectHeight - containerPos.top - winPad[2]; + this.sizeInfo.selectOffsetLeft = pos.left - containerPos.left - $window.scrollLeft(); + this.sizeInfo.selectOffsetRight = $window.width() - this.sizeInfo.selectOffsetLeft - this.sizeInfo.selectWidth - containerPos.left - winPad[1]; + this.sizeInfo.selectOffsetTop -= winPad[0]; + this.sizeInfo.selectOffsetLeft -= winPad[3]; + }, + + setMenuSize: function (isAuto) { + this.getSelectPosition(); + + var selectWidth = this.sizeInfo.selectWidth, + liHeight = this.sizeInfo.liHeight, + headerHeight = this.sizeInfo.headerHeight, + searchHeight = this.sizeInfo.searchHeight, + actionsHeight = this.sizeInfo.actionsHeight, + doneButtonHeight = this.sizeInfo.doneButtonHeight, + divHeight = this.sizeInfo.dividerHeight, + menuPadding = this.sizeInfo.menuPadding, + menuInnerHeight, + menuHeight, + divLength = 0, + minHeight, + _minHeight, + maxHeight, + menuInnerMinHeight, + estimate, + isDropup; + + if (this.options.dropupAuto) { + // Get the estimated height of the menu without scrollbars. + // This is useful for smaller menus, where there might be plenty of room + // below the button without setting dropup, but we can't know + // the exact height of the menu until createView is called later + estimate = liHeight * this.selectpicker.current.elements.length + menuPadding.vert; + + isDropup = this.sizeInfo.selectOffsetTop - this.sizeInfo.selectOffsetBot > this.sizeInfo.menuExtras.vert && estimate + this.sizeInfo.menuExtras.vert + 50 > this.sizeInfo.selectOffsetBot; + + // ensure dropup doesn't change while searching (so menu doesn't bounce back and forth) + if (this.selectpicker.isSearching === true) { + isDropup = this.selectpicker.dropup; + } + + this.$newElement.toggleClass(classNames.DROPUP, isDropup); + this.selectpicker.dropup = isDropup; + } + + if (this.options.size === 'auto') { + _minHeight = this.selectpicker.current.elements.length > 3 ? this.sizeInfo.liHeight * 3 + this.sizeInfo.menuExtras.vert - 2 : 0; + menuHeight = this.sizeInfo.selectOffsetBot - this.sizeInfo.menuExtras.vert; + minHeight = _minHeight + headerHeight + searchHeight + actionsHeight + doneButtonHeight; + menuInnerMinHeight = Math.max(_minHeight - menuPadding.vert, 0); + + if (this.$newElement.hasClass(classNames.DROPUP)) { + menuHeight = this.sizeInfo.selectOffsetTop - this.sizeInfo.menuExtras.vert; + } + + maxHeight = menuHeight; + menuInnerHeight = menuHeight - headerHeight - searchHeight - actionsHeight - doneButtonHeight - menuPadding.vert; + } else if (this.options.size && this.options.size != 'auto' && this.selectpicker.current.elements.length > this.options.size) { + for (var i = 0; i < this.options.size; i++) { + if (this.selectpicker.current.data[i].type === 'divider') divLength++; + } + + menuHeight = liHeight * this.options.size + divLength * divHeight + menuPadding.vert; + menuInnerHeight = menuHeight - menuPadding.vert; + maxHeight = menuHeight + headerHeight + searchHeight + actionsHeight + doneButtonHeight; + minHeight = menuInnerMinHeight = ''; + } + + this.$menu.css({ + 'max-height': maxHeight + 'px', + 'overflow': 'hidden', + 'min-height': minHeight + 'px' + }); + + this.$menuInner.css({ + 'max-height': menuInnerHeight + 'px', + 'overflow-y': 'auto', + 'min-height': menuInnerMinHeight + 'px' + }); + + // ensure menuInnerHeight is always a positive number to prevent issues calculating chunkSize in createView + this.sizeInfo.menuInnerHeight = Math.max(menuInnerHeight, 1); + + if (this.selectpicker.current.data.length && this.selectpicker.current.data[this.selectpicker.current.data.length - 1].position > this.sizeInfo.menuInnerHeight) { + this.sizeInfo.hasScrollBar = true; + this.sizeInfo.totalMenuWidth = this.sizeInfo.menuWidth + this.sizeInfo.scrollBarWidth; + } + + if (this.options.dropdownAlignRight === 'auto') { + this.$menu.toggleClass(classNames.MENURIGHT, this.sizeInfo.selectOffsetLeft > this.sizeInfo.selectOffsetRight && this.sizeInfo.selectOffsetRight < (this.sizeInfo.totalMenuWidth - selectWidth)); + } + + if (this.dropdown && this.dropdown._popper) this.dropdown._popper.update(); + }, + + setSize: function (refresh) { + this.liHeight(refresh); + + if (this.options.header) this.$menu.css('padding-top', 0); + + if (this.options.size !== false) { + var that = this, + $window = $(window); + + this.setMenuSize(); + + if (this.options.liveSearch) { + this.$searchbox + .off('input.setMenuSize propertychange.setMenuSize') + .on('input.setMenuSize propertychange.setMenuSize', function () { + return that.setMenuSize(); + }); + } + + if (this.options.size === 'auto') { + $window + .off('resize' + EVENT_KEY + '.' + this.selectId + '.setMenuSize' + ' scroll' + EVENT_KEY + '.' + this.selectId + '.setMenuSize') + .on('resize' + EVENT_KEY + '.' + this.selectId + '.setMenuSize' + ' scroll' + EVENT_KEY + '.' + this.selectId + '.setMenuSize', function () { + return that.setMenuSize(); + }); + } else if (this.options.size && this.options.size != 'auto' && this.selectpicker.current.elements.length > this.options.size) { + $window.off('resize' + EVENT_KEY + '.' + this.selectId + '.setMenuSize' + ' scroll' + EVENT_KEY + '.' + this.selectId + '.setMenuSize'); + } + } + + this.createView(false, true, refresh); + }, + + setWidth: function () { + var that = this; + + if (this.options.width === 'auto') { + requestAnimationFrame(function () { + that.$menu.css('min-width', '0'); + + that.$element.on('loaded' + EVENT_KEY, function () { + that.liHeight(); + that.setMenuSize(); + + // Get correct width if element is hidden + var $selectClone = that.$newElement.clone().appendTo('body'), + btnWidth = $selectClone.css('width', 'auto').children('button').outerWidth(); + + $selectClone.remove(); + + // Set width to whatever's larger, button title or longest option + that.sizeInfo.selectWidth = Math.max(that.sizeInfo.totalMenuWidth, btnWidth); + that.$newElement.css('width', that.sizeInfo.selectWidth + 'px'); + }); + }); + } else if (this.options.width === 'fit') { + // Remove inline min-width so width can be changed from 'auto' + this.$menu.css('min-width', ''); + this.$newElement.css('width', '').addClass('fit-width'); + } else if (this.options.width) { + // Remove inline min-width so width can be changed from 'auto' + this.$menu.css('min-width', ''); + this.$newElement.css('width', this.options.width); + } else { + // Remove inline min-width/width so width can be changed + this.$menu.css('min-width', ''); + this.$newElement.css('width', ''); + } + // Remove fit-width class if width is changed programmatically + if (this.$newElement.hasClass('fit-width') && this.options.width !== 'fit') { + this.$newElement[0].classList.remove('fit-width'); + } + }, + + selectPosition: function () { + this.$bsContainer = $('
    '); + + var that = this, + $container = $(this.options.container), + pos, + containerPos, + actualHeight, + getPlacement = function ($element) { + var containerPosition = {}, + // fall back to dropdown's default display setting if display is not manually set + display = that.options.display || ( + // Bootstrap 3 doesn't have $.fn.dropdown.Constructor.Default + $.fn.dropdown.Constructor.Default ? $.fn.dropdown.Constructor.Default.display + : false + ); + + that.$bsContainer.addClass($element.attr('class').replace(/form-control|fit-width/gi, '')).toggleClass(classNames.DROPUP, $element.hasClass(classNames.DROPUP)); + pos = $element.offset(); + + if (!$container.is('body')) { + containerPos = $container.offset(); + containerPos.top += parseInt($container.css('borderTopWidth')) - $container.scrollTop(); + containerPos.left += parseInt($container.css('borderLeftWidth')) - $container.scrollLeft(); + } else { + containerPos = { top: 0, left: 0 }; + } + + actualHeight = $element.hasClass(classNames.DROPUP) ? 0 : $element[0].offsetHeight; + + // Bootstrap 4+ uses Popper for menu positioning + if (version.major < 4 || display === 'static') { + containerPosition.top = pos.top - containerPos.top + actualHeight; + containerPosition.left = pos.left - containerPos.left; + } + + containerPosition.width = $element[0].offsetWidth; + + that.$bsContainer.css(containerPosition); + }; + + this.$button.on('click.bs.dropdown.data-api', function () { + if (that.isDisabled()) { + return; + } + + getPlacement(that.$newElement); + + that.$bsContainer + .appendTo(that.options.container) + .toggleClass(classNames.SHOW, !that.$button.hasClass(classNames.SHOW)) + .append(that.$menu); + }); + + $(window) + .off('resize' + EVENT_KEY + '.' + this.selectId + ' scroll' + EVENT_KEY + '.' + this.selectId) + .on('resize' + EVENT_KEY + '.' + this.selectId + ' scroll' + EVENT_KEY + '.' + this.selectId, function () { + var isActive = that.$newElement.hasClass(classNames.SHOW); + + if (isActive) getPlacement(that.$newElement); + }); + + this.$element.on('hide' + EVENT_KEY, function () { + that.$menu.data('height', that.$menu.height()); + that.$bsContainer.detach(); + }); + }, + + setOptionStatus: function (selectedOnly) { + var that = this; + + that.noScroll = false; + + if (that.selectpicker.view.visibleElements && that.selectpicker.view.visibleElements.length) { + for (var i = 0; i < that.selectpicker.view.visibleElements.length; i++) { + var liData = that.selectpicker.current.data[i + that.selectpicker.view.position0], + option = liData.option; + + if (option) { + if (selectedOnly !== true) { + that.setDisabled( + liData.index, + liData.disabled + ); + } + + that.setSelected( + liData.index, + option.selected + ); + } + } + } + }, + + /** + * @param {number} index - the index of the option that is being changed + * @param {boolean} selected - true if the option is being selected, false if being deselected + */ + setSelected: function (index, selected) { + var li = this.selectpicker.main.elements[index], + liData = this.selectpicker.main.data[index], + activeIndexIsSet = this.activeIndex !== undefined, + thisIsActive = this.activeIndex === index, + prevActive, + a, + // if current option is already active + // OR + // if the current option is being selected, it's NOT multiple, and + // activeIndex is undefined: + // - when the menu is first being opened, OR + // - after a search has been performed, OR + // - when retainActive is false when selecting a new option (i.e. index of the newly selected option is not the same as the current activeIndex) + keepActive = thisIsActive || (selected && !this.multiple && !activeIndexIsSet); + + liData.selected = selected; + + a = li.firstChild; + + if (selected) { + this.selectedIndex = index; + } + + li.classList.toggle('selected', selected); + + if (keepActive) { + this.focusItem(li, liData); + this.selectpicker.view.currentActive = li; + this.activeIndex = index; + } else { + this.defocusItem(li); + } + + if (a) { + a.classList.toggle('selected', selected); + + if (selected) { + a.setAttribute('aria-selected', true); + } else { + if (this.multiple) { + a.setAttribute('aria-selected', false); + } else { + a.removeAttribute('aria-selected'); + } + } + } + + if (!keepActive && !activeIndexIsSet && selected && this.prevActiveIndex !== undefined) { + prevActive = this.selectpicker.main.elements[this.prevActiveIndex]; + + this.defocusItem(prevActive); + } + }, + + /** + * @param {number} index - the index of the option that is being disabled + * @param {boolean} disabled - true if the option is being disabled, false if being enabled + */ + setDisabled: function (index, disabled) { + var li = this.selectpicker.main.elements[index], + a; + + this.selectpicker.main.data[index].disabled = disabled; + + a = li.firstChild; + + li.classList.toggle(classNames.DISABLED, disabled); + + if (a) { + if (version.major === '4') a.classList.toggle(classNames.DISABLED, disabled); + + if (disabled) { + a.setAttribute('aria-disabled', disabled); + a.setAttribute('tabindex', -1); + } else { + a.removeAttribute('aria-disabled'); + a.setAttribute('tabindex', 0); + } + } + }, + + isDisabled: function () { + return this.$element[0].disabled; + }, + + checkDisabled: function () { + if (this.isDisabled()) { + this.$newElement[0].classList.add(classNames.DISABLED); + this.$button.addClass(classNames.DISABLED).attr('aria-disabled', true); + } else { + if (this.$button[0].classList.contains(classNames.DISABLED)) { + this.$newElement[0].classList.remove(classNames.DISABLED); + this.$button.removeClass(classNames.DISABLED).attr('aria-disabled', false); + } + } + }, + + clickListener: function () { + var that = this, + $document = $(document); + + $document.data('spaceSelect', false); + + this.$button.on('keyup', function (e) { + if (/(32)/.test(e.keyCode.toString(10)) && $document.data('spaceSelect')) { + e.preventDefault(); + $document.data('spaceSelect', false); + } + }); + + this.$newElement.on('show.bs.dropdown', function () { + if (version.major > 3 && !that.dropdown) { + that.dropdown = that.$button.data('bs.dropdown'); + that.dropdown._menu = that.$menu[0]; + } + }); + + this.$button.on('click.bs.dropdown.data-api', function () { + if (!that.$newElement.hasClass(classNames.SHOW)) { + that.setSize(); + } + }); + + function setFocus () { + if (that.options.liveSearch) { + that.$searchbox.trigger('focus'); + } else { + that.$menuInner.trigger('focus'); + } + } + + function checkPopperExists () { + if (that.dropdown && that.dropdown._popper && that.dropdown._popper.state.isCreated) { + setFocus(); + } else { + requestAnimationFrame(checkPopperExists); + } + } + + this.$element.on('shown' + EVENT_KEY, function () { + if (that.$menuInner[0].scrollTop !== that.selectpicker.view.scrollTop) { + that.$menuInner[0].scrollTop = that.selectpicker.view.scrollTop; + } + + if (version.major > 3) { + requestAnimationFrame(checkPopperExists); + } else { + setFocus(); + } + }); + + // ensure posinset and setsize are correct before selecting an option via a click + this.$menuInner.on('mouseenter', 'li a', function (e) { + var hoverLi = this.parentElement, + position0 = that.isVirtual() ? that.selectpicker.view.position0 : 0, + index = Array.prototype.indexOf.call(hoverLi.parentElement.children, hoverLi), + hoverData = that.selectpicker.current.data[index + position0]; + + that.focusItem(hoverLi, hoverData, true); + }); + + this.$menuInner.on('click', 'li a', function (e, retainActive) { + var $this = $(this), + element = that.$element[0], + position0 = that.isVirtual() ? that.selectpicker.view.position0 : 0, + clickedData = that.selectpicker.current.data[$this.parent().index() + position0], + clickedIndex = clickedData.index, + prevValue = getSelectValues(element), + prevIndex = element.selectedIndex, + prevOption = element.options[prevIndex], + triggerChange = true; + + // Don't close on multi choice menu + if (that.multiple && that.options.maxOptions !== 1) { + e.stopPropagation(); + } + + e.preventDefault(); + + // Don't run if the select is disabled + if (!that.isDisabled() && !$this.parent().hasClass(classNames.DISABLED)) { + var option = clickedData.option, + $option = $(option), + state = option.selected, + $optgroup = $option.parent('optgroup'), + $optgroupOptions = $optgroup.find('option'), + maxOptions = that.options.maxOptions, + maxOptionsGrp = $optgroup.data('maxOptions') || false; + + if (clickedIndex === that.activeIndex) retainActive = true; + + if (!retainActive) { + that.prevActiveIndex = that.activeIndex; + that.activeIndex = undefined; + } + + if (!that.multiple) { // Deselect all others if not multi select box + if (prevOption) prevOption.selected = false; + option.selected = true; + that.setSelected(clickedIndex, true); + } else { // Toggle the one we have chosen if we are multi select. + option.selected = !state; + + that.setSelected(clickedIndex, !state); + that.focusedParent.focus(); + + if (maxOptions !== false || maxOptionsGrp !== false) { + var maxReached = maxOptions < getSelectedOptions(element).length, + maxReachedGrp = maxOptionsGrp < $optgroup.find('option:selected').length; + + if ((maxOptions && maxReached) || (maxOptionsGrp && maxReachedGrp)) { + if (maxOptions && maxOptions == 1) { + element.selectedIndex = -1; + option.selected = true; + that.setOptionStatus(true); + } else if (maxOptionsGrp && maxOptionsGrp == 1) { + for (var i = 0; i < $optgroupOptions.length; i++) { + var _option = $optgroupOptions[i]; + _option.selected = false; + that.setSelected(_option.liIndex, false); + } + + option.selected = true; + that.setSelected(clickedIndex, true); + } else { + var maxOptionsText = typeof that.options.maxOptionsText === 'string' ? [that.options.maxOptionsText, that.options.maxOptionsText] : that.options.maxOptionsText, + maxOptionsArr = typeof maxOptionsText === 'function' ? maxOptionsText(maxOptions, maxOptionsGrp) : maxOptionsText, + maxTxt = maxOptionsArr[0].replace('{n}', maxOptions), + maxTxtGrp = maxOptionsArr[1].replace('{n}', maxOptionsGrp), + $notify = $('
    '); + // If {var} is set in array, replace it + /** @deprecated */ + if (maxOptionsArr[2]) { + maxTxt = maxTxt.replace('{var}', maxOptionsArr[2][maxOptions > 1 ? 0 : 1]); + maxTxtGrp = maxTxtGrp.replace('{var}', maxOptionsArr[2][maxOptionsGrp > 1 ? 0 : 1]); + } + + option.selected = false; + + that.$menu.append($notify); + + if (maxOptions && maxReached) { + $notify.append($('
    ' + maxTxt + '
    ')); + triggerChange = false; + that.$element.trigger('maxReached' + EVENT_KEY); + } + + if (maxOptionsGrp && maxReachedGrp) { + $notify.append($('
    ' + maxTxtGrp + '
    ')); + triggerChange = false; + that.$element.trigger('maxReachedGrp' + EVENT_KEY); + } + + setTimeout(function () { + that.setSelected(clickedIndex, false); + }, 10); + + $notify[0].classList.add('fadeOut'); + + setTimeout(function () { + $notify.remove(); + }, 1050); + } + } + } + } + + if (!that.multiple || (that.multiple && that.options.maxOptions === 1)) { + that.$button.trigger('focus'); + } else if (that.options.liveSearch) { + that.$searchbox.trigger('focus'); + } + + // Trigger select 'change' + if (triggerChange) { + if (that.multiple || prevIndex !== element.selectedIndex) { + // $option.prop('selected') is current option state (selected/unselected). prevValue is the value of the select prior to being changed. + changedArguments = [option.index, $option.prop('selected'), prevValue]; + that.$element + .triggerNative('change'); + } + } + } + }); + + this.$menu.on('click', 'li.' + classNames.DISABLED + ' a, .' + classNames.POPOVERHEADER + ', .' + classNames.POPOVERHEADER + ' :not(.close)', function (e) { + if (e.currentTarget == this) { + e.preventDefault(); + e.stopPropagation(); + if (that.options.liveSearch && !$(e.target).hasClass('close')) { + that.$searchbox.trigger('focus'); + } else { + that.$button.trigger('focus'); + } + } + }); + + this.$menuInner.on('click', '.divider, .dropdown-header', function (e) { + e.preventDefault(); + e.stopPropagation(); + if (that.options.liveSearch) { + that.$searchbox.trigger('focus'); + } else { + that.$button.trigger('focus'); + } + }); + + this.$menu.on('click', '.' + classNames.POPOVERHEADER + ' .close', function () { + that.$button.trigger('click'); + }); + + this.$searchbox.on('click', function (e) { + e.stopPropagation(); + }); + + this.$menu.on('click', '.actions-btn', function (e) { + if (that.options.liveSearch) { + that.$searchbox.trigger('focus'); + } else { + that.$button.trigger('focus'); + } + + e.preventDefault(); + e.stopPropagation(); + + if ($(this).hasClass('bs-select-all')) { + that.selectAll(); + } else { + that.deselectAll(); + } + }); + + this.$button + .on('focus' + EVENT_KEY, function (e) { + var tabindex = that.$element[0].getAttribute('tabindex'); + + // only change when button is actually focused + if (tabindex !== undefined && e.originalEvent && e.originalEvent.isTrusted) { + // apply select element's tabindex to ensure correct order is followed when tabbing to the next element + this.setAttribute('tabindex', tabindex); + // set element's tabindex to -1 to allow for reverse tabbing + that.$element[0].setAttribute('tabindex', -1); + that.selectpicker.view.tabindex = tabindex; + } + }) + .on('blur' + EVENT_KEY, function (e) { + // revert everything to original tabindex + if (that.selectpicker.view.tabindex !== undefined && e.originalEvent && e.originalEvent.isTrusted) { + that.$element[0].setAttribute('tabindex', that.selectpicker.view.tabindex); + this.setAttribute('tabindex', -1); + that.selectpicker.view.tabindex = undefined; + } + }); + + this.$element + .on('change' + EVENT_KEY, function () { + that.render(); + that.$element.trigger('changed' + EVENT_KEY, changedArguments); + changedArguments = null; + }) + .on('focus' + EVENT_KEY, function () { + if (!that.options.mobile) that.$button[0].focus(); + }); + }, + + liveSearchListener: function () { + var that = this; + + this.$button.on('click.bs.dropdown.data-api', function () { + if (!!that.$searchbox.val()) { + that.$searchbox.val(''); + that.selectpicker.search.previousValue = undefined; + } + }); + + this.$searchbox.on('click.bs.dropdown.data-api focus.bs.dropdown.data-api touchend.bs.dropdown.data-api', function (e) { + e.stopPropagation(); + }); + + this.$searchbox.on('input propertychange', function () { + var searchValue = that.$searchbox[0].value; + + that.selectpicker.search.elements = []; + that.selectpicker.search.data = []; + + if (searchValue) { + var i, + searchMatch = [], + q = searchValue.toUpperCase(), + cache = {}, + cacheArr = [], + searchStyle = that._searchStyle(), + normalizeSearch = that.options.liveSearchNormalize; + + if (normalizeSearch) q = normalizeToBase(q); + + for (var i = 0; i < that.selectpicker.main.data.length; i++) { + var li = that.selectpicker.main.data[i]; + + if (!cache[i]) { + cache[i] = stringSearch(li, q, searchStyle, normalizeSearch); + } + + if (cache[i] && li.headerIndex !== undefined && cacheArr.indexOf(li.headerIndex) === -1) { + if (li.headerIndex > 0) { + cache[li.headerIndex - 1] = true; + cacheArr.push(li.headerIndex - 1); + } + + cache[li.headerIndex] = true; + cacheArr.push(li.headerIndex); + + cache[li.lastIndex + 1] = true; + } + + if (cache[i] && li.type !== 'optgroup-label') cacheArr.push(i); + } + + for (var i = 0, cacheLen = cacheArr.length; i < cacheLen; i++) { + var index = cacheArr[i], + prevIndex = cacheArr[i - 1], + li = that.selectpicker.main.data[index], + liPrev = that.selectpicker.main.data[prevIndex]; + + if (li.type !== 'divider' || (li.type === 'divider' && liPrev && liPrev.type !== 'divider' && cacheLen - 1 !== i)) { + that.selectpicker.search.data.push(li); + searchMatch.push(that.selectpicker.main.elements[index]); + } + } + + that.activeIndex = undefined; + that.noScroll = true; + that.$menuInner.scrollTop(0); + that.selectpicker.search.elements = searchMatch; + that.createView(true); + showNoResults.call(that, searchMatch, searchValue); + } else if (that.selectpicker.search.previousValue) { // for IE11 (#2402) + that.$menuInner.scrollTop(0); + that.createView(false); + } + + that.selectpicker.search.previousValue = searchValue; + }); + }, + + _searchStyle: function () { + return this.options.liveSearchStyle || 'contains'; + }, + + val: function (value) { + var element = this.$element[0]; + + if (typeof value !== 'undefined') { + var prevValue = getSelectValues(element); + + changedArguments = [null, null, prevValue]; + + this.$element + .val(value) + .trigger('changed' + EVENT_KEY, changedArguments); + + if (this.$newElement.hasClass(classNames.SHOW)) { + if (this.multiple) { + this.setOptionStatus(true); + } else { + var liSelectedIndex = (element.options[element.selectedIndex] || {}).liIndex; + + if (typeof liSelectedIndex === 'number') { + this.setSelected(this.selectedIndex, false); + this.setSelected(liSelectedIndex, true); + } + } + } + + this.render(); + + changedArguments = null; + + return this.$element; + } else { + return this.$element.val(); + } + }, + + changeAll: function (status) { + if (!this.multiple) return; + if (typeof status === 'undefined') status = true; + + var element = this.$element[0], + previousSelected = 0, + currentSelected = 0, + prevValue = getSelectValues(element); + + element.classList.add('bs-select-hidden'); + + for (var i = 0, data = this.selectpicker.current.data, len = data.length; i < len; i++) { + var liData = data[i], + option = liData.option; + + if (option && !liData.disabled && liData.type !== 'divider') { + if (liData.selected) previousSelected++; + option.selected = status; + if (status === true) currentSelected++; + } + } + + element.classList.remove('bs-select-hidden'); + + if (previousSelected === currentSelected) return; + + this.setOptionStatus(); + + changedArguments = [null, null, prevValue]; + + this.$element + .triggerNative('change'); + }, + + selectAll: function () { + return this.changeAll(true); + }, + + deselectAll: function () { + return this.changeAll(false); + }, + + toggle: function (e) { + e = e || window.event; + + if (e) e.stopPropagation(); + + this.$button.trigger('click.bs.dropdown.data-api'); + }, + + keydown: function (e) { + var $this = $(this), + isToggle = $this.hasClass('dropdown-toggle'), + $parent = isToggle ? $this.closest('.dropdown') : $this.closest(Selector.MENU), + that = $parent.data('this'), + $items = that.findLis(), + index, + isActive, + liActive, + activeLi, + offset, + updateScroll = false, + downOnTab = e.which === keyCodes.TAB && !isToggle && !that.options.selectOnTab, + isArrowKey = REGEXP_ARROW.test(e.which) || downOnTab, + scrollTop = that.$menuInner[0].scrollTop, + isVirtual = that.isVirtual(), + position0 = isVirtual === true ? that.selectpicker.view.position0 : 0; + + // do nothing if a function key is pressed + if (e.which >= 112 && e.which <= 123) return; + + isActive = that.$newElement.hasClass(classNames.SHOW); + + if ( + !isActive && + ( + isArrowKey || + (e.which >= 48 && e.which <= 57) || + (e.which >= 96 && e.which <= 105) || + (e.which >= 65 && e.which <= 90) + ) + ) { + that.$button.trigger('click.bs.dropdown.data-api'); + + if (that.options.liveSearch) { + that.$searchbox.trigger('focus'); + return; + } + } + + if (e.which === keyCodes.ESCAPE && isActive) { + e.preventDefault(); + that.$button.trigger('click.bs.dropdown.data-api').trigger('focus'); + } + + if (isArrowKey) { // if up or down + if (!$items.length) return; + + liActive = that.selectpicker.main.elements[that.activeIndex]; + index = liActive ? Array.prototype.indexOf.call(liActive.parentElement.children, liActive) : -1; + + if (index !== -1) { + that.defocusItem(liActive); + } + + if (e.which === keyCodes.ARROW_UP) { // up + if (index !== -1) index--; + if (index + position0 < 0) index += $items.length; + + if (!that.selectpicker.view.canHighlight[index + position0]) { + index = that.selectpicker.view.canHighlight.slice(0, index + position0).lastIndexOf(true) - position0; + if (index === -1) index = $items.length - 1; + } + } else if (e.which === keyCodes.ARROW_DOWN || downOnTab) { // down + index++; + if (index + position0 >= that.selectpicker.view.canHighlight.length) index = that.selectpicker.view.firstHighlightIndex; + + if (!that.selectpicker.view.canHighlight[index + position0]) { + index = index + 1 + that.selectpicker.view.canHighlight.slice(index + position0 + 1).indexOf(true); + } + } + + e.preventDefault(); + + var liActiveIndex = position0 + index; + + if (e.which === keyCodes.ARROW_UP) { // up + // scroll to bottom and highlight last option + if (position0 === 0 && index === $items.length - 1) { + that.$menuInner[0].scrollTop = that.$menuInner[0].scrollHeight; + + liActiveIndex = that.selectpicker.current.elements.length - 1; + } else { + activeLi = that.selectpicker.current.data[liActiveIndex]; + offset = activeLi.position - activeLi.height; + + updateScroll = offset < scrollTop; + } + } else if (e.which === keyCodes.ARROW_DOWN || downOnTab) { // down + // scroll to top and highlight first option + if (index === that.selectpicker.view.firstHighlightIndex) { + that.$menuInner[0].scrollTop = 0; + + liActiveIndex = that.selectpicker.view.firstHighlightIndex; + } else { + activeLi = that.selectpicker.current.data[liActiveIndex]; + offset = activeLi.position - that.sizeInfo.menuInnerHeight; + + updateScroll = offset > scrollTop; + } + } + + liActive = that.selectpicker.current.elements[liActiveIndex]; + + that.activeIndex = that.selectpicker.current.data[liActiveIndex].index; + + that.focusItem(liActive); + + that.selectpicker.view.currentActive = liActive; + + if (updateScroll) that.$menuInner[0].scrollTop = offset; + + if (that.options.liveSearch) { + that.$searchbox.trigger('focus'); + } else { + $this.trigger('focus'); + } + } else if ( + (!$this.is('input') && !REGEXP_TAB_OR_ESCAPE.test(e.which)) || + (e.which === keyCodes.SPACE && that.selectpicker.keydown.keyHistory) + ) { + var searchMatch, + matches = [], + keyHistory; + + e.preventDefault(); + + that.selectpicker.keydown.keyHistory += keyCodeMap[e.which]; + + if (that.selectpicker.keydown.resetKeyHistory.cancel) clearTimeout(that.selectpicker.keydown.resetKeyHistory.cancel); + that.selectpicker.keydown.resetKeyHistory.cancel = that.selectpicker.keydown.resetKeyHistory.start(); + + keyHistory = that.selectpicker.keydown.keyHistory; + + // if all letters are the same, set keyHistory to just the first character when searching + if (/^(.)\1+$/.test(keyHistory)) { + keyHistory = keyHistory.charAt(0); + } + + // find matches + for (var i = 0; i < that.selectpicker.current.data.length; i++) { + var li = that.selectpicker.current.data[i], + hasMatch; + + hasMatch = stringSearch(li, keyHistory, 'startsWith', true); + + if (hasMatch && that.selectpicker.view.canHighlight[i]) { + matches.push(li.index); + } + } + + if (matches.length) { + var matchIndex = 0; + + $items.removeClass('active').find('a').removeClass('active'); + + // either only one key has been pressed or they are all the same key + if (keyHistory.length === 1) { + matchIndex = matches.indexOf(that.activeIndex); + + if (matchIndex === -1 || matchIndex === matches.length - 1) { + matchIndex = 0; + } else { + matchIndex++; + } + } + + searchMatch = matches[matchIndex]; + + activeLi = that.selectpicker.main.data[searchMatch]; + + if (scrollTop - activeLi.position > 0) { + offset = activeLi.position - activeLi.height; + updateScroll = true; + } else { + offset = activeLi.position - that.sizeInfo.menuInnerHeight; + // if the option is already visible at the current scroll position, just keep it the same + updateScroll = activeLi.position > scrollTop + that.sizeInfo.menuInnerHeight; + } + + liActive = that.selectpicker.main.elements[searchMatch]; + + that.activeIndex = matches[matchIndex]; + + that.focusItem(liActive); + + if (liActive) liActive.firstChild.focus(); + + if (updateScroll) that.$menuInner[0].scrollTop = offset; + + $this.trigger('focus'); + } + } + + // Select focused option if "Enter", "Spacebar" or "Tab" (when selectOnTab is true) are pressed inside the menu. + if ( + isActive && + ( + (e.which === keyCodes.SPACE && !that.selectpicker.keydown.keyHistory) || + e.which === keyCodes.ENTER || + (e.which === keyCodes.TAB && that.options.selectOnTab) + ) + ) { + if (e.which !== keyCodes.SPACE) e.preventDefault(); + + if (!that.options.liveSearch || e.which !== keyCodes.SPACE) { + that.$menuInner.find('.active a').trigger('click', true); // retain active class + $this.trigger('focus'); + + if (!that.options.liveSearch) { + // Prevent screen from scrolling if the user hits the spacebar + e.preventDefault(); + // Fixes spacebar selection of dropdown items in FF & IE + $(document).data('spaceSelect', true); + } + } + } + }, + + mobile: function () { + // ensure mobile is set to true if mobile function is called after init + this.options.mobile = true; + this.$element[0].classList.add('mobile-device'); + }, + + refresh: function () { + // update options if data attributes have been changed + var config = $.extend({}, this.options, this.$element.data()); + this.options = config; + + this.checkDisabled(); + this.buildData(); + this.setStyle(); + this.render(); + this.buildList(); + this.setWidth(); + + this.setSize(true); + + this.$element.trigger('refreshed' + EVENT_KEY); + }, + + hide: function () { + this.$newElement.hide(); + }, + + show: function () { + this.$newElement.show(); + }, + + remove: function () { + this.$newElement.remove(); + this.$element.remove(); + }, + + destroy: function () { + this.$newElement.before(this.$element).remove(); + + if (this.$bsContainer) { + this.$bsContainer.remove(); + } else { + this.$menu.remove(); + } + + if (this.selectpicker.view.titleOption && this.selectpicker.view.titleOption.parentNode) { + this.selectpicker.view.titleOption.parentNode.removeChild(this.selectpicker.view.titleOption); + } + + this.$element + .off(EVENT_KEY) + .removeData('selectpicker') + .removeClass('bs-select-hidden selectpicker'); + + $(window).off(EVENT_KEY + '.' + this.selectId); + } + }; + + // SELECTPICKER PLUGIN DEFINITION + // ============================== + function Plugin (option) { + // get the args of the outer function.. + var args = arguments; + // The arguments of the function are explicitly re-defined from the argument list, because the shift causes them + // to get lost/corrupted in android 2.3 and IE9 #715 #775 + var _option = option; + + [].shift.apply(args); + + // if the version was not set successfully + if (!version.success) { + // try to retreive it again + try { + version.full = ($.fn.dropdown.Constructor.VERSION || '').split(' ')[0].split('.'); + } catch (err) { + // fall back to use BootstrapVersion if set + if (Selectpicker.BootstrapVersion) { + version.full = Selectpicker.BootstrapVersion.split(' ')[0].split('.'); + } else { + version.full = [version.major, '0', '0']; + + console.warn( + 'There was an issue retrieving Bootstrap\'s version. ' + + 'Ensure Bootstrap is being loaded before bootstrap-select and there is no namespace collision. ' + + 'If loading Bootstrap asynchronously, the version may need to be manually specified via $.fn.selectpicker.Constructor.BootstrapVersion.', + err + ); + } + } + + version.major = version.full[0]; + version.success = true; + } + + if (version.major === '4') { + // some defaults need to be changed if using Bootstrap 4 + // check to see if they have already been manually changed before forcing them to update + var toUpdate = []; + + if (Selectpicker.DEFAULTS.style === classNames.BUTTONCLASS) toUpdate.push({ name: 'style', className: 'BUTTONCLASS' }); + if (Selectpicker.DEFAULTS.iconBase === classNames.ICONBASE) toUpdate.push({ name: 'iconBase', className: 'ICONBASE' }); + if (Selectpicker.DEFAULTS.tickIcon === classNames.TICKICON) toUpdate.push({ name: 'tickIcon', className: 'TICKICON' }); + + classNames.DIVIDER = 'dropdown-divider'; + classNames.SHOW = 'show'; + classNames.BUTTONCLASS = 'btn-light'; + classNames.POPOVERHEADER = 'popover-header'; + classNames.ICONBASE = ''; + classNames.TICKICON = 'bs-ok-default'; + + for (var i = 0; i < toUpdate.length; i++) { + var option = toUpdate[i]; + Selectpicker.DEFAULTS[option.name] = classNames[option.className]; + } + } + + var value; + var chain = this.each(function () { + var $this = $(this); + if ($this.is('select')) { + var data = $this.data('selectpicker'), + options = typeof _option == 'object' && _option; + + if (!data) { + var dataAttributes = $this.data(); + + for (var dataAttr in dataAttributes) { + if (Object.prototype.hasOwnProperty.call(dataAttributes, dataAttr) && $.inArray(dataAttr, DISALLOWED_ATTRIBUTES) !== -1) { + delete dataAttributes[dataAttr]; + } + } + + var config = $.extend({}, Selectpicker.DEFAULTS, $.fn.selectpicker.defaults || {}, dataAttributes, options); + config.template = $.extend({}, Selectpicker.DEFAULTS.template, ($.fn.selectpicker.defaults ? $.fn.selectpicker.defaults.template : {}), dataAttributes.template, options.template); + $this.data('selectpicker', (data = new Selectpicker(this, config))); + } else if (options) { + for (var i in options) { + if (Object.prototype.hasOwnProperty.call(options, i)) { + data.options[i] = options[i]; + } + } + } + + if (typeof _option == 'string') { + if (data[_option] instanceof Function) { + value = data[_option].apply(data, args); + } else { + value = data.options[_option]; + } + } + } + }); + + if (typeof value !== 'undefined') { + // noinspection JSUnusedAssignment + return value; + } else { + return chain; + } + } + + var old = $.fn.selectpicker; + $.fn.selectpicker = Plugin; + $.fn.selectpicker.Constructor = Selectpicker; + + // SELECTPICKER NO CONFLICT + // ======================== + $.fn.selectpicker.noConflict = function () { + $.fn.selectpicker = old; + return this; + }; + + // get Bootstrap's keydown event handler for either Bootstrap 4 or Bootstrap 3 + function keydownHandler () { + if ($.fn.dropdown) { + // wait to define until function is called in case Bootstrap isn't loaded yet + var bootstrapKeydown = $.fn.dropdown.Constructor._dataApiKeydownHandler || $.fn.dropdown.Constructor.prototype.keydown; + return bootstrapKeydown.apply(this, arguments); + } + } + + $(document) + .off('keydown.bs.dropdown.data-api') + .on('keydown.bs.dropdown.data-api', ':not(.bootstrap-select) > [data-toggle="dropdown"]', keydownHandler) + .on('keydown.bs.dropdown.data-api', ':not(.bootstrap-select) > .dropdown-menu', keydownHandler) + .on('keydown' + EVENT_KEY, '.bootstrap-select [data-toggle="dropdown"], .bootstrap-select [role="listbox"], .bootstrap-select .bs-searchbox input', Selectpicker.prototype.keydown) + .on('focusin.modal', '.bootstrap-select [data-toggle="dropdown"], .bootstrap-select [role="listbox"], .bootstrap-select .bs-searchbox input', function (e) { + e.stopPropagation(); + }); + + // SELECTPICKER DATA-API + // ===================== + $(window).on('load' + EVENT_KEY + '.data-api', function () { + $('.selectpicker').each(function () { + var $selectpicker = $(this); + Plugin.call($selectpicker, $selectpicker.data()); + }) + }); +})(jQuery); + + +})); diff --git a/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.min.css b/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.min.css new file mode 100644 index 0000000..8d0f049 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.min.css @@ -0,0 +1,6 @@ +/*! + * Bootstrap-select v1.13.18 (https://developer.snapappointments.com/bootstrap-select) + * + * Copyright 2012-2020 SnapAppointments, LLC + * Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE) + */@-webkit-keyframes bs-notify-fadeOut{0%{opacity:.9}100%{opacity:0}}@-o-keyframes bs-notify-fadeOut{0%{opacity:.9}100%{opacity:0}}@keyframes bs-notify-fadeOut{0%{opacity:.9}100%{opacity:0}}.bootstrap-select>select.bs-select-hidden,select.bs-select-hidden,select.selectpicker{display:none!important}.bootstrap-select{width:220px\0;vertical-align:middle}.bootstrap-select>.dropdown-toggle{position:relative;width:100%;text-align:right;white-space:nowrap;display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.bootstrap-select>.dropdown-toggle:after{margin-top:-1px}.bootstrap-select>.dropdown-toggle.bs-placeholder,.bootstrap-select>.dropdown-toggle.bs-placeholder:active,.bootstrap-select>.dropdown-toggle.bs-placeholder:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder:hover{color:#999}.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-danger,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-danger:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-danger:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-danger:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-dark,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-dark:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-dark:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-dark:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-info,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-info:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-info:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-info:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-primary,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-primary:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-primary:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-primary:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-success,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-success:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-success:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-success:hover{color:rgba(255,255,255,.5)}.bootstrap-select>select{position:absolute!important;bottom:0;left:50%;display:block!important;width:.5px!important;height:100%!important;padding:0!important;opacity:0!important;border:none;z-index:0!important}.bootstrap-select>select.mobile-device{top:0;left:0;display:block!important;width:100%!important;z-index:2!important}.bootstrap-select.is-invalid .dropdown-toggle,.error .bootstrap-select .dropdown-toggle,.has-error .bootstrap-select .dropdown-toggle,.was-validated .bootstrap-select select:invalid+.dropdown-toggle{border-color:#b94a48}.bootstrap-select.is-valid .dropdown-toggle,.was-validated .bootstrap-select select:valid+.dropdown-toggle{border-color:#28a745}.bootstrap-select.fit-width{width:auto!important}.bootstrap-select:not([class*=col-]):not([class*=form-control]):not(.input-group-btn){width:220px}.bootstrap-select .dropdown-toggle:focus,.bootstrap-select>select.mobile-device:focus+.dropdown-toggle{outline:thin dotted #333!important;outline:5px auto -webkit-focus-ring-color!important;outline-offset:-2px}.bootstrap-select.form-control{margin-bottom:0;padding:0;border:none;height:auto}:not(.input-group)>.bootstrap-select.form-control:not([class*=col-]){width:100%}.bootstrap-select.form-control.input-group-btn{float:none;z-index:auto}.form-inline .bootstrap-select,.form-inline .bootstrap-select.form-control:not([class*=col-]){width:auto}.bootstrap-select:not(.input-group-btn),.bootstrap-select[class*=col-]{float:none;display:inline-block;margin-left:0}.bootstrap-select.dropdown-menu-right,.bootstrap-select[class*=col-].dropdown-menu-right,.row .bootstrap-select[class*=col-].dropdown-menu-right{float:right}.form-group .bootstrap-select,.form-horizontal .bootstrap-select,.form-inline .bootstrap-select{margin-bottom:0}.form-group-lg .bootstrap-select.form-control,.form-group-sm .bootstrap-select.form-control{padding:0}.form-group-lg .bootstrap-select.form-control .dropdown-toggle,.form-group-sm .bootstrap-select.form-control .dropdown-toggle{height:100%;font-size:inherit;line-height:inherit;border-radius:inherit}.bootstrap-select.form-control-lg .dropdown-toggle,.bootstrap-select.form-control-sm .dropdown-toggle{font-size:inherit;line-height:inherit;border-radius:inherit}.bootstrap-select.form-control-sm .dropdown-toggle{padding:.25rem .5rem}.bootstrap-select.form-control-lg .dropdown-toggle{padding:.5rem 1rem}.form-inline .bootstrap-select .form-control{width:100%}.bootstrap-select.disabled,.bootstrap-select>.disabled{cursor:not-allowed}.bootstrap-select.disabled:focus,.bootstrap-select>.disabled:focus{outline:0!important}.bootstrap-select.bs-container{position:absolute;top:0;left:0;height:0!important;padding:0!important}.bootstrap-select.bs-container .dropdown-menu{z-index:1060}.bootstrap-select .dropdown-toggle .filter-option{position:static;top:0;left:0;float:left;height:100%;width:100%;text-align:left;overflow:hidden;-webkit-box-flex:0;-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto}.bs3.bootstrap-select .dropdown-toggle .filter-option{padding-right:inherit}.input-group .bs3-has-addon.bootstrap-select .dropdown-toggle .filter-option{position:absolute;padding-top:inherit;padding-bottom:inherit;padding-left:inherit;float:none}.input-group .bs3-has-addon.bootstrap-select .dropdown-toggle .filter-option .filter-option-inner{padding-right:inherit}.bootstrap-select .dropdown-toggle .filter-option-inner-inner{overflow:hidden}.bootstrap-select .dropdown-toggle .filter-expand{width:0!important;float:left;opacity:0!important;overflow:hidden}.bootstrap-select .dropdown-toggle .caret{position:absolute;top:50%;right:12px;margin-top:-2px;vertical-align:middle}.input-group .bootstrap-select.form-control .dropdown-toggle{border-radius:inherit}.bootstrap-select[class*=col-] .dropdown-toggle{width:100%}.bootstrap-select .dropdown-menu{min-width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select .dropdown-menu>.inner:focus{outline:0!important}.bootstrap-select .dropdown-menu.inner{position:static;float:none;border:0;padding:0;margin:0;border-radius:0;-webkit-box-shadow:none;box-shadow:none}.bootstrap-select .dropdown-menu li{position:relative}.bootstrap-select .dropdown-menu li.active small{color:rgba(255,255,255,.5)!important}.bootstrap-select .dropdown-menu li.disabled a{cursor:not-allowed}.bootstrap-select .dropdown-menu li a{cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.bootstrap-select .dropdown-menu li a.opt{position:relative;padding-left:2.25em}.bootstrap-select .dropdown-menu li a span.check-mark{display:none}.bootstrap-select .dropdown-menu li a span.text{display:inline-block}.bootstrap-select .dropdown-menu li small{padding-left:.5em}.bootstrap-select .dropdown-menu .notify{position:absolute;bottom:5px;width:96%;margin:0 2%;min-height:26px;padding:3px 5px;background:#f5f5f5;border:1px solid #e3e3e3;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05);pointer-events:none;opacity:.9;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select .dropdown-menu .notify.fadeOut{-webkit-animation:.3s linear 750ms forwards bs-notify-fadeOut;-o-animation:.3s linear 750ms forwards bs-notify-fadeOut;animation:.3s linear 750ms forwards bs-notify-fadeOut}.bootstrap-select .no-results{padding:3px;background:#f5f5f5;margin:0 5px;white-space:nowrap}.bootstrap-select.fit-width .dropdown-toggle .filter-option{position:static;display:inline;padding:0}.bootstrap-select.fit-width .dropdown-toggle .filter-option-inner,.bootstrap-select.fit-width .dropdown-toggle .filter-option-inner-inner{display:inline}.bootstrap-select.fit-width .dropdown-toggle .bs-caret:before{content:'\00a0'}.bootstrap-select.fit-width .dropdown-toggle .caret{position:static;top:auto;margin-top:-1px}.bootstrap-select.show-tick .dropdown-menu .selected span.check-mark{position:absolute;display:inline-block;right:15px;top:5px}.bootstrap-select.show-tick .dropdown-menu li a span.text{margin-right:34px}.bootstrap-select .bs-ok-default:after{content:'';display:block;width:.5em;height:1em;border-style:solid;border-width:0 .26em .26em 0;-webkit-transform-style:preserve-3d;transform-style:preserve-3d;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);-o-transform:rotate(45deg);transform:rotate(45deg)}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle,.bootstrap-select.show-menu-arrow.show>.dropdown-toggle{z-index:1061}.bootstrap-select.show-menu-arrow .dropdown-toggle .filter-option:before{content:'';border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid rgba(204,204,204,.2);position:absolute;bottom:-4px;left:9px;display:none}.bootstrap-select.show-menu-arrow .dropdown-toggle .filter-option:after{content:'';border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;position:absolute;bottom:-4px;left:10px;display:none}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle .filter-option:before{bottom:auto;top:-4px;border-top:7px solid rgba(204,204,204,.2);border-bottom:0}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle .filter-option:after{bottom:auto;top:-4px;border-top:6px solid #fff;border-bottom:0}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle .filter-option:before{right:12px;left:auto}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle .filter-option:after{right:13px;left:auto}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle .filter-option:after,.bootstrap-select.show-menu-arrow.open>.dropdown-toggle .filter-option:before,.bootstrap-select.show-menu-arrow.show>.dropdown-toggle .filter-option:after,.bootstrap-select.show-menu-arrow.show>.dropdown-toggle .filter-option:before{display:block}.bs-actionsbox,.bs-donebutton,.bs-searchbox{padding:4px 8px}.bs-actionsbox{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bs-actionsbox .btn-group button{width:50%}.bs-donebutton{float:left;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bs-donebutton .btn-group button{width:100%}.bs-searchbox+.bs-actionsbox{padding:0 8px 4px}.bs-searchbox .form-control{margin-bottom:0;width:100%;float:none} \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.min.js b/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.min.js new file mode 100644 index 0000000..46cf10e --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.min.js @@ -0,0 +1,8 @@ +/*! + * Bootstrap-select v1.13.18 (https://developer.snapappointments.com/bootstrap-select) + * + * Copyright 2012-2020 SnapAppointments, LLC + * Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE) + */ + +!function(e,t){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return t(e)}):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(e.jQuery)}(this,function(e){!function(P){"use strict";var d=["sanitize","whiteList","sanitizeFn"],r=["background","cite","href","itemtype","longdesc","poster","src","xlink:href"],e={"*":["class","dir","id","lang","role","tabindex","style",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},l=/^(?:(?:https?|mailto|ftp|tel|file):|[^&:/?#]*(?:[/?#]|$))/gi,a=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+/]+=*$/i;function v(e,t){var i=e.nodeName.toLowerCase();if(-1!==P.inArray(i,t))return-1===P.inArray(i,r)||Boolean(e.nodeValue.match(l)||e.nodeValue.match(a));for(var s=P(t).filter(function(e,t){return t instanceof RegExp}),n=0,o=s.length;n]+>/g,"")),s&&(a=w(a)),a=a.toUpperCase(),o="contains"===i?0<=a.indexOf(t):a.startsWith(t)))break}return o}function N(e){return parseInt(e,10)||0}P.fn.triggerNative=function(e){var t,i=this[0];i.dispatchEvent?(u?t=new Event(e,{bubbles:!0}):(t=document.createEvent("Event")).initEvent(e,!0,!1),i.dispatchEvent(t)):i.fireEvent?((t=document.createEventObject()).eventType=e,i.fireEvent("on"+e,t)):this.trigger(e)};var f={"\xc0":"A","\xc1":"A","\xc2":"A","\xc3":"A","\xc4":"A","\xc5":"A","\xe0":"a","\xe1":"a","\xe2":"a","\xe3":"a","\xe4":"a","\xe5":"a","\xc7":"C","\xe7":"c","\xd0":"D","\xf0":"d","\xc8":"E","\xc9":"E","\xca":"E","\xcb":"E","\xe8":"e","\xe9":"e","\xea":"e","\xeb":"e","\xcc":"I","\xcd":"I","\xce":"I","\xcf":"I","\xec":"i","\xed":"i","\xee":"i","\xef":"i","\xd1":"N","\xf1":"n","\xd2":"O","\xd3":"O","\xd4":"O","\xd5":"O","\xd6":"O","\xd8":"O","\xf2":"o","\xf3":"o","\xf4":"o","\xf5":"o","\xf6":"o","\xf8":"o","\xd9":"U","\xda":"U","\xdb":"U","\xdc":"U","\xf9":"u","\xfa":"u","\xfb":"u","\xfc":"u","\xdd":"Y","\xfd":"y","\xff":"y","\xc6":"Ae","\xe6":"ae","\xde":"Th","\xfe":"th","\xdf":"ss","\u0100":"A","\u0102":"A","\u0104":"A","\u0101":"a","\u0103":"a","\u0105":"a","\u0106":"C","\u0108":"C","\u010a":"C","\u010c":"C","\u0107":"c","\u0109":"c","\u010b":"c","\u010d":"c","\u010e":"D","\u0110":"D","\u010f":"d","\u0111":"d","\u0112":"E","\u0114":"E","\u0116":"E","\u0118":"E","\u011a":"E","\u0113":"e","\u0115":"e","\u0117":"e","\u0119":"e","\u011b":"e","\u011c":"G","\u011e":"G","\u0120":"G","\u0122":"G","\u011d":"g","\u011f":"g","\u0121":"g","\u0123":"g","\u0124":"H","\u0126":"H","\u0125":"h","\u0127":"h","\u0128":"I","\u012a":"I","\u012c":"I","\u012e":"I","\u0130":"I","\u0129":"i","\u012b":"i","\u012d":"i","\u012f":"i","\u0131":"i","\u0134":"J","\u0135":"j","\u0136":"K","\u0137":"k","\u0138":"k","\u0139":"L","\u013b":"L","\u013d":"L","\u013f":"L","\u0141":"L","\u013a":"l","\u013c":"l","\u013e":"l","\u0140":"l","\u0142":"l","\u0143":"N","\u0145":"N","\u0147":"N","\u014a":"N","\u0144":"n","\u0146":"n","\u0148":"n","\u014b":"n","\u014c":"O","\u014e":"O","\u0150":"O","\u014d":"o","\u014f":"o","\u0151":"o","\u0154":"R","\u0156":"R","\u0158":"R","\u0155":"r","\u0157":"r","\u0159":"r","\u015a":"S","\u015c":"S","\u015e":"S","\u0160":"S","\u015b":"s","\u015d":"s","\u015f":"s","\u0161":"s","\u0162":"T","\u0164":"T","\u0166":"T","\u0163":"t","\u0165":"t","\u0167":"t","\u0168":"U","\u016a":"U","\u016c":"U","\u016e":"U","\u0170":"U","\u0172":"U","\u0169":"u","\u016b":"u","\u016d":"u","\u016f":"u","\u0171":"u","\u0173":"u","\u0174":"W","\u0175":"w","\u0176":"Y","\u0177":"y","\u0178":"Y","\u0179":"Z","\u017b":"Z","\u017d":"Z","\u017a":"z","\u017c":"z","\u017e":"z","\u0132":"IJ","\u0133":"ij","\u0152":"Oe","\u0153":"oe","\u0149":"'n","\u017f":"s"},m=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,g=RegExp("[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff\\u1ab0-\\u1aff\\u1dc0-\\u1dff]","g");function b(e){return f[e]}function w(e){return(e=e.toString())&&e.replace(m,b).replace(g,"")}var I,x,y,$,S=(I={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},x="(?:"+Object.keys(I).join("|")+")",y=RegExp(x),$=RegExp(x,"g"),function(e){return e=null==e?"":""+e,y.test(e)?e.replace($,E):e});function E(e){return I[e]}var C={32:" ",48:"0",49:"1",50:"2",51:"3",52:"4",53:"5",54:"6",55:"7",56:"8",57:"9",59:";",65:"A",66:"B",67:"C",68:"D",69:"E",70:"F",71:"G",72:"H",73:"I",74:"J",75:"K",76:"L",77:"M",78:"N",79:"O",80:"P",81:"Q",82:"R",83:"S",84:"T",85:"U",86:"V",87:"W",88:"X",89:"Y",90:"Z",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9"},A=27,L=13,D=32,H=9,B=38,R=40,M={success:!1,major:"3"};try{M.full=(P.fn.dropdown.Constructor.VERSION||"").split(" ")[0].split("."),M.major=M.full[0],M.success=!0}catch(e){}var U=0,j=".bs.select",V={DISABLED:"disabled",DIVIDER:"divider",SHOW:"open",DROPUP:"dropup",MENU:"dropdown-menu",MENURIGHT:"dropdown-menu-right",MENULEFT:"dropdown-menu-left",BUTTONCLASS:"btn-default",POPOVERHEADER:"popover-title",ICONBASE:"glyphicon",TICKICON:"glyphicon-ok"},F={MENU:"."+V.MENU},_={div:document.createElement("div"),span:document.createElement("span"),i:document.createElement("i"),subtext:document.createElement("small"),a:document.createElement("a"),li:document.createElement("li"),whitespace:document.createTextNode("\xa0"),fragment:document.createDocumentFragment()};_.noResults=_.li.cloneNode(!1),_.noResults.className="no-results",_.a.setAttribute("role","option"),_.a.className="dropdown-item",_.subtext.className="text-muted",_.text=_.span.cloneNode(!1),_.text.className="text",_.checkMark=_.span.cloneNode(!1);var G=new RegExp(B+"|"+R),q=new RegExp("^"+H+"$|"+A),K={li:function(e,t,i){var s=_.li.cloneNode(!1);return e&&(1===e.nodeType||11===e.nodeType?s.appendChild(e):s.innerHTML=e),void 0!==t&&""!==t&&(s.className=t),null!=i&&s.classList.add("optgroup-"+i),s},a:function(e,t,i){var s=_.a.cloneNode(!0);return e&&(11===e.nodeType?s.appendChild(e):s.insertAdjacentHTML("beforeend",e)),void 0!==t&&""!==t&&s.classList.add.apply(s.classList,t.split(/\s+/)),i&&s.setAttribute("style",i),s},text:function(e,t){var i,s,n=_.text.cloneNode(!1);if(e.content)n.innerHTML=e.content;else{if(n.textContent=e.text,e.icon){var o=_.whitespace.cloneNode(!1);(s=(!0===t?_.i:_.span).cloneNode(!1)).className=this.options.iconBase+" "+e.icon,_.fragment.appendChild(s),_.fragment.appendChild(o)}e.subtext&&((i=_.subtext.cloneNode(!1)).textContent=e.subtext,n.appendChild(i))}if(!0===t)for(;0'},maxOptions:!1,mobile:!1,selectOnTab:!1,dropdownAlignRight:!1,windowPadding:0,virtualScroll:600,display:!1,sanitize:!0,sanitizeFn:null,whiteList:e},Y.prototype={constructor:Y,init:function(){var i=this,e=this.$element.attr("id"),t=this.$element[0],s=t.form;U++,this.selectId="bs-select-"+U,t.classList.add("bs-select-hidden"),this.multiple=this.$element.prop("multiple"),this.autofocus=this.$element.prop("autofocus"),t.classList.contains("show-tick")&&(this.options.showTick=!0),this.$newElement=this.createDropdown(),this.buildData(),this.$element.after(this.$newElement).prependTo(this.$newElement),s&&null===t.form&&(s.id||(s.id="form-"+this.selectId),t.setAttribute("form",s.id)),this.$button=this.$newElement.children("button"),this.$menu=this.$newElement.children(F.MENU),this.$menuInner=this.$menu.children(".inner"),this.$searchbox=this.$menu.find("input"),t.classList.remove("bs-select-hidden"),!0===this.options.dropdownAlignRight&&this.$menu[0].classList.add(V.MENURIGHT),void 0!==e&&this.$button.attr("data-id",e),this.checkDisabled(),this.clickListener(),this.options.liveSearch?(this.liveSearchListener(),this.focusedParent=this.$searchbox[0]):this.focusedParent=this.$menuInner[0],this.setStyle(),this.render(),this.setWidth(),this.options.container?this.selectPosition():this.$element.on("hide"+j,function(){if(i.isVirtual()){var e=i.$menuInner[0],t=e.firstChild.cloneNode(!1);e.replaceChild(t,e.firstChild),e.scrollTop=0}}),this.$menu.data("this",this),this.$newElement.data("this",this),this.options.mobile&&this.mobile(),this.$newElement.on({"hide.bs.dropdown":function(e){i.$element.trigger("hide"+j,e)},"hidden.bs.dropdown":function(e){i.$element.trigger("hidden"+j,e)},"show.bs.dropdown":function(e){i.$element.trigger("show"+j,e)},"shown.bs.dropdown":function(e){i.$element.trigger("shown"+j,e)}}),t.hasAttribute("required")&&this.$element.on("invalid"+j,function(){i.$button[0].classList.add("bs-invalid"),i.$element.on("shown"+j+".invalid",function(){i.$element.val(i.$element.val()).off("shown"+j+".invalid")}).on("rendered"+j,function(){this.validity.valid&&i.$button[0].classList.remove("bs-invalid"),i.$element.off("rendered"+j)}),i.$button.on("blur"+j,function(){i.$element.trigger("focus").trigger("blur"),i.$button.off("blur"+j)})}),setTimeout(function(){i.buildList(),i.$element.trigger("loaded"+j)})},createDropdown:function(){var e=this.multiple||this.options.showTick?" show-tick":"",t=this.multiple?' aria-multiselectable="true"':"",i="",s=this.autofocus?" autofocus":"";M.major<4&&this.$element.parent().hasClass("input-group")&&(i=" input-group-btn");var n,o="",r="",l="",a="";return this.options.header&&(o='
    '+this.options.header+"
    "),this.options.liveSearch&&(r=''),this.multiple&&this.options.actionsBox&&(l='
    "),this.multiple&&this.options.doneButton&&(a='
    "),n='",P(n)},setPositionData:function(){this.selectpicker.view.canHighlight=[],this.selectpicker.view.size=0,this.selectpicker.view.firstHighlightIndex=!1;for(var e=0;e=this.options.virtualScroll||!0===this.options.virtualScroll},createView:function(N,e,t){var A,L,D=this,i=0,H=[];if(this.selectpicker.isSearching=N,this.selectpicker.current=N?this.selectpicker.search:this.selectpicker.main,this.setPositionData(),e)if(t)i=this.$menuInner[0].scrollTop;else if(!D.multiple){var s=D.$element[0],n=(s.options[s.selectedIndex]||{}).liIndex;if("number"==typeof n&&!1!==D.options.size){var o=D.selectpicker.main.data[n],r=o&&o.position;r&&(i=r-(D.sizeInfo.menuInnerHeight+D.sizeInfo.liHeight)/2)}}function l(e,t){var i,s,n,o,r,l,a,c,d=D.selectpicker.current.elements.length,h=[],p=!0,u=D.isVirtual();D.selectpicker.view.scrollTop=e,i=Math.ceil(D.sizeInfo.menuInnerHeight/D.sizeInfo.liHeight*1.5),s=Math.round(d/i)||1;for(var f=0;fd-1?0:D.selectpicker.current.data[d-1].position-D.selectpicker.current.data[D.selectpicker.view.position1-1].position,b.firstChild.style.marginTop=v+"px",b.firstChild.style.marginBottom=g+"px"):(b.firstChild.style.marginTop=0,b.firstChild.style.marginBottom=0),b.firstChild.appendChild(w),!0===u&&D.sizeInfo.hasScrollBar){var C=b.firstChild.offsetWidth;if(t&&CD.sizeInfo.selectWidth)b.firstChild.style.minWidth=D.sizeInfo.menuInnerInnerWidth+"px";else if(C>D.sizeInfo.menuInnerInnerWidth){D.$menu[0].style.minWidth=0;var O=b.firstChild.offsetWidth;O>D.sizeInfo.menuInnerInnerWidth&&(D.sizeInfo.menuInnerInnerWidth=O,b.firstChild.style.minWidth=D.sizeInfo.menuInnerInnerWidth+"px"),D.$menu[0].style.minWidth=""}}}if(D.prevActiveIndex=D.activeIndex,D.options.liveSearch){if(N&&t){var z,T=0;D.selectpicker.view.canHighlight[T]||(T=1+D.selectpicker.view.canHighlight.slice(1).indexOf(!0)),z=D.selectpicker.view.visibleElements[T],D.defocusItem(D.selectpicker.view.currentActive),D.activeIndex=(D.selectpicker.current.data[T]||{}).index,D.focusItem(z)}}else D.$menuInner.trigger("focus")}l(i,!0),this.$menuInner.off("scroll.createView").on("scroll.createView",function(e,t){D.noScroll||l(this.scrollTop,t),D.noScroll=!1}),P(window).off("resize"+j+"."+this.selectId+".createView").on("resize"+j+"."+this.selectId+".createView",function(){D.$newElement.hasClass(V.SHOW)&&l(D.$menuInner[0].scrollTop)})},focusItem:function(e,t,i){if(e){t=t||this.selectpicker.main.data[this.activeIndex];var s=e.firstChild;s&&(s.setAttribute("aria-setsize",this.selectpicker.view.size),s.setAttribute("aria-posinset",t.posinset),!0!==i&&(this.focusedParent.setAttribute("aria-activedescendant",s.id),e.classList.add("active"),s.classList.add("active")))}},defocusItem:function(e){e&&(e.classList.remove("active"),e.firstChild&&e.firstChild.classList.remove("active"))},setPlaceholder:function(){var e=this,t=!1;if(this.options.title&&!this.multiple){this.selectpicker.view.titleOption||(this.selectpicker.view.titleOption=document.createElement("option")),t=!0;var i=this.$element[0],s=!1,n=!this.selectpicker.view.titleOption.parentNode,o=i.selectedIndex,r=i.options[o],l=window.performance&&window.performance.getEntriesByType("navigation"),a=l&&l.length?"back_forward"!==l[0].type:2!==window.performance.navigation.type;n&&(this.selectpicker.view.titleOption.className="bs-title-option",this.selectpicker.view.titleOption.value="",s=!r||0===o&&!1===r.defaultSelected&&void 0===this.$element.data("selected")),!n&&0===this.selectpicker.view.titleOption.index||i.insertBefore(this.selectpicker.view.titleOption,i.firstChild),s&&a?i.selectedIndex=0:"complete"!==document.readyState&&window.addEventListener("pageshow",function(){e.selectpicker.view.displayedValue!==i.value&&e.render()})}return t},buildData:function(){var p=':not([hidden]):not([data-hidden="true"])',u=[],f=0,m=this.setPlaceholder()?1:0;this.options.hideDisabled&&(p+=":not(:disabled)");var e=this.$element[0].querySelectorAll("select > *"+p);function v(e){var t=u[u.length-1];t&&"divider"===t.type&&(t.optID||e.optID)||((e=e||{}).type="divider",u.push(e))}function g(e,t){if((t=t||{}).divider="true"===e.getAttribute("data-divider"),t.divider)v({optID:t.optID});else{var i=u.length,s=e.style.cssText,n=s?S(s):"",o=(e.className||"")+(t.optgroupClass||"");t.optID&&(o="opt "+o),t.optionClass=o.trim(),t.inlineStyle=n,t.text=e.textContent,t.content=e.getAttribute("data-content"),t.tokens=e.getAttribute("data-tokens"),t.subtext=e.getAttribute("data-subtext"),t.icon=e.getAttribute("data-icon"),e.liIndex=i,t.display=t.content||t.text,t.type="option",t.index=i,t.option=e,t.selected=!!e.selected,t.disabled=t.disabled||!!e.disabled,u.push(t)}}function t(e,t){var i=t[e],s=!(e-1 li")},render:function(){var e,t=this,i=this.$element[0],s=this.setPlaceholder()&&0===i.selectedIndex,n=O(i,this.options.hideDisabled),o=n.length,r=this.$button[0],l=r.querySelector(".filter-option-inner-inner"),a=document.createTextNode(this.options.multipleSeparator),c=_.fragment.cloneNode(!1),d=!1;if(r.classList.toggle("bs-placeholder",t.multiple?!o:!z(i,n)),t.multiple||1!==n.length||(t.selectpicker.view.displayedValue=z(i,n)),"static"===this.options.selectedTextFormat)c=K.text.call(this,{text:this.options.title},!0);else if(!1===(this.multiple&&-1!==this.options.selectedTextFormat.indexOf("count")&&1")).length&&o>e[1]||1===e.length&&2<=o))){if(!s){for(var h=0;h option"+m+", optgroup"+m+" option"+m).length,g="function"==typeof this.options.countSelectedText?this.options.countSelectedText(o,v):this.options.countSelectedText;c=K.text.call(this,{text:g.replace("{0}",o.toString()).replace("{1}",v.toString())},!0)}if(null==this.options.title&&(this.options.title=this.$element.attr("title")),c.childNodes.length||(c=K.text.call(this,{text:void 0!==this.options.title?this.options.title:this.options.noneSelectedText},!0)),r.title=c.textContent.replace(/<[^>]*>?/g,"").trim(),this.options.sanitize&&d&&W([c],t.options.whiteList,t.options.sanitizeFn),l.innerHTML="",l.appendChild(c),M.major<4&&this.$newElement[0].classList.contains("bs3-has-addon")){var b=r.querySelector(".filter-expand"),w=l.cloneNode(!0);w.className="filter-expand",b?r.replaceChild(w,b):r.appendChild(w)}this.$element.trigger("rendered"+j)},setStyle:function(e,t){var i,s=this.$button[0],n=this.$newElement[0],o=this.options.style.trim();this.$element.attr("class")&&this.$newElement.addClass(this.$element.attr("class").replace(/selectpicker|mobile-device|bs-select-hidden|validate\[.*\]/gi,"")),M.major<4&&(n.classList.add("bs3"),n.parentNode.classList&&n.parentNode.classList.contains("input-group")&&(n.previousElementSibling||n.nextElementSibling)&&(n.previousElementSibling||n.nextElementSibling).classList.contains("input-group-addon")&&n.classList.add("bs3-has-addon")),i=e?e.trim():o,"add"==t?i&&s.classList.add.apply(s.classList,i.split(" ")):"remove"==t?i&&s.classList.remove.apply(s.classList,i.split(" ")):(o&&s.classList.remove.apply(s.classList,o.split(" ")),i&&s.classList.add.apply(s.classList,i.split(" ")))},liHeight:function(e){if(e||!1!==this.options.size&&!Object.keys(this.sizeInfo).length){var t,i=_.div.cloneNode(!1),s=_.div.cloneNode(!1),n=_.div.cloneNode(!1),o=document.createElement("ul"),r=_.li.cloneNode(!1),l=_.li.cloneNode(!1),a=_.a.cloneNode(!1),c=_.span.cloneNode(!1),d=this.options.header&&0this.sizeInfo.menuExtras.vert&&l+this.sizeInfo.menuExtras.vert+50>this.sizeInfo.selectOffsetBot,!0===this.selectpicker.isSearching&&(a=this.selectpicker.dropup),this.$newElement.toggleClass(V.DROPUP,a),this.selectpicker.dropup=a),"auto"===this.options.size)n=3this.options.size){for(var b=0;bthis.sizeInfo.menuInnerHeight&&(this.sizeInfo.hasScrollBar=!0,this.sizeInfo.totalMenuWidth=this.sizeInfo.menuWidth+this.sizeInfo.scrollBarWidth),"auto"===this.options.dropdownAlignRight&&this.$menu.toggleClass(V.MENURIGHT,this.sizeInfo.selectOffsetLeft>this.sizeInfo.selectOffsetRight&&this.sizeInfo.selectOffsetRightthis.options.size&&i.off("resize"+j+"."+this.selectId+".setMenuSize scroll"+j+"."+this.selectId+".setMenuSize")}this.createView(!1,!0,e)},setWidth:function(){var i=this;"auto"===this.options.width?requestAnimationFrame(function(){i.$menu.css("min-width","0"),i.$element.on("loaded"+j,function(){i.liHeight(),i.setMenuSize();var e=i.$newElement.clone().appendTo("body"),t=e.css("width","auto").children("button").outerWidth();e.remove(),i.sizeInfo.selectWidth=Math.max(i.sizeInfo.totalMenuWidth,t),i.$newElement.css("width",i.sizeInfo.selectWidth+"px")})}):"fit"===this.options.width?(this.$menu.css("min-width",""),this.$newElement.css("width","").addClass("fit-width")):this.options.width?(this.$menu.css("min-width",""),this.$newElement.css("width",this.options.width)):(this.$menu.css("min-width",""),this.$newElement.css("width","")),this.$newElement.hasClass("fit-width")&&"fit"!==this.options.width&&this.$newElement[0].classList.remove("fit-width")},selectPosition:function(){this.$bsContainer=P('
    ');function e(e){var t={},i=r.options.display||!!P.fn.dropdown.Constructor.Default&&P.fn.dropdown.Constructor.Default.display;r.$bsContainer.addClass(e.attr("class").replace(/form-control|fit-width/gi,"")).toggleClass(V.DROPUP,e.hasClass(V.DROPUP)),s=e.offset(),l.is("body")?n={top:0,left:0}:((n=l.offset()).top+=parseInt(l.css("borderTopWidth"))-l.scrollTop(),n.left+=parseInt(l.css("borderLeftWidth"))-l.scrollLeft()),o=e.hasClass(V.DROPUP)?0:e[0].offsetHeight,(M.major<4||"static"===i)&&(t.top=s.top-n.top+o,t.left=s.left-n.left),t.width=e[0].offsetWidth,r.$bsContainer.css(t)}var s,n,o,r=this,l=P(this.options.container);this.$button.on("click.bs.dropdown.data-api",function(){r.isDisabled()||(e(r.$newElement),r.$bsContainer.appendTo(r.options.container).toggleClass(V.SHOW,!r.$button.hasClass(V.SHOW)).append(r.$menu))}),P(window).off("resize"+j+"."+this.selectId+" scroll"+j+"."+this.selectId).on("resize"+j+"."+this.selectId+" scroll"+j+"."+this.selectId,function(){r.$newElement.hasClass(V.SHOW)&&e(r.$newElement)}),this.$element.on("hide"+j,function(){r.$menu.data("height",r.$menu.height()),r.$bsContainer.detach()})},setOptionStatus:function(e){var t=this;if(t.noScroll=!1,t.selectpicker.view.visibleElements&&t.selectpicker.view.visibleElements.length)for(var i=0;i
    ');y[2]&&($=$.replace("{var}",y[2][1"+$+"
    ")),d=!1,C.$element.trigger("maxReached"+j)),g&&w&&(E.append(P("
    "+S+"
    ")),d=!1,C.$element.trigger("maxReachedGrp"+j)),setTimeout(function(){C.setSelected(r,!1)},10),E[0].classList.add("fadeOut"),setTimeout(function(){E.remove()},1050)}}}else c&&(c.selected=!1),h.selected=!0,C.setSelected(r,!0);!C.multiple||C.multiple&&1===C.options.maxOptions?C.$button.trigger("focus"):C.options.liveSearch&&C.$searchbox.trigger("focus"),d&&(!C.multiple&&a===s.selectedIndex||(T=[h.index,p.prop("selected"),l],C.$element.triggerNative("change")))}}),this.$menu.on("click","li."+V.DISABLED+" a, ."+V.POPOVERHEADER+", ."+V.POPOVERHEADER+" :not(.close)",function(e){e.currentTarget==this&&(e.preventDefault(),e.stopPropagation(),C.options.liveSearch&&!P(e.target).hasClass("close")?C.$searchbox.trigger("focus"):C.$button.trigger("focus"))}),this.$menuInner.on("click",".divider, .dropdown-header",function(e){e.preventDefault(),e.stopPropagation(),C.options.liveSearch?C.$searchbox.trigger("focus"):C.$button.trigger("focus")}),this.$menu.on("click","."+V.POPOVERHEADER+" .close",function(){C.$button.trigger("click")}),this.$searchbox.on("click",function(e){e.stopPropagation()}),this.$menu.on("click",".actions-btn",function(e){C.options.liveSearch?C.$searchbox.trigger("focus"):C.$button.trigger("focus"),e.preventDefault(),e.stopPropagation(),P(this).hasClass("bs-select-all")?C.selectAll():C.deselectAll()}),this.$button.on("focus"+j,function(e){var t=C.$element[0].getAttribute("tabindex");void 0!==t&&e.originalEvent&&e.originalEvent.isTrusted&&(this.setAttribute("tabindex",t),C.$element[0].setAttribute("tabindex",-1),C.selectpicker.view.tabindex=t)}).on("blur"+j,function(e){void 0!==C.selectpicker.view.tabindex&&e.originalEvent&&e.originalEvent.isTrusted&&(C.$element[0].setAttribute("tabindex",C.selectpicker.view.tabindex),this.setAttribute("tabindex",-1),C.selectpicker.view.tabindex=void 0)}),this.$element.on("change"+j,function(){C.render(),C.$element.trigger("changed"+j,T),T=null}).on("focus"+j,function(){C.options.mobile||C.$button[0].focus()})},liveSearchListener:function(){var u=this;this.$button.on("click.bs.dropdown.data-api",function(){u.$searchbox.val()&&(u.$searchbox.val(""),u.selectpicker.search.previousValue=void 0)}),this.$searchbox.on("click.bs.dropdown.data-api focus.bs.dropdown.data-api touchend.bs.dropdown.data-api",function(e){e.stopPropagation()}),this.$searchbox.on("input propertychange",function(){var e=u.$searchbox[0].value;if(u.selectpicker.search.elements=[],u.selectpicker.search.data=[],e){var t=[],i=e.toUpperCase(),s={},n=[],o=u._searchStyle(),r=u.options.liveSearchNormalize;r&&(i=w(i));for(var l=0;l=a.selectpicker.view.canHighlight.length&&(t=a.selectpicker.view.firstHighlightIndex),a.selectpicker.view.canHighlight[t+f]||(t=t+1+a.selectpicker.view.canHighlight.slice(t+f+1).indexOf(!0))),e.preventDefault();var m=f+t;e.which===B?0===f&&t===c.length-1?(a.$menuInner[0].scrollTop=a.$menuInner[0].scrollHeight,m=a.selectpicker.current.elements.length-1):d=(o=(n=a.selectpicker.current.data[m]).position-n.height)u+a.sizeInfo.menuInnerHeight),s=a.selectpicker.main.elements[v],a.activeIndex=b[x],a.focusItem(s),s&&s.firstChild.focus(),d&&(a.$menuInner[0].scrollTop=o),r.trigger("focus")}}i&&(e.which===D&&!a.selectpicker.keydown.keyHistory||e.which===L||e.which===H&&a.options.selectOnTab)&&(e.which!==D&&e.preventDefault(),a.options.liveSearch&&e.which===D||(a.$menuInner.find(".active a").trigger("click",!0),r.trigger("focus"),a.options.liveSearch||(e.preventDefault(),P(document).data("spaceSelect",!0))))}},mobile:function(){this.options.mobile=!0,this.$element[0].classList.add("mobile-device")},refresh:function(){var e=P.extend({},this.options,this.$element.data());this.options=e,this.checkDisabled(),this.buildData(),this.setStyle(),this.render(),this.buildList(),this.setWidth(),this.setSize(!0),this.$element.trigger("refreshed"+j)},hide:function(){this.$newElement.hide()},show:function(){this.$newElement.show()},remove:function(){this.$newElement.remove(),this.$element.remove()},destroy:function(){this.$newElement.before(this.$element).remove(),this.$bsContainer?this.$bsContainer.remove():this.$menu.remove(),this.selectpicker.view.titleOption&&this.selectpicker.view.titleOption.parentNode&&this.selectpicker.view.titleOption.parentNode.removeChild(this.selectpicker.view.titleOption),this.$element.off(j).removeData("selectpicker").removeClass("bs-select-hidden selectpicker"),P(window).off(j+"."+this.selectId)}};var J=P.fn.selectpicker;function Q(){if(P.fn.dropdown)return(P.fn.dropdown.Constructor._dataApiKeydownHandler||P.fn.dropdown.Constructor.prototype.keydown).apply(this,arguments)}P.fn.selectpicker=Z,P.fn.selectpicker.Constructor=Y,P.fn.selectpicker.noConflict=function(){return P.fn.selectpicker=J,this},P(document).off("keydown.bs.dropdown.data-api").on("keydown.bs.dropdown.data-api",':not(.bootstrap-select) > [data-toggle="dropdown"]',Q).on("keydown.bs.dropdown.data-api",":not(.bootstrap-select) > .dropdown-menu",Q).on("keydown"+j,'.bootstrap-select [data-toggle="dropdown"], .bootstrap-select [role="listbox"], .bootstrap-select .bs-searchbox input',Y.prototype.keydown).on("focusin.modal",'.bootstrap-select [data-toggle="dropdown"], .bootstrap-select [role="listbox"], .bootstrap-select .bs-searchbox input',function(e){e.stopPropagation()}),P(window).on("load"+j+".data-api",function(){P(".selectpicker").each(function(){var e=P(this);Z.call(e,e.data())})})}(e)}); \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/bootstrap-table.min.css b/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/bootstrap-table.min.css new file mode 100644 index 0000000..7e150cf --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/bootstrap-table.min.css @@ -0,0 +1,6 @@ +/** + * @author zhixin wen + * version: 1.19.1 + * https://github.com/wenzhixin/bootstrap-table/ + */ +.bootstrap-table .fixed-table-toolbar::after{content:"";display:block;clear:both}.bootstrap-table .fixed-table-toolbar .bs-bars,.bootstrap-table .fixed-table-toolbar .columns,.bootstrap-table .fixed-table-toolbar .search{position:relative;margin-top:10px;margin-bottom:10px}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group{display:inline-block;margin-left:-1px!important}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group>.btn{border-radius:0}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group:first-child>.btn{border-top-left-radius:4px;border-bottom-left-radius:4px}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group:last-child>.btn{border-top-right-radius:4px;border-bottom-right-radius:4px}.bootstrap-table .fixed-table-toolbar .columns .dropdown-menu{text-align:left;max-height:300px;overflow:auto;-ms-overflow-style:scrollbar;z-index:1001}.bootstrap-table .fixed-table-toolbar .columns label{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.428571429}.bootstrap-table .fixed-table-toolbar .columns-left{margin-right:5px}.bootstrap-table .fixed-table-toolbar .columns-right{margin-left:5px}.bootstrap-table .fixed-table-toolbar .pull-right .dropdown-menu{right:0;left:auto}.bootstrap-table .fixed-table-container{position:relative;clear:both}.bootstrap-table .fixed-table-container .table{width:100%;margin-bottom:0!important}.bootstrap-table .fixed-table-container .table td,.bootstrap-table .fixed-table-container .table th{vertical-align:middle;box-sizing:border-box}.bootstrap-table .fixed-table-container .table thead th{vertical-align:bottom;padding:0;margin:0}.bootstrap-table .fixed-table-container .table thead th:focus{outline:0 solid transparent}.bootstrap-table .fixed-table-container .table thead th.detail{width:30px}.bootstrap-table .fixed-table-container .table thead th .th-inner{padding:.75rem;vertical-align:bottom;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.bootstrap-table .fixed-table-container .table thead th .sortable{cursor:pointer;background-position:right;background-repeat:no-repeat;padding-right:30px!important}.bootstrap-table .fixed-table-container .table thead th .both{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAQAAADYWf5HAAAAkElEQVQoz7X QMQ5AQBCF4dWQSJxC5wwax1Cq1e7BAdxD5SL+Tq/QCM1oNiJidwox0355mXnG/DrEtIQ6azioNZQxI0ykPhTQIwhCR+BmBYtlK7kLJYwWCcJA9M4qdrZrd8pPjZWPtOqdRQy320YSV17OatFC4euts6z39GYMKRPCTKY9UnPQ6P+GtMRfGtPnBCiqhAeJPmkqAAAAAElFTkSuQmCC")}.bootstrap-table .fixed-table-container .table thead th .asc{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZ0lEQVQ4y2NgGLKgquEuFxBPAGI2ahhWCsS/gDibUoO0gPgxEP8H4ttArEyuQYxAPBdqEAxPBImTY5gjEL9DM+wTENuQahAvEO9DMwiGdwAxOymGJQLxTyD+jgWDxCMZRsEoGAVoAADeemwtPcZI2wAAAABJRU5ErkJggg==")}.bootstrap-table .fixed-table-container .table thead th .desc{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZUlEQVQ4y2NgGAWjYBSggaqGu5FA/BOIv2PBIPFEUgxjB+IdQPwfC94HxLykus4GiD+hGfQOiB3J8SojEE9EM2wuSJzcsFMG4ttQgx4DsRalkZENxL+AuJQaMcsGxBOAmGvopk8AVz1sLZgg0bsAAAAASUVORK5CYII= ")}.bootstrap-table .fixed-table-container .table tbody tr.selected td{background-color:rgba(0,0,0,.075)}.bootstrap-table .fixed-table-container .table tbody tr.no-records-found td{text-align:center}.bootstrap-table .fixed-table-container .table tbody tr .card-view{display:flex}.bootstrap-table .fixed-table-container .table tbody tr .card-view .card-view-title{font-weight:700;display:inline-block;min-width:30%;width:auto!important;text-align:left!important}.bootstrap-table .fixed-table-container .table tbody tr .card-view .card-view-value{width:100%!important}.bootstrap-table .fixed-table-container .table .bs-checkbox{text-align:center}.bootstrap-table .fixed-table-container .table .bs-checkbox label{margin-bottom:0}.bootstrap-table .fixed-table-container .table .bs-checkbox label input[type=checkbox],.bootstrap-table .fixed-table-container .table .bs-checkbox label input[type=radio]{margin:0 auto!important}.bootstrap-table .fixed-table-container .table.table-sm .th-inner{padding:.3rem}.bootstrap-table .fixed-table-container.fixed-height:not(.has-footer){border-bottom:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height.has-card-view{border-top:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height .fixed-table-border{border-left:1px solid #dee2e6;border-right:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height .table thead th{border-bottom:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height .table-dark thead th{border-bottom:1px solid #32383e}.bootstrap-table .fixed-table-container .fixed-table-header{overflow:hidden}.bootstrap-table .fixed-table-container .fixed-table-body{overflow-x:auto;overflow-y:auto;height:100%}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading{align-items:center;background:#fff;display:flex;justify-content:center;position:absolute;bottom:0;width:100%;max-width:100%;z-index:1000;transition:visibility 0s,opacity .15s ease-in-out;opacity:0;visibility:hidden}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.open{visibility:visible;opacity:1}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap{align-items:baseline;display:flex;justify-content:center}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .loading-text{margin-right:6px}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap{align-items:center;display:flex;justify-content:center}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-dot,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::after,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::before{content:"";animation-duration:1.5s;animation-iteration-count:infinite;animation-name:LOADING;background:#212529;border-radius:50%;display:block;height:5px;margin:0 4px;opacity:0;width:5px}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-dot{animation-delay:.3s}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::after{animation-delay:.6s}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark{background:#212529}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-dot,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-wrap::after,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-wrap::before{background:#fff}.bootstrap-table .fixed-table-container .fixed-table-footer{overflow:hidden}.bootstrap-table .fixed-table-pagination::after{content:"";display:block;clear:both}.bootstrap-table .fixed-table-pagination>.pagination,.bootstrap-table .fixed-table-pagination>.pagination-detail{margin-top:10px;margin-bottom:10px}.bootstrap-table .fixed-table-pagination>.pagination-detail .pagination-info{line-height:34px;margin-right:5px}.bootstrap-table .fixed-table-pagination>.pagination-detail .page-list{display:inline-block}.bootstrap-table .fixed-table-pagination>.pagination-detail .page-list .btn-group{position:relative;display:inline-block;vertical-align:middle}.bootstrap-table .fixed-table-pagination>.pagination-detail .page-list .btn-group .dropdown-menu{margin-bottom:0}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination{margin:0}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.page-intermediate a{color:#c8c8c8}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.page-intermediate a::before{content:'\2B05'}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.page-intermediate a::after{content:'\27A1'}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.disabled a{pointer-events:none;cursor:default}.bootstrap-table.fullscreen{position:fixed;top:0;left:0;z-index:1050;width:100%!important;background:#fff;height:calc(100vh);overflow-y:scroll}.bootstrap-table.bootstrap4 .pagination-lg .page-link,.bootstrap-table.bootstrap5 .pagination-lg .page-link{padding:.5rem 1rem}.bootstrap-table.bootstrap5 .float-left{float:left}.bootstrap-table.bootstrap5 .float-right{float:right}div.fixed-table-scroll-inner{width:100%;height:200px}div.fixed-table-scroll-outer{top:0;left:0;visibility:hidden;width:200px;height:150px;overflow:hidden}@keyframes LOADING{0%{opacity:0}50%{opacity:1}to{opacity:0}} \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/bootstrap-table.min.js b/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/bootstrap-table.min.js new file mode 100644 index 0000000..ac557c8 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/bootstrap-table.min.js @@ -0,0 +1,6 @@ +/** + * @author zhixin wen + * version: 1.19.1 + * https://github.com/wenzhixin/bootstrap-table/ + */ +function getRememberRowIds(t,e){return $.isArray(t)?props=$.map(t,function(t){return t[e]}):props=[t[e]],props}function addRememberRow(t,e){var i=null==table.options.uniqueId?table.options.columns[1].field:table.options.uniqueId,n=getRememberRowIds(t,i);-1==$.inArray(e[i],n)&&(t[t.length]=e)}function removeRememberRow(t,e){var i=null==table.options.uniqueId?table.options.columns[1].field:table.options.uniqueId,n=getRememberRowIds(t,i),o=$.inArray(e[i],n);-1!=o&&t.splice(o,1)}!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("jquery")):"function"==typeof define&&define.amd?define(["jquery"],e):(t="undefined"!=typeof globalThis?globalThis:t||self,t.BootstrapTable=e(t.jQuery))}(this,function(t){function e(t){return t&&"object"==typeof t&&"default" in t?t:{"default":t}}function i(t){return(i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function n(t,e){if(!(t instanceof e)){throw new TypeError("Cannot call a class as a function")}}function o(t,e){for(var i=0;it.length)&&(e=t.length);for(var i=0,n=Array(e);e>i;i++){n[i]=t[i]}return n}function p(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function g(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function v(t,e){var i;if("undefined"==typeof Symbol||null==t[Symbol.iterator]){if(Array.isArray(t)||(i=d(t))||e&&t&&"number"==typeof t.length){i&&(t=i);var n=0,o=function(){};return{s:o,n:function(){return n>=t.length?{done:!0}:{done:!1,value:t[n++]}},e:function(t){throw t},f:o}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var a,s=!0,r=!1;return{s:function(){i=t[Symbol.iterator]()},n:function(){var t=i.next();return s=t.done,t},e:function(t){r=!0,a=t},f:function(){try{s||null==i["return"]||i["return"]()}finally{if(r){throw a}}}}}function b(t,e){return e={exports:{}},t(e,e.exports),e.exports}function m(t,e){return RegExp(t,e)}var y=e(t),w="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},S=function(t){return t&&t.Math==Math&&t},x=S("object"==typeof globalThis&&globalThis)||S("object"==typeof window&&window)||S("object"==typeof self&&self)||S("object"==typeof w&&w)||function(){return this}()||Function("return this")(),k=function(t){try{return !!t()}catch(e){return !0}},O=!k(function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]}),T={}.propertyIsEnumerable,C=Object.getOwnPropertyDescriptor,P=C&&!T.call({1:2},1),I=P?function(t){var e=C(this,t);return !!e&&e.enumerable}:T,A={f:I},$=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}},R={}.toString,E=function(t){return R.call(t).slice(8,-1)},j="".split,_=k(function(){return !Object("z").propertyIsEnumerable(0)})?function(t){return"String"==E(t)?j.call(t,""):Object(t)}:Object,N=function(t){if(void 0==t){throw TypeError("Can't call method on "+t)}return t},F=function(t){return _(N(t))},D=function(t){return"object"==typeof t?null!==t:"function"==typeof t},V=function(t,e){if(!D(t)){return t}var i,n;if(e&&"function"==typeof(i=t.toString)&&!D(n=i.call(t))){return n}if("function"==typeof(i=t.valueOf)&&!D(n=i.call(t))){return n}if(!e&&"function"==typeof(i=t.toString)&&!D(n=i.call(t))){return n}throw TypeError("Can't convert object to primitive value")},B={}.hasOwnProperty,L=function(t,e){return B.call(t,e)},H=x.document,M=D(H)&&D(H.createElement),U=function(t){return M?H.createElement(t):{}},q=!O&&!k(function(){return 7!=Object.defineProperty(U("div"),"a",{get:function(){return 7}}).a}),z=Object.getOwnPropertyDescriptor,W=O?z:function(t,e){if(t=F(t),e=V(e,!0),q){try{return z(t,e)}catch(i){}}return L(t,e)?$(!A.f.call(t,e),t[e]):void 0},G={f:W},K=function(t){if(!D(t)){throw TypeError(t+" is not an object")}return t},Y=Object.defineProperty,X=O?Y:function(t,e,i){if(K(t),e=V(e,!0),K(i),q){try{return Y(t,e,i)}catch(n){}}if("get" in i||"set" in i){throw TypeError("Accessors not supported")}return"value" in i&&(t[e]=i.value),t},J={f:X},Q=O?function(t,e,i){return J.f(t,e,$(1,i))}:function(t,e,i){return t[e]=i,t},Z=function(t,e){try{Q(x,t,e)}catch(i){x[t]=e}return e},tt="__core-js_shared__",et=x[tt]||Z(tt,{}),it=et,nt=Function.toString;"function"!=typeof it.inspectSource&&(it.inspectSource=function(t){return nt.call(t)});var ot,at,st,rt=it.inspectSource,lt=x.WeakMap,ct="function"==typeof lt&&/native code/.test(rt(lt)),ht=b(function(t){(t.exports=function(t,e){return it[t]||(it[t]=void 0!==e?e:{})})("versions",[]).push({version:"3.10.1",mode:"global",copyright:"© 2021 Denis Pushkarev (zloirock.ru)"})}),ut=0,dt=Math.random(),ft=function(t){return"Symbol("+((void 0===t?"":t)+"")+")_"+(++ut+dt).toString(36)},pt=ht("keys"),gt=function(t){return pt[t]||(pt[t]=ft(t))},vt={},bt=x.WeakMap,mt=function(t){return st(t)?at(t):ot(t,{})},yt=function(t){return function(e){var i;if(!D(e)||(i=at(e)).type!==t){throw TypeError("Incompatible receiver, "+t+" required")}return i}};if(ct){var wt=it.state||(it.state=new bt),St=wt.get,xt=wt.has,kt=wt.set;ot=function(t,e){return e.facade=t,kt.call(wt,t,e),e},at=function(t){return St.call(wt,t)||{}},st=function(t){return xt.call(wt,t)}}else{var Ot=gt("state");vt[Ot]=!0,ot=function(t,e){return e.facade=t,Q(t,Ot,e),e},at=function(t){return L(t,Ot)?t[Ot]:{}},st=function(t){return L(t,Ot)}}var Tt={set:ot,get:at,has:st,enforce:mt,getterFor:yt},Ct=b(function(t){var e=Tt.get,i=Tt.enforce,n=(String+"").split("String");(t.exports=function(t,e,o,a){var s,r=a?!!a.unsafe:!1,l=a?!!a.enumerable:!1,c=a?!!a.noTargetGet:!1;return"function"==typeof o&&("string"!=typeof e||L(o,"name")||Q(o,"name",e),s=i(o),s.source||(s.source=n.join("string"==typeof e?e:""))),t===x?void (l?t[e]=o:Z(e,o)):(r?!c&&t[e]&&(l=!0):delete t[e],void (l?t[e]=o:Q(t,e,o)))})(Function.prototype,"toString",function(){return"function"==typeof this&&e(this).source||rt(this)})}),Pt=x,It=function(t){return"function"==typeof t?t:void 0},At=function(t,e){return arguments.length<2?It(Pt[t])||It(x[t]):Pt[t]&&Pt[t][e]||x[t]&&x[t][e]},$t=Math.ceil,Rt=Math.floor,Et=function(t){return isNaN(t=+t)?0:(t>0?Rt:$t)(t)},jt=Math.min,_t=function(t){return t>0?jt(Et(t),9007199254740991):0},Nt=Math.max,Ft=Math.min,Dt=function(t,e){var i=Et(t);return 0>i?Nt(i+e,0):Ft(i,e)},Vt=function(t){return function(e,i,n){var o,a=F(e),s=_t(a.length),r=Dt(n,s);if(t&&i!=i){for(;s>r;){if(o=a[r++],o!=o){return !0}}}else{for(;s>r;r++){if((t||r in a)&&a[r]===i){return t||r||0}}}return !t&&-1}},Bt={includes:Vt(!0),indexOf:Vt(!1)},Lt=Bt.indexOf,Ht=function(t,e){var i,n=F(t),o=0,a=[];for(i in n){!L(vt,i)&&L(n,i)&&a.push(i)}for(;e.length>o;){L(n,i=e[o++])&&(~Lt(a,i)||a.push(i))}return a},Mt=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"],Ut=Mt.concat("length","prototype"),qt=Object.getOwnPropertyNames||function(t){return Ht(t,Ut)},zt={f:qt},Wt=Object.getOwnPropertySymbols,Gt={f:Wt},Kt=At("Reflect","ownKeys")||function(t){var e=zt.f(K(t)),i=Gt.f;return i?e.concat(i(t)):e},Yt=function(t,e){for(var i=Kt(e),n=J.f,o=G.f,a=0;a0&&(!a.multiline||a.multiline&&"\n"!==t[a.lastIndex-1])&&(l="(?: "+l+")",h=" "+h,c++),i=RegExp("^(?:"+l+")",r)),Pe&&(i=RegExp("^"+l+"$(?!\\s)",r)),Te&&(e=a.lastIndex),n=xe.call(s?i:a,h),s?n?(n.input=n.input.slice(c),n[0]=n[0].slice(c),n.index=a.lastIndex,a.lastIndex+=n[0].length):a.lastIndex=0:Te&&n&&(a.lastIndex=a.global?n.index+n[0].length:e),Pe&&n&&n.length>1&&ke.call(n[0],i,function(){for(o=1;o=74)&&($e=je.match(/Chrome\/(\d+)/),$e&&(Re=$e[1])));var De=Re&&+Re,Ve=!!Object.getOwnPropertySymbols&&!k(function(){return !Symbol.sham&&(Ee?38===De:De>37&&41>De)}),Be=Ve&&!Symbol.sham&&"symbol"==typeof Symbol.iterator,Le=ht("wks"),He=x.Symbol,Me=Be?He:He&&He.withoutSetter||ft,Ue=function(t){return(!L(Le,t)||!Ve&&"string"!=typeof Le[t])&&(Ve&&L(He,t)?Le[t]=He[t]:Le[t]=Me("Symbol."+t)),Le[t]},qe=Ue("species"),ze=!k(function(){var t=/./;return t.exec=function(){var t=[];return t.groups={a:"7"},t},"7"!=="".replace(t,"$")}),We=function(){return"$0"==="a".replace(/./,"$0")}(),Ge=Ue("replace"),Ke=function(){return/./[Ge]?""===/./[Ge]("a","$0"):!1}(),Ye=!k(function(){var t=/(?:)/,e=t.exec;t.exec=function(){return e.apply(this,arguments)};var i="ab".split(t);return 2!==i.length||"a"!==i[0]||"b"!==i[1]}),Xe=function(t,e,i,n){var o=Ue(t),a=!k(function(){var e={};return e[o]=function(){return 7},7!=""[t](e)}),s=a&&!k(function(){var e=!1,i=/a/;return"split"===t&&(i={},i.constructor={},i.constructor[qe]=function(){return i},i.flags="",i[o]=/./[o]),i.exec=function(){return e=!0,null},i[o](""),!e});if(!a||!s||"replace"===t&&(!ze||!We||Ke)||"split"===t&&!Ye){var r=/./[o],l=i(o,""[t],function(t,e,i,n,o){return e.exec===RegExp.prototype.exec?a&&!o?{done:!0,value:r.call(e,i,n)}:{done:!0,value:t.call(i,e,n)}:{done:!1}},{REPLACE_KEEPS_$0:We,REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE:Ke}),c=l[0],h=l[1];Ct(String.prototype,t,c),Ct(RegExp.prototype,o,2==e?function(t,e){return h.call(t,this,e)}:function(t){return h.call(t,this)})}n&&Q(RegExp.prototype[o],"sham",!0)},Je=Ue("match"),Qe=function(t){var e;return D(t)&&(void 0!==(e=t[Je])?!!e:"RegExp"==E(t))},Ze=function(t){if("function"!=typeof t){throw TypeError(t+" is not a function")}return t},ti=Ue("species"),ei=function(t,e){var i,n=K(t).constructor;return void 0===n||void 0==(i=K(n)[ti])?e:Ze(i)},ii=function(t){return function(e,i){var n,o,a=N(e)+"",s=Et(i),r=a.length;return 0>s||s>=r?t?"":void 0:(n=a.charCodeAt(s),55296>n||n>56319||s+1===r||(o=a.charCodeAt(s+1))<56320||o>57343?t?a.charAt(s):n:t?a.slice(s,s+2):(n-55296<<10)+(o-56320)+65536)}},ni={codeAt:ii(!1),charAt:ii(!0)},oi=ni.charAt,ai=function(t,e,i){return e+(i?oi(t,e).length:1)},si=function(t,e){var i=t.exec;if("function"==typeof i){var n=i.call(t,e);if("object"!=typeof n){throw TypeError("RegExp exec method returned something other than an Object or null")}return n}if("RegExp"!==E(t)){throw TypeError("RegExp#exec called on incompatible receiver")}return Ae.call(t,e)},ri=Se.UNSUPPORTED_Y,li=[].push,ci=Math.min,hi=4294967295;Xe("split",2,function(t,e,i){var n;return n="c"=="abbc".split(/(b)*/)[1]||4!="test".split(/(?:)/,-1).length||2!="ab".split(/(?:ab)*/).length||4!=".".split(/(.?)(.?)/).length||".".split(/()()/).length>1||"".split(/.?/).length?function(t,i){var n=N(this)+"",o=void 0===i?hi:i>>>0;if(0===o){return[]}if(void 0===t){return[n]}if(!Qe(t)){return e.call(n,t,o)}for(var a,s,r,l=[],c=(t.ignoreCase?"i":"")+(t.multiline?"m":"")+(t.unicode?"u":"")+(t.sticky?"y":""),h=0,u=RegExp(t.source,c+"g");(a=Ae.call(u,n))&&(s=u.lastIndex,!(s>h&&(l.push(n.slice(h,a.index)),a.length>1&&a.index=o)));){u.lastIndex===a.index&&u.lastIndex++}return h===n.length?(r||!u.test(""))&&l.push(""):l.push(n.slice(h)),l.length>o?l.slice(0,o):l}:"0".split(void 0,0).length?function(t,i){return void 0===t&&0===i?[]:e.call(this,t,i)}:e,[function(e,i){var o=N(this),a=void 0==e?void 0:e[t];return void 0!==a?a.call(e,o,i):n.call(o+"",e,i)},function(t,o){var a=i(n,t,this,o,n!==e);if(a.done){return a.value}var s=K(t),r=this+"",l=ei(s,RegExp),c=s.unicode,h=(s.ignoreCase?"i":"")+(s.multiline?"m":"")+(s.unicode?"u":"")+(ri?"g":"y"),u=new l(ri?"^(?:"+s.source+")":s,h),d=void 0===o?hi:o>>>0;if(0===d){return[]}if(0===r.length){return null===si(u,r)?[r]:[]}for(var f=0,p=0,g=[];ps;){i=o[s++],(!O||di.call(n,i))&&r.push(t?[i,n[i]]:n[i])}return r}},pi={entries:fi(!0),values:fi(!1)},gi=pi.entries;oe({target:"Object",stat:!0},{entries:function(t){return gi(t)}});var vi,bi=O?Object.defineProperties:function(t,e){K(t);for(var i,n=ui(e),o=n.length,a=0;o>a;){J.f(t,i=n[a++],e[i])}return t},mi=At("document","documentElement"),yi=">",wi="<",Si="prototype",xi="script",ki=gt("IE_PROTO"),Oi=function(){},Ti=function(t){return wi+xi+yi+t+wi+"/"+xi+yi},Ci=function(t){t.write(Ti("")),t.close();var e=t.parentWindow.Object;return t=null,e},Pi=function(){var t,e=U("iframe"),i="java"+xi+":";return e.style.display="none",mi.appendChild(e),e.src=i+"",t=e.contentWindow.document,t.open(),t.write(Ti("document.F=Object")),t.close(),t.F},Ii=function(){try{vi=document.domain&&new ActiveXObject("htmlfile")}catch(t){}Ii=vi?Ci(vi):Pi();for(var e=Mt.length;e--;){delete Ii[Si][Mt[e]]}return Ii()};vt[ki]=!0;var Ai=Object.create||function(t,e){var i;return null!==t?(Oi[Si]=K(t),i=new Oi,Oi[Si]=null,i[ki]=t):i=Ii(),void 0===e?i:bi(i,e)},$i=Ue("unscopables"),Ri=Array.prototype;void 0==Ri[$i]&&J.f(Ri,$i,{configurable:!0,value:Ai(null)});var Ei=function(t){Ri[$i][t]=!0},ji=Bt.includes;oe({target:"Array",proto:!0},{includes:function(t){return ji(this,t,arguments.length>1?arguments[1]:void 0)}}),Ei("includes");var _i=Array.isArray||function(t){return"Array"==E(t)},Ni=function(t){return Object(N(t))},Fi=function(t,e,i){var n=V(e);n in t?J.f(t,n,$(0,i)):t[n]=i},Di=Ue("species"),Vi=function(t,e){var i;return _i(t)&&(i=t.constructor,"function"!=typeof i||i!==Array&&!_i(i.prototype)?D(i)&&(i=i[Di],null===i&&(i=void 0)):i=void 0),new (void 0===i?Array:i)(0===e?0:e)},Bi=Ue("species"),Li=function(t){return De>=51||!k(function(){var e=[],i=e.constructor={};return i[Bi]=function(){return{foo:1}},1!==e[t](Boolean).foo})},Hi=Ue("isConcatSpreadable"),Mi=9007199254740991,Ui="Maximum allowed index exceeded",qi=De>=51||!k(function(){var t=[];return t[Hi]=!1,t.concat()[0]!==t}),zi=Li("concat"),Wi=function(t){if(!D(t)){return !1}var e=t[Hi];return void 0!==e?!!e:_i(t)},Gi=!qi||!zi;oe({target:"Array",proto:!0,forced:Gi},{concat:function(t){var e,i,n,o,a,s=Ni(this),r=Vi(s,0),l=0;for(e=-1,n=arguments.length;n>e;e++){if(a=-1===e?s:arguments[e],Wi(a)){if(o=_t(a.length),l+o>Mi){throw TypeError(Ui)}for(i=0;o>i;i++,l++){i in a&&Fi(r,l,a[i])}}else{if(l>=Mi){throw TypeError(Ui)}Fi(r,l++,a)}}return r.length=l,r}});var Ki=function(t,e,i){if(Ze(t),void 0===e){return t}switch(i){case 0:return function(){return t.call(e)};case 1:return function(i){return t.call(e,i)};case 2:return function(i,n){return t.call(e,i,n)};case 3:return function(i,n,o){return t.call(e,i,n,o)}}return function(){return t.apply(e,arguments)}},Yi=[].push,Xi=function(t){var e=1==t,i=2==t,n=3==t,o=4==t,a=6==t,s=7==t,r=5==t||a;return function(l,c,h,u){for(var d,f,p=Ni(l),g=_(p),v=Ki(c,h,3),b=_t(g.length),m=0,y=u||Vi,w=e?y(l,b):i||s?y(l,0):void 0;b>m;m++){if((r||m in g)&&(d=g[m],f=v(d,m,p),t)){if(e){w[m]=f}else{if(f){switch(t){case 3:return !0;case 5:return d;case 6:return m;case 2:Yi.call(w,d)}}else{switch(t){case 4:return !1;case 7:Yi.call(w,d)}}}}}return a?-1:n||o?o:w}},Ji={forEach:Xi(0),map:Xi(1),filter:Xi(2),some:Xi(3),every:Xi(4),find:Xi(5),findIndex:Xi(6),filterOut:Xi(7)},Qi=Ji.find,Zi="find",tn=!0;Zi in []&&Array(1)[Zi](function(){tn=!1}),oe({target:"Array",proto:!0,forced:tn},{find:function(t){return Qi(this,t,arguments.length>1?arguments[1]:void 0)}}),Ei(Zi);var en=function(t){if(Qe(t)){throw TypeError("The method doesn't accept regular expressions")}return t},nn=Ue("match"),on=function(t){var e=/./;try{"/./"[t](e)}catch(i){try{return e[nn]=!1,"/./"[t](e)}catch(n){}}return !1};oe({target:"String",proto:!0,forced:!on("includes")},{includes:function(t){return !!~(N(this)+"").indexOf(en(t),arguments.length>1?arguments[1]:void 0)}});var an={CSSRuleList:0,CSSStyleDeclaration:0,CSSValueList:0,ClientRectList:0,DOMRectList:0,DOMStringList:0,DOMTokenList:1,DataTransferItemList:0,FileList:0,HTMLAllCollection:0,HTMLCollection:0,HTMLFormElement:0,HTMLSelectElement:0,MediaList:0,MimeTypeArray:0,NamedNodeMap:0,NodeList:1,PaintRequestList:0,Plugin:0,PluginArray:0,SVGLengthList:0,SVGNumberList:0,SVGPathSegList:0,SVGPointList:0,SVGStringList:0,SVGTransformList:0,SourceBufferList:0,StyleSheetList:0,TextTrackCueList:0,TextTrackList:0,TouchList:0},sn=Ji.forEach,rn=pe("forEach"),ln=rn?[].forEach:function(t){return sn(this,t,arguments.length>1?arguments[1]:void 0)};for(var cn in an){var hn=x[cn],un=hn&&hn.prototype;if(un&&un.forEach!==ln){try{Q(un,"forEach",ln)}catch(dn){un.forEach=ln}}}var fn=he.trim,pn=x.parseFloat,gn=1/pn(ae+"-0")!==-(1/0),vn=gn?function(t){var e=fn(t+""),i=pn(e);return 0===i&&"-"==e.charAt(0)?-0:i}:pn;oe({global:!0,forced:parseFloat!=vn},{parseFloat:vn});var bn=Bt.indexOf,mn=[].indexOf,yn=!!mn&&1/[1].indexOf(1,-0)<0,wn=pe("indexOf");oe({target:"Array",proto:!0,forced:yn||!wn},{indexOf:function(t){return yn?mn.apply(this,arguments)||0:bn(this,t,arguments.length>1?arguments[1]:void 0)}});var Sn=[],xn=Sn.sort,kn=k(function(){Sn.sort(void 0)}),On=k(function(){Sn.sort(null)}),Tn=pe("sort"),Cn=kn||!On||!Tn;oe({target:"Array",proto:!0,forced:Cn},{sort:function(t){return void 0===t?xn.call(Ni(this)):xn.call(Ni(this),Ze(t))}});var Pn=Math.floor,In="".replace,An=/\$([$&'`]|\d{1,2}|<[^>]*>)/g,$n=/\$([$&'`]|\d{1,2})/g,Rn=function(t,e,i,n,o,a){var s=i+t.length,r=n.length,l=$n;return void 0!==o&&(o=Ni(o),l=An),In.call(a,l,function(a,l){var c;switch(l.charAt(0)){case"$":return"$";case"&":return t;case"`":return e.slice(0,i);case"'":return e.slice(s);case"<":c=o[l.slice(1,-1)];break;default:var h=+l;if(0===h){return a}if(h>r){var u=Pn(h/10);return 0===u?a:r>=u?void 0===n[u-1]?l.charAt(1):n[u-1]+l.charAt(1):a}c=n[h-1]}return void 0===c?"":c})},En=Math.max,jn=Math.min,_n=function(t){return void 0===t?t:t+""};Xe("replace",2,function(t,e,i,n){var o=n.REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE,a=n.REPLACE_KEEPS_$0,s=o?"$":"$0";return[function(i,n){var o=N(this),a=void 0==i?void 0:i[t];return void 0!==a?a.call(i,o,n):e.call(o+"",i,n)},function(t,n){if(!o&&a||"string"==typeof n&&-1===n.indexOf(s)){var r=i(e,t,this,n);if(r.done){return r.value}}var l=K(t),c=this+"",h="function"==typeof n;h||(n+="");var u=l.global;if(u){var d=l.unicode;l.lastIndex=0}for(var f=[];;){var p=si(l,c);if(null===p){break}if(f.push(p),!u){break}var g=p[0]+"";""===g&&(l.lastIndex=ai(c,_t(l.lastIndex),d))}for(var v="",b=0,m=0;m=b&&(v+=c.slice(b,w)+T,b=w+y.length)}return v+c.slice(b)}]});var Nn=Object.assign,Fn=Object.defineProperty,Dn=!Nn||k(function(){if(O&&1!==Nn({b:1},Nn(Fn({},"a",{enumerable:!0,get:function(){Fn(this,"b",{value:3,enumerable:!1})}}),{b:2})).b){return !0}var t={},e={},i=Symbol(),n="abcdefghijklmnopqrst";return t[i]=7,n.split("").forEach(function(t){e[t]=t}),7!=Nn({},t)[i]||ui(Nn({},e)).join("")!=n})?function(t,e){for(var i=Ni(t),n=arguments.length,o=1,a=Gt.f,s=A.f;n>o;){for(var r,l=_(arguments[o++]),c=a?ui(l).concat(a(l)):ui(l),h=c.length,u=0;h>u;){r=c[u++],(!O||s.call(l,r))&&(i[r]=l[r])}}return i}:Nn;oe({target:"Object",stat:!0,forced:Object.assign!==Dn},{assign:Dn});var Vn=Ji.filter,Bn=Li("filter");oe({target:"Array",proto:!0,forced:!Bn},{filter:function(t){return Vn(this,t,arguments.length>1?arguments[1]:void 0)}});var Ln=Object.is||function(t,e){return t===e?0!==t||1/t===1/e:t!=t&&e!=e};Xe("search",1,function(t,e,i){return[function(e){var i=N(this),n=void 0==e?void 0:e[t];return void 0!==n?n.call(e,i):RegExp(e)[t](i+"")},function(t){var n=i(e,t,this);if(n.done){return n.value}var o=K(t),a=this+"",s=o.lastIndex;Ln(s,0)||(o.lastIndex=0);var r=si(o,a);return Ln(o.lastIndex,s)||(o.lastIndex=s),null===r?-1:r.index}]});var Hn=he.trim,Mn=x.parseInt,Un=/^[+-]?0[Xx]/,qn=8!==Mn(ae+"08")||22!==Mn(ae+"0x16"),zn=qn?function(t,e){var i=Hn(t+"");return Mn(i,e>>>0||(Un.test(i)?16:10))}:Mn;oe({global:!0,forced:parseInt!=zn},{parseInt:zn});var Wn=Ji.map,Gn=Li("map");oe({target:"Array",proto:!0,forced:!Gn},{map:function(t){return Wn(this,t,arguments.length>1?arguments[1]:void 0)}});var Kn=Ji.findIndex,Yn="findIndex",Xn=!0;Yn in []&&Array(1)[Yn](function(){Xn=!1}),oe({target:"Array",proto:!0,forced:Xn},{findIndex:function(t){return Kn(this,t,arguments.length>1?arguments[1]:void 0)}}),Ei(Yn);var Jn=function(t){if(!D(t)&&null!==t){throw TypeError("Can't set "+(t+"")+" as a prototype")}return t},Qn=Object.setPrototypeOf||("__proto__" in {}?function(){var t,e=!1,i={};try{t=Object.getOwnPropertyDescriptor(Object.prototype,"__proto__").set,t.call(i,[]),e=i instanceof Array}catch(n){}return function(i,n){return K(i),Jn(n),e?t.call(i,n):i.__proto__=n,i}}():void 0),Zn=function(t,e,i){var n,o;return Qn&&"function"==typeof(n=e.constructor)&&n!==i&&D(o=n.prototype)&&o!==i.prototype&&Qn(t,o),t},to=Ue("species"),eo=function(t){var e=At(t),i=J.f;O&&e&&!e[to]&&i(e,to,{configurable:!0,get:function(){return this}})},io=J.f,no=zt.f,oo=Tt.set,ao=Ue("match"),so=x.RegExp,ro=so.prototype,lo=/a/g,co=/a/g,ho=new so(lo)!==lo,uo=Se.UNSUPPORTED_Y,fo=O&&ie("RegExp",!ho||uo||k(function(){return co[ao]=!1,so(lo)!=lo||so(co)==co||"/a/i"!=so(lo,"i")}));if(fo){for(var po=function(t,e){var i,n=this instanceof po,o=Qe(t),a=void 0===e;if(!n&&o&&t.constructor===po&&a){return t}ho?o&&!a&&(t=t.source):t instanceof po&&(a&&(e=me.call(t)),t=t.source),uo&&(i=!!e&&e.indexOf("y")>-1,i&&(e=e.replace(/y/g,"")));var s=Zn(ho?new so(t,e):so(t,e),n?this:ro,po);return uo&&i&&oo(s,{sticky:i}),s},go=(function(t){t in po||io(po,t,{configurable:!0,get:function(){return so[t]},set:function(e){so[t]=e}})}),vo=no(so),bo=0;vo.length>bo;){go(vo[bo++])}ro.constructor=po,po.prototype=ro,Ct(x,"RegExp",po)}eo("RegExp");var mo="toString",yo=RegExp.prototype,wo=yo[mo],So=k(function(){return"/a/b"!=wo.call({source:"a",flags:"b"})}),xo=wo.name!=mo;(So||xo)&&Ct(RegExp.prototype,mo,function(){var t=K(this),e=t.source+"",i=t.flags,n=(void 0===i&&t instanceof RegExp&&!("flags" in yo)?me.call(t):i)+"";return"/"+e+"/"+n},{unsafe:!0});var ko=Ue("toStringTag"),Oo={};Oo[ko]="z";var To=Oo+""=="[object z]",Co=Ue("toStringTag"),Po="Arguments"==E(function(){return arguments}()),Io=function(t,e){try{return t[e]}catch(i){}},Ao=To?E:function(t){var e,i,n;return void 0===t?"Undefined":null===t?"Null":"string"==typeof(i=Io(e=Object(t),Co))?i:Po?E(e):"Object"==(n=E(e))&&"function"==typeof e.callee?"Arguments":n},$o=To?{}.toString:function(){return"[object "+Ao(this)+"]"};To||Ct(Object.prototype,"toString",$o,{unsafe:!0});var Ro=Li("slice"),Eo=Ue("species"),jo=[].slice,_o=Math.max;oe({target:"Array",proto:!0,forced:!Ro},{slice:function(t,e){var i,n,o,a=F(this),s=_t(a.length),r=Dt(t,s),l=Dt(void 0===e?s:e,s);if(_i(a)&&(i=a.constructor,"function"!=typeof i||i!==Array&&!_i(i.prototype)?D(i)&&(i=i[Eo],null===i&&(i=void 0)):i=void 0,i===Array||void 0===i)){return jo.call(a,r,l)}for(n=new (void 0===i?Array:i)(_o(l-r,0)),o=0;l>r;r++,o++){r in a&&Fi(n,o,a[r])}return n.length=o,n}});var No,Fo,Do,Vo=!k(function(){function t(){}return t.prototype.constructor=null,Object.getPrototypeOf(new t)!==t.prototype}),Bo=gt("IE_PROTO"),Lo=Object.prototype,Ho=Vo?Object.getPrototypeOf:function(t){return t=Ni(t),L(t,Bo)?t[Bo]:"function"==typeof t.constructor&&t instanceof t.constructor?t.constructor.prototype:t instanceof Object?Lo:null},Mo=Ue("iterator"),Uo=!1,qo=function(){return this};[].keys&&(Do=[].keys(),"next" in Do?(Fo=Ho(Ho(Do)),Fo!==Object.prototype&&(No=Fo)):Uo=!0);var zo=void 0==No||k(function(){var t={};return No[Mo].call(t)!==t});zo&&(No={}),L(No,Mo)||Q(No,Mo,qo);var Wo={IteratorPrototype:No,BUGGY_SAFARI_ITERATORS:Uo},Go=J.f,Ko=Ue("toStringTag"),Yo=function(t,e,i){t&&!L(t=i?t:t.prototype,Ko)&&Go(t,Ko,{configurable:!0,value:e})},Xo=Wo.IteratorPrototype,Jo=function(t,e,i){var n=e+" Iterator";return t.prototype=Ai(Xo,{next:$(1,i)}),Yo(t,n,!1),t},Qo=Wo.IteratorPrototype,Zo=Wo.BUGGY_SAFARI_ITERATORS,ta=Ue("iterator"),ea="keys",ia="values",na="entries",oa=function(){return this},aa=function(t,e,i,n,o,a,s){Jo(i,e,n);var r,l,c,h=function(t){if(t===o&&g){return g}if(!Zo&&t in f){return f[t]}switch(t){case ea:return function(){return new i(this,t)};case ia:return function(){return new i(this,t)};case na:return function(){return new i(this,t)}}return function(){return new i(this)}},u=e+" Iterator",d=!1,f=t.prototype,p=f[ta]||f["@@iterator"]||o&&f[o],g=!Zo&&p||h(o),v="Array"==e?f.entries||p:p;if(v&&(r=Ho(v.call(new t)),Qo!==Object.prototype&&r.next&&(Ho(r)!==Qo&&(Qn?Qn(r,Qo):"function"!=typeof r[ta]&&Q(r,ta,oa)),Yo(r,u,!0))),o==ia&&p&&p.name!==ia&&(d=!0,g=function(){return p.call(this)}),f[ta]!==g&&Q(f,ta,g),o){if(l={values:h(ia),keys:a?g:h(ea),entries:h(na)},s){for(c in l){!Zo&&!d&&c in f||Ct(f,c,l[c])}}else{oe({target:e,proto:!0,forced:Zo||d},l)}}return l},sa="Array Iterator",ra=Tt.set,la=Tt.getterFor(sa),ca=aa(Array,"Array",function(t,e){ra(this,{type:sa,target:F(t),index:0,kind:e})},function(){var t=la(this),e=t.target,i=t.kind,n=t.index++;return !e||n>=e.length?(t.target=void 0,{value:void 0,done:!0}):"keys"==i?{value:n,done:!1}:"values"==i?{value:e[n],done:!1}:{value:[n,e[n]],done:!1}},"values");Ei("keys"),Ei("values"),Ei("entries");var ha=Ue("iterator"),ua=Ue("toStringTag"),da=ca.values;for(var fa in an){var pa=x[fa],ga=pa&&pa.prototype;if(ga){if(ga[ha]!==da){try{Q(ga,ha,da)}catch(dn){ga[ha]=da}}if(ga[ua]||Q(ga,ua,fa),an[fa]){for(var va in ca){if(ga[va]!==ca[va]){try{Q(ga,va,ca[va])}catch(dn){ga[va]=ca[va]}}}}}}var ba=Li("splice"),ma=Math.max,ya=Math.min,wa=9007199254740991,Sa="Maximum allowed length exceeded";oe({target:"Array",proto:!0,forced:!ba},{splice:function(t,e){var i,n,o,a,s,r,l=Ni(this),c=_t(l.length),h=Dt(t,c),u=arguments.length;if(0===u?i=n=0:1===u?(i=0,n=c-h):(i=u-2,n=ya(ma(Et(e),0),c-h)),c+i-n>wa){throw TypeError(Sa)}for(o=Vi(l,n),a=0;n>a;a++){s=h+a,s in l&&Fi(o,a,l[s])}if(o.length=n,n>i){for(a=h;c-n>a;a++){s=a+n,r=a+i,s in l?l[r]=l[s]:delete l[r]}for(a=c;a>c-n+i;a--){delete l[a-1]}}else{if(i>n){for(a=c-n;a>h;a--){s=a+n-1,r=a+i-1,s in l?l[r]=l[s]:delete l[r]}}}for(a=0;i>a;a++){l[a+h]=arguments[a+2]}return l.length=c-n+i,o}});var xa=zt.f,ka=G.f,Oa=J.f,Ta=he.trim,Ca="Number",Pa=x[Ca],Ia=Pa.prototype,Aa=E(Ai(Ia))==Ca,$a=function(t){var e,i,n,o,a,s,r,l,c=V(t,!1);if("string"==typeof c&&c.length>2){if(c=Ta(c),e=c.charCodeAt(0),43===e||45===e){if(i=c.charCodeAt(2),88===i||120===i){return NaN}}else{if(48===e){switch(c.charCodeAt(1)){case 66:case 98:n=2,o=49;break;case 79:case 111:n=8,o=55;break;default:return +c}for(a=c.slice(2),s=a.length,r=0;s>r;r++){if(l=a.charCodeAt(r),48>l||l>o){return NaN}}return parseInt(a,n)}}}return +c};if(ie(Ca,!Pa(" 0o1")||!Pa("0b1")||Pa("+0x1"))){for(var Ra,Ea=function(t){var e=arguments.length<1?0:t,i=this;return i instanceof Ea&&(Aa?k(function(){Ia.valueOf.call(i)}):E(i)!=Ca)?Zn(new Pa($a(e)),i,Ea):$a(e)},ja=O?xa(Pa):"MAX_VALUE,MIN_VALUE,NaN,NEGATIVE_INFINITY,POSITIVE_INFINITY,EPSILON,isFinite,isInteger,isNaN,isSafeInteger,MAX_SAFE_INTEGER,MIN_SAFE_INTEGER,parseFloat,parseInt,isInteger,fromString,range".split(","),_a=0;ja.length>_a;_a++){L(Pa,Ra=ja[_a])&&!L(Ea,Ra)&&Oa(Ea,Ra,ka(Pa,Ra))}Ea.prototype=Ia,Ia.constructor=Ea,Ct(x,Ca,Ea)}var Na=[].reverse,Fa=[1,2];oe({target:"Array",proto:!0,forced:Fa+""==Fa.reverse()+""},{reverse:function(){return _i(this)&&(this.length=this.length),Na.call(this)}});var Da="1.19.1",Va=4;try{var Ba=y["default"].fn.dropdown.Constructor.VERSION;void 0!==Ba&&(Va=parseInt(Ba,10))}catch(La){}try{var Ha=bootstrap.Tooltip.VERSION;void 0!==Ha&&(Va=parseInt(Ha,10))}catch(La){}var Ma={3:{iconsPrefix:"glyphicon",icons:{paginationSwitchDown:"glyphicon-collapse-down icon-chevron-down",paginationSwitchUp:"glyphicon-collapse-up icon-chevron-up",refresh:"glyphicon-refresh icon-refresh",toggleOff:"glyphicon-list-alt icon-list-alt",toggleOn:"glyphicon-list-alt icon-list-alt",columns:"glyphicon-th icon-th",detailOpen:"glyphicon-plus icon-plus",detailClose:"glyphicon-minus icon-minus",fullscreen:"glyphicon-fullscreen",search:"glyphicon-search",clearSearch:"glyphicon-trash"},classes:{buttonsPrefix:"btn",buttons:"default",buttonsGroup:"btn-group",buttonsDropdown:"btn-group",pull:"pull",inputGroup:"input-group",inputPrefix:"input-",input:"form-control",paginationDropdown:"btn-group dropdown",dropup:"dropup",dropdownActive:"active",paginationActive:"active",buttonActive:"active"},html:{toolbarDropdown:['"],toolbarDropdownItem:'
  • ',toolbarDropdownSeparator:'
  • ',pageDropdown:['"],pageDropdownItem:'
    ',dropdownCaret:'',pagination:['
      ',"
    "],paginationItem:'
  • %s
  • ',icon:'',inputGroup:'
    %s%s
    ',searchInput:'',searchButton:'',searchClearButton:''}},4:{iconsPrefix:"fa",icons:{paginationSwitchDown:"fa-caret-square-down",paginationSwitchUp:"fa-caret-square-up",refresh:"fa-sync",toggleOff:"fa-toggle-off",toggleOn:"fa-toggle-on",columns:"fa-th-list",detailOpen:"fa-plus",detailClose:"fa-minus",fullscreen:"fa-arrows-alt",search:"fa-search",clearSearch:"fa-trash"},classes:{buttonsPrefix:"btn",buttons:"secondary",buttonsGroup:"btn-group",buttonsDropdown:"btn-group",pull:"float",inputGroup:"btn-group",inputPrefix:"form-control-",input:"form-control",paginationDropdown:"btn-group dropdown",dropup:"dropup",dropdownActive:"active",paginationActive:"active",buttonActive:"active"},html:{toolbarDropdown:['"],toolbarDropdownItem:'',pageDropdown:['"],pageDropdownItem:'%s',toolbarDropdownSeparator:'',dropdownCaret:'',pagination:['
      ',"
    "],paginationItem:'
  • %s
  • ',icon:'',inputGroup:'
    %s
    %s
    ',searchInput:'',searchButton:'',searchClearButton:''}},5:{iconsPrefix:"bi",icons:{paginationSwitchDown:"bi-caret-down-square",paginationSwitchUp:"bi-caret-up-square",refresh:"bi-arrow-clockwise",toggleOff:"bi-toggle-off",toggleOn:"bi-toggle-on",columns:"bi-list-ul",detailOpen:"bi-plus",detailClose:"bi-dash",fullscreen:"bi-arrows-move",search:"bi-search",clearSearch:"bi-trash"},classes:{buttonsPrefix:"btn",buttons:"secondary",buttonsGroup:"btn-group",buttonsDropdown:"btn-group",pull:"float",inputGroup:"btn-group",inputPrefix:"form-control-",input:"form-control",paginationDropdown:"btn-group dropdown",dropup:"dropup",dropdownActive:"active",paginationActive:"active",buttonActive:"active"},html:{dataToggle:"data-bs-toggle",toolbarDropdown:['"],toolbarDropdownItem:'',pageDropdown:['"],pageDropdownItem:'%s',toolbarDropdownSeparator:'',dropdownCaret:'',pagination:['
      ',"
    "],paginationItem:'
  • %s
  • ',icon:'',inputGroup:'
    %s%s
    ',searchInput:'',searchButton:'',searchClearButton:''}}}[Va],Ua={id:void 0,firstLoad:!0,height:void 0,classes:"table table-bordered table-hover",buttons:{},theadClasses:"",striped:!1,headerStyle:function(t){return{}},rowStyle:function(t,e){return{}},rowAttributes:function(t,e){return{}},undefinedText:"-",locale:void 0,virtualScroll:!1,virtualScrollItemHeight:void 0,sortable:!0,sortClass:void 0,silentSort:!0,sortName:void 0,sortOrder:void 0,sortReset:!1,sortStable:!1,rememberOrder:!1,serverSort:!0,customSort:void 0,columns:[[]],data:[],url:void 0,method:"get",cache:!0,contentType:"application/json",dataType:"json",ajax:void 0,ajaxOptions:{},queryParams:function(t){return t},queryParamsType:"limit",responseHandler:function(t){return t},totalField:"total",totalNotFilteredField:"totalNotFiltered",dataField:"rows",footerField:"footer",pagination:!1,paginationParts:["pageInfo","pageSize","pageList"],showExtendedPagination:!1,paginationLoop:!0,sidePagination:"client",totalRows:0,totalNotFiltered:0,pageNumber:1,pageSize:10,pageList:[10,25,50,100],paginationHAlign:"right",paginationVAlign:"bottom",paginationDetailHAlign:"left",paginationPreText:"‹",paginationNextText:"›",paginationSuccessivelySize:5,paginationPagesBySide:1,paginationUseIntermediate:!1,search:!1,searchHighlight:!1,searchOnEnterKey:!1,strictSearch:!1,regexSearch:!1,searchSelector:!1,visibleSearch:!1,showButtonIcons:!0,showButtonText:!1,showSearchButton:!1,showSearchClearButton:!1,trimOnSearch:!0,searchAlign:"right",searchTimeOut:500,searchText:"",customSearch:void 0,showHeader:!0,showFooter:!1,footerStyle:function(t){return{}},searchAccentNeutralise:!1,showColumns:!1,showSearch:!1,showPageGo:!1,showColumnsToggleAll:!1,showColumnsSearch:!1,minimumCountColumns:1,showPaginationSwitch:!1,showRefresh:!1,showToggle:!1,showFullscreen:!1,smartDisplay:!0,escape:!1,filterOptions:{filterAlgorithm:"and"},idField:void 0,selectItemName:"btSelectItem",clickToSelect:!1,ignoreClickToSelectOn:function(t){var e=t.tagName;return["A","BUTTON"].includes(e)},singleSelect:!1,checkboxHeader:!0,maintainMetaData:!1,multipleSelectRow:!1,uniqueId:void 0,cardView:!1,detailView:!1,detailViewIcon:!0,detailViewByClick:!1,detailViewAlign:"left",detailFormatter:function(t,e){return""},detailFilter:function(t,e){return !0},toolbar:void 0,toolbarAlign:"left",buttonsToolbar:void 0,buttonsAlign:"right",buttonsOrder:["search","paginationSwitch","refresh","toggle","fullscreen","columns"],buttonsPrefix:Ma.classes.buttonsPrefix,buttonsClass:Ma.classes.buttons,icons:Ma.icons,iconSize:void 0,iconsPrefix:Ma.iconsPrefix,loadingFontSize:"auto",loadingTemplate:function(t){return'\n '.concat(t,'\n \n \n ')},onAll:function(t,e){return !1},onClickCell:function(t,e,i,n){return !1},onDblClickCell:function(t,e,i,n){return !1},onClickRow:function(t,e){return !1},onDblClickRow:function(t,e){return !1},onSort:function(t,e){return !1},onCheck:function(t){return !1},onUncheck:function(t){return !1},onCheckAll:function(t){return !1},onUncheckAll:function(t){return !1},onCheckSome:function(t){return !1},onUncheckSome:function(t){return !1},onLoadSuccess:function(t){return !1},onLoadError:function(t){return !1},onColumnSwitch:function(t,e){return !1},onPageChange:function(t,e){return !1},onSearch:function(t){return !1},onShowSearch:function(){return !1},onToggle:function(t){return !1},onPreBody:function(t){return !1},onPostBody:function(){return !1},onPostHeader:function(){return !1},onPostFooter:function(){return !1},onExpandRow:function(t,e,i){return !1},onCollapseRow:function(t,e){return !1},onRefreshOptions:function(t){return !1},onRefresh:function(t){return !1},onResetView:function(){return !1},onScrollBody:function(){return !1},onTogglePagination:function(t){return !1}},qa={formatLoadingMessage:function(){return"Loading, please wait"},formatRecordsPerPage:function(t){return"".concat(t," rows per page")},formatShowingRows:function(t,e,i,n){return void 0!==n&&n>0&&n>i?"Showing ".concat(t," to ").concat(e," of ").concat(i," rows (filtered from ").concat(n," total rows)"):"Showing ".concat(t," to ").concat(e," of ").concat(i," rows")},formatSRPaginationPreText:function(){return"previous page"},formatSRPaginationPageText:function(t){return"to page ".concat(t)},formatSRPaginationNextText:function(){return"next page"},formatDetailPagination:function(t){return"Showing ".concat(t," rows")},formatSearch:function(){return"Search"},formatShowSearch:function(){return"Show Search"},formatPageGo:function(){return"Go"},formatClearSearch:function(){return"Clear Search"},formatNoMatches:function(){return"No matching records found"},formatPaginationSwitch:function(){return"Hide/Show pagination"},formatPaginationSwitchDown:function(){return"Show pagination"},formatPaginationSwitchUp:function(){return"Hide pagination"},formatRefresh:function(){return"Refresh"},formatToggle:function(){return"Toggle"},formatToggleOn:function(){return"Show card view"},formatToggleOff:function(){return"Hide card view"},formatColumns:function(){return"Columns"},formatColumnsToggleAll:function(){return"Toggle all"},formatFullscreen:function(){return"Fullscreen"},formatAllRows:function(){return"All"}},za={field:void 0,title:void 0,titleTooltip:void 0,"class":void 0,width:void 0,widthUnit:"px",rowspan:void 0,colspan:void 0,align:void 0,halign:void 0,falign:void 0,valign:void 0,cellStyle:void 0,radio:!1,checkbox:!1,checkboxEnabled:!0,clickToSelect:!0,showSelectTitle:!1,sortable:!1,sortName:void 0,order:"asc",sorter:void 0,visible:!0,ignore:!1,switchable:!0,cardVisible:!0,searchable:!0,formatter:void 0,footerFormatter:void 0,detailFormatter:void 0,searchFormatter:!0,searchHighlightFormatter:!1,escape:!1,events:void 0},Wa=["getOptions","refreshOptions","getData","getSelections","load","append","prepend","remove","removeAll","insertRow","updateRow","getRowByUniqueId","updateByUniqueId","removeByUniqueId","updateCell","updateCellByUniqueId","showRow","hideRow","getHiddenRows","showColumn","hideColumn","getVisibleColumns","getHiddenColumns","showAllColumns","hideAllColumns","mergeCells","checkAll","uncheckAll","checkInvert","check","uncheck","checkBy","uncheckBy","refresh","destroy","resetView","showLoading","hideLoading","togglePagination","toggleFullscreen","toggleView","resetSearch","filterBy","scrollTo","getScrollPosition","selectPage","prevPage","nextPage","toggleDetailView","expandRow","collapseRow","expandRowByUniqueId","collapseRowByUniqueId","expandAllRows","collapseAllRows","updateColumnTitle","updateFormatText"],Ga={"all.bs.table":"onAll","click-row.bs.table":"onClickRow","dbl-click-row.bs.table":"onDblClickRow","click-cell.bs.table":"onClickCell","dbl-click-cell.bs.table":"onDblClickCell","sort.bs.table":"onSort","check.bs.table":"onCheck","uncheck.bs.table":"onUncheck","check-all.bs.table":"onCheckAll","uncheck-all.bs.table":"onUncheckAll","check-some.bs.table":"onCheckSome","uncheck-some.bs.table":"onUncheckSome","load-success.bs.table":"onLoadSuccess","load-error.bs.table":"onLoadError","column-switch.bs.table":"onColumnSwitch","page-change.bs.table":"onPageChange","search.bs.table":"onSearch","toggle.bs.table":"onToggle","pre-body.bs.table":"onPreBody","post-body.bs.table":"onPostBody","post-header.bs.table":"onPostHeader","post-footer.bs.table":"onPostFooter","expand-row.bs.table":"onExpandRow","collapse-row.bs.table":"onCollapseRow","refresh-options.bs.table":"onRefreshOptions","reset-view.bs.table":"onResetView","refresh.bs.table":"onRefresh","scroll-body.bs.table":"onScrollBody","toggle-pagination.bs.table":"onTogglePagination","virtual-scroll.bs.table":"onVirtualScroll"};Object.assign(Ua,qa);var Ka={VERSION:Da,THEME:"bootstrap".concat(Va),CONSTANTS:Ma,DEFAULTS:Ua,COLUMN_DEFAULTS:za,METHODS:Wa,EVENTS:Ga,LOCALES:{en:qa,"en-US":qa}},Ya=k(function(){ui(1)});oe({target:"Object",stat:!0,forced:Ya},{keys:function(t){return ui(Ni(t))}}),Xe("match",1,function(t,e,i){return[function(e){var i=N(this),n=void 0==e?void 0:e[t];return void 0!==n?n.call(e,i):RegExp(e)[t](i+"")},function(t){var n=i(e,t,this);if(n.done){return n.value}var o=K(t),a=this+"";if(!o.global){return si(o,a)}var s=o.unicode;o.lastIndex=0;for(var r,l=[],c=0;null!==(r=si(o,a));){var h=r[0]+"";l[c]=h,""===h&&(o.lastIndex=ai(a,_t(o.lastIndex),s)),c++}return 0===c?null:l}]});var Xa=G.f,Ja="".startsWith,Qa=Math.min,Za=on("startsWith"),ts=!Za&&!!function(){var t=Xa(String.prototype,"startsWith");return t&&!t.writable}();oe({target:"String",proto:!0,forced:!ts&&!Za},{startsWith:function(t){var e=N(this)+"";en(t);var i=_t(Qa(arguments.length>1?arguments[1]:void 0,e.length)),n=t+"";return Ja?Ja.call(e,n,i):e.slice(i,i+n.length)===n}});var es=G.f,is="".endsWith,ns=Math.min,os=on("endsWith"),as=!os&&!!function(){var t=es(String.prototype,"endsWith");return t&&!t.writable}();oe({target:"String",proto:!0,forced:!as&&!os},{endsWith:function(t){var e=N(this)+"";en(t);var i=arguments.length>1?arguments[1]:void 0,n=_t(e.length),o=void 0===i?n:ns(_t(i),n),a=t+"";return is?is.call(e,a,o):e.slice(o-a.length,o)===a}});var ss={getSearchInput:function(t){return"string"==typeof t.options.searchSelector?y["default"](t.options.searchSelector):t.$toolbar.find(".search input")},sprintf:function(t){for(var e=arguments.length,i=Array(e>1?e-1:0),n=1;e>n;n++){i[n-1]=arguments[n]}var o=!0,a=0,s=t.replace(/%s/g,function(){var t=i[a++];return void 0===t?(o=!1,""):t});return o?s:""},isObject:function(t){return t instanceof Object&&!Array.isArray(t)},isEmptyObject:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return 0===Object.entries(t).length&&t.constructor===Object},isNumeric:function(t){return !isNaN(parseFloat(t))&&isFinite(t)},getFieldTitle:function(t,e){var i,n=v(t);try{for(n.s();!(i=n.n()).done;){var o=i.value;if(o.field===e){return o.title}}}catch(a){n.e(a)}finally{n.f()}return""},setFieldIndex:function(t){var e,i=0,n=[],o=v(t[0]);try{for(o.s();!(e=o.n()).done;){var a=e.value;i+=a.colspan||1}}catch(s){o.e(s)}finally{o.f()}for(var r=0;rl;l++){n[r][l]=!1}}for(var c=0;cb;b++){for(var m=0;p>m;m++){n[c+b][g+m]=!0}}}}catch(s){u.e(s)}finally{u.f()}}},normalizeAccent:function(t){return"string"!=typeof t?t:t.normalize("NFD").replace(/[\u0300-\u036f]/g,"")},updateFieldGroup:function(t){var e,i,n=(e=[]).concat.apply(e,r(t)),o=v(t);try{for(o.s();!(i=o.n()).done;){var a,s=i.value,l=v(s);try{for(l.s();!(a=l.n()).done;){var c=a.value;if(c.colspanGroup>1){for(var h=0,u=function(t){var e=n.find(function(e){return e.fieldIndex===t});e.visible&&h++},d=c.colspanIndex;d0}}}catch(f){l.e(f)}finally{l.f()}}}catch(f){o.e(f)}finally{o.f()}},getScrollBarWidth:function(){if(void 0===this.cachedWidth){var t=y["default"]("
    ").addClass("fixed-table-scroll-inner"),e=y["default"]("
    ").addClass("fixed-table-scroll-outer");e.append(t),y["default"]("body").append(e);var i=t[0].offsetWidth;e.css("overflow","scroll");var n=t[0].offsetWidth;i===n&&(n=e[0].clientWidth),e.remove(),this.cachedWidth=i-n}return this.cachedWidth},calculateObjectValue:function(t,e,n,o){var a=e;if("string"==typeof e){var s=e.split(".");if(s.length>1){a=window;var l,c=v(s);try{for(c.s();!(l=c.n()).done;){var h=l.value;a=a[h]}}catch(u){c.e(u)}finally{c.f()}}else{a=window[e]}}return null!==a&&"object"===i(a)?a:"function"==typeof a?a.apply(t,n||[]):!a&&"string"==typeof e&&this.sprintf.apply(this,[e].concat(r(n)))?this.sprintf.apply(this,[e].concat(r(n))):o},compareObjects:function(t,e,i){var n=Object.keys(t),o=Object.keys(e);if(i&&n.length!==o.length){return !1}for(var a=0,s=n;a/g,">").replace(/"/g,""").replace(/'/g,"'"):t},unescapeHTML:function(t){return t?(""+t).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'"):t},getRealDataAttr:function(t){for(var e=0,i=Object.entries(t);etd,>th").each(function(n,a){for(var s=y["default"](a),l=+s.attr("colspan")||1,c=+s.attr("rowspan")||1,h=n;o[e]&&o[e][h];h++){}for(var u=h;h+l>u;u++){for(var d=e;e+c>d;d++){o[d]||(o[d]=[]),o[d][u]=!0}}var f=t[h].field;r[f]=s.html().trim(),r["_".concat(f,"_id")]=s.attr("id"),r["_".concat(f,"_class")]=s.attr("class"),r["_".concat(f,"_rowspan")]=s.attr("rowspan"),r["_".concat(f,"_colspan")]=s.attr("colspan"),r["_".concat(f,"_title")]=s.attr("title"),r["_".concat(f,"_data")]=i.getRealDataAttr(s.data()),r["_".concat(f,"_style")]=s.attr("style")}),n.push(r)}),n},sort:function(t,e,i,n,o,a){return(void 0===t||null===t)&&(t=""),(void 0===e||null===e)&&(e=""),n&&t===e&&(t=o,e=a),this.isNumeric(t)&&this.isNumeric(e)?(t=parseFloat(t),e=parseFloat(e),e>t?-1*i:t>e?i:0):t===e?0:("string"!=typeof t&&(t=""+t),-1===t.localeCompare(e)?-1*i:i)},getEventName:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return e=e||"".concat(+new Date).concat(~~(1000000*Math.random())),"".concat(t,"-").concat(e)},hasDetailViewIcon:function(t){return t.detailView&&t.detailViewIcon&&!t.cardView},getDetailViewIndexOffset:function(t){return this.hasDetailViewIcon(t)&&"right"!==t.detailViewAlign?1:0},checkAutoMergeCells:function(t){var e,i=v(t);try{for(i.s();!(e=i.n()).done;){for(var n=e.value,o=0,a=Object.keys(n);oo&&r++;for(var l=i;n>l;l++){t[l]&&s.push(t[l])}return{start:i,end:n,topOffset:o,bottomOffset:a,rowsAbove:r,rows:s}}},{key:"checkChanges",value:function(t,e){var i=e!==this.cache[t];return this.cache[t]=e,i}},{key:"getExtra",value:function(t,e){var i=document.createElement("tr");return i.className="virtual-scroll-".concat(t),e&&(i.style.height="".concat(e,"px")),i.outerHTML}}]),t}(),hs=function(){function e(t,i){n(this,e),this.options=i,this.$el=y["default"](t),this.$el_=this.$el.clone(),this.timeoutId_=0,this.timeoutFooter_=0}return a(e,[{key:"init",value:function(){this.initConstants(),this.initLocale(),this.initContainer(),this.initTable(),this.initHeader(),this.initData(),this.initHiddenRows(),this.initToolbar(),this.initPagination(),this.initBody(),this.initSearchText(),this.initServer()}},{key:"initConstants",value:function(){var t=this.options;this.constants=Ka.CONSTANTS,this.constants.theme=y["default"].fn.bootstrapTable.theme,this.constants.dataToggle=this.constants.html.dataToggle||"data-toggle";var e=t.buttonsPrefix?"".concat(t.buttonsPrefix,"-"):"";this.constants.buttonsClass=[t.buttonsPrefix,e+t.buttonsClass,ss.sprintf("".concat(e,"%s"),t.iconSize)].join(" ").trim(),this.buttons=ss.calculateObjectValue(this,t.buttons,[],{}),"object"!==i(this.buttons)&&(this.buttons={}),"string"==typeof t.icons&&(t.icons=ss.calculateObjectValue(null,t.icons))}},{key:"initLocale",value:function(){if(this.options.locale){var t=y["default"].fn.bootstrapTable.locales,i=this.options.locale.split(/-|_/);i[0]=i[0].toLowerCase(),i[1]&&(i[1]=i[1].toUpperCase());var n={};t[this.options.locale]?n=t[this.options.locale]:t[i.join("-")]?n=t[i.join("-")]:t[i[0]]&&(n=t[i[0]]);for(var o=0,a=Object.entries(n);o
    ':"",e=["bottom","both"].includes(this.options.paginationVAlign)?'
    ':"",i=ss.calculateObjectValue(this.options,this.options.loadingTemplate,[this.options.formatLoadingMessage()]);this.$container=y["default"]('\n
    \n
    \n ').concat(t,'\n
    \n
    \n
    \n
    \n ').concat(i,'\n
    \n
    \n \n
    \n ').concat(e,"\n
    \n ")),this.$container.insertAfter(this.$el),this.$tableContainer=this.$container.find(".fixed-table-container"),this.$tableHeader=this.$container.find(".fixed-table-header"),this.$tableBody=this.$container.find(".fixed-table-body"),this.$tableLoading=this.$container.find(".fixed-table-loading"),this.$tableFooter=this.$el.find("tfoot"),this.options.buttonsToolbar?this.$toolbar=y["default"]("body").find(this.options.buttonsToolbar):this.$toolbar=this.$container.find(".fixed-table-toolbar"),this.$pagination=this.$container.find(".fixed-table-pagination"),this.$tableBody.append(this.$el),this.$container.after('
    '),this.$el.addClass(this.options.classes),this.$tableLoading.addClass(this.options.classes),this.options.striped&&this.$el.addClass("table-striped"),this.options.height&&(this.$tableContainer.addClass("fixed-height"),this.options.showFooter&&this.$tableContainer.addClass("has-footer"),this.options.classes.split(" ").includes("table-bordered")&&(this.$tableBody.append('
    '),this.$tableBorder=this.$tableBody.find(".fixed-table-border"),this.$tableLoading.addClass("fixed-table-border")),this.$tableFooter=this.$container.find(".fixed-table-footer"))}},{key:"initTable",value:function(){var t=this,i=[];if(this.$header=this.$el.find(">thead"),this.$header.length?this.options.theadClasses&&this.$header.addClass(this.options.theadClasses):this.$header=y["default"]('')).appendTo(this.$el),this._headerTrClasses=[],this._headerTrStyles=[],this.$header.find("tr").each(function(e,n){var o=y["default"](n),a=[];o.find("th").each(function(t,e){var i=y["default"](e);void 0!==i.data("field")&&i.data("field","".concat(i.data("field"))),a.push(y["default"].extend({},{title:i.html(),"class":i.attr("class"),titleTooltip:i.attr("title"),rowspan:i.attr("rowspan")?+i.attr("rowspan"):void 0,colspan:i.attr("colspan")?+i.attr("colspan"):void 0},i.data()))}),i.push(a),o.attr("class")&&t._headerTrClasses.push(o.attr("class")),o.attr("style")&&t._headerTrStyles.push(o.attr("style"))}),Array.isArray(this.options.columns[0])||(this.options.columns=[this.options.columns]),this.options.columns=y["default"].extend(!0,[],i,this.options.columns),this.columns=[],this.fieldsColumnsIndex=[],ss.setFieldIndex(this.options.columns),this.options.columns.forEach(function(i,n){i.forEach(function(i,o){var a=y["default"].extend({},e.COLUMN_DEFAULTS,i);void 0!==a.fieldIndex&&(t.columns[a.fieldIndex]=a,t.fieldsColumnsIndex[a.field]=a.fieldIndex),t.options.columns[n][o]=a})}),!this.options.data.length){var n=ss.trToData(this.columns,this.$el.find(">tbody>tr"));n.length&&(this.options.data=n,this.fromHtml=!0)}this.options.pagination&&"server"!==this.options.sidePagination||(this.footerData=ss.trToData(this.columns,this.$el.find(">tfoot>tr"))),this.footerData&&this.$el.find("tfoot").html(""),!this.options.showFooter||this.options.cardView?this.$tableFooter.hide():this.$tableFooter.show()}},{key:"initHeader",value:function(){var t=this,e={},i=[];this.header={fields:[],styles:[],classes:[],formatters:[],detailFormatters:[],events:[],sorters:[],sortNames:[],cellStyles:[],searchables:[]},ss.updateFieldGroup(this.options.columns),this.options.columns.forEach(function(n,o){var a=[];a.push(""));var r="";if(0===o&&ss.hasDetailViewIcon(t.options)){var l=t.options.columns.length>1?' rowspan="'.concat(t.options.columns.length,'"'):"";r='\n
    \n ')}r&&"right"!==t.options.detailViewAlign&&a.push(r),n.forEach(function(i,n){var r=ss.sprintf(' class="%s"',i["class"]),l=i.widthUnit,c=parseFloat(i.width),h=ss.sprintf("text-align: %s; ",i.halign?i.halign:i.align),u=ss.sprintf("text-align: %s; ",i.align),d=ss.sprintf("vertical-align: %s; ",i.valign);if(d+=ss.sprintf("width: %s; ",!i.checkbox&&!i.radio||c?c?c+l:void 0:i.showSelectTitle?void 0:"36px"),void 0!==i.fieldIndex||i.visible){var f=ss.calculateObjectValue(null,t.options.headerStyle,[i]),p=[],g="";if(f&&f.css){for(var v=0,b=Object.entries(f.css);v0?" data-not-first-th":"",">"),a.push(ss.sprintf('
    ',t.options.sortable&&i.sortable?"sortable both":""));var S=t.options.escape?ss.escapeHTML(i.title):i.title,x=S;i.checkbox&&(S="",!t.options.singleSelect&&t.options.checkboxHeader&&(S=''),t.header.stateField=i.field),i.radio&&(S="",t.header.stateField=i.field),!S&&i.showSelectTitle&&(S+=x),a.push(S),a.push("
    "),a.push('
    '),a.push("
    "),a.push("")}}),r&&"right"===t.options.detailViewAlign&&a.push(r),a.push(""),a.length>3&&i.push(a.join(""))}),this.$header.html(i.join("")),this.$header.find("th[data-field]").each(function(t,i){y["default"](i).data(e[y["default"](i).data("field")])}),this.$container.off("click",".th-inner").on("click",".th-inner",function(e){var i=y["default"](e.currentTarget);return t.options.detailView&&!i.parent().hasClass("bs-checkbox")&&i.closest(".bootstrap-table")[0]!==t.$container[0]?!1:void (t.options.sortable&&i.parent().data().sortable&&t.onSort(e))});var n=ss.getEventName("resize.bootstrap-table",this.$el.attr("id"));y["default"](window).off(n),!this.options.showHeader||this.options.cardView?(this.$header.hide(),this.$tableHeader.hide(),this.$tableLoading.css("top",0)):(this.$header.show(),this.$tableHeader.show(),this.$tableLoading.css("top",this.$header.outerHeight()+1),this.getCaret(),y["default"](window).on(n,function(){return t.resetView()})),this.$selectAll=this.$header.find('[name="btSelectAll"]'),this.$selectAll.off("click").on("click",function(e){e.stopPropagation();var i=y["default"](e.currentTarget).prop("checked");t[i?"checkAll":"uncheckAll"](),t.updateSelected()})}},{key:"initData",value:function(t,e){"append"===e?this.options.data=this.options.data.concat(t):"prepend"===e?this.options.data=[].concat(t).concat(this.options.data):(t=t||ss.deepCopy(this.options.data),this.options.data=Array.isArray(t)?t:t[this.options.dataField]),this.data=r(this.options.data),this.options.sortReset&&(this.unsortedData=r(this.data)),"server"!==this.options.sidePagination&&this.initSort()}},{key:"initSort",value:function(){var t=this,e=this.options.sortName,i="desc"===this.options.sortOrder?-1:1,n=this.header.fields.indexOf(this.options.sortName),o=0;-1!==n?(this.options.sortStable&&this.data.forEach(function(t,e){t.hasOwnProperty("_position")||(t._position=e)}),this.options.customSort?ss.calculateObjectValue(this.options,this.options.customSort,[this.options.sortName,this.options.sortOrder,this.data]):this.data.sort(function(o,a){t.header.sortNames[n]&&(e=t.header.sortNames[n]);var s=ss.getItemField(o,e,t.options.escape),r=ss.getItemField(a,e,t.options.escape),l=ss.calculateObjectValue(t.header,t.header.sorters[n],[s,r,o,a]);return void 0!==l?t.options.sortStable&&0===l?i*(o._position-a._position):i*l:ss.sort(s,r,i,t.options.sortStable,o._position,a._position)}),void 0!==this.options.sortClass&&(clearTimeout(o),o=setTimeout(function(){t.$el.removeClass(t.options.sortClass);var e=t.$header.find('[data-field="'.concat(t.options.sortName,'"]')).index();t.$el.find("tr td:nth-child(".concat(e+1,")")).addClass(t.options.sortClass)},250))):this.options.sortReset&&(this.data=r(this.unsortedData))}},{key:"onSort",value:function(t){var e=t.type,i=t.currentTarget,n="keypress"===e?y["default"](i):y["default"](i).parent(),o=this.$header.find("th").eq(n.index());if(this.$header.add(this.$header_).find("span.order").remove(),this.options.sortName===n.data("field")){var a=this.options.sortOrder;void 0===a?this.options.sortOrder="asc":"asc"===a?this.options.sortOrder="desc":"desc"===this.options.sortOrder&&(this.options.sortOrder=this.options.sortReset?void 0:"asc"),void 0===this.options.sortOrder&&(this.options.sortName=void 0)}else{this.options.sortName=n.data("field"),this.options.rememberOrder?this.options.sortOrder="asc"===n.data("order")?"desc":"asc":this.options.sortOrder=this.columns[this.fieldsColumnsIndex[n.data("field")]].sortOrder||this.columns[this.fieldsColumnsIndex[n.data("field")]].order}return this.trigger("sort",this.options.sortName,this.options.sortOrder),n.add(o).data("order",this.options.sortOrder),this.getCaret(),"server"===this.options.sidePagination&&this.options.serverSort?(this.options.pageNumber=1,void this.initServer(this.options.silentSort)):(this.initSort(),void this.initBody())}},{key:"initToolbar",value:function(){var t,e=this,n=this.options,o=[],a=0,r=0;this.$toolbar.find(".bs-bars").children().length&&y["default"]("body").append(y["default"](n.toolbar)),this.$toolbar.html(""),("string"==typeof n.toolbar||"object"===i(n.toolbar))&&y["default"](ss.sprintf('
    ',this.constants.classes.pull,n.toolbarAlign)).appendTo(this.$toolbar).append(y["default"](n.toolbar)),o=['
    ')],"string"==typeof n.buttonsOrder&&(n.buttonsOrder=n.buttonsOrder.replace(/\[|\]| |'/g,"").split(",")),this.buttons=Object.assign(this.buttons,{search:{text:n.formatSearch(),icon:n.icons.search,render:!1,event:this.toggleShowSearch,attributes:{"aria-label":n.formatShowSearch(),title:n.formatShowSearch()}},paginationSwitch:{text:n.pagination?n.formatPaginationSwitchUp():n.formatPaginationSwitchDown(),icon:n.pagination?n.icons.paginationSwitchDown:n.icons.paginationSwitchUp,render:!1,event:this.togglePagination,attributes:{"aria-label":n.formatPaginationSwitch(),title:n.formatPaginationSwitch()}},refresh:{text:n.formatRefresh(),icon:n.icons.refresh,render:!1,event:this.refresh,attributes:{"aria-label":n.formatRefresh(),title:n.formatRefresh()}},toggle:{text:n.formatToggle(),icon:n.icons.toggleOff,render:!1,event:this.toggleView,attributes:{"aria-label":n.formatToggleOn(),title:n.formatToggleOn()}},fullscreen:{text:n.formatFullscreen(),icon:n.icons.fullscreen,render:!1,event:this.toggleFullscreen,attributes:{"aria-label":n.formatFullscreen(),title:n.formatFullscreen()}},columns:{render:!1,html:function X(){var X=[];if(X.push('
    \n \n ").concat(e.constants.html.toolbarDropdown[0])),n.showColumnsSearch&&(X.push(ss.sprintf(e.constants.html.toolbarDropdownItem,ss.sprintf('',e.constants.classes.input,n.formatSearch()))),X.push(e.constants.html.toolbarDropdownSeparator)),n.showColumnsToggleAll){var t=e.getVisibleColumns().length===e.columns.filter(function(t){return !e.isSelectionColumn(t)}).length;X.push(ss.sprintf(e.constants.html.toolbarDropdownItem,ss.sprintf(' %s',t?'checked="checked"':"",n.formatColumnsToggleAll()))),X.push(e.constants.html.toolbarDropdownSeparator)}var i=0;return e.columns.forEach(function(t){t.visible&&i++}),e.columns.forEach(function(t,o){if(!e.isSelectionColumn(t)&&(!n.cardView||t.cardVisible)&&!t.ignore){var a=t.visible?' checked="checked"':"",s=i<=n.minimumCountColumns&&a?' disabled="disabled"':"";t.switchable&&(X.push(ss.sprintf(e.constants.html.toolbarDropdownItem,ss.sprintf(' %s',t.field,o,a,s,t.title))),r++)}}),X.push(e.constants.html.toolbarDropdown[1],"
    "),X.join("")}}});for(var l={},c=0,h=Object.entries(this.buttons);c"}l[d]=p;var x="show".concat(d.charAt(0).toUpperCase()).concat(d.substring(1)),k=n[x];!(!f.hasOwnProperty("render")||f.hasOwnProperty("render")&&f.render)||void 0!==k&&k!==!0||(n[x]=!0),n.buttonsOrder.includes(d)||n.buttonsOrder.push(d)}var O,T=v(n.buttonsOrder);try{for(T.s();!(O=T.n()).done;){var C=O.value,P=n["show".concat(C.charAt(0).toUpperCase()).concat(C.substring(1))];P&&o.push(l[C])}}catch(I){T.e(I)}finally{T.f()}o.push("
    "),(this.showToolbar||o.length>2)&&this.$toolbar.append(o.join("")),n.showSearch&&this.$toolbar.find('button[name="showSearch"]').off("click").on("click",function(){return e.toggleShowSearch()});for(var A=0,$=Object.entries(this.buttons);A<$.length;A++){var R=s($[A],2),E=R[0],j=R[1];if(j.hasOwnProperty("event")){if("function"==typeof j.event||"string"==typeof j.event){var _=function(){var t="string"==typeof j.event?window[j.event]:j.event;return e.$toolbar.find('button[name="'.concat(E,'"]')).off("click").on("click",function(){return t.call(e)}),"continue"}();if("continue"===_){continue}}for(var N=function(){var t=s(D[F],2),i=t[0],n=t[1],o="string"==typeof n?window[n]:n;e.$toolbar.find('button[name="'.concat(E,'"]')).off(i).on(i,function(){return o.call(e)})},F=0,D=Object.entries(j.event);F'),W=z;if(n.showSearchButton||n.showSearchClearButton){var G=(n.showSearchButton?U:"")+(n.showSearchClearButton?q:"");W=n.search?ss.sprintf(this.constants.html.inputGroup,z,G):G}o.push(ss.sprintf('\n
    \n %s\n
    \n '),W)),this.$toolbar.append(o.join(""));var K=ss.getSearchInput(this);n.showSearchButton?(this.$toolbar.find(".search button[name=search]").off("click").on("click",function(){clearTimeout(a),a=setTimeout(function(){e.onSearch({currentTarget:K})},n.searchTimeOut)}),n.searchOnEnterKey&&M(K)):M(K),n.showSearchClearButton&&this.$toolbar.find(".search button[name=clearSearch]").click(function(){e.resetSearch()})}else{if("string"==typeof n.searchSelector){var Y=ss.getSearchInput(this);M(Y)}}}},{key:"onSearch",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.currentTarget,i=t.firedByInitSearchText,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:!0;if(void 0!==e&&y["default"](e).length&&n){var o=y["default"](e).val().trim();if(this.options.trimOnSearch&&y["default"](e).val()!==o&&y["default"](e).val(o),this.searchText===o){return}var a=ss.getSearchInput(this),s=e instanceof jQuery?e:y["default"](e);(s.is(a)||s.hasClass("search-input"))&&(this.searchText=o,this.options.searchText=o)}i||(this.options.pageNumber=1),this.initSearch(),i?"client"===this.options.sidePagination&&this.updatePagination():this.updatePagination(),this.trigger("search",this.searchText)}},{key:"initSearch",value:function(){var t=this;if(this.filterOptions=this.filterOptions||this.options.filterOptions,"server"!==this.options.sidePagination){if(this.options.customSearch){return this.data=ss.calculateObjectValue(this.options,this.options.customSearch,[this.options.data,this.searchText,this.filterColumns]),void (this.options.sortReset&&(this.unsortedData=r(this.data)))}var e=this.searchText&&(this.fromHtml?ss.escapeHTML(this.searchText):this.searchText),i=e?e.toLowerCase():"",n=ss.isEmptyObject(this.filterColumns)?null:this.filterColumns;this.options.searchAccentNeutralise&&(i=ss.normalizeAccent(i)),"function"==typeof this.filterOptions.filterAlgorithm?this.data=this.options.data.filter(function(e){return t.filterOptions.filterAlgorithm.apply(null,[e,n])}):"string"==typeof this.filterOptions.filterAlgorithm&&(this.data=n?this.options.data.filter(function(e){var i=t.filterOptions.filterAlgorithm;if("and"===i){for(var o in n){if(Array.isArray(n[o])&&!n[o].includes(e[o])||!Array.isArray(n[o])&&e[o]!==n[o]){return !1}}}else{if("or"===i){var a=!1;for(var s in n){(Array.isArray(n[s])&&n[s].includes(e[s])||!Array.isArray(n[s])&&e[s]===n[s])&&(a=!0)}return a}}return !0}):r(this.options.data));var o=this.getVisibleFields();this.data=i?this.data.filter(function(n,a){for(var s=0;s|=<|>=|>|<)(?:\s+)?(-?\d+)?|(-?\d+)?(\s+)?(<=|=>|=<|>=|>|<))/gm,f=d.exec(t.searchText),p=!1;if(f){var g=f[1]||"".concat(f[5],"l"),v=f[2]||f[3],b=parseInt(c,10),m=parseInt(v,10);switch(g){case">":case"m;break;case"<":case">l":p=m>b;break;case"<=":case"=<":case">=l":case"=>l":p=m>=b;break;case">=":case"=>":case"<=l":case"==m}}if(p||"".concat(c).toLowerCase().includes(i)){return !0}}}}return !1}):this.data,this.options.sortReset&&(this.unsortedData=r(this.data)),this.initSort()}}},{key:"initPagination",value:function(){var e=this,i=this.options;if(!i.pagination){return void this.$pagination.hide()}this.$pagination.show();var n,o,a,s,r,l,c,h=[],u=!1,d=this.getData({includeHiddenRows:!1}),f=i.pageList;if("string"==typeof f&&(f=f.replace(/\[|\]| /g,"").toLowerCase().split(",")),f=f.map(function(t){return"string"==typeof t?t.toLowerCase()===i.formatAllRows().toLowerCase()||["all","unlimited"].includes(t.toLowerCase())?i.formatAllRows():+t:t}),this.paginationParts=i.paginationParts,"string"==typeof this.paginationParts&&(this.paginationParts=this.paginationParts.replace(/\[|\]| |'/g,"").split(",")),"server"!==i.sidePagination&&(i.totalRows=d.length),this.totalPages=0,i.totalRows&&(i.pageSize===i.formatAllRows()&&(i.pageSize=i.totalRows,u=!0),this.totalPages=~~((i.totalRows-1)/i.pageSize)+1,i.totalPages=this.totalPages),this.totalPages>0&&i.pageNumber>this.totalPages&&(i.pageNumber=this.totalPages),this.pageFrom=(i.pageNumber-1)*i.pageSize+1,this.pageTo=i.pageNumber*i.pageSize,this.pageTo>i.totalRows&&(this.pageTo=i.totalRows),this.options.pagination&&"server"!==this.options.sidePagination&&(this.options.totalNotFiltered=this.options.data.length),this.options.showExtendedPagination||(this.options.totalNotFiltered=void 0),(this.paginationParts.includes("pageInfo")||this.paginationParts.includes("pageInfoShort")||this.paginationParts.includes("pageSize"))&&h.push('
    ')),this.paginationParts.includes("pageInfo")||this.paginationParts.includes("pageInfoShort")){var p=this.paginationParts.includes("pageInfoShort")?i.formatDetailPagination(i.totalRows):i.formatShowingRows(this.pageFrom,this.pageTo,i.totalRows,i.totalNotFiltered);h.push('\n '.concat(p,"\n "))}if(this.paginationParts.includes("pageSize")){h.push('
    ');var g=['
    \n \n ").concat(this.constants.html.pageDropdown[0])];f.forEach(function(t,n){if(!i.smartDisplay||0===n||f[n-1]")),h.push(i.formatRecordsPerPage(g.join("")))}if((this.paginationParts.includes("pageInfo")||this.paginationParts.includes("pageInfoShort")||this.paginationParts.includes("pageSize"))&&h.push("
    "),this.paginationParts.includes("pageList")){h.push('
    '),ss.sprintf(this.constants.html.pagination[0],ss.sprintf(" pagination-%s",i.iconSize)),ss.sprintf(this.constants.html.paginationItem," page-pre",i.formatSRPaginationPreText(),i.paginationPreText)),this.totalPagesthis.totalPages-o&&(o=o-(i.paginationSuccessivelySize-(this.totalPages-o))+1),1>o&&(o=1),a>this.totalPages&&(a=this.totalPages);var v=Math.round(i.paginationPagesBySide/2),b=function(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return ss.sprintf(e.constants.html.paginationItem,n+(t===i.pageNumber?" ".concat(e.constants.classes.paginationActive):""),i.formatSRPaginationPageText(t),t)};if(o>1){var m=i.paginationPagesBySide;for(m>=o&&(m=o-1),n=1;m>=n;n++){h.push(b(n))}o-1===m+1?(n=o-1,h.push(b(n))):o-1>m&&(o-2*i.paginationPagesBySide>i.paginationPagesBySide&&i.paginationUseIntermediate?(n=Math.round((o-v)/2+v),h.push(b(n," page-intermediate"))):h.push(ss.sprintf(this.constants.html.paginationItem," page-first-separator disabled","","...")))}for(n=o;a>=n;n++){h.push(b(n))}if(this.totalPages>a){var y=this.totalPages-(i.paginationPagesBySide-1);for(a>=y&&(y=a+1),a+1===y-1?(n=a+1,h.push(b(n))):y>a+1&&(this.totalPages-a>2*i.paginationPagesBySide&&i.paginationUseIntermediate?(n=Math.round((this.totalPages-v-a)/2+a),h.push(b(n," page-intermediate"))):h.push(ss.sprintf(this.constants.html.paginationItem," page-last-separator disabled","","..."))),n=y;n<=this.totalPages;n++){h.push(b(n))}}h.push(ss.sprintf(this.constants.html.paginationItem," page-next",i.formatSRPaginationNextText(),i.paginationNextText)),h.push(this.constants.html.pagination[1],"
    ")}this.$pagination.html(h.join(""));var w=["bottom","both"].includes(i.paginationVAlign)?" ".concat(this.constants.classes.dropup):"";if(this.$pagination.last().find(".page-list > div").addClass(w),!i.onlyInfoPagination&&(s=this.$pagination.find(".page-list a"),r=this.$pagination.find(".page-pre"),l=this.$pagination.find(".page-next"),c=this.$pagination.find(".page-item").not(".page-next, .page-pre, .page-last-separator, .page-first-separator"),this.totalPages<=1&&this.$pagination.find("div.pagination").hide(),i.smartDisplay&&(f.length<2||i.totalRows<=f[0])&&this.$pagination.find("div.page-list").hide(),this.$pagination[this.getData().length?"show":"hide"](),i.paginationLoop||(1===i.pageNumber&&r.addClass("disabled"),i.pageNumber===this.totalPages&&l.addClass("disabled")),u&&(i.pageSize=i.formatAllRows()),s.off("click").on("click",function(t){return e.onPageListChange(t)}),r.off("click").on("click",function(t){return e.onPagePre(t)}),l.off("click").on("click",function(t){return e.onPageNext(t)}),c.off("click").on("click",function(t){return e.onPageNumber(t)}),this.options.showPageGo)){var S=this,x=this.$pagination.find("ul.pagination"),k=x.find("li.pageGo");k.length||(k=t('
  • '+ss.sprintf('',this.options.pageNumber)+('
  • ").appendTo(x),k.find("button").click(function(){var t=parseInt(k.find("input").val())||1;(1>t||t>S.options.totalPages)&&(t=1),S.selectPage(t)}))}}},{key:"updatePagination",value:function(t){t&&y["default"](t.currentTarget).hasClass("disabled")||(this.options.maintainMetaData||this.resetRows(),this.initPagination(),this.trigger("page-change",this.options.pageNumber,this.options.pageSize),"server"===this.options.sidePagination?this.initServer():this.initBody())}},{key:"onPageListChange",value:function(t){t.preventDefault();var e=y["default"](t.currentTarget);return e.parent().addClass(this.constants.classes.dropdownActive).siblings().removeClass(this.constants.classes.dropdownActive),this.options.pageSize=e.text().toUpperCase()===this.options.formatAllRows().toUpperCase()?this.options.formatAllRows():+e.text(),this.$toolbar.find(".page-size").text(this.options.pageSize),this.updatePagination(t),!1}},{key:"onPagePre",value:function(t){return y["default"](t.target).hasClass("disabled")?void 0:(t.preventDefault(),this.options.pageNumber-1===0?this.options.pageNumber=this.options.totalPages:this.options.pageNumber--,this.updatePagination(t),!1)}},{key:"onPageNext",value:function(t){return y["default"](t.target).hasClass("disabled")?void 0:(t.preventDefault(),this.options.pageNumber+1>this.options.totalPages?this.options.pageNumber=1:this.options.pageNumber++,this.updatePagination(t),!1)}},{key:"onPageNumber",value:function(t){return t.preventDefault(),this.options.pageNumber!==+y["default"](t.currentTarget).text()?(this.options.pageNumber=+y["default"](t.currentTarget).text(),this.updatePagination(t),!1):void 0}},{key:"initRow",value:function(t,e,n,o){var a=this,r=[],l={},c=[],h="",u={},d=[];if(!(ss.findIndex(this.hiddenRows,t)>-1)){if(l=ss.calculateObjectValue(this.options,this.options.rowStyle,[t,e],l),l&&l.css){for(var f=0,p=Object.entries(l.css);f"),this.options.cardView&&r.push('
    '));var I="";return ss.hasDetailViewIcon(this.options)&&(I="",ss.calculateObjectValue(null,this.options.detailFilter,[e,t])&&(I+='\n \n '.concat(ss.sprintf(this.constants.html.icon,this.options.iconsPrefix,this.options.icons.detailOpen),"\n \n ")),I+=""),I&&"right"!==this.options.detailViewAlign&&r.push(I),this.header.fields.forEach(function(i,n){var o="",l=ss.getItemField(t,i,a.options.escape),h="",u="",d={},f="",p=a.header.classes[n],g="",v="",b="",m="",y="",w="",S=a.columns[n];if((!a.fromHtml&&!a.autoMergeCells||void 0!==l||S.checkbox||S.radio)&&S.visible&&(!a.options.cardView||S.cardVisible)){if(S.escape&&(l=ss.escapeHTML(l)),c.concat([a.header.styles[n]]).length&&(v+="".concat(c.concat([a.header.styles[n]]).join("; "))),t["_".concat(i,"_style")]&&(v+="".concat(t["_".concat(i,"_style")])),v&&(g=' style="'.concat(v,'"')),t["_".concat(i,"_id")]&&(f=ss.sprintf(' id="%s"',t["_".concat(i,"_id")])),t["_".concat(i,"_class")]&&(p=ss.sprintf(' class="%s"',t["_".concat(i,"_class")])),t["_".concat(i,"_rowspan")]&&(m=ss.sprintf(' rowspan="%s"',t["_".concat(i,"_rowspan")])),t["_".concat(i,"_colspan")]&&(y=ss.sprintf(' colspan="%s"',t["_".concat(i,"_colspan")])),t["_".concat(i,"_title")]&&(w=ss.sprintf(' title="%s"',t["_".concat(i,"_title")])),d=ss.calculateObjectValue(a.header,a.header.cellStyles[n],[l,t,e,i],d),d.classes&&(p=' class="'.concat(d.classes,'"')),d.css){for(var x=[],k=0,O=Object.entries(d.css);k$1",R=h&&/<(?=.*? .*?\/ ?>|br|hr|input|!--|wbr)[a-z]+.*?>|<([a-z]+).*?<\/\1>/i.test(h);if(R){var E=(new DOMParser).parseFromString(""+h,"text/html").documentElement.textContent,j=E.replace(A,$);E=E.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),I=h.replace(RegExp("(>\\s*)(".concat(E,")(\\s*)"),"gm"),"$1".concat(j,"$3"))}else{I=(""+h).replace(A,$)}h=ss.calculateObjectValue(S,S.searchHighlightFormatter,[h,a.searchText],I)}if(t["_".concat(i,"_data")]&&!ss.isEmptyObject(t["_".concat(i,"_data")])){for(var _=0,N=Object.entries(t["_".concat(i,"_data")]);_'):'"))+'")+(a.header.formatters[n]&&"string"==typeof h?h:"")+(a.options.cardView?"
    ":""),t[a.header.stateField]=h===!0||!!l||h&&h.checked}else{if(a.options.cardView){var M=a.options.showHeader?'").concat(ss.getFieldTitle(a.columns,i),""):"";o='
    '.concat(M,'").concat(h,"
    "),a.options.smartDisplay&&""===h&&(o='
    ')}else{o="").concat(h,"")}}r.push(o)}}),I&&"right"===this.options.detailViewAlign&&r.push(I),this.options.cardView&&r.push("
    "),r.push(""),r.join("")}}},{key:"initBody",value:function(t,e){var i=this,n=this.getData();this.trigger("pre-body",n),this.$body=this.$el.find(">tbody"),this.$body.length||(this.$body=y["default"]("").appendTo(this.$el)),this.options.pagination&&"server"!==this.options.sidePagination||(this.pageFrom=1,this.pageTo=n.length);var o=[],a=y["default"](document.createDocumentFragment()),s=!1,r=[];this.autoMergeCells=ss.checkAutoMergeCells(n.slice(this.pageFrom-1,this.pageTo));for(var l=this.pageFrom-1;l tr[data-uniqueid="%s"][data-has-detail-view]',d)),p=f.next();p.is("tr.detail-view")&&(r.push(l),e&&d===e||(h+=p[0].outerHTML))}this.options.virtualScroll?o.push(h):a.append(h)}}s?this.options.virtualScroll?(this.virtualScroll&&this.virtualScroll.destroy(),this.virtualScroll=new cs({rows:o,fixedScroll:t,scrollEl:this.$tableBody[0],contentEl:this.$body[0],itemHeight:this.options.virtualScrollItemHeight,callback:function(t,e){i.fitHeader(),i.initBodyEvent(),i.trigger("virtual-scroll",t,e)}})):this.$body.html(a):this.$body.html(''.concat(ss.sprintf('%s',this.getVisibleFields().length+ss.getDetailViewIndexOffset(this.options),this.options.formatNoMatches()),"")),r.forEach(function(t){i.expandRow(t)}),t||this.scrollTo(0),this.initBodyEvent(),this.initFooter(),this.resetView(),this.updateSelected(),"server"!==this.options.sidePagination&&(this.options.totalRows=n.length),this.trigger("post-body",n)}},{key:"initBodyEvent",value:function(){var t=this;this.$body.find("> tr[data-index] > td").off("click dblclick").on("click dblclick",function(e){var i=y["default"](e.currentTarget),n=i.parent(),o=y["default"](e.target).parents(".card-views").children(),a=y["default"](e.target).parents(".card-view"),s=n.data("index"),r=t.data[s],l=t.options.cardView?o.index(a):i[0].cellIndex,c=t.getVisibleFields(),h=c[l-ss.getDetailViewIndexOffset(t.options)],u=t.columns[t.fieldsColumnsIndex[h]],d=ss.getItemField(r,h,t.options.escape);if(!i.find(".detail-icon").length){if(t.trigger("click"===e.type?"click-cell":"dbl-click-cell",h,d,r,i),t.trigger("click"===e.type?"click-row":"dbl-click-row",r,n,h),"click"===e.type&&t.options.clickToSelect&&u.clickToSelect&&!ss.calculateObjectValue(t.options,t.options.ignoreClickToSelectOn,[e.target])){var f=n.find(ss.sprintf('[name="%s"]',t.options.selectItemName));f.length&&f[0].click()}"click"===e.type&&t.options.detailViewByClick&&t.toggleDetailView(s,t.header.detailFormatters[t.fieldsColumnsIndex[h]])}}).off("mousedown").on("mousedown",function(e){t.multipleSelectRowCtrlKey=e.ctrlKey||e.metaKey,t.multipleSelectRowShiftKey=e.shiftKey}),this.$body.find("> tr[data-index] > td > .detail-icon").off("click").on("click",function(e){return e.preventDefault(),t.toggleDetailView(y["default"](e.currentTarget).parent().parent().data("index")),!1}),this.$selectItem=this.$body.find(ss.sprintf('[name="%s"]',this.options.selectItemName)),this.$selectItem.off("click").on("click",function(e){e.stopImmediatePropagation();var i=y["default"](e.currentTarget);t._toggleCheck(i.prop("checked"),i.data("index"))}),this.header.events.forEach(function(e,i){var n=e;if(n){"string"==typeof n&&(n=ss.calculateObjectValue(null,n));var o=t.header.fields[i],a=t.getVisibleFields().indexOf(o);if(-1!==a){a+=ss.getDetailViewIndexOffset(t.options);var s=function(e){if(!n.hasOwnProperty(e)){return"continue"}var i=n[e];t.$body.find(">tr:not(.no-records-found)").each(function(n,s){var r=y["default"](s),l=r.find(t.options.cardView?".card-views>.card-view":">td").eq(a),c=e.indexOf(" "),h=e.substring(0,c),u=e.substring(c+1);l.find(u).off(h).on(h,function(e){var n=r.data("index"),a=t.data[n],s=a[o];i.apply(t,[e,s,a,n])})})};for(var r in n){s(r)}}}})}},{key:"initServer",value:function(t,e,i){var n=this,o={},a=this.header.fields.indexOf(this.options.sortName),s={searchText:this.searchText,sortName:this.options.sortName,sortOrder:this.options.sortOrder};if(this.header.sortNames[a]&&(s.sortName=this.header.sortNames[a]),this.options.pagination&&"server"===this.options.sidePagination&&(s.pageSize=this.options.pageSize===this.options.formatAllRows()?this.options.totalRows:this.options.pageSize,s.pageNumber=this.options.pageNumber),!this.options.firstLoad&&!firstLoadTable.includes(this.options.id)){return void firstLoadTable.push(this.options.id)}if(i||this.options.url||this.options.ajax){if("limit"===this.options.queryParamsType&&(s={search:s.searchText,sort:s.sortName,order:s.sortOrder},this.options.pagination&&"server"===this.options.sidePagination&&(s.offset=this.options.pageSize===this.options.formatAllRows()?0:this.options.pageSize*(this.options.pageNumber-1),s.limit=this.options.pageSize,(0===s.limit||this.options.pageSize===this.options.formatAllRows())&&delete s.limit)),this.options.search&&"server"===this.options.sidePagination&&this.columns.filter(function(t){return !t.searchable}).length){s.searchable=[];var r,l=v(this.columns);try{for(l.s();!(r=l.n()).done;){var c=r.value;!c.checkbox&&c.searchable&&(this.options.visibleSearch&&c.visible||!this.options.visibleSearch)&&s.searchable.push(c.field)}}catch(h){l.e(h)}finally{l.f()}}if(ss.isEmptyObject(this.filterColumnsPartial)||(s.filter=JSON.stringify(this.filterColumnsPartial,null)),y["default"].extend(s,e||{}),o=ss.calculateObjectValue(this.options,this.options.queryParams,[s],o),o!==!1){t||this.showLoading();var u=y["default"].extend({},ss.calculateObjectValue(null,this.options.ajaxOptions),{type:this.options.method,url:i||this.options.url,data:"application/json"===this.options.contentType&&"post"===this.options.method?JSON.stringify(o):o,cache:this.options.cache,contentType:this.options.contentType,dataType:this.options.dataType,success:function(e,i,o){var a=ss.calculateObjectValue(n.options,n.options.responseHandler,[e,o],e);n.load(a),n.trigger("load-success",a,o&&o.status,o),t||n.hideLoading(),"server"===n.options.sidePagination&&n.options.pageNumber>1&&a[n.options.totalField]>0&&!a[n.options.dataField].length&&n.updatePagination()},error:function(e){if(e&&0===e.status&&n._xhrAbort){return void (n._xhrAbort=!1)}var i=[];"server"===n.options.sidePagination&&(i={},i[n.options.totalField]=0,i[n.options.dataField]=[]),n.load(i),n.trigger("load-error",e&&e.status,e),t||n.$tableLoading.hide()}});return this.options.ajax?ss.calculateObjectValue(this,this.options.ajax,[u],null):(this._xhr&&4!==this._xhr.readyState&&(this._xhrAbort=!0,this._xhr.abort()),this._xhr=y["default"].ajax(u)),o}}}},{key:"initSearchText",value:function(){if(this.options.search&&(this.searchText="",""!==this.options.searchText)){var t=ss.getSearchInput(this);t.val(this.options.searchText),this.onSearch({currentTarget:t,firedByInitSearchText:!0})}}},{key:"getCaret",value:function(){var t=this;this.$header.find("th").each(function(e,i){y["default"](i).find(".sortable").removeClass("desc asc").addClass(y["default"](i).data("field")===t.options.sortName?t.options.sortOrder:"both")})}},{key:"updateSelected",value:function(){var t=this.$selectItem.filter(":enabled").length&&this.$selectItem.filter(":enabled").length===this.$selectItem.filter(":enabled").filter(":checked").length;this.$selectAll.add(this.$selectAll_).prop("checked",t),this.$selectItem.each(function(t,e){y["default"](e).closest("tr")[y["default"](e).prop("checked")?"addClass":"removeClass"]("selected")})}},{key:"updateRows",value:function(){var t=this;this.$selectItem.each(function(e,i){t.data[y["default"](i).data("index")][t.header.stateField]=y["default"](i).prop("checked")})}},{key:"resetRows",value:function(){var t,e=v(this.data);try{for(e.s();!(t=e.n()).done;){var i=t.value;this.$selectAll.prop("checked",!1),this.$selectItem.prop("checked",!1),this.header.stateField&&(i[this.header.stateField]=!1)}}catch(n){e.e(n)}finally{e.f()}this.initHiddenRows()}},{key:"trigger",value:function(t){for(var i,n,o="".concat(t,".bs.table"),a=arguments.length,s=Array(a>1?a-1:0),r=1;a>r;r++){s[r-1]=arguments[r]}(i=this.options)[e.EVENTS[o]].apply(i,[].concat(s,[this])),this.$el.trigger(y["default"].Event(o,{sender:this}),s),(n=this.options).onAll.apply(n,[o].concat([].concat(s,[this]))),this.$el.trigger(y["default"].Event("all.bs.table",{sender:this}),[o,s])}},{key:"resetHeader",value:function(){var t=this;clearTimeout(this.timeoutId_),this.timeoutId_=setTimeout(function(){return t.fitHeader()},this.$el.is(":hidden")?100:0)}},{key:"fitHeader",value:function(){var t=this;if(this.$el.is(":hidden")){return void (this.timeoutId_=setTimeout(function(){return t.fitHeader()},100))}var e=this.$tableBody.get(0),i=e.scrollWidth>e.clientWidth&&e.scrollHeight>e.clientHeight+this.$header.outerHeight()?ss.getScrollBarWidth():0;this.$el.css("margin-top",-this.$header.outerHeight());var n=y["default"](":focus");if(n.length>0){var o=n.parents("th");if(o.length>0){var a=o.attr("data-field");if(void 0!==a){var s=this.$header.find("[data-field='".concat(a,"']"));s.length>0&&s.find(":input").addClass("focus-temp")}}}this.$header_=this.$header.clone(!0,!0),this.$selectAll_=this.$header_.find('[name="btSelectAll"]'),this.$tableHeader.css("margin-right",i).find("table").css("width",this.$el.outerWidth()).html("").attr("class",this.$el.attr("class")).append(this.$header_),this.$tableLoading.css("width",this.$el.outerWidth());var r=y["default"](".focus-temp:visible:eq(0)");r.length>0&&(r.focus(),this.$header.find(".focus-temp").removeClass("focus-temp")),this.$header.find("th[data-field]").each(function(e,i){t.$header_.find(ss.sprintf('th[data-field="%s"]',y["default"](i).data("field"))).data(y["default"](i).data())});for(var l=this.getVisibleFields(),c=this.$header_.find("th"),h=this.$body.find(">tr:not(.no-records-found,.virtual-scroll-top)").eq(0);h.length&&h.find('>td[colspan]:not([colspan="1"])').length;){h=h.next()}var u=h.find("> *").length;h.find("> *").each(function(e,i){var n=y["default"](i);if(ss.hasDetailViewIcon(t.options)&&(0===e&&"right"!==t.options.detailViewAlign||e===u-1&&"right"===t.options.detailViewAlign)){var o=c.filter(".detail"),a=o.innerWidth()-o.find(".fht-cell").width();return void o.find(".fht-cell").width(n.innerWidth()-a)}var s=e-ss.getDetailViewIndexOffset(t.options),r=t.$header_.find(ss.sprintf('th[data-field="%s"]',l[s]));r.length>1&&(r=y["default"](c[n[0].cellIndex]));var h=r.innerWidth()-r.find(".fht-cell").width();r.find(".fht-cell").width(n.innerWidth()-h)}),this.horizontalScroll(),this.trigger("post-header")}},{key:"initFooter",value:function(){if(this.options.showFooter&&!this.options.cardView){var t=this.getData(),e=[],i="";ss.hasDetailViewIcon(this.options)&&(i='
    '),i&&"right"!==this.options.detailViewAlign&&e.push(i);var n,o=v(this.columns);try{for(o.s();!(n=o.n()).done;){var a=n.value,r="",l="",c=[],h={},u=ss.sprintf(' class="%s"',a["class"]);if(a.visible&&(!(this.footerData&&this.footerData.length>0)||a.field in this.footerData[0])){if(this.options.cardView&&!a.cardVisible){return}if(r=ss.sprintf("text-align: %s; ",a.falign?a.falign:a.align),l=ss.sprintf("vertical-align: %s; ",a.valign),h=ss.calculateObjectValue(null,this.options.footerStyle,[a]),h&&h.css){for(var d=0,f=Object.entries(h.css);d0&&(m=this.footerData[0]["_".concat(a.field,"_colspan")]||0),m&&e.push(' colspan="'.concat(m,'" ')),e.push(">"),e.push('
    ');var y="";this.footerData&&this.footerData.length>0&&(y=this.footerData[0][a.field]||""),e.push(ss.calculateObjectValue(a,a.footerFormatter,[t,y],y)),e.push("
    "),e.push('
    '),e.push("
    "),e.push("")}}}catch(w){o.e(w)}finally{o.f()}i&&"right"===this.options.detailViewAlign&&e.push(i),this.options.height||this.$tableFooter.length||(this.$el.append(""),this.$tableFooter=this.$el.find("tfoot")),this.$tableFooter.find("tr").length||this.$tableFooter.html("
    "),this.$tableFooter.find("tr").html(e.join("")),this.trigger("post-footer",this.$tableFooter)}}},{key:"fitFooter",value:function(){var t=this;if(this.$el.is(":hidden")){return void setTimeout(function(){return t.fitFooter()},100)}var e=this.$tableBody.get(0),i=e.scrollWidth>e.clientWidth&&e.scrollHeight>e.clientHeight+this.$header.outerHeight()?ss.getScrollBarWidth():0;this.$tableFooter.css("margin-right",i).find("table").css("width",this.$el.outerWidth()).attr("class",this.$el.attr("class"));var n=this.$tableFooter.find("th"),o=this.$body.find(">tr:first-child:not(.no-records-found)");for(n.find(".fht-cell").width("auto");o.length&&o.find('>td[colspan]:not([colspan="1"])').length;){o=o.next()}var a=o.find("> *").length;o.find("> *").each(function(e,i){var o=y["default"](i);if(ss.hasDetailViewIcon(t.options)&&(0===e&&"left"===t.options.detailViewAlign||e===a-1&&"right"===t.options.detailViewAlign)){var s=n.filter(".detail"),r=s.innerWidth()-s.find(".fht-cell").width();return void s.find(".fht-cell").width(o.innerWidth()-r)}var l=n.eq(e),c=l.innerWidth()-l.find(".fht-cell").width();l.find(".fht-cell").width(o.innerWidth()-c)}),this.horizontalScroll()}},{key:"horizontalScroll",value:function(){var t=this;this.$tableBody.off("scroll").on("scroll",function(){var e=t.$tableBody.scrollLeft();t.options.showHeader&&t.options.height&&t.$tableHeader.scrollLeft(e),t.options.showFooter&&!t.options.cardView&&t.$tableFooter.scrollLeft(e),t.trigger("scroll-body",t.$tableBody)})}},{key:"getVisibleFields",value:function(){var t,e=[],i=v(this.header.fields);try{for(i.s();!(t=i.n()).done;){var n=t.value,o=this.columns[this.fieldsColumnsIndex[n]];o&&o.visible&&e.push(n)}}catch(a){i.e(a)}finally{i.f()}return e}},{key:"initHiddenRows",value:function(){this.hiddenRows=[]}},{key:"getOptions",value:function(){var t=y["default"].extend({},this.options);return delete t.data,y["default"].extend(!0,{},t)}},{key:"refreshOptions",value:function(t){ss.compareObjects(this.options,t,!0)||(this.options=y["default"].extend(this.options,t),this.trigger("refresh-options",this.options),this.destroy(),this.init())}},{key:"getData",value:function(t){var e=this,i=this.options.data;if(!(this.searchText||this.options.customSearch||void 0!==this.options.sortName||this.enableCustomSort)&&ss.isEmptyObject(this.filterColumns)&&ss.isEmptyObject(this.filterColumnsPartial)||t&&t.unfiltered||(i=this.data),t&&t.useCurrentPage&&(i=i.slice(this.pageFrom-1,this.pageTo)),t&&!t.includeHiddenRows){var n=this.getHiddenRows();i=i.filter(function(t){return -1===ss.findIndex(n,t)})}return t&&t.formatted&&i.forEach(function(t){for(var i=0,n=Object.entries(t);i=0;i--){var n=this.options.data[i];(n.hasOwnProperty(t.field)||"$index"===t.field)&&(!n.hasOwnProperty(t.field)&&"$index"===t.field&&t.values.includes(i)||t.values.includes(n[t.field]))&&(e++,this.options.data.splice(i,1))}e&&("server"===this.options.sidePagination&&(this.options.totalRows-=e,this.data=r(this.options.data)),this.initSearch(),this.initPagination(),this.initSort(),this.initBody(!0))}},{key:"removeAll",value:function(){this.options.data.length>0&&(this.options.data.splice(0,this.options.data.length),this.initSearch(),this.initPagination(),this.initBody(!0))}},{key:"insertRow",value:function(t){t.hasOwnProperty("index")&&t.hasOwnProperty("row")&&(this.options.data.splice(t.index,0,t.row),this.initSearch(),this.initPagination(),this.initSort(),this.initBody(!0))}},{key:"updateRow",value:function(t){var e,i=Array.isArray(t)?t:[t],n=v(i);try{for(n.s();!(e=n.n()).done;){var o=e.value;o.hasOwnProperty("index")&&o.hasOwnProperty("row")&&(o.hasOwnProperty("replace")&&o.replace?this.options.data[o.index]=o.row:y["default"].extend(this.options.data[o.index],o.row))}}catch(a){n.e(a)}finally{n.f()}this.initSearch(),this.initPagination(),this.initSort(),this.initBody(!0)}},{key:"getRowByUniqueId",value:function(t){var e,i,n,o=this.options.uniqueId,a=this.options.data.length,s=t,r=null;for(e=a-1;e>=0;e--){if(i=this.options.data[e],i.hasOwnProperty(o)){n=i[o]}else{if(!i._data||!i._data.hasOwnProperty(o)){continue}n=i._data[o]}if("string"==typeof n?s=""+s:"number"==typeof n&&(+n===n&&n%1===0?s=parseInt(s,10):n===+n&&0!==n&&(s=parseFloat(s))),n===s){r=i;break}}return r}},{key:"updateByUniqueId",value:function(t){var e,i=Array.isArray(t)?t:[t],n=null,o=v(i);try{for(o.s();!(e=o.n()).done;){var a=e.value;if(a.hasOwnProperty("id")&&a.hasOwnProperty("row")){var s=this.options.data.indexOf(this.getRowByUniqueId(a.id));-1!==s&&(a.hasOwnProperty("replace")&&a.replace?this.options.data[s]=a.row:y["default"].extend(this.options.data[s],a.row),n=a.id)}}}catch(r){o.e(r)}finally{o.f()}this.initSearch(),this.initPagination(),this.initSort(),this.initBody(!0,n)}},{key:"removeByUniqueId",value:function(t){var e=this.options.data.length,i=this.getRowByUniqueId(t);i&&this.options.data.splice(this.options.data.indexOf(i),1),e!==this.options.data.length&&("server"===this.options.sidePagination&&(this.options.totalRows-=1,this.data=r(this.options.data)),this.initSearch(),this.initPagination(),this.initBody(!0))}},{key:"updateCell",value:function(t){t.hasOwnProperty("index")&&t.hasOwnProperty("field")&&t.hasOwnProperty("value")&&(this.data[t.index][t.field]=t.value,t.reinit!==!1&&(this.initSort(),this.initBody(!0)))}},{key:"updateCellByUniqueId",value:function(t){var e=this,i=Array.isArray(t)?t:[t];i.forEach(function(t){var i=t.id,n=t.field,o=t.value,a=e.options.data.indexOf(e.getRowByUniqueId(i));-1!==a&&(e.options.data[a][n]=o)}),t.reinit!==!1&&(this.initSort(),this.initBody(!0))}},{key:"showRow",value:function(t){this._toggleRow(t,!0)}},{key:"hideRow",value:function(t){this._toggleRow(t,!1)}},{key:"_toggleRow",value:function(t,e){var i;if(t.hasOwnProperty("index")?i=this.getData()[t.index]:t.hasOwnProperty("uniqueId")&&(i=this.getRowByUniqueId(t.uniqueId)),i){var n=ss.findIndex(this.hiddenRows,i);e||-1!==n?e&&n>-1&&this.hiddenRows.splice(n,1):this.hiddenRows.push(i),this.initBody(!0),this.initPagination()}}},{key:"getHiddenRows",value:function(t){if(t){return this.initHiddenRows(),this.initBody(!0),void this.initPagination()}var e,i=this.getData(),n=[],o=v(i);try{for(o.s();!(e=o.n()).done;){var a=e.value;this.hiddenRows.includes(a)&&n.push(a)}}catch(s){o.e(s)}finally{o.f()}return this.hiddenRows=n,n}},{key:"showColumn",value:function(t){var e=this,i=Array.isArray(t)?t:[t];i.forEach(function(t){e._toggleColumn(e.fieldsColumnsIndex[t],!0,!0)})}},{key:"hideColumn",value:function(t){var e=this,i=Array.isArray(t)?t:[t];i.forEach(function(t){e._toggleColumn(e.fieldsColumnsIndex[t],!1,!0)})}},{key:"_toggleColumn",value:function(t,e,i){if(-1!==t&&this.columns[t].visible!==e&&(this.columns[t].visible=e,this.initHeader(),this.initSearch(),this.initPagination(),this.initBody(),this.options.showColumns)){var n=this.$toolbar.find('.keep-open input:not(".toggle-all")').prop("disabled",!1);i&&n.filter(ss.sprintf('[value="%s"]',t)).prop("checked",e),n.filter(":checked").length<=this.options.minimumCountColumns&&n.filter(":checked").prop("disabled",!0)}}},{key:"getVisibleColumns",value:function(){var t=this;return this.columns.filter(function(e){return e.visible&&!t.isSelectionColumn(e)})}},{key:"getHiddenColumns",value:function(){return this.columns.filter(function(t){var e=t.visible;return !e})}},{key:"isSelectionColumn",value:function(t){return t.radio||t.checkbox}},{key:"showAllColumns",value:function(){this._toggleAllColumns(!0)}},{key:"hideAllColumns",value:function(){this._toggleAllColumns(!1)}},{key:"_toggleAllColumns",value:function(t){var e,i=this,n=v(this.columns.slice().reverse());try{for(n.s();!(e=n.n()).done;){var o=e.value;if(o.switchable){if(!t&&this.options.showColumns&&this.getVisibleColumns().length===this.options.minimumCountColumns){continue}o.visible=t}}}catch(a){n.e(a)}finally{n.f()}if(this.initHeader(),this.initSearch(),this.initPagination(),this.initBody(),this.options.showColumns){var s=this.$toolbar.find('.keep-open input[type="checkbox"]:not(".toggle-all")').prop("disabled",!1);t?s.prop("checked",t):s.get().reverse().forEach(function(e){s.filter(":checked").length>i.options.minimumCountColumns&&y["default"](e).prop("checked",t)}),s.filter(":checked").length<=this.options.minimumCountColumns&&s.filter(":checked").prop("disabled",!0)}}},{key:"mergeCells",value:function(t){var e,i,n=t.index,o=this.getVisibleFields().indexOf(t.field),a=t.rowspan||1,s=t.colspan||1,r=this.$body.find(">tr[data-index]");o+=ss.getDetailViewIndexOffset(this.options);var l=r.eq(n).find(">td").eq(o);if(!(0>n||0>o||n>=this.data.length)){for(e=n;n+a>e;e++){for(i=o;o+s>i;i++){r.eq(e).find(">td").eq(i).hide()}}l.attr("rowspan",a).attr("colspan",s).show()}}},{key:"checkAll",value:function(){this._toggleCheckAll(!0)}},{key:"uncheckAll",value:function(){this._toggleCheckAll(!1)}},{key:"_toggleCheckAll",value:function(t){var e=this.getSelections();this.$selectAll.add(this.$selectAll_).prop("checked",t),this.$selectItem.filter(":enabled").prop("checked",t),this.updateRows(),this.updateSelected();var i=this.getSelections();return t?void this.trigger("check-all",i,e):void this.trigger("uncheck-all",i,e)}},{key:"checkInvert",value:function(){var t=this.$selectItem.filter(":enabled"),e=t.filter(":checked");t.each(function(t,e){y["default"](e).prop("checked",!y["default"](e).prop("checked"))}),this.updateRows(),this.updateSelected(),this.trigger("uncheck-some",e),e=this.getSelections(),this.trigger("check-some",e)}},{key:"check",value:function(t){this._toggleCheck(!0,t)}},{key:"uncheck",value:function(t){this._toggleCheck(!1,t)}},{key:"_toggleCheck",value:function(t,e){var i=this.$selectItem.filter('[data-index="'.concat(e,'"]')),n=this.data[e];if(i.is(":radio")||this.options.singleSelect||this.options.multipleSelectRow&&!this.multipleSelectRowCtrlKey&&!this.multipleSelectRowShiftKey){var o,a=v(this.options.data);try{for(a.s();!(o=a.n()).done;){var r=o.value;r[this.header.stateField]=!1}}catch(l){a.e(l)}finally{a.f()}this.$selectItem.filter(":checked").not(i).prop("checked",!1)}if(n[this.header.stateField]=t,this.options.multipleSelectRow){if(this.multipleSelectRowShiftKey&&this.multipleSelectRowLastSelectedIndex>=0){for(var c=this.multipleSelectRowLastSelectedIndexf;f++){this.data[f][this.header.stateField]=!0,this.$selectItem.filter('[data-index="'.concat(f,'"]')).prop("checked",!0)}}this.multipleSelectRowCtrlKey=!1,this.multipleSelectRowShiftKey=!1,this.multipleSelectRowLastSelectedIndex=t?e:-1}i.prop("checked",t),this.updateSelected(),this.trigger(t?"check":"uncheck",this.data[e],i)}},{key:"checkBy",value:function(t){this._toggleCheckBy(!0,t)}},{key:"uncheckBy",value:function(t){this._toggleCheckBy(!1,t)}},{key:"_toggleCheckBy",value:function(t,e){var i=this;if(e.hasOwnProperty("field")&&e.hasOwnProperty("values")){var n=[];this.data.forEach(function(o,a){if(!o.hasOwnProperty(e.field)){return !1}if(e.values.includes(o[e.field])){var s=i.$selectItem.filter(":enabled").filter(ss.sprintf('[data-index="%s"]',a)),r=e.hasOwnProperty("onlyCurrentPage")?e.onlyCurrentPage:!1;if(s=t?s.not(":checked"):s.filter(":checked"),!s.length&&r){return}s.prop("checked",t),o[i.header.stateField]=t,n.push(o),i.trigger(t?"check":"uncheck",o,s)}}),this.updateSelected(),this.trigger(t?"check-some":"uncheck-some",n)}}},{key:"refresh",value:function(t){t&&t.url&&(this.options.url=t.url),t&&t.pageNumber&&(this.options.pageNumber=t.pageNumber),t&&t.pageSize&&(this.options.pageSize=t.pageSize),table.rememberSelecteds={},table.rememberSelectedIds={},this.trigger("refresh",this.initServer(t&&t.silent,t&&t.query,t&&t.url))}},{key:"destroy",value:function(){this.$el.insertBefore(this.$container),y["default"](this.options.toolbar).insertBefore(this.$el),this.$container.next().remove(),this.$container.remove(),this.$el.html(this.$el_.html()).css("margin-top","0").attr("class",this.$el_.attr("class")||"")}},{key:"resetView",value:function(t){var e=0;if(t&&t.height&&(this.options.height=t.height),this.$tableContainer.toggleClass("has-card-view",this.options.cardView),!this.options.cardView&&this.options.showHeader&&this.options.height?(this.$tableHeader.show(),this.resetHeader(),e+=this.$header.outerHeight(!0)+1):(this.$tableHeader.hide(),this.trigger("post-header")),!this.options.cardView&&this.options.showFooter&&(this.$tableFooter.show(),this.fitFooter(),this.options.height&&(e+=this.$tableFooter.outerHeight(!0))),this.$container.hasClass("fullscreen")){this.$tableContainer.css("height",""),this.$tableContainer.css("width","")}else{if(this.options.height){this.$tableBorder&&(this.$tableBorder.css("width",""),this.$tableBorder.css("height",""));var i=this.$toolbar.outerHeight(!0),n=this.$pagination.outerHeight(!0),o=this.options.height-i-n,a=this.$tableBody.find(">table"),s=a.outerHeight();if(this.$tableContainer.css("height","".concat(o,"px")),this.$tableBorder&&a.is(":visible")){var r=o-s-2;this.$tableBody[0].scrollWidth-this.$tableBody.innerWidth()&&(r-=ss.getScrollBarWidth()),this.$tableBorder.css("width","".concat(a.outerWidth(),"px")),this.$tableBorder.css("height","".concat(r,"px"))}}}this.options.cardView?(this.$el.css("margin-top","0"),this.$tableContainer.css("padding-bottom","0"),this.$tableFooter.hide()):(this.getCaret(),this.$tableContainer.css("padding-bottom","".concat(e,"px"))),this.trigger("reset-view")}},{key:"showLoading",value:function(){this.$tableLoading.toggleClass("open",!0);var t=this.options.loadingFontSize;"auto"===this.options.loadingFontSize&&(t=0.04*this.$tableLoading.width(),t=Math.max(12,t),t=Math.min(32,t),t="".concat(t,"px")),this.$tableLoading.find(".loading-text").css("font-size",t)}},{key:"hideLoading",value:function(){this.$tableLoading.toggleClass("open",!1)}},{key:"toggleShowSearch",value:function(){this.$el.parents(".select-table").siblings().slideToggle()}},{key:"togglePagination",value:function(){this.options.pagination=!this.options.pagination;var t=this.options.showButtonIcons?this.options.pagination?this.options.icons.paginationSwitchDown:this.options.icons.paginationSwitchUp:"",e=this.options.showButtonText?this.options.pagination?this.options.formatPaginationSwitchUp():this.options.formatPaginationSwitchDown():"";this.$toolbar.find('button[name="paginationSwitch"]').html("".concat(ss.sprintf(this.constants.html.icon,this.options.iconsPrefix,t)," ").concat(e)),this.updatePagination(),this.trigger("toggle-pagination",this.options.pagination)}},{key:"toggleFullscreen",value:function(){this.$el.closest(".bootstrap-table").toggleClass("fullscreen"),this.resetView()}},{key:"toggleView",value:function(){this.options.cardView=!this.options.cardView,this.initHeader();var t=this.options.showButtonIcons?this.options.cardView?this.options.icons.toggleOn:this.options.icons.toggleOff:"",e=this.options.showButtonText?this.options.cardView?this.options.formatToggleOff():this.options.formatToggleOn():"";this.$toolbar.find('button[name="toggle"]').html("".concat(ss.sprintf(this.constants.html.icon,this.options.iconsPrefix,t)," ").concat(e)),this.initBody(),this.trigger("toggle",this.options.cardView)}},{key:"resetSearch",value:function(t){var e=ss.getSearchInput(this);e.val(t||""),this.onSearch({currentTarget:e})}},{key:"filterBy",value:function(t,e){this.filterOptions=ss.isEmptyObject(e)?this.options.filterOptions:y["default"].extend(this.options.filterOptions,e),this.filterColumns=ss.isEmptyObject(t)?{}:t,this.options.pageNumber=1,this.initSearch(),this.updatePagination()}},{key:"scrollTo",value:function o(t){var e={unit:"px",value:0};"object"===i(t)?e=Object.assign(e,t):"string"==typeof t&&"bottom"===t?e.value=this.$tableBody[0].scrollHeight:("string"==typeof t||"number"==typeof t)&&(e.value=t);var o=e.value;"rows"===e.unit&&(o=0,this.$body.find("> tr:lt(".concat(e.value,")")).each(function(t,e){o+=y["default"](e).outerHeight(!0)})),this.$tableBody.scrollTop(o)}},{key:"getScrollPosition",value:function(){return this.$tableBody.scrollTop()}},{key:"selectPage",value:function(t){t>0&&t<=this.options.totalPages&&(this.options.pageNumber=t,this.updatePagination())}},{key:"prevPage",value:function(){this.options.pageNumber>1&&(this.options.pageNumber--,this.updatePagination())}},{key:"nextPage",value:function(){this.options.pageNumber tr[data-index="%s"]',t));i.next().is("tr.detail-view")?this.collapseRow(t):this.expandRow(t,e),this.resetView()}},{key:"expandRow",value:function(t,e){var i=this.data[t],n=this.$body.find(ss.sprintf('> tr[data-index="%s"][data-has-detail-view]',t));if(this.options.detailViewIcon&&n.find("a.detail-icon").html(ss.sprintf(this.constants.html.icon,this.options.iconsPrefix,this.options.icons.detailClose)),!n.next().is("tr.detail-view")){n.after(ss.sprintf('',n.children("td").length));var o=n.next().find("td"),a=e||this.options.detailFormatter,s=ss.calculateObjectValue(this.options,a,[t,i,o],"");1===o.length&&o.append(s),this.trigger("expand-row",t,i,o)}}},{key:"expandRowByUniqueId",value:function(t){var e=this.getRowByUniqueId(t);e&&this.expandRow(this.data.indexOf(e))}},{key:"collapseRow",value:function(t){var e=this.data[t],i=this.$body.find(ss.sprintf('> tr[data-index="%s"][data-has-detail-view]',t));i.next().is("tr.detail-view")&&(this.options.detailViewIcon&&i.find("a.detail-icon").html(ss.sprintf(this.constants.html.icon,this.options.iconsPrefix,this.options.icons.detailOpen)),this.trigger("collapse-row",t,e,i.next()),i.next().remove())}},{key:"collapseRowByUniqueId",value:function(t){var e=this.getRowByUniqueId(t);e&&this.collapseRow(this.data.indexOf(e))}},{key:"expandAllRows",value:function(){for(var t=this.$body.find("> tr[data-index][data-has-detail-view]"),e=0;e tr[data-index][data-has-detail-view]"),e=0;e1?e-1:0),o=1;e>o;o++){n[o-1]=arguments[o]}var a;return this.each(function(e,o){var s=y["default"](o).data("bootstrap.table"),r=y["default"].extend({},hs.DEFAULTS,y["default"](o).data(),"object"===i(t)&&t);if("string"==typeof t){var l;if(!Ka.METHODS.includes(t)){throw Error("Unknown method: ".concat(t))}if(!s){return}a=(l=s)[t].apply(l,n),"destroy"===t&&y["default"](o).removeData("bootstrap.table")}s||(s=new y["default"].BootstrapTable(o,r),y["default"](o).data("bootstrap.table",s),s.init())}),void 0===a?this:a},y["default"].fn.bootstrapTable.Constructor=hs,y["default"].fn.bootstrapTable.theme=Ka.THEME,y["default"].fn.bootstrapTable.VERSION=Ka.VERSION,y["default"].fn.bootstrapTable.defaults=hs.DEFAULTS,y["default"].fn.bootstrapTable.columnDefaults=hs.COLUMN_DEFAULTS,y["default"].fn.bootstrapTable.events=hs.EVENTS,y["default"].fn.bootstrapTable.locales=hs.LOCALES,y["default"].fn.bootstrapTable.methods=hs.METHODS,y["default"].fn.bootstrapTable.utils=ss,y["default"](function(){y["default"]('[data-toggle="table"]').bootstrapTable()}),hs});var TABLE_EVENTS="all.bs.table click-cell.bs.table dbl-click-cell.bs.table click-row.bs.table dbl-click-row.bs.table sort.bs.table check.bs.table uncheck.bs.table onUncheck check-all.bs.table uncheck-all.bs.table check-some.bs.table uncheck-some.bs.table load-success.bs.table load-error.bs.table column-switch.bs.table page-change.bs.table search.bs.table toggle.bs.table show-search.bs.table expand-row.bs.table collapse-row.bs.table refresh-options.bs.table reset-view.bs.table refresh.bs.table",firstLoadTable=[],union=function(t,e){return $.isPlainObject(e)?addRememberRow(t,e):$.isArray(e)?$.each(e,function(e,i){$.isPlainObject(i)?addRememberRow(t,i):-1==$.inArray(i,t)&&(t[t.length]=i)}):-1==$.inArray(e,t)&&(t[t.length]=e),t},difference=function(t,e){if($.isPlainObject(e)){removeRememberRow(t,e)}else{if($.isArray(e)){$.each(e,function(e,i){if($.isPlainObject(i)){removeRememberRow(t,i)}else{var n=$.inArray(i,t);-1!=n&&t.splice(n,1)}})}else{var i=$.inArray(e,t);-1!=i&&t.splice(i,1)}}return t},_={union:union,difference:difference}; \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/auto-refresh/bootstrap-table-auto-refresh.js b/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/auto-refresh/bootstrap-table-auto-refresh.js new file mode 100644 index 0000000..f142185 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/auto-refresh/bootstrap-table-auto-refresh.js @@ -0,0 +1,95 @@ +/** + * @author: Alec Fenichel + * @webSite: https://fenichelar.com + * @update: zhixin wen + */ + +var Utils = $.fn.bootstrapTable.utils + +$.extend($.fn.bootstrapTable.defaults, { + autoRefresh: false, + showAutoRefresh: true, + autoRefreshInterval: 60, + autoRefreshSilent: true, + autoRefreshStatus: true, + autoRefreshFunction: null +}) + +$.extend($.fn.bootstrapTable.defaults.icons, { + autoRefresh: { + bootstrap3: 'glyphicon-time icon-time', + bootstrap5: 'bi-clock', + materialize: 'access_time', + 'bootstrap-table': 'icon-clock' + }[$.fn.bootstrapTable.theme] || 'fa-clock' +}) + +$.extend($.fn.bootstrapTable.locales, { + formatAutoRefresh () { + return 'Auto Refresh' + } +}) + +$.extend($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales) + +$.BootstrapTable = class extends $.BootstrapTable { + init (...args) { + super.init(...args) + + if (this.options.autoRefresh && this.options.autoRefreshStatus) { + this.setupRefreshInterval() + } + } + + initToolbar (...args) { + if (this.options.autoRefresh) { + this.buttons = Object.assign(this.buttons, { + autoRefresh: { + html: ` + + `, + event: this.toggleAutoRefresh + } + }) + } + + super.initToolbar(...args) + } + + toggleAutoRefresh () { + if (this.options.autoRefresh) { + if (this.options.autoRefreshStatus) { + clearInterval(this.options.autoRefreshFunction) + this.$toolbar.find('>.columns .auto-refresh') + .removeClass(this.constants.classes.buttonActive) + } else { + this.setupRefreshInterval() + this.$toolbar.find('>.columns .auto-refresh') + .addClass(this.constants.classes.buttonActive) + } + this.options.autoRefreshStatus = !this.options.autoRefreshStatus + } + } + + destroy () { + if (this.options.autoRefresh && this.options.autoRefreshStatus) { + clearInterval(this.options.autoRefreshFunction) + } + + super.destroy() + } + + setupRefreshInterval () { + this.options.autoRefreshFunction = setInterval(() => { + if (!this.options.autoRefresh || !this.options.autoRefreshStatus) { + return + } + this.refresh({ silent: this.options.autoRefreshSilent }) + }, this.options.autoRefreshInterval * 1000) + } +} diff --git a/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/columns/bootstrap-table-fixed-columns.js b/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/columns/bootstrap-table-fixed-columns.js new file mode 100644 index 0000000..e35b89f --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/columns/bootstrap-table-fixed-columns.js @@ -0,0 +1,5 @@ +/** + * @author zhixin wen + * @github: bootstrap-table/dist/extensions/fixed-columns/bootstrap-table-fixed-columns.min.js + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(require("jquery")):"function"==typeof define&&define.amd?define(["jquery"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).jQuery)}(this,(function(t){"use strict";function e(t){return t&&"object"==typeof t&&"default"in t?t:{default:t}}var n=e(t);function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function r(t,e){for(var n=0;n0&&S[0]<4?1:+(S[0]+S[1])),!j&<&&(!(S=lt.match(/Edge\/(\d+)/))||S[1]>=74)&&(S=lt.match(/Chrome\/(\d+)/))&&(j=+S[1]);var bt=j,gt=bt,vt=y,mt=!!Object.getOwnPropertySymbols&&!vt((function(){var t=Symbol();return!String(t)||!(Object(t)instanceof Symbol)||!Symbol.sham&>&><41})),xt=mt&&!Symbol.sham&&"symbol"==typeof Symbol.iterator,$t=at,wt=et,Ct=ct,Ot=Object,St=xt?function(t){return"symbol"==typeof t}:function(t){var e=$t("Symbol");return wt(e)&&Ct(e.prototype,Ot(t))},jt=String,Rt=et,Bt=function(t){try{return jt(t)}catch(t){return"Object"}},Tt=TypeError,Ft=function(t){if(Rt(t))return t;throw Tt(Bt(t)+" is not a function")},kt=Ft,Pt=Y,Et=x,At=et,Nt=rt,Ht=TypeError,Dt={exports:{}},It=h,Lt=Object.defineProperty,Mt=function(t,e){try{Lt(It,t,{value:e,configurable:!0,writable:!0})}catch(n){It[t]=e}return e},_t=Mt,Wt="__core-js_shared__",zt=h[Wt]||_t(Wt,{}),Xt=zt;(Dt.exports=function(t,e){return Xt[t]||(Xt[t]=void 0!==e?e:{})})("versions",[]).push({version:"3.25.5",mode:"global",copyright:"© 2014-2022 Denis Pushkarev (zloirock.ru)",license:"https://github.com/zloirock/core-js/blob/v3.25.5/LICENSE",source:"https://github.com/zloirock/core-js"});var Yt=V,qt=Object,Gt=function(t){return qt(Yt(t))},Vt=Gt,Ut=L({}.hasOwnProperty),Kt=Object.hasOwn||function(t,e){return Ut(Vt(t),e)},Qt=L,Zt=0,Jt=Math.random(),te=Qt(1..toString),ee=function(t){return"Symbol("+(void 0===t?"":t)+")_"+te(++Zt+Jt,36)},ne=h,ie=Dt.exports,re=Kt,oe=ee,ue=mt,fe=xt,ae=ie("wks"),ce=ne.Symbol,se=ce&&ce.for,le=fe?ce:ce&&ce.withoutSetter||oe,de=function(t){if(!re(ae,t)||!ue&&"string"!=typeof ae[t]){var e="Symbol."+t;ue&&re(ce,t)?ae[t]=ce[t]:ae[t]=fe&&se?se(e):le(e)}return ae[t]},he=x,pe=rt,ye=St,be=function(t,e){var n=t[e];return Pt(n)?void 0:kt(n)},ge=function(t,e){var n,i;if("string"===e&&At(n=t.toString)&&!Nt(i=Et(n,t)))return i;if(At(n=t.valueOf)&&!Nt(i=Et(n,t)))return i;if("string"!==e&&At(n=t.toString)&&!Nt(i=Et(n,t)))return i;throw Ht("Can't convert object to primitive value")},ve=TypeError,me=de("toPrimitive"),xe=function(t,e){if(!pe(t)||ye(t))return t;var n,i=be(t,me);if(i){if(void 0===e&&(e="default"),n=he(i,t,e),!pe(n)||ye(n))return n;throw ve("Can't convert object to primitive value")}return void 0===e&&(e="number"),ge(t,e)},$e=St,we=function(t){var e=xe(t,"string");return $e(e)?e:e+""},Ce=rt,Oe=h.document,Se=Ce(Oe)&&Ce(Oe.createElement),je=function(t){return Se?Oe.createElement(t):{}},Re=je,Be=!b&&!y((function(){return 7!=Object.defineProperty(Re("div"),"a",{get:function(){return 7}}).a})),Te=b,Fe=x,ke=$,Pe=R,Ee=Q,Ae=we,Ne=Kt,He=Be,De=Object.getOwnPropertyDescriptor;p.f=Te?De:function(t,e){if(t=Ee(t),e=Ae(e),He)try{return De(t,e)}catch(t){}if(Ne(t,e))return Pe(!Fe(ke.f,t,e),t[e])};var Ie={},Le=b&&y((function(){return 42!=Object.defineProperty((function(){}),"prototype",{value:42,writable:!1}).prototype})),Me=rt,_e=String,We=TypeError,ze=function(t){if(Me(t))return t;throw We(_e(t)+" is not an object")},Xe=b,Ye=Be,qe=Le,Ge=ze,Ve=we,Ue=TypeError,Ke=Object.defineProperty,Qe=Object.getOwnPropertyDescriptor,Ze="enumerable",Je="configurable",tn="writable";Ie.f=Xe?qe?function(t,e,n){if(Ge(t),e=Ve(e),Ge(n),"function"==typeof t&&"prototype"===e&&"value"in n&&tn in n&&!n.writable){var i=Qe(t,e);i&&i.writable&&(t[e]=n.value,n={configurable:Je in n?n.configurable:i.configurable,enumerable:Ze in n?n.enumerable:i.enumerable,writable:!1})}return Ke(t,e,n)}:Ke:function(t,e,n){if(Ge(t),e=Ve(e),Ge(n),Ye)try{return Ke(t,e,n)}catch(t){}if("get"in n||"set"in n)throw Ue("Accessors not supported");return"value"in n&&(t[e]=n.value),t};var en=Ie,nn=R,rn=b?function(t,e,n){return en.f(t,e,nn(1,n))}:function(t,e,n){return t[e]=n,t},on={exports:{}},un=b,fn=Kt,an=Function.prototype,cn=un&&Object.getOwnPropertyDescriptor,sn=fn(an,"name"),ln={EXISTS:sn,PROPER:sn&&"something"===function(){}.name,CONFIGURABLE:sn&&(!un||un&&cn(an,"name").configurable)},dn=et,hn=zt,pn=L(Function.toString);dn(hn.inspectSource)||(hn.inspectSource=function(t){return pn(t)});var yn,bn,gn,vn=hn.inspectSource,mn=et,xn=h.WeakMap,$n=mn(xn)&&/native code/.test(String(xn)),wn=Dt.exports,Cn=ee,On=wn("keys"),Sn=function(t){return On[t]||(On[t]=Cn(t))},jn={},Rn=$n,Bn=h,Tn=rt,Fn=rn,kn=Kt,Pn=zt,En=Sn,An=jn,Nn="Object already initialized",Hn=Bn.TypeError,Dn=Bn.WeakMap;if(Rn||Pn.state){var In=Pn.state||(Pn.state=new Dn);In.get=In.get,In.has=In.has,In.set=In.set,yn=function(t,e){if(In.has(t))throw Hn(Nn);return e.facade=t,In.set(t,e),e},bn=function(t){return In.get(t)||{}},gn=function(t){return In.has(t)}}else{var Ln=En("state");An[Ln]=!0,yn=function(t,e){if(kn(t,Ln))throw Hn(Nn);return e.facade=t,Fn(t,Ln,e),e},bn=function(t){return kn(t,Ln)?t[Ln]:{}},gn=function(t){return kn(t,Ln)}}var Mn={set:yn,get:bn,has:gn,enforce:function(t){return gn(t)?bn(t):yn(t,{})},getterFor:function(t){return function(e){var n;if(!Tn(e)||(n=bn(e)).type!==t)throw Hn("Incompatible receiver, "+t+" required");return n}}},_n=y,Wn=et,zn=Kt,Xn=b,Yn=ln.CONFIGURABLE,qn=vn,Gn=Mn.enforce,Vn=Mn.get,Un=Object.defineProperty,Kn=Xn&&!_n((function(){return 8!==Un((function(){}),"length",{value:8}).length})),Qn=String(String).split("String"),Zn=on.exports=function(t,e,n){"Symbol("===String(e).slice(0,7)&&(e="["+String(e).replace(/^Symbol\(([^)]*)\)/,"$1")+"]"),n&&n.getter&&(e="get "+e),n&&n.setter&&(e="set "+e),(!zn(t,"name")||Yn&&t.name!==e)&&(Xn?Un(t,"name",{value:e,configurable:!0}):t.name=e),Kn&&n&&zn(n,"arity")&&t.length!==n.arity&&Un(t,"length",{value:n.arity});try{n&&zn(n,"constructor")&&n.constructor?Xn&&Un(t,"prototype",{writable:!1}):t.prototype&&(t.prototype=void 0)}catch(t){}var i=Gn(t);return zn(i,"source")||(i.source=Qn.join("string"==typeof e?e:"")),t};Function.prototype.toString=Zn((function(){return Wn(this)&&Vn(this).source||qn(this)}),"toString");var Jn=et,ti=Ie,ei=on.exports,ni=Mt,ii=function(t,e,n,i){i||(i={});var r=i.enumerable,o=void 0!==i.name?i.name:e;if(Jn(n)&&ei(n,o,i),i.global)r?t[e]=n:ni(e,n);else{try{i.unsafe?t[e]&&(r=!0):delete t[e]}catch(t){}r?t[e]=n:ti.f(t,e,{value:n,enumerable:!1,configurable:!i.nonConfigurable,writable:!i.nonWritable})}return t},ri={},oi=Math.ceil,ui=Math.floor,fi=Math.trunc||function(t){var e=+t;return(e>0?ui:oi)(e)},ai=function(t){var e=+t;return e!=e||0===e?0:fi(e)},ci=ai,si=Math.max,li=Math.min,di=ai,hi=Math.min,pi=function(t){return t>0?hi(di(t),9007199254740991):0},yi=function(t){return pi(t.length)},bi=Q,gi=function(t,e){var n=ci(t);return n<0?si(n+e,0):li(n,e)},vi=yi,mi=function(t){return function(e,n,i){var r,o=bi(e),u=vi(o),f=gi(i,u);if(t&&n!=n){for(;u>f;)if((r=o[f++])!=r)return!0}else for(;u>f;f++)if((t||f in o)&&o[f]===n)return t||f||0;return!t&&-1}},xi={includes:mi(!0),indexOf:mi(!1)},$i=Kt,wi=Q,Ci=xi.indexOf,Oi=jn,Si=L([].push),ji=function(t,e){var n,i=wi(t),r=0,o=[];for(n in i)!$i(Oi,n)&&$i(i,n)&&Si(o,n);for(;e.length>r;)$i(i,n=e[r++])&&(~Ci(o,n)||Si(o,n));return o},Ri=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"],Bi=ji,Ti=Ri.concat("length","prototype");ri.f=Object.getOwnPropertyNames||function(t){return Bi(t,Ti)};var Fi={};Fi.f=Object.getOwnPropertySymbols;var ki=at,Pi=ri,Ei=Fi,Ai=ze,Ni=L([].concat),Hi=ki("Reflect","ownKeys")||function(t){var e=Pi.f(Ai(t)),n=Ei.f;return n?Ni(e,n(t)):e},Di=Kt,Ii=Hi,Li=p,Mi=Ie,_i=y,Wi=et,zi=/#|\.prototype\./,Xi=function(t,e){var n=qi[Yi(t)];return n==Vi||n!=Gi&&(Wi(e)?_i(e):!!e)},Yi=Xi.normalize=function(t){return String(t).replace(zi,".").toLowerCase()},qi=Xi.data={},Gi=Xi.NATIVE="N",Vi=Xi.POLYFILL="P",Ui=Xi,Ki=h,Qi=p.f,Zi=rn,Ji=ii,tr=Mt,er=function(t,e,n){for(var i=Ii(e),r=Mi.f,o=Li.f,u=0;uv;v++)if((f||v in y)&&(h=b(d=y[v],v,p),t))if(e)x[v]=h;else if(h)switch(t){case 3:return!0;case 5:return d;case 6:return v;case 2:Xr(x,d)}else switch(t){case 4:return!1;case 7:Xr(x,d)}return o?-1:i||r?r:x}},qr={forEach:Yr(0),map:Yr(1),filter:Yr(2),some:Yr(3),every:Yr(4),find:Yr(5),findIndex:Yr(6),filterReject:Yr(7)},Gr={},Vr=ji,Ur=Ri,Kr=Object.keys||function(t){return Vr(t,Ur)},Qr=b,Zr=Le,Jr=Ie,to=ze,eo=Q,no=Kr;Gr.f=Qr&&!Zr?Object.defineProperties:function(t,e){to(t);for(var n,i=eo(e),r=no(e),o=r.length,u=0;o>u;)Jr.f(t,n=r[u++],i[n]);return t};var io,ro=at("document","documentElement"),oo=ze,uo=Gr,fo=Ri,ao=jn,co=ro,so=je,lo=Sn("IE_PROTO"),ho=function(){},po=function(t){return" + + diff --git a/ruoyi-admin/src/main/resources/templates/demo/form/basic.html b/ruoyi-admin/src/main/resources/templates/demo/form/basic.html new file mode 100644 index 0000000..c8100f0 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/form/basic.html @@ -0,0 +1,593 @@ + + + + + + +
    +
    +
    +
    +
    +
    基本表单 简单登录表单示例
    +
    + + + + + + + + + + +
    +
    +
    +
    +
    +

    登录

    +

    欢迎登录本站(⊙o⊙)

    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    +
    +

    还不是会员?

    +

    您可以注册一个新账户

    +

    + +

    +
    +
    +
    +
    +
    +
    +
    +
    +
    横向表单
    +
    + + + + + + + + + + +
    +
    +
    +
    +

    欢迎登录本站(⊙o⊙)

    +
    + + +
    + 请输入您注册时所填的E-mail +
    +
    +
    + + +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    内联表单
    +
    + + + + + + + + + + +
    +
    +
    +
    +
    + + +
    +
    + + +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    +
    弹出表单 弹出框登录示例
    +
    + + + + + + + + + + +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    所有表单元素 包括自定义样式的复选和单选按钮
    +
    + + + + + + + + + + +
    +
    +
    +
    +
    + + +
    + +
    +
    +
    +
    + +
    + 帮助文本,可能会超过一行,以块级元素显示 +
    +
    +
    +
    + + +
    + +
    +
    +
    +
    + + +
    + +
    +
    +
    +
    + + +
    + +
    +
    +
    +
    + + +
    +

    ruoyi.vip

    +
    +
    +
    +
    + + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    +
    + + +
    + + + +
    +
    +
    +
    + + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    +
    + + +
    + + + +
    +
    +
    +
    + + +
    + + +
    + +
    +
    +
    +
    +
    + + +
    + +
    +
    +
    +
    + + +
    + +
    +
    +
    +
    + + +
    + +
    +
    +
    +
    + + +
    + + + +
    +
    +
    +
    + + +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    + + +
    +
    @ + +
    +
    + .00 +
    +
    ¥ + .00 +
    +
    + +
    +
    + +
    +
    +
    +
    +
    + + +
    +
    + + +
    +
    + +
    +
    +
    +
    +
    + + +
    +
    +
    + + +
    + +
    +
    + + +
    + + +
    +
    +
    +
    +
    +
    + + +
    +
    +
    + + + +
    + +
    +
    + + +
    + + + +
    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    + + + + + diff --git a/ruoyi-admin/src/main/resources/templates/demo/form/button.html b/ruoyi-admin/src/main/resources/templates/demo/form/button.html new file mode 100644 index 0000000..f45bd6b --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/form/button.html @@ -0,0 +1,620 @@ + + + + + + +
    +
    +
    +
    +
    按钮颜色
    +
    + + + + + + + + + + +
    +
    +
    +

    + 可使用class来快速改变按钮的颜色,如.btn-primary +

    + +

    + 普通按钮 +

    +

    + + + + + + + + +

    +
    +
    +
    +
    +
    +
    +
    按钮大小
    +
    + + + + + + + + + + +
    +
    +
    +

    + 可以通过添加class的值为.btn-lg, .btn-sm, or .btn-xs来修改按钮的大小 +

    +

    按钮尺寸

    +

    + + +
    + + +
    + + +
    + + +

    +
    +
    +
    +
    +
    +
    +
    线性按钮
    +
    + + + + + + + + + + +
    +
    +
    +

    + 要使用线性按钮,可添加class.btn-block.btn-outline +

    + +

    线性按钮

    +

    + + + + + + + +

    +

    块级按钮

    +

    + +

    +
    +
    +
    +
    +
    +
    +
    3D按钮
    +
    + + + + + + + + + + +
    +
    +
    +

    + 可以通过添加.dimclass来使用3D按钮. +

    +

    3D按钮

    + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    下拉按钮
    +
    + + + + + + + + + + +
    +
    +
    +

    + 下拉按钮可使用任何颜色任何大小 +

    + +

    下拉按钮

    +
    + + +
    +
    + + +
    +
    + + +
    + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    按钮组
    +
    + + + + + + + + + + +
    +
    +
    + +

    按钮组

    +
    + + + +
    +
    +
    +
    + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    图标按钮
    +
    + + + + + + + + + + +
    +
    +
    +

    + 任何按钮都可以在左侧或右侧添加图标 +

    + +

    图标按钮

    +

    + + + + + + + + 分享到微信 + + + 使用QQ账号登录 + + + + + + + + + + + + + + + + + + + + + + + + 收藏 + +

    + +

    按钮切换

    + + +
    + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    圆形图标按钮
    +
    + + + + + + + + + + +
    +
    +
    +

    + 要使用圆形图标按钮,可以通过添加class为.btn-circle实现 +

    + +

    圆形按钮

    +
    + + + + + + + +
    +
    + + + + + + + + +
    +
    +
    +
    +
    +
    +
    圆角按钮
    +
    + + + + + + + + + + +
    +
    +
    +

    + 可以通过添加class的值微.btn-rounded来实现圆角按钮 +

    + +

    按钮组

    +

    + 默认 + 主要 + 成果 + 信息 + 警告 + 危险 + 危险 +
    +
    + 圆角块级带图标按钮 +

    +
    +
    +
    + +
    +
    +
    + + + diff --git a/ruoyi-admin/src/main/resources/templates/demo/form/cards.html b/ruoyi-admin/src/main/resources/templates/demo/form/cards.html new file mode 100644 index 0000000..35a7f68 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/form/cards.html @@ -0,0 +1,319 @@ + + + + + + +
    +
    +
    +
    +
    + NEW +
    IT-01 - 设计部
    +
    +
    +

    部门简介

    +

    + 平面设计(graphic design),也称为视觉传达设计,是以“视觉”作为沟通和表现的方式,透过多种方式来创造和结合符号、图片和文字,借此作出用来传达想法或讯息的视觉表现。 +

    +
    + 当前项目进度: +
    48%
    +
    +
    +
    +
    +
    +
    +
    项目
    + 12 +
    +
    +
    周期
    + 4个月 +
    +
    +
    预算
    + ¥200,913 +
    +
    + +
    +
    +
    +
    +
    IT-04 - 市场部
    +
    +
    +

    部门简介

    +

    + 平面设计(graphic design),也称为视觉传达设计,是以“视觉”作为沟通和表现的方式,透过多种方式来创造和结合符号、图片和文字,借此作出用来传达想法或讯息的视觉表现。 +

    +
    + 当前项目进度: +
    32%
    +
    +
    +
    +
    +
    +
    +
    项目
    + 24 +
    +
    +
    周期
    + 3个月 +
    +
    +
    预算
    + ¥190,325 +
    +
    + +
    +
    +
    +
    +
    IT-07 - 财务部
    +
    +
    +

    部门简介

    +

    + 平面设计(graphic design),也称为视觉传达设计,是以“视觉”作为沟通和表现的方式,透过多种方式来创造和结合符号、图片和文字,借此作出用来传达想法或讯息的视觉表现。 +

    +
    + 当前项目进度: +
    73%
    +
    +
    +
    +
    +
    +
    +
    项目
    + 11 +
    +
    +
    周期
    + 6个月 +
    +
    +
    预算
    + ¥560,105 +
    +
    + +
    +
    +
    +
    +
    +
    +
    IT-02 - 开发部
    +
    +
    +

    部门简介

    +

    + 平面设计(graphic design),也称为视觉传达设计,是以“视觉”作为沟通和表现的方式,透过多种方式来创造和结合符号、图片和文字,借此作出用来传达想法或讯息的视觉表现。 +

    +
    + 当前项目进度: +
    61%
    +
    +
    +
    +
    +
    +
    +
    项目
    + 43 +
    +
    +
    周期
    + 1个月 +
    +
    +
    预算
    + ¥705,913 +
    +
    + +
    +
    +
    +
    + 截止 +
    IT-05 - 管理层
    +
    +
    +

    部门简介

    +

    + 平面设计(graphic design),也称为视觉传达设计,是以“视觉”作为沟通和表现的方式,透过多种方式来创造和结合符号、图片和文字,借此作出用来传达想法或讯息的视觉表现。 +

    +
    + 当前项目进度: +
    14%
    +
    +
    +
    +
    +
    +
    +
    项目
    + 8 +
    +
    +
    周期
    + 7个月 +
    +
    +
    预算
    + ¥40,200 +
    +
    + +
    +
    +
    +
    +
    IT-08 - 销售部
    +
    +
    +

    部门简介

    +

    + 平面设计(graphic design),也称为视觉传达设计,是以“视觉”作为沟通和表现的方式,透过多种方式来创造和结合符号、图片和文字,借此作出用来传达想法或讯息的视觉表现。 +

    +
    + 当前项目进度: +
    25%
    +
    +
    +
    +
    +
    +
    +
    项目
    + 25 +
    +
    +
    周期
    + 4个月 +
    +
    +
    预算
    + ¥140,105 +
    +
    + +
    +
    +
    +
    +
    +
    + +
    IT-02 - 销售部
    +
    +
    +

    部门简介

    +

    + 平面设计(graphic design),也称为视觉传达设计,是以“视觉”作为沟通和表现的方式,透过多种方式来创造和结合符号、图片和文字,借此作出用来传达想法或讯息的视觉表现。 +

    +
    + 当前项目进度: +
    82%
    +
    +
    +
    +
    +
    +
    +
    项目
    + 68 +
    +
    +
    周期
    + 2个月 +
    +
    +
    预算
    + ¥701,400 +
    +
    + +
    +
    +
    +
    +
    IT-06 - 销售部
    +
    +
    +

    部门简介

    +

    + 平面设计(graphic design),也称为视觉传达设计,是以“视觉”作为沟通和表现的方式,透过多种方式来创造和结合符号、图片和文字,借此作出用来传达想法或讯息的视觉表现。 +

    +
    + 当前项目进度: +
    26%
    +
    +
    +
    +
    +
    +
    +
    项目
    + 16 +
    +
    +
    周期
    + 8个月 +
    +
    +
    预算
    + ¥160,100 +
    +
    + +
    +
    +
    +
    +
    IT-09 - 销售部
    +
    +
    +

    部门简介

    +

    + 平面设计(graphic design),也称为视觉传达设计,是以“视觉”作为沟通和表现的方式,透过多种方式来创造和结合符号、图片和文字,借此作出用来传达想法或讯息的视觉表现。 +

    +
    + 当前项目进度: +
    18%
    +
    +
    +
    +
    +
    +
    +
    项目
    + 53 +
    +
    +
    周期
    + 9个月 +
    +
    +
    预算
    + ¥60,140 +
    +
    + +
    +
    +
    +
    +
    + + + diff --git a/ruoyi-admin/src/main/resources/templates/demo/form/cxselect.html b/ruoyi-admin/src/main/resources/templates/demo/form/cxselect.html new file mode 100644 index 0000000..9b2ab07 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/form/cxselect.html @@ -0,0 +1,161 @@ + + + + + + +
    +
    +
    +
    +
    +
    多级联动下拉https://github.com/ciaoca/cxSelect
    +
    +
    +

    简单联动示例。

    +
    +
    + +
    +
    + +
    +
    +
    + +

    国内省市区联动。

    +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    + +

    自定义选项。

    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    + +
    +
    +
    +
    +
    + + + + + diff --git a/ruoyi-admin/src/main/resources/templates/demo/form/datetime.html b/ruoyi-admin/src/main/resources/templates/demo/form/datetime.html new file mode 100644 index 0000000..c331f0a --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/form/datetime.html @@ -0,0 +1,236 @@ + + + + + + + +
    +
    +
    +
    +
    +
    日期选择器 https://github.com/smalot/bootstrap-datetimepicker
    +
    +
    +
    + +
    + + +
    +
    + +
    + +
    + + +
    +
    + +
    + +
    + + +
    +
    + +
    + +
    + + + +
    +
    + +
    + +
    + + + +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    日期选择器 https://github.com/sentsin/laydate
    +
    +
    +
    + +
    + + +
    +
    + +
    + +
    + + +
    +
    + +
    + +
    + + +
    +
    + +
    + +
    + + +
    +
    + +
    + +
    + + + +
    +
    +
    + +
    +
    +
    +
    +
    + + + + + diff --git a/ruoyi-admin/src/main/resources/templates/demo/form/duallistbox.html b/ruoyi-admin/src/main/resources/templates/demo/form/duallistbox.html new file mode 100644 index 0000000..d9f7246 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/form/duallistbox.html @@ -0,0 +1,65 @@ + + + + + + + +
    +
    +
    +
    +
    +
    双重列表框 https://github.com/istvan-ujjmeszaros/bootstrap-duallistbox
    +
    +
    +

    + Bootstrap Dual Listbox是针对Twitter Bootstrap进行了优化的响应式双列表框。它适用于所有现代浏览器和触摸设备。 +

    + +
    + +
    +
    + +
    +
    +
    +
    +
    + + + + + diff --git a/ruoyi-admin/src/main/resources/templates/demo/form/grid.html b/ruoyi-admin/src/main/resources/templates/demo/form/grid.html new file mode 100644 index 0000000..2274d30 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/form/grid.html @@ -0,0 +1,432 @@ + + + + + + +
    +
    +
    +
    +
    +
    栅格设置
    +
    + + + + + + + + + + +
    +
    +
    + +

    通过下表可以详细查看 Bootstrap 的栅格系统是如何在多种屏幕设备上工作的。

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + 超小屏幕 + 手机 (<768px) + + 小屏幕 + 平板 (≥768px) + + 中等屏幕 + 桌面显示器 (≥992px) + + 大屏幕 + 大桌面显示器 (≥1200px) +
    栅格系统行为总是水平排列开始是堆叠在一起的,当大于这些阈值时将变为水平排列C
    .container 最大宽度None (自动)750px970px1170px
    类前缀.col-xs- + .col-sm- + .col-md- + .col-lg- +
    列(column)数12
    最大列(column)宽自动~62px~81px~97px
    槽(gutter)宽30px (每列左右均有 15px)
    可嵌套
    偏移(Offsets)
    列排序
    +
    + + +
    +
    +
    + +
    +
    +
    +
    +
    +
    从堆叠到水平排列
    +
    + + + + + + + + + + +
    +
    +
    + +

    使用单一的一组 .col-md-* 栅格类,就可以创建一个基本的栅格系统,在手机和平板设备上一开始是堆叠在一起的(超小屏幕到小屏幕这一范围),在桌面(中等)屏幕设备上变为水平排列。所有“列(column)必须放在 ” .row 内。

    +
    +
    .col-md-1
    +
    .col-md-1
    +
    .col-md-1
    +
    .col-md-1
    +
    .col-md-1
    +
    .col-md-1
    +
    .col-md-1
    +
    .col-md-1
    +
    .col-md-1
    +
    .col-md-1
    +
    .col-md-1
    +
    .col-md-1
    +
    +
    +
    .col-md-8
    +
    .col-md-4
    +
    +
    +
    .col-md-4
    +
    .col-md-4
    +
    .col-md-4
    +
    +
    +
    .col-md-6
    +
    .col-md-6
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    移动设备和桌面屏幕
    +
    + + + + + + + + + + +
    +
    +
    + +

    是否不希望在小屏幕设备上所有列都堆叠在一起?那就使用针对超小屏幕和中等屏幕设备所定义的类吧,即 .col-xs-*.col-md-*。请看下面的实例,研究一下这些是如何工作的。

    +
    +
    .col-xs-12 .col-md-8
    +
    .col-xs-6 .col-md-4
    +
    +
    +
    .col-xs-6 .col-md-4
    +
    .col-xs-6 .col-md-4
    +
    .col-xs-6 .col-md-4
    +
    +
    +
    .col-xs-6
    +
    .col-xs-6
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    手机、平板、桌面
    +
    + + + + + + + + + + +
    +
    +
    + +

    在上面案例的基础上,通过使用针对平板设备的 .col-sm-* 类,我们来创建更加动态和强大的布局吧。

    +
    +
    .col-xs-12 .col-sm-6 .col-md-8
    +
    .col-xs-6 .col-md-4
    +
    +
    +
    .col-xs-6 .col-sm-4
    +
    .col-xs-6 .col-sm-4
    + +
    +
    .col-xs-6 .col-sm-4
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    多余的列(column)将另起一行排列
    + +
    + + + + + + + + + + +
    +
    +
    +

    在等宽的4网格中,网格不等高会碰到问题,为了解决这个问题,可使用.clearfix响应实用工具类 +

    +
    +
    + .col-xs-6 .col-sm-3 +
    调整窗口大小或者在手机上查看本示例 +
    +
    .col-xs-6 .col-sm-3
    + + +
    + +
    .col-xs-6 .col-sm-3
    +
    .col-xs-6 .col-sm-3
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    列偏移
    + +
    + + + + + + + + + + +
    +
    +
    + +

    使用 .col-md-offset-* 类可以将列向右侧偏移。这些类实际是通过使用 * 选择器为当前元素增加了左侧的边距(margin)。例如,.col-md-offset-4 类将 .col-md-4 元素向右侧偏移了4个列(column)的宽度。

    +
    +
    .col-md-4
    +
    .col-md-4 .col-md-offset-4
    +
    +
    +
    .col-md-3 .col-md-offset-3
    +
    .col-md-3 .col-md-offset-3
    +
    +
    +
    .col-md-6 .col-md-offset-3
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    嵌套列
    + +
    + + + + + + + + + + +
    +
    +
    +

    为了使用内置的栅格系统将内容再次嵌套,可以通过添加一个新的 .row 元素和一系列 .col-sm-* 元素到已经存在的 .col-sm-* 元素内。被嵌套的行(row)所包含的列(column)的个数不能超过12(其实,没有要求你必须占满12列)。

    +
    +
    + 第一级: .col-md-9 +
    +
    + 第二级: .col-md-6 +
    +
    + 第二级: .col-md-6 +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    列排序
    +
    + + + + + + + + + + +
    +
    +
    +

    通过使用 .col-md-push-*.col-md-pull-* 类就可以很容易的改变列(column)的顺序。

    +
    +
    .col-md-9 .col-md-push-3
    +
    .col-md-3 .col-md-pull-9
    +
    +
    +
    + +
    +
    +
    + + + diff --git a/ruoyi-admin/src/main/resources/templates/demo/form/invoice.html b/ruoyi-admin/src/main/resources/templates/demo/form/invoice.html new file mode 100644 index 0000000..42b4e80 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/form/invoice.html @@ -0,0 +1,122 @@ + + + + + + +
    + +
    +
    +
    +
    +
    +
    + 北京百度在线网络技术有限公司
    + 北京市海淀区上地十街10号
    + 总机: (+86 10) 5992 8888 +
    +
    + +
    +

    单据编号:

    +

    H+-000567F7-00

    +
    + 阿里巴巴集团
    + 中国杭州市华星路99号东部软件园创业大厦6层(310099)
    + 总机: (86) 571-8502-2088 +
    +

    + 日期: 2014-11-11 +

    +
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    清单数量单价税率总价
    +
    尚都比拉2013冬装新款女装 韩版修身呢子大衣 秋冬气质羊毛呢外套 +
    +
    1¥26.00¥1.20¥31,98
    +
    11*11夏娜 新款斗篷毛呢外套 女秋冬呢子大衣 韩版大码宽松呢大衣 +
    + 双十一特价 + +
    2¥80.00¥1.20¥196.80
    +
    2013秋装 新款女装韩版学生秋冬加厚加绒保暖开衫卫衣 百搭女外套 +
    +
    3¥420.00¥1.20¥1033.20
    +
    + + + + + + + + + + + + + + + + + +
    总价: + ¥1026.00
    税: + ¥235.98
    总计 + ¥1261.98
    + +
    + +
    + +
    注意: 请在30日内完成付款,否则订单会自动取消。 +
    +
    +
    +
    +
    + + + + diff --git a/ruoyi-admin/src/main/resources/templates/demo/form/jasny.html b/ruoyi-admin/src/main/resources/templates/demo/form/jasny.html new file mode 100644 index 0000000..4bc8f71 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/form/jasny.html @@ -0,0 +1,118 @@ + + + + + + + +
    +
    +
    +
    +
    +
    文件上传控件 https://github.com/jasny/bootstrap
    +
    +
    +
    + + +
    +
    + 选择文件更改 + 清除 +
    +
    + +
    + +
    +
    + 选择文件更改 + + × +
    +
    + +
    + +
    +
    +
    +
    + 选择图片更改 + 清除 +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    +
    + 选择图片更改 + 清除 +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    固定格式文本 https://github.com/jasny/bootstrap
    +
    +
    +
    + + + 158-8888-88888 +
    + +
    + + + 0730-8888888 +
    + +
    + + + yyyy-mm-dd +
    + +
    + + + 192.168.100.200 +
    + +
    + + + 99-9999999 +
    +
    + +
    +
    +
    +
    +
    + + + + diff --git a/ruoyi-admin/src/main/resources/templates/demo/form/labels_tips.html b/ruoyi-admin/src/main/resources/templates/demo/form/labels_tips.html new file mode 100644 index 0000000..3ee6563 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/form/labels_tips.html @@ -0,0 +1,237 @@ + + + + + + +
    +
    +
    +
    +
    +
    徽章 (Badges)
    +
    + + + + + + + + + + +
    +
    +
    +

    + 要添加徽章,只需要在元素上添加.badge即可,改变徽章的颜色可使用如下class,如.badge-primary。 +

    +

    +

    +

    badge-primary +

    +

    badge-info +

    +

    badge-success +

    +

    badge-warning +

    +

    badge-danger +

    +
    +
    +
    + +
    +
    +
    +
    标签 (Labels)
    +
    + + + + + + + + + + +
    +
    +
    +

    + 要添加徽章,只需要在元素上添加class.label即可,如果需要修改颜色,添加如下class,如.label-primary +

    +

    +

    +

    label-primary +

    +

    label-info +

    +

    label-success +

    +

    label-warning +

    +

    label-danger +

    +
    +
    +
    +
    + +
    +
    +
    +
    +
    通知样式
    +
    + + + + + + + + + + +
    +
    +
    +
    + RuoYi是一个很棒的后台UI框架 了解更多. +
    +
    + RuoYi是一个很棒的后台UI框架 了解更多. +
    +
    + RuoYi是一个很棒的后台UI框架 了解更多. +
    +
    + RuoYi是一个很棒的后台UI框架 了解更多. +
    +
    +
    +
    +
    +
    +
    +
    带关闭按钮的通知样式
    +
    + + + + + + + + + + +
    +
    +
    +
    + + RuoYi是一个很棒的后台UI框架 了解更多. +
    +
    + + RuoYi是一个很棒的后台UI框架 了解更多. +
    +
    + + RuoYi是一个很棒的后台UI框架 了解更多. +
    +
    + + RuoYi是一个很棒的后台UI框架 了解更多. +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    工具提示
    +
    + + + + + + + + + + +
    +
    +
    +

    工具提示示例 深色背景

    +
    + + + + +
    +
    +

    工具提示 - 单击提示

    +
    + + + + +
    +
    +
    +
    + +
    + +
    + + + + diff --git a/ruoyi-admin/src/main/resources/templates/demo/form/localrefresh.html b/ruoyi-admin/src/main/resources/templates/demo/form/localrefresh.html new file mode 100644 index 0000000..cc2ad09 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/form/localrefresh.html @@ -0,0 +1,61 @@ + + + + + + +
    +
    +
    +
    +
    +

    任务列表

    +

    + + 点击刷新按钮刷新数据到列表中 +

    + +
    + + + + +
    +
    + +
    +
    +
    +
    +
    +
    + + + + diff --git a/ruoyi-admin/src/main/resources/templates/demo/form/progress_bars.html b/ruoyi-admin/src/main/resources/templates/demo/form/progress_bars.html new file mode 100644 index 0000000..47ab695 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/form/progress_bars.html @@ -0,0 +1,91 @@ + + + + + + +
    +
    +
    +
    +
    +
    进度条 (Progress Bars)
    +
    + + + + + + + + + + +
    +
    +
    + +
    基本
    +
    +
    + 35% Complete (success) +
    +
    + +
    +
    + 43% Complete (success) +
    +
    + +
    条纹效果
    +
    +
    + 50% Complete (success) +
    +
    + +
    动画效果
    +
    +
    + 75% Complete (success) +
    +
    + +
    堆叠效果
    +
    +
    + 30% Complete (success) +
    +
    + 20% Complete (warning) +
    +
    + 40% Complete (danger) +
    +
    + +
    带有提示标签的进度条
    +
    +
    + 95% +
    +
    + +
    +
    +
    +
    +
    + + + + diff --git a/ruoyi-admin/src/main/resources/templates/demo/form/select.html b/ruoyi-admin/src/main/resources/templates/demo/form/select.html new file mode 100644 index 0000000..cd5e94f --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/form/select.html @@ -0,0 +1,148 @@ + + + + + + + + +
    +
    +
    +
    +
    +
    +
    下拉框 https://github.com/select2/select2
    +
    +
    +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    +
    + +
    +
    +
    +
    +
    +
    +
    下拉框 https://github.com/snapappointments/bootstrap-select
    +
    +
    +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    +
    + +
    +
    +
    +
    +
    +
    + + + + + diff --git a/ruoyi-admin/src/main/resources/templates/demo/form/sortable.html b/ruoyi-admin/src/main/resources/templates/demo/form/sortable.html new file mode 100644 index 0000000..478be4c --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/form/sortable.html @@ -0,0 +1,198 @@ + + + + + + +
    +
    +
    +
    +
    +

    任务列表

    +

    在列表之间拖动任务面板

    + +
    + + + + +
    + +
      +
    • + 加强过程管理,及时统计教育经费使用情况,做到底码清楚, +
      + 标签 + 2018.09.01 +
      +
    • +
    • + 支持财会人员的继续培训工作。 +
      + 标记 + 2018.05.12 +
      +
    • +
    • + 协同教导处搞好助学金、减免教科书费的工作。 +
      + 标记 + 2018.09.10 +
      +
    • +
    • + 要求会计、出纳人员严格执行财务制度,遵守岗位职责,按时上报各种资料。 +
      + 确定 + 2018.06.10 +
      +
    • +
    • + 做好职工公费医疗工作,按时发放门诊费。 +
      + 标签 + 2018.09.09 +
      +
    • +
    • + 有计划地把课本复习三至五遍。 +
      + 确定 + 2018.08.04 +
      +
    • +
    • + 看一本高质量的高中语法书 +
      + 标记 + 2018.05.12 +
      +
    • +
    • + 选择一份较好的英语报纸,通过阅读提高英语学习效果。 +
      + 标记 + 2018.09.10 +
      +
    • +
    +
    +
    +
    +
    +
    +
    +

    进行中

    +

    在列表之间拖动任务面板

    +
      +
    • + 全面、较深入地掌握我们“产品”的功能、特色和优势并做到应用自如。 +
      + 标签 + 2018.09.01 +
      +
    • +
    • + 根据自己以前所了解的和从其他途径搜索到的信息,录入客户资料150家。 +
      + 标记 + 2018.05.12 +
      +
    • +
    • + 锁定有意向客户20家。 +
      + 标记 + 2018.09.10 +
      +
    • +
    • + 力争完成销售指标。 +
      + 标签 + 2018.09.09 +
      +
    • +
    • + 在总结和摸索中前进。 +
      + 确定 + 2018.08.04 +
      +
    • +
    • + 不断学习行业知识、产品知识,为客户带来实用介绍内容 +
      + 标记 + 2018.05.12 +
      +
    • +
    • + 先友后单:与客户发展良好友谊,转换销售员角色,处处为客户着想 +
      + 标记 + 2018.11.04 +
      +
    • +
    +
    +
    +
    +
    +
    +
    +

    已完成

    +

    在列表之间拖动任务面板

    +
      +
    • + 制定工作日程表 +
      + 标记 + 2018.09.10 +
      +
    • +
    • + 每天坚持打40个有效电话,挖掘潜在客户 +
      + 标签 + 2018.09.09 +
      +
    • +
    • + 拜访客户之前要对该客户做全面的了解(客户的潜在需求、职位、权限以及个人性格和爱好) +
      + 标签 + 2018.09.09 +
      +
    • +
    • + 提高自己电话营销技巧,灵活专业地与客户进行电话交流 +
      + 确定 + 2018.08.04 +
      +
    • +
    • + 通过电话销售过程中了解各盛市的设备仪器使用、采购情况及相关重要追踪人 +
      + 标记 + 2018.05.12 +
      +
    • + +
    +
    +
    +
    +
    +
    + + + + + diff --git a/ruoyi-admin/src/main/resources/templates/demo/form/summernote.html b/ruoyi-admin/src/main/resources/templates/demo/form/summernote.html new file mode 100644 index 0000000..fbf3208 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/form/summernote.html @@ -0,0 +1,93 @@ + + + + + + + +
    +
    +
    +
    +
    +
    Summernote 富文本编辑器
    +
    +
    +
    +

    若依后台管理系统

    +

    ruoyi是一个完全响应式,基于Bootstrap3.3.7最新版本开发的扁平化主题,她采用了主流的左右两栏式布局,使用了Html5+CSS3等现代技术,她提供了诸多的强大的可以重新组合的UI组件,并集成了最新的jQuery版本(v2.1.1),当然,也集成了很多功能强大,用途广泛的就jQuery插件,她可以用于所有的Web应用程序,如网站管理后台网站会员中心CMSCRMOA等等,当然,您也可以对她进行深度定制,以做出更强系统。

    +

    + 当前版本:v4.7.6 +

    +

    + 免费开源 +

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    Summernote

    +

    + Summernote是一个简单的基于Bootstrap的WYSIWYG富文本编辑器 +

    +
    官方文档请参考: + https://github.com/summernote/summernote +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    编辑/保存为html代码示例
    + + +
    +
    + +
    +

    你好,若依

    +

    H+是一个完全响应式,基于Bootstrap3.3.7最新版本开发的扁平化主题,她采用了主流的左右两栏式布局,使用了Html5+CSS3等现代技术,她提供了诸多的强大的可以重新组合的UI组件,并集成了最新的jQuery版本(v2.1.1),当然,也集成了很多功能强大,用途广泛的就jQuery插件,她可以用于所有的Web应用程序,如网站管理后台网站会员中心CMSCRMOA等等,当然,您也可以对她进行深度定制,以做出更强系统。

    +

    + 当前版本:v4.7.6 +

    +

    + 开源免费 +

    +
    + +
    +
    +
    +
    +
    + + + + + diff --git a/ruoyi-admin/src/main/resources/templates/demo/form/tabs_panels.html b/ruoyi-admin/src/main/resources/templates/demo/form/tabs_panels.html new file mode 100644 index 0000000..88f00d7 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/form/tabs_panels.html @@ -0,0 +1,353 @@ + + + + + + +
    +
    +
    +
    +
    +
    基本面板 这是一个自定义面板
    +
    + + + + + + + + + + +
    +
    +
    +

    + Bootstrap
    +

    +

    + 简洁、直观、强悍的前端开发框架,让web开发更迅速、简单。

    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    + HTML5 文档类型 +

    Bootstrap 使用到的某些 HTML 元素和 CSS 属性需要将页面设置为 HTML5 文档类型。在你项目中的每个页面都要参照下面的格式进行设置。

    +
    +
    +
    +
    + 移动设备优先 +

    在 Bootstrap 2 中,我们对框架中的某些关键部分增加了对移动设备友好的样式。而在 Bootstrap 3 中,我们重写了整个框架,使其一开始就是对移动设备友好的。这次不是简单的增加一些可选的针对移动设备的样式,而是直接融合进了框架的内核中。也就是说,Bootstrap 是移动设备优先的。针对移动设备的样式融合进了框架的每个角落,而不是增加一个额外的文件。

    +
    +
    +
    + + +
    +
    + +
    +
    + +
    +
    +

    图标选项卡

    +
    +
    + + +
    +
    + +
    +
    +
    + 排版与链接 + +

    Bootstrap 排版、链接样式设置了基本的全局样式。分别是: 为 body 元素设置 background-color: #fff; 使用 @font-family-base、@font-size-base 和 @line-height-base a变量作为排版的基本参数 为所有链接设置了基本颜色 @link-color ,并且当链接处于 :hover 状态时才添加下划线 这些样式都能在 scaffolding.less 文件中找到对应的源码。

    +
    + +
    + Normalize.css + +

    为了增强跨浏览器表现的一致性,我们使用了 Normalize.css,这是由 Nicolas Gallagher 和 Jonathan Neal 维护的一个CSS 重置样式库。

    +
    +
    + 布局容器 + +

    Bootstrap 需要为页面内容和栅格系统包裹一个 .container 容器。我们提供了两个作此用处的类。注意,由于 padding 等属性的原因,这两种 容器类不能互相嵌套。

    +
    +
    + 栅格系统 + +

    Bootstrap 提供了一套响应式、移动设备优先的流式栅格系统,随着屏幕或视口(viewport)尺寸的增加,系统会自动分为最多12列。它包含了易于使用的预定义类,还有强大的mixin 用于生成更具语义的布局。

    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + 排版与链接 + +

    Bootstrap 排版、链接样式设置了基本的全局样式。分别是: 为 body 元素设置 background-color: #fff; 使用 @font-family-base、@font-size-base 和 @line-height-base a变量作为排版的基本参数 为所有链接设置了基本颜色 @link-color ,并且当链接处于 :hover 状态时才添加下划线 这些样式都能在 scaffolding.less 文件中找到对应的源码。

    +
    +
    +
    +
    + 栅格系统 + +

    Bootstrap 提供了一套响应式、移动设备优先的流式栅格系统,随着屏幕或视口(viewport)尺寸的增加,系统会自动分为最多12列。它包含了易于使用的预定义类,还有强大的mixin 用于生成更具语义的布局。

    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    +
    +
    + 排版与链接 + +

    Bootstrap 排版、链接样式设置了基本的全局样式。分别是: 为 body 元素设置 background-color: #fff; 使用 @font-family-base、@font-size-base 和 @line-height-base a变量作为排版的基本参数 为所有链接设置了基本颜色 @link-color ,并且当链接处于 :hover 状态时才添加下划线 这些样式都能在 scaffolding.less 文件中找到对应的源码。

    +
    +
    +
    +
    + 栅格系统 + +

    Bootstrap 提供了一套响应式、移动设备优先的流式栅格系统,随着屏幕或视口(viewport)尺寸的增加,系统会自动分为最多12列。它包含了易于使用的预定义类,还有强大的mixin 用于生成更具语义的布局。

    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    Bootstrap面板 自定义背景
    +
    +
    + +
    +
    +
    +
    + 默认面板 +
    +
    +

    通过 .panel-heading 可以很简单地为面板加入一个标题容器。你也可以通过添加设置了 .panel-title 类的标签,添加一个预定义样式的标题。 为了给链接设置合适的颜色,务必将链接放到带有 .panel-title 类的标题标签内。

    +
    + +
    +
    +
    +
    +
    + 主要 +
    +
    +

    通过 .panel-heading 可以很简单地为面板加入一个标题容器。你也可以通过添加设置了 .panel-title 类的标签,添加一个预定义样式的标题。 为了给链接设置合适的颜色,务必将链接放到带有 .panel-title 类的标题标签内。

    +
    +
    +
    +
    +
    +
    + 成功 +
    +
    +

    通过 .panel-heading 可以很简单地为面板加入一个标题容器。你也可以通过添加设置了 .panel-title 类的标签,添加一个预定义样式的标题。 为了给链接设置合适的颜色,务必将链接放到带有 .panel-title 类的标题标签内。

    +
    +
    +
    +
    +
    +
    +
    +
    + 信息 +
    +
    +

    通过 .panel-heading 可以很简单地为面板加入一个标题容器。你也可以通过添加设置了 .panel-title 类的标签,添加一个预定义样式的标题。 为了给链接设置合适的颜色,务必将链接放到带有 .panel-title 类的标题标签内。

    +
    + +
    +
    +
    +
    +
    + 警告 +
    +
    +

    通过 .panel-heading 可以很简单地为面板加入一个标题容器。你也可以通过添加设置了 .panel-title 类的标签,添加一个预定义样式的标题。 为了给链接设置合适的颜色,务必将链接放到带有 .panel-title 类的标题标签内。

    +
    +
    +
    +
    +
    +
    + 危险 +
    +
    +

    通过 .panel-heading 可以很简单地为面板加入一个标题容器。你也可以通过添加设置了 .panel-title 类的标签,添加一个预定义样式的标题。 为了给链接设置合适的颜色,务必将链接放到带有 .panel-title 类的标题标签内。

    +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    折叠面板
    +
    + + + + + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    + 标题 #1 +
    +
    +
    +
    + Bootstrap相关优质项目推荐 这些项目或者是对Bootstrap进行了有益的补充,或者是基于Bootstrap开发的 +
    +
    +
    +
    +
    +

    + 标题 #2 +

    +
    +
    +
    + Bootstrap相关优质项目推荐 这些项目或者是对Bootstrap进行了有益的补充,或者是基于Bootstrap开发的 +
    +
    +
    +
    +
    +

    + 标题 #3 +

    +
    +
    +
    + Bootstrap相关优质项目推荐 这些项目或者是对Bootstrap进行了有益的补充,或者是基于Bootstrap开发的 +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    超大屏幕

    +

    Bootstrap 支持的另一个特性,超大屏幕(Jumbotron)。顾名思义该组件可以增加标题的大小,并为登录页面内容添加更多的外边距(margin)。使用超大屏幕(Jumbotron)的步骤如下:

    +
    +
      +
    1. 创建一个带有 class .jumbotron. 的容器
    2. +
    3. 除了更大的 <h1>,字体粗细 font-weight 被减为 200px。
    4. +
    +
    + +

    了解更多 +

    +
    +
    +
    +
    + + + diff --git a/ruoyi-admin/src/main/resources/templates/demo/form/timeline.html b/ruoyi-admin/src/main/resources/templates/demo/form/timeline.html new file mode 100644 index 0000000..13699ac --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/form/timeline.html @@ -0,0 +1,113 @@ + + + + + + 时间轴 + + + + + + +
    +
    +
    + 打开/关闭颜色/背景或方向版本: + 轻型版本 + 黑色版本 +
    +
    +
    +
    + + +
    +

    会议

    +

    上一年的销售业绩发布会。总结产品营销和销售趋势及销售的现状。 +

    + 更多信息 + + 今天
    + 2月3日 +
    +
    +
    + +
    +
    + +
    + +
    +

    给张三发送文档

    +

    发送上年度《销售业绩报告》

    + 下载文档 + + 今天
    + 2月3日 +
    +
    +
    + +
    +
    + +
    + +
    +

    喝咖啡休息

    +

    喝咖啡啦,啦啦啦~~

    + 更多 + 昨天
    2月2日
    +
    +
    + +
    +
    + +
    + +
    +

    给李四打电话

    +

    给李四打电话分配本月工作任务

    + 昨天
    2月2日
    +
    +
    + +
    +
    + +
    + +
    +

    公司年会

    +

    发年终奖啦,啦啦啦~~

    + 前天
    2月1日
    +
    +
    +
    +
    +
    +
    + + + + diff --git a/ruoyi-admin/src/main/resources/templates/demo/form/upload.html b/ruoyi-admin/src/main/resources/templates/demo/form/upload.html new file mode 100644 index 0000000..9b8b4a2 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/form/upload.html @@ -0,0 +1,75 @@ + + + + + + + +
    +
    +
    +
    +
    +
    文件上传控件 https://github.com/kartik-v/bootstrap-fileinput
    +
    +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    +
    + +
    +
    +
    +
    +
    + + + + + diff --git a/ruoyi-admin/src/main/resources/templates/demo/form/validate.html b/ruoyi-admin/src/main/resources/templates/demo/form/validate.html new file mode 100644 index 0000000..31c0872 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/form/validate.html @@ -0,0 +1,193 @@ + + + + + + +
    +
    +
    +
    +
    +
    jQuery Validate 简介
    +
    +
    +

    jquery.validate.js 是一款优秀的jQuery表单验证插件。它具有如下特点:

    +
      +
    • 安装简单
    • +
    • 内置超过20种数据验证方法
    • +
    • 直列错误提示信息
    • +
    • 可扩展的数据验证方法
    • +
    • 使用内置的元数据或插件选项来指定您的验证规则
    • +
    • 优雅的交互设计
    • +
    +

    官网:http://jqueryvalidation.org/ +

    +
    +
    +
    +
    +
    +
    +
    +
    +
    简单示例
    +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +

    更多示例请访问官方示例页面:查看 +

    +

    中文API可参考:http://doc.ruoyi.vip/ruoyi/document/zjwd.html#jquery-validate +

    +
    +
    +
    +
    +
    +
    +
    完整验证表单
    +
    +
    +
    +
    + +
    + + 这里写点提示的内容 +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + + 请再次输入您的密码 +
    +
    +
    + +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    + + + + diff --git a/ruoyi-admin/src/main/resources/templates/demo/form/wizard.html b/ruoyi-admin/src/main/resources/templates/demo/form/wizard.html new file mode 100644 index 0000000..d0b5ee8 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/form/wizard.html @@ -0,0 +1,339 @@ + + + + + + + + +
    +
    +
    +
    +
    +
    + 表单向导 + https://github.com/techlab/jquery-smartwizard +
    +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    +
    + +
    + + + + 这里写点提示的内容 + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + + + + 请再次输入您的密码 + +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    +

    1、如果不需要工具栏固定在页面底部, 将style中下面的部分取消注释

    .sw>.toolbar-bottom

    +

    2、如果设置了自动调节高度(autoAdjustHeight)为true, 将style中下面的部分取消注释

    .sw>.tab-content

    +

    3、工具栏的按钮样式会被表单插件中.btn样式覆盖导致bootstrap中的按钮样式无效, 如果需要改变按钮样式可以自己定义并提高优先级

    +
    +
    +
    +
    +

    测试多行显示

    +
    +$('#smartwizard').smartWizard({
    +  selected: 0, // Initial selected step, 0 = first step
    +  theme: 'default', // theme for the wizard, related css need to include for other than default theme
    +  justified: true, // Nav menu justification. true/false
    +  darkMode:false, // Enable/disable Dark Mode if the theme supports. true/false
    +  autoAdjustHeight: true, // Automatically adjust content height
    +  cycleSteps: false, // Allows to cycle the navigation of steps
    +  backButtonSupport: true, // Enable the back button support
    +  enableURLhash: true, // Enable selection of the step based on url hash
    +  transition: {
    +      animation: 'none', // Effect on navigation, none/fade/slide-horizontal/slide-vertical/slide-swing
    +      speed: '400', // Transition animation speed
    +      easing:'' // Transition animation easing. Not supported without a jQuery easing plugin
    +  },
    +  toolbarSettings: {
    +      toolbarPosition: 'bottom', // none, top, bottom, both
    +      toolbarButtonPosition: 'right', // left, right, center
    +      showNextButton: true, // show/hide a Next button
    +      showPreviousButton: true, // show/hide a Previous button
    +      toolbarExtraButtons: [] // Extra buttons to show on toolbar, array of jQuery input/buttons elements
    +  },
    +  anchorSettings: {
    +      anchorClickable: true, // Enable/Disable anchor navigation
    +      enableAllAnchors: false, // Activates all anchors clickable all times
    +      markDoneStep: true, // Add done state on navigation
    +      markAllPreviousStepsAsDone: true, // When a step selected by url hash, all previous steps are marked done
    +      removeDoneStepOnNavigateBack: false, // While navigate back done step after active step will be cleared
    +      enableAnchorOnDoneStep: true // Enable/Disable the done steps navigation
    +  },
    +  keyboardSettings: {
    +      keyNavigation: true, // Enable/Disable keyboard navigation(left and right keys are used if enabled)
    +      keyLeft: [37], // Left key code
    +      keyRight: [39] // Right key code
    +  },
    +  lang: { // Language variables for button
    +      next: 'Next',
    +      previous: 'Previous'
    +  },
    +  disabledSteps: [], // Array Steps disabled
    +  errorSteps: [], // Highlight step with errors
    +  hiddenSteps: [] // Hidden steps
    +});
    +										
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    + + + + + diff --git a/ruoyi-admin/src/main/resources/templates/demo/icon/fontawesome.html b/ruoyi-admin/src/main/resources/templates/demo/icon/fontawesome.html new file mode 100644 index 0000000..ebe096c --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/icon/fontawesome.html @@ -0,0 +1,1054 @@ + + + + + + + +
    +
    +
    +
    +
    +
    所有图标 所有图标集合 - Font Awesome
    +
    + + + + + + + + + + +
    +
    +
    +
    + +
    +
    address-book
    +
    address-book-o
    +
    address-card
    +
    address-card-o
    +
    adjust
    +
    american-sign-language-interpreting
    +
    anchor
    +
    archive
    +
    area-chart
    +
    arrows
    +
    arrows-h
    +
    arrows-v
    +
    asl-interpreting (alias)
    +
    assistive-listening-systems
    +
    asterisk
    +
    at
    +
    audio-description
    +
    automobile (alias)
    +
    balance-scale
    +
    ban
    +
    bank (alias)
    +
    bar-chart
    +
    bar-chart-o (alias)
    +
    barcode
    +
    bars
    +
    bath
    +
    bathtub (alias)
    +
    battery (alias)
    +
    battery-0 (alias)
    +
    battery-1 (alias)
    +
    battery-2 (alias)
    +
    battery-3 (alias)
    +
    battery-4 (alias)
    +
    battery-empty
    +
    battery-full
    +
    battery-half
    +
    battery-quarter
    +
    battery-three-quarters
    +
    bed
    +
    beer
    +
    bell
    +
    bell-o
    +
    bell-slash
    +
    bell-slash-o
    +
    bicycle
    +
    binoculars
    +
    birthday-cake
    +
    blind
    +
    bluetooth
    +
    bluetooth-b
    +
    bolt
    +
    bomb
    +
    book
    +
    bookmark
    +
    bookmark-o
    +
    braille
    +
    briefcase
    +
    bug
    +
    building
    +
    building-o
    +
    bullhorn
    +
    bullseye
    +
    bus
    +
    cab (alias)
    +
    calculator
    +
    calendar
    +
    calendar-check-o
    +
    calendar-minus-o
    +
    calendar-o
    +
    calendar-plus-o
    +
    calendar-times-o
    +
    camera
    +
    camera-retro
    +
    car
    +
    caret-square-o-down
    +
    caret-square-o-left
    +
    caret-square-o-right
    +
    caret-square-o-up
    +
    cart-arrow-down
    +
    cart-plus
    +
    cc
    +
    certificate
    +
    check
    +
    check-circle
    +
    check-circle-o
    +
    check-square
    +
    check-square-o
    +
    child
    +
    circle
    +
    circle-o
    +
    circle-o-notch
    +
    circle-thin
    +
    clock-o
    +
    clone
    +
    close (alias)
    +
    cloud
    +
    cloud-download
    +
    cloud-upload
    +
    code
    +
    code-fork
    +
    coffee
    +
    cog
    +
    cogs
    +
    comment
    +
    comment-o
    +
    commenting
    +
    commenting-o
    +
    comments
    +
    comments-o
    +
    compass
    +
    copyright
    +
    creative-commons
    +
    credit-card
    +
    credit-card-alt
    +
    crop
    +
    crosshairs
    +
    cube
    +
    cubes
    +
    cutlery
    +
    dashboard (alias)
    +
    database
    +
    deaf
    +
    deafness (alias)
    +
    desktop
    +
    diamond
    +
    dot-circle-o
    +
    download
    +
    drivers-license (alias)
    +
    drivers-license-o (alias)
    +
    edit (alias)
    +
    ellipsis-h
    +
    ellipsis-v
    +
    envelope
    +
    envelope-o
    +
    envelope-open
    +
    envelope-open-o
    +
    envelope-square
    +
    eraser
    +
    exchange
    +
    exclamation
    +
    exclamation-circle
    +
    exclamation-triangle
    +
    external-link
    +
    external-link-square
    +
    eye
    +
    eye-slash
    +
    eyedropper
    +
    fax
    +
    feed (alias)
    +
    female
    +
    fighter-jet
    +
    file-archive-o
    +
    file-audio-o
    +
    file-code-o
    +
    file-excel-o
    +
    file-image-o
    +
    file-movie-o (alias)
    +
    file-pdf-o
    +
    file-photo-o (alias)
    +
    file-picture-o (alias)
    +
    file-powerpoint-o
    +
    file-sound-o (alias)
    +
    file-video-o
    +
    file-word-o
    +
    file-zip-o (alias)
    +
    film
    +
    filter
    +
    fire
    +
    fire-extinguisher
    +
    flag
    +
    flag-checkered
    +
    flag-o
    +
    flash (alias)
    +
    flask
    +
    folder
    +
    folder-o
    +
    folder-open
    +
    folder-open-o
    +
    frown-o
    +
    futbol-o
    +
    gamepad
    +
    gavel
    +
    gear (alias)
    +
    gears (alias)
    +
    gift
    +
    glass
    +
    globe
    +
    graduation-cap
    +
    group (alias)
    +
    hand-grab-o (alias)
    +
    hand-lizard-o
    +
    hand-paper-o
    +
    hand-peace-o
    +
    hand-pointer-o
    +
    hand-rock-o
    +
    hand-scissors-o
    +
    hand-spock-o
    +
    hand-stop-o (alias)
    +
    handshake-o
    +
    hard-of-hearing (alias)
    +
    hashtag
    +
    hdd-o
    +
    headphones
    +
    heart
    +
    heart-o
    +
    heartbeat
    +
    history
    +
    home
    +
    hotel (alias)
    +
    hourglass
    +
    hourglass-1 (alias)
    +
    hourglass-2 (alias)
    +
    hourglass-3 (alias)
    +
    hourglass-end
    +
    hourglass-half
    +
    hourglass-o
    +
    hourglass-start
    +
    i-cursor
    +
    id-badge
    +
    id-card
    +
    id-card-o
    +
    image (alias)
    +
    inbox
    +
    industry
    +
    info
    +
    info-circle
    +
    institution (alias)
    +
    key
    +
    keyboard-o
    +
    language
    +
    laptop
    +
    leaf
    +
    legal (alias)
    +
    lemon-o
    +
    level-down
    +
    level-up
    +
    life-bouy (alias)
    +
    life-buoy (alias)
    +
    life-ring
    +
    life-saver (alias)
    +
    lightbulb-o
    +
    line-chart
    +
    location-arrow
    +
    lock
    +
    low-vision
    +
    magic
    +
    magnet
    +
    mail-forward (alias)
    +
    mail-reply (alias)
    +
    mail-reply-all (alias)
    +
    male
    +
    map
    +
    map-marker
    +
    map-o
    +
    map-pin
    +
    map-signs
    +
    meh-o
    +
    microchip
    +
    microphone
    +
    microphone-slash
    +
    minus
    +
    minus-circle
    +
    minus-square
    +
    minus-square-o
    +
    mobile
    +
    mobile-phone (alias)
    +
    money
    +
    moon-o
    +
    mortar-board (alias)
    +
    motorcycle
    +
    mouse-pointer
    +
    music
    +
    navicon (alias)
    +
    newspaper-o
    +
    object-group
    +
    object-ungroup
    +
    paint-brush
    +
    paper-plane
    +
    paper-plane-o
    +
    paw
    +
    pencil
    +
    pencil-square
    +
    pencil-square-o
    +
    percent
    +
    phone
    +
    phone-square
    +
    photo (alias)
    +
    picture-o
    +
    pie-chart
    +
    plane
    +
    plug
    +
    plus
    +
    plus-circle
    +
    plus-square
    +
    plus-square-o
    +
    podcast
    +
    power-off
    +
    print
    +
    puzzle-piece
    +
    qrcode
    +
    question
    +
    question-circle
    +
    question-circle-o
    +
    quote-left
    +
    quote-right
    +
    random
    +
    recycle
    +
    refresh
    +
    registered
    +
    remove (alias)
    +
    reorder (alias)
    +
    reply
    +
    reply-all
    +
    retweet
    +
    road
    +
    rocket
    +
    rss
    +
    rss-square
    +
    s15 (alias)
    +
    search
    +
    search-minus
    +
    search-plus
    +
    send (alias)
    +
    send-o (alias)
    +
    server
    +
    share
    +
    share-alt
    +
    share-alt-square
    +
    share-square
    +
    share-square-o
    +
    shield
    +
    ship
    +
    shopping-bag
    +
    shopping-basket
    +
    shopping-cart
    +
    shower
    +
    sign-in
    +
    sign-language
    +
    sign-out
    +
    signal
    +
    signing (alias)
    +
    sitemap
    +
    sliders
    +
    smile-o
    +
    snowflake-o
    +
    soccer-ball-o (alias)
    +
    sort
    +
    sort-alpha-asc
    +
    sort-alpha-desc
    +
    sort-amount-asc
    +
    sort-amount-desc
    +
    sort-asc
    +
    sort-desc
    +
    sort-down (alias)
    +
    sort-numeric-asc
    +
    sort-numeric-desc
    +
    sort-up (alias)
    +
    space-shuttle
    +
    spinner
    +
    spoon
    +
    square
    +
    square-o
    +
    star
    +
    star-half
    +
    star-half-empty (alias)
    +
    star-half-full (alias)
    +
    star-half-o
    +
    star-o
    +
    sticky-note
    +
    sticky-note-o
    +
    street-view
    +
    suitcase
    +
    sun-o
    +
    support (alias)
    +
    tablet
    +
    tachometer
    +
    tag
    +
    tags
    +
    tasks
    +
    taxi
    +
    television
    +
    terminal
    +
    thermometer (alias)
    +
    thermometer-0 (alias)
    +
    thermometer-1 (alias)
    +
    thermometer-2 (alias)
    +
    thermometer-3 (alias)
    +
    thermometer-4 (alias)
    +
    thermometer-empty
    +
    thermometer-full
    +
    thermometer-half
    +
    thermometer-quarter
    +
    thermometer-three-quarters
    +
    thumb-tack
    +
    thumbs-down
    +
    thumbs-o-down
    +
    thumbs-o-up
    +
    thumbs-up
    +
    ticket
    +
    times
    +
    times-circle
    +
    times-circle-o
    +
    times-rectangle (alias)
    +
    times-rectangle-o (alias)
    +
    tint
    +
    toggle-down (alias)
    +
    toggle-left (alias)
    +
    toggle-off
    +
    toggle-on
    +
    toggle-right (alias)
    +
    toggle-up (alias)
    +
    trademark
    +
    trash
    +
    trash-o
    +
    tree
    +
    trophy
    +
    truck
    +
    tty
    +
    tv (alias)
    +
    umbrella
    +
    universal-access
    +
    university
    +
    unlock
    +
    unlock-alt
    +
    unsorted (alias)
    +
    upload
    +
    user
    +
    user-circle
    +
    user-circle-o
    +
    user-o
    +
    user-plus
    +
    user-secret
    +
    user-times
    +
    users
    +
    vcard (alias)
    +
    vcard-o (alias)
    +
    video-camera
    +
    volume-control-phone
    +
    volume-down
    +
    volume-off
    +
    volume-up
    +
    warning (alias)
    +
    wheelchair
    +
    wheelchair-alt
    +
    wifi
    +
    window-close
    +
    window-close-o
    +
    window-maximize
    +
    window-minimize
    +
    window-restore
    +
    wrench
    +
    +
    +
    + +
    +
    american-sign-language-interpreting
    +
    asl-interpreting (alias)
    +
    assistive-listening-systems
    +
    audio-description
    +
    blind
    +
    braille
    +
    cc
    +
    deaf
    +
    deafness (alias)
    +
    hard-of-hearing (alias)
    +
    low-vision
    +
    question-circle-o
    +
    sign-language
    +
    signing (alias)
    +
    tty
    +
    universal-access
    +
    volume-control-phone
    +
    wheelchair
    +
    wheelchair-alt
    +
    +
    +
    + +
    +
    hand-grab-o (alias)
    +
    hand-lizard-o
    +
    hand-o-down
    +
    hand-o-left
    +
    hand-o-right
    +
    hand-o-up
    +
    hand-paper-o
    +
    hand-peace-o
    +
    hand-pointer-o
    +
    hand-rock-o
    +
    hand-scissors-o
    +
    hand-spock-o
    +
    hand-stop-o (alias)
    +
    thumbs-down
    +
    thumbs-o-down
    +
    thumbs-o-up
    +
    thumbs-up
    +
    +
    +
    + +
    +
    ambulance
    +
    automobile (alias)
    +
    bicycle
    +
    bus
    +
    cab (alias)
    +
    car
    +
    fighter-jet
    +
    motorcycle
    +
    plane
    +
    rocket
    +
    ship
    +
    space-shuttle
    +
    subway
    +
    taxi
    +
    train
    +
    truck
    +
    wheelchair
    +
    wheelchair-alt
    +
    +
    +
    + +
    +
    genderless
    +
    intersex (alias)
    +
    mars
    +
    mars-double
    +
    mars-stroke
    +
    mars-stroke-h
    +
    mars-stroke-v
    +
    mercury
    +
    neuter
    +
    transgender
    +
    transgender-alt
    +
    venus
    +
    venus-double
    +
    venus-mars
    +
    +
    +
    + +
    +
    file
    +
    file-archive-o
    +
    file-audio-o
    +
    file-code-o
    +
    file-excel-o
    +
    file-image-o
    +
    file-movie-o (alias)
    +
    file-o
    +
    file-pdf-o
    +
    file-photo-o (alias)
    +
    file-picture-o (alias)
    +
    file-powerpoint-o
    +
    file-sound-o (alias)
    +
    file-text
    +
    file-text-o
    +
    file-video-o
    +
    file-word-o
    +
    file-zip-o (alias)
    +
    +
    +
    + +
    +
      +
    • + 给这些图标加上 + fa-spin class,就可以表现出加载动画了 +
    • +
    +
    +
    +
    circle-o-notch
    +
    cog
    +
    gear (alias)
    +
    refresh
    +
    spinner
    +
    +
    +
    + +
    +
    check-square
    +
    check-square-o
    +
    circle
    +
    circle-o
    +
    dot-circle-o
    +
    minus-square
    +
    minus-square-o
    +
    plus-square
    +
    plus-square-o
    +
    square
    +
    square-o
    +
    +
    +
    + +
    +
    cc-amex
    +
    cc-diners-club
    +
    cc-discover
    +
    cc-jcb
    +
    cc-mastercard
    +
    cc-paypal
    +
    cc-stripe
    +
    cc-visa
    +
    credit-card
    +
    credit-card-alt
    +
    google-wallet
    +
    paypal
    +
    +
    +
    + +
    +
    area-chart
    +
    bar-chart
    +
    bar-chart-o (alias)
    +
    line-chart
    +
    pie-chart
    +
    +
    +
    + +
    +
    bitcoin (alias)
    +
    btc
    +
    cny (alias)
    +
    dollar (alias)
    +
    eur
    +
    euro (alias)
    +
    gbp
    +
    gg
    +
    gg-circle
    +
    ils
    +
    inr
    +
    jpy
    +
    krw
    +
    money
    +
    rmb (alias)
    +
    rouble (alias)
    +
    rub
    +
    ruble (alias)
    +
    rupee (alias)
    +
    shekel (alias)
    +
    sheqel (alias)
    +
    try
    +
    turkish-lira (alias)
    +
    usd
    +
    won (alias)
    +
    yen (alias)
    +
    +
    +
    + +
    +
    align-center
    +
    align-justify
    +
    align-left
    +
    align-right
    +
    bold
    +
    chain (alias)
    +
    chain-broken
    +
    clipboard
    +
    columns
    +
    copy (alias)
    +
    cut (alias)
    +
    dedent (alias)
    +
    eraser
    +
    file
    +
    file-o
    +
    file-text
    +
    file-text-o
    +
    files-o
    +
    floppy-o
    +
    font
    +
    header
    +
    indent
    +
    italic
    +
    link
    +
    list
    +
    list-alt
    +
    list-ol
    +
    list-ul
    +
    outdent
    +
    paperclip
    +
    paragraph
    +
    paste (alias)
    +
    repeat
    +
    rotate-left (alias)
    +
    rotate-right (alias)
    +
    save (alias)
    +
    scissors
    +
    strikethrough
    +
    subscript
    +
    superscript
    +
    table
    +
    text-height
    +
    text-width
    +
    th
    +
    th-large
    +
    th-list
    +
    underline
    +
    undo
    +
    unlink (alias)
    +
    +
    +
    + +
    +
    angle-double-down
    +
    angle-double-left
    +
    angle-double-right
    +
    angle-double-up
    +
    angle-down
    +
    angle-left
    +
    angle-right
    +
    angle-up
    +
    arrow-circle-down
    +
    arrow-circle-left
    +
    arrow-circle-o-down
    +
    arrow-circle-o-left
    +
    arrow-circle-o-right
    +
    arrow-circle-o-up
    +
    arrow-circle-right
    +
    arrow-circle-up
    +
    arrow-down
    +
    arrow-left
    +
    arrow-right
    +
    arrow-up
    +
    arrows
    +
    arrows-alt
    +
    arrows-h
    +
    arrows-v
    +
    caret-down
    +
    caret-left
    +
    caret-right
    +
    caret-square-o-down
    +
    caret-square-o-left
    +
    caret-square-o-right
    +
    caret-square-o-up
    +
    caret-up
    +
    chevron-circle-down
    +
    chevron-circle-left
    +
    chevron-circle-right
    +
    chevron-circle-up
    +
    chevron-down
    +
    chevron-left
    +
    chevron-right
    +
    chevron-up
    +
    exchange
    +
    hand-o-down
    +
    hand-o-left
    +
    hand-o-right
    +
    hand-o-up
    +
    long-arrow-down
    +
    long-arrow-left
    +
    long-arrow-right
    +
    long-arrow-up
    +
    toggle-down (alias)
    +
    toggle-left (alias)
    +
    toggle-right (alias)
    +
    toggle-up (alias)
    +
    +
    +
    + +
    +
    arrows-alt
    +
    backward
    +
    compress
    +
    eject
    +
    expand
    +
    fast-backward
    +
    fast-forward
    +
    forward
    +
    pause
    +
    pause-circle
    +
    pause-circle-o
    +
    play
    +
    play-circle
    +
    play-circle-o
    +
    random
    +
    step-backward
    +
    step-forward
    +
    stop
    +
    stop-circle
    +
    stop-circle-o
    +
    youtube-play
    +
    +
    +
    + +
    +
    500px
    +
    adn
    +
    amazon
    +
    android
    +
    angellist
    +
    apple
    +
    bandcamp
    +
    behance
    +
    behance-square
    +
    bitbucket
    +
    bitbucket-square
    +
    bitcoin (alias)
    +
    black-tie
    +
    bluetooth
    +
    bluetooth-b
    +
    btc
    +
    buysellads
    +
    cc-amex
    +
    cc-diners-club
    +
    cc-discover
    +
    cc-jcb
    +
    cc-mastercard
    +
    cc-paypal
    +
    cc-stripe
    +
    cc-visa
    +
    chrome
    +
    codepen
    +
    codiepie
    +
    connectdevelop
    +
    contao
    +
    css3
    +
    dashcube
    +
    delicious
    +
    deviantart
    +
    digg
    +
    dribbble
    +
    dropbox
    +
    drupal
    +
    edge
    +
    eercast
    +
    empire
    +
    envira
    +
    etsy
    +
    expeditedssl
    +
    fa (alias)
    +
    facebook
    +
    facebook-f (alias)
    +
    facebook-official
    +
    facebook-square
    +
    firefox
    +
    first-order
    +
    flickr
    +
    font-awesome
    +
    fonticons
    +
    fort-awesome
    +
    forumbee
    +
    foursquare
    +
    free-code-camp
    +
    ge (alias)
    +
    get-pocket
    +
    gg
    +
    gg-circle
    +
    git
    +
    git-square
    +
    github
    +
    github-alt
    +
    github-square
    +
    gitlab
    +
    gittip (alias)
    +
    glide
    +
    glide-g
    +
    google
    +
    google-plus
    +
    google-plus-circle (alias)
    +
    google-plus-official
    +
    google-plus-square
    +
    google-wallet
    +
    gratipay
    +
    grav
    +
    hacker-news
    +
    houzz
    +
    html5
    +
    imdb
    +
    instagram
    +
    internet-explorer
    +
    ioxhost
    +
    joomla
    +
    jsfiddle
    +
    lastfm
    +
    lastfm-square
    +
    leanpub
    +
    linkedin
    +
    linkedin-square
    +
    linode
    +
    linux
    +
    maxcdn
    +
    meanpath
    +
    medium
    +
    meetup
    +
    mixcloud
    +
    modx
    +
    odnoklassniki
    +
    odnoklassniki-square
    +
    opencart
    +
    openid
    +
    opera
    +
    optin-monster
    +
    pagelines
    +
    paypal
    +
    pied-piper
    +
    pied-piper-alt
    +
    pied-piper-pp
    +
    pinterest
    +
    pinterest-p
    +
    pinterest-square
    +
    product-hunt
    +
    qq
    +
    quora
    +
    ra (alias)
    +
    ravelry
    +
    rebel
    +
    reddit
    +
    reddit-alien
    +
    reddit-square
    +
    renren
    +
    resistance (alias)
    +
    safari
    +
    scribd
    +
    sellsy
    +
    share-alt
    +
    share-alt-square
    +
    shirtsinbulk
    +
    simplybuilt
    +
    skyatlas
    +
    skype
    +
    slack
    +
    slideshare
    +
    snapchat
    +
    snapchat-ghost
    +
    snapchat-square
    +
    soundcloud
    +
    spotify
    +
    stack-exchange
    +
    stack-overflow
    +
    steam
    +
    steam-square
    +
    stumbleupon
    +
    stumbleupon-circle
    +
    superpowers
    +
    telegram
    +
    tencent-weibo
    +
    themeisle
    +
    trello
    +
    tripadvisor
    +
    tumblr
    +
    tumblr-square
    +
    twitch
    +
    twitter
    +
    twitter-square
    +
    usb
    +
    viacoin
    +
    viadeo
    +
    viadeo-square
    +
    vimeo
    +
    vimeo-square
    +
    vine
    +
    vk
    +
    wechat (alias)
    +
    weibo
    +
    weixin
    +
    whatsapp
    +
    wikipedia-w
    +
    windows
    +
    wordpress
    +
    wpbeginner
    +
    wpexplorer
    +
    wpforms
    +
    xing
    +
    xing-square
    +
    y-combinator
    +
    y-combinator-square (alias)
    +
    yahoo
    +
    yc (alias)
    +
    yc-square (alias)
    +
    yelp
    +
    yoast
    +
    youtube
    +
    youtube-play
    +
    youtube-square
    +
    +
    +
    + +
    +
    ambulance
    +
    h-square
    +
    heart
    +
    heart-o
    +
    heartbeat
    +
    hospital-o
    +
    medkit
    +
    plus-square
    +
    stethoscope
    +
    user-md
    +
    wheelchair
    +
    wheelchair-alt
    +
    +
    +
    +
    +
    +
    +
    + + + diff --git a/ruoyi-admin/src/main/resources/templates/demo/icon/glyphicons.html b/ruoyi-admin/src/main/resources/templates/demo/icon/glyphicons.html new file mode 100644 index 0000000..b9c6f6b --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/icon/glyphicons.html @@ -0,0 +1,1364 @@ + + + + + + +
    +
    +
    +
    +

    Glyphicons 字体图标

    包括250多个来自 Glyphicon Halflings 的字体图标。Glyphicons Halflings 一般是收费的,但是他们的作者允许 Bootstrap 免费使用。为了表示感谢,希望你在使用时尽量为 Glyphicons 添加一个友情链接。 +
    +
    +
    +
    +
    +
    所有图标 所有图标集合 - Glyphicons
    +
    + + + + + + + + + + +
    +
    +
    +
    +
      + +
    • + + glyphicon glyphicon-asterisk +
    • + +
    • + + glyphicon glyphicon-plus +
    • + +
    • + + glyphicon glyphicon-euro +
    • + +
    • + + glyphicon glyphicon-eur +
    • + +
    • + + glyphicon glyphicon-minus +
    • + +
    • + + glyphicon glyphicon-cloud +
    • + +
    • + + glyphicon glyphicon-envelope +
    • + +
    • + + glyphicon glyphicon-pencil +
    • + +
    • + + glyphicon glyphicon-glass +
    • + +
    • + + glyphicon glyphicon-music +
    • + +
    • + + glyphicon glyphicon-search +
    • + +
    • + + glyphicon glyphicon-heart +
    • + +
    • + + glyphicon glyphicon-star +
    • + +
    • + + glyphicon glyphicon-star-empty +
    • + +
    • + + glyphicon glyphicon-user +
    • + +
    • + + glyphicon glyphicon-film +
    • + +
    • + + glyphicon glyphicon-th-large +
    • + +
    • + + glyphicon glyphicon-th +
    • + +
    • + + glyphicon glyphicon-th-list +
    • + +
    • + + glyphicon glyphicon-ok +
    • + +
    • + + glyphicon glyphicon-remove +
    • + +
    • + + glyphicon glyphicon-zoom-in +
    • + +
    • + + glyphicon glyphicon-zoom-out +
    • + +
    • + + glyphicon glyphicon-off +
    • + +
    • + + glyphicon glyphicon-signal +
    • + +
    • + + glyphicon glyphicon-cog +
    • + +
    • + + glyphicon glyphicon-trash +
    • + +
    • + + glyphicon glyphicon-home +
    • + +
    • + + glyphicon glyphicon-file +
    • + +
    • + + glyphicon glyphicon-time +
    • + +
    • + + glyphicon glyphicon-road +
    • + +
    • + + glyphicon glyphicon-download-alt +
    • + +
    • + + glyphicon glyphicon-download +
    • + +
    • + + glyphicon glyphicon-upload +
    • + +
    • + + glyphicon glyphicon-inbox +
    • + +
    • + + glyphicon glyphicon-play-circle +
    • + +
    • + + glyphicon glyphicon-repeat +
    • + +
    • + + glyphicon glyphicon-refresh +
    • + +
    • + + glyphicon glyphicon-list-alt +
    • + +
    • + + glyphicon glyphicon-lock +
    • + +
    • + + glyphicon glyphicon-flag +
    • + +
    • + + glyphicon glyphicon-headphones +
    • + +
    • + + glyphicon glyphicon-volume-off +
    • + +
    • + + glyphicon glyphicon-volume-down +
    • + +
    • + + glyphicon glyphicon-volume-up +
    • + +
    • + + glyphicon glyphicon-qrcode +
    • + +
    • + + glyphicon glyphicon-barcode +
    • + +
    • + + glyphicon glyphicon-tag +
    • + +
    • + + glyphicon glyphicon-tags +
    • + +
    • + + glyphicon glyphicon-book +
    • + +
    • + + glyphicon glyphicon-bookmark +
    • + +
    • + + glyphicon glyphicon-print +
    • + +
    • + + glyphicon glyphicon-camera +
    • + +
    • + + glyphicon glyphicon-font +
    • + +
    • + + glyphicon glyphicon-bold +
    • + +
    • + + glyphicon glyphicon-italic +
    • + +
    • + + glyphicon glyphicon-text-height +
    • + +
    • + + glyphicon glyphicon-text-width +
    • + +
    • + + glyphicon glyphicon-align-left +
    • + +
    • + + glyphicon glyphicon-align-center +
    • + +
    • + + glyphicon glyphicon-align-right +
    • + +
    • + + glyphicon glyphicon-align-justify +
    • + +
    • + + glyphicon glyphicon-list +
    • + +
    • + + glyphicon glyphicon-indent-left +
    • + +
    • + + glyphicon glyphicon-indent-right +
    • + +
    • + + glyphicon glyphicon-facetime-video +
    • + +
    • + + glyphicon glyphicon-picture +
    • + +
    • + + glyphicon glyphicon-map-marker +
    • + +
    • + + glyphicon glyphicon-adjust +
    • + +
    • + + glyphicon glyphicon-tint +
    • + +
    • + + glyphicon glyphicon-edit +
    • + +
    • + + glyphicon glyphicon-share +
    • + +
    • + + glyphicon glyphicon-check +
    • + +
    • + + glyphicon glyphicon-move +
    • + +
    • + + glyphicon glyphicon-step-backward +
    • + +
    • + + glyphicon glyphicon-fast-backward +
    • + +
    • + + glyphicon glyphicon-backward +
    • + +
    • + + glyphicon glyphicon-play +
    • + +
    • + + glyphicon glyphicon-pause +
    • + +
    • + + glyphicon glyphicon-stop +
    • + +
    • + + glyphicon glyphicon-forward +
    • + +
    • + + glyphicon glyphicon-fast-forward +
    • + +
    • + + glyphicon glyphicon-step-forward +
    • + +
    • + + glyphicon glyphicon-eject +
    • + +
    • + + glyphicon glyphicon-chevron-left +
    • + +
    • + + glyphicon glyphicon-chevron-right +
    • + +
    • + + glyphicon glyphicon-plus-sign +
    • + +
    • + + glyphicon glyphicon-minus-sign +
    • + +
    • + + glyphicon glyphicon-remove-sign +
    • + +
    • + + glyphicon glyphicon-ok-sign +
    • + +
    • + + glyphicon glyphicon-question-sign +
    • + +
    • + + glyphicon glyphicon-info-sign +
    • + +
    • + + glyphicon glyphicon-screenshot +
    • + +
    • + + glyphicon glyphicon-remove-circle +
    • + +
    • + + glyphicon glyphicon-ok-circle +
    • + +
    • + + glyphicon glyphicon-ban-circle +
    • + +
    • + + glyphicon glyphicon-arrow-left +
    • + +
    • + + glyphicon glyphicon-arrow-right +
    • + +
    • + + glyphicon glyphicon-arrow-up +
    • + +
    • + + glyphicon glyphicon-arrow-down +
    • + +
    • + + glyphicon glyphicon-share-alt +
    • + +
    • + + glyphicon glyphicon-resize-full +
    • + +
    • + + glyphicon glyphicon-resize-small +
    • + +
    • + + glyphicon glyphicon-exclamation-sign +
    • + +
    • + + glyphicon glyphicon-gift +
    • + +
    • + + glyphicon glyphicon-leaf +
    • + +
    • + + glyphicon glyphicon-fire +
    • + +
    • + + glyphicon glyphicon-eye-open +
    • + +
    • + + glyphicon glyphicon-eye-close +
    • + +
    • + + glyphicon glyphicon-warning-sign +
    • + +
    • + + glyphicon glyphicon-plane +
    • + +
    • + + glyphicon glyphicon-calendar +
    • + +
    • + + glyphicon glyphicon-random +
    • + +
    • + + glyphicon glyphicon-comment +
    • + +
    • + + glyphicon glyphicon-magnet +
    • + +
    • + + glyphicon glyphicon-chevron-up +
    • + +
    • + + glyphicon glyphicon-chevron-down +
    • + +
    • + + glyphicon glyphicon-retweet +
    • + +
    • + + glyphicon glyphicon-shopping-cart +
    • + +
    • + + glyphicon glyphicon-folder-close +
    • + +
    • + + glyphicon glyphicon-folder-open +
    • + +
    • + + glyphicon glyphicon-resize-vertical +
    • + +
    • + + glyphicon glyphicon-resize-horizontal +
    • + +
    • + + glyphicon glyphicon-hdd +
    • + +
    • + + glyphicon glyphicon-bullhorn +
    • + +
    • + + glyphicon glyphicon-bell +
    • + +
    • + + glyphicon glyphicon-certificate +
    • + +
    • + + glyphicon glyphicon-thumbs-up +
    • + +
    • + + glyphicon glyphicon-thumbs-down +
    • + +
    • + + glyphicon glyphicon-hand-right +
    • + +
    • + + glyphicon glyphicon-hand-left +
    • + +
    • + + glyphicon glyphicon-hand-up +
    • + +
    • + + glyphicon glyphicon-hand-down +
    • + +
    • + + glyphicon glyphicon-circle-arrow-right +
    • + +
    • + + glyphicon glyphicon-circle-arrow-left +
    • + +
    • + + glyphicon glyphicon-circle-arrow-up +
    • + +
    • + + glyphicon glyphicon-circle-arrow-down +
    • + +
    • + + glyphicon glyphicon-globe +
    • + +
    • + + glyphicon glyphicon-wrench +
    • + +
    • + + glyphicon glyphicon-tasks +
    • + +
    • + + glyphicon glyphicon-filter +
    • + +
    • + + glyphicon glyphicon-briefcase +
    • + +
    • + + glyphicon glyphicon-fullscreen +
    • + +
    • + + glyphicon glyphicon-dashboard +
    • + +
    • + + glyphicon glyphicon-paperclip +
    • + +
    • + + glyphicon glyphicon-heart-empty +
    • + +
    • + + glyphicon glyphicon-link +
    • + +
    • + + glyphicon glyphicon-phone +
    • + +
    • + + glyphicon glyphicon-pushpin +
    • + +
    • + + glyphicon glyphicon-usd +
    • + +
    • + + glyphicon glyphicon-gbp +
    • + +
    • + + glyphicon glyphicon-sort +
    • + +
    • + + glyphicon glyphicon-sort-by-alphabet +
    • + +
    • + + glyphicon glyphicon-sort-by-alphabet-alt +
    • + +
    • + + glyphicon glyphicon-sort-by-order +
    • + +
    • + + glyphicon glyphicon-sort-by-order-alt +
    • + +
    • + + glyphicon glyphicon-sort-by-attributes +
    • + +
    • + + glyphicon glyphicon-sort-by-attributes-alt +
    • + +
    • + + glyphicon glyphicon-unchecked +
    • + +
    • + + glyphicon glyphicon-expand +
    • + +
    • + + glyphicon glyphicon-collapse-down +
    • + +
    • + + glyphicon glyphicon-collapse-up +
    • + +
    • + + glyphicon glyphicon-log-in +
    • + +
    • + + glyphicon glyphicon-flash +
    • + +
    • + + glyphicon glyphicon-log-out +
    • + +
    • + + glyphicon glyphicon-new-window +
    • + +
    • + + glyphicon glyphicon-record +
    • + +
    • + + glyphicon glyphicon-save +
    • + +
    • + + glyphicon glyphicon-open +
    • + +
    • + + glyphicon glyphicon-saved +
    • + +
    • + + glyphicon glyphicon-import +
    • + +
    • + + glyphicon glyphicon-export +
    • + +
    • + + glyphicon glyphicon-send +
    • + +
    • + + glyphicon glyphicon-floppy-disk +
    • + +
    • + + glyphicon glyphicon-floppy-saved +
    • + +
    • + + glyphicon glyphicon-floppy-remove +
    • + +
    • + + glyphicon glyphicon-floppy-save +
    • + +
    • + + glyphicon glyphicon-floppy-open +
    • + +
    • + + glyphicon glyphicon-credit-card +
    • + +
    • + + glyphicon glyphicon-transfer +
    • + +
    • + + glyphicon glyphicon-cutlery +
    • + +
    • + + glyphicon glyphicon-header +
    • + +
    • + + glyphicon glyphicon-compressed +
    • + +
    • + + glyphicon glyphicon-earphone +
    • + +
    • + + glyphicon glyphicon-phone-alt +
    • + +
    • + + glyphicon glyphicon-tower +
    • + +
    • + + glyphicon glyphicon-stats +
    • + +
    • + + glyphicon glyphicon-sd-video +
    • + +
    • + + glyphicon glyphicon-hd-video +
    • + +
    • + + glyphicon glyphicon-subtitles +
    • + +
    • + + glyphicon glyphicon-sound-stereo +
    • + +
    • + + glyphicon glyphicon-sound-dolby +
    • + +
    • + + glyphicon glyphicon-sound-5-1 +
    • + +
    • + + glyphicon glyphicon-sound-6-1 +
    • + +
    • + + glyphicon glyphicon-sound-7-1 +
    • + +
    • + + glyphicon glyphicon-copyright-mark +
    • + +
    • + + glyphicon glyphicon-registration-mark +
    • + +
    • + + glyphicon glyphicon-cloud-download +
    • + +
    • + + glyphicon glyphicon-cloud-upload +
    • + +
    • + + glyphicon glyphicon-tree-conifer +
    • + +
    • + + glyphicon glyphicon-tree-deciduous +
    • + +
    • + + glyphicon glyphicon-cd +
    • + +
    • + + glyphicon glyphicon-save-file +
    • + +
    • + + glyphicon glyphicon-open-file +
    • + +
    • + + glyphicon glyphicon-level-up +
    • + +
    • + + glyphicon glyphicon-copy +
    • + +
    • + + glyphicon glyphicon-paste +
    • + +
    • + + glyphicon glyphicon-alert +
    • + +
    • + + glyphicon glyphicon-equalizer +
    • + +
    • + + glyphicon glyphicon-king +
    • + +
    • + + glyphicon glyphicon-queen +
    • + +
    • + + glyphicon glyphicon-pawn +
    • + +
    • + + glyphicon glyphicon-bishop +
    • + +
    • + + glyphicon glyphicon-knight +
    • + +
    • + + glyphicon glyphicon-baby-formula +
    • + +
    • + + glyphicon glyphicon-tent +
    • + +
    • + + glyphicon glyphicon-blackboard +
    • + +
    • + + glyphicon glyphicon-bed +
    • + +
    • + + glyphicon glyphicon-apple +
    • + +
    • + + glyphicon glyphicon-erase +
    • + +
    • + + glyphicon glyphicon-hourglass +
    • + +
    • + + glyphicon glyphicon-lamp +
    • + +
    • + + glyphicon glyphicon-duplicate +
    • + +
    • + + glyphicon glyphicon-piggy-bank +
    • + +
    • + + glyphicon glyphicon-scissors +
    • + +
    • + + glyphicon glyphicon-bitcoin +
    • + +
    • + + glyphicon glyphicon-btc +
    • + +
    • + + glyphicon glyphicon-xbt +
    • + +
    • + + glyphicon glyphicon-yen +
    • + +
    • + + glyphicon glyphicon-jpy +
    • + +
    • + + glyphicon glyphicon-ruble +
    • + +
    • + + glyphicon glyphicon-rub +
    • + +
    • + + glyphicon glyphicon-scale +
    • + +
    • + + glyphicon glyphicon-ice-lolly +
    • + +
    • + + glyphicon glyphicon-ice-lolly-tasted +
    • + +
    • + + glyphicon glyphicon-education +
    • + +
    • + + glyphicon glyphicon-option-horizontal +
    • + +
    • + + glyphicon glyphicon-option-vertical +
    • + +
    • + + glyphicon glyphicon-menu-hamburger +
    • + +
    • + + glyphicon glyphicon-modal-window +
    • + +
    • + + glyphicon glyphicon-oil +
    • + +
    • + + glyphicon glyphicon-grain +
    • + +
    • + + glyphicon glyphicon-sunglasses +
    • + +
    • + + glyphicon glyphicon-text-size +
    • + +
    • + + glyphicon glyphicon-text-color +
    • + +
    • + + glyphicon glyphicon-text-background +
    • + +
    • + + glyphicon glyphicon-object-align-top +
    • + +
    • + + glyphicon glyphicon-object-align-bottom +
    • + +
    • + + glyphicon glyphicon-object-align-horizontal +
    • + +
    • + + glyphicon glyphicon-object-align-left +
    • + +
    • + + glyphicon glyphicon-object-align-vertical +
    • + +
    • + + glyphicon glyphicon-object-align-right +
    • + +
    • + + glyphicon glyphicon-triangle-right +
    • + +
    • + + glyphicon glyphicon-triangle-left +
    • + +
    • + + glyphicon glyphicon-triangle-bottom +
    • + +
    • + + glyphicon glyphicon-triangle-top +
    • + +
    • + + glyphicon glyphicon-console +
    • + +
    • + + glyphicon glyphicon-superscript +
    • + +
    • + + glyphicon glyphicon-subscript +
    • + +
    • + + glyphicon glyphicon-menu-left +
    • + +
    • + + glyphicon glyphicon-menu-right +
    • + +
    • + + glyphicon glyphicon-menu-down +
    • + +
    • + + glyphicon glyphicon-menu-up +
    • + +
    +
    +
    +
    +
    +
    +
    + + + diff --git a/ruoyi-admin/src/main/resources/templates/demo/modal/dialog.html b/ruoyi-admin/src/main/resources/templates/demo/modal/dialog.html new file mode 100644 index 0000000..de0fd32 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/modal/dialog.html @@ -0,0 +1,215 @@ + + + + + + +
    +
    +
    +
    +
    +
    模态窗口
    + +
    +
    +

    创建自定义的RuoYi模态窗口可通过添加.inmodal类来实现。

    +
    + +
    + + +
    +
    +
    +
    +
    大小设置
    + +
    +
    +

    模态窗口提供两种大小尺寸,可以通过为模态窗口的.modal-dialog添加类来实现

    + +
    + + +
    + + + +
    +
    +
    +
    +
    +
    +
    动画窗口
    + +
    +
    +

    您可以通过为模态窗口的.modal-content添加类来实现动画效果

    + + + + + + + +
    +
    +
    +
    +
    设置选项
    + +
    +
    +

    可以通过数据绑定或者Javascript来实现模态窗口的相关功能,如果使用数据绑定,可以为元素添加data-,如data-backdrop=""

    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    名称类型默认值说明
    backdropboolean 或 string 'static'true遮罩层,或使用'static'指定遮罩层与关闭模态窗口不关联
    keyboardbooleantrue按Esc键时退出模态窗口
    showbooleantrue初始化完成后显示模态窗口
    remotepathfalse +

    推荐使用数据绑定方式,或使用 + jQuery.load

    +

    远程URL示例:

    +
    +
    <a data-toggle="modal" href="remote.html" data-target="#modal">Click me</a>
    +
    +
    +
    +
    +
    +
    +
    +
    + + + diff --git a/ruoyi-admin/src/main/resources/templates/demo/modal/form.html b/ruoyi-admin/src/main/resources/templates/demo/modal/form.html new file mode 100644 index 0000000..de6e628 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/modal/form.html @@ -0,0 +1,102 @@ + + + + + + +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    + + + + diff --git a/ruoyi-admin/src/main/resources/templates/demo/modal/layer.html b/ruoyi-admin/src/main/resources/templates/demo/modal/layer.html new file mode 100644 index 0000000..3cbfb29 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/modal/layer.html @@ -0,0 +1,288 @@ + + + + + + + +
    +
    +
    +
    +
    +
    信息框
    +
    +
    +

    通过调用$.modal.alert()实现。

    + + + + +
    +
    +
    + +
    +
    +
    +
    提示框
    +
    +
    +

    通过调用$.modal.msg()实现。

    + + + + +
    +
    +
    +
    + +
    +
    +
    +
    +
    询问框
    +
    +
    +

    通过调用$.modal.confirm()实现。

    + +
    +
    +
    + +
    +
    +
    +
    消息提示并刷新父窗体
    +
    +
    +

    通过调用$.modal.msgReload()实现。

    + +
    +
    +
    + +
    +
    +
    +
    普通弹出层
    +
    +
    +

    通过调用$.modal.open()实现。

    + + + + + +
    +
    +
    + +
    +
    +
    +
    选卡页方式
    +
    +
    +

    通过调用$.modal.openTab()实现。

    + + + + +
    +
    +
    + +
    +
    +
    +
    其他内容
    +
    +
    +

    通过调用layer实现。

    + + + + +
    +
    +
    + +
    +
    +
    +
    遮罩层
    +
    +
    +

    通过调用blockUI实现。

    + + + +
    +
    +
    + +
    +
    +
    + + +
    +
    + +
    +
    + + + + diff --git a/ruoyi-admin/src/main/resources/templates/demo/modal/table.html b/ruoyi-admin/src/main/resources/templates/demo/modal/table.html new file mode 100644 index 0000000..fc17215 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/modal/table.html @@ -0,0 +1,124 @@ + + + + + + +
    +
    +
    +
    +
    +
    弹层框
    +
    +
    +

    弹出复选框表格及单选框表格(点击提交后得到数据)。

    + + +
    +
    +
    +
    +
    +
    +
    弹层框
    +
    +
    +

    弹出层,点击提交后得到数据并回显到父窗体。

    + + + +

    +
    +
    +
    +
    +
    +
    +
    弹层框
    +
    +
    +

    多层弹出,点击提交后得到数据并回显到父窗体。

    + +

    +
    +
    +
    +
    +
    + + + + diff --git a/ruoyi-admin/src/main/resources/templates/demo/modal/table/check.html b/ruoyi-admin/src/main/resources/templates/demo/modal/table/check.html new file mode 100644 index 0000000..1ba7784 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/modal/table/check.html @@ -0,0 +1,86 @@ + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/modal/table/frame1.html b/ruoyi-admin/src/main/resources/templates/demo/modal/table/frame1.html new file mode 100644 index 0000000..af5fe62 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/modal/table/frame1.html @@ -0,0 +1,53 @@ + + + + + + +
    +
    +
    +
    +
    +
    + +
    + +
    + +
    +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/modal/table/frame2.html b/ruoyi-admin/src/main/resources/templates/demo/modal/table/frame2.html new file mode 100644 index 0000000..b4940fe --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/modal/table/frame2.html @@ -0,0 +1,24 @@ + + + + + + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/modal/table/parent.html b/ruoyi-admin/src/main/resources/templates/demo/modal/table/parent.html new file mode 100644 index 0000000..28f402b --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/modal/table/parent.html @@ -0,0 +1,102 @@ + + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/modal/table/radio.html b/ruoyi-admin/src/main/resources/templates/demo/modal/table/radio.html new file mode 100644 index 0000000..0df48f0 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/modal/table/radio.html @@ -0,0 +1,86 @@ + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/operate/add.html b/ruoyi-admin/src/main/resources/templates/demo/operate/add.html new file mode 100644 index 0000000..ee8e332 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/operate/add.html @@ -0,0 +1,78 @@ + + + + + + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    + + + + diff --git a/ruoyi-admin/src/main/resources/templates/demo/operate/detail.html b/ruoyi-admin/src/main/resources/templates/demo/operate/detail.html new file mode 100644 index 0000000..8e10c0f --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/operate/detail.html @@ -0,0 +1,71 @@ + + + + + + +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + + + + diff --git a/ruoyi-admin/src/main/resources/templates/demo/operate/edit.html b/ruoyi-admin/src/main/resources/templates/demo/operate/edit.html new file mode 100644 index 0000000..94ed965 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/operate/edit.html @@ -0,0 +1,79 @@ + + + + + + +
    +
    + +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    + + + + diff --git a/ruoyi-admin/src/main/resources/templates/demo/operate/other.html b/ruoyi-admin/src/main/resources/templates/demo/operate/other.html new file mode 100644 index 0000000..2c44685 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/operate/other.html @@ -0,0 +1,77 @@ + + + + + + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    +   +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    +   +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/operate/table.html b/ruoyi-admin/src/main/resources/templates/demo/operate/table.html new file mode 100644 index 0000000..6a3f897 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/operate/table.html @@ -0,0 +1,125 @@ + + + + + + + +
    + + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/report/echarts.html b/ruoyi-admin/src/main/resources/templates/demo/report/echarts.html new file mode 100644 index 0000000..828dd6a --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/report/echarts.html @@ -0,0 +1,1264 @@ + + + + + + +
    +
    +

    ECharts开源来自百度商业前端数据可视化团队,基于html5 Canvas,是一个纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据可视化图表。创新的拖拽重计算、数据视图、值域漫游等特性大大增强了用户体验,赋予了用户对数据进行挖掘、整合的能力。 了解更多 +

    +

    ECharts官网:https://echarts.apache.org/ +

    + +
    +
    +
    +
    +
    +
    +
    +
    折线图
    +
    + + + + + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    柱状图
    +
    + + + + + + + + + + +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    散点图
    +
    + + + + + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    K线图
    +
    + + + + + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    饼状图
    +
    + + + + + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    雷达图
    +
    + + + + + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    仪表盘
    +
    + + + + + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    漏斗图
    +
    + + + + + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    中国地图
    +
    + + + + + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/report/metrics.html b/ruoyi-admin/src/main/resources/templates/demo/report/metrics.html new file mode 100644 index 0000000..853f672 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/report/metrics.html @@ -0,0 +1,478 @@ + + + + + + +
    +
    +
    +
    +
    +
    Q1 销量
    +

    + 上升 +

    + 更新时间:12天以前 +
    +
    +
    +
    +
    +
    +
    Q2 销量
    +

    + 上升 +

    + 更新时间:12天以前 +
    +
    +
    +
    +
    +
    +
    Q3 销量
    +

    + 下降 +

    + 更新时间:12天以前 +
    +
    +
    +
    +
    +
    +
    Q4 销量
    +

    + 下降 +

    + 更新时间:12天以前 +
    +
    +
    + +
    +
    +
    +
    +
    +
    本日访问量
    +

    198 009

    +
    +
    +
    +
    +
    +
    +
    +
    本周访问量
    +

    65 000

    +
    +
    +
    +
    +
    +
    +
    +
    本月访问量
    +

    680 900

    +
    +
    +
    +
    +
    +
    +
    +
    平均停留时间
    +

    00:06:40

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    使用率
    +

    65%

    +
    +
    +
    + +
    4:32更新
    +
    +
    +
    + +
    +
    +
    +
    使用率
    +

    50%

    +
    +
    +
    + +
    4:32更新
    +
    +
    +
    + +
    +
    +
    +
    使用率
    +

    14%

    +
    +
    +
    + +
    4:32更新
    +
    +
    +
    + +
    +
    +
    +
    使用率
    +

    20%

    +
    +
    +
    + +
    4:32更新
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    百分比
    +

    42/20

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    百分比
    +

    100/54

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    百分比
    +

    685/211

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    百分比
    +

    240/32

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    收入
    +

    886,200

    +
    98%
    + 总收入 +
    +
    +
    +
    +
    +
    +
    本月收入
    +

    1 738,200

    +
    98%
    + 总收入 +
    +
    +
    +
    +
    +
    +
    本日收入
    +

    -200,100

    +
    12%
    + 总收入 +
    +
    +
    +
    +
    +
    +
    搜索有收入
    +

    54,200

    +
    24%
    + 总收入 +
    +
    +
    +
    +
    +
    +
    +
    +
    预警
    + + + + + + + + + + + + + + + +
    + + + 示例 01 +
    + + + 示例 02 +
    + + + 示例 03 +
    +
    +
    +
    +
    +
    +
    +
    项目
    + + + + + + + + + + + + + + + +
    + + + 示例 01 +
    + + + 示例 02 +
    + + + 示例 03 +
    +
    +
    +
    +
    +
    +
    +
    消息
    + + + + + + + + + + + + + + + +
    + + + 示例 01 +
    + + + 示例 02 +
    + + + 示例 03 +
    +
    +
    +
    +
    +
    +
    +
    通知
    + + + + + + + + + + + + + + + +
    + + + 示例 01 +
    + + + 示例 02 +
    + + + 示例 03 +
    +
    +
    +
    +
    +
    + + + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/report/peity.html b/ruoyi-admin/src/main/resources/templates/demo/report/peity.html new file mode 100644 index 0000000..93c5194 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/report/peity.html @@ -0,0 +1,206 @@ + + + + + + +
    + +
    +
    +
    +

    Peity图表

    +

    是一个内嵌数据图形可视化的图表库

    +

    了解 Peity +

    +
    +
    +
    +
    +
    +
    饼状图 自定义颜色
    +
    + + + +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    图表代码
    + 1/5 + + <span class="pie">1/5</span> +
    + 226/360 + + <span class="pie">226/360</span> +
    + 0.52/1.561 + + <span class="pie">0.52/1.561</span> +
    + 1,4 + + <span class="pie">1,4</span> +
    + 226,134 + + <span class="pie">226,134</span> +
    + 0.52,1.041 + + <span class="pie">0.52,1.041</span> +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    线性图
    +
    + + + +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    图表代码
    + 5,3,9,6,5,9,7,3,5,2,5,3,9,6,5,9,7,3,5,2 + + <span class="line">5,3,9,6,5,9,7,3,5,2</span> +
    + 5,3,9,6,5,9,7,3,5,2 + + <span class="line">5,3,9,6,5,9,7,3,5,2</span> +
    + 5,3,2,-1,-3,-2,2,3,5,2 + + <span class="line">5,3,2,-1,-3,-2,2,3,5,2</span> +
    + 0,-3,-6,-4,-5,-4,-7,-3,-5,-2 + + <span class="line">0,-3,-6,-4,-5,-4,-7,-3,-5,-2</span> +
    + 5,3,9,6,5,9,7,3,5,2 + + <span class="bar">5,3,9,6,5,9,7,3,5,2</span> +
    + 5,3,2,-1,-3,-2,2,3,5,2 + + <span class="bar">5,3,2,-1,-3,-2,2,3,5,2</span> +
    +
    +
    +
    + +
    +
    + + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/report/sparkline.html b/ruoyi-admin/src/main/resources/templates/demo/report/sparkline.html new file mode 100644 index 0000000..d73c18f --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/report/sparkline.html @@ -0,0 +1,232 @@ + + + + + + +
    + +
    +
    +
    +

    Sparkline

    +

    这是另一个可视化图表库

    +

    了解 Sparkline +

    +
    +
    +
    +
    +
    +
    Sparkline图表 自定义颜色
    +
    + + + +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    图表类型
    + + + 内联线性图 +
    + + + 柱状图 +
    + + + 饼状图 +
    + + + 长线性图 +
    + + + 三态图 +
    + + + 散点图 +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    自定义饼状图尺寸
    +
    + + + + + + + + + + +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    自定义柱状图尺寸
    +
    + + + + + + + + + + +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    自定义线性图尺寸
    +
    + + + + + + + + + + +
    +
    +
    + +
    +
    +
    +
    +
    + + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/table/asynTree.html b/ruoyi-admin/src/main/resources/templates/demo/table/asynTree.html new file mode 100644 index 0000000..c5af5c0 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/table/asynTree.html @@ -0,0 +1,85 @@ + + + + + + +
    +
    +
    +
    +
    + +
    +
    +
    + + +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/table/button.html b/ruoyi-admin/src/main/resources/templates/demo/table/button.html new file mode 100644 index 0000000..7a86644 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/table/button.html @@ -0,0 +1,92 @@ + + + + + + +
    +
    +
    +
    +
    + +
    +
    +
    + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/table/child.html b/ruoyi-admin/src/main/resources/templates/demo/table/child.html new file mode 100644 index 0000000..53578f9 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/table/child.html @@ -0,0 +1,116 @@ + + + + + + +
    +
    +
    + +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/table/curd.html b/ruoyi-admin/src/main/resources/templates/demo/table/curd.html new file mode 100644 index 0000000..ee76177 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/table/curd.html @@ -0,0 +1,178 @@ + + + + + + + +
    + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/table/customView.html b/ruoyi-admin/src/main/resources/templates/demo/table/customView.html new file mode 100644 index 0000000..a2f4d1c --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/table/customView.html @@ -0,0 +1,122 @@ + + + + + + +
    +
    +
    + +
    +
    +
    +
    + + + +
    + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/table/data.html b/ruoyi-admin/src/main/resources/templates/demo/table/data.html new file mode 100644 index 0000000..ce43cef --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/table/data.html @@ -0,0 +1,76 @@ + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/table/detail.html b/ruoyi-admin/src/main/resources/templates/demo/table/detail.html new file mode 100644 index 0000000..e5b8ad0 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/table/detail.html @@ -0,0 +1,86 @@ + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/table/dynamicColumns.html b/ruoyi-admin/src/main/resources/templates/demo/table/dynamicColumns.html new file mode 100644 index 0000000..ceb645f --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/table/dynamicColumns.html @@ -0,0 +1,123 @@ + + + + + + +
    +
    +
    +
    +
    +
      +
    • + 要增加的列: + +
    • +
    • +  搜索 +  重置 +
    • +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/table/editable.html b/ruoyi-admin/src/main/resources/templates/demo/table/editable.html new file mode 100644 index 0000000..33af09b --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/table/editable.html @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/table/event.html b/ruoyi-admin/src/main/resources/templates/demo/table/event.html new file mode 100644 index 0000000..b6d268b --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/table/event.html @@ -0,0 +1,133 @@ + + + + + + +
    +
    +
    +

    自定义触发事件(点击某行/双击某行/单击某格/双击某格/服务器发送数据前触发/数据被加载时触发)

    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/table/export.html b/ruoyi-admin/src/main/resources/templates/demo/table/export.html new file mode 100644 index 0000000..4135d4d --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/table/export.html @@ -0,0 +1,85 @@ + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/table/exportSelected.html b/ruoyi-admin/src/main/resources/templates/demo/table/exportSelected.html new file mode 100644 index 0000000..18e0014 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/table/exportSelected.html @@ -0,0 +1,120 @@ + + + + + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + 勾选数据导出指定列,否则为全部 + + 导出 + +
    +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/table/fixedColumns.html b/ruoyi-admin/src/main/resources/templates/demo/table/fixedColumns.html new file mode 100644 index 0000000..c79dc6c --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/table/fixedColumns.html @@ -0,0 +1,145 @@ + + + + + + + +
    + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/table/footer.html b/ruoyi-admin/src/main/resources/templates/demo/table/footer.html new file mode 100644 index 0000000..e4654b6 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/table/footer.html @@ -0,0 +1,95 @@ + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/table/groupHeader.html b/ruoyi-admin/src/main/resources/templates/demo/table/groupHeader.html new file mode 100644 index 0000000..77226ff --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/table/groupHeader.html @@ -0,0 +1,80 @@ + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/table/headerStyle.html b/ruoyi-admin/src/main/resources/templates/demo/table/headerStyle.html new file mode 100644 index 0000000..5d63bb4 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/table/headerStyle.html @@ -0,0 +1,91 @@ + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/table/image.html b/ruoyi-admin/src/main/resources/templates/demo/table/image.html new file mode 100644 index 0000000..e891ed9 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/table/image.html @@ -0,0 +1,79 @@ + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/table/multi.html b/ruoyi-admin/src/main/resources/templates/demo/table/multi.html new file mode 100644 index 0000000..e5fd480 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/table/multi.html @@ -0,0 +1,224 @@ + + + + + + +
    +
    +
    +
    +
    + +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/table/other.html b/ruoyi-admin/src/main/resources/templates/demo/table/other.html new file mode 100644 index 0000000..6321db6 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/table/other.html @@ -0,0 +1,106 @@ + + + + + + + +
    + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/table/pageGo.html b/ruoyi-admin/src/main/resources/templates/demo/table/pageGo.html new file mode 100644 index 0000000..26db1f6 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/table/pageGo.html @@ -0,0 +1,77 @@ + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/table/params.html b/ruoyi-admin/src/main/resources/templates/demo/table/params.html new file mode 100644 index 0000000..ff64ea7 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/table/params.html @@ -0,0 +1,158 @@ + + + + + + +
    +
    +
    +

    通过queryParams方法设置

    +
    +
    + +
    +
    +
    +
      +
    • + 用户姓名: +
    • +
    +
    +
    +
    +
    +

    通过form自动填充

    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/table/print.html b/ruoyi-admin/src/main/resources/templates/demo/table/print.html new file mode 100644 index 0000000..8695376 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/table/print.html @@ -0,0 +1,131 @@ + + + + + + +
    +
    +
    + +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/table/refresh.html b/ruoyi-admin/src/main/resources/templates/demo/table/refresh.html new file mode 100644 index 0000000..5917bff --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/table/refresh.html @@ -0,0 +1,79 @@ + + + + + + +
    +
    +
    + +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/table/remember.html b/ruoyi-admin/src/main/resources/templates/demo/table/remember.html new file mode 100644 index 0000000..2f55670 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/table/remember.html @@ -0,0 +1,86 @@ + + + + + + +
    + +
    +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/table/reorderColumns.html b/ruoyi-admin/src/main/resources/templates/demo/table/reorderColumns.html new file mode 100644 index 0000000..a461d60 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/table/reorderColumns.html @@ -0,0 +1,84 @@ + + + + + + +
    + +
    +
    +

    按住表格列拖拽

    +
    +
    +
    +
    +
    + + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/table/reorderRows.html b/ruoyi-admin/src/main/resources/templates/demo/table/reorderRows.html new file mode 100644 index 0000000..ded9ece --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/table/reorderRows.html @@ -0,0 +1,91 @@ + + + + + + +
    + +
    +
    +

    按住表格行拖拽

    +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/table/resizable.html b/ruoyi-admin/src/main/resources/templates/demo/table/resizable.html new file mode 100644 index 0000000..224c7df --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/table/resizable.html @@ -0,0 +1,78 @@ + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    + + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/demo/table/search.html b/ruoyi-admin/src/main/resources/templates/demo/table/search.html new file mode 100644 index 0000000..e6781b1 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/table/search.html @@ -0,0 +1,202 @@ + + + + + + + +
    +
    +
    +

    普通条件查询

    +
    +
    +
      +
    • + 商户编号: +
    • +
    • + 终端编号: +
    • +
    • + 处理状态: +
    • +
    • +  搜索 +  重置 +
    • +
    +
    +
    +
    + +
    +

    时间条件查询

    +
    +
    +
      +
    • + 商户编号: +
    • +
    • + 终端编号: +
    • +
    • + + + - + +
    • +
    • +  搜索 +  重置 +
    • +
    +
    +
    +
    + +
    +

    多级联动下拉查询

    +
    +
    +
      +
    • + 商户编号: +
    • +
    • + 充值类型: +
    • +
    • + 充值路由: +
    • +
    • +  搜索 +  重置 +
    • +
    +
    +
    +
    + +
    +

    下拉多选条件查询

    +
    +
    +
      +
    • + 商户编号: +
    • +
    • + 终端编号: +
    • +
    • + +
    • +
    • +  搜索 +  重置 +
    • +
    +
    +
    +
    + +
    +

    复杂条件查询

    +
    +
    +
      +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + + - + +
    • + +
    • +  搜索 +  重置 +
    • +
    +
    +
    +
    +
    +
    + + + + + + + diff --git a/ruoyi-admin/src/main/resources/templates/demo/table/subdata.html b/ruoyi-admin/src/main/resources/templates/demo/table/subdata.html new file mode 100644 index 0000000..72ee6e9 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/demo/table/subdata.html @@ -0,0 +1,242 @@ + + + + + + + + +
    +
    +

    客户信息

    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +

    商品数据

    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    + +
    +
    +   + +
    +
    + + + + + + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/error/404.html b/ruoyi-admin/src/main/resources/templates/error/404.html new file mode 100644 index 0000000..8c41e27 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/error/404.html @@ -0,0 +1,27 @@ + + + + + + RuoYi - 404 + + + + + +
    +

    404

    +

    找不到网页!

    +
    + 对不起,您正在寻找的页面不存在。尝试检查URL的错误,然后按浏览器上的刷新按钮或尝试在我们的应用程序中找到其他内容。 + 主页 +
    +
    + + + diff --git a/ruoyi-admin/src/main/resources/templates/error/500.html b/ruoyi-admin/src/main/resources/templates/error/500.html new file mode 100644 index 0000000..918e965 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/error/500.html @@ -0,0 +1,28 @@ + + + + + + RuoYi - 500 + + + + + +
    +

    500

    +

    内部服务器错误!

    + +
    + 服务器遇到意外事件,不允许完成请求。我们抱歉。您可以返回主页面。 + 主页 +
    +
    + + + diff --git a/ruoyi-admin/src/main/resources/templates/error/service.html b/ruoyi-admin/src/main/resources/templates/error/service.html new file mode 100644 index 0000000..b64341d --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/error/service.html @@ -0,0 +1,20 @@ + + + + + + RuoYi - 500 + + + + + +
    +

    操作异常!

    + +
    + [[${errorMessage}]] +
    +
    + + diff --git a/ruoyi-admin/src/main/resources/templates/error/unauth.html b/ruoyi-admin/src/main/resources/templates/error/unauth.html new file mode 100644 index 0000000..74053c7 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/error/unauth.html @@ -0,0 +1,28 @@ + + + + + + RuoYi - 403 + + + + + +
    +

    403

    +

    您没有访问权限!

    + +
    + 对不起,您没有访问权限,请不要进行非法操作!您可以返回主页面 + 返回主页 +
    +
    + + + diff --git a/ruoyi-admin/src/main/resources/templates/include.html b/ruoyi-admin/src/main/resources/templates/include.html new file mode 100644 index 0000000..78fe06a --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/include.html @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    + +
    + + +
    + + +
    +
    + +
    + + +
    + +
    +
    + +
    + + +
    + +
    +
    + +
    + + +
    + +
    +
    + +
    + + +
    + +
    +
    + + +
    + + +
    + +
    +
    + +
    + + +
    + +
    +
    + +
    + + +
    + +
    +
    + +
    + + +
    + +
    +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    +
    + +
    + + +
    + +
    +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + +
    +
    + + +
    + + +
    + + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    diff --git a/ruoyi-admin/src/main/resources/templates/index-topnav.html b/ruoyi-admin/src/main/resources/templates/index-topnav.html new file mode 100644 index 0000000..84bec3e --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/index-topnav.html @@ -0,0 +1,445 @@ + + + + + + + 若依系统首页 + + + + + + + + + + + + + +
    + + + + + + +
    + +
    + + + + 刷新 +
    + + + +
    + +
    + + +
    + +
    + + + + + + + + + + + + + + + + diff --git a/ruoyi-admin/src/main/resources/templates/index.html b/ruoyi-admin/src/main/resources/templates/index.html new file mode 100644 index 0000000..b330122 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/index.html @@ -0,0 +1,379 @@ + + + + + + + 若依系统首页 + + + + + + + + + + + + +
    + + + + + + +
    + +
    + + + + 刷新 +
    + + + +
    + +
    + + +
    + +
    + + + + + + + + + + + + + + + diff --git a/ruoyi-admin/src/main/resources/templates/lock.html b/ruoyi-admin/src/main/resources/templates/lock.html new file mode 100644 index 0000000..24bae1c --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/lock.html @@ -0,0 +1,208 @@ + + + + + + + 锁定屏幕 + + + + + + +
    +
    +
    [[ ${user.loginName} ]] / [[${#strings.defaultString(user.userName, '-')}]]
    + +
    +
    + User Image +
    +
    +
    + +
    + +
    +
    +
    +
    + +
    系统锁屏,请输入密码登录!
    + +
    + + + + + + + + diff --git a/ruoyi-admin/src/main/resources/templates/login.html b/ruoyi-admin/src/main/resources/templates/login.html new file mode 100644 index 0000000..06d2f80 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/login.html @@ -0,0 +1,82 @@ + + + + + + 登录若依系统 + + + + + + + + + + + + + + + +
    +
    +
    + +
    +
    +
    +

    登录:

    +

    你若不离不弃,我必生死相依

    + + +
    +
    + +
    +
    + + + +
    +
    +
    + +
    + +
    +
    +
    + +
    + + + + + + + + + + + diff --git a/ruoyi-admin/src/main/resources/templates/main.html b/ruoyi-admin/src/main/resources/templates/main.html new file mode 100644 index 0000000..e2d6edc --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/main.html @@ -0,0 +1,1626 @@ + + + + + + + 若依介绍 + + + + + + + +
    +
    +
    + 领取阿里云通用云产品1888优惠券 +
    https://www.aliyun.com/minisite/goods?userCode=brki8iof
    + 领取腾讯云通用云产品2860优惠券 +
    https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console
    + 阿里云服务器折扣区 ☛☛点我进入☚☚     腾讯云服务器秒杀区 ☛☛点我进入☚☚
    +

    云产品通用红包,可叠加官网常规优惠使用。(仅限新用户)

    +
    + +
    +
    +
    +

    Hello,Guest

    + 移动设备访问请扫描以下二维码: +
    +
    + +
    +
    +
    +

    若依后台管理框架

    +

    一直想做一款后台管理系统,看了很多优秀的开源项目但是发现没有合适自己的。于是利用空闲休息时间开始自己写一套后台系统。如此有了若依管理系统。,她可以用于所有的Web应用程序,如网站管理后台网站会员中心CMSCRMOA等等,当然,您也可以对她进行深度定制,以做出更强系统。所有前端后台代码封装过后十分精简易上手,出错概率低。同时支持移动客户端访问。系统会陆续更新一些实用功能。

    +

    + 当前版本:v[[${version}]] +

    +

    + ¥免费开源 +

    +
    +

    + + 访问码云 + + + 访问主页 + +

    +
    +
    +

    技术选型:

    +
      +
    1. 核心框架:Spring Boot。
    2. +
    3. 安全框架:Apache Shiro。
    4. +
    5. 模板引擎:Thymeleaf。
    6. +
    7. 持久层框架:MyBatis。
    8. +
    9. 定时任务:Quartz。
    10. +
    11. 数据库连接池:Druid。
    12. +
    13. 工具类:Fastjson。
    14. +
    15. 更多……
    16. +
    +
    + +
    +
    +
    +
    + +
    +
    +
    联系信息
    + +
    +
    +

    官网:http://www.ruoyi.vip +

    +

    QQ群:满1389287 满1679294 满1529866 满1772718 满1366522 满1382251 满1145125 满86752435 满134072510 满210336300 满339522636 满130035985 满143151071 满158781320 满201531282 满101526938 满264355400 满298522656 满139845794 满185760789 175104288 +

    +

    微信:/ *若依 +

    +

    支付宝:/ *若依 +

    +
    +
    +
    +
    +
    +
    +
    更新日志
    +
    +
    +
    +
    +
    +
    +
    + v4.7.62022.12.16 +
    +
    +
    +
    +
      +
    1. 定时任务违规的字符
    2. +
    3. 忽略不必要的属性数据返回
    4. +
    5. 导入更新用户数据前校验数据权限
    6. +
    7. 修改参数键名时移除前缓存配置
    8. +
    9. 修改用户登录账号进行重复验证
    10. +
    11. 兼容Excel下拉框内容过多无法显示
    12. +
    13. 升级oshi到最新版本6.4.0
    14. +
    15. 升级kaptcha到最新版2.3.3
    16. +
    17. 升级druid到最新版本1.2.15
    18. +
    19. 升级shiro到最新版本1.10.1
    20. +
    21. 升级pagehelper到最新版1.4.6
    22. +
    23. 升级bootstrap-fileinput到最新版本5.5.2
    24. +
    25. 修复sheet超出最大行数异常问题
    26. +
    27. 修复关闭父页签后提交无法跳转的问题
    28. +
    29. 修复操作日志类型多选导出不生效问题
    30. +
    31. 修复导出包含空子列表数据异常的问题
    32. +
    33. 优化树形表格层级显示
    34. +
    35. 优化SQL关键字检查防止注入
    36. +
    37. 优化用户管理重置时取消部门选择
    38. +
    39. 优化代码生成同步后字典值NULL问题
    40. +
    41. 优化导出对象的子列表为空会出现[]问题
    42. +
    43. 优化select2搜索下拉后校验必填样式问题
    44. +
    45. 其他细节优化
    46. +
    +
    +
    +
    +
    +
    +
    + v4.7.52022.09.05 +
    +
    +
    +
    +
      +
    1. Excel支持导出对象的子列表方法
    2. +
    3. 数据逻辑删除不进行唯一验证
    4. +
    5. 优化多角色数据权限匹配规则
    6. +
    7. 新增主子表提交校验示例
    8. +
    9. 支持自定义隐藏Excel属性列
    10. +
    11. Excel注解支持backgroundColor属性设置背景颜色
    12. +
    13. 菜单配置刷新时Tab页签切换时刷新
    14. +
    15. 增加对AjaxResult消息结果类型的判断
    16. +
    17. 新增示例(进度条)
    18. +
    19. 新增内容编码/解码方便插件集成使用
    20. +
    21. 升级jquery到最新版3.6.1
    22. +
    23. 升级layui到最新版本2.7.5
    24. +
    25. 升级shiro到最新版本1.9.1
    26. +
    27. 升级druid到最新版本1.2.11
    28. +
    29. 升级pagehelper到最新版1.4.3
    30. +
    31. 升级oshi到最新版本6.2.2
    32. +
    33. 修复树表onLoadSuccess不生效的问题
    34. +
    35. 修复用户分配角色大于默认页数丢失问题
    36. +
    37. 定时任务支持执行父类方法
    38. +
    39. 自动设置切换多个树表格实例配置
    40. +
    41. 页签创建标题优先data-title属性
    42. +
    43. 优化任务过期不执行调度
    44. +
    45. 优化横向菜单下激活菜单样式
    46. +
    47. 优化按钮打开窗口后按回车反复弹出
    48. +
    49. 优化excel/scale属性导出单元格数值类型
    50. +
    51. 优化druid开启wall过滤器出现的异常问题
    52. +
    53. 优化多个相同角色数据导致权限SQL重复问题
    54. +
    55. 其他细节优化
    56. +
    +
    +
    +
    +
    +
    +
    + v4.7.42022.06.01 +
    +
    +
    +
    +
      +
    1. 用户头像上传图片格式限制
    2. +
    3. Excel注解支持color属性设置字体颜色
    4. +
    5. 设置分页参数默认值
    6. +
    7. 主子表操作列新增单个删除
    8. +
    9. 定时任务检查Bean包名是否为白名单配置
    10. +
    11. 升级spring-boot到最新版本2.5.14
    12. +
    13. 升级shiro到最新版本1.9.0
    14. +
    15. 升级oshi到最新版本6.1.6
    16. +
    17. 升级fastjson到最新版1.2.83 安全修复版本
    18. +
    19. 文件上传兼容Weblogic环境
    20. +
    21. 新增清理分页的线程变量方法
    22. +
    23. 新增获取不带后缀文件名称方法
    24. +
    25. 用户缓存信息添加部门ancestors祖级列表
    26. +
    27. 自定义ShiroFilterFactoryBean防止中文请求被拦截
    28. +
    29. 字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)
    30. +
    31. 优化IP地址获取到多个的问题
    32. +
    33. 优化表格冻结列阴影效果显示
    34. +
    35. 优化菜单侧边栏滚动条尺寸及颜色
    36. +
    37. 优化显示顺序orderNum类型为整型
    38. +
    39. 优化接口使用泛型使其看到响应属性字段
    40. +
    41. 优化导出数据LocalDateTime类型无数据问题
    42. +
    43. 修复导入Excel时字典字段类型为Long转义为空问题
    44. +
    45. 优化导出excel单元格验证,包含变更为开头.防止正常内容被替换
    46. +
    47. 修复URL类型回退键被禁止问题
    48. +
    49. 修复表格客户端分页序号显示错误问题
    50. +
    51. 修复代码生成拖拽多次出现的排序不正确问题
    52. +
    53. 修复表格打印组件不识别多层对象属性值问题
    54. +
    55. 修复操作日志查询类型条件为0时会查到所有数据
    56. +
    57. 修复Excel注解prompt/combo同时使用不生效问题
    58. +
    59. 修复初始化多表格处理回调函数时获取的表格配置不一致问题
    60. +
    61. 其他细节优化
    62. +
    +
    +
    +
    +
    +
    +
    + v4.7.32022.03.01 +
    +
    +
    +
    +
      +
    1. 表格树支持分页/异步加载
    2. +
    3. 代码生成预览支持复制内容
    4. +
    5. 定时任务默认保存到内存中执行
    6. +
    7. 代码生成同步保留必填/类型选项
    8. +
    9. 页面若未匹配到字典标签则返回原字典值
    10. +
    11. 用户访问控制时校验数据权限,防止越权
    12. +
    13. 导出Excel时屏蔽公式,防止CSV注入风险
    14. +
    15. 升级spring-boot到最新版本2.5.10
    16. +
    17. 升级spring-boot-mybatis到最新版2.2.2
    18. +
    19. 升级pagehelper到最新版1.4.1
    20. +
    21. 升级oshi到最新版本6.1.2
    22. +
    23. 升级bootstrap-table到最新版本1.19.1
    24. +
    25. 服务监控新增运行参数信息显示
    26. +
    27. 定时任务目标字符串验证包名白名单
    28. +
    29. 文件上传接口新增原/新文件名返回参数
    30. +
    31. 定时任务屏蔽违规的字符
    32. +
    33. 分页数据新增分页参数合理化参数
    34. +
    35. 表格父子视图添加点击事件打开示例
    36. +
    37. 优化上传文件名称命名规则
    38. +
    39. 优化加载字典缓存数据
    40. +
    41. 优化任务队列满时任务拒绝策略
    42. +
    43. 优化IE11上传预览不显示的问题
    44. +
    45. 优化Excel格式化不同类型的日期对象
    46. +
    47. 优化国际化配置多余的zh请求问题
    48. +
    49. 优化新版Chrome浏览器回退出现的遮罩层
    50. +
    51. 修复EMAIL类型回退键被禁止问题
    52. +
    53. 修复Xss注解字段值为空时的异常问题
    54. +
    55. 其他细节优化
    56. +
    +
    +
    +
    +
    +
    +
    + v4.7.22021.12.23 +
    +
    +
    +
    +
      +
    1. 自定义xss校验注解实现
    2. +
    3. 进入修改页面方法添加权限标识
    4. +
    5. 代码生成创建按钮添加超级管理员权限
    6. +
    7. 代码生成创建表检查关键字,防止注入风险
    8. +
    9. 修复定时任务多参数逗号分隔的问题
    10. +
    11. 修复表格插件一起使用出现的声明报错问题
    12. +
    13. 修复代码生成主子表模板删除方法缺少事务
    14. +
    15. 升级oshi到最新版本v5.8.6
    16. +
    17. 升级velocity到最新版本2.3
    18. +
    19. 升级fastjson到最新版1.2.79
    20. +
    21. 升级log4j2到最新版2.17.0 防止漏洞风险
    22. +
    23. 升级thymeleaf到最新版3.0.14 阻止远程代码执行漏洞
    24. +
    25. 优化修改/授权角色实时生效
    26. +
    27. 修整tomcat配置参数已过期问题
    28. +
    29. 前端添加单独的二代身份证校验
    30. +
    31. 优化新增部门时验证用户所属部门
    32. +
    33. 优化查询用户的角色组&岗位组代码
    34. +
    35. 请求分页方法设置成通用方便灵活调用
    36. +
    37. 优化日期类型错误提示与图标重叠问题
    38. +
    39. 其他细节优化
    40. +
    +
    +
    +
    +
    +
    +
    + v4.7.12021.11.10 +
    +
    +
    +
    +
      +
    1. 新增是否开启页签功能
    2. +
    3. 代码生成的模块增加创建表功能
    4. +
    5. Excel导入支持@Excels注解
    6. +
    7. Excel注解支持导入导出标题信息
    8. +
    9. Excel注解支持自定义数据处理器
    10. +
    11. 日志注解新增是否保存响应参数
    12. +
    13. 防重提交注解支持配置间隔时间/提示消息
    14. +
    15. 网页部分操作禁止使用后退键(Backspace)
    16. +
    17. 实例演示中增加多层窗口获取值
    18. +
    19. 弹出层openOptions增加动画属性
    20. +
    21. 升级spring-boot到最新版本2.5.6
    22. +
    23. 升级spring-boot-mybatis到最新版2.2.0
    24. +
    25. 升级pagehelper到最新版1.4.0
    26. +
    27. 升级oshi到最新版本v5.8.2
    28. +
    29. 升级druid到最新版1.2.8
    30. +
    31. 升级fastjson到最新版1.2.78
    32. +
    33. 升级thymeleaf-extras-shiro到最新版本v2.1.0
    34. +
    35. 升级bootstrap-fileinput到最新版本v5.2.4
    36. +
    37. 修改阿里云maven仓库地址为新版地址
    38. +
    39. 定时任务屏蔽违规字符
    40. +
    41. 增加sendGet无参请求方法
    42. +
    43. 代码生成去掉多余的排序字段
    44. +
    45. 优化启动脚本参数优化
    46. +
    47. 优化页签关闭右侧清除iframe元素
    48. +
    49. 优化多表格切换表单查询参数
    50. +
    51. 优化表格实例切换event不能为空
    52. +
    53. 优化mybatis全局默认的执行器
    54. +
    55. 优化导入Excel数据关闭时清理file
    56. +
    57. 优化Excel导入图片可能出现的异常
    58. +
    59. 优化记录登录信息,防止不必要的修改
    60. +
    61. 优化aop语法,使用spring自动注入注解
    62. +
    63. 修复无法被反转义问题
    64. +
    65. 修复拖拽行数据错位问题
    66. +
    67. 修复新窗口打开页面关闭弹窗报错
    68. +
    69. 修复富文本回退键被禁止&控制台报错问题
    70. +
    71. 修复自定义弹出层全屏参数无效问题
    72. +
    73. 修复树表代码生成短字段无法识别问题
    74. +
    75. 修复apple/webkit浏览器时间无法格式化
    76. +
    77. 修复后端主子表代码模板方法名生成错误问题
    78. +
    79. 修复swagger没有指定dataTypeClass导致启动出现warn日志
    80. +
    81. 其他细节优化
    82. +
    +
    +
    +
    +
    +
    +
    + v4.7.02021.09.01 +
    +
    +
    +
    +
      +
    1. 优化弹出层显示在顶层窗口
    2. +
    3. 定时任务支持在线生成cron表达式
    4. +
    5. Excel注解支持Image图片导入
    6. +
    7. 支持配置是否开启记住我功能
    8. +
    9. 修改时检查用户数据权限范围
    10. +
    11. 表单重置开始/结束时间控件
    12. +
    13. 新增多图上传示例
    14. +
    15. 启用父部门状态排除顶级节点
    16. +
    17. 富文本默认dialogsInBody属性
    18. +
    19. 去除默认分页合理化参数
    20. +
    21. 顶部菜单跳转添加绝对路径
    22. +
    23. 升级oshi到最新版本v5.8.0
    24. +
    25. 升级shiro到最新版本v1.8.0
    26. +
    27. 升级commons.io到最新版本v2.11.0
    28. +
    29. 升级jquery到最新版v3.6.0
    30. +
    31. 升级icheck到最新版v1.0.3
    32. +
    33. 升级layer到最新版本v3.5.1
    34. +
    35. 升级layui到最新版本v2.6.8
    36. +
    37. 升级laydate到最新版本v5.3.1
    38. +
    39. 升级select2到最新版v4.0.13
    40. +
    41. 升级cropper到最新版本v1.5.12
    42. +
    43. 升级summernote到最新版本v0.8.18
    44. +
    45. 升级duallistbox到最新版本v3.0.9
    46. +
    47. 升级jquery.validate到最新版本v1.19.3
    48. +
    49. 升级bootstrap-suggest到最新版本v0.1.29
    50. +
    51. 升级bootstrap-select到最新版本v1.13.18
    52. +
    53. 升级bootstrap-fileinput到最新版本v5.2.3
    54. +
    55. 查询表格指定列值增加是否去重属性
    56. +
    57. 删除sourceMappingURL源映射
    58. +
    59. 去除多余的favicon.ico引入
    60. +
    61. 优化代码生成模板
    62. +
    63. 优化XSS跨站脚本过滤
    64. +
    65. 补充定时任务表字段注释
    66. +
    67. 定时任务屏蔽ldap远程调用
    68. +
    69. 定时任务屏蔽http(s)远程调用
    70. +
    71. 定时任务对检查异常进行事务回滚
    72. +
    73. 调度日志详细页添加关闭按钮
    74. +
    75. 优化异常打印输出信息
    76. +
    77. 优化移动端进入首页样式
    78. +
    79. 优化用户操作不能删除自己
    80. +
    81. 默认开始/结束时间绑定控件选择类型
    82. +
    83. 其他细节优化
    84. +
    +
    +
    +
    +
    +
    +
    + v4.6.22021.07.01 +
    +
    +
    +
    +
      +
    1. 优化参数&字典缓存操作
    2. +
    3. 新增表格参数(导出方式&导出文件类型)
    4. +
    5. 新增表格示例(自定义视图分页)
    6. +
    7. 新增示例(表格列拖拽)
    8. +
    9. 集成yuicompressor实现(CSS/JS压缩)
    10. +
    11. 新增表格参数(是否支持打印页面showPrint)
    12. +
    13. 支持bat脚本执行应用
    14. +
    15. 修复存在的SQL注入漏洞问题
    16. +
    17. 定时任务屏蔽rmi远程调用
    18. +
    19. 导出Excel文件支持数据流下载方式
    20. +
    21. 实例演示弹层组件增加相册层示例
    22. +
    23. 删除操作日志记录信息
    24. +
    25. 增加表格重置分页的参数
    26. +
    27. 限制超级管理员不允许操作
    28. +
    29. 树级结构更新子节点使用replaceFirst
    30. +
    31. 支持动态生成密匙,防止默认密钥泄露
    32. +
    33. 升级pagehelper到最新版1.3.1
    34. +
    35. 升级oshi到最新版本v5.7.4
    36. +
    37. 升级swagger到最新版本v3.0.0
    38. +
    39. 升级commons.io到最新版本v2.10.0
    40. +
    41. 升级commons.fileupload到最新版本v1.4
    42. +
    43. 升级bootstrap-table到最新版本v1.18.3
    44. +
    45. 升级druid到最新版本v1.2.6
    46. +
    47. 升级fastjson到最新版1.2.76
    48. +
    49. 升级layui到最新版本v2.6.6
    50. +
    51. 升级layer到最新版本v3.5.0
    52. +
    53. 升级laydate到最新版本v5.3.0
    54. +
    55. 优化表格树移动端&边框显示
    56. +
    57. 新增表格刷新options配置方法
    58. +
    59. 优化图片工具类读取文件,防止异常
    60. +
    61. 修复表格图片预览移动端宽高无效问题
    62. +
    63. 主子表通用操作封装处理增加文本域类型
    64. +
    65. 日志注解兼容获取json类型的参数
    66. +
    67. 修复表单向导插件有滚动条时底部工具栏无法固定问题
    68. +
    69. 修复导出角色数据范围翻译缺少仅本人
    70. +
    71. 修正Velocity模板初始字符集
    72. +
    73. 升级mybatis到最新版3.5.6 阻止远程代码执行漏洞
    74. +
    75. 优化代码生成导出模板名称
    76. +
    77. 修改个人中心密码长度提醒
    78. +
    79. 实例演示中弹出表格增加以回调形式回显到父窗体
    80. +
    81. 修复登录页面弹窗文字不显示的问题
    82. +
    83. 其他细节优化
    84. +
    +
    +
    +
    +
    +
    +
    + v4.6.12021.04.12 +
    +
    +
    +
    +
      +
    1. 新增IE浏览器版本过低提示页面
    2. +
    3. 新增详细信息tab页签方式
    4. +
    5. 新增解锁屏幕打开上次页签
    6. +
    7. 数据监控默认账户密码防止越权访问
    8. +
    9. 新增表格示例(导出选择列)
    10. +
    11. 个人信息添加手机&邮箱重复验证
    12. +
    13. 个人中心刷新后样式问题
    14. +
    15. 操作日志返回参数添加非空验证
    16. +
    17. velocity剔除commons-collections版本,防止3.2.1版本的反序列化漏洞
    18. +
    19. 子表模板默认日期格式化
    20. +
    21. 代码生成预览语言根据后缀名高亮显示
    22. +
    23. 代码生成主子表相同字段导致数据问题
    24. +
    25. 升级SpringBoot到最新版本2.2.13
    26. +
    27. 升级shiro到最新版1.7.1 阻止身份认证绕过漏洞
    28. +
    29. 升级bootstrapTable到最新版本v1.18.2
    30. +
    31. 升级bootstrapTable相关组件到最新版本v1.18.2
    32. +
    33. 升级fastjson到最新版1.2.75
    34. +
    35. 升级druid到最新版本v1.2.4
    36. +
    37. 升级oshi到最新版本v5.6.0
    38. +
    39. 修改ip字段长度防止ipv6地址长度不够
    40. +
    41. 搜索建议示例选择后隐藏列表
    42. +
    43. 主子表示例增加初始化数据
    44. +
    45. 优化Excel导入增加空行判断
    46. +
    47. 修复横向菜单无法打开页签问题
    48. +
    49. 修复导入数据为负浮点数时,导入结果会丢失精度问题
    50. +
    51. 优化更多操作按钮左侧移入内容闪现消失情况
    52. +
    53. 修复主子表提交中列隐藏后出现列偏移问题
    54. +
    55. 单据打印网页时通过hidden-print隐藏元素
    56. +
    57. 表格销毁清除记住选择数据
    58. +
    59. 增加表格动态列示例
    60. +
    61. 代码生成选择主子表关联元素必填
    62. +
    63. tree根据Id和Name选中指定节点增加空判断
    64. +
    65. 其他细节优化
    66. +
    +
    +
    +
    +
    +
    +
    + v4.6.02021.01.01 +
    +
    +
    +
    +
      +
    1. 新增缓存监控管理
    2. +
    3. 新增锁定屏幕功能
    4. +
    5. 菜单新增是否刷新页面
    6. +
    7. 删除用户和角色解绑关联
    8. +
    9. 新增密码强度字符范围提示
    10. +
    11. 防止匿名访问进行过滤
    12. +
    13. 升级SpringBoot到最新版本2.2.12
    14. +
    15. 升级poi到最新版本4.1.2
    16. +
    17. 升级bitwalker到最新版本1.21
    18. +
    19. 升级bootstrap-fileinput到最新版本5.1.3
    20. +
    21. 升级bootstrapTable到最新版本v1.18.0
    22. +
    23. 升级bootstrapTable相关组件到最新版本v1.18.0
    24. +
    25. 升级oshi到最新版本v5.3.6
    26. +
    27. 新增示例(标签 & 提示)
    28. +
    29. 添加单据打印示例
    30. +
    31. 修改表格初始参数sortName默认值为undefined
    32. +
    33. 新增表格参数(自定义打印页面模板printPageBuilder)
    34. +
    35. 新增表格参数(是否显示行间隔色striped)
    36. +
    37. 新增表格参数(渲染完成后执行的事件onPostBody)
    38. +
    39. Excel注解支持Image图片导出
    40. +
    41. Excel支持注解align对齐方式
    42. +
    43. Excel支持导入Boolean型数据
    44. +
    45. 主子表操作添加通用addColumn方法
    46. +
    47. 代码生成日期控件区分范围
    48. +
    49. 代码生成数据库文本类型生成表单文本域
    50. +
    51. 修复生成主子表外键名错误
    52. +
    53. 选项卡新增是否刷新属性
    54. +
    55. 修复树表格表头跟表格宽度不同步的问题
    56. +
    57. 表格树加载完成触发tooltip方法
    58. +
    59. 使用widthUnit定义树表格选项单位
    60. +
    61. 修复主子表editColumn序列问题
    62. +
    63. 修复添加全屏在无参数时没有替换url参数问题
    64. +
    65. 弹出层openOptions移动端自适应
    66. +
    67. 防止错误页返回主页出现嵌套问题
    68. +
    69. 设置回显数据字典验证防止空值
    70. +
    71. 其他细节优化
    72. +
    +
    +
    +
    +
    +
    +
    + v4.5.12020.11.18 +
    +
    +
    +
    +
      +
    1. 阻止任意文件下载漏洞
    2. +
    3. 升级shiro到最新版1.7.0 阻止权限绕过漏洞
    4. +
    5. 升级druid到最新版本v1.2.2
    6. +
    7. 新增表格行触发事件(onCheck、onUncheck、onCheckAll、onUncheckAll)
    8. +
    9. 修复多页签关闭非当前选项出现空白问题
    10. +
    11. 代码生成预览支持高亮显示
    12. +
    13. mapperLocations配置支持分隔符
    14. +
    15. 权限信息调整
    16. +
    17. 个人中心头像和上传头像增加默认图片
    18. +
    19. 全局配置类保持和其他应用命名相同
    20. +
    +
    +
    +
    +
    +
    +
    + v4.5.02020.10.20 +
    +
    +
    +
    +
      +
    1. 新增菜单导航显示风格(default为左侧导航菜单,topnav为顶部导航菜单)
    2. +
    3. 菜单&数据权限新增(展开/折叠 全选/全不选 父子联动)
    4. +
    5. 账号密码支持自定义更新周期
    6. +
    7. 初始密码支持自定义修改策略
    8. +
    9. 新增校验用户修改新密码不能与旧密码相同
    10. +
    11. 添加检查密码范围支持的特殊字符包括:~!@#$%^&*()-=_+
    12. +
    13. 注册账号设置默认用户名称及密码最后更新时间
    14. +
    15. 去除用户手机邮箱部门必填验证
    16. +
    17. 新增日期格式化方法
    18. +
    19. 代码生成添加bit类型
    20. +
    21. 树结构加载添加callBack回调方法
    22. +
    23. 修复用户管理页面滚动返回顶部条失效
    24. +
    25. 修复代码生成模板文件上传组件缺少ctx的问题
    26. +
    27. 限制系统内置参数不允许删除
    28. +
    29. 新增表格列宽拖动插件
    30. +
    31. 新增Ajax局部刷新demo
    32. +
    33. 新增是否开启页脚功能
    34. +
    35. 新增表格参数(通过自定义函数设置标题样式headerStyle)
    36. +
    37. 新增表格参数(通过自定义函数设置页脚样式footerStyle)
    38. +
    39. 修复窗体大小改变后浮动提示框失效问题
    40. +
    41. 生成代码补充必填样式
    42. +
    43. 生成页面时不忽略remark属性
    44. +
    45. 字典数据列表页添加关闭按钮
    46. +
    47. Excel注解支持自动统计数据总和
    48. +
    49. 升级springboot到2.1.17 提升安全性
    50. +
    51. 升级pagehelper到最新版1.3.0
    52. +
    53. 升级druid到最新版本v1.2.1
    54. +
    55. 升级fastjson到最新版1.2.74
    56. +
    57. 升级bootstrap-fileinput到最新版本5.1.2
    58. +
    59. 升级oshi到最新版本v5.2.5
    60. +
    61. 表单向导插件更换为jquery-smartwizard
    62. +
    63. 修改主子表提交示例代码防止渲染失效
    64. +
    65. 添加导入数据弹出窗体自定义宽高
    66. +
    67. 用户信息参数返回忽略掉密码字段
    68. +
    69. 优化关闭窗体添加index参数
    70. +
    71. 回显数据字典(字符串数组)增加空值判断
    72. +
    73. 修改前端密码长度校验和错误提示不符问题
    74. +
    75. AjaxResult重写put方法,以方便链式调用
    76. +
    77. 增强验证码校验的语义,更易懂
    78. +
    79. 导入excel整形值校验优化
    80. +
    81. Excel导出类型NUMERIC支持精度浮点类型
    82. +
    83. 导出Excel调整targetAttr获取值方法,防止get方法不规范
    84. +
    85. 输入框组验证错误后置图标提示颜色
    86. +
    87. 上传媒体类型添加视频格式
    88. +
    89. 数据权限判断参数类型
    90. +
    91. 修正数据库字符串类型nvarchar
    92. +
    93. 优化递归子节点
    94. +
    95. 修复多表格搜索formId无效
    96. +
    97. 其他细节优化
    98. +
    +
    +
    +
    +
    +
    +
    + v4.4.02020.08.24 +
    +
    +
    +
    +
      +
    1. 升级bootstrapTable到最新版本1.17.1
    2. +
    3. 升级shiro到最新版1.6.0 阻止权限绕过漏洞
    4. +
    5. 升级fastjson到最新版1.2.73
    6. +
    7. 代码生成支持同步数据库
    8. +
    9. 代码生成支持富文本控件
    10. +
    11. 用户密码支持自定义配置规则
    12. +
    13. 新增表格自动刷新插件
    14. +
    15. 新增表格打印配置插件
    16. +
    17. 更换图片裁剪工具为cropper
    18. +
    19. Excel支持sort导出排序
    20. +
    21. 代码生成支持自定义路径
    22. +
    23. 代码生成支持选择上级菜单
    24. +
    25. 代码生成支持上传控件
    26. +
    27. 新增表格参数(自定义加载文本的字体大小loadingFontSize)
    28. +
    29. Excel注解支持设置BigDecimal精度&舍入规则
    30. +
    31. 操作日志记录排除敏感属性字段
    32. +
    33. 修复不同浏览器附件下载中文名乱码的问题
    34. +
    35. 用户分配角色不允许选择超级管理员角色
    36. +
    37. 更换表格冻结列插件
    38. +
    39. 添加右侧冻结列示例
    40. +
    41. 升级表格行编辑&移动端适应插件
    42. +
    43. 修复更新表格插件后无法设置实例配置问题
    44. +
    45. 修复更新表格插件后导致的主子表错误
    46. +
    47. 修复页面存在多表格,回调函数res数据不正确问题
    48. +
    49. 强退&过期清理登录帐号缓存会话
    50. +
    51. 表格树标题内容支持html语义化标签
    52. +
    53. 修复配置应用的访问路径首页页签重复问题
    54. +
    55. 优化openTab打开时滚动到当前页签
    56. +
    57. 表格请求方式method支持自定义配置
    58. +
    59. 菜单页签联动优化
    60. +
    61. 用户邮箱长度限制修改为50
    62. +
    63. 主子表示例添加日期格式案例
    64. +
    65. 修改表格行内编辑示例旧值参数
    66. +
    67. 操作日志查询方式调整
    68. +
    69. 唯一限制条件只返回单条数据
    70. +
    71. 修改Excel设置STRING单元格类型
    72. +
    73. 添加获取当前的环境配置方法
    74. +
    75. 截取返回参数长度,防止超出异常
    76. +
    77. 定时任务cron表达式验证
    78. +
    79. 拆分表格插件,按需引入
    80. +
    81. 多行文本框补齐必填错误提示背景
    82. +
    83. 其他细节优化
    84. +
    +
    +
    +
    +
    +
    +
    + v4.3.12020.07.05 +
    +
    +
    +
    +
      +
    1. 国家信息安全漏洞(请务必保持cipherKey密钥唯一性)
    2. +
    3. 升级shiro到最新版1.5.3 阻止权限绕过漏洞
    4. +
    5. 修改验证码在使用后清除,防止多次使用
    6. +
    7. 检查字符支持小数点&降级改成异常提醒
    8. +
    9. openOptions函数中加入自定义maxmin属性
    10. +
    11. 支持openOptions方法最大化
    12. +
    13. 支持openOptions方法多个按钮回调
    14. +
    15. 新增isLinkage支持页签与菜单联动
    16. +
    17. 修改代码生成导入表结构出现异常页面不提醒问题
    18. +
    19. 优化用户头像发生错误,则显示一个默认头像
    20. +
    21. Excel导出支持字典类型
    22. +
    +
    +
    +
    +
    +
    +
    + v4.3.02020.06.22 +
    +
    +
    +
    +
      +
    1. 代码生成模板支持主子表
    2. +
    3. 代码生成显示类型支持复选框
    4. +
    5. 前端表单样式修改成圆角
    6. +
    7. 新增回显数据字典(字符串数组)
    8. +
    9. 修复浏览器手动缩放比例后菜单无法自适应问题
    10. +
    11. 限制用户不允许选择系统管理员角色
    12. +
    13. 用户信息添加输入框组图标&鼠标按下显示密码
    14. +
    15. 升级fastjson到最新版1.2.70 修复高危安全漏洞
    16. +
    17. 升级Bootstrap版本到v3.3.7
    18. +
    19. 修复selectColumns方法获取子对象数据无效问题
    20. +
    21. 修改数据源类型优先级,先根据方法,再根据类
    22. +
    23. 修改上级部门(选择项排除本身和下级)
    24. +
    25. 首页菜单显示调整
    26. +
    27. 添加是否开启swagger配置
    28. +
    29. 新增示例(主子表提交)
    30. +
    31. 新增示例(多级联动下拉示例)
    32. +
    33. 新增示例(表格属性data数据加载)
    34. +
    35. 新增表格列参数(是否列选项可见ignore)
    36. +
    37. 新增表格参数(是否启用显示卡片视图cardView)
    38. +
    39. 新增表格参数(是否显示全屏按钮showFullscreen)
    40. +
    41. 新增表格参数(是否启用分页条无限循环的功能paginationLoop)
    42. +
    43. 新增表格参数(是否显示表头showHeader)
    44. +
    45. 表格添加显示/隐藏所有列方法 showAllColumns/hideAllColumns
    46. +
    47. 修复部分情况节点不展开问题
    48. +
    49. 修复关闭标签页后刷新还是上次地址问题
    50. +
    51. 修复选择菜单后刷新页面,菜单箭头显示不对问题
    52. +
    53. 修复jquery表单序列化时复选框未选中不会序列化到对象中问题
    54. +
    55. Excel支持readConverterExp读取字符串组内容
    56. +
    57. 更换IP地址查询接口
    58. +
    59. 默认关闭获取ip地址
    60. +
    61. 操作处理ajaxSuccess判断修正
    62. +
    63. HttpUtils.sendPost()方法,参数无需拼接参数到url
    64. +
    65. 通用http发送方法增加参数 contentType 编码类型
    66. +
    67. HTML过滤器不替换&实体
    68. +
    69. 代码生成浮点型改用BigDecimal
    70. +
    71. 修复表单构建单选和多选框渲染问题
    72. +
    73. 代码生成模板调整,字段为String并且必填则加空串条件
    74. +
    75. 字典数据查询列表根据dictSort升序排序
    76. +
    77. 修复树表对imageView和tooltip方法无效问题
    78. +
    79. 修复Long类型比较相等问题调整
    80. +
    81. 示例demo页面清除html链接,防止点击后跳转出现404
    82. +
    83. 在线用户强退方法合并
    84. +
    85. 添加校验部门包含未停用的子部门
    86. +
    87. 取消回车自动提交表单
    88. +
    89. 'A','I','BUTTON' 标签忽略clickToSelect事件,防止点击操作按钮时选中
    90. +
    91. 邮箱显示截取部分字符串,防止低分辨率错位
    92. +
    93. 代码生成列属性根据sort排序
    94. +
    95. 修复更多操作部分浏览器不兼容情况
    96. +
    97. 图片预览事件属性修正
    98. +
    99. 修复冻结列排序样式无效问题
    100. +
    101. 修复context-path的情况下个人中心刷新导致样式问题
    102. +
    103. 全屏editFull打开适配表树
    104. +
    105. 其他细节优化
    106. +
    +
    +
    +
    +
    +
    +
    + v4.2.02020.03.23 +
    +
    +
    +
    +
      +
    1. 用户管理添加分配角色页面
    2. +
    3. 定时任务添加调度日志按钮
    4. +
    5. 新增是否开启用户注册功能
    6. +
    7. 新增页面滚动显示返回顶部按钮
    8. +
    9. 用户&角色&任务添加更多操作按钮
    10. +
    11. iframe框架页会话过期弹出超时提示
    12. +
    13. 移动端登录不显示左侧菜单
    14. +
    15. 侧边栏添加一套深蓝色主题
    16. +
    17. 首页logo固定,不随菜单滚动
    18. +
    19. 支持mode配置history(表示去掉地址栏的#)
    20. +
    21. 任务分组字典翻译(调度日志详细)
    22. +
    23. 字典管理添加缓存读取
    24. +
    25. 字典数据列表标签显示样式
    26. +
    27. 参数管理支持缓存操作
    28. +
    29. 日期控件清空结束时间设置开始默认值为2099-12-31
    30. +
    31. 表格树添加获取数据后响应回调处理
    32. +
    33. 批量替换表前缀调整
    34. +
    35. 支持表格导入模板的弹窗表单加入其它输入控件
    36. +
    37. 表单重置刷新表格树
    38. +
    39. 新增支持导出数据字段排序
    40. +
    41. 新增表格参数(是否单选checkbox)
    42. +
    43. druid未授权不允许访问
    44. +
    45. 表格树父节点兼容0,'0','',null
    46. +
    47. 表单必填的项添加星号
    48. +
    49. 修复select2不显示校验错误信息
    50. +
    51. 添加自定义HTML过滤器
    52. +
    53. 修复多数据源下开关关闭出现异常问题
    54. +
    55. 修复翻页记住选择项数据问题
    56. +
    57. 用户邮箱长度限制20
    58. +
    59. 修改错误页面返回主页出现嵌套问题
    60. +
    61. 表格浮动提示单双引号转义
    62. +
    63. 支持配置四级菜单
    64. +
    65. 升级shiro到最新版1.4.2 阻止rememberMe漏洞攻击
    66. +
    67. 升级summernote到最新版本v0.8.12
    68. +
    69. 导入Excel根据dateFormat属性格式处理
    70. +
    71. 修复War部署无法正常shutdown,ehcache内存泄漏
    72. +
    73. 修复代码生成短字段无法识别问题
    74. +
    75. 修复serviceImpl模版,修改方法判断日期错误
    76. +
    77. 代码生成模板增加导出功能日志记录
    78. +
    79. 代码生成唯一编号调整为tableId
    80. +
    81. 代码生成查询时忽略大小写
    82. +
    83. 代码生成支持翻页记住选中
    84. +
    85. 代码生成表注释未填写也允许导入
    86. +
    87. Global全局配置类修改为注解,防止多环境配置下读取问题
    88. +
    89. 修复多表格情况下,firstLoad只对第一个表格生效
    90. +
    91. 处理Maven打包出现警告问题
    92. +
    93. 默认主题样式,防止网速慢情况下出现空白
    94. +
    95. 修复文件上传多级目录识别问题
    96. +
    97. 锚链接解码url,防止中文导致页面不能加载问题
    98. +
    99. 修复右键Tab页刷新事件重复请求问题
    100. +
    101. 角色禁用&菜单隐藏不查询权限
    102. +
    103. 其他细节优化
    104. +
    +
    +
    +
    +
    +
    +
    + v4.1.02019.10.22 +
    +
    +
    +
    +
      +
    1. 支持多表格实例操作
    2. +
    3. 浮动提示方法tooltip支持弹窗
    4. +
    5. 代码生成&字典数据支持模糊条件查询
    6. +
    7. 增加页签全屏方法
    8. +
    9. 增加清除表单验证错误信息方法
    10. +
    11. 支持iframe局部刷新页面
    12. +
    13. 支持在线切换主题
    14. +
    15. 修改图片预览设置的高宽参数颠倒问题
    16. +
    17. 操作日志新增解锁账户功能
    18. +
    19. 管理员用户&角色不允许操作
    20. +
    21. 去掉jsoup包调用自定义转义工具
    22. +
    23. 添加时间轴示例
    24. +
    25. 修复翻页记住选择时获取指定列值的问题
    26. +
    27. 代码生成sql脚本添加导出按钮
    28. +
    29. 添加表格父子视图示例
    30. +
    31. 添加表格行内编辑示例
    32. +
    33. 升级fastjson到最新版1.2.60 阻止漏洞攻击
    34. +
    35. 升级echarts到最新版4.2.1
    36. +
    37. 操作日志新增返回参数
    38. +
    39. 支持mybatis通配符扫描任意多个包
    40. +
    41. 权限验证多种情况处理
    42. +
    43. 修复树形类型的代码生成的部分必要属性无法显示
    44. +
    45. 修复非表格插件情况下重置出现异常
    46. +
    47. 修复富文本编辑器有序列表冲突
    48. +
    49. 代码生成表前缀配置支持多个
    50. +
    51. 修复自动去除表前缀配置无效问题
    52. +
    53. 菜单列表按钮数据可见不显示(权限标识控制)
    54. +
    55. 修复设置会话超时时间无效问题
    56. +
    57. 新增本地资源通用下载方法
    58. +
    59. 操作日志记录新增请求方式
    60. +
    61. 代码生成单选按钮属性重名修复
    62. +
    63. 优化select2下拉框宽度不会随浏览器改变
    64. +
    65. 修复代码生成树表异常
    66. +
    67. 其他细节优化
    68. +
    +
    +
    +
    +
    +
    +
    + v4.0.02019.08.08 +
    +
    +
    +
    +
      +
    1. 代码生成支持预览、编辑,保存方案
    2. +
    3. 新增防止表单重复提交注解
    4. +
    5. 新增后端校验(和前端保持一致)
    6. +
    7. 新增同一个用户最大会话数控制
    8. +
    9. Excel导出子对象支持多个字段
    10. +
    11. 定时任务支持静态调用和多参数
    12. +
    13. 定时任务增加分组条件查询
    14. +
    15. 字典类型增加任务分组数据
    16. +
    17. 新增表格是否首次加载数据
    18. +
    19. 新增parentTab选项卡可在同一页签打开
    20. +
    21. 多数据源支持类注解(允许继承父类的注解)
    22. +
    23. 部门及以下数据权限(调整为以下及所有子节点)
    24. +
    25. 新增角色数据权限配(仅本人数据权限)
    26. +
    27. 修改菜单权限显示问题
    28. +
    29. 上传文件修改路径及返回名称
    30. +
    31. 添加报表插件及示例
    32. +
    33. 添加首页统计模板
    34. +
    35. 添加表格拖拽示例
    36. +
    37. 添加卡片列表示例
    38. +
    39. 添加富文本编辑器示例
    40. +
    41. 添加表格动态增删改查示例
    42. +
    43. 添加用户页面岗位选择框提示
    44. +
    45. 点击菜单操作添加背景高亮显示
    46. +
    47. 表格树新增showSearch是否显示检索信息
    48. +
    49. 解决表格列设置sortName无效问题
    50. +
    51. 表格图片预览支持自定义设置宽高
    52. +
    53. 添加表格列浮动提示(单击文本复制)
    54. +
    55. PC端收起菜单后支持浮动显示
    56. +
    57. 详细操作样式调整
    58. +
    59. 修改用户更新描述空串不更新问题
    60. +
    61. 导入修改为模板渲染
    62. +
    63. 修改菜单及部门排序规则
    64. +
    65. 角色导出数据范围表达式翻译
    66. +
    67. 添加summernote富文本字体大小
    68. +
    69. 优化表格底部下边框防重叠&汇总像素问题
    70. +
    71. 树表格支持属性多层级访问
    72. +
    73. 修复IE浏览器用户管理界面右侧留白问题
    74. +
    75. 重置按钮刷新表格
    76. +
    77. 重置密码更新用户缓存
    78. +
    79. 优化验证码属性参数
    80. +
    81. 支持数据监控配置用户名和密码
    82. +
    83. 文件上传修改按钮背景及加载动画
    84. +
    85. 支持配置一级菜单href跳转
    86. +
    87. 侧边栏添加一套浅色主题
    88. +
    89. 树表格添加回调函数(校验异常状态)
    90. +
    91. 用户个人中心适配手机端显示
    92. +
    93. Excel支持设置导出类型&更换样式
    94. +
    95. 检查属性改变修改为克隆方式(防止热部署强转异常)
    96. +
    97. 其他细节优化
    98. +
    +
    +
    +
    +
    +
    +
    + v3.4.02019.06.03 +
    +
    +
    +
    +
      +
    1. 新增实例演示菜单及demo
    2. +
    3. 新增页签右键操作
    4. +
    5. 菜单管理新增打开方式
    6. +
    7. 新增点击某行触发的事件
    8. +
    9. 新增双击某行触发的事件
    10. +
    11. 新增单击某格触发的事件
    12. +
    13. 新增双击某格触发的事件
    14. +
    15. 新增是否启用显示细节视图
    16. +
    17. 支持上传任意格式文件
    18. +
    19. 修复角色权限注解失效问题
    20. +
    21. 左侧的菜单栏宽度调整
    22. +
    23. 新增响应完成后自定义回调函数
    24. +
    25. 支持前端及其他模块直接获取用户信息
    26. +
    27. 升级swagger到最新版2.9.2
    28. +
    29. 升级jquery.slimscroll到最新版1.3.8
    30. +
    31. 升级select2到最新版4.0.7
    32. +
    33. 新增角色配置本部门数据权限
    34. +
    35. 新增角色配置本部门及以下数据权限
    36. +
    37. 优化底部操作防止跳到页面顶端
    38. +
    39. 修改冻结列选框无效及样式问题
    40. +
    41. 修复部门四层级修改祖级无效问题
    42. +
    43. 更换开关切换按钮样式
    44. +
    45. 新增select2-bootstrap美化下拉框
    46. +
    47. 添加表格内图片预览方法
    48. +
    49. 修复权限校验失败跳转页面路径错误
    50. +
    51. 国际化资源文件调整
    52. +
    53. 通知公告布局调整
    54. +
    55. 删除页签操作功能
    56. +
    57. 表格树新增查询指定列值
    58. +
    59. 更改系统接口扫描方式及完善测试案例
    60. +
    61. 表格列浮动提示及字典回显默认去背景
    62. +
    63. 修复启用翻页记住前面的选择check没选中问题
    64. +
    65. 去除监控页面底部的广告
    66. +
    67. 日期控件功问题修复及data功能增强
    68. +
    69. 新增角色权限可见性(前端直接调用)
    70. +
    71. 新增获取当前登录用户方法(前端及子模块调用)
    72. +
    73. 修复热部署重启导致菜单丢失问题
    74. +
    75. 优化业务校验失败普通请求跳转页面
    76. +
    77. 操作日志新增状态条件查询
    78. +
    79. 操作类型支持多选条件查询
    80. +
    81. 通知公告防止滚动触底回弹优化
    82. +
    83. 其他细节优化
    84. +
    +
    +
    +
    +
    +
    +
    + v3.3.02019.04.01 +
    +
    +
    +
    +
      +
    1. 新增线程池统一管理
    2. +
    3. 新增支持左右冻结列
    4. +
    5. 新增表格字符超长浮动提示
    6. +
    7. 升级datepicker拓展并汉化
    8. +
    9. 升级druid到最新版本v1.1.14
    10. +
    11. 修复个人头像为图片服务器跨域问题
    12. +
    13. 修改上传文件按日期存储
    14. +
    15. 新增表格客户端分页选项
    16. +
    17. 新增表格的高度参数
    18. +
    19. 新增表格销毁方法
    20. +
    21. 新增表格下拉按钮切换方法
    22. +
    23. 新增表格分页跳转到指定页码
    24. +
    25. 新增表格启用点击选中行参数
    26. +
    27. 修复表格数据重新加载未触发部分按钮禁用
    28. +
    29. 使用jsonview展示操作日志参数
    30. +
    31. 新增方法(addTab、editTab)
    32. +
    33. 修改用户管理界面为Tab打开方式
    34. +
    35. 表单验证代码优化
    36. +
    37. 修复@Excel注解 prompt 属性使用报错
    38. +
    39. 修复combo属性Excel兼容性问题
    40. +
    41. 新增@Excel导入导出支持父类字段
    42. +
    43. 修复关闭最后选项卡无法激活滚动问题
    44. +
    45. 增加日期控件显示类型及回显格式扩展选项
    46. +
    47. 修复定时任务执行失败后入库状态为成功状态
    48. +
    49. 支持定时任务并发开关控制
    50. +
    51. 优化权限校验失败普通请求跳转页面
    52. +
    53. 捕获线程池执行任务抛出的异常
    54. +
    55. 修复IE浏览器导出功能报错
    56. +
    57. 新增角色管理分配用户功能
    58. +
    59. 新增表格翻页记住前面的选择
    60. +
    61. 调整用户个人中心页面
    62. +
    63. 修复界面存在的一些安全问题
    64. +
    65. 其他细节优化
    66. +
    +
    +
    +
    +
    +
    +
    + v3.2.02019.01.18 +
    +
    +
    +
    +
      +
    1. 部门修改时不允许选择最后节点
    2. +
    3. 修复部门菜单排序字段无效
    4. +
    5. 修复光驱磁盘导致服务监控异常
    6. +
    7. 登录界面去除check插件
    8. +
    9. 验证码文本字符间距修正
    10. +
    11. 升级SpringBoot到最新版本2.1.1
    12. +
    13. 升级MYSQL驱动
    14. +
    15. 修正登录必填项位置偏移
    16. +
    17. Session会话检查优化
    18. +
    19. Excel注解支持多级获取
    20. +
    21. 新增序列号生成方法
    22. +
    23. 修复WAR部署tomcat退出线程异常
    24. +
    25. 全屏操作增加默认确认/关闭
    26. +
    27. 修复个人信息可能导致漏洞
    28. +
    29. 字典数据根据下拉选择新增类型
    30. +
    31. 升级Summernote到最新版本v0.8.11
    32. +
    33. 新增用户数据导入
    34. +
    35. 首页主题样式更换
    36. +
    37. layer扩展主题更换
    38. +
    39. 用户管理移动端默认隐藏左侧布局
    40. +
    41. 详细信息弹出层显示在顶层
    42. +
    43. 表格支持切换状态(用户/角色/定时任务)
    44. +
    45. Druid数据源支持配置继承
    46. +
    47. 修正部分iPhone手机端表格适配问题
    48. +
    49. 新增防止重复提交表单方法
    50. +
    51. 新增表格数据统计汇总方法
    52. +
    53. 支持富文本上传图片文件
    54. +
    +
    +
    +
    +
    +
    +
    + v3.1.02018.12.03 +
    +
    +
    +
    +
      +
    1. 新增内网不获取IP地址
    2. +
    3. 新增cron表达式有效校验
    4. +
    5. 定时任务新增详细信息
    6. +
    7. 定时任务默认策略修改(不触发立即执行)
    8. +
    9. 定时任务显示下一个执行周期
    10. +
    11. 支持前端任意日期格式处理
    12. +
    13. 上传头像删除多余提交按钮
    14. +
    15. 表格增加行间隔色配置项
    16. +
    17. 表格增加转义HTML字符串配置项
    18. +
    19. 表格增加显示/隐藏指定列
    20. +
    21. 代码生成优化
    22. +
    23. 操作日志参数格式化显示
    24. +
    25. 页签新增新增全屏显示
    26. +
    27. 新增一键打包部署
    28. +
    29. Excel注解新增多个参数
    30. +
    31. 新增提交静默更新表格方法
    32. +
    33. 新增服务监控菜单
    34. +
    +
    +
    +
    +
    +
    +
    + v3.0.02018.10.08 +
    +
    +
    +
    +
      +
    1. 升级poi到最新版3.17
    2. +
    3. 导出修改临时目录绝对路径
    4. +
    5. 升级laydate到最新版5.0.9
    6. +
    7. 升级SpringBoot到最新版本2.0.5
    8. +
    9. 优化开始/结束时间校验限制
    10. +
    11. 重置密码参数表中获取默认值
    12. +
    13. 修复头像修改显示问题
    14. +
    15. 新增数据权限过滤注解
    16. +
    17. 新增表格检索折叠按钮
    18. +
    19. 新增清空(登录、操作、调度)日志
    20. +
    21. 固定按钮位置(提交/关闭)
    22. +
    23. 部门/菜单支持(展开/折叠)
    24. +
    25. 部分细节调整优化
    26. +
    27. 项目采用分模块
    28. +
    +
    +
    +
    +
    +
    +
    + v2.4.02018.09.03 +
    +
    +
    +
    +
      +
    1. 支持部门多级查询
    2. +
    3. 修复菜单状态查询无效
    4. +
    5. 支持IP地址开关
    6. +
    7. 支持XSS开关
    8. +
    9. 记录日志异步处理
    10. +
    11. 字典回显样式更改为下拉框
    12. +
    13. 菜单类型必填校验
    14. +
    15. 修复在线用户排序报错
    16. +
    17. 增加重置按钮
    18. +
    19. 支持注解导入数据
    20. +
    21. 支持弹层外区域关闭
    22. +
    23. 备注更换为文本区域
    24. +
    25. 新增角色逻辑删除
    26. +
    27. 新增部门逻辑删除
    28. +
    29. 支持部门数据权限
    30. +
    31. 管理员默认拥有所有授权
    32. +
    33. 字典数据采用分页
    34. +
    35. 部分细节调整优化
    36. +
    +
    +
    +
    +
    +
    +
    + v2.3.02018.08.06 +
    +
    +
    +
    +
      +
    1. 支持表格不分页开关控制
    2. +
    3. 修改字典类型同步修改字典数据
    4. +
    5. 代码生成新增修改后缀处理
    6. +
    7. 代码生成新增实体toString
    8. +
    9. 代码生成非字符串去除!=''
    10. +
    11. 导出数据前加载遮罩层
    12. +
    13. 部门删除校验条件修改
    14. +
    15. 搜索查询下载优化
    16. +
    17. 手机打开弹出层自适应
    18. +
    19. 角色岗位禁用显示置灰
    20. +
    21. 角色禁用不显示菜单
    22. +
    23. 新增导出权限
    24. +
    25. 角色权限唯一校验
    26. +
    27. 岗位名称编码唯一校验
    28. +
    29. TreeTable优化
    30. +
    31. 支持多数据源
    32. +
    33. 其他细节优化
    34. +
    +
    +
    +
    +
    +
    +
    + v2.2.02018.07.23 +
    +
    +
    +
    +
      +
    1. 修复批量生成代码异常问题
    2. +
    3. 修复定时器保存失败问题
    4. +
    5. 修复热部署转换问题
    6. +
    7. 支持查询菜单管理,部门管理
    8. +
    9. 大多数功能支持时间查询
    10. +
    11. 自定义导出注解自动匹配column
    12. +
    13. 新增任务执行策略
    14. +
    15. 操作详细动态显示类型
    16. +
    17. 支持动态回显字典数据
    18. +
    19. 后台代码优化调整
    20. +
    21. 其他细节优化
    22. +
    +
    +
    +
    +
    +
    +
    + v2.1.02018.07.10 +
    +
    +
    +
    +
      +
    1. 新增登录超时提醒
    2. +
    3. 修复定时器热部署转换问题
    4. +
    5. 修复登录验证码校验无效问题
    6. +
    7. 定时任务新增立即执行一次
    8. +
    9. 存在字典数据不允许删除字典
    10. +
    11. 字典数据支持按名称查询
    12. +
    13. 代码生成增加日志注解&表格优化
    14. +
    15. 修复用户逻辑删除后能登录问题
    16. +
    17. 表格支持多字段动态排序
    18. +
    19. 支持三级菜单显示
    20. +
    21. 新增ry.sh启动程序脚本
    22. +
    23. 其他细节优化
    24. +
    +
    +
    +
    +
    +
    +
    + v2.0.02018.07.02 +
    +
    +
    +
    +
      +
    1. 升级SpringBoot到最新版本2.0.3
    2. +
    3. 新增公告管理
    4. +
    5. 表单校验示提体验优化
    6. +
    7. 前端通用方法封装调整
    8. +
    9. 前端去除js文件,合并到html
    10. +
    11. 操作加载遮罩层
    12. +
    13. 支持全屏模式操作
    14. +
    15. 支持注解导出数据
    16. +
    17. 系统支持多查询&下载
    18. +
    19. 系统样式调整
    20. +
    +
    +
    +
    +
    +
    +
    + v1.1.62018.06.04 +
    +
    +
    +
    +
      +
    1. 新增用户列表部门列
    2. +
    3. 新增登录地点
    4. +
    5. 新增swagger
    6. +
    7. 修复排序数字校验
    8. +
    9. 优化头像上传文件类型限定为图片
    10. +
    11. 新增XSS过滤
    12. +
    13. 新增热部署提高开发效率
    14. +
    15. 修复treegrid居中无效
    16. +
    17. 角色多条件查询
    18. +
    +
    +
    +
    +
    +
    +
    + v1.1.52018.05.28 +
    +
    +
    +
    +
      +
    1. 优化登录失败刷新验证码
    2. +
    3. 新增用户登录地址时间
    4. +
    5. 修复ajax超时退出问题
    6. +
    7. 新增html调用数据字典(若依首创)
    8. +
    9. 调整系统部分样式
    10. +
    11. 新增用户逻辑删除
    12. +
    13. 新增管理员不允许删除修改
    14. +
    15. 升级bootstrapTable到最新版本1.12.1
    16. +
    17. 升级layer到最新版本3.1.1
    18. +
    +
    +
    +
    +
    +
    +
    + v1.1.42018.05.20 +
    +
    +
    +
    +
      +
    1. 新增参数管理
    2. +
    3. 修复头像上传bug
    4. +
    5. 手机邮箱唯一校验
    6. +
    7. 支持手机邮箱登录
    8. +
    9. 代码生成优化
    10. +
    11. 支持模糊查询
    12. +
    13. 支持切换主题皮肤
    14. +
    15. 修改权限即时生效
    16. +
    17. 修复页签Tab关闭问题
    18. +
    +
    +
    +
    +
    +
    +
    + v1.1.32018.05.14 +
    +
    +
    +
    +
      +
    1. 新增验证码(数组计算、字符验证)
    2. +
    3. 新增cookie记住我
    4. +
    5. 新增头像上传
    6. +
    7. 用户名密码长度限制
    8. +
    9. 通用字段提取
    10. +
    11. 支持自定义条件查询
    12. +
    13. 部门名称必填、时间格式调整
    14. +
    15. 其他细节优化
    16. +
    +
    +
    +
    +
    +
    +
    + v1.1.22018.05.07 +
    +
    +
    +
    +
      +
    1. 新增个人信息修改
    2. +
    3. 菜单存在子菜单不允许删除
    4. +
    5. 菜单分配角色不允许删除
    6. +
    7. 角色分配人员不允许删除
    8. +
    9. 岗位使用后不允许删除
    10. +
    11. 保证用户的数据完整性加入事物
    12. +
    13. 新增环境使用手册、数据建模
    14. +
    15. Thymeleaf升级到3.0
    16. +
    17. 支持非ROOT部署
    18. +
    +
    +
    +
    +
    +
    +
    + v1.1.12018.04.23 +
    +
    +
    +
    +
      +
    1. 新增表单构建器
    2. +
    3. 代码生成优化
    4. +
    5. 支持新增主部门
    6. +
    7. 支持选择上级部门、上级菜单
    8. +
    9. 新增字典管理单条删除
    10. +
    11. 优化一些其他细节
    12. +
    +
    +
    +
    +
    +
    +
    + v1.1.02018.04.20 +
    +
    +
    +
    +
      +
    1. 支持密码盐
    2. +
    3. 支持新增主目录
    4. +
    5. 支持批量生成代码
    6. +
    7. 支持表格导出(csv、txt、doc、excel)
    8. +
    9. 自动适应宽高模式窗体
    10. +
    11. 重复校验(角色名、菜单名、部门名)
    12. +
    13. 优化一些其他细节
    14. +
    +
    +
    +
    +
    +
    +
    + v1.0.92018.04.14 +
    +
    +
    +
    +
      +
    1. 新增代码生成(生成包括 java、html、js、xml、sql)
    2. +
    3. 新增按钮权限控制隐藏(若依首创)
    4. +
    +
    +
    +
    +
    +
    +
    + v1.0.82018.04.08 +
    +
    +
    +
    +
      +
    1. 新增定时任务(新增、修改、删除、查询、启动/暂停)
    2. +
    3. 新增调度日志(查询、删除)
    4. +
    +
    +
    +
    +
    +
    +
    + v1.0.72018.04.04 +
    +
    +
    +
    +
      +
    1. 新增岗位管理(新增、修改、删除、查询)
    2. +
    3. 优化用户管理,菜单管理部分细节
    4. +
    +
    +
    +
    +
    +
    +
    + v1.0.62018.03.15 +
    +
    +
    +
    +
      +
    1. 新增字典管理(新增、删除、修改、查询、数据选择)
    2. +
    3. 新增用户密码重置
    4. +
    5. 优化一些其他细节
    6. +
    +
    +
    +
    +
    +
    +
    + v1.0.52018.03.12 +
    +
    +
    +
    +
      +
    1. 新增菜单管理(新增、删除、修改、查询、图标选择)
    2. +
    3. 部门管理优化(添加责任人、联系电话、邮箱、修改者)
    4. +
    +
    +
    +
    +
    +
    +
    + v1.0.42018.03.11 +
    +
    +
    +
    +
      +
    1. 新增角色管理(新增、删除、修改、查询、菜单选择)
    2. +
    +
    +
    +
    +
    +
    +
    + v1.0.32018.03.08 +
    +
    +
    +
    +
      +
    1. 新增用户管理(新增、删除、修改、查询、部门选择)
    2. +
    +
    +
    +
    +
    +
    +
    + v1.0.22018.03.04 +
    +
    +
    +
    +
      +
    1. 新增部门管理 (新增、删除、修改、查询)
    2. +
    +
    +
    +
    +
    +
    +
    + v1.0.12018.03.03 +
    +
    +
    +
    +
      +
    1. 新增在线用户 (批量强退、单条强退、查询)
    2. +
    3. 新增登录日志 (批量删除、查询)
    4. +
    5. 新增操作日志 (批量删除、查询、详细)
    6. +
    7. 新增数据监控 (监控DB池连接和SQL的执行)
    8. +
    +
    +
    +
    +
    +
    +

    + v1.0.02018.03.01 +

    +
    +
    +
    +
      +
    1. 若依管理系统正式发布。
    2. +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    捐赠
    +
    +
    +
    + 请作者喝杯咖啡(点击图片放大) +
    +

    + 请使用手机支付宝或者微信扫码支付 + +

    + +
    +
    +
    +
    +
    + + + + + + diff --git a/ruoyi-admin/src/main/resources/templates/main_v1.html b/ruoyi-admin/src/main/resources/templates/main_v1.html new file mode 100644 index 0000000..e9bf06d --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/main_v1.html @@ -0,0 +1,336 @@ + + + + + + 统计 + + + + + + + + +
    + +
    +
    +
    +
    + +
    收入
    +
    +
    +

    40 886,200

    +
    98% +
    + 总收入 +
    +
    +
    +
    +
    +
    + 全年 +
    订单
    +
    +
    +

    275,800

    +
    20% +
    + 新订单 +
    +
    +
    +
    +
    +
    + 今天 +
    访客
    +
    +
    +

    106,120

    +
    44% +
    + 新访客 +
    +
    +
    +
    +
    +
    + 最近一个月 +
    活跃用户
    +
    +
    +

    80,600

    +
    38% +
    + 12月 +
    +
    +
    +
    + +
    +
    +
    +
    +
    订单
    +
    +
    + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
      +
    • +

      2,346

      + 订单总数 +
      48% +
      +
      +
      +
      +
    • +
    • +

      4,422

      + 最近一个月订单 +
      60% +
      +
      +
      +
      +
    • +
    • +

      9,180

      + 最近一个月销售额 +
      22% +
      +
      +
      +
      +
    • +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    用户项目列表
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    状态日期用户
    进行中... + 11:20青衣5858 24%
    已取消 + 10:40徐子崴 66%
    进行中... + 01:30姜岚昕 54%
    进行中... + 02:20武汉大兵哥 12%
    进行中... + 09:40荆莹儿 22%
    已完成 + 04:10栾某某 66%
    进行中... + 12:08范范范二妮 23%
    +
    +
    +
    +
    +
    +
    + + + + + + + + diff --git a/ruoyi-admin/src/main/resources/templates/monitor/cache/cache.html b/ruoyi-admin/src/main/resources/templates/monitor/cache/cache.html new file mode 100644 index 0000000..a9e74eb --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/monitor/cache/cache.html @@ -0,0 +1,184 @@ + + + + + + +
    + +
    +
    +
    +
    +
    +
    缓存列表
    +
    + +
    +
    +
    + + + + + + + + + + + + + + + +
    缓存名称操作
    [[${stat.index + 1}]][[${cacheName}]]
    +
    +
    +
    +
    +
    +
    +
    键名列表
    +
    + +
    +
    +
    + + + + + + + + + + + + + + + + +
    缓存键名操作
    [[${stat.index + 1}]][[${cacheKey}]]
    +
    +
    +
    +
    +
    +
    +
    缓存内容
    + +
    +
    +
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + diff --git a/ruoyi-admin/src/main/resources/templates/monitor/logininfor/logininfor.html b/ruoyi-admin/src/main/resources/templates/monitor/logininfor/logininfor.html new file mode 100644 index 0000000..b484163 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/monitor/logininfor/logininfor.html @@ -0,0 +1,146 @@ + + + + + + +
    +
    +
    +
    +
    +
      +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + + + - + +
    • +
    • +  搜索 +  重置 +
    • +
    +
    +
    +
    + + + +
    +
    +
    +
    +
    + + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/monitor/online/online.html b/ruoyi-admin/src/main/resources/templates/monitor/online/online.html new file mode 100644 index 0000000..158126e --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/monitor/online/online.html @@ -0,0 +1,152 @@ + + + + + + +
    +
    +
    +
    +
    + +
    +
    +
    + + + +
    +
    +
    +
    +
    + + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/monitor/operlog/detail.html b/ruoyi-admin/src/main/resources/templates/monitor/operlog/detail.html new file mode 100644 index 0000000..7d9306c --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/monitor/operlog/detail.html @@ -0,0 +1,69 @@ + + + + + + + +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    + + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/monitor/operlog/operlog.html b/ruoyi-admin/src/main/resources/templates/monitor/operlog/operlog.html new file mode 100644 index 0000000..0695d1e --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/monitor/operlog/operlog.html @@ -0,0 +1,185 @@ + + + + + + + +
    +
    +
    +
    +
    +
      +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + + + - + +
    • +
    • +  搜索 +  重置 +
    • +
    +
    +
    +
    + + + +
    +
    +
    +
    +
    + + + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/monitor/server/server.html b/ruoyi-admin/src/main/resources/templates/monitor/server/server.html new file mode 100644 index 0000000..00dbe80 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/monitor/server/server.html @@ -0,0 +1,258 @@ + + + + + + +
    +
    +
    +
    +
    +
    +
    CPU
    +
    + + + +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    属性
    核心数0个
    用户使用率0%
    系统使用率0%
    当前空闲率0%
    +
    +
    +
    + +
    +
    +
    +
    内存
    +
    + + +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    属性内存JVM
    总内存0GB0MB
    已用内存0GB0MB
    剩余内存0GB0MB
    使用率[[${server.mem.usage}]]%[[${server.jvm.usage}]]%
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    服务器信息
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + +
    服务器名称RuoYi操作系统Linux
    服务器IP127.0.0.1系统架构amd64
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    Java虚拟机信息
    + +
    +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Java名称JavaJava版本1.8.0
    启动时间2018-12-31 00:00:00运行时长0天0时0分0秒
    安装路径
    项目路径
    运行参数
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    磁盘状态
    + +
    +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + +
    盘符路径文件系统盘符类型总大小可用大小已用大小已用百分比
    C:\NTFSlocal0GB0GB0GB[[${sysFile.usage}]]%
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + diff --git a/ruoyi-admin/src/main/resources/templates/register.html b/ruoyi-admin/src/main/resources/templates/register.html new file mode 100644 index 0000000..604c070 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/register.html @@ -0,0 +1,80 @@ + + + + + + 注册若依系统 + + + + + + + + + + + + + + +
    +
    +
    + +
    +
    +
    +

    注册:

    +

    你若不离不弃,我必生死相依

    + + + +
    +
    + +
    +
    + + + +
    +
    +
    + + 使用条款 +
    + +
    +
    +
    + +
    + + + + + + + + + + diff --git a/ruoyi-admin/src/main/resources/templates/skin.html b/ruoyi-admin/src/main/resources/templates/skin.html new file mode 100644 index 0000000..3b20e4f --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/skin.html @@ -0,0 +1,165 @@ + + + + + + + 主题选择 + + + + + + + + + + + + + diff --git a/ruoyi-admin/src/main/resources/templates/system/config/add.html b/ruoyi-admin/src/main/resources/templates/system/config/add.html new file mode 100644 index 0000000..c80d13f --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/config/add.html @@ -0,0 +1,79 @@ + + + + + + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    + + + + diff --git a/ruoyi-admin/src/main/resources/templates/system/config/config.html b/ruoyi-admin/src/main/resources/templates/system/config/config.html new file mode 100644 index 0000000..fc151f1 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/config/config.html @@ -0,0 +1,144 @@ + + + + + + +
    +
    +
    +
    +
    +
      +
    • + 参数名称: +
    • +
    • + 参数键名: +
    • +
    • + 系统内置: +
    • +
    • + + + - + +
    • +
    • +  搜索 +  重置 +
    • +
    +
    +
    +
    + + +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/system/config/edit.html b/ruoyi-admin/src/main/resources/templates/system/config/edit.html new file mode 100644 index 0000000..74d40cd --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/config/edit.html @@ -0,0 +1,83 @@ + + + + + + +
    +
    + +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    + + + + diff --git a/ruoyi-admin/src/main/resources/templates/system/dept/add.html b/ruoyi-admin/src/main/resources/templates/system/dept/add.html new file mode 100644 index 0000000..ef15245 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/dept/add.html @@ -0,0 +1,130 @@ + + + + + + +
    +
    + +
    + +
    +
    + + +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    + + + + diff --git a/ruoyi-admin/src/main/resources/templates/system/dept/dept.html b/ruoyi-admin/src/main/resources/templates/system/dept/dept.html new file mode 100644 index 0000000..56d94e3 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/dept/dept.html @@ -0,0 +1,112 @@ + + + + + + +
    +
    +
    +
    +
    +
      +
    • + 部门名称: +
    • +
    • + 部门状态: +
    • +
    • +  搜索 +  重置 +
    • +
    +
    +
    +
    + + +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/system/dept/edit.html b/ruoyi-admin/src/main/resources/templates/system/dept/edit.html new file mode 100644 index 0000000..3469b32 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/dept/edit.html @@ -0,0 +1,138 @@ + + + + + + +
    +
    + + +
    + +
    +
    + + +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    + + + + diff --git a/ruoyi-admin/src/main/resources/templates/system/dept/tree.html b/ruoyi-admin/src/main/resources/templates/system/dept/tree.html new file mode 100644 index 0000000..e7bc229 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/dept/tree.html @@ -0,0 +1,52 @@ + + + + + + + + + + +
    + + +
    + +
    + 展开 / + 折叠 +
    +
    +
    + + + + + diff --git a/ruoyi-admin/src/main/resources/templates/system/dict/data/add.html b/ruoyi-admin/src/main/resources/templates/system/dict/data/add.html new file mode 100644 index 0000000..a050762 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/dict/data/add.html @@ -0,0 +1,100 @@ + + + + + + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + + table表格字典列显示样式属性 +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    + + + + diff --git a/ruoyi-admin/src/main/resources/templates/system/dict/data/data.html b/ruoyi-admin/src/main/resources/templates/system/dict/data/data.html new file mode 100644 index 0000000..49ca424 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/dict/data/data.html @@ -0,0 +1,151 @@ + + + + + + + +
    +
    +
    +
    +
    +
      +
    • + 字典名称: +
    • +
    • + 字典标签: +
    • +
    • + 数据状态: +
    • +
    • +  搜索 +  重置 +
    • +
    +
    +
    +
    + + + +
    +
    +
    +
    +
    + + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/system/dict/data/edit.html b/ruoyi-admin/src/main/resources/templates/system/dict/data/edit.html new file mode 100644 index 0000000..0855513 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/dict/data/edit.html @@ -0,0 +1,101 @@ + + + + + + +
    +
    + +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + + table表格字典列显示样式属性 +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    + + + + diff --git a/ruoyi-admin/src/main/resources/templates/system/dict/type/add.html b/ruoyi-admin/src/main/resources/templates/system/dict/type/add.html new file mode 100644 index 0000000..ee3e439 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/dict/type/add.html @@ -0,0 +1,75 @@ + + + + + + +
    +
    +
    + +
    + +
    +
    +
    + +
    + + 数据存储中的Key值,如:sys_user_sex +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    + + + + diff --git a/ruoyi-admin/src/main/resources/templates/system/dict/type/edit.html b/ruoyi-admin/src/main/resources/templates/system/dict/type/edit.html new file mode 100644 index 0000000..af7523f --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/dict/type/edit.html @@ -0,0 +1,79 @@ + + + + + + +
    +
    + +
    + +
    + +
    +
    +
    + +
    + + 数据存储中的Key值,如:sys_user_sex +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    + + + + diff --git a/ruoyi-admin/src/main/resources/templates/system/dict/type/tree.html b/ruoyi-admin/src/main/resources/templates/system/dict/type/tree.html new file mode 100644 index 0000000..88ea049 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/dict/type/tree.html @@ -0,0 +1,42 @@ + + + + + + + + + + + +
    + + +
    + +
    +
    + + + + + diff --git a/ruoyi-admin/src/main/resources/templates/system/dict/type/type.html b/ruoyi-admin/src/main/resources/templates/system/dict/type/type.html new file mode 100644 index 0000000..27646f2 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/dict/type/type.html @@ -0,0 +1,148 @@ + + + + + + +
    +
    +
    +
    +
    +
      +
    • + 字典名称: +
    • +
    • + 字典类型: +
    • +
    • + 字典状态: +
    • +
    • + + + - + +
    • +
    • +  搜索 +  重置 +
    • +
    +
    +
    +
    + + + +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/system/menu/add.html b/ruoyi-admin/src/main/resources/templates/system/menu/add.html new file mode 100644 index 0000000..3a87298 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/menu/add.html @@ -0,0 +1,199 @@ + + + + + + +
    +
    + +
    + +
    +
    + + +
    +
    +
    +
    + +
    + + + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + + 控制器中定义的权限标识,如:@RequiresPermissions("") +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    + +
    +
    +
    +
    + +
    +
    + + +
    +
    + +
    +
    + + +
    +
    + + +
    +
    +
    +
    +
    + + + + diff --git a/ruoyi-admin/src/main/resources/templates/system/menu/edit.html b/ruoyi-admin/src/main/resources/templates/system/menu/edit.html new file mode 100644 index 0000000..3c93638 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/menu/edit.html @@ -0,0 +1,227 @@ + + + + + + +
    +
    + + +
    + +
    +
    + + +
    +
    +
    +
    + +
    + + + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + + 控制器中定义的权限标识,如:@RequiresPermissions("") +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    + +
    +
    +
    +
    + +
    +
    + + +
    +
    + +
    +
    + + +
    +
    + + +
    +
    +
    +
    +
    + + + + diff --git a/ruoyi-admin/src/main/resources/templates/system/menu/icon.html b/ruoyi-admin/src/main/resources/templates/system/menu/icon.html new file mode 100644 index 0000000..f9f7f19 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/menu/icon.html @@ -0,0 +1,928 @@ + + + + + Font Awesome Ico list + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/system/menu/menu.html b/ruoyi-admin/src/main/resources/templates/system/menu/menu.html new file mode 100644 index 0000000..3b7c2c1 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/menu/menu.html @@ -0,0 +1,161 @@ + + + + + + +
    +
    +
    + +
    + + +
    +
    +
    +
    +
    + + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/system/menu/tree.html b/ruoyi-admin/src/main/resources/templates/system/menu/tree.html new file mode 100644 index 0000000..a38e851 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/menu/tree.html @@ -0,0 +1,49 @@ + + + + + + + + + + +
    + + +
    + +
    + 展开 / + 折叠 +
    +
    +
    + + + + + diff --git a/ruoyi-admin/src/main/resources/templates/system/notice/add.html b/ruoyi-admin/src/main/resources/templates/system/notice/add.html new file mode 100644 index 0000000..ae8febf --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/notice/add.html @@ -0,0 +1,98 @@ + + + + + + + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    + + + + + diff --git a/ruoyi-admin/src/main/resources/templates/system/notice/edit.html b/ruoyi-admin/src/main/resources/templates/system/notice/edit.html new file mode 100644 index 0000000..6facae9 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/notice/edit.html @@ -0,0 +1,103 @@ + + + + + + + +
    +
    + +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    + + + + + diff --git a/ruoyi-admin/src/main/resources/templates/system/notice/notice.html b/ruoyi-admin/src/main/resources/templates/system/notice/notice.html new file mode 100644 index 0000000..91ecd78 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/notice/notice.html @@ -0,0 +1,117 @@ + + + + + + +
    +
    +
    +
    +
    +
      +
    • + 公告标题: +
    • +
    • + 操作人员: +
    • +
    • + 公告类型: +
    • +
    • +  搜索 +  重置 +
    • +
    +
    +
    +
    + + + +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/system/post/add.html b/ruoyi-admin/src/main/resources/templates/system/post/add.html new file mode 100644 index 0000000..4bbcb67 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/post/add.html @@ -0,0 +1,97 @@ + + + + + + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    + + + + diff --git a/ruoyi-admin/src/main/resources/templates/system/post/edit.html b/ruoyi-admin/src/main/resources/templates/system/post/edit.html new file mode 100644 index 0000000..b2e5dcb --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/post/edit.html @@ -0,0 +1,104 @@ + + + + + + +
    +
    + +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    + + + + diff --git a/ruoyi-admin/src/main/resources/templates/system/post/post.html b/ruoyi-admin/src/main/resources/templates/system/post/post.html new file mode 100644 index 0000000..3c3fc42 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/post/post.html @@ -0,0 +1,120 @@ + + + + + + +
    +
    +
    +
    +
    +
      +
    • + 岗位编码: +
    • +
    • + 岗位名称: +
    • +
    • + 岗位状态: +
    • +
    • +  搜索 +  重置 +
    • +
    +
    +
    +
    + + + +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/system/role/add.html b/ruoyi-admin/src/main/resources/templates/system/role/add.html new file mode 100644 index 0000000..2990c6d --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/role/add.html @@ -0,0 +1,174 @@ + + + + + + + +
    +
    +
    + +
    + +
    +
    +
    + +
    + + 控制器中定义的权限字符,如:@RequiresRoles("") +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + diff --git a/ruoyi-admin/src/main/resources/templates/system/role/authUser.html b/ruoyi-admin/src/main/resources/templates/system/role/authUser.html new file mode 100644 index 0000000..0bce407 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/role/authUser.html @@ -0,0 +1,142 @@ + + + + + + +
    +
    +
    +
    + +
    + +
    +
    +
    + + + +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/system/role/dataScope.html b/ruoyi-admin/src/main/resources/templates/system/role/dataScope.html new file mode 100644 index 0000000..af7237b --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/role/dataScope.html @@ -0,0 +1,137 @@ + + + + + + + +
    +
    + +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + + 特殊情况下,设置为“自定数据权限” +
    +
    +
    + +
    + + + +
    +
    +
    +
    +
    + + + + + diff --git a/ruoyi-admin/src/main/resources/templates/system/role/edit.html b/ruoyi-admin/src/main/resources/templates/system/role/edit.html new file mode 100644 index 0000000..d2e75b3 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/role/edit.html @@ -0,0 +1,183 @@ + + + + + + + +
    +
    + +
    + +
    + +
    +
    +
    + +
    + + 控制器中定义的权限字符,如:@RequiresRoles("") +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    + + + + + diff --git a/ruoyi-admin/src/main/resources/templates/system/role/role.html b/ruoyi-admin/src/main/resources/templates/system/role/role.html new file mode 100644 index 0000000..342551f --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/role/role.html @@ -0,0 +1,169 @@ + + + + + + +
    +
    +
    +
    +
    +
      +
    • + 角色名称: +
    • +
    • + 权限字符: +
    • +
    • + 角色状态: +
    • +
    • + + + - + +
    • +
    • +  搜索 +  重置 +
    • +
    +
    +
    +
    + + + +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/system/role/selectUser.html b/ruoyi-admin/src/main/resources/templates/system/role/selectUser.html new file mode 100644 index 0000000..e964062 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/role/selectUser.html @@ -0,0 +1,113 @@ + + + + + + + +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/system/user/add.html b/ruoyi-admin/src/main/resources/templates/system/user/add.html new file mode 100644 index 0000000..fa5b522 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/user/add.html @@ -0,0 +1,258 @@ + + + + + + + +
    +
    + +

    基本信息

    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +

    其他信息

    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +   + +
    +
    + + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/system/user/authRole.html b/ruoyi-admin/src/main/resources/templates/system/user/authRole.html new file mode 100644 index 0000000..a2a131d --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/user/authRole.html @@ -0,0 +1,114 @@ + + + + + + +
    +
    + +

    基本信息

    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    + +

    分配角色

    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +   + +
    +
    + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/system/user/deptTree.html b/ruoyi-admin/src/main/resources/templates/system/user/deptTree.html new file mode 100644 index 0000000..2caeddf --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/user/deptTree.html @@ -0,0 +1,51 @@ + + + + + + + + + + +
    + + +
    + +
    + 展开 / + 折叠 +
    +
    +
    + + + + + diff --git a/ruoyi-admin/src/main/resources/templates/system/user/edit.html b/ruoyi-admin/src/main/resources/templates/system/user/edit.html new file mode 100644 index 0000000..255239e --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/user/edit.html @@ -0,0 +1,225 @@ + + + + + + + +
    +
    + + +

    基本信息

    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +

    其他信息

    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    +
    +   + +
    +
    + + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/system/user/profile/avatar.html b/ruoyi-admin/src/main/resources/templates/system/user/profile/avatar.html new file mode 100644 index 0000000..e473ff1 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/user/profile/avatar.html @@ -0,0 +1,261 @@ + + + + + + + + +
    +
    +
    + +
    +
    + + + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + + diff --git a/ruoyi-admin/src/main/resources/templates/system/user/profile/profile.html b/ruoyi-admin/src/main/resources/templates/system/user/profile/profile.html new file mode 100644 index 0000000..35c0cb8 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/user/profile/profile.html @@ -0,0 +1,298 @@ + + + + + + + + + +
    +
    +
    +
    +
    +
    个人资料
    +
    +
    +
    + +

    修改头像

    +
    +
      +
    • + 登录名称: +

      [[${user.loginName}]]

      +
    • +
    • + 手机号码: +

      [[${user.phonenumber}]]

      +
    • +
    • + 所属部门: +

      [[${user.dept?.deptName}]] / [[${#strings.defaultString(postGroup,'无岗位')}]]

      +
    • +
    • + 邮箱地址: +

      [[${#strings.abbreviate(user.email, 16)}]]

      +
    • +
    • + 创建时间: +

      [[${#dates.format(user.createTime, 'yyyy-MM-dd')}]]

      +
    • +
    +
    +
    +
    + +
    +
    +
    +
    基本资料
    +
    +
    + +
    +
    +
    +
    +
    + + + + + diff --git a/ruoyi-admin/src/main/resources/templates/system/user/profile/resetPwd.html b/ruoyi-admin/src/main/resources/templates/system/user/profile/resetPwd.html new file mode 100644 index 0000000..92f621d --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/user/profile/resetPwd.html @@ -0,0 +1,104 @@ + + + + + + +
    +
    + +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + + + + + 密码只能为0-9数字 + 密码只能为a-z和A-Z字母 + 密码必须包含(字母,数字) + 密码必须包含(字母,数字,特殊字符!@#$%^&*()-=_+) + + + +
    +
    +
    + +
    + + 请再次输入您的密码 +
    +
    +
    +
    + + + + + + diff --git a/ruoyi-admin/src/main/resources/templates/system/user/resetPwd.html b/ruoyi-admin/src/main/resources/templates/system/user/resetPwd.html new file mode 100644 index 0000000..ec00812 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/user/resetPwd.html @@ -0,0 +1,45 @@ + + + + + + +
    +
    + +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    + + + + + diff --git a/ruoyi-admin/src/main/resources/templates/system/user/user.html b/ruoyi-admin/src/main/resources/templates/system/user/user.html new file mode 100644 index 0000000..1d9f18e --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/user/user.html @@ -0,0 +1,292 @@ + + + + + + + + +
    +
    +
    +
    + 组织机构 +
    +
    + + + + +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + + +
    +
      +
    • + 登录名称: +
    • +
    • + 手机号码: +
    • +
    • + 用户状态: +
    • +
    • + + + - + +
    • +
    • +  搜索 +  重置 +
    • +
    +
    +
    +
    + + + +
    +
    +
    +
    +
    +
    + + + + + + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/tool/build/build.html b/ruoyi-admin/src/main/resources/templates/tool/build/build.html new file mode 100644 index 0000000..1fbf8a9 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/tool/build/build.html @@ -0,0 +1,168 @@ + + + + + + + + +
    +
    +
    +
    +
    +
    元素
    +
    + + + + + + + + + + +
    +
    +
    +
    + 拖拽左侧的表单元素到右侧区域,即可生成相应的HTML代码,表单代码,轻松搞定! +
    +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + + +
    +

    这里是纯文字信息

    +
    +
    +
    + + +
    + + +
    +
    +
    + + +
    + + + +
    +
    +
    + + +
    + +
    +
    +
    + + +
    +
    + + +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    拖拽左侧表单元素到此区域
    +
    + 请选择显示的列数: + +
    +
    + +
    +
    +
    +
    + + +
    + +
    +
    +
    +
    +
    + + + + + + + diff --git a/ruoyi-common/pom.xml b/ruoyi-common/pom.xml new file mode 100644 index 0000000..fa26863 --- /dev/null +++ b/ruoyi-common/pom.xml @@ -0,0 +1,100 @@ + + + + ruoyi + com.ruoyi + 4.7.6 + + 4.0.0 + + ruoyi-common + + + common通用工具 + + + + + + + org.springframework + spring-context-support + + + + + org.springframework + spring-web + + + + + org.apache.shiro + shiro-core + + + + + org.apache.shiro + shiro-ehcache + + + + + com.github.pagehelper + pagehelper-spring-boot-starter + + + + + org.springframework.boot + spring-boot-starter-validation + + + + + org.apache.commons + commons-lang3 + + + + + com.fasterxml.jackson.core + jackson-databind + + + + + com.alibaba + fastjson + + + + + commons-io + commons-io + + + + + org.apache.poi + poi-ooxml + + + + + org.yaml + snakeyaml + + + + + javax.servlet + javax.servlet-api + + + + + \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataScope.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataScope.java new file mode 100644 index 0000000..bafce5b --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataScope.java @@ -0,0 +1,33 @@ +package com.ruoyi.common.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 数据权限过滤注解 + * + * @author ruoyi + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DataScope +{ + /** + * 部门表的别名 + */ + public String deptAlias() default ""; + + /** + * 用户表的别名 + */ + public String userAlias() default ""; + + /** + * 权限字符(用于多个角色匹配符合要求的权限)默认根据权限注解@RequiresPermissions获取,多个权限用逗号分隔开来 + */ + public String permission() default ""; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSource.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSource.java new file mode 100644 index 0000000..79cd191 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSource.java @@ -0,0 +1,28 @@ +package com.ruoyi.common.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import com.ruoyi.common.enums.DataSourceType; + +/** + * 自定义多数据源切换注解 + * + * 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准 + * + * @author ruoyi + */ +@Target({ ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface DataSource +{ + /** + * 切换数据源名称 + */ + public DataSourceType value() default DataSourceType.MASTER; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excel.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excel.java new file mode 100644 index 0000000..f7aaca5 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excel.java @@ -0,0 +1,187 @@ +package com.ruoyi.common.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.math.BigDecimal; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.IndexedColors; +import com.ruoyi.common.utils.poi.ExcelHandlerAdapter; + +/** + * 自定义导出Excel数据注解 + * + * @author ruoyi + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Excel +{ + /** + * 导出时在excel中排序 + */ + public int sort() default Integer.MAX_VALUE; + + /** + * 导出到Excel中的名字. + */ + public String name() default ""; + + /** + * 日期格式, 如: yyyy-MM-dd + */ + public String dateFormat() default ""; + + /** + * 如果是字典类型,请设置字典的type值 (如: sys_user_sex) + */ + public String dictType() default ""; + + /** + * 读取内容转表达式 (如: 0=男,1=女,2=未知) + */ + public String readConverterExp() default ""; + + /** + * 分隔符,读取字符串组内容 + */ + public String separator() default ","; + + /** + * BigDecimal 精度 默认:-1(默认不开启BigDecimal格式化) + */ + public int scale() default -1; + + /** + * BigDecimal 舍入规则 默认:BigDecimal.ROUND_HALF_EVEN + */ + public int roundingMode() default BigDecimal.ROUND_HALF_EVEN; + + /** + * 导出时在excel中每个列的高度 单位为字符 + */ + public double height() default 14; + + /** + * 导出时在excel中每个列的宽 单位为字符 + */ + public double width() default 16; + + /** + * 文字后缀,如% 90 变成90% + */ + public String suffix() default ""; + + /** + * 当值为空时,字段的默认值 + */ + public String defaultValue() default ""; + + /** + * 提示信息 + */ + public String prompt() default ""; + + /** + * 设置只能选择不能输入的列内容. + */ + public String[] combo() default {}; + + /** + * 是否需要纵向合并单元格,应对需求:含有list集合单元格) + */ + public boolean needMerge() default false; + + /** + * 是否导出数据,应对需求:有时我们需要导出一份模板,这是标题需要但内容需要用户手工填写. + */ + public boolean isExport() default true; + + /** + * 另一个类中的属性名称,支持多级获取,以小数点隔开 + */ + public String targetAttr() default ""; + + /** + * 是否自动统计数据,在最后追加一行统计数据总和 + */ + public boolean isStatistics() default false; + + /** + * 导出类型(0数字 1字符串 2图片) + */ + public ColumnType cellType() default ColumnType.STRING; + + /** + * 导出列头背景色 + */ + public IndexedColors headerBackgroundColor() default IndexedColors.GREY_50_PERCENT; + + /** + * 导出列头字体颜色 + */ + public IndexedColors headerColor() default IndexedColors.WHITE; + + /** + * 导出单元格背景色 + */ + public IndexedColors backgroundColor() default IndexedColors.WHITE; + + /** + * 导出单元格字体颜色 + */ + public IndexedColors color() default IndexedColors.BLACK; + + /** + * 导出字段对齐方式 + */ + public HorizontalAlignment align() default HorizontalAlignment.CENTER; + + /** + * 自定义数据处理器 + */ + public Class handler() default ExcelHandlerAdapter.class; + + /** + * 自定义数据处理器参数 + */ + public String[] args() default {}; + + /** + * 字段类型(0:导出导入;1:仅导出;2:仅导入) + */ + Type type() default Type.ALL; + + public enum Type + { + ALL(0), EXPORT(1), IMPORT(2); + private final int value; + + Type(int value) + { + this.value = value; + } + + public int value() + { + return this.value; + } + } + + public enum ColumnType + { + NUMERIC(0), STRING(1), IMAGE(2); + private final int value; + + ColumnType(int value) + { + this.value = value; + } + + public int value() + { + return this.value; + } + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excels.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excels.java new file mode 100644 index 0000000..8c6870c --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excels.java @@ -0,0 +1,18 @@ +package com.ruoyi.common.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Excel注解集 + * + * @author ruoyi + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Excels +{ + Excel[] value(); +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Log.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Log.java new file mode 100644 index 0000000..e395169 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Log.java @@ -0,0 +1,50 @@ +package com.ruoyi.common.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.enums.OperatorType; + +/** + * 自定义操作日志记录注解 + * + * @author ruoyi + */ +@Target({ ElementType.PARAMETER, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Log +{ + /** + * 模块 + */ + public String title() default ""; + + /** + * 功能 + */ + public BusinessType businessType() default BusinessType.OTHER; + + /** + * 操作人类别 + */ + public OperatorType operatorType() default OperatorType.MANAGE; + + /** + * 是否保存请求的参数 + */ + public boolean isSaveRequestData() default true; + + /** + * 是否保存响应的参数 + */ + public boolean isSaveResponseData() default true; + + /** + * 排除指定的请求参数 + */ + public String[] excludeParamNames() default {}; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java new file mode 100644 index 0000000..3e06e95 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java @@ -0,0 +1,29 @@ +package com.ruoyi.common.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 自定义注解防止表单重复提交 + * + * @author ruoyi + * + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RepeatSubmit +{ + /** + * 间隔时间(ms),小于此时间视为重复提交 + */ + public int interval() default 5000; + + /** + * 提示消息 + */ + public String message() default "不允许重复提交,请稍后再试"; +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java b/ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java new file mode 100644 index 0000000..46a5734 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java @@ -0,0 +1,124 @@ +package com.ruoyi.common.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 全局配置类 + * + * @author ruoyi + */ +@Component +@ConfigurationProperties(prefix = "ruoyi") +public class RuoYiConfig +{ + /** 项目名称 */ + private static String name; + + /** 版本 */ + private static String version; + + /** 版权年份 */ + private static String copyrightYear; + + /** 实例演示开关 */ + private static boolean demoEnabled; + + /** 上传路径 */ + private static String profile; + + /** 获取地址开关 */ + private static boolean addressEnabled; + + public static String getName() + { + return name; + } + + public void setName(String name) + { + RuoYiConfig.name = name; + } + + public static String getVersion() + { + return version; + } + + public void setVersion(String version) + { + RuoYiConfig.version = version; + } + + public static String getCopyrightYear() + { + return copyrightYear; + } + + public void setCopyrightYear(String copyrightYear) + { + RuoYiConfig.copyrightYear = copyrightYear; + } + + public static boolean isDemoEnabled() + { + return demoEnabled; + } + + public void setDemoEnabled(boolean demoEnabled) + { + RuoYiConfig.demoEnabled = demoEnabled; + } + + public static String getProfile() + { + return profile; + } + + public void setProfile(String profile) + { + RuoYiConfig.profile = profile; + } + + public static boolean isAddressEnabled() + { + return addressEnabled; + } + + public void setAddressEnabled(boolean addressEnabled) + { + RuoYiConfig.addressEnabled = addressEnabled; + } + + /** + * 获取导入上传路径 + */ + public static String getImportPath() + { + return getProfile() + "/import"; + } + + /** + * 获取头像上传路径 + */ + public static String getAvatarPath() + { + return getProfile() + "/avatar"; + } + + /** + * 获取下载路径 + */ + public static String getDownloadPath() + { + return getProfile() + "/download/"; + } + + /** + * 获取上传路径 + */ + public static String getUploadPath() + { + return getProfile() + "/upload"; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/config/ServerConfig.java b/ruoyi-common/src/main/java/com/ruoyi/common/config/ServerConfig.java new file mode 100644 index 0000000..b6b35e4 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/config/ServerConfig.java @@ -0,0 +1,33 @@ +package com.ruoyi.common.config; + +import javax.servlet.http.HttpServletRequest; +import org.springframework.stereotype.Component; +import com.ruoyi.common.utils.ServletUtils; + +/** + * 服务相关配置 + * + * @author ruoyi + * + */ +@Component +public class ServerConfig +{ + /** + * 获取完整的请求路径,包括:域名,端口,上下文访问路径 + * + * @return 服务地址 + */ + public String getUrl() + { + HttpServletRequest request = ServletUtils.getRequest(); + return getDomain(request); + } + + public static String getDomain(HttpServletRequest request) + { + StringBuffer url = request.getRequestURL(); + String contextPath = request.getServletContext().getContextPath(); + return url.delete(url.length() - request.getRequestURI().length(), url.length()).append(contextPath).toString(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/config/datasource/DynamicDataSourceContextHolder.java b/ruoyi-common/src/main/java/com/ruoyi/common/config/datasource/DynamicDataSourceContextHolder.java new file mode 100644 index 0000000..6937dca --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/config/datasource/DynamicDataSourceContextHolder.java @@ -0,0 +1,45 @@ +package com.ruoyi.common.config.datasource; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 数据源切换处理 + * + * @author ruoyi + */ +public class DynamicDataSourceContextHolder +{ + public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class); + + /** + * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本, + * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 + */ + private static final ThreadLocal CONTEXT_HOLDER = new ThreadLocal<>(); + + /** + * 设置数据源的变量 + */ + public static void setDataSourceType(String dsType) + { + log.info("切换到{}数据源", dsType); + CONTEXT_HOLDER.set(dsType); + } + + /** + * 获得数据源的变量 + */ + public static String getDataSourceType() + { + return CONTEXT_HOLDER.get(); + } + + /** + * 清空数据源变量 + */ + public static void clearDataSourceType() + { + CONTEXT_HOLDER.remove(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/config/thread/ThreadPoolConfig.java b/ruoyi-common/src/main/java/com/ruoyi/common/config/thread/ThreadPoolConfig.java new file mode 100644 index 0000000..955a4f9 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/config/thread/ThreadPoolConfig.java @@ -0,0 +1,63 @@ +package com.ruoyi.common.config.thread; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor; +import org.apache.commons.lang3.concurrent.BasicThreadFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import com.ruoyi.common.utils.Threads; + +/** + * 线程池配置 + * + * @author ruoyi + **/ +@Configuration +public class ThreadPoolConfig +{ + // 核心线程池大小 + private int corePoolSize = 50; + + // 最大可创建的线程数 + private int maxPoolSize = 200; + + // 队列最大长度 + private int queueCapacity = 1000; + + // 线程池维护线程所允许的空闲时间 + private int keepAliveSeconds = 300; + + @Bean(name = "threadPoolTaskExecutor") + public ThreadPoolTaskExecutor threadPoolTaskExecutor() + { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setMaxPoolSize(maxPoolSize); + executor.setCorePoolSize(corePoolSize); + executor.setQueueCapacity(queueCapacity); + executor.setKeepAliveSeconds(keepAliveSeconds); + // 线程池对拒绝任务(无线程可用)的处理策略 + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + return executor; + } + + /** + * 执行周期性或定时任务 + */ + @Bean(name = "scheduledExecutorService") + protected ScheduledExecutorService scheduledExecutorService() + { + return new ScheduledThreadPoolExecutor(corePoolSize, + new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(), + new ThreadPoolExecutor.CallerRunsPolicy()) + { + @Override + protected void afterExecute(Runnable r, Throwable t) + { + super.afterExecute(r, t); + Threads.printException(r, t); + } + }; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java new file mode 100644 index 0000000..9a8c6a5 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java @@ -0,0 +1,115 @@ +package com.ruoyi.common.constant; + +/** + * 通用常量信息 + * + * @author ruoyi + */ +public class Constants +{ + /** + * UTF-8 字符集 + */ + public static final String UTF8 = "UTF-8"; + + /** + * GBK 字符集 + */ + public static final String GBK = "GBK"; + + /** + * http请求 + */ + public static final String HTTP = "http://"; + + /** + * https请求 + */ + public static final String HTTPS = "https://"; + + /** + * 通用成功标识 + */ + public static final String SUCCESS = "0"; + + /** + * 通用失败标识 + */ + public static final String FAIL = "1"; + + /** + * 登录成功 + */ + public static final String LOGIN_SUCCESS = "Success"; + + /** + * 注销 + */ + public static final String LOGOUT = "Logout"; + + /** + * 注册 + */ + public static final String REGISTER = "Register"; + + /** + * 登录失败 + */ + public static final String LOGIN_FAIL = "Error"; + + /** + * 系统用户授权缓存 + */ + public static final String SYS_AUTH_CACHE = "sys-authCache"; + + /** + * 参数管理 cache name + */ + public static final String SYS_CONFIG_CACHE = "sys-config"; + + /** + * 参数管理 cache key + */ + public static final String SYS_CONFIG_KEY = "sys_config:"; + + /** + * 字典管理 cache name + */ + public static final String SYS_DICT_CACHE = "sys-dict"; + + /** + * 字典管理 cache key + */ + public static final String SYS_DICT_KEY = "sys_dict:"; + + /** + * 资源映射路径 前缀 + */ + public static final String RESOURCE_PREFIX = "/profile"; + + /** + * RMI 远程方法调用 + */ + public static final String LOOKUP_RMI = "rmi:"; + + /** + * LDAP 远程方法调用 + */ + public static final String LOOKUP_LDAP = "ldap:"; + + /** + * LDAPS 远程方法调用 + */ + public static final String LOOKUP_LDAPS = "ldaps:"; + + /** + * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加) + */ + public static final String[] JOB_WHITELIST_STR = { "com.ruoyi" }; + + /** + * 定时任务违规的字符 + */ + public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml", + "org.springframework", "org.apache", "com.ruoyi.common.utils.file", "com.ruoyi.common.config" }; +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java new file mode 100644 index 0000000..98b4711 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java @@ -0,0 +1,114 @@ +package com.ruoyi.common.constant; + +/** + * 代码生成通用常量 + * + * @author ruoyi + */ +public class GenConstants +{ + /** 单表(增删改查) */ + public static final String TPL_CRUD = "crud"; + + /** 树表(增删改查) */ + public static final String TPL_TREE = "tree"; + + /** 主子表(增删改查) */ + public static final String TPL_SUB = "sub"; + + /** 树编码字段 */ + public static final String TREE_CODE = "treeCode"; + + /** 树父编码字段 */ + public static final String TREE_PARENT_CODE = "treeParentCode"; + + /** 树名称字段 */ + public static final String TREE_NAME = "treeName"; + + /** 上级菜单ID字段 */ + public static final String PARENT_MENU_ID = "parentMenuId"; + + /** 上级菜单名称字段 */ + public static final String PARENT_MENU_NAME = "parentMenuName"; + + /** 数据库字符串类型 */ + public static final String[] COLUMNTYPE_STR = { "char", "varchar", "nvarchar", "varchar2" }; + + /** 数据库文本类型 */ + public static final String[] COLUMNTYPE_TEXT = { "tinytext", "text", "mediumtext", "longtext" }; + + /** 数据库时间类型 */ + public static final String[] COLUMNTYPE_TIME = { "datetime", "time", "date", "timestamp" }; + + /** 数据库数字类型 */ + public static final String[] COLUMNTYPE_NUMBER = { "tinyint", "smallint", "mediumint", "int", "number", "integer", + "bit", "bigint", "float", "double", "decimal" }; + + /** 页面不需要编辑字段 */ + public static final String[] COLUMNNAME_NOT_EDIT = { "id", "create_by", "create_time", "del_flag" }; + + /** 页面不需要显示的列表字段 */ + public static final String[] COLUMNNAME_NOT_LIST = { "id", "create_by", "create_time", "del_flag", "update_by", + "update_time" }; + + /** 页面不需要查询字段 */ + public static final String[] COLUMNNAME_NOT_QUERY = { "id", "create_by", "create_time", "del_flag", "update_by", + "update_time", "remark" }; + + /** Entity基类字段 */ + public static final String[] BASE_ENTITY = { "createBy", "createTime", "updateBy", "updateTime", "remark" }; + + /** Tree基类字段 */ + public static final String[] TREE_ENTITY = { "parentName", "parentId", "orderNum", "ancestors" }; + + /** 文本框 */ + public static final String HTML_INPUT = "input"; + + /** 文本域 */ + public static final String HTML_TEXTAREA = "textarea"; + + /** 下拉框 */ + public static final String HTML_SELECT = "select"; + + /** 单选框 */ + public static final String HTML_RADIO = "radio"; + + /** 复选框 */ + public static final String HTML_CHECKBOX = "checkbox"; + + /** 日期控件 */ + public static final String HTML_DATETIME = "datetime"; + + /** 上传控件 */ + public static final String HTML_UPLOAD = "upload"; + + /** 富文本控件 */ + public static final String HTML_SUMMERNOTE = "summernote"; + + /** 字符串类型 */ + public static final String TYPE_STRING = "String"; + + /** 整型 */ + public static final String TYPE_INTEGER = "Integer"; + + /** 长整型 */ + public static final String TYPE_LONG = "Long"; + + /** 浮点型 */ + public static final String TYPE_DOUBLE = "Double"; + + /** 高精度计算类型 */ + public static final String TYPE_BIGDECIMAL = "BigDecimal"; + + /** 时间类型 */ + public static final String TYPE_DATE = "Date"; + + /** 模糊查询 */ + public static final String QUERY_LIKE = "LIKE"; + + /** 相等查询 */ + public static final String QUERY_EQ = "EQ"; + + /** 需要 */ + public static final String REQUIRE = "1"; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/PermissionConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/PermissionConstants.java new file mode 100644 index 0000000..8967214 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/PermissionConstants.java @@ -0,0 +1,27 @@ +package com.ruoyi.common.constant; + +/** + * 权限通用常量 + * + * @author ruoyi + */ +public class PermissionConstants +{ + /** 新增权限 */ + public static final String ADD_PERMISSION = "add"; + + /** 修改权限 */ + public static final String EDIT_PERMISSION = "edit"; + + /** 删除权限 */ + public static final String REMOVE_PERMISSION = "remove"; + + /** 导出权限 */ + public static final String EXPORT_PERMISSION = "export"; + + /** 显示权限 */ + public static final String VIEW_PERMISSION = "view"; + + /** 查询权限 */ + public static final String LIST_PERMISSION = "list"; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/ScheduleConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/ScheduleConstants.java new file mode 100644 index 0000000..62ad815 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/ScheduleConstants.java @@ -0,0 +1,50 @@ +package com.ruoyi.common.constant; + +/** + * 任务调度通用常量 + * + * @author ruoyi + */ +public class ScheduleConstants +{ + public static final String TASK_CLASS_NAME = "TASK_CLASS_NAME"; + + /** 执行目标key */ + public static final String TASK_PROPERTIES = "TASK_PROPERTIES"; + + /** 默认 */ + public static final String MISFIRE_DEFAULT = "0"; + + /** 立即触发执行 */ + public static final String MISFIRE_IGNORE_MISFIRES = "1"; + + /** 触发一次执行 */ + public static final String MISFIRE_FIRE_AND_PROCEED = "2"; + + /** 不触发立即执行 */ + public static final String MISFIRE_DO_NOTHING = "3"; + + public enum Status + { + /** + * 正常 + */ + NORMAL("0"), + /** + * 暂停 + */ + PAUSE("1"); + + private String value; + + private Status(String value) + { + this.value = value; + } + + public String getValue() + { + return value; + } + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/ShiroConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/ShiroConstants.java new file mode 100644 index 0000000..b52f1e9 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/ShiroConstants.java @@ -0,0 +1,79 @@ +package com.ruoyi.common.constant; + +/** + * Shiro通用常量 + * + * @author ruoyi + */ +public class ShiroConstants +{ + /** + * 当前登录的用户 + */ + public static final String CURRENT_USER = "currentUser"; + + /** + * 用户名字段 + */ + public static final String CURRENT_USERNAME = "username"; + + /** + * 锁定屏幕字段 + */ + public static final String LOCK_SCREEN = "lockscreen"; + + /** + * 消息key + */ + public static final String MESSAGE = "message"; + + /** + * 错误key + */ + public static final String ERROR = "errorMsg"; + + /** + * 编码格式 + */ + public static final String ENCODING = "UTF-8"; + + /** + * 当前在线会话 + */ + public static final String ONLINE_SESSION = "online_session"; + + /** + * 验证码key + */ + public static final String CURRENT_CAPTCHA = "captcha"; + + /** + * 验证码开关 + */ + public static final String CURRENT_ENABLED = "captchaEnabled"; + + /** + * 验证码类型 + */ + public static final String CURRENT_TYPE = "captchaType"; + + /** + * 验证码 + */ + public static final String CURRENT_VALIDATECODE = "validateCode"; + + /** + * 验证码错误 + */ + public static final String CAPTCHA_ERROR = "captchaError"; + + /** + * 登录记录缓存 + */ + public static final String LOGINRECORDCACHE = "loginRecordCache"; + + /** + * 系统活跃用户缓存 + */ + public static final String SYS_USERCACHE = "sys-userCache"; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java new file mode 100644 index 0000000..c59080f --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java @@ -0,0 +1,70 @@ +package com.ruoyi.common.constant; + +/** + * 用户常量信息 + * + * @author ruoyi + */ +public class UserConstants +{ + /** + * 平台内系统用户的唯一标志 + */ + public static final String SYS_USER = "SYS_USER"; + + /** 正常状态 */ + public static final String NORMAL = "0"; + + /** 异常状态 */ + public static final String EXCEPTION = "1"; + + /** 用户封禁状态 */ + public static final String USER_DISABLE = "1"; + + /** 角色封禁状态 */ + public static final String ROLE_DISABLE = "1"; + + /** 部门正常状态 */ + public static final String DEPT_NORMAL = "0"; + + /** 部门停用状态 */ + public static final String DEPT_DISABLE = "1"; + + /** 字典正常状态 */ + public static final String DICT_NORMAL = "0"; + + /** 是否为系统默认(是) */ + public static final String YES = "Y"; + + /** 是否唯一的返回标识 */ + public final static boolean UNIQUE = true; + public final static boolean NOT_UNIQUE = false; + + /** + * 用户名长度限制 + */ + public static final int USERNAME_MIN_LENGTH = 2; + public static final int USERNAME_MAX_LENGTH = 20; + + /** + * 密码长度限制 + */ + public static final int PASSWORD_MIN_LENGTH = 5; + public static final int PASSWORD_MAX_LENGTH = 20; + + /** + * 用户类型 + */ + public static final String SYSTEM_USER_TYPE = "00"; + public static final String REGISTER_USER_TYPE = "01"; + + /** + * 手机号码格式限制 + */ + public static final String MOBILE_PHONE_NUMBER_PATTERN = "^0{0,1}(13[0-9]|15[0-9]|14[0-9]|18[0-9])[0-9]{8}$"; + + /** + * 邮箱格式限制 + */ + public static final String EMAIL_PATTERN = "^((([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+(\\.([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(\\\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.)+(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.?"; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/context/PermissionContextHolder.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/context/PermissionContextHolder.java new file mode 100644 index 0000000..f06b8fa --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/context/PermissionContextHolder.java @@ -0,0 +1,27 @@ +package com.ruoyi.common.core.context; + +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import com.ruoyi.common.core.text.Convert; + +/** + * 权限信息 + * + * @author ruoyi + */ +public class PermissionContextHolder +{ + private static final String PERMISSION_CONTEXT_ATTRIBUTES = "PERMISSION_CONTEXT"; + + public static void setContext(String permission) + { + RequestContextHolder.currentRequestAttributes().setAttribute(PERMISSION_CONTEXT_ATTRIBUTES, permission, + RequestAttributes.SCOPE_REQUEST); + } + + public static String getContext() + { + return Convert.toStr(RequestContextHolder.currentRequestAttributes().getAttribute(PERMISSION_CONTEXT_ATTRIBUTES, + RequestAttributes.SCOPE_REQUEST)); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/controller/BaseController.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/controller/BaseController.java new file mode 100644 index 0000000..3f1cb30 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/controller/BaseController.java @@ -0,0 +1,229 @@ +package com.ruoyi.common.core.controller; + +import java.beans.PropertyEditorSupport; +import java.util.Date; +import java.util.List; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.InitBinder; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.AjaxResult.Type; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.page.PageDomain; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.core.page.TableSupport; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.PageUtils; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.ShiroUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.sql.SqlUtil; + +/** + * web层通用数据处理 + * + * @author ruoyi + */ +public class BaseController +{ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** + * 将前台传递过来的日期格式的字符串,自动转化为Date类型 + */ + @InitBinder + public void initBinder(WebDataBinder binder) + { + // Date 类型转换 + binder.registerCustomEditor(Date.class, new PropertyEditorSupport() + { + @Override + public void setAsText(String text) + { + setValue(DateUtils.parseDate(text)); + } + }); + } + + /** + * 设置请求分页数据 + */ + protected void startPage() + { + PageUtils.startPage(); + } + + /** + * 设置请求排序数据 + */ + protected void startOrderBy() + { + PageDomain pageDomain = TableSupport.buildPageRequest(); + if (StringUtils.isNotEmpty(pageDomain.getOrderBy())) + { + String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy()); + PageHelper.orderBy(orderBy); + } + } + + /** + * 清理分页的线程变量 + */ + protected void clearPage() + { + PageUtils.clearPage(); + } + + /** + * 获取request + */ + public HttpServletRequest getRequest() + { + return ServletUtils.getRequest(); + } + + /** + * 获取response + */ + public HttpServletResponse getResponse() + { + return ServletUtils.getResponse(); + } + + /** + * 获取session + */ + public HttpSession getSession() + { + return getRequest().getSession(); + } + + /** + * 响应请求分页数据 + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + protected TableDataInfo getDataTable(List list) + { + TableDataInfo rspData = new TableDataInfo(); + rspData.setCode(0); + rspData.setRows(list); + rspData.setTotal(new PageInfo(list).getTotal()); + return rspData; + } + + /** + * 响应返回结果 + * + * @param rows 影响行数 + * @return 操作结果 + */ + protected AjaxResult toAjax(int rows) + { + return rows > 0 ? success() : error(); + } + + /** + * 响应返回结果 + * + * @param result 结果 + * @return 操作结果 + */ + protected AjaxResult toAjax(boolean result) + { + return result ? success() : error(); + } + + /** + * 返回成功 + */ + public AjaxResult success() + { + return AjaxResult.success(); + } + + /** + * 返回失败消息 + */ + public AjaxResult error() + { + return AjaxResult.error(); + } + + /** + * 返回成功消息 + */ + public AjaxResult success(String message) + { + return AjaxResult.success(message); + } + + /** + * 返回成功数据 + */ + public static AjaxResult success(Object data) + { + return AjaxResult.success("操作成功", data); + } + + /** + * 返回失败消息 + */ + public AjaxResult error(String message) + { + return AjaxResult.error(message); + } + + /** + * 返回错误码消息 + */ + public AjaxResult error(Type type, String message) + { + return new AjaxResult(type, message); + } + + /** + * 页面跳转 + */ + public String redirect(String url) + { + return StringUtils.format("redirect:{}", url); + } + + /** + * 获取用户缓存信息 + */ + public SysUser getSysUser() + { + return ShiroUtils.getSysUser(); + } + + /** + * 设置用户缓存信息 + */ + public void setSysUser(SysUser user) + { + ShiroUtils.setSysUser(user); + } + + /** + * 获取登录用户id + */ + public Long getUserId() + { + return getSysUser().getUserId(); + } + + /** + * 获取登录用户名 + */ + public String getLoginName() + { + return getSysUser().getLoginName(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/AjaxResult.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/AjaxResult.java new file mode 100644 index 0000000..42cb354 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/AjaxResult.java @@ -0,0 +1,217 @@ +package com.ruoyi.common.core.domain; + +import java.util.HashMap; +import java.util.Objects; +import com.ruoyi.common.utils.StringUtils; + +/** + * 操作消息提醒 + * + * @author ruoyi + */ +public class AjaxResult extends HashMap +{ + private static final long serialVersionUID = 1L; + + /** 状态码 */ + public static final String CODE_TAG = "code"; + + /** 返回内容 */ + public static final String MSG_TAG = "msg"; + + /** 数据对象 */ + public static final String DATA_TAG = "data"; + + /** + * 状态类型 + */ + public enum Type + { + /** 成功 */ + SUCCESS(0), + /** 警告 */ + WARN(301), + /** 错误 */ + ERROR(500); + private final int value; + + Type(int value) + { + this.value = value; + } + + public int value() + { + return this.value; + } + } + + /** + * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。 + */ + public AjaxResult() + { + } + + /** + * 初始化一个新创建的 AjaxResult 对象 + * + * @param type 状态类型 + * @param msg 返回内容 + */ + public AjaxResult(Type type, String msg) + { + super.put(CODE_TAG, type.value); + super.put(MSG_TAG, msg); + } + + /** + * 初始化一个新创建的 AjaxResult 对象 + * + * @param type 状态类型 + * @param msg 返回内容 + * @param data 数据对象 + */ + public AjaxResult(Type type, String msg, Object data) + { + super.put(CODE_TAG, type.value); + super.put(MSG_TAG, msg); + if (StringUtils.isNotNull(data)) + { + super.put(DATA_TAG, data); + } + } + + /** + * 返回成功消息 + * + * @return 成功消息 + */ + public static AjaxResult success() + { + return AjaxResult.success("操作成功"); + } + + /** + * 返回成功数据 + * + * @return 成功消息 + */ + public static AjaxResult success(Object data) + { + return AjaxResult.success("操作成功", data); + } + + /** + * 返回成功消息 + * + * @param msg 返回内容 + * @return 成功消息 + */ + public static AjaxResult success(String msg) + { + return AjaxResult.success(msg, null); + } + + /** + * 返回成功消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 成功消息 + */ + public static AjaxResult success(String msg, Object data) + { + return new AjaxResult(Type.SUCCESS, msg, data); + } + + /** + * 返回警告消息 + * + * @param msg 返回内容 + * @return 警告消息 + */ + public static AjaxResult warn(String msg) + { + return AjaxResult.warn(msg, null); + } + + /** + * 返回警告消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 警告消息 + */ + public static AjaxResult warn(String msg, Object data) + { + return new AjaxResult(Type.WARN, msg, data); + } + + /** + * 返回错误消息 + * + * @return + */ + public static AjaxResult error() + { + return AjaxResult.error("操作失败"); + } + + /** + * 返回错误消息 + * + * @param msg 返回内容 + * @return 警告消息 + */ + public static AjaxResult error(String msg) + { + return AjaxResult.error(msg, null); + } + + /** + * 返回错误消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 警告消息 + */ + public static AjaxResult error(String msg, Object data) + { + return new AjaxResult(Type.ERROR, msg, data); + } + + /** + * 是否为成功消息 + * + * @return 结果 + */ + public boolean isSuccess() + { + return !isError(); + } + + /** + * 是否为错误消息 + * + * @return 结果 + */ + public boolean isError() + { + return Objects.equals(Type.ERROR.value, this.get(CODE_TAG)); + } + + /** + * 方便链式调用 + * + * @param key 键 + * @param value 值 + * @return 数据对象 + */ + @Override + public AjaxResult put(String key, Object value) + { + super.put(key, value); + return this; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/BaseEntity.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/BaseEntity.java new file mode 100644 index 0000000..15bf66b --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/BaseEntity.java @@ -0,0 +1,118 @@ +package com.ruoyi.common.core.domain; + +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; + +/** + * Entity基类 + * + * @author ruoyi + */ +public class BaseEntity implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 搜索值 */ + @JsonIgnore + private String searchValue; + + /** 创建者 */ + private String createBy; + + /** 创建时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + /** 更新者 */ + private String updateBy; + + /** 更新时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + + /** 备注 */ + private String remark; + + /** 请求参数 */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Map params; + + public String getSearchValue() + { + return searchValue; + } + + public void setSearchValue(String searchValue) + { + this.searchValue = searchValue; + } + + public String getCreateBy() + { + return createBy; + } + + public void setCreateBy(String createBy) + { + this.createBy = createBy; + } + + public Date getCreateTime() + { + return createTime; + } + + public void setCreateTime(Date createTime) + { + this.createTime = createTime; + } + + public String getUpdateBy() + { + return updateBy; + } + + public void setUpdateBy(String updateBy) + { + this.updateBy = updateBy; + } + + public Date getUpdateTime() + { + return updateTime; + } + + public void setUpdateTime(Date updateTime) + { + this.updateTime = updateTime; + } + + public String getRemark() + { + return remark; + } + + public void setRemark(String remark) + { + this.remark = remark; + } + + public Map getParams() + { + if (params == null) + { + params = new HashMap<>(); + } + return params; + } + + public void setParams(Map params) + { + this.params = params; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/CxSelect.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/CxSelect.java new file mode 100644 index 0000000..3cac069 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/CxSelect.java @@ -0,0 +1,69 @@ +package com.ruoyi.common.core.domain; + +import java.io.Serializable; +import java.util.List; + +/** + * CxSelect树结构实体类 + * + * @author ruoyi + */ +public class CxSelect implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** + * 数据值字段名称 + */ + private String v; + + /** + * 数据标题字段名称 + */ + private String n; + + /** + * 子集数据字段名称 + */ + private List s; + + public CxSelect() + { + } + + public CxSelect(String v, String n) + { + this.v = v; + this.n = n; + } + + public List getS() + { + return s; + } + + public void setN(String n) + { + this.n = n; + } + + public String getN() + { + return n; + } + + public void setS(List s) + { + this.s = s; + } + + public String getV() + { + return v; + } + + public void setV(String v) + { + this.v = v; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/R.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/R.java new file mode 100644 index 0000000..67337dd --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/R.java @@ -0,0 +1,114 @@ +package com.ruoyi.common.core.domain; + +import java.io.Serializable; + +/** + * 响应信息主体 + * + * @author ruoyi + */ +public class R implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 成功 */ + public static final int SUCCESS = 0; + + /** 失败 */ + public static final int FAIL = 500; + + private int code; + + private String msg; + + private T data; + + public static R ok() + { + return restResult(null, SUCCESS, "操作成功"); + } + + public static R ok(T data) + { + return restResult(data, SUCCESS, "操作成功"); + } + + public static R ok(T data, String msg) + { + return restResult(data, SUCCESS, msg); + } + + public static R fail() + { + return restResult(null, FAIL, "操作失败"); + } + + public static R fail(String msg) + { + return restResult(null, FAIL, msg); + } + + public static R fail(T data) + { + return restResult(data, FAIL, "操作失败"); + } + + public static R fail(T data, String msg) + { + return restResult(data, FAIL, msg); + } + + public static R fail(int code, String msg) + { + return restResult(null, code, msg); + } + + private static R restResult(T data, int code, String msg) + { + R apiResult = new R<>(); + apiResult.setCode(code); + apiResult.setData(data); + apiResult.setMsg(msg); + return apiResult; + } + + public int getCode() + { + return code; + } + + public void setCode(int code) + { + this.code = code; + } + + public String getMsg() + { + return msg; + } + + public void setMsg(String msg) + { + this.msg = msg; + } + + public T getData() + { + return data; + } + + public void setData(T data) + { + this.data = data; + } + + public static Boolean isError(R ret) + { + return !isSuccess(ret); + } + + public static Boolean isSuccess(R ret) + { + return R.SUCCESS == ret.getCode(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeEntity.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeEntity.java new file mode 100644 index 0000000..27da2d5 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeEntity.java @@ -0,0 +1,63 @@ +package com.ruoyi.common.core.domain; + +/** + * Tree基类 + * + * @author ruoyi + */ +public class TreeEntity extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 父菜单名称 */ + private String parentName; + + /** 父菜单ID */ + private Long parentId; + + /** 显示顺序 */ + private Integer orderNum; + + /** 祖级列表 */ + private String ancestors; + + public String getParentName() + { + return parentName; + } + + public void setParentName(String parentName) + { + this.parentName = parentName; + } + + public Long getParentId() + { + return parentId; + } + + public void setParentId(Long parentId) + { + this.parentId = parentId; + } + + public Integer getOrderNum() + { + return orderNum; + } + + public void setOrderNum(Integer orderNum) + { + this.orderNum = orderNum; + } + + public String getAncestors() + { + return ancestors; + } + + public void setAncestors(String ancestors) + { + this.ancestors = ancestors; + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/Ztree.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/Ztree.java new file mode 100644 index 0000000..5567a4d --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/Ztree.java @@ -0,0 +1,104 @@ +package com.ruoyi.common.core.domain; + +import java.io.Serializable; + +/** + * Ztree树结构实体类 + * + * @author ruoyi + */ +public class Ztree implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 节点ID */ + private Long id; + + /** 节点父ID */ + private Long pId; + + /** 节点名称 */ + private String name; + + /** 节点标题 */ + private String title; + + /** 是否勾选 */ + private boolean checked = false; + + /** 是否展开 */ + private boolean open = false; + + /** 是否能勾选 */ + private boolean nocheck = false; + + public Long getId() + { + return id; + } + + public void setId(Long id) + { + this.id = id; + } + + public Long getpId() + { + return pId; + } + + public void setpId(Long pId) + { + this.pId = pId; + } + + public String getName() + { + return name; + } + + public void setName(String name) + { + this.name = name; + } + + public String getTitle() + { + return title; + } + + public void setTitle(String title) + { + this.title = title; + } + + public boolean isChecked() + { + return checked; + } + + public void setChecked(boolean checked) + { + this.checked = checked; + } + + public boolean isOpen() + { + return open; + } + + public void setOpen(boolean open) + { + this.open = open; + } + + public boolean isNocheck() + { + return nocheck; + } + + public void setNocheck(boolean nocheck) + { + this.nocheck = nocheck; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java new file mode 100644 index 0000000..c62783f --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java @@ -0,0 +1,203 @@ +package com.ruoyi.common.core.domain.entity; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 部门表 sys_dept + * + * @author ruoyi + */ +public class SysDept extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 部门ID */ + private Long deptId; + + /** 父部门ID */ + private Long parentId; + + /** 祖级列表 */ + private String ancestors; + + /** 部门名称 */ + private String deptName; + + /** 显示顺序 */ + private Integer orderNum; + + /** 负责人 */ + private String leader; + + /** 联系电话 */ + private String phone; + + /** 邮箱 */ + private String email; + + /** 部门状态:0正常,1停用 */ + private String status; + + /** 删除标志(0代表存在 2代表删除) */ + private String delFlag; + + /** 父部门名称 */ + private String parentName; + + /** 排除编号 */ + private Long excludeId; + + public Long getDeptId() + { + return deptId; + } + + public void setDeptId(Long deptId) + { + this.deptId = deptId; + } + + public Long getParentId() + { + return parentId; + } + + public void setParentId(Long parentId) + { + this.parentId = parentId; + } + + public String getAncestors() + { + return ancestors; + } + + public void setAncestors(String ancestors) + { + this.ancestors = ancestors; + } + + @NotBlank(message = "部门名称不能为空") + @Size(min = 0, max = 30, message = "部门名称长度不能超过30个字符") + public String getDeptName() + { + return deptName; + } + + public void setDeptName(String deptName) + { + this.deptName = deptName; + } + + @NotNull(message = "显示顺序不能为空") + public Integer getOrderNum() + { + return orderNum; + } + + public void setOrderNum(Integer orderNum) + { + this.orderNum = orderNum; + } + + public String getLeader() + { + return leader; + } + + public void setLeader(String leader) + { + this.leader = leader; + } + + @Size(min = 0, max = 11, message = "联系电话长度不能超过11个字符") + public String getPhone() + { + return phone; + } + + public void setPhone(String phone) + { + this.phone = phone; + } + + @Email(message = "邮箱格式不正确") + @Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符") + public String getEmail() + { + return email; + } + + public void setEmail(String email) + { + this.email = email; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getDelFlag() + { + return delFlag; + } + + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + public String getParentName() + { + return parentName; + } + + public void setParentName(String parentName) + { + this.parentName = parentName; + } + + @JsonIgnore + public Long getExcludeId() + { + return excludeId; + } + + public void setExcludeId(Long excludeId) + { + this.excludeId = excludeId; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("deptId", getDeptId()) + .append("parentId", getParentId()) + .append("ancestors", getAncestors()) + .append("deptName", getDeptName()) + .append("orderNum", getOrderNum()) + .append("leader", getLeader()) + .append("phone", getPhone()) + .append("email", getEmail()) + .append("status", getStatus()) + .append("delFlag", getDelFlag()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .toString(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictData.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictData.java new file mode 100644 index 0000000..7ef830d --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictData.java @@ -0,0 +1,176 @@ +package com.ruoyi.common.core.domain.entity; + +import javax.validation.constraints.*; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.annotation.Excel.ColumnType; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 字典数据表 sys_dict_data + * + * @author ruoyi + */ +public class SysDictData extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 字典编码 */ + @Excel(name = "字典编码", cellType = ColumnType.NUMERIC) + private Long dictCode; + + /** 字典排序 */ + @Excel(name = "字典排序", cellType = ColumnType.NUMERIC) + private Long dictSort; + + /** 字典标签 */ + @Excel(name = "字典标签") + private String dictLabel; + + /** 字典键值 */ + @Excel(name = "字典键值") + private String dictValue; + + /** 字典类型 */ + @Excel(name = "字典类型") + private String dictType; + + /** 样式属性(其他样式扩展) */ + @Excel(name = "字典样式") + private String cssClass; + + /** 表格字典样式 */ + private String listClass; + + /** 是否默认(Y是 N否) */ + @Excel(name = "是否默认", readConverterExp = "Y=是,N=否") + private String isDefault; + + /** 状态(0正常 1停用) */ + @Excel(name = "状态", readConverterExp = "0=正常,1=停用") + private String status; + + public Long getDictCode() + { + return dictCode; + } + + public void setDictCode(Long dictCode) + { + this.dictCode = dictCode; + } + + public Long getDictSort() + { + return dictSort; + } + + public void setDictSort(Long dictSort) + { + this.dictSort = dictSort; + } + + @NotBlank(message = "字典标签不能为空") + @Size(min = 0, max = 100, message = "字典标签长度不能超过100个字符") + public String getDictLabel() + { + return dictLabel; + } + + public void setDictLabel(String dictLabel) + { + this.dictLabel = dictLabel; + } + + @NotBlank(message = "字典键值不能为空") + @Size(min = 0, max = 100, message = "字典键值长度不能超过100个字符") + public String getDictValue() + { + return dictValue; + } + + public void setDictValue(String dictValue) + { + this.dictValue = dictValue; + } + + @NotBlank(message = "字典类型不能为空") + @Size(min = 0, max = 100, message = "字典类型长度不能超过100个字符") + public String getDictType() + { + return dictType; + } + + public void setDictType(String dictType) + { + this.dictType = dictType; + } + + @Size(min = 0, max = 100, message = "样式属性长度不能超过100个字符") + public String getCssClass() + { + return cssClass; + } + + public void setCssClass(String cssClass) + { + this.cssClass = cssClass; + } + + public String getListClass() + { + return listClass; + } + + public void setListClass(String listClass) + { + this.listClass = listClass; + } + + public boolean getDefault() + { + return UserConstants.YES.equals(this.isDefault); + } + + public String getIsDefault() + { + return isDefault; + } + + public void setIsDefault(String isDefault) + { + this.isDefault = isDefault; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("dictCode", getDictCode()) + .append("dictSort", getDictSort()) + .append("dictLabel", getDictLabel()) + .append("dictValue", getDictValue()) + .append("dictType", getDictType()) + .append("cssClass", getCssClass()) + .append("listClass", getListClass()) + .append("isDefault", getIsDefault()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictType.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictType.java new file mode 100644 index 0000000..9697c8e --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictType.java @@ -0,0 +1,94 @@ +package com.ruoyi.common.core.domain.entity; + +import javax.validation.constraints.*; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.annotation.Excel.ColumnType; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 字典类型表 sys_dict_type + * + * @author ruoyi + */ +public class SysDictType extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 字典主键 */ + @Excel(name = "字典主键", cellType = ColumnType.NUMERIC) + private Long dictId; + + /** 字典名称 */ + @Excel(name = "字典名称") + private String dictName; + + /** 字典类型 */ + @Excel(name = "字典类型") + private String dictType; + + /** 状态(0正常 1停用) */ + @Excel(name = "状态", readConverterExp = "0=正常,1=停用") + private String status; + + public Long getDictId() + { + return dictId; + } + + public void setDictId(Long dictId) + { + this.dictId = dictId; + } + + @NotBlank(message = "字典名称不能为空") + @Size(min = 0, max = 100, message = "字典类型名称长度不能超过100个字符") + public String getDictName() + { + return dictName; + } + + public void setDictName(String dictName) + { + this.dictName = dictName; + } + + @NotBlank(message = "字典类型不能为空") + @Size(min = 0, max = 100, message = "字典类型类型长度不能超过100个字符") + @Pattern(regexp = "^[a-z][a-z0-9_]*$", message = "字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)") + public String getDictType() + { + return dictType; + } + + public void setDictType(String dictType) + { + this.dictType = dictType; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("dictId", getDictId()) + .append("dictName", getDictName()) + .append("dictType", getDictType()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java new file mode 100644 index 0000000..765c4a7 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java @@ -0,0 +1,214 @@ +package com.ruoyi.common.core.domain.entity; + +import java.util.List; +import java.util.ArrayList; +import javax.validation.constraints.*; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 菜单权限表 sys_menu + * + * @author ruoyi + */ +public class SysMenu extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 菜单ID */ + private Long menuId; + + /** 菜单名称 */ + private String menuName; + + /** 父菜单名称 */ + private String parentName; + + /** 父菜单ID */ + private Long parentId; + + /** 显示顺序 */ + private String orderNum; + + /** 菜单URL */ + private String url; + + /** 打开方式(menuItem页签 menuBlank新窗口) */ + private String target; + + /** 类型(M目录 C菜单 F按钮) */ + private String menuType; + + /** 菜单状态(0显示 1隐藏) */ + private String visible; + + /** 是否刷新(0刷新 1不刷新) */ + private String isRefresh; + + /** 权限字符串 */ + private String perms; + + /** 菜单图标 */ + private String icon; + + /** 子菜单 */ + private List children = new ArrayList(); + + public Long getMenuId() + { + return menuId; + } + + public void setMenuId(Long menuId) + { + this.menuId = menuId; + } + + @NotBlank(message = "菜单名称不能为空") + @Size(min = 0, max = 50, message = "菜单名称长度不能超过50个字符") + public String getMenuName() + { + return menuName; + } + + public void setMenuName(String menuName) + { + this.menuName = menuName; + } + + public String getParentName() + { + return parentName; + } + + public void setParentName(String parentName) + { + this.parentName = parentName; + } + + public Long getParentId() + { + return parentId; + } + + public void setParentId(Long parentId) + { + this.parentId = parentId; + } + + @NotBlank(message = "显示顺序不能为空") + public String getOrderNum() + { + return orderNum; + } + + public void setOrderNum(String orderNum) + { + this.orderNum = orderNum; + } + + @Size(min = 0, max = 200, message = "请求地址不能超过200个字符") + public String getUrl() + { + return url; + } + + public void setUrl(String url) + { + this.url = url; + } + + public String getTarget() + { + return target; + } + + public void setTarget(String target) + { + this.target = target; + } + + @NotBlank(message = "菜单类型不能为空") + public String getMenuType() + { + return menuType; + } + + public void setMenuType(String menuType) + { + this.menuType = menuType; + } + + public String getVisible() + { + return visible; + } + + public void setVisible(String visible) + { + this.visible = visible; + } + + public String getIsRefresh() + { + return isRefresh; + } + + public void setIsRefresh(String isRefresh) + { + this.isRefresh = isRefresh; + } + + @Size(min = 0, max = 100, message = "权限标识长度不能超过100个字符") + public String getPerms() + { + return perms; + } + + public void setPerms(String perms) + { + this.perms = perms; + } + + public String getIcon() + { + return icon; + } + + public void setIcon(String icon) + { + this.icon = icon; + } + + public List getChildren() + { + return children; + } + + public void setChildren(List children) + { + this.children = children; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("menuId", getMenuId()) + .append("menuName", getMenuName()) + .append("parentId", getParentId()) + .append("orderNum", getOrderNum()) + .append("url", getUrl()) + .append("target", getTarget()) + .append("menuType", getMenuType()) + .append("visible", getVisible()) + .append("perms", getPerms()) + .append("icon", getIcon()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysRole.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysRole.java new file mode 100644 index 0000000..b6ffb5e --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysRole.java @@ -0,0 +1,211 @@ +package com.ruoyi.common.core.domain.entity; + +import java.util.Set; +import javax.validation.constraints.*; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.annotation.Excel.ColumnType; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 角色表 sys_role + * + * @author ruoyi + */ +public class SysRole extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 角色ID */ + @Excel(name = "角色序号", cellType = ColumnType.NUMERIC) + private Long roleId; + + /** 角色名称 */ + @Excel(name = "角色名称") + private String roleName; + + /** 角色权限 */ + @Excel(name = "角色权限") + private String roleKey; + + /** 角色排序 */ + @Excel(name = "角色排序", cellType = ColumnType.NUMERIC) + private String roleSort; + + /** 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限) */ + @Excel(name = "数据范围", readConverterExp = "1=所有数据权限,2=自定义数据权限,3=本部门数据权限,4=本部门及以下数据权限,5=仅本人数据权限") + private String dataScope; + + /** 角色状态(0正常 1停用) */ + @Excel(name = "角色状态", readConverterExp = "0=正常,1=停用") + private String status; + + /** 删除标志(0代表存在 2代表删除) */ + private String delFlag; + + /** 用户是否存在此角色标识 默认不存在 */ + private boolean flag = false; + + /** 菜单组 */ + private Long[] menuIds; + + /** 部门组(数据权限) */ + private Long[] deptIds; + + /** 角色菜单权限 */ + private Set permissions; + + public SysRole() + { + + } + + public SysRole(Long roleId) + { + this.roleId = roleId; + } + + public Long getRoleId() + { + return roleId; + } + + public void setRoleId(Long roleId) + { + this.roleId = roleId; + } + + public boolean isAdmin() + { + return isAdmin(this.roleId); + } + + public static boolean isAdmin(Long roleId) + { + return roleId != null && 1L == roleId; + } + + public String getDataScope() + { + return dataScope; + } + + public void setDataScope(String dataScope) + { + this.dataScope = dataScope; + } + + @NotBlank(message = "角色名称不能为空") + @Size(min = 0, max = 30, message = "角色名称长度不能超过30个字符") + public String getRoleName() + { + return roleName; + } + + public void setRoleName(String roleName) + { + this.roleName = roleName; + } + + @NotBlank(message = "权限字符不能为空") + @Size(min = 0, max = 100, message = "权限字符长度不能超过100个字符") + public String getRoleKey() + { + return roleKey; + } + + public void setRoleKey(String roleKey) + { + this.roleKey = roleKey; + } + + @NotBlank(message = "显示顺序不能为空") + public String getRoleSort() + { + return roleSort; + } + + public void setRoleSort(String roleSort) + { + this.roleSort = roleSort; + } + + public String getStatus() + { + return status; + } + + public String getDelFlag() + { + return delFlag; + } + + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + public void setStatus(String status) + { + this.status = status; + } + + public boolean isFlag() + { + return flag; + } + + public void setFlag(boolean flag) + { + this.flag = flag; + } + + public Long[] getMenuIds() + { + return menuIds; + } + + public void setMenuIds(Long[] menuIds) + { + this.menuIds = menuIds; + } + + public Long[] getDeptIds() + { + return deptIds; + } + + public void setDeptIds(Long[] deptIds) + { + this.deptIds = deptIds; + } + + public Set getPermissions() + { + return permissions; + } + + public void setPermissions(Set permissions) + { + this.permissions = permissions; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("roleId", getRoleId()) + .append("roleName", getRoleName()) + .append("roleKey", getRoleKey()) + .append("roleSort", getRoleSort()) + .append("dataScope", getDataScope()) + .append("status", getStatus()) + .append("delFlag", getDelFlag()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java new file mode 100644 index 0000000..763aab5 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java @@ -0,0 +1,385 @@ +package com.ruoyi.common.core.domain.entity; + +import java.util.Date; +import java.util.List; +import javax.validation.constraints.*; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.annotation.Excel.ColumnType; +import com.ruoyi.common.annotation.Excel.Type; +import com.ruoyi.common.annotation.Excels; +import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.common.xss.Xss; + +/** + * 用户对象 sys_user + * + * @author ruoyi + */ +public class SysUser extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 用户ID */ + @Excel(name = "用户序号", cellType = ColumnType.NUMERIC, prompt = "用户编号") + private Long userId; + + /** 部门ID */ + @Excel(name = "部门编号", type = Type.IMPORT) + private Long deptId; + + /** 部门父ID */ + private Long parentId; + + /** 角色ID */ + private Long roleId; + + /** 登录名称 */ + @Excel(name = "登录名称") + private String loginName; + + /** 用户名称 */ + @Excel(name = "用户名称") + private String userName; + + /** 用户类型 */ + private String userType; + + /** 用户邮箱 */ + @Excel(name = "用户邮箱") + private String email; + + /** 手机号码 */ + @Excel(name = "手机号码") + private String phonenumber; + + /** 用户性别 */ + @Excel(name = "用户性别", readConverterExp = "0=男,1=女,2=未知") + private String sex; + + /** 用户头像 */ + private String avatar; + + /** 密码 */ + private String password; + + /** 盐加密 */ + private String salt; + + /** 帐号状态(0正常 1停用) */ + @Excel(name = "帐号状态", readConverterExp = "0=正常,1=停用") + private String status; + + /** 删除标志(0代表存在 2代表删除) */ + private String delFlag; + + /** 最后登录IP */ + @Excel(name = "最后登录IP", type = Type.EXPORT) + private String loginIp; + + /** 最后登录时间 */ + @Excel(name = "最后登录时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Type.EXPORT) + private Date loginDate; + + /** 密码最后更新时间 */ + private Date pwdUpdateDate; + + /** 部门对象 */ + @Excels({ + @Excel(name = "部门名称", targetAttr = "deptName", type = Type.EXPORT), + @Excel(name = "部门负责人", targetAttr = "leader", type = Type.EXPORT) + }) + private SysDept dept; + + private List roles; + + /** 角色组 */ + private Long[] roleIds; + + /** 岗位组 */ + private Long[] postIds; + + public SysUser() + { + + } + + public SysUser(Long userId) + { + this.userId = userId; + } + + public Long getUserId() + { + return userId; + } + + public void setUserId(Long userId) + { + this.userId = userId; + } + + public boolean isAdmin() + { + return isAdmin(this.userId); + } + + public static boolean isAdmin(Long userId) + { + return userId != null && 1L == userId; + } + + public Long getDeptId() + { + return deptId; + } + + public void setDeptId(Long deptId) + { + this.deptId = deptId; + } + + public Long getParentId() + { + return parentId; + } + + public void setParentId(Long parentId) + { + this.parentId = parentId; + } + + public Long getRoleId() + { + return roleId; + } + + public void setRoleId(Long roleId) + { + this.roleId = roleId; + } + + @Xss(message = "登录账号不能包含脚本字符") + @NotBlank(message = "登录账号不能为空") + @Size(min = 0, max = 30, message = "登录账号长度不能超过30个字符") + public String getLoginName() + { + return loginName; + } + + public void setLoginName(String loginName) + { + this.loginName = loginName; + } + + @Xss(message = "用户昵称不能包含脚本字符") + @Size(min = 0, max = 30, message = "用户昵称长度不能超过30个字符") + public String getUserName() + { + return userName; + } + + public void setUserName(String userName) + { + this.userName = userName; + } + + public String getUserType() + { + return userType; + } + + public void setUserType(String userType) + { + this.userType = userType; + } + + @Email(message = "邮箱格式不正确") + @Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符") + public String getEmail() + { + return email; + } + + public void setEmail(String email) + { + this.email = email; + } + + @Size(min = 0, max = 11, message = "手机号码长度不能超过11个字符") + public String getPhonenumber() + { + return phonenumber; + } + + public void setPhonenumber(String phonenumber) + { + this.phonenumber = phonenumber; + } + + public String getSex() + { + return sex; + } + + public void setSex(String sex) + { + this.sex = sex; + } + + public String getAvatar() + { + return avatar; + } + + public void setAvatar(String avatar) + { + this.avatar = avatar; + } + + @JsonIgnore + public String getPassword() + { + return password; + } + + public void setPassword(String password) + { + this.password = password; + } + + @JsonIgnore + public String getSalt() + { + return salt; + } + + public void setSalt(String salt) + { + this.salt = salt; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getDelFlag() + { + return delFlag; + } + + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + public String getLoginIp() + { + return loginIp; + } + + public void setLoginIp(String loginIp) + { + this.loginIp = loginIp; + } + + public Date getLoginDate() + { + return loginDate; + } + + public void setLoginDate(Date loginDate) + { + this.loginDate = loginDate; + } + + public Date getPwdUpdateDate() + { + return pwdUpdateDate; + } + + public void setPwdUpdateDate(Date pwdUpdateDate) + { + this.pwdUpdateDate = pwdUpdateDate; + } + + public SysDept getDept() + { + if (dept == null) + { + dept = new SysDept(); + } + return dept; + } + + public void setDept(SysDept dept) + { + this.dept = dept; + } + + public List getRoles() + { + return roles; + } + + public void setRoles(List roles) + { + this.roles = roles; + } + + public Long[] getRoleIds() + { + return roleIds; + } + + public void setRoleIds(Long[] roleIds) + { + this.roleIds = roleIds; + } + + public Long[] getPostIds() + { + return postIds; + } + + public void setPostIds(Long[] postIds) + { + this.postIds = postIds; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("userId", getUserId()) + .append("deptId", getDeptId()) + .append("loginName", getLoginName()) + .append("userName", getUserName()) + .append("userType", getUserType()) + .append("email", getEmail()) + .append("phonenumber", getPhonenumber()) + .append("sex", getSex()) + .append("avatar", getAvatar()) + .append("password", getPassword()) + .append("salt", getSalt()) + .append("status", getStatus()) + .append("delFlag", getDelFlag()) + .append("loginIp", getLoginIp()) + .append("loginDate", getLoginDate()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .append("dept", getDept()) + .append("roles", getRoles()) + .toString(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/page/PageDomain.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/page/PageDomain.java new file mode 100644 index 0000000..61f6d85 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/page/PageDomain.java @@ -0,0 +1,89 @@ +package com.ruoyi.common.core.page; + +import com.ruoyi.common.utils.StringUtils; + +/** + * 分页数据 + * + * @author ruoyi + */ +public class PageDomain +{ + /** 当前记录起始索引 */ + private Integer pageNum; + + /** 每页显示记录数 */ + private Integer pageSize; + + /** 排序列 */ + private String orderByColumn; + + /** 排序的方向desc或者asc */ + private String isAsc = "asc"; + + /** 分页参数合理化 */ + private Boolean reasonable = true; + + public String getOrderBy() + { + if (StringUtils.isEmpty(orderByColumn)) + { + return ""; + } + return StringUtils.toUnderScoreCase(orderByColumn) + " " + isAsc; + } + + public Integer getPageNum() + { + return pageNum; + } + + public void setPageNum(Integer pageNum) + { + this.pageNum = pageNum; + } + + public Integer getPageSize() + { + return pageSize; + } + + public void setPageSize(Integer pageSize) + { + this.pageSize = pageSize; + } + + public String getOrderByColumn() + { + return orderByColumn; + } + + public void setOrderByColumn(String orderByColumn) + { + this.orderByColumn = orderByColumn; + } + + public String getIsAsc() + { + return isAsc; + } + + public void setIsAsc(String isAsc) + { + this.isAsc = isAsc; + } + + public Boolean getReasonable() + { + if (StringUtils.isNull(reasonable)) + { + return Boolean.TRUE; + } + return reasonable; + } + + public void setReasonable(Boolean reasonable) + { + this.reasonable = reasonable; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableDataInfo.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableDataInfo.java new file mode 100644 index 0000000..373e592 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableDataInfo.java @@ -0,0 +1,85 @@ +package com.ruoyi.common.core.page; + +import java.io.Serializable; +import java.util.List; + +/** + * 表格分页数据对象 + * + * @author ruoyi + */ +public class TableDataInfo implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 总记录数 */ + private long total; + + /** 列表数据 */ + private List rows; + + /** 消息状态码 */ + private int code; + + /** 消息内容 */ + private String msg; + + /** + * 表格数据对象 + */ + public TableDataInfo() + { + } + + /** + * 分页 + * + * @param list 列表数据 + * @param total 总记录数 + */ + public TableDataInfo(List list, int total) + { + this.rows = list; + this.total = total; + } + + public long getTotal() + { + return total; + } + + public void setTotal(long total) + { + this.total = total; + } + + public List getRows() + { + return rows; + } + + public void setRows(List rows) + { + this.rows = rows; + } + + public int getCode() + { + return code; + } + + public void setCode(int code) + { + this.code = code; + } + + public String getMsg() + { + return msg; + } + + public void setMsg(String msg) + { + this.msg = msg; + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableSupport.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableSupport.java new file mode 100644 index 0000000..a120c30 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableSupport.java @@ -0,0 +1,56 @@ +package com.ruoyi.common.core.page; + +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.utils.ServletUtils; + +/** + * 表格数据处理 + * + * @author ruoyi + */ +public class TableSupport +{ + /** + * 当前记录起始索引 + */ + public static final String PAGE_NUM = "pageNum"; + + /** + * 每页显示记录数 + */ + public static final String PAGE_SIZE = "pageSize"; + + /** + * 排序列 + */ + public static final String ORDER_BY_COLUMN = "orderByColumn"; + + /** + * 排序的方向 "desc" 或者 "asc". + */ + public static final String IS_ASC = "isAsc"; + + /** + * 分页参数合理化 + */ + public static final String REASONABLE = "reasonable"; + + /** + * 封装分页对象 + */ + public static PageDomain getPageDomain() + { + PageDomain pageDomain = new PageDomain(); + pageDomain.setPageNum(Convert.toInt(ServletUtils.getParameter(PAGE_NUM), 1)); + pageDomain.setPageSize(Convert.toInt(ServletUtils.getParameter(PAGE_SIZE), 10)); + pageDomain.setOrderByColumn(ServletUtils.getParameter(ORDER_BY_COLUMN)); + pageDomain.setIsAsc(ServletUtils.getParameter(IS_ASC)); + pageDomain.setReasonable(ServletUtils.getParameterToBool(REASONABLE)); + return pageDomain; + } + + public static PageDomain buildPageRequest() + { + return getPageDomain(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/text/CharsetKit.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/text/CharsetKit.java new file mode 100644 index 0000000..84124aa --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/text/CharsetKit.java @@ -0,0 +1,86 @@ +package com.ruoyi.common.core.text; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import com.ruoyi.common.utils.StringUtils; + +/** + * 字符集工具类 + * + * @author ruoyi + */ +public class CharsetKit +{ + /** ISO-8859-1 */ + public static final String ISO_8859_1 = "ISO-8859-1"; + /** UTF-8 */ + public static final String UTF_8 = "UTF-8"; + /** GBK */ + public static final String GBK = "GBK"; + + /** ISO-8859-1 */ + public static final Charset CHARSET_ISO_8859_1 = Charset.forName(ISO_8859_1); + /** UTF-8 */ + public static final Charset CHARSET_UTF_8 = Charset.forName(UTF_8); + /** GBK */ + public static final Charset CHARSET_GBK = Charset.forName(GBK); + + /** + * 转换为Charset对象 + * + * @param charset 字符集,为空则返回默认字符集 + * @return Charset + */ + public static Charset charset(String charset) + { + return StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset); + } + + /** + * 转换字符串的字符集编码 + * + * @param source 字符串 + * @param srcCharset 源字符集,默认ISO-8859-1 + * @param destCharset 目标字符集,默认UTF-8 + * @return 转换后的字符集 + */ + public static String convert(String source, String srcCharset, String destCharset) + { + return convert(source, Charset.forName(srcCharset), Charset.forName(destCharset)); + } + + /** + * 转换字符串的字符集编码 + * + * @param source 字符串 + * @param srcCharset 源字符集,默认ISO-8859-1 + * @param destCharset 目标字符集,默认UTF-8 + * @return 转换后的字符集 + */ + public static String convert(String source, Charset srcCharset, Charset destCharset) + { + if (null == srcCharset) + { + srcCharset = StandardCharsets.ISO_8859_1; + } + + if (null == destCharset) + { + destCharset = StandardCharsets.UTF_8; + } + + if (StringUtils.isEmpty(source) || srcCharset.equals(destCharset)) + { + return source; + } + return new String(source.getBytes(srcCharset), destCharset); + } + + /** + * @return 系统字符集编码 + */ + public static String systemCharset() + { + return Charset.defaultCharset().name(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/text/Convert.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/text/Convert.java new file mode 100644 index 0000000..a864872 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/text/Convert.java @@ -0,0 +1,1000 @@ +package com.ruoyi.common.core.text; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.text.NumberFormat; +import java.util.Set; +import com.ruoyi.common.utils.StringUtils; +import org.apache.commons.lang3.ArrayUtils; + +/** + * 类型转换器 + * + * @author ruoyi + */ +public class Convert +{ + /** + * 转换为字符串
    + * 如果给定的值为null,或者转换失败,返回默认值
    + * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static String toStr(Object value, String defaultValue) + { + if (null == value) + { + return defaultValue; + } + if (value instanceof String) + { + return (String) value; + } + return value.toString(); + } + + /** + * 转换为字符串
    + * 如果给定的值为null,或者转换失败,返回默认值null
    + * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static String toStr(Object value) + { + return toStr(value, null); + } + + /** + * 转换为字符
    + * 如果给定的值为null,或者转换失败,返回默认值
    + * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Character toChar(Object value, Character defaultValue) + { + if (null == value) + { + return defaultValue; + } + if (value instanceof Character) + { + return (Character) value; + } + + final String valueStr = toStr(value, null); + return StringUtils.isEmpty(valueStr) ? defaultValue : valueStr.charAt(0); + } + + /** + * 转换为字符
    + * 如果给定的值为null,或者转换失败,返回默认值null
    + * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Character toChar(Object value) + { + return toChar(value, null); + } + + /** + * 转换为byte
    + * 如果给定的值为null,或者转换失败,返回默认值
    + * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Byte toByte(Object value, Byte defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Byte) + { + return (Byte) value; + } + if (value instanceof Number) + { + return ((Number) value).byteValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Byte.parseByte(valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为byte
    + * 如果给定的值为null,或者转换失败,返回默认值null
    + * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Byte toByte(Object value) + { + return toByte(value, null); + } + + /** + * 转换为Short
    + * 如果给定的值为null,或者转换失败,返回默认值
    + * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Short toShort(Object value, Short defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Short) + { + return (Short) value; + } + if (value instanceof Number) + { + return ((Number) value).shortValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Short.parseShort(valueStr.trim()); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为Short
    + * 如果给定的值为null,或者转换失败,返回默认值null
    + * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Short toShort(Object value) + { + return toShort(value, null); + } + + /** + * 转换为Number
    + * 如果给定的值为空,或者转换失败,返回默认值
    + * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Number toNumber(Object value, Number defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Number) + { + return (Number) value; + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return NumberFormat.getInstance().parse(valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为Number
    + * 如果给定的值为空,或者转换失败,返回默认值null
    + * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Number toNumber(Object value) + { + return toNumber(value, null); + } + + /** + * 转换为int
    + * 如果给定的值为空,或者转换失败,返回默认值
    + * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Integer toInt(Object value, Integer defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Integer) + { + return (Integer) value; + } + if (value instanceof Number) + { + return ((Number) value).intValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Integer.parseInt(valueStr.trim()); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为int
    + * 如果给定的值为null,或者转换失败,返回默认值null
    + * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Integer toInt(Object value) + { + return toInt(value, null); + } + + /** + * 转换为Integer数组
    + * + * @param str 被转换的值 + * @return 结果 + */ + public static Integer[] toIntArray(String str) + { + return toIntArray(",", str); + } + + /** + * 转换为Long数组
    + * + * @param str 被转换的值 + * @return 结果 + */ + public static Long[] toLongArray(String str) + { + return toLongArray(",", str); + } + + /** + * 转换为Integer数组
    + * + * @param split 分隔符 + * @param split 被转换的值 + * @return 结果 + */ + public static Integer[] toIntArray(String split, String str) + { + if (StringUtils.isEmpty(str)) + { + return new Integer[] {}; + } + String[] arr = str.split(split); + final Integer[] ints = new Integer[arr.length]; + for (int i = 0; i < arr.length; i++) + { + final Integer v = toInt(arr[i], 0); + ints[i] = v; + } + return ints; + } + + /** + * 转换为Long数组
    + * + * @param split 分隔符 + * @param str 被转换的值 + * @return 结果 + */ + public static Long[] toLongArray(String split, String str) + { + if (StringUtils.isEmpty(str)) + { + return new Long[] {}; + } + String[] arr = str.split(split); + final Long[] longs = new Long[arr.length]; + for (int i = 0; i < arr.length; i++) + { + final Long v = toLong(arr[i], null); + longs[i] = v; + } + return longs; + } + + /** + * 转换为String数组
    + * + * @param str 被转换的值 + * @return 结果 + */ + public static String[] toStrArray(String str) + { + return toStrArray(",", str); + } + + /** + * 转换为String数组
    + * + * @param split 分隔符 + * @param split 被转换的值 + * @return 结果 + */ + public static String[] toStrArray(String split, String str) + { + return str.split(split); + } + + /** + * 转换为long
    + * 如果给定的值为空,或者转换失败,返回默认值
    + * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Long toLong(Object value, Long defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Long) + { + return (Long) value; + } + if (value instanceof Number) + { + return ((Number) value).longValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + // 支持科学计数法 + return new BigDecimal(valueStr.trim()).longValue(); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为long
    + * 如果给定的值为null,或者转换失败,返回默认值null
    + * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Long toLong(Object value) + { + return toLong(value, null); + } + + /** + * 转换为double
    + * 如果给定的值为空,或者转换失败,返回默认值
    + * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Double toDouble(Object value, Double defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Double) + { + return (Double) value; + } + if (value instanceof Number) + { + return ((Number) value).doubleValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + // 支持科学计数法 + return new BigDecimal(valueStr.trim()).doubleValue(); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为double
    + * 如果给定的值为空,或者转换失败,返回默认值null
    + * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Double toDouble(Object value) + { + return toDouble(value, null); + } + + /** + * 转换为Float
    + * 如果给定的值为空,或者转换失败,返回默认值
    + * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Float toFloat(Object value, Float defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Float) + { + return (Float) value; + } + if (value instanceof Number) + { + return ((Number) value).floatValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Float.parseFloat(valueStr.trim()); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为Float
    + * 如果给定的值为空,或者转换失败,返回默认值null
    + * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Float toFloat(Object value) + { + return toFloat(value, null); + } + + /** + * 转换为boolean
    + * String支持的值为:true、false、yes、ok、no,1,0 如果给定的值为空,或者转换失败,返回默认值
    + * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Boolean toBool(Object value, Boolean defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Boolean) + { + return (Boolean) value; + } + String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + valueStr = valueStr.trim().toLowerCase(); + switch (valueStr) + { + case "true": + case "yes": + case "ok": + case "1": + return true; + case "false": + case "no": + case "0": + return false; + default: + return defaultValue; + } + } + + /** + * 转换为boolean
    + * 如果给定的值为空,或者转换失败,返回默认值null
    + * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Boolean toBool(Object value) + { + return toBool(value, null); + } + + /** + * 转换为Enum对象
    + * 如果给定的值为空,或者转换失败,返回默认值
    + * + * @param clazz Enum的Class + * @param value 值 + * @param defaultValue 默认值 + * @return Enum + */ + public static > E toEnum(Class clazz, Object value, E defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (clazz.isAssignableFrom(value.getClass())) + { + @SuppressWarnings("unchecked") + E myE = (E) value; + return myE; + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Enum.valueOf(clazz, valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为Enum对象
    + * 如果给定的值为空,或者转换失败,返回默认值null
    + * + * @param clazz Enum的Class + * @param value 值 + * @return Enum + */ + public static > E toEnum(Class clazz, Object value) + { + return toEnum(clazz, value, null); + } + + /** + * 转换为BigInteger
    + * 如果给定的值为空,或者转换失败,返回默认值
    + * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static BigInteger toBigInteger(Object value, BigInteger defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof BigInteger) + { + return (BigInteger) value; + } + if (value instanceof Long) + { + return BigInteger.valueOf((Long) value); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return new BigInteger(valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为BigInteger
    + * 如果给定的值为空,或者转换失败,返回默认值null
    + * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static BigInteger toBigInteger(Object value) + { + return toBigInteger(value, null); + } + + /** + * 转换为BigDecimal
    + * 如果给定的值为空,或者转换失败,返回默认值
    + * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static BigDecimal toBigDecimal(Object value, BigDecimal defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof BigDecimal) + { + return (BigDecimal) value; + } + if (value instanceof Long) + { + return new BigDecimal((Long) value); + } + if (value instanceof Double) + { + return BigDecimal.valueOf((Double) value); + } + if (value instanceof Integer) + { + return new BigDecimal((Integer) value); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return new BigDecimal(valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为BigDecimal
    + * 如果给定的值为空,或者转换失败,返回默认值
    + * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static BigDecimal toBigDecimal(Object value) + { + return toBigDecimal(value, null); + } + + /** + * 将对象转为字符串
    + * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 + * + * @param obj 对象 + * @return 字符串 + */ + public static String utf8Str(Object obj) + { + return str(obj, CharsetKit.CHARSET_UTF_8); + } + + /** + * 将对象转为字符串
    + * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 + * + * @param obj 对象 + * @param charsetName 字符集 + * @return 字符串 + */ + public static String str(Object obj, String charsetName) + { + return str(obj, Charset.forName(charsetName)); + } + + /** + * 将对象转为字符串
    + * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 + * + * @param obj 对象 + * @param charset 字符集 + * @return 字符串 + */ + public static String str(Object obj, Charset charset) + { + if (null == obj) + { + return null; + } + + if (obj instanceof String) + { + return (String) obj; + } + else if (obj instanceof byte[]) + { + return str((byte[]) obj, charset); + } + else if (obj instanceof Byte[]) + { + byte[] bytes = ArrayUtils.toPrimitive((Byte[]) obj); + return str(bytes, charset); + } + else if (obj instanceof ByteBuffer) + { + return str((ByteBuffer) obj, charset); + } + return obj.toString(); + } + + /** + * 将byte数组转为字符串 + * + * @param bytes byte数组 + * @param charset 字符集 + * @return 字符串 + */ + public static String str(byte[] bytes, String charset) + { + return str(bytes, StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset)); + } + + /** + * 解码字节码 + * + * @param data 字符串 + * @param charset 字符集,如果此字段为空,则解码的结果取决于平台 + * @return 解码后的字符串 + */ + public static String str(byte[] data, Charset charset) + { + if (data == null) + { + return null; + } + + if (null == charset) + { + return new String(data); + } + return new String(data, charset); + } + + /** + * 将编码的byteBuffer数据转换为字符串 + * + * @param data 数据 + * @param charset 字符集,如果为空使用当前系统字符集 + * @return 字符串 + */ + public static String str(ByteBuffer data, String charset) + { + if (data == null) + { + return null; + } + + return str(data, Charset.forName(charset)); + } + + /** + * 将编码的byteBuffer数据转换为字符串 + * + * @param data 数据 + * @param charset 字符集,如果为空使用当前系统字符集 + * @return 字符串 + */ + public static String str(ByteBuffer data, Charset charset) + { + if (null == charset) + { + charset = Charset.defaultCharset(); + } + return charset.decode(data).toString(); + } + + // ----------------------------------------------------------------------- 全角半角转换 + /** + * 半角转全角 + * + * @param input String. + * @return 全角字符串. + */ + public static String toSBC(String input) + { + return toSBC(input, null); + } + + /** + * 半角转全角 + * + * @param input String + * @param notConvertSet 不替换的字符集合 + * @return 全角字符串. + */ + public static String toSBC(String input, Set notConvertSet) + { + char c[] = input.toCharArray(); + for (int i = 0; i < c.length; i++) + { + if (null != notConvertSet && notConvertSet.contains(c[i])) + { + // 跳过不替换的字符 + continue; + } + + if (c[i] == ' ') + { + c[i] = '\u3000'; + } + else if (c[i] < '\177') + { + c[i] = (char) (c[i] + 65248); + + } + } + return new String(c); + } + + /** + * 全角转半角 + * + * @param input String. + * @return 半角字符串 + */ + public static String toDBC(String input) + { + return toDBC(input, null); + } + + /** + * 替换全角为半角 + * + * @param text 文本 + * @param notConvertSet 不替换的字符集合 + * @return 替换后的字符 + */ + public static String toDBC(String text, Set notConvertSet) + { + char c[] = text.toCharArray(); + for (int i = 0; i < c.length; i++) + { + if (null != notConvertSet && notConvertSet.contains(c[i])) + { + // 跳过不替换的字符 + continue; + } + + if (c[i] == '\u3000') + { + c[i] = ' '; + } + else if (c[i] > '\uFF00' && c[i] < '\uFF5F') + { + c[i] = (char) (c[i] - 65248); + } + } + String returnString = new String(c); + + return returnString; + } + + /** + * 数字金额大写转换 先写个完整的然后将如零拾替换成零 + * + * @param n 数字 + * @return 中文大写数字 + */ + public static String digitUppercase(double n) + { + String[] fraction = { "角", "分" }; + String[] digit = { "零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖" }; + String[][] unit = { { "元", "万", "亿" }, { "", "拾", "佰", "仟" } }; + + String head = n < 0 ? "负" : ""; + n = Math.abs(n); + + String s = ""; + for (int i = 0; i < fraction.length; i++) + { + s += (digit[(int) (Math.floor(n * 10 * Math.pow(10, i)) % 10)] + fraction[i]).replaceAll("(零.)+", ""); + } + if (s.length() < 1) + { + s = "整"; + } + int integerPart = (int) Math.floor(n); + + for (int i = 0; i < unit[0].length && integerPart > 0; i++) + { + String p = ""; + for (int j = 0; j < unit[1].length && n > 0; j++) + { + p = digit[integerPart % 10] + unit[1][j] + p; + integerPart = integerPart / 10; + } + s = p.replaceAll("(零.)*零$", "").replaceAll("^$", "零") + unit[0][i] + s; + } + return head + s.replaceAll("(零.)*零元", "元").replaceFirst("(零.)+", "").replaceAll("(零.)+", "零").replaceAll("^整$", "零元整"); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/text/StrFormatter.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/text/StrFormatter.java new file mode 100644 index 0000000..c78ac77 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/text/StrFormatter.java @@ -0,0 +1,92 @@ +package com.ruoyi.common.core.text; + +import com.ruoyi.common.utils.StringUtils; + +/** + * 字符串格式化 + * + * @author ruoyi + */ +public class StrFormatter +{ + public static final String EMPTY_JSON = "{}"; + public static final char C_BACKSLASH = '\\'; + public static final char C_DELIM_START = '{'; + public static final char C_DELIM_END = '}'; + + /** + * 格式化字符串
    + * 此方法只是简单将占位符 {} 按照顺序替换为参数
    + * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可
    + * 例:
    + * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b
    + * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a
    + * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b
    + * + * @param strPattern 字符串模板 + * @param argArray 参数列表 + * @return 结果 + */ + public static String format(final String strPattern, final Object... argArray) + { + if (StringUtils.isEmpty(strPattern) || StringUtils.isEmpty(argArray)) + { + return strPattern; + } + final int strPatternLength = strPattern.length(); + + // 初始化定义好的长度以获得更好的性能 + StringBuilder sbuf = new StringBuilder(strPatternLength + 50); + + int handledPosition = 0; + int delimIndex;// 占位符所在位置 + for (int argIndex = 0; argIndex < argArray.length; argIndex++) + { + delimIndex = strPattern.indexOf(EMPTY_JSON, handledPosition); + if (delimIndex == -1) + { + if (handledPosition == 0) + { + return strPattern; + } + else + { // 字符串模板剩余部分不再包含占位符,加入剩余部分后返回结果 + sbuf.append(strPattern, handledPosition, strPatternLength); + return sbuf.toString(); + } + } + else + { + if (delimIndex > 0 && strPattern.charAt(delimIndex - 1) == C_BACKSLASH) + { + if (delimIndex > 1 && strPattern.charAt(delimIndex - 2) == C_BACKSLASH) + { + // 转义符之前还有一个转义符,占位符依旧有效 + sbuf.append(strPattern, handledPosition, delimIndex - 1); + sbuf.append(Convert.utf8Str(argArray[argIndex])); + handledPosition = delimIndex + 2; + } + else + { + // 占位符被转义 + argIndex--; + sbuf.append(strPattern, handledPosition, delimIndex - 1); + sbuf.append(C_DELIM_START); + handledPosition = delimIndex + 1; + } + } + else + { + // 正常占位符 + sbuf.append(strPattern, handledPosition, delimIndex); + sbuf.append(Convert.utf8Str(argArray[argIndex])); + handledPosition = delimIndex + 2; + } + } + } + // 加入最后一个占位符后所有的字符 + sbuf.append(strPattern, handledPosition, strPattern.length()); + + return sbuf.toString(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessStatus.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessStatus.java new file mode 100644 index 0000000..c6640bb --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessStatus.java @@ -0,0 +1,19 @@ +package com.ruoyi.common.enums; + +/** + * 操作状态 + * + * @author ruoyi + */ +public enum BusinessStatus +{ + /** + * 成功 + */ + SUCCESS, + + /** + * 失败 + */ + FAIL, +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessType.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessType.java new file mode 100644 index 0000000..24d076a --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessType.java @@ -0,0 +1,59 @@ +package com.ruoyi.common.enums; + +/** + * 业务操作类型 + * + * @author ruoyi + */ +public enum BusinessType +{ + /** + * 其它 + */ + OTHER, + + /** + * 新增 + */ + INSERT, + + /** + * 修改 + */ + UPDATE, + + /** + * 删除 + */ + DELETE, + + /** + * 授权 + */ + GRANT, + + /** + * 导出 + */ + EXPORT, + + /** + * 导入 + */ + IMPORT, + + /** + * 强退 + */ + FORCE, + + /** + * 生成代码 + */ + GENCODE, + + /** + * 清空 + */ + CLEAN, +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/DataSourceType.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/DataSourceType.java new file mode 100644 index 0000000..0d945be --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/DataSourceType.java @@ -0,0 +1,19 @@ +package com.ruoyi.common.enums; + +/** + * 数据源 + * + * @author ruoyi + */ +public enum DataSourceType +{ + /** + * 主库 + */ + MASTER, + + /** + * 从库 + */ + SLAVE +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/OnlineStatus.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/OnlineStatus.java new file mode 100644 index 0000000..a05d5ff --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/OnlineStatus.java @@ -0,0 +1,24 @@ +package com.ruoyi.common.enums; + +/** + * 用户会话 + * + * @author ruoyi + */ +public enum OnlineStatus +{ + /** 用户状态 */ + on_line("在线"), off_line("离线"); + + private final String info; + + private OnlineStatus(String info) + { + this.info = info; + } + + public String getInfo() + { + return info; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/OperatorType.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/OperatorType.java new file mode 100644 index 0000000..bdd143c --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/OperatorType.java @@ -0,0 +1,24 @@ +package com.ruoyi.common.enums; + +/** + * 操作人类别 + * + * @author ruoyi + */ +public enum OperatorType +{ + /** + * 其它 + */ + OTHER, + + /** + * 后台用户 + */ + MANAGE, + + /** + * 手机端用户 + */ + MOBILE +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/UserStatus.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/UserStatus.java new file mode 100644 index 0000000..d7ff44a --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/UserStatus.java @@ -0,0 +1,30 @@ +package com.ruoyi.common.enums; + +/** + * 用户状态 + * + * @author ruoyi + */ +public enum UserStatus +{ + OK("0", "正常"), DISABLE("1", "停用"), DELETED("2", "删除"); + + private final String code; + private final String info; + + UserStatus(String code, String info) + { + this.code = code; + this.info = info; + } + + public String getCode() + { + return code; + } + + public String getInfo() + { + return info; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/DemoModeException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/DemoModeException.java new file mode 100644 index 0000000..f6ad2ab --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/DemoModeException.java @@ -0,0 +1,15 @@ +package com.ruoyi.common.exception; + +/** + * 演示模式异常 + * + * @author ruoyi + */ +public class DemoModeException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + public DemoModeException() + { + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/GlobalException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/GlobalException.java new file mode 100644 index 0000000..81a71b5 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/GlobalException.java @@ -0,0 +1,58 @@ +package com.ruoyi.common.exception; + +/** + * 全局异常 + * + * @author ruoyi + */ +public class GlobalException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + /** + * 错误提示 + */ + private String message; + + /** + * 错误明细,内部调试错误 + * + * 和 {@link CommonResult#getDetailMessage()} 一致的设计 + */ + private String detailMessage; + + /** + * 空构造方法,避免反序列化问题 + */ + public GlobalException() + { + } + + public GlobalException(String message) + { + this.message = message; + } + + public String getDetailMessage() + { + return detailMessage; + } + + public GlobalException setDetailMessage(String detailMessage) + { + this.detailMessage = detailMessage; + return this; + } + + @Override + public String getMessage() + { + return message; + } + + public GlobalException setMessage(String message) + { + this.message = message; + return this; + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/ServiceException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/ServiceException.java new file mode 100644 index 0000000..7f053a3 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/ServiceException.java @@ -0,0 +1,58 @@ +package com.ruoyi.common.exception; + +/** + * 业务异常 + * + * @author ruoyi + */ +public final class ServiceException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + /** + * 错误提示 + */ + private String message; + + /** + * 错误明细,内部调试错误 + * + * 和 {@link CommonResult#getDetailMessage()} 一致的设计 + */ + private String detailMessage; + + /** + * 空构造方法,避免反序列化问题 + */ + public ServiceException() + { + } + + public ServiceException(String message) + { + this.message = message; + } + + public String getDetailMessage() + { + return detailMessage; + } + + public ServiceException setDetailMessage(String detailMessage) + { + this.detailMessage = detailMessage; + return this; + } + + @Override + public String getMessage() + { + return message; + } + + public ServiceException setMessage(String message) + { + this.message = message; + return this; + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/UtilException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/UtilException.java new file mode 100644 index 0000000..980fa46 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/UtilException.java @@ -0,0 +1,26 @@ +package com.ruoyi.common.exception; + +/** + * 工具类异常 + * + * @author ruoyi + */ +public class UtilException extends RuntimeException +{ + private static final long serialVersionUID = 8247610319171014183L; + + public UtilException(Throwable e) + { + super(e.getMessage(), e); + } + + public UtilException(String message) + { + super(message); + } + + public UtilException(String message, Throwable throwable) + { + super(message, throwable); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/base/BaseException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/base/BaseException.java new file mode 100644 index 0000000..b55d72e --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/base/BaseException.java @@ -0,0 +1,97 @@ +package com.ruoyi.common.exception.base; + +import com.ruoyi.common.utils.MessageUtils; +import com.ruoyi.common.utils.StringUtils; + +/** + * 基础异常 + * + * @author ruoyi + */ +public class BaseException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + /** + * 所属模块 + */ + private String module; + + /** + * 错误码 + */ + private String code; + + /** + * 错误码对应的参数 + */ + private Object[] args; + + /** + * 错误消息 + */ + private String defaultMessage; + + public BaseException(String module, String code, Object[] args, String defaultMessage) + { + this.module = module; + this.code = code; + this.args = args; + this.defaultMessage = defaultMessage; + } + + public BaseException(String module, String code, Object[] args) + { + this(module, code, args, null); + } + + public BaseException(String module, String defaultMessage) + { + this(module, null, null, defaultMessage); + } + + public BaseException(String code, Object[] args) + { + this(null, code, args, null); + } + + public BaseException(String defaultMessage) + { + this(null, null, null, defaultMessage); + } + + @Override + public String getMessage() + { + String message = null; + if (!StringUtils.isEmpty(code)) + { + message = MessageUtils.message(code, args); + } + if (message == null) + { + message = defaultMessage; + } + return message; + } + + public String getModule() + { + return module; + } + + public String getCode() + { + return code; + } + + public Object[] getArgs() + { + return args; + } + + public String getDefaultMessage() + { + return defaultMessage; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileException.java new file mode 100644 index 0000000..871f09b --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileException.java @@ -0,0 +1,19 @@ +package com.ruoyi.common.exception.file; + +import com.ruoyi.common.exception.base.BaseException; + +/** + * 文件信息异常类 + * + * @author ruoyi + */ +public class FileException extends BaseException +{ + private static final long serialVersionUID = 1L; + + public FileException(String code, Object[] args) + { + super("file", code, args, null); + } + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileNameLengthLimitExceededException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileNameLengthLimitExceededException.java new file mode 100644 index 0000000..70e0ec9 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileNameLengthLimitExceededException.java @@ -0,0 +1,16 @@ +package com.ruoyi.common.exception.file; + +/** + * 文件名称超长限制异常类 + * + * @author ruoyi + */ +public class FileNameLengthLimitExceededException extends FileException +{ + private static final long serialVersionUID = 1L; + + public FileNameLengthLimitExceededException(int defaultFileNameLength) + { + super("upload.filename.exceed.length", new Object[] { defaultFileNameLength }); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileSizeLimitExceededException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileSizeLimitExceededException.java new file mode 100644 index 0000000..ec6ab05 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileSizeLimitExceededException.java @@ -0,0 +1,16 @@ +package com.ruoyi.common.exception.file; + +/** + * 文件名大小限制异常类 + * + * @author ruoyi + */ +public class FileSizeLimitExceededException extends FileException +{ + private static final long serialVersionUID = 1L; + + public FileSizeLimitExceededException(long defaultMaxSize) + { + super("upload.exceed.maxSize", new Object[] { defaultMaxSize }); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileUploadException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileUploadException.java new file mode 100644 index 0000000..f45e7ef --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileUploadException.java @@ -0,0 +1,61 @@ +package com.ruoyi.common.exception.file; + +import java.io.PrintStream; +import java.io.PrintWriter; + +/** + * 文件上传异常类 + * + * @author ruoyi + */ +public class FileUploadException extends Exception +{ + + private static final long serialVersionUID = 1L; + + private final Throwable cause; + + public FileUploadException() + { + this(null, null); + } + + public FileUploadException(final String msg) + { + this(msg, null); + } + + public FileUploadException(String msg, Throwable cause) + { + super(msg); + this.cause = cause; + } + + @Override + public void printStackTrace(PrintStream stream) + { + super.printStackTrace(stream); + if (cause != null) + { + stream.println("Caused by:"); + cause.printStackTrace(stream); + } + } + + @Override + public void printStackTrace(PrintWriter writer) + { + super.printStackTrace(writer); + if (cause != null) + { + writer.println("Caused by:"); + cause.printStackTrace(writer); + } + } + + @Override + public Throwable getCause() + { + return cause; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/InvalidExtensionException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/InvalidExtensionException.java new file mode 100644 index 0000000..b805fc1 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/InvalidExtensionException.java @@ -0,0 +1,80 @@ +package com.ruoyi.common.exception.file; + +import java.util.Arrays; + +/** + * 文件上传 误异常类 + * + * @author ruoyi + */ +public class InvalidExtensionException extends FileUploadException +{ + private static final long serialVersionUID = 1L; + + private String[] allowedExtension; + private String extension; + private String filename; + + public InvalidExtensionException(String[] allowedExtension, String extension, String filename) + { + super("文件[" + filename + "]后缀[" + extension + "]不正确,请上传" + Arrays.toString(allowedExtension) + "格式"); + this.allowedExtension = allowedExtension; + this.extension = extension; + this.filename = filename; + } + + public String[] getAllowedExtension() + { + return allowedExtension; + } + + public String getExtension() + { + return extension; + } + + public String getFilename() + { + return filename; + } + + public static class InvalidImageExtensionException extends InvalidExtensionException + { + private static final long serialVersionUID = 1L; + + public InvalidImageExtensionException(String[] allowedExtension, String extension, String filename) + { + super(allowedExtension, extension, filename); + } + } + + public static class InvalidFlashExtensionException extends InvalidExtensionException + { + private static final long serialVersionUID = 1L; + + public InvalidFlashExtensionException(String[] allowedExtension, String extension, String filename) + { + super(allowedExtension, extension, filename); + } + } + + public static class InvalidMediaExtensionException extends InvalidExtensionException + { + private static final long serialVersionUID = 1L; + + public InvalidMediaExtensionException(String[] allowedExtension, String extension, String filename) + { + super(allowedExtension, extension, filename); + } + } + + public static class InvalidVideoExtensionException extends InvalidExtensionException + { + private static final long serialVersionUID = 1L; + + public InvalidVideoExtensionException(String[] allowedExtension, String extension, String filename) + { + super(allowedExtension, extension, filename); + } + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/job/TaskException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/job/TaskException.java new file mode 100644 index 0000000..a567b40 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/job/TaskException.java @@ -0,0 +1,34 @@ +package com.ruoyi.common.exception.job; + +/** + * 计划策略异常 + * + * @author ruoyi + */ +public class TaskException extends Exception +{ + private static final long serialVersionUID = 1L; + + private Code code; + + public TaskException(String msg, Code code) + { + this(msg, code, null); + } + + public TaskException(String msg, Code code, Exception nestedEx) + { + super(msg, nestedEx); + this.code = code; + } + + public Code getCode() + { + return code; + } + + public enum Code + { + TASK_EXISTS, NO_TASK_EXISTS, TASK_ALREADY_STARTED, UNKNOWN, CONFIG_ERROR, TASK_NODE_NOT_AVAILABLE + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/BlackListException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/BlackListException.java new file mode 100644 index 0000000..2bf5038 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/BlackListException.java @@ -0,0 +1,16 @@ +package com.ruoyi.common.exception.user; + +/** + * 黑名单IP异常类 + * + * @author ruoyi + */ +public class BlackListException extends UserException +{ + private static final long serialVersionUID = 1L; + + public BlackListException() + { + super("login.blocked", null); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaException.java new file mode 100644 index 0000000..389dbc7 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaException.java @@ -0,0 +1,16 @@ +package com.ruoyi.common.exception.user; + +/** + * 验证码错误异常类 + * + * @author ruoyi + */ +public class CaptchaException extends UserException +{ + private static final long serialVersionUID = 1L; + + public CaptchaException() + { + super("user.jcaptcha.error", null); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/RoleBlockedException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/RoleBlockedException.java new file mode 100644 index 0000000..1c4fd4a --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/RoleBlockedException.java @@ -0,0 +1,16 @@ +package com.ruoyi.common.exception.user; + +/** + * 角色锁定异常类 + * + * @author ruoyi + */ +public class RoleBlockedException extends UserException +{ + private static final long serialVersionUID = 1L; + + public RoleBlockedException() + { + super("role.blocked", null); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserBlockedException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserBlockedException.java new file mode 100644 index 0000000..5150f52 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserBlockedException.java @@ -0,0 +1,16 @@ +package com.ruoyi.common.exception.user; + +/** + * 用户锁定异常类 + * + * @author ruoyi + */ +public class UserBlockedException extends UserException +{ + private static final long serialVersionUID = 1L; + + public UserBlockedException() + { + super("user.blocked", null); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserDeleteException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserDeleteException.java new file mode 100644 index 0000000..3520030 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserDeleteException.java @@ -0,0 +1,16 @@ +package com.ruoyi.common.exception.user; + +/** + * 用户账号已被删除 + * + * @author ruoyi + */ +public class UserDeleteException extends UserException +{ + private static final long serialVersionUID = 1L; + + public UserDeleteException() + { + super("user.password.delete", null); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserException.java new file mode 100644 index 0000000..c292d70 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserException.java @@ -0,0 +1,18 @@ +package com.ruoyi.common.exception.user; + +import com.ruoyi.common.exception.base.BaseException; + +/** + * 用户信息异常类 + * + * @author ruoyi + */ +public class UserException extends BaseException +{ + private static final long serialVersionUID = 1L; + + public UserException(String code, Object[] args) + { + super("user", code, args, null); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserNotExistsException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserNotExistsException.java new file mode 100644 index 0000000..eff8181 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserNotExistsException.java @@ -0,0 +1,16 @@ +package com.ruoyi.common.exception.user; + +/** + * 用户不存在异常类 + * + * @author ruoyi + */ +public class UserNotExistsException extends UserException +{ + private static final long serialVersionUID = 1L; + + public UserNotExistsException() + { + super("user.not.exists", null); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordNotMatchException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordNotMatchException.java new file mode 100644 index 0000000..a7f3e5f --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordNotMatchException.java @@ -0,0 +1,16 @@ +package com.ruoyi.common.exception.user; + +/** + * 用户密码不正确或不符合规范异常类 + * + * @author ruoyi + */ +public class UserPasswordNotMatchException extends UserException +{ + private static final long serialVersionUID = 1L; + + public UserPasswordNotMatchException() + { + super("user.password.not.match", null); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordRetryLimitCountException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordRetryLimitCountException.java new file mode 100644 index 0000000..7ead89b --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordRetryLimitCountException.java @@ -0,0 +1,16 @@ +package com.ruoyi.common.exception.user; + +/** + * 用户错误记数异常类 + * + * @author ruoyi + */ +public class UserPasswordRetryLimitCountException extends UserException +{ + private static final long serialVersionUID = 1L; + + public UserPasswordRetryLimitCountException(int retryLimitCount) + { + super("user.password.retry.limit.count", new Object[] { retryLimitCount }); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordRetryLimitExceedException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordRetryLimitExceedException.java new file mode 100644 index 0000000..b5ccfb9 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordRetryLimitExceedException.java @@ -0,0 +1,16 @@ +package com.ruoyi.common.exception.user; + +/** + * 用户错误最大次数异常类 + * + * @author ruoyi + */ +public class UserPasswordRetryLimitExceedException extends UserException +{ + private static final long serialVersionUID = 1L; + + public UserPasswordRetryLimitExceedException(int retryLimitCount) + { + super("user.password.retry.limit.exceed", new Object[] { retryLimitCount }); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/json/JSON.java b/ruoyi-common/src/main/java/com/ruoyi/common/json/JSON.java new file mode 100644 index 0000000..1e5b928 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/json/JSON.java @@ -0,0 +1,187 @@ +package com.ruoyi.common.json; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import com.fasterxml.jackson.core.JsonGenerationException; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; + +/** + * JSON解析处理 + * + * @author ruoyi + */ +public class JSON +{ + public static final String DEFAULT_FAIL = "\"Parse failed\""; + private static final ObjectMapper objectMapper = new ObjectMapper(); + private static final ObjectWriter objectWriter = objectMapper.writerWithDefaultPrettyPrinter(); + + public static void marshal(File file, Object value) throws Exception + { + try + { + objectWriter.writeValue(file, value); + } + catch (JsonGenerationException e) + { + throw new Exception(e); + } + catch (JsonMappingException e) + { + throw new Exception(e); + } + catch (IOException e) + { + throw new Exception(e); + } + } + + public static void marshal(OutputStream os, Object value) throws Exception + { + try + { + objectWriter.writeValue(os, value); + } + catch (JsonGenerationException e) + { + throw new Exception(e); + } + catch (JsonMappingException e) + { + throw new Exception(e); + } + catch (IOException e) + { + throw new Exception(e); + } + } + + public static String marshal(Object value) throws Exception + { + try + { + return objectWriter.writeValueAsString(value); + } + catch (JsonGenerationException e) + { + throw new Exception(e); + } + catch (JsonMappingException e) + { + throw new Exception(e); + } + catch (IOException e) + { + throw new Exception(e); + } + } + + public static byte[] marshalBytes(Object value) throws Exception + { + try + { + return objectWriter.writeValueAsBytes(value); + } + catch (JsonGenerationException e) + { + throw new Exception(e); + } + catch (JsonMappingException e) + { + throw new Exception(e); + } + catch (IOException e) + { + throw new Exception(e); + } + } + + public static T unmarshal(File file, Class valueType) throws Exception + { + try + { + return objectMapper.readValue(file, valueType); + } + catch (JsonParseException e) + { + throw new Exception(e); + } + catch (JsonMappingException e) + { + throw new Exception(e); + } + catch (IOException e) + { + throw new Exception(e); + } + } + + public static T unmarshal(InputStream is, Class valueType) throws Exception + { + try + { + return objectMapper.readValue(is, valueType); + } + catch (JsonParseException e) + { + throw new Exception(e); + } + catch (JsonMappingException e) + { + throw new Exception(e); + } + catch (IOException e) + { + throw new Exception(e); + } + } + + public static T unmarshal(String str, Class valueType) throws Exception + { + try + { + return objectMapper.readValue(str, valueType); + } + catch (JsonParseException e) + { + throw new Exception(e); + } + catch (JsonMappingException e) + { + throw new Exception(e); + } + catch (IOException e) + { + throw new Exception(e); + } + } + + public static T unmarshal(byte[] bytes, Class valueType) throws Exception + { + try + { + if (bytes == null) + { + bytes = new byte[0]; + } + return objectMapper.readValue(bytes, 0, bytes.length, valueType); + } + catch (JsonParseException e) + { + throw new Exception(e); + } + catch (JsonMappingException e) + { + throw new Exception(e); + } + catch (IOException e) + { + throw new Exception(e); + } + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/json/JSONObject.java b/ruoyi-common/src/main/java/com/ruoyi/common/json/JSONObject.java new file mode 100644 index 0000000..2f4afe7 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/json/JSONObject.java @@ -0,0 +1,749 @@ +package com.ruoyi.common.json; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ruoyi.common.utils.StringUtils; + +/** + * 通用消息对象,基于Map实现的可嵌套数据结构。 支持JSON数据结构。 + * + * @author ruoyi + */ +public class JSONObject extends LinkedHashMap +{ + private static final long serialVersionUID = 1L; + private static final Pattern arrayNamePattern = Pattern.compile("(\\w+)((\\[\\d+\\])+)"); + private static final ObjectMapper objectMapper = new ObjectMapper(); + + /** + * 数组结构。 + */ + public static class JSONArray extends ArrayList + { + private static final long serialVersionUID = 1L; + + public JSONArray() + { + super(); + } + + public JSONArray(int size) + { + super(size); + } + + @Override + public String toString() + { + try + { + return JSON.marshal(this); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + @Override + public Object set(int index, Object element) + { + return super.set(index, transfer(element)); + } + + @Override + public boolean add(Object element) + { + return super.add(transfer(element)); + } + + @Override + public void add(int index, Object element) + { + super.add(index, transfer(element)); + } + } + + public JSONObject() + { + super(); + } + + public JSONObject(final JSONObject other) + { + super(other); + } + + @Override + public String toString() + { + try + { + return JSON.marshal(this); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + /** + * 转换为紧凑格式的字符串。 + * + * @return 返回本对象紧凑格式字符串。 + */ + public String toCompactString() + { + try + { + return objectMapper.writeValueAsString(this); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + /** + * 获取指定字段的整数值。如果字段不存在,或者无法转换为整数,返回null。 + * + * @param name 字段名,支持多级。 + * @return 返回指定的整数值,或者null。 + */ + public Integer intValue(final String name) + { + return valueAsInt(value(name)); + } + + /** + * 获取指定字段的整数值。如果字段不存在,或者无法转换为整数,返回defaultValue。 + * + * @param name 字段名,支持多级。 + * @param defaultValue 查询失败时,返回的值。 + * @return 返回指定的整数值,或者defaultValue。 + */ + public Integer intValue(final String name, final Integer defaultValue) + { + return StringUtils.nvl(intValue(name), defaultValue); + } + + /** + * 获取指定字段的长整数值。如果字段不存在,或者无法转换为长整数,返回null。 + * + * @param name 字段名,支持多级。 + * @return 返回指定的长整数值,或者null。 + */ + public Long longValue(final String name) + { + return valueAsLong(value(name)); + } + + /** + * 获取指定字段的长整数值。如果字段不存在,或者无法转换为长整数,返回defaultValue。 + * + * @param name 字段名,支持多级。 + * @param defaultValue 查询失败时,返回的值。 + * @return 返回指定的长整数值,或者defaultValue。 + */ + public Long longValue(final String name, final Long defaultValue) + { + return StringUtils.nvl(longValue(name), defaultValue); + } + + /** + * 获取指定字段的布尔值。如果字段不存在,或者无法转换为布尔型,返回null。 + * + * @param name 字段名,支持多级。 + * @return 返回指定的布尔值,或者null。 + */ + public Boolean boolValue(final String name) + { + return valueAsBool(value(name)); + } + + /** + * 获取指定字段的布尔值。如果字段不存在,或者无法转换为布尔型,返回defaultValue。 + * + * @param name 字段名,支持多级。 + * @param defaultValue 查询失败时,返回的值。 + * @return 返回指定的布尔值,或者defaultValue。 + */ + public Boolean boolValue(final String name, final Boolean defaultValue) + { + return StringUtils.nvl(boolValue(name), defaultValue); + } + + /** + * 获取指定字段的字符串值。如果字段不存在,返回null。 + * + * @param name 字段名,支持多级。 + * @return 返回指定的字符串值,或者null。 + */ + public String strValue(final String name) + { + return valueAsStr(value(name)); + } + + /** + * 获取指定字段的字符串值。如果字段不存在,返回defaultValue。 + * + * @param name 字段名,支持多级。 + * @param defaultValue 查询失败时,返回的值。 + * @return 返回指定的字符串值,或者defaultValue。 + */ + public String strValue(final String name, final String defaultValue) + { + return StringUtils.nvl(strValue(name), defaultValue); + } + + /** + * 获取指定字段的值。 + * + * @param name 字段名,支持多级,支持数组下标。 + * @return 返回指定字段的值。 + */ + public Object value(final String name) + { + final int indexDot = name.indexOf('.'); + if (indexDot >= 0) + { + return obj(name.substring(0, indexDot)).value(name.substring(indexDot + 1)); + } + else + { + final Matcher matcher = arrayNamePattern.matcher(name); + if (matcher.find()) + { + return endArray(matcher.group(1), matcher.group(2), new EndArrayCallback() + { + @Override + public Object callback(JSONArray arr, int index) + { + return elementAt(arr, index); + } + }); + } + else + { + return get(name); + } + } + } + + /** + * 设置指定字段的值。 + * + * @param name 字段名,支持多级,支持数组下标。 + * @param value 字段值。 + * @return 返回本对象。 + */ + public JSONObject value(final String name, final Object value) + { + final int indexDot = name.indexOf('.'); + if (indexDot >= 0) + { + obj(name.substring(0, indexDot)).value(name.substring(indexDot + 1), value); + } + else + { + final Matcher matcher = arrayNamePattern.matcher(name); + if (matcher.find()) + { + endArray(matcher.group(1), matcher.group(2), new EndArrayCallback() + { + @Override + public Void callback(JSONArray arr, int index) + { + elementAt(arr, index, value); + return null; + } + }); + } + else + { + set(name, value); + } + } + return this; + } + + /** + * 获取对象(非标量类型)字段。返回的数据是一个结构体。当不存在指定对象时,则为指定的名字创建一个空的MessageObject对象。 + * + * @param name 字段名。不支持多级名字,支持数组下标。 + * @return 返回指定的对象。如果对象不存在,则为指定的名字创建一个空的MessageObject对象。 + */ + public JSONObject obj(final String name) + { + final Matcher matcher = arrayNamePattern.matcher(name); + if (matcher.find()) + { + return endArray(matcher.group(1), matcher.group(2), new EndArrayCallback() + { + @Override + public JSONObject callback(JSONArray arr, int index) + { + return objAt(arr, index); + } + }); + } + else + { + JSONObject obj = getObj(name); + if (obj == null) + { + obj = new JSONObject(); + put(name, obj); + } + return obj; + } + } + + /** + * 获取数组字段。将名字对应的对象以数组对象返回,当指定的字段不存在时,创建一个空的数组。 + * + * @param name 字段名。不支持多级名字,不支持下标。 + * @return 返回一个数组(List)。 + */ + public JSONArray arr(final String name) + { + JSONArray arr = getArr(name); + if (arr == null) + { + arr = new JSONArray(); + put(name, arr); + } + return arr; + } + + /** + * 获取对象(非标量类型)字段。返回的数据是一个结构体。 + * + * @param name 字段名。 + * @return 返回指定的对象字段。 + */ + public JSONObject getObj(final String name) + { + return (JSONObject) get(name); + } + + /** + * 获取数组类型字段。 + * + * @param name 字段名。 + * @return 返回数组类型字段。 + */ + public JSONArray getArr(final String name) + { + return (JSONArray) get(name); + } + + /** + * 返回字段整数值。如果不存在,返回null。 + * + * @param name 字段名。 + * @return 返回指定字段整数值。 + */ + public Integer getInt(final String name) + { + return valueAsInt(get(name)); + } + + /** + * 返回字段整数值。如果不存在,返回defaultValue。 + * + * @param name 字段名。 + * @param defaultValue 字段不存在时,返回的值。 + * @return 返回指定字段整数值。 + */ + public Integer getInt(final String name, Integer defaultValue) + { + return StringUtils.nvl(getInt(name), defaultValue); + } + + /** + * 返回字段长整数值。如果不存在,返回null。 + * + * @param name 字段名。 + * @return 返回指定字段长整数值。 + */ + public Long getLong(final String name) + { + return valueAsLong(get(name)); + } + + /** + * 返回字段长整数值。如果不存在,返回defaultValue。 + * + * @param name 字段名。 + * @param defaultValue 字段不存在时,返回的值。 + * @return 返回指定字段长整数值。 + */ + public Long getLong(final String name, Long defaultValue) + { + return StringUtils.nvl(getLong(name), defaultValue); + } + + /** + * 返回字段字符串值。如果不存在,返回null。 + * + * @param name 字段名。 + * @return 返回指定字段字符串值。 + */ + public String getStr(final String name) + { + return valueAsStr(get(name)); + } + + /** + * 返回字段字符串值。如果不存在,返回defaultValue。 + * + * @param name 字段名。 + * @param defaultValue 字段不存在时,返回的值。 + * @return 返回指定字段字符串值。 + */ + public String getStr(final String name, final String defaultValue) + { + return StringUtils.nvl(getStr(name), defaultValue); + } + + /** + * 字段值按照布尔类型返回。如果不存在,返回null。 + * + * @param name 字段名。 + * @return 字段值。 + */ + public Boolean getBool(final String name) + { + return valueAsBool(get(name)); + } + + /** + * 字段值按照布尔类型返回。如果不存在,返回defaultValue。 + * + * @param name 字段名。 + * @param defaultValue 字段不存在时,返回的值。 + * @return 字段值。 + */ + public Boolean getBool(final String name, final Boolean defaultValue) + { + return StringUtils.nvl(getBool(name), defaultValue); + } + + /** + * 设置字段值 + * + * @param name 字段名 + * @param value 字段值(标量:数字、字符串、布尔型;结构体:MessageObject)。 如果是Map类型同时非MessageObject类型,则自动转换为MessageObject类型再存入 + * (此时,再修改Map中的数据,将不会体现到本对象中)。 + * @return 返回本对象 + */ + public JSONObject set(final String name, final Object value) + { + put(name, value); + return this; + } + + /** + * 将本对象转换为Java Bean。 + * + * @param beanClass Java Bean的类对象。 + * @return 返回转换后的Java Bean。 + */ + public T asBean(Class beanClass) + { + try + { + return JSON.unmarshal(JSON.marshal(this), beanClass); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + /** + * 重载基类的方法。如果 value 是 Map 类型,但不是 MessageObject 类型,则创建一个包含内容等同于原 Map 的 MessageObject 作为 value(注意:此后再更改 Map 的内容,将不会反映到 + * MessageObject 中)。 重载此方法的目的是为了使JSON能够正确地解析为MessageObject对象。不建议直接调用此方法,请使用 set(name, value)方法设置字段值。 + */ + @Override + public Object put(String key, Object value) + { + return super.put(key, transfer(value)); + } + + public static Integer valueAsInt(Object value) + { + if (value instanceof Integer) + { + return (Integer) value; + } + else if (value instanceof Number) + { + return ((Number) value).intValue(); + } + else if (value instanceof String) + { + return Integer.valueOf((String) value); + } + else if (value instanceof Boolean) + { + return ((Boolean) value) ? 1 : 0; + } + else + { + return null; + } + } + + public static Long valueAsLong(Object value) + { + if (value instanceof Long) + { + return (Long) value; + } + else if (value instanceof Number) + { + return ((Number) value).longValue(); + } + else if (value instanceof String) + { + return Long.valueOf((String) value); + } + else if (value instanceof Boolean) + { + return ((Boolean) value) ? 1L : 0L; + } + else + { + return null; + } + } + + public static String valueAsStr(Object value) + { + if (value instanceof String) + { + return (String) value; + } + else if (value != null) + { + return value.toString(); + } + else + { + return null; + } + } + + public static Boolean valueAsBool(Object value) + { + if (value instanceof Boolean) + { + return (Boolean) value; + } + else if (value instanceof Number) + { + return ((Number) value).doubleValue() != 0.0; + } + else if (value instanceof String) + { + return Boolean.valueOf((String) value); + } + else + { + return null; + } + } + + /** + * 将所有层次中凡是Map类型同时又不是MessageObject的类型,转换为MessageObject类型。 + * + * @param value 值。 + * @return 返回转换后的值。 + */ + @SuppressWarnings("unchecked") + private static Object transfer(final Object value) + { + if (!(value instanceof JSONObject) && value instanceof Map) + { + return toObj((Map) value); + } + else if (!(value instanceof JSONArray) && value instanceof Collection) + { + return toArr((Collection) value); + } + else + { + return value; + } + } + + private static JSONArray toArr(final Collection list) + { + final JSONArray arr = new JSONArray(list.size()); + for (final Object element : list) + { + arr.add(element); + } + return arr; + } + + private static JSONObject toObj(final Map map) + { + final JSONObject obj = new JSONObject(); + for (final Map.Entry ent : map.entrySet()) + { + obj.put(ent.getKey(), transfer(ent.getValue())); + } + return obj; + } + + /** + * 将指定下标元素作为数组返回,如果不存在,则在该位置创建一个空的数组。 + * + * @param arr 当前数组。 + * @param index 下标。 + * @return 返回当前数组指定下标的元素,该元素应该是一个数组。 + */ + private static JSONArray arrayAt(JSONArray arr, int index) + { + expand(arr, index); + if (arr.get(index) == null) + { + arr.set(index, new JSONArray()); + } + return (JSONArray) arr.get(index); + } + + /** + * 将指定下标元素作为结构体返回,如果不存在,则在该位置创建一个空的结构体。 + * + * @param arr 当前数组。 + * @param index 下标。 + * @return 返回当前数组指定下标元素,该元素是一个结构体。 + */ + private static JSONObject objAt(final JSONArray arr, int index) + { + expand(arr, index); + if (arr.get(index) == null) + { + arr.set(index, new JSONObject()); + } + return (JSONObject) arr.get(index); + } + + /** + * 设置数组指定下标位置的值。 + * + * @param arr 数组。 + * @param index 下标。 + * @param value 值。 + */ + private static void elementAt(final JSONArray arr, final int index, final Object value) + { + expand(arr, index).set(index, value); + } + + /** + * 获取数组指定下标元素的值。 + * + * @param arr 数组。 + * @param index 下标。 + * @return 值。 + */ + private static Object elementAt(final JSONArray arr, final int index) + { + return expand(arr, index).get(index); + } + + /** + * 扩展数组到指定下标,以防止访问时下标越界。 + * + * @param arr 数组 + * @param index 下标 + * @return 返回传入的数组 + */ + private static JSONArray expand(final JSONArray arr, final int index) + { + while (arr.size() <= index) + { + arr.add(null); + } + return arr; + } + + /** + * 最后数组回调。 + * + * @author Mike + * + * @param 回调返回数据类型。 + */ + private interface EndArrayCallback + { + /** + * 当定位到最后一级数组,将调用本方法。 + * + * @param arr 最后一级数组对象。 + * @param index 最后一级索引。 + * @return 返回回调的返回值。 + */ + T callback(JSONArray arr, int index); + } + + /** + * 处理多维数组的工具函数(包括一维数组)。多维数组的名字如:arrary[1][2][3], 则name=array,indexStr=[1][2][3],在callback中,endArr将是 + * array[1][2]指定的对象,indexe=3。 + * + * @param name 不带下标的名字,不支持多级名字。 + * @param indexesStr 索引部分的字符串,如:[1][2][3] + * @param callback 回调函数。 + * @return 返回回调函数的返回值。 + */ + private T endArray(final String name, final String indexesStr, final EndArrayCallback callback) + { + JSONArray endArr = arr(name); + final int[] indexes = parseIndexes(indexesStr); + int i = 0; + while (i < indexes.length - 1) + { + endArr = arrayAt(endArr, indexes[i++]); + } + return callback.callback(endArr, indexes[i]); + } + + private static int[] parseIndexes(final String s) + { + int[] indexes = null; + List list = new ArrayList(); + + final StringTokenizer st = new StringTokenizer(s, "[]"); + while (st.hasMoreTokens()) + { + final int index = Integer.valueOf(st.nextToken()); + if (index < 0) + { + throw new RuntimeException(String.format("Illegal index %1$d in \"%2$s\"", index, s)); + } + + list.add(index); + } + + indexes = new int[list.size()]; + int i = 0; + for (Integer tmp : list.toArray(new Integer[list.size()])) + { + indexes[i++] = tmp; + } + + return indexes; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/AddressUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/AddressUtils.java new file mode 100644 index 0000000..9afafc1 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/AddressUtils.java @@ -0,0 +1,54 @@ +package com.ruoyi.common.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.alibaba.fastjson.JSONObject; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.utils.http.HttpUtils; + +/** + * 获取地址类 + * + * @author ruoyi + */ +public class AddressUtils +{ + private static final Logger log = LoggerFactory.getLogger(AddressUtils.class); + + // IP地址查询 + public static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp"; + + // 未知地址 + public static final String UNKNOWN = "XX XX"; + + public static String getRealAddressByIP(String ip) + { + // 内网不查询 + if (IpUtils.internalIp(ip)) + { + return "内网IP"; + } + if (RuoYiConfig.isAddressEnabled()) + { + try + { + String rspStr = HttpUtils.sendGet(IP_URL, "ip=" + ip + "&json=true", Constants.GBK); + if (StringUtils.isEmpty(rspStr)) + { + log.error("获取地理位置异常 {}", ip); + return UNKNOWN; + } + JSONObject obj = JSONObject.parseObject(rspStr); + String region = obj.getString("pro"); + String city = obj.getString("city"); + return String.format("%s %s", region, city); + } + catch (Exception e) + { + log.error("获取地理位置异常 {}", e); + } + } + return UNKNOWN; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/Arith.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/Arith.java new file mode 100644 index 0000000..b6326c2 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/Arith.java @@ -0,0 +1,114 @@ +package com.ruoyi.common.utils; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +/** + * 精确的浮点数运算 + * + * @author ruoyi + */ +public class Arith +{ + + /** 默认除法运算精度 */ + private static final int DEF_DIV_SCALE = 10; + + /** 这个类不能实例化 */ + private Arith() + { + } + + /** + * 提供精确的加法运算。 + * @param v1 被加数 + * @param v2 加数 + * @return 两个参数的和 + */ + public static double add(double v1, double v2) + { + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + return b1.add(b2).doubleValue(); + } + + /** + * 提供精确的减法运算。 + * @param v1 被减数 + * @param v2 减数 + * @return 两个参数的差 + */ + public static double sub(double v1, double v2) + { + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + return b1.subtract(b2).doubleValue(); + } + + /** + * 提供精确的乘法运算。 + * @param v1 被乘数 + * @param v2 乘数 + * @return 两个参数的积 + */ + public static double mul(double v1, double v2) + { + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + return b1.multiply(b2).doubleValue(); + } + + /** + * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到 + * 小数点以后10位,以后的数字四舍五入。 + * @param v1 被除数 + * @param v2 除数 + * @return 两个参数的商 + */ + public static double div(double v1, double v2) + { + return div(v1, v2, DEF_DIV_SCALE); + } + + /** + * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 + * 定精度,以后的数字四舍五入。 + * @param v1 被除数 + * @param v2 除数 + * @param scale 表示表示需要精确到小数点以后几位。 + * @return 两个参数的商 + */ + public static double div(double v1, double v2, int scale) + { + if (scale < 0) + { + throw new IllegalArgumentException( + "The scale must be a positive integer or zero"); + } + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + if (b1.compareTo(BigDecimal.ZERO) == 0) + { + return BigDecimal.ZERO.doubleValue(); + } + return b1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue(); + } + + /** + * 提供精确的小数位四舍五入处理。 + * @param v 需要四舍五入的数字 + * @param scale 小数点后保留几位 + * @return 四舍五入后的结果 + */ + public static double round(double v, int scale) + { + if (scale < 0) + { + throw new IllegalArgumentException( + "The scale must be a positive integer or zero"); + } + BigDecimal b = new BigDecimal(Double.toString(v)); + BigDecimal one = BigDecimal.ONE; + return b.divide(one, scale, RoundingMode.HALF_UP).doubleValue(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/CacheUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/CacheUtils.java new file mode 100644 index 0000000..ae27682 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/CacheUtils.java @@ -0,0 +1,197 @@ +package com.ruoyi.common.utils; + +import java.util.Iterator; +import java.util.Set; +import org.apache.shiro.cache.Cache; +import org.apache.shiro.cache.CacheManager; +import org.apache.shiro.cache.ehcache.EhCacheManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.ruoyi.common.utils.spring.SpringUtils; + +/** + * Cache工具类 + * + * @author ruoyi + */ +public class CacheUtils +{ + private static Logger logger = LoggerFactory.getLogger(CacheUtils.class); + + private static CacheManager cacheManager = SpringUtils.getBean(CacheManager.class); + + private static final String SYS_CACHE = "sys-cache"; + + /** + * 获取SYS_CACHE缓存 + * + * @param key + * @return + */ + public static Object get(String key) + { + return get(SYS_CACHE, key); + } + + /** + * 获取SYS_CACHE缓存 + * + * @param key + * @param defaultValue + * @return + */ + public static Object get(String key, Object defaultValue) + { + Object value = get(key); + return value != null ? value : defaultValue; + } + + /** + * 写入SYS_CACHE缓存 + * + * @param key + * @return + */ + public static void put(String key, Object value) + { + put(SYS_CACHE, key, value); + } + + /** + * 从SYS_CACHE缓存中移除 + * + * @param key + * @return + */ + public static void remove(String key) + { + remove(SYS_CACHE, key); + } + + /** + * 获取缓存 + * + * @param cacheName + * @param key + * @return + */ + public static Object get(String cacheName, String key) + { + return getCache(cacheName).get(getKey(key)); + } + + /** + * 获取缓存 + * + * @param cacheName + * @param key + * @param defaultValue + * @return + */ + public static Object get(String cacheName, String key, Object defaultValue) + { + Object value = get(cacheName, getKey(key)); + return value != null ? value : defaultValue; + } + + /** + * 写入缓存 + * + * @param cacheName + * @param key + * @param value + */ + public static void put(String cacheName, String key, Object value) + { + getCache(cacheName).put(getKey(key), value); + } + + /** + * 从缓存中移除 + * + * @param cacheName + * @param key + */ + public static void remove(String cacheName, String key) + { + getCache(cacheName).remove(getKey(key)); + } + + /** + * 从缓存中移除所有 + * + * @param cacheName + */ + public static void removeAll(String cacheName) + { + Cache cache = getCache(cacheName); + Set keys = cache.keys(); + for (Iterator it = keys.iterator(); it.hasNext();) + { + cache.remove(it.next()); + } + logger.info("清理缓存: {} => {}", cacheName, keys); + } + + /** + * 从缓存中移除指定key + * + * @param keys + */ + public static void removeByKeys(Set keys) + { + removeByKeys(SYS_CACHE, keys); + } + + /** + * 从缓存中移除指定key + * + * @param cacheName + * @param keys + */ + public static void removeByKeys(String cacheName, Set keys) + { + for (Iterator it = keys.iterator(); it.hasNext();) + { + remove(it.next()); + } + logger.info("清理缓存: {} => {}", cacheName, keys); + } + + /** + * 获取缓存键名 + * + * @param key + * @return + */ + private static String getKey(String key) + { + return key; + } + + /** + * 获得一个Cache,没有则显示日志。 + * + * @param cacheName + * @return + */ + public static Cache getCache(String cacheName) + { + Cache cache = cacheManager.getCache(cacheName); + if (cache == null) + { + throw new RuntimeException("当前系统中没有定义“" + cacheName + "”这个缓存。"); + } + return cache; + } + + /** + * 获取所有缓存 + * + * @return 缓存组 + */ + public static String[] getCacheNames() + { + return ((EhCacheManager) cacheManager).getCacheManager().getCacheNames(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/CookieUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/CookieUtils.java new file mode 100644 index 0000000..4fa8339 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/CookieUtils.java @@ -0,0 +1,138 @@ +package com.ruoyi.common.utils; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Cookie工具类 + * + * @author ruoyi + */ +public class CookieUtils +{ + /** + * 设置 Cookie(生成时间为1天) + * + * @param name 名称 + * @param value 值 + */ + public static void setCookie(HttpServletResponse response, String name, String value) + { + setCookie(response, name, value, 60 * 60 * 24); + } + + /** + * 设置 Cookie + * + * @param name 名称 + * @param value 值 + * @param maxAge 生存时间(单位秒) + * @param uri 路径 + */ + public static void setCookie(HttpServletResponse response, String name, String value, String path) + { + setCookie(response, name, value, path, 60 * 60 * 24); + } + + /** + * 设置 Cookie + * + * @param name 名称 + * @param value 值 + * @param maxAge 生存时间(单位秒) + * @param uri 路径 + */ + public static void setCookie(HttpServletResponse response, String name, String value, int maxAge) + { + setCookie(response, name, value, "/", maxAge); + } + + /** + * 设置 Cookie + * + * @param name 名称 + * @param value 值 + * @param maxAge 生存时间(单位秒) + * @param uri 路径 + */ + public static void setCookie(HttpServletResponse response, String name, String value, String path, int maxAge) + { + Cookie cookie = new Cookie(name, null); + cookie.setPath(path); + cookie.setMaxAge(maxAge); + try + { + cookie.setValue(URLEncoder.encode(value, "utf-8")); + } + catch (UnsupportedEncodingException e) + { + e.printStackTrace(); + } + response.addCookie(cookie); + } + + /** + * 获得指定Cookie的值 + * + * @param name 名称 + * @return 值 + */ + public static String getCookie(HttpServletRequest request, String name) + { + return getCookie(request, null, name, false); + } + + /** + * 获得指定Cookie的值,并删除。 + * + * @param name 名称 + * @return 值 + */ + public static String getCookie(HttpServletRequest request, HttpServletResponse response, String name) + { + return getCookie(request, response, name, true); + } + + /** + * 获得指定Cookie的值 + * + * @param request 请求对象 + * @param response 响应对象 + * @param name 名字 + * @param isRemove 是否移除 + * @return 值 + */ + public static String getCookie(HttpServletRequest request, HttpServletResponse response, String name, + boolean isRemove) + { + String value = null; + Cookie[] cookies = request.getCookies(); + if (cookies != null) + { + for (Cookie cookie : cookies) + { + if (cookie.getName().equals(name)) + { + try + { + value = URLDecoder.decode(cookie.getValue(), "utf-8"); + } + catch (UnsupportedEncodingException e) + { + e.printStackTrace(); + } + if (isRemove) + { + cookie.setMaxAge(0); + response.addCookie(cookie); + } + } + } + } + return value; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/DateUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DateUtils.java new file mode 100644 index 0000000..27cb193 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DateUtils.java @@ -0,0 +1,191 @@ +package com.ruoyi.common.utils; + +import java.lang.management.ManagementFactory; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Date; +import org.apache.commons.lang3.time.DateFormatUtils; + +/** + * 时间工具类 + * + * @author ruoyi + */ +public class DateUtils extends org.apache.commons.lang3.time.DateUtils +{ + public static String YYYY = "yyyy"; + + public static String YYYY_MM = "yyyy-MM"; + + public static String YYYY_MM_DD = "yyyy-MM-dd"; + + public static String YYYYMMDDHHMMSS = "yyyyMMddHHmmss"; + + public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss"; + + private static String[] parsePatterns = { + "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM", + "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM", + "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"}; + + /** + * 获取当前Date型日期 + * + * @return Date() 当前日期 + */ + public static Date getNowDate() + { + return new Date(); + } + + /** + * 获取当前日期, 默认格式为yyyy-MM-dd + * + * @return String + */ + public static String getDate() + { + return dateTimeNow(YYYY_MM_DD); + } + + public static final String getTime() + { + return dateTimeNow(YYYY_MM_DD_HH_MM_SS); + } + + public static final String dateTimeNow() + { + return dateTimeNow(YYYYMMDDHHMMSS); + } + + public static final String dateTimeNow(final String format) + { + return parseDateToStr(format, new Date()); + } + + public static final String dateTime(final Date date) + { + return parseDateToStr(YYYY_MM_DD, date); + } + + public static final String parseDateToStr(final String format, final Date date) + { + return new SimpleDateFormat(format).format(date); + } + + public static final Date dateTime(final String format, final String ts) + { + try + { + return new SimpleDateFormat(format).parse(ts); + } + catch (ParseException e) + { + throw new RuntimeException(e); + } + } + + /** + * 日期路径 即年/月/日 如2018/08/08 + */ + public static final String datePath() + { + Date now = new Date(); + return DateFormatUtils.format(now, "yyyy/MM/dd"); + } + + /** + * 日期路径 即年/月/日 如20180808 + */ + public static final String dateTime() + { + Date now = new Date(); + return DateFormatUtils.format(now, "yyyyMMdd"); + } + + /** + * 日期型字符串转化为日期 格式 + */ + public static Date parseDate(Object str) + { + if (str == null) + { + return null; + } + try + { + return parseDate(str.toString(), parsePatterns); + } + catch (ParseException e) + { + return null; + } + } + + /** + * 获取服务器启动时间 + */ + public static Date getServerStartDate() + { + long time = ManagementFactory.getRuntimeMXBean().getStartTime(); + return new Date(time); + } + + /** + * 计算相差天数 + */ + public static int differentDaysByMillisecond(Date date1, Date date2) + { + return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24))); + } + + /** + * 计算时间差 + * + * @param endTime 最后时间 + * @param startTime 开始时间 + * @return 时间差(天/小时/分钟) + */ + public static String timeDistance(Date endDate, Date startTime) + { + long nd = 1000 * 24 * 60 * 60; + long nh = 1000 * 60 * 60; + long nm = 1000 * 60; + // long ns = 1000; + // 获得两个时间的毫秒时间差异 + long diff = endDate.getTime() - startTime.getTime(); + // 计算差多少天 + long day = diff / nd; + // 计算差多少小时 + long hour = diff % nd / nh; + // 计算差多少分钟 + long min = diff % nd % nh / nm; + // 计算差多少秒//输出结果 + // long sec = diff % nd % nh % nm / ns; + return day + "天" + hour + "小时" + min + "分钟"; + } + + /** + * 增加 LocalDateTime ==> Date + */ + public static Date toDate(LocalDateTime temporalAccessor) + { + ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault()); + return Date.from(zdt.toInstant()); + } + + /** + * 增加 LocalDate ==> Date + */ + public static Date toDate(LocalDate temporalAccessor) + { + LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0)); + ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault()); + return Date.from(zdt.toInstant()); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java new file mode 100644 index 0000000..5d5dedf --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java @@ -0,0 +1,190 @@ +package com.ruoyi.common.utils; + +import java.util.List; +import org.springframework.stereotype.Component; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.domain.entity.SysDictData; + +/** + * 字典工具类 + * + * @author ruoyi + */ +@Component +public class DictUtils +{ + /** + * 分隔符 + */ + public static final String SEPARATOR = ","; + + /** + * 设置字典缓存 + * + * @param key 参数键 + * @param dictDatas 字典数据列表 + */ + public static void setDictCache(String key, List dictDatas) + { + CacheUtils.put(getCacheName(), getCacheKey(key), dictDatas); + } + + /** + * 获取字典缓存 + * + * @param key 参数键 + * @return dictDatas 字典数据列表 + */ + public static List getDictCache(String key) + { + Object cacheObj = CacheUtils.get(getCacheName(), getCacheKey(key)); + if (StringUtils.isNotNull(cacheObj)) + { + return StringUtils.cast(cacheObj); + } + return null; + } + + /** + * 根据字典类型和字典值获取字典标签 + * + * @param dictType 字典类型 + * @param dictValue 字典值 + * @return 字典标签 + */ + public static String getDictLabel(String dictType, String dictValue) + { + return getDictLabel(dictType, dictValue, SEPARATOR); + } + + /** + * 根据字典类型和字典标签获取字典值 + * + * @param dictType 字典类型 + * @param dictLabel 字典标签 + * @return 字典值 + */ + public static String getDictValue(String dictType, String dictLabel) + { + return getDictValue(dictType, dictLabel, SEPARATOR); + } + + /** + * 根据字典类型和字典值获取字典标签 + * + * @param dictType 字典类型 + * @param dictValue 字典值 + * @param separator 分隔符 + * @return 字典标签 + */ + public static String getDictLabel(String dictType, String dictValue, String separator) + { + StringBuilder propertyString = new StringBuilder(); + List datas = getDictCache(dictType); + + if (StringUtils.containsAny(dictValue, separator) && StringUtils.isNotEmpty(datas)) + { + for (SysDictData dict : datas) + { + for (String value : dictValue.split(separator)) + { + if (value.equals(dict.getDictValue())) + { + propertyString.append(dict.getDictLabel()).append(separator); + break; + } + } + } + } + else + { + for (SysDictData dict : datas) + { + if (dictValue.equals(dict.getDictValue())) + { + return dict.getDictLabel(); + } + } + } + return StringUtils.stripEnd(propertyString.toString(), separator); + } + + /** + * 根据字典类型和字典标签获取字典值 + * + * @param dictType 字典类型 + * @param dictLabel 字典标签 + * @param separator 分隔符 + * @return 字典值 + */ + public static String getDictValue(String dictType, String dictLabel, String separator) + { + StringBuilder propertyString = new StringBuilder(); + List datas = getDictCache(dictType); + + if (StringUtils.containsAny(dictLabel, separator) && StringUtils.isNotEmpty(datas)) + { + for (SysDictData dict : datas) + { + for (String label : dictLabel.split(separator)) + { + if (label.equals(dict.getDictLabel())) + { + propertyString.append(dict.getDictValue()).append(separator); + break; + } + } + } + } + else + { + for (SysDictData dict : datas) + { + if (dictLabel.equals(dict.getDictLabel())) + { + return dict.getDictValue(); + } + } + } + return StringUtils.stripEnd(propertyString.toString(), separator); + } + + /** + * 删除指定字典缓存 + * + * @param key 字典键 + */ + public static void removeDictCache(String key) + { + CacheUtils.remove(getCacheName(), getCacheKey(key)); + } + + /** + * 清空字典缓存 + */ + public static void clearDictCache() + { + CacheUtils.removeAll(getCacheName()); + } + + /** + * 获取cache name + * + * @return 缓存名 + */ + public static String getCacheName() + { + return Constants.SYS_DICT_CACHE; + } + + /** + * 设置cache key + * + * @param configKey 参数键 + * @return 缓存键key + */ + public static String getCacheKey(String configKey) + { + return Constants.SYS_DICT_KEY + configKey; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/ExceptionUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ExceptionUtil.java new file mode 100644 index 0000000..214e4a0 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ExceptionUtil.java @@ -0,0 +1,39 @@ +package com.ruoyi.common.utils; + +import java.io.PrintWriter; +import java.io.StringWriter; +import org.apache.commons.lang3.exception.ExceptionUtils; + +/** + * 错误信息处理类。 + * + * @author ruoyi + */ +public class ExceptionUtil +{ + /** + * 获取exception的详细错误信息。 + */ + public static String getExceptionMessage(Throwable e) + { + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw, true)); + return sw.toString(); + } + + public static String getRootErrorMessage(Exception e) + { + Throwable root = ExceptionUtils.getRootCause(e); + root = (root == null ? e : root); + if (root == null) + { + return ""; + } + String msg = root.getMessage(); + if (msg == null) + { + return "null"; + } + return StringUtils.defaultString(msg); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/IpUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/IpUtils.java new file mode 100644 index 0000000..8fde230 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/IpUtils.java @@ -0,0 +1,370 @@ +package com.ruoyi.common.utils; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import javax.servlet.http.HttpServletRequest; + +/** + * 获取IP方法 + * + * @author ruoyi + */ +public class IpUtils +{ + public final static String REGX_0_255 = "(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)"; + // 匹配 ip + public final static String REGX_IP = "((" + REGX_0_255 + "\\.){3}" + REGX_0_255 + ")"; + public final static String REGX_IP_WILDCARD = "(((\\*\\.){3}\\*)|(" + REGX_0_255 + "(\\.\\*){3})|(" + REGX_0_255 + "\\." + REGX_0_255 + ")(\\.\\*){2}" + "|((" + REGX_0_255 + "\\.){3}\\*))"; + // 匹配网段 + public final static String REGX_IP_SEG = "(" + REGX_IP + "\\-" + REGX_IP + ")"; + + /** + * 获取客户端IP + * + * @param request 请求对象 + * @return IP地址 + */ + public static String getIpAddr(HttpServletRequest request) + { + if (request == null) + { + return "unknown"; + } + String ip = request.getHeader("x-forwarded-for"); + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getHeader("X-Forwarded-For"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getHeader("X-Real-IP"); + } + + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getRemoteAddr(); + } + + return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip); + } + + /** + * 检查是否为内部IP地址 + * + * @param ip IP地址 + * @return 结果 + */ + public static boolean internalIp(String ip) + { + byte[] addr = textToNumericFormatV4(ip); + return internalIp(addr) || "127.0.0.1".equals(ip); + } + + /** + * 检查是否为内部IP地址 + * + * @param addr byte地址 + * @return 结果 + */ + private static boolean internalIp(byte[] addr) + { + if (StringUtils.isNull(addr) || addr.length < 2) + { + return true; + } + final byte b0 = addr[0]; + final byte b1 = addr[1]; + // 10.x.x.x/8 + final byte SECTION_1 = 0x0A; + // 172.16.x.x/12 + final byte SECTION_2 = (byte) 0xAC; + final byte SECTION_3 = (byte) 0x10; + final byte SECTION_4 = (byte) 0x1F; + // 192.168.x.x/16 + final byte SECTION_5 = (byte) 0xC0; + final byte SECTION_6 = (byte) 0xA8; + switch (b0) + { + case SECTION_1: + return true; + case SECTION_2: + if (b1 >= SECTION_3 && b1 <= SECTION_4) + { + return true; + } + case SECTION_5: + switch (b1) + { + case SECTION_6: + return true; + } + default: + return false; + } + } + + /** + * 将IPv4地址转换成字节 + * + * @param text IPv4地址 + * @return byte 字节 + */ + public static byte[] textToNumericFormatV4(String text) + { + if (text.length() == 0) + { + return null; + } + + byte[] bytes = new byte[4]; + String[] elements = text.split("\\.", -1); + try + { + long l; + int i; + switch (elements.length) + { + case 1: + l = Long.parseLong(elements[0]); + if ((l < 0L) || (l > 4294967295L)) + { + return null; + } + bytes[0] = (byte) (int) (l >> 24 & 0xFF); + bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF); + bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 2: + l = Integer.parseInt(elements[0]); + if ((l < 0L) || (l > 255L)) + { + return null; + } + bytes[0] = (byte) (int) (l & 0xFF); + l = Integer.parseInt(elements[1]); + if ((l < 0L) || (l > 16777215L)) + { + return null; + } + bytes[1] = (byte) (int) (l >> 16 & 0xFF); + bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 3: + for (i = 0; i < 2; ++i) + { + l = Integer.parseInt(elements[i]); + if ((l < 0L) || (l > 255L)) + { + return null; + } + bytes[i] = (byte) (int) (l & 0xFF); + } + l = Integer.parseInt(elements[2]); + if ((l < 0L) || (l > 65535L)) + { + return null; + } + bytes[2] = (byte) (int) (l >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 4: + for (i = 0; i < 4; ++i) + { + l = Integer.parseInt(elements[i]); + if ((l < 0L) || (l > 255L)) + { + return null; + } + bytes[i] = (byte) (int) (l & 0xFF); + } + break; + default: + return null; + } + } + catch (NumberFormatException e) + { + return null; + } + return bytes; + } + + /** + * 获取IP地址 + * + * @return 本地IP地址 + */ + public static String getHostIp() + { + try + { + return InetAddress.getLocalHost().getHostAddress(); + } + catch (UnknownHostException e) + { + } + return "127.0.0.1"; + } + + /** + * 获取主机名 + * + * @return 本地主机名 + */ + public static String getHostName() + { + try + { + return InetAddress.getLocalHost().getHostName(); + } + catch (UnknownHostException e) + { + } + return "未知"; + } + + /** + * 从多级反向代理中获得第一个非unknown IP地址 + * + * @param ip 获得的IP地址 + * @return 第一个非unknown IP地址 + */ + public static String getMultistageReverseProxyIp(String ip) + { + // 多级反向代理检测 + if (ip != null && ip.indexOf(",") > 0) + { + final String[] ips = ip.trim().split(","); + for (String subIp : ips) + { + if (false == isUnknown(subIp)) + { + ip = subIp; + break; + } + } + } + return StringUtils.substring(ip, 0, 255); + } + + /** + * 检测给定字符串是否为未知,多用于检测HTTP请求相关 + * + * @param checkString 被检测的字符串 + * @return 是否未知 + */ + public static boolean isUnknown(String checkString) + { + return StringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString); + } + + /** + * 是否为IP + */ + public static boolean isIP(String ip) + { + return StringUtils.isNotBlank(ip) && ip.matches(REGX_IP); + } + + /** + * 是否为IP,或 *为间隔的通配符地址 + */ + public static boolean isIpWildCard(String ip) + { + return StringUtils.isNotBlank(ip) && ip.matches(REGX_IP_WILDCARD); + } + + /** + * 检测参数是否在ip通配符里 + */ + public static boolean ipIsInWildCardNoCheck(String ipWildCard, String ip) + { + String[] s1 = ipWildCard.split("\\."); + String[] s2 = ip.split("\\."); + boolean isMatchedSeg = true; + for (int i = 0; i < s1.length && !s1[i].equals("*"); i++) + { + if (!s1[i].equals(s2[i])) + { + isMatchedSeg = false; + break; + } + } + return isMatchedSeg; + } + + /** + * 是否为特定格式如:“10.10.10.1-10.10.10.99”的ip段字符串 + */ + public static boolean isIPSegment(String ipSeg) + { + return StringUtils.isNotBlank(ipSeg) && ipSeg.matches(REGX_IP_SEG); + } + + /** + * 判断ip是否在指定网段中 + */ + public static boolean ipIsInNetNoCheck(String iparea, String ip) + { + int idx = iparea.indexOf('-'); + String[] sips = iparea.substring(0, idx).split("\\."); + String[] sipe = iparea.substring(idx + 1).split("\\."); + String[] sipt = ip.split("\\."); + long ips = 0L, ipe = 0L, ipt = 0L; + for (int i = 0; i < 4; ++i) + { + ips = ips << 8 | Integer.parseInt(sips[i]); + ipe = ipe << 8 | Integer.parseInt(sipe[i]); + ipt = ipt << 8 | Integer.parseInt(sipt[i]); + } + if (ips > ipe) + { + long t = ips; + ips = ipe; + ipe = t; + } + return ips <= ipt && ipt <= ipe; + } + + /** + * 校验ip是否符合过滤串规则 + * + * @param filter 过滤IP列表,支持后缀'*'通配,支持网段如:`10.10.10.1-10.10.10.99` + * @param ip 校验IP地址 + * @return boolean 结果 + */ + public static boolean isMatchedIp(String filter, String ip) + { + if (StringUtils.isEmpty(filter) || StringUtils.isEmpty(ip)) + { + return false; + } + String[] ips = filter.split(";"); + for (String iStr : ips) + { + if (isIP(iStr) && iStr.equals(ip)) + { + return true; + } + else if (isIpWildCard(iStr) && ipIsInWildCardNoCheck(iStr, ip)) + { + return true; + } + else if (isIPSegment(iStr) && ipIsInNetNoCheck(iStr, ip)) + { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/LogUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/LogUtils.java new file mode 100644 index 0000000..a0e7d6c --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/LogUtils.java @@ -0,0 +1,135 @@ +package com.ruoyi.common.utils; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import org.apache.shiro.SecurityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.ruoyi.common.json.JSON; + +/** + * 处理并记录日志文件 + * + * @author ruoyi + */ +public class LogUtils +{ + public static final Logger ERROR_LOG = LoggerFactory.getLogger("sys-error"); + public static final Logger ACCESS_LOG = LoggerFactory.getLogger("sys-access"); + + /** + * 记录访问日志 [username][jsessionid][ip][accept][UserAgent][url][params][Referer] + * + * @param request + * @throws Exception + */ + public static void logAccess(HttpServletRequest request) throws Exception + { + String username = getUsername(); + String jsessionId = request.getRequestedSessionId(); + String ip = IpUtils.getIpAddr(request); + String accept = request.getHeader("accept"); + String userAgent = request.getHeader("User-Agent"); + String url = request.getRequestURI(); + String params = getParams(request); + + StringBuilder s = new StringBuilder(); + s.append(getBlock(username)); + s.append(getBlock(jsessionId)); + s.append(getBlock(ip)); + s.append(getBlock(accept)); + s.append(getBlock(userAgent)); + s.append(getBlock(url)); + s.append(getBlock(params)); + s.append(getBlock(request.getHeader("Referer"))); + getAccessLog().info(s.toString()); + } + + /** + * 记录异常错误 格式 [exception] + * + * @param message + * @param e + */ + public static void logError(String message, Throwable e) + { + String username = getUsername(); + StringBuilder s = new StringBuilder(); + s.append(getBlock("exception")); + s.append(getBlock(username)); + s.append(getBlock(message)); + ERROR_LOG.error(s.toString(), e); + } + + /** + * 记录页面错误 错误日志记录 [page/eception][username][statusCode][errorMessage][servletName][uri][exceptionName][ip][exception] + * + * @param request + */ + public static void logPageError(HttpServletRequest request) + { + String username = getUsername(); + + Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code"); + String message = (String) request.getAttribute("javax.servlet.error.message"); + String uri = (String) request.getAttribute("javax.servlet.error.request_uri"); + Throwable t = (Throwable) request.getAttribute("javax.servlet.error.exception"); + + if (statusCode == null) + { + statusCode = 0; + } + + StringBuilder s = new StringBuilder(); + s.append(getBlock(t == null ? "page" : "exception")); + s.append(getBlock(username)); + s.append(getBlock(statusCode)); + s.append(getBlock(message)); + s.append(getBlock(IpUtils.getIpAddr(request))); + + s.append(getBlock(uri)); + s.append(getBlock(request.getHeader("Referer"))); + StringWriter sw = new StringWriter(); + + while (t != null) + { + t.printStackTrace(new PrintWriter(sw)); + t = t.getCause(); + } + s.append(getBlock(sw.toString())); + getErrorLog().error(s.toString()); + + } + + public static String getBlock(Object msg) + { + if (msg == null) + { + msg = ""; + } + return "[" + msg.toString() + "]"; + } + + protected static String getParams(HttpServletRequest request) throws Exception + { + Map params = request.getParameterMap(); + return JSON.marshal(params); + } + + protected static String getUsername() + { + return (String) SecurityUtils.getSubject().getPrincipal(); + } + + public static Logger getAccessLog() + { + return ACCESS_LOG; + } + + public static Logger getErrorLog() + { + return ERROR_LOG; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/MapDataUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/MapDataUtil.java new file mode 100644 index 0000000..d932bfe --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/MapDataUtil.java @@ -0,0 +1,54 @@ +package com.ruoyi.common.utils; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import javax.servlet.http.HttpServletRequest; + +/** + * Map通用处理方法 + * + * @author ruoyi + */ +public class MapDataUtil +{ + public static Map convertDataMap(HttpServletRequest request) + { + Map properties = request.getParameterMap(); + Map returnMap = new HashMap(); + Iterator entries = properties.entrySet().iterator(); + Map.Entry entry; + String name = ""; + String value = ""; + while (entries.hasNext()) + { + entry = (Entry) entries.next(); + name = (String) entry.getKey(); + Object valueObj = entry.getValue(); + if (null == valueObj) + { + value = ""; + } + else if (valueObj instanceof String[]) + { + String[] values = (String[]) valueObj; + value = ""; + for (int i = 0; i < values.length; i++) + { + value += values[i] + ","; + } + if (value.length() > 0) + { + value = value.substring(0, value.length() - 1); + } + } + else + { + value = valueObj.toString(); + } + returnMap.put(name, value); + } + return returnMap; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/MessageUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/MessageUtils.java new file mode 100644 index 0000000..7dac75a --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/MessageUtils.java @@ -0,0 +1,26 @@ +package com.ruoyi.common.utils; + +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; +import com.ruoyi.common.utils.spring.SpringUtils; + +/** + * 获取i18n资源文件 + * + * @author ruoyi + */ +public class MessageUtils +{ + /** + * 根据消息键和参数 获取消息 委托给spring messageSource + * + * @param code 消息键 + * @param args 参数 + * @return 获取国际化翻译值 + */ + public static String message(String code, Object... args) + { + MessageSource messageSource = SpringUtils.getBean(MessageSource.class); + return messageSource.getMessage(code, args, LocaleContextHolder.getLocale()); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/PageUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/PageUtils.java new file mode 100644 index 0000000..70e9b08 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/PageUtils.java @@ -0,0 +1,35 @@ +package com.ruoyi.common.utils; + +import com.github.pagehelper.PageHelper; +import com.ruoyi.common.core.page.PageDomain; +import com.ruoyi.common.core.page.TableSupport; +import com.ruoyi.common.utils.sql.SqlUtil; + +/** + * 分页工具类 + * + * @author ruoyi + */ +public class PageUtils extends PageHelper +{ + /** + * 设置请求分页数据 + */ + public static void startPage() + { + PageDomain pageDomain = TableSupport.buildPageRequest(); + Integer pageNum = pageDomain.getPageNum(); + Integer pageSize = pageDomain.getPageSize(); + String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy()); + Boolean reasonable = pageDomain.getReasonable(); + PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable); + } + + /** + * 清理分页的线程变量 + */ + public static void clearPage() + { + PageHelper.clearPage(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java new file mode 100644 index 0000000..1f136ec --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java @@ -0,0 +1,216 @@ +package com.ruoyi.common.utils; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.text.Convert; + +/** + * 客户端工具类 + * + * @author ruoyi + */ +public class ServletUtils +{ + /** + * 定义移动端请求的所有可能类型 + */ + private final static String[] agent = { "Android", "iPhone", "iPod", "iPad", "Windows Phone", "MQQBrowser" }; + + /** + * 获取String参数 + */ + public static String getParameter(String name) + { + return getRequest().getParameter(name); + } + + /** + * 获取String参数 + */ + public static String getParameter(String name, String defaultValue) + { + return Convert.toStr(getRequest().getParameter(name), defaultValue); + } + + /** + * 获取Integer参数 + */ + public static Integer getParameterToInt(String name) + { + return Convert.toInt(getRequest().getParameter(name)); + } + + /** + * 获取Integer参数 + */ + public static Integer getParameterToInt(String name, Integer defaultValue) + { + return Convert.toInt(getRequest().getParameter(name), defaultValue); + } + + /** + * 获取Boolean参数 + */ + public static Boolean getParameterToBool(String name) + { + return Convert.toBool(getRequest().getParameter(name)); + } + + /** + * 获取Boolean参数 + */ + public static Boolean getParameterToBool(String name, Boolean defaultValue) + { + return Convert.toBool(getRequest().getParameter(name), defaultValue); + } + + /** + * 获取request + */ + public static HttpServletRequest getRequest() + { + return getRequestAttributes().getRequest(); + } + + /** + * 获取response + */ + public static HttpServletResponse getResponse() + { + return getRequestAttributes().getResponse(); + } + + /** + * 获取session + */ + public static HttpSession getSession() + { + return getRequest().getSession(); + } + + public static ServletRequestAttributes getRequestAttributes() + { + RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); + return (ServletRequestAttributes) attributes; + } + + /** + * 将字符串渲染到客户端 + * + * @param response 渲染对象 + * @param string 待渲染的字符串 + * @return null + */ + public static String renderString(HttpServletResponse response, String string) + { + try + { + response.setContentType("application/json"); + response.setCharacterEncoding("utf-8"); + response.getWriter().print(string); + } + catch (IOException e) + { + e.printStackTrace(); + } + return null; + } + + /** + * 是否是Ajax异步请求 + * + * @param request + */ + public static boolean isAjaxRequest(HttpServletRequest request) + { + String accept = request.getHeader("accept"); + if (accept != null && accept.contains("application/json")) + { + return true; + } + + String xRequestedWith = request.getHeader("X-Requested-With"); + if (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest")) + { + return true; + } + + String uri = request.getRequestURI(); + if (StringUtils.inStringIgnoreCase(uri, ".json", ".xml")) + { + return true; + } + + String ajax = request.getParameter("__ajax"); + return StringUtils.inStringIgnoreCase(ajax, "json", "xml"); + } + + /** + * 判断User-Agent 是不是来自于手机 + */ + public static boolean checkAgentIsMobile(String ua) + { + boolean flag = false; + if (!ua.contains("Windows NT") || (ua.contains("Windows NT") && ua.contains("compatible; MSIE 9.0;"))) + { + // 排除 苹果桌面系统 + if (!ua.contains("Windows NT") && !ua.contains("Macintosh")) + { + for (String item : agent) + { + if (ua.contains(item)) + { + flag = true; + break; + } + } + } + } + return flag; + } + + /** + * 内容编码 + * + * @param str 内容 + * @return 编码后的内容 + */ + public static String urlEncode(String str) + { + try + { + return URLEncoder.encode(str, Constants.UTF8); + } + catch (UnsupportedEncodingException e) + { + return StringUtils.EMPTY; + } + } + + /** + * 内容解码 + * + * @param str 内容 + * @return 解码后的内容 + */ + public static String urlDecode(String str) + { + try + { + return URLDecoder.decode(str, Constants.UTF8); + } + catch (UnsupportedEncodingException e) + { + return StringUtils.EMPTY; + } + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/ShiroUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ShiroUtils.java new file mode 100644 index 0000000..23d266e --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ShiroUtils.java @@ -0,0 +1,86 @@ +package com.ruoyi.common.utils; + +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.crypto.SecureRandomNumberGenerator; +import org.apache.shiro.session.Session; +import org.apache.shiro.subject.Subject; +import org.apache.shiro.subject.PrincipalCollection; +import org.apache.shiro.subject.SimplePrincipalCollection; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.utils.bean.BeanUtils; + +/** + * shiro 工具类 + * + * @author ruoyi + */ +public class ShiroUtils +{ + public static Subject getSubject() + { + return SecurityUtils.getSubject(); + } + + public static Session getSession() + { + return SecurityUtils.getSubject().getSession(); + } + + public static void logout() + { + getSubject().logout(); + } + + public static SysUser getSysUser() + { + SysUser user = null; + Object obj = getSubject().getPrincipal(); + if (StringUtils.isNotNull(obj)) + { + user = new SysUser(); + BeanUtils.copyBeanProp(user, obj); + } + return user; + } + + public static void setSysUser(SysUser user) + { + Subject subject = getSubject(); + PrincipalCollection principalCollection = subject.getPrincipals(); + String realmName = principalCollection.getRealmNames().iterator().next(); + PrincipalCollection newPrincipalCollection = new SimplePrincipalCollection(user, realmName); + // 重新加载Principal + subject.runAs(newPrincipalCollection); + } + + public static Long getUserId() + { + return getSysUser().getUserId().longValue(); + } + + public static String getLoginName() + { + return getSysUser().getLoginName(); + } + + public static String getIp() + { + return StringUtils.substring(getSubject().getSession().getHost(), 0, 128); + } + + public static String getSessionId() + { + return String.valueOf(getSubject().getSession().getId()); + } + + /** + * 生成随机盐 + */ + public static String randomSalt() + { + // 一个Byte占两个字节,此处生成的3字节,字符串长度为6 + SecureRandomNumberGenerator secureRandom = new SecureRandomNumberGenerator(); + String hex = secureRandom.nextBytes(3).toHex(); + return hex; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java new file mode 100644 index 0000000..dca86e5 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java @@ -0,0 +1,630 @@ +package com.ruoyi.common.utils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.springframework.util.AntPathMatcher; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.text.StrFormatter; + +/** + * 字符串工具类 + * + * @author ruoyi + */ +public class StringUtils extends org.apache.commons.lang3.StringUtils +{ + /** 空字符串 */ + private static final String NULLSTR = ""; + + /** 下划线 */ + private static final char SEPARATOR = '_'; + + /** + * 获取参数不为空值 + * + * @param value defaultValue 要判断的value + * @return value 返回值 + */ + public static T nvl(T value, T defaultValue) + { + return value != null ? value : defaultValue; + } + + /** + * * 判断一个Collection是否为空, 包含List,Set,Queue + * + * @param coll 要判断的Collection + * @return true:为空 false:非空 + */ + public static boolean isEmpty(Collection coll) + { + return isNull(coll) || coll.isEmpty(); + } + + /** + * * 判断一个Collection是否非空,包含List,Set,Queue + * + * @param coll 要判断的Collection + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Collection coll) + { + return !isEmpty(coll); + } + + /** + * * 判断一个对象数组是否为空 + * + * @param objects 要判断的对象数组 + ** @return true:为空 false:非空 + */ + public static boolean isEmpty(Object[] objects) + { + return isNull(objects) || (objects.length == 0); + } + + /** + * * 判断一个对象数组是否非空 + * + * @param objects 要判断的对象数组 + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Object[] objects) + { + return !isEmpty(objects); + } + + /** + * * 判断一个Map是否为空 + * + * @param map 要判断的Map + * @return true:为空 false:非空 + */ + public static boolean isEmpty(Map map) + { + return isNull(map) || map.isEmpty(); + } + + /** + * * 判断一个Map是否为空 + * + * @param map 要判断的Map + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Map map) + { + return !isEmpty(map); + } + + /** + * * 判断一个字符串是否为空串 + * + * @param str String + * @return true:为空 false:非空 + */ + public static boolean isEmpty(String str) + { + return isNull(str) || NULLSTR.equals(str.trim()); + } + + /** + * * 判断一个字符串是否为非空串 + * + * @param str String + * @return true:非空串 false:空串 + */ + public static boolean isNotEmpty(String str) + { + return !isEmpty(str); + } + + /** + * * 判断一个对象是否为空 + * + * @param object Object + * @return true:为空 false:非空 + */ + public static boolean isNull(Object object) + { + return object == null; + } + + /** + * * 判断一个对象是否非空 + * + * @param object Object + * @return true:非空 false:空 + */ + public static boolean isNotNull(Object object) + { + return !isNull(object); + } + + /** + * * 判断一个对象是否是数组类型(Java基本型别的数组) + * + * @param object 对象 + * @return true:是数组 false:不是数组 + */ + public static boolean isArray(Object object) + { + return isNotNull(object) && object.getClass().isArray(); + } + + /** + * 去空格 + */ + public static String trim(String str) + { + return (str == null ? "" : str.trim()); + } + + /** + * 截取字符串 + * + * @param str 字符串 + * @param start 开始 + * @return 结果 + */ + public static String substring(final String str, int start) + { + if (str == null) + { + return NULLSTR; + } + + if (start < 0) + { + start = str.length() + start; + } + + if (start < 0) + { + start = 0; + } + if (start > str.length()) + { + return NULLSTR; + } + + return str.substring(start); + } + + /** + * 截取字符串 + * + * @param str 字符串 + * @param start 开始 + * @param end 结束 + * @return 结果 + */ + public static String substring(final String str, int start, int end) + { + if (str == null) + { + return NULLSTR; + } + + if (end < 0) + { + end = str.length() + end; + } + if (start < 0) + { + start = str.length() + start; + } + + if (end > str.length()) + { + end = str.length(); + } + + if (start > end) + { + return NULLSTR; + } + + if (start < 0) + { + start = 0; + } + if (end < 0) + { + end = 0; + } + + return str.substring(start, end); + } + + /** + * 格式化文本, {} 表示占位符
    + * 此方法只是简单将占位符 {} 按照顺序替换为参数
    + * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可
    + * 例:
    + * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b
    + * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a
    + * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b
    + * + * @param template 文本模板,被替换的部分用 {} 表示 + * @param params 参数值 + * @return 格式化后的文本 + */ + public static String format(String template, Object... params) + { + if (isEmpty(params) || isEmpty(template)) + { + return template; + } + return StrFormatter.format(template, params); + } + + /** + * 是否为http(s)://开头 + * + * @param link 链接 + * @return 结果 + */ + public static boolean ishttp(String link) + { + return StringUtils.startsWithAny(link, Constants.HTTP, Constants.HTTPS); + } + + /** + * 字符串转set + * + * @param str 字符串 + * @param sep 分隔符 + * @return set集合 + */ + public static final Set str2Set(String str, String sep) + { + return new HashSet(str2List(str, sep, true, false)); + } + + /** + * 字符串转list + * + * @param str 字符串 + * @param sep 分隔符 + * @param filterBlank 过滤纯空白 + * @param trim 去掉首尾空白 + * @return list集合 + */ + public static final List str2List(String str, String sep, boolean filterBlank, boolean trim) + { + List list = new ArrayList(); + if (StringUtils.isEmpty(str)) + { + return list; + } + + // 过滤空白字符串 + if (filterBlank && StringUtils.isBlank(str)) + { + return list; + } + String[] split = str.split(sep); + for (String string : split) + { + if (filterBlank && StringUtils.isBlank(string)) + { + continue; + } + if (trim) + { + string = string.trim(); + } + list.add(string); + } + + return list; + } + + /** + * 判断给定的set列表中是否包含数组array 判断给定的数组array中是否包含给定的元素value + * + * @param set 给定的集合 + * @param array 给定的数组 + * @return boolean 结果 + */ + public static boolean containsAny(Collection collection, String... array) + { + if (isEmpty(collection) || isEmpty(array)) + { + return false; + } + else + { + for (String str : array) + { + if (collection.contains(str)) + { + return true; + } + } + return false; + } + } + + /** + * 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写 + * + * @param cs 指定字符串 + * @param searchCharSequences 需要检查的字符串数组 + * @return 是否包含任意一个字符串 + */ + public static boolean containsAnyIgnoreCase(CharSequence cs, CharSequence... searchCharSequences) + { + if (isEmpty(cs) || isEmpty(searchCharSequences)) + { + return false; + } + for (CharSequence testStr : searchCharSequences) + { + if (containsIgnoreCase(cs, testStr)) + { + return true; + } + } + return false; + } + + /** + * 驼峰转下划线命名 + */ + public static String toUnderScoreCase(String str) + { + if (str == null) + { + return null; + } + StringBuilder sb = new StringBuilder(); + // 前置字符是否大写 + boolean preCharIsUpperCase = true; + // 当前字符是否大写 + boolean curreCharIsUpperCase = true; + // 下一字符是否大写 + boolean nexteCharIsUpperCase = true; + for (int i = 0; i < str.length(); i++) + { + char c = str.charAt(i); + if (i > 0) + { + preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1)); + } + else + { + preCharIsUpperCase = false; + } + + curreCharIsUpperCase = Character.isUpperCase(c); + + if (i < (str.length() - 1)) + { + nexteCharIsUpperCase = Character.isUpperCase(str.charAt(i + 1)); + } + + if (preCharIsUpperCase && curreCharIsUpperCase && !nexteCharIsUpperCase) + { + sb.append(SEPARATOR); + } + else if ((i != 0 && !preCharIsUpperCase) && curreCharIsUpperCase) + { + sb.append(SEPARATOR); + } + sb.append(Character.toLowerCase(c)); + } + + return sb.toString(); + } + + /** + * 是否包含字符串 + * + * @param str 验证字符串 + * @param strs 字符串组 + * @return 包含返回true + */ + public static boolean inStringIgnoreCase(String str, String... strs) + { + if (str != null && strs != null) + { + for (String s : strs) + { + if (str.equalsIgnoreCase(trim(s))) + { + return true; + } + } + } + return false; + } + + /** + * 删除最后一个字符串 + * + * @param str 输入字符串 + * @param spit 以什么类型结尾的 + * @return 截取后的字符串 + */ + public static String lastStringDel(String str, String spit) + { + if (!StringUtils.isEmpty(str) && str.endsWith(spit)) + { + return str.subSequence(0, str.length() - 1).toString(); + } + return str; + } + + /** + * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 例如:HELLO_WORLD->HelloWorld + * + * @param name 转换前的下划线大写方式命名的字符串 + * @return 转换后的驼峰式命名的字符串 + */ + public static String convertToCamelCase(String name) + { + StringBuilder result = new StringBuilder(); + // 快速检查 + if (name == null || name.isEmpty()) + { + // 没必要转换 + return ""; + } + else if (!name.contains("_")) + { + // 不含下划线,仅将首字母大写 + return name.substring(0, 1).toUpperCase() + name.substring(1); + } + // 用下划线将原始字符串分割 + String[] camels = name.split("_"); + for (String camel : camels) + { + // 跳过原始字符串中开头、结尾的下换线或双重下划线 + if (camel.isEmpty()) + { + continue; + } + // 首字母大写 + result.append(camel.substring(0, 1).toUpperCase()); + result.append(camel.substring(1).toLowerCase()); + } + return result.toString(); + } + + /** + * 驼峰式命名法 + * 例如:user_name->userName + */ + public static String toCamelCase(String s) + { + if (s == null) + { + return null; + } + if (s.indexOf(SEPARATOR) == -1) + { + return s; + } + s = s.toLowerCase(); + StringBuilder sb = new StringBuilder(s.length()); + boolean upperCase = false; + for (int i = 0; i < s.length(); i++) + { + char c = s.charAt(i); + + if (c == SEPARATOR) + { + upperCase = true; + } + else if (upperCase) + { + sb.append(Character.toUpperCase(c)); + upperCase = false; + } + else + { + sb.append(c); + } + } + return sb.toString(); + } + + /** + * 查找指定字符串是否匹配指定字符串列表中的任意一个字符串 + * + * @param str 指定字符串 + * @param strs 需要检查的字符串数组 + * @return 是否匹配 + */ + public static boolean matches(String str, List strs) + { + if (isEmpty(str) || isEmpty(strs)) + { + return false; + } + for (String pattern : strs) + { + if (isMatch(pattern, str)) + { + return true; + } + } + return false; + } + + /** + * 判断url是否与规则配置: + * ? 表示单个字符; + * * 表示一层路径内的任意字符串,不可跨层级; + * ** 表示任意层路径; + * + * @param pattern 匹配规则 + * @param url 需要匹配的url + * @return + */ + public static boolean isMatch(String pattern, String url) + { + AntPathMatcher matcher = new AntPathMatcher(); + return matcher.match(pattern, url); + } + + @SuppressWarnings("unchecked") + public static T cast(Object obj) + { + return (T) obj; + } + + /** + * 数字左边补齐0,使之达到指定长度。注意,如果数字转换为字符串后,长度大于size,则只保留 最后size个字符。 + * + * @param num 数字对象 + * @param size 字符串指定长度 + * @return 返回数字的字符串格式,该字符串为指定长度。 + */ + public static final String padl(final Number num, final int size) + { + return padl(num.toString(), size, '0'); + } + + /** + * 字符串左补齐。如果原始字符串s长度大于size,则只保留最后size个字符。 + * + * @param s 原始字符串 + * @param size 字符串指定长度 + * @param c 用于补齐的字符 + * @return 返回指定长度的字符串,由原字符串左补齐或截取得到。 + */ + public static final String padl(final String s, final int size, final char c) + { + final StringBuilder sb = new StringBuilder(size); + if (s != null) + { + final int len = s.length(); + if (s.length() <= size) + { + for (int i = size - len; i > 0; i--) + { + sb.append(c); + } + sb.append(s); + } + else + { + return s.substring(len - size, len); + } + } + else + { + for (int i = size; i > 0; i--) + { + sb.append(c); + } + } + return sb.toString(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/Threads.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/Threads.java new file mode 100644 index 0000000..1934195 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/Threads.java @@ -0,0 +1,99 @@ +package com.ruoyi.common.utils; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 线程相关工具类. + * + * @author ruoyi + */ +public class Threads +{ + private static final Logger logger = LoggerFactory.getLogger(Threads.class); + + /** + * sleep等待,单位为毫秒 + */ + public static void sleep(long milliseconds) + { + try + { + Thread.sleep(milliseconds); + } + catch (InterruptedException e) + { + return; + } + } + + /** + * 停止线程池 + * 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务. + * 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数. + * 如果仍人超時,則強制退出. + * 另对在shutdown时线程本身被调用中断做了处理. + */ + public static void shutdownAndAwaitTermination(ExecutorService pool) + { + if (pool != null && !pool.isShutdown()) + { + pool.shutdown(); + try + { + if (!pool.awaitTermination(120, TimeUnit.SECONDS)) + { + pool.shutdownNow(); + if (!pool.awaitTermination(120, TimeUnit.SECONDS)) + { + logger.info("Pool did not terminate"); + } + } + } + catch (InterruptedException ie) + { + pool.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + } + + /** + * 打印线程异常信息 + */ + public static void printException(Runnable r, Throwable t) + { + if (t == null && r instanceof Future) + { + try + { + Future future = (Future) r; + if (future.isDone()) + { + future.get(); + } + } + catch (CancellationException ce) + { + t = ce; + } + catch (ExecutionException ee) + { + t = ee.getCause(); + } + catch (InterruptedException ie) + { + Thread.currentThread().interrupt(); + } + } + if (t != null) + { + logger.error(t.getMessage(), t); + } + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanUtils.java new file mode 100644 index 0000000..4463662 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanUtils.java @@ -0,0 +1,110 @@ +package com.ruoyi.common.utils.bean; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Bean 工具类 + * + * @author ruoyi + */ +public class BeanUtils extends org.springframework.beans.BeanUtils +{ + /** Bean方法名中属性名开始的下标 */ + private static final int BEAN_METHOD_PROP_INDEX = 3; + + /** * 匹配getter方法的正则表达式 */ + private static final Pattern GET_PATTERN = Pattern.compile("get(\\p{javaUpperCase}\\w*)"); + + /** * 匹配setter方法的正则表达式 */ + private static final Pattern SET_PATTERN = Pattern.compile("set(\\p{javaUpperCase}\\w*)"); + + /** + * Bean属性复制工具方法。 + * + * @param dest 目标对象 + * @param src 源对象 + */ + public static void copyBeanProp(Object dest, Object src) + { + try + { + copyProperties(src, dest); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /** + * 获取对象的setter方法。 + * + * @param obj 对象 + * @return 对象的setter方法列表 + */ + public static List getSetterMethods(Object obj) + { + // setter方法列表 + List setterMethods = new ArrayList(); + + // 获取所有方法 + Method[] methods = obj.getClass().getMethods(); + + // 查找setter方法 + + for (Method method : methods) + { + Matcher m = SET_PATTERN.matcher(method.getName()); + if (m.matches() && (method.getParameterTypes().length == 1)) + { + setterMethods.add(method); + } + } + // 返回setter方法列表 + return setterMethods; + } + + /** + * 获取对象的getter方法。 + * + * @param obj 对象 + * @return 对象的getter方法列表 + */ + + public static List getGetterMethods(Object obj) + { + // getter方法列表 + List getterMethods = new ArrayList(); + // 获取所有方法 + Method[] methods = obj.getClass().getMethods(); + // 查找getter方法 + for (Method method : methods) + { + Matcher m = GET_PATTERN.matcher(method.getName()); + if (m.matches() && (method.getParameterTypes().length == 0)) + { + getterMethods.add(method); + } + } + // 返回getter方法列表 + return getterMethods; + } + + /** + * 检查Bean方法名中的属性名是否相等。
    + * 如getName()和setName()属性名一样,getName()和setAge()属性名不一样。 + * + * @param m1 方法名1 + * @param m2 方法名2 + * @return 属性名一样返回true,否则返回false + */ + + public static boolean isMethodPropEquals(String m1, String m2) + { + return m1.substring(BEAN_METHOD_PROP_INDEX).equals(m2.substring(BEAN_METHOD_PROP_INDEX)); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanValidators.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanValidators.java new file mode 100644 index 0000000..80bfed7 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanValidators.java @@ -0,0 +1,24 @@ +package com.ruoyi.common.utils.bean; + +import java.util.Set; +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.Validator; + +/** + * bean对象属性验证 + * + * @author ruoyi + */ +public class BeanValidators +{ + public static void validateWithException(Validator validator, Object object, Class... groups) + throws ConstraintViolationException + { + Set> constraintViolations = validator.validate(object, groups); + if (!constraintViolations.isEmpty()) + { + throw new ConstraintViolationException(constraintViolations); + } + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileTypeUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileTypeUtils.java new file mode 100644 index 0000000..68130b9 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileTypeUtils.java @@ -0,0 +1,76 @@ +package com.ruoyi.common.utils.file; + +import java.io.File; +import org.apache.commons.lang3.StringUtils; + +/** + * 文件类型工具类 + * + * @author ruoyi + */ +public class FileTypeUtils +{ + /** + * 获取文件类型 + *

    + * 例如: ruoyi.txt, 返回: txt + * + * @param file 文件名 + * @return 后缀(不含".") + */ + public static String getFileType(File file) + { + if (null == file) + { + return StringUtils.EMPTY; + } + return getFileType(file.getName()); + } + + /** + * 获取文件类型 + *

    + * 例如: ruoyi.txt, 返回: txt + * + * @param fileName 文件名 + * @return 后缀(不含".") + */ + public static String getFileType(String fileName) + { + int separatorIndex = fileName.lastIndexOf("."); + if (separatorIndex < 0) + { + return ""; + } + return fileName.substring(separatorIndex + 1).toLowerCase(); + } + + /** + * 获取文件类型 + * + * @param photoByte 文件字节码 + * @return 后缀(不含".") + */ + public static String getFileExtendName(byte[] photoByte) + { + String strFileExtendName = "JPG"; + if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56) + && ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97)) + { + strFileExtendName = "GIF"; + } + else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70)) + { + strFileExtendName = "JPG"; + } + else if ((photoByte[0] == 66) && (photoByte[1] == 77)) + { + strFileExtendName = "BMP"; + } + else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71)) + { + strFileExtendName = "PNG"; + } + return strFileExtendName; + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java new file mode 100644 index 0000000..7d9e57d --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java @@ -0,0 +1,232 @@ +package com.ruoyi.common.utils.file; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.Objects; +import org.apache.commons.io.FilenameUtils; +import org.springframework.web.multipart.MultipartFile; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.exception.file.FileNameLengthLimitExceededException; +import com.ruoyi.common.exception.file.FileSizeLimitExceededException; +import com.ruoyi.common.exception.file.InvalidExtensionException; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.uuid.Seq; + +/** + * 文件上传工具类 + * + * @author ruoyi + */ +public class FileUploadUtils +{ + /** + * 默认大小 50M + */ + public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024; + + /** + * 默认的文件名最大长度 100 + */ + public static final int DEFAULT_FILE_NAME_LENGTH = 100; + + /** + * 默认上传的地址 + */ + private static String defaultBaseDir = RuoYiConfig.getProfile(); + + public static void setDefaultBaseDir(String defaultBaseDir) + { + FileUploadUtils.defaultBaseDir = defaultBaseDir; + } + + public static String getDefaultBaseDir() + { + return defaultBaseDir; + } + + /** + * 以默认配置进行文件上传 + * + * @param file 上传的文件 + * @return 文件名称 + * @throws Exception + */ + public static final String upload(MultipartFile file) throws IOException + { + try + { + return upload(getDefaultBaseDir(), file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); + } + catch (Exception e) + { + throw new IOException(e.getMessage(), e); + } + } + + /** + * 根据文件路径上传 + * + * @param baseDir 相对应用的基目录 + * @param file 上传的文件 + * @return 文件名称 + * @throws IOException + */ + public static final String upload(String baseDir, MultipartFile file) throws IOException + { + try + { + return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); + } + catch (Exception e) + { + throw new IOException(e.getMessage(), e); + } + } + + /** + * 文件上传 + * + * @param baseDir 相对应用的基目录 + * @param file 上传的文件 + * @param allowedExtension 上传文件类型 + * @return 返回上传成功的文件名 + * @throws FileSizeLimitExceededException 如果超出最大大小 + * @throws FileNameLengthLimitExceededException 文件名太长 + * @throws IOException 比如读写文件出错时 + * @throws InvalidExtensionException 文件校验异常 + */ + public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension) + throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException, + InvalidExtensionException + { + int fileNameLength = Objects.requireNonNull(file.getOriginalFilename()).length(); + if (fileNameLength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) + { + throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH); + } + + assertAllowed(file, allowedExtension); + + String fileName = extractFilename(file); + + String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath(); + file.transferTo(Paths.get(absPath)); + return getPathFileName(baseDir, fileName); + } + + /** + * 编码文件名 + */ + public static final String extractFilename(MultipartFile file) + { + return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(), + FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file)); + } + + public static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException + { + File desc = new File(uploadDir + File.separator + fileName); + + if (!desc.exists()) + { + if (!desc.getParentFile().exists()) + { + desc.getParentFile().mkdirs(); + } + } + return desc; + } + + public static final String getPathFileName(String uploadDir, String fileName) throws IOException + { + int dirLastIndex = RuoYiConfig.getProfile().length() + 1; + String currentDir = StringUtils.substring(uploadDir, dirLastIndex); + return Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName; + } + + /** + * 文件大小校验 + * + * @param file 上传的文件 + * @return + * @throws FileSizeLimitExceededException 如果超出最大大小 + * @throws InvalidExtensionException + */ + public static final void assertAllowed(MultipartFile file, String[] allowedExtension) + throws FileSizeLimitExceededException, InvalidExtensionException + { + long size = file.getSize(); + if (size > DEFAULT_MAX_SIZE) + { + throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024); + } + + String fileName = file.getOriginalFilename(); + String extension = getExtension(file); + if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension)) + { + if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION) + { + throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension, + fileName); + } + else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION) + { + throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension, + fileName); + } + else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION) + { + throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension, + fileName); + } + else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION) + { + throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension, + fileName); + } + else + { + throw new InvalidExtensionException(allowedExtension, extension, fileName); + } + } + } + + /** + * 判断MIME类型是否是允许的MIME类型 + * + * @param extension + * @param allowedExtension + * @return + */ + public static final boolean isAllowedExtension(String extension, String[] allowedExtension) + { + for (String str : allowedExtension) + { + if (str.equalsIgnoreCase(extension)) + { + return true; + } + } + return false; + } + + /** + * 获取文件名的后缀 + * + * @param file 表单文件 + * @return 后缀名 + */ + public static final String getExtension(MultipartFile file) + { + String extension = FilenameUtils.getExtension(file.getOriginalFilename()); + if (StringUtils.isEmpty(extension)) + { + extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType())); + } + return extension; + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java new file mode 100644 index 0000000..62f0c62 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java @@ -0,0 +1,291 @@ +package com.ruoyi.common.utils.file; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.ArrayUtils; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.uuid.IdUtils; + +/** + * 文件处理工具类 + * + * @author ruoyi + */ +public class FileUtils +{ + public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+"; + + /** + * 输出指定文件的byte数组 + * + * @param filePath 文件路径 + * @param os 输出流 + * @return + */ + public static void writeBytes(String filePath, OutputStream os) throws IOException + { + FileInputStream fis = null; + try + { + File file = new File(filePath); + if (!file.exists()) + { + throw new FileNotFoundException(filePath); + } + fis = new FileInputStream(file); + byte[] b = new byte[1024]; + int length; + while ((length = fis.read(b)) > 0) + { + os.write(b, 0, length); + } + } + catch (IOException e) + { + throw e; + } + finally + { + IOUtils.close(os); + IOUtils.close(fis); + } + } + + /** + * 写数据到文件中 + * + * @param data 数据 + * @return 目标文件 + * @throws IOException IO异常 + */ + public static String writeImportBytes(byte[] data) throws IOException + { + return writeBytes(data, RuoYiConfig.getImportPath()); + } + + /** + * 写数据到文件中 + * + * @param data 数据 + * @param uploadDir 目标文件 + * @return 目标文件 + * @throws IOException IO异常 + */ + public static String writeBytes(byte[] data, String uploadDir) throws IOException + { + FileOutputStream fos = null; + String pathName = ""; + try + { + String extension = getFileExtendName(data); + pathName = DateUtils.datePath() + "/" + IdUtils.fastUUID() + "." + extension; + File file = FileUploadUtils.getAbsoluteFile(uploadDir, pathName); + fos = new FileOutputStream(file); + fos.write(data); + } + finally + { + IOUtils.close(fos); + } + return FileUploadUtils.getPathFileName(uploadDir, pathName); + } + + /** + * 删除文件 + * + * @param filePath 文件 + * @return + */ + public static boolean deleteFile(String filePath) + { + boolean flag = false; + File file = new File(filePath); + // 路径为文件且不为空则进行删除 + if (file.isFile() && file.exists()) + { + flag = file.delete(); + } + return flag; + } + + /** + * 文件名称验证 + * + * @param filename 文件名称 + * @return true 正常 false 非法 + */ + public static boolean isValidFilename(String filename) + { + return filename.matches(FILENAME_PATTERN); + } + + /** + * 检查文件是否可下载 + * + * @param resource 需要下载的文件 + * @return true 正常 false 非法 + */ + public static boolean checkAllowDownload(String resource) + { + // 禁止目录上跳级别 + if (StringUtils.contains(resource, "..")) + { + return false; + } + + // 检查允许下载的文件规则 + if (ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(resource))) + { + return true; + } + + // 不在允许下载的文件规则 + return false; + } + + /** + * 下载文件名重新编码 + * + * @param request 请求对象 + * @param fileName 文件名 + * @return 编码后的文件名 + */ + public static String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException + { + final String agent = request.getHeader("USER-AGENT"); + String filename = fileName; + if (agent.contains("MSIE")) + { + // IE浏览器 + filename = URLEncoder.encode(filename, "utf-8"); + filename = filename.replace("+", " "); + } + else if (agent.contains("Firefox")) + { + // 火狐浏览器 + filename = new String(fileName.getBytes(), "ISO8859-1"); + } + else if (agent.contains("Chrome")) + { + // google浏览器 + filename = URLEncoder.encode(filename, "utf-8"); + } + else + { + // 其它浏览器 + filename = URLEncoder.encode(filename, "utf-8"); + } + return filename; + } + + /** + * 下载文件名重新编码 + * + * @param response 响应对象 + * @param realFileName 真实文件名 + * @return + */ + public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException + { + String percentEncodedFileName = percentEncode(realFileName); + + StringBuilder contentDispositionValue = new StringBuilder(); + contentDispositionValue.append("attachment; filename=") + .append(percentEncodedFileName) + .append(";") + .append("filename*=") + .append("utf-8''") + .append(percentEncodedFileName); + + response.setHeader("Content-disposition", contentDispositionValue.toString()); + } + + /** + * 百分号编码工具方法 + * + * @param s 需要百分号编码的字符串 + * @return 百分号编码后的字符串 + */ + public static String percentEncode(String s) throws UnsupportedEncodingException + { + String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString()); + return encode.replaceAll("\\+", "%20"); + } + + /** + * 获取图像后缀 + * + * @param photoByte 图像数据 + * @return 后缀名 + */ + public static String getFileExtendName(byte[] photoByte) + { + String strFileExtendName = "jpg"; + if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56) + && ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97)) + { + strFileExtendName = "gif"; + } + else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70)) + { + strFileExtendName = "jpg"; + } + else if ((photoByte[0] == 66) && (photoByte[1] == 77)) + { + strFileExtendName = "bmp"; + } + else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71)) + { + strFileExtendName = "png"; + } + return strFileExtendName; + } + + /** + * 获取文件名称 /profile/upload/2022/04/16/ruoyi.png -- ruoyi.png + * + * @param fileName 路径名称 + * @return 没有文件路径的名称 + */ + public static String getName(String fileName) + { + if (fileName == null) + { + return null; + } + int lastUnixPos = fileName.lastIndexOf('/'); + int lastWindowsPos = fileName.lastIndexOf('\\'); + int index = Math.max(lastUnixPos, lastWindowsPos); + return fileName.substring(index + 1); + } + + /** + * 获取不带后缀文件名称 /profile/upload/2022/04/16/ruoyi.png -- ruoyi + * + * @param fileName 路径名称 + * @return 没有文件路径和后缀的名称 + */ + public static String getNameNotSuffix(String fileName) + { + if (fileName == null) + { + return null; + } + String baseName = FilenameUtils.getBaseName(fileName); + return baseName; + } +} + diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/ImageUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/ImageUtils.java new file mode 100644 index 0000000..432dfda --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/ImageUtils.java @@ -0,0 +1,98 @@ +package com.ruoyi.common.utils.file; + +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.util.Arrays; +import org.apache.poi.util.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.utils.StringUtils; + +/** + * 图片处理工具类 + * + * @author ruoyi + */ +public class ImageUtils +{ + private static final Logger log = LoggerFactory.getLogger(ImageUtils.class); + + public static byte[] getImage(String imagePath) + { + InputStream is = getFile(imagePath); + try + { + return IOUtils.toByteArray(is); + } + catch (Exception e) + { + log.error("图片加载异常 {}", e); + return null; + } + finally + { + IOUtils.closeQuietly(is); + } + } + + public static InputStream getFile(String imagePath) + { + try + { + byte[] result = readFile(imagePath); + result = Arrays.copyOf(result, result.length); + return new ByteArrayInputStream(result); + } + catch (Exception e) + { + log.error("获取图片异常 {}", e); + } + return null; + } + + /** + * 读取文件为字节数据 + * + * @param url 地址 + * @return 字节数据 + */ + public static byte[] readFile(String url) + { + InputStream in = null; + try + { + if (url.startsWith("http")) + { + // 网络地址 + URL urlObj = new URL(url); + URLConnection urlConnection = urlObj.openConnection(); + urlConnection.setConnectTimeout(30 * 1000); + urlConnection.setReadTimeout(60 * 1000); + urlConnection.setDoInput(true); + in = urlConnection.getInputStream(); + } + else + { + // 本机地址 + String localPath = RuoYiConfig.getProfile(); + String downloadPath = localPath + StringUtils.substringAfter(url, Constants.RESOURCE_PREFIX); + in = new FileInputStream(downloadPath); + } + return IOUtils.toByteArray(in); + } + catch (Exception e) + { + log.error("获取文件路径异常 {}", e); + return null; + } + finally + { + IOUtils.closeQuietly(in); + } + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java new file mode 100644 index 0000000..371e823 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java @@ -0,0 +1,59 @@ +package com.ruoyi.common.utils.file; + +/** + * 媒体类型工具类 + * + * @author ruoyi + */ +public class MimeTypeUtils +{ + public static final String IMAGE_PNG = "image/png"; + + public static final String IMAGE_JPG = "image/jpg"; + + public static final String IMAGE_JPEG = "image/jpeg"; + + public static final String IMAGE_BMP = "image/bmp"; + + public static final String IMAGE_GIF = "image/gif"; + + public static final String[] IMAGE_EXTENSION = { "bmp", "gif", "jpg", "jpeg", "png" }; + + public static final String[] FLASH_EXTENSION = { "swf", "flv" }; + + public static final String[] MEDIA_EXTENSION = { "swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg", + "asf", "rm", "rmvb" }; + + public static final String[] VIDEO_EXTENSION = { "mp4", "avi", "rmvb" }; + + public static final String[] DEFAULT_ALLOWED_EXTENSION = { + // 图片 + "bmp", "gif", "jpg", "jpeg", "png", + // word excel powerpoint + "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt", + // 压缩文件 + "rar", "zip", "gz", "bz2", + // 视频格式 + "mp4", "avi", "rmvb", + // pdf + "pdf" }; + + public static String getExtension(String prefix) + { + switch (prefix) + { + case IMAGE_PNG: + return "png"; + case IMAGE_JPG: + return "jpg"; + case IMAGE_JPEG: + return "jpeg"; + case IMAGE_BMP: + return "bmp"; + case IMAGE_GIF: + return "gif"; + default: + return ""; + } + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/html/EscapeUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/html/EscapeUtil.java new file mode 100644 index 0000000..f52e83e --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/html/EscapeUtil.java @@ -0,0 +1,167 @@ +package com.ruoyi.common.utils.html; + +import com.ruoyi.common.utils.StringUtils; + +/** + * 转义和反转义工具类 + * + * @author ruoyi + */ +public class EscapeUtil +{ + public static final String RE_HTML_MARK = "(<[^<]*?>)|(<[\\s]*?/[^<]*?>)|(<[^<]*?/[\\s]*?>)"; + + private static final char[][] TEXT = new char[64][]; + + static + { + for (int i = 0; i < 64; i++) + { + TEXT[i] = new char[] { (char) i }; + } + + // special HTML characters + TEXT['\''] = "'".toCharArray(); // 单引号 + TEXT['"'] = """.toCharArray(); // 双引号 + TEXT['&'] = "&".toCharArray(); // &符 + TEXT['<'] = "<".toCharArray(); // 小于号 + TEXT['>'] = ">".toCharArray(); // 大于号 + } + + /** + * 转义文本中的HTML字符为安全的字符 + * + * @param text 被转义的文本 + * @return 转义后的文本 + */ + public static String escape(String text) + { + return encode(text); + } + + /** + * 还原被转义的HTML特殊字符 + * + * @param content 包含转义符的HTML内容 + * @return 转换后的字符串 + */ + public static String unescape(String content) + { + return decode(content); + } + + /** + * 清除所有HTML标签,但是不删除标签内的内容 + * + * @param content 文本 + * @return 清除标签后的文本 + */ + public static String clean(String content) + { + return new HTMLFilter().filter(content); + } + + /** + * Escape编码 + * + * @param text 被编码的文本 + * @return 编码后的字符 + */ + private static String encode(String text) + { + if (StringUtils.isEmpty(text)) + { + return StringUtils.EMPTY; + } + + final StringBuilder tmp = new StringBuilder(text.length() * 6); + char c; + for (int i = 0; i < text.length(); i++) + { + c = text.charAt(i); + if (c < 256) + { + tmp.append("%"); + if (c < 16) + { + tmp.append("0"); + } + tmp.append(Integer.toString(c, 16)); + } + else + { + tmp.append("%u"); + if (c <= 0xfff) + { + // issue#I49JU8@Gitee + tmp.append("0"); + } + tmp.append(Integer.toString(c, 16)); + } + } + return tmp.toString(); + } + + /** + * Escape解码 + * + * @param content 被转义的内容 + * @return 解码后的字符串 + */ + public static String decode(String content) + { + if (StringUtils.isEmpty(content)) + { + return content; + } + + StringBuilder tmp = new StringBuilder(content.length()); + int lastPos = 0, pos = 0; + char ch; + while (lastPos < content.length()) + { + pos = content.indexOf("%", lastPos); + if (pos == lastPos) + { + if (content.charAt(pos + 1) == 'u') + { + ch = (char) Integer.parseInt(content.substring(pos + 2, pos + 6), 16); + tmp.append(ch); + lastPos = pos + 6; + } + else + { + ch = (char) Integer.parseInt(content.substring(pos + 1, pos + 3), 16); + tmp.append(ch); + lastPos = pos + 3; + } + } + else + { + if (pos == -1) + { + tmp.append(content.substring(lastPos)); + lastPos = content.length(); + } + else + { + tmp.append(content.substring(lastPos, pos)); + lastPos = pos; + } + } + } + return tmp.toString(); + } + + public static void main(String[] args) + { + String html = ""; + String escape = EscapeUtil.escape(html); + // String html = "ipt>alert(\"XSS\")ipt>"; + // String html = "<123"; + // String html = "123>"; + System.out.println("clean: " + EscapeUtil.clean(html)); + System.out.println("escape: " + escape); + System.out.println("unescape: " + EscapeUtil.unescape(escape)); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/html/HTMLFilter.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/html/HTMLFilter.java new file mode 100644 index 0000000..cd8cd4f --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/html/HTMLFilter.java @@ -0,0 +1,570 @@ +package com.ruoyi.common.utils.html; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * HTML过滤器,用于去除XSS漏洞隐患。 + * + * @author ruoyi + */ +public final class HTMLFilter +{ + /** + * regex flag union representing /si modifiers in php + **/ + private static final int REGEX_FLAGS_SI = Pattern.CASE_INSENSITIVE | Pattern.DOTALL; + private static final Pattern P_COMMENTS = Pattern.compile("", Pattern.DOTALL); + private static final Pattern P_COMMENT = Pattern.compile("^!--(.*)--$", REGEX_FLAGS_SI); + private static final Pattern P_TAGS = Pattern.compile("<(.*?)>", Pattern.DOTALL); + private static final Pattern P_END_TAG = Pattern.compile("^/([a-z0-9]+)", REGEX_FLAGS_SI); + private static final Pattern P_START_TAG = Pattern.compile("^([a-z0-9]+)(.*?)(/?)$", REGEX_FLAGS_SI); + private static final Pattern P_QUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)=([\"'])(.*?)\\2", REGEX_FLAGS_SI); + private static final Pattern P_UNQUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)(=)([^\"\\s']+)", REGEX_FLAGS_SI); + private static final Pattern P_PROTOCOL = Pattern.compile("^([^:]+):", REGEX_FLAGS_SI); + private static final Pattern P_ENTITY = Pattern.compile("&#(\\d+);?"); + private static final Pattern P_ENTITY_UNICODE = Pattern.compile("&#x([0-9a-f]+);?"); + private static final Pattern P_ENCODE = Pattern.compile("%([0-9a-f]{2});?"); + private static final Pattern P_VALID_ENTITIES = Pattern.compile("&([^&;]*)(?=(;|&|$))"); + private static final Pattern P_VALID_QUOTES = Pattern.compile("(>|^)([^<]+?)(<|$)", Pattern.DOTALL); + private static final Pattern P_END_ARROW = Pattern.compile("^>"); + private static final Pattern P_BODY_TO_END = Pattern.compile("<([^>]*?)(?=<|$)"); + private static final Pattern P_XML_CONTENT = Pattern.compile("(^|>)([^<]*?)(?=>)"); + private static final Pattern P_STRAY_LEFT_ARROW = Pattern.compile("<([^>]*?)(?=<|$)"); + private static final Pattern P_STRAY_RIGHT_ARROW = Pattern.compile("(^|>)([^<]*?)(?=>)"); + private static final Pattern P_AMP = Pattern.compile("&"); + private static final Pattern P_QUOTE = Pattern.compile("\""); + private static final Pattern P_LEFT_ARROW = Pattern.compile("<"); + private static final Pattern P_RIGHT_ARROW = Pattern.compile(">"); + private static final Pattern P_BOTH_ARROWS = Pattern.compile("<>"); + + // @xxx could grow large... maybe use sesat's ReferenceMap + private static final ConcurrentMap P_REMOVE_PAIR_BLANKS = new ConcurrentHashMap<>(); + private static final ConcurrentMap P_REMOVE_SELF_BLANKS = new ConcurrentHashMap<>(); + + /** + * set of allowed html elements, along with allowed attributes for each element + **/ + private final Map> vAllowed; + /** + * counts of open tags for each (allowable) html element + **/ + private final Map vTagCounts = new HashMap<>(); + + /** + * html elements which must always be self-closing (e.g. "") + **/ + private final String[] vSelfClosingTags; + /** + * html elements which must always have separate opening and closing tags (e.g. "") + **/ + private final String[] vNeedClosingTags; + /** + * set of disallowed html elements + **/ + private final String[] vDisallowed; + /** + * attributes which should be checked for valid protocols + **/ + private final String[] vProtocolAtts; + /** + * allowed protocols + **/ + private final String[] vAllowedProtocols; + /** + * tags which should be removed if they contain no content (e.g. "" or "") + **/ + private final String[] vRemoveBlanks; + /** + * entities allowed within html markup + **/ + private final String[] vAllowedEntities; + /** + * flag determining whether comments are allowed in input String. + */ + private final boolean stripComment; + private final boolean encodeQuotes; + /** + * flag determining whether to try to make tags when presented with "unbalanced" angle brackets (e.g. "" + * becomes " text "). If set to false, unbalanced angle brackets will be html escaped. + */ + private final boolean alwaysMakeTags; + + /** + * Default constructor. + */ + public HTMLFilter() + { + vAllowed = new HashMap<>(); + + final ArrayList a_atts = new ArrayList<>(); + a_atts.add("href"); + a_atts.add("target"); + vAllowed.put("a", a_atts); + + final ArrayList img_atts = new ArrayList<>(); + img_atts.add("src"); + img_atts.add("width"); + img_atts.add("height"); + img_atts.add("alt"); + vAllowed.put("img", img_atts); + + final ArrayList no_atts = new ArrayList<>(); + vAllowed.put("b", no_atts); + vAllowed.put("strong", no_atts); + vAllowed.put("i", no_atts); + vAllowed.put("em", no_atts); + + vSelfClosingTags = new String[] { "img" }; + vNeedClosingTags = new String[] { "a", "b", "strong", "i", "em" }; + vDisallowed = new String[] {}; + vAllowedProtocols = new String[] { "http", "mailto", "https" }; // no ftp. + vProtocolAtts = new String[] { "src", "href" }; + vRemoveBlanks = new String[] { "a", "b", "strong", "i", "em" }; + vAllowedEntities = new String[] { "amp", "gt", "lt", "quot" }; + stripComment = true; + encodeQuotes = true; + alwaysMakeTags = false; + } + + /** + * Map-parameter configurable constructor. + * + * @param conf map containing configuration. keys match field names. + */ + @SuppressWarnings("unchecked") + public HTMLFilter(final Map conf) + { + + assert conf.containsKey("vAllowed") : "configuration requires vAllowed"; + assert conf.containsKey("vSelfClosingTags") : "configuration requires vSelfClosingTags"; + assert conf.containsKey("vNeedClosingTags") : "configuration requires vNeedClosingTags"; + assert conf.containsKey("vDisallowed") : "configuration requires vDisallowed"; + assert conf.containsKey("vAllowedProtocols") : "configuration requires vAllowedProtocols"; + assert conf.containsKey("vProtocolAtts") : "configuration requires vProtocolAtts"; + assert conf.containsKey("vRemoveBlanks") : "configuration requires vRemoveBlanks"; + assert conf.containsKey("vAllowedEntities") : "configuration requires vAllowedEntities"; + + vAllowed = Collections.unmodifiableMap((HashMap>) conf.get("vAllowed")); + vSelfClosingTags = (String[]) conf.get("vSelfClosingTags"); + vNeedClosingTags = (String[]) conf.get("vNeedClosingTags"); + vDisallowed = (String[]) conf.get("vDisallowed"); + vAllowedProtocols = (String[]) conf.get("vAllowedProtocols"); + vProtocolAtts = (String[]) conf.get("vProtocolAtts"); + vRemoveBlanks = (String[]) conf.get("vRemoveBlanks"); + vAllowedEntities = (String[]) conf.get("vAllowedEntities"); + stripComment = conf.containsKey("stripComment") ? (Boolean) conf.get("stripComment") : true; + encodeQuotes = conf.containsKey("encodeQuotes") ? (Boolean) conf.get("encodeQuotes") : true; + alwaysMakeTags = conf.containsKey("alwaysMakeTags") ? (Boolean) conf.get("alwaysMakeTags") : true; + } + + private void reset() + { + vTagCounts.clear(); + } + + // --------------------------------------------------------------- + // my versions of some PHP library functions + public static String chr(final int decimal) + { + return String.valueOf((char) decimal); + } + + public static String htmlSpecialChars(final String s) + { + String result = s; + result = regexReplace(P_AMP, "&", result); + result = regexReplace(P_QUOTE, """, result); + result = regexReplace(P_LEFT_ARROW, "<", result); + result = regexReplace(P_RIGHT_ARROW, ">", result); + return result; + } + + // --------------------------------------------------------------- + + /** + * given a user submitted input String, filter out any invalid or restricted html. + * + * @param input text (i.e. submitted by a user) than may contain html + * @return "clean" version of input, with only valid, whitelisted html elements allowed + */ + public String filter(final String input) + { + reset(); + String s = input; + + s = escapeComments(s); + + s = balanceHTML(s); + + s = checkTags(s); + + s = processRemoveBlanks(s); + + // s = validateEntities(s); + + return s; + } + + public boolean isAlwaysMakeTags() + { + return alwaysMakeTags; + } + + public boolean isStripComments() + { + return stripComment; + } + + private String escapeComments(final String s) + { + final Matcher m = P_COMMENTS.matcher(s); + final StringBuffer buf = new StringBuffer(); + if (m.find()) + { + final String match = m.group(1); // (.*?) + m.appendReplacement(buf, Matcher.quoteReplacement("")); + } + m.appendTail(buf); + + return buf.toString(); + } + + private String balanceHTML(String s) + { + if (alwaysMakeTags) + { + // + // try and form html + // + s = regexReplace(P_END_ARROW, "", s); + // 不追加结束标签 + s = regexReplace(P_BODY_TO_END, "<$1>", s); + s = regexReplace(P_XML_CONTENT, "$1<$2", s); + + } + else + { + // + // escape stray brackets + // + s = regexReplace(P_STRAY_LEFT_ARROW, "<$1", s); + s = regexReplace(P_STRAY_RIGHT_ARROW, "$1$2><", s); + + // + // the last regexp causes '<>' entities to appear + // (we need to do a lookahead assertion so that the last bracket can + // be used in the next pass of the regexp) + // + s = regexReplace(P_BOTH_ARROWS, "", s); + } + + return s; + } + + private String checkTags(String s) + { + Matcher m = P_TAGS.matcher(s); + + final StringBuffer buf = new StringBuffer(); + while (m.find()) + { + String replaceStr = m.group(1); + replaceStr = processTag(replaceStr); + m.appendReplacement(buf, Matcher.quoteReplacement(replaceStr)); + } + m.appendTail(buf); + + // these get tallied in processTag + // (remember to reset before subsequent calls to filter method) + final StringBuilder sBuilder = new StringBuilder(buf.toString()); + for (String key : vTagCounts.keySet()) + { + for (int ii = 0; ii < vTagCounts.get(key); ii++) + { + sBuilder.append(""); + } + } + s = sBuilder.toString(); + + return s; + } + + private String processRemoveBlanks(final String s) + { + String result = s; + for (String tag : vRemoveBlanks) + { + if (!P_REMOVE_PAIR_BLANKS.containsKey(tag)) + { + P_REMOVE_PAIR_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?>")); + } + result = regexReplace(P_REMOVE_PAIR_BLANKS.get(tag), "", result); + if (!P_REMOVE_SELF_BLANKS.containsKey(tag)) + { + P_REMOVE_SELF_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?/>")); + } + result = regexReplace(P_REMOVE_SELF_BLANKS.get(tag), "", result); + } + + return result; + } + + private static String regexReplace(final Pattern regex_pattern, final String replacement, final String s) + { + Matcher m = regex_pattern.matcher(s); + return m.replaceAll(replacement); + } + + private String processTag(final String s) + { + // ending tags + Matcher m = P_END_TAG.matcher(s); + if (m.find()) + { + final String name = m.group(1).toLowerCase(); + if (allowed(name)) + { + if (false == inArray(name, vSelfClosingTags)) + { + if (vTagCounts.containsKey(name)) + { + vTagCounts.put(name, vTagCounts.get(name) - 1); + return ""; + } + } + } + } + + // starting tags + m = P_START_TAG.matcher(s); + if (m.find()) + { + final String name = m.group(1).toLowerCase(); + final String body = m.group(2); + String ending = m.group(3); + + // debug( "in a starting tag, name='" + name + "'; body='" + body + "'; ending='" + ending + "'" ); + if (allowed(name)) + { + final StringBuilder params = new StringBuilder(); + + final Matcher m2 = P_QUOTED_ATTRIBUTES.matcher(body); + final Matcher m3 = P_UNQUOTED_ATTRIBUTES.matcher(body); + final List paramNames = new ArrayList<>(); + final List paramValues = new ArrayList<>(); + while (m2.find()) + { + paramNames.add(m2.group(1)); // ([a-z0-9]+) + paramValues.add(m2.group(3)); // (.*?) + } + while (m3.find()) + { + paramNames.add(m3.group(1)); // ([a-z0-9]+) + paramValues.add(m3.group(3)); // ([^\"\\s']+) + } + + String paramName, paramValue; + for (int ii = 0; ii < paramNames.size(); ii++) + { + paramName = paramNames.get(ii).toLowerCase(); + paramValue = paramValues.get(ii); + + // debug( "paramName='" + paramName + "'" ); + // debug( "paramValue='" + paramValue + "'" ); + // debug( "allowed? " + vAllowed.get( name ).contains( paramName ) ); + + if (allowedAttribute(name, paramName)) + { + if (inArray(paramName, vProtocolAtts)) + { + paramValue = processParamProtocol(paramValue); + } + params.append(' ').append(paramName).append("=\"").append(paramValue).append("\""); + } + } + + if (inArray(name, vSelfClosingTags)) + { + ending = " /"; + } + + if (inArray(name, vNeedClosingTags)) + { + ending = ""; + } + + if (ending == null || ending.length() < 1) + { + if (vTagCounts.containsKey(name)) + { + vTagCounts.put(name, vTagCounts.get(name) + 1); + } + else + { + vTagCounts.put(name, 1); + } + } + else + { + ending = " /"; + } + return "<" + name + params + ending + ">"; + } + else + { + return ""; + } + } + + // comments + m = P_COMMENT.matcher(s); + if (!stripComment && m.find()) + { + return "<" + m.group() + ">"; + } + + return ""; + } + + private String processParamProtocol(String s) + { + s = decodeEntities(s); + final Matcher m = P_PROTOCOL.matcher(s); + if (m.find()) + { + final String protocol = m.group(1); + if (!inArray(protocol, vAllowedProtocols)) + { + // bad protocol, turn into local anchor link instead + s = "#" + s.substring(protocol.length() + 1); + if (s.startsWith("#//")) + { + s = "#" + s.substring(3); + } + } + } + + return s; + } + + private String decodeEntities(String s) + { + StringBuffer buf = new StringBuffer(); + + Matcher m = P_ENTITY.matcher(s); + while (m.find()) + { + final String match = m.group(1); + final int decimal = Integer.decode(match).intValue(); + m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); + } + m.appendTail(buf); + s = buf.toString(); + + buf = new StringBuffer(); + m = P_ENTITY_UNICODE.matcher(s); + while (m.find()) + { + final String match = m.group(1); + final int decimal = Integer.valueOf(match, 16).intValue(); + m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); + } + m.appendTail(buf); + s = buf.toString(); + + buf = new StringBuffer(); + m = P_ENCODE.matcher(s); + while (m.find()) + { + final String match = m.group(1); + final int decimal = Integer.valueOf(match, 16).intValue(); + m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); + } + m.appendTail(buf); + s = buf.toString(); + + s = validateEntities(s); + return s; + } + + private String validateEntities(final String s) + { + StringBuffer buf = new StringBuffer(); + + // validate entities throughout the string + Matcher m = P_VALID_ENTITIES.matcher(s); + while (m.find()) + { + final String one = m.group(1); // ([^&;]*) + final String two = m.group(2); // (?=(;|&|$)) + m.appendReplacement(buf, Matcher.quoteReplacement(checkEntity(one, two))); + } + m.appendTail(buf); + + return encodeQuotes(buf.toString()); + } + + private String encodeQuotes(final String s) + { + if (encodeQuotes) + { + StringBuffer buf = new StringBuffer(); + Matcher m = P_VALID_QUOTES.matcher(s); + while (m.find()) + { + final String one = m.group(1); // (>|^) + final String two = m.group(2); // ([^<]+?) + final String three = m.group(3); // (<|$) + // 不替换双引号为",防止json格式无效 regexReplace(P_QUOTE, """, two) + m.appendReplacement(buf, Matcher.quoteReplacement(one + two + three)); + } + m.appendTail(buf); + return buf.toString(); + } + else + { + return s; + } + } + + private String checkEntity(final String preamble, final String term) + { + + return ";".equals(term) && isValidEntity(preamble) ? '&' + preamble : "&" + preamble; + } + + private boolean isValidEntity(final String entity) + { + return inArray(entity, vAllowedEntities); + } + + private static boolean inArray(final String s, final String[] array) + { + for (String item : array) + { + if (item != null && item.equals(s)) + { + return true; + } + } + return false; + } + + private boolean allowed(final String name) + { + return (vAllowed.isEmpty() || vAllowed.containsKey(name)) && !inArray(name, vDisallowed); + } + + private boolean allowedAttribute(final String name, final String paramName) + { + return allowed(name) && (vAllowed.isEmpty() || vAllowed.get(name).contains(paramName)); + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java new file mode 100644 index 0000000..2ac2603 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java @@ -0,0 +1,274 @@ +package com.ruoyi.common.utils.http; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.net.ConnectException; +import java.net.SocketTimeoutException; +import java.net.URL; +import java.net.URLConnection; +import java.nio.charset.StandardCharsets; +import java.security.cert.X509Certificate; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.utils.StringUtils; + +/** + * 通用http发送方法 + * + * @author ruoyi + */ +public class HttpUtils +{ + private static final Logger log = LoggerFactory.getLogger(HttpUtils.class); + + /** + * 向指定 URL 发送GET方法的请求 + * + * @param url 发送请求的 URL + * @return 所代表远程资源的响应结果 + */ + public static String sendGet(String url) + { + return sendGet(url, StringUtils.EMPTY); + } + + /** + * 向指定 URL 发送GET方法的请求 + * + * @param url 发送请求的 URL + * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @return 所代表远程资源的响应结果 + */ + public static String sendGet(String url, String param) + { + return sendGet(url, param, Constants.UTF8); + } + + /** + * 向指定 URL 发送GET方法的请求 + * + * @param url 发送请求的 URL + * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @param contentType 编码类型 + * @return 所代表远程资源的响应结果 + */ + public static String sendGet(String url, String param, String contentType) + { + StringBuilder result = new StringBuilder(); + BufferedReader in = null; + try + { + String urlNameString = StringUtils.isNotBlank(param) ? url + "?" + param : url; + log.info("sendGet - {}", urlNameString); + URL realUrl = new URL(urlNameString); + URLConnection connection = realUrl.openConnection(); + connection.setRequestProperty("accept", "*/*"); + connection.setRequestProperty("connection", "Keep-Alive"); + connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + connection.connect(); + in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType)); + String line; + while ((line = in.readLine()) != null) + { + result.append(line); + } + log.info("recv - {}", result); + } + catch (ConnectException e) + { + log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e); + } + catch (SocketTimeoutException e) + { + log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e); + } + catch (IOException e) + { + log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e); + } + catch (Exception e) + { + log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e); + } + finally + { + try + { + if (in != null) + { + in.close(); + } + } + catch (Exception ex) + { + log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); + } + } + return result.toString(); + } + + /** + * 向指定 URL 发送POST方法的请求 + * + * @param url 发送请求的 URL + * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @return 所代表远程资源的响应结果 + */ + public static String sendPost(String url, String param) + { + PrintWriter out = null; + BufferedReader in = null; + StringBuilder result = new StringBuilder(); + try + { + log.info("sendPost - {}", url); + URL realUrl = new URL(url); + URLConnection conn = realUrl.openConnection(); + conn.setRequestProperty("accept", "*/*"); + conn.setRequestProperty("connection", "Keep-Alive"); + conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + conn.setRequestProperty("Accept-Charset", "utf-8"); + conn.setRequestProperty("contentType", "utf-8"); + conn.setDoOutput(true); + conn.setDoInput(true); + out = new PrintWriter(conn.getOutputStream()); + out.print(param); + out.flush(); + in = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8)); + String line; + while ((line = in.readLine()) != null) + { + result.append(line); + } + log.info("recv - {}", result); + } + catch (ConnectException e) + { + log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e); + } + catch (SocketTimeoutException e) + { + log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e); + } + catch (IOException e) + { + log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e); + } + catch (Exception e) + { + log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e); + } + finally + { + try + { + if (out != null) + { + out.close(); + } + if (in != null) + { + in.close(); + } + } + catch (IOException ex) + { + log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); + } + } + return result.toString(); + } + + public static String sendSSLPost(String url, String param) + { + StringBuilder result = new StringBuilder(); + String urlNameString = url + "?" + param; + try + { + log.info("sendSSLPost - {}", urlNameString); + SSLContext sc = SSLContext.getInstance("SSL"); + sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom()); + URL console = new URL(urlNameString); + HttpsURLConnection conn = (HttpsURLConnection) console.openConnection(); + conn.setRequestProperty("accept", "*/*"); + conn.setRequestProperty("connection", "Keep-Alive"); + conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + conn.setRequestProperty("Accept-Charset", "utf-8"); + conn.setRequestProperty("contentType", "utf-8"); + conn.setDoOutput(true); + conn.setDoInput(true); + + conn.setSSLSocketFactory(sc.getSocketFactory()); + conn.setHostnameVerifier(new TrustAnyHostnameVerifier()); + conn.connect(); + InputStream is = conn.getInputStream(); + BufferedReader br = new BufferedReader(new InputStreamReader(is)); + String ret = ""; + while ((ret = br.readLine()) != null) + { + if (ret != null && !ret.trim().equals("")) + { + result.append(new String(ret.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8)); + } + } + log.info("recv - {}", result); + conn.disconnect(); + br.close(); + } + catch (ConnectException e) + { + log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e); + } + catch (SocketTimeoutException e) + { + log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e); + } + catch (IOException e) + { + log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e); + } + catch (Exception e) + { + log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e); + } + return result.toString(); + } + + private static class TrustAnyTrustManager implements X509TrustManager + { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) + { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) + { + } + + @Override + public X509Certificate[] getAcceptedIssuers() + { + return new X509Certificate[] {}; + } + } + + private static class TrustAnyHostnameVerifier implements HostnameVerifier + { + @Override + public boolean verify(String hostname, SSLSession session) + { + return true; + } + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelHandlerAdapter.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelHandlerAdapter.java new file mode 100644 index 0000000..5ea74c1 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelHandlerAdapter.java @@ -0,0 +1,19 @@ +package com.ruoyi.common.utils.poi; + +/** + * Excel数据格式处理适配器 + * + * @author ruoyi + */ +public interface ExcelHandlerAdapter +{ + /** + * 格式化 + * + * @param value 单元格数据值 + * @param args excel注解args参数组 + * + * @return 处理后的值 + */ + Object format(Object value, String[] args); +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java new file mode 100644 index 0000000..c6489e2 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java @@ -0,0 +1,1735 @@ +package com.ruoyi.common.utils.poi; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.RegExUtils; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.poi.hssf.usermodel.HSSFClientAnchor; +import org.apache.poi.hssf.usermodel.HSSFPicture; +import org.apache.poi.hssf.usermodel.HSSFPictureData; +import org.apache.poi.hssf.usermodel.HSSFShape; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ooxml.POIXMLDocumentPart; +import org.apache.poi.ss.usermodel.BorderStyle; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.ClientAnchor; +import org.apache.poi.ss.usermodel.DataValidation; +import org.apache.poi.ss.usermodel.DataValidationConstraint; +import org.apache.poi.ss.usermodel.DataValidationHelper; +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.ss.usermodel.Drawing; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.apache.poi.ss.usermodel.Name; +import org.apache.poi.ss.usermodel.PictureData; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.VerticalAlignment; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.ss.util.CellRangeAddressList; +import org.apache.poi.util.IOUtils; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.apache.poi.xssf.usermodel.XSSFClientAnchor; +import org.apache.poi.xssf.usermodel.XSSFDataValidation; +import org.apache.poi.xssf.usermodel.XSSFDrawing; +import org.apache.poi.xssf.usermodel.XSSFPicture; +import org.apache.poi.xssf.usermodel.XSSFShape; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.annotation.Excel.ColumnType; +import com.ruoyi.common.annotation.Excel.Type; +import com.ruoyi.common.annotation.Excels; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.exception.UtilException; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.DictUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.file.FileTypeUtils; +import com.ruoyi.common.utils.file.FileUtils; +import com.ruoyi.common.utils.file.ImageUtils; +import com.ruoyi.common.utils.reflect.ReflectUtils; + +/** + * Excel相关处理 + * + * @author ruoyi + */ +public class ExcelUtil +{ + private static final Logger log = LoggerFactory.getLogger(ExcelUtil.class); + + public static final String FORMULA_REGEX_STR = "=|-|\\+|@"; + + public static final String[] FORMULA_STR = { "=", "-", "+", "@" }; + + /** + * Excel sheet最大行数,默认65536 + */ + public static final int sheetSize = 65536; + + /** + * 工作表名称 + */ + private String sheetName; + + /** + * 导出类型(EXPORT:导出数据;IMPORT:导入模板) + */ + private Type type; + + /** + * 工作薄对象 + */ + private Workbook wb; + + /** + * 工作表对象 + */ + private Sheet sheet; + + /** + * 样式列表 + */ + private Map styles; + + /** + * 导入导出数据列表 + */ + private List list; + + /** + * 注解列表 + */ + private List fields; + + /** + * 当前行号 + */ + private int rownum; + + /** + * 标题 + */ + private String title; + + /** + * 最大高度 + */ + private short maxHeight; + + /** + * 合并后最后行数 + */ + private int subMergedLastRowNum = 0; + + /** + * 合并后开始行数 + */ + private int subMergedFirstRowNum = 1; + + /** + * 对象的子列表方法 + */ + private Method subMethod; + + /** + * 对象的子列表属性 + */ + private List subFields; + + /** + * 统计列表 + */ + private Map statistics = new HashMap(); + + /** + * 数字格式 + */ + private static final DecimalFormat DOUBLE_FORMAT = new DecimalFormat("######0.00"); + + /** + * 实体对象 + */ + public Class clazz; + + /** + * 需要排除列属性 + */ + public String[] excludeFields; + + public ExcelUtil(Class clazz) + { + this.clazz = clazz; + } + + /** + * 隐藏Excel中列属性 + * + * @param fields 列属性名 示例[单个"name"/多个"id","name"] + * @throws Exception + */ + public void hideColumn(String... fields) + { + this.excludeFields = fields; + } + + public void init(List list, String sheetName, String title, Type type) + { + if (list == null) + { + list = new ArrayList(); + } + this.list = list; + this.sheetName = sheetName; + this.type = type; + this.title = title; + createExcelField(); + createWorkbook(); + createTitle(); + createSubHead(); + } + + /** + * 创建excel第一行标题 + */ + public void createTitle() + { + if (StringUtils.isNotEmpty(title)) + { + subMergedFirstRowNum++; + subMergedLastRowNum++; + int titleLastCol = this.fields.size() - 1; + if (isSubList()) + { + titleLastCol = titleLastCol + subFields.size() - 1; + } + Row titleRow = sheet.createRow(rownum == 0 ? rownum++ : 0); + titleRow.setHeightInPoints(30); + Cell titleCell = titleRow.createCell(0); + titleCell.setCellStyle(styles.get("title")); + titleCell.setCellValue(title); + sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), titleRow.getRowNum(), titleRow.getRowNum(), titleLastCol)); + } + } + + /** + * 创建对象的子列表名称 + */ + public void createSubHead() + { + if (isSubList()) + { + subMergedFirstRowNum++; + subMergedLastRowNum++; + Row subRow = sheet.createRow(rownum); + int excelNum = 0; + for (Object[] objects : fields) + { + Excel attr = (Excel) objects[1]; + Cell headCell1 = subRow.createCell(excelNum); + headCell1.setCellValue(attr.name()); + headCell1.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor()))); + excelNum++; + } + int headFirstRow = excelNum - 1; + int headLastRow = headFirstRow + subFields.size() - 1; + if (headLastRow > headFirstRow) + { + sheet.addMergedRegion(new CellRangeAddress(rownum, rownum, headFirstRow, headLastRow)); + } + rownum++; + } + } + + /** + * 对excel表单默认第一个索引名转换成list + * + * @param is 输入流 + * @return 转换后集合 + */ + public List importExcel(InputStream is) throws Exception + { + return importExcel(is, 0); + } + + /** + * 对excel表单默认第一个索引名转换成list + * + * @param is 输入流 + * @param titleNum 标题占用行数 + * @return 转换后集合 + */ + public List importExcel(InputStream is, int titleNum) throws Exception + { + return importExcel(StringUtils.EMPTY, is, titleNum); + } + + /** + * 对excel表单指定表格索引名转换成list + * + * @param sheetName 表格索引名 + * @param titleNum 标题占用行数 + * @param is 输入流 + * @return 转换后集合 + */ + public List importExcel(String sheetName, InputStream is, int titleNum) throws Exception + { + this.type = Type.IMPORT; + this.wb = WorkbookFactory.create(is); + List list = new ArrayList(); + // 如果指定sheet名,则取指定sheet中的内容 否则默认指向第1个sheet + Sheet sheet = StringUtils.isNotEmpty(sheetName) ? wb.getSheet(sheetName) : wb.getSheetAt(0); + if (sheet == null) + { + throw new IOException("文件sheet不存在"); + } + boolean isXSSFWorkbook = !(wb instanceof HSSFWorkbook); + Map pictures; + if (isXSSFWorkbook) + { + pictures = getSheetPictures07((XSSFSheet) sheet, (XSSFWorkbook) wb); + } + else + { + pictures = getSheetPictures03((HSSFSheet) sheet, (HSSFWorkbook) wb); + } + // 获取最后一个非空行的行下标,比如总行数为n,则返回的为n-1 + int rows = sheet.getLastRowNum(); + + if (rows > 0) + { + // 定义一个map用于存放excel列的序号和field. + Map cellMap = new HashMap(); + // 获取表头 + Row heard = sheet.getRow(titleNum); + for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++) + { + Cell cell = heard.getCell(i); + if (StringUtils.isNotNull(cell)) + { + String value = this.getCellValue(heard, i).toString(); + cellMap.put(value, i); + } + else + { + cellMap.put(null, i); + } + } + // 有数据时才处理 得到类的所有field. + List fields = this.getFields(); + Map fieldsMap = new HashMap(); + for (Object[] objects : fields) + { + Excel attr = (Excel) objects[1]; + Integer column = cellMap.get(attr.name()); + if (column != null) + { + fieldsMap.put(column, objects); + } + } + for (int i = titleNum + 1; i <= rows; i++) + { + // 从第2行开始取数据,默认第一行是表头. + Row row = sheet.getRow(i); + // 判断当前行是否是空行 + if (isRowEmpty(row)) + { + continue; + } + T entity = null; + for (Map.Entry entry : fieldsMap.entrySet()) + { + Object val = this.getCellValue(row, entry.getKey()); + + // 如果不存在实例则新建. + entity = (entity == null ? clazz.newInstance() : entity); + // 从map中得到对应列的field. + Field field = (Field) entry.getValue()[0]; + Excel attr = (Excel) entry.getValue()[1]; + // 取得类型,并根据对象类型设置值. + Class fieldType = field.getType(); + if (String.class == fieldType) + { + String s = Convert.toStr(val); + if (StringUtils.endsWith(s, ".0")) + { + val = StringUtils.substringBefore(s, ".0"); + } + else + { + String dateFormat = field.getAnnotation(Excel.class).dateFormat(); + if (StringUtils.isNotEmpty(dateFormat)) + { + val = parseDateToStr(dateFormat, val); + } + else + { + val = Convert.toStr(val); + } + } + } + else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))) + { + val = Convert.toInt(val); + } + else if ((Long.TYPE == fieldType || Long.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))) + { + val = Convert.toLong(val); + } + else if (Double.TYPE == fieldType || Double.class == fieldType) + { + val = Convert.toDouble(val); + } + else if (Float.TYPE == fieldType || Float.class == fieldType) + { + val = Convert.toFloat(val); + } + else if (BigDecimal.class == fieldType) + { + val = Convert.toBigDecimal(val); + } + else if (Date.class == fieldType) + { + if (val instanceof String) + { + val = DateUtils.parseDate(val); + } + else if (val instanceof Double) + { + val = DateUtil.getJavaDate((Double) val); + } + } + else if (Boolean.TYPE == fieldType || Boolean.class == fieldType) + { + val = Convert.toBool(val, false); + } + if (StringUtils.isNotNull(fieldType)) + { + String propertyName = field.getName(); + if (StringUtils.isNotEmpty(attr.targetAttr())) + { + propertyName = field.getName() + "." + attr.targetAttr(); + } + else if (StringUtils.isNotEmpty(attr.readConverterExp())) + { + val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator()); + } + else if (StringUtils.isNotEmpty(attr.dictType())) + { + val = reverseDictByExp(Convert.toStr(val), attr.dictType(), attr.separator()); + } + else if (!attr.handler().equals(ExcelHandlerAdapter.class)) + { + val = dataFormatHandlerAdapter(val, attr); + } + else if (ColumnType.IMAGE == attr.cellType() && StringUtils.isNotEmpty(pictures)) + { + PictureData image = pictures.get(row.getRowNum() + "_" + entry.getKey()); + if (image == null) + { + val = ""; + } + else + { + byte[] data = image.getData(); + val = FileUtils.writeImportBytes(data); + } + } + ReflectUtils.invokeSetter(entity, propertyName, val); + } + } + list.add(entity); + } + } + return list; + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @return 结果 + */ + public AjaxResult exportExcel(List list, String sheetName) + { + return exportExcel(list, sheetName, StringUtils.EMPTY); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param title 标题 + * @return 结果 + */ + public AjaxResult exportExcel(List list, String sheetName, String title) + { + this.init(list, sheetName, title, Type.EXPORT); + return exportExcel(); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param response 返回数据 + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @return 结果 + */ + public void exportExcel(HttpServletResponse response, List list, String sheetName) + { + exportExcel(response, list, sheetName, StringUtils.EMPTY); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param response 返回数据 + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param title 标题 + * @return 结果 + */ + public void exportExcel(HttpServletResponse response, List list, String sheetName, String title) + { + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + this.init(list, sheetName, title, Type.EXPORT); + exportExcel(response); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param sheetName 工作表的名称 + * @return 结果 + */ + public AjaxResult importTemplateExcel(String sheetName) + { + return importTemplateExcel(sheetName, StringUtils.EMPTY); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param sheetName 工作表的名称 + * @param title 标题 + * @return 结果 + */ + public AjaxResult importTemplateExcel(String sheetName, String title) + { + this.init(null, sheetName, title, Type.IMPORT); + return exportExcel(); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param sheetName 工作表的名称 + * @return 结果 + */ + public void importTemplateExcel(HttpServletResponse response, String sheetName) + { + importTemplateExcel(response, sheetName, StringUtils.EMPTY); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param sheetName 工作表的名称 + * @param title 标题 + * @return 结果 + */ + public void importTemplateExcel(HttpServletResponse response, String sheetName, String title) + { + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + this.init(null, sheetName, title, Type.IMPORT); + exportExcel(response); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @return 结果 + */ + public void exportExcel(HttpServletResponse response) + { + try + { + writeSheet(); + wb.write(response.getOutputStream()); + } + catch (Exception e) + { + log.error("导出Excel异常{}", e.getMessage()); + } + finally + { + IOUtils.closeQuietly(wb); + } + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @return 结果 + */ + public AjaxResult exportExcel() + { + OutputStream out = null; + try + { + writeSheet(); + String filename = encodingFilename(sheetName); + out = new FileOutputStream(getAbsoluteFile(filename)); + wb.write(out); + return AjaxResult.success(filename); + } + catch (Exception e) + { + log.error("导出Excel异常{}", e.getMessage()); + throw new UtilException("导出Excel失败,请联系网站管理员!"); + } + finally + { + IOUtils.closeQuietly(wb); + IOUtils.closeQuietly(out); + } + } + + /** + * 创建写入数据到Sheet + */ + public void writeSheet() + { + // 取出一共有多少个sheet. + int sheetNo = Math.max(1, (int) Math.ceil(list.size() * 1.0 / sheetSize)); + for (int index = 0; index < sheetNo; index++) + { + createSheet(sheetNo, index); + + // 产生一行 + Row row = sheet.createRow(rownum); + int column = 0; + // 写入各个字段的列头名称 + for (Object[] os : fields) + { + Field field = (Field) os[0]; + Excel excel = (Excel) os[1]; + if (Collection.class.isAssignableFrom(field.getType())) + { + for (Field subField : subFields) + { + Excel subExcel = subField.getAnnotation(Excel.class); + this.createHeadCell(subExcel, row, column++); + } + } + else + { + this.createHeadCell(excel, row, column++); + } + } + if (Type.EXPORT.equals(type)) + { + fillExcelData(index, row); + addStatisticsRow(); + } + } + } + + /** + * 填充excel数据 + * + * @param index 序号 + * @param row 单元格行 + */ + @SuppressWarnings("unchecked") + public void fillExcelData(int index, Row row) + { + int startNo = index * sheetSize; + int endNo = Math.min(startNo + sheetSize, list.size()); + int rowNo = (1 + rownum) - startNo; + for (int i = startNo; i < endNo; i++) + { + rowNo = isSubList() ? (i > 1 ? rowNo + 1 : rowNo + i) : i + 1 + rownum - startNo; + row = sheet.createRow(rowNo); + // 得到导出对象. + T vo = (T) list.get(i); + Collection subList = null; + if (isSubList()) + { + if (isSubListValue(vo)) + { + subList = getListCellValue(vo); + subMergedLastRowNum = subMergedLastRowNum + subList.size(); + } + else + { + subMergedFirstRowNum++; + subMergedLastRowNum++; + } + } + int column = 0; + for (Object[] os : fields) + { + Field field = (Field) os[0]; + Excel excel = (Excel) os[1]; + if (Collection.class.isAssignableFrom(field.getType()) && StringUtils.isNotNull(subList)) + { + boolean subFirst = false; + for (Object obj : subList) + { + if (subFirst) + { + rowNo++; + row = sheet.createRow(rowNo); + } + List subFields = FieldUtils.getFieldsListWithAnnotation(obj.getClass(), Excel.class); + int subIndex = 0; + for (Field subField : subFields) + { + if (subField.isAnnotationPresent(Excel.class)) + { + subField.setAccessible(true); + Excel attr = subField.getAnnotation(Excel.class); + this.addCell(attr, row, (T) obj, subField, column + subIndex); + } + subIndex++; + } + subFirst = true; + } + this.subMergedFirstRowNum = this.subMergedFirstRowNum + subList.size(); + } + else + { + this.addCell(excel, row, vo, field, column++); + } + } + } + } + + /** + * 创建表格样式 + * + * @param wb 工作薄对象 + * @return 样式列表 + */ + private Map createStyles(Workbook wb) + { + // 写入各条记录,每条记录对应excel表中的一行 + Map styles = new HashMap(); + CellStyle style = wb.createCellStyle(); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + Font titleFont = wb.createFont(); + titleFont.setFontName("Arial"); + titleFont.setFontHeightInPoints((short) 16); + titleFont.setBold(true); + style.setFont(titleFont); + styles.put("title", style); + + style = wb.createCellStyle(); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setBorderRight(BorderStyle.THIN); + style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderLeft(BorderStyle.THIN); + style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderTop(BorderStyle.THIN); + style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderBottom(BorderStyle.THIN); + style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + Font dataFont = wb.createFont(); + dataFont.setFontName("Arial"); + dataFont.setFontHeightInPoints((short) 10); + style.setFont(dataFont); + styles.put("data", style); + + style = wb.createCellStyle(); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + Font totalFont = wb.createFont(); + totalFont.setFontName("Arial"); + totalFont.setFontHeightInPoints((short) 10); + style.setFont(totalFont); + styles.put("total", style); + + styles.putAll(annotationHeaderStyles(wb, styles)); + + styles.putAll(annotationDataStyles(wb)); + + return styles; + } + + /** + * 根据Excel注解创建表格头样式 + * + * @param wb 工作薄对象 + * @return 自定义样式列表 + */ + private Map annotationHeaderStyles(Workbook wb, Map styles) + { + Map headerStyles = new HashMap(); + for (Object[] os : fields) + { + Excel excel = (Excel) os[1]; + String key = StringUtils.format("header_{}_{}", excel.headerColor(), excel.headerBackgroundColor()); + if (!headerStyles.containsKey(key)) + { + CellStyle style = wb.createCellStyle(); + style.cloneStyleFrom(styles.get("data")); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setFillForegroundColor(excel.headerBackgroundColor().index); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + Font headerFont = wb.createFont(); + headerFont.setFontName("Arial"); + headerFont.setFontHeightInPoints((short) 10); + headerFont.setBold(true); + headerFont.setColor(excel.headerColor().index); + style.setFont(headerFont); + headerStyles.put(key, style); + } + } + return headerStyles; + } + + /** + * 根据Excel注解创建表格列样式 + * + * @param wb 工作薄对象 + * @return 自定义样式列表 + */ + private Map annotationDataStyles(Workbook wb) + { + Map styles = new HashMap(); + for (Object[] os : fields) + { + Excel excel = (Excel) os[1]; + String key = StringUtils.format("data_{}_{}_{}", excel.align(), excel.color(), excel.backgroundColor()); + if (!styles.containsKey(key)) + { + CellStyle style = wb.createCellStyle(); + style.setAlignment(excel.align()); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setBorderRight(BorderStyle.THIN); + style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderLeft(BorderStyle.THIN); + style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderTop(BorderStyle.THIN); + style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderBottom(BorderStyle.THIN); + style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + style.setFillForegroundColor(excel.backgroundColor().getIndex()); + Font dataFont = wb.createFont(); + dataFont.setFontName("Arial"); + dataFont.setFontHeightInPoints((short) 10); + dataFont.setColor(excel.color().index); + style.setFont(dataFont); + styles.put(key, style); + } + } + return styles; + } + + /** + * 创建单元格 + */ + public Cell createHeadCell(Excel attr, Row row, int column) + { + // 创建列 + Cell cell = row.createCell(column); + // 写入列信息 + cell.setCellValue(attr.name()); + setDataValidation(attr, row, column); + cell.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor()))); + if (isSubList()) + { + // 填充默认样式,防止合并单元格样式失效 + sheet.setDefaultColumnStyle(column, styles.get(StringUtils.format("data_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor()))); + if (attr.needMerge()) + { + sheet.addMergedRegion(new CellRangeAddress(rownum - 1, rownum, column, column)); + } + } + return cell; + } + + /** + * 设置单元格信息 + * + * @param value 单元格值 + * @param attr 注解相关 + * @param cell 单元格信息 + */ + public void setCellVo(Object value, Excel attr, Cell cell) + { + if (ColumnType.STRING == attr.cellType()) + { + String cellValue = Convert.toStr(value); + // 对于任何以表达式触发字符 =-+@开头的单元格,直接使用tab字符作为前缀,防止CSV注入。 + if (StringUtils.startsWithAny(cellValue, FORMULA_STR)) + { + cellValue = RegExUtils.replaceFirst(cellValue, FORMULA_REGEX_STR, "\t$0"); + } + if (value instanceof Collection && StringUtils.equals("[]", cellValue)) + { + cellValue = StringUtils.EMPTY; + } + cell.setCellValue(StringUtils.isNull(cellValue) ? attr.defaultValue() : cellValue + attr.suffix()); + } + else if (ColumnType.NUMERIC == attr.cellType()) + { + if (StringUtils.isNotNull(value)) + { + cell.setCellValue(StringUtils.contains(Convert.toStr(value), ".") ? Convert.toDouble(value) : Convert.toInt(value)); + } + } + else if (ColumnType.IMAGE == attr.cellType()) + { + ClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, (short) cell.getColumnIndex(), cell.getRow().getRowNum(), (short) (cell.getColumnIndex() + 1), cell.getRow().getRowNum() + 1); + String imagePath = Convert.toStr(value); + if (StringUtils.isNotEmpty(imagePath)) + { + byte[] data = ImageUtils.getImage(imagePath); + getDrawingPatriarch(cell.getSheet()).createPicture(anchor, + cell.getSheet().getWorkbook().addPicture(data, getImageType(data))); + } + } + } + + /** + * 获取画布 + */ + public static Drawing getDrawingPatriarch(Sheet sheet) + { + if (sheet.getDrawingPatriarch() == null) + { + sheet.createDrawingPatriarch(); + } + return sheet.getDrawingPatriarch(); + } + + /** + * 获取图片类型,设置图片插入类型 + */ + public int getImageType(byte[] value) + { + String type = FileTypeUtils.getFileExtendName(value); + if ("JPG".equalsIgnoreCase(type)) + { + return Workbook.PICTURE_TYPE_JPEG; + } + else if ("PNG".equalsIgnoreCase(type)) + { + return Workbook.PICTURE_TYPE_PNG; + } + return Workbook.PICTURE_TYPE_JPEG; + } + + /** + * 创建表格样式 + */ + public void setDataValidation(Excel attr, Row row, int column) + { + if (attr.name().indexOf("注:") >= 0) + { + sheet.setColumnWidth(column, 6000); + } + else + { + // 设置列宽 + sheet.setColumnWidth(column, (int) ((attr.width() + 0.72) * 256)); + } + if (StringUtils.isNotEmpty(attr.prompt()) || attr.combo().length > 0) + { + if (attr.combo().length > 15 || StringUtils.join(attr.combo()).length() > 255) + { + // 如果下拉数大于15或字符串长度大于255,则使用一个新sheet存储,避免生成的模板下拉值获取不到 + setXSSFValidationWithHidden(sheet, attr.combo(), attr.prompt(), 1, 100, column, column); + } + else + { + // 提示信息或只能选择不能输入的列内容. + setPromptOrValidation(sheet, attr.combo(), attr.prompt(), 1, 100, column, column); + } + } + } + + /** + * 添加单元格 + */ + public Cell addCell(Excel attr, Row row, T vo, Field field, int column) + { + Cell cell = null; + try + { + // 设置行高 + row.setHeight(maxHeight); + // 根据Excel中设置情况决定是否导出,有些情况需要保持为空,希望用户填写这一列. + if (attr.isExport()) + { + // 创建cell + cell = row.createCell(column); + if (isSubListValue(vo) && getListCellValue(vo).size() > 1 && attr.needMerge()) + { + CellRangeAddress cellAddress = new CellRangeAddress(subMergedFirstRowNum, subMergedLastRowNum, column, column); + sheet.addMergedRegion(cellAddress); + } + cell.setCellStyle(styles.get(StringUtils.format("data_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor()))); + + // 用于读取对象中的属性 + Object value = getTargetValue(vo, field, attr); + String dateFormat = attr.dateFormat(); + String readConverterExp = attr.readConverterExp(); + String separator = attr.separator(); + String dictType = attr.dictType(); + if (StringUtils.isNotEmpty(dateFormat) && StringUtils.isNotNull(value)) + { + cell.setCellValue(parseDateToStr(dateFormat, value)); + } + else if (StringUtils.isNotEmpty(readConverterExp) && StringUtils.isNotNull(value)) + { + cell.setCellValue(convertByExp(Convert.toStr(value), readConverterExp, separator)); + } + else if (StringUtils.isNotEmpty(dictType) && StringUtils.isNotNull(value)) + { + cell.setCellValue(convertDictByExp(Convert.toStr(value), dictType, separator)); + } + else if (value instanceof BigDecimal && -1 != attr.scale()) + { + cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).doubleValue()); + } + else if (!attr.handler().equals(ExcelHandlerAdapter.class)) + { + cell.setCellValue(dataFormatHandlerAdapter(value, attr)); + } + else + { + // 设置列类型 + setCellVo(value, attr, cell); + } + addStatisticsData(column, Convert.toStr(value), attr); + } + } + catch (Exception e) + { + log.error("导出Excel失败{}", e); + } + return cell; + } + + /** + * 设置 POI XSSFSheet 单元格提示或选择框 + * + * @param sheet 表单 + * @param textlist 下拉框显示的内容 + * @param promptContent 提示内容 + * @param firstRow 开始行 + * @param endRow 结束行 + * @param firstCol 开始列 + * @param endCol 结束列 + */ + public void setPromptOrValidation(Sheet sheet, String[] textlist, String promptContent, int firstRow, int endRow, + int firstCol, int endCol) + { + DataValidationHelper helper = sheet.getDataValidationHelper(); + DataValidationConstraint constraint = textlist.length > 0 ? helper.createExplicitListConstraint(textlist) : helper.createCustomConstraint("DD1"); + CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol); + DataValidation dataValidation = helper.createValidation(constraint, regions); + if (StringUtils.isNotEmpty(promptContent)) + { + // 如果设置了提示信息则鼠标放上去提示 + dataValidation.createPromptBox("", promptContent); + dataValidation.setShowPromptBox(true); + } + // 处理Excel兼容性问题 + if (dataValidation instanceof XSSFDataValidation) + { + dataValidation.setSuppressDropDownArrow(true); + dataValidation.setShowErrorBox(true); + } + else + { + dataValidation.setSuppressDropDownArrow(false); + } + sheet.addValidationData(dataValidation); + } + + /** + * 设置某些列的值只能输入预制的数据,显示下拉框(兼容超出一定数量的下拉框). + * + * @param sheet 要设置的sheet. + * @param textlist 下拉框显示的内容 + * @param promptContent 提示内容 + * @param firstRow 开始行 + * @param endRow 结束行 + * @param firstCol 开始列 + * @param endCol 结束列 + */ + public void setXSSFValidationWithHidden(Sheet sheet, String[] textlist, String promptContent, int firstRow, int endRow, int firstCol, int endCol) + { + String hideSheetName = "combo_" + firstCol + "_" + endCol; + Sheet hideSheet = wb.createSheet(hideSheetName); // 用于存储 下拉菜单数据 + for (int i = 0; i < textlist.length; i++) + { + hideSheet.createRow(i).createCell(0).setCellValue(textlist[i]); + } + // 创建名称,可被其他单元格引用 + Name name = wb.createName(); + name.setNameName(hideSheetName + "_data"); + name.setRefersToFormula(hideSheetName + "!$A$1:$A$" + textlist.length); + DataValidationHelper helper = sheet.getDataValidationHelper(); + // 加载下拉列表内容 + DataValidationConstraint constraint = helper.createFormulaListConstraint(hideSheetName + "_data"); + // 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列 + CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol); + // 数据有效性对象 + DataValidation dataValidation = helper.createValidation(constraint, regions); + if (StringUtils.isNotEmpty(promptContent)) + { + // 如果设置了提示信息则鼠标放上去提示 + dataValidation.createPromptBox("", promptContent); + dataValidation.setShowPromptBox(true); + } + // 处理Excel兼容性问题 + if (dataValidation instanceof XSSFDataValidation) + { + dataValidation.setSuppressDropDownArrow(true); + dataValidation.setShowErrorBox(true); + } + else + { + dataValidation.setSuppressDropDownArrow(false); + } + + sheet.addValidationData(dataValidation); + // 设置hiddenSheet隐藏 + wb.setSheetHidden(wb.getSheetIndex(hideSheet), true); + } + + /** + * 解析导出值 0=男,1=女,2=未知 + * + * @param propertyValue 参数值 + * @param converterExp 翻译注解 + * @param separator 分隔符 + * @return 解析后值 + */ + public static String convertByExp(String propertyValue, String converterExp, String separator) + { + StringBuilder propertyString = new StringBuilder(); + String[] convertSource = converterExp.split(","); + for (String item : convertSource) + { + String[] itemArray = item.split("="); + if (StringUtils.containsAny(propertyValue, separator)) + { + for (String value : propertyValue.split(separator)) + { + if (itemArray[0].equals(value)) + { + propertyString.append(itemArray[1] + separator); + break; + } + } + } + else + { + if (itemArray[0].equals(propertyValue)) + { + return itemArray[1]; + } + } + } + return StringUtils.stripEnd(propertyString.toString(), separator); + } + + /** + * 反向解析值 男=0,女=1,未知=2 + * + * @param propertyValue 参数值 + * @param converterExp 翻译注解 + * @param separator 分隔符 + * @return 解析后值 + */ + public static String reverseByExp(String propertyValue, String converterExp, String separator) + { + StringBuilder propertyString = new StringBuilder(); + String[] convertSource = converterExp.split(","); + for (String item : convertSource) + { + String[] itemArray = item.split("="); + if (StringUtils.containsAny(propertyValue, separator)) + { + for (String value : propertyValue.split(separator)) + { + if (itemArray[1].equals(value)) + { + propertyString.append(itemArray[0] + separator); + break; + } + } + } + else + { + if (itemArray[1].equals(propertyValue)) + { + return itemArray[0]; + } + } + } + return StringUtils.stripEnd(propertyString.toString(), separator); + } + + /** + * 解析字典值 + * + * @param dictValue 字典值 + * @param dictType 字典类型 + * @param separator 分隔符 + * @return 字典标签 + */ + public static String convertDictByExp(String dictValue, String dictType, String separator) + { + return DictUtils.getDictLabel(dictType, dictValue, separator); + } + + /** + * 反向解析值字典值 + * + * @param dictLabel 字典标签 + * @param dictType 字典类型 + * @param separator 分隔符 + * @return 字典值 + */ + public static String reverseDictByExp(String dictLabel, String dictType, String separator) + { + return DictUtils.getDictValue(dictType, dictLabel, separator); + } + + /** + * 数据处理器 + * + * @param value 数据值 + * @param excel 数据注解 + * @return + */ + public String dataFormatHandlerAdapter(Object value, Excel excel) + { + try + { + Object instance = excel.handler().newInstance(); + Method formatMethod = excel.handler().getMethod("format", new Class[] { Object.class, String[].class }); + value = formatMethod.invoke(instance, value, excel.args()); + } + catch (Exception e) + { + log.error("不能格式化数据 " + excel.handler(), e.getMessage()); + } + return Convert.toStr(value); + } + + /** + * 合计统计信息 + */ + private void addStatisticsData(Integer index, String text, Excel entity) + { + if (entity != null && entity.isStatistics()) + { + Double temp = 0D; + if (!statistics.containsKey(index)) + { + statistics.put(index, temp); + } + try + { + temp = Double.valueOf(text); + } + catch (NumberFormatException e) + { + } + statistics.put(index, statistics.get(index) + temp); + } + } + + /** + * 创建统计行 + */ + public void addStatisticsRow() + { + if (statistics.size() > 0) + { + Row row = sheet.createRow(sheet.getLastRowNum() + 1); + Set keys = statistics.keySet(); + Cell cell = row.createCell(0); + cell.setCellStyle(styles.get("total")); + cell.setCellValue("合计"); + + for (Integer key : keys) + { + cell = row.createCell(key); + cell.setCellStyle(styles.get("total")); + cell.setCellValue(DOUBLE_FORMAT.format(statistics.get(key))); + } + statistics.clear(); + } + } + + /** + * 编码文件名 + */ + public String encodingFilename(String filename) + { + filename = UUID.randomUUID() + "_" + filename + ".xlsx"; + return filename; + } + + /** + * 获取下载路径 + * + * @param filename 文件名称 + */ + public String getAbsoluteFile(String filename) + { + String downloadPath = RuoYiConfig.getDownloadPath() + filename; + File desc = new File(downloadPath); + if (!desc.getParentFile().exists()) + { + desc.getParentFile().mkdirs(); + } + return downloadPath; + } + + /** + * 获取bean中的属性值 + * + * @param vo 实体对象 + * @param field 字段 + * @param excel 注解 + * @return 最终的属性值 + * @throws Exception + */ + private Object getTargetValue(T vo, Field field, Excel excel) throws Exception + { + Object o = field.get(vo); + if (StringUtils.isNotEmpty(excel.targetAttr())) + { + String target = excel.targetAttr(); + if (target.contains(".")) + { + String[] targets = target.split("[.]"); + for (String name : targets) + { + o = getValue(o, name); + } + } + else + { + o = getValue(o, target); + } + } + return o; + } + + /** + * 以类的属性的get方法方法形式获取值 + * + * @param o + * @param name + * @return value + * @throws Exception + */ + private Object getValue(Object o, String name) throws Exception + { + if (StringUtils.isNotNull(o) && StringUtils.isNotEmpty(name)) + { + Class clazz = o.getClass(); + Field field = clazz.getDeclaredField(name); + field.setAccessible(true); + o = field.get(o); + } + return o; + } + + /** + * 得到所有定义字段 + */ + private void createExcelField() + { + this.fields = getFields(); + this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort())).collect(Collectors.toList()); + this.maxHeight = getRowHeight(); + } + + /** + * 获取字段注解信息 + */ + public List getFields() + { + List fields = new ArrayList(); + List tempFields = new ArrayList<>(); + tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields())); + tempFields.addAll(Arrays.asList(clazz.getDeclaredFields())); + for (Field field : tempFields) + { + if (!ArrayUtils.contains(this.excludeFields, field.getName())) + { + // 单注解 + if (field.isAnnotationPresent(Excel.class)) + { + Excel attr = field.getAnnotation(Excel.class); + if (attr != null && (attr.type() == Type.ALL || attr.type() == type)) + { + field.setAccessible(true); + fields.add(new Object[] { field, attr }); + } + if (Collection.class.isAssignableFrom(field.getType())) + { + subMethod = getSubMethod(field.getName(), clazz); + ParameterizedType pt = (ParameterizedType) field.getGenericType(); + Class subClass = (Class) pt.getActualTypeArguments()[0]; + this.subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class); + } + } + + // 多注解 + if (field.isAnnotationPresent(Excels.class)) + { + Excels attrs = field.getAnnotation(Excels.class); + Excel[] excels = attrs.value(); + for (Excel attr : excels) + { + if (!ArrayUtils.contains(this.excludeFields, field.getName() + "." + attr.targetAttr()) + && (attr != null && (attr.type() == Type.ALL || attr.type() == type))) + { + field.setAccessible(true); + fields.add(new Object[] { field, attr }); + } + } + } + } + } + return fields; + } + + /** + * 根据注解获取最大行高 + */ + public short getRowHeight() + { + double maxHeight = 0; + for (Object[] os : this.fields) + { + Excel excel = (Excel) os[1]; + maxHeight = Math.max(maxHeight, excel.height()); + } + return (short) (maxHeight * 20); + } + + /** + * 创建一个工作簿 + */ + public void createWorkbook() + { + this.wb = new SXSSFWorkbook(500); + this.sheet = wb.createSheet(); + wb.setSheetName(0, sheetName); + this.styles = createStyles(wb); + } + + /** + * 创建工作表 + * + * @param sheetNo sheet数量 + * @param index 序号 + */ + public void createSheet(int sheetNo, int index) + { + // 设置工作表的名称. + if (sheetNo > 1 && index > 0) + { + this.sheet = wb.createSheet(); + this.createTitle(); + wb.setSheetName(index, sheetName + index); + } + } + + /** + * 获取单元格值 + * + * @param row 获取的行 + * @param column 获取单元格列号 + * @return 单元格值 + */ + public Object getCellValue(Row row, int column) + { + if (row == null) + { + return row; + } + Object val = ""; + try + { + Cell cell = row.getCell(column); + if (StringUtils.isNotNull(cell)) + { + if (cell.getCellType() == CellType.NUMERIC || cell.getCellType() == CellType.FORMULA) + { + val = cell.getNumericCellValue(); + if (DateUtil.isCellDateFormatted(cell)) + { + val = DateUtil.getJavaDate((Double) val); // POI Excel 日期格式转换 + } + else + { + if ((Double) val % 1 != 0) + { + val = new BigDecimal(val.toString()); + } + else + { + val = new DecimalFormat("0").format(val); + } + } + } + else if (cell.getCellType() == CellType.STRING) + { + val = cell.getStringCellValue(); + } + else if (cell.getCellType() == CellType.BOOLEAN) + { + val = cell.getBooleanCellValue(); + } + else if (cell.getCellType() == CellType.ERROR) + { + val = cell.getErrorCellValue(); + } + + } + } + catch (Exception e) + { + return val; + } + return val; + } + + /** + * 判断是否是空行 + * + * @param row 判断的行 + * @return + */ + private boolean isRowEmpty(Row row) + { + if (row == null) + { + return true; + } + for (int i = row.getFirstCellNum(); i < row.getLastCellNum(); i++) + { + Cell cell = row.getCell(i); + if (cell != null && cell.getCellType() != CellType.BLANK) + { + return false; + } + } + return true; + } + + /** + * 获取Excel2003图片 + * + * @param sheet 当前sheet对象 + * @param workbook 工作簿对象 + * @return Map key:图片单元格索引(1_1)String,value:图片流PictureData + */ + public static Map getSheetPictures03(HSSFSheet sheet, HSSFWorkbook workbook) + { + Map sheetIndexPicMap = new HashMap(); + List pictures = workbook.getAllPictures(); + if (!pictures.isEmpty()) + { + for (HSSFShape shape : sheet.getDrawingPatriarch().getChildren()) + { + HSSFClientAnchor anchor = (HSSFClientAnchor) shape.getAnchor(); + if (shape instanceof HSSFPicture) + { + HSSFPicture pic = (HSSFPicture) shape; + int pictureIndex = pic.getPictureIndex() - 1; + HSSFPictureData picData = pictures.get(pictureIndex); + String picIndex = anchor.getRow1() + "_" + anchor.getCol1(); + sheetIndexPicMap.put(picIndex, picData); + } + } + return sheetIndexPicMap; + } + else + { + return sheetIndexPicMap; + } + } + + /** + * 获取Excel2007图片 + * + * @param sheet 当前sheet对象 + * @param workbook 工作簿对象 + * @return Map key:图片单元格索引(1_1)String,value:图片流PictureData + */ + public static Map getSheetPictures07(XSSFSheet sheet, XSSFWorkbook workbook) + { + Map sheetIndexPicMap = new HashMap(); + for (POIXMLDocumentPart dr : sheet.getRelations()) + { + if (dr instanceof XSSFDrawing) + { + XSSFDrawing drawing = (XSSFDrawing) dr; + List shapes = drawing.getShapes(); + for (XSSFShape shape : shapes) + { + if (shape instanceof XSSFPicture) + { + XSSFPicture pic = (XSSFPicture) shape; + XSSFClientAnchor anchor = pic.getPreferredSize(); + CTMarker ctMarker = anchor.getFrom(); + String picIndex = ctMarker.getRow() + "_" + ctMarker.getCol(); + sheetIndexPicMap.put(picIndex, pic.getPictureData()); + } + } + } + } + return sheetIndexPicMap; + } + + /** + * 格式化不同类型的日期对象 + * + * @param dateFormat 日期格式 + * @param val 被格式化的日期对象 + * @return 格式化后的日期字符 + */ + public String parseDateToStr(String dateFormat, Object val) + { + if (val == null) + { + return ""; + } + String str; + if (val instanceof Date) + { + str = DateUtils.parseDateToStr(dateFormat, (Date) val); + } + else if (val instanceof LocalDateTime) + { + str = DateUtils.parseDateToStr(dateFormat, DateUtils.toDate((LocalDateTime) val)); + } + else if (val instanceof LocalDate) + { + str = DateUtils.parseDateToStr(dateFormat, DateUtils.toDate((LocalDate) val)); + } + else + { + str = val.toString(); + } + return str; + } + + /** + * 是否有对象的子列表 + */ + public boolean isSubList() + { + return StringUtils.isNotNull(subFields) && subFields.size() > 0; + } + + /** + * 是否有对象的子列表,集合不为空 + */ + public boolean isSubListValue(T vo) + { + return StringUtils.isNotNull(subFields) && subFields.size() > 0 && StringUtils.isNotNull(getListCellValue(vo)) && getListCellValue(vo).size() > 0; + } + + /** + * 获取集合的值 + */ + public Collection getListCellValue(Object obj) + { + Object value; + try + { + value = subMethod.invoke(obj, new Object[] {}); + } + catch (Exception e) + { + return new ArrayList(); + } + return (Collection) value; + } + + /** + * 获取对象的子列表方法 + * + * @param name 名称 + * @param pojoClass 类对象 + * @return 子列表方法 + */ + public Method getSubMethod(String name, Class pojoClass) + { + StringBuffer getMethodName = new StringBuffer("get"); + getMethodName.append(name.substring(0, 1).toUpperCase()); + getMethodName.append(name.substring(1)); + Method method = null; + try + { + method = pojoClass.getMethod(getMethodName.toString(), new Class[] {}); + } + catch (Exception e) + { + log.error("获取对象异常{}", e.getMessage()); + } + return method; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/reflect/ReflectUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/reflect/ReflectUtils.java new file mode 100644 index 0000000..b19953e --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/reflect/ReflectUtils.java @@ -0,0 +1,410 @@ +package com.ruoyi.common.utils.reflect; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Date; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; +import org.apache.poi.ss.usermodel.DateUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.utils.DateUtils; + +/** + * 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数. + * + * @author ruoyi + */ +@SuppressWarnings("rawtypes") +public class ReflectUtils +{ + private static final String SETTER_PREFIX = "set"; + + private static final String GETTER_PREFIX = "get"; + + private static final String CGLIB_CLASS_SEPARATOR = "$$"; + + private static Logger logger = LoggerFactory.getLogger(ReflectUtils.class); + + /** + * 调用Getter方法. + * 支持多级,如:对象名.对象名.方法 + */ + @SuppressWarnings("unchecked") + public static E invokeGetter(Object obj, String propertyName) + { + Object object = obj; + for (String name : StringUtils.split(propertyName, ".")) + { + String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name); + object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {}); + } + return (E) object; + } + + /** + * 调用Setter方法, 仅匹配方法名。 + * 支持多级,如:对象名.对象名.方法 + */ + public static void invokeSetter(Object obj, String propertyName, E value) + { + Object object = obj; + String[] names = StringUtils.split(propertyName, "."); + for (int i = 0; i < names.length; i++) + { + if (i < names.length - 1) + { + String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]); + object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {}); + } + else + { + String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]); + invokeMethodByName(object, setterMethodName, new Object[] { value }); + } + } + } + + /** + * 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数. + */ + @SuppressWarnings("unchecked") + public static E getFieldValue(final Object obj, final String fieldName) + { + Field field = getAccessibleField(obj, fieldName); + if (field == null) + { + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); + return null; + } + E result = null; + try + { + result = (E) field.get(obj); + } + catch (IllegalAccessException e) + { + logger.error("不可能抛出的异常{}", e.getMessage()); + } + return result; + } + + /** + * 直接设置对象属性值, 无视private/protected修饰符, 不经过setter函数. + */ + public static void setFieldValue(final Object obj, final String fieldName, final E value) + { + Field field = getAccessibleField(obj, fieldName); + if (field == null) + { + // throw new IllegalArgumentException("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); + return; + } + try + { + field.set(obj, value); + } + catch (IllegalAccessException e) + { + logger.error("不可能抛出的异常: {}", e.getMessage()); + } + } + + /** + * 直接调用对象方法, 无视private/protected修饰符. + * 用于一次性调用的情况,否则应使用getAccessibleMethod()函数获得Method后反复调用. + * 同时匹配方法名+参数类型, + */ + @SuppressWarnings("unchecked") + public static E invokeMethod(final Object obj, final String methodName, final Class[] parameterTypes, + final Object[] args) + { + if (obj == null || methodName == null) + { + return null; + } + Method method = getAccessibleMethod(obj, methodName, parameterTypes); + if (method == null) + { + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 "); + return null; + } + try + { + return (E) method.invoke(obj, args); + } + catch (Exception e) + { + String msg = "method: " + method + ", obj: " + obj + ", args: " + args + ""; + throw convertReflectionExceptionToUnchecked(msg, e); + } + } + + /** + * 直接调用对象方法, 无视private/protected修饰符, + * 用于一次性调用的情况,否则应使用getAccessibleMethodByName()函数获得Method后反复调用. + * 只匹配函数名,如果有多个同名函数调用第一个。 + */ + @SuppressWarnings("unchecked") + public static E invokeMethodByName(final Object obj, final String methodName, final Object[] args) + { + Method method = getAccessibleMethodByName(obj, methodName, args.length); + if (method == null) + { + // 如果为空不报错,直接返回空。 + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 "); + return null; + } + try + { + // 类型转换(将参数数据类型转换为目标方法参数类型) + Class[] cs = method.getParameterTypes(); + for (int i = 0; i < cs.length; i++) + { + if (args[i] != null && !args[i].getClass().equals(cs[i])) + { + if (cs[i] == String.class) + { + args[i] = Convert.toStr(args[i]); + if (StringUtils.endsWith((String) args[i], ".0")) + { + args[i] = StringUtils.substringBefore((String) args[i], ".0"); + } + } + else if (cs[i] == Integer.class) + { + args[i] = Convert.toInt(args[i]); + } + else if (cs[i] == Long.class) + { + args[i] = Convert.toLong(args[i]); + } + else if (cs[i] == Double.class) + { + args[i] = Convert.toDouble(args[i]); + } + else if (cs[i] == Float.class) + { + args[i] = Convert.toFloat(args[i]); + } + else if (cs[i] == Date.class) + { + if (args[i] instanceof String) + { + args[i] = DateUtils.parseDate(args[i]); + } + else + { + args[i] = DateUtil.getJavaDate((Double) args[i]); + } + } + else if (cs[i] == boolean.class || cs[i] == Boolean.class) + { + args[i] = Convert.toBool(args[i]); + } + } + } + return (E) method.invoke(obj, args); + } + catch (Exception e) + { + String msg = "method: " + method + ", obj: " + obj + ", args: " + args + ""; + throw convertReflectionExceptionToUnchecked(msg, e); + } + } + + /** + * 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问. + * 如向上转型到Object仍无法找到, 返回null. + */ + public static Field getAccessibleField(final Object obj, final String fieldName) + { + // 为空不报错。直接返回 null + if (obj == null) + { + return null; + } + Validate.notBlank(fieldName, "fieldName can't be blank"); + for (Class superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) + { + try + { + Field field = superClass.getDeclaredField(fieldName); + makeAccessible(field); + return field; + } + catch (NoSuchFieldException e) + { + continue; + } + } + return null; + } + + /** + * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. + * 如向上转型到Object仍无法找到, 返回null. + * 匹配函数名+参数类型。 + * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) + */ + public static Method getAccessibleMethod(final Object obj, final String methodName, + final Class... parameterTypes) + { + // 为空不报错。直接返回 null + if (obj == null) + { + return null; + } + Validate.notBlank(methodName, "methodName can't be blank"); + for (Class searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) + { + try + { + Method method = searchType.getDeclaredMethod(methodName, parameterTypes); + makeAccessible(method); + return method; + } + catch (NoSuchMethodException e) + { + continue; + } + } + return null; + } + + /** + * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. + * 如向上转型到Object仍无法找到, 返回null. + * 只匹配函数名。 + * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) + */ + public static Method getAccessibleMethodByName(final Object obj, final String methodName, int argsNum) + { + // 为空不报错。直接返回 null + if (obj == null) + { + return null; + } + Validate.notBlank(methodName, "methodName can't be blank"); + for (Class searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) + { + Method[] methods = searchType.getDeclaredMethods(); + for (Method method : methods) + { + if (method.getName().equals(methodName) && method.getParameterTypes().length == argsNum) + { + makeAccessible(method); + return method; + } + } + } + return null; + } + + /** + * 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 + */ + public static void makeAccessible(Method method) + { + if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) + && !method.isAccessible()) + { + method.setAccessible(true); + } + } + + /** + * 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 + */ + public static void makeAccessible(Field field) + { + if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) + || Modifier.isFinal(field.getModifiers())) && !field.isAccessible()) + { + field.setAccessible(true); + } + } + + /** + * 通过反射, 获得Class定义中声明的泛型参数的类型, 注意泛型必须定义在父类处 + * 如无法找到, 返回Object.class. + */ + @SuppressWarnings("unchecked") + public static Class getClassGenricType(final Class clazz) + { + return getClassGenricType(clazz, 0); + } + + /** + * 通过反射, 获得Class定义中声明的父类的泛型参数的类型. + * 如无法找到, 返回Object.class. + */ + public static Class getClassGenricType(final Class clazz, final int index) + { + Type genType = clazz.getGenericSuperclass(); + + if (!(genType instanceof ParameterizedType)) + { + logger.debug(clazz.getSimpleName() + "'s superclass not ParameterizedType"); + return Object.class; + } + + Type[] params = ((ParameterizedType) genType).getActualTypeArguments(); + + if (index >= params.length || index < 0) + { + logger.debug("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: " + + params.length); + return Object.class; + } + if (!(params[index] instanceof Class)) + { + logger.debug(clazz.getSimpleName() + " not set the actual class on superclass generic parameter"); + return Object.class; + } + + return (Class) params[index]; + } + + public static Class getUserClass(Object instance) + { + if (instance == null) + { + throw new RuntimeException("Instance must not be null"); + } + Class clazz = instance.getClass(); + if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) + { + Class superClass = clazz.getSuperclass(); + if (superClass != null && !Object.class.equals(superClass)) + { + return superClass; + } + } + return clazz; + + } + + /** + * 将反射时的checked exception转换为unchecked exception. + */ + public static RuntimeException convertReflectionExceptionToUnchecked(String msg, Exception e) + { + if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException + || e instanceof NoSuchMethodException) + { + return new IllegalArgumentException(msg, e); + } + else if (e instanceof InvocationTargetException) + { + return new RuntimeException(msg, ((InvocationTargetException) e).getTargetException()); + } + return new RuntimeException(msg, e); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/security/CipherUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/security/CipherUtils.java new file mode 100644 index 0000000..43b9f24 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/security/CipherUtils.java @@ -0,0 +1,36 @@ +package com.ruoyi.common.utils.security; + +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import javax.crypto.KeyGenerator; + +/** + * 对称密钥密码算法工具类 + * + * @author ruoyi + */ +public class CipherUtils +{ + /** + * 生成随机秘钥 + * + * @param keyBitSize 字节大小 + * @param algorithmName 算法名称 + * @return 创建密匙 + */ + public static Key generateNewKey(int keyBitSize, String algorithmName) + { + KeyGenerator kg; + try + { + kg = KeyGenerator.getInstance(algorithmName); + } + catch (NoSuchAlgorithmException e) + { + String msg = "Unable to acquire " + algorithmName + " algorithm. This is required to function."; + throw new IllegalStateException(msg, e); + } + kg.init(keyBitSize); + return kg.generateKey(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/security/Md5Utils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/security/Md5Utils.java new file mode 100644 index 0000000..ff9937f --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/security/Md5Utils.java @@ -0,0 +1,67 @@ +package com.ruoyi.common.utils.security; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Md5加密方法 + * + * @author ruoyi + */ +public class Md5Utils +{ + private static final Logger log = LoggerFactory.getLogger(Md5Utils.class); + + private static byte[] md5(String s) + { + MessageDigest algorithm; + try + { + algorithm = MessageDigest.getInstance("MD5"); + algorithm.reset(); + algorithm.update(s.getBytes("UTF-8")); + byte[] messageDigest = algorithm.digest(); + return messageDigest; + } + catch (Exception e) + { + log.error("MD5 Error...", e); + } + return null; + } + + private static final String toHex(byte hash[]) + { + if (hash == null) + { + return null; + } + StringBuffer buf = new StringBuffer(hash.length * 2); + int i; + + for (i = 0; i < hash.length; i++) + { + if ((hash[i] & 0xff) < 0x10) + { + buf.append("0"); + } + buf.append(Long.toString(hash[i] & 0xff, 16)); + } + return buf.toString(); + } + + public static String hash(String s) + { + try + { + return new String(toHex(md5(s)).getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); + } + catch (Exception e) + { + log.error("not supported charset...{}", e); + return s; + } + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/security/PermissionUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/security/PermissionUtils.java new file mode 100644 index 0000000..296f2ca --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/security/PermissionUtils.java @@ -0,0 +1,118 @@ +package com.ruoyi.common.utils.security; + +import java.beans.BeanInfo; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import org.apache.commons.lang3.StringUtils; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.subject.Subject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.ruoyi.common.constant.PermissionConstants; +import com.ruoyi.common.utils.MessageUtils; + +/** + * permission 工具类 + * + * @author ruoyi + */ +public class PermissionUtils +{ + private static final Logger log = LoggerFactory.getLogger(PermissionUtils.class); + + /** + * 查看数据的权限 + */ + public static final String VIEW_PERMISSION = "no.view.permission"; + + /** + * 创建数据的权限 + */ + public static final String CREATE_PERMISSION = "no.create.permission"; + + /** + * 修改数据的权限 + */ + public static final String UPDATE_PERMISSION = "no.update.permission"; + + /** + * 删除数据的权限 + */ + public static final String DELETE_PERMISSION = "no.delete.permission"; + + /** + * 导出数据的权限 + */ + public static final String EXPORT_PERMISSION = "no.export.permission"; + + /** + * 其他数据的权限 + */ + public static final String PERMISSION = "no.permission"; + + /** + * 权限错误消息提醒 + * + * @param permissionsStr 错误信息 + * @return 提示信息 + */ + public static String getMsg(String permissionsStr) + { + String permission = StringUtils.substringBetween(permissionsStr, "[", "]"); + String msg = MessageUtils.message(PERMISSION, permission); + if (StringUtils.endsWithIgnoreCase(permission, PermissionConstants.ADD_PERMISSION)) + { + msg = MessageUtils.message(CREATE_PERMISSION, permission); + } + else if (StringUtils.endsWithIgnoreCase(permission, PermissionConstants.EDIT_PERMISSION)) + { + msg = MessageUtils.message(UPDATE_PERMISSION, permission); + } + else if (StringUtils.endsWithIgnoreCase(permission, PermissionConstants.REMOVE_PERMISSION)) + { + msg = MessageUtils.message(DELETE_PERMISSION, permission); + } + else if (StringUtils.endsWithIgnoreCase(permission, PermissionConstants.EXPORT_PERMISSION)) + { + msg = MessageUtils.message(EXPORT_PERMISSION, permission); + } + else if (StringUtils.endsWithAny(permission, + new String[] { PermissionConstants.VIEW_PERMISSION, PermissionConstants.LIST_PERMISSION })) + { + msg = MessageUtils.message(VIEW_PERMISSION, permission); + } + return msg; + } + + /** + * 返回用户属性值 + * + * @param property 属性名称 + * @return 用户属性值 + */ + public static Object getPrincipalProperty(String property) + { + Subject subject = SecurityUtils.getSubject(); + if (subject != null) + { + Object principal = subject.getPrincipal(); + try + { + BeanInfo bi = Introspector.getBeanInfo(principal.getClass()); + for (PropertyDescriptor pd : bi.getPropertyDescriptors()) + { + if (pd.getName().equals(property) == true) + { + return pd.getReadMethod().invoke(principal, (Object[]) null); + } + } + } + catch (Exception e) + { + log.error("Error reading property [{}] from principal of type [{}]", property, + principal.getClass().getName()); + } + } + return null; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/spring/SpringUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/spring/SpringUtils.java new file mode 100644 index 0000000..c5699ad --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/spring/SpringUtils.java @@ -0,0 +1,159 @@ +package com.ruoyi.common.utils.spring; + +import org.springframework.aop.framework.AopContext; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; +import com.ruoyi.common.utils.StringUtils; + +/** + * spring工具类 方便在非spring管理环境中获取bean + * + * @author ruoyi + */ +@Component +public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware +{ + /** Spring应用上下文环境 */ + private static ConfigurableListableBeanFactory beanFactory; + + private static ApplicationContext applicationContext; + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException + { + SpringUtils.beanFactory = beanFactory; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + { + SpringUtils.applicationContext = applicationContext; + } + + /** + * 获取对象 + * + * @param name + * @return Object 一个以所给名字注册的bean的实例 + * @throws org.springframework.beans.BeansException + * + */ + @SuppressWarnings("unchecked") + public static T getBean(String name) throws BeansException + { + return (T) beanFactory.getBean(name); + } + + /** + * 获取类型为requiredType的对象 + * + * @param clz + * @return + * @throws org.springframework.beans.BeansException + * + */ + public static T getBean(Class clz) throws BeansException + { + T result = (T) beanFactory.getBean(clz); + return result; + } + + /** + * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true + * + * @param name + * @return boolean + */ + public static boolean containsBean(String name) + { + return beanFactory.containsBean(name); + } + + /** + * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException) + * + * @param name + * @return boolean + * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException + * + */ + public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.isSingleton(name); + } + + /** + * @param name + * @return Class 注册对象的类型 + * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException + * + */ + public static Class getType(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.getType(name); + } + + /** + * 如果给定的bean名字在bean定义中有别名,则返回这些别名 + * + * @param name + * @return + * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException + * + */ + public static String[] getAliases(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.getAliases(name); + } + + /** + * 获取aop代理对象 + * + * @param invoker + * @return + */ + @SuppressWarnings("unchecked") + public static T getAopProxy(T invoker) + { + return (T) AopContext.currentProxy(); + } + + /** + * 获取当前的环境配置,无配置返回null + * + * @return 当前的环境配置 + */ + public static String[] getActiveProfiles() + { + return applicationContext.getEnvironment().getActiveProfiles(); + } + + /** + * 获取当前的环境配置,当有多个环境配置时,只获取第一个 + * + * @return 当前的环境配置 + */ + public static String getActiveProfile() + { + final String[] activeProfiles = getActiveProfiles(); + return StringUtils.isNotEmpty(activeProfiles) ? activeProfiles[0] : null; + } + + /** + * 获取配置文件中的值 + * + * @param key 配置文件的key + * @return 当前的配置文件的值 + * + */ + public static String getRequiredProperty(String key) + { + return applicationContext.getEnvironment().getRequiredProperty(key); + } + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/sql/SqlUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/sql/SqlUtil.java new file mode 100644 index 0000000..51e1f92 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/sql/SqlUtil.java @@ -0,0 +1,61 @@ +package com.ruoyi.common.utils.sql; + +import com.ruoyi.common.exception.UtilException; +import com.ruoyi.common.utils.StringUtils; + +/** + * sql操作工具类 + * + * @author ruoyi + */ +public class SqlUtil +{ + /** + * 定义常用的 sql关键字 + */ + public static String SQL_REGEX = "and |extractvalue|updatexml|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |+|user()"; + + /** + * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序) + */ + public static String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+"; + + /** + * 检查字符,防止注入绕过 + */ + public static String escapeOrderBySql(String value) + { + if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) + { + throw new UtilException("参数不符合规范,不能进行查询"); + } + return value; + } + + /** + * 验证 order by 语法是否符合规范 + */ + public static boolean isValidOrderBySql(String value) + { + return value.matches(SQL_PATTERN); + } + + /** + * SQL关键字检查 + */ + public static void filterKeyword(String value) + { + if (StringUtils.isEmpty(value)) + { + return; + } + String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\\|"); + for (String sqlKeyword : sqlKeywords) + { + if (StringUtils.indexOfIgnoreCase(value, sqlKeyword) > -1) + { + throw new UtilException("参数存在SQL注入风险"); + } + } + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/IdUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/IdUtils.java new file mode 100644 index 0000000..2c84427 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/IdUtils.java @@ -0,0 +1,49 @@ +package com.ruoyi.common.utils.uuid; + +/** + * ID生成器工具类 + * + * @author ruoyi + */ +public class IdUtils +{ + /** + * 获取随机UUID + * + * @return 随机UUID + */ + public static String randomUUID() + { + return UUID.randomUUID().toString(); + } + + /** + * 简化的UUID,去掉了横线 + * + * @return 简化的UUID,去掉了横线 + */ + public static String simpleUUID() + { + return UUID.randomUUID().toString(true); + } + + /** + * 获取随机UUID,使用性能更好的ThreadLocalRandom生成UUID + * + * @return 随机UUID + */ + public static String fastUUID() + { + return UUID.fastUUID().toString(); + } + + /** + * 简化的UUID,去掉了横线,使用性能更好的ThreadLocalRandom生成UUID + * + * @return 简化的UUID,去掉了横线 + */ + public static String fastSimpleUUID() + { + return UUID.fastUUID().toString(true); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/Seq.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/Seq.java new file mode 100644 index 0000000..bf99611 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/Seq.java @@ -0,0 +1,86 @@ +package com.ruoyi.common.utils.uuid; + +import java.util.concurrent.atomic.AtomicInteger; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.StringUtils; + +/** + * @author ruoyi 序列生成类 + */ +public class Seq +{ + // 通用序列类型 + public static final String commSeqType = "COMMON"; + + // 上传序列类型 + public static final String uploadSeqType = "UPLOAD"; + + // 通用接口序列数 + private static AtomicInteger commSeq = new AtomicInteger(1); + + // 上传接口序列数 + private static AtomicInteger uploadSeq = new AtomicInteger(1); + + // 机器标识 + private static final String machineCode = "A"; + + /** + * 获取通用序列号 + * + * @return 序列值 + */ + public static String getId() + { + return getId(commSeqType); + } + + /** + * 默认16位序列号 yyMMddHHmmss + 一位机器标识 + 3长度循环递增字符串 + * + * @return 序列值 + */ + public static String getId(String type) + { + AtomicInteger atomicInt = commSeq; + if (uploadSeqType.equals(type)) + { + atomicInt = uploadSeq; + } + return getId(atomicInt, 3); + } + + /** + * 通用接口序列号 yyMMddHHmmss + 一位机器标识 + length长度循环递增字符串 + * + * @param atomicInt 序列数 + * @param length 数值长度 + * @return 序列值 + */ + public static String getId(AtomicInteger atomicInt, int length) + { + String result = DateUtils.dateTimeNow(); + result += machineCode; + result += getSeq(atomicInt, length); + return result; + } + + /** + * 序列循环递增字符串[1, 10 的 (length)幂次方), 用0左补齐length位数 + * + * @return 序列值 + */ + private synchronized static String getSeq(AtomicInteger atomicInt, int length) + { + // 先取值再+1 + int value = atomicInt.getAndIncrement(); + + // 如果更新后值>=10 的 (length)幂次方则重置为1 + int maxSeq = (int) Math.pow(10, length); + if (atomicInt.get() >= maxSeq) + { + atomicInt.set(1); + } + // 转字符串,用0左补齐 + return StringUtils.padl(value, length); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/UUID.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/UUID.java new file mode 100644 index 0000000..062d633 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/UUID.java @@ -0,0 +1,484 @@ +package com.ruoyi.common.utils.uuid; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; +import com.ruoyi.common.exception.UtilException; + +/** + * 提供通用唯一识别码(universally unique identifier)(UUID)实现 + * + * @author ruoyi + */ +public final class UUID implements java.io.Serializable, Comparable +{ + private static final long serialVersionUID = -1185015143654744140L; + + /** + * SecureRandom 的单例 + * + */ + private static class Holder + { + static final SecureRandom numberGenerator = getSecureRandom(); + } + + /** 此UUID的最高64有效位 */ + private final long mostSigBits; + + /** 此UUID的最低64有效位 */ + private final long leastSigBits; + + /** + * 私有构造 + * + * @param data 数据 + */ + private UUID(byte[] data) + { + long msb = 0; + long lsb = 0; + assert data.length == 16 : "data must be 16 bytes in length"; + for (int i = 0; i < 8; i++) + { + msb = (msb << 8) | (data[i] & 0xff); + } + for (int i = 8; i < 16; i++) + { + lsb = (lsb << 8) | (data[i] & 0xff); + } + this.mostSigBits = msb; + this.leastSigBits = lsb; + } + + /** + * 使用指定的数据构造新的 UUID。 + * + * @param mostSigBits 用于 {@code UUID} 的最高有效 64 位 + * @param leastSigBits 用于 {@code UUID} 的最低有效 64 位 + */ + public UUID(long mostSigBits, long leastSigBits) + { + this.mostSigBits = mostSigBits; + this.leastSigBits = leastSigBits; + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的本地线程伪随机数生成器生成该 UUID。 + * + * @return 随机生成的 {@code UUID} + */ + public static UUID fastUUID() + { + return randomUUID(false); + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。 + * + * @return 随机生成的 {@code UUID} + */ + public static UUID randomUUID() + { + return randomUUID(true); + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。 + * + * @param isSecure 是否使用{@link SecureRandom}如果是可以获得更安全的随机码,否则可以得到更好的性能 + * @return 随机生成的 {@code UUID} + */ + public static UUID randomUUID(boolean isSecure) + { + final Random ng = isSecure ? Holder.numberGenerator : getRandom(); + + byte[] randomBytes = new byte[16]; + ng.nextBytes(randomBytes); + randomBytes[6] &= 0x0f; /* clear version */ + randomBytes[6] |= 0x40; /* set to version 4 */ + randomBytes[8] &= 0x3f; /* clear variant */ + randomBytes[8] |= 0x80; /* set to IETF variant */ + return new UUID(randomBytes); + } + + /** + * 根据指定的字节数组获取类型 3(基于名称的)UUID 的静态工厂。 + * + * @param name 用于构造 UUID 的字节数组。 + * + * @return 根据指定数组生成的 {@code UUID} + */ + public static UUID nameUUIDFromBytes(byte[] name) + { + MessageDigest md; + try + { + md = MessageDigest.getInstance("MD5"); + } + catch (NoSuchAlgorithmException nsae) + { + throw new InternalError("MD5 not supported"); + } + byte[] md5Bytes = md.digest(name); + md5Bytes[6] &= 0x0f; /* clear version */ + md5Bytes[6] |= 0x30; /* set to version 3 */ + md5Bytes[8] &= 0x3f; /* clear variant */ + md5Bytes[8] |= 0x80; /* set to IETF variant */ + return new UUID(md5Bytes); + } + + /** + * 根据 {@link #toString()} 方法中描述的字符串标准表示形式创建{@code UUID}。 + * + * @param name 指定 {@code UUID} 字符串 + * @return 具有指定值的 {@code UUID} + * @throws IllegalArgumentException 如果 name 与 {@link #toString} 中描述的字符串表示形式不符抛出此异常 + * + */ + public static UUID fromString(String name) + { + String[] components = name.split("-"); + if (components.length != 5) + { + throw new IllegalArgumentException("Invalid UUID string: " + name); + } + for (int i = 0; i < 5; i++) + { + components[i] = "0x" + components[i]; + } + + long mostSigBits = Long.decode(components[0]).longValue(); + mostSigBits <<= 16; + mostSigBits |= Long.decode(components[1]).longValue(); + mostSigBits <<= 16; + mostSigBits |= Long.decode(components[2]).longValue(); + + long leastSigBits = Long.decode(components[3]).longValue(); + leastSigBits <<= 48; + leastSigBits |= Long.decode(components[4]).longValue(); + + return new UUID(mostSigBits, leastSigBits); + } + + /** + * 返回此 UUID 的 128 位值中的最低有效 64 位。 + * + * @return 此 UUID 的 128 位值中的最低有效 64 位。 + */ + public long getLeastSignificantBits() + { + return leastSigBits; + } + + /** + * 返回此 UUID 的 128 位值中的最高有效 64 位。 + * + * @return 此 UUID 的 128 位值中最高有效 64 位。 + */ + public long getMostSignificantBits() + { + return mostSigBits; + } + + /** + * 与此 {@code UUID} 相关联的版本号. 版本号描述此 {@code UUID} 是如何生成的。 + *

    + * 版本号具有以下含意: + *

      + *
    • 1 基于时间的 UUID + *
    • 2 DCE 安全 UUID + *
    • 3 基于名称的 UUID + *
    • 4 随机生成的 UUID + *
    + * + * @return 此 {@code UUID} 的版本号 + */ + public int version() + { + // Version is bits masked by 0x000000000000F000 in MS long + return (int) ((mostSigBits >> 12) & 0x0f); + } + + /** + * 与此 {@code UUID} 相关联的变体号。变体号描述 {@code UUID} 的布局。 + *

    + * 变体号具有以下含意: + *

      + *
    • 0 为 NCS 向后兼容保留 + *
    • 2 IETF RFC 4122(Leach-Salz), 用于此类 + *
    • 6 保留,微软向后兼容 + *
    • 7 保留供以后定义使用 + *
    + * + * @return 此 {@code UUID} 相关联的变体号 + */ + public int variant() + { + // This field is composed of a varying number of bits. + // 0 - - Reserved for NCS backward compatibility + // 1 0 - The IETF aka Leach-Salz variant (used by this class) + // 1 1 0 Reserved, Microsoft backward compatibility + // 1 1 1 Reserved for future definition. + return (int) ((leastSigBits >>> (64 - (leastSigBits >>> 62))) & (leastSigBits >> 63)); + } + + /** + * 与此 UUID 相关联的时间戳值。 + * + *

    + * 60 位的时间戳值根据此 {@code UUID} 的 time_low、time_mid 和 time_hi 字段构造。
    + * 所得到的时间戳以 100 毫微秒为单位,从 UTC(通用协调时间) 1582 年 10 月 15 日零时开始。 + * + *

    + * 时间戳值仅在在基于时间的 UUID(其 version 类型为 1)中才有意义。
    + * 如果此 {@code UUID} 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。 + * + * @throws UnsupportedOperationException 如果此 {@code UUID} 不是 version 为 1 的 UUID。 + */ + public long timestamp() throws UnsupportedOperationException + { + checkTimeBase(); + return (mostSigBits & 0x0FFFL) << 48// + | ((mostSigBits >> 16) & 0x0FFFFL) << 32// + | mostSigBits >>> 32; + } + + /** + * 与此 UUID 相关联的时钟序列值。 + * + *

    + * 14 位的时钟序列值根据此 UUID 的 clock_seq 字段构造。clock_seq 字段用于保证在基于时间的 UUID 中的时间唯一性。 + *

    + * {@code clockSequence} 值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。 如果此 UUID 不是基于时间的 UUID,则此方法抛出 + * UnsupportedOperationException。 + * + * @return 此 {@code UUID} 的时钟序列 + * + * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1 + */ + public int clockSequence() throws UnsupportedOperationException + { + checkTimeBase(); + return (int) ((leastSigBits & 0x3FFF000000000000L) >>> 48); + } + + /** + * 与此 UUID 相关的节点值。 + * + *

    + * 48 位的节点值根据此 UUID 的 node 字段构造。此字段旨在用于保存机器的 IEEE 802 地址,该地址用于生成此 UUID 以保证空间唯一性。 + *

    + * 节点值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。
    + * 如果此 UUID 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。 + * + * @return 此 {@code UUID} 的节点值 + * + * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1 + */ + public long node() throws UnsupportedOperationException + { + checkTimeBase(); + return leastSigBits & 0x0000FFFFFFFFFFFFL; + } + + /** + * 返回此{@code UUID} 的字符串表现形式。 + * + *

    + * UUID 的字符串表示形式由此 BNF 描述: + * + *

    +     * {@code
    +     * UUID                   = ----
    +     * time_low               = 4*
    +     * time_mid               = 2*
    +     * time_high_and_version  = 2*
    +     * variant_and_sequence   = 2*
    +     * node                   = 6*
    +     * hexOctet               = 
    +     * hexDigit               = [0-9a-fA-F]
    +     * }
    +     * 
    + * + * + * + * @return 此{@code UUID} 的字符串表现形式 + * @see #toString(boolean) + */ + @Override + public String toString() + { + return toString(false); + } + + /** + * 返回此{@code UUID} 的字符串表现形式。 + * + *

    + * UUID 的字符串表示形式由此 BNF 描述: + * + *

    +     * {@code
    +     * UUID                   = ----
    +     * time_low               = 4*
    +     * time_mid               = 2*
    +     * time_high_and_version  = 2*
    +     * variant_and_sequence   = 2*
    +     * node                   = 6*
    +     * hexOctet               = 
    +     * hexDigit               = [0-9a-fA-F]
    +     * }
    +     * 
    + * + * + * + * @param isSimple 是否简单模式,简单模式为不带'-'的UUID字符串 + * @return 此{@code UUID} 的字符串表现形式 + */ + public String toString(boolean isSimple) + { + final StringBuilder builder = new StringBuilder(isSimple ? 32 : 36); + // time_low + builder.append(digits(mostSigBits >> 32, 8)); + if (false == isSimple) + { + builder.append('-'); + } + // time_mid + builder.append(digits(mostSigBits >> 16, 4)); + if (false == isSimple) + { + builder.append('-'); + } + // time_high_and_version + builder.append(digits(mostSigBits, 4)); + if (false == isSimple) + { + builder.append('-'); + } + // variant_and_sequence + builder.append(digits(leastSigBits >> 48, 4)); + if (false == isSimple) + { + builder.append('-'); + } + // node + builder.append(digits(leastSigBits, 12)); + + return builder.toString(); + } + + /** + * 返回此 UUID 的哈希码。 + * + * @return UUID 的哈希码值。 + */ + @Override + public int hashCode() + { + long hilo = mostSigBits ^ leastSigBits; + return ((int) (hilo >> 32)) ^ (int) hilo; + } + + /** + * 将此对象与指定对象比较。 + *

    + * 当且仅当参数不为 {@code null}、而是一个 UUID 对象、具有与此 UUID 相同的 varriant、包含相同的值(每一位均相同)时,结果才为 {@code true}。 + * + * @param obj 要与之比较的对象 + * + * @return 如果对象相同,则返回 {@code true};否则返回 {@code false} + */ + @Override + public boolean equals(Object obj) + { + if ((null == obj) || (obj.getClass() != UUID.class)) + { + return false; + } + UUID id = (UUID) obj; + return (mostSigBits == id.mostSigBits && leastSigBits == id.leastSigBits); + } + + // Comparison Operations + + /** + * 将此 UUID 与指定的 UUID 比较。 + * + *

    + * 如果两个 UUID 不同,且第一个 UUID 的最高有效字段大于第二个 UUID 的对应字段,则第一个 UUID 大于第二个 UUID。 + * + * @param val 与此 UUID 比较的 UUID + * + * @return 在此 UUID 小于、等于或大于 val 时,分别返回 -1、0 或 1。 + * + */ + @Override + public int compareTo(UUID val) + { + // The ordering is intentionally set up so that the UUIDs + // can simply be numerically compared as two numbers + return (this.mostSigBits < val.mostSigBits ? -1 : // + (this.mostSigBits > val.mostSigBits ? 1 : // + (this.leastSigBits < val.leastSigBits ? -1 : // + (this.leastSigBits > val.leastSigBits ? 1 : // + 0)))); + } + + // ------------------------------------------------------------------------------------------------------------------- + // Private method start + /** + * 返回指定数字对应的hex值 + * + * @param val 值 + * @param digits 位 + * @return 值 + */ + private static String digits(long val, int digits) + { + long hi = 1L << (digits * 4); + return Long.toHexString(hi | (val & (hi - 1))).substring(1); + } + + /** + * 检查是否为time-based版本UUID + */ + private void checkTimeBase() + { + if (version() != 1) + { + throw new UnsupportedOperationException("Not a time-based UUID"); + } + } + + /** + * 获取{@link SecureRandom},类提供加密的强随机数生成器 (RNG) + * + * @return {@link SecureRandom} + */ + public static SecureRandom getSecureRandom() + { + try + { + return SecureRandom.getInstance("SHA1PRNG"); + } + catch (NoSuchAlgorithmException e) + { + throw new UtilException(e); + } + } + + /** + * 获取随机数生成器对象
    + * ThreadLocalRandom是JDK 7之后提供并发产生随机数,能够解决多个线程发生的竞争争夺。 + * + * @return {@link ThreadLocalRandom} + */ + public static ThreadLocalRandom getRandom() + { + return ThreadLocalRandom.current(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/xss/Xss.java b/ruoyi-common/src/main/java/com/ruoyi/common/xss/Xss.java new file mode 100644 index 0000000..7bfdf04 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/xss/Xss.java @@ -0,0 +1,27 @@ +package com.ruoyi.common.xss; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 自定义xss校验注解 + * + * @author ruoyi + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value = { ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER }) +@Constraint(validatedBy = { XssValidator.class }) +public @interface Xss +{ + String message() + + default "不允许任何脚本运行"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/xss/XssFilter.java b/ruoyi-common/src/main/java/com/ruoyi/common/xss/XssFilter.java new file mode 100644 index 0000000..7918056 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/xss/XssFilter.java @@ -0,0 +1,74 @@ +package com.ruoyi.common.xss; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import com.ruoyi.common.utils.StringUtils; + +/** + * 防止XSS攻击的过滤器 + * + * @author ruoyi + */ +public class XssFilter implements Filter +{ + /** + * 排除链接 + */ + public List excludes = new ArrayList<>(); + + @Override + public void init(FilterConfig filterConfig) throws ServletException + { + String tempExcludes = filterConfig.getInitParameter("excludes"); + if (StringUtils.isNotEmpty(tempExcludes)) + { + String[] url = tempExcludes.split(","); + for (int i = 0; url != null && i < url.length; i++) + { + excludes.add(url[i]); + } + } + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException + { + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse resp = (HttpServletResponse) response; + if (handleExcludeURL(req, resp)) + { + chain.doFilter(request, response); + return; + } + XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request); + chain.doFilter(xssRequest, response); + } + + private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) + { + String url = request.getServletPath(); + String method = request.getMethod(); + // GET DELETE 不过滤 + if (method == null || method.matches("GET") || method.matches("DELETE")) + { + return true; + } + return StringUtils.matches(url, excludes); + } + + @Override + public void destroy() + { + + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/xss/XssHttpServletRequestWrapper.java b/ruoyi-common/src/main/java/com/ruoyi/common/xss/XssHttpServletRequestWrapper.java new file mode 100644 index 0000000..929de41 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/xss/XssHttpServletRequestWrapper.java @@ -0,0 +1,39 @@ +package com.ruoyi.common.xss; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import com.ruoyi.common.utils.html.EscapeUtil; + +/** + * XSS过滤处理 + * + * @author ruoyi + */ +public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper +{ + /** + * @param request + */ + public XssHttpServletRequestWrapper(HttpServletRequest request) + { + super(request); + } + + @Override + public String[] getParameterValues(String name) + { + String[] values = super.getParameterValues(name); + if (values != null) + { + int length = values.length; + String[] escapseValues = new String[length]; + for (int i = 0; i < length; i++) + { + // 防xss攻击和过滤前后空格 + escapseValues[i] = EscapeUtil.clean(values[i]).trim(); + } + return escapseValues; + } + return super.getParameterValues(name); + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/xss/XssValidator.java b/ruoyi-common/src/main/java/com/ruoyi/common/xss/XssValidator.java new file mode 100644 index 0000000..ed9ec1f --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/xss/XssValidator.java @@ -0,0 +1,34 @@ +package com.ruoyi.common.xss; + +import com.ruoyi.common.utils.StringUtils; +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 自定义xss校验注解实现 + * + * @author ruoyi + */ +public class XssValidator implements ConstraintValidator +{ + private static final String HTML_PATTERN = "<(\\S*?)[^>]*>.*?|<.*? />"; + + @Override + public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) + { + if (StringUtils.isBlank(value)) + { + return true; + } + return !containsHtml(value); + } + + public static boolean containsHtml(String value) + { + Pattern pattern = Pattern.compile(HTML_PATTERN); + Matcher matcher = pattern.matcher(value); + return matcher.matches(); + } +} \ No newline at end of file diff --git a/ruoyi-framework/pom.xml b/ruoyi-framework/pom.xml new file mode 100644 index 0000000..2c23f61 --- /dev/null +++ b/ruoyi-framework/pom.xml @@ -0,0 +1,82 @@ + + + + ruoyi + com.ruoyi + 4.7.6 + + 4.0.0 + + ruoyi-framework + + + framework框架核心 + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-aop + + + + + com.alibaba + druid-spring-boot-starter + + + + + pro.fessional + kaptcha + + + javax.servlet-api + javax.servlet + + + + + + + org.apache.shiro + shiro-spring + + + + + com.github.theborakompanioni + thymeleaf-extras-shiro + + + + + eu.bitwalker + UserAgentUtils + + + + + com.github.oshi + oshi-core + + + + + com.ruoyi + ruoyi-system + + + + + \ No newline at end of file diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java new file mode 100644 index 0000000..d35315d --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java @@ -0,0 +1,172 @@ +package com.ruoyi.framework.aspectj; + +import java.util.ArrayList; +import java.util.List; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.stereotype.Component; +import com.ruoyi.common.annotation.DataScope; +import com.ruoyi.common.core.context.PermissionContextHolder; +import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.utils.ShiroUtils; +import com.ruoyi.common.utils.StringUtils; + +/** + * 数据过滤处理 + * + * @author ruoyi + */ +@Aspect +@Component +public class DataScopeAspect +{ + /** + * 全部数据权限 + */ + public static final String DATA_SCOPE_ALL = "1"; + + /** + * 自定数据权限 + */ + public static final String DATA_SCOPE_CUSTOM = "2"; + + /** + * 部门数据权限 + */ + public static final String DATA_SCOPE_DEPT = "3"; + + /** + * 部门及以下数据权限 + */ + public static final String DATA_SCOPE_DEPT_AND_CHILD = "4"; + + /** + * 仅本人数据权限 + */ + public static final String DATA_SCOPE_SELF = "5"; + + /** + * 数据权限过滤关键字 + */ + public static final String DATA_SCOPE = "dataScope"; + + @Before("@annotation(controllerDataScope)") + public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable + { + clearDataScope(point); + handleDataScope(point, controllerDataScope); + } + + protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope) + { + // 获取当前的用户 + SysUser currentUser = ShiroUtils.getSysUser(); + if (currentUser != null) + { + // 如果是超级管理员,则不过滤数据 + if (!currentUser.isAdmin()) + { + String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), PermissionContextHolder.getContext()); + dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(), + controllerDataScope.userAlias(), permission); + } + } + } + + /** + * 数据范围过滤 + * + * @param joinPoint 切点 + * @param user 用户 + * @param deptAlias 部门别名 + * @param userAlias 用户别名 + * @param permission 权限字符 + */ + public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias, String permission) + { + StringBuilder sqlString = new StringBuilder(); + List conditions = new ArrayList(); + + for (SysRole role : user.getRoles()) + { + String dataScope = role.getDataScope(); + if (!DATA_SCOPE_CUSTOM.equals(dataScope) && conditions.contains(dataScope)) + { + continue; + } + if (StringUtils.isNotEmpty(permission) && StringUtils.isNotEmpty(role.getPermissions()) + && !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) + { + continue; + } + if (DATA_SCOPE_ALL.equals(dataScope)) + { + sqlString = new StringBuilder(); + conditions.add(dataScope); + break; + } + else if (DATA_SCOPE_CUSTOM.equals(dataScope)) + { + sqlString.append(StringUtils.format( + " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias, + role.getRoleId())); + } + else if (DATA_SCOPE_DEPT.equals(dataScope)) + { + sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId())); + } + else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) + { + sqlString.append(StringUtils.format( + " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )", + deptAlias, user.getDeptId(), user.getDeptId())); + } + else if (DATA_SCOPE_SELF.equals(dataScope)) + { + if (StringUtils.isNotBlank(userAlias)) + { + sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId())); + } + else + { + // 数据权限为仅本人且没有userAlias别名不查询任何数据 + sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias)); + } + } + conditions.add(dataScope); + } + + // 多角色情况下,所有角色都不包含传递过来的权限字符,这个时候sqlString也会为空,所以要限制一下,不查询任何数据 + if (StringUtils.isEmpty(conditions)) + { + sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias)); + } + + if (StringUtils.isNotBlank(sqlString.toString())) + { + Object params = joinPoint.getArgs()[0]; + if (StringUtils.isNotNull(params) && params instanceof BaseEntity) + { + BaseEntity baseEntity = (BaseEntity) params; + baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")"); + } + } + } + + /** + * 拼接权限sql前先清空params.dataScope参数防止注入 + */ + private void clearDataScope(final JoinPoint joinPoint) + { + Object params = joinPoint.getArgs()[0]; + if (StringUtils.isNotNull(params) && params instanceof BaseEntity) + { + BaseEntity baseEntity = (BaseEntity) params; + baseEntity.getParams().put(DATA_SCOPE, ""); + } + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataSourceAspect.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataSourceAspect.java new file mode 100644 index 0000000..4951bc9 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataSourceAspect.java @@ -0,0 +1,72 @@ +package com.ruoyi.framework.aspectj; + +import java.util.Objects; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import com.ruoyi.common.annotation.DataSource; +import com.ruoyi.common.config.datasource.DynamicDataSourceContextHolder; +import com.ruoyi.common.utils.StringUtils; + +/** + * 多数据源处理 + * + * @author ruoyi + */ +@Aspect +@Order(1) +@Component +public class DataSourceAspect +{ + protected Logger logger = LoggerFactory.getLogger(getClass()); + + @Pointcut("@annotation(com.ruoyi.common.annotation.DataSource)" + + "|| @within(com.ruoyi.common.annotation.DataSource)") + public void dsPointCut() + { + + } + + @Around("dsPointCut()") + public Object around(ProceedingJoinPoint point) throws Throwable + { + DataSource dataSource = getDataSource(point); + + if (StringUtils.isNotNull(dataSource)) + { + DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name()); + } + + try + { + return point.proceed(); + } + finally + { + // 销毁数据源 在执行方法之后 + DynamicDataSourceContextHolder.clearDataSourceType(); + } + } + + /** + * 获取需要切换的数据源 + */ + public DataSource getDataSource(ProceedingJoinPoint point) + { + MethodSignature signature = (MethodSignature) point.getSignature(); + DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class); + if (Objects.nonNull(dataSource)) + { + return dataSource; + } + + return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java new file mode 100644 index 0000000..25215b5 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java @@ -0,0 +1,255 @@ +package com.ruoyi.framework.aspectj; + +import java.util.Collection; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.ArrayUtils; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.NamedThreadLocal; +import org.springframework.stereotype.Component; +import org.springframework.validation.BindingResult; +import org.springframework.web.multipart.MultipartFile; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.support.spring.PropertyPreFilters; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.enums.BusinessStatus; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.ShiroUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.manager.AsyncManager; +import com.ruoyi.framework.manager.factory.AsyncFactory; +import com.ruoyi.system.domain.SysOperLog; + +/** + * 操作日志记录处理 + * + * @author ruoyi + */ +@Aspect +@Component +public class LogAspect +{ + private static final Logger log = LoggerFactory.getLogger(LogAspect.class); + + /** 排除敏感属性字段 */ + public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" }; + + /** 计算操作消耗时间 */ + private static final ThreadLocal TIME_THREADLOCAL = new NamedThreadLocal("Cost Time"); + + /** + * 处理请求前执行 + */ + @Before(value = "@annotation(controllerLog)") + public void boBefore(JoinPoint joinPoint, Log controllerLog) + { + TIME_THREADLOCAL.set(System.currentTimeMillis()); + } + + /** + * 处理完请求后执行 + * + * @param joinPoint 切点 + */ + @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult") + public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) + { + handleLog(joinPoint, controllerLog, null, jsonResult); + } + + /** + * 拦截异常操作 + * + * @param joinPoint 切点 + * @param e 异常 + */ + @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e") + public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) + { + handleLog(joinPoint, controllerLog, e, null); + } + + protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) + { + try + { + // 获取当前的用户 + SysUser currentUser = ShiroUtils.getSysUser(); + + // *========数据库日志=========*// + SysOperLog operLog = new SysOperLog(); + operLog.setStatus(BusinessStatus.SUCCESS.ordinal()); + // 请求的地址 + String ip = ShiroUtils.getIp(); + operLog.setOperIp(ip); + operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255)); + if (currentUser != null) + { + operLog.setOperName(currentUser.getLoginName()); + if (StringUtils.isNotNull(currentUser.getDept()) + && StringUtils.isNotEmpty(currentUser.getDept().getDeptName())) + { + operLog.setDeptName(currentUser.getDept().getDeptName()); + } + } + + if (e != null) + { + operLog.setStatus(BusinessStatus.FAIL.ordinal()); + operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000)); + } + // 设置方法名称 + String className = joinPoint.getTarget().getClass().getName(); + String methodName = joinPoint.getSignature().getName(); + operLog.setMethod(className + "." + methodName + "()"); + // 设置请求方式 + operLog.setRequestMethod(ServletUtils.getRequest().getMethod()); + // 处理设置注解上的参数 + getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult); + // 设置消耗时间 + operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get()); + // 保存数据库 + AsyncManager.me().execute(AsyncFactory.recordOper(operLog)); + } + catch (Exception exp) + { + // 记录本地异常日志 + log.error("异常信息:{}", exp.getMessage()); + exp.printStackTrace(); + } + finally + { + TIME_THREADLOCAL.remove(); + } + } + + /** + * 获取注解中对方法的描述信息 用于Controller层注解 + * + * @param log 日志 + * @param operLog 操作日志 + * @throws Exception + */ + public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog, Object jsonResult) throws Exception + { + // 设置action动作 + operLog.setBusinessType(log.businessType().ordinal()); + // 设置标题 + operLog.setTitle(log.title()); + // 设置操作人类别 + operLog.setOperatorType(log.operatorType().ordinal()); + // 是否需要保存request,参数和值 + if (log.isSaveRequestData()) + { + // 获取参数的信息,传入到数据库中。 + setRequestValue(joinPoint, operLog, log.excludeParamNames()); + } + // 是否需要保存response,参数和值 + if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)) + { + operLog.setJsonResult(StringUtils.substring(JSONObject.toJSONString(jsonResult), 0, 2000)); + } + } + + /** + * 获取请求的参数,放到log中 + * + * @param operLog 操作日志 + * @throws Exception 异常 + */ + private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog, String[] excludeParamNames) throws Exception + { + Map map = ServletUtils.getRequest().getParameterMap(); + if (StringUtils.isNotEmpty(map)) + { + String params = JSONObject.toJSONString(map, excludePropertyPreFilter(excludeParamNames)); + operLog.setOperParam(StringUtils.substring(params, 0, 2000)); + } + else + { + Object args = joinPoint.getArgs(); + if (StringUtils.isNotNull(args)) + { + String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames); + operLog.setOperParam(StringUtils.substring(params, 0, 2000)); + } + } + } + + /** + * 忽略敏感属性 + */ + public PropertyPreFilters.MySimplePropertyPreFilter excludePropertyPreFilter(String[] excludeParamNames) + { + return new PropertyPreFilters().addFilter().addExcludes(ArrayUtils.addAll(EXCLUDE_PROPERTIES, excludeParamNames)); + } + + /** + * 参数拼装 + */ + private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) + { + String params = ""; + if (paramsArray != null && paramsArray.length > 0) + { + for (Object o : paramsArray) + { + if (StringUtils.isNotNull(o) && !isFilterObject(o)) + { + try + { + Object jsonObj = JSONObject.toJSONString(o, excludePropertyPreFilter(excludeParamNames)); + params += jsonObj.toString() + " "; + } + catch (Exception e) + { + } + } + } + } + return params.trim(); + } + + /** + * 判断是否需要过滤的对象。 + * + * @param o 对象信息。 + * @return 如果是需要过滤的对象,则返回true;否则返回false。 + */ + @SuppressWarnings("rawtypes") + public boolean isFilterObject(final Object o) + { + Class clazz = o.getClass(); + if (clazz.isArray()) + { + return clazz.getComponentType().isAssignableFrom(MultipartFile.class); + } + else if (Collection.class.isAssignableFrom(clazz)) + { + Collection collection = (Collection) o; + for (Object value : collection) + { + return value instanceof MultipartFile; + } + } + else if (Map.class.isAssignableFrom(clazz)) + { + Map map = (Map) o; + for (Object value : map.entrySet()) + { + Map.Entry entry = (Map.Entry) value; + return entry.getValue() instanceof MultipartFile; + } + } + return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse + || o instanceof BindingResult; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/PermissionsAspect.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/PermissionsAspect.java new file mode 100644 index 0000000..9d9831f --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/PermissionsAspect.java @@ -0,0 +1,30 @@ +package com.ruoyi.framework.aspectj; + +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.stereotype.Component; +import com.ruoyi.common.core.context.PermissionContextHolder; +import com.ruoyi.common.utils.StringUtils; + +/** + * 自定义权限拦截器,将权限字符串放到当前请求中以便用于多个角色匹配符合要求的权限 + * + * @author ruoyi + */ +@Aspect +@Component +public class PermissionsAspect +{ + @Before("@annotation(controllerRequiresPermissions)") + public void doBefore(JoinPoint point, RequiresPermissions controllerRequiresPermissions) throws Throwable + { + handleRequiresPermissions(point, controllerRequiresPermissions); + } + + protected void handleRequiresPermissions(final JoinPoint joinPoint, RequiresPermissions requiresPermissions) + { + PermissionContextHolder.setContext(StringUtils.join(requiresPermissions.value(), ",")); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ApplicationConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ApplicationConfig.java new file mode 100644 index 0000000..b6b0e88 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ApplicationConfig.java @@ -0,0 +1,20 @@ +package com.ruoyi.framework.config; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; + +/** + * 程序注解配置 + * + * @author ruoyi + */ +@Configuration +// 表示通过aop框架暴露该代理对象,AopContext能够访问 +@EnableAspectJAutoProxy(exposeProxy = true) +// 指定要扫描的Mapper类的包的路径 +@MapperScan("com.ruoyi.**.mapper") +public class ApplicationConfig +{ + +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/CaptchaConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/CaptchaConfig.java new file mode 100644 index 0000000..43e78ae --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/CaptchaConfig.java @@ -0,0 +1,83 @@ +package com.ruoyi.framework.config; + +import java.util.Properties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import com.google.code.kaptcha.impl.DefaultKaptcha; +import com.google.code.kaptcha.util.Config; +import static com.google.code.kaptcha.Constants.*; + +/** + * 验证码配置 + * + * @author ruoyi + */ +@Configuration +public class CaptchaConfig +{ + @Bean(name = "captchaProducer") + public DefaultKaptcha getKaptchaBean() + { + DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); + Properties properties = new Properties(); + // 是否有边框 默认为true 我们可以自己设置yes,no + properties.setProperty(KAPTCHA_BORDER, "yes"); + // 验证码文本字符颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black"); + // 验证码图片宽度 默认为200 + properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); + // 验证码图片高度 默认为50 + properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); + // 验证码文本字符大小 默认为40 + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38"); + // KAPTCHA_SESSION_KEY + properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode"); + // 验证码文本字符长度 默认为5 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4"); + // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); + // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy + properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); + Config config = new Config(properties); + defaultKaptcha.setConfig(config); + return defaultKaptcha; + } + + @Bean(name = "captchaProducerMath") + public DefaultKaptcha getKaptchaBeanMath() + { + DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); + Properties properties = new Properties(); + // 是否有边框 默认为true 我们可以自己设置yes,no + properties.setProperty(KAPTCHA_BORDER, "yes"); + // 边框颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90"); + // 验证码文本字符颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue"); + // 验证码图片宽度 默认为200 + properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); + // 验证码图片高度 默认为50 + properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); + // 验证码文本字符大小 默认为40 + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35"); + // KAPTCHA_SESSION_KEY + properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath"); + // 验证码文本生成器 + properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.ruoyi.framework.config.KaptchaTextCreator"); + // 验证码文本字符间距 默认为2 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3"); + // 验证码文本字符长度 默认为5 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6"); + // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); + // 验证码噪点颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_NOISE_COLOR, "white"); + // 干扰实现类 + properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise"); + // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy + properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); + Config config = new Config(properties); + defaultKaptcha.setConfig(config); + return defaultKaptcha; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/DruidConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/DruidConfig.java new file mode 100644 index 0000000..1e3ebef --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/DruidConfig.java @@ -0,0 +1,128 @@ +package com.ruoyi.framework.config; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.sql.DataSource; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; +import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties; +import com.alibaba.druid.util.Utils; +import com.ruoyi.common.enums.DataSourceType; +import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.framework.config.properties.DruidProperties; +import com.ruoyi.framework.datasource.DynamicDataSource; + +/** + * druid 配置多数据源 + * + * @author ruoyi + */ +@Configuration +public class DruidConfig +{ + @Bean + @ConfigurationProperties("spring.datasource.druid.master") + public DataSource masterDataSource(DruidProperties druidProperties) + { + DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); + return druidProperties.dataSource(dataSource); + } + + @Bean + @ConfigurationProperties("spring.datasource.druid.slave") + @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true") + public DataSource slaveDataSource(DruidProperties druidProperties) + { + DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); + return druidProperties.dataSource(dataSource); + } + + @Bean(name = "dynamicDataSource") + @Primary + public DynamicDataSource dataSource(DataSource masterDataSource) + { + Map targetDataSources = new HashMap<>(); + targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource); + setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource"); + return new DynamicDataSource(masterDataSource, targetDataSources); + } + + /** + * 设置数据源 + * + * @param targetDataSources 备选数据源集合 + * @param sourceName 数据源名称 + * @param beanName bean名称 + */ + public void setDataSource(Map targetDataSources, String sourceName, String beanName) + { + try + { + DataSource dataSource = SpringUtils.getBean(beanName); + targetDataSources.put(sourceName, dataSource); + } + catch (Exception e) + { + } + } + + /** + * 去除监控页面底部的广告 + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Bean + @ConditionalOnProperty(name = "spring.datasource.druid.statViewServlet.enabled", havingValue = "true") + public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties) + { + // 获取web监控页面的参数 + DruidStatProperties.StatViewServlet config = properties.getStatViewServlet(); + // 提取common.js的配置路径 + String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*"; + String commonJsPattern = pattern.replaceAll("\\*", "js/common.js"); + final String filePath = "support/http/resources/js/common.js"; + // 创建filter进行过滤 + Filter filter = new Filter() + { + @Override + public void init(javax.servlet.FilterConfig filterConfig) throws ServletException + { + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException + { + chain.doFilter(request, response); + // 重置缓冲区,响应头不会被重置 + response.resetBuffer(); + // 获取common.js + String text = Utils.readFromResource(filePath); + // 正则替换banner, 除去底部的广告信息 + text = text.replaceAll("
    ", ""); + text = text.replaceAll("powered.*?shrek.wang", ""); + response.getWriter().write(text); + } + + @Override + public void destroy() + { + } + }; + FilterRegistrationBean registrationBean = new FilterRegistrationBean(); + registrationBean.setFilter(filter); + registrationBean.addUrlPatterns(commonJsPattern); + return registrationBean; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java new file mode 100644 index 0000000..6abb9a2 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java @@ -0,0 +1,44 @@ +package com.ruoyi.framework.config; + +import java.util.HashMap; +import java.util.Map; +import javax.servlet.DispatcherType; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.xss.XssFilter; + +/** + * Filter配置 + * + * @author ruoyi + */ +@Configuration +@ConditionalOnProperty(value = "xss.enabled", havingValue = "true") +public class FilterConfig +{ + @Value("${xss.excludes}") + private String excludes; + + @Value("${xss.urlPatterns}") + private String urlPatterns; + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Bean + public FilterRegistrationBean xssFilterRegistration() + { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setDispatcherTypes(DispatcherType.REQUEST); + registration.setFilter(new XssFilter()); + registration.addUrlPatterns(StringUtils.split(urlPatterns, ",")); + registration.setName("xssFilter"); + registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE); + Map initParameters = new HashMap(); + initParameters.put("excludes", excludes); + registration.setInitParameters(initParameters); + return registration; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/I18nConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/I18nConfig.java new file mode 100644 index 0000000..c5c209e --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/I18nConfig.java @@ -0,0 +1,43 @@ +package com.ruoyi.framework.config; + +import java.util.Locale; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; +import org.springframework.web.servlet.i18n.SessionLocaleResolver; + +/** + * 资源文件配置加载 + * + * @author ruoyi + */ +@Configuration +public class I18nConfig implements WebMvcConfigurer +{ + @Bean + public LocaleResolver localeResolver() + { + SessionLocaleResolver slr = new SessionLocaleResolver(); + // 默认语言 + slr.setDefaultLocale(Locale.SIMPLIFIED_CHINESE); + return slr; + } + + @Bean + public LocaleChangeInterceptor localeChangeInterceptor() + { + LocaleChangeInterceptor lci = new LocaleChangeInterceptor(); + // 参数名 + lci.setParamName("lang"); + return lci; + } + + @Override + public void addInterceptors(InterceptorRegistry registry) + { + registry.addInterceptor(localeChangeInterceptor()); + } +} \ No newline at end of file diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/KaptchaTextCreator.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/KaptchaTextCreator.java new file mode 100644 index 0000000..ae89d3c --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/KaptchaTextCreator.java @@ -0,0 +1,76 @@ +package com.ruoyi.framework.config; + +import java.security.SecureRandom; +import java.util.Random; +import com.google.code.kaptcha.text.impl.DefaultTextCreator; + +/** + * 验证码文本生成器 + * + * @author ruoyi + */ +public class KaptchaTextCreator extends DefaultTextCreator +{ + private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(","); + + @Override + public String getText() + { + Integer result = 0; + Random random = new SecureRandom(); + int x = random.nextInt(10); + int y = random.nextInt(10); + StringBuilder suChinese = new StringBuilder(); + int randomoperands = (int) Math.round(Math.random() * 2); + if (randomoperands == 0) + { + result = x * y; + suChinese.append(CNUMBERS[x]); + suChinese.append("*"); + suChinese.append(CNUMBERS[y]); + } + else if (randomoperands == 1) + { + if (!(x == 0) && y % x == 0) + { + result = y / x; + suChinese.append(CNUMBERS[y]); + suChinese.append("/"); + suChinese.append(CNUMBERS[x]); + } + else + { + result = x + y; + suChinese.append(CNUMBERS[x]); + suChinese.append("+"); + suChinese.append(CNUMBERS[y]); + } + } + else if (randomoperands == 2) + { + if (x >= y) + { + result = x - y; + suChinese.append(CNUMBERS[x]); + suChinese.append("-"); + suChinese.append(CNUMBERS[y]); + } + else + { + result = y - x; + suChinese.append(CNUMBERS[y]); + suChinese.append("-"); + suChinese.append(CNUMBERS[x]); + } + } + else + { + result = x + y; + suChinese.append(CNUMBERS[x]); + suChinese.append("+"); + suChinese.append(CNUMBERS[y]); + } + suChinese.append("=?@" + result); + return suChinese.toString(); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/MyBatisConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/MyBatisConfig.java new file mode 100644 index 0000000..057c941 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/MyBatisConfig.java @@ -0,0 +1,132 @@ +package com.ruoyi.framework.config; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import javax.sql.DataSource; +import org.apache.ibatis.io.VFS; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionFactoryBean; +import org.mybatis.spring.boot.autoconfigure.SpringBootVFS; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.core.type.classreading.CachingMetadataReaderFactory; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.util.ClassUtils; +import com.ruoyi.common.utils.StringUtils; + +/** + * Mybatis支持*匹配扫描包 + * + * @author ruoyi + */ +@Configuration +public class MyBatisConfig +{ + @Autowired + private Environment env; + + static final String DEFAULT_RESOURCE_PATTERN = "**/*.class"; + + public static String setTypeAliasesPackage(String typeAliasesPackage) + { + ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver(); + MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver); + List allResult = new ArrayList(); + try + { + for (String aliasesPackage : typeAliasesPackage.split(",")) + { + List result = new ArrayList(); + aliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + + ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/" + DEFAULT_RESOURCE_PATTERN; + Resource[] resources = resolver.getResources(aliasesPackage); + if (resources != null && resources.length > 0) + { + MetadataReader metadataReader = null; + for (Resource resource : resources) + { + if (resource.isReadable()) + { + metadataReader = metadataReaderFactory.getMetadataReader(resource); + try + { + result.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage().getName()); + } + catch (ClassNotFoundException e) + { + e.printStackTrace(); + } + } + } + } + if (result.size() > 0) + { + HashSet hashResult = new HashSet(result); + allResult.addAll(hashResult); + } + } + if (allResult.size() > 0) + { + typeAliasesPackage = String.join(",", (String[]) allResult.toArray(new String[0])); + } + else + { + throw new RuntimeException("mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:" + typeAliasesPackage + "未找到任何包"); + } + } + catch (IOException e) + { + e.printStackTrace(); + } + return typeAliasesPackage; + } + + public Resource[] resolveMapperLocations(String[] mapperLocations) + { + ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver(); + List resources = new ArrayList(); + if (mapperLocations != null) + { + for (String mapperLocation : mapperLocations) + { + try + { + Resource[] mappers = resourceResolver.getResources(mapperLocation); + resources.addAll(Arrays.asList(mappers)); + } + catch (IOException e) + { + // ignore + } + } + } + return resources.toArray(new Resource[resources.size()]); + } + + @Bean + public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception + { + String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage"); + String mapperLocations = env.getProperty("mybatis.mapperLocations"); + String configLocation = env.getProperty("mybatis.configLocation"); + typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage); + VFS.addImplClass(SpringBootVFS.class); + + final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); + sessionFactory.setDataSource(dataSource); + sessionFactory.setTypeAliasesPackage(typeAliasesPackage); + sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ","))); + sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation)); + return sessionFactory.getObject(); + } +} \ No newline at end of file diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java new file mode 100644 index 0000000..5322ecd --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java @@ -0,0 +1,58 @@ +package com.ruoyi.framework.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor; + +/** + * 通用配置 + * + * @author ruoyi + */ +@Configuration +public class ResourcesConfig implements WebMvcConfigurer +{ + /** + * 首页地址 + */ + @Value("${shiro.user.indexUrl}") + private String indexUrl; + + @Autowired + private RepeatSubmitInterceptor repeatSubmitInterceptor; + + /** + * 默认首页的设置,当输入域名是可以自动跳转到默认指定的网页 + */ + @Override + public void addViewControllers(ViewControllerRegistry registry) + { + registry.addViewController("/").setViewName("forward:" + indexUrl); + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) + { + /** 本地文件上传路径 */ + registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**").addResourceLocations("file:" + RuoYiConfig.getProfile() + "/"); + + /** swagger配置 */ + registry.addResourceHandler("/swagger-ui/**").addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/"); + } + + /** + * 自定义拦截规则 + */ + @Override + public void addInterceptors(InterceptorRegistry registry) + { + registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**"); + } +} \ No newline at end of file diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ShiroConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ShiroConfig.java new file mode 100644 index 0000000..f307fc5 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ShiroConfig.java @@ -0,0 +1,416 @@ +package com.ruoyi.framework.config; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.LinkedHashMap; +import java.util.Map; +import javax.servlet.Filter; +import org.apache.commons.io.IOUtils; +import org.apache.shiro.cache.ehcache.EhCacheManager; +import org.apache.shiro.codec.Base64; +import org.apache.shiro.config.ConfigurationException; +import org.apache.shiro.io.ResourceUtils; +import org.apache.shiro.mgt.SecurityManager; +import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; +import org.apache.shiro.spring.web.ShiroFilterFactoryBean; +import org.apache.shiro.web.mgt.CookieRememberMeManager; +import org.apache.shiro.web.mgt.DefaultWebSecurityManager; +import org.apache.shiro.web.servlet.SimpleCookie; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.security.CipherUtils; +import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.framework.shiro.realm.UserRealm; +import com.ruoyi.framework.shiro.session.OnlineSessionDAO; +import com.ruoyi.framework.shiro.session.OnlineSessionFactory; +import com.ruoyi.framework.shiro.web.CustomShiroFilterFactoryBean; +import com.ruoyi.framework.shiro.web.filter.LogoutFilter; +import com.ruoyi.framework.shiro.web.filter.captcha.CaptchaValidateFilter; +import com.ruoyi.framework.shiro.web.filter.kickout.KickoutSessionFilter; +import com.ruoyi.framework.shiro.web.filter.online.OnlineSessionFilter; +import com.ruoyi.framework.shiro.web.filter.sync.SyncOnlineSessionFilter; +import com.ruoyi.framework.shiro.web.session.OnlineWebSessionManager; +import com.ruoyi.framework.shiro.web.session.SpringSessionValidationScheduler; +import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; + +/** + * 权限配置加载 + * + * @author ruoyi + */ +@Configuration +public class ShiroConfig +{ + /** + * Session超时时间,单位为毫秒(默认30分钟) + */ + @Value("${shiro.session.expireTime}") + private int expireTime; + + /** + * 相隔多久检查一次session的有效性,单位毫秒,默认就是10分钟 + */ + @Value("${shiro.session.validationInterval}") + private int validationInterval; + + /** + * 同一个用户最大会话数 + */ + @Value("${shiro.session.maxSession}") + private int maxSession; + + /** + * 踢出之前登录的/之后登录的用户,默认踢出之前登录的用户 + */ + @Value("${shiro.session.kickoutAfter}") + private boolean kickoutAfter; + + /** + * 验证码开关 + */ + @Value("${shiro.user.captchaEnabled}") + private boolean captchaEnabled; + + /** + * 验证码类型 + */ + @Value("${shiro.user.captchaType}") + private String captchaType; + + /** + * 设置Cookie的域名 + */ + @Value("${shiro.cookie.domain}") + private String domain; + + /** + * 设置cookie的有效访问路径 + */ + @Value("${shiro.cookie.path}") + private String path; + + /** + * 设置HttpOnly属性 + */ + @Value("${shiro.cookie.httpOnly}") + private boolean httpOnly; + + /** + * 设置Cookie的过期时间,秒为单位 + */ + @Value("${shiro.cookie.maxAge}") + private int maxAge; + + /** + * 设置cipherKey密钥 + */ + @Value("${shiro.cookie.cipherKey}") + private String cipherKey; + + /** + * 登录地址 + */ + @Value("${shiro.user.loginUrl}") + private String loginUrl; + + /** + * 权限认证失败地址 + */ + @Value("${shiro.user.unauthorizedUrl}") + private String unauthorizedUrl; + + /** + * 是否开启记住我功能 + */ + @Value("${shiro.rememberMe.enabled: false}") + private boolean rememberMe; + + /** + * 缓存管理器 使用Ehcache实现 + */ + @Bean + public EhCacheManager getEhCacheManager() + { + net.sf.ehcache.CacheManager cacheManager = net.sf.ehcache.CacheManager.getCacheManager("ruoyi"); + EhCacheManager em = new EhCacheManager(); + if (StringUtils.isNull(cacheManager)) + { + em.setCacheManager(new net.sf.ehcache.CacheManager(getCacheManagerConfigFileInputStream())); + return em; + } + else + { + em.setCacheManager(cacheManager); + return em; + } + } + + /** + * 返回配置文件流 避免ehcache配置文件一直被占用,无法完全销毁项目重新部署 + */ + protected InputStream getCacheManagerConfigFileInputStream() + { + String configFile = "classpath:ehcache/ehcache-shiro.xml"; + InputStream inputStream = null; + try + { + inputStream = ResourceUtils.getInputStreamForPath(configFile); + byte[] b = IOUtils.toByteArray(inputStream); + InputStream in = new ByteArrayInputStream(b); + return in; + } + catch (IOException e) + { + throw new ConfigurationException( + "Unable to obtain input stream for cacheManagerConfigFile [" + configFile + "]", e); + } + finally + { + IOUtils.closeQuietly(inputStream); + } + } + + /** + * 自定义Realm + */ + @Bean + public UserRealm userRealm(EhCacheManager cacheManager) + { + UserRealm userRealm = new UserRealm(); + userRealm.setAuthorizationCacheName(Constants.SYS_AUTH_CACHE); + userRealm.setCacheManager(cacheManager); + return userRealm; + } + + /** + * 自定义sessionDAO会话 + */ + @Bean + public OnlineSessionDAO sessionDAO() + { + OnlineSessionDAO sessionDAO = new OnlineSessionDAO(); + return sessionDAO; + } + + /** + * 自定义sessionFactory会话 + */ + @Bean + public OnlineSessionFactory sessionFactory() + { + OnlineSessionFactory sessionFactory = new OnlineSessionFactory(); + return sessionFactory; + } + + /** + * 会话管理器 + */ + @Bean + public OnlineWebSessionManager sessionManager() + { + OnlineWebSessionManager manager = new OnlineWebSessionManager(); + // 加入缓存管理器 + manager.setCacheManager(getEhCacheManager()); + // 删除过期的session + manager.setDeleteInvalidSessions(true); + // 设置全局session超时时间 + manager.setGlobalSessionTimeout(expireTime * 60 * 1000); + // 去掉 JSESSIONID + manager.setSessionIdUrlRewritingEnabled(false); + // 定义要使用的无效的Session定时调度器 + manager.setSessionValidationScheduler(SpringUtils.getBean(SpringSessionValidationScheduler.class)); + // 是否定时检查session + manager.setSessionValidationSchedulerEnabled(true); + // 自定义SessionDao + manager.setSessionDAO(sessionDAO()); + // 自定义sessionFactory + manager.setSessionFactory(sessionFactory()); + return manager; + } + + /** + * 安全管理器 + */ + @Bean + public SecurityManager securityManager(UserRealm userRealm) + { + DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); + // 设置realm. + securityManager.setRealm(userRealm); + // 记住我 + securityManager.setRememberMeManager(rememberMe ? rememberMeManager() : null); + // 注入缓存管理器; + securityManager.setCacheManager(getEhCacheManager()); + // session管理器 + securityManager.setSessionManager(sessionManager()); + return securityManager; + } + + /** + * 退出过滤器 + */ + public LogoutFilter logoutFilter() + { + LogoutFilter logoutFilter = new LogoutFilter(); + logoutFilter.setLoginUrl(loginUrl); + return logoutFilter; + } + + /** + * Shiro过滤器配置 + */ + @Bean + public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) + { + CustomShiroFilterFactoryBean shiroFilterFactoryBean = new CustomShiroFilterFactoryBean(); + // Shiro的核心安全接口,这个属性是必须的 + shiroFilterFactoryBean.setSecurityManager(securityManager); + // 身份认证失败,则跳转到登录页面的配置 + shiroFilterFactoryBean.setLoginUrl(loginUrl); + // 权限认证失败,则跳转到指定页面 + shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl); + // Shiro连接约束配置,即过滤链的定义 + LinkedHashMap filterChainDefinitionMap = new LinkedHashMap<>(); + // 对静态资源设置匿名访问 + filterChainDefinitionMap.put("/favicon.ico**", "anon"); + filterChainDefinitionMap.put("/ruoyi.png**", "anon"); + filterChainDefinitionMap.put("/html/**", "anon"); + filterChainDefinitionMap.put("/css/**", "anon"); + filterChainDefinitionMap.put("/docs/**", "anon"); + filterChainDefinitionMap.put("/fonts/**", "anon"); + filterChainDefinitionMap.put("/img/**", "anon"); + filterChainDefinitionMap.put("/ajax/**", "anon"); + filterChainDefinitionMap.put("/js/**", "anon"); + filterChainDefinitionMap.put("/ruoyi/**", "anon"); + filterChainDefinitionMap.put("/captcha/captchaImage**", "anon"); + // 退出 logout地址,shiro去清除session + filterChainDefinitionMap.put("/logout", "logout"); + // 不需要拦截的访问 + filterChainDefinitionMap.put("/login", "anon,captchaValidate"); + // 注册相关 + filterChainDefinitionMap.put("/register", "anon,captchaValidate"); + // 系统权限列表 + // filterChainDefinitionMap.putAll(SpringUtils.getBean(IMenuService.class).selectPermsAll()); + + Map filters = new LinkedHashMap(); + filters.put("onlineSession", onlineSessionFilter()); + filters.put("syncOnlineSession", syncOnlineSessionFilter()); + filters.put("captchaValidate", captchaValidateFilter()); + filters.put("kickout", kickoutSessionFilter()); + // 注销成功,则跳转到指定页面 + filters.put("logout", logoutFilter()); + shiroFilterFactoryBean.setFilters(filters); + + // 所有请求需要认证 + filterChainDefinitionMap.put("/**", "user,kickout,onlineSession,syncOnlineSession"); + shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); + + return shiroFilterFactoryBean; + } + + /** + * 自定义在线用户处理过滤器 + */ + public OnlineSessionFilter onlineSessionFilter() + { + OnlineSessionFilter onlineSessionFilter = new OnlineSessionFilter(); + onlineSessionFilter.setLoginUrl(loginUrl); + onlineSessionFilter.setOnlineSessionDAO(sessionDAO()); + return onlineSessionFilter; + } + + /** + * 自定义在线用户同步过滤器 + */ + public SyncOnlineSessionFilter syncOnlineSessionFilter() + { + SyncOnlineSessionFilter syncOnlineSessionFilter = new SyncOnlineSessionFilter(); + syncOnlineSessionFilter.setOnlineSessionDAO(sessionDAO()); + return syncOnlineSessionFilter; + } + + /** + * 自定义验证码过滤器 + */ + public CaptchaValidateFilter captchaValidateFilter() + { + CaptchaValidateFilter captchaValidateFilter = new CaptchaValidateFilter(); + captchaValidateFilter.setCaptchaEnabled(captchaEnabled); + captchaValidateFilter.setCaptchaType(captchaType); + return captchaValidateFilter; + } + + /** + * cookie 属性设置 + */ + public SimpleCookie rememberMeCookie() + { + SimpleCookie cookie = new SimpleCookie("rememberMe"); + cookie.setDomain(domain); + cookie.setPath(path); + cookie.setHttpOnly(httpOnly); + cookie.setMaxAge(maxAge * 24 * 60 * 60); + return cookie; + } + + /** + * 记住我 + */ + public CookieRememberMeManager rememberMeManager() + { + CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager(); + cookieRememberMeManager.setCookie(rememberMeCookie()); + if (StringUtils.isNotEmpty(cipherKey)) + { + cookieRememberMeManager.setCipherKey(Base64.decode(cipherKey)); + } + else + { + cookieRememberMeManager.setCipherKey(CipherUtils.generateNewKey(128, "AES").getEncoded()); + } + return cookieRememberMeManager; + } + + /** + * 同一个用户多设备登录限制 + */ + public KickoutSessionFilter kickoutSessionFilter() + { + KickoutSessionFilter kickoutSessionFilter = new KickoutSessionFilter(); + kickoutSessionFilter.setCacheManager(getEhCacheManager()); + kickoutSessionFilter.setSessionManager(sessionManager()); + // 同一个用户最大的会话数,默认-1无限制;比如2的意思是同一个用户允许最多同时两个人登录 + kickoutSessionFilter.setMaxSession(maxSession); + // 是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;踢出顺序 + kickoutSessionFilter.setKickoutAfter(kickoutAfter); + // 被踢出后重定向到的地址; + kickoutSessionFilter.setKickoutUrl("/login?kickout=1"); + return kickoutSessionFilter; + } + + /** + * thymeleaf模板引擎和shiro框架的整合 + */ + @Bean + public ShiroDialect shiroDialect() + { + return new ShiroDialect(); + } + + /** + * 开启Shiro注解通知器 + */ + @Bean + public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor( + @Qualifier("securityManager") SecurityManager securityManager) + { + AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); + authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); + return authorizationAttributeSourceAdvisor; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/DruidProperties.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/DruidProperties.java new file mode 100644 index 0000000..c8a5c8a --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/DruidProperties.java @@ -0,0 +1,89 @@ +package com.ruoyi.framework.config.properties; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import com.alibaba.druid.pool.DruidDataSource; + +/** + * druid 配置属性 + * + * @author ruoyi + */ +@Configuration +public class DruidProperties +{ + @Value("${spring.datasource.druid.initialSize}") + private int initialSize; + + @Value("${spring.datasource.druid.minIdle}") + private int minIdle; + + @Value("${spring.datasource.druid.maxActive}") + private int maxActive; + + @Value("${spring.datasource.druid.maxWait}") + private int maxWait; + + @Value("${spring.datasource.druid.connectTimeout}") + private int connectTimeout; + + @Value("${spring.datasource.druid.socketTimeout}") + private int socketTimeout; + + @Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}") + private int timeBetweenEvictionRunsMillis; + + @Value("${spring.datasource.druid.minEvictableIdleTimeMillis}") + private int minEvictableIdleTimeMillis; + + @Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}") + private int maxEvictableIdleTimeMillis; + + @Value("${spring.datasource.druid.validationQuery}") + private String validationQuery; + + @Value("${spring.datasource.druid.testWhileIdle}") + private boolean testWhileIdle; + + @Value("${spring.datasource.druid.testOnBorrow}") + private boolean testOnBorrow; + + @Value("${spring.datasource.druid.testOnReturn}") + private boolean testOnReturn; + + public DruidDataSource dataSource(DruidDataSource datasource) + { + /** 配置初始化大小、最小、最大 */ + datasource.setInitialSize(initialSize); + datasource.setMaxActive(maxActive); + datasource.setMinIdle(minIdle); + + /** 配置获取连接等待超时的时间 */ + datasource.setMaxWait(maxWait); + + /** 配置驱动连接超时时间,检测数据库建立连接的超时时间,单位是毫秒 */ + datasource.setConnectTimeout(connectTimeout); + + /** 配置网络超时时间,等待数据库操作完成的网络超时时间,单位是毫秒 */ + datasource.setSocketTimeout(socketTimeout); + + /** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */ + datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); + + /** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */ + datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); + datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis); + + /** + * 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。 + */ + datasource.setValidationQuery(validationQuery); + /** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */ + datasource.setTestWhileIdle(testWhileIdle); + /** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */ + datasource.setTestOnBorrow(testOnBorrow); + /** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */ + datasource.setTestOnReturn(testOnReturn); + return datasource; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSource.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSource.java new file mode 100644 index 0000000..94b3d0d --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSource.java @@ -0,0 +1,27 @@ +package com.ruoyi.framework.datasource; + +import java.util.Map; +import javax.sql.DataSource; +import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; +import com.ruoyi.common.config.datasource.DynamicDataSourceContextHolder; + +/** + * 动态数据源 + * + * @author ruoyi + */ +public class DynamicDataSource extends AbstractRoutingDataSource +{ + public DynamicDataSource(DataSource defaultTargetDataSource, Map targetDataSources) + { + super.setDefaultTargetDataSource(defaultTargetDataSource); + super.setTargetDataSources(targetDataSources); + super.afterPropertiesSet(); + } + + @Override + protected Object determineCurrentLookupKey() + { + return DynamicDataSourceContextHolder.getDataSourceType(); + } +} \ No newline at end of file diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.java new file mode 100644 index 0000000..6df2e5e --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.java @@ -0,0 +1,55 @@ +package com.ruoyi.framework.interceptor; + +import java.lang.reflect.Method; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; +import com.ruoyi.common.json.JSON; +import com.ruoyi.common.annotation.RepeatSubmit; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.utils.ServletUtils; + +/** + * 防止重复提交拦截器 + * + * @author ruoyi + */ +@Component +public abstract class RepeatSubmitInterceptor implements HandlerInterceptor +{ + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception + { + if (handler instanceof HandlerMethod) + { + HandlerMethod handlerMethod = (HandlerMethod) handler; + Method method = handlerMethod.getMethod(); + RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class); + if (annotation != null) + { + if (this.isRepeatSubmit(request, annotation)) + { + AjaxResult ajaxResult = AjaxResult.error(annotation.message()); + ServletUtils.renderString(response, JSON.marshal(ajaxResult)); + return false; + } + } + return true; + } + else + { + return true; + } + } + + /** + * 验证是否重复提交由子类实现具体的防重复提交的规则 + * + * @param request 请求对象 + * @param annotation 防复注解 + * @return 结果 + */ + public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) throws Exception; +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java new file mode 100644 index 0000000..fc0e1e9 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java @@ -0,0 +1,83 @@ +package com.ruoyi.framework.interceptor.impl; + +import java.util.HashMap; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import org.springframework.stereotype.Component; +import com.ruoyi.common.annotation.RepeatSubmit; +import com.ruoyi.common.json.JSON; +import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor; + +/** + * 判断请求url和数据是否和上一次相同, + * 如果和上次相同,则是重复提交表单。 有效时间为10秒内。 + * + * @author ruoyi + */ +@Component +public class SameUrlDataInterceptor extends RepeatSubmitInterceptor +{ + public final String REPEAT_PARAMS = "repeatParams"; + + public final String REPEAT_TIME = "repeatTime"; + + public final String SESSION_REPEAT_KEY = "repeatData"; + + @SuppressWarnings("unchecked") + @Override + public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) throws Exception + { + // 本次参数及系统时间 + String nowParams = JSON.marshal(request.getParameterMap()); + Map nowDataMap = new HashMap(); + nowDataMap.put(REPEAT_PARAMS, nowParams); + nowDataMap.put(REPEAT_TIME, System.currentTimeMillis()); + + // 请求地址(作为存放session的key值) + String url = request.getRequestURI(); + + HttpSession session = request.getSession(); + Object sessionObj = session.getAttribute(SESSION_REPEAT_KEY); + if (sessionObj != null) + { + Map sessionMap = (Map) sessionObj; + if (sessionMap.containsKey(url)) + { + Map preDataMap = (Map) sessionMap.get(url); + if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap, annotation.interval())) + { + return true; + } + } + } + Map sessionMap = new HashMap(); + sessionMap.put(url, nowDataMap); + session.setAttribute(SESSION_REPEAT_KEY, sessionMap); + return false; + } + + /** + * 判断参数是否相同 + */ + private boolean compareParams(Map nowMap, Map preMap) + { + String nowParams = (String) nowMap.get(REPEAT_PARAMS); + String preParams = (String) preMap.get(REPEAT_PARAMS); + return nowParams.equals(preParams); + } + + /** + * 判断两次间隔时间 + */ + private boolean compareTime(Map nowMap, Map preMap, int interval) + { + long time1 = (Long) nowMap.get(REPEAT_TIME); + long time2 = (Long) preMap.get(REPEAT_TIME); + if ((time1 - time2) < interval) + { + return true; + } + return false; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/AsyncManager.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/AsyncManager.java new file mode 100644 index 0000000..8f78300 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/AsyncManager.java @@ -0,0 +1,55 @@ +package com.ruoyi.framework.manager; + +import java.util.TimerTask; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import com.ruoyi.common.utils.Threads; +import com.ruoyi.common.utils.spring.SpringUtils; + +/** + * 异步任务管理器 + * + * @author liuhulu + */ +public class AsyncManager +{ + /** + * 操作延迟10毫秒 + */ + private final int OPERATE_DELAY_TIME = 10; + + /** + * 异步操作任务调度线程池 + */ + private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService"); + + /** + * 单例模式 + */ + private AsyncManager(){} + + private static AsyncManager me = new AsyncManager(); + + public static AsyncManager me() + { + return me; + } + + /** + * 执行任务 + * + * @param task 任务 + */ + public void execute(TimerTask task) + { + executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS); + } + + /** + * 停止任务线程池 + */ + public void shutdown() + { + Threads.shutdownAndAwaitTermination(executor); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/ShutdownManager.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/ShutdownManager.java new file mode 100644 index 0000000..78a4af3 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/ShutdownManager.java @@ -0,0 +1,87 @@ +package com.ruoyi.framework.manager; + +import com.ruoyi.framework.shiro.web.session.SpringSessionValidationScheduler; +import net.sf.ehcache.CacheManager; +import org.apache.shiro.cache.ehcache.EhCacheManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import javax.annotation.PreDestroy; + +/** + * 确保应用退出时能关闭后台线程 + * + * @author cj + */ +@Component +public class ShutdownManager +{ + private static final Logger logger = LoggerFactory.getLogger("sys-user"); + + @Autowired(required = false) + private SpringSessionValidationScheduler springSessionValidationScheduler; + + @Autowired(required = false) + private EhCacheManager ehCacheManager; + + @PreDestroy + public void destroy() + { + shutdownSpringSessionValidationScheduler(); + shutdownAsyncManager(); + shutdownEhCacheManager(); + } + + /** + * 停止Seesion会话检查 + */ + private void shutdownSpringSessionValidationScheduler() + { + if (springSessionValidationScheduler != null && springSessionValidationScheduler.isEnabled()) + { + try + { + logger.info("====关闭会话验证任务===="); + springSessionValidationScheduler.disableSessionValidation(); + } + catch (Exception e) + { + logger.error(e.getMessage(), e); + } + } + } + + /** + * 停止异步执行任务 + */ + private void shutdownAsyncManager() + { + try + { + logger.info("====关闭后台任务任务线程池===="); + AsyncManager.me().shutdown(); + } + catch (Exception e) + { + logger.error(e.getMessage(), e); + } + } + + private void shutdownEhCacheManager() + { + try + { + logger.info("====关闭缓存===="); + if (ehCacheManager != null) + { + CacheManager cacheManager = ehCacheManager.getCacheManager(); + cacheManager.shutdown(); + } + } + catch (Exception e) + { + logger.error(e.getMessage(), e); + } + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java new file mode 100644 index 0000000..befa9ca --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java @@ -0,0 +1,136 @@ +package com.ruoyi.framework.manager.factory; + +import java.util.TimerTask; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.utils.AddressUtils; +import com.ruoyi.common.utils.LogUtils; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.ShiroUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.framework.shiro.session.OnlineSession; +import com.ruoyi.system.domain.SysLogininfor; +import com.ruoyi.system.domain.SysOperLog; +import com.ruoyi.system.domain.SysUserOnline; +import com.ruoyi.system.service.ISysOperLogService; +import com.ruoyi.system.service.ISysUserOnlineService; +import com.ruoyi.system.service.impl.SysLogininforServiceImpl; +import eu.bitwalker.useragentutils.UserAgent; + +/** + * 异步工厂(产生任务用) + * + * @author liuhulu + * + */ +public class AsyncFactory +{ + private static final Logger sys_user_logger = LoggerFactory.getLogger("sys-user"); + + /** + * 同步session到数据库 + * + * @param session 在线用户会话 + * @return 任务task + */ + public static TimerTask syncSessionToDb(final OnlineSession session) + { + return new TimerTask() + { + @Override + public void run() + { + SysUserOnline online = new SysUserOnline(); + online.setSessionId(String.valueOf(session.getId())); + online.setDeptName(session.getDeptName()); + online.setLoginName(session.getLoginName()); + online.setStartTimestamp(session.getStartTimestamp()); + online.setLastAccessTime(session.getLastAccessTime()); + online.setExpireTime(session.getTimeout()); + online.setIpaddr(session.getHost()); + online.setLoginLocation(AddressUtils.getRealAddressByIP(session.getHost())); + online.setBrowser(session.getBrowser()); + online.setOs(session.getOs()); + online.setStatus(session.getStatus()); + SpringUtils.getBean(ISysUserOnlineService.class).saveOnline(online); + + } + }; + } + + /** + * 操作日志记录 + * + * @param operLog 操作日志信息 + * @return 任务task + */ + public static TimerTask recordOper(final SysOperLog operLog) + { + return new TimerTask() + { + @Override + public void run() + { + // 远程查询操作地点 + operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp())); + SpringUtils.getBean(ISysOperLogService.class).insertOperlog(operLog); + } + }; + } + + /** + * 记录登录信息 + * + * @param username 用户名 + * @param status 状态 + * @param message 消息 + * @param args 列表 + * @return 任务task + */ + public static TimerTask recordLogininfor(final String username, final String status, final String message, final Object... args) + { + final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent")); + final String ip = ShiroUtils.getIp(); + return new TimerTask() + { + @Override + public void run() + { + String address = AddressUtils.getRealAddressByIP(ip); + StringBuilder s = new StringBuilder(); + s.append(LogUtils.getBlock(ip)); + s.append(address); + s.append(LogUtils.getBlock(username)); + s.append(LogUtils.getBlock(status)); + s.append(LogUtils.getBlock(message)); + // 打印信息到日志 + sys_user_logger.info(s.toString(), args); + // 获取客户端操作系统 + String os = userAgent.getOperatingSystem().getName(); + // 获取客户端浏览器 + String browser = userAgent.getBrowser().getName(); + // 封装对象 + SysLogininfor logininfor = new SysLogininfor(); + logininfor.setLoginName(username); + logininfor.setIpaddr(ip); + logininfor.setLoginLocation(address); + logininfor.setBrowser(browser); + logininfor.setOs(os); + logininfor.setMsg(message); + // 日志状态 + if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER)) + { + logininfor.setStatus(Constants.SUCCESS); + } + else if (Constants.LOGIN_FAIL.equals(status)) + { + logininfor.setStatus(Constants.FAIL); + } + // 插入数据 + SpringUtils.getBean(SysLogininforServiceImpl.class).insertLogininfor(logininfor); + } + }; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/realm/UserRealm.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/realm/UserRealm.java new file mode 100644 index 0000000..e77fafc --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/realm/UserRealm.java @@ -0,0 +1,158 @@ +package com.ruoyi.framework.shiro.realm; + +import java.util.HashSet; +import java.util.Set; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.ExcessiveAttemptsException; +import org.apache.shiro.authc.IncorrectCredentialsException; +import org.apache.shiro.authc.LockedAccountException; +import org.apache.shiro.authc.SimpleAuthenticationInfo; +import org.apache.shiro.authc.UnknownAccountException; +import org.apache.shiro.authc.UsernamePasswordToken; +import org.apache.shiro.authz.AuthorizationInfo; +import org.apache.shiro.authz.SimpleAuthorizationInfo; +import org.apache.shiro.cache.Cache; +import org.apache.shiro.realm.AuthorizingRealm; +import org.apache.shiro.subject.PrincipalCollection; +import org.apache.shiro.subject.SimplePrincipalCollection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.exception.user.CaptchaException; +import com.ruoyi.common.exception.user.RoleBlockedException; +import com.ruoyi.common.exception.user.UserBlockedException; +import com.ruoyi.common.exception.user.UserNotExistsException; +import com.ruoyi.common.exception.user.UserPasswordNotMatchException; +import com.ruoyi.common.exception.user.UserPasswordRetryLimitExceedException; +import com.ruoyi.common.utils.ShiroUtils; +import com.ruoyi.framework.shiro.service.SysLoginService; +import com.ruoyi.system.service.ISysMenuService; +import com.ruoyi.system.service.ISysRoleService; + +/** + * 自定义Realm 处理登录 权限 + * + * @author ruoyi + */ +public class UserRealm extends AuthorizingRealm +{ + private static final Logger log = LoggerFactory.getLogger(UserRealm.class); + + @Autowired + private ISysMenuService menuService; + + @Autowired + private ISysRoleService roleService; + + @Autowired + private SysLoginService loginService; + + /** + * 授权 + */ + @Override + protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) + { + SysUser user = ShiroUtils.getSysUser(); + // 角色列表 + Set roles = new HashSet(); + // 功能列表 + Set menus = new HashSet(); + SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); + // 管理员拥有所有权限 + if (user.isAdmin()) + { + info.addRole("admin"); + info.addStringPermission("*:*:*"); + } + else + { + roles = roleService.selectRoleKeys(user.getUserId()); + menus = menuService.selectPermsByUserId(user.getUserId()); + // 角色加入AuthorizationInfo认证对象 + info.setRoles(roles); + // 权限加入AuthorizationInfo认证对象 + info.setStringPermissions(menus); + } + return info; + } + + /** + * 登录认证 + */ + @Override + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException + { + UsernamePasswordToken upToken = (UsernamePasswordToken) token; + String username = upToken.getUsername(); + String password = ""; + if (upToken.getPassword() != null) + { + password = new String(upToken.getPassword()); + } + + SysUser user = null; + try + { + user = loginService.login(username, password); + } + catch (CaptchaException e) + { + throw new AuthenticationException(e.getMessage(), e); + } + catch (UserNotExistsException e) + { + throw new UnknownAccountException(e.getMessage(), e); + } + catch (UserPasswordNotMatchException e) + { + throw new IncorrectCredentialsException(e.getMessage(), e); + } + catch (UserPasswordRetryLimitExceedException e) + { + throw new ExcessiveAttemptsException(e.getMessage(), e); + } + catch (UserBlockedException e) + { + throw new LockedAccountException(e.getMessage(), e); + } + catch (RoleBlockedException e) + { + throw new LockedAccountException(e.getMessage(), e); + } + catch (Exception e) + { + log.info("对用户[" + username + "]进行登录验证..验证未通过{}", e.getMessage()); + throw new AuthenticationException(e.getMessage(), e); + } + SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName()); + return info; + } + + /** + * 清理指定用户授权信息缓存 + */ + public void clearCachedAuthorizationInfo(Object principal) + { + SimplePrincipalCollection principals = new SimplePrincipalCollection(principal, getName()); + this.clearCachedAuthorizationInfo(principals); + } + + /** + * 清理所有用户授权信息缓存 + */ + public void clearAllCachedAuthorizationInfo() + { + Cache cache = getAuthorizationCache(); + if (cache != null) + { + for (Object key : cache.keys()) + { + cache.remove(key); + } + } + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysLoginService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysLoginService.java new file mode 100644 index 0000000..c36f700 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysLoginService.java @@ -0,0 +1,185 @@ +package com.ruoyi.framework.shiro.service; + +import java.util.List; +import java.util.Set; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.constant.ShiroConstants; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.enums.UserStatus; +import com.ruoyi.common.exception.user.BlackListException; +import com.ruoyi.common.exception.user.CaptchaException; +import com.ruoyi.common.exception.user.UserBlockedException; +import com.ruoyi.common.exception.user.UserDeleteException; +import com.ruoyi.common.exception.user.UserNotExistsException; +import com.ruoyi.common.exception.user.UserPasswordNotMatchException; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.IpUtils; +import com.ruoyi.common.utils.MessageUtils; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.ShiroUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.manager.AsyncManager; +import com.ruoyi.framework.manager.factory.AsyncFactory; +import com.ruoyi.system.service.ISysConfigService; +import com.ruoyi.system.service.ISysMenuService; +import com.ruoyi.system.service.ISysUserService; + +/** + * 登录校验方法 + * + * @author ruoyi + */ +@Component +public class SysLoginService +{ + @Autowired + private SysPasswordService passwordService; + + @Autowired + private ISysUserService userService; + + @Autowired + private ISysMenuService menuService; + + @Autowired + private ISysConfigService configService; + + /** + * 登录 + */ + public SysUser login(String username, String password) + { + // 验证码校验 + if (ShiroConstants.CAPTCHA_ERROR.equals(ServletUtils.getRequest().getAttribute(ShiroConstants.CURRENT_CAPTCHA))) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"))); + throw new CaptchaException(); + } + // 用户名或密码为空 错误 + if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("not.null"))); + throw new UserNotExistsException(); + } + // 密码如果不在指定范围内 错误 + if (password.length() < UserConstants.PASSWORD_MIN_LENGTH + || password.length() > UserConstants.PASSWORD_MAX_LENGTH) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); + throw new UserPasswordNotMatchException(); + } + + // 用户名不在指定范围内 错误 + if (username.length() < UserConstants.USERNAME_MIN_LENGTH + || username.length() > UserConstants.USERNAME_MAX_LENGTH) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); + throw new UserPasswordNotMatchException(); + } + + // IP黑名单校验 + String blackStr = configService.selectConfigByKey("sys.login.blackIPList"); + if (IpUtils.isMatchedIp(blackStr, ShiroUtils.getIp())) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("login.blocked"))); + throw new BlackListException(); + } + + // 查询用户信息 + SysUser user = userService.selectUserByLoginName(username); + + /** + if (user == null && maybeMobilePhoneNumber(username)) + { + user = userService.selectUserByPhoneNumber(username); + } + + if (user == null && maybeEmail(username)) + { + user = userService.selectUserByEmail(username); + } + */ + + if (user == null) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.not.exists"))); + throw new UserNotExistsException(); + } + + if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.delete"))); + throw new UserDeleteException(); + } + + if (UserStatus.DISABLE.getCode().equals(user.getStatus())) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.blocked", user.getRemark()))); + throw new UserBlockedException(); + } + + passwordService.validate(user, password); + + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"))); + setRolePermission(user); + recordLoginInfo(user.getUserId()); + return user; + } + + /** + private boolean maybeEmail(String username) + { + if (!username.matches(UserConstants.EMAIL_PATTERN)) + { + return false; + } + return true; + } + + private boolean maybeMobilePhoneNumber(String username) + { + if (!username.matches(UserConstants.MOBILE_PHONE_NUMBER_PATTERN)) + { + return false; + } + return true; + } + */ + + /** + * 设置角色权限 + * + * @param user 用户信息 + */ + public void setRolePermission(SysUser user) + { + List roles = user.getRoles(); + if (!roles.isEmpty() && roles.size() > 1) + { + // 多角色设置permissions属性,以便数据权限匹配权限 + for (SysRole role : roles) + { + Set rolePerms = menuService.selectPermsByRoleId(role.getRoleId()); + role.setPermissions(rolePerms); + } + } + } + + /** + * 记录登录信息 + * + * @param userId 用户ID + */ + public void recordLoginInfo(Long userId) + { + SysUser user = new SysUser(); + user.setUserId(userId); + user.setLoginIp(ShiroUtils.getIp()); + user.setLoginDate(DateUtils.getNowDate()); + userService.updateUserInfo(user); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysPasswordService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysPasswordService.java new file mode 100644 index 0000000..0aea63f --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysPasswordService.java @@ -0,0 +1,85 @@ +package com.ruoyi.framework.shiro.service; + +import java.util.concurrent.atomic.AtomicInteger; +import javax.annotation.PostConstruct; +import org.apache.shiro.cache.Cache; +import org.apache.shiro.cache.CacheManager; +import org.apache.shiro.crypto.hash.Md5Hash; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.constant.ShiroConstants; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.exception.user.UserPasswordNotMatchException; +import com.ruoyi.common.exception.user.UserPasswordRetryLimitExceedException; +import com.ruoyi.common.utils.MessageUtils; +import com.ruoyi.framework.manager.AsyncManager; +import com.ruoyi.framework.manager.factory.AsyncFactory; + +/** + * 登录密码方法 + * + * @author ruoyi + */ +@Component +public class SysPasswordService +{ + @Autowired + private CacheManager cacheManager; + + private Cache loginRecordCache; + + @Value(value = "${user.password.maxRetryCount}") + private String maxRetryCount; + + @PostConstruct + public void init() + { + loginRecordCache = cacheManager.getCache(ShiroConstants.LOGINRECORDCACHE); + } + + public void validate(SysUser user, String password) + { + String loginName = user.getLoginName(); + + AtomicInteger retryCount = loginRecordCache.get(loginName); + + if (retryCount == null) + { + retryCount = new AtomicInteger(0); + loginRecordCache.put(loginName, retryCount); + } + if (retryCount.incrementAndGet() > Integer.valueOf(maxRetryCount).intValue()) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginName, Constants.LOGIN_FAIL, MessageUtils.message("user.password.retry.limit.exceed", maxRetryCount))); + throw new UserPasswordRetryLimitExceedException(Integer.valueOf(maxRetryCount).intValue()); + } + + if (!matches(user, password)) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginName, Constants.LOGIN_FAIL, MessageUtils.message("user.password.retry.limit.count", retryCount))); + loginRecordCache.put(loginName, retryCount); + throw new UserPasswordNotMatchException(); + } + else + { + clearLoginRecordCache(loginName); + } + } + + public boolean matches(SysUser user, String newPassword) + { + return user.getPassword().equals(encryptPassword(user.getLoginName(), newPassword, user.getSalt())); + } + + public void clearLoginRecordCache(String loginName) + { + loginRecordCache.remove(loginName); + } + + public String encryptPassword(String loginName, String password, String salt) + { + return new Md5Hash(loginName + password + salt).toHex(); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysRegisterService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysRegisterService.java new file mode 100644 index 0000000..690fbf2 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysRegisterService.java @@ -0,0 +1,83 @@ +package com.ruoyi.framework.shiro.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.constant.ShiroConstants; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.MessageUtils; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.ShiroUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.manager.AsyncManager; +import com.ruoyi.framework.manager.factory.AsyncFactory; +import com.ruoyi.system.service.ISysUserService; + +/** + * 注册校验方法 + * + * @author ruoyi + */ +@Component +public class SysRegisterService +{ + @Autowired + private ISysUserService userService; + + @Autowired + private SysPasswordService passwordService; + + /** + * 注册 + */ + public String register(SysUser user) + { + String msg = "", loginName = user.getLoginName(), password = user.getPassword(); + + if (ShiroConstants.CAPTCHA_ERROR.equals(ServletUtils.getRequest().getAttribute(ShiroConstants.CURRENT_CAPTCHA))) + { + msg = "验证码错误"; + } + else if (StringUtils.isEmpty(loginName)) + { + msg = "用户名不能为空"; + } + else if (StringUtils.isEmpty(password)) + { + msg = "用户密码不能为空"; + } + else if (password.length() < UserConstants.PASSWORD_MIN_LENGTH + || password.length() > UserConstants.PASSWORD_MAX_LENGTH) + { + msg = "密码长度必须在5到20个字符之间"; + } + else if (loginName.length() < UserConstants.USERNAME_MIN_LENGTH + || loginName.length() > UserConstants.USERNAME_MAX_LENGTH) + { + msg = "账户长度必须在2到20个字符之间"; + } + else if (!userService.checkLoginNameUnique(user)) + { + msg = "保存用户'" + loginName + "'失败,注册账号已存在"; + } + else + { + user.setPwdUpdateDate(DateUtils.getNowDate()); + user.setUserName(loginName); + user.setSalt(ShiroUtils.randomSalt()); + user.setPassword(passwordService.encryptPassword(loginName, password, user.getSalt())); + boolean regFlag = userService.registerUser(user); + if (!regFlag) + { + msg = "注册失败,请联系系统管理人员"; + } + else + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginName, Constants.REGISTER, MessageUtils.message("user.register.success"))); + } + } + return msg; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysShiroService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysShiroService.java new file mode 100644 index 0000000..1fb9c7e --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysShiroService.java @@ -0,0 +1,62 @@ +package com.ruoyi.framework.shiro.service; + +import java.io.Serializable; +import org.apache.shiro.session.Session; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.shiro.session.OnlineSession; +import com.ruoyi.system.domain.SysUserOnline; +import com.ruoyi.system.service.ISysUserOnlineService; + +/** + * 会话db操作处理 + * + * @author ruoyi + */ +@Component +public class SysShiroService +{ + @Autowired + private ISysUserOnlineService onlineService; + + /** + * 删除会话 + * + * @param onlineSession 会话信息 + */ + public void deleteSession(OnlineSession onlineSession) + { + onlineService.deleteOnlineById(String.valueOf(onlineSession.getId())); + } + + /** + * 获取会话信息 + * + * @param sessionId + * @return + */ + public Session getSession(Serializable sessionId) + { + SysUserOnline userOnline = onlineService.selectOnlineById(String.valueOf(sessionId)); + return StringUtils.isNull(userOnline) ? null : createSession(userOnline); + } + + public Session createSession(SysUserOnline userOnline) + { + OnlineSession onlineSession = new OnlineSession(); + if (StringUtils.isNotNull(userOnline)) + { + onlineSession.setId(userOnline.getSessionId()); + onlineSession.setHost(userOnline.getIpaddr()); + onlineSession.setBrowser(userOnline.getBrowser()); + onlineSession.setOs(userOnline.getOs()); + onlineSession.setDeptName(userOnline.getDeptName()); + onlineSession.setLoginName(userOnline.getLoginName()); + onlineSession.setStartTimestamp(userOnline.getStartTimestamp()); + onlineSession.setLastAccessTime(userOnline.getLastAccessTime()); + onlineSession.setTimeout(userOnline.getExpireTime()); + } + return onlineSession; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSession.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSession.java new file mode 100644 index 0000000..f9c417c --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSession.java @@ -0,0 +1,165 @@ +package com.ruoyi.framework.shiro.session; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import org.apache.shiro.session.mgt.SimpleSession; +import com.ruoyi.common.enums.OnlineStatus; + +/** + * 在线用户会话属性 + * + * @author ruoyi + */ +public class OnlineSession extends SimpleSession +{ + private static final long serialVersionUID = 1L; + + /** 用户ID */ + private Long userId; + + /** 用户名称 */ + private String loginName; + + /** 部门名称 */ + private String deptName; + + /** 用户头像 */ + private String avatar; + + /** 登录IP地址 */ + private String host; + + /** 浏览器类型 */ + private String browser; + + /** 操作系统 */ + private String os; + + /** 在线状态 */ + private OnlineStatus status = OnlineStatus.on_line; + + /** 属性是否改变 优化session数据同步 */ + private transient boolean attributeChanged = false; + + @Override + public String getHost() + { + return host; + } + + @Override + public void setHost(String host) + { + this.host = host; + } + + public String getBrowser() + { + return browser; + } + + public void setBrowser(String browser) + { + this.browser = browser; + } + + public String getOs() + { + return os; + } + + public void setOs(String os) + { + this.os = os; + } + + public Long getUserId() + { + return userId; + } + + public void setUserId(Long userId) + { + this.userId = userId; + } + + public String getLoginName() + { + return loginName; + } + + public void setLoginName(String loginName) + { + this.loginName = loginName; + } + + public String getDeptName() + { + return deptName; + } + + public void setDeptName(String deptName) + { + this.deptName = deptName; + } + + public OnlineStatus getStatus() + { + return status; + } + + public void setStatus(OnlineStatus status) + { + this.status = status; + } + + public void markAttributeChanged() + { + this.attributeChanged = true; + } + + public void resetAttributeChanged() + { + this.attributeChanged = false; + } + + public boolean isAttributeChanged() + { + return attributeChanged; + } + + public String getAvatar() { + return avatar; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + @Override + public void setAttribute(Object key, Object value) + { + super.setAttribute(key, value); + } + + @Override + public Object removeAttribute(Object key) + { + return super.removeAttribute(key); + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("userId", getUserId()) + .append("loginName", getLoginName()) + .append("deptName", getDeptName()) + .append("avatar", getAvatar()) + .append("host", getHost()) + .append("browser", getBrowser()) + .append("os", getOs()) + .append("status", getStatus()) + .append("attributeChanged", isAttributeChanged()) + .toString(); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSessionDAO.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSessionDAO.java new file mode 100644 index 0000000..3ee1862 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSessionDAO.java @@ -0,0 +1,117 @@ +package com.ruoyi.framework.shiro.session; + +import java.io.Serializable; +import java.util.Date; +import org.apache.shiro.session.Session; +import org.apache.shiro.session.UnknownSessionException; +import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import com.ruoyi.common.enums.OnlineStatus; +import com.ruoyi.framework.manager.AsyncManager; +import com.ruoyi.framework.manager.factory.AsyncFactory; +import com.ruoyi.framework.shiro.service.SysShiroService; + +/** + * 针对自定义的ShiroSession的db操作 + * + * @author ruoyi + */ +public class OnlineSessionDAO extends EnterpriseCacheSessionDAO +{ + /** + * 同步session到数据库的周期 单位为毫秒(默认1分钟) + */ + @Value("${shiro.session.dbSyncPeriod}") + private int dbSyncPeriod; + + /** + * 上次同步数据库的时间戳 + */ + private static final String LAST_SYNC_DB_TIMESTAMP = OnlineSessionDAO.class.getName() + "LAST_SYNC_DB_TIMESTAMP"; + + @Autowired + private SysShiroService sysShiroService; + + public OnlineSessionDAO() + { + super(); + } + + public OnlineSessionDAO(long expireTime) + { + super(); + } + + /** + * 根据会话ID获取会话 + * + * @param sessionId 会话ID + * @return ShiroSession + */ + @Override + protected Session doReadSession(Serializable sessionId) + { + return sysShiroService.getSession(sessionId); + } + + @Override + public void update(Session session) throws UnknownSessionException + { + super.update(session); + } + + /** + * 更新会话;如更新会话最后访问时间/停止会话/设置超时时间/设置移除属性等会调用 + */ + public void syncToDb(OnlineSession onlineSession) + { + Date lastSyncTimestamp = (Date) onlineSession.getAttribute(LAST_SYNC_DB_TIMESTAMP); + if (lastSyncTimestamp != null) + { + boolean needSync = true; + long deltaTime = onlineSession.getLastAccessTime().getTime() - lastSyncTimestamp.getTime(); + if (deltaTime < dbSyncPeriod * 60 * 1000) + { + // 时间差不足 无需同步 + needSync = false; + } + // isGuest = true 访客 + boolean isGuest = onlineSession.getUserId() == null || onlineSession.getUserId() == 0L; + + // session 数据变更了 同步 + if (!isGuest && onlineSession.isAttributeChanged()) + { + needSync = true; + } + + if (!needSync) + { + return; + } + } + // 更新上次同步数据库时间 + onlineSession.setAttribute(LAST_SYNC_DB_TIMESTAMP, onlineSession.getLastAccessTime()); + // 更新完后 重置标识 + if (onlineSession.isAttributeChanged()) + { + onlineSession.resetAttributeChanged(); + } + AsyncManager.me().execute(AsyncFactory.syncSessionToDb(onlineSession)); + } + + /** + * 当会话过期/停止(如用户退出时)属性等会调用 + */ + @Override + protected void doDelete(Session session) + { + OnlineSession onlineSession = (OnlineSession) session; + if (null == onlineSession) + { + return; + } + onlineSession.setStatus(OnlineStatus.off_line); + sysShiroService.deleteSession(onlineSession); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSessionFactory.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSessionFactory.java new file mode 100644 index 0000000..24d9d75 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSessionFactory.java @@ -0,0 +1,42 @@ +package com.ruoyi.framework.shiro.session; + +import javax.servlet.http.HttpServletRequest; +import org.apache.shiro.session.Session; +import org.apache.shiro.session.mgt.SessionContext; +import org.apache.shiro.session.mgt.SessionFactory; +import org.apache.shiro.web.session.mgt.WebSessionContext; +import org.springframework.stereotype.Component; +import com.ruoyi.common.utils.IpUtils; +import eu.bitwalker.useragentutils.UserAgent; + +/** + * 自定义sessionFactory会话 + * + * @author ruoyi + */ +@Component +public class OnlineSessionFactory implements SessionFactory +{ + @Override + public Session createSession(SessionContext initData) + { + OnlineSession session = new OnlineSession(); + if (initData != null && initData instanceof WebSessionContext) + { + WebSessionContext sessionContext = (WebSessionContext) initData; + HttpServletRequest request = (HttpServletRequest) sessionContext.getServletRequest(); + if (request != null) + { + UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent")); + // 获取客户端操作系统 + String os = userAgent.getOperatingSystem().getName(); + // 获取客户端浏览器 + String browser = userAgent.getBrowser().getName(); + session.setHost(IpUtils.getIpAddr(request)); + session.setBrowser(browser); + session.setOs(os); + } + } + return session; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/util/AuthorizationUtils.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/util/AuthorizationUtils.java new file mode 100644 index 0000000..7ea7e6f --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/util/AuthorizationUtils.java @@ -0,0 +1,30 @@ +package com.ruoyi.framework.shiro.util; + +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.mgt.RealmSecurityManager; +import com.ruoyi.framework.shiro.realm.UserRealm; + +/** + * 用户授权信息 + * + * @author ruoyi + */ +public class AuthorizationUtils +{ + /** + * 清理所有用户授权信息缓存 + */ + public static void clearAllCachedAuthorizationInfo() + { + getUserRealm().clearAllCachedAuthorizationInfo(); + } + + /** + * 获取自定义Realm + */ + public static UserRealm getUserRealm() + { + RealmSecurityManager rsm = (RealmSecurityManager) SecurityUtils.getSecurityManager(); + return (UserRealm) rsm.getRealms().iterator().next(); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/CustomShiroFilterFactoryBean.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/CustomShiroFilterFactoryBean.java new file mode 100644 index 0000000..a859fa1 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/CustomShiroFilterFactoryBean.java @@ -0,0 +1,85 @@ +package com.ruoyi.framework.shiro.web; + +import org.apache.shiro.spring.web.ShiroFilterFactoryBean; +import org.apache.shiro.web.filter.InvalidRequestFilter; +import org.apache.shiro.web.filter.mgt.DefaultFilter; +import org.apache.shiro.web.filter.mgt.FilterChainManager; +import org.apache.shiro.web.filter.mgt.FilterChainResolver; +import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver; +import org.apache.shiro.web.mgt.WebSecurityManager; +import org.apache.shiro.web.servlet.AbstractShiroFilter; +import org.apache.shiro.mgt.SecurityManager; +import org.springframework.beans.factory.BeanInitializationException; +import javax.servlet.Filter; +import java.util.Map; + +/** + * 自定义ShiroFilterFactoryBean解决资源中文路径问题 + * + * @author ruoyi + */ +public class CustomShiroFilterFactoryBean extends ShiroFilterFactoryBean +{ + @Override + public Class getObjectType() + { + return MySpringShiroFilter.class; + } + + @Override + protected AbstractShiroFilter createInstance() throws Exception + { + + SecurityManager securityManager = getSecurityManager(); + if (securityManager == null) + { + String msg = "SecurityManager property must be set."; + throw new BeanInitializationException(msg); + } + + if (!(securityManager instanceof WebSecurityManager)) + { + String msg = "The security manager does not implement the WebSecurityManager interface."; + throw new BeanInitializationException(msg); + } + + FilterChainManager manager = createFilterChainManager(); + // Expose the constructed FilterChainManager by first wrapping it in a + // FilterChainResolver implementation. The AbstractShiroFilter implementations + // do not know about FilterChainManagers - only resolvers: + PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver(); + chainResolver.setFilterChainManager(manager); + + Map filterMap = manager.getFilters(); + Filter invalidRequestFilter = filterMap.get(DefaultFilter.invalidRequest.name()); + if (invalidRequestFilter instanceof InvalidRequestFilter) + { + // 此处是关键,设置false跳过URL携带中文400,servletPath中文校验bug + ((InvalidRequestFilter) invalidRequestFilter).setBlockNonAscii(false); + } + // Now create a concrete ShiroFilter instance and apply the acquired SecurityManager and built + // FilterChainResolver. It doesn't matter that the instance is an anonymous inner class + // here - we're just using it because it is a concrete AbstractShiroFilter instance that accepts + // injection of the SecurityManager and FilterChainResolver: + return new MySpringShiroFilter((WebSecurityManager) securityManager, chainResolver); + } + + private static final class MySpringShiroFilter extends AbstractShiroFilter + { + protected MySpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) + { + if (webSecurityManager == null) + { + throw new IllegalArgumentException("WebSecurityManager property cannot be null."); + } + else + { + this.setSecurityManager(webSecurityManager); + if (resolver != null) + { + this.setFilterChainResolver(resolver); + } + } + } + } +} \ No newline at end of file diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/LogoutFilter.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/LogoutFilter.java new file mode 100644 index 0000000..c6dbfa5 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/LogoutFilter.java @@ -0,0 +1,90 @@ +package com.ruoyi.framework.shiro.web.filter; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import org.apache.shiro.session.SessionException; +import org.apache.shiro.subject.Subject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.utils.MessageUtils; +import com.ruoyi.common.utils.ShiroUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.framework.manager.AsyncManager; +import com.ruoyi.framework.manager.factory.AsyncFactory; +import com.ruoyi.system.service.ISysUserOnlineService; + +/** + * 退出过滤器 + * + * @author ruoyi + */ +public class LogoutFilter extends org.apache.shiro.web.filter.authc.LogoutFilter +{ + private static final Logger log = LoggerFactory.getLogger(LogoutFilter.class); + + /** + * 退出后重定向的地址 + */ + private String loginUrl; + + public String getLoginUrl() + { + return loginUrl; + } + + public void setLoginUrl(String loginUrl) + { + this.loginUrl = loginUrl; + } + + @Override + protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception + { + try + { + Subject subject = getSubject(request, response); + String redirectUrl = getRedirectUrl(request, response, subject); + try + { + SysUser user = ShiroUtils.getSysUser(); + if (StringUtils.isNotNull(user)) + { + String loginName = user.getLoginName(); + // 记录用户退出日志 + AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginName, Constants.LOGOUT, MessageUtils.message("user.logout.success"))); + // 清理缓存 + SpringUtils.getBean(ISysUserOnlineService.class).removeUserCache(loginName, ShiroUtils.getSessionId()); + } + // 退出登录 + subject.logout(); + } + catch (SessionException ise) + { + log.error("logout fail.", ise); + } + issueRedirect(request, response, redirectUrl); + } + catch (Exception e) + { + log.error("Encountered session exception during logout. This can generally safely be ignored.", e); + } + return false; + } + + /** + * 退出跳转URL + */ + @Override + protected String getRedirectUrl(ServletRequest request, ServletResponse response, Subject subject) + { + String url = getLoginUrl(); + if (StringUtils.isNotEmpty(url)) + { + return url; + } + return super.getRedirectUrl(request, response, subject); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/captcha/CaptchaValidateFilter.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/captcha/CaptchaValidateFilter.java new file mode 100644 index 0000000..22dd339 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/captcha/CaptchaValidateFilter.java @@ -0,0 +1,79 @@ +package com.ruoyi.framework.shiro.web.filter.captcha; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import org.apache.shiro.web.filter.AccessControlFilter; +import com.google.code.kaptcha.Constants; +import com.ruoyi.common.constant.ShiroConstants; +import com.ruoyi.common.utils.ShiroUtils; +import com.ruoyi.common.utils.StringUtils; + +/** + * 验证码过滤器 + * + * @author ruoyi + */ +public class CaptchaValidateFilter extends AccessControlFilter +{ + /** + * 是否开启验证码 + */ + private boolean captchaEnabled = true; + + /** + * 验证码类型 + */ + private String captchaType = "math"; + + public void setCaptchaEnabled(boolean captchaEnabled) + { + this.captchaEnabled = captchaEnabled; + } + + public void setCaptchaType(String captchaType) + { + this.captchaType = captchaType; + } + + @Override + public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception + { + request.setAttribute(ShiroConstants.CURRENT_ENABLED, captchaEnabled); + request.setAttribute(ShiroConstants.CURRENT_TYPE, captchaType); + return super.onPreHandle(request, response, mappedValue); + } + + @Override + protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) + throws Exception + { + HttpServletRequest httpServletRequest = (HttpServletRequest) request; + // 验证码禁用 或不是表单提交 允许访问 + if (captchaEnabled == false || !"post".equals(httpServletRequest.getMethod().toLowerCase())) + { + return true; + } + return validateResponse(httpServletRequest, httpServletRequest.getParameter(ShiroConstants.CURRENT_VALIDATECODE)); + } + + public boolean validateResponse(HttpServletRequest request, String validateCode) + { + Object obj = ShiroUtils.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY); + String code = String.valueOf(obj != null ? obj : ""); + // 验证码清除,防止多次使用。 + request.getSession().removeAttribute(Constants.KAPTCHA_SESSION_KEY); + if (StringUtils.isEmpty(validateCode) || !validateCode.equalsIgnoreCase(code)) + { + return false; + } + return true; + } + + @Override + protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception + { + request.setAttribute(ShiroConstants.CURRENT_CAPTCHA, ShiroConstants.CAPTCHA_ERROR); + return true; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/kickout/KickoutSessionFilter.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/kickout/KickoutSessionFilter.java new file mode 100644 index 0000000..d92db34 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/kickout/KickoutSessionFilter.java @@ -0,0 +1,176 @@ +package com.ruoyi.framework.shiro.web.filter.kickout; + +import java.io.IOException; +import java.io.Serializable; +import java.util.ArrayDeque; +import java.util.Deque; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.shiro.cache.Cache; +import org.apache.shiro.cache.CacheManager; +import org.apache.shiro.session.Session; +import org.apache.shiro.session.mgt.DefaultSessionKey; +import org.apache.shiro.session.mgt.SessionManager; +import org.apache.shiro.subject.Subject; +import org.apache.shiro.web.filter.AccessControlFilter; +import org.apache.shiro.web.util.WebUtils; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ruoyi.common.constant.ShiroConstants; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.ShiroUtils; + +/** + * 登录帐号控制过滤器 + * + * @author ruoyi + */ +public class KickoutSessionFilter extends AccessControlFilter +{ + private final static ObjectMapper objectMapper = new ObjectMapper(); + + /** + * 同一个用户最大会话数 + **/ + private int maxSession = -1; + + /** + * 踢出之前登录的/之后登录的用户 默认false踢出之前登录的用户 + **/ + private Boolean kickoutAfter = false; + + /** + * 踢出后到的地址 + **/ + private String kickoutUrl; + + private SessionManager sessionManager; + private Cache> cache; + + @Override + protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) + throws Exception + { + return false; + } + + @Override + protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception + { + Subject subject = getSubject(request, response); + if (!subject.isAuthenticated() && !subject.isRemembered() || maxSession == -1) + { + // 如果没有登录或用户最大会话数为-1,直接进行之后的流程 + return true; + } + try + { + Session session = subject.getSession(); + // 当前登录用户 + SysUser user = ShiroUtils.getSysUser(); + String loginName = user.getLoginName(); + Serializable sessionId = session.getId(); + + // 读取缓存用户 没有就存入 + Deque deque = cache.get(loginName); + if (deque == null) + { + // 初始化队列 + deque = new ArrayDeque(); + } + + // 如果队列里没有此sessionId,且用户没有被踢出;放入队列 + if (!deque.contains(sessionId) && session.getAttribute("kickout") == null) + { + // 将sessionId存入队列 + deque.push(sessionId); + // 将用户的sessionId队列缓存 + cache.put(loginName, deque); + } + + // 如果队列里的sessionId数超出最大会话数,开始踢人 + while (deque.size() > maxSession) + { + // 是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户; + Serializable kickoutSessionId = kickoutAfter ? deque.removeFirst() : deque.removeLast(); + // 踢出后再更新下缓存队列 + cache.put(loginName, deque); + + try + { + // 获取被踢出的sessionId的session对象 + Session kickoutSession = sessionManager.getSession(new DefaultSessionKey(kickoutSessionId)); + if (null != kickoutSession) + { + // 设置会话的kickout属性表示踢出了 + kickoutSession.setAttribute("kickout", true); + } + } + catch (Exception e) + { + // 面对异常,我们选择忽略 + } + } + + // 如果被踢出了,(前者或后者)直接退出,重定向到踢出后的地址 + if (session.getAttribute("kickout") != null && (Boolean) session.getAttribute("kickout") == true) + { + // 退出登录 + subject.logout(); + saveRequest(request); + return isAjaxResponse(request, response); + } + return true; + } + catch (Exception e) + { + return isAjaxResponse(request, response); + } + } + + private boolean isAjaxResponse(ServletRequest request, ServletResponse response) throws IOException + { + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse res = (HttpServletResponse) response; + if (ServletUtils.isAjaxRequest(req)) + { + AjaxResult ajaxResult = AjaxResult.error("您已在别处登录,请您修改密码或重新登录"); + ServletUtils.renderString(res, objectMapper.writeValueAsString(ajaxResult)); + } + else + { + WebUtils.issueRedirect(request, response, kickoutUrl); + } + return false; + } + + public void setMaxSession(int maxSession) + { + this.maxSession = maxSession; + } + + public void setKickoutAfter(boolean kickoutAfter) + { + this.kickoutAfter = kickoutAfter; + } + + public void setKickoutUrl(String kickoutUrl) + { + this.kickoutUrl = kickoutUrl; + } + + public void setSessionManager(SessionManager sessionManager) + { + this.sessionManager = sessionManager; + } + + // 设置Cache的key的前缀 + public void setCacheManager(CacheManager cacheManager) + { + // 必须和ehcache缓存配置中的缓存name一致 + this.cache = cacheManager.getCache(ShiroConstants.SYS_USERCACHE); + } +} \ No newline at end of file diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/online/OnlineSessionFilter.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/online/OnlineSessionFilter.java new file mode 100644 index 0000000..adf5120 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/online/OnlineSessionFilter.java @@ -0,0 +1,99 @@ +package com.ruoyi.framework.shiro.web.filter.online; + +import java.io.IOException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import org.apache.shiro.session.Session; +import org.apache.shiro.subject.Subject; +import org.apache.shiro.web.filter.AccessControlFilter; +import org.apache.shiro.web.util.WebUtils; +import org.springframework.beans.factory.annotation.Value; +import com.ruoyi.common.constant.ShiroConstants; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.enums.OnlineStatus; +import com.ruoyi.common.utils.ShiroUtils; +import com.ruoyi.framework.shiro.session.OnlineSession; +import com.ruoyi.framework.shiro.session.OnlineSessionDAO; + +/** + * 自定义访问控制 + * + * @author ruoyi + */ +public class OnlineSessionFilter extends AccessControlFilter +{ + /** + * 强制退出后重定向的地址 + */ + @Value("${shiro.user.loginUrl}") + private String loginUrl; + + private OnlineSessionDAO onlineSessionDAO; + + /** + * 表示是否允许访问;mappedValue就是[urls]配置中拦截器参数部分,如果允许访问返回true,否则false; + */ + @Override + protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) + throws Exception + { + Subject subject = getSubject(request, response); + if (subject == null || subject.getSession() == null) + { + return true; + } + Session session = onlineSessionDAO.readSession(subject.getSession().getId()); + if (session != null && session instanceof OnlineSession) + { + OnlineSession onlineSession = (OnlineSession) session; + request.setAttribute(ShiroConstants.ONLINE_SESSION, onlineSession); + // 把user对象设置进去 + boolean isGuest = onlineSession.getUserId() == null || onlineSession.getUserId() == 0L; + if (isGuest == true) + { + SysUser user = ShiroUtils.getSysUser(); + if (user != null) + { + onlineSession.setUserId(user.getUserId()); + onlineSession.setLoginName(user.getLoginName()); + onlineSession.setAvatar(user.getAvatar()); + onlineSession.setDeptName(user.getDept().getDeptName()); + onlineSession.markAttributeChanged(); + } + } + + if (onlineSession.getStatus() == OnlineStatus.off_line) + { + return false; + } + } + return true; + } + + /** + * 表示当访问拒绝时是否已经处理了;如果返回true表示需要继续处理;如果返回false表示该拦截器实例已经处理了,将直接返回即可。 + */ + @Override + protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception + { + Subject subject = getSubject(request, response); + if (subject != null) + { + subject.logout(); + } + saveRequestAndRedirectToLogin(request, response); + return false; + } + + // 跳转到登录页 + @Override + protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException + { + WebUtils.issueRedirect(request, response, loginUrl); + } + + public void setOnlineSessionDAO(OnlineSessionDAO onlineSessionDAO) + { + this.onlineSessionDAO = onlineSessionDAO; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/sync/SyncOnlineSessionFilter.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/sync/SyncOnlineSessionFilter.java new file mode 100644 index 0000000..db83cbc --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/sync/SyncOnlineSessionFilter.java @@ -0,0 +1,39 @@ +package com.ruoyi.framework.shiro.web.filter.sync; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import org.apache.shiro.web.filter.PathMatchingFilter; +import com.ruoyi.common.constant.ShiroConstants; +import com.ruoyi.framework.shiro.session.OnlineSession; +import com.ruoyi.framework.shiro.session.OnlineSessionDAO; + +/** + * 同步Session数据到Db + * + * @author ruoyi + */ +public class SyncOnlineSessionFilter extends PathMatchingFilter +{ + private OnlineSessionDAO onlineSessionDAO; + + /** + * 同步会话数据到DB 一次请求最多同步一次 防止过多处理 需要放到Shiro过滤器之前 + */ + @Override + protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception + { + OnlineSession session = (OnlineSession) request.getAttribute(ShiroConstants.ONLINE_SESSION); + // 如果session stop了 也不同步 + // session停止时间,如果stopTimestamp不为null,则代表已停止 + if (session != null && session.getUserId() != null && session.getStopTimestamp() == null) + { + onlineSessionDAO.syncToDb(session); + } + return true; + } + + public void setOnlineSessionDAO(OnlineSessionDAO onlineSessionDAO) + { + this.onlineSessionDAO = onlineSessionDAO; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/session/OnlineWebSessionManager.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/session/OnlineWebSessionManager.java new file mode 100644 index 0000000..7ceebad --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/session/OnlineWebSessionManager.java @@ -0,0 +1,175 @@ +package com.ruoyi.framework.shiro.web.session; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import org.apache.commons.lang3.time.DateUtils; +import org.apache.shiro.session.ExpiredSessionException; +import org.apache.shiro.session.InvalidSessionException; +import org.apache.shiro.session.Session; +import org.apache.shiro.session.mgt.DefaultSessionKey; +import org.apache.shiro.session.mgt.SessionKey; +import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.ruoyi.common.constant.ShiroConstants; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.bean.BeanUtils; +import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.framework.shiro.session.OnlineSession; +import com.ruoyi.system.domain.SysUserOnline; +import com.ruoyi.system.service.ISysUserOnlineService; + +/** + * 主要是在此如果会话的属性修改了 就标识下其修改了 然后方便 OnlineSessionDao同步 + * + * @author ruoyi + */ +public class OnlineWebSessionManager extends DefaultWebSessionManager +{ + private static final Logger log = LoggerFactory.getLogger(OnlineWebSessionManager.class); + + @Override + public void setAttribute(SessionKey sessionKey, Object attributeKey, Object value) throws InvalidSessionException + { + super.setAttribute(sessionKey, attributeKey, value); + if (value != null && needMarkAttributeChanged(attributeKey)) + { + OnlineSession session = getOnlineSession(sessionKey); + session.markAttributeChanged(); + } + } + + private boolean needMarkAttributeChanged(Object attributeKey) + { + if (attributeKey == null) + { + return false; + } + String attributeKeyStr = attributeKey.toString(); + // 优化 flash属性没必要持久化 + if (attributeKeyStr.startsWith("org.springframework")) + { + return false; + } + if (attributeKeyStr.startsWith("javax.servlet")) + { + return false; + } + if (attributeKeyStr.equals(ShiroConstants.CURRENT_USERNAME)) + { + return false; + } + return true; + } + + @Override + public Object removeAttribute(SessionKey sessionKey, Object attributeKey) throws InvalidSessionException + { + Object removed = super.removeAttribute(sessionKey, attributeKey); + if (removed != null) + { + OnlineSession s = getOnlineSession(sessionKey); + s.markAttributeChanged(); + } + + return removed; + } + + public OnlineSession getOnlineSession(SessionKey sessionKey) + { + OnlineSession session = null; + Object obj = doGetSession(sessionKey); + if (StringUtils.isNotNull(obj)) + { + session = new OnlineSession(); + BeanUtils.copyBeanProp(session, obj); + } + return session; + } + + /** + * 验证session是否有效 用于删除过期session + */ + @Override + public void validateSessions() + { + if (log.isInfoEnabled()) + { + log.info("invalidation sessions..."); + } + + int invalidCount = 0; + + int timeout = (int) this.getGlobalSessionTimeout(); + if (timeout < 0) + { + // 永不过期不进行处理 + return; + } + Date expiredDate = DateUtils.addMilliseconds(new Date(), 0 - timeout); + ISysUserOnlineService userOnlineService = SpringUtils.getBean(ISysUserOnlineService.class); + List userOnlineList = userOnlineService.selectOnlineByExpired(expiredDate); + // 批量过期删除 + List needOfflineIdList = new ArrayList(); + for (SysUserOnline userOnline : userOnlineList) + { + try + { + SessionKey key = new DefaultSessionKey(userOnline.getSessionId()); + Session session = retrieveSession(key); + if (session != null) + { + throw new InvalidSessionException(); + } + } + catch (InvalidSessionException e) + { + if (log.isDebugEnabled()) + { + boolean expired = (e instanceof ExpiredSessionException); + String msg = "Invalidated session with id [" + userOnline.getSessionId() + "]" + + (expired ? " (expired)" : " (stopped)"); + log.debug(msg); + } + invalidCount++; + needOfflineIdList.add(userOnline.getSessionId()); + userOnlineService.removeUserCache(userOnline.getLoginName(), userOnline.getSessionId()); + } + + } + if (needOfflineIdList.size() > 0) + { + try + { + userOnlineService.batchDeleteOnline(needOfflineIdList); + } + catch (Exception e) + { + log.error("batch delete db session error.", e); + } + } + + if (log.isInfoEnabled()) + { + String msg = "Finished invalidation session."; + if (invalidCount > 0) + { + msg += " [" + invalidCount + "] sessions were stopped."; + } + else + { + msg += " No sessions were stopped."; + } + log.info(msg); + } + + } + + @Override + protected Collection getActiveSessions() + { + throw new UnsupportedOperationException("getActiveSessions method not supported"); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/session/SpringSessionValidationScheduler.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/session/SpringSessionValidationScheduler.java new file mode 100644 index 0000000..60174c0 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/session/SpringSessionValidationScheduler.java @@ -0,0 +1,131 @@ +package com.ruoyi.framework.shiro.web.session; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import org.apache.shiro.session.mgt.DefaultSessionManager; +import org.apache.shiro.session.mgt.SessionValidationScheduler; +import org.apache.shiro.session.mgt.ValidatingSessionManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; +import com.ruoyi.common.utils.Threads; + +/** + * 自定义任务调度器完成 + * + * @author ruoyi + */ +@Component +public class SpringSessionValidationScheduler implements SessionValidationScheduler +{ + private static final Logger log = LoggerFactory.getLogger(SpringSessionValidationScheduler.class); + + public static final long DEFAULT_SESSION_VALIDATION_INTERVAL = DefaultSessionManager.DEFAULT_SESSION_VALIDATION_INTERVAL; + + /** + * 定时器,用于处理超时的挂起请求,也用于连接断开时的重连。 + */ + @Autowired + @Qualifier("scheduledExecutorService") + private ScheduledExecutorService executorService; + + private volatile boolean enabled = false; + + /** + * 会话验证管理器 + */ + @Autowired + @Qualifier("sessionManager") + @Lazy + private ValidatingSessionManager sessionManager; + + // 相隔多久检查一次session的有效性,单位毫秒,默认就是10分钟 + @Value("${shiro.session.validationInterval}") + private long sessionValidationInterval; + + @Override + public boolean isEnabled() + { + return this.enabled; + } + + /** + * Specifies how frequently (in milliseconds) this Scheduler will call the + * {@link org.apache.shiro.session.mgt.ValidatingSessionManager#validateSessions() + * ValidatingSessionManager#validateSessions()} method. + * + *

    + * Unless this method is called, the default value is {@link #DEFAULT_SESSION_VALIDATION_INTERVAL}. + * + * @param sessionValidationInterval + */ + public void setSessionValidationInterval(long sessionValidationInterval) + { + this.sessionValidationInterval = sessionValidationInterval; + } + + /** + * Starts session validation by creating a spring PeriodicTrigger. + */ + @Override + public void enableSessionValidation() + { + + enabled = true; + + if (log.isDebugEnabled()) + { + log.debug("Scheduling session validation job using Spring Scheduler with " + + "session validation interval of [" + sessionValidationInterval + "]ms..."); + } + + try + { + executorService.scheduleAtFixedRate(new Runnable() + { + @Override + public void run() + { + if (enabled) + { + sessionManager.validateSessions(); + } + } + }, 1000, sessionValidationInterval * 60 * 1000, TimeUnit.MILLISECONDS); + + this.enabled = true; + + if (log.isDebugEnabled()) + { + log.debug("Session validation job successfully scheduled with Spring Scheduler."); + } + + } + catch (Exception e) + { + if (log.isErrorEnabled()) + { + log.error("Error starting the Spring Scheduler session validation job. Session validation may not occur.", e); + } + } + } + + @Override + public void disableSessionValidation() + { + if (log.isDebugEnabled()) + { + log.debug("Stopping Spring Scheduler session validation job..."); + } + + if (this.enabled) + { + Threads.shutdownAndAwaitTermination(executorService); + } + this.enabled = false; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/Server.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/Server.java new file mode 100644 index 0000000..6a549f8 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/Server.java @@ -0,0 +1,241 @@ +package com.ruoyi.framework.web.domain; + +import java.net.UnknownHostException; +import java.util.LinkedList; +import java.util.List; +import java.util.Properties; +import com.ruoyi.common.utils.Arith; +import com.ruoyi.common.utils.IpUtils; +import com.ruoyi.framework.web.domain.server.Cpu; +import com.ruoyi.framework.web.domain.server.Jvm; +import com.ruoyi.framework.web.domain.server.Mem; +import com.ruoyi.framework.web.domain.server.Sys; +import com.ruoyi.framework.web.domain.server.SysFile; +import oshi.SystemInfo; +import oshi.hardware.CentralProcessor; +import oshi.hardware.CentralProcessor.TickType; +import oshi.hardware.GlobalMemory; +import oshi.hardware.HardwareAbstractionLayer; +import oshi.software.os.FileSystem; +import oshi.software.os.OSFileStore; +import oshi.software.os.OperatingSystem; +import oshi.util.Util; + +/** + * 服务器相关信息 + * + * @author ruoyi + */ +public class Server +{ + + private static final int OSHI_WAIT_SECOND = 1000; + + /** + * CPU相关信息 + */ + private Cpu cpu = new Cpu(); + + /** + * 內存相关信息 + */ + private Mem mem = new Mem(); + + /** + * JVM相关信息 + */ + private Jvm jvm = new Jvm(); + + /** + * 服务器相关信息 + */ + private Sys sys = new Sys(); + + /** + * 磁盘相关信息 + */ + private List sysFiles = new LinkedList(); + + public Cpu getCpu() + { + return cpu; + } + + public void setCpu(Cpu cpu) + { + this.cpu = cpu; + } + + public Mem getMem() + { + return mem; + } + + public void setMem(Mem mem) + { + this.mem = mem; + } + + public Jvm getJvm() + { + return jvm; + } + + public void setJvm(Jvm jvm) + { + this.jvm = jvm; + } + + public Sys getSys() + { + return sys; + } + + public void setSys(Sys sys) + { + this.sys = sys; + } + + public List getSysFiles() + { + return sysFiles; + } + + public void setSysFiles(List sysFiles) + { + this.sysFiles = sysFiles; + } + + public void copyTo() throws Exception + { + SystemInfo si = new SystemInfo(); + HardwareAbstractionLayer hal = si.getHardware(); + + setCpuInfo(hal.getProcessor()); + + setMemInfo(hal.getMemory()); + + setSysInfo(); + + setJvmInfo(); + + setSysFiles(si.getOperatingSystem()); + } + + /** + * 设置CPU信息 + */ + private void setCpuInfo(CentralProcessor processor) + { + // CPU信息 + long[] prevTicks = processor.getSystemCpuLoadTicks(); + Util.sleep(OSHI_WAIT_SECOND); + long[] ticks = processor.getSystemCpuLoadTicks(); + long nice = ticks[TickType.NICE.getIndex()] - prevTicks[TickType.NICE.getIndex()]; + long irq = ticks[TickType.IRQ.getIndex()] - prevTicks[TickType.IRQ.getIndex()]; + long softirq = ticks[TickType.SOFTIRQ.getIndex()] - prevTicks[TickType.SOFTIRQ.getIndex()]; + long steal = ticks[TickType.STEAL.getIndex()] - prevTicks[TickType.STEAL.getIndex()]; + long cSys = ticks[TickType.SYSTEM.getIndex()] - prevTicks[TickType.SYSTEM.getIndex()]; + long user = ticks[TickType.USER.getIndex()] - prevTicks[TickType.USER.getIndex()]; + long iowait = ticks[TickType.IOWAIT.getIndex()] - prevTicks[TickType.IOWAIT.getIndex()]; + long idle = ticks[TickType.IDLE.getIndex()] - prevTicks[TickType.IDLE.getIndex()]; + long totalCpu = user + nice + cSys + idle + iowait + irq + softirq + steal; + cpu.setCpuNum(processor.getLogicalProcessorCount()); + cpu.setTotal(totalCpu); + cpu.setSys(cSys); + cpu.setUsed(user); + cpu.setWait(iowait); + cpu.setFree(idle); + } + + /** + * 设置内存信息 + */ + private void setMemInfo(GlobalMemory memory) + { + mem.setTotal(memory.getTotal()); + mem.setUsed(memory.getTotal() - memory.getAvailable()); + mem.setFree(memory.getAvailable()); + } + + /** + * 设置服务器信息 + */ + private void setSysInfo() + { + Properties props = System.getProperties(); + sys.setComputerName(IpUtils.getHostName()); + sys.setComputerIp(IpUtils.getHostIp()); + sys.setOsName(props.getProperty("os.name")); + sys.setOsArch(props.getProperty("os.arch")); + sys.setUserDir(props.getProperty("user.dir")); + } + + /** + * 设置Java虚拟机 + */ + private void setJvmInfo() throws UnknownHostException + { + Properties props = System.getProperties(); + jvm.setTotal(Runtime.getRuntime().totalMemory()); + jvm.setMax(Runtime.getRuntime().maxMemory()); + jvm.setFree(Runtime.getRuntime().freeMemory()); + jvm.setVersion(props.getProperty("java.version")); + jvm.setHome(props.getProperty("java.home")); + } + + /** + * 设置磁盘信息 + */ + private void setSysFiles(OperatingSystem os) + { + FileSystem fileSystem = os.getFileSystem(); + List fsArray = fileSystem.getFileStores(); + for (OSFileStore fs : fsArray) + { + long free = fs.getUsableSpace(); + long total = fs.getTotalSpace(); + long used = total - free; + SysFile sysFile = new SysFile(); + sysFile.setDirName(fs.getMount()); + sysFile.setSysTypeName(fs.getType()); + sysFile.setTypeName(fs.getName()); + sysFile.setTotal(convertFileSize(total)); + sysFile.setFree(convertFileSize(free)); + sysFile.setUsed(convertFileSize(used)); + sysFile.setUsage(Arith.mul(Arith.div(used, total, 4), 100)); + sysFiles.add(sysFile); + } + } + + /** + * 字节转换 + * + * @param size 字节大小 + * @return 转换后值 + */ + public String convertFileSize(long size) + { + long kb = 1024; + long mb = kb * 1024; + long gb = mb * 1024; + if (size >= gb) + { + return String.format("%.1f GB", (float) size / gb); + } + else if (size >= mb) + { + float f = (float) size / mb; + return String.format(f > 100 ? "%.0f MB" : "%.1f MB", f); + } + else if (size >= kb) + { + float f = (float) size / kb; + return String.format(f > 100 ? "%.0f KB" : "%.1f KB", f); + } + else + { + return String.format("%d B", size); + } + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Cpu.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Cpu.java new file mode 100644 index 0000000..a13a66c --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Cpu.java @@ -0,0 +1,101 @@ +package com.ruoyi.framework.web.domain.server; + +import com.ruoyi.common.utils.Arith; + +/** + * CPU相关信息 + * + * @author ruoyi + */ +public class Cpu +{ + /** + * 核心数 + */ + private int cpuNum; + + /** + * CPU总的使用率 + */ + private double total; + + /** + * CPU系统使用率 + */ + private double sys; + + /** + * CPU用户使用率 + */ + private double used; + + /** + * CPU当前等待率 + */ + private double wait; + + /** + * CPU当前空闲率 + */ + private double free; + + public int getCpuNum() + { + return cpuNum; + } + + public void setCpuNum(int cpuNum) + { + this.cpuNum = cpuNum; + } + + public double getTotal() + { + return Arith.round(Arith.mul(total, 100), 2); + } + + public void setTotal(double total) + { + this.total = total; + } + + public double getSys() + { + return Arith.round(Arith.mul(sys / total, 100), 2); + } + + public void setSys(double sys) + { + this.sys = sys; + } + + public double getUsed() + { + return Arith.round(Arith.mul(used / total, 100), 2); + } + + public void setUsed(double used) + { + this.used = used; + } + + public double getWait() + { + return Arith.round(Arith.mul(wait / total, 100), 2); + } + + public void setWait(double wait) + { + this.wait = wait; + } + + public double getFree() + { + return Arith.round(Arith.mul(free / total, 100), 2); + } + + public void setFree(double free) + { + this.free = free; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Jvm.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Jvm.java new file mode 100644 index 0000000..1fdc6ac --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Jvm.java @@ -0,0 +1,130 @@ +package com.ruoyi.framework.web.domain.server; + +import java.lang.management.ManagementFactory; +import com.ruoyi.common.utils.Arith; +import com.ruoyi.common.utils.DateUtils; + +/** + * JVM相关信息 + * + * @author ruoyi + */ +public class Jvm +{ + /** + * 当前JVM占用的内存总数(M) + */ + private double total; + + /** + * JVM最大可用内存总数(M) + */ + private double max; + + /** + * JVM空闲内存(M) + */ + private double free; + + /** + * JDK版本 + */ + private String version; + + /** + * JDK路径 + */ + private String home; + + public double getTotal() + { + return Arith.div(total, (1024 * 1024), 2); + } + + public void setTotal(double total) + { + this.total = total; + } + + public double getMax() + { + return Arith.div(max, (1024 * 1024), 2); + } + + public void setMax(double max) + { + this.max = max; + } + + public double getFree() + { + return Arith.div(free, (1024 * 1024), 2); + } + + public void setFree(double free) + { + this.free = free; + } + + public double getUsed() + { + return Arith.div(total - free, (1024 * 1024), 2); + } + + public double getUsage() + { + return Arith.mul(Arith.div(total - free, total, 4), 100); + } + + /** + * 获取JDK名称 + */ + public String getName() + { + return ManagementFactory.getRuntimeMXBean().getVmName(); + } + + public String getVersion() + { + return version; + } + + public void setVersion(String version) + { + this.version = version; + } + + public String getHome() + { + return home; + } + + public void setHome(String home) + { + this.home = home; + } + + /** + * JDK启动时间 + */ + public String getStartTime() + { + return DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, DateUtils.getServerStartDate()); + } + + /** + * JDK运行时间 + */ + public String getRunTime() + { + return DateUtils.timeDistance(DateUtils.getNowDate(), DateUtils.getServerStartDate()); + } + + /** + * 运行参数 + */ + public String getInputArgs() + { + return ManagementFactory.getRuntimeMXBean().getInputArguments().toString(); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Mem.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Mem.java new file mode 100644 index 0000000..13eec52 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Mem.java @@ -0,0 +1,61 @@ +package com.ruoyi.framework.web.domain.server; + +import com.ruoyi.common.utils.Arith; + +/** + * 內存相关信息 + * + * @author ruoyi + */ +public class Mem +{ + /** + * 内存总量 + */ + private double total; + + /** + * 已用内存 + */ + private double used; + + /** + * 剩余内存 + */ + private double free; + + public double getTotal() + { + return Arith.div(total, (1024 * 1024 * 1024), 2); + } + + public void setTotal(long total) + { + this.total = total; + } + + public double getUsed() + { + return Arith.div(used, (1024 * 1024 * 1024), 2); + } + + public void setUsed(long used) + { + this.used = used; + } + + public double getFree() + { + return Arith.div(free, (1024 * 1024 * 1024), 2); + } + + public void setFree(long free) + { + this.free = free; + } + + public double getUsage() + { + return Arith.mul(Arith.div(used, total, 4), 100); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Sys.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Sys.java new file mode 100644 index 0000000..45d64d9 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Sys.java @@ -0,0 +1,84 @@ +package com.ruoyi.framework.web.domain.server; + +/** + * 系统相关信息 + * + * @author ruoyi + */ +public class Sys +{ + /** + * 服务器名称 + */ + private String computerName; + + /** + * 服务器Ip + */ + private String computerIp; + + /** + * 项目路径 + */ + private String userDir; + + /** + * 操作系统 + */ + private String osName; + + /** + * 系统架构 + */ + private String osArch; + + public String getComputerName() + { + return computerName; + } + + public void setComputerName(String computerName) + { + this.computerName = computerName; + } + + public String getComputerIp() + { + return computerIp; + } + + public void setComputerIp(String computerIp) + { + this.computerIp = computerIp; + } + + public String getUserDir() + { + return userDir; + } + + public void setUserDir(String userDir) + { + this.userDir = userDir; + } + + public String getOsName() + { + return osName; + } + + public void setOsName(String osName) + { + this.osName = osName; + } + + public String getOsArch() + { + return osArch; + } + + public void setOsArch(String osArch) + { + this.osArch = osArch; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/SysFile.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/SysFile.java new file mode 100644 index 0000000..1320cde --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/SysFile.java @@ -0,0 +1,114 @@ +package com.ruoyi.framework.web.domain.server; + +/** + * 系统文件相关信息 + * + * @author ruoyi + */ +public class SysFile +{ + /** + * 盘符路径 + */ + private String dirName; + + /** + * 盘符类型 + */ + private String sysTypeName; + + /** + * 文件类型 + */ + private String typeName; + + /** + * 总大小 + */ + private String total; + + /** + * 剩余大小 + */ + private String free; + + /** + * 已经使用量 + */ + private String used; + + /** + * 资源的使用率 + */ + private double usage; + + public String getDirName() + { + return dirName; + } + + public void setDirName(String dirName) + { + this.dirName = dirName; + } + + public String getSysTypeName() + { + return sysTypeName; + } + + public void setSysTypeName(String sysTypeName) + { + this.sysTypeName = sysTypeName; + } + + public String getTypeName() + { + return typeName; + } + + public void setTypeName(String typeName) + { + this.typeName = typeName; + } + + public String getTotal() + { + return total; + } + + public void setTotal(String total) + { + this.total = total; + } + + public String getFree() + { + return free; + } + + public void setFree(String free) + { + this.free = free; + } + + public String getUsed() + { + return used; + } + + public void setUsed(String used) + { + this.used = used; + } + + public double getUsage() + { + return usage; + } + + public void setUsage(double usage) + { + this.usage = usage; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..e315906 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java @@ -0,0 +1,116 @@ +package com.ruoyi.framework.web.exception; + +import javax.servlet.http.HttpServletRequest; +import org.apache.shiro.authz.AuthorizationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.validation.BindException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.ModelAndView; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.exception.DemoModeException; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.security.PermissionUtils; + +/** + * 全局异常处理器 + * + * @author ruoyi + */ +@RestControllerAdvice +public class GlobalExceptionHandler +{ + private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); + + /** + * 权限校验异常(ajax请求返回json,redirect请求跳转页面) + */ + @ExceptionHandler(AuthorizationException.class) + public Object handleAuthorizationException(AuthorizationException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage()); + if (ServletUtils.isAjaxRequest(request)) + { + return AjaxResult.error(PermissionUtils.getMsg(e.getMessage())); + } + else + { + return new ModelAndView("error/unauth"); + } + } + + /** + * 请求方式不支持 + */ + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, + HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod()); + return AjaxResult.error(e.getMessage()); + } + + /** + * 拦截未知的运行时异常 + */ + @ExceptionHandler(RuntimeException.class) + public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生未知异常.", requestURI, e); + return AjaxResult.error(e.getMessage()); + } + + /** + * 系统异常 + */ + @ExceptionHandler(Exception.class) + public AjaxResult handleException(Exception e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生系统异常.", requestURI, e); + return AjaxResult.error(e.getMessage()); + } + + /** + * 业务异常 + */ + @ExceptionHandler(ServiceException.class) + public Object handleServiceException(ServiceException e, HttpServletRequest request) + { + log.error(e.getMessage(), e); + if (ServletUtils.isAjaxRequest(request)) + { + return AjaxResult.error(e.getMessage()); + } + else + { + return new ModelAndView("error/service", "errorMessage", e.getMessage()); + } + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(BindException.class) + public AjaxResult handleBindException(BindException e) + { + log.error(e.getMessage(), e); + String message = e.getAllErrors().get(0).getDefaultMessage(); + return AjaxResult.error(message); + } + + /** + * 演示模式异常 + */ + @ExceptionHandler(DemoModeException.class) + public AjaxResult handleDemoModeException(DemoModeException e) + { + return AjaxResult.error("演示模式,不允许操作"); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/CacheService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/CacheService.java new file mode 100644 index 0000000..a8b037e --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/CacheService.java @@ -0,0 +1,83 @@ +package com.ruoyi.framework.web.service; + +import java.util.Set; +import org.apache.commons.lang3.ArrayUtils; +import org.springframework.stereotype.Service; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.utils.CacheUtils; + +/** + * 缓存操作处理 + * + * @author ruoyi + */ +@Service +public class CacheService +{ + /** + * 获取所有缓存名称 + * + * @return 缓存列表 + */ + public String[] getCacheNames() + { + String[] cacheNames = CacheUtils.getCacheNames(); + return ArrayUtils.removeElement(cacheNames, Constants.SYS_AUTH_CACHE); + } + + /** + * 根据缓存名称获取所有键名 + * + * @param cacheName 缓存名称 + * @return 键名列表 + */ + public Set getCacheKeys(String cacheName) + { + return CacheUtils.getCache(cacheName).keys(); + } + + /** + * 根据缓存名称和键名获取内容值 + * + * @param cacheName 缓存名称 + * @param cacheKey 键名 + * @return 键值 + */ + public Object getCacheValue(String cacheName, String cacheKey) + { + return CacheUtils.get(cacheName, cacheKey); + } + + /** + * 根据名称删除缓存信息 + * + * @param cacheName 缓存名称 + */ + public void clearCacheName(String cacheName) + { + CacheUtils.removeAll(cacheName); + } + + /** + * 根据名称和键名删除缓存信息 + * + * @param cacheName 缓存名称 + * @param cacheKey 键名 + */ + public void clearCacheKey(String cacheName, String cacheKey) + { + CacheUtils.remove(cacheName, cacheKey); + } + + /** + * 清理所有缓存 + */ + public void clearAll() + { + String[] cacheNames = getCacheNames(); + for (String cacheName : cacheNames) + { + CacheUtils.removeAll(cacheName); + } + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/ConfigService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/ConfigService.java new file mode 100644 index 0000000..2859588 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/ConfigService.java @@ -0,0 +1,28 @@ +package com.ruoyi.framework.web.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.system.service.ISysConfigService; + +/** + * RuoYi首创 html调用 thymeleaf 实现参数管理 + * + * @author ruoyi + */ +@Service("config") +public class ConfigService +{ + @Autowired + private ISysConfigService configService; + + /** + * 根据键名查询参数配置信息 + * + * @param configKey 参数键名 + * @return 参数键值 + */ + public String getKey(String configKey) + { + return configService.selectConfigByKey(configKey); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/DictService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/DictService.java new file mode 100644 index 0000000..f485811 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/DictService.java @@ -0,0 +1,46 @@ +package com.ruoyi.framework.web.service; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.common.core.domain.entity.SysDictData; +import com.ruoyi.system.service.ISysDictDataService; +import com.ruoyi.system.service.ISysDictTypeService; + +/** + * RuoYi首创 html调用 thymeleaf 实现字典读取 + * + * @author ruoyi + */ +@Service("dict") +public class DictService +{ + @Autowired + private ISysDictTypeService dictTypeService; + + @Autowired + private ISysDictDataService dictDataService; + + /** + * 根据字典类型查询字典数据信息 + * + * @param dictType 字典类型 + * @return 参数键值 + */ + public List getType(String dictType) + { + return dictTypeService.selectDictDataByType(dictType); + } + + /** + * 根据字典类型和字典键值查询字典数据信息 + * + * @param dictType 字典类型 + * @param dictValue 字典键值 + * @return 字典标签 + */ + public String getLabel(String dictType, String dictValue) + { + return dictDataService.selectDictLabel(dictType, dictValue); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/PermissionService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/PermissionService.java new file mode 100644 index 0000000..6441807 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/PermissionService.java @@ -0,0 +1,262 @@ +package com.ruoyi.framework.web.service; + +import java.beans.BeanInfo; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.subject.Subject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import com.ruoyi.common.utils.StringUtils; + +/** + * RuoYi首创 js调用 thymeleaf 实现按钮权限可见性 + * + * @author ruoyi + */ +@Service("permission") +public class PermissionService +{ + private static final Logger log = LoggerFactory.getLogger(PermissionService.class); + + /** 没有权限,hidden用于前端隐藏按钮 */ + public static final String NOACCESS = "hidden"; + + private static final String ROLE_DELIMETER = ","; + + private static final String PERMISSION_DELIMETER = ","; + + /** + * 验证用户是否具备某权限,无权限返回hidden用于前端隐藏(如需返回Boolean使用isPermitted) + * + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + public String hasPermi(String permission) + { + return isPermitted(permission) ? StringUtils.EMPTY : NOACCESS; + } + + /** + * 验证用户是否不具备某权限,与 hasPermi逻辑相反。无权限返回hidden用于前端隐藏(如需返回Boolean使用isLacksPermitted) + * + * @param permission 权限字符串 + * @return 用户是否不具备某权限 + */ + public String lacksPermi(String permission) + { + return isLacksPermitted(permission) ? StringUtils.EMPTY : NOACCESS; + } + + /** + * 验证用户是否具有以下任意一个权限,无权限返回hidden用于隐藏(如需返回Boolean使用hasAnyPermissions) + * + * @param permissions 以 PERMISSION_NAMES_DELIMETER 为分隔符的权限列表 + * @return 用户是否具有以下任意一个权限 + */ + public String hasAnyPermi(String permissions) + { + return hasAnyPermissions(permissions, PERMISSION_DELIMETER) ? StringUtils.EMPTY : NOACCESS; + } + + /** + * 验证用户是否具备某角色,无权限返回hidden用于隐藏(如需返回Boolean使用isRole) + * + * @param role 角色字符串 + * @return 用户是否具备某角色 + */ + public String hasRole(String role) + { + return isRole(role) ? StringUtils.EMPTY : NOACCESS; + } + + /** + * 验证用户是否不具备某角色,与hasRole逻辑相反。无权限返回hidden用于隐藏(如需返回Boolean使用isLacksRole) + * + * @param role 角色字符串 + * @return 用户是否不具备某角色 + */ + public String lacksRole(String role) + { + return isLacksRole(role) ? StringUtils.EMPTY : NOACCESS; + } + + /** + * 验证用户是否具有以下任意一个角色,无权限返回hidden用于隐藏(如需返回Boolean使用isAnyRoles) + * + * @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表 + * @return 用户是否具有以下任意一个角色 + */ + public String hasAnyRoles(String roles) + { + return isAnyRoles(roles, ROLE_DELIMETER) ? StringUtils.EMPTY : NOACCESS; + } + + /** + * 验证用户是否认证通过或已记住的用户。 + * + * @return 用户是否认证通过或已记住的用户 + */ + public boolean isUser() + { + Subject subject = SecurityUtils.getSubject(); + return subject != null && subject.getPrincipal() != null; + } + + /** + * 判断用户是否拥有某个权限 + * + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + public boolean isPermitted(String permission) + { + return SecurityUtils.getSubject().isPermitted(permission); + } + + /** + * 判断用户是否不具备某权限,与 isPermitted逻辑相反。 + * + * @param permission 权限名称 + * @return 用户是否不具备某权限 + */ + public boolean isLacksPermitted(String permission) + { + return isPermitted(permission) != true; + } + + /** + * 验证用户是否具有以下任意一个权限。 + * + * @param permissions 以 PERMISSION_NAMES_DELIMETER 为分隔符的权限列表 + * @return 用户是否具有以下任意一个权限 + */ + public boolean hasAnyPermissions(String permissions) + { + return hasAnyPermissions(permissions, PERMISSION_DELIMETER); + } + + /** + * 验证用户是否具有以下任意一个权限。 + * + * @param permissions 以 delimeter 为分隔符的权限列表 + * @param delimeter 权限列表分隔符 + * @return 用户是否具有以下任意一个权限 + */ + public boolean hasAnyPermissions(String permissions, String delimeter) + { + Subject subject = SecurityUtils.getSubject(); + + if (subject != null) + { + if (delimeter == null || delimeter.length() == 0) + { + delimeter = PERMISSION_DELIMETER; + } + + for (String permission : permissions.split(delimeter)) + { + if (permission != null && subject.isPermitted(permission.trim()) == true) + { + return true; + } + } + } + + return false; + } + + /** + * 判断用户是否拥有某个角色 + * + * @param role 角色字符串 + * @return 用户是否具备某角色 + */ + public boolean isRole(String role) + { + return SecurityUtils.getSubject().hasRole(role); + } + + /** + * 验证用户是否不具备某角色,与 isRole逻辑相反。 + * + * @param role 角色名称 + * @return 用户是否不具备某角色 + */ + public boolean isLacksRole(String role) + { + return isRole(role) != true; + } + + /** + * 验证用户是否具有以下任意一个角色。 + * + * @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表 + * @return 用户是否具有以下任意一个角色 + */ + public boolean isAnyRoles(String roles) + { + return isAnyRoles(roles, ROLE_DELIMETER); + } + + /** + * 验证用户是否具有以下任意一个角色。 + * + * @param roles 以 delimeter 为分隔符的角色列表 + * @param delimeter 角色列表分隔符 + * @return 用户是否具有以下任意一个角色 + */ + public boolean isAnyRoles(String roles, String delimeter) + { + Subject subject = SecurityUtils.getSubject(); + if (subject != null) + { + if (delimeter == null || delimeter.length() == 0) + { + delimeter = ROLE_DELIMETER; + } + + for (String role : roles.split(delimeter)) + { + if (subject.hasRole(role.trim()) == true) + { + return true; + } + } + } + + return false; + } + + /** + * 返回用户属性值 + * + * @param property 属性名称 + * @return 用户属性值 + */ + public Object getPrincipalProperty(String property) + { + Subject subject = SecurityUtils.getSubject(); + if (subject != null) + { + Object principal = subject.getPrincipal(); + try + { + BeanInfo bi = Introspector.getBeanInfo(principal.getClass()); + for (PropertyDescriptor pd : bi.getPropertyDescriptors()) + { + if (pd.getName().equals(property) == true) + { + return pd.getReadMethod().invoke(principal, (Object[]) null); + } + } + } + catch (Exception e) + { + log.error("Error reading property [{}] from principal of type [{}]", property, principal.getClass().getName()); + } + } + return null; + } +} diff --git a/ruoyi-generator/pom.xml b/ruoyi-generator/pom.xml new file mode 100644 index 0000000..5e319c4 --- /dev/null +++ b/ruoyi-generator/pom.xml @@ -0,0 +1,40 @@ + + + + ruoyi + com.ruoyi + 4.7.6 + + 4.0.0 + + ruoyi-generator + + + generator代码生成 + + + + + + + org.apache.velocity + velocity-engine-core + + + + + com.ruoyi + ruoyi-common + + + + + com.alibaba + druid-spring-boot-starter + + + + + \ No newline at end of file diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/config/GenConfig.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/config/GenConfig.java new file mode 100644 index 0000000..cc4cd14 --- /dev/null +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/config/GenConfig.java @@ -0,0 +1,73 @@ +package com.ruoyi.generator.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.PropertySource; +import org.springframework.stereotype.Component; + +/** + * 读取代码生成相关配置 + * + * @author ruoyi + */ +@Component +@ConfigurationProperties(prefix = "gen") +@PropertySource(value = { "classpath:generator.yml" }) +public class GenConfig +{ + /** 作者 */ + public static String author; + + /** 生成包路径 */ + public static String packageName; + + /** 自动去除表前缀,默认是false */ + public static boolean autoRemovePre; + + /** 表前缀(类名不会包含表前缀) */ + public static String tablePrefix; + + public static String getAuthor() + { + return author; + } + + @Value("${author}") + public void setAuthor(String author) + { + GenConfig.author = author; + } + + public static String getPackageName() + { + return packageName; + } + + @Value("${packageName}") + public void setPackageName(String packageName) + { + GenConfig.packageName = packageName; + } + + public static boolean getAutoRemovePre() + { + return autoRemovePre; + } + + @Value("${autoRemovePre}") + public void setAutoRemovePre(boolean autoRemovePre) + { + GenConfig.autoRemovePre = autoRemovePre; + } + + public static String getTablePrefix() + { + return tablePrefix; + } + + @Value("${tablePrefix}") + public void setTablePrefix(String tablePrefix) + { + GenConfig.tablePrefix = tablePrefix; + } +} diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/controller/GenController.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/controller/GenController.java new file mode 100644 index 0000000..49b02ba --- /dev/null +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/controller/GenController.java @@ -0,0 +1,304 @@ +package com.ruoyi.generator.controller; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.io.IOUtils; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.apache.shiro.authz.annotation.RequiresRoles; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import com.alibaba.druid.DbType; +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlCreateTableStatement; +import com.alibaba.fastjson.JSON; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.CxSelect; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.security.PermissionUtils; +import com.ruoyi.common.utils.sql.SqlUtil; +import com.ruoyi.generator.domain.GenTable; +import com.ruoyi.generator.domain.GenTableColumn; +import com.ruoyi.generator.service.IGenTableColumnService; +import com.ruoyi.generator.service.IGenTableService; + +/** + * 代码生成 操作处理 + * + * @author ruoyi + */ +@Controller +@RequestMapping("/tool/gen") +public class GenController extends BaseController +{ + private String prefix = "tool/gen"; + + @Autowired + private IGenTableService genTableService; + + @Autowired + private IGenTableColumnService genTableColumnService; + + @RequiresPermissions("tool:gen:view") + @GetMapping() + public String gen() + { + return prefix + "/gen"; + } + + /** + * 查询代码生成列表 + */ + @RequiresPermissions("tool:gen:list") + @PostMapping("/list") + @ResponseBody + public TableDataInfo genList(GenTable genTable) + { + startPage(); + List list = genTableService.selectGenTableList(genTable); + return getDataTable(list); + } + + /** + * 查询数据库列表 + */ + @RequiresPermissions("tool:gen:list") + @PostMapping("/db/list") + @ResponseBody + public TableDataInfo dataList(GenTable genTable) + { + startPage(); + List list = genTableService.selectDbTableList(genTable); + return getDataTable(list); + } + + /** + * 查询数据表字段列表 + */ + @RequiresPermissions("tool:gen:list") + @PostMapping("/column/list") + @ResponseBody + public TableDataInfo columnList(GenTableColumn genTableColumn) + { + TableDataInfo dataInfo = new TableDataInfo(); + List list = genTableColumnService.selectGenTableColumnListByTableId(genTableColumn); + dataInfo.setRows(list); + dataInfo.setTotal(list.size()); + return dataInfo; + } + + /** + * 导入表结构 + */ + @RequiresPermissions("tool:gen:list") + @GetMapping("/importTable") + public String importTable() + { + return prefix + "/importTable"; + } + + /** + * 创建表结构 + */ + @GetMapping("/createTable") + public String createTable() + { + return prefix + "/createTable"; + } + + /** + * 导入表结构(保存) + */ + @RequiresPermissions("tool:gen:list") + @Log(title = "代码生成", businessType = BusinessType.IMPORT) + @PostMapping("/importTable") + @ResponseBody + public AjaxResult importTableSave(String tables) + { + String[] tableNames = Convert.toStrArray(tables); + // 查询表信息 + List tableList = genTableService.selectDbTableListByNames(tableNames); + String operName = Convert.toStr(PermissionUtils.getPrincipalProperty("loginName")); + genTableService.importGenTable(tableList, operName); + return AjaxResult.success(); + } + + /** + * 修改代码生成业务 + */ + @RequiresPermissions("tool:gen:edit") + @GetMapping("/edit/{tableId}") + public String edit(@PathVariable("tableId") Long tableId, ModelMap mmap) + { + GenTable table = genTableService.selectGenTableById(tableId); + List genTables = genTableService.selectGenTableAll(); + List cxSelect = new ArrayList(); + for (GenTable genTable : genTables) + { + if (!StringUtils.equals(table.getTableName(), genTable.getTableName())) + { + CxSelect cxTable = new CxSelect(genTable.getTableName(), genTable.getTableName() + ':' + genTable.getTableComment()); + List cxColumns = new ArrayList(); + for (GenTableColumn tableColumn : genTable.getColumns()) + { + cxColumns.add(new CxSelect(tableColumn.getColumnName(), tableColumn.getColumnName() + ':' + tableColumn.getColumnComment())); + } + cxTable.setS(cxColumns); + cxSelect.add(cxTable); + } + } + mmap.put("table", table); + mmap.put("data", JSON.toJSON(cxSelect)); + return prefix + "/edit"; + } + + /** + * 修改保存代码生成业务 + */ + @RequiresPermissions("tool:gen:edit") + @Log(title = "代码生成", businessType = BusinessType.UPDATE) + @PostMapping("/edit") + @ResponseBody + public AjaxResult editSave(@Validated GenTable genTable) + { + genTableService.validateEdit(genTable); + genTableService.updateGenTable(genTable); + return AjaxResult.success(); + } + + @RequiresPermissions("tool:gen:remove") + @Log(title = "代码生成", businessType = BusinessType.DELETE) + @PostMapping("/remove") + @ResponseBody + public AjaxResult remove(String ids) + { + genTableService.deleteGenTableByIds(ids); + return AjaxResult.success(); + } + + @RequiresRoles("admin") + @Log(title = "创建表", businessType = BusinessType.OTHER) + @PostMapping("/createTable") + @ResponseBody + public AjaxResult create(String sql) + { + try + { + SqlUtil.filterKeyword(sql); + List sqlStatements = SQLUtils.parseStatements(sql, DbType.mysql); + List tableNames = new ArrayList<>(); + for (SQLStatement sqlStatement : sqlStatements) + { + if (sqlStatement instanceof MySqlCreateTableStatement) + { + MySqlCreateTableStatement createTableStatement = (MySqlCreateTableStatement) sqlStatement; + if (genTableService.createTable(createTableStatement.toString())) + { + String tableName = createTableStatement.getTableName().replaceAll("`", ""); + tableNames.add(tableName); + } + } + } + List tableList = genTableService.selectDbTableListByNames(tableNames.toArray(new String[tableNames.size()])); + String operName = Convert.toStr(PermissionUtils.getPrincipalProperty("loginName")); + genTableService.importGenTable(tableList, operName); + return AjaxResult.success(); + } + catch (Exception e) + { + logger.error(e.getMessage(), e); + return AjaxResult.error("创建表结构异常"); + } + } + + /** + * 预览代码 + */ + @RequiresPermissions("tool:gen:preview") + @GetMapping("/preview/{tableId}") + @ResponseBody + public AjaxResult preview(@PathVariable("tableId") Long tableId) throws IOException + { + Map dataMap = genTableService.previewCode(tableId); + return AjaxResult.success(dataMap); + } + + /** + * 生成代码(下载方式) + */ + @RequiresPermissions("tool:gen:code") + @Log(title = "代码生成", businessType = BusinessType.GENCODE) + @GetMapping("/download/{tableName}") + public void download(HttpServletResponse response, @PathVariable("tableName") String tableName) throws IOException + { + byte[] data = genTableService.downloadCode(tableName); + genCode(response, data); + } + + /** + * 生成代码(自定义路径) + */ + @RequiresPermissions("tool:gen:code") + @Log(title = "代码生成", businessType = BusinessType.GENCODE) + @GetMapping("/genCode/{tableName}") + @ResponseBody + public AjaxResult genCode(@PathVariable("tableName") String tableName) + { + genTableService.generatorCode(tableName); + return AjaxResult.success(); + } + + /** + * 同步数据库 + */ + @RequiresPermissions("tool:gen:edit") + @Log(title = "代码生成", businessType = BusinessType.UPDATE) + @GetMapping("/synchDb/{tableName}") + @ResponseBody + public AjaxResult synchDb(@PathVariable("tableName") String tableName) + { + genTableService.synchDb(tableName); + return AjaxResult.success(); + } + + /** + * 批量生成代码 + */ + @RequiresPermissions("tool:gen:code") + @Log(title = "代码生成", businessType = BusinessType.GENCODE) + @GetMapping("/batchGenCode") + @ResponseBody + public void batchGenCode(HttpServletResponse response, String tables) throws IOException + { + String[] tableNames = Convert.toStrArray(tables); + byte[] data = genTableService.downloadCode(tableNames); + genCode(response, data); + } + + /** + * 生成zip文件 + */ + private void genCode(HttpServletResponse response, byte[] data) throws IOException + { + response.reset(); + response.setHeader("Content-Disposition", "attachment; filename=\"ruoyi.zip\""); + response.addHeader("Content-Length", "" + data.length); + response.setContentType("application/octet-stream; charset=UTF-8"); + IOUtils.write(data, response.getOutputStream()); + } +} \ No newline at end of file diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTable.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTable.java new file mode 100644 index 0000000..269779c --- /dev/null +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTable.java @@ -0,0 +1,372 @@ +package com.ruoyi.generator.domain; + +import java.util.List; +import javax.validation.Valid; +import javax.validation.constraints.NotBlank; +import org.apache.commons.lang3.ArrayUtils; +import com.ruoyi.common.constant.GenConstants; +import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.common.utils.StringUtils; + +/** + * 业务表 gen_table + * + * @author ruoyi + */ +public class GenTable extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 编号 */ + private Long tableId; + + /** 表名称 */ + @NotBlank(message = "表名称不能为空") + private String tableName; + + /** 表描述 */ + @NotBlank(message = "表描述不能为空") + private String tableComment; + + /** 关联父表的表名 */ + private String subTableName; + + /** 本表关联父表的外键名 */ + private String subTableFkName; + + /** 实体类名称(首字母大写) */ + @NotBlank(message = "实体类名称不能为空") + private String className; + + /** 使用的模板(crud单表操作 tree树表操作 sub主子表操作) */ + private String tplCategory; + + /** 生成包路径 */ + @NotBlank(message = "生成包路径不能为空") + private String packageName; + + /** 生成模块名 */ + @NotBlank(message = "生成模块名不能为空") + private String moduleName; + + /** 生成业务名 */ + @NotBlank(message = "生成业务名不能为空") + private String businessName; + + /** 生成功能名 */ + @NotBlank(message = "生成功能名不能为空") + private String functionName; + + /** 生成作者 */ + @NotBlank(message = "作者不能为空") + private String functionAuthor; + + /** 生成代码方式(0zip压缩包 1自定义路径) */ + private String genType; + + /** 生成路径(不填默认项目路径) */ + private String genPath; + + /** 主键信息 */ + private GenTableColumn pkColumn; + + /** 子表信息 */ + private GenTable subTable; + + /** 表列信息 */ + @Valid + private List columns; + + /** 其它生成选项 */ + private String options; + + /** 树编码字段 */ + private String treeCode; + + /** 树父编码字段 */ + private String treeParentCode; + + /** 树名称字段 */ + private String treeName; + + /** 上级菜单ID字段 */ + private String parentMenuId; + + /** 上级菜单名称字段 */ + private String parentMenuName; + + public Long getTableId() + { + return tableId; + } + + public void setTableId(Long tableId) + { + this.tableId = tableId; + } + + public String getTableName() + { + return tableName; + } + + public void setTableName(String tableName) + { + this.tableName = tableName; + } + + public String getTableComment() + { + return tableComment; + } + + public void setTableComment(String tableComment) + { + this.tableComment = tableComment; + } + + public String getSubTableName() + { + return subTableName; + } + + public void setSubTableName(String subTableName) + { + this.subTableName = subTableName; + } + + public String getSubTableFkName() + { + return subTableFkName; + } + + public void setSubTableFkName(String subTableFkName) + { + this.subTableFkName = subTableFkName; + } + + public String getClassName() + { + return className; + } + + public void setClassName(String className) + { + this.className = className; + } + + public String getTplCategory() + { + return tplCategory; + } + + public void setTplCategory(String tplCategory) + { + this.tplCategory = tplCategory; + } + + public String getPackageName() + { + return packageName; + } + + public void setPackageName(String packageName) + { + this.packageName = packageName; + } + + public String getModuleName() + { + return moduleName; + } + + public void setModuleName(String moduleName) + { + this.moduleName = moduleName; + } + + public String getBusinessName() + { + return businessName; + } + + public void setBusinessName(String businessName) + { + this.businessName = businessName; + } + + public String getFunctionName() + { + return functionName; + } + + public void setFunctionName(String functionName) + { + this.functionName = functionName; + } + + public String getFunctionAuthor() + { + return functionAuthor; + } + + public void setFunctionAuthor(String functionAuthor) + { + this.functionAuthor = functionAuthor; + } + + public String getGenType() + { + return genType; + } + + public void setGenType(String genType) + { + this.genType = genType; + } + + public String getGenPath() + { + return genPath; + } + + public void setGenPath(String genPath) + { + this.genPath = genPath; + } + + public GenTableColumn getPkColumn() + { + return pkColumn; + } + + public void setPkColumn(GenTableColumn pkColumn) + { + this.pkColumn = pkColumn; + } + + public GenTable getSubTable() + { + return subTable; + } + + public void setSubTable(GenTable subTable) + { + this.subTable = subTable; + } + + public List getColumns() + { + return columns; + } + + public void setColumns(List columns) + { + this.columns = columns; + } + + public String getOptions() + { + return options; + } + + public void setOptions(String options) + { + this.options = options; + } + + public String getTreeCode() + { + return treeCode; + } + + public void setTreeCode(String treeCode) + { + this.treeCode = treeCode; + } + + public String getTreeParentCode() + { + return treeParentCode; + } + + public void setTreeParentCode(String treeParentCode) + { + this.treeParentCode = treeParentCode; + } + + public String getTreeName() + { + return treeName; + } + + public void setTreeName(String treeName) + { + this.treeName = treeName; + } + + public String getParentMenuId() + { + return parentMenuId; + } + + public void setParentMenuId(String parentMenuId) + { + this.parentMenuId = parentMenuId; + } + + public String getParentMenuName() + { + return parentMenuName; + } + + public void setParentMenuName(String parentMenuName) + { + this.parentMenuName = parentMenuName; + } + + public boolean isSub() + { + return isSub(this.tplCategory); + } + + public static boolean isSub(String tplCategory) + { + return tplCategory != null && StringUtils.equals(GenConstants.TPL_SUB, tplCategory); + } + + public boolean isTree() + { + return isTree(this.tplCategory); + } + + public static boolean isTree(String tplCategory) + { + return tplCategory != null && StringUtils.equals(GenConstants.TPL_TREE, tplCategory); + } + + public boolean isCrud() + { + return isCrud(this.tplCategory); + } + + public static boolean isCrud(String tplCategory) + { + return tplCategory != null && StringUtils.equals(GenConstants.TPL_CRUD, tplCategory); + } + + public boolean isSuperColumn(String javaField) + { + return isSuperColumn(this.tplCategory, javaField); + } + + public static boolean isSuperColumn(String tplCategory, String javaField) + { + if (isTree(tplCategory)) + { + return StringUtils.equalsAnyIgnoreCase(javaField, + ArrayUtils.addAll(GenConstants.TREE_ENTITY, GenConstants.BASE_ENTITY)); + } + return StringUtils.equalsAnyIgnoreCase(javaField, GenConstants.BASE_ENTITY); + } +} \ No newline at end of file diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTableColumn.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTableColumn.java new file mode 100644 index 0000000..dbf1837 --- /dev/null +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTableColumn.java @@ -0,0 +1,373 @@ +package com.ruoyi.generator.domain; + +import javax.validation.constraints.NotBlank; +import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.common.utils.StringUtils; + +/** + * 代码生成业务字段表 gen_table_column + * + * @author ruoyi + */ +public class GenTableColumn extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 编号 */ + private Long columnId; + + /** 归属表编号 */ + private Long tableId; + + /** 列名称 */ + private String columnName; + + /** 列描述 */ + private String columnComment; + + /** 列类型 */ + private String columnType; + + /** JAVA类型 */ + private String javaType; + + /** JAVA字段名 */ + @NotBlank(message = "Java属性不能为空") + private String javaField; + + /** 是否主键(1是) */ + private String isPk; + + /** 是否自增(1是) */ + private String isIncrement; + + /** 是否必填(1是) */ + private String isRequired; + + /** 是否为插入字段(1是) */ + private String isInsert; + + /** 是否编辑字段(1是) */ + private String isEdit; + + /** 是否列表字段(1是) */ + private String isList; + + /** 是否查询字段(1是) */ + private String isQuery; + + /** 查询方式(EQ等于、NE不等于、GT大于、LT小于、LIKE模糊、BETWEEN范围) */ + private String queryType; + + /** 显示类型(input文本框、textarea文本域、select下拉框、checkbox复选框、radio单选框、datetime日期控件、upload上传控件、summernote富文本控件) */ + private String htmlType; + + /** 字典类型 */ + private String dictType = ""; + + /** 排序 */ + private Integer sort; + + public void setColumnId(Long columnId) + { + this.columnId = columnId; + } + + public Long getColumnId() + { + return columnId; + } + + public void setTableId(Long tableId) + { + this.tableId = tableId; + } + + public Long getTableId() + { + return tableId; + } + + public void setColumnName(String columnName) + { + this.columnName = columnName; + } + + public String getColumnName() + { + return columnName; + } + + public void setColumnComment(String columnComment) + { + this.columnComment = columnComment; + } + + public String getColumnComment() + { + return columnComment; + } + + public void setColumnType(String columnType) + { + this.columnType = columnType; + } + + public String getColumnType() + { + return columnType; + } + + public void setJavaType(String javaType) + { + this.javaType = javaType; + } + + public String getJavaType() + { + return javaType; + } + + public void setJavaField(String javaField) + { + this.javaField = javaField; + } + + public String getJavaField() + { + return javaField; + } + + public String getCapJavaField() + { + return StringUtils.capitalize(javaField); + } + + public void setIsPk(String isPk) + { + this.isPk = isPk; + } + + public String getIsPk() + { + return isPk; + } + + public boolean isPk() + { + return isPk(this.isPk); + } + + public boolean isPk(String isPk) + { + return isPk != null && StringUtils.equals("1", isPk); + } + + public String getIsIncrement() + { + return isIncrement; + } + + public void setIsIncrement(String isIncrement) + { + this.isIncrement = isIncrement; + } + + public boolean isIncrement() + { + return isIncrement(this.isIncrement); + } + + public boolean isIncrement(String isIncrement) + { + return isIncrement != null && StringUtils.equals("1", isIncrement); + } + + public void setIsRequired(String isRequired) + { + this.isRequired = isRequired; + } + + public String getIsRequired() + { + return isRequired; + } + + public boolean isRequired() + { + return isRequired(this.isRequired); + } + + public boolean isRequired(String isRequired) + { + return isRequired != null && StringUtils.equals("1", isRequired); + } + + public void setIsInsert(String isInsert) + { + this.isInsert = isInsert; + } + + public String getIsInsert() + { + return isInsert; + } + + public boolean isInsert() + { + return isInsert(this.isInsert); + } + + public boolean isInsert(String isInsert) + { + return isInsert != null && StringUtils.equals("1", isInsert); + } + + public void setIsEdit(String isEdit) + { + this.isEdit = isEdit; + } + + public String getIsEdit() + { + return isEdit; + } + + public boolean isEdit() + { + return isInsert(this.isEdit); + } + + public boolean isEdit(String isEdit) + { + return isEdit != null && StringUtils.equals("1", isEdit); + } + + public void setIsList(String isList) + { + this.isList = isList; + } + + public String getIsList() + { + return isList; + } + + public boolean isList() + { + return isList(this.isList); + } + + public boolean isList(String isList) + { + return isList != null && StringUtils.equals("1", isList); + } + + public void setIsQuery(String isQuery) + { + this.isQuery = isQuery; + } + + public String getIsQuery() + { + return isQuery; + } + + public boolean isQuery() + { + return isQuery(this.isQuery); + } + + public boolean isQuery(String isQuery) + { + return isQuery != null && StringUtils.equals("1", isQuery); + } + + public void setQueryType(String queryType) + { + this.queryType = queryType; + } + + public String getQueryType() + { + return queryType; + } + + public String getHtmlType() + { + return htmlType; + } + + public void setHtmlType(String htmlType) + { + this.htmlType = htmlType; + } + + public void setDictType(String dictType) + { + this.dictType = dictType; + } + + public String getDictType() + { + return dictType; + } + + public void setSort(Integer sort) + { + this.sort = sort; + } + + public Integer getSort() + { + return sort; + } + + public boolean isSuperColumn() + { + return isSuperColumn(this.javaField); + } + + public static boolean isSuperColumn(String javaField) + { + return StringUtils.equalsAnyIgnoreCase(javaField, + // BaseEntity + "createBy", "createTime", "updateBy", "updateTime", "remark", + // TreeEntity + "parentName", "parentId", "orderNum", "ancestors"); + } + + public boolean isUsableColumn() + { + return isUsableColumn(javaField); + } + + public static boolean isUsableColumn(String javaField) + { + // isSuperColumn()中的名单用于避免生成多余Domain属性,若某些属性在生成页面时需要用到不能忽略,则放在此处白名单 + return StringUtils.equalsAnyIgnoreCase(javaField, "parentId", "orderNum", "remark"); + } + + public String readConverterExp() + { + String remarks = StringUtils.substringBetween(this.columnComment, "(", ")"); + StringBuffer sb = new StringBuffer(); + if (StringUtils.isNotEmpty(remarks)) + { + for (String value : remarks.split(" ")) + { + if (StringUtils.isNotEmpty(value)) + { + Object startStr = value.subSequence(0, 1); + String endStr = value.substring(1); + sb.append("").append(startStr).append("=").append(endStr).append(","); + } + } + return sb.deleteCharAt(sb.length() - 1).toString(); + } + else + { + return this.columnComment; + } + } +} \ No newline at end of file diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableColumnMapper.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableColumnMapper.java new file mode 100644 index 0000000..0b7bdb5 --- /dev/null +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableColumnMapper.java @@ -0,0 +1,60 @@ +package com.ruoyi.generator.mapper; + +import java.util.List; +import com.ruoyi.generator.domain.GenTableColumn; + +/** + * 业务字段 数据层 + * + * @author ruoyi + */ +public interface GenTableColumnMapper +{ + /** + * 根据表名称查询列信息 + * + * @param tableName 表名称 + * @return 列信息 + */ + public List selectDbTableColumnsByName(String tableName); + + /** + * 查询业务字段列表 + * + * @param genTableColumn 业务字段信息 + * @return 业务字段集合 + */ + public List selectGenTableColumnListByTableId(GenTableColumn genTableColumn); + + /** + * 新增业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + public int insertGenTableColumn(GenTableColumn genTableColumn); + + /** + * 修改业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + public int updateGenTableColumn(GenTableColumn genTableColumn); + + /** + * 删除业务字段 + * + * @param genTableColumns 列数据 + * @return 结果 + */ + public int deleteGenTableColumns(List genTableColumns); + + /** + * 批量删除业务字段 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteGenTableColumnByIds(Long[] ids); +} diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableMapper.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableMapper.java new file mode 100644 index 0000000..7265d42 --- /dev/null +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableMapper.java @@ -0,0 +1,91 @@ +package com.ruoyi.generator.mapper; + +import java.util.List; +import com.ruoyi.generator.domain.GenTable; + +/** + * 业务 数据层 + * + * @author ruoyi + */ +public interface GenTableMapper +{ + /** + * 查询业务列表 + * + * @param genTable 业务信息 + * @return 业务集合 + */ + public List selectGenTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param genTable 业务信息 + * @return 数据库表集合 + */ + public List selectDbTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param tableNames 表名称组 + * @return 数据库表集合 + */ + public List selectDbTableListByNames(String[] tableNames); + + /** + * 查询所有表信息 + * + * @return 表信息集合 + */ + public List selectGenTableAll(); + + /** + * 查询表ID业务信息 + * + * @param id 业务ID + * @return 业务信息 + */ + public GenTable selectGenTableById(Long id); + + /** + * 查询表名称业务信息 + * + * @param tableName 表名称 + * @return 业务信息 + */ + public GenTable selectGenTableByName(String tableName); + + /** + * 新增业务 + * + * @param genTable 业务信息 + * @return 结果 + */ + public int insertGenTable(GenTable genTable); + + /** + * 修改业务 + * + * @param genTable 业务信息 + * @return 结果 + */ + public int updateGenTable(GenTable genTable); + + /** + * 批量删除业务 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteGenTableByIds(Long[] ids); + + /** + * 创建表 + * + * @param sql + * @return 结果 + */ + public int createTable(String sql); +} \ No newline at end of file diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableColumnService.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableColumnService.java new file mode 100644 index 0000000..69a1e21 --- /dev/null +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableColumnService.java @@ -0,0 +1,44 @@ +package com.ruoyi.generator.service; + +import java.util.List; +import com.ruoyi.generator.domain.GenTableColumn; + +/** + * 业务字段 服务层 + * + * @author ruoyi + */ +public interface IGenTableColumnService +{ + /** + * 查询业务字段列表 + * + * @param genTableColumn 业务字段信息 + * @return 业务字段集合 + */ + public List selectGenTableColumnListByTableId(GenTableColumn genTableColumn); + + /** + * 新增业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + public int insertGenTableColumn(GenTableColumn genTableColumn); + + /** + * 修改业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + public int updateGenTableColumn(GenTableColumn genTableColumn); + + /** + * 删除业务字段信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteGenTableColumnByIds(String ids); +} diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableService.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableService.java new file mode 100644 index 0000000..4af8fea --- /dev/null +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableService.java @@ -0,0 +1,129 @@ +package com.ruoyi.generator.service; + +import java.util.List; +import java.util.Map; +import com.ruoyi.generator.domain.GenTable; + +/** + * 业务 服务层 + * + * @author ruoyi + */ +public interface IGenTableService +{ + /** + * 查询业务列表 + * + * @param genTable 业务信息 + * @return 业务集合 + */ + public List selectGenTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param genTable 业务信息 + * @return 数据库表集合 + */ + public List selectDbTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param tableNames 表名称组 + * @return 数据库表集合 + */ + public List selectDbTableListByNames(String[] tableNames); + + /** + * 查询所有表信息 + * + * @return 表信息集合 + */ + public List selectGenTableAll(); + + /** + * 查询业务信息 + * + * @param id 业务ID + * @return 业务信息 + */ + public GenTable selectGenTableById(Long id); + + /** + * 修改业务 + * + * @param genTable 业务信息 + * @return 结果 + */ + public void updateGenTable(GenTable genTable); + + /** + * 删除业务信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public void deleteGenTableByIds(String ids); + + /** + * 创建表 + * + * @param sql 创建表语句 + * @return 结果 + */ + public boolean createTable(String sql); + + /** + * 导入表结构 + * + * @param tableList 导入表列表 + * @param operName 操作人员 + */ + public void importGenTable(List tableList, String operName); + + /** + * 预览代码 + * + * @param tableId 表编号 + * @return 预览数据列表 + */ + public Map previewCode(Long tableId); + + /** + * 生成代码(下载方式) + * + * @param tableName 表名称 + * @return 数据 + */ + public byte[] downloadCode(String tableName); + + /** + * 生成代码(自定义路径) + * + * @param tableName 表名称 + */ + public void generatorCode(String tableName); + + /** + * 同步数据库 + * + * @param tableName 表名称 + */ + public void synchDb(String tableName); + + /** + * 批量生成代码(下载方式) + * + * @param tableNames 表数组 + * @return 数据 + */ + public byte[] downloadCode(String[] tableNames); + + /** + * 修改保存参数校验 + * + * @param genTable 业务信息 + */ + public void validateEdit(GenTable genTable); +} diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/service/impl/GenTableColumnServiceImpl.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/impl/GenTableColumnServiceImpl.java new file mode 100644 index 0000000..bd65ec2 --- /dev/null +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/impl/GenTableColumnServiceImpl.java @@ -0,0 +1,69 @@ +package com.ruoyi.generator.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.generator.domain.GenTableColumn; +import com.ruoyi.generator.mapper.GenTableColumnMapper; +import com.ruoyi.generator.service.IGenTableColumnService; + +/** + * 业务字段 服务层实现 + * + * @author ruoyi + */ +@Service +public class GenTableColumnServiceImpl implements IGenTableColumnService +{ + @Autowired + private GenTableColumnMapper genTableColumnMapper; + + /** + * 查询业务字段列表 + * + * @param genTableColumn 业务字段信息 + * @return 业务字段集合 + */ + @Override + public List selectGenTableColumnListByTableId(GenTableColumn genTableColumn) + { + return genTableColumnMapper.selectGenTableColumnListByTableId(genTableColumn); + } + + /** + * 新增业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + @Override + public int insertGenTableColumn(GenTableColumn genTableColumn) + { + return genTableColumnMapper.insertGenTableColumn(genTableColumn); + } + + /** + * 修改业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + @Override + public int updateGenTableColumn(GenTableColumn genTableColumn) + { + return genTableColumnMapper.updateGenTableColumn(genTableColumn); + } + + /** + * 删除业务字段对象 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + @Override + public int deleteGenTableColumnByIds(String ids) + { + return genTableColumnMapper.deleteGenTableColumnByIds(Convert.toLongArray(ids)); + } +} \ No newline at end of file diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/service/impl/GenTableServiceImpl.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/impl/GenTableServiceImpl.java new file mode 100644 index 0000000..1f25c53 --- /dev/null +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/impl/GenTableServiceImpl.java @@ -0,0 +1,534 @@ +package com.ruoyi.generator.service.impl; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.velocity.Template; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.Velocity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.constant.GenConstants; +import com.ruoyi.common.core.text.CharsetKit; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.generator.domain.GenTable; +import com.ruoyi.generator.domain.GenTableColumn; +import com.ruoyi.generator.mapper.GenTableColumnMapper; +import com.ruoyi.generator.mapper.GenTableMapper; +import com.ruoyi.generator.service.IGenTableService; +import com.ruoyi.generator.util.GenUtils; +import com.ruoyi.generator.util.VelocityInitializer; +import com.ruoyi.generator.util.VelocityUtils; + +/** + * 业务 服务层实现 + * + * @author ruoyi + */ +@Service +public class GenTableServiceImpl implements IGenTableService +{ + private static final Logger log = LoggerFactory.getLogger(GenTableServiceImpl.class); + + @Autowired + private GenTableMapper genTableMapper; + + @Autowired + private GenTableColumnMapper genTableColumnMapper; + + /** + * 查询业务信息 + * + * @param id 业务ID + * @return 业务信息 + */ + @Override + public GenTable selectGenTableById(Long id) + { + GenTable genTable = genTableMapper.selectGenTableById(id); + setTableFromOptions(genTable); + return genTable; + } + + /** + * 查询业务列表 + * + * @param genTable 业务信息 + * @return 业务集合 + */ + @Override + public List selectGenTableList(GenTable genTable) + { + return genTableMapper.selectGenTableList(genTable); + } + + /** + * 查询据库列表 + * + * @param genTable 业务信息 + * @return 数据库表集合 + */ + @Override + public List selectDbTableList(GenTable genTable) + { + return genTableMapper.selectDbTableList(genTable); + } + + /** + * 查询据库列表 + * + * @param tableNames 表名称组 + * @return 数据库表集合 + */ + @Override + public List selectDbTableListByNames(String[] tableNames) + { + return genTableMapper.selectDbTableListByNames(tableNames); + } + + /** + * 查询所有表信息 + * + * @return 表信息集合 + */ + @Override + public List selectGenTableAll() + { + return genTableMapper.selectGenTableAll(); + } + + /** + * 修改业务 + * + * @param genTable 业务信息 + * @return 结果 + */ + @Override + @Transactional + public void updateGenTable(GenTable genTable) + { + String options = JSON.toJSONString(genTable.getParams()); + genTable.setOptions(options); + int row = genTableMapper.updateGenTable(genTable); + if (row > 0) + { + for (GenTableColumn genTableColumn : genTable.getColumns()) + { + genTableColumnMapper.updateGenTableColumn(genTableColumn); + } + } + } + + /** + * 删除业务对象 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + @Override + @Transactional + public void deleteGenTableByIds(String ids) + { + genTableMapper.deleteGenTableByIds(Convert.toLongArray(ids)); + genTableColumnMapper.deleteGenTableColumnByIds(Convert.toLongArray(ids)); + } + + /** + * 创建表 + * + * @param sql 创建表语句 + * @return 结果 + */ + @Override + public boolean createTable(String sql) + { + return genTableMapper.createTable(sql) == 0; + } + + /** + * 导入表结构 + * + * @param tableList 导入表列表 + * @param operName 操作人员 + */ + @Override + @Transactional + public void importGenTable(List tableList, String operName) + { + try + { + for (GenTable table : tableList) + { + String tableName = table.getTableName(); + GenUtils.initTable(table, operName); + int row = genTableMapper.insertGenTable(table); + if (row > 0) + { + // 保存列信息 + List genTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName); + for (GenTableColumn column : genTableColumns) + { + GenUtils.initColumnField(column, table); + genTableColumnMapper.insertGenTableColumn(column); + } + } + } + } + catch (Exception e) + { + throw new ServiceException("导入失败:" + e.getMessage()); + } + } + + /** + * 预览代码 + * + * @param tableId 表编号 + * @return 预览数据列表 + */ + @Override + public Map previewCode(Long tableId) + { + Map dataMap = new LinkedHashMap<>(); + // 查询表信息 + GenTable table = genTableMapper.selectGenTableById(tableId); + // 设置主子表信息 + setSubTable(table); + // 设置主键列信息 + setPkColumn(table); + VelocityInitializer.initVelocity(); + + VelocityContext context = VelocityUtils.prepareContext(table); + + // 获取模板列表 + List templates = VelocityUtils.getTemplateList(table.getTplCategory()); + for (String template : templates) + { + // 渲染模板 + StringWriter sw = new StringWriter(); + Template tpl = Velocity.getTemplate(template, Constants.UTF8); + tpl.merge(context, sw); + dataMap.put(template, sw.toString()); + } + return dataMap; + } + + /** + * 生成代码(下载方式) + * + * @param tableName 表名称 + * @return 数据 + */ + @Override + public byte[] downloadCode(String tableName) + { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ZipOutputStream zip = new ZipOutputStream(outputStream); + generatorCode(tableName, zip); + IOUtils.closeQuietly(zip); + return outputStream.toByteArray(); + } + + /** + * 生成代码(自定义路径) + * + * @param tableName 表名称 + */ + @Override + public void generatorCode(String tableName) + { + // 查询表信息 + GenTable table = genTableMapper.selectGenTableByName(tableName); + // 设置主子表信息 + setSubTable(table); + // 设置主键列信息 + setPkColumn(table); + + VelocityInitializer.initVelocity(); + + VelocityContext context = VelocityUtils.prepareContext(table); + + // 获取模板列表 + List templates = VelocityUtils.getTemplateList(table.getTplCategory()); + for (String template : templates) + { + if (!StringUtils.contains(template, "sql.vm")) + { + // 渲染模板 + StringWriter sw = new StringWriter(); + Template tpl = Velocity.getTemplate(template, Constants.UTF8); + tpl.merge(context, sw); + try + { + String path = getGenPath(table, template); + FileUtils.writeStringToFile(new File(path), sw.toString(), CharsetKit.UTF_8); + } + catch (IOException e) + { + throw new ServiceException("渲染模板失败,表名:" + table.getTableName()); + } + } + } + } + + /** + * 同步数据库 + * + * @param tableName 表名称 + */ + @Override + @Transactional + public void synchDb(String tableName) + { + GenTable table = genTableMapper.selectGenTableByName(tableName); + List tableColumns = table.getColumns(); + Map tableColumnMap = tableColumns.stream().collect(Collectors.toMap(GenTableColumn::getColumnName, Function.identity())); + + List dbTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName); + if (StringUtils.isEmpty(dbTableColumns)) + { + throw new ServiceException("同步数据失败,原表结构不存在"); + } + List dbTableColumnNames = dbTableColumns.stream().map(GenTableColumn::getColumnName).collect(Collectors.toList()); + + dbTableColumns.forEach(column -> { + GenUtils.initColumnField(column, table); + if (tableColumnMap.containsKey(column.getColumnName())) + { + GenTableColumn prevColumn = tableColumnMap.get(column.getColumnName()); + column.setColumnId(prevColumn.getColumnId()); + if (column.isList()) + { + // 如果是列表,继续保留查询方式/字典类型选项 + column.setDictType(prevColumn.getDictType()); + column.setQueryType(prevColumn.getQueryType()); + } + if (StringUtils.isNotEmpty(prevColumn.getIsRequired()) && !column.isPk() + && (column.isInsert() || column.isEdit()) + && ((column.isUsableColumn()) || (!column.isSuperColumn()))) + { + // 如果是(新增/修改&非主键/非忽略及父属性),继续保留必填/显示类型选项 + column.setIsRequired(prevColumn.getIsRequired()); + column.setHtmlType(prevColumn.getHtmlType()); + } + genTableColumnMapper.updateGenTableColumn(column); + } + else + { + genTableColumnMapper.insertGenTableColumn(column); + } + }); + + List delColumns = tableColumns.stream().filter(column -> !dbTableColumnNames.contains(column.getColumnName())).collect(Collectors.toList()); + if (StringUtils.isNotEmpty(delColumns)) + { + genTableColumnMapper.deleteGenTableColumns(delColumns); + } + } + + /** + * 批量生成代码(下载方式) + * + * @param tableNames 表数组 + * @return 数据 + */ + @Override + public byte[] downloadCode(String[] tableNames) + { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ZipOutputStream zip = new ZipOutputStream(outputStream); + for (String tableName : tableNames) + { + generatorCode(tableName, zip); + } + IOUtils.closeQuietly(zip); + return outputStream.toByteArray(); + } + + /** + * 查询表信息并生成代码 + */ + private void generatorCode(String tableName, ZipOutputStream zip) + { + // 查询表信息 + GenTable table = genTableMapper.selectGenTableByName(tableName); + // 设置主子表信息 + setSubTable(table); + // 设置主键列信息 + setPkColumn(table); + + VelocityInitializer.initVelocity(); + + VelocityContext context = VelocityUtils.prepareContext(table); + + // 获取模板列表 + List templates = VelocityUtils.getTemplateList(table.getTplCategory()); + for (String template : templates) + { + // 渲染模板 + StringWriter sw = new StringWriter(); + Template tpl = Velocity.getTemplate(template, Constants.UTF8); + tpl.merge(context, sw); + try + { + // 添加到zip + zip.putNextEntry(new ZipEntry(VelocityUtils.getFileName(template, table))); + IOUtils.write(sw.toString(), zip, Constants.UTF8); + IOUtils.closeQuietly(sw); + zip.flush(); + zip.closeEntry(); + } + catch (IOException e) + { + log.error("渲染模板失败,表名:" + table.getTableName(), e); + } + } + } + + /** + * 修改保存参数校验 + * + * @param genTable 业务信息 + */ + @Override + public void validateEdit(GenTable genTable) + { + if (GenConstants.TPL_TREE.equals(genTable.getTplCategory())) + { + String options = JSON.toJSONString(genTable.getParams()); + JSONObject paramsObj = JSONObject.parseObject(options); + if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_CODE))) + { + throw new ServiceException("树编码字段不能为空"); + } + else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_PARENT_CODE))) + { + throw new ServiceException("树父编码字段不能为空"); + } + else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_NAME))) + { + throw new ServiceException("树名称字段不能为空"); + } + } + else if (GenConstants.TPL_SUB.equals(genTable.getTplCategory())) + { + if (StringUtils.isEmpty(genTable.getSubTableName())) + { + throw new ServiceException("关联子表的表名不能为空"); + } + else if (StringUtils.isEmpty(genTable.getSubTableFkName())) + { + throw new ServiceException("子表关联的外键名不能为空"); + } + } + } + + /** + * 设置主键列信息 + * + * @param table 业务表信息 + */ + public void setPkColumn(GenTable table) + { + for (GenTableColumn column : table.getColumns()) + { + if (column.isPk()) + { + table.setPkColumn(column); + break; + } + } + if (StringUtils.isNull(table.getPkColumn())) + { + table.setPkColumn(table.getColumns().get(0)); + } + if (GenConstants.TPL_SUB.equals(table.getTplCategory())) + { + for (GenTableColumn column : table.getSubTable().getColumns()) + { + if (column.isPk()) + { + table.getSubTable().setPkColumn(column); + break; + } + } + if (StringUtils.isNull(table.getSubTable().getPkColumn())) + { + table.getSubTable().setPkColumn(table.getSubTable().getColumns().get(0)); + } + } + } + + /** + * 设置主子表信息 + * + * @param table 业务表信息 + */ + public void setSubTable(GenTable table) + { + String subTableName = table.getSubTableName(); + if (StringUtils.isNotEmpty(subTableName)) + { + table.setSubTable(genTableMapper.selectGenTableByName(subTableName)); + } + } + + /** + * 设置代码生成其他选项值 + * + * @param genTable 设置后的生成对象 + */ + public void setTableFromOptions(GenTable genTable) + { + JSONObject paramsObj = JSONObject.parseObject(genTable.getOptions()); + if (StringUtils.isNotNull(paramsObj)) + { + String treeCode = paramsObj.getString(GenConstants.TREE_CODE); + String treeParentCode = paramsObj.getString(GenConstants.TREE_PARENT_CODE); + String treeName = paramsObj.getString(GenConstants.TREE_NAME); + String parentMenuId = paramsObj.getString(GenConstants.PARENT_MENU_ID); + String parentMenuName = paramsObj.getString(GenConstants.PARENT_MENU_NAME); + + genTable.setTreeCode(treeCode); + genTable.setTreeParentCode(treeParentCode); + genTable.setTreeName(treeName); + genTable.setParentMenuId(parentMenuId); + genTable.setParentMenuName(parentMenuName); + } + } + + /** + * 获取代码生成地址 + * + * @param table 业务表信息 + * @param template 模板文件路径 + * @return 生成地址 + */ + public static String getGenPath(GenTable table, String template) + { + String genPath = table.getGenPath(); + if (StringUtils.equals(genPath, "/")) + { + return System.getProperty("user.dir") + File.separator + "src" + File.separator + VelocityUtils.getFileName(template, table); + } + return genPath + File.separator + VelocityUtils.getFileName(template, table); + } +} \ No newline at end of file diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/util/GenUtils.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/util/GenUtils.java new file mode 100644 index 0000000..ece4cd6 --- /dev/null +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/util/GenUtils.java @@ -0,0 +1,252 @@ +package com.ruoyi.generator.util; + +import java.util.Arrays; +import org.apache.commons.lang3.RegExUtils; +import com.ruoyi.common.constant.GenConstants; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.generator.config.GenConfig; +import com.ruoyi.generator.domain.GenTable; +import com.ruoyi.generator.domain.GenTableColumn; + +/** + * 代码生成器 工具类 + * + * @author ruoyi + */ +public class GenUtils +{ + /** + * 初始化表信息 + */ + public static void initTable(GenTable genTable, String operName) + { + genTable.setClassName(convertClassName(genTable.getTableName())); + genTable.setPackageName(GenConfig.getPackageName()); + genTable.setModuleName(getModuleName(GenConfig.getPackageName())); + genTable.setBusinessName(getBusinessName(genTable.getTableName())); + genTable.setFunctionName(replaceText(genTable.getTableComment())); + genTable.setFunctionAuthor(GenConfig.getAuthor()); + genTable.setCreateBy(operName); + } + + /** + * 初始化列属性字段 + */ + public static void initColumnField(GenTableColumn column, GenTable table) + { + String dataType = getDbType(column.getColumnType()); + String columnName = column.getColumnName(); + column.setTableId(table.getTableId()); + column.setCreateBy(table.getCreateBy()); + // 设置java字段名 + column.setJavaField(StringUtils.toCamelCase(columnName)); + // 设置默认类型 + column.setJavaType(GenConstants.TYPE_STRING); + column.setQueryType(GenConstants.QUERY_EQ); + + if (arraysContains(GenConstants.COLUMNTYPE_STR, dataType) || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType)) + { + // 字符串长度超过500设置为文本域 + Integer columnLength = getColumnLength(column.getColumnType()); + String htmlType = columnLength >= 500 || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType) ? GenConstants.HTML_TEXTAREA : GenConstants.HTML_INPUT; + column.setHtmlType(htmlType); + } + else if (arraysContains(GenConstants.COLUMNTYPE_TIME, dataType)) + { + column.setJavaType(GenConstants.TYPE_DATE); + column.setHtmlType(GenConstants.HTML_DATETIME); + } + else if (arraysContains(GenConstants.COLUMNTYPE_NUMBER, dataType)) + { + column.setHtmlType(GenConstants.HTML_INPUT); + + // 如果是浮点型 统一用BigDecimal + String[] str = StringUtils.split(StringUtils.substringBetween(column.getColumnType(), "(", ")"), ","); + if (str != null && str.length == 2 && Integer.parseInt(str[1]) > 0) + { + column.setJavaType(GenConstants.TYPE_BIGDECIMAL); + } + // 如果是整形 + else if (str != null && str.length == 1 && Integer.parseInt(str[0]) <= 10) + { + column.setJavaType(GenConstants.TYPE_INTEGER); + } + // 长整形 + else + { + column.setJavaType(GenConstants.TYPE_LONG); + } + } + + // 插入字段(默认所有字段都需要插入) + column.setIsInsert(GenConstants.REQUIRE); + + // 编辑字段 + if (!arraysContains(GenConstants.COLUMNNAME_NOT_EDIT, columnName) && !column.isPk()) + { + column.setIsEdit(GenConstants.REQUIRE); + } + // 列表字段 + if (!arraysContains(GenConstants.COLUMNNAME_NOT_LIST, columnName) && !column.isPk()) + { + column.setIsList(GenConstants.REQUIRE); + } + // 查询字段 + if (!arraysContains(GenConstants.COLUMNNAME_NOT_QUERY, columnName) && !column.isPk()) + { + column.setIsQuery(GenConstants.REQUIRE); + } + + // 查询字段类型 + if (StringUtils.endsWithIgnoreCase(columnName, "name")) + { + column.setQueryType(GenConstants.QUERY_LIKE); + } + // 状态字段设置单选框 + if (StringUtils.endsWithIgnoreCase(columnName, "status")) + { + column.setHtmlType(GenConstants.HTML_RADIO); + } + // 类型&性别字段设置下拉框 + else if (StringUtils.endsWithIgnoreCase(columnName, "type") + || StringUtils.endsWithIgnoreCase(columnName, "sex")) + { + column.setHtmlType(GenConstants.HTML_SELECT); + } + // 文件字段设置上传控件 + else if (StringUtils.endsWithIgnoreCase(columnName, "file")) + { + column.setHtmlType(GenConstants.HTML_UPLOAD); + } + // 内容字段设置富文本控件 + else if (StringUtils.endsWithIgnoreCase(columnName, "content")) + { + column.setHtmlType(GenConstants.HTML_SUMMERNOTE); + } + } + + /** + * 校验数组是否包含指定值 + * + * @param arr 数组 + * @param targetValue 值 + * @return 是否包含 + */ + public static boolean arraysContains(String[] arr, String targetValue) + { + return Arrays.asList(arr).contains(targetValue); + } + + /** + * 获取模块名 + * + * @param packageName 包名 + * @return 模块名 + */ + public static String getModuleName(String packageName) + { + int lastIndex = packageName.lastIndexOf("."); + int nameLength = packageName.length(); + return StringUtils.substring(packageName, lastIndex + 1, nameLength); + } + + /** + * 获取业务名 + * + * @param tableName 表名 + * @return 业务名 + */ + public static String getBusinessName(String tableName) + { + int lastIndex = tableName.lastIndexOf("_"); + int nameLength = tableName.length(); + return StringUtils.substring(tableName, lastIndex + 1, nameLength); + } + + /** + * 表名转换成Java类名 + * + * @param tableName 表名称 + * @return 类名 + */ + public static String convertClassName(String tableName) + { + boolean autoRemovePre = GenConfig.getAutoRemovePre(); + String tablePrefix = GenConfig.getTablePrefix(); + if (autoRemovePre && StringUtils.isNotEmpty(tablePrefix)) + { + String[] searchList = StringUtils.split(tablePrefix, ","); + tableName = replaceFirst(tableName, searchList); + } + return StringUtils.convertToCamelCase(tableName); + } + + /** + * 批量替换前缀 + * + * @param replacementm 替换值 + * @param searchList 替换列表 + * @return + */ + public static String replaceFirst(String replacementm, String[] searchList) + { + String text = replacementm; + for (String searchString : searchList) + { + if (replacementm.startsWith(searchString)) + { + text = replacementm.replaceFirst(searchString, ""); + break; + } + } + return text; + } + + /** + * 关键字替换 + * + * @param text 需要被替换的名字 + * @return 替换后的名字 + */ + public static String replaceText(String text) + { + return RegExUtils.replaceAll(text, "(?:表|若依)", ""); + } + + /** + * 获取数据库类型字段 + * + * @param columnType 列类型 + * @return 截取后的列类型 + */ + public static String getDbType(String columnType) + { + if (StringUtils.indexOf(columnType, "(") > 0) + { + return StringUtils.substringBefore(columnType, "("); + } + else + { + return columnType; + } + } + + /** + * 获取字段长度 + * + * @param columnType 列类型 + * @return 截取后的列类型 + */ + public static Integer getColumnLength(String columnType) + { + if (StringUtils.indexOf(columnType, "(") > 0) + { + String length = StringUtils.substringBetween(columnType, "(", ")"); + return Integer.valueOf(length); + } + else + { + return 0; + } + } +} diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityInitializer.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityInitializer.java new file mode 100644 index 0000000..9f69403 --- /dev/null +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityInitializer.java @@ -0,0 +1,34 @@ +package com.ruoyi.generator.util; + +import java.util.Properties; +import org.apache.velocity.app.Velocity; +import com.ruoyi.common.constant.Constants; + +/** + * VelocityEngine工厂 + * + * @author ruoyi + */ +public class VelocityInitializer +{ + /** + * 初始化vm方法 + */ + public static void initVelocity() + { + Properties p = new Properties(); + try + { + // 加载classpath目录下的vm文件 + p.setProperty("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); + // 定义字符集 + p.setProperty(Velocity.INPUT_ENCODING, Constants.UTF8); + // 初始化Velocity引擎,指定配置Properties + Velocity.init(p); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } +} diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java new file mode 100644 index 0000000..2698e0b --- /dev/null +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java @@ -0,0 +1,384 @@ +package com.ruoyi.generator.util; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import org.apache.velocity.VelocityContext; +import com.alibaba.fastjson.JSONObject; +import com.ruoyi.common.constant.GenConstants; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.generator.config.GenConfig; +import com.ruoyi.generator.domain.GenTable; +import com.ruoyi.generator.domain.GenTableColumn; + +public class VelocityUtils +{ + /** 项目空间路径 */ + private static final String PROJECT_PATH = "main/java"; + + /** mybatis空间路径 */ + private static final String MYBATIS_PATH = "main/resources/mapper"; + + /** html空间路径 */ + private static final String TEMPLATES_PATH = "main/resources/templates"; + + /** 默认上级菜单,系统工具 */ + private static final String DEFAULT_PARENT_MENU_ID = "3"; + + /** + * 设置模板变量信息 + * + * @return 模板列表 + */ + public static VelocityContext prepareContext(GenTable genTable) + { + String moduleName = genTable.getModuleName(); + String businessName = genTable.getBusinessName(); + String packageName = genTable.getPackageName(); + String tplCategory = genTable.getTplCategory(); + String functionName = genTable.getFunctionName(); + + VelocityContext velocityContext = new VelocityContext(); + velocityContext.put("tplCategory", genTable.getTplCategory()); + velocityContext.put("tableName", genTable.getTableName()); + velocityContext.put("functionName", StringUtils.isNotEmpty(functionName) ? functionName : "【请填写功能名称】"); + velocityContext.put("ClassName", genTable.getClassName()); + velocityContext.put("className", StringUtils.uncapitalize(genTable.getClassName())); + velocityContext.put("moduleName", genTable.getModuleName()); + velocityContext.put("businessName", genTable.getBusinessName()); + velocityContext.put("basePackage", getPackagePrefix(packageName)); + velocityContext.put("packageName", packageName); + velocityContext.put("author", genTable.getFunctionAuthor()); + velocityContext.put("datetime", DateUtils.getDate()); + velocityContext.put("pkColumn", genTable.getPkColumn()); + velocityContext.put("importList", getImportList(genTable)); + velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName)); + velocityContext.put("columns", genTable.getColumns()); + velocityContext.put("table", genTable); + setMenuVelocityContext(velocityContext, genTable); + if (GenConstants.TPL_TREE.equals(tplCategory)) + { + setTreeVelocityContext(velocityContext, genTable); + } + if (GenConstants.TPL_SUB.equals(tplCategory)) + { + setSubVelocityContext(velocityContext, genTable); + } + return velocityContext; + } + + public static void setMenuVelocityContext(VelocityContext context, GenTable genTable) + { + String options = genTable.getOptions(); + JSONObject paramsObj = JSONObject.parseObject(options); + String parentMenuId = getParentMenuId(paramsObj); + context.put("parentMenuId", parentMenuId); + } + + public static void setTreeVelocityContext(VelocityContext context, GenTable genTable) + { + String options = genTable.getOptions(); + JSONObject paramsObj = JSONObject.parseObject(options); + String treeCode = getTreecode(paramsObj); + String treeParentCode = getTreeParentCode(paramsObj); + String treeName = getTreeName(paramsObj); + + context.put("treeCode", treeCode); + context.put("treeParentCode", treeParentCode); + context.put("treeName", treeName); + context.put("expandColumn", getExpandColumn(genTable)); + if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) + { + context.put("tree_parent_code", paramsObj.getString(GenConstants.TREE_PARENT_CODE)); + } + if (paramsObj.containsKey(GenConstants.TREE_NAME)) + { + context.put("tree_name", paramsObj.getString(GenConstants.TREE_NAME)); + } + } + + public static void setSubVelocityContext(VelocityContext context, GenTable genTable) + { + GenTable subTable = genTable.getSubTable(); + String subTableName = genTable.getSubTableName(); + String subTableFkName = genTable.getSubTableFkName(); + String subClassName = genTable.getSubTable().getClassName(); + String subTableFkClassName = StringUtils.convertToCamelCase(subTableFkName); + + context.put("subTable", subTable); + context.put("subTableName", subTableName); + context.put("subTableFkName", subTableFkName); + context.put("subTableFkClassName", subTableFkClassName); + context.put("subTableFkclassName", StringUtils.uncapitalize(subTableFkClassName)); + context.put("subClassName", subClassName); + context.put("subclassName", StringUtils.uncapitalize(subClassName)); + context.put("subImportList", getImportList(genTable.getSubTable())); + } + + /** + * 获取模板信息 + * + * @return 模板列表 + */ + public static List getTemplateList(String tplCategory) + { + List templates = new ArrayList(); + templates.add("vm/java/domain.java.vm"); + templates.add("vm/java/mapper.java.vm"); + templates.add("vm/java/service.java.vm"); + templates.add("vm/java/serviceImpl.java.vm"); + templates.add("vm/java/controller.java.vm"); + templates.add("vm/xml/mapper.xml.vm"); + if (GenConstants.TPL_CRUD.equals(tplCategory)) + { + templates.add("vm/html/list.html.vm"); + } + else if (GenConstants.TPL_TREE.equals(tplCategory)) + { + templates.add("vm/html/tree.html.vm"); + templates.add("vm/html/list-tree.html.vm"); + } + else if (GenConstants.TPL_SUB.equals(tplCategory)) + { + templates.add("vm/html/list.html.vm"); + templates.add("vm/java/sub-domain.java.vm"); + } + templates.add("vm/html/add.html.vm"); + templates.add("vm/html/edit.html.vm"); + templates.add("vm/sql/sql.vm"); + return templates; + } + + /** + * 获取文件名 + */ + public static String getFileName(String template, GenTable genTable) + { + // 文件名称 + String fileName = ""; + // 包路径 + String packageName = genTable.getPackageName(); + // 模块名 + String moduleName = genTable.getModuleName(); + // 大写类名 + String className = genTable.getClassName(); + // 业务名称 + String businessName = genTable.getBusinessName(); + + String javaPath = PROJECT_PATH + "/" + StringUtils.replace(packageName, ".", "/"); + String mybatisPath = MYBATIS_PATH + "/" + moduleName; + String htmlPath = TEMPLATES_PATH + "/" + moduleName + "/" + businessName; + + if (template.contains("domain.java.vm")) + { + fileName = StringUtils.format("{}/domain/{}.java", javaPath, className); + } + if (template.contains("sub-domain.java.vm") && StringUtils.equals(GenConstants.TPL_SUB, genTable.getTplCategory())) + { + fileName = StringUtils.format("{}/domain/{}.java", javaPath, genTable.getSubTable().getClassName()); + } + else if (template.contains("mapper.java.vm")) + { + fileName = StringUtils.format("{}/mapper/{}Mapper.java", javaPath, className); + } + else if (template.contains("service.java.vm")) + { + fileName = StringUtils.format("{}/service/I{}Service.java", javaPath, className); + } + else if (template.contains("serviceImpl.java.vm")) + { + fileName = StringUtils.format("{}/service/impl/{}ServiceImpl.java", javaPath, className); + } + else if (template.contains("controller.java.vm")) + { + fileName = StringUtils.format("{}/controller/{}Controller.java", javaPath, className); + } + else if (template.contains("mapper.xml.vm")) + { + fileName = StringUtils.format("{}/{}Mapper.xml", mybatisPath, className); + } + else if (template.contains("list.html.vm")) + { + fileName = StringUtils.format("{}/{}.html", htmlPath, businessName); + } + else if (template.contains("list-tree.html.vm")) + { + fileName = StringUtils.format("{}/{}.html", htmlPath, businessName); + } + else if (template.contains("tree.html.vm")) + { + fileName = StringUtils.format("{}/tree.html", htmlPath); + } + else if (template.contains("add.html.vm")) + { + fileName = StringUtils.format("{}/add.html", htmlPath); + } + else if (template.contains("edit.html.vm")) + { + fileName = StringUtils.format("{}/edit.html", htmlPath); + } + else if (template.contains("sql.vm")) + { + fileName = businessName + "Menu.sql"; + } + return fileName; + } + + /** + * 获取项目文件路径 + * + * @return 路径 + */ + public static String getProjectPath() + { + String packageName = GenConfig.getPackageName(); + StringBuffer projectPath = new StringBuffer(); + projectPath.append("main/java/"); + projectPath.append(packageName.replace(".", "/")); + projectPath.append("/"); + return projectPath.toString(); + } + + /** + * 获取包前缀 + * + * @param packageName 包名称 + * @return 包前缀名称 + */ + public static String getPackagePrefix(String packageName) + { + int lastIndex = packageName.lastIndexOf("."); + return StringUtils.substring(packageName, 0, lastIndex); + } + + /** + * 根据列类型获取导入包 + * + * @param genTable 业务表对象 + * @return 返回需要导入的包列表 + */ + public static HashSet getImportList(GenTable genTable) + { + List columns = genTable.getColumns(); + GenTable subGenTable = genTable.getSubTable(); + HashSet importList = new HashSet(); + if (StringUtils.isNotNull(subGenTable)) + { + importList.add("java.util.List"); + } + for (GenTableColumn column : columns) + { + if (!column.isSuperColumn() && GenConstants.TYPE_DATE.equals(column.getJavaType())) + { + importList.add("java.util.Date"); + importList.add("com.fasterxml.jackson.annotation.JsonFormat"); + } + else if (!column.isSuperColumn() && GenConstants.TYPE_BIGDECIMAL.equals(column.getJavaType())) + { + importList.add("java.math.BigDecimal"); + } + } + return importList; + } + + /** + * 获取权限前缀 + * + * @param moduleName 模块名称 + * @param businessName 业务名称 + * @return 返回权限前缀 + */ + public static String getPermissionPrefix(String moduleName, String businessName) + { + return StringUtils.format("{}:{}", moduleName, businessName); + } + + /** + * 获取上级菜单ID字段 + * + * @param paramsObj 生成其他选项 + * @return 上级菜单ID字段 + */ + public static String getParentMenuId(JSONObject paramsObj) + { + if (StringUtils.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.PARENT_MENU_ID) + && StringUtils.isNotEmpty(paramsObj.getString(GenConstants.PARENT_MENU_ID))) + { + return paramsObj.getString(GenConstants.PARENT_MENU_ID); + } + return DEFAULT_PARENT_MENU_ID; + } + + /** + * 获取树编码 + * + * @param paramsObj 生成其他选项 + * @return 树编码 + */ + public static String getTreecode(JSONObject paramsObj) + { + if (paramsObj.containsKey(GenConstants.TREE_CODE)) + { + return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_CODE)); + } + return StringUtils.EMPTY; + } + + /** + * 获取树父编码 + * + * @param paramsObj 生成其他选项 + * @return 树父编码 + */ + public static String getTreeParentCode(JSONObject paramsObj) + { + if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) + { + return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_PARENT_CODE)); + } + return StringUtils.EMPTY; + } + + /** + * 获取树名称 + * + * @param paramsObj 生成其他选项 + * @return 树名称 + */ + public static String getTreeName(JSONObject paramsObj) + { + if (paramsObj.containsKey(GenConstants.TREE_NAME)) + { + return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_NAME)); + } + return StringUtils.EMPTY; + } + + /** + * 获取需要在哪一列上面显示展开按钮 + * + * @param genTable 业务表对象 + * @return 展开按钮列序号 + */ + public static int getExpandColumn(GenTable genTable) + { + String options = genTable.getOptions(); + JSONObject paramsObj = JSONObject.parseObject(options); + String treeName = paramsObj.getString(GenConstants.TREE_NAME); + int num = 0; + for (GenTableColumn column : genTable.getColumns()) + { + if (column.isList()) + { + num++; + String columnName = column.getColumnName(); + if (columnName.equals(treeName)) + { + break; + } + } + } + return num; + } +} diff --git a/ruoyi-generator/src/main/resources/generator.yml b/ruoyi-generator/src/main/resources/generator.yml new file mode 100644 index 0000000..8e3224e --- /dev/null +++ b/ruoyi-generator/src/main/resources/generator.yml @@ -0,0 +1,11 @@ + +# 代码生成 +gen: + # 作者 + author: ruoyi + # 默认生成包路径 system 需改成自己的模块名称 如 system monitor tool + packageName: com.ruoyi.system + # 自动去除表前缀,默认是false + autoRemovePre: false + # 表前缀(生成类名不会包含表前缀,多个用逗号分隔) + tablePrefix: sys_ \ No newline at end of file diff --git a/ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml b/ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml new file mode 100644 index 0000000..86f62de --- /dev/null +++ b/ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select column_id, table_id, column_name, column_comment, column_type, java_type, java_field, is_pk, is_increment, is_required, is_insert, is_edit, is_list, is_query, query_type, html_type, dict_type, sort, create_by, create_time, update_by, update_time from gen_table_column + + + + + + + + insert into gen_table_column ( + table_id, + column_name, + column_comment, + column_type, + java_type, + java_field, + is_pk, + is_increment, + is_required, + is_insert, + is_edit, + is_list, + is_query, + query_type, + html_type, + dict_type, + sort, + create_by, + create_time + )values( + #{tableId}, + #{columnName}, + #{columnComment}, + #{columnType}, + #{javaType}, + #{javaField}, + #{isPk}, + #{isIncrement}, + #{isRequired}, + #{isInsert}, + #{isEdit}, + #{isList}, + #{isQuery}, + #{queryType}, + #{htmlType}, + #{dictType}, + #{sort}, + #{createBy}, + sysdate() + ) + + + + update gen_table_column + + column_comment = #{columnComment}, + java_type = #{javaType}, + java_field = #{javaField}, + is_insert = #{isInsert}, + is_edit = #{isEdit}, + is_list = #{isList}, + is_query = #{isQuery}, + is_required = #{isRequired}, + query_type = #{queryType}, + html_type = #{htmlType}, + dict_type = #{dictType}, + sort = #{sort}, + update_by = #{updateBy}, + update_time = sysdate() + + where column_id = #{columnId} + + + + delete from gen_table_column where table_id in + + #{tableId} + + + + + delete from gen_table_column where column_id in + + #{item.columnId} + + + + \ No newline at end of file diff --git a/ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml b/ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml new file mode 100644 index 0000000..bc7cf52 --- /dev/null +++ b/ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml @@ -0,0 +1,194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select table_id, table_name, table_comment, sub_table_name, sub_table_fk_name, class_name, tpl_category, package_name, module_name, business_name, function_name, function_author, gen_type, gen_path, options, create_by, create_time, update_by, update_time, remark from gen_table + + + + + + + + + + + + + + + + + + insert into gen_table ( + table_name, + table_comment, + class_name, + tpl_category, + package_name, + module_name, + business_name, + function_name, + function_author, + gen_type, + gen_path, + remark, + create_by, + create_time + )values( + #{tableName}, + #{tableComment}, + #{className}, + #{tplCategory}, + #{packageName}, + #{moduleName}, + #{businessName}, + #{functionName}, + #{functionAuthor}, + #{genType}, + #{genPath}, + #{remark}, + #{createBy}, + sysdate() + ) + + + + ${sql} + + + + update gen_table + + table_name = #{tableName}, + table_comment = #{tableComment}, + sub_table_name = #{subTableName}, + sub_table_fk_name = #{subTableFkName}, + class_name = #{className}, + function_author = #{functionAuthor}, + gen_type = #{genType}, + gen_path = #{genPath}, + tpl_category = #{tplCategory}, + package_name = #{packageName}, + module_name = #{moduleName}, + business_name = #{businessName}, + function_name = #{functionName}, + options = #{options}, + update_by = #{updateBy}, + remark = #{remark}, + update_time = sysdate() + + where table_id = #{tableId} + + + + delete from gen_table where table_id in + + #{tableId} + + + + \ No newline at end of file diff --git a/ruoyi-generator/src/main/resources/templates/tool/gen/createTable.html b/ruoyi-generator/src/main/resources/templates/tool/gen/createTable.html new file mode 100644 index 0000000..c9b9dcf --- /dev/null +++ b/ruoyi-generator/src/main/resources/templates/tool/gen/createTable.html @@ -0,0 +1,30 @@ + + + + + + +

    + +
    + +
    +
    + + + + + \ No newline at end of file diff --git a/ruoyi-generator/src/main/resources/templates/tool/gen/edit.html b/ruoyi-generator/src/main/resources/templates/tool/gen/edit.html new file mode 100644 index 0000000..f988635 --- /dev/null +++ b/ruoyi-generator/src/main/resources/templates/tool/gen/edit.html @@ -0,0 +1,608 @@ + + + + + + + + +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + diff --git a/ruoyi-generator/src/main/resources/templates/tool/gen/gen.html b/ruoyi-generator/src/main/resources/templates/tool/gen/gen.html new file mode 100644 index 0000000..bf980d9 --- /dev/null +++ b/ruoyi-generator/src/main/resources/templates/tool/gen/gen.html @@ -0,0 +1,219 @@ + + + + + + +
    +
    +
    +
    +
    +
      +
    • + 表名称: +
    • +
    • + 表描述: +
    • +
    • + + + - + +
    • +
    • +  搜索 +  重置 +
    • +
    +
    +
    +
    + + + +
    +
    +
    +
    +
    + + + + + + \ No newline at end of file diff --git a/ruoyi-generator/src/main/resources/templates/tool/gen/importTable.html b/ruoyi-generator/src/main/resources/templates/tool/gen/importTable.html new file mode 100644 index 0000000..91562e7 --- /dev/null +++ b/ruoyi-generator/src/main/resources/templates/tool/gen/importTable.html @@ -0,0 +1,95 @@ + + + + + + +
    +
    +
    +
    +
    + +
    +
    +
    + +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/ruoyi-generator/src/main/resources/vm/html/add.html.vm b/ruoyi-generator/src/main/resources/vm/html/add.html.vm new file mode 100644 index 0000000..1638244 --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/html/add.html.vm @@ -0,0 +1,379 @@ + + + + +#foreach($column in $columns) +#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "datetime") + +#break +#end +#end +#foreach($column in $columns) +#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "upload") + +#break +#end +#end +#foreach($column in $columns) +#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "summernote") + +#break +#end +#end + + +
    +
    +#if($table.sub) +

    ${functionName}信息

    +#end +#foreach($column in $columns) +#set($field=$column.javaField) +#if($column.insert && !$column.pk) +#if(($column.usableColumn) || (!$column.superColumn)) +#set($parentheseIndex=$column.columnComment.indexOf("(")) +#if($parentheseIndex != -1) +#set($comment=$column.columnComment.substring(0, $parentheseIndex)) +#else +#set($comment=$column.columnComment) +#end +#set($dictType=$column.dictType) +#if("" != $treeParentCode && $column.javaField == $treeParentCode) +
    + +
    +
    +#set($BusinessName=$businessName.substring(0,1).toUpperCase() + ${businessName.substring(1)}) +#set($treeId = "${className}?.${treeCode}") + + + +
    +
    +
    +#elseif($column.htmlType == "input") +
    + +
    + +
    +
    +#elseif($column.htmlType == "upload") +
    + +
    + +
    + +
    +
    +
    +#elseif($column.htmlType == "summernote") +
    + +
    + +
    +
    +
    +#elseif($column.htmlType == "select" && "" != $dictType) +
    + +
    + +
    +
    +#elseif($column.htmlType == "select" && $dictType) +
    + +
    + + 代码生成请选择字典属性 +
    +
    +#elseif($column.htmlType == "checkbox" && "" != $dictType) +
    + +
    + +
    +
    +#elseif($column.htmlType == "checkbox" && $dictType) +
    + +
    + + 代码生成请选择字典属性 +
    +
    +#elseif($column.htmlType == "radio" && "" != $dictType) +
    + +
    +
    + + +
    +
    +
    +#elseif($column.htmlType == "radio" && $dictType) +
    + +
    +
    + + +
    + 代码生成请选择字典属性 +
    +
    +#elseif($column.htmlType == "datetime") +
    + +
    +
    + + +
    +
    +
    +#elseif($column.htmlType == "textarea") +
    + +
    + +
    +
    +#end +#end +#end +#end +#if($table.sub) +

    ${subTable.functionName}信息

    +
    +
    + + +
    +
    +
    +
    +
    +#end +
    +
    + +#foreach($column in $columns) +#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "datetime") + +#break +#end +#end +#foreach($column in $columns) +#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "upload") + +#break +#end +#end +#foreach($column in $columns) +#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "summernote") + +#break +#end +#end + + + \ No newline at end of file diff --git a/ruoyi-generator/src/main/resources/vm/html/edit.html.vm b/ruoyi-generator/src/main/resources/vm/html/edit.html.vm new file mode 100644 index 0000000..aebc6a9 --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/html/edit.html.vm @@ -0,0 +1,391 @@ + + + + +#foreach($column in $columns) +#if($column.edit && !$column.superColumn && !$column.pk && $column.htmlType == "datetime") + +#break +#end +#end +#foreach($column in $columns) +#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "upload") + +#break +#end +#end +#foreach($column in $columns) +#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "summernote") + +#break +#end +#end + + +
    +
    +#if($table.sub) +

    ${functionName}信息

    +#end + +#foreach($column in $columns) +#if($column.edit && !$column.pk) +#if(($column.usableColumn) || (!$column.superColumn)) +#set($parentheseIndex=$column.columnComment.indexOf("(")) +#if($parentheseIndex != -1) +#set($comment=$column.columnComment.substring(0, $parentheseIndex)) +#else +#set($comment=$column.columnComment) +#end +#set($field=$column.javaField) +#set($dictType=$column.dictType) +#if("" != $treeParentCode && $column.javaField == $treeParentCode) +
    + +
    +
    +#set($BusinessName=$businessName.substring(0,1).toUpperCase() + ${businessName.substring(1)}) + + + +
    +
    +
    +#elseif($column.htmlType == "input") +
    + +
    + +
    +
    +#elseif($column.htmlType == "upload") +
    + +
    + +
    + +
    +
    +
    +#elseif($column.htmlType == "summernote") +
    + +
    + +
    +
    +
    +#elseif($column.htmlType == "select" && "" != $dictType) +
    + +
    + +
    +
    +#elseif($column.htmlType == "select" && $dictType) +
    + +
    + + 代码生成请选择字典属性 +
    +
    +#elseif($column.htmlType == "checkbox" && "" != $dictType) +
    + +
    + +
    +
    +#elseif($column.htmlType == "checkbox" && $dictType) +
    + +
    + + 代码生成请选择字典属性 +
    +
    +#elseif($column.htmlType == "radio" && "" != $dictType) +
    + +
    +
    + + +
    +
    +
    +#elseif($column.htmlType == "radio" && $dictType) +
    + +
    +
    + + +
    + 代码生成请选择字典属性 +
    +
    +#elseif($column.htmlType == "datetime") +
    + +
    +
    + + +
    +
    +
    +#elseif($column.htmlType == "textarea") +
    + +
    + +
    +
    +#end +#end +#end +#end +#if($table.sub) +

    ${subTable.functionName}信息

    +
    +
    + + +
    +
    +
    +
    +
    +#end +
    +
    + +#foreach($column in $columns) +#if($column.edit && !$column.superColumn && !$column.pk && $column.htmlType == "datetime") + +#break +#end +#end +#foreach($column in $columns) +#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "upload") + +#break +#end +#end +#foreach($column in $columns) +#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "summernote") + +#break +#end +#end + + + \ No newline at end of file diff --git a/ruoyi-generator/src/main/resources/vm/html/list-tree.html.vm b/ruoyi-generator/src/main/resources/vm/html/list-tree.html.vm new file mode 100644 index 0000000..9e3734e --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/html/list-tree.html.vm @@ -0,0 +1,155 @@ + + + + + + +
    +
    +
    +
    +
    +
      +#foreach($column in $columns) +#if($column.query) +#set($dictType=$column.dictType) +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) +#set($parentheseIndex=$column.columnComment.indexOf("(")) +#if($parentheseIndex != -1) +#set($comment=$column.columnComment.substring(0, $parentheseIndex)) +#else +#set($comment=$column.columnComment) +#end +#if($column.htmlType == "input") +
    • + + +
    • +#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType) +
    • + + +
    • +#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType) +
    • + + +
    • +#elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN") +
    • + + +
    • +#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN") +
    • + + + - + +
    • +#end +#end +#end +
    • +  搜索 +  重置 +
    • +
    +
    +
    +
    + + +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/ruoyi-generator/src/main/resources/vm/html/list.html.vm b/ruoyi-generator/src/main/resources/vm/html/list.html.vm new file mode 100644 index 0000000..f2e9c5a --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/html/list.html.vm @@ -0,0 +1,154 @@ + + + + + + +
    +
    +
    +
    +
    +
      +#foreach($column in $columns) +#if($column.query) +#set($dictType=$column.dictType) +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) +#set($parentheseIndex=$column.columnComment.indexOf("(")) +#if($parentheseIndex != -1) +#set($comment=$column.columnComment.substring(0, $parentheseIndex)) +#else +#set($comment=$column.columnComment) +#end +#if($column.htmlType == "input") +
    • + + +
    • +#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType) +
    • + + +
    • +#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType) +
    • + + +
    • +#elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN") +
    • + + +
    • +#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN") +
    • + + + - + +
    • +#end +#end +#end +
    • +  搜索 +  重置 +
    • +
    +
    +
    +
    + + +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/ruoyi-generator/src/main/resources/vm/html/tree.html.vm b/ruoyi-generator/src/main/resources/vm/html/tree.html.vm new file mode 100644 index 0000000..80364ad --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/html/tree.html.vm @@ -0,0 +1,51 @@ + + + + + + + + +#set($treeId = "${className}?." + $treeCode) +#set($treeName = "${className}?." + $treeName) + + +
    + + +
    + +
    + 展开 / + 折叠 +
    +
    +
    + + + + + \ No newline at end of file diff --git a/ruoyi-generator/src/main/resources/vm/java/controller.java.vm b/ruoyi-generator/src/main/resources/vm/java/controller.java.vm new file mode 100644 index 0000000..e826fd4 --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/java/controller.java.vm @@ -0,0 +1,202 @@ +package ${packageName}.controller; + +import java.util.List; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.enums.BusinessType; +import ${packageName}.domain.${ClassName}; +import ${packageName}.service.I${ClassName}Service; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.utils.poi.ExcelUtil; +#if($table.crud || $table.sub) +import com.ruoyi.common.core.page.TableDataInfo; +#elseif($table.tree) +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.core.domain.Ztree; +#end + +/** + * ${functionName}Controller + * + * @author ${author} + * @date ${datetime} + */ +@Controller +@RequestMapping("/${moduleName}/${businessName}") +public class ${ClassName}Controller extends BaseController +{ + private String prefix = "${moduleName}/${businessName}"; + + @Autowired + private I${ClassName}Service ${className}Service; + + @RequiresPermissions("${permissionPrefix}:view") + @GetMapping() + public String ${businessName}() + { + return prefix + "/${businessName}"; + } + +#if($table.crud || $table.sub) + /** + * 查询${functionName}列表 + */ + @RequiresPermissions("${permissionPrefix}:list") + @PostMapping("/list") + @ResponseBody + public TableDataInfo list(${ClassName} ${className}) + { + startPage(); + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + return getDataTable(list); + } +#elseif($table.tree) + /** + * 查询${functionName}树列表 + */ + @RequiresPermissions("${permissionPrefix}:list") + @PostMapping("/list") + @ResponseBody + public List<${ClassName}> list(${ClassName} ${className}) + { + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + return list; + } +#end + + /** + * 导出${functionName}列表 + */ + @RequiresPermissions("${permissionPrefix}:export") + @Log(title = "${functionName}", businessType = BusinessType.EXPORT) + @PostMapping("/export") + @ResponseBody + public AjaxResult export(${ClassName} ${className}) + { + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + ExcelUtil<${ClassName}> util = new ExcelUtil<${ClassName}>(${ClassName}.class); + return util.exportExcel(list, "${functionName}数据"); + } + +#if($table.crud || $table.sub) + /** + * 新增${functionName} + */ + @GetMapping("/add") + public String add() + { + return prefix + "/add"; + } +#elseif($table.tree) + /** + * 新增${functionName} + */ + @GetMapping(value = { "/add/{${pkColumn.javaField}}", "/add/" }) + public String add(@PathVariable(value = "${pkColumn.javaField}", required = false) Long ${pkColumn.javaField}, ModelMap mmap) + { + if (StringUtils.isNotNull(${pkColumn.javaField})) + { + mmap.put("${className}", ${className}Service.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField})); + } + return prefix + "/add"; + } +#end + + /** + * 新增保存${functionName} + */ + @RequiresPermissions("${permissionPrefix}:add") + @Log(title = "${functionName}", businessType = BusinessType.INSERT) + @PostMapping("/add") + @ResponseBody + public AjaxResult addSave(${ClassName} ${className}) + { + return toAjax(${className}Service.insert${ClassName}(${className})); + } + + /** + * 修改${functionName} + */ + @RequiresPermissions("${permissionPrefix}:edit") + @GetMapping("/edit/{${pkColumn.javaField}}") + public String edit(@PathVariable("${pkColumn.javaField}") ${pkColumn.javaType} ${pkColumn.javaField}, ModelMap mmap) + { + ${ClassName} ${className} = ${className}Service.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}); + mmap.put("${className}", ${className}); + return prefix + "/edit"; + } + + /** + * 修改保存${functionName} + */ + @RequiresPermissions("${permissionPrefix}:edit") + @Log(title = "${functionName}", businessType = BusinessType.UPDATE) + @PostMapping("/edit") + @ResponseBody + public AjaxResult editSave(${ClassName} ${className}) + { + return toAjax(${className}Service.update${ClassName}(${className})); + } + +#if($table.crud || $table.sub) + /** + * 删除${functionName} + */ + @RequiresPermissions("${permissionPrefix}:remove") + @Log(title = "${functionName}", businessType = BusinessType.DELETE) + @PostMapping( "/remove") + @ResponseBody + public AjaxResult remove(String ids) + { + return toAjax(${className}Service.delete${ClassName}By${pkColumn.capJavaField}s(ids)); + } +#elseif($table.tree) + /** + * 删除 + */ + @RequiresPermissions("${permissionPrefix}:remove") + @Log(title = "${functionName}", businessType = BusinessType.DELETE) + @GetMapping("/remove/{${pkColumn.javaField}}") + @ResponseBody + public AjaxResult remove(@PathVariable("${pkColumn.javaField}") ${pkColumn.javaType} ${pkColumn.javaField}) + { + return toAjax(${className}Service.delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField})); + } +#end +#if($table.tree) + + /** + * 选择${functionName}树 + */ +#set($BusinessName=$businessName.substring(0,1).toUpperCase() + ${businessName.substring(1)}) + @GetMapping(value = { "/select${BusinessName}Tree/{${pkColumn.javaField}}", "/select${BusinessName}Tree/" }) + public String select${BusinessName}Tree(@PathVariable(value = "${pkColumn.javaField}", required = false) Long ${pkColumn.javaField}, ModelMap mmap) + { + if (StringUtils.isNotNull(${pkColumn.javaField})) + { + mmap.put("${className}", ${className}Service.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField})); + } + return prefix + "/tree"; + } + + /** + * 加载${functionName}树列表 + */ + @GetMapping("/treeData") + @ResponseBody + public List treeData() + { + List ztrees = ${className}Service.select${ClassName}Tree(); + return ztrees; + } +#end +} diff --git a/ruoyi-generator/src/main/resources/vm/java/domain.java.vm b/ruoyi-generator/src/main/resources/vm/java/domain.java.vm new file mode 100644 index 0000000..bd51c17 --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/java/domain.java.vm @@ -0,0 +1,105 @@ +package ${packageName}.domain; + +#foreach ($import in $importList) +import ${import}; +#end +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +#if($table.crud || $table.sub) +import com.ruoyi.common.core.domain.BaseEntity; +#elseif($table.tree) +import com.ruoyi.common.core.domain.TreeEntity; +#end + +/** + * ${functionName}对象 ${tableName} + * + * @author ${author} + * @date ${datetime} + */ +#if($table.crud || $table.sub) +#set($Entity="BaseEntity") +#elseif($table.tree) +#set($Entity="TreeEntity") +#end +public class ${ClassName} extends ${Entity} +{ + private static final long serialVersionUID = 1L; + +#foreach ($column in $columns) +#if(!$table.isSuperColumn($column.javaField)) + /** $column.columnComment */ +#if($column.list) +#set($parentheseIndex=$column.columnComment.indexOf("(")) +#if($parentheseIndex != -1) +#set($comment=$column.columnComment.substring(0, $parentheseIndex)) +#else +#set($comment=$column.columnComment) +#end +#if($parentheseIndex != -1) + @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()") +#elseif($column.javaType == 'Date') + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd") +#else + @Excel(name = "${comment}") +#end +#end + private $column.javaType $column.javaField; + +#end +#end +#if($table.sub) + /** $table.subTable.functionName信息 */ + private List<${subClassName}> ${subclassName}List; + +#end +#foreach ($column in $columns) +#if(!$table.isSuperColumn($column.javaField)) +#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) +#set($AttrName=$column.javaField) +#else +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) +#end + public void set${AttrName}($column.javaType $column.javaField) + { + this.$column.javaField = $column.javaField; + } + + public $column.javaType get${AttrName}() + { + return $column.javaField; + } +#end +#end + +#if($table.sub) + public List<${subClassName}> get${subClassName}List() + { + return ${subclassName}List; + } + + public void set${subClassName}List(List<${subClassName}> ${subclassName}List) + { + this.${subclassName}List = ${subclassName}List; + } + +#end + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) +#foreach ($column in $columns) +#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) +#set($AttrName=$column.javaField) +#else +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) +#end + .append("${column.javaField}", get${AttrName}()) +#end +#if($table.sub) + .append("${subclassName}List", get${subClassName}List()) +#end + .toString(); + } +} diff --git a/ruoyi-generator/src/main/resources/vm/java/mapper.java.vm b/ruoyi-generator/src/main/resources/vm/java/mapper.java.vm new file mode 100644 index 0000000..ebee538 --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/java/mapper.java.vm @@ -0,0 +1,91 @@ +package ${packageName}.mapper; + +import java.util.List; +import ${packageName}.domain.${ClassName}; +#if($table.sub) +import ${packageName}.domain.${subClassName}; +#end + +/** + * ${functionName}Mapper接口 + * + * @author ${author} + * @date ${datetime} + */ +public interface ${ClassName}Mapper +{ + /** + * 查询${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return ${functionName} + */ + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); + + /** + * 查询${functionName}列表 + * + * @param ${className} ${functionName} + * @return ${functionName}集合 + */ + public List<${ClassName}> select${ClassName}List(${ClassName} ${className}); + + /** + * 新增${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int insert${ClassName}(${ClassName} ${className}); + + /** + * 修改${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int update${ClassName}(${ClassName} ${className}); + + /** + * 删除${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); + + /** + * 批量删除${functionName} + * + * @param ${pkColumn.javaField}s 需要删除的数据主键集合 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}s(String[] ${pkColumn.javaField}s); +#if($table.sub) + + /** + * 批量删除${subTable.functionName} + * + * @param ${pkColumn.javaField}s 需要删除的数据主键集合 + * @return 结果 + */ + public int delete${subClassName}By${subTableFkClassName}s(String[] ${pkColumn.javaField}s); + + /** + * 批量新增${subTable.functionName} + * + * @param ${subclassName}List ${subTable.functionName}列表 + * @return 结果 + */ + public int batch${subClassName}(List<${subClassName}> ${subclassName}List); + + + /** + * 通过${functionName}主键删除${subTable.functionName}信息 + * + * @param ${pkColumn.javaField} ${functionName}ID + * @return 结果 + */ + public int delete${subClassName}By${subTableFkClassName}(${pkColumn.javaType} ${pkColumn.javaField}); +#end +} diff --git a/ruoyi-generator/src/main/resources/vm/java/service.java.vm b/ruoyi-generator/src/main/resources/vm/java/service.java.vm new file mode 100644 index 0000000..765c9d6 --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/java/service.java.vm @@ -0,0 +1,73 @@ +package ${packageName}.service; + +import java.util.List; +import ${packageName}.domain.${ClassName}; +#if($table.tree) +import com.ruoyi.common.core.domain.Ztree; +#end + +/** + * ${functionName}Service接口 + * + * @author ${author} + * @date ${datetime} + */ +public interface I${ClassName}Service +{ + /** + * 查询${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return ${functionName} + */ + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); + + /** + * 查询${functionName}列表 + * + * @param ${className} ${functionName} + * @return ${functionName}集合 + */ + public List<${ClassName}> select${ClassName}List(${ClassName} ${className}); + + /** + * 新增${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int insert${ClassName}(${ClassName} ${className}); + + /** + * 修改${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int update${ClassName}(${ClassName} ${className}); + + /** + * 批量删除${functionName} + * + * @param ${pkColumn.javaField}s 需要删除的${functionName}主键集合 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}s(String ${pkColumn.javaField}s); + + /** + * 删除${functionName}信息 + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); +#if($table.tree) + + /** + * 查询${functionName}树列表 + * + * @return 所有${functionName}信息 + */ + public List select${ClassName}Tree(); +#end +} diff --git a/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm b/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm new file mode 100644 index 0000000..242b1c7 --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm @@ -0,0 +1,213 @@ +package ${packageName}.service.impl; + +import java.util.List; +#if($table.tree) +import java.util.ArrayList; +import com.ruoyi.common.core.domain.Ztree; +#end +#foreach ($column in $columns) +#if($column.javaField == 'createTime' || $column.javaField == 'updateTime') +import com.ruoyi.common.utils.DateUtils; +#break +#end +#end +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +#if($table.sub) +import java.util.ArrayList; +import com.ruoyi.common.utils.StringUtils; +import org.springframework.transaction.annotation.Transactional; +import ${packageName}.domain.${subClassName}; +#end +import ${packageName}.mapper.${ClassName}Mapper; +import ${packageName}.domain.${ClassName}; +import ${packageName}.service.I${ClassName}Service; +import com.ruoyi.common.core.text.Convert; + +/** + * ${functionName}Service业务层处理 + * + * @author ${author} + * @date ${datetime} + */ +@Service +public class ${ClassName}ServiceImpl implements I${ClassName}Service +{ + @Autowired + private ${ClassName}Mapper ${className}Mapper; + + /** + * 查询${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return ${functionName} + */ + @Override + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) + { + return ${className}Mapper.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}); + } + + /** + * 查询${functionName}列表 + * + * @param ${className} ${functionName} + * @return ${functionName} + */ + @Override + public List<${ClassName}> select${ClassName}List(${ClassName} ${className}) + { + return ${className}Mapper.select${ClassName}List(${className}); + } + + /** + * 新增${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ +#if($table.sub) + @Transactional +#end + @Override + public int insert${ClassName}(${ClassName} ${className}) + { +#foreach ($column in $columns) +#if($column.javaField == 'createTime') + ${className}.setCreateTime(DateUtils.getNowDate()); +#end +#end +#if($table.sub) + int rows = ${className}Mapper.insert${ClassName}(${className}); + insert${subClassName}(${className}); + return rows; +#else + return ${className}Mapper.insert${ClassName}(${className}); +#end + } + + /** + * 修改${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ +#if($table.sub) + @Transactional +#end + @Override + public int update${ClassName}(${ClassName} ${className}) + { +#foreach ($column in $columns) +#if($column.javaField == 'updateTime') + ${className}.setUpdateTime(DateUtils.getNowDate()); +#end +#end +#if($table.sub) + ${className}Mapper.delete${subClassName}By${subTableFkClassName}(${className}.get${pkColumn.capJavaField}()); + insert${subClassName}(${className}); +#end + return ${className}Mapper.update${ClassName}(${className}); + } + + /** + * 批量删除${functionName} + * + * @param ${pkColumn.javaField}s 需要删除的${functionName}主键 + * @return 结果 + */ +#if($table.sub) + @Transactional +#end + @Override + public int delete${ClassName}By${pkColumn.capJavaField}s(String ${pkColumn.javaField}s) + { +#if($table.sub) + ${className}Mapper.delete${subClassName}By${subTableFkClassName}s(Convert.toStrArray(${pkColumn.javaField}s)); +#end + return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}s(Convert.toStrArray(${pkColumn.javaField}s)); + } + + /** + * 删除${functionName}信息 + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return 结果 + */ +#if($table.sub) + @Transactional +#end + @Override + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) + { +#if($table.sub) + ${className}Mapper.delete${subClassName}By${subTableFkClassName}(${pkColumn.javaField}); +#end + return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}); + } +#if($table.tree) + + /** + * 查询${functionName}树列表 + * + * @return 所有${functionName}信息 + */ + @Override + public List select${ClassName}Tree() + { + List<${ClassName}> ${className}List = ${className}Mapper.select${ClassName}List(new ${ClassName}()); + List ztrees = new ArrayList(); + for (${ClassName} ${className} : ${className}List) + { + Ztree ztree = new Ztree(); +#if($treeCode.length() > 2 && $treeCode.substring(1,2).matches("[A-Z]")) +#set($TreeCode=$treeCode) +#else +#set($TreeCode=$treeCode.substring(0,1).toUpperCase() + ${treeCode.substring(1)}) +#end +#if($treeParentCode.length() > 2 && $treeParentCode.substring(1,2).matches("[A-Z]")) +#set($TreeParentCode=$treeParentCode) +#else +#set($TreeParentCode=$treeParentCode.substring(0,1).toUpperCase() + ${treeParentCode.substring(1)}) +#end +#if($treeName.length() > 2 && $treeName.substring(1,2).matches("[A-Z]")) +#set($TreeName=$treeName) +#else +#set($TreeName=$treeName.substring(0,1).toUpperCase() + ${treeName.substring(1)}) +#end + ztree.setId(${className}.get${TreeCode}()); + ztree.setpId(${className}.get${TreeParentCode}()); + ztree.setName(${className}.get${TreeName}()); + ztree.setTitle(${className}.get${TreeName}()); + ztrees.add(ztree); + } + return ztrees; + } +#end +#if($table.sub) + + /** + * 新增${subTable.functionName}信息 + * + * @param ${className} ${functionName}对象 + */ + public void insert${subClassName}(${ClassName} ${className}) + { + List<${subClassName}> ${subclassName}List = ${className}.get${subClassName}List(); + ${pkColumn.javaType} ${pkColumn.javaField} = ${className}.get${pkColumn.capJavaField}(); + if (StringUtils.isNotNull(${subclassName}List)) + { + List<${subClassName}> list = new ArrayList<${subClassName}>(); + for (${subClassName} ${subclassName} : ${subclassName}List) + { + ${subclassName}.set${subTableFkClassName}(${pkColumn.javaField}); + list.add(${subclassName}); + } + if (list.size() > 0) + { + ${className}Mapper.batch${subClassName}(list); + } + } + } +#end +} diff --git a/ruoyi-generator/src/main/resources/vm/java/sub-domain.java.vm b/ruoyi-generator/src/main/resources/vm/java/sub-domain.java.vm new file mode 100644 index 0000000..a3f53eb --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/java/sub-domain.java.vm @@ -0,0 +1,76 @@ +package ${packageName}.domain; + +#foreach ($import in $subImportList) +import ${import}; +#end +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * ${subTable.functionName}对象 ${subTableName} + * + * @author ${author} + * @date ${datetime} + */ +public class ${subClassName} extends BaseEntity +{ + private static final long serialVersionUID = 1L; + +#foreach ($column in $subTable.columns) +#if(!$table.isSuperColumn($column.javaField)) + /** $column.columnComment */ +#if($column.list) +#set($parentheseIndex=$column.columnComment.indexOf("(")) +#if($parentheseIndex != -1) +#set($comment=$column.columnComment.substring(0, $parentheseIndex)) +#else +#set($comment=$column.columnComment) +#end +#if($parentheseIndex != -1) + @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()") +#elseif($column.javaType == 'Date') + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd") +#else + @Excel(name = "${comment}") +#end +#end + private $column.javaType $column.javaField; + +#end +#end +#foreach ($column in $subTable.columns) +#if(!$table.isSuperColumn($column.javaField)) +#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) +#set($AttrName=$column.javaField) +#else +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) +#end + public void set${AttrName}($column.javaType $column.javaField) + { + this.$column.javaField = $column.javaField; + } + + public $column.javaType get${AttrName}() + { + return $column.javaField; + } +#end +#end + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) +#foreach ($column in $subTable.columns) +#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) +#set($AttrName=$column.javaField) +#else +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) +#end + .append("${column.javaField}", get${AttrName}()) +#end + .toString(); + } +} diff --git a/ruoyi-generator/src/main/resources/vm/sql/sql.vm b/ruoyi-generator/src/main/resources/vm/sql/sql.vm new file mode 100644 index 0000000..d5121af --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/sql/sql.vm @@ -0,0 +1,22 @@ +-- 菜单 SQL +insert into sys_menu (menu_name, parent_id, order_num, url, menu_type, visible, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}', '${parentMenuId}', '1', '/${moduleName}/${businessName}', 'C', '0', '${permissionPrefix}:view', '#', 'admin', sysdate(), '', null, '${functionName}菜单'); + +-- 按钮父菜单ID +SELECT @parentId := LAST_INSERT_ID(); + +-- 按钮 SQL +insert into sys_menu (menu_name, parent_id, order_num, url, menu_type, visible, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}查询', @parentId, '1', '#', 'F', '0', '${permissionPrefix}:list', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, url, menu_type, visible, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}新增', @parentId, '2', '#', 'F', '0', '${permissionPrefix}:add', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, url, menu_type, visible, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}修改', @parentId, '3', '#', 'F', '0', '${permissionPrefix}:edit', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, url, menu_type, visible, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}删除', @parentId, '4', '#', 'F', '0', '${permissionPrefix}:remove', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, url, menu_type, visible, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}导出', @parentId, '5', '#', 'F', '0', '${permissionPrefix}:export', '#', 'admin', sysdate(), '', null, ''); diff --git a/ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm b/ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm new file mode 100644 index 0000000..9de59b5 --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm @@ -0,0 +1,147 @@ + + + + + +#foreach ($column in $columns) + +#end +#if($table.tree) + +#end + +#if($table.sub) + + + + + + +#foreach ($column in $subTable.columns) + +#end + +#end + + + select#foreach($column in $columns) $column.columnName#if($foreach.count != $columns.size()),#end#end from ${tableName} + + + + + + + + insert into ${tableName} + +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName || !$pkColumn.increment) + $column.columnName, +#end +#end + + +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName || !$pkColumn.increment) + #{$column.javaField}, +#end +#end + + + + + update ${tableName} + +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName) + $column.columnName = #{$column.javaField}, +#end +#end + + where ${pkColumn.columnName} = #{${pkColumn.javaField}} + + + + delete from ${tableName} where ${pkColumn.columnName} = #{${pkColumn.javaField}} + + + + delete from ${tableName} where ${pkColumn.columnName} in + + #{${pkColumn.javaField}} + + +#if($table.sub) + + + delete from ${subTableName} where ${subTableFkName} in + + #{${subTableFkclassName}} + + + + + delete from ${subTableName} where ${subTableFkName} = #{${subTableFkclassName}} + + + + insert into ${subTableName}(#foreach($column in $subTable.columns) $column.columnName#if($foreach.count != $subTable.columns.size()),#end#end) values + + (#foreach($column in $subTable.columns) #{item.$column.javaField}#if($foreach.count != $subTable.columns.size()),#end#end) + + +#end + + \ No newline at end of file diff --git a/ruoyi-quartz/pom.xml b/ruoyi-quartz/pom.xml new file mode 100644 index 0000000..293aaf1 --- /dev/null +++ b/ruoyi-quartz/pom.xml @@ -0,0 +1,40 @@ + + + + ruoyi + com.ruoyi + 4.7.6 + + 4.0.0 + + ruoyi-quartz + + + quartz定时任务 + + + + + + + org.quartz-scheduler + quartz + + + com.mchange + c3p0 + + + + + + + com.ruoyi + ruoyi-common + + + + + \ No newline at end of file diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/config/ScheduleConfig.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/config/ScheduleConfig.java new file mode 100644 index 0000000..3f2cb5f --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/config/ScheduleConfig.java @@ -0,0 +1,57 @@ +//package com.ruoyi.quartz.config; +// +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +//import org.springframework.scheduling.quartz.SchedulerFactoryBean; +//import javax.sql.DataSource; +//import java.util.Properties; +// +///** +// * 定时任务配置(单机部署建议默认走内存,如需集群需要创建qrtz数据库表/打开类注释) +// * +// * @author ruoyi +// */ +//@Configuration +//public class ScheduleConfig +//{ +// @Bean +// public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) +// { +// SchedulerFactoryBean factory = new SchedulerFactoryBean(); +// factory.setDataSource(dataSource); +// +// // quartz参数 +// Properties prop = new Properties(); +// prop.put("org.quartz.scheduler.instanceName", "RuoyiScheduler"); +// prop.put("org.quartz.scheduler.instanceId", "AUTO"); +// // 线程池配置 +// prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool"); +// prop.put("org.quartz.threadPool.threadCount", "20"); +// prop.put("org.quartz.threadPool.threadPriority", "5"); +// // JobStore配置 +// prop.put("org.quartz.jobStore.class", "org.springframework.scheduling.quartz.LocalDataSourceJobStore"); +// // 集群配置 +// prop.put("org.quartz.jobStore.isClustered", "true"); +// prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000"); +// prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1"); +// prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true"); +// +// // sqlserver 启用 +// // prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?"); +// prop.put("org.quartz.jobStore.misfireThreshold", "12000"); +// prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_"); +// factory.setQuartzProperties(prop); +// +// factory.setSchedulerName("RuoyiScheduler"); +// // 延时启动 +// factory.setStartupDelay(1); +// factory.setApplicationContextSchedulerContextKey("applicationContextKey"); +// // 可选,QuartzScheduler +// // 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了 +// factory.setOverwriteExistingJobs(true); +// // 设置自动启动,默认为true +// factory.setAutoStartup(true); +// +// return factory; +// } +//} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobController.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobController.java new file mode 100644 index 0000000..93b4108 --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobController.java @@ -0,0 +1,247 @@ +package com.ruoyi.quartz.controller; + +import java.util.List; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.quartz.SchedulerException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.exception.job.TaskException; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.quartz.domain.SysJob; +import com.ruoyi.quartz.service.ISysJobService; +import com.ruoyi.quartz.util.CronUtils; +import com.ruoyi.quartz.util.ScheduleUtils; + +/** + * 调度任务信息操作处理 + * + * @author ruoyi + */ +@Controller +@RequestMapping("/monitor/job") +public class SysJobController extends BaseController +{ + private String prefix = "monitor/job"; + + @Autowired + private ISysJobService jobService; + + @RequiresPermissions("monitor:job:view") + @GetMapping() + public String job() + { + return prefix + "/job"; + } + + @RequiresPermissions("monitor:job:list") + @PostMapping("/list") + @ResponseBody + public TableDataInfo list(SysJob job) + { + startPage(); + List list = jobService.selectJobList(job); + return getDataTable(list); + } + + @Log(title = "定时任务", businessType = BusinessType.EXPORT) + @RequiresPermissions("monitor:job:export") + @PostMapping("/export") + @ResponseBody + public AjaxResult export(SysJob job) + { + List list = jobService.selectJobList(job); + ExcelUtil util = new ExcelUtil(SysJob.class); + return util.exportExcel(list, "定时任务"); + } + + @Log(title = "定时任务", businessType = BusinessType.DELETE) + @RequiresPermissions("monitor:job:remove") + @PostMapping("/remove") + @ResponseBody + public AjaxResult remove(String ids) throws SchedulerException + { + jobService.deleteJobByIds(ids); + return success(); + } + + @RequiresPermissions("monitor:job:detail") + @GetMapping("/detail/{jobId}") + public String detail(@PathVariable("jobId") Long jobId, ModelMap mmap) + { + mmap.put("name", "job"); + mmap.put("job", jobService.selectJobById(jobId)); + return prefix + "/detail"; + } + + /** + * 任务调度状态修改 + */ + @Log(title = "定时任务", businessType = BusinessType.UPDATE) + @RequiresPermissions("monitor:job:changeStatus") + @PostMapping("/changeStatus") + @ResponseBody + public AjaxResult changeStatus(SysJob job) throws SchedulerException + { + SysJob newJob = jobService.selectJobById(job.getJobId()); + newJob.setStatus(job.getStatus()); + return toAjax(jobService.changeStatus(newJob)); + } + + /** + * 任务调度立即执行一次 + */ + @Log(title = "定时任务", businessType = BusinessType.UPDATE) + @RequiresPermissions("monitor:job:changeStatus") + @PostMapping("/run") + @ResponseBody + public AjaxResult run(SysJob job) throws SchedulerException + { + boolean result = jobService.run(job); + return result ? success() : error("任务不存在或已过期!"); + } + + /** + * 新增调度 + */ + @GetMapping("/add") + public String add() + { + return prefix + "/add"; + } + + /** + * 新增保存调度 + */ + @Log(title = "定时任务", businessType = BusinessType.INSERT) + @RequiresPermissions("monitor:job:add") + @PostMapping("/add") + @ResponseBody + public AjaxResult addSave(@Validated SysJob job) throws SchedulerException, TaskException + { + if (!CronUtils.isValid(job.getCronExpression())) + { + return error("新增任务'" + job.getJobName() + "'失败,Cron表达式不正确"); + } + else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI)) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS })) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap(s)'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS })) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR)) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串存在违规"); + } + else if (!ScheduleUtils.whiteList(job.getInvokeTarget())) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串不在白名单内"); + } + job.setCreateBy(getLoginName()); + return toAjax(jobService.insertJob(job)); + } + + /** + * 修改调度 + */ + @RequiresPermissions("monitor:job:edit") + @GetMapping("/edit/{jobId}") + public String edit(@PathVariable("jobId") Long jobId, ModelMap mmap) + { + mmap.put("job", jobService.selectJobById(jobId)); + return prefix + "/edit"; + } + + /** + * 修改保存调度 + */ + @Log(title = "定时任务", businessType = BusinessType.UPDATE) + @RequiresPermissions("monitor:job:edit") + @PostMapping("/edit") + @ResponseBody + public AjaxResult editSave(@Validated SysJob job) throws SchedulerException, TaskException + { + if (!CronUtils.isValid(job.getCronExpression())) + { + return error("修改任务'" + job.getJobName() + "'失败,Cron表达式不正确"); + } + else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI)) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS })) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS })) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR)) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串存在违规"); + } + else if (!ScheduleUtils.whiteList(job.getInvokeTarget())) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串不在白名单内"); + } + return toAjax(jobService.updateJob(job)); + } + + /** + * 校验cron表达式是否有效 + */ + @PostMapping("/checkCronExpressionIsValid") + @ResponseBody + public boolean checkCronExpressionIsValid(SysJob job) + { + return jobService.checkCronExpressionIsValid(job.getCronExpression()); + } + + /** + * Cron表达式在线生成 + */ + @GetMapping("/cron") + public String cron() + { + return prefix + "/cron"; + } + + /** + * 查询cron表达式近5次的执行时间 + */ + @GetMapping("/queryCronExpression") + @ResponseBody + public AjaxResult queryCronExpression(@RequestParam(value = "cronExpression", required = false) String cronExpression) + { + if (jobService.checkCronExpressionIsValid(cronExpression)) + { + List dateList = CronUtils.getRecentTriggerTime(cronExpression); + return success(dateList); + } + else + { + return error("表达式无效"); + } + } +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobLogController.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobLogController.java new file mode 100644 index 0000000..f44533c --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobLogController.java @@ -0,0 +1,103 @@ +package com.ruoyi.quartz.controller; + +import java.util.List; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.quartz.domain.SysJob; +import com.ruoyi.quartz.domain.SysJobLog; +import com.ruoyi.quartz.service.ISysJobLogService; +import com.ruoyi.quartz.service.ISysJobService; + +/** + * 调度日志操作处理 + * + * @author ruoyi + */ +@Controller +@RequestMapping("/monitor/jobLog") +public class SysJobLogController extends BaseController +{ + private String prefix = "monitor/job"; + + @Autowired + private ISysJobService jobService; + + @Autowired + private ISysJobLogService jobLogService; + + @RequiresPermissions("monitor:job:view") + @GetMapping() + public String jobLog(@RequestParam(value = "jobId", required = false) Long jobId, ModelMap mmap) + { + if (StringUtils.isNotNull(jobId)) + { + SysJob job = jobService.selectJobById(jobId); + mmap.put("job", job); + } + return prefix + "/jobLog"; + } + + @RequiresPermissions("monitor:job:list") + @PostMapping("/list") + @ResponseBody + public TableDataInfo list(SysJobLog jobLog) + { + startPage(); + List list = jobLogService.selectJobLogList(jobLog); + return getDataTable(list); + } + + @Log(title = "调度日志", businessType = BusinessType.EXPORT) + @RequiresPermissions("monitor:job:export") + @PostMapping("/export") + @ResponseBody + public AjaxResult export(SysJobLog jobLog) + { + List list = jobLogService.selectJobLogList(jobLog); + ExcelUtil util = new ExcelUtil(SysJobLog.class); + return util.exportExcel(list, "调度日志"); + } + + @Log(title = "调度日志", businessType = BusinessType.DELETE) + @RequiresPermissions("monitor:job:remove") + @PostMapping("/remove") + @ResponseBody + public AjaxResult remove(String ids) + { + return toAjax(jobLogService.deleteJobLogByIds(ids)); + } + + @RequiresPermissions("monitor:job:detail") + @GetMapping("/detail/{jobLogId}") + public String detail(@PathVariable("jobLogId") Long jobLogId, ModelMap mmap) + { + mmap.put("name", "jobLog"); + mmap.put("jobLog", jobLogService.selectJobLogById(jobLogId)); + return prefix + "/detail"; + } + + @Log(title = "调度日志", businessType = BusinessType.CLEAN) + @RequiresPermissions("monitor:job:remove") + @PostMapping("/clean") + @ResponseBody + public AjaxResult clean() + { + jobLogService.cleanJobLog(); + return success(); + } +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/SysJob.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/SysJob.java new file mode 100644 index 0000000..ea63675 --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/SysJob.java @@ -0,0 +1,169 @@ +package com.ruoyi.quartz.domain; + +import java.io.Serializable; +import java.util.Date; +import javax.validation.constraints.*; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.annotation.Excel.ColumnType; +import com.ruoyi.common.constant.ScheduleConstants; +import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.quartz.util.CronUtils; + +/** + * 定时任务调度表 sys_job + * + * @author ruoyi + */ +public class SysJob extends BaseEntity implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 任务ID */ + @Excel(name = "任务序号", cellType = ColumnType.NUMERIC) + private Long jobId; + + /** 任务名称 */ + @Excel(name = "任务名称") + private String jobName; + + /** 任务组名 */ + @Excel(name = "任务组名") + private String jobGroup; + + /** 调用目标字符串 */ + @Excel(name = "调用目标字符串") + private String invokeTarget; + + /** cron执行表达式 */ + @Excel(name = "执行表达式 ") + private String cronExpression; + + /** cron计划策略 */ + @Excel(name = "计划策略 ", readConverterExp = "0=默认,1=立即触发执行,2=触发一次执行,3=不触发立即执行") + private String misfirePolicy = ScheduleConstants.MISFIRE_DEFAULT; + + /** 是否并发执行(0允许 1禁止) */ + @Excel(name = "并发执行", readConverterExp = "0=允许,1=禁止") + private String concurrent; + + /** 任务状态(0正常 1暂停) */ + @Excel(name = "任务状态", readConverterExp = "0=正常,1=暂停") + private String status; + + public Long getJobId() + { + return jobId; + } + + public void setJobId(Long jobId) + { + this.jobId = jobId; + } + + @NotBlank(message = "任务名称不能为空") + @Size(min = 0, max = 64, message = "任务名称不能超过64个字符") + public String getJobName() + { + return jobName; + } + + public void setJobName(String jobName) + { + this.jobName = jobName; + } + + public String getJobGroup() + { + return jobGroup; + } + + public void setJobGroup(String jobGroup) + { + this.jobGroup = jobGroup; + } + + @NotBlank(message = "调用目标字符串不能为空") + @Size(min = 0, max = 1000, message = "调用目标字符串长度不能超过500个字符") + public String getInvokeTarget() + { + return invokeTarget; + } + + public void setInvokeTarget(String invokeTarget) + { + this.invokeTarget = invokeTarget; + } + + @NotBlank(message = "Cron执行表达式不能为空") + @Size(min = 0, max = 255, message = "Cron执行表达式不能超过255个字符") + public String getCronExpression() + { + return cronExpression; + } + + public void setCronExpression(String cronExpression) + { + this.cronExpression = cronExpression; + } + + public Date getNextValidTime() + { + if (StringUtils.isNotEmpty(cronExpression)) + { + return CronUtils.getNextExecution(cronExpression); + } + return null; + } + + public String getMisfirePolicy() + { + return misfirePolicy; + } + + public void setMisfirePolicy(String misfirePolicy) + { + this.misfirePolicy = misfirePolicy; + } + + public String getConcurrent() + { + return concurrent; + } + + public void setConcurrent(String concurrent) + { + this.concurrent = concurrent; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("jobId", getJobId()) + .append("jobName", getJobName()) + .append("jobGroup", getJobGroup()) + .append("cronExpression", getCronExpression()) + .append("nextValidTime", getNextValidTime()) + .append("misfirePolicy", getMisfirePolicy()) + .append("concurrent", getConcurrent()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} \ No newline at end of file diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/SysJobLog.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/SysJobLog.java new file mode 100644 index 0000000..89602bd --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/SysJobLog.java @@ -0,0 +1,155 @@ +package com.ruoyi.quartz.domain; + +import java.util.Date; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 定时任务调度日志表 sys_job_log + * + * @author ruoyi + */ +public class SysJobLog extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** ID */ + @Excel(name = "日志序号") + private Long jobLogId; + + /** 任务名称 */ + @Excel(name = "任务名称") + private String jobName; + + /** 任务组名 */ + @Excel(name = "任务组名") + private String jobGroup; + + /** 调用目标字符串 */ + @Excel(name = "调用目标字符串") + private String invokeTarget; + + /** 日志信息 */ + @Excel(name = "日志信息") + private String jobMessage; + + /** 执行状态(0正常 1失败) */ + @Excel(name = "执行状态", readConverterExp = "0=正常,1=失败") + private String status; + + /** 异常信息 */ + @Excel(name = "异常信息") + private String exceptionInfo; + + /** 开始时间 */ + private Date startTime; + + /** 结束时间 */ + private Date endTime; + + public Long getJobLogId() + { + return jobLogId; + } + + public void setJobLogId(Long jobLogId) + { + this.jobLogId = jobLogId; + } + + public String getJobName() + { + return jobName; + } + + public void setJobName(String jobName) + { + this.jobName = jobName; + } + + public String getJobGroup() + { + return jobGroup; + } + + public void setJobGroup(String jobGroup) + { + this.jobGroup = jobGroup; + } + + public String getInvokeTarget() + { + return invokeTarget; + } + + public void setInvokeTarget(String invokeTarget) + { + this.invokeTarget = invokeTarget; + } + + public String getJobMessage() + { + return jobMessage; + } + + public void setJobMessage(String jobMessage) + { + this.jobMessage = jobMessage; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getExceptionInfo() + { + return exceptionInfo; + } + + public void setExceptionInfo(String exceptionInfo) + { + this.exceptionInfo = exceptionInfo; + } + + public Date getStartTime() + { + return startTime; + } + + public void setStartTime(Date startTime) + { + this.startTime = startTime; + } + + public Date getEndTime() + { + return endTime; + } + + public void setEndTime(Date endTime) + { + this.endTime = endTime; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("jobLogId", getJobLogId()) + .append("jobName", getJobName()) + .append("jobGroup", getJobGroup()) + .append("jobMessage", getJobMessage()) + .append("status", getStatus()) + .append("exceptionInfo", getExceptionInfo()) + .append("startTime", getStartTime()) + .append("endTime", getEndTime()) + .toString(); + } +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/mapper/SysJobLogMapper.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/mapper/SysJobLogMapper.java new file mode 100644 index 0000000..c2f79e6 --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/mapper/SysJobLogMapper.java @@ -0,0 +1,64 @@ +package com.ruoyi.quartz.mapper; + +import com.ruoyi.quartz.domain.SysJobLog; +import java.util.List; + +/** + * 调度任务日志信息 数据层 + * + * @author ruoyi + */ +public interface SysJobLogMapper +{ + /** + * 获取quartz调度器日志的计划任务 + * + * @param jobLog 调度日志信息 + * @return 调度任务日志集合 + */ + public List selectJobLogList(SysJobLog jobLog); + + /** + * 查询所有调度任务日志 + * + * @return 调度任务日志列表 + */ + public List selectJobLogAll(); + + /** + * 通过调度任务日志ID查询调度信息 + * + * @param jobLogId 调度任务日志ID + * @return 调度任务日志对象信息 + */ + public SysJobLog selectJobLogById(Long jobLogId); + + /** + * 新增任务日志 + * + * @param jobLog 调度日志信息 + * @return 结果 + */ + public int insertJobLog(SysJobLog jobLog); + + /** + * 批量删除调度日志信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteJobLogByIds(String[] ids); + + /** + * 删除任务日志 + * + * @param jobId 调度日志ID + * @return 结果 + */ + public int deleteJobLogById(Long jobId); + + /** + * 清空任务日志 + */ + public void cleanJobLog(); +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/mapper/SysJobMapper.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/mapper/SysJobMapper.java new file mode 100644 index 0000000..5207c7a --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/mapper/SysJobMapper.java @@ -0,0 +1,67 @@ +package com.ruoyi.quartz.mapper; + +import com.ruoyi.quartz.domain.SysJob; +import java.util.List; + +/** + * 调度任务信息 数据层 + * + * @author ruoyi + */ +public interface SysJobMapper +{ + /** + * 查询调度任务日志集合 + * + * @param job 调度信息 + * @return 操作日志集合 + */ + public List selectJobList(SysJob job); + + /** + * 查询所有调度任务 + * + * @return 调度任务列表 + */ + public List selectJobAll(); + + /** + * 通过调度ID查询调度任务信息 + * + * @param jobId 调度ID + * @return 角色对象信息 + */ + public SysJob selectJobById(Long jobId); + + /** + * 通过调度ID删除调度任务信息 + * + * @param jobId 调度ID + * @return 结果 + */ + public int deleteJobById(Long jobId); + + /** + * 批量删除调度任务信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteJobByIds(Long[] ids); + + /** + * 修改调度任务信息 + * + * @param job 调度任务信息 + * @return 结果 + */ + public int updateJob(SysJob job); + + /** + * 新增调度任务信息 + * + * @param job 调度任务信息 + * @return 结果 + */ + public int insertJob(SysJob job); +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/ISysJobLogService.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/ISysJobLogService.java new file mode 100644 index 0000000..4e112d5 --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/ISysJobLogService.java @@ -0,0 +1,56 @@ +package com.ruoyi.quartz.service; + +import java.util.List; +import com.ruoyi.quartz.domain.SysJobLog; + +/** + * 定时任务调度日志信息信息 服务层 + * + * @author ruoyi + */ +public interface ISysJobLogService +{ + /** + * 获取quartz调度器日志的计划任务 + * + * @param jobLog 调度日志信息 + * @return 调度任务日志集合 + */ + public List selectJobLogList(SysJobLog jobLog); + + /** + * 通过调度任务日志ID查询调度信息 + * + * @param jobLogId 调度任务日志ID + * @return 调度任务日志对象信息 + */ + public SysJobLog selectJobLogById(Long jobLogId); + + /** + * 新增任务日志 + * + * @param jobLog 调度日志信息 + */ + public void addJobLog(SysJobLog jobLog); + + /** + * 批量删除调度日志信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteJobLogByIds(String ids); + + /** + * 删除任务日志 + * + * @param jobId 调度日志ID + * @return 结果 + */ + public int deleteJobLogById(Long jobId); + + /** + * 清空任务日志 + */ + public void cleanJobLog(); +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/ISysJobService.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/ISysJobService.java new file mode 100644 index 0000000..c503e3a --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/ISysJobService.java @@ -0,0 +1,102 @@ +package com.ruoyi.quartz.service; + +import java.util.List; +import org.quartz.SchedulerException; +import com.ruoyi.common.exception.job.TaskException; +import com.ruoyi.quartz.domain.SysJob; + +/** + * 定时任务调度信息信息 服务层 + * + * @author ruoyi + */ +public interface ISysJobService +{ + /** + * 获取quartz调度器的计划任务 + * + * @param job 调度信息 + * @return 调度任务集合 + */ + public List selectJobList(SysJob job); + + /** + * 通过调度任务ID查询调度信息 + * + * @param jobId 调度任务ID + * @return 调度任务对象信息 + */ + public SysJob selectJobById(Long jobId); + + /** + * 暂停任务 + * + * @param job 调度信息 + * @return 结果 + */ + public int pauseJob(SysJob job) throws SchedulerException; + + /** + * 恢复任务 + * + * @param job 调度信息 + * @return 结果 + */ + public int resumeJob(SysJob job) throws SchedulerException; + + /** + * 删除任务后,所对应的trigger也将被删除 + * + * @param job 调度信息 + * @return 结果 + */ + public int deleteJob(SysJob job) throws SchedulerException; + + /** + * 批量删除调度信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public void deleteJobByIds(String ids) throws SchedulerException; + + /** + * 任务调度状态修改 + * + * @param job 调度信息 + * @return 结果 + */ + public int changeStatus(SysJob job) throws SchedulerException; + + /** + * 立即运行任务 + * + * @param job 调度信息 + * @return 结果 + */ + public boolean run(SysJob job) throws SchedulerException; + + /** + * 新增任务 + * + * @param job 调度信息 + * @return 结果 + */ + public int insertJob(SysJob job) throws SchedulerException, TaskException; + + /** + * 更新任务 + * + * @param job 调度信息 + * @return 结果 + */ + public int updateJob(SysJob job) throws SchedulerException, TaskException; + + /** + * 校验cron表达式是否有效 + * + * @param cronExpression 表达式 + * @return 结果 + */ + public boolean checkCronExpressionIsValid(String cronExpression); +} \ No newline at end of file diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobLogServiceImpl.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobLogServiceImpl.java new file mode 100644 index 0000000..ccd7379 --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobLogServiceImpl.java @@ -0,0 +1,88 @@ +package com.ruoyi.quartz.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.quartz.domain.SysJobLog; +import com.ruoyi.quartz.mapper.SysJobLogMapper; +import com.ruoyi.quartz.service.ISysJobLogService; + +/** + * 定时任务调度日志信息 服务层 + * + * @author ruoyi + */ +@Service +public class SysJobLogServiceImpl implements ISysJobLogService +{ + @Autowired + private SysJobLogMapper jobLogMapper; + + /** + * 获取quartz调度器日志的计划任务 + * + * @param jobLog 调度日志信息 + * @return 调度任务日志集合 + */ + @Override + public List selectJobLogList(SysJobLog jobLog) + { + return jobLogMapper.selectJobLogList(jobLog); + } + + /** + * 通过调度任务日志ID查询调度信息 + * + * @param jobLogId 调度任务日志ID + * @return 调度任务日志对象信息 + */ + @Override + public SysJobLog selectJobLogById(Long jobLogId) + { + return jobLogMapper.selectJobLogById(jobLogId); + } + + /** + * 新增任务日志 + * + * @param jobLog 调度日志信息 + */ + @Override + public void addJobLog(SysJobLog jobLog) + { + jobLogMapper.insertJobLog(jobLog); + } + + /** + * 批量删除调度日志信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + @Override + public int deleteJobLogByIds(String ids) + { + return jobLogMapper.deleteJobLogByIds(Convert.toStrArray(ids)); + } + + /** + * 删除任务日志 + * + * @param jobId 调度日志ID + */ + @Override + public int deleteJobLogById(Long jobId) + { + return jobLogMapper.deleteJobLogById(jobId); + } + + /** + * 清空任务日志 + */ + @Override + public void cleanJobLog() + { + jobLogMapper.cleanJobLog(); + } +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobServiceImpl.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobServiceImpl.java new file mode 100644 index 0000000..61ce8bd --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobServiceImpl.java @@ -0,0 +1,263 @@ +package com.ruoyi.quartz.service.impl; + +import java.util.List; +import javax.annotation.PostConstruct; +import org.quartz.JobDataMap; +import org.quartz.JobKey; +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.ruoyi.common.constant.ScheduleConstants; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.exception.job.TaskException; +import com.ruoyi.quartz.domain.SysJob; +import com.ruoyi.quartz.mapper.SysJobMapper; +import com.ruoyi.quartz.service.ISysJobService; +import com.ruoyi.quartz.util.CronUtils; +import com.ruoyi.quartz.util.ScheduleUtils; + +/** + * 定时任务调度信息 服务层 + * + * @author ruoyi + */ +@Service +public class SysJobServiceImpl implements ISysJobService +{ + @Autowired + private Scheduler scheduler; + + @Autowired + private SysJobMapper jobMapper; + + /** + * 项目启动时,初始化定时器 + * 主要是防止手动修改数据库导致未同步到定时任务处理(注:不能手动修改数据库ID和任务组名,否则会导致脏数据) + */ + @PostConstruct + public void init() throws SchedulerException, TaskException + { + scheduler.clear(); + List jobList = jobMapper.selectJobAll(); + for (SysJob job : jobList) + { + ScheduleUtils.createScheduleJob(scheduler, job); + } + } + + /** + * 获取quartz调度器的计划任务列表 + * + * @param job 调度信息 + * @return + */ + @Override + public List selectJobList(SysJob job) + { + return jobMapper.selectJobList(job); + } + + /** + * 通过调度任务ID查询调度信息 + * + * @param jobId 调度任务ID + * @return 调度任务对象信息 + */ + @Override + public SysJob selectJobById(Long jobId) + { + return jobMapper.selectJobById(jobId); + } + + /** + * 暂停任务 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int pauseJob(SysJob job) throws SchedulerException + { + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + job.setStatus(ScheduleConstants.Status.PAUSE.getValue()); + int rows = jobMapper.updateJob(job); + if (rows > 0) + { + scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup)); + } + return rows; + } + + /** + * 恢复任务 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int resumeJob(SysJob job) throws SchedulerException + { + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + job.setStatus(ScheduleConstants.Status.NORMAL.getValue()); + int rows = jobMapper.updateJob(job); + if (rows > 0) + { + scheduler.resumeJob(ScheduleUtils.getJobKey(jobId, jobGroup)); + } + return rows; + } + + /** + * 删除任务后,所对应的trigger也将被删除 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int deleteJob(SysJob job) throws SchedulerException + { + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + int rows = jobMapper.deleteJobById(jobId); + if (rows > 0) + { + scheduler.deleteJob(ScheduleUtils.getJobKey(jobId, jobGroup)); + } + return rows; + } + + /** + * 批量删除调度信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteJobByIds(String ids) throws SchedulerException + { + Long[] jobIds = Convert.toLongArray(ids); + for (Long jobId : jobIds) + { + SysJob job = jobMapper.selectJobById(jobId); + deleteJob(job); + } + } + + /** + * 任务调度状态修改 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int changeStatus(SysJob job) throws SchedulerException + { + int rows = 0; + String status = job.getStatus(); + if (ScheduleConstants.Status.NORMAL.getValue().equals(status)) + { + rows = resumeJob(job); + } + else if (ScheduleConstants.Status.PAUSE.getValue().equals(status)) + { + rows = pauseJob(job); + } + return rows; + } + + /** + * 立即运行任务 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public boolean run(SysJob job) throws SchedulerException + { + boolean result = false; + Long jobId = job.getJobId(); + SysJob tmpObj = selectJobById(job.getJobId()); + // 参数 + JobDataMap dataMap = new JobDataMap(); + dataMap.put(ScheduleConstants.TASK_PROPERTIES, tmpObj); + JobKey jobKey = ScheduleUtils.getJobKey(jobId, tmpObj.getJobGroup()); + if (scheduler.checkExists(jobKey)) + { + result = true; + scheduler.triggerJob(jobKey, dataMap); + } + return result; + } + + /** + * 新增任务 + * + * @param job 调度信息 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int insertJob(SysJob job) throws SchedulerException, TaskException + { + job.setStatus(ScheduleConstants.Status.PAUSE.getValue()); + int rows = jobMapper.insertJob(job); + if (rows > 0) + { + ScheduleUtils.createScheduleJob(scheduler, job); + } + return rows; + } + + /** + * 更新任务的时间表达式 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int updateJob(SysJob job) throws SchedulerException, TaskException + { + SysJob properties = selectJobById(job.getJobId()); + int rows = jobMapper.updateJob(job); + if (rows > 0) + { + updateSchedulerJob(job, properties.getJobGroup()); + } + return rows; + } + + /** + * 更新任务 + * + * @param job 任务对象 + * @param jobGroup 任务组名 + */ + public void updateSchedulerJob(SysJob job, String jobGroup) throws SchedulerException, TaskException + { + Long jobId = job.getJobId(); + // 判断是否存在 + JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup); + if (scheduler.checkExists(jobKey)) + { + // 防止创建时存在数据问题 先移除,然后在执行创建操作 + scheduler.deleteJob(jobKey); + } + ScheduleUtils.createScheduleJob(scheduler, job); + } + + /** + * 校验cron表达式是否有效 + * + * @param cronExpression 表达式 + * @return 结果 + */ + @Override + public boolean checkCronExpressionIsValid(String cronExpression) + { + return CronUtils.isValid(cronExpression); + } +} \ No newline at end of file diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java new file mode 100644 index 0000000..853243b --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java @@ -0,0 +1,28 @@ +package com.ruoyi.quartz.task; + +import org.springframework.stereotype.Component; +import com.ruoyi.common.utils.StringUtils; + +/** + * 定时任务调度测试 + * + * @author ruoyi + */ +@Component("ryTask") +public class RyTask +{ + public void ryMultipleParams(String s, Boolean b, Long l, Double d, Integer i) + { + System.out.println(StringUtils.format("执行多参方法: 字符串类型{},布尔类型{},长整型{},浮点型{},整形{}", s, b, l, d, i)); + } + + public void ryParams(String params) + { + System.out.println("执行有参方法:" + params); + } + + public void ryNoParams() + { + System.out.println("执行无参方法"); + } +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/AbstractQuartzJob.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/AbstractQuartzJob.java new file mode 100644 index 0000000..55eb51f --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/AbstractQuartzJob.java @@ -0,0 +1,107 @@ +package com.ruoyi.quartz.util; + +import java.util.Date; +import org.quartz.Job; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.constant.ScheduleConstants; +import com.ruoyi.common.utils.ExceptionUtil; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.bean.BeanUtils; +import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.quartz.domain.SysJob; +import com.ruoyi.quartz.domain.SysJobLog; +import com.ruoyi.quartz.service.ISysJobLogService; + +/** + * 抽象quartz调用 + * + * @author ruoyi + */ +public abstract class AbstractQuartzJob implements Job +{ + private static final Logger log = LoggerFactory.getLogger(AbstractQuartzJob.class); + + /** + * 线程本地变量 + */ + private static ThreadLocal threadLocal = new ThreadLocal<>(); + + @Override + public void execute(JobExecutionContext context) throws JobExecutionException + { + SysJob sysJob = new SysJob(); + BeanUtils.copyBeanProp(sysJob, context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES)); + try + { + before(context, sysJob); + if (sysJob != null) + { + doExecute(context, sysJob); + } + after(context, sysJob, null); + } + catch (Exception e) + { + log.error("任务执行异常 - :", e); + after(context, sysJob, e); + } + } + + /** + * 执行前 + * + * @param context 工作执行上下文对象 + * @param sysJob 系统计划任务 + */ + protected void before(JobExecutionContext context, SysJob sysJob) + { + threadLocal.set(new Date()); + } + + /** + * 执行后 + * + * @param context 工作执行上下文对象 + * @param sysJob 系统计划任务 + */ + protected void after(JobExecutionContext context, SysJob sysJob, Exception e) + { + Date startTime = threadLocal.get(); + threadLocal.remove(); + + final SysJobLog sysJobLog = new SysJobLog(); + sysJobLog.setJobName(sysJob.getJobName()); + sysJobLog.setJobGroup(sysJob.getJobGroup()); + sysJobLog.setInvokeTarget(sysJob.getInvokeTarget()); + sysJobLog.setStartTime(startTime); + sysJobLog.setEndTime(new Date()); + long runMs = sysJobLog.getEndTime().getTime() - sysJobLog.getStartTime().getTime(); + sysJobLog.setJobMessage(sysJobLog.getJobName() + " 总共耗时:" + runMs + "毫秒"); + if (e != null) + { + sysJobLog.setStatus(Constants.FAIL); + String errorMsg = StringUtils.substring(ExceptionUtil.getExceptionMessage(e), 0, 2000); + sysJobLog.setExceptionInfo(errorMsg); + } + else + { + sysJobLog.setStatus(Constants.SUCCESS); + } + + // 写入数据库当中 + SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog); + } + + /** + * 执行方法,由子类重载 + * + * @param context 工作执行上下文对象 + * @param sysJob 系统计划任务 + * @throws Exception 执行过程中的异常 + */ + protected abstract void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception; +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/CronUtils.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/CronUtils.java new file mode 100644 index 0000000..01a84cb --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/CronUtils.java @@ -0,0 +1,94 @@ +package com.ruoyi.quartz.util; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import org.quartz.CronExpression; +import org.quartz.TriggerUtils; +import org.quartz.impl.triggers.CronTriggerImpl; +import com.ruoyi.common.utils.DateUtils; + +/** + * cron表达式工具类 + * + * @author ruoyi + * + */ +public class CronUtils +{ + /** + * 返回一个布尔值代表一个给定的Cron表达式的有效性 + * + * @param cronExpression Cron表达式 + * @return boolean 表达式是否有效 + */ + public static boolean isValid(String cronExpression) + { + return CronExpression.isValidExpression(cronExpression); + } + + /** + * 返回一个字符串值,表示该消息无效Cron表达式给出有效性 + * + * @param cronExpression Cron表达式 + * @return String 无效时返回表达式错误描述,如果有效返回null + */ + public static String getInvalidMessage(String cronExpression) + { + try + { + new CronExpression(cronExpression); + return null; + } + catch (ParseException pe) + { + return pe.getMessage(); + } + } + + /** + * 返回下一个执行时间根据给定的Cron表达式 + * + * @param cronExpression Cron表达式 + * @return Date 下次Cron表达式执行时间 + */ + public static Date getNextExecution(String cronExpression) + { + try + { + CronExpression cron = new CronExpression(cronExpression); + return cron.getNextValidTimeAfter(new Date(System.currentTimeMillis())); + } + catch (ParseException e) + { + throw new IllegalArgumentException(e.getMessage()); + } + } + + /** + * 通过表达式获取近10次的执行时间 + * + * @param cron 表达式 + * @return 时间列表 + */ + public static List getRecentTriggerTime(String cron) + { + List list = new ArrayList(); + try + { + CronTriggerImpl cronTriggerImpl = new CronTriggerImpl(); + cronTriggerImpl.setCronExpression(cron); + List dates = TriggerUtils.computeFireTimes(cronTriggerImpl, null, 10); + for (Date date : dates) + { + list.add(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, date)); + } + } + catch (ParseException e) + { + return null; + } + return list; + } +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/JobInvokeUtil.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/JobInvokeUtil.java new file mode 100644 index 0000000..3da377a --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/JobInvokeUtil.java @@ -0,0 +1,182 @@ +package com.ruoyi.quartz.util; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.LinkedList; +import java.util.List; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.quartz.domain.SysJob; + +/** + * 任务执行工具 + * + * @author ruoyi + */ +public class JobInvokeUtil +{ + /** + * 执行方法 + * + * @param sysJob 系统任务 + */ + public static void invokeMethod(SysJob sysJob) throws Exception + { + String invokeTarget = sysJob.getInvokeTarget(); + String beanName = getBeanName(invokeTarget); + String methodName = getMethodName(invokeTarget); + List methodParams = getMethodParams(invokeTarget); + + if (!isValidClassName(beanName)) + { + Object bean = SpringUtils.getBean(beanName); + invokeMethod(bean, methodName, methodParams); + } + else + { + Object bean = Class.forName(beanName).newInstance(); + invokeMethod(bean, methodName, methodParams); + } + } + + /** + * 调用任务方法 + * + * @param bean 目标对象 + * @param methodName 方法名称 + * @param methodParams 方法参数 + */ + private static void invokeMethod(Object bean, String methodName, List methodParams) + throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, + InvocationTargetException + { + if (StringUtils.isNotNull(methodParams) && methodParams.size() > 0) + { + Method method = bean.getClass().getMethod(methodName, getMethodParamsType(methodParams)); + method.invoke(bean, getMethodParamsValue(methodParams)); + } + else + { + Method method = bean.getClass().getMethod(methodName); + method.invoke(bean); + } + } + + /** + * 校验是否为为class包名 + * + * @param invokeTarget 名称 + * @return true是 false否 + */ + public static boolean isValidClassName(String invokeTarget) + { + return StringUtils.countMatches(invokeTarget, ".") > 1; + } + + /** + * 获取bean名称 + * + * @param invokeTarget 目标字符串 + * @return bean名称 + */ + public static String getBeanName(String invokeTarget) + { + String beanName = StringUtils.substringBefore(invokeTarget, "("); + return StringUtils.substringBeforeLast(beanName, "."); + } + + /** + * 获取bean方法 + * + * @param invokeTarget 目标字符串 + * @return method方法 + */ + public static String getMethodName(String invokeTarget) + { + String methodName = StringUtils.substringBefore(invokeTarget, "("); + return StringUtils.substringAfterLast(methodName, "."); + } + + /** + * 获取method方法参数相关列表 + * + * @param invokeTarget 目标字符串 + * @return method方法相关参数列表 + */ + public static List getMethodParams(String invokeTarget) + { + String methodStr = StringUtils.substringBetween(invokeTarget, "(", ")"); + if (StringUtils.isEmpty(methodStr)) + { + return null; + } + String[] methodParams = methodStr.split(",(?=([^\"']*[\"'][^\"']*[\"'])*[^\"']*$)"); + List classs = new LinkedList<>(); + for (int i = 0; i < methodParams.length; i++) + { + String str = StringUtils.trimToEmpty(methodParams[i]); + // String字符串类型,以'或"开头 + if (StringUtils.startsWithAny(str, "'", "\"")) + { + classs.add(new Object[] { StringUtils.substring(str, 1, str.length() - 1), String.class }); + } + // boolean布尔类型,等于true或者false + else if ("true".equalsIgnoreCase(str) || "false".equalsIgnoreCase(str)) + { + classs.add(new Object[] { Boolean.valueOf(str), Boolean.class }); + } + // long长整形,以L结尾 + else if (StringUtils.endsWith(str, "L")) + { + classs.add(new Object[] { Long.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Long.class }); + } + // double浮点类型,以D结尾 + else if (StringUtils.endsWith(str, "D")) + { + classs.add(new Object[] { Double.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Double.class }); + } + // 其他类型归类为整形 + else + { + classs.add(new Object[] { Integer.valueOf(str), Integer.class }); + } + } + return classs; + } + + /** + * 获取参数类型 + * + * @param methodParams 参数相关列表 + * @return 参数类型列表 + */ + public static Class[] getMethodParamsType(List methodParams) + { + Class[] classs = new Class[methodParams.size()]; + int index = 0; + for (Object[] os : methodParams) + { + classs[index] = (Class) os[1]; + index++; + } + return classs; + } + + /** + * 获取参数值 + * + * @param methodParams 参数相关列表 + * @return 参数值列表 + */ + public static Object[] getMethodParamsValue(List methodParams) + { + Object[] classs = new Object[methodParams.size()]; + int index = 0; + for (Object[] os : methodParams) + { + classs[index] = (Object) os[0]; + index++; + } + return classs; + } +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/QuartzDisallowConcurrentExecution.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/QuartzDisallowConcurrentExecution.java new file mode 100644 index 0000000..5e13558 --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/QuartzDisallowConcurrentExecution.java @@ -0,0 +1,21 @@ +package com.ruoyi.quartz.util; + +import org.quartz.DisallowConcurrentExecution; +import org.quartz.JobExecutionContext; +import com.ruoyi.quartz.domain.SysJob; + +/** + * 定时任务处理(禁止并发执行) + * + * @author ruoyi + * + */ +@DisallowConcurrentExecution +public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob +{ + @Override + protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception + { + JobInvokeUtil.invokeMethod(sysJob); + } +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/QuartzJobExecution.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/QuartzJobExecution.java new file mode 100644 index 0000000..e975326 --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/QuartzJobExecution.java @@ -0,0 +1,19 @@ +package com.ruoyi.quartz.util; + +import org.quartz.JobExecutionContext; +import com.ruoyi.quartz.domain.SysJob; + +/** + * 定时任务处理(允许并发执行) + * + * @author ruoyi + * + */ +public class QuartzJobExecution extends AbstractQuartzJob +{ + @Override + protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception + { + JobInvokeUtil.invokeMethod(sysJob); + } +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/ScheduleUtils.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/ScheduleUtils.java new file mode 100644 index 0000000..206791a --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/ScheduleUtils.java @@ -0,0 +1,141 @@ +package com.ruoyi.quartz.util; + +import org.quartz.CronScheduleBuilder; +import org.quartz.CronTrigger; +import org.quartz.Job; +import org.quartz.JobBuilder; +import org.quartz.JobDetail; +import org.quartz.JobKey; +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.quartz.TriggerBuilder; +import org.quartz.TriggerKey; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.constant.ScheduleConstants; +import com.ruoyi.common.exception.job.TaskException; +import com.ruoyi.common.exception.job.TaskException.Code; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.quartz.domain.SysJob; + +/** + * 定时任务工具类 + * + * @author ruoyi + * + */ +public class ScheduleUtils +{ + /** + * 得到quartz任务类 + * + * @param sysJob 执行计划 + * @return 具体执行任务类 + */ + private static Class getQuartzJobClass(SysJob sysJob) + { + boolean isConcurrent = "0".equals(sysJob.getConcurrent()); + return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class; + } + + /** + * 构建任务触发对象 + */ + public static TriggerKey getTriggerKey(Long jobId, String jobGroup) + { + return TriggerKey.triggerKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup); + } + + /** + * 构建任务键对象 + */ + public static JobKey getJobKey(Long jobId, String jobGroup) + { + return JobKey.jobKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup); + } + + /** + * 创建定时任务 + */ + public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, TaskException + { + Class jobClass = getQuartzJobClass(job); + // 构建job信息 + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build(); + + // 表达式调度构建器 + CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression()); + cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder); + + // 按新的cronExpression表达式构建一个新的trigger + CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup)) + .withSchedule(cronScheduleBuilder).build(); + + // 放入参数,运行时的方法可以获取 + jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job); + + // 判断是否存在 + if (scheduler.checkExists(getJobKey(jobId, jobGroup))) + { + // 防止创建时存在数据问题 先移除,然后在执行创建操作 + scheduler.deleteJob(getJobKey(jobId, jobGroup)); + } + + // 判断任务是否过期 + if (StringUtils.isNotNull(CronUtils.getNextExecution(job.getCronExpression()))) + { + // 执行调度任务 + scheduler.scheduleJob(jobDetail, trigger); + } + + // 暂停任务 + if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue())) + { + scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup)); + } + } + + /** + * 设置定时任务策略 + */ + public static CronScheduleBuilder handleCronScheduleMisfirePolicy(SysJob job, CronScheduleBuilder cb) + throws TaskException + { + switch (job.getMisfirePolicy()) + { + case ScheduleConstants.MISFIRE_DEFAULT: + return cb; + case ScheduleConstants.MISFIRE_IGNORE_MISFIRES: + return cb.withMisfireHandlingInstructionIgnoreMisfires(); + case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED: + return cb.withMisfireHandlingInstructionFireAndProceed(); + case ScheduleConstants.MISFIRE_DO_NOTHING: + return cb.withMisfireHandlingInstructionDoNothing(); + default: + throw new TaskException("The task misfire policy '" + job.getMisfirePolicy() + + "' cannot be used in cron schedule tasks", Code.CONFIG_ERROR); + } + } + + /** + * 检查包名是否为白名单配置 + * + * @param invokeTarget 目标字符串 + * @return 结果 + */ + public static boolean whiteList(String invokeTarget) + { + String packageName = StringUtils.substringBefore(invokeTarget, "("); + int count = StringUtils.countMatches(packageName, "."); + if (count > 1) + { + return StringUtils.containsAnyIgnoreCase(invokeTarget, Constants.JOB_WHITELIST_STR); + } + Object obj = SpringUtils.getBean(StringUtils.split(invokeTarget, ".")[0]); + String beanPackageName = obj.getClass().getPackage().getName(); + return StringUtils.containsAnyIgnoreCase(beanPackageName, Constants.JOB_WHITELIST_STR) + && !StringUtils.containsAnyIgnoreCase(beanPackageName, Constants.JOB_ERROR_STR); + } +} \ No newline at end of file diff --git a/ruoyi-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml b/ruoyi-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml new file mode 100644 index 0000000..b412976 --- /dev/null +++ b/ruoyi-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + select job_log_id, job_name, job_group, invoke_target, job_message, status, exception_info, create_time + from sys_job_log + + + + + + + + + + delete from sys_job_log where job_log_id = #{jobLogId} + + + + delete from sys_job_log where job_log_id in + + #{jobLogId} + + + + + truncate table sys_job_log + + + + insert into sys_job_log( + job_log_id, + job_name, + job_group, + invoke_target, + job_message, + status, + exception_info, + create_time + )values( + #{jobLogId}, + #{jobName}, + #{jobGroup}, + #{invokeTarget}, + #{jobMessage}, + #{status}, + #{exceptionInfo}, + sysdate() + ) + + + \ No newline at end of file diff --git a/ruoyi-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml b/ruoyi-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml new file mode 100644 index 0000000..5605c44 --- /dev/null +++ b/ruoyi-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + select job_id, job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, remark + from sys_job + + + + + + + + + + delete from sys_job where job_id = #{jobId} + + + + delete from sys_job where job_id in + + #{jobId} + + + + + update sys_job + + job_name = #{jobName}, + job_group = #{jobGroup}, + invoke_target = #{invokeTarget}, + cron_expression = #{cronExpression}, + misfire_policy = #{misfirePolicy}, + concurrent = #{concurrent}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where job_id = #{jobId} + + + + insert into sys_job( + job_id, + job_name, + job_group, + invoke_target, + cron_expression, + misfire_policy, + concurrent, + status, + remark, + create_by, + create_time + )values( + #{jobId}, + #{jobName}, + #{jobGroup}, + #{invokeTarget}, + #{cronExpression}, + #{misfirePolicy}, + #{concurrent}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + \ No newline at end of file diff --git a/ruoyi-quartz/src/main/resources/templates/monitor/job/add.html b/ruoyi-quartz/src/main/resources/templates/monitor/job/add.html new file mode 100644 index 0000000..1893cfd --- /dev/null +++ b/ruoyi-quartz/src/main/resources/templates/monitor/job/add.html @@ -0,0 +1,109 @@ + + + + + + +
    +
    + +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + + Bean调用示例:ryTask.ryParams('ry') + Class类调用示例:com.ruoyi.quartz.task.RyTask.ryParams('ry') + 参数说明:支持字符串,布尔类型,长整型,浮点型,整型 +
    +
    +
    + +
    + +
    +
    +
    + +
    + + + +
    +
    +
    + +
    + + +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    + + + + diff --git a/ruoyi-quartz/src/main/resources/templates/monitor/job/cron.html b/ruoyi-quartz/src/main/resources/templates/monitor/job/cron.html new file mode 100644 index 0000000..36f9c09 --- /dev/null +++ b/ruoyi-quartz/src/main/resources/templates/monitor/job/cron.html @@ -0,0 +1,1172 @@ + + + + + + Cron表达式在线生成 + + + + +
    +
    + +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    +

    表达式

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    分钟小时星期
    表达式字段
    Cron 表达式
    +
    + + + +
    +
    +
    +
    + + + + + + + + + \ No newline at end of file diff --git a/ruoyi-quartz/src/main/resources/templates/monitor/job/detail.html b/ruoyi-quartz/src/main/resources/templates/monitor/job/detail.html new file mode 100644 index 0000000..730fa81 --- /dev/null +++ b/ruoyi-quartz/src/main/resources/templates/monitor/job/detail.html @@ -0,0 +1,99 @@ + + + + + + +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    默认策略
    +
    立即执行
    +
    执行一次
    +
    放弃执行
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    + + \ No newline at end of file diff --git a/ruoyi-quartz/src/main/resources/templates/monitor/job/edit.html b/ruoyi-quartz/src/main/resources/templates/monitor/job/edit.html new file mode 100644 index 0000000..12ae1d7 --- /dev/null +++ b/ruoyi-quartz/src/main/resources/templates/monitor/job/edit.html @@ -0,0 +1,111 @@ + + + + + + +
    +
    + + +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + + Bean调用示例:ryTask.ryParams('ry') + Class类调用示例:com.ruoyi.quartz.task.RyTask.ryParams('ry') + 参数说明:支持字符串,布尔类型,长整型,浮点型,整型 +
    +
    +
    + +
    + +
    +
    +
    + +
    + + + +
    +
    +
    + +
    + + +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    + + + + diff --git a/ruoyi-quartz/src/main/resources/templates/monitor/job/job.html b/ruoyi-quartz/src/main/resources/templates/monitor/job/job.html new file mode 100644 index 0000000..cefc99d --- /dev/null +++ b/ruoyi-quartz/src/main/resources/templates/monitor/job/job.html @@ -0,0 +1,198 @@ + + + + + + +
    +
    +
    +
    +
    +
      +
    • + 任务名称: +
    • +
    • + 任务分组: +
    • +
    • + 任务状态: +
    • +
    • +  搜索 +  重置 +
    • +
    +
    +
    +
    + + + +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/ruoyi-quartz/src/main/resources/templates/monitor/job/jobLog.html b/ruoyi-quartz/src/main/resources/templates/monitor/job/jobLog.html new file mode 100644 index 0000000..71028c6 --- /dev/null +++ b/ruoyi-quartz/src/main/resources/templates/monitor/job/jobLog.html @@ -0,0 +1,138 @@ + + + + + + + +
    +
    +
    +
    +
    +
      +
    • + 任务名称: +
    • +
    • + 任务分组: +
    • +
    • + 执行状态: +
    • +
    • + + + - + +
    • +
    • +  搜索 +  重置 +
    • +
    +
    +
    +
    + + + +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/ruoyi-system/pom.xml b/ruoyi-system/pom.xml new file mode 100644 index 0000000..8456bd2 --- /dev/null +++ b/ruoyi-system/pom.xml @@ -0,0 +1,28 @@ + + + + ruoyi + com.ruoyi + 4.7.6 + + 4.0.0 + + ruoyi-system + + + system系统模块 + + + + + + + com.ruoyi + ruoyi-common + + + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysConfig.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysConfig.java new file mode 100644 index 0000000..56f3767 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysConfig.java @@ -0,0 +1,110 @@ +package com.ruoyi.system.domain; + +import javax.validation.constraints.*; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.annotation.Excel.ColumnType; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 参数配置表 sys_config + * + * @author ruoyi + */ +public class SysConfig extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 参数主键 */ + @Excel(name = "参数主键", cellType = ColumnType.NUMERIC) + private Long configId; + + /** 参数名称 */ + @Excel(name = "参数名称") + private String configName; + + /** 参数键名 */ + @Excel(name = "参数键名") + private String configKey; + + /** 参数键值 */ + @Excel(name = "参数键值") + private String configValue; + + /** 系统内置(Y是 N否) */ + @Excel(name = "系统内置", readConverterExp = "Y=是,N=否") + private String configType; + + public Long getConfigId() + { + return configId; + } + + public void setConfigId(Long configId) + { + this.configId = configId; + } + + @NotBlank(message = "参数名称不能为空") + @Size(min = 0, max = 100, message = "参数名称不能超过100个字符") + public String getConfigName() + { + return configName; + } + + public void setConfigName(String configName) + { + this.configName = configName; + } + + @NotBlank(message = "参数键名长度不能为空") + @Size(min = 0, max = 100, message = "参数键名长度不能超过100个字符") + public String getConfigKey() + { + return configKey; + } + + public void setConfigKey(String configKey) + { + this.configKey = configKey; + } + + @NotBlank(message = "参数键值不能为空") + @Size(min = 0, max = 500, message = "参数键值长度不能超过500个字符") + public String getConfigValue() + { + return configValue; + } + + public void setConfigValue(String configValue) + { + this.configValue = configValue; + } + + public String getConfigType() + { + return configType; + } + + public void setConfigType(String configType) + { + this.configType = configType; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("configId", getConfigId()) + .append("configName", getConfigName()) + .append("configKey", getConfigKey()) + .append("configValue", getConfigValue()) + .append("configType", getConfigType()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysLogininfor.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysLogininfor.java new file mode 100644 index 0000000..29b3af1 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysLogininfor.java @@ -0,0 +1,159 @@ +package com.ruoyi.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import java.util.Date; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.annotation.Excel.ColumnType; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 系统访问记录表 sys_logininfor + * + * @author ruoyi + */ +public class SysLogininfor extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** ID */ + @Excel(name = "序号", cellType = ColumnType.NUMERIC) + private Long infoId; + + /** 用户账号 */ + @Excel(name = "用户账号") + private String loginName; + + /** 登录状态 0成功 1失败 */ + @Excel(name = "登录状态", readConverterExp = "0=成功,1=失败") + private String status; + + /** 登录IP地址 */ + @Excel(name = "登录地址") + private String ipaddr; + + /** 登录地点 */ + @Excel(name = "登录地点") + private String loginLocation; + + /** 浏览器类型 */ + @Excel(name = "浏览器") + private String browser; + + /** 操作系统 */ + @Excel(name = "操作系统") + private String os; + + /** 提示消息 */ + @Excel(name = "提示消息") + private String msg; + + /** 访问时间 */ + @Excel(name = "访问时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + private Date loginTime; + + public Long getInfoId() + { + return infoId; + } + + public void setInfoId(Long infoId) + { + this.infoId = infoId; + } + + public String getLoginName() + { + return loginName; + } + + public void setLoginName(String loginName) + { + this.loginName = loginName; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getIpaddr() + { + return ipaddr; + } + + public void setIpaddr(String ipaddr) + { + this.ipaddr = ipaddr; + } + + public String getLoginLocation() + { + return loginLocation; + } + + public void setLoginLocation(String loginLocation) + { + this.loginLocation = loginLocation; + } + + public String getBrowser() + { + return browser; + } + + public void setBrowser(String browser) + { + this.browser = browser; + } + + public String getOs() + { + return os; + } + + public void setOs(String os) + { + this.os = os; + } + + public String getMsg() + { + return msg; + } + + public void setMsg(String msg) + { + this.msg = msg; + } + + public Date getLoginTime() + { + return loginTime; + } + + public void setLoginTime(Date loginTime) + { + this.loginTime = loginTime; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("infoId", getInfoId()) + .append("loginName", getLoginName()) + .append("ipaddr", getIpaddr()) + .append("loginLocation", getLoginLocation()) + .append("browser", getBrowser()) + .append("os", getOs()) + .append("status", getStatus()) + .append("msg", getMsg()) + .append("loginTime", getLoginTime()) + .toString(); + } +} \ No newline at end of file diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysNotice.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysNotice.java new file mode 100644 index 0000000..8c07a54 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysNotice.java @@ -0,0 +1,102 @@ +package com.ruoyi.system.domain; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.common.xss.Xss; + +/** + * 通知公告表 sys_notice + * + * @author ruoyi + */ +public class SysNotice extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 公告ID */ + private Long noticeId; + + /** 公告标题 */ + private String noticeTitle; + + /** 公告类型(1通知 2公告) */ + private String noticeType; + + /** 公告内容 */ + private String noticeContent; + + /** 公告状态(0正常 1关闭) */ + private String status; + + public Long getNoticeId() + { + return noticeId; + } + + public void setNoticeId(Long noticeId) + { + this.noticeId = noticeId; + } + + public void setNoticeTitle(String noticeTitle) + { + this.noticeTitle = noticeTitle; + } + + @Xss(message = "公告标题不能包含脚本字符") + @NotBlank(message = "公告标题不能为空") + @Size(min = 0, max = 50, message = "公告标题不能超过50个字符") + public String getNoticeTitle() + { + return noticeTitle; + } + + public void setNoticeType(String noticeType) + { + this.noticeType = noticeType; + } + + public String getNoticeType() + { + return noticeType; + } + + public void setNoticeContent(String noticeContent) + { + this.noticeContent = noticeContent; + } + + public String getNoticeContent() + { + return noticeContent; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getStatus() + { + return status; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("noticeId", getNoticeId()) + .append("noticeTitle", getNoticeTitle()) + .append("noticeType", getNoticeType()) + .append("noticeContent", getNoticeContent()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysOperLog.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysOperLog.java new file mode 100644 index 0000000..e758412 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysOperLog.java @@ -0,0 +1,292 @@ +package com.ruoyi.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import java.util.Date; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.annotation.Excel.ColumnType; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 操作日志记录表 oper_log + * + * @author ruoyi + */ +public class SysOperLog extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 日志主键 */ + @Excel(name = "操作序号", cellType = ColumnType.NUMERIC) + private Long operId; + + /** 操作模块 */ + @Excel(name = "操作模块") + private String title; + + /** 业务类型(0其它 1新增 2修改 3删除) */ + @Excel(name = "业务类型", readConverterExp = "0=其它,1=新增,2=修改,3=删除,4=授权,5=导出,6=导入,7=强退,8=生成代码,9=清空数据") + private Integer businessType; + + /** 业务类型数组 */ + private Integer[] businessTypes; + + /** 请求方法 */ + @Excel(name = "请求方法") + private String method; + + /** 请求方式 */ + @Excel(name = "请求方式") + private String requestMethod; + + /** 操作类别(0其它 1后台用户 2手机端用户) */ + @Excel(name = "操作类别", readConverterExp = "0=其它,1=后台用户,2=手机端用户") + private Integer operatorType; + + /** 操作人员 */ + @Excel(name = "操作人员") + private String operName; + + /** 部门名称 */ + @Excel(name = "部门名称") + private String deptName; + + /** 请求url */ + @Excel(name = "请求地址") + private String operUrl; + + /** 操作地址 */ + @Excel(name = "操作地址") + private String operIp; + + /** 操作地点 */ + @Excel(name = "操作地点") + private String operLocation; + + /** 请求参数 */ + @Excel(name = "请求参数") + private String operParam; + + /** 返回参数 */ + @Excel(name = "返回参数") + private String jsonResult; + + /** 操作状态(0正常 1异常) */ + @Excel(name = "状态", readConverterExp = "0=正常,1=异常") + private Integer status; + + /** 错误消息 */ + @Excel(name = "错误消息") + private String errorMsg; + + /** 操作时间 */ + @Excel(name = "操作时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + private Date operTime; + + /** 消耗时间 */ + @Excel(name = "消耗时间", suffix = "毫秒") + private Long costTime; + + public Long getOperId() + { + return operId; + } + + public void setOperId(Long operId) + { + this.operId = operId; + } + + public String getTitle() + { + return title; + } + + public void setTitle(String title) + { + this.title = title; + } + + public Integer getBusinessType() + { + return businessType; + } + + public void setBusinessType(Integer businessType) + { + this.businessType = businessType; + } + + public Integer[] getBusinessTypes() + { + return businessTypes; + } + + public void setBusinessTypes(Integer[] businessTypes) + { + this.businessTypes = businessTypes; + } + + public String getMethod() + { + return method; + } + + public void setMethod(String method) + { + this.method = method; + } + + public String getRequestMethod() + { + return requestMethod; + } + + public void setRequestMethod(String requestMethod) + { + this.requestMethod = requestMethod; + } + + public Integer getOperatorType() + { + return operatorType; + } + + public void setOperatorType(Integer operatorType) + { + this.operatorType = operatorType; + } + + public String getOperName() + { + return operName; + } + + public void setOperName(String operName) + { + this.operName = operName; + } + + public String getDeptName() + { + return deptName; + } + + public void setDeptName(String deptName) + { + this.deptName = deptName; + } + + public String getOperUrl() + { + return operUrl; + } + + public void setOperUrl(String operUrl) + { + this.operUrl = operUrl; + } + + public String getOperIp() + { + return operIp; + } + + public void setOperIp(String operIp) + { + this.operIp = operIp; + } + + public String getOperLocation() + { + return operLocation; + } + + public void setOperLocation(String operLocation) + { + this.operLocation = operLocation; + } + + public String getOperParam() + { + return operParam; + } + + public void setOperParam(String operParam) + { + this.operParam = operParam; + } + + public String getJsonResult() + { + return jsonResult; + } + + public void setJsonResult(String jsonResult) + { + this.jsonResult = jsonResult; + } + + public Integer getStatus() + { + return status; + } + + public void setStatus(Integer status) + { + this.status = status; + } + + public String getErrorMsg() + { + return errorMsg; + } + + public void setErrorMsg(String errorMsg) + { + this.errorMsg = errorMsg; + } + + public Date getOperTime() + { + return operTime; + } + + public void setOperTime(Date operTime) + { + this.operTime = operTime; + } + + public Long getCostTime() + { + return costTime; + } + + public void setCostTime(Long costTime) + { + this.costTime = costTime; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("operId", getOperId()) + .append("title", getTitle()) + .append("businessType", getBusinessType()) + .append("businessTypes", getBusinessTypes()) + .append("method", getMethod()) + .append("requestMethod", getRequestMethod()) + .append("operatorType", getOperatorType()) + .append("operName", getOperName()) + .append("deptName", getDeptName()) + .append("operUrl", getOperUrl()) + .append("operIp", getOperIp()) + .append("operLocation", getOperLocation()) + .append("operParam", getOperParam()) + .append("status", getStatus()) + .append("errorMsg", getErrorMsg()) + .append("operTime", getOperTime()) + .append("costTime", getCostTime()) + .toString(); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysPost.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysPost.java new file mode 100644 index 0000000..a172f66 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysPost.java @@ -0,0 +1,122 @@ +package com.ruoyi.system.domain; + +import javax.validation.constraints.*; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.annotation.Excel.ColumnType; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 岗位表 sys_post + * + * @author ruoyi + */ +public class SysPost extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 岗位序号 */ + @Excel(name = "岗位序号", cellType = ColumnType.NUMERIC) + private Long postId; + + /** 岗位编码 */ + @Excel(name = "岗位编码") + private String postCode; + + /** 岗位名称 */ + @Excel(name = "岗位名称") + private String postName; + + /** 岗位排序 */ + @Excel(name = "岗位排序", cellType = ColumnType.NUMERIC) + private String postSort; + + /** 状态(0正常 1停用) */ + @Excel(name = "状态", readConverterExp = "0=正常,1=停用") + private String status; + + /** 用户是否存在此岗位标识 默认不存在 */ + private boolean flag = false; + + public Long getPostId() + { + return postId; + } + + public void setPostId(Long postId) + { + this.postId = postId; + } + + @NotBlank(message = "岗位编码不能为空") + @Size(min = 0, max = 64, message = "岗位编码长度不能超过64个字符") + public String getPostCode() + { + return postCode; + } + + public void setPostCode(String postCode) + { + this.postCode = postCode; + } + + @NotBlank(message = "岗位名称不能为空") + @Size(min = 0, max = 50, message = "岗位名称长度不能超过50个字符") + public String getPostName() + { + return postName; + } + + public void setPostName(String postName) + { + this.postName = postName; + } + + @NotBlank(message = "显示顺序不能为空") + public String getPostSort() + { + return postSort; + } + + public void setPostSort(String postSort) + { + this.postSort = postSort; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public boolean isFlag() + { + return flag; + } + + public void setFlag(boolean flag) + { + this.flag = flag; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("postId", getPostId()) + .append("postCode", getPostCode()) + .append("postName", getPostName()) + .append("postSort", getPostSort()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysRoleDept.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysRoleDept.java new file mode 100644 index 0000000..47b21bf --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysRoleDept.java @@ -0,0 +1,46 @@ +package com.ruoyi.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +/** + * 角色和部门关联 sys_role_dept + * + * @author ruoyi + */ +public class SysRoleDept +{ + /** 角色ID */ + private Long roleId; + + /** 部门ID */ + private Long deptId; + + public Long getRoleId() + { + return roleId; + } + + public void setRoleId(Long roleId) + { + this.roleId = roleId; + } + + public Long getDeptId() + { + return deptId; + } + + public void setDeptId(Long deptId) + { + this.deptId = deptId; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("roleId", getRoleId()) + .append("deptId", getDeptId()) + .toString(); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysRoleMenu.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysRoleMenu.java new file mode 100644 index 0000000..de10a74 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysRoleMenu.java @@ -0,0 +1,46 @@ +package com.ruoyi.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +/** + * 角色和菜单关联 sys_role_menu + * + * @author ruoyi + */ +public class SysRoleMenu +{ + /** 角色ID */ + private Long roleId; + + /** 菜单ID */ + private Long menuId; + + public Long getRoleId() + { + return roleId; + } + + public void setRoleId(Long roleId) + { + this.roleId = roleId; + } + + public Long getMenuId() + { + return menuId; + } + + public void setMenuId(Long menuId) + { + this.menuId = menuId; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("roleId", getRoleId()) + .append("menuId", getMenuId()) + .toString(); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserOnline.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserOnline.java new file mode 100644 index 0000000..e13506e --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserOnline.java @@ -0,0 +1,177 @@ +package com.ruoyi.system.domain; + +import java.util.Date; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.common.enums.OnlineStatus; + +/** + * 当前在线会话 sys_user_online + * + * @author ruoyi + */ +public class SysUserOnline extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 用户会话id */ + private String sessionId; + + /** 部门名称 */ + private String deptName; + + /** 登录名称 */ + private String loginName; + + /** 登录IP地址 */ + private String ipaddr; + + /** 登录地址 */ + private String loginLocation; + + /** 浏览器类型 */ + private String browser; + + /** 操作系统 */ + private String os; + + /** session创建时间 */ + private Date startTimestamp; + + /** session最后访问时间 */ + private Date lastAccessTime; + + /** 超时时间,单位为分钟 */ + private Long expireTime; + + /** 在线状态 */ + private OnlineStatus status = OnlineStatus.on_line; + + public String getSessionId() + { + return sessionId; + } + + public void setSessionId(String sessionId) + { + this.sessionId = sessionId; + } + + public String getDeptName() + { + return deptName; + } + + public void setDeptName(String deptName) + { + this.deptName = deptName; + } + + public String getLoginName() + { + return loginName; + } + + public void setLoginName(String loginName) + { + this.loginName = loginName; + } + + public String getIpaddr() + { + return ipaddr; + } + + public void setIpaddr(String ipaddr) + { + this.ipaddr = ipaddr; + } + + public String getLoginLocation() + { + return loginLocation; + } + + public void setLoginLocation(String loginLocation) + { + this.loginLocation = loginLocation; + } + + public String getBrowser() + { + return browser; + } + + public void setBrowser(String browser) + { + this.browser = browser; + } + + public String getOs() + { + return os; + } + + public void setOs(String os) + { + this.os = os; + } + + public Date getStartTimestamp() + { + return startTimestamp; + } + + public void setStartTimestamp(Date startTimestamp) + { + this.startTimestamp = startTimestamp; + } + + public Date getLastAccessTime() + { + return lastAccessTime; + } + + public void setLastAccessTime(Date lastAccessTime) + { + this.lastAccessTime = lastAccessTime; + } + + public Long getExpireTime() + { + return expireTime; + } + + public void setExpireTime(Long expireTime) + { + this.expireTime = expireTime; + } + + public OnlineStatus getStatus() + { + return status; + } + + public void setStatus(OnlineStatus status) + { + this.status = status; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("sessionId", getSessionId()) + .append("loginName", getLoginName()) + .append("deptName", getDeptName()) + .append("ipaddr", getIpaddr()) + .append("loginLocation", getLoginLocation()) + .append("browser", getBrowser()) + .append("os", getOs()) + .append("status", getStatus()) + .append("startTimestamp", getStartTimestamp()) + .append("lastAccessTime", getLastAccessTime()) + .append("expireTime", getExpireTime()) + .toString(); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserPost.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserPost.java new file mode 100644 index 0000000..6e8c416 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserPost.java @@ -0,0 +1,46 @@ +package com.ruoyi.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +/** + * 用户和岗位关联 sys_user_post + * + * @author ruoyi + */ +public class SysUserPost +{ + /** 用户ID */ + private Long userId; + + /** 岗位ID */ + private Long postId; + + public Long getUserId() + { + return userId; + } + + public void setUserId(Long userId) + { + this.userId = userId; + } + + public Long getPostId() + { + return postId; + } + + public void setPostId(Long postId) + { + this.postId = postId; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("userId", getUserId()) + .append("postId", getPostId()) + .toString(); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserRole.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserRole.java new file mode 100644 index 0000000..4d15810 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserRole.java @@ -0,0 +1,46 @@ +package com.ruoyi.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +/** + * 用户和角色关联 sys_user_role + * + * @author ruoyi + */ +public class SysUserRole +{ + /** 用户ID */ + private Long userId; + + /** 角色ID */ + private Long roleId; + + public Long getUserId() + { + return userId; + } + + public void setUserId(Long userId) + { + this.userId = userId; + } + + public Long getRoleId() + { + return roleId; + } + + public void setRoleId(Long roleId) + { + this.roleId = roleId; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("userId", getUserId()) + .append("roleId", getRoleId()) + .toString(); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysConfigMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysConfigMapper.java new file mode 100644 index 0000000..84c949c --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysConfigMapper.java @@ -0,0 +1,76 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import com.ruoyi.system.domain.SysConfig; + +/** + * 参数配置 数据层 + * + * @author ruoyi + */ +public interface SysConfigMapper +{ + /** + * 查询参数配置信息 + * + * @param config 参数配置信息 + * @return 参数配置信息 + */ + public SysConfig selectConfig(SysConfig config); + + /** + * 通过ID查询配置 + * + * @param configId 参数ID + * @return 参数配置信息 + */ + public SysConfig selectConfigById(Long configId); + + /** + * 查询参数配置列表 + * + * @param config 参数配置信息 + * @return 参数配置集合 + */ + public List selectConfigList(SysConfig config); + + /** + * 根据键名查询参数配置信息 + * + * @param configKey 参数键名 + * @return 参数配置信息 + */ + public SysConfig checkConfigKeyUnique(String configKey); + + /** + * 新增参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + public int insertConfig(SysConfig config); + + /** + * 修改参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + public int updateConfig(SysConfig config); + + /** + * 删除参数配置 + * + * @param configId 参数主键 + * @return 结果 + */ + public int deleteConfigById(Long configId); + + /** + * 批量删除参数配置 + * + * @param configIds 需要删除的数据ID + * @return 结果 + */ + public int deleteConfigByIds(String[] configIds); +} \ No newline at end of file diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDeptMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDeptMapper.java new file mode 100644 index 0000000..af2b705 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDeptMapper.java @@ -0,0 +1,117 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import org.apache.ibatis.annotations.Param; +import com.ruoyi.common.core.domain.entity.SysDept; + +/** + * 部门管理 数据层 + * + * @author ruoyi + */ +public interface SysDeptMapper +{ + /** + * 查询下级部门数量 + * + * @param dept 部门信息 + * @return 结果 + */ + public int selectDeptCount(SysDept dept); + + /** + * 查询部门是否存在用户 + * + * @param deptId 部门ID + * @return 结果 + */ + public int checkDeptExistUser(Long deptId); + + /** + * 查询部门管理数据 + * + * @param dept 部门信息 + * @return 部门信息集合 + */ + public List selectDeptList(SysDept dept); + + /** + * 删除部门管理信息 + * + * @param deptId 部门ID + * @return 结果 + */ + public int deleteDeptById(Long deptId); + + /** + * 新增部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + public int insertDept(SysDept dept); + + /** + * 修改部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + public int updateDept(SysDept dept); + + /** + * 修改子元素关系 + * + * @param depts 子元素 + * @return 结果 + */ + public int updateDeptChildren(@Param("depts") List depts); + + /** + * 根据部门ID查询信息 + * + * @param deptId 部门ID + * @return 部门信息 + */ + public SysDept selectDeptById(Long deptId); + + /** + * 校验部门名称是否唯一 + * + * @param deptName 部门名称 + * @param parentId 父部门ID + * @return 结果 + */ + public SysDept checkDeptNameUnique(@Param("deptName") String deptName, @Param("parentId") Long parentId); + + /** + * 根据角色ID查询部门 + * + * @param roleId 角色ID + * @return 部门列表 + */ + public List selectRoleDeptTree(Long roleId); + + /** + * 修改所在部门正常状态 + * + * @param deptIds 部门ID组 + */ + public void updateDeptStatusNormal(Long[] deptIds); + + /** + * 根据ID查询所有子部门 + * + * @param deptId 部门ID + * @return 部门列表 + */ + public List selectChildrenDeptById(Long deptId); + + /** + * 根据ID查询所有子部门(正常状态) + * + * @param deptId 部门ID + * @return 子部门数 + */ + public int selectNormalChildrenDeptById(Long deptId); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictDataMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictDataMapper.java new file mode 100644 index 0000000..368252a --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictDataMapper.java @@ -0,0 +1,95 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import org.apache.ibatis.annotations.Param; +import com.ruoyi.common.core.domain.entity.SysDictData; + +/** + * 字典表 数据层 + * + * @author ruoyi + */ +public interface SysDictDataMapper +{ + /** + * 根据条件分页查询字典数据 + * + * @param dictData 字典数据信息 + * @return 字典数据集合信息 + */ + public List selectDictDataList(SysDictData dictData); + + /** + * 根据字典类型查询字典数据 + * + * @param dictType 字典类型 + * @return 字典数据集合信息 + */ + public List selectDictDataByType(String dictType); + + /** + * 根据字典类型和字典键值查询字典数据信息 + * + * @param dictType 字典类型 + * @param dictValue 字典键值 + * @return 字典标签 + */ + public String selectDictLabel(@Param("dictType") String dictType, @Param("dictValue") String dictValue); + + /** + * 根据字典数据ID查询信息 + * + * @param dictCode 字典数据ID + * @return 字典数据 + */ + public SysDictData selectDictDataById(Long dictCode); + + /** + * 查询字典数据 + * + * @param dictType 字典类型 + * @return 字典数据 + */ + public int countDictDataByType(String dictType); + + /** + * 通过字典ID删除字典数据信息 + * + * @param dictCode 字典数据ID + * @return 结果 + */ + public int deleteDictDataById(Long dictCode); + + /** + * 批量删除字典数据 + * + * @param ids 需要删除的数据 + * @return 结果 + */ + public int deleteDictDataByIds(String[] ids); + + /** + * 新增字典数据信息 + * + * @param dictData 字典数据信息 + * @return 结果 + */ + public int insertDictData(SysDictData dictData); + + /** + * 修改字典数据信息 + * + * @param dictData 字典数据信息 + * @return 结果 + */ + public int updateDictData(SysDictData dictData); + + /** + * 同步修改字典类型 + * + * @param oldDictType 旧字典类型 + * @param newDictType 新旧字典类型 + * @return 结果 + */ + public int updateDictDataType(@Param("oldDictType") String oldDictType, @Param("newDictType") String newDictType); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictTypeMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictTypeMapper.java new file mode 100644 index 0000000..25be4f4 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictTypeMapper.java @@ -0,0 +1,83 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import com.ruoyi.common.core.domain.entity.SysDictType; + +/** + * 字典表 数据层 + * + * @author ruoyi + */ +public interface SysDictTypeMapper +{ + /** + * 根据条件分页查询字典类型 + * + * @param dictType 字典类型信息 + * @return 字典类型集合信息 + */ + public List selectDictTypeList(SysDictType dictType); + + /** + * 根据所有字典类型 + * + * @return 字典类型集合信息 + */ + public List selectDictTypeAll(); + + /** + * 根据字典类型ID查询信息 + * + * @param dictId 字典类型ID + * @return 字典类型 + */ + public SysDictType selectDictTypeById(Long dictId); + + /** + * 根据字典类型查询信息 + * + * @param dictType 字典类型 + * @return 字典类型 + */ + public SysDictType selectDictTypeByType(String dictType); + + /** + * 通过字典ID删除字典信息 + * + * @param dictId 字典ID + * @return 结果 + */ + public int deleteDictTypeById(Long dictId); + + /** + * 批量删除字典类型 + * + * @param ids 需要删除的数据 + * @return 结果 + */ + public int deleteDictTypeByIds(Long[] ids); + + /** + * 新增字典类型信息 + * + * @param dictType 字典类型信息 + * @return 结果 + */ + public int insertDictType(SysDictType dictType); + + /** + * 修改字典类型信息 + * + * @param dictType 字典类型信息 + * @return 结果 + */ + public int updateDictType(SysDictType dictType); + + /** + * 校验字典类型称是否唯一 + * + * @param dictType 字典类型 + * @return 结果 + */ + public SysDictType checkDictTypeUnique(String dictType); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysLogininforMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysLogininforMapper.java new file mode 100644 index 0000000..7ee47dc --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysLogininforMapper.java @@ -0,0 +1,42 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import com.ruoyi.system.domain.SysLogininfor; + +/** + * 系统访问日志情况信息 数据层 + * + * @author ruoyi + */ +public interface SysLogininforMapper +{ + /** + * 新增系统登录日志 + * + * @param logininfor 访问日志对象 + */ + public void insertLogininfor(SysLogininfor logininfor); + + /** + * 查询系统登录日志集合 + * + * @param logininfor 访问日志对象 + * @return 登录记录集合 + */ + public List selectLogininforList(SysLogininfor logininfor); + + /** + * 批量删除系统登录日志 + * + * @param ids 需要删除的数据 + * @return 结果 + */ + public int deleteLogininforByIds(String[] ids); + + /** + * 清空系统登录日志 + * + * @return 结果 + */ + public int cleanLogininfor(); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysMenuMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysMenuMapper.java new file mode 100644 index 0000000..e07903f --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysMenuMapper.java @@ -0,0 +1,132 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import org.apache.ibatis.annotations.Param; +import com.ruoyi.common.core.domain.entity.SysMenu; + +/** + * 菜单表 数据层 + * + * @author ruoyi + */ +public interface SysMenuMapper +{ + /** + * 查询系统所有菜单(含按钮) + * + * @return 菜单列表 + */ + public List selectMenuAll(); + + /** + * 根据用户ID查询菜单 + * + * @param userId 用户ID + * @return 菜单列表 + */ + public List selectMenuAllByUserId(Long userId); + + /** + * 查询系统正常显示菜单(不含按钮) + * + * @return 菜单列表 + */ + public List selectMenuNormalAll(); + + /** + * 根据用户ID查询菜单 + * + * @param userId 用户ID + * @return 菜单列表 + */ + public List selectMenusByUserId(Long userId); + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + public List selectPermsByUserId(Long userId); + + /** + * 根据角色ID查询权限 + * + * @param roleId 角色ID + * @return 权限列表 + */ + public List selectPermsByRoleId(Long roleId); + + /** + * 根据角色ID查询菜单 + * + * @param roleId 角色ID + * @return 菜单列表 + */ + public List selectMenuTree(Long roleId); + + /** + * 查询系统菜单列表 + * + * @param menu 菜单信息 + * @return 菜单列表 + */ + public List selectMenuList(SysMenu menu); + + /** + * 查询系统菜单列表 + * + * @param menu 菜单信息 + * @return 菜单列表 + */ + public List selectMenuListByUserId(SysMenu menu); + + /** + * 删除菜单管理信息 + * + * @param menuId 菜单ID + * @return 结果 + */ + public int deleteMenuById(Long menuId); + + /** + * 根据菜单ID查询信息 + * + * @param menuId 菜单ID + * @return 菜单信息 + */ + public SysMenu selectMenuById(Long menuId); + + /** + * 查询菜单数量 + * + * @param parentId 菜单父ID + * @return 结果 + */ + public int selectCountMenuByParentId(Long parentId); + + /** + * 新增菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + public int insertMenu(SysMenu menu); + + /** + * 修改菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + public int updateMenu(SysMenu menu); + + /** + * 校验菜单名称是否唯一 + * + * @param menuName 菜单名称 + * @param parentId 父菜单ID + * @return 结果 + */ + public SysMenu checkMenuNameUnique(@Param("menuName") String menuName, @Param("parentId") Long parentId); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysNoticeMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysNoticeMapper.java new file mode 100644 index 0000000..dd402e2 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysNoticeMapper.java @@ -0,0 +1,52 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import com.ruoyi.system.domain.SysNotice; + +/** + * 公告 数据层 + * + * @author ruoyi + */ +public interface SysNoticeMapper +{ + /** + * 查询公告信息 + * + * @param noticeId 公告ID + * @return 公告信息 + */ + public SysNotice selectNoticeById(Long noticeId); + + /** + * 查询公告列表 + * + * @param notice 公告信息 + * @return 公告集合 + */ + public List selectNoticeList(SysNotice notice); + + /** + * 新增公告 + * + * @param notice 公告信息 + * @return 结果 + */ + public int insertNotice(SysNotice notice); + + /** + * 修改公告 + * + * @param notice 公告信息 + * @return 结果 + */ + public int updateNotice(SysNotice notice); + + /** + * 批量删除公告 + * + * @param noticeIds 需要删除的数据ID + * @return 结果 + */ + public int deleteNoticeByIds(String[] noticeIds); +} \ No newline at end of file diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysOperLogMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysOperLogMapper.java new file mode 100644 index 0000000..b40bd29 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysOperLogMapper.java @@ -0,0 +1,48 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import com.ruoyi.system.domain.SysOperLog; + +/** + * 操作日志 数据层 + * + * @author ruoyi + */ +public interface SysOperLogMapper +{ + /** + * 新增操作日志 + * + * @param operLog 操作日志对象 + */ + public void insertOperlog(SysOperLog operLog); + + /** + * 查询系统操作日志集合 + * + * @param operLog 操作日志对象 + * @return 操作日志集合 + */ + public List selectOperLogList(SysOperLog operLog); + + /** + * 批量删除系统操作日志 + * + * @param ids 需要删除的数据 + * @return 结果 + */ + public int deleteOperLogByIds(String[] ids); + + /** + * 查询操作日志详细 + * + * @param operId 操作ID + * @return 操作日志对象 + */ + public SysOperLog selectOperLogById(Long operId); + + /** + * 清空操作日志 + */ + public void cleanOperLog(); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysPostMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysPostMapper.java new file mode 100644 index 0000000..188f335 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysPostMapper.java @@ -0,0 +1,83 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import com.ruoyi.system.domain.SysPost; + +/** + * 岗位信息 数据层 + * + * @author ruoyi + */ +public interface SysPostMapper +{ + /** + * 查询岗位数据集合 + * + * @param post 岗位信息 + * @return 岗位数据集合 + */ + public List selectPostList(SysPost post); + + /** + * 查询所有岗位 + * + * @return 岗位列表 + */ + public List selectPostAll(); + + /** + * 根据用户ID查询岗位 + * + * @param userId 用户ID + * @return 岗位列表 + */ + public List selectPostsByUserId(Long userId); + + /** + * 通过岗位ID查询岗位信息 + * + * @param postId 岗位ID + * @return 角色对象信息 + */ + public SysPost selectPostById(Long postId); + + /** + * 批量删除岗位信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deletePostByIds(Long[] ids); + + /** + * 修改岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + public int updatePost(SysPost post); + + /** + * 新增岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + public int insertPost(SysPost post); + + /** + * 校验岗位名称 + * + * @param postName 岗位名称 + * @return 结果 + */ + public SysPost checkPostNameUnique(String postName); + + /** + * 校验岗位编码 + * + * @param postCode 岗位编码 + * @return 结果 + */ + public SysPost checkPostCodeUnique(String postCode); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleDeptMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleDeptMapper.java new file mode 100644 index 0000000..f9d3a2f --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleDeptMapper.java @@ -0,0 +1,44 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import com.ruoyi.system.domain.SysRoleDept; + +/** + * 角色与部门关联表 数据层 + * + * @author ruoyi + */ +public interface SysRoleDeptMapper +{ + /** + * 通过角色ID删除角色和部门关联 + * + * @param roleId 角色ID + * @return 结果 + */ + public int deleteRoleDeptByRoleId(Long roleId); + + /** + * 批量删除角色部门关联信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteRoleDept(Long[] ids); + + /** + * 查询部门使用数量 + * + * @param deptId 部门ID + * @return 结果 + */ + public int selectCountRoleDeptByDeptId(Long deptId); + + /** + * 批量新增角色部门信息 + * + * @param roleDeptList 角色部门列表 + * @return 结果 + */ + public int batchRoleDept(List roleDeptList); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMapper.java new file mode 100644 index 0000000..78a3ec7 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMapper.java @@ -0,0 +1,84 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import com.ruoyi.common.core.domain.entity.SysRole; + +/** + * 角色表 数据层 + * + * @author ruoyi + */ +public interface SysRoleMapper +{ + /** + * 根据条件分页查询角色数据 + * + * @param role 角色信息 + * @return 角色数据集合信息 + */ + public List selectRoleList(SysRole role); + + /** + * 根据用户ID查询角色 + * + * @param userId 用户ID + * @return 角色列表 + */ + public List selectRolesByUserId(Long userId); + + /** + * 通过角色ID查询角色 + * + * @param roleId 角色ID + * @return 角色对象信息 + */ + public SysRole selectRoleById(Long roleId); + + /** + * 通过角色ID删除角色 + * + * @param roleId 角色ID + * @return 结果 + */ + public int deleteRoleById(Long roleId); + + /** + * 批量角色用户信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteRoleByIds(Long[] ids); + + /** + * 修改角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + public int updateRole(SysRole role); + + /** + * 新增角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + public int insertRole(SysRole role); + + /** + * 校验角色名称是否唯一 + * + * @param roleName 角色名称 + * @return 角色信息 + */ + public SysRole checkRoleNameUnique(String roleName); + + /** + * 校验角色权限是否唯一 + * + * @param roleKey 角色权限 + * @return 角色信息 + */ + public SysRole checkRoleKeyUnique(String roleKey); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMenuMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMenuMapper.java new file mode 100644 index 0000000..9b88796 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMenuMapper.java @@ -0,0 +1,44 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import com.ruoyi.system.domain.SysRoleMenu; + +/** + * 角色与菜单关联表 数据层 + * + * @author ruoyi + */ +public interface SysRoleMenuMapper +{ + /** + * 通过角色ID删除角色和菜单关联 + * + * @param roleId 角色ID + * @return 结果 + */ + public int deleteRoleMenuByRoleId(Long roleId); + + /** + * 批量删除角色菜单关联信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteRoleMenu(Long[] ids); + + /** + * 查询菜单使用数量 + * + * @param menuId 菜单ID + * @return 结果 + */ + public int selectCountRoleMenuByMenuId(Long menuId); + + /** + * 批量新增角色菜单信息 + * + * @param roleMenuList 角色菜单列表 + * @return 结果 + */ + public int batchRoleMenu(List roleMenuList); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java new file mode 100644 index 0000000..309d88e --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java @@ -0,0 +1,124 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import com.ruoyi.common.core.domain.entity.SysUser; + +/** + * 用户表 数据层 + * + * @author ruoyi + */ +public interface SysUserMapper +{ + /** + * 根据条件分页查询用户列表 + * + * @param sysUser 用户信息 + * @return 用户信息集合信息 + */ + public List selectUserList(SysUser sysUser); + + /** + * 根据条件分页查询已配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List selectAllocatedList(SysUser user); + + /** + * 根据条件分页查询未分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List selectUnallocatedList(SysUser user); + + /** + * 通过用户名查询用户 + * + * @param userName 用户名 + * @return 用户对象信息 + */ + public SysUser selectUserByLoginName(String userName); + + /** + * 通过手机号码查询用户 + * + * @param phoneNumber 手机号码 + * @return 用户对象信息 + */ + public SysUser selectUserByPhoneNumber(String phoneNumber); + + /** + * 通过邮箱查询用户 + * + * @param email 邮箱 + * @return 用户对象信息 + */ + public SysUser selectUserByEmail(String email); + + /** + * 通过用户ID查询用户 + * + * @param userId 用户ID + * @return 用户对象信息 + */ + public SysUser selectUserById(Long userId); + + /** + * 通过用户ID删除用户 + * + * @param userId 用户ID + * @return 结果 + */ + public int deleteUserById(Long userId); + + /** + * 批量删除用户信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteUserByIds(Long[] ids); + + /** + * 修改用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int updateUser(SysUser user); + + /** + * 新增用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int insertUser(SysUser user); + + /** + * 校验用户名称是否唯一 + * + * @param loginName 登录名称 + * @return 结果 + */ + public SysUser checkLoginNameUnique(String loginName); + + /** + * 校验手机号码是否唯一 + * + * @param phonenumber 手机号码 + * @return 结果 + */ + public SysUser checkPhoneUnique(String phonenumber); + + /** + * 校验email是否唯一 + * + * @param email 用户邮箱 + * @return 结果 + */ + public SysUser checkEmailUnique(String email); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserOnlineMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserOnlineMapper.java new file mode 100644 index 0000000..5bd68ed --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserOnlineMapper.java @@ -0,0 +1,52 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import com.ruoyi.system.domain.SysUserOnline; + +/** + * 在线用户 数据层 + * + * @author ruoyi + */ +public interface SysUserOnlineMapper +{ + /** + * 通过会话序号查询信息 + * + * @param sessionId 会话ID + * @return 在线用户信息 + */ + public SysUserOnline selectOnlineById(String sessionId); + + /** + * 通过会话序号删除信息 + * + * @param sessionId 会话ID + * @return 在线用户信息 + */ + public int deleteOnlineById(String sessionId); + + /** + * 保存会话信息 + * + * @param online 会话信息 + * @return 结果 + */ + public int saveOnline(SysUserOnline online); + + /** + * 查询会话集合 + * + * @param userOnline 会话参数 + * @return 会话集合 + */ + public List selectUserOnlineList(SysUserOnline userOnline); + + /** + * 查询过期会话集合 + * + * @param lastAccessTime 过期时间 + * @return 会话集合 + */ + public List selectOnlineByExpired(String lastAccessTime); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserPostMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserPostMapper.java new file mode 100644 index 0000000..d72ac45 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserPostMapper.java @@ -0,0 +1,44 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import com.ruoyi.system.domain.SysUserPost; + +/** + * 用户与岗位关联表 数据层 + * + * @author ruoyi + */ +public interface SysUserPostMapper +{ + /** + * 通过用户ID删除用户和岗位关联 + * + * @param userId 用户ID + * @return 结果 + */ + public int deleteUserPostByUserId(Long userId); + + /** + * 通过岗位ID查询岗位使用数量 + * + * @param postId 岗位ID + * @return 结果 + */ + public int countUserPostById(Long postId); + + /** + * 批量删除用户和岗位关联 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteUserPost(Long[] ids); + + /** + * 批量新增用户岗位信息 + * + * @param userPostList 用户角色列表 + * @return 结果 + */ + public int batchUserPost(List userPostList); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserRoleMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserRoleMapper.java new file mode 100644 index 0000000..669d1ec --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserRoleMapper.java @@ -0,0 +1,70 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import org.apache.ibatis.annotations.Param; +import com.ruoyi.system.domain.SysUserRole; + +/** + * 用户与角色关联表 数据层 + * + * @author ruoyi + */ +public interface SysUserRoleMapper +{ + /** + * 通过用户ID查询用户和角色关联 + * + * @param userId 用户ID + * @return 用户和角色关联列表 + */ + public List selectUserRoleByUserId(Long userId); + + /** + * 通过用户ID删除用户和角色关联 + * + * @param userId 用户ID + * @return 结果 + */ + public int deleteUserRoleByUserId(Long userId); + + /** + * 批量删除用户和角色关联 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteUserRole(Long[] ids); + + /** + * 通过角色ID查询角色使用数量 + * + * @param roleId 角色ID + * @return 结果 + */ + public int countUserRoleByRoleId(Long roleId); + + /** + * 批量新增用户角色信息 + * + * @param userRoleList 用户角色列表 + * @return 结果 + */ + public int batchUserRole(List userRoleList); + + /** + * 删除用户和角色关联信息 + * + * @param userRole 用户和角色关联信息 + * @return 结果 + */ + public int deleteUserRoleInfo(SysUserRole userRole); + + /** + * 批量取消授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要删除的用户数据ID + * @return 结果 + */ + public int deleteUserRoleInfos(@Param("roleId") Long roleId, @Param("userIds") Long[] userIds); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysConfigService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysConfigService.java new file mode 100644 index 0000000..e1c553c --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysConfigService.java @@ -0,0 +1,82 @@ +package com.ruoyi.system.service; + +import java.util.List; +import com.ruoyi.system.domain.SysConfig; + +/** + * 参数配置 服务层 + * + * @author ruoyi + */ +public interface ISysConfigService +{ + /** + * 查询参数配置信息 + * + * @param configId 参数配置ID + * @return 参数配置信息 + */ + public SysConfig selectConfigById(Long configId); + + /** + * 根据键名查询参数配置信息 + * + * @param configKey 参数键名 + * @return 参数键值 + */ + public String selectConfigByKey(String configKey); + + /** + * 查询参数配置列表 + * + * @param config 参数配置信息 + * @return 参数配置集合 + */ + public List selectConfigList(SysConfig config); + + /** + * 新增参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + public int insertConfig(SysConfig config); + + /** + * 修改参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + public int updateConfig(SysConfig config); + + /** + * 批量删除参数配置信息 + * + * @param ids 需要删除的数据ID + */ + public void deleteConfigByIds(String ids); + + /** + * 加载参数缓存数据 + */ + public void loadingConfigCache(); + + /** + * 清空参数缓存数据 + */ + public void clearConfigCache(); + + /** + * 重置参数缓存数据 + */ + public void resetConfigCache(); + + /** + * 校验参数键名是否唯一 + * + * @param config 参数信息 + * @return 结果 + */ + public boolean checkConfigKeyUnique(SysConfig config); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDeptService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDeptService.java new file mode 100644 index 0000000..10ce107 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDeptService.java @@ -0,0 +1,117 @@ +package com.ruoyi.system.service; + +import java.util.List; +import com.ruoyi.common.core.domain.Ztree; +import com.ruoyi.common.core.domain.entity.SysDept; +import com.ruoyi.common.core.domain.entity.SysRole; + +/** + * 部门管理 服务层 + * + * @author ruoyi + */ +public interface ISysDeptService +{ + /** + * 查询部门管理数据 + * + * @param dept 部门信息 + * @return 部门信息集合 + */ + public List selectDeptList(SysDept dept); + + /** + * 查询部门管理树 + * + * @param dept 部门信息 + * @return 所有部门信息 + */ + public List selectDeptTree(SysDept dept); + + /** + * 查询部门管理树(排除下级) + * + * @param dept 部门信息 + * @return 所有部门信息 + */ + public List selectDeptTreeExcludeChild(SysDept dept); + + /** + * 根据角色ID查询菜单 + * + * @param role 角色对象 + * @return 菜单列表 + */ + public List roleDeptTreeData(SysRole role); + + /** + * 根据父部门ID查询下级部门数量 + * + * @param parentId 父部门ID + * @return 结果 + */ + public int selectDeptCount(Long parentId); + + /** + * 查询部门是否存在用户 + * + * @param deptId 部门ID + * @return 结果 true 存在 false 不存在 + */ + public boolean checkDeptExistUser(Long deptId); + + /** + * 删除部门管理信息 + * + * @param deptId 部门ID + * @return 结果 + */ + public int deleteDeptById(Long deptId); + + /** + * 新增保存部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + public int insertDept(SysDept dept); + + /** + * 修改保存部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + public int updateDept(SysDept dept); + + /** + * 根据部门ID查询信息 + * + * @param deptId 部门ID + * @return 部门信息 + */ + public SysDept selectDeptById(Long deptId); + + /** + * 根据ID查询所有子部门(正常状态) + * + * @param deptId 部门ID + * @return 子部门数 + */ + public int selectNormalChildrenDeptById(Long deptId); + + /** + * 校验部门名称是否唯一 + * + * @param dept 部门信息 + * @return 结果 + */ + public boolean checkDeptNameUnique(SysDept dept); + + /** + * 校验部门是否有数据权限 + * + * @param deptId 部门id + */ + public void checkDeptDataScope(Long deptId); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictDataService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictDataService.java new file mode 100644 index 0000000..efb6bdd --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictDataService.java @@ -0,0 +1,60 @@ +package com.ruoyi.system.service; + +import java.util.List; +import com.ruoyi.common.core.domain.entity.SysDictData; + +/** + * 字典 业务层 + * + * @author ruoyi + */ +public interface ISysDictDataService +{ + /** + * 根据条件分页查询字典数据 + * + * @param dictData 字典数据信息 + * @return 字典数据集合信息 + */ + public List selectDictDataList(SysDictData dictData); + + /** + * 根据字典类型和字典键值查询字典数据信息 + * + * @param dictType 字典类型 + * @param dictValue 字典键值 + * @return 字典标签 + */ + public String selectDictLabel(String dictType, String dictValue); + + /** + * 根据字典数据ID查询信息 + * + * @param dictCode 字典数据ID + * @return 字典数据 + */ + public SysDictData selectDictDataById(Long dictCode); + + /** + * 批量删除字典数据 + * + * @param ids 需要删除的数据 + */ + public void deleteDictDataByIds(String ids); + + /** + * 新增保存字典数据信息 + * + * @param dictData 字典数据信息 + * @return 结果 + */ + public int insertDictData(SysDictData dictData); + + /** + * 修改保存字典数据信息 + * + * @param dictData 字典数据信息 + * @return 结果 + */ + public int updateDictData(SysDictData dictData); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictTypeService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictTypeService.java new file mode 100644 index 0000000..126d258 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictTypeService.java @@ -0,0 +1,107 @@ +package com.ruoyi.system.service; + +import java.util.List; +import com.ruoyi.common.core.domain.Ztree; +import com.ruoyi.common.core.domain.entity.SysDictData; +import com.ruoyi.common.core.domain.entity.SysDictType; + +/** + * 字典 业务层 + * + * @author ruoyi + */ +public interface ISysDictTypeService +{ + /** + * 根据条件分页查询字典类型 + * + * @param dictType 字典类型信息 + * @return 字典类型集合信息 + */ + public List selectDictTypeList(SysDictType dictType); + + /** + * 根据所有字典类型 + * + * @return 字典类型集合信息 + */ + public List selectDictTypeAll(); + + /** + * 根据字典类型查询字典数据 + * + * @param dictType 字典类型 + * @return 字典数据集合信息 + */ + public List selectDictDataByType(String dictType); + + /** + * 根据字典类型ID查询信息 + * + * @param dictId 字典类型ID + * @return 字典类型 + */ + public SysDictType selectDictTypeById(Long dictId); + + /** + * 根据字典类型查询信息 + * + * @param dictType 字典类型 + * @return 字典类型 + */ + public SysDictType selectDictTypeByType(String dictType); + + /** + * 批量删除字典类型 + * + * @param ids 需要删除的数据 + */ + public void deleteDictTypeByIds(String ids); + + /** + * 加载字典缓存数据 + */ + public void loadingDictCache(); + + /** + * 清空字典缓存数据 + */ + public void clearDictCache(); + + /** + * 重置字典缓存数据 + */ + public void resetDictCache(); + + /** + * 新增保存字典类型信息 + * + * @param dictType 字典类型信息 + * @return 结果 + */ + public int insertDictType(SysDictType dictType); + + /** + * 修改保存字典类型信息 + * + * @param dictType 字典类型信息 + * @return 结果 + */ + public int updateDictType(SysDictType dictType); + + /** + * 校验字典类型称是否唯一 + * + * @param dictType 字典类型 + * @return 结果 + */ + public boolean checkDictTypeUnique(SysDictType dictType); + + /** + * 查询字典类型树 + * + * @param dictType 字典类型 + * @return 所有字典类型 + */ + public List selectDictTree(SysDictType dictType); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysLogininforService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysLogininforService.java new file mode 100644 index 0000000..ea99dff --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysLogininforService.java @@ -0,0 +1,40 @@ +package com.ruoyi.system.service; + +import java.util.List; +import com.ruoyi.system.domain.SysLogininfor; + +/** + * 系统访问日志情况信息 服务层 + * + * @author ruoyi + */ +public interface ISysLogininforService +{ + /** + * 新增系统登录日志 + * + * @param logininfor 访问日志对象 + */ + public void insertLogininfor(SysLogininfor logininfor); + + /** + * 查询系统登录日志集合 + * + * @param logininfor 访问日志对象 + * @return 登录记录集合 + */ + public List selectLogininforList(SysLogininfor logininfor); + + /** + * 批量删除系统登录日志 + * + * @param ids 需要删除的数据 + * @return 结果 + */ + public int deleteLogininforByIds(String ids); + + /** + * 清空系统登录日志 + */ + public void cleanLogininfor(); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysMenuService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysMenuService.java new file mode 100644 index 0000000..d6db936 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysMenuService.java @@ -0,0 +1,139 @@ +package com.ruoyi.system.service; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import com.ruoyi.common.core.domain.Ztree; +import com.ruoyi.common.core.domain.entity.SysMenu; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; + +/** + * 菜单 业务层 + * + * @author ruoyi + */ +public interface ISysMenuService +{ + /** + * 根据用户ID查询菜单 + * + * @param user 用户信息 + * @return 菜单列表 + */ + public List selectMenusByUser(SysUser user); + + /** + * 查询系统菜单列表 + * + * @param menu 菜单信息 + * @param userId 用户ID + * @return 菜单列表 + */ + public List selectMenuList(SysMenu menu, Long userId); + + /** + * 查询菜单集合 + * + * @param userId 用户ID + * @return 所有菜单信息 + */ + public List selectMenuAll(Long userId); + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + public Set selectPermsByUserId(Long userId); + + /** + * 根据角色ID查询权限 + * + * @param roleId 角色ID + * @return 权限列表 + */ + public Set selectPermsByRoleId(Long roleId); + + /** + * 根据角色ID查询菜单 + * + * @param role 角色对象 + * @param userId 用户ID + * @return 菜单列表 + */ + public List roleMenuTreeData(SysRole role, Long userId); + + /** + * 查询所有菜单信息 + * + * @param userId 用户ID + * @return 菜单列表 + */ + public List menuTreeData(Long userId); + + /** + * 查询系统所有权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + public Map selectPermsAll(Long userId); + + /** + * 删除菜单管理信息 + * + * @param menuId 菜单ID + * @return 结果 + */ + public int deleteMenuById(Long menuId); + + /** + * 根据菜单ID查询信息 + * + * @param menuId 菜单ID + * @return 菜单信息 + */ + public SysMenu selectMenuById(Long menuId); + + /** + * 查询菜单数量 + * + * @param parentId 菜单父ID + * @return 结果 + */ + public int selectCountMenuByParentId(Long parentId); + + /** + * 查询菜单使用数量 + * + * @param menuId 菜单ID + * @return 结果 + */ + public int selectCountRoleMenuByMenuId(Long menuId); + + /** + * 新增保存菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + public int insertMenu(SysMenu menu); + + /** + * 修改保存菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + public int updateMenu(SysMenu menu); + + /** + * 校验菜单名称是否唯一 + * + * @param menu 菜单信息 + * @return 结果 + */ + public boolean checkMenuNameUnique(SysMenu menu); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysNoticeService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysNoticeService.java new file mode 100644 index 0000000..6398b5a --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysNoticeService.java @@ -0,0 +1,52 @@ +package com.ruoyi.system.service; + +import java.util.List; +import com.ruoyi.system.domain.SysNotice; + +/** + * 公告 服务层 + * + * @author ruoyi + */ +public interface ISysNoticeService +{ + /** + * 查询公告信息 + * + * @param noticeId 公告ID + * @return 公告信息 + */ + public SysNotice selectNoticeById(Long noticeId); + + /** + * 查询公告列表 + * + * @param notice 公告信息 + * @return 公告集合 + */ + public List selectNoticeList(SysNotice notice); + + /** + * 新增公告 + * + * @param notice 公告信息 + * @return 结果 + */ + public int insertNotice(SysNotice notice); + + /** + * 修改公告 + * + * @param notice 公告信息 + * @return 结果 + */ + public int updateNotice(SysNotice notice); + + /** + * 删除公告信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteNoticeByIds(String ids); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysOperLogService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysOperLogService.java new file mode 100644 index 0000000..3cdc5d2 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysOperLogService.java @@ -0,0 +1,48 @@ +package com.ruoyi.system.service; + +import java.util.List; +import com.ruoyi.system.domain.SysOperLog; + +/** + * 操作日志 服务层 + * + * @author ruoyi + */ +public interface ISysOperLogService +{ + /** + * 新增操作日志 + * + * @param operLog 操作日志对象 + */ + public void insertOperlog(SysOperLog operLog); + + /** + * 查询系统操作日志集合 + * + * @param operLog 操作日志对象 + * @return 操作日志集合 + */ + public List selectOperLogList(SysOperLog operLog); + + /** + * 批量删除系统操作日志 + * + * @param ids 需要删除的数据 + * @return 结果 + */ + public int deleteOperLogByIds(String ids); + + /** + * 查询操作日志详细 + * + * @param operId 操作ID + * @return 操作日志对象 + */ + public SysOperLog selectOperLogById(Long operId); + + /** + * 清空操作日志 + */ + public void cleanOperLog(); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysPostService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysPostService.java new file mode 100644 index 0000000..7f2a189 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysPostService.java @@ -0,0 +1,91 @@ +package com.ruoyi.system.service; + +import java.util.List; +import com.ruoyi.system.domain.SysPost; + +/** + * 岗位信息 服务层 + * + * @author ruoyi + */ +public interface ISysPostService +{ + /** + * 查询岗位信息集合 + * + * @param post 岗位信息 + * @return 岗位信息集合 + */ + public List selectPostList(SysPost post); + + /** + * 查询所有岗位 + * + * @return 岗位列表 + */ + public List selectPostAll(); + + /** + * 根据用户ID查询岗位 + * + * @param userId 用户ID + * @return 岗位列表 + */ + public List selectPostsByUserId(Long userId); + + /** + * 通过岗位ID查询岗位信息 + * + * @param postId 岗位ID + * @return 角色对象信息 + */ + public SysPost selectPostById(Long postId); + + /** + * 批量删除岗位信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deletePostByIds(String ids); + + /** + * 新增保存岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + public int insertPost(SysPost post); + + /** + * 修改保存岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + public int updatePost(SysPost post); + + /** + * 通过岗位ID查询岗位使用数量 + * + * @param postId 岗位ID + * @return 结果 + */ + public int countUserPostById(Long postId); + + /** + * 校验岗位名称 + * + * @param post 岗位信息 + * @return 结果 + */ + public boolean checkPostNameUnique(SysPost post); + + /** + * 校验岗位编码 + * + * @param post 岗位信息 + * @return 结果 + */ + public boolean checkPostCodeUnique(SysPost post); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysRoleService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysRoleService.java new file mode 100644 index 0000000..76d1bb5 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysRoleService.java @@ -0,0 +1,166 @@ +package com.ruoyi.system.service; + +import java.util.List; +import java.util.Set; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.system.domain.SysUserRole; + +/** + * 角色业务层 + * + * @author ruoyi + */ +public interface ISysRoleService +{ + /** + * 根据条件分页查询角色数据 + * + * @param role 角色信息 + * @return 角色数据集合信息 + */ + public List selectRoleList(SysRole role); + + /** + * 根据用户ID查询角色列表 + * + * @param userId 用户ID + * @return 权限列表 + */ + public Set selectRoleKeys(Long userId); + + /** + * 根据用户ID查询角色权限 + * + * @param userId 用户ID + * @return 角色列表 + */ + public List selectRolesByUserId(Long userId); + + /** + * 查询所有角色 + * + * @return 角色列表 + */ + public List selectRoleAll(); + + /** + * 通过角色ID查询角色 + * + * @param roleId 角色ID + * @return 角色对象信息 + */ + public SysRole selectRoleById(Long roleId); + + /** + * 通过角色ID删除角色 + * + * @param roleId 角色ID + * @return 结果 + */ + public boolean deleteRoleById(Long roleId); + + /** + * 批量删除角色用户信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + * @throws Exception 异常 + */ + public int deleteRoleByIds(String ids); + + /** + * 新增保存角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + public int insertRole(SysRole role); + + /** + * 修改保存角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + public int updateRole(SysRole role); + + /** + * 修改数据权限信息 + * + * @param role 角色信息 + * @return 结果 + */ + public int authDataScope(SysRole role); + + /** + * 校验角色名称是否唯一 + * + * @param role 角色信息 + * @return 结果 + */ + public boolean checkRoleNameUnique(SysRole role); + + /** + * 校验角色权限是否唯一 + * + * @param role 角色信息 + * @return 结果 + */ + public boolean checkRoleKeyUnique(SysRole role); + + /** + * 校验角色是否允许操作 + * + * @param role 角色信息 + */ + public void checkRoleAllowed(SysRole role); + + /** + * 校验角色是否有数据权限 + * + * @param roleId 角色id + */ + public void checkRoleDataScope(Long roleId); + + /** + * 通过角色ID查询角色使用数量 + * + * @param roleId 角色ID + * @return 结果 + */ + public int countUserRoleByRoleId(Long roleId); + + /** + * 角色状态修改 + * + * @param role 角色信息 + * @return 结果 + */ + public int changeStatus(SysRole role); + + /** + * 取消授权用户角色 + * + * @param userRole 用户和角色关联信息 + * @return 结果 + */ + public int deleteAuthUser(SysUserRole userRole); + + /** + * 批量取消授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要删除的用户数据ID + * @return 结果 + */ + public int deleteAuthUsers(Long roleId, String userIds); + + /** + * 批量选择授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要删除的用户数据ID + * @return 结果 + */ + public int insertAuthUsers(Long roleId, String userIds); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserOnlineService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserOnlineService.java new file mode 100644 index 0000000..50c99a1 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserOnlineService.java @@ -0,0 +1,75 @@ +package com.ruoyi.system.service; + +import java.util.Date; +import java.util.List; +import com.ruoyi.system.domain.SysUserOnline; + +/** + * 在线用户 服务层 + * + * @author ruoyi + */ +public interface ISysUserOnlineService +{ + /** + * 通过会话序号查询信息 + * + * @param sessionId 会话ID + * @return 在线用户信息 + */ + public SysUserOnline selectOnlineById(String sessionId); + + /** + * 通过会话序号删除信息 + * + * @param sessionId 会话ID + * @return 在线用户信息 + */ + public void deleteOnlineById(String sessionId); + + /** + * 通过会话序号删除信息 + * + * @param sessions 会话ID集合 + * @return 在线用户信息 + */ + public void batchDeleteOnline(List sessions); + + /** + * 保存会话信息 + * + * @param online 会话信息 + */ + public void saveOnline(SysUserOnline online); + + /** + * 查询会话集合 + * + * @param userOnline 分页参数 + * @return 会话集合 + */ + public List selectUserOnlineList(SysUserOnline userOnline); + + /** + * 强退用户 + * + * @param sessionId 会话ID + */ + public void forceLogout(String sessionId); + + /** + * 清理用户缓存 + * + * @param loginName 登录名称 + * @param sessionId 会话ID + */ + public void removeUserCache(String loginName, String sessionId); + + /** + * 查询会话集合 + * + * @param expiredDate 有效期 + * @return 会话集合 + */ + public List selectOnlineByExpired(Date expiredDate); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java new file mode 100644 index 0000000..35ad2e2 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java @@ -0,0 +1,214 @@ +package com.ruoyi.system.service; + +import java.util.List; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.system.domain.SysUserRole; + +/** + * 用户 业务层 + * + * @author ruoyi + */ +public interface ISysUserService +{ + /** + * 根据条件分页查询用户列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List selectUserList(SysUser user); + + /** + * 根据条件分页查询已分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List selectAllocatedList(SysUser user); + + /** + * 根据条件分页查询未分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List selectUnallocatedList(SysUser user); + + /** + * 通过用户名查询用户 + * + * @param userName 用户名 + * @return 用户对象信息 + */ + public SysUser selectUserByLoginName(String userName); + + /** + * 通过手机号码查询用户 + * + * @param phoneNumber 手机号码 + * @return 用户对象信息 + */ + public SysUser selectUserByPhoneNumber(String phoneNumber); + + /** + * 通过邮箱查询用户 + * + * @param email 邮箱 + * @return 用户对象信息 + */ + public SysUser selectUserByEmail(String email); + + /** + * 通过用户ID查询用户 + * + * @param userId 用户ID + * @return 用户对象信息 + */ + public SysUser selectUserById(Long userId); + + /** + * 通过用户ID查询用户和角色关联 + * + * @param userId 用户ID + * @return 用户和角色关联列表 + */ + public List selectUserRoleByUserId(Long userId); + + /** + * 通过用户ID删除用户 + * + * @param userId 用户ID + * @return 结果 + */ + public int deleteUserById(Long userId); + + /** + * 批量删除用户信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + * @throws Exception 异常 + */ + public int deleteUserByIds(String ids); + + /** + * 保存用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int insertUser(SysUser user); + + /** + * 注册用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public boolean registerUser(SysUser user); + + /** + * 保存用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int updateUser(SysUser user); + + /** + * 修改用户详细信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int updateUserInfo(SysUser user); + + /** + * 用户授权角色 + * + * @param userId 用户ID + * @param roleIds 角色组 + */ + public void insertUserAuth(Long userId, Long[] roleIds); + + /** + * 修改用户密码信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int resetUserPwd(SysUser user); + + /** + * 校验用户名称是否唯一 + * + * @param user 用户信息 + * @return 结果 + */ + public boolean checkLoginNameUnique(SysUser user); + + /** + * 校验手机号码是否唯一 + * + * @param user 用户信息 + * @return 结果 + */ + public boolean checkPhoneUnique(SysUser user); + + /** + * 校验email是否唯一 + * + * @param user 用户信息 + * @return 结果 + */ + public boolean checkEmailUnique(SysUser user); + + /** + * 校验用户是否允许操作 + * + * @param user 用户信息 + */ + public void checkUserAllowed(SysUser user); + + /** + * 校验用户是否有数据权限 + * + * @param userId 用户id + */ + public void checkUserDataScope(Long userId); + + /** + * 根据用户ID查询用户所属角色组 + * + * @param userId 用户ID + * @return 结果 + */ + public String selectUserRoleGroup(Long userId); + + /** + * 根据用户ID查询用户所属岗位组 + * + * @param userId 用户ID + * @return 结果 + */ + public String selectUserPostGroup(Long userId); + + /** + * 导入用户数据 + * + * @param userList 用户数据列表 + * @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据 + * @param operName 操作用户 + * @return 结果 + */ + public String importUser(List userList, Boolean isUpdateSupport, String operName); + + /** + * 用户状态修改 + * + * @param user 用户信息 + * @return 结果 + */ + public int changeStatus(SysUser user); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysConfigServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysConfigServiceImpl.java new file mode 100644 index 0000000..cf164e5 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysConfigServiceImpl.java @@ -0,0 +1,219 @@ +package com.ruoyi.system.service.impl; + +import java.util.List; +import javax.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.CacheUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.domain.SysConfig; +import com.ruoyi.system.mapper.SysConfigMapper; +import com.ruoyi.system.service.ISysConfigService; + +/** + * 参数配置 服务层实现 + * + * @author ruoyi + */ +@Service +public class SysConfigServiceImpl implements ISysConfigService +{ + @Autowired + private SysConfigMapper configMapper; + + /** + * 项目启动时,初始化参数到缓存 + */ + @PostConstruct + public void init() + { + loadingConfigCache(); + } + + /** + * 查询参数配置信息 + * + * @param configId 参数配置ID + * @return 参数配置信息 + */ + @Override + public SysConfig selectConfigById(Long configId) + { + SysConfig config = new SysConfig(); + config.setConfigId(configId); + return configMapper.selectConfig(config); + } + + /** + * 根据键名查询参数配置信息 + * + * @param configKey 参数key + * @return 参数键值 + */ + @Override + public String selectConfigByKey(String configKey) + { + String configValue = Convert.toStr(CacheUtils.get(getCacheName(), getCacheKey(configKey))); + if (StringUtils.isNotEmpty(configValue)) + { + return configValue; + } + SysConfig config = new SysConfig(); + config.setConfigKey(configKey); + SysConfig retConfig = configMapper.selectConfig(config); + if (StringUtils.isNotNull(retConfig)) + { + CacheUtils.put(getCacheName(), getCacheKey(configKey), retConfig.getConfigValue()); + return retConfig.getConfigValue(); + } + return StringUtils.EMPTY; + } + + /** + * 查询参数配置列表 + * + * @param config 参数配置信息 + * @return 参数配置集合 + */ + @Override + public List selectConfigList(SysConfig config) + { + return configMapper.selectConfigList(config); + } + + /** + * 新增参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + @Override + public int insertConfig(SysConfig config) + { + int row = configMapper.insertConfig(config); + if (row > 0) + { + CacheUtils.put(getCacheName(), getCacheKey(config.getConfigKey()), config.getConfigValue()); + } + return row; + } + + /** + * 修改参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + @Override + public int updateConfig(SysConfig config) + { + SysConfig temp = configMapper.selectConfigById(config.getConfigId()); + if (!StringUtils.equals(temp.getConfigKey(), config.getConfigKey())) + { + CacheUtils.remove(getCacheName(), getCacheKey(temp.getConfigKey())); + } + + int row = configMapper.updateConfig(config); + if (row > 0) + { + CacheUtils.put(getCacheName(), getCacheKey(config.getConfigKey()), config.getConfigValue()); + } + return row; + } + + /** + * 批量删除参数配置对象 + * + * @param ids 需要删除的数据ID + */ + @Override + public void deleteConfigByIds(String ids) + { + Long[] configIds = Convert.toLongArray(ids); + for (Long configId : configIds) + { + SysConfig config = selectConfigById(configId); + if (StringUtils.equals(UserConstants.YES, config.getConfigType())) + { + throw new ServiceException(String.format("内置参数【%1$s】不能删除 ", config.getConfigKey())); + } + configMapper.deleteConfigById(configId); + CacheUtils.remove(getCacheName(), getCacheKey(config.getConfigKey())); + } + } + + /** + * 加载参数缓存数据 + */ + @Override + public void loadingConfigCache() + { + List configsList = configMapper.selectConfigList(new SysConfig()); + for (SysConfig config : configsList) + { + CacheUtils.put(getCacheName(), getCacheKey(config.getConfigKey()), config.getConfigValue()); + } + } + + /** + * 清空参数缓存数据 + */ + @Override + public void clearConfigCache() + { + CacheUtils.removeAll(getCacheName()); + } + + /** + * 重置参数缓存数据 + */ + @Override + public void resetConfigCache() + { + clearConfigCache(); + loadingConfigCache(); + } + + /** + * 校验参数键名是否唯一 + * + * @param config 参数配置信息 + * @return 结果 + */ + @Override + public boolean checkConfigKeyUnique(SysConfig config) + { + Long configId = StringUtils.isNull(config.getConfigId()) ? -1L : config.getConfigId(); + SysConfig info = configMapper.checkConfigKeyUnique(config.getConfigKey()); + if (StringUtils.isNotNull(info) && info.getConfigId().longValue() != configId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 获取cache name + * + * @return 缓存名 + */ + private String getCacheName() + { + return Constants.SYS_CONFIG_CACHE; + } + + /** + * 设置cache key + * + * @param configKey 参数键 + * @return 缓存键key + */ + private String getCacheKey(String configKey) + { + return Constants.SYS_CONFIG_KEY + configKey; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java new file mode 100644 index 0000000..ede0caf --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java @@ -0,0 +1,328 @@ +package com.ruoyi.system.service.impl; + +import java.util.ArrayList; +import java.util.List; +import org.apache.commons.lang3.ArrayUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.ruoyi.common.annotation.DataScope; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.domain.Ztree; +import com.ruoyi.common.core.domain.entity.SysDept; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.ShiroUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.system.mapper.SysDeptMapper; +import com.ruoyi.system.service.ISysDeptService; + +/** + * 部门管理 服务实现 + * + * @author ruoyi + */ +@Service +public class SysDeptServiceImpl implements ISysDeptService +{ + @Autowired + private SysDeptMapper deptMapper; + + /** + * 查询部门管理数据 + * + * @param dept 部门信息 + * @return 部门信息集合 + */ + @Override + @DataScope(deptAlias = "d") + public List selectDeptList(SysDept dept) + { + return deptMapper.selectDeptList(dept); + } + + /** + * 查询部门管理树 + * + * @param dept 部门信息 + * @return 所有部门信息 + */ + @Override + @DataScope(deptAlias = "d") + public List selectDeptTree(SysDept dept) + { + List deptList = deptMapper.selectDeptList(dept); + List ztrees = initZtree(deptList); + return ztrees; + } + + /** + * 查询部门管理树(排除下级) + * + * @param deptId 部门ID + * @return 所有部门信息 + */ + @Override + @DataScope(deptAlias = "d") + public List selectDeptTreeExcludeChild(SysDept dept) + { + Long excludeId = dept.getExcludeId(); + List depts = deptMapper.selectDeptList(dept); + if (excludeId.intValue() > 0) + { + depts.removeIf(d -> d.getDeptId().intValue() == excludeId || ArrayUtils.contains(StringUtils.split(d.getAncestors(), ","), excludeId + "")); + } + List ztrees = initZtree(depts); + return ztrees; + } + + /** + * 根据角色ID查询部门(数据权限) + * + * @param role 角色对象 + * @return 部门列表(数据权限) + */ + @Override + public List roleDeptTreeData(SysRole role) + { + Long roleId = role.getRoleId(); + List ztrees = new ArrayList(); + List deptList = SpringUtils.getAopProxy(this).selectDeptList(new SysDept()); + if (StringUtils.isNotNull(roleId)) + { + List roleDeptList = deptMapper.selectRoleDeptTree(roleId); + ztrees = initZtree(deptList, roleDeptList); + } + else + { + ztrees = initZtree(deptList); + } + return ztrees; + } + + /** + * 对象转部门树 + * + * @param deptList 部门列表 + * @return 树结构列表 + */ + public List initZtree(List deptList) + { + return initZtree(deptList, null); + } + + /** + * 对象转部门树 + * + * @param deptList 部门列表 + * @param roleDeptList 角色已存在菜单列表 + * @return 树结构列表 + */ + public List initZtree(List deptList, List roleDeptList) + { + + List ztrees = new ArrayList(); + boolean isCheck = StringUtils.isNotNull(roleDeptList); + for (SysDept dept : deptList) + { + if (UserConstants.DEPT_NORMAL.equals(dept.getStatus())) + { + Ztree ztree = new Ztree(); + ztree.setId(dept.getDeptId()); + ztree.setpId(dept.getParentId()); + ztree.setName(dept.getDeptName()); + ztree.setTitle(dept.getDeptName()); + if (isCheck) + { + ztree.setChecked(roleDeptList.contains(dept.getDeptId() + dept.getDeptName())); + } + ztrees.add(ztree); + } + } + return ztrees; + } + + /** + * 根据父部门ID查询下级部门数量 + * + * @param parentId 部门ID + * @return 结果 + */ + @Override + public int selectDeptCount(Long parentId) + { + SysDept dept = new SysDept(); + dept.setParentId(parentId); + return deptMapper.selectDeptCount(dept); + } + + /** + * 查询部门是否存在用户 + * + * @param deptId 部门ID + * @return 结果 true 存在 false 不存在 + */ + @Override + public boolean checkDeptExistUser(Long deptId) + { + int result = deptMapper.checkDeptExistUser(deptId); + return result > 0; + } + + /** + * 删除部门管理信息 + * + * @param deptId 部门ID + * @return 结果 + */ + @Override + public int deleteDeptById(Long deptId) + { + return deptMapper.deleteDeptById(deptId); + } + + /** + * 新增保存部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + @Override + public int insertDept(SysDept dept) + { + SysDept info = deptMapper.selectDeptById(dept.getParentId()); + // 如果父节点不为"正常"状态,则不允许新增子节点 + if (!UserConstants.DEPT_NORMAL.equals(info.getStatus())) + { + throw new ServiceException("部门停用,不允许新增"); + } + dept.setAncestors(info.getAncestors() + "," + dept.getParentId()); + return deptMapper.insertDept(dept); + } + + /** + * 修改保存部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + @Override + @Transactional + public int updateDept(SysDept dept) + { + SysDept newParentDept = deptMapper.selectDeptById(dept.getParentId()); + SysDept oldDept = selectDeptById(dept.getDeptId()); + if (StringUtils.isNotNull(newParentDept) && StringUtils.isNotNull(oldDept)) + { + String newAncestors = newParentDept.getAncestors() + "," + newParentDept.getDeptId(); + String oldAncestors = oldDept.getAncestors(); + dept.setAncestors(newAncestors); + updateDeptChildren(dept.getDeptId(), newAncestors, oldAncestors); + } + int result = deptMapper.updateDept(dept); + if (UserConstants.DEPT_NORMAL.equals(dept.getStatus()) && StringUtils.isNotEmpty(dept.getAncestors()) + && !StringUtils.equals("0", dept.getAncestors())) + { + // 如果该部门是启用状态,则启用该部门的所有上级部门 + updateParentDeptStatusNormal(dept); + } + return result; + } + + /** + * 修改该部门的父级部门状态 + * + * @param dept 当前部门 + */ + private void updateParentDeptStatusNormal(SysDept dept) + { + String ancestors = dept.getAncestors(); + Long[] deptIds = Convert.toLongArray(ancestors); + deptMapper.updateDeptStatusNormal(deptIds); + } + + /** + * 修改子元素关系 + * + * @param deptId 被修改的部门ID + * @param newAncestors 新的父ID集合 + * @param oldAncestors 旧的父ID集合 + */ + public void updateDeptChildren(Long deptId, String newAncestors, String oldAncestors) + { + List children = deptMapper.selectChildrenDeptById(deptId); + for (SysDept child : children) + { + child.setAncestors(child.getAncestors().replaceFirst(oldAncestors, newAncestors)); + } + if (children.size() > 0) + { + deptMapper.updateDeptChildren(children); + } + } + + /** + * 根据部门ID查询信息 + * + * @param deptId 部门ID + * @return 部门信息 + */ + @Override + public SysDept selectDeptById(Long deptId) + { + return deptMapper.selectDeptById(deptId); + } + + /** + * 根据ID查询所有子部门(正常状态) + * + * @param deptId 部门ID + * @return 子部门数 + */ + @Override + public int selectNormalChildrenDeptById(Long deptId) + { + return deptMapper.selectNormalChildrenDeptById(deptId); + } + + /** + * 校验部门名称是否唯一 + * + * @param dept 部门信息 + * @return 结果 + */ + @Override + public boolean checkDeptNameUnique(SysDept dept) + { + Long deptId = StringUtils.isNull(dept.getDeptId()) ? -1L : dept.getDeptId(); + SysDept info = deptMapper.checkDeptNameUnique(dept.getDeptName(), dept.getParentId()); + if (StringUtils.isNotNull(info) && info.getDeptId().longValue() != deptId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验部门是否有数据权限 + * + * @param deptId 部门id + */ + @Override + public void checkDeptDataScope(Long deptId) + { + if (!SysUser.isAdmin(ShiroUtils.getUserId())) + { + SysDept dept = new SysDept(); + dept.setDeptId(deptId); + List depts = SpringUtils.getAopProxy(this).selectDeptList(dept); + if (StringUtils.isEmpty(depts)) + { + throw new ServiceException("没有权限访问部门数据!"); + } + } + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictDataServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictDataServiceImpl.java new file mode 100644 index 0000000..bf9e6e5 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictDataServiceImpl.java @@ -0,0 +1,113 @@ +package com.ruoyi.system.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.common.core.domain.entity.SysDictData; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.utils.DictUtils; +import com.ruoyi.system.mapper.SysDictDataMapper; +import com.ruoyi.system.service.ISysDictDataService; + +/** + * 字典 业务层处理 + * + * @author ruoyi + */ +@Service +public class SysDictDataServiceImpl implements ISysDictDataService +{ + @Autowired + private SysDictDataMapper dictDataMapper; + + /** + * 根据条件分页查询字典数据 + * + * @param dictData 字典数据信息 + * @return 字典数据集合信息 + */ + @Override + public List selectDictDataList(SysDictData dictData) + { + return dictDataMapper.selectDictDataList(dictData); + } + + /** + * 根据字典类型和字典键值查询字典数据信息 + * + * @param dictType 字典类型 + * @param dictValue 字典键值 + * @return 字典标签 + */ + @Override + public String selectDictLabel(String dictType, String dictValue) + { + return dictDataMapper.selectDictLabel(dictType, dictValue); + } + + /** + * 根据字典数据ID查询信息 + * + * @param dictCode 字典数据ID + * @return 字典数据 + */ + @Override + public SysDictData selectDictDataById(Long dictCode) + { + return dictDataMapper.selectDictDataById(dictCode); + } + + /** + * 批量删除字典数据 + * + * @param ids 需要删除的数据 + */ + @Override + public void deleteDictDataByIds(String ids) + { + Long[] dictCodes = Convert.toLongArray(ids); + for (Long dictCode : dictCodes) + { + SysDictData data = selectDictDataById(dictCode); + dictDataMapper.deleteDictDataById(dictCode); + List dictDatas = dictDataMapper.selectDictDataByType(data.getDictType()); + DictUtils.setDictCache(data.getDictType(), dictDatas); + } + } + + /** + * 新增保存字典数据信息 + * + * @param data 字典数据信息 + * @return 结果 + */ + @Override + public int insertDictData(SysDictData data) + { + int row = dictDataMapper.insertDictData(data); + if (row > 0) + { + List dictDatas = dictDataMapper.selectDictDataByType(data.getDictType()); + DictUtils.setDictCache(data.getDictType(), dictDatas); + } + return row; + } + + /** + * 修改保存字典数据信息 + * + * @param data 字典数据信息 + * @return 结果 + */ + @Override + public int updateDictData(SysDictData data) + { + int row = dictDataMapper.updateDictData(data); + if (row > 0) + { + List dictDatas = dictDataMapper.selectDictDataByType(data.getDictType()); + DictUtils.setDictCache(data.getDictType(), dictDatas); + } + return row; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictTypeServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictTypeServiceImpl.java new file mode 100644 index 0000000..f5f3a13 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictTypeServiceImpl.java @@ -0,0 +1,260 @@ +package com.ruoyi.system.service.impl; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import javax.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.domain.Ztree; +import com.ruoyi.common.core.domain.entity.SysDictData; +import com.ruoyi.common.core.domain.entity.SysDictType; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.DictUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.mapper.SysDictDataMapper; +import com.ruoyi.system.mapper.SysDictTypeMapper; +import com.ruoyi.system.service.ISysDictTypeService; + +/** + * 字典 业务层处理 + * + * @author ruoyi + */ +@Service +public class SysDictTypeServiceImpl implements ISysDictTypeService +{ + @Autowired + private SysDictTypeMapper dictTypeMapper; + + @Autowired + private SysDictDataMapper dictDataMapper; + + /** + * 项目启动时,初始化字典到缓存 + */ + @PostConstruct + public void init() + { + loadingDictCache(); + } + + /** + * 根据条件分页查询字典类型 + * + * @param dictType 字典类型信息 + * @return 字典类型集合信息 + */ + @Override + public List selectDictTypeList(SysDictType dictType) + { + return dictTypeMapper.selectDictTypeList(dictType); + } + + /** + * 根据所有字典类型 + * + * @return 字典类型集合信息 + */ + @Override + public List selectDictTypeAll() + { + return dictTypeMapper.selectDictTypeAll(); + } + + /** + * 根据字典类型查询字典数据 + * + * @param dictType 字典类型 + * @return 字典数据集合信息 + */ + @Override + public List selectDictDataByType(String dictType) + { + List dictDatas = DictUtils.getDictCache(dictType); + if (StringUtils.isNotEmpty(dictDatas)) + { + return dictDatas; + } + dictDatas = dictDataMapper.selectDictDataByType(dictType); + if (StringUtils.isNotEmpty(dictDatas)) + { + DictUtils.setDictCache(dictType, dictDatas); + return dictDatas; + } + return null; + } + + /** + * 根据字典类型ID查询信息 + * + * @param dictId 字典类型ID + * @return 字典类型 + */ + @Override + public SysDictType selectDictTypeById(Long dictId) + { + return dictTypeMapper.selectDictTypeById(dictId); + } + + /** + * 根据字典类型查询信息 + * + * @param dictType 字典类型 + * @return 字典类型 + */ + @Override + public SysDictType selectDictTypeByType(String dictType) + { + return dictTypeMapper.selectDictTypeByType(dictType); + } + + /** + * 批量删除字典类型 + * + * @param ids 需要删除的数据 + */ + @Override + public void deleteDictTypeByIds(String ids) + { + Long[] dictIds = Convert.toLongArray(ids); + for (Long dictId : dictIds) + { + SysDictType dictType = selectDictTypeById(dictId); + if (dictDataMapper.countDictDataByType(dictType.getDictType()) > 0) + { + throw new ServiceException(String.format("%1$s已分配,不能删除", dictType.getDictName())); + } + dictTypeMapper.deleteDictTypeById(dictId); + DictUtils.removeDictCache(dictType.getDictType()); + } + } + + /** + * 加载字典缓存数据 + */ + @Override + public void loadingDictCache() + { + SysDictData dictData = new SysDictData(); + dictData.setStatus("0"); + Map> dictDataMap = dictDataMapper.selectDictDataList(dictData).stream().collect(Collectors.groupingBy(SysDictData::getDictType)); + for (Map.Entry> entry : dictDataMap.entrySet()) + { + DictUtils.setDictCache(entry.getKey(), entry.getValue().stream().sorted(Comparator.comparing(SysDictData::getDictSort)).collect(Collectors.toList())); + } + } + + /** + * 清空字典缓存数据 + */ + @Override + public void clearDictCache() + { + DictUtils.clearDictCache(); + } + + /** + * 重置字典缓存数据 + */ + @Override + public void resetDictCache() + { + clearDictCache(); + loadingDictCache(); + } + + /** + * 新增保存字典类型信息 + * + * @param dict 字典类型信息 + * @return 结果 + */ + @Override + public int insertDictType(SysDictType dict) + { + int row = dictTypeMapper.insertDictType(dict); + if (row > 0) + { + DictUtils.setDictCache(dict.getDictType(), null); + } + return row; + } + + /** + * 修改保存字典类型信息 + * + * @param dict 字典类型信息 + * @return 结果 + */ + @Override + @Transactional + public int updateDictType(SysDictType dict) + { + SysDictType oldDict = dictTypeMapper.selectDictTypeById(dict.getDictId()); + dictDataMapper.updateDictDataType(oldDict.getDictType(), dict.getDictType()); + int row = dictTypeMapper.updateDictType(dict); + if (row > 0) + { + List dictDatas = dictDataMapper.selectDictDataByType(dict.getDictType()); + DictUtils.setDictCache(dict.getDictType(), dictDatas); + } + return row; + } + + /** + * 校验字典类型称是否唯一 + * + * @param dict 字典类型 + * @return 结果 + */ + @Override + public boolean checkDictTypeUnique(SysDictType dict) + { + Long dictId = StringUtils.isNull(dict.getDictId()) ? -1L : dict.getDictId(); + SysDictType dictType = dictTypeMapper.checkDictTypeUnique(dict.getDictType()); + if (StringUtils.isNotNull(dictType) && dictType.getDictId().longValue() != dictId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 查询字典类型树 + * + * @param dictType 字典类型 + * @return 所有字典类型 + */ + @Override + public List selectDictTree(SysDictType dictType) + { + List ztrees = new ArrayList(); + List dictList = dictTypeMapper.selectDictTypeList(dictType); + for (SysDictType dict : dictList) + { + if (UserConstants.DICT_NORMAL.equals(dict.getStatus())) + { + Ztree ztree = new Ztree(); + ztree.setId(dict.getDictId()); + ztree.setName(transDictName(dict)); + ztree.setTitle(dict.getDictType()); + ztrees.add(ztree); + } + } + return ztrees; + } + + public String transDictName(SysDictType dictType) + { + StringBuffer sb = new StringBuffer(); + sb.append("(" + dictType.getDictName() + ")"); + sb.append("   " + dictType.getDictType()); + return sb.toString(); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysLogininforServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysLogininforServiceImpl.java new file mode 100644 index 0000000..13f5c1a --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysLogininforServiceImpl.java @@ -0,0 +1,66 @@ +package com.ruoyi.system.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.system.domain.SysLogininfor; +import com.ruoyi.system.mapper.SysLogininforMapper; +import com.ruoyi.system.service.ISysLogininforService; + +/** + * 系统访问日志情况信息 服务层处理 + * + * @author ruoyi + */ +@Service +public class SysLogininforServiceImpl implements ISysLogininforService +{ + + @Autowired + private SysLogininforMapper logininforMapper; + + /** + * 新增系统登录日志 + * + * @param logininfor 访问日志对象 + */ + @Override + public void insertLogininfor(SysLogininfor logininfor) + { + logininforMapper.insertLogininfor(logininfor); + } + + /** + * 查询系统登录日志集合 + * + * @param logininfor 访问日志对象 + * @return 登录记录集合 + */ + @Override + public List selectLogininforList(SysLogininfor logininfor) + { + return logininforMapper.selectLogininforList(logininfor); + } + + /** + * 批量删除系统登录日志 + * + * @param ids 需要删除的数据 + * @return 结果 + */ + @Override + public int deleteLogininforByIds(String ids) + { + return logininforMapper.deleteLogininforByIds(Convert.toStrArray(ids)); + } + + /** + * 清空系统登录日志 + */ + @Override + public void cleanLogininfor() + { + logininforMapper.cleanLogininfor(); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java new file mode 100644 index 0000000..4ff052b --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java @@ -0,0 +1,410 @@ +package com.ruoyi.system.service.impl; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.domain.Ztree; +import com.ruoyi.common.core.domain.entity.SysMenu; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.mapper.SysMenuMapper; +import com.ruoyi.system.mapper.SysRoleMenuMapper; +import com.ruoyi.system.service.ISysMenuService; + +/** + * 菜单 业务层处理 + * + * @author ruoyi + */ +@Service +public class SysMenuServiceImpl implements ISysMenuService +{ + public static final String PREMISSION_STRING = "perms[\"{0}\"]"; + + @Autowired + private SysMenuMapper menuMapper; + + @Autowired + private SysRoleMenuMapper roleMenuMapper; + + /** + * 根据用户查询菜单 + * + * @param user 用户信息 + * @return 菜单列表 + */ + @Override + public List selectMenusByUser(SysUser user) + { + List menus = new LinkedList(); + // 管理员显示所有菜单信息 + if (user.isAdmin()) + { + menus = menuMapper.selectMenuNormalAll(); + } + else + { + menus = menuMapper.selectMenusByUserId(user.getUserId()); + } + return getChildPerms(menus, 0); + } + + /** + * 查询菜单集合 + * + * @return 所有菜单信息 + */ + @Override + public List selectMenuList(SysMenu menu, Long userId) + { + List menuList = null; + if (SysUser.isAdmin(userId)) + { + menuList = menuMapper.selectMenuList(menu); + } + else + { + menu.getParams().put("userId", userId); + menuList = menuMapper.selectMenuListByUserId(menu); + } + return menuList; + } + + /** + * 查询菜单集合 + * + * @return 所有菜单信息 + */ + @Override + public List selectMenuAll(Long userId) + { + List menuList = null; + if (SysUser.isAdmin(userId)) + { + menuList = menuMapper.selectMenuAll(); + } + else + { + menuList = menuMapper.selectMenuAllByUserId(userId); + } + return menuList; + } + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + @Override + public Set selectPermsByUserId(Long userId) + { + List perms = menuMapper.selectPermsByUserId(userId); + Set permsSet = new HashSet<>(); + for (String perm : perms) + { + if (StringUtils.isNotEmpty(perm)) + { + permsSet.addAll(Arrays.asList(perm.trim().split(","))); + } + } + return permsSet; + } + + /** + * 根据角色ID查询权限 + * + * @param roleId 角色ID + * @return 权限列表 + */ + @Override + public Set selectPermsByRoleId(Long roleId) + { + List perms = menuMapper.selectPermsByRoleId(roleId); + Set permsSet = new HashSet<>(); + for (String perm : perms) + { + if (StringUtils.isNotEmpty(perm)) + { + permsSet.addAll(Arrays.asList(perm.trim().split(","))); + } + } + return permsSet; + } + + /** + * 根据角色ID查询菜单 + * + * @param role 角色对象 + * @return 菜单列表 + */ + @Override + public List roleMenuTreeData(SysRole role, Long userId) + { + Long roleId = role.getRoleId(); + List ztrees = new ArrayList(); + List menuList = selectMenuAll(userId); + if (StringUtils.isNotNull(roleId)) + { + List roleMenuList = menuMapper.selectMenuTree(roleId); + ztrees = initZtree(menuList, roleMenuList, true); + } + else + { + ztrees = initZtree(menuList, null, true); + } + return ztrees; + } + + /** + * 查询所有菜单 + * + * @return 菜单列表 + */ + @Override + public List menuTreeData(Long userId) + { + List menuList = selectMenuAll(userId); + List ztrees = initZtree(menuList); + return ztrees; + } + + /** + * 查询系统所有权限 + * + * @return 权限列表 + */ + @Override + public LinkedHashMap selectPermsAll(Long userId) + { + LinkedHashMap section = new LinkedHashMap<>(); + List permissions = selectMenuAll(userId); + if (StringUtils.isNotEmpty(permissions)) + { + for (SysMenu menu : permissions) + { + section.put(menu.getUrl(), MessageFormat.format(PREMISSION_STRING, menu.getPerms())); + } + } + return section; + } + + /** + * 对象转菜单树 + * + * @param menuList 菜单列表 + * @return 树结构列表 + */ + public List initZtree(List menuList) + { + return initZtree(menuList, null, false); + } + + /** + * 对象转菜单树 + * + * @param menuList 菜单列表 + * @param roleMenuList 角色已存在菜单列表 + * @param permsFlag 是否需要显示权限标识 + * @return 树结构列表 + */ + public List initZtree(List menuList, List roleMenuList, boolean permsFlag) + { + List ztrees = new ArrayList(); + boolean isCheck = StringUtils.isNotNull(roleMenuList); + for (SysMenu menu : menuList) + { + Ztree ztree = new Ztree(); + ztree.setId(menu.getMenuId()); + ztree.setpId(menu.getParentId()); + ztree.setName(transMenuName(menu, permsFlag)); + ztree.setTitle(menu.getMenuName()); + if (isCheck) + { + ztree.setChecked(roleMenuList.contains(menu.getMenuId() + menu.getPerms())); + } + ztrees.add(ztree); + } + return ztrees; + } + + public String transMenuName(SysMenu menu, boolean permsFlag) + { + StringBuffer sb = new StringBuffer(); + sb.append(menu.getMenuName()); + if (permsFlag) + { + sb.append("   " + menu.getPerms() + ""); + } + return sb.toString(); + } + + /** + * 删除菜单管理信息 + * + * @param menuId 菜单ID + * @return 结果 + */ + @Override + public int deleteMenuById(Long menuId) + { + return menuMapper.deleteMenuById(menuId); + } + + /** + * 根据菜单ID查询信息 + * + * @param menuId 菜单ID + * @return 菜单信息 + */ + @Override + public SysMenu selectMenuById(Long menuId) + { + return menuMapper.selectMenuById(menuId); + } + + /** + * 查询子菜单数量 + * + * @param parentId 父级菜单ID + * @return 结果 + */ + @Override + public int selectCountMenuByParentId(Long parentId) + { + return menuMapper.selectCountMenuByParentId(parentId); + } + + /** + * 查询菜单使用数量 + * + * @param menuId 菜单ID + * @return 结果 + */ + @Override + public int selectCountRoleMenuByMenuId(Long menuId) + { + return roleMenuMapper.selectCountRoleMenuByMenuId(menuId); + } + + /** + * 新增保存菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + @Override + public int insertMenu(SysMenu menu) + { + return menuMapper.insertMenu(menu); + } + + /** + * 修改保存菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + @Override + public int updateMenu(SysMenu menu) + { + return menuMapper.updateMenu(menu); + } + + /** + * 校验菜单名称是否唯一 + * + * @param menu 菜单信息 + * @return 结果 + */ + @Override + public boolean checkMenuNameUnique(SysMenu menu) + { + Long menuId = StringUtils.isNull(menu.getMenuId()) ? -1L : menu.getMenuId(); + SysMenu info = menuMapper.checkMenuNameUnique(menu.getMenuName(), menu.getParentId()); + if (StringUtils.isNotNull(info) && info.getMenuId().longValue() != menuId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 根据父节点的ID获取所有子节点 + * + * @param list 分类表 + * @param parentId 传入的父节点ID + * @return String + */ + public List getChildPerms(List list, int parentId) + { + List returnList = new ArrayList(); + for (Iterator iterator = list.iterator(); iterator.hasNext();) + { + SysMenu t = (SysMenu) iterator.next(); + // 一、根据传入的某个父节点ID,遍历该父节点的所有子节点 + if (t.getParentId() == parentId) + { + recursionFn(list, t); + returnList.add(t); + } + } + return returnList; + } + + /** + * 递归列表 + * + * @param list + * @param t + */ + private void recursionFn(List list, SysMenu t) + { + // 得到子节点列表 + List childList = getChildList(list, t); + t.setChildren(childList); + for (SysMenu tChild : childList) + { + if (hasChild(list, tChild)) + { + recursionFn(list, tChild); + } + } + } + + /** + * 得到子节点列表 + */ + private List getChildList(List list, SysMenu t) + { + List tlist = new ArrayList(); + Iterator it = list.iterator(); + while (it.hasNext()) + { + SysMenu n = (SysMenu) it.next(); + if (n.getParentId().longValue() == t.getMenuId().longValue()) + { + tlist.add(n); + } + } + return tlist; + } + + /** + * 判断是否有子节点 + */ + private boolean hasChild(List list, SysMenu t) + { + return getChildList(list, t).size() > 0; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysNoticeServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysNoticeServiceImpl.java new file mode 100644 index 0000000..7968b9a --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysNoticeServiceImpl.java @@ -0,0 +1,82 @@ +package com.ruoyi.system.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.system.domain.SysNotice; +import com.ruoyi.system.mapper.SysNoticeMapper; +import com.ruoyi.system.service.ISysNoticeService; + +/** + * 公告 服务层实现 + * + * @author ruoyi + * @date 2018-06-25 + */ +@Service +public class SysNoticeServiceImpl implements ISysNoticeService +{ + @Autowired + private SysNoticeMapper noticeMapper; + + /** + * 查询公告信息 + * + * @param noticeId 公告ID + * @return 公告信息 + */ + @Override + public SysNotice selectNoticeById(Long noticeId) + { + return noticeMapper.selectNoticeById(noticeId); + } + + /** + * 查询公告列表 + * + * @param notice 公告信息 + * @return 公告集合 + */ + @Override + public List selectNoticeList(SysNotice notice) + { + return noticeMapper.selectNoticeList(notice); + } + + /** + * 新增公告 + * + * @param notice 公告信息 + * @return 结果 + */ + @Override + public int insertNotice(SysNotice notice) + { + return noticeMapper.insertNotice(notice); + } + + /** + * 修改公告 + * + * @param notice 公告信息 + * @return 结果 + */ + @Override + public int updateNotice(SysNotice notice) + { + return noticeMapper.updateNotice(notice); + } + + /** + * 删除公告对象 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + @Override + public int deleteNoticeByIds(String ids) + { + return noticeMapper.deleteNoticeByIds(Convert.toStrArray(ids)); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysOperLogServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysOperLogServiceImpl.java new file mode 100644 index 0000000..78ae353 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysOperLogServiceImpl.java @@ -0,0 +1,77 @@ +package com.ruoyi.system.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.system.domain.SysOperLog; +import com.ruoyi.system.mapper.SysOperLogMapper; +import com.ruoyi.system.service.ISysOperLogService; + +/** + * 操作日志 服务层处理 + * + * @author ruoyi + */ +@Service +public class SysOperLogServiceImpl implements ISysOperLogService +{ + @Autowired + private SysOperLogMapper operLogMapper; + + /** + * 新增操作日志 + * + * @param operLog 操作日志对象 + */ + @Override + public void insertOperlog(SysOperLog operLog) + { + operLogMapper.insertOperlog(operLog); + } + + /** + * 查询系统操作日志集合 + * + * @param operLog 操作日志对象 + * @return 操作日志集合 + */ + @Override + public List selectOperLogList(SysOperLog operLog) + { + return operLogMapper.selectOperLogList(operLog); + } + + /** + * 批量删除系统操作日志 + * + * @param ids 需要删除的数据 + * @return + */ + @Override + public int deleteOperLogByIds(String ids) + { + return operLogMapper.deleteOperLogByIds(Convert.toStrArray(ids)); + } + + /** + * 查询操作日志详细 + * + * @param operId 操作ID + * @return 操作日志对象 + */ + @Override + public SysOperLog selectOperLogById(Long operId) + { + return operLogMapper.selectOperLogById(operId); + } + + /** + * 清空操作日志 + */ + @Override + public void cleanOperLog() + { + operLogMapper.cleanOperLog(); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysPostServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysPostServiceImpl.java new file mode 100644 index 0000000..41a5e71 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysPostServiceImpl.java @@ -0,0 +1,181 @@ +package com.ruoyi.system.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.domain.SysPost; +import com.ruoyi.system.mapper.SysPostMapper; +import com.ruoyi.system.mapper.SysUserPostMapper; +import com.ruoyi.system.service.ISysPostService; + +/** + * 岗位信息 服务层处理 + * + * @author ruoyi + */ +@Service +public class SysPostServiceImpl implements ISysPostService +{ + @Autowired + private SysPostMapper postMapper; + + @Autowired + private SysUserPostMapper userPostMapper; + + /** + * 查询岗位信息集合 + * + * @param post 岗位信息 + * @return 岗位信息集合 + */ + @Override + public List selectPostList(SysPost post) + { + return postMapper.selectPostList(post); + } + + /** + * 查询所有岗位 + * + * @return 岗位列表 + */ + @Override + public List selectPostAll() + { + return postMapper.selectPostAll(); + } + + /** + * 根据用户ID查询岗位 + * + * @param userId 用户ID + * @return 岗位列表 + */ + @Override + public List selectPostsByUserId(Long userId) + { + List userPosts = postMapper.selectPostsByUserId(userId); + List posts = postMapper.selectPostAll(); + for (SysPost post : posts) + { + for (SysPost userRole : userPosts) + { + if (post.getPostId().longValue() == userRole.getPostId().longValue()) + { + post.setFlag(true); + break; + } + } + } + return posts; + } + + /** + * 通过岗位ID查询岗位信息 + * + * @param postId 岗位ID + * @return 角色对象信息 + */ + @Override + public SysPost selectPostById(Long postId) + { + return postMapper.selectPostById(postId); + } + + /** + * 批量删除岗位信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + @Override + public int deletePostByIds(String ids) + { + Long[] postIds = Convert.toLongArray(ids); + for (Long postId : postIds) + { + SysPost post = selectPostById(postId); + if (countUserPostById(postId) > 0) + { + throw new ServiceException(String.format("%1$s已分配,不能删除", post.getPostName())); + } + } + return postMapper.deletePostByIds(postIds); + } + + /** + * 新增保存岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + @Override + public int insertPost(SysPost post) + { + return postMapper.insertPost(post); + } + + /** + * 修改保存岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + @Override + public int updatePost(SysPost post) + { + return postMapper.updatePost(post); + } + + /** + * 通过岗位ID查询岗位使用数量 + * + * @param postId 岗位ID + * @return 结果 + */ + @Override + public int countUserPostById(Long postId) + { + return userPostMapper.countUserPostById(postId); + } + + /** + * 校验岗位名称是否唯一 + * + * @param post 岗位信息 + * @return 结果 + */ + @Override + public boolean checkPostNameUnique(SysPost post) + { + Long postId = StringUtils.isNull(post.getPostId()) ? -1L : post.getPostId(); + SysPost info = postMapper.checkPostNameUnique(post.getPostName()); + if (StringUtils.isNotNull(info) && info.getPostId().longValue() != postId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验岗位编码是否唯一 + * + * @param post 岗位信息 + * @return 结果 + */ + @Override + public boolean checkPostCodeUnique(SysPost post) + { + Long postId = StringUtils.isNull(post.getPostId()) ? -1L : post.getPostId(); + SysPost info = postMapper.checkPostCodeUnique(post.getPostCode()); + if (StringUtils.isNotNull(info) && info.getPostId().longValue() != postId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java new file mode 100644 index 0000000..bf7335e --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java @@ -0,0 +1,415 @@ +package com.ruoyi.system.service.impl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.ruoyi.common.annotation.DataScope; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.ShiroUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.system.domain.SysRoleDept; +import com.ruoyi.system.domain.SysRoleMenu; +import com.ruoyi.system.domain.SysUserRole; +import com.ruoyi.system.mapper.SysRoleDeptMapper; +import com.ruoyi.system.mapper.SysRoleMapper; +import com.ruoyi.system.mapper.SysRoleMenuMapper; +import com.ruoyi.system.mapper.SysUserRoleMapper; +import com.ruoyi.system.service.ISysRoleService; + +/** + * 角色 业务层处理 + * + * @author ruoyi + */ +@Service +public class SysRoleServiceImpl implements ISysRoleService +{ + @Autowired + private SysRoleMapper roleMapper; + + @Autowired + private SysRoleMenuMapper roleMenuMapper; + + @Autowired + private SysUserRoleMapper userRoleMapper; + + @Autowired + private SysRoleDeptMapper roleDeptMapper; + + /** + * 根据条件分页查询角色数据 + * + * @param role 角色信息 + * @return 角色数据集合信息 + */ + @Override + @DataScope(deptAlias = "d") + public List selectRoleList(SysRole role) + { + return roleMapper.selectRoleList(role); + } + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + @Override + public Set selectRoleKeys(Long userId) + { + List perms = roleMapper.selectRolesByUserId(userId); + Set permsSet = new HashSet<>(); + for (SysRole perm : perms) + { + if (StringUtils.isNotNull(perm)) + { + permsSet.addAll(Arrays.asList(perm.getRoleKey().trim().split(","))); + } + } + return permsSet; + } + + /** + * 根据用户ID查询角色 + * + * @param userId 用户ID + * @return 角色列表 + */ + @Override + public List selectRolesByUserId(Long userId) + { + List userRoles = roleMapper.selectRolesByUserId(userId); + List roles = selectRoleAll(); + for (SysRole role : roles) + { + for (SysRole userRole : userRoles) + { + if (role.getRoleId().longValue() == userRole.getRoleId().longValue()) + { + role.setFlag(true); + break; + } + } + } + return roles; + } + + /** + * 查询所有角色 + * + * @return 角色列表 + */ + @Override + public List selectRoleAll() + { + return SpringUtils.getAopProxy(this).selectRoleList(new SysRole()); + } + + /** + * 通过角色ID查询角色 + * + * @param roleId 角色ID + * @return 角色对象信息 + */ + @Override + public SysRole selectRoleById(Long roleId) + { + return roleMapper.selectRoleById(roleId); + } + + /** + * 通过角色ID删除角色 + * + * @param roleId 角色ID + * @return 结果 + */ + @Override + @Transactional + public boolean deleteRoleById(Long roleId) + { + // 删除角色与菜单关联 + roleMenuMapper.deleteRoleMenuByRoleId(roleId); + // 删除角色与部门关联 + roleDeptMapper.deleteRoleDeptByRoleId(roleId); + return roleMapper.deleteRoleById(roleId) > 0 ? true : false; + } + + /** + * 批量删除角色信息 + * + * @param ids 需要删除的数据ID + * @throws Exception + */ + @Override + @Transactional + public int deleteRoleByIds(String ids) + { + Long[] roleIds = Convert.toLongArray(ids); + for (Long roleId : roleIds) + { + checkRoleAllowed(new SysRole(roleId)); + checkRoleDataScope(roleId); + SysRole role = selectRoleById(roleId); + if (countUserRoleByRoleId(roleId) > 0) + { + throw new ServiceException(String.format("%1$s已分配,不能删除", role.getRoleName())); + } + } + // 删除角色与菜单关联 + roleMenuMapper.deleteRoleMenu(roleIds); + // 删除角色与部门关联 + roleDeptMapper.deleteRoleDept(roleIds); + return roleMapper.deleteRoleByIds(roleIds); + } + + /** + * 新增保存角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + @Transactional + public int insertRole(SysRole role) + { + // 新增角色信息 + roleMapper.insertRole(role); + return insertRoleMenu(role); + } + + /** + * 修改保存角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + @Transactional + public int updateRole(SysRole role) + { + // 修改角色信息 + roleMapper.updateRole(role); + // 删除角色与菜单关联 + roleMenuMapper.deleteRoleMenuByRoleId(role.getRoleId()); + return insertRoleMenu(role); + } + + /** + * 修改数据权限信息 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + @Transactional + public int authDataScope(SysRole role) + { + // 修改角色信息 + roleMapper.updateRole(role); + // 删除角色与部门关联 + roleDeptMapper.deleteRoleDeptByRoleId(role.getRoleId()); + // 新增角色和部门信息(数据权限) + return insertRoleDept(role); + } + + /** + * 新增角色菜单信息 + * + * @param role 角色对象 + */ + public int insertRoleMenu(SysRole role) + { + int rows = 1; + // 新增用户与角色管理 + List list = new ArrayList(); + for (Long menuId : role.getMenuIds()) + { + SysRoleMenu rm = new SysRoleMenu(); + rm.setRoleId(role.getRoleId()); + rm.setMenuId(menuId); + list.add(rm); + } + if (list.size() > 0) + { + rows = roleMenuMapper.batchRoleMenu(list); + } + return rows; + } + + /** + * 新增角色部门信息(数据权限) + * + * @param role 角色对象 + */ + public int insertRoleDept(SysRole role) + { + int rows = 1; + // 新增角色与部门(数据权限)管理 + List list = new ArrayList(); + for (Long deptId : role.getDeptIds()) + { + SysRoleDept rd = new SysRoleDept(); + rd.setRoleId(role.getRoleId()); + rd.setDeptId(deptId); + list.add(rd); + } + if (list.size() > 0) + { + rows = roleDeptMapper.batchRoleDept(list); + } + return rows; + } + + /** + * 校验角色名称是否唯一 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + public boolean checkRoleNameUnique(SysRole role) + { + Long roleId = StringUtils.isNull(role.getRoleId()) ? -1L : role.getRoleId(); + SysRole info = roleMapper.checkRoleNameUnique(role.getRoleName()); + if (StringUtils.isNotNull(info) && info.getRoleId().longValue() != roleId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验角色权限是否唯一 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + public boolean checkRoleKeyUnique(SysRole role) + { + Long roleId = StringUtils.isNull(role.getRoleId()) ? -1L : role.getRoleId(); + SysRole info = roleMapper.checkRoleKeyUnique(role.getRoleKey()); + if (StringUtils.isNotNull(info) && info.getRoleId().longValue() != roleId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验角色是否允许操作 + * + * @param role 角色信息 + */ + @Override + public void checkRoleAllowed(SysRole role) + { + if (StringUtils.isNotNull(role.getRoleId()) && role.isAdmin()) + { + throw new ServiceException("不允许操作超级管理员角色"); + } + } + + /** + * 校验角色是否有数据权限 + * + * @param roleId 角色id + */ + @Override + public void checkRoleDataScope(Long roleId) + { + if (!SysUser.isAdmin(ShiroUtils.getUserId())) + { + SysRole role = new SysRole(); + role.setRoleId(roleId); + List roles = SpringUtils.getAopProxy(this).selectRoleList(role); + if (StringUtils.isEmpty(roles)) + { + throw new ServiceException("没有权限访问角色数据!"); + } + } + } + + /** + * 通过角色ID查询角色使用数量 + * + * @param roleId 角色ID + * @return 结果 + */ + @Override + public int countUserRoleByRoleId(Long roleId) + { + return userRoleMapper.countUserRoleByRoleId(roleId); + } + + /** + * 角色状态修改 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + public int changeStatus(SysRole role) + { + return roleMapper.updateRole(role); + } + + /** + * 取消授权用户角色 + * + * @param userRole 用户和角色关联信息 + * @return 结果 + */ + @Override + public int deleteAuthUser(SysUserRole userRole) + { + return userRoleMapper.deleteUserRoleInfo(userRole); + } + + /** + * 批量取消授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要删除的用户数据ID + * @return 结果 + */ + @Override + public int deleteAuthUsers(Long roleId, String userIds) + { + return userRoleMapper.deleteUserRoleInfos(roleId, Convert.toLongArray(userIds)); + } + + /** + * 批量选择授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要授权的用户数据ID + * @return 结果 + */ + @Override + public int insertAuthUsers(Long roleId, String userIds) + { + Long[] users = Convert.toLongArray(userIds); + // 新增用户与角色管理 + List list = new ArrayList(); + for (Long userId : users) + { + SysUserRole ur = new SysUserRole(); + ur.setUserId(userId); + ur.setRoleId(roleId); + list.add(ur); + } + return userRoleMapper.batchUserRole(list); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserOnlineServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserOnlineServiceImpl.java new file mode 100644 index 0000000..ae6087a --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserOnlineServiceImpl.java @@ -0,0 +1,140 @@ +package com.ruoyi.system.service.impl; + +import java.io.Serializable; +import java.util.Date; +import java.util.Deque; +import java.util.List; +import com.ruoyi.common.utils.spring.SpringUtils; +import org.apache.shiro.cache.Cache; +import org.apache.shiro.cache.ehcache.EhCacheManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.common.constant.ShiroConstants; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.domain.SysUserOnline; +import com.ruoyi.system.mapper.SysUserOnlineMapper; +import com.ruoyi.system.service.ISysUserOnlineService; + +/** + * 在线用户 服务层处理 + * + * @author ruoyi + */ +@Service +public class SysUserOnlineServiceImpl implements ISysUserOnlineService +{ + @Autowired + private SysUserOnlineMapper userOnlineDao; + + /** + * 通过会话序号查询信息 + * + * @param sessionId 会话ID + * @return 在线用户信息 + */ + @Override + public SysUserOnline selectOnlineById(String sessionId) + { + return userOnlineDao.selectOnlineById(sessionId); + } + + /** + * 通过会话序号删除信息 + * + * @param sessionId 会话ID + * @return 在线用户信息 + */ + @Override + public void deleteOnlineById(String sessionId) + { + SysUserOnline userOnline = selectOnlineById(sessionId); + if (StringUtils.isNotNull(userOnline)) + { + userOnlineDao.deleteOnlineById(sessionId); + } + } + + /** + * 通过会话序号删除信息 + * + * @param sessions 会话ID集合 + * @return 在线用户信息 + */ + @Override + public void batchDeleteOnline(List sessions) + { + for (String sessionId : sessions) + { + SysUserOnline userOnline = selectOnlineById(sessionId); + if (StringUtils.isNotNull(userOnline)) + { + userOnlineDao.deleteOnlineById(sessionId); + } + } + } + + /** + * 保存会话信息 + * + * @param online 会话信息 + */ + @Override + public void saveOnline(SysUserOnline online) + { + userOnlineDao.saveOnline(online); + } + + /** + * 查询会话集合 + * + * @param userOnline 在线用户 + */ + @Override + public List selectUserOnlineList(SysUserOnline userOnline) + { + return userOnlineDao.selectUserOnlineList(userOnline); + } + + /** + * 强退用户 + * + * @param sessionId 会话ID + */ + @Override + public void forceLogout(String sessionId) + { + userOnlineDao.deleteOnlineById(sessionId); + } + + /** + * 清理用户缓存 + * + * @param loginName 登录名称 + * @param sessionId 会话ID + */ + @Override + public void removeUserCache(String loginName, String sessionId) + { + EhCacheManager ehCacheManager = SpringUtils.getBean(EhCacheManager.class); + Cache> cache = ehCacheManager.getCache(ShiroConstants.SYS_USERCACHE); + Deque deque = cache.get(loginName); + if (StringUtils.isEmpty(deque) || deque.size() == 0) + { + return; + } + deque.remove(sessionId); + } + + /** + * 查询会话集合 + * + * @param expiredDate 失效日期 + */ + @Override + public List selectOnlineByExpired(Date expiredDate) + { + String lastAccessTime = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, expiredDate); + return userOnlineDao.selectOnlineByExpired(lastAccessTime); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java new file mode 100644 index 0000000..841f958 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java @@ -0,0 +1,553 @@ +package com.ruoyi.system.service.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import javax.validation.Validator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; +import com.ruoyi.common.annotation.DataScope; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.ShiroUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.bean.BeanValidators; +import com.ruoyi.common.utils.security.Md5Utils; +import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.system.domain.SysPost; +import com.ruoyi.system.domain.SysUserPost; +import com.ruoyi.system.domain.SysUserRole; +import com.ruoyi.system.mapper.SysPostMapper; +import com.ruoyi.system.mapper.SysRoleMapper; +import com.ruoyi.system.mapper.SysUserMapper; +import com.ruoyi.system.mapper.SysUserPostMapper; +import com.ruoyi.system.mapper.SysUserRoleMapper; +import com.ruoyi.system.service.ISysConfigService; +import com.ruoyi.system.service.ISysUserService; + +/** + * 用户 业务层处理 + * + * @author ruoyi + */ +@Service +public class SysUserServiceImpl implements ISysUserService +{ + private static final Logger log = LoggerFactory.getLogger(SysUserServiceImpl.class); + + @Autowired + private SysUserMapper userMapper; + + @Autowired + private SysRoleMapper roleMapper; + + @Autowired + private SysPostMapper postMapper; + + @Autowired + private SysUserPostMapper userPostMapper; + + @Autowired + private SysUserRoleMapper userRoleMapper; + + @Autowired + private ISysConfigService configService; + + @Autowired + protected Validator validator; + + /** + * 根据条件分页查询用户列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + @Override + @DataScope(deptAlias = "d", userAlias = "u") + public List selectUserList(SysUser user) + { + return userMapper.selectUserList(user); + } + + /** + * 根据条件分页查询已分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + @Override + @DataScope(deptAlias = "d", userAlias = "u") + public List selectAllocatedList(SysUser user) + { + return userMapper.selectAllocatedList(user); + } + + /** + * 根据条件分页查询未分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + @Override + @DataScope(deptAlias = "d", userAlias = "u") + public List selectUnallocatedList(SysUser user) + { + return userMapper.selectUnallocatedList(user); + } + + /** + * 通过用户名查询用户 + * + * @param userName 用户名 + * @return 用户对象信息 + */ + @Override + public SysUser selectUserByLoginName(String userName) + { + return userMapper.selectUserByLoginName(userName); + } + + /** + * 通过手机号码查询用户 + * + * @param phoneNumber 手机号码 + * @return 用户对象信息 + */ + @Override + public SysUser selectUserByPhoneNumber(String phoneNumber) + { + return userMapper.selectUserByPhoneNumber(phoneNumber); + } + + /** + * 通过邮箱查询用户 + * + * @param email 邮箱 + * @return 用户对象信息 + */ + @Override + public SysUser selectUserByEmail(String email) + { + return userMapper.selectUserByEmail(email); + } + + /** + * 通过用户ID查询用户 + * + * @param userId 用户ID + * @return 用户对象信息 + */ + @Override + public SysUser selectUserById(Long userId) + { + return userMapper.selectUserById(userId); + } + + /** + * 通过用户ID查询用户和角色关联 + * + * @param userId 用户ID + * @return 用户和角色关联列表 + */ + @Override + public List selectUserRoleByUserId(Long userId) + { + return userRoleMapper.selectUserRoleByUserId(userId); + } + + /** + * 通过用户ID删除用户 + * + * @param userId 用户ID + * @return 结果 + */ + @Override + @Transactional + public int deleteUserById(Long userId) + { + // 删除用户与角色关联 + userRoleMapper.deleteUserRoleByUserId(userId); + // 删除用户与岗位表 + userPostMapper.deleteUserPostByUserId(userId); + return userMapper.deleteUserById(userId); + } + + /** + * 批量删除用户信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + @Override + @Transactional + public int deleteUserByIds(String ids) + { + Long[] userIds = Convert.toLongArray(ids); + for (Long userId : userIds) + { + checkUserAllowed(new SysUser(userId)); + checkUserDataScope(userId); + } + // 删除用户与角色关联 + userRoleMapper.deleteUserRole(userIds); + // 删除用户与岗位关联 + userPostMapper.deleteUserPost(userIds); + return userMapper.deleteUserByIds(userIds); + } + + /** + * 新增保存用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + @Transactional + public int insertUser(SysUser user) + { + // 新增用户信息 + int rows = userMapper.insertUser(user); + // 新增用户岗位关联 + insertUserPost(user); + // 新增用户与角色管理 + insertUserRole(user.getUserId(), user.getRoleIds()); + return rows; + } + + /** + * 注册用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public boolean registerUser(SysUser user) + { + user.setUserType(UserConstants.REGISTER_USER_TYPE); + return userMapper.insertUser(user) > 0; + } + + /** + * 修改保存用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + @Transactional + public int updateUser(SysUser user) + { + Long userId = user.getUserId(); + // 删除用户与角色关联 + userRoleMapper.deleteUserRoleByUserId(userId); + // 新增用户与角色管理 + insertUserRole(user.getUserId(), user.getRoleIds()); + // 删除用户与岗位关联 + userPostMapper.deleteUserPostByUserId(userId); + // 新增用户与岗位管理 + insertUserPost(user); + return userMapper.updateUser(user); + } + + /** + * 修改用户个人详细信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public int updateUserInfo(SysUser user) + { + return userMapper.updateUser(user); + } + + /** + * 用户授权角色 + * + * @param userId 用户ID + * @param roleIds 角色组 + */ + @Override + @Transactional + public void insertUserAuth(Long userId, Long[] roleIds) + { + userRoleMapper.deleteUserRoleByUserId(userId); + insertUserRole(userId, roleIds); + } + + /** + * 修改用户密码 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public int resetUserPwd(SysUser user) + { + return updateUserInfo(user); + } + + /** + * 新增用户角色信息 + * + * @param userId 用户ID + * @param roleIds 角色组 + */ + public void insertUserRole(Long userId, Long[] roleIds) + { + if (StringUtils.isNotNull(roleIds)) + { + // 新增用户与角色管理 + List list = new ArrayList(); + for (Long roleId : roleIds) + { + SysUserRole ur = new SysUserRole(); + ur.setUserId(userId); + ur.setRoleId(roleId); + list.add(ur); + } + if (list.size() > 0) + { + userRoleMapper.batchUserRole(list); + } + } + } + + /** + * 新增用户岗位信息 + * + * @param user 用户对象 + */ + public void insertUserPost(SysUser user) + { + Long[] posts = user.getPostIds(); + if (StringUtils.isNotNull(posts)) + { + // 新增用户与岗位管理 + List list = new ArrayList(); + for (Long postId : posts) + { + SysUserPost up = new SysUserPost(); + up.setUserId(user.getUserId()); + up.setPostId(postId); + list.add(up); + } + if (list.size() > 0) + { + userPostMapper.batchUserPost(list); + } + } + } + + /** + * 校验用户名称是否唯一 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public boolean checkLoginNameUnique(SysUser user) + { + Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId(); + SysUser info = userMapper.checkLoginNameUnique(user.getLoginName()); + if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验手机号码是否唯一 + * + * @param user 用户信息 + * @return + */ + @Override + public boolean checkPhoneUnique(SysUser user) + { + Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId(); + SysUser info = userMapper.checkPhoneUnique(user.getPhonenumber()); + if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验email是否唯一 + * + * @param user 用户信息 + * @return + */ + @Override + public boolean checkEmailUnique(SysUser user) + { + Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId(); + SysUser info = userMapper.checkEmailUnique(user.getEmail()); + if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验用户是否允许操作 + * + * @param user 用户信息 + */ + @Override + public void checkUserAllowed(SysUser user) + { + if (StringUtils.isNotNull(user.getUserId()) && user.isAdmin()) + { + throw new ServiceException("不允许操作超级管理员用户"); + } + } + + /** + * 校验用户是否有数据权限 + * + * @param userId 用户id + */ + @Override + public void checkUserDataScope(Long userId) + { + if (!SysUser.isAdmin(ShiroUtils.getUserId())) + { + SysUser user = new SysUser(); + user.setUserId(userId); + List users = SpringUtils.getAopProxy(this).selectUserList(user); + if (StringUtils.isEmpty(users)) + { + throw new ServiceException("没有权限访问用户数据!"); + } + } + } + + /** + * 查询用户所属角色组 + * + * @param userId 用户ID + * @return 结果 + */ + @Override + public String selectUserRoleGroup(Long userId) + { + List list = roleMapper.selectRolesByUserId(userId); + if (CollectionUtils.isEmpty(list)) + { + return StringUtils.EMPTY; + } + return list.stream().map(SysRole::getRoleName).collect(Collectors.joining(",")); + } + + /** + * 查询用户所属岗位组 + * + * @param userId 用户ID + * @return 结果 + */ + @Override + public String selectUserPostGroup(Long userId) + { + List list = postMapper.selectPostsByUserId(userId); + if (CollectionUtils.isEmpty(list)) + { + return StringUtils.EMPTY; + } + return list.stream().map(SysPost::getPostName).collect(Collectors.joining(",")); + } + + /** + * 导入用户数据 + * + * @param userList 用户数据列表 + * @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据 + * @param operName 操作用户 + * @return 结果 + */ + @Override + public String importUser(List userList, Boolean isUpdateSupport, String operName) + { + if (StringUtils.isNull(userList) || userList.size() == 0) + { + throw new ServiceException("导入用户数据不能为空!"); + } + int successNum = 0; + int failureNum = 0; + StringBuilder successMsg = new StringBuilder(); + StringBuilder failureMsg = new StringBuilder(); + String password = configService.selectConfigByKey("sys.user.initPassword"); + for (SysUser user : userList) + { + try + { + // 验证是否存在这个用户 + SysUser u = userMapper.selectUserByLoginName(user.getLoginName()); + if (StringUtils.isNull(u)) + { + BeanValidators.validateWithException(validator, user); + user.setPassword(Md5Utils.hash(user.getLoginName() + password)); + user.setCreateBy(operName); + this.insertUser(user); + successNum++; + successMsg.append("
    " + successNum + "、账号 " + user.getLoginName() + " 导入成功"); + } + else if (isUpdateSupport) + { + BeanValidators.validateWithException(validator, user); + checkUserAllowed(user); + checkUserDataScope(user.getUserId()); + user.setUpdateBy(operName); + this.updateUser(user); + successNum++; + successMsg.append("
    " + successNum + "、账号 " + user.getLoginName() + " 更新成功"); + } + else + { + failureNum++; + failureMsg.append("
    " + failureNum + "、账号 " + user.getLoginName() + " 已存在"); + } + } + catch (Exception e) + { + failureNum++; + String msg = "
    " + failureNum + "、账号 " + user.getLoginName() + " 导入失败:"; + failureMsg.append(msg + e.getMessage()); + log.error(msg, e); + } + } + if (failureNum > 0) + { + failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:"); + throw new ServiceException(failureMsg.toString()); + } + else + { + successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:"); + } + return successMsg.toString(); + } + + /** + * 用户状态修改 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public int changeStatus(SysUser user) + { + return userMapper.updateUser(user); + } +} diff --git a/ruoyi-system/src/main/resources/mapper/system/SysConfigMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysConfigMapper.xml new file mode 100644 index 0000000..e64ba57 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysConfigMapper.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + select config_id, config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark + from sys_config + + + + + + + and config_id = #{configId} + + + and config_key = #{configKey} + + + + + + + + + + + + + + insert into sys_config ( + config_name, + config_key, + config_value, + config_type, + create_by, + remark, + create_time + )values( + #{configName}, + #{configKey}, + #{configValue}, + #{configType}, + #{createBy}, + #{remark}, + sysdate() + ) + + + + update sys_config + + config_name = #{configName}, + config_key = #{configKey}, + config_value = #{configValue}, + config_type = #{configType}, + update_by = #{updateBy}, + remark = #{remark}, + update_time = sysdate() + + where config_id = #{configId} + + + + delete from sys_config where config_id = #{configId} + + + + delete from sys_config where config_id in + + #{configId} + + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml new file mode 100644 index 0000000..8f880f1 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.del_flag, d.create_by, d.create_time + from sys_dept d + + + + + + + + + + + + + + + + + + + + insert into sys_dept( + dept_id, + parent_id, + dept_name, + ancestors, + order_num, + leader, + phone, + email, + status, + create_by, + create_time + )values( + #{deptId}, + #{parentId}, + #{deptName}, + #{ancestors}, + #{orderNum}, + #{leader}, + #{phone}, + #{email}, + #{status}, + #{createBy}, + sysdate() + ) + + + + update sys_dept + + parent_id = #{parentId}, + dept_name = #{deptName}, + ancestors = #{ancestors}, + order_num = #{orderNum}, + leader = #{leader}, + phone = #{phone}, + email = #{email}, + status = #{status}, + update_by = #{updateBy}, + update_time = sysdate() + + where dept_id = #{deptId} + + + + update sys_dept set ancestors = + + when #{item.deptId} then #{item.ancestors} + + where dept_id in + + #{item.deptId} + + + + + update sys_dept set del_flag = '2' where dept_id = #{deptId} + + + + update sys_dept set status = '0' where dept_id in + + #{deptId} + + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysDictDataMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysDictDataMapper.xml new file mode 100644 index 0000000..b26e075 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysDictDataMapper.xml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + select dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark + from sys_dict_data + + + + + + + + + + + + + + delete from sys_dict_data where dict_code = #{dictCode} + + + + delete from sys_dict_data where dict_code in + + #{dictCode} + + + + + update sys_dict_data + + dict_sort = #{dictSort}, + dict_label = #{dictLabel}, + dict_value = #{dictValue}, + dict_type = #{dictType}, + css_class = #{cssClass}, + list_class = #{listClass}, + is_default = #{isDefault}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where dict_code = #{dictCode} + + + + update sys_dict_data set dict_type = #{newDictType} where dict_type = #{oldDictType} + + + + insert into sys_dict_data( + dict_sort, + dict_label, + dict_value, + dict_type, + css_class, + list_class, + is_default, + status, + remark, + create_by, + create_time + )values( + #{dictSort}, + #{dictLabel}, + #{dictValue}, + #{dictType}, + #{cssClass}, + #{listClass}, + #{isDefault}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysDictTypeMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysDictTypeMapper.xml new file mode 100644 index 0000000..15de8cc --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysDictTypeMapper.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + select dict_id, dict_name, dict_type, status, create_by, create_time, remark + from sys_dict_type + + + + + + + + + + + + + + delete from sys_dict_type where dict_id = #{dictId} + + + + delete from sys_dict_type where dict_id in + + #{dictId} + + + + + update sys_dict_type + + dict_name = #{dictName}, + dict_type = #{dictType}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where dict_id = #{dictId} + + + + insert into sys_dict_type( + dict_name, + dict_type, + status, + remark, + create_by, + create_time + )values( + #{dictName}, + #{dictType}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysLogininforMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysLogininforMapper.xml new file mode 100644 index 0000000..ac0afb7 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysLogininforMapper.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + insert into sys_logininfor (login_name, status, ipaddr, login_location, browser, os, msg, login_time) + values (#{loginName}, #{status}, #{ipaddr}, #{loginLocation}, #{browser}, #{os}, #{msg}, sysdate()) + + + + + + delete from sys_logininfor where info_id in + + #{infoId} + + + + + truncate table sys_logininfor + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml new file mode 100644 index 0000000..1f08c56 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + select menu_id, menu_name, parent_id, order_num, url, target, menu_type, visible, is_refresh, ifnull(perms,'') as perms, icon, create_by, create_time + from sys_menu + + + + + + + + + + + + + + + + + + + + + + delete from sys_menu where menu_id = #{menuId} or parent_id = #{menuId} + + + + + + + + + + update sys_menu + + menu_name = #{menuName}, + parent_id = #{parentId}, + order_num = #{orderNum}, + url = #{url}, + target = #{target}, + menu_type = #{menuType}, + visible = #{visible}, + is_refresh = #{isRefresh}, + perms = #{perms}, + icon = #{icon}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where menu_id = #{menuId} + + + + insert into sys_menu( + menu_id, + parent_id, + menu_name, + order_num, + url, + target, + menu_type, + visible, + is_refresh, + perms, + icon, + remark, + create_by, + create_time + )values( + #{menuId}, + #{parentId}, + #{menuName}, + #{orderNum}, + #{url}, + #{target}, + #{menuType}, + #{visible}, + #{isRefresh}, + #{perms}, + #{icon}, + #{remark}, + #{createBy}, + sysdate() + ) + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysNoticeMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysNoticeMapper.xml new file mode 100644 index 0000000..5883573 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysNoticeMapper.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + select notice_id, notice_title, notice_type, notice_content, status, create_by, create_time, update_by, update_time, remark + from sys_notice + + + + + + + + insert into sys_notice ( + notice_title, + notice_type, + notice_content, + status, + remark, + create_by, + create_time + )values( + #{noticeTitle}, + #{noticeType}, + #{noticeContent}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + + update sys_notice + + notice_title = #{noticeTitle}, + notice_type = #{noticeType}, + notice_content = #{noticeContent}, + status = #{status}, + update_by = #{updateBy}, + update_time = sysdate() + + where notice_id = #{noticeId} + + + + delete from sys_notice where notice_id in + + #{noticeId} + + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml new file mode 100644 index 0000000..d9a238b --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + select oper_id, title, business_type, method, request_method, operator_type, oper_name, dept_name, oper_url, oper_ip, oper_location, oper_param, json_result, status, error_msg, oper_time, cost_time + from sys_oper_log + + + + insert into sys_oper_log(title, business_type, method, request_method, operator_type, oper_name, dept_name, oper_url, oper_ip, oper_location, oper_param, json_result, status, error_msg, cost_time, oper_time) + values (#{title}, #{businessType}, #{method}, #{requestMethod}, #{operatorType}, #{operName}, #{deptName}, #{operUrl}, #{operIp}, #{operLocation}, #{operParam}, #{jsonResult}, #{status}, #{errorMsg}, #{costTime}, sysdate()) + + + + + + delete from sys_oper_log where oper_id in + + #{operId} + + + + + + + truncate table sys_oper_log + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml new file mode 100644 index 0000000..86e4751 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + select post_id, post_code, post_name, post_sort, status, create_by, create_time, remark + from sys_post + + + + + + + + + + + + + + + + delete from sys_post where post_id in + + #{postId} + + + + + update sys_post + + post_code = #{postCode}, + post_name = #{postName}, + post_sort = #{postSort}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where post_id = #{postId} + + + + insert into sys_post( + post_id, + post_code, + post_name, + post_sort, + status, + remark, + create_by, + create_time + )values( + #{postId}, + #{postCode}, + #{postName}, + #{postSort}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml new file mode 100644 index 0000000..7c4139b --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + delete from sys_role_dept where role_id=#{roleId} + + + + + + delete from sys_role_dept where role_id in + + #{roleId} + + + + + insert into sys_role_dept(role_id, dept_id) values + + (#{item.roleId},#{item.deptId}) + + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysRoleMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysRoleMapper.xml new file mode 100644 index 0000000..92ba9b8 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysRoleMapper.xml @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + select distinct r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, + r.status, r.del_flag, r.create_time, r.remark + from sys_role r + left join sys_user_role ur on ur.role_id = r.role_id + left join sys_user u on u.user_id = ur.user_id + left join sys_dept d on u.dept_id = d.dept_id + + + + select r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status, r.del_flag, r.create_time, r.remark + from sys_role r + + + + + + + + + + + + + + update sys_role set del_flag = '2' where role_id = #{roleId} + + + + update sys_role set del_flag = '2' where role_id in + + #{roleId} + + + + + update sys_role + + role_name = #{roleName}, + role_key = #{roleKey}, + role_sort = #{roleSort}, + data_scope = #{dataScope}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where role_id = #{roleId} + + + + insert into sys_role( + role_id, + role_name, + role_key, + role_sort, + data_scope, + status, + remark, + create_by, + create_time + )values( + #{roleId}, + #{roleName}, + #{roleKey}, + #{roleSort}, + #{dataScope}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml new file mode 100644 index 0000000..d213bdd --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + delete from sys_role_menu where role_id=#{roleId} + + + + + + delete from sys_role_menu where role_id in + + #{roleId} + + + + + insert into sys_role_menu(role_id, menu_id) values + + (#{item.roleId},#{item.menuId}) + + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml new file mode 100644 index 0000000..74d66d1 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml @@ -0,0 +1,231 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select u.user_id, u.dept_id, u.login_name, u.user_name, u.user_type, u.email, u.avatar, u.phonenumber, u.sex, u.password, u.salt, u.status, u.del_flag, u.login_ip, u.login_date, u.pwd_update_date, u.create_time, u.remark, + d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.status as dept_status, + r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status + from sys_user u + left join sys_dept d on u.dept_id = d.dept_id + left join sys_user_role ur on u.user_id = ur.user_id + left join sys_role r on r.role_id = ur.role_id + + + + + + + + + + + + + + + + + + + + + + + + update sys_user set del_flag = '2' where user_id = #{userId} + + + + update sys_user set del_flag = '2' where user_id in + + #{userId} + + + + + update sys_user + + dept_id = #{deptId}, + login_name = #{loginName}, + user_name = #{userName}, + user_type = #{userType}, + email = #{email}, + phonenumber = #{phonenumber}, + sex = #{sex}, + avatar = #{avatar}, + password = #{password}, + salt = #{salt}, + status = #{status}, + login_ip = #{loginIp}, + login_date = #{loginDate}, + pwd_update_date = #{pwdUpdateDate}, + update_by = #{updateBy}, + remark = #{remark}, + update_time = sysdate() + + where user_id = #{userId} + + + + insert into sys_user( + user_id, + dept_id, + login_name, + user_name, + user_type, + email, + avatar, + phonenumber, + sex, + password, + salt, + status, + pwd_update_date, + create_by, + remark, + create_time + )values( + #{userId}, + #{deptId}, + #{loginName}, + #{userName}, + #{userType}, + #{email}, + #{avatar}, + #{phonenumber}, + #{sex}, + #{password}, + #{salt}, + #{status}, + #{pwdUpdateDate}, + #{createBy}, + #{remark}, + sysdate() + ) + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysUserOnlineMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysUserOnlineMapper.xml new file mode 100644 index 0000000..ff85fd8 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysUserOnlineMapper.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + select sessionId, login_name, dept_name, ipaddr, login_location, browser, os, status, start_timestamp, last_access_time, expire_time + from sys_user_online + + + + + + replace into sys_user_online(sessionId, login_name, dept_name, ipaddr, login_location, browser, os, status, start_timestamp, last_access_time, expire_time) + values (#{sessionId}, #{loginName}, #{deptName}, #{ipaddr}, #{loginLocation}, #{browser}, #{os}, #{status}, #{startTimestamp}, #{lastAccessTime}, #{expireTime}) + + + + delete from sys_user_online where sessionId = #{sessionId} + + + + + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysUserPostMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysUserPostMapper.xml new file mode 100644 index 0000000..2b90bc4 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysUserPostMapper.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + delete from sys_user_post where user_id=#{userId} + + + + + + delete from sys_user_post where user_id in + + #{userId} + + + + + insert into sys_user_post(user_id, post_id) values + + (#{item.userId},#{item.postId}) + + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysUserRoleMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysUserRoleMapper.xml new file mode 100644 index 0000000..7606692 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysUserRoleMapper.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + delete from sys_user_role where user_id = #{userId} + + + + + + delete from sys_user_role where user_id in + + #{userId} + + + + + insert into sys_user_role(user_id, role_id) values + + (#{item.userId},#{item.roleId}) + + + + + delete from sys_user_role where user_id=#{userId} and role_id=#{roleId} + + + + delete from sys_user_role where role_id=#{roleId} and user_id in + + #{userId} + + + \ No newline at end of file diff --git a/ry.bat b/ry.bat new file mode 100644 index 0000000..ac1e437 --- /dev/null +++ b/ry.bat @@ -0,0 +1,67 @@ +@echo off + +rem jarƽĿ¼ +set AppName=ruoyi-admin.jar + +rem JVM +set JVM_OPTS="-Dname=%AppName% -Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:NewRatio=1 -XX:SurvivorRatio=30 -XX:+UseParallelGC -XX:+UseParallelOldGC" + + +ECHO. + ECHO. [1] %AppName% + ECHO. [2] ر%AppName% + ECHO. [3] %AppName% + ECHO. [4] ״̬ %AppName% + ECHO. [5] +ECHO. + +ECHO.ѡĿ: +set /p ID= + IF "%id%"=="1" GOTO start + IF "%id%"=="2" GOTO stop + IF "%id%"=="3" GOTO restart + IF "%id%"=="4" GOTO status + IF "%id%"=="5" EXIT +PAUSE +:start + for /f "usebackq tokens=1-2" %%a in (`jps -l ^| findstr %AppName%`) do ( + set pid=%%a + set image_name=%%b + ) + if defined pid ( + echo %%is running + PAUSE + ) + +start javaw %JVM_OPTS% -jar %AppName% + +echo starting +echo Start %AppName% success... +goto:eof + +rem stopͨjpspid +:stop + for /f "usebackq tokens=1-2" %%a in (`jps -l ^| findstr %AppName%`) do ( + set pid=%%a + set image_name=%%b + ) + if not defined pid (echo process %AppName% does not exists) else ( + echo prepare to kill %image_name% + echo start kill %pid% ... + rem ݽIDkill + taskkill /f /pid %pid% + ) +goto:eof +:restart + call :stop + call :start +goto:eof +:status + for /f "usebackq tokens=1-2" %%a in (`jps -l ^| findstr %AppName%`) do ( + set pid=%%a + set image_name=%%b + ) + if not defined pid (echo process %AppName% is dead ) else ( + echo %image_name% is running + ) +goto:eof diff --git a/ry.sh b/ry.sh new file mode 100644 index 0000000..d6a9cf3 --- /dev/null +++ b/ry.sh @@ -0,0 +1,86 @@ +#!/bin/sh +# ./ry.sh start 启动 stop 停止 restart 重启 status 状态 +AppName=ruoyi-admin.jar + +# JVM参数 +JVM_OPTS="-Dname=$AppName -Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:NewRatio=1 -XX:SurvivorRatio=30 -XX:+UseParallelGC -XX:+UseParallelOldGC" +APP_HOME=`pwd` +LOG_PATH=$APP_HOME/logs/$AppName.log + +if [ "$1" = "" ]; +then + echo -e "\033[0;31m 未输入操作名 \033[0m \033[0;34m {start|stop|restart|status} \033[0m" + exit 1 +fi + +if [ "$AppName" = "" ]; +then + echo -e "\033[0;31m 未输入应用名 \033[0m" + exit 1 +fi + +function start() +{ + PID=`ps -ef |grep java|grep $AppName|grep -v grep|awk '{print $2}'` + + if [ x"$PID" != x"" ]; then + echo "$AppName is running..." + else + nohup java $JVM_OPTS -jar $AppName > /dev/null 2>&1 & + echo "Start $AppName success..." + fi +} + +function stop() +{ + echo "Stop $AppName" + + PID="" + query(){ + PID=`ps -ef |grep java|grep $AppName|grep -v grep|awk '{print $2}'` + } + + query + if [ x"$PID" != x"" ]; then + kill -TERM $PID + echo "$AppName (pid:$PID) exiting..." + while [ x"$PID" != x"" ] + do + sleep 1 + query + done + echo "$AppName exited." + else + echo "$AppName already stopped." + fi +} + +function restart() +{ + stop + sleep 2 + start +} + +function status() +{ + PID=`ps -ef |grep java|grep $AppName|grep -v grep|wc -l` + if [ $PID != 0 ];then + echo "$AppName is running..." + else + echo "$AppName is not running..." + fi +} + +case $1 in + start) + start;; + stop) + stop;; + restart) + restart;; + status) + status;; + *) + +esac diff --git a/sql/quartz.sql b/sql/quartz.sql new file mode 100644 index 0000000..cee613b --- /dev/null +++ b/sql/quartz.sql @@ -0,0 +1,174 @@ +DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS; +DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE; +DROP TABLE IF EXISTS QRTZ_LOCKS; +DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_JOB_DETAILS; +DROP TABLE IF EXISTS QRTZ_CALENDARS; + +-- ---------------------------- +-- 1、存储每一个已配置的 jobDetail 的详细信息 +-- ---------------------------- +create table QRTZ_JOB_DETAILS ( + sched_name varchar(120) not null comment '调度名称', + job_name varchar(200) not null comment '任务名称', + job_group varchar(200) not null comment '任务组名', + description varchar(250) null comment '相关介绍', + job_class_name varchar(250) not null comment '执行任务类名称', + is_durable varchar(1) not null comment '是否持久化', + is_nonconcurrent varchar(1) not null comment '是否并发', + is_update_data varchar(1) not null comment '是否更新数据', + requests_recovery varchar(1) not null comment '是否接受恢复执行', + job_data blob null comment '存放持久化job对象', + primary key (sched_name, job_name, job_group) +) engine=innodb comment = '任务详细信息表'; + +-- ---------------------------- +-- 2、 存储已配置的 Trigger 的信息 +-- ---------------------------- +create table QRTZ_TRIGGERS ( + sched_name varchar(120) not null comment '调度名称', + trigger_name varchar(200) not null comment '触发器的名字', + trigger_group varchar(200) not null comment '触发器所属组的名字', + job_name varchar(200) not null comment 'qrtz_job_details表job_name的外键', + job_group varchar(200) not null comment 'qrtz_job_details表job_group的外键', + description varchar(250) null comment '相关介绍', + next_fire_time bigint(13) null comment '上一次触发时间(毫秒)', + prev_fire_time bigint(13) null comment '下一次触发时间(默认为-1表示不触发)', + priority integer null comment '优先级', + trigger_state varchar(16) not null comment '触发器状态', + trigger_type varchar(8) not null comment '触发器的类型', + start_time bigint(13) not null comment '开始时间', + end_time bigint(13) null comment '结束时间', + calendar_name varchar(200) null comment '日程表名称', + misfire_instr smallint(2) null comment '补偿执行的策略', + job_data blob null comment '存放持久化job对象', + primary key (sched_name, trigger_name, trigger_group), + foreign key (sched_name, job_name, job_group) references QRTZ_JOB_DETAILS(sched_name, job_name, job_group) +) engine=innodb comment = '触发器详细信息表'; + +-- ---------------------------- +-- 3、 存储简单的 Trigger,包括重复次数,间隔,以及已触发的次数 +-- ---------------------------- +create table QRTZ_SIMPLE_TRIGGERS ( + sched_name varchar(120) not null comment '调度名称', + trigger_name varchar(200) not null comment 'qrtz_triggers表trigger_name的外键', + trigger_group varchar(200) not null comment 'qrtz_triggers表trigger_group的外键', + repeat_count bigint(7) not null comment '重复的次数统计', + repeat_interval bigint(12) not null comment '重复的间隔时间', + times_triggered bigint(10) not null comment '已经触发的次数', + primary key (sched_name, trigger_name, trigger_group), + foreign key (sched_name, trigger_name, trigger_group) references QRTZ_TRIGGERS(sched_name, trigger_name, trigger_group) +) engine=innodb comment = '简单触发器的信息表'; + +-- ---------------------------- +-- 4、 存储 Cron Trigger,包括 Cron 表达式和时区信息 +-- ---------------------------- +create table QRTZ_CRON_TRIGGERS ( + sched_name varchar(120) not null comment '调度名称', + trigger_name varchar(200) not null comment 'qrtz_triggers表trigger_name的外键', + trigger_group varchar(200) not null comment 'qrtz_triggers表trigger_group的外键', + cron_expression varchar(200) not null comment 'cron表达式', + time_zone_id varchar(80) comment '时区', + primary key (sched_name, trigger_name, trigger_group), + foreign key (sched_name, trigger_name, trigger_group) references QRTZ_TRIGGERS(sched_name, trigger_name, trigger_group) +) engine=innodb comment = 'Cron类型的触发器表'; + +-- ---------------------------- +-- 5、 Trigger 作为 Blob 类型存储(用于 Quartz 用户用 JDBC 创建他们自己定制的 Trigger 类型,JobStore 并不知道如何存储实例的时候) +-- ---------------------------- +create table QRTZ_BLOB_TRIGGERS ( + sched_name varchar(120) not null comment '调度名称', + trigger_name varchar(200) not null comment 'qrtz_triggers表trigger_name的外键', + trigger_group varchar(200) not null comment 'qrtz_triggers表trigger_group的外键', + blob_data blob null comment '存放持久化Trigger对象', + primary key (sched_name, trigger_name, trigger_group), + foreign key (sched_name, trigger_name, trigger_group) references QRTZ_TRIGGERS(sched_name, trigger_name, trigger_group) +) engine=innodb comment = 'Blob类型的触发器表'; + +-- ---------------------------- +-- 6、 以 Blob 类型存储存放日历信息, quartz可配置一个日历来指定一个时间范围 +-- ---------------------------- +create table QRTZ_CALENDARS ( + sched_name varchar(120) not null comment '调度名称', + calendar_name varchar(200) not null comment '日历名称', + calendar blob not null comment '存放持久化calendar对象', + primary key (sched_name, calendar_name) +) engine=innodb comment = '日历信息表'; + +-- ---------------------------- +-- 7、 存储已暂停的 Trigger 组的信息 +-- ---------------------------- +create table QRTZ_PAUSED_TRIGGER_GRPS ( + sched_name varchar(120) not null comment '调度名称', + trigger_group varchar(200) not null comment 'qrtz_triggers表trigger_group的外键', + primary key (sched_name, trigger_group) +) engine=innodb comment = '暂停的触发器表'; + +-- ---------------------------- +-- 8、 存储与已触发的 Trigger 相关的状态信息,以及相联 Job 的执行信息 +-- ---------------------------- +create table QRTZ_FIRED_TRIGGERS ( + sched_name varchar(120) not null comment '调度名称', + entry_id varchar(95) not null comment '调度器实例id', + trigger_name varchar(200) not null comment 'qrtz_triggers表trigger_name的外键', + trigger_group varchar(200) not null comment 'qrtz_triggers表trigger_group的外键', + instance_name varchar(200) not null comment '调度器实例名', + fired_time bigint(13) not null comment '触发的时间', + sched_time bigint(13) not null comment '定时器制定的时间', + priority integer not null comment '优先级', + state varchar(16) not null comment '状态', + job_name varchar(200) null comment '任务名称', + job_group varchar(200) null comment '任务组名', + is_nonconcurrent varchar(1) null comment '是否并发', + requests_recovery varchar(1) null comment '是否接受恢复执行', + primary key (sched_name, entry_id) +) engine=innodb comment = '已触发的触发器表'; + +-- ---------------------------- +-- 9、 存储少量的有关 Scheduler 的状态信息,假如是用于集群中,可以看到其他的 Scheduler 实例 +-- ---------------------------- +create table QRTZ_SCHEDULER_STATE ( + sched_name varchar(120) not null comment '调度名称', + instance_name varchar(200) not null comment '实例名称', + last_checkin_time bigint(13) not null comment '上次检查时间', + checkin_interval bigint(13) not null comment '检查间隔时间', + primary key (sched_name, instance_name) +) engine=innodb comment = '调度器状态表'; + +-- ---------------------------- +-- 10、 存储程序的悲观锁的信息(假如使用了悲观锁) +-- ---------------------------- +create table QRTZ_LOCKS ( + sched_name varchar(120) not null comment '调度名称', + lock_name varchar(40) not null comment '悲观锁名称', + primary key (sched_name, lock_name) +) engine=innodb comment = '存储的悲观锁信息表'; + +-- ---------------------------- +-- 11、 Quartz集群实现同步机制的行锁表 +-- ---------------------------- +create table QRTZ_SIMPROP_TRIGGERS ( + sched_name varchar(120) not null comment '调度名称', + trigger_name varchar(200) not null comment 'qrtz_triggers表trigger_name的外键', + trigger_group varchar(200) not null comment 'qrtz_triggers表trigger_group的外键', + str_prop_1 varchar(512) null comment 'String类型的trigger的第一个参数', + str_prop_2 varchar(512) null comment 'String类型的trigger的第二个参数', + str_prop_3 varchar(512) null comment 'String类型的trigger的第三个参数', + int_prop_1 int null comment 'int类型的trigger的第一个参数', + int_prop_2 int null comment 'int类型的trigger的第二个参数', + long_prop_1 bigint null comment 'long类型的trigger的第一个参数', + long_prop_2 bigint null comment 'long类型的trigger的第二个参数', + dec_prop_1 numeric(13,4) null comment 'decimal类型的trigger的第一个参数', + dec_prop_2 numeric(13,4) null comment 'decimal类型的trigger的第二个参数', + bool_prop_1 varchar(1) null comment 'Boolean类型的trigger的第一个参数', + bool_prop_2 varchar(1) null comment 'Boolean类型的trigger的第二个参数', + primary key (sched_name, trigger_name, trigger_group), + foreign key (sched_name, trigger_name, trigger_group) references QRTZ_TRIGGERS(sched_name, trigger_name, trigger_group) +) engine=innodb comment = '同步机制的行锁表'; + +commit; \ No newline at end of file diff --git a/sql/ruoyi.html b/sql/ruoyi.html new file mode 100644 index 0000000..abd2596 --- /dev/null +++ b/sql/ruoyi.html @@ -0,0 +1,2890 @@ + + + + +RuoYi + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RuoYi + Move the mouse over tables & columns to read the comments. + + + + + + + Fk qrtz_blob_triggers_ibfk_1 +qrtz_blob_triggers ref qrtz_triggers ( sched_name, trigger_name, trigger_group ) + + +sched_name,trigger_name,trigger_group + + + Fk qrtz_cron_triggers_ibfk_1 +qrtz_cron_triggers ref qrtz_triggers ( sched_name, trigger_name, trigger_group ) + + +sched_name,trigger_name,trigger_group + + + Fk qrtz_simple_triggers_ibfk_1 +qrtz_simple_triggers ref qrtz_triggers ( sched_name, trigger_name, trigger_group ) + + +sched_name,trigger_name,trigger_group + + + Fk qrtz_simprop_triggers_ibfk_1 +qrtz_simprop_triggers ref qrtz_triggers ( sched_name, trigger_name, trigger_group ) + + +sched_name,trigger_name,trigger_group + + + Fk qrtz_triggers_ibfk_1 +qrtz_triggers ref qrtz_job_details ( sched_name, job_name, job_group ) + + +sched_name,job_name,job_group + + + + + + + +qrtz_blob_triggersTable ry.qrtz_blob_triggers + Pk pk_qrtz_blob_triggers ( sched_name, trigger_name, trigger_group ) +sched_namesched_name +* varchar(120) +References qrtz_triggers ( sched_name, trigger_name, trigger_group ) + Pk pk_qrtz_blob_triggers ( sched_name, trigger_name, trigger_group ) +trigger_nametrigger_name +* varchar(200) +References qrtz_triggers ( sched_name, trigger_name, trigger_group ) + Pk pk_qrtz_blob_triggers ( sched_name, trigger_name, trigger_group ) +trigger_grouptrigger_group +* varchar(200) +References qrtz_triggers ( sched_name, trigger_name, trigger_group ) + blob_datablob_data +blob +~ + + + + + + + +qrtz_calendarsTable ry.qrtz_calendars + Pk pk_qrtz_calendars ( sched_name, calendar_name ) +sched_namesched_name +* varchar(120) +t Pk pk_qrtz_calendars ( sched_name, calendar_name ) +calendar_namecalendar_name +* varchar(200) +t calendarcalendar +* blob +~ + + + + + + + +qrtz_cron_triggersTable ry.qrtz_cron_triggers + Pk pk_qrtz_cron_triggers ( sched_name, trigger_name, trigger_group ) +sched_namesched_name +* varchar(120) +References qrtz_triggers ( sched_name, trigger_name, trigger_group ) + Pk pk_qrtz_cron_triggers ( sched_name, trigger_name, trigger_group ) +trigger_nametrigger_name +* varchar(200) +References qrtz_triggers ( sched_name, trigger_name, trigger_group ) + Pk pk_qrtz_cron_triggers ( sched_name, trigger_name, trigger_group ) +trigger_grouptrigger_group +* varchar(200) +References qrtz_triggers ( sched_name, trigger_name, trigger_group ) + cron_expressioncron_expression +* varchar(200) +t time_zone_idtime_zone_id +varchar(80) +t + + + + + + + +qrtz_job_detailsTable ry.qrtz_job_details + Pk pk_qrtz_job_details ( sched_name, job_name, job_group ) +sched_namesched_name +* varchar(120) +Referred by qrtz_triggers ( sched_name, job_name, job_group ) + Pk pk_qrtz_job_details ( sched_name, job_name, job_group ) +job_namejob_name +* varchar(200) +Referred by qrtz_triggers ( sched_name, job_name, job_group ) + Pk pk_qrtz_job_details ( sched_name, job_name, job_group ) +job_groupjob_group +* varchar(200) +Referred by qrtz_triggers ( sched_name, job_name, job_group ) + descriptiondescription +varchar(250) +t job_class_namejob_class_name +* varchar(250) +t is_durableis_durable +* varchar(1) +t is_nonconcurrentis_nonconcurrent +* varchar(1) +t is_update_datais_update_data +* varchar(1) +t requests_recoveryrequests_recovery +* varchar(1) +t job_datajob_data +blob +~ + + + + + + + +qrtz_locksTable ry.qrtz_locks + Pk pk_qrtz_locks ( sched_name, lock_name ) +sched_namesched_name +* varchar(120) +t Pk pk_qrtz_locks ( sched_name, lock_name ) +lock_namelock_name +* varchar(40) +t + + + + + + + +qrtz_scheduler_stateTable ry.qrtz_scheduler_state + Pk pk_qrtz_scheduler_state ( sched_name, instance_name ) +sched_namesched_name +* varchar(120) +t Pk pk_qrtz_scheduler_state ( sched_name, instance_name ) +instance_nameinstance_name +* varchar(200) +t last_checkin_timelast_checkin_time +* bigint +# checkin_intervalcheckin_interval +* bigint +# + + + + + + + +qrtz_simple_triggersTable ry.qrtz_simple_triggers + Pk pk_qrtz_simple_triggers ( sched_name, trigger_name, trigger_group ) +sched_namesched_name +* varchar(120) +References qrtz_triggers ( sched_name, trigger_name, trigger_group ) + Pk pk_qrtz_simple_triggers ( sched_name, trigger_name, trigger_group ) +trigger_nametrigger_name +* varchar(200) +References qrtz_triggers ( sched_name, trigger_name, trigger_group ) + Pk pk_qrtz_simple_triggers ( sched_name, trigger_name, trigger_group ) +trigger_grouptrigger_group +* varchar(200) +References qrtz_triggers ( sched_name, trigger_name, trigger_group ) + repeat_countrepeat_count +* bigint +# repeat_intervalrepeat_interval +* bigint +# times_triggeredtimes_triggered +* bigint +# + + + + + + + +qrtz_simprop_triggersTable ry.qrtz_simprop_triggers + Pk pk_qrtz_simprop_triggers ( sched_name, trigger_name, trigger_group ) +sched_namesched_name +* varchar(120) +References qrtz_triggers ( sched_name, trigger_name, trigger_group ) + Pk pk_qrtz_simprop_triggers ( sched_name, trigger_name, trigger_group ) +trigger_nametrigger_name +* varchar(200) +References qrtz_triggers ( sched_name, trigger_name, trigger_group ) + Pk pk_qrtz_simprop_triggers ( sched_name, trigger_name, trigger_group ) +trigger_grouptrigger_group +* varchar(200) +References qrtz_triggers ( sched_name, trigger_name, trigger_group ) + str_prop_1str_prop_1 +varchar(512) +t str_prop_2str_prop_2 +varchar(512) +t str_prop_3str_prop_3 +varchar(512) +t int_prop_1int_prop_1 +int +# int_prop_2int_prop_2 +int +# long_prop_1long_prop_1 +bigint +# long_prop_2long_prop_2 +bigint +# dec_prop_1dec_prop_1 +decimal(13,4) +# dec_prop_2dec_prop_2 +decimal(13,4) +# bool_prop_1bool_prop_1 +varchar(1) +t bool_prop_2bool_prop_2 +varchar(1) +t + + + + + + + +qrtz_triggersTable ry.qrtz_triggers + Pk pk_qrtz_triggers ( sched_name, trigger_name, trigger_group ) sched_name ( sched_name, job_name, job_group ) +sched_namesched_name +* varchar(120) +References qrtz_job_details ( sched_name, job_name, job_group ) +Referred by qrtz_blob_triggers ( sched_name, trigger_name, trigger_group ) +Referred by qrtz_cron_triggers ( sched_name, trigger_name, trigger_group ) +Referred by qrtz_simple_triggers ( sched_name, trigger_name, trigger_group ) +Referred by qrtz_simprop_triggers ( sched_name, trigger_name, trigger_group ) + Pk pk_qrtz_triggers ( sched_name, trigger_name, trigger_group ) +trigger_nametrigger_name +* varchar(200) +Referred by qrtz_blob_triggers ( sched_name, trigger_name, trigger_group ) +Referred by qrtz_cron_triggers ( sched_name, trigger_name, trigger_group ) +Referred by qrtz_simple_triggers ( sched_name, trigger_name, trigger_group ) +Referred by qrtz_simprop_triggers ( sched_name, trigger_name, trigger_group ) + Pk pk_qrtz_triggers ( sched_name, trigger_name, trigger_group ) +trigger_grouptrigger_group +* varchar(200) +Referred by qrtz_blob_triggers ( sched_name, trigger_name, trigger_group ) +Referred by qrtz_cron_triggers ( sched_name, trigger_name, trigger_group ) +Referred by qrtz_simple_triggers ( sched_name, trigger_name, trigger_group ) +Referred by qrtz_simprop_triggers ( sched_name, trigger_name, trigger_group ) + sched_name ( sched_name, job_name, job_group ) +job_namejob_name +* varchar(200) +References qrtz_job_details ( sched_name, job_name, job_group ) + sched_name ( sched_name, job_name, job_group ) +job_groupjob_group +* varchar(200) +References qrtz_job_details ( sched_name, job_name, job_group ) + descriptiondescription +varchar(250) +t next_fire_timenext_fire_time +bigint +# prev_fire_timeprev_fire_time +bigint +# prioritypriority +int +# trigger_statetrigger_state +* varchar(16) +t trigger_typetrigger_type +* varchar(8) +t start_timestart_time +* bigint +# end_timeend_time +bigint +# calendar_namecalendar_name +varchar(200) +t misfire_instrmisfire_instr +smallint +# job_datajob_data +blob +~ + + + + + + + +sys_dict_dataTable ry.sys_dict_data + Pk pk_sys_dict_data ( dict_code ) +dict_codedict_code +* int +字典编码 +# dict_sortdict_sort +int default 0 +字典排序 +# dict_labeldict_label +varchar(100) default '' +字典标签 +t dict_valuedict_value +varchar(100) default '' +字典键值 +t dict_typedict_type +varchar(100) default '' +字典类型 +t statusstatus +int default 0 +状态(0正常 1禁用) +# create_bycreate_by +varchar(64) default '' +创建者 +t create_timecreate_time +* timestamp default CURRENT_TIMESTAMP +创建时间 +d update_byupdate_by +varchar(64) default '' +更新者 +t update_timeupdate_time +* timestamp default '0000-00-00 00:00:00' +更新时间 +d remarkremark +varchar(500) default '' +备注 +t + + + + + + + +sys_dict_typeTable ry.sys_dict_type + Pk pk_sys_dict_type ( dict_id ) +dict_iddict_id +* int +字典主键 +# dict_namedict_name +varchar(100) default '' +字典名称 +t Unq dict_type ( dict_type ) +dict_typedict_type +varchar(100) default '' +字典类型 +t statusstatus +int default 0 +状态(0正常 1禁用) +# create_bycreate_by +varchar(64) default '' +创建者 +t create_timecreate_time +* timestamp default CURRENT_TIMESTAMP +创建时间 +d update_byupdate_by +varchar(64) default '' +更新者 +t update_timeupdate_time +* timestamp default '0000-00-00 00:00:00' +更新时间 +d remarkremark +varchar(500) default '' +备注 +t + + + + + + + +sys_jobTable ry.sys_job + Pk pk_sys_job ( job_id, job_name, job_group ) +job_idjob_id +* int +任务ID +# Pk pk_sys_job ( job_id, job_name, job_group ) +job_namejob_name +* varchar(64) default '' +任务名称 +t Pk pk_sys_job ( job_id, job_name, job_group ) +job_groupjob_group +* varchar(64) default '' +任务组名 +t method_namemethod_name +varchar(500) default '' +任务方法 +t paramsparams +varchar(200) default '' +方法参数 +t cron_expressioncron_expression +varchar(255) default '' +cron执行表达式 +t statusstatus +int default 0 +状态(0正常 1暂停) +# create_bycreate_by +varchar(64) default '' +创建者 +t create_timecreate_time +* timestamp default CURRENT_TIMESTAMP +创建时间 +d update_byupdate_by +varchar(64) default '' +更新者 +t update_timeupdate_time +* timestamp default '0000-00-00 00:00:00' +更新时间 +d remarkremark +varchar(500) default '' +备注信息 +t + + + + + + + +sys_job_logTable ry.sys_job_log + Pk pk_sys_job_log ( job_log_id ) +job_log_idjob_log_id +* int +任务日志ID +# job_namejob_name +* varchar(64) +任务名称 +t job_groupjob_group +* varchar(64) +任务组名 +t method_namemethod_name +varchar(500) +任务方法 +t paramsparams +varchar(200) default '' +方法参数 +t job_messagejob_message +varchar(500) +日志信息 +t is_exceptionis_exception +int default 0 +是否异常 +# exception_infoexception_info +text +异常信息 +t create_timecreate_time +* timestamp default CURRENT_TIMESTAMP +创建时间 +d + + + + + + + +sys_logininforTable ry.sys_logininfor + Pk pk_sys_logininfor ( info_id ) +info_idinfo_id +* int +访问ID +# login_namelogin_name +varchar(50) default '' +登录账号 +t ipaddripaddr +varchar(50) default '' +登录IP地址 +t browserbrowser +varchar(50) default '' +浏览器类型 +t osos +varchar(50) default '' +操作系统 +t statusstatus +int default 0 +登录状态 0成功 1失败 +# msgmsg +varchar(255) default '' +提示消息 +t login_timelogin_time +* timestamp default CURRENT_TIMESTAMP +访问时间 +d + + + + + + + +sys_menuTable ry.sys_menu + Pk pk_sys_menu ( menu_id ) +menu_idmenu_id +* int +菜单ID +# menu_namemenu_name +* varchar(50) +菜单名称 +t parent_idparent_id +int default 0 +父菜单ID +# order_numorder_num +int +显示顺序 +# urlurl +varchar(200) default '' +请求地址 +t menu_typemenu_type +char(1) default '' +类型:M目录,C菜单,F按钮 +c visiblevisible +int default 0 +菜单状态:0显示,1隐藏 +# permsperms +varchar(100) default '' +权限标识 +t iconicon +varchar(100) default '' +菜单图标 +t create_bycreate_by +varchar(64) default '' +创建者 +t create_timecreate_time +* timestamp default CURRENT_TIMESTAMP +创建时间 +d update_byupdate_by +varchar(64) default '' +更新者 +t update_timeupdate_time +* timestamp default '0000-00-00 00:00:00' +更新时间 +d remarkremark +varchar(500) default '' +备注 +t + + + + + + + +sys_oper_logTable ry.sys_oper_log + Pk pk_sys_oper_log ( oper_id ) +oper_idoper_id +* int +日志主键 +# titletitle +varchar(50) default '' +模块标题 +t actionaction +varchar(100) default '' +功能请求 +t methodmethod +varchar(100) default '' +方法名称 +t channelchannel +varchar(20) default '' +来源渠道 +t login_namelogin_name +varchar(50) default '' +登录账号 +t dept_namedept_name +varchar(50) default '' +部门名称 +t oper_urloper_url +varchar(255) default '' +请求URL +t oper_ipoper_ip +varchar(30) default '' +主机地址 +t oper_paramoper_param +varchar(255) default '' +请求参数 +t statusstatus +int default 0 +操作状态 0正常 1异常 +# error_msgerror_msg +varchar(2000) default '' +错误消息 +t oper_timeoper_time +* timestamp default CURRENT_TIMESTAMP +操作时间 +d + + + + + + + +sys_postTable ry.sys_post + Pk pk_sys_post ( post_id ) +post_idpost_id +* int +岗位ID +# post_codepost_code +* varchar(64) +岗位编码 +t post_namepost_name +* varchar(100) +岗位名称 +t post_sortpost_sort +* int +显示顺序 +# statusstatus +* int +状态(0正常 1停用) +# create_bycreate_by +varchar(64) default '' +创建者 +t create_timecreate_time +* timestamp default CURRENT_TIMESTAMP +创建时间 +d update_byupdate_by +varchar(64) default '' +更新者 +t update_timeupdate_time +* timestamp default '0000-00-00 00:00:00' +更新时间 +d remarkremark +varchar(500) default '' +备注 +t + + + + + + + +sys_roleTable ry.sys_role + Pk pk_sys_role ( role_id ) +role_idrole_id +* int +角色ID +# role_namerole_name +* varchar(30) +角色名称 +t role_keyrole_key +* varchar(100) +角色权限字符串 +t role_sortrole_sort +* int +显示顺序 +# statusstatus +int default 0 +角色状态:0正常,1禁用 +# create_bycreate_by +varchar(64) default '' +创建者 +t create_timecreate_time +* timestamp default CURRENT_TIMESTAMP +创建时间 +d update_byupdate_by +varchar(64) default '' +更新者 +t update_timeupdate_time +* timestamp default '0000-00-00 00:00:00' +更新时间 +d remarkremark +varchar(500) default '' +备注 +t + + + + + + + +sys_role_menuTable ry.sys_role_menu + Pk pk_sys_role_menu ( role_id, menu_id ) +role_idrole_id +* int +角色ID +# Pk pk_sys_role_menu ( role_id, menu_id ) +menu_idmenu_id +* int +菜单ID +# + + + + + + + +sys_userTable ry.sys_user + Pk pk_sys_user ( user_id ) +user_iduser_id +* int +用户ID +# dept_iddept_id +int +部门ID +# login_namelogin_name +varchar(30) default '' +登录账号 +t user_nameuser_name +varchar(30) default '' +用户昵称 +t emailemail +varchar(100) default '' +用户邮箱 +t phonenumberphonenumber +varchar(20) default '' +手机号码 +t passwordpassword +varchar(100) default '' +密码 +t saltsalt +varchar(100) default '' +盐加密 +t user_typeuser_type +char(1) default 'N' +类型:Y默认用户,N非默认用户 +c statusstatus +int default 0 +帐号状态:0正常,1禁用 +# refuse_desrefuse_des +varchar(500) default '' +拒绝登录描述 +t create_bycreate_by +varchar(64) default '' +创建者 +t create_timecreate_time +* timestamp default CURRENT_TIMESTAMP +创建时间 +d update_byupdate_by +varchar(64) default '' +更新者 +t update_timeupdate_time +* timestamp default '0000-00-00 00:00:00' +更新时间 +d + + + + + + + +sys_user_onlineTable ry.sys_user_online + Pk pk_sys_user_online ( sessionId ) +sessionIdsessionId +* varchar(50) default '' +用户会话id +t login_namelogin_name +varchar(50) default '' +登录账号 +t dept_namedept_name +varchar(50) default '' +部门名称 +t ipaddripaddr +varchar(50) default '' +登录IP地址 +t browserbrowser +varchar(50) default '' +浏览器类型 +t osos +varchar(50) default '' +操作系统 +t statusstatus +varchar(10) default '' +在线状态on_line在线off_line离线 +t start_timestampstart_timestamp +* timestamp default CURRENT_TIMESTAMP +session创建时间 +d last_access_timelast_access_time +* timestamp default '0000-00-00 00:00:00' +session最后访问时间 +d expire_timeexpire_time +int default 0 +超时时间,单位为分钟 +# + + + + + + + +sys_user_postTable ry.sys_user_post + Pk pk_sys_user_post ( user_id, post_id ) +user_iduser_id +* varchar(64) +用户ID +t Pk pk_sys_user_post ( user_id, post_id ) +post_idpost_id +* varchar(64) +岗位ID +t + + + + + + + +sys_user_roleTable ry.sys_user_role + Pk pk_sys_user_role ( user_id, role_id ) +user_iduser_id +* int +用户ID +# Pk pk_sys_user_role ( user_id, role_id ) +role_idrole_id +* int +角色ID +# + + + + + + + +sys_deptTable ry.sys_dept + Pk pk_sys_dept ( dept_id ) +dept_iddept_id +* int +部门id +# parent_idparent_id +int default 0 +父部门id +# dept_namedept_name +varchar(30) default '' +部门名称 +t order_numorder_num +int default 0 +显示顺序 +# leaderleader +varchar(20) default '' +负责人 +t phonephone +varchar(20) default '' +联系电话 +t emailemail +varchar(20) default '' +邮箱 +t statusstatus +int default 0 +部门状态:0正常,1停用 +# create_bycreate_by +varchar(64) default '' +创建者 +t create_timecreate_time +* timestamp default CURRENT_TIMESTAMP +创建时间 +d update_byupdate_by +varchar(64) default '' +更新者 +t update_timeupdate_time +* timestamp default '0000-00-00 00:00:00' +更新时间 +d + + + + + + + +qrtz_paused_trigger_grpsTable ry.qrtz_paused_trigger_grps + Pk pk_qrtz_paused_trigger_grps ( sched_name, trigger_group ) +sched_namesched_name +* varchar(120) +t Pk pk_qrtz_paused_trigger_grps ( sched_name, trigger_group ) +trigger_grouptrigger_group +* varchar(200) +t + + + + + + + +qrtz_fired_triggersTable ry.qrtz_fired_triggers + Pk pk_qrtz_fired_triggers ( sched_name, entry_id ) +sched_namesched_name +* varchar(120) +t Pk pk_qrtz_fired_triggers ( sched_name, entry_id ) +entry_identry_id +* varchar(95) +t trigger_nametrigger_name +* varchar(200) +t trigger_grouptrigger_group +* varchar(200) +t instance_nameinstance_name +* varchar(200) +t fired_timefired_time +* bigint +# sched_timesched_time +* bigint +# prioritypriority +* int +# statestate +* varchar(16) +t job_namejob_name +varchar(200) +t job_groupjob_group +varchar(200) +t is_nonconcurrentis_nonconcurrent +varchar(1) +t requests_recoveryrequests_recovery +varchar(1) +t +
    + + +

    +

    Table qrtz_blob_triggers

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    IndexesField NameData TypeDescription
    *sched_name varchar( 120 )
    *trigger_name varchar( 200 )
    *trigger_group varchar( 200 )
     blob_data blob
    Indexes
    pk_qrtz_blob_triggers ON sched_name, trigger_name, trigger_group
    Foreign Keys
    qrtz_blob_triggers_ibfk_1 ( sched_name, trigger_name, trigger_group ) ref qrtz_triggers (sched_name, trigger_name, trigger_group)
    + +

    +

    Table qrtz_calendars

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    IndexesField NameData TypeDescription
    *sched_name varchar( 120 )
    *calendar_name varchar( 200 )
    *calendar blob
    Indexes
    pk_qrtz_calendars ON sched_name, calendar_name
    + +

    +

    Table qrtz_cron_triggers

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    IndexesField NameData TypeDescription
    *sched_name varchar( 120 )
    *trigger_name varchar( 200 )
    *trigger_group varchar( 200 )
    *cron_expression varchar( 200 )
     time_zone_id varchar( 80 )
    Indexes
    pk_qrtz_cron_triggers ON sched_name, trigger_name, trigger_group
    Foreign Keys
    qrtz_cron_triggers_ibfk_1 ( sched_name, trigger_name, trigger_group ) ref qrtz_triggers (sched_name, trigger_name, trigger_group)
    + +

    +

    Table qrtz_fired_triggers

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    IndexesField NameData TypeDescription
    *sched_name varchar( 120 )
    *entry_id varchar( 95 )
    *trigger_name varchar( 200 )
    *trigger_group varchar( 200 )
    *instance_name varchar( 200 )
    *fired_time bigint
    *sched_time bigint
    *priority int
    *state varchar( 16 )
     job_name varchar( 200 )
     job_group varchar( 200 )
     is_nonconcurrent varchar( 1 )
     requests_recovery varchar( 1 )
    Indexes
    pk_qrtz_fired_triggers ON sched_name, entry_id
    + +

    +

    Table qrtz_job_details

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    IndexesField NameData TypeDescription
    *sched_name varchar( 120 )
    *job_name varchar( 200 )
    *job_group varchar( 200 )
     description varchar( 250 )
    *job_class_name varchar( 250 )
    *is_durable varchar( 1 )
    *is_nonconcurrent varchar( 1 )
    *is_update_data varchar( 1 )
    *requests_recovery varchar( 1 )
     job_data blob
    Indexes
    pk_qrtz_job_details ON sched_name, job_name, job_group
    + +

    +

    Table qrtz_locks

    + + + + + + + + + + + + + + + + + + + + + + +
    IndexesField NameData TypeDescription
    *sched_name varchar( 120 )
    *lock_name varchar( 40 )
    Indexes
    pk_qrtz_locks ON sched_name, lock_name
    + +

    +

    Table qrtz_paused_trigger_grps

    + + + + + + + + + + + + + + + + + + + + + + +
    IndexesField NameData TypeDescription
    *sched_name varchar( 120 )
    *trigger_group varchar( 200 )
    Indexes
    pk_qrtz_paused_trigger_grps ON sched_name, trigger_group
    + +

    +

    Table qrtz_scheduler_state

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    IndexesField NameData TypeDescription
    *sched_name varchar( 120 )
    *instance_name varchar( 200 )
    *last_checkin_time bigint
    *checkin_interval bigint
    Indexes
    pk_qrtz_scheduler_state ON sched_name, instance_name
    + +

    +

    Table qrtz_simple_triggers

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    IndexesField NameData TypeDescription
    *sched_name varchar( 120 )
    *trigger_name varchar( 200 )
    *trigger_group varchar( 200 )
    *repeat_count bigint
    *repeat_interval bigint
    *times_triggered bigint
    Indexes
    pk_qrtz_simple_triggers ON sched_name, trigger_name, trigger_group
    Foreign Keys
    qrtz_simple_triggers_ibfk_1 ( sched_name, trigger_name, trigger_group ) ref qrtz_triggers (sched_name, trigger_name, trigger_group)
    + +

    +

    Table qrtz_simprop_triggers

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    IndexesField NameData TypeDescription
    *sched_name varchar( 120 )
    *trigger_name varchar( 200 )
    *trigger_group varchar( 200 )
     str_prop_1 varchar( 512 )
     str_prop_2 varchar( 512 )
     str_prop_3 varchar( 512 )
     int_prop_1 int
     int_prop_2 int
     long_prop_1 bigint
     long_prop_2 bigint
     dec_prop_1 decimal( 13, 4 )
     dec_prop_2 decimal( 13, 4 )
     bool_prop_1 varchar( 1 )
     bool_prop_2 varchar( 1 )
    Indexes
    pk_qrtz_simprop_triggers ON sched_name, trigger_name, trigger_group
    Foreign Keys
    qrtz_simprop_triggers_ibfk_1 ( sched_name, trigger_name, trigger_group ) ref qrtz_triggers (sched_name, trigger_name, trigger_group)
    + +

    +

    Table qrtz_triggers

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    IndexesField NameData TypeDescription
    *sched_name varchar( 120 )
    *trigger_name varchar( 200 )
    *trigger_group varchar( 200 )
    *job_name varchar( 200 )
    *job_group varchar( 200 )
     description varchar( 250 )
     next_fire_time bigint
     prev_fire_time bigint
     priority int
    *trigger_state varchar( 16 )
    *trigger_type varchar( 8 )
    *start_time bigint
     end_time bigint
     calendar_name varchar( 200 )
     misfire_instr smallint
     job_data blob
    Indexes
    pk_qrtz_triggers ON sched_name, trigger_name, trigger_group
    sched_name ON sched_name, job_name, job_group
    Foreign Keys
    qrtz_triggers_ibfk_1 ( sched_name, job_name, job_group ) ref qrtz_job_details (sched_name, job_name, job_group)
    + +

    +

    Table sys_dept

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    IndexesField NameData TypeDescription
    *dept_id int AUTOINCREMENT 部门id
     parent_id int DEFAULT 0 父部门id
     dept_name varchar( 30 ) DEFAULT '' 部门名称
     order_num int DEFAULT 0 显示顺序
     leader varchar( 20 ) DEFAULT '' 负责人
     phone varchar( 20 ) DEFAULT '' 联系电话
     email varchar( 20 ) DEFAULT '' 邮箱
     status int DEFAULT 0 部门状态:0正常,1停用
     create_by varchar( 64 ) DEFAULT '' 创建者
    *create_time timestamp DEFAULT CURRENT_TIMESTAMP 创建时间
     update_by varchar( 64 ) DEFAULT '' 更新者
    *update_time timestamp DEFAULT '0000-00-00 00:00:00' 更新时间
    Indexes
    pk_sys_dept ON dept_id
    + +

    +

    Table sys_dict_data

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    IndexesField NameData TypeDescription
    *dict_code int AUTOINCREMENT 字典编码
     dict_sort int DEFAULT 0 字典排序
     dict_label varchar( 100 ) DEFAULT '' 字典标签
     dict_value varchar( 100 ) DEFAULT '' 字典键值
     dict_type varchar( 100 ) DEFAULT '' 字典类型
     status int DEFAULT 0 状态(0正常 1禁用)
     create_by varchar( 64 ) DEFAULT '' 创建者
    *create_time timestamp DEFAULT CURRENT_TIMESTAMP 创建时间
     update_by varchar( 64 ) DEFAULT '' 更新者
    *update_time timestamp DEFAULT '0000-00-00 00:00:00' 更新时间
     remark varchar( 500 ) DEFAULT '' 备注
    Indexes
    pk_sys_dict_data ON dict_code
    + +

    +

    Table sys_dict_type

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    IndexesField NameData TypeDescription
    *dict_id int AUTOINCREMENT 字典主键
     dict_name varchar( 100 ) DEFAULT '' 字典名称
    dict_type varchar( 100 ) DEFAULT '' 字典类型
     status int DEFAULT 0 状态(0正常 1禁用)
     create_by varchar( 64 ) DEFAULT '' 创建者
    *create_time timestamp DEFAULT CURRENT_TIMESTAMP 创建时间
     update_by varchar( 64 ) DEFAULT '' 更新者
    *update_time timestamp DEFAULT '0000-00-00 00:00:00' 更新时间
     remark varchar( 500 ) DEFAULT '' 备注
    Indexes
    pk_sys_dict_type ON dict_id
    dict_type ON dict_type
    + +

    +

    Table sys_job

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    IndexesField NameData TypeDescription
    *job_id int AUTOINCREMENT 任务ID
    *job_name varchar( 64 ) DEFAULT '' 任务名称
    *job_group varchar( 64 ) DEFAULT '' 任务组名
     method_name varchar( 500 ) DEFAULT '' 任务方法
     params varchar( 200 ) DEFAULT '' 方法参数
     cron_expression varchar( 255 ) DEFAULT '' cron执行表达式
     status int DEFAULT 0 状态(0正常 1暂停)
     create_by varchar( 64 ) DEFAULT '' 创建者
    *create_time timestamp DEFAULT CURRENT_TIMESTAMP 创建时间
     update_by varchar( 64 ) DEFAULT '' 更新者
    *update_time timestamp DEFAULT '0000-00-00 00:00:00' 更新时间
     remark varchar( 500 ) DEFAULT '' 备注信息
    Indexes
    pk_sys_job ON job_id, job_name, job_group
    + +

    +

    Table sys_job_log

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    IndexesField NameData TypeDescription
    *job_log_id int AUTOINCREMENT 任务日志ID
    *job_name varchar( 64 ) 任务名称
    *job_group varchar( 64 ) 任务组名
     method_name varchar( 500 ) 任务方法
     params varchar( 200 ) DEFAULT '' 方法参数
     job_message varchar( 500 ) 日志信息
     is_exception int DEFAULT 0 是否异常
     exception_info text 异常信息
    *create_time timestamp DEFAULT CURRENT_TIMESTAMP 创建时间
    Indexes
    pk_sys_job_log ON job_log_id
    + +

    +

    Table sys_logininfor

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    IndexesField NameData TypeDescription
    *info_id int AUTOINCREMENT 访问ID
     login_name varchar( 50 ) DEFAULT '' 登录账号
     ipaddr varchar( 50 ) DEFAULT '' 登录IP地址
     browser varchar( 50 ) DEFAULT '' 浏览器类型
     os varchar( 50 ) DEFAULT '' 操作系统
     status int DEFAULT 0 登录状态 0成功 1失败
     msg varchar( 255 ) DEFAULT '' 提示消息
    *login_time timestamp DEFAULT CURRENT_TIMESTAMP 访问时间
    Indexes
    pk_sys_logininfor ON info_id
    + +

    +

    Table sys_menu

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    IndexesField NameData TypeDescription
    *menu_id int AUTOINCREMENT 菜单ID
    *menu_name varchar( 50 ) 菜单名称
     parent_id int DEFAULT 0 父菜单ID
     order_num int 显示顺序
     url varchar( 200 ) DEFAULT '' 请求地址
     menu_type char( 1 ) DEFAULT '' 类型:M目录,C菜单,F按钮
     visible int DEFAULT 0 菜单状态:0显示,1隐藏
     perms varchar( 100 ) DEFAULT '' 权限标识
     icon varchar( 100 ) DEFAULT '' 菜单图标
     create_by varchar( 64 ) DEFAULT '' 创建者
    *create_time timestamp DEFAULT CURRENT_TIMESTAMP 创建时间
     update_by varchar( 64 ) DEFAULT '' 更新者
    *update_time timestamp DEFAULT '0000-00-00 00:00:00' 更新时间
     remark varchar( 500 ) DEFAULT '' 备注
    Indexes
    pk_sys_menu ON menu_id
    + +

    +

    Table sys_oper_log

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    IndexesField NameData TypeDescription
    *oper_id int AUTOINCREMENT 日志主键
     title varchar( 50 ) DEFAULT '' 模块标题
     action varchar( 100 ) DEFAULT '' 功能请求
     method varchar( 100 ) DEFAULT '' 方法名称
     channel varchar( 20 ) DEFAULT '' 来源渠道
     login_name varchar( 50 ) DEFAULT '' 登录账号
     dept_name varchar( 50 ) DEFAULT '' 部门名称
     oper_url varchar( 255 ) DEFAULT '' 请求URL
     oper_ip varchar( 30 ) DEFAULT '' 主机地址
     oper_param varchar( 255 ) DEFAULT '' 请求参数
     status int DEFAULT 0 操作状态 0正常 1异常
     error_msg varchar( 2000 ) DEFAULT '' 错误消息
    *oper_time timestamp DEFAULT CURRENT_TIMESTAMP 操作时间
    Indexes
    pk_sys_oper_log ON oper_id
    + +

    +

    Table sys_post

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    IndexesField NameData TypeDescription
    *post_id int AUTOINCREMENT 岗位ID
    *post_code varchar( 64 ) 岗位编码
    *post_name varchar( 100 ) 岗位名称
    *post_sort int 显示顺序
    *status int 状态(0正常 1停用)
     create_by varchar( 64 ) DEFAULT '' 创建者
    *create_time timestamp DEFAULT CURRENT_TIMESTAMP 创建时间
     update_by varchar( 64 ) DEFAULT '' 更新者
    *update_time timestamp DEFAULT '0000-00-00 00:00:00' 更新时间
     remark varchar( 500 ) DEFAULT '' 备注
    Indexes
    pk_sys_post ON post_id
    + +

    +

    Table sys_role

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    IndexesField NameData TypeDescription
    *role_id int AUTOINCREMENT 角色ID
    *role_name varchar( 30 ) 角色名称
    *role_key varchar( 100 ) 角色权限字符串
    *role_sort int 显示顺序
     status int DEFAULT 0 角色状态:0正常,1禁用
     create_by varchar( 64 ) DEFAULT '' 创建者
    *create_time timestamp DEFAULT CURRENT_TIMESTAMP 创建时间
     update_by varchar( 64 ) DEFAULT '' 更新者
    *update_time timestamp DEFAULT '0000-00-00 00:00:00' 更新时间
     remark varchar( 500 ) DEFAULT '' 备注
    Indexes
    pk_sys_role ON role_id
    + +

    +

    Table sys_role_menu

    + + + + + + + + + + + + + + + + + + + + + + +
    IndexesField NameData TypeDescription
    *role_id int 角色ID
    *menu_id int 菜单ID
    Indexes
    pk_sys_role_menu ON role_id, menu_id
    + +

    +

    Table sys_user

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    IndexesField NameData TypeDescription
    *user_id int AUTOINCREMENT 用户ID
     dept_id int 部门ID
     login_name varchar( 30 ) DEFAULT '' 登录账号
     user_name varchar( 30 ) DEFAULT '' 用户昵称
     email varchar( 100 ) DEFAULT '' 用户邮箱
     phonenumber varchar( 20 ) DEFAULT '' 手机号码
     password varchar( 100 ) DEFAULT '' 密码
     salt varchar( 100 ) DEFAULT '' 盐加密
     user_type char( 1 ) DEFAULT 'N' 类型:Y默认用户,N非默认用户
     status int DEFAULT 0 帐号状态:0正常,1禁用
     refuse_des varchar( 500 ) DEFAULT '' 拒绝登录描述
     create_by varchar( 64 ) DEFAULT '' 创建者
    *create_time timestamp DEFAULT CURRENT_TIMESTAMP 创建时间
     update_by varchar( 64 ) DEFAULT '' 更新者
    *update_time timestamp DEFAULT '0000-00-00 00:00:00' 更新时间
    Indexes
    pk_sys_user ON user_id
    + +

    +

    Table sys_user_online

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    IndexesField NameData TypeDescription
    *sessionId varchar( 50 ) DEFAULT '' 用户会话id
     login_name varchar( 50 ) DEFAULT '' 登录账号
     dept_name varchar( 50 ) DEFAULT '' 部门名称
     ipaddr varchar( 50 ) DEFAULT '' 登录IP地址
     browser varchar( 50 ) DEFAULT '' 浏览器类型
     os varchar( 50 ) DEFAULT '' 操作系统
     status varchar( 10 ) DEFAULT '' 在线状态on_line在线off_line离线
    *start_timestsamp timestamp DEFAULT CURRENT_TIMESTAMP session创建时间
    *last_access_time timestamp DEFAULT '0000-00-00 00:00:00' session最后访问时间
     expire_time int DEFAULT 0 超时时间,单位为分钟
    Indexes
    pk_sys_user_online ON sessionId
    + +

    +

    Table sys_user_post

    + + + + + + + + + + + + + + + + + + + + + + +
    IndexesField NameData TypeDescription
    *user_id varchar( 64 ) 用户ID
    *post_id varchar( 64 ) 岗位ID
    Indexes
    pk_sys_user_post ON user_id, post_id
    + +

    +

    Table sys_user_role

    + + + + + + + + + + + + + + + + + + + + + + +
    IndexesField NameData TypeDescription
    *user_id int 用户ID
    *role_id int 角色ID
    Indexes
    pk_sys_user_role ON user_id, role_id
    + +

    Powered by DbSchema

    \ No newline at end of file diff --git a/sql/ruoyi.pdm b/sql/ruoyi.pdm new file mode 100644 index 0000000..78d8504 --- /dev/null +++ b/sql/ruoyi.pdm @@ -0,0 +1,4851 @@ + + + + + + + + + +21C20947-ED50-4632-B638-DC1A02BD948A +ruoyi +ruoyi +1524449337 +Administrator +1538297587 +admin +[FolderOptions] + +[FolderOptions\Physical Objects] +GenerationCheckModel=Yes +GenerationPath= +GenerationOptions= +GenerationTasks= +GenerationTargets= +GenerationSelections= +RevPkey=Yes +RevFkey=Yes +RevAkey=Yes +RevCheck=Yes +RevIndx=Yes +RevOpts=Yes +RevViewAsTabl=No +RevViewOpts=Yes +RevSystAsTabl=Yes +RevTablPerm=No +RevViewPerm=No +RevProcPerm=No +RevDbpkPerm=No +RevSqncPerm=No +RevAdtPerm=No +RevUserPriv=No +RevUserOpts=No +RevGrpePriv=No +RevRolePriv=No +RevDtbsOpts=Yes +RevDtbsPerm=No +RevViewIndx=Yes +RevJidxOpts=Yes +RevStats=No +RevTspcPerm=No +RevCaseSensitive=No +GenTrgrStdMsg=Yes +GenTrgrMsgTab= +GenTrgrMsgNo= +GenTrgrMsgTxt= +TrgrPreserve=No +TrgrIns=Yes +TrgrUpd=Yes +TrgrDel=Yes +TrgrC2Ins=Yes +TrgrC2Upd=Yes +TrgrC3=Yes +TrgrC4=Yes +TrgrC5=Yes +TrgrC6=Yes +TrgrC7=Yes +TrgrC8=Yes +TrgrC9=Yes +TrgrC10=Yes +TrgrC11=Yes +TrgrC1=Yes +TrgrC12Ins=Yes +TrgrC12Upd=Yes +TrgrC13=Yes +UpdateTableStatistics=Yes +UpdateColumnStatistics=Yes + +[FolderOptions\Physical Objects\Database Generation] +GenScriptName=orders.sql +GenScriptName0=orders.sql +GenScriptName1=studentsystem.sql +GenScriptName2=NetCTOSS.sql +GenScriptName3=product.sql +GenScriptName4=voteSystem.sql +GenScriptName5=.sql +GenScriptName6=enterpriseManagement.sql +GenScriptName7=crebas.sql +GenScriptName8= +GenScriptName9= +GenPathName=C:\Users\Administrator\Desktop\ +GenSingleFile=Yes +GenODBC=No +GenCheckModel=Yes +GenScriptPrev=Yes +GenArchiveModel=No +GenUseSync=No +GenSyncChoice=0 +GenSyncArch= +GenSyncRmg=0 + +[FolderOptions\Physical Objects\Database Generation\Format] +GenScriptTitle=Yes +GenScriptNamLabl=No +GenScriptQDtbs=No +GenScriptQOwnr=Yes +GenScriptCase=0 +GenScriptEncoding=ANSI +GenScriptNAcct=No +IdentifierDelimiter=" + +[FolderOptions\Physical Objects\Database Generation\Database] +Create=Yes +Open=Yes +Close=Yes +Drop=Yes +Permission=No + +[FolderOptions\Physical Objects\Database Generation\Database\Create] +Physical Options=Yes +Header=Yes +Footer=Yes + +[FolderOptions\Physical Objects\Database Generation\Tablespace] +Create=Yes +Drop=Yes +Comment=Yes +Permission=No + +[FolderOptions\Physical Objects\Database Generation\Tablespace\Create] +Header=Yes +Footer=Yes + +[FolderOptions\Physical Objects\Database Generation\Storage] +Create=Yes +Drop=Yes +Comment=Yes + +[FolderOptions\Physical Objects\Database Generation\User] +Create=Yes +Grant=Yes +Drop=Yes +Comment=Yes +Privilege=No + +[FolderOptions\Physical Objects\Database Generation\User\Create] +Physical Options=No + +[FolderOptions\Physical Objects\Database Generation\Group] +Create=Yes +Drop=Yes +Comment=Yes +Privilege=No + +[FolderOptions\Physical Objects\Database Generation\Role] +Create=Yes +Drop=Yes +Privilege=No + +[FolderOptions\Physical Objects\Database Generation\UserDefinedDataType] +Create=Yes +Comment=Yes +Drop=Yes + +[FolderOptions\Physical Objects\Database Generation\UserDefinedDataType\Create] +Default value=Yes +Check=Yes + +[FolderOptions\Physical Objects\Database Generation\AbstractDataType] +Create=Yes +Header=Yes +Footer=Yes +Drop=Yes +Comment=Yes +Install JAVA class=Yes +Remove JAVA class=Yes +Permission=No + +[FolderOptions\Physical Objects\Database Generation\Rule] +Create=Yes +Drop=Yes +Comment=Yes + +[FolderOptions\Physical Objects\Database Generation\Default] +Create=Yes +Comment=Yes +Drop=Yes + +[FolderOptions\Physical Objects\Database Generation\Sequence] +Create=Yes +Drop=Yes +Comment=Yes +Permission=No + +[FolderOptions\Physical Objects\Database Generation\Table&&Column] + +[FolderOptions\Physical Objects\Database Generation\Table&&Column\Table] +Create=Yes +Drop=Yes +Comment=Yes +Permission=No + +[FolderOptions\Physical Objects\Database Generation\Table&&Column\Table\Create] +Check=Yes +Physical Options=Yes +Header=Yes +Footer=Yes + +[FolderOptions\Physical Objects\Database Generation\Table&&Column\Table\Create\Check] +Constraint declaration=No + +[FolderOptions\Physical Objects\Database Generation\Table&&Column\Column] +User datatype=No +Default value=Yes +Check=Yes +Physical Options=Yes +Comment=Yes + +[FolderOptions\Physical Objects\Database Generation\Table&&Column\Column\Check] +Constraint declaration=No + +[FolderOptions\Physical Objects\Database Generation\Table&&Column\Key] + +[FolderOptions\Physical Objects\Database Generation\Table&&Column\Key\Primary key] +Create=Yes +Drop=Yes +Comment=Yes + +[FolderOptions\Physical Objects\Database Generation\Table&&Column\Key\Primary key\Create] +Constraint declaration=No +Physical Options=Yes + +[FolderOptions\Physical Objects\Database Generation\Table&&Column\Key\Alternate key] +Create=Yes +Drop=Yes +Comment=Yes + +[FolderOptions\Physical Objects\Database Generation\Table&&Column\Key\Alternate key\Create] +Constraint declaration=No +Physical Options=Yes + +[FolderOptions\Physical Objects\Database Generation\Table&&Column\Foreign key] +Create=Yes +Drop=Yes +Comment=Yes + +[FolderOptions\Physical Objects\Database Generation\Table&&Column\Foreign key\Create] +Constraint declaration=Yes + +[FolderOptions\Physical Objects\Database Generation\Table&&Column\Index] +Create=Yes +Drop=Yes +Comment=Yes + +[FolderOptions\Physical Objects\Database Generation\Table&&Column\Index\Create] +Constraint declaration=Yes +Physical Options=Yes + +[FolderOptions\Physical Objects\Database Generation\Table&&Column\Index\Filter] +Primary key=No +Foreign key=No +Alternate key=No +Cluster=Yes +Other=Yes + +[FolderOptions\Physical Objects\Database Generation\Table&&Column\Trigger] +Create=Yes +Drop=Yes +Comment=Yes + +[FolderOptions\Physical Objects\Database Generation\Table&&Column\Trigger\Filter] +For insert=Yes +For update=Yes +For delete=Yes +For other=Yes + +[FolderOptions\Physical Objects\Database Generation\View] +Create=Yes +Drop=Yes +Comment=Yes +Permission=No + +[FolderOptions\Physical Objects\Database Generation\View\Create] +Force Column list=No +Physical Options=Yes +Header=Yes +Footer=Yes + +[FolderOptions\Physical Objects\Database Generation\View\ViewColumn] +Comment=Yes + +[FolderOptions\Physical Objects\Database Generation\View\ViewIndex] +Create=Yes +Drop=Yes +Comment=Yes + +[FolderOptions\Physical Objects\Database Generation\View\ViewIndex\Create] +Physical Options=Yes + +[FolderOptions\Physical Objects\Database Generation\View\ViewIndex\Filter] +Cluster=Yes +Other=Yes + +[FolderOptions\Physical Objects\Database Generation\View\Trigger] +Create=Yes +Drop=Yes +Comment=Yes + +[FolderOptions\Physical Objects\Database Generation\View\Trigger\Filter] +For insert=Yes +For update=Yes +For delete=Yes +For other=Yes + +[FolderOptions\Physical Objects\Database Generation\DBMSTrigger] +Create=Yes +Drop=Yes +Comment=Yes + +[FolderOptions\Physical Objects\Database Generation\Synonym] +Create=Yes +Drop=Yes + +[FolderOptions\Physical Objects\Database Generation\Synonym\Filter] +Table=Yes +View=Yes +Proc=Yes +Synonym=Yes +Database Package=Yes +Sequence=Yes + +[FolderOptions\Physical Objects\Database Generation\JoinIndex] +Create=Yes +Drop=Yes +Comment=Yes + +[FolderOptions\Physical Objects\Database Generation\JoinIndex\Create] +Physical Options=Yes +Header=Yes +Footer=Yes + +[FolderOptions\Physical Objects\Database Generation\Procedure] +Create=Yes +Drop=Yes +Comment=Yes +Permission=No + +[FolderOptions\Physical Objects\Database Generation\Procedure\Create] +Header=Yes +Footer=Yes + +[FolderOptions\Physical Objects\Database Generation\DatabasePackage] +Create=Yes +Drop=Yes +Permission=No + +[FolderOptions\Physical Objects\Database Generation\WebService] +Create=Yes +Drop=Yes +Comment=Yes + +[FolderOptions\Physical Objects\Database Generation\Dimension] +Create=Yes +Drop=Yes + +[FolderOptions\Physical Objects\Database Generation\Synchronization] +GenBackupTabl=1 +GenKeepBackTabl=1 +GenTmpTablDrop=No +GenKeepTablOpts=No + +[FolderOptions\Physical Objects\Test Data] +GenDataPathName= +GenDataSinglefile=Yes +GenDataScriptName=testdata +GenDataScriptName0= +GenDataScriptName1= +GenDataScriptName2= +GenDataScriptName3= +GenDataScriptName4= +GenDataScriptName5= +GenDataScriptName6= +GenDataScriptName7= +GenDataScriptName8= +GenDataScriptName9= +GenDataOdbc=0 +GenDataDelOld=No +GenDataTitle=No +GenDataDefNumRows=20 +GenDataCommit=0 +GenDataPacket=0 +GenDataOwner=No +GenDataProfNumb= +GenDataProfChar= +GenDataProfDate= +GenDataCSVSeparator=, +GenDataFileFormat=CSV +GenDataUseWizard=No + +[FolderOptions\Pdm] +IndxIQName=%COLUMN%_%INDEXTYPE% +IndxPK=Yes +IndxFK=Yes +IndxAK=Yes +IndxPKName=%TABLE%_PK +IndxFKName=%REFR%_FK +IndxAKName=%AKEY%_AK +IndxPreserve=No +IndxThreshold=0 +IndxStats=No +RefrPreserve=No +JidxPreserve=No +RbldMultiFact=Yes +RbldMultiDim=Yes +RbldMultiJidx=Yes +CubePreserve=No +TablStProcPreserve=No +ProcDepPreserve=Yes +TrgrDepPreserve=Yes +CubeScriptPath= +CubeScriptCase=0 +CubeScriptEncoding=ANSI +CubeScriptNacct=No +CubeScriptHeader=No +CubeScriptExt=csv +CubeScriptExt0=txt +CubeScriptExt1= +CubeScriptExt2= +CubeScriptSep=, +CubeScriptDeli=" +DfltDomnName=D_%.U:VALUE% +DfltColnName=D_%.U:VALUE% +DfltReuse=Yes +DfltDrop=Yes +[ModelOptions] + +[ModelOptions\Physical Objects] +CaseSensitive=No +DisplayName=Yes +EnableTrans=No +EnableRequirements=No +DefaultDttp= +IgnoreOwner=No +RebuildTrigger=Yes +RefrUnique=No +RefrAutoMigrate=Yes +RefrMigrateReuse=Yes +RefrMigrateDomain=Yes +RefrMigrateCheck=Yes +RefrMigrateRule=Yes +RefrMigrateExtd=No +RefrMigrDefaultLink=No +RefrDfltImpl=D +RefrPrgtColn=No +RefrMigrateToEnd=No +RebuildTriggerDep=No +ColnFKName=%.3:PARENT%_%COLUMN% +ColnFKNameUse=No +DomnCopyDttp=Yes +DomnCopyChck=No +DomnCopyRule=No +DomnCopyMand=No +DomnCopyExtd=No +DomnCopyProf=No +Notation=0 +DomnDefaultMandatory=No +ColnDefaultMandatory=No +TablDefaultOwner= +ViewDefaultOwner= +TrgrDefaultOwnerTabl= +TrgrDefaultOwnerView= +IdxDefaultOwnerTabl= +IdxDefaultOwnerView= +JdxDefaultOwner= +DBPackDefaultOwner= +SeqDefaultOwner= +ProcDefaultOwner= +DBMSTrgrDefaultOwner= +Currency=USD +RefrDeleteConstraint=1 +RefrUpdateConstraint=1 +RefrParentMandatory=No +RefrParentChangeAllow=Yes +RefrCheckOnCommit=No + +[ModelOptions\Physical Objects\NamingOptionsTemplates] + +[ModelOptions\Physical Objects\ClssNamingOptions] + +[ModelOptions\Physical Objects\ClssNamingOptions\PDMPCKG] + +[ModelOptions\Physical Objects\ClssNamingOptions\PDMPCKG\Name] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\PDMPCKG\Code] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\PDMDOMN] + +[ModelOptions\Physical Objects\ClssNamingOptions\PDMDOMN\Name] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\PDMDOMN\Code] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\TABL] + +[ModelOptions\Physical Objects\ClssNamingOptions\TABL\Name] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\TABL\Code] +Template= +MaxLen=64 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\COLN] + +[ModelOptions\Physical Objects\ClssNamingOptions\COLN\Name] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\COLN\Code] +Template= +MaxLen=64 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\INDX] + +[ModelOptions\Physical Objects\ClssNamingOptions\INDX\Name] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\INDX\Code] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\REFR] + +[ModelOptions\Physical Objects\ClssNamingOptions\REFR\Name] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\REFR\Code] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\VREF] + +[ModelOptions\Physical Objects\ClssNamingOptions\VREF\Name] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\VREF\Code] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\VIEW] + +[ModelOptions\Physical Objects\ClssNamingOptions\VIEW\Name] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\VIEW\Code] +Template= +MaxLen=64 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\VIEWC] + +[ModelOptions\Physical Objects\ClssNamingOptions\VIEWC\Name] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\VIEWC\Code] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\WEBSERV] + +[ModelOptions\Physical Objects\ClssNamingOptions\WEBSERV\Name] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\WEBSERV\Code] +Template= +MaxLen=254 +Case=M +ValidChar='a'-'z','A'-'Z','0'-'9',"/-_.!~*'()" +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\WEBOP] + +[ModelOptions\Physical Objects\ClssNamingOptions\WEBOP\Name] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\WEBOP\Code] +Template= +MaxLen=254 +Case=M +ValidChar='a'-'z','A'-'Z','0'-'9',"/-_.!~*'()" +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\WPARAM] + +[ModelOptions\Physical Objects\ClssNamingOptions\WPARAM\Name] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\WPARAM\Code] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\FACT] + +[ModelOptions\Physical Objects\ClssNamingOptions\FACT\Name] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\FACT\Code] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\DIMN] + +[ModelOptions\Physical Objects\ClssNamingOptions\DIMN\Name] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\DIMN\Code] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\CUBE] + +[ModelOptions\Physical Objects\ClssNamingOptions\CUBE\Name] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\CUBE\Code] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\MEAS] + +[ModelOptions\Physical Objects\ClssNamingOptions\MEAS\Name] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\MEAS\Code] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\DATTR] + +[ModelOptions\Physical Objects\ClssNamingOptions\DATTR\Name] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\DATTR\Code] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\FILO] + +[ModelOptions\Physical Objects\ClssNamingOptions\FILO\Name] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\FILO\Code] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\FRMEOBJ] + +[ModelOptions\Physical Objects\ClssNamingOptions\FRMEOBJ\Name] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\FRMEOBJ\Code] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\FRMELNK] + +[ModelOptions\Physical Objects\ClssNamingOptions\FRMELNK\Name] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\FRMELNK\Code] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\DefaultClass] + +[ModelOptions\Physical Objects\ClssNamingOptions\DefaultClass\Name] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Physical Objects\ClssNamingOptions\DefaultClass\Code] +Template= +MaxLen=254 +Case=M +ValidChar= +InvldChar= +AllValid=Yes +NoAccent=No +DefaultChar= +Script= +ConvTable= +ConvTablePath=%_HOME%\Resource Files\Conversion Tables + +[ModelOptions\Connection] + +[ModelOptions\Pdm] + +[ModelOptions\Generate] + +[ModelOptions\Generate\Pdm] +RRMapping=No + +[ModelOptions\Generate\Cdm] +CheckModel=Yes +SaveLinks=Yes +NameToCode=No +Notation=2 + +[ModelOptions\Generate\Oom] +CheckModel=Yes +SaveLinks=Yes +ORMapping=No +NameToCode=Yes +ClassPrefix= + +[ModelOptions\Generate\Xsm] +CheckModel=Yes +SaveLinks=Yes +ORMapping=No +NameToCode=No + +[ModelOptions\Generate\Ldm] +CheckModel=Yes +SaveLinks=Yes +NameToCode=No + +[ModelOptions\Default Opts] + +[ModelOptions\Default Opts\TABL] +PhysOpts= + +[ModelOptions\Default Opts\COLN] +PhysOpts= + +[ModelOptions\Default Opts\INDX] +PhysOpts= + +[ModelOptions\Default Opts\AKEY] +PhysOpts= + +[ModelOptions\Default Opts\PKEY] +PhysOpts= + +[ModelOptions\Default Opts\STOR] +PhysOpts= + +[ModelOptions\Default Opts\TSPC] +PhysOpts= + +[ModelOptions\Default Opts\SQNC] +PhysOpts= + +[ModelOptions\Default Opts\DTBS] +PhysOpts= + +[ModelOptions\Default Opts\USER] +PhysOpts= + +[ModelOptions\Default Opts\JIDX] +PhysOpts= + + +AFAD9ECF-F417-4FCE-BEA4-884857D4C1A9 +MySQL 5.0 +MYSQL50 +1524449337 +Administrator +1524449337 +Administrator + +F4F16ECD-F2F1-4006-AF6F-638D5C65F35E +4BA9F647-DAB1-11D1-9944-006097355D9B + + + + +B6C2C4A4-6A8A-41F3-909D-C7B514E1EAE2 +PhysicalDiagram_1 +PhysicalDiagram_1 +1524449325 +Administrator +1538297386 +admin +[DisplayPreferences] + +[DisplayPreferences\PDM] + +[DisplayPreferences\General] +Adjust to text=Yes +Snap Grid=No +Constrain Labels=Yes +Display Grid=No +Show Page Delimiter=Yes +Grid size=0 +Graphic unit=2 +Window color=255, 255, 255 +Background image= +Background mode=8 +Watermark image= +Watermark mode=8 +Show watermark on screen=No +Gradient mode=0 +Gradient end color=255, 255, 255 +Show Swimlane=No +SwimlaneVert=Yes +TreeVert=No +CompDark=0 + +[DisplayPreferences\Object] +Mode=0 +Trunc Length=80 +Word Length=80 +Word Text=!""#$%&'()*+,-./:;<=>?@[\]^_`{|}~ +Shortcut IntIcon=Yes +Shortcut IntLoct=Yes +Shortcut IntFullPath=No +Shortcut IntLastPackage=Yes +Shortcut ExtIcon=Yes +Shortcut ExtLoct=No +Shortcut ExtFullPath=No +Shortcut ExtLastPackage=Yes +Shortcut ExtIncludeModl=Yes +EObjShowStrn=Yes +ExtendedObject.Comment=No +ExtendedObject.IconPicture=No +ExtendedObject_SymbolLayout=<Form>[CRLF] <StandardAttribute Name="Stereotype" Attribute="Stereotype" Prefix="&lt;&lt;" Suffix="&gt;&gt;" Alignment="CNTR" Caption="" Mandatory="No" />[CRLF] <StandardAttribute Name="Object Name" Attribute="DisplayName" Prefix="" Suffix="" Alignment="CNTR" Caption="" Mandatory="Yes" />[CRLF] <Separator Name="Separator" />[CRLF] <StandardAttribute Name="Comment" Attribute="Comment" Prefix="" Suffix="" Alignment="LEFT" Caption="" Mandatory="No" />[CRLF] <StandardAttribute Name="Icon" Attribute="IconPicture" Prefix="" Suffix="" Alignment="CNTR" Caption="" Mandatory="Yes" />[CRLF]</Form> +ELnkShowStrn=Yes +ELnkShowName=Yes +ExtendedLink_SymbolLayout=<Form>[CRLF] <Form Name="Center" >[CRLF] <StandardAttribute Name="Stereotype" Attribute="Stereotype" Prefix="&lt;&lt;" Suffix="&gt;&gt;" Caption="" Mandatory="No" />[CRLF] <StandardAttribute Name="Name" Attribute="DisplayName" Prefix="" Suffix="" Caption="" Mandatory="No" />[CRLF] </Form>[CRLF] <Form Name="Source" >[CRLF] </Form>[CRLF] <Form Name="Destination" >[CRLF] </Form>[CRLF]</Form> +FileObject.Stereotype=No +FileObject.DisplayName=Yes +FileObject.LocationOrName=No +FileObject.IconPicture=No +FileObject.IconMode=Yes +FileObject_SymbolLayout=<Form>[CRLF] <StandardAttribute Name="Stereotype" Attribute="Stereotype" Prefix="&lt;&lt;" Suffix="&gt;&gt;" Alignment="CNTR" Caption="" Mandatory="No" />[CRLF] <ExclusiveChoice Name="Exclusive Choice" Mandatory="Yes" Display="HorizontalRadios" >[CRLF] <StandardAttribute Name="Name" Attribute="DisplayName" Prefix="" Suffix="" Alignment="CNTR" Caption="" Mandatory="No" />[CRLF] <StandardAttribute Name="Location" Attribute="LocationOrName" Prefix="" Suffix="" Alignment="CNTR" Caption="" Mandatory="No" />[CRLF] </ExclusiveChoice>[CRLF] <StandardAttribute Name="Icon" Attribute="IconPicture" Prefix="" Suffix="" Alignment="CNTR" Caption="" Mandatory="Yes" />[CRLF]</Form> +PckgShowStrn=Yes +Package.Comment=No +Package.IconPicture=No +Package_SymbolLayout= +Display Model Version=Yes +Table.Stereotype=Yes +Table.DisplayName=Yes +Table.OwnerDisplayName=No +Table.Columns=Yes +Table.Columns._Filter=""PDMCOLNALL +Table.Columns._Columns=Stereotype DataType KeyIndicator +Table.Columns._Limit=-5 +Table.Keys=No +Table.Keys._Columns=Stereotype Indicator +Table.Indexes=No +Table.Indexes._Columns=Stereotype +Table.Triggers=No +Table.Triggers._Columns=Stereotype +Table.Comment=No +Table.IconPicture=No +Table_SymbolLayout=<Form>[CRLF] <StandardAttribute Name="Stereotype" Attribute="Stereotype" Prefix="&lt;&lt;" Suffix="&gt;&gt;" Alignment="CNTR" Caption="" Mandatory="No" />[CRLF] <ExclusiveChoice Name="Exclusive Choice" Mandatory="Yes" Display="HorizontalRadios" >[CRLF] <StandardAttribute Name="Name" Attribute="DisplayName" Prefix="" Suffix="" Alignment="CNTR" Caption="" Mandatory="No" />[CRLF] <StandardAttribute Name="Owner and Name" Attribute="OwnerDisplayName" Prefix="" Suffix="" Alignment="CNTR" Caption="" Mandatory="No" />[CRLF] </ExclusiveChoice>[CRLF] <Separator Name="Separator" />[CRLF] <StandardCollection Name="Columns" Collection="Columns" Columns="Stereotype No\r\nDisplayName Yes\r\nDataType No\r\nSymbolDataType No &quot;Domain or Data type&quot;\r\nDomain No\r\nKeyIndicator No\r\nIndexIndicator No\r\nNullStatus No" Filters="&quot;All Columns&quot; PDMCOLNALL &quot;&quot;\r\n&quot;PK Columns&quot; PDMCOLNPK &quot;PRIM \&quot;TRUE\&quot; TRUE&quot;\r\n&quot;Key Columns&quot; PDMCOLNKEY &quot;KEYS \&quot;TRUE\&quot; TRUE&quot;" HasLimit="Yes" Caption="" Mandatory="No" />[CRLF] <StandardCollection Name="Keys" Collection="Keys" Columns="Stereotype No\r\nDisplayName Yes\r\nIndicator No" HasLimit="No" Caption="" Mandatory="No" />[CRLF] <StandardCollection Name="Indexes" Collection="Indexes" Columns="Stereotype No\r\nDisplayName Yes\r\nIndicator No" HasLimit="No" Caption="" Mandatory="No" />[CRLF] <StandardCollection Name="Triggers" Collection="Triggers" Columns="Stereotype No\r\nDisplayName Yes" HasLimit="No" Caption="" Mandatory="No" />[CRLF] <StandardAttribute Name="Comment" Attribute="Comment" Prefix="" Suffix="" Alignment="LEFT" Caption="" Mandatory="No" />[CRLF] <StandardAttribute Name="Icon" Attribute="IconPicture" Prefix="" Suffix="" Alignment="CNTR" Caption="" Mandatory="Yes" />[CRLF]</Form> +View.Stereotype=Yes +View.DisplayName=Yes +View.OwnerDisplayName=No +View.Columns=Yes +View.Columns._Columns=DisplayName +View.Columns._Limit=-5 +View.TemporaryVTables=Yes +View.Indexes=No +View.Comment=No +View.IconPicture=No +View_SymbolLayout=<Form>[CRLF] <StandardAttribute Name="Stereotype" Attribute="Stereotype" Prefix="&lt;&lt;" Suffix="&gt;&gt;" Alignment="CNTR" Caption="" Mandatory="No" />[CRLF] <ExclusiveChoice Name="Exclusive Choice" Mandatory="Yes" Display="HorizontalRadios" >[CRLF] <StandardAttribute Name="Name" Attribute="DisplayName" Prefix="" Suffix="" Alignment="CNTR" Caption="" Mandatory="No" />[CRLF] <StandardAttribute Name="Owner and Name" Attribute="OwnerDisplayName" Prefix="" Suffix="" Alignment="CNTR" Caption="" Mandatory="No" />[CRLF] </ExclusiveChoice>[CRLF] <Separator Name="Separator" />[CRLF] <StandardCollection Name="Columns" Collection="Columns" Columns="DisplayName No\r\nExpression No\r\nDataType No\r\nSymbolDataType No &quot;Domain or Data type&quot;\r\nIndexIndicator No" HasLimit="Yes" Caption="" Mandatory="No" />[CRLF] <StandardCollection Name="Tables" Collection="TemporaryVTables" Columns="Name Yes" HasLimit="No" Caption="" Mandatory="No" />[CRLF] <StandardCollection Name="Indexes" Collection="Indexes" Columns="DisplayName Yes" HasLimit="No" Caption="" Mandatory="No" />[CRLF] <StandardAttribute Name="Comment" Attribute="Comment" Prefix="" Suffix="" Alignment="LEFT" Caption="" Mandatory="No" />[CRLF] <StandardAttribute Name="Icon" Attribute="IconPicture" Prefix="" Suffix="" Alignment="CNTR" Caption="" Mandatory="Yes" />[CRLF]</Form> +Procedure.Stereotype=No +Procedure.DisplayName=Yes +Procedure.OwnerDisplayName=No +Procedure.Comment=No +Procedure.IconPicture=No +Procedure_SymbolLayout=<Form>[CRLF] <StandardAttribute Name="Stereotype" Attribute="Stereotype" Prefix="&lt;&lt;" Suffix="&gt;&gt;" Alignment="CNTR" Caption="" Mandatory="No" />[CRLF] <ExclusiveChoice Name="Exclusive Choice" Mandatory="Yes" Display="HorizontalRadios" >[CRLF] <StandardAttribute Name="Name" Attribute="DisplayName" Prefix="" Suffix="" Alignment="CNTR" Caption="" Mandatory="No" />[CRLF] <StandardAttribute Name="Owner and Name" Attribute="OwnerDisplayName" Prefix="" Suffix="" Alignment="CNTR" Caption="" Mandatory="No" />[CRLF] </ExclusiveChoice>[CRLF] <Separator Name="Separator" />[CRLF] <StandardAttribute Name="Comment" Attribute="Comment" Prefix="" Suffix="" Alignment="LEFT" Caption="" Mandatory="No" />[CRLF] <StandardAttribute Name="Icon" Attribute="IconPicture" Prefix="" Suffix="" Alignment="CNTR" Caption="" Mandatory="Yes" />[CRLF]</Form> +Reference.Cardinality=No +Reference.ImplementationType=No +Reference.ChildRole=Yes +Reference.Stereotype=Yes +Reference.DisplayName=No +Reference.ForeignKeyConstraintName=Yes +Reference.JoinExpression=No +Reference.Integrity=No +Reference.ParentRole=Yes +Reference_SymbolLayout=<Form>[CRLF] <Form Name="Source" >[CRLF] <StandardAttribute Name="Cardinality" Attribute="Cardinality" Prefix="" Suffix="" Caption="" Mandatory="No" />[CRLF] <StandardAttribute Name="Implementation" Attribute="ImplementationType" Prefix="" Suffix="" Caption="" Mandatory="No" />[CRLF] <StandardAttribute Name="Child Role" Attribute="ChildRole" Prefix="" Suffix="" Caption="" Mandatory="No" />[CRLF] </Form>[CRLF] <Form Name="Center" >[CRLF] <StandardAttribute Name="Stereotype" Attribute="Stereotype" Prefix="&lt;&lt;" Suffix="&gt;&gt;" Caption="" Mandatory="No" />[CRLF] <ExclusiveChoice Name="Exclusive Choice" Mandatory="No" Display="HorizontalRadios" >[CRLF] <StandardAttribute Name="Name" Attribute="DisplayName" Prefix="" Suffix="" Caption="" Mandatory="No" />[CRLF] <StandardAttribute Name="Cons&amp;traint Name" Attribute="ForeignKeyConstraintName" Prefix="" Suffix="" Caption="Cons&amp;traint Name" Mandatory="No" />[CRLF] <StandardAttribute Name="Join" Attribute="JoinExpression" Prefix="" Suffix="" Caption="Join" Mandatory="No" />[CRLF] </ExclusiveChoice>[CRLF] <StandardAttribute Name="Referential integrity" Attribute="Integrity" Prefix="" Suffix="" Caption="Referential integrity" Mandatory="No" />[CRLF] </Form>[CRLF] <Form Name="Destination" >[CRLF] <StandardAttribute Name="Parent Role" Attribute="ParentRole" Prefix="" Suffix="" Caption="" Mandatory="No" />[CRLF] </Form>[CRLF]</Form> +ViewReference.ChildRole=Yes +ViewReference.Stereotype=Yes +ViewReference.DisplayName=No +ViewReference.JoinExpression=No +ViewReference.ParentRole=Yes +ViewReference_SymbolLayout=<Form>[CRLF] <Form Name="Source" >[CRLF] <StandardAttribute Name="Child Role" Attribute="ChildRole" Prefix="" Suffix="" Caption="" Mandatory="No" />[CRLF] </Form>[CRLF] <Form Name="Center" >[CRLF] <StandardAttribute Name="Stereotype" Attribute="Stereotype" Prefix="&lt;&lt;" Suffix="&gt;&gt;" Caption="" Mandatory="No" />[CRLF] <ExclusiveChoice Name="Exclusive Choice" Mandatory="No" Display="HorizontalRadios" >[CRLF] <StandardAttribute Name="Name" Attribute="DisplayName" Prefix="" Suffix="" Caption="" Mandatory="No" />[CRLF] <StandardAttribute Name="Join Expression" Attribute="JoinExpression" Prefix="" Suffix="" Caption="" Mandatory="No" />[CRLF] </ExclusiveChoice>[CRLF] </Form>[CRLF] <Form Name="Destination" >[CRLF] <StandardAttribute Name="Parent Role" Attribute="ParentRole" Prefix="" Suffix="" Caption="" Mandatory="No" />[CRLF] </Form>[CRLF]</Form> +File Location=No +PckgStrn=Yes +ColnMode=0 +ColnMax=5 +TablOwnr=No +ColnDttp=Yes +ColnDomn=No +ColnShowDomn=No +ColnKey=Yes +ColnIndx=No +ColnMand=No +ColnStrn=Yes +VColName=Yes +VColExpr=No +VColDttp=No +VColIndx=No +VColCMod=0 +VColCMax=5 +ProcOwnr=No +KeyStrn=Yes +IndxStrn=Yes +TrgrStrn=Yes + +[DisplayPreferences\Symbol] + +[DisplayPreferences\Symbol\FRMEOBJ] +STRNFont=新宋体,8,N +STRNFont color=0, 0, 0 +DISPNAMEFont=新宋体,8,N +DISPNAMEFont color=0, 0, 0 +LABLFont=新宋体,8,N +LABLFont color=0, 0, 0 +AutoAdjustToText=Yes +Keep aspect=No +Keep center=No +Keep size=No +Width=6000 +Height=2000 +Brush color=255 255 255 +Fill Color=Yes +Brush style=6 +Brush bitmap mode=12 +Brush gradient mode=64 +Brush gradient color=192 192 192 +Brush background image= +Custom shape= +Custom text mode=0 +Pen=1 0 255 128 128 +Shadow color=192 192 192 +Shadow=0 + +[DisplayPreferences\Symbol\FRMELNK] +CENTERFont=新宋体,8,N +CENTERFont color=0, 0, 0 +Line style=0 +AutoAdjustToText=Yes +Keep aspect=No +Keep center=No +Keep size=No +Brush color=255 255 255 +Fill Color=Yes +Brush style=1 +Brush bitmap mode=12 +Brush gradient mode=0 +Brush gradient color=118 118 118 +Brush background image= +Custom shape= +Custom text mode=0 +Pen=1 0 128 128 255 +Shadow color=192 192 192 +Shadow=0 + +[DisplayPreferences\Symbol\FILO] +OBJSTRNFont=新宋体,8,N +OBJSTRNFont color=0, 0, 0 +DISPNAMEFont=新宋体,8,N +DISPNAMEFont color=0, 0, 0 +LCNMFont=新宋体,8,N +LCNMFont color=0, 0, 0 +AutoAdjustToText=Yes +Keep aspect=Yes +Keep center=Yes +Keep size=No +Width=2400 +Height=2400 +Brush color=255 255 255 +Fill Color=No +Brush style=1 +Brush bitmap mode=12 +Brush gradient mode=0 +Brush gradient color=118 118 118 +Brush background image= +Custom shape= +Custom text mode=0 +Pen=1 0 0 0 255 +Shadow color=192 192 192 +Shadow=0 + +[DisplayPreferences\Symbol\PDMPCKG] +STRNFont=新宋体,8,N +STRNFont color=0, 0, 0 +DISPNAMEFont=新宋体,8,N +DISPNAMEFont color=0, 0, 0 +LABLFont=新宋体,8,N +LABLFont color=0, 0, 0 +AutoAdjustToText=Yes +Keep aspect=No +Keep center=No +Keep size=No +Width=4800 +Height=3600 +Brush color=255 255 192 +Fill Color=Yes +Brush style=6 +Brush bitmap mode=12 +Brush gradient mode=65 +Brush gradient color=255 255 255 +Brush background image= +Custom shape= +Custom text mode=0 +Pen=1 0 178 178 178 +Shadow color=192 192 192 +Shadow=0 + +[DisplayPreferences\Symbol\TABL] +STRNFont=新宋体,8,N +STRNFont color=0, 0, 0 +DISPNAMEFont=新宋体,8,N +DISPNAMEFont color=0, 0, 0 +OWNRDISPNAMEFont=新宋体,8,N +OWNRDISPNAMEFont color=0, 0, 0 +ColumnsFont=新宋体,8,N +ColumnsFont color=0, 0, 0 +TablePkColumnsFont=新宋体,8,U +TablePkColumnsFont color=0, 0, 0 +TableFkColumnsFont=新宋体,8,N +TableFkColumnsFont color=0, 0, 0 +KeysFont=新宋体,8,N +KeysFont color=0, 0, 0 +IndexesFont=新宋体,8,N +IndexesFont color=0, 0, 0 +TriggersFont=新宋体,8,N +TriggersFont color=0, 0, 0 +LABLFont=新宋体,8,N +LABLFont color=0, 0, 0 +AutoAdjustToText=Yes +Keep aspect=No +Keep center=No +Keep size=No +Width=4800 +Height=4000 +Brush color=178 214 252 +Fill Color=Yes +Brush style=6 +Brush bitmap mode=12 +Brush gradient mode=65 +Brush gradient color=255 255 255 +Brush background image= +Custom shape= +Custom text mode=0 +Pen=1 0 0 128 192 +Shadow color=192 192 192 +Shadow=0 + +[DisplayPreferences\Symbol\VIEW] +STRNFont=新宋体,8,N +STRNFont color=0, 0, 0 +DISPNAMEFont=新宋体,8,N +DISPNAMEFont color=0, 0, 0 +OWNRDISPNAMEFont=新宋体,8,N +OWNRDISPNAMEFont color=0, 0, 0 +ColumnsFont=新宋体,8,N +ColumnsFont color=0, 0, 0 +TablePkColumnsFont=新宋体,8,U +TablePkColumnsFont color=0, 0, 0 +TableFkColumnsFont=新宋体,8,N +TableFkColumnsFont color=0, 0, 0 +TemporaryVTablesFont=新宋体,8,N +TemporaryVTablesFont color=0, 0, 0 +IndexesFont=新宋体,8,N +IndexesFont color=0, 0, 0 +LABLFont=新宋体,8,N +LABLFont color=0, 0, 0 +AutoAdjustToText=Yes +Keep aspect=No +Keep center=No +Keep size=No +Width=4800 +Height=4000 +Brush color=208 208 255 +Fill Color=Yes +Brush style=6 +Brush bitmap mode=12 +Brush gradient mode=65 +Brush gradient color=255 255 255 +Brush background image= +Custom shape= +Custom text mode=0 +Pen=1 0 128 128 192 +Shadow color=192 192 192 +Shadow=0 + +[DisplayPreferences\Symbol\PROC] +STRNFont=新宋体,8,N +STRNFont color=0, 0, 0 +DISPNAMEFont=新宋体,8,N +DISPNAMEFont color=0, 0, 0 +OWNRDISPNAMEFont=新宋体,8,N +OWNRDISPNAMEFont color=0, 0, 0 +LABLFont=新宋体,8,N +LABLFont color=0, 0, 0 +AutoAdjustToText=Yes +Keep aspect=No +Keep center=No +Keep size=No +Width=4000 +Height=1000 +Brush color=255 255 192 +Fill Color=Yes +Brush style=6 +Brush bitmap mode=12 +Brush gradient mode=65 +Brush gradient color=255 255 255 +Brush background image= +Custom shape= +Custom text mode=0 +Pen=1 0 128 108 0 +Shadow color=192 192 192 +Shadow=0 + +[DisplayPreferences\Symbol\REFR] +SOURCEFont=新宋体,8,N +SOURCEFont color=0, 0, 0 +CENTERFont=新宋体,8,N +CENTERFont color=0, 0, 0 +DESTINATIONFont=新宋体,8,N +DESTINATIONFont color=0, 0, 0 +Line style=0 +AutoAdjustToText=Yes +Keep aspect=No +Keep center=No +Keep size=No +Brush color=255 255 255 +Fill Color=Yes +Brush style=1 +Brush bitmap mode=12 +Brush gradient mode=0 +Brush gradient color=118 118 118 +Brush background image= +Custom shape= +Custom text mode=0 +Pen=1 0 0 128 192 +Shadow color=192 192 192 +Shadow=0 + +[DisplayPreferences\Symbol\VREF] +SOURCEFont=新宋体,8,N +SOURCEFont color=0, 0, 0 +CENTERFont=新宋体,8,N +CENTERFont color=0, 0, 0 +DESTINATIONFont=新宋体,8,N +DESTINATIONFont color=0, 0, 0 +Line style=0 +AutoAdjustToText=Yes +Keep aspect=No +Keep center=No +Keep size=No +Brush color=255 255 255 +Fill Color=Yes +Brush style=1 +Brush bitmap mode=12 +Brush gradient mode=0 +Brush gradient color=118 118 118 +Brush background image= +Custom shape= +Custom text mode=0 +Pen=1 0 128 128 192 +Shadow color=192 192 192 +Shadow=0 + +[DisplayPreferences\Symbol\USRDEPD] +OBJXSTRFont=新宋体,8,N +OBJXSTRFont color=0, 0, 0 +Line style=0 +AutoAdjustToText=Yes +Keep aspect=No +Keep center=No +Keep size=No +Brush color=255 255 255 +Fill Color=Yes +Brush style=1 +Brush bitmap mode=12 +Brush gradient mode=0 +Brush gradient color=118 118 118 +Brush background image= +Custom shape= +Custom text mode=0 +Pen=2 0 128 128 255 +Shadow color=192 192 192 +Shadow=0 + +[DisplayPreferences\Symbol\Free Symbol] +Free TextFont=新宋体,8,N +Free TextFont color=0, 0, 0 +Line style=0 +AutoAdjustToText=Yes +Keep aspect=No +Keep center=No +Keep size=No +Brush color=255 255 255 +Fill Color=Yes +Brush style=1 +Brush bitmap mode=12 +Brush gradient mode=0 +Brush gradient color=118 118 118 +Brush background image= +Custom shape= +Custom text mode=0 +Pen=1 0 0 0 255 +Shadow color=192 192 192 +Shadow=0 +(8268, 11693) +((315,354), (433,354)) +1 +15 + + +1524449375 +1538296407 +-1 +((-38123,15297), (-26435,28269)) +12615680 +16570034 +12632256 +STRN 0 新宋体,8,N +DISPNAME 0 新宋体,8,N +OWNRDISPNAME 0 新宋体,8,N +Columns 0 新宋体,8,N +TablePkColumns 0 新宋体,8,U +TableFkColumns 0 新宋体,8,N +Keys 0 新宋体,8,N +Indexes 0 新宋体,8,N +Triggers 0 新宋体,8,N +LABL 0 新宋体,8,N +6 +65 +16777215 + + + + + +1524449375 +1524449886 +-1 +((-23935,12010), (-11861,28282)) +12615680 +16570034 +12632256 +STRN 0 新宋体,8,N +DISPNAME 0 新宋体,8,N +OWNRDISPNAME 0 新宋体,8,N +Columns 0 新宋体,8,N +TablePkColumns 0 新宋体,8,U +TableFkColumns 0 新宋体,8,N +Keys 0 新宋体,8,N +Indexes 0 新宋体,8,N +Triggers 0 新宋体,8,N +LABL 0 新宋体,8,N +6 +65 +16777215 + + + + + +1524449375 +1538296409 +-1 +((-9361,18172), (2713,27845)) +12615680 +16570034 +12632256 +STRN 0 新宋体,8,N +DISPNAME 0 新宋体,8,N +OWNRDISPNAME 0 新宋体,8,N +Columns 0 新宋体,8,N +TablePkColumns 0 新宋体,8,U +TableFkColumns 0 新宋体,8,N +Keys 0 新宋体,8,N +Indexes 0 新宋体,8,N +Triggers 0 新宋体,8,N +LABL 0 新宋体,8,N +6 +65 +16777215 + + + + + +1524449375 +1524449886 +-1 +((5214,16547), (17288,27869)) +12615680 +16570034 +12632256 +STRN 0 新宋体,8,N +DISPNAME 0 新宋体,8,N +OWNRDISPNAME 0 新宋体,8,N +Columns 0 新宋体,8,N +TablePkColumns 0 新宋体,8,U +TableFkColumns 0 新宋体,8,N +Keys 0 新宋体,8,N +Indexes 0 新宋体,8,N +Triggers 0 新宋体,8,N +LABL 0 新宋体,8,N +6 +65 +16777215 + + + + + +1524449375 +1538296412 +-1 +((19788,14872), (31862,27845)) +12615680 +16570034 +12632256 +STRN 0 新宋体,8,N +DISPNAME 0 新宋体,8,N +OWNRDISPNAME 0 新宋体,8,N +Columns 0 新宋体,8,N +TablePkColumns 0 新宋体,8,U +TableFkColumns 0 新宋体,8,N +Keys 0 新宋体,8,N +Indexes 0 新宋体,8,N +Triggers 0 新宋体,8,N +LABL 0 新宋体,8,N +6 +65 +16777215 + + + + + +1524449375 +1538296204 +-1 +((-37598,8498), (-29000,12497)) +12615680 +16570034 +12632256 +STRN 0 新宋体,8,N +DISPNAME 0 新宋体,8,N +OWNRDISPNAME 0 新宋体,8,N +Columns 0 新宋体,8,N +TablePkColumns 0 新宋体,8,U +TableFkColumns 0 新宋体,8,N +Keys 0 新宋体,8,N +Indexes 0 新宋体,8,N +Triggers 0 新宋体,8,N +LABL 0 新宋体,8,N +6 +65 +16777215 + + + + + +1524449375 +1538296205 +-1 +((-37674,3548), (-29076,7547)) +12615680 +16570034 +12632256 +STRN 0 新宋体,8,N +DISPNAME 0 新宋体,8,N +OWNRDISPNAME 0 新宋体,8,N +Columns 0 新宋体,8,N +TablePkColumns 0 新宋体,8,U +TableFkColumns 0 新宋体,8,N +Keys 0 新宋体,8,N +Indexes 0 新宋体,8,N +Triggers 0 新宋体,8,N +LABL 0 新宋体,8,N +6 +65 +16777215 + + + + + +1524449375 +1538296308 +-1 +((-37528,-6452), (-28929,-2453)) +12615680 +16570034 +12632256 +STRN 0 新宋体,8,N +DISPNAME 0 新宋体,8,N +OWNRDISPNAME 0 新宋体,8,N +Columns 0 新宋体,8,N +TablePkColumns 0 新宋体,8,U +TableFkColumns 0 新宋体,8,N +Keys 0 新宋体,8,N +Indexes 0 新宋体,8,N +Triggers 0 新宋体,8,N +LABL 0 新宋体,8,N +6 +65 +16777215 + + + + + +1524449375 +1538296401 +-1 +((-24280,-1775), (-11048,10373)) +12615680 +16570034 +12632256 +STRN 0 新宋体,8,N +DISPNAME 0 新宋体,8,N +OWNRDISPNAME 0 新宋体,8,N +Columns 0 新宋体,8,N +TablePkColumns 0 新宋体,8,U +TableFkColumns 0 新宋体,8,N +Keys 0 新宋体,8,N +Indexes 0 新宋体,8,N +Triggers 0 新宋体,8,N +LABL 0 新宋体,8,N +6 +65 +16777215 + + + + + +1524449375 +1538296599 +-1 +((-9182,625), (2892,10298)) +12615680 +16570034 +12632256 +STRN 0 新宋体,8,N +DISPNAME 0 新宋体,8,N +OWNRDISPNAME 0 新宋体,8,N +Columns 0 新宋体,8,N +TablePkColumns 0 新宋体,8,U +TableFkColumns 0 新宋体,8,N +Keys 0 新宋体,8,N +Indexes 0 新宋体,8,N +Triggers 0 新宋体,8,N +LABL 0 新宋体,8,N +6 +65 +16777215 + + + + + +1524449375 +1538296612 +-1 +((5017,-2538), (17091,10434)) +12615680 +16570034 +12632256 +STRN 0 新宋体,8,N +DISPNAME 0 新宋体,8,N +OWNRDISPNAME 0 新宋体,8,N +Columns 0 新宋体,8,N +TablePkColumns 0 新宋体,8,U +TableFkColumns 0 新宋体,8,N +Keys 0 新宋体,8,N +Indexes 0 新宋体,8,N +Triggers 0 新宋体,8,N +LABL 0 新宋体,8,N +6 +65 +16777215 + + + + + +1524449375 +1538297772 +-1 +((-39520,-17440), (-26288,-8592)) +12615680 +16570034 +12632256 +STRN 0 新宋体,8,N +DISPNAME 0 新宋体,8,N +OWNRDISPNAME 0 新宋体,8,N +Columns 0 新宋体,8,N +TablePkColumns 0 新宋体,8,U +TableFkColumns 0 新宋体,8,N +Keys 0 新宋体,8,N +Indexes 0 新宋体,8,N +Triggers 0 新宋体,8,N +LABL 0 新宋体,8,N +6 +65 +16777215 + + + + + +1524449375 +1538297770 +-1 +((-24744,-19106), (-10738,-8608)) +12615680 +16570034 +12632256 +STRN 0 新宋体,8,N +DISPNAME 0 新宋体,8,N +OWNRDISPNAME 0 新宋体,8,N +Columns 0 新宋体,8,N +TablePkColumns 0 新宋体,8,U +TableFkColumns 0 新宋体,8,N +Keys 0 新宋体,8,N +Indexes 0 新宋体,8,N +Triggers 0 新宋体,8,N +LABL 0 新宋体,8,N +6 +65 +16777215 + + + + + +1524449375 +1538297380 +-1 +((-9749,-20696), (3870,-8548)) +12615680 +16570034 +12632256 +STRN 0 新宋体,8,N +DISPNAME 0 新宋体,8,N +OWNRDISPNAME 0 新宋体,8,N +Columns 0 新宋体,8,N +TablePkColumns 0 新宋体,8,U +TableFkColumns 0 新宋体,8,N +Keys 0 新宋体,8,N +Indexes 0 新宋体,8,N +Triggers 0 新宋体,8,N +LABL 0 新宋体,8,N +6 +65 +16777215 + + + + + +1524449375 +1538297383 +-1 +((5261,-17623), (18494,-8774)) +12615680 +16570034 +12632256 +STRN 0 新宋体,8,N +DISPNAME 0 新宋体,8,N +OWNRDISPNAME 0 新宋体,8,N +Columns 0 新宋体,8,N +TablePkColumns 0 新宋体,8,U +TableFkColumns 0 新宋体,8,N +Keys 0 新宋体,8,N +Indexes 0 新宋体,8,N +Triggers 0 新宋体,8,N +LABL 0 新宋体,8,N +6 +65 +16777215 + + + + + +1538296083 +1538296211 +-1 +((-37675,-1349), (-29076,2650)) +12615680 +16570034 +12632256 +STRN 0 新宋体,8,N +DISPNAME 0 新宋体,8,N +OWNRDISPNAME 0 新宋体,8,N +Columns 0 新宋体,8,N +TablePkColumns 0 新宋体,8,U +TableFkColumns 0 新宋体,8,N +Keys 0 新宋体,8,N +Indexes 0 新宋体,8,N +Triggers 0 新宋体,8,N +LABL 0 新宋体,8,N +6 +65 +16777215 + + + + + +1538296587 +1538296608 +-1 +((19570,-987), (32030,8687)) +12615680 +16570034 +12632256 +STRN 0 新宋体,8,N +DISPNAME 0 新宋体,8,N +OWNRDISPNAME 0 新宋体,8,N +Columns 0 新宋体,8,N +TablePkColumns 0 新宋体,8,U +TableFkColumns 0 新宋体,8,N +Keys 0 新宋体,8,N +Indexes 0 新宋体,8,N +Triggers 0 新宋体,8,N +LABL 0 新宋体,8,N +6 +65 +16777215 + + + + + +config_id +1538296632 +1538297253 +((-13950,-17175), (73200,19575)) +4130 +1 +0 +7 +16777215 +16777215 +新宋体,8,N + + +1538297386 +1538297498 +-1 +((19859,-18262), (33092,-8589)) +12615680 +16570034 +12632256 +STRN 0 新宋体,8,N +DISPNAME 0 新宋体,8,N +OWNRDISPNAME 0 新宋体,8,N +Columns 0 新宋体,8,N +TablePkColumns 0 新宋体,8,U +TableFkColumns 0 新宋体,8,N +Keys 0 新宋体,8,N +Indexes 0 新宋体,8,N +Triggers 0 新宋体,8,N +LABL 0 新宋体,8,N +6 +65 +16777215 + + + + + + + + + + + + +BB11FFF9-9DBB-4648-87AA-9A50E1214549 +sys_dept +sys_dept +1524449375 +Administrator +1538297518 +admin +部门表 + + + +00C66282-419A-4915-8509-DFFFE6352DE8 +dept_id +dept_id +1524449375 +Administrator +1524449375 +Administrator +部门id +int(11) +11 +1 +1 + + +5B6FB0B1-5B1E-4E86-AF2A-72C49EBB315E +parent_id +parent_id +1524449375 +Administrator +1524449375 +Administrator +父部门id +0 +int(11) +11 + + +065E33A5-6AB5-44F1-8FEC-A72311EECD66 +ancestors +ancestors +1538295690 +admin +1538295792 +admin +varchar(50) +50 + + +EBB59EC8-AFD4-40E3-B811-DD5040728D91 +dept_name +dept_name +1524449375 +Administrator +1524449375 +Administrator +部门名称 +'' +varchar(30) +30 + + +2F26C025-82B0-4AC5-AEE0-32BA07B7B529 +order_num +order_num +1524449375 +Administrator +1524449375 +Administrator +显示顺序 +0 +int(4) +4 + + +CA504E09-528C-482E-A0C7-F86C559AA3A6 +leader +leader +1524449375 +Administrator +1524449375 +Administrator +负责人 +'' +varchar(20) +20 + + +9CFC55C4-DF2B-4A90-A789-C3839FAA43A8 +phone +phone +1524449375 +Administrator +1524449375 +Administrator +联系电话 +'' +varchar(20) +20 + + +1A9407E5-D74E-4CE9-9078-C4EC25393F7B +email +email +1524449375 +Administrator +1524449375 +Administrator +邮箱 +'' +varchar(20) +20 + + +B6772812-4B69-4248-871D-FA1B4BA0E5F7 +status +status +1524449375 +Administrator +1538295792 +admin +部门状态:0正常,1停用 +0 +char(1) +1 + + +6EBD2BFF-861E-4247-BAAB-B37CCBAF6F8D +del_flag +del_flag +1538295690 +admin +1538295792 +admin +char(1) +1 + + +2504A090-F6D6-493F-855E-5154E01AF0CA +create_by +create_by +1524449375 +Administrator +1524449375 +Administrator +创建者 +'' +varchar(64) +64 + + +D866AE9E-E7FF-47B2-BF3D-9BC1605A2F39 +create_time +create_time +1524449375 +Administrator +1524449375 +Administrator +创建时间 +timestamp + + +7C6C9836-FC23-4492-8CF1-A4439E01B57C +update_by +update_by +1524449375 +Administrator +1524449375 +Administrator +更新者 +'' +varchar(64) +64 + + +FCED770D-005C-4531-A9D7-D1FD0A054719 +update_time +update_time +1524449375 +Administrator +1524449375 +Administrator +更新时间 +timestamp + + + + +15C1774B-9F17-48B6-A61F-728A25220B30 +Key_1 +Key_1 +1524449375 +Administrator +1524449375 +Administrator + + + + + + + + + + +AA56FD91-4450-4282-8F31-AE302DF6AFEC +sys_user +sys_user +1524449375 +Administrator +1538297540 +admin +用户信息表 + + + +4A920BCE-4040-4F12-89D2-7DF345B90321 +user_id +user_id +1524449375 +Administrator +1524449375 +Administrator +用户ID +int(11) +11 +1 +1 + + +174E10B2-4A4D-40FF-80B8-B4D285561E42 +dept_id +dept_id +1524449375 +Administrator +1538297552 +admin +部门ID +NULL +int(11) +11 + + +1D4908A9-5416-4252-BA09-FA122D0194C3 +login_name +login_name +1524449375 +Administrator +1524449375 +Administrator +登录账号 +'' +varchar(30) +30 + + +2EF63346-9E82-4746-81B7-AB67D727446D +user_name +user_name +1524449375 +Administrator +1524449375 +Administrator +用户昵称 +'' +varchar(30) +30 + + +477EA57C-0E0B-4596-9A85-EC91E72F5160 +user_type +user_type +1524449375 +Administrator +1524449375 +Administrator +类型:Y默认用户,N非默认用户 +N +char(1) +1 + + +CD16FFF4-F214-473B-A9A8-FA30A3E357D1 +email +email +1524449375 +Administrator +1524449375 +Administrator +用户邮箱 +'' +varchar(100) +100 + + +61603FA5-3EBC-4389-AED7-1B54D238A563 +phonenumber +phonenumber +1524449375 +Administrator +1524449375 +Administrator +手机号码 +'' +varchar(20) +20 + + +65E9DE55-ED58-4BD9-B96C-7C081D1119B2 +sex +sex +1538295815 +admin +1538295948 +admin +char(1) +1 + + +E5E35061-221A-4BB9-AA22-3CF20F1FCCF6 +avatar +avatar +1538295815 +admin +1538295948 +admin +varchar(100) +100 + + +4ED1C2BF-B826-4A82-9464-EEBF271F4054 +password +password +1524449375 +Administrator +1524449375 +Administrator +密码 +'' +varchar(100) +100 + + +53E6BB49-3435-46E0-832F-BCAFE1A021CB +salt +salt +1524449375 +Administrator +1524449375 +Administrator +盐加密 +'' +varchar(100) +100 + + +245CAD53-B33B-4EED-8CFA-7AA10ED943B8 +status +status +1524449375 +Administrator +1538297540 +admin +帐号状态:0正常,1禁用 +0 +char(1) +1 + + +7F851464-6CC5-445B-9413-2A89B9CE90CB +del_flag +del_flag +1524449375 +Administrator +1538295948 +admin +拒绝登录描述 +'' +char(1) +1 + + +3DC8EC79-D75A-4BF8-8FBC-152E938AC14F +create_by +create_by +1524449375 +Administrator +1524449375 +Administrator +创建者 +'' +varchar(64) +64 + + +48C8C936-7A34-4A97-AACA-A6F07751FFAD +create_time +create_time +1524449375 +Administrator +1524449375 +Administrator +创建时间 +timestamp + + +6050B4F3-9B26-4B40-AB4C-BA483F179958 +update_by +update_by +1524449375 +Administrator +1524449375 +Administrator +更新者 +'' +varchar(64) +64 + + +CD1E7E11-8EB6-4C9C-A69C-39CBCF10573E +update_time +update_time +1524449375 +Administrator +1524449375 +Administrator +更新时间 +timestamp + + +F9F55D4C-13E6-49A0-BFDB-E0AFE0FA5501 +remark +remark +1538295815 +admin +1538295948 +admin +varchar(500) +500 + + + + +2E35FD67-A7A7-4B10-85E4-85115AD0E143 +Key_1 +Key_1 +1524449375 +Administrator +1524449375 +Administrator + + + + + + + + + + +2711A520-532C-4F14-A034-BFF047C9CD6B +sys_post +sys_post +1524449375 +Administrator +1538297571 +admin +岗位信息表 + + + +FB04D29E-41F0-49A3-BFDB-58E222843F21 +post_id +post_id +1524449375 +Administrator +1524449375 +Administrator +岗位ID +int(11) +11 +1 +1 + + +50010C4E-4F59-47B9-8F08-05E8E071E8B1 +post_code +post_code +1524449375 +Administrator +1524449375 +Administrator +岗位编码 +varchar(64) +64 +1 + + +0F929250-051E-4344-B22A-C30E071A543B +post_name +post_name +1524449375 +Administrator +1524449375 +Administrator +岗位名称 +varchar(100) +100 +1 + + +2BC9005E-350F-46BE-98D6-9B13060F1B20 +post_sort +post_sort +1524449375 +Administrator +1524449375 +Administrator +显示顺序 +int(4) +4 +1 + + +F6D7AD3E-5EA0-4759-B6BF-6334B7105B78 +status +status +1524449375 +Administrator +1538297565 +admin +状态(0正常 1停用) +char(1) +1 +1 + + +CED01369-5063-479D-A444-32936369A486 +create_by +create_by +1524449375 +Administrator +1524449375 +Administrator +创建者 +'' +varchar(64) +64 + + +A29528FF-A2B9-4149-B997-1B0204D42E40 +create_time +create_time +1524449375 +Administrator +1524449375 +Administrator +创建时间 +timestamp + + +6026A05D-0C1E-497E-8EAF-FDB704BE6A52 +update_by +update_by +1524449375 +Administrator +1524449375 +Administrator +更新者 +'' +varchar(64) +64 + + +DF516F5F-CD82-4347-AC57-BDCB4E5DD75E +update_time +update_time +1524449375 +Administrator +1524449375 +Administrator +更新时间 +timestamp + + +539CEC34-49F0-49A0-9B7C-B84655FD2233 +remark +remark +1524449375 +Administrator +1524449375 +Administrator +备注 +'' +varchar(500) +500 + + + + +14E893B1-D0BA-46A7-A905-F0FFA089B65A +Key_1 +Key_1 +1524449375 +Administrator +1524449375 +Administrator + + + + + + + + + + +11337551-BA45-43CD-9148-92BE60E2F8F5 +sys_role +sys_role +1524449375 +Administrator +1538297608 +admin +角色信息表 + + + +A420E2C9-8FE3-452A-9047-C7BEACE8490C +role_id +role_id +1524449375 +Administrator +1524449375 +Administrator +角色ID +int(10) +10 +1 +1 + + +9342763D-5B89-4440-965B-2B55DB4ACD86 +role_name +role_name +1524449375 +Administrator +1524449375 +Administrator +角色名称 +varchar(30) +30 +1 + + +54480009-0C7E-40F2-AA76-CD914A6D66C5 +role_key +role_key +1524449375 +Administrator +1524449375 +Administrator +角色权限字符串 +varchar(100) +100 +1 + + +E73F4D0E-12A0-42B5-B3CE-B573D499DD6C +role_sort +role_sort +1524449375 +Administrator +1538296031 +admin +显示顺序 +int(10) +10 + + +5F836F54-9EBD-4768-AA3C-F268F5FAFE8D +data_scope +data_scope +1538295973 +admin +1538296031 +admin +char(1) +1 + + +424ED799-E4C1-44AD-A172-C2B3C405E9C5 +status +status +1524449375 +Administrator +1538297608 +admin +角色状态:0正常,1禁用 +0 +char(1) +1 + + +8E034C76-5966-4246-B81B-7B12F37D96A7 +del_flag +del_flag +1538295973 +admin +1538296031 +admin +char(1) +1 + + +214F6E1F-28B1-454B-ABF0-D1C43220129D +create_by +create_by +1524449375 +Administrator +1524449375 +Administrator +创建者 +'' +varchar(64) +64 + + +1A6D5791-0353-4ABC-8BC2-921BB87A2E5A +create_time +create_time +1524449375 +Administrator +1524449375 +Administrator +创建时间 +timestamp + + +D6394880-A49C-4B83-B43A-5FDBAA918AA3 +update_by +update_by +1524449375 +Administrator +1524449375 +Administrator +更新者 +'' +varchar(64) +64 + + +34285DF5-8E36-452B-A3AA-9F4290C20F7E +update_time +update_time +1524449375 +Administrator +1524449375 +Administrator +更新时间 +timestamp + + +2FAB98F7-68A2-460B-8A20-5D5DA73F5103 +remark +remark +1524449375 +Administrator +1524449375 +Administrator +备注 +'' +varchar(500) +500 + + + + +4342E67F-D33C-435F-9865-973E053B6075 +Key_1 +Key_1 +1524449375 +Administrator +1524449375 +Administrator + + + + + + + + + + +FBC2A590-443B-43C9-82D5-687B850C8B3D +sys_menu +sys_menu +1524449375 +Administrator +1538297627 +admin +菜单权限表 + + + +BB061292-3B99-432E-9B96-5362AAD918B9 +menu_id +menu_id +1524449375 +Administrator +1524449375 +Administrator +菜单ID +int(11) +11 +1 +1 + + +EA8422AB-37B1-4D60-A3C9-A4BF9039A9D4 +menu_name +menu_name +1524449375 +Administrator +1524449375 +Administrator +菜单名称 +varchar(50) +50 +1 + + +E56E04A8-63F6-4271-92E3-974DC84DD536 +parent_id +parent_id +1524449375 +Administrator +1524449375 +Administrator +父菜单ID +0 +int(11) +11 + + +1809914E-6B09-4CD2-8916-E603D6717557 +order_num +order_num +1524449375 +Administrator +1524449375 +Administrator +显示顺序 +NULL +int(4) +4 + + +FCB44D46-3C21-40CB-B942-57823E52E5B1 +url +url +1524449375 +Administrator +1524449375 +Administrator +请求地址 +'' +varchar(200) +200 + + +667EE044-6805-4668-BAF4-E78B3052051F +menu_type +menu_type +1524449375 +Administrator +1524449375 +Administrator +类型:M目录,C菜单,F按钮 +'' +char(1) +1 + + +F7658083-BCAB-46F7-AF31-8A4B1D8749EF +visible +visible +1524449375 +Administrator +1538297627 +admin +菜单状态:0显示,1隐藏 +0 +char(1) +1 + + +528611C8-C319-430F-8F00-68FBA60F310B +perms +perms +1524449375 +Administrator +1524449375 +Administrator +权限标识 +'' +varchar(100) +100 + + +38004CD7-8DD0-43F1-9E59-B50132CB6F1A +icon +icon +1524449375 +Administrator +1524449375 +Administrator +菜单图标 +'' +varchar(100) +100 + + +6927665F-EC42-4E1F-A275-4B27F442B6B8 +create_by +create_by +1524449375 +Administrator +1524449375 +Administrator +创建者 +'' +varchar(64) +64 + + +1A6A4D0F-0B0B-4522-B4DA-3F1D592CB889 +create_time +create_time +1524449375 +Administrator +1524449375 +Administrator +创建时间 +timestamp + + +605D7776-4820-4BA9-91E8-AD837B73AEFB +update_by +update_by +1524449375 +Administrator +1524449375 +Administrator +更新者 +'' +varchar(64) +64 + + +4CFF26BB-8736-4864-855E-C7C1B133370B +update_time +update_time +1524449375 +Administrator +1524449375 +Administrator +更新时间 +timestamp + + +67C6E46C-DF06-480A-BC74-E927406E5D26 +remark +remark +1524449375 +Administrator +1524449375 +Administrator +备注 +'' +varchar(500) +500 + + + + +08EBE713-9E4D-4312-AA7D-2E4E439734E5 +Key_1 +Key_1 +1524449375 +Administrator +1524449375 +Administrator + + + + + + + + + + +F8CB66D1-3632-4509-97C4-17016BE261FC +sys_user_role +sys_user_role +1524449375 +Administrator +1538297676 +admin +用户和角色关联表 + + + +73701F72-C45B-4CA0-8A62-632890E3DEF0 +user_id +user_id +1524449375 +Administrator +1524449375 +Administrator +用户ID +int(11) +11 +1 + + +CABD458B-DA59-46A8-99C3-088AD8D34097 +role_id +role_id +1524449375 +Administrator +1524449375 +Administrator +角色ID +int(11) +11 +1 + + + + +37C3213B-EF22-4CD4-A91F-9A9A2503FB2A +Key_1 +Key_1 +1524449375 +Administrator +1524449375 +Administrator + + + + + + + + + + + +9F8C6A9F-3221-410E-AEA4-D1A80026397E +sys_role_menu +sys_role_menu +1524449375 +Administrator +1538297683 +admin +角色和菜单关联表 + + + +D2E151A5-6156-46EF-844E-0ADC3070293B +role_id +role_id +1524449375 +Administrator +1524449375 +Administrator +角色ID +int(11) +11 +1 + + +6B8C1E62-FD8B-4504-8FA0-F69917722FBD +menu_id +menu_id +1524449375 +Administrator +1524449375 +Administrator +菜单ID +int(11) +11 +1 + + + + +2E72304F-91F0-4392-BAE8-BBF7A4346B7D +Key_1 +Key_1 +1524449375 +Administrator +1524449375 +Administrator + + + + + + + + + + + +726CB18E-7D5B-4E2E-9CF8-047AD5AF89E3 +sys_user_post +sys_user_post +1524449375 +Administrator +1538297694 +admin +用户与岗位关联表 + + + +E4A1CAB6-0F63-4917-ACEF-418DE7F894BA +user_id +user_id +1524449375 +Administrator +1538296306 +admin +用户ID +int(11) +11 +1 + + +8E7188D5-B3A5-4F1D-B6CB-D77D652414DE +post_id +post_id +1524449375 +Administrator +1538296306 +admin +岗位ID +int(11) +11 +1 + + + + +4091B7D3-2404-4C20-BBCD-B63E22A5E960 +Key_1 +Key_1 +1524449375 +Administrator +1524449375 +Administrator + + + + + + + + + + + +FE347A45-D8EC-423B-9B38-4D315A3ABE42 +sys_oper_log +sys_oper_log +1524449375 +Administrator +1538297699 +admin +操作日志记录 + + + +F5FC8AC1-7415-4A57-BA2C-EE2E7B9E1EFC +oper_id +oper_id +1524449375 +Administrator +1524449375 +Administrator +日志主键 +int(11) +11 +1 +1 + + +2103BC5C-E28D-4369-8369-E898B218587A +title +title +1524449375 +Administrator +1524449375 +Administrator +模块标题 +'' +varchar(50) +50 + + +6816377B-3DB6-424A-99ED-1D20FEB30ED4 +business_type +business_type +1524449375 +Administrator +1538296397 +admin +功能请求 +'' +int(2) +2 + + +9CA3B7C3-F52C-4E2E-893F-8E6EBA7B2667 +method +method +1524449375 +Administrator +1524449375 +Administrator +方法名称 +'' +varchar(100) +100 + + +A5744803-C050-4108-9D15-7A0B95F03642 +operator_type +operator_type +1524449375 +Administrator +1538296397 +admin +来源渠道 +'' +int(1) +1 + + +B0DF8235-6BC1-452C-8B30-A56F0430E4F5 +oper_name +oper_name +1524449375 +Administrator +1538296397 +admin +登录账号 +'' +varchar(50) +50 + + +25315A12-4EB9-4B67-9E2C-9F40F8EF7FAB +dept_name +dept_name +1524449375 +Administrator +1524449375 +Administrator +部门名称 +'' +varchar(50) +50 + + +7AF8602B-A1DA-4EA3-BFB2-7638F96A86C0 +oper_url +oper_url +1524449375 +Administrator +1524449375 +Administrator +请求URL +'' +varchar(255) +255 + + +F2A56B63-7A56-43FA-8099-411F3578B30D +oper_ip +oper_ip +1524449375 +Administrator +1524449375 +Administrator +主机地址 +'' +varchar(30) +30 + + +1EF1BAF6-F5C1-496C-98E0-8B10C37279A1 +oper_param +oper_param +1524449375 +Administrator +1524449375 +Administrator +请求参数 +'' +varchar(255) +255 + + +AA3F3A4E-D375-4232-B152-01DCFB8F6B6D +status +status +1524449375 +Administrator +1524449375 +Administrator +操作状态 0正常 1异常 +0 +int(1) +1 + + +29E44D4A-6AC7-4220-A502-4BFC8746397A +error_msg +error_msg +1524449375 +Administrator +1524449375 +Administrator +错误消息 +'' +varchar(2000) +2000 + + +22343C35-D913-485B-862E-2CEF579AAF22 +oper_time +oper_time +1524449375 +Administrator +1524449375 +Administrator +操作时间 +timestamp + + + + +C0561C20-CC22-471B-A764-414C0D378FD6 +Key_1 +Key_1 +1524449375 +Administrator +1524449375 +Administrator + + + + + + + + + + +AA2CFBA5-FA97-4AF1-92FE-645370B5848D +sys_dict_type +sys_dict_type +1524449375 +Administrator +1538297703 +admin +字典类型表 + + + +79CB7D43-B999-4D92-9477-D3AFEBD94248 +dict_id +dict_id +1524449375 +Administrator +1524449375 +Administrator +字典主键 +int(11) +11 +1 +1 + + +2490B755-3E0A-4935-97F0-2EFDF9A72D05 +dict_name +dict_name +1524449375 +Administrator +1524449375 +Administrator +字典名称 +'' +varchar(100) +100 + + +7421238A-82DB-4992-AA28-41726AB6A5D6 +dict_type +dict_type +1524449375 +Administrator +1524449375 +Administrator +字典类型 +'' +varchar(100) +100 + + +971D2FBD-1A24-4EE4-B943-9367609C7472 +status +status +1524449375 +Administrator +1538296458 +admin +状态(0正常 1禁用) +0 +char(1) +1 + + +B8876246-5BBA-4A03-86D7-98CA4EBEE342 +create_by +create_by +1524449375 +Administrator +1524449375 +Administrator +创建者 +'' +varchar(64) +64 + + +5237CED2-0853-41DE-ACF4-BE442BC9E112 +create_time +create_time +1524449375 +Administrator +1524449375 +Administrator +创建时间 +timestamp + + +2CACFBC0-8349-4B3A-9183-208B18C9F56F +update_by +update_by +1524449375 +Administrator +1524449375 +Administrator +更新者 +'' +varchar(64) +64 + + +ABEE7806-4F61-4B97-980C-CA081F61CA7C +update_time +update_time +1524449375 +Administrator +1524449375 +Administrator +更新时间 +timestamp + + +3966B558-B911-45DE-86C6-57F3DB9267BA +remark +remark +1524449375 +Administrator +1524449375 +Administrator +备注 +'' +varchar(500) +500 + + +AFC0A0ED-A469-40B2-A6C4-4616444830AA +unique +unique +1524449375 +Administrator +1524449375 +Administrator +(dict_type) + + + + +BAD40D8E-BC11-44F5-918E-B27CABBCB051 +Key_1 +Key_1 +1524449375 +Administrator +1524449375 +Administrator + + + + + + + + + + +493D6B25-21D0-45B1-BBA0-764B9C09B57D +sys_dict_data +sys_dict_data +1524449375 +Administrator +1538297709 +admin +字典数据表 + + + +CFDB23A8-AE38-4051-973A-2DABAC8283F9 +dict_code +dict_code +1524449375 +Administrator +1524449375 +Administrator +字典编码 +int(11) +11 +1 +1 + + +EAA405BD-12A8-472F-A42D-CDA6A82E291A +dict_sort +dict_sort +1524449375 +Administrator +1524449375 +Administrator +字典排序 +0 +int(4) +4 + + +F13017F5-2AA0-4DE9-9DC2-A9A3D73A98E6 +dict_label +dict_label +1524449375 +Administrator +1524449375 +Administrator +字典标签 +'' +varchar(100) +100 + + +EEEC4136-823D-4892-9BB9-BB0B4ADD83E3 +dict_value +dict_value +1524449375 +Administrator +1524449375 +Administrator +字典键值 +'' +varchar(100) +100 + + +ADF5A383-D055-40BE-BBFC-06E2B93D4E6A +dict_type +dict_type +1524449375 +Administrator +1524449375 +Administrator +字典类型 +'' +varchar(100) +100 + + +A0B2DDF2-251D-4701-9B00-6893C74CC449 +css_class +css_class +1538296497 +admin +1538296556 +admin +varchar(100) +100 + + +3CBFBA8E-7609-458D-9E53-A825C3F307A2 +list_class +list_class +1538296497 +admin +1538296556 +admin +varchar(100) +100 + + +BA974839-DEE0-4684-BBEF-6D7776C34354 +is_default +is_default +1538296497 +admin +1538296556 +admin +char(1) +1 + + +1676CDF5-01CA-4749-BA1D-6E5399257BD0 +status +status +1524449375 +Administrator +1524449375 +Administrator +状态(0正常 1禁用) +0 +int(1) +1 + + +8798B094-1AAF-4A23-B2F1-4C19DACF1AA3 +create_by +create_by +1524449375 +Administrator +1524449375 +Administrator +创建者 +'' +varchar(64) +64 + + +D1CB9293-D762-403C-85CB-4B974ACF7328 +create_time +create_time +1524449375 +Administrator +1524449375 +Administrator +创建时间 +timestamp + + +5A34AF87-B25E-4349-9713-69DC50F6F5F2 +update_by +update_by +1524449375 +Administrator +1524449375 +Administrator +更新者 +'' +varchar(64) +64 + + +3204FBAC-1F61-4571-ADC4-BF1BE9CED85A +update_time +update_time +1524449375 +Administrator +1524449375 +Administrator +更新时间 +timestamp + + +B7DE1842-809C-4401-9C80-C9A37DF9B053 +remark +remark +1524449375 +Administrator +1524449375 +Administrator +备注 +'' +varchar(500) +500 + + + + +2809F417-7FA5-48DA-B613-662C7C28061E +Key_1 +Key_1 +1524449375 +Administrator +1524449375 +Administrator + + + + + + + + + + +0A7C2F56-6E3B-4E70-A549-0EC60779D180 +sys_logininfor +sys_logininfor +1524449375 +Administrator +1538297756 +admin +系统访问记录 + + + +5CB5D942-D52B-487D-BC86-476481B0FB8D +info_id +info_id +1524449375 +Administrator +1524449375 +Administrator +访问ID +int(11) +11 +1 +1 + + +A1C66DBC-9DB7-428B-9275-3D014B6CE388 +login_name +login_name +1524449375 +Administrator +1524449375 +Administrator +登录账号 +'' +varchar(50) +50 + + +8E0F50A6-F98D-48B0-8D9D-78F3A76ED171 +ipaddr +ipaddr +1524449375 +Administrator +1524449375 +Administrator +登录IP地址 +'' +varchar(50) +50 + + +91B70723-1A7E-4277-A100-63B775A504B3 +login_location +login_location +1538297350 +admin +1538297369 +admin +varchar(255) +255 + + +AA04F533-A044-428B-80F8-515B6BB1A302 +browser +browser +1524449375 +Administrator +1524449375 +Administrator +浏览器类型 +'' +varchar(50) +50 + + +D37570E9-9EEE-4349-B875-494A5415C736 +os +os +1524449375 +Administrator +1524449375 +Administrator +操作系统 +'' +varchar(50) +50 + + +CF10A80C-123E-42F3-A2DD-1B770E5F9D86 +status +status +1524449375 +Administrator +1524449375 +Administrator +登录状态 0成功 1失败 +0 +int(1) +1 + + +9113784E-932A-4FAF-82CB-A75B8C827309 +msg +msg +1524449375 +Administrator +1524449375 +Administrator +提示消息 +'' +varchar(255) +255 + + +BCA519C6-19C9-45DF-A0B5-F88E9E6D3557 +login_time +login_time +1524449375 +Administrator +1524449375 +Administrator +访问时间 +timestamp + + + + +C14E656C-0645-49EB-8B42-AD82232E0416 +Key_1 +Key_1 +1524449375 +Administrator +1524449375 +Administrator + + + + + + + + + + +4DCA223F-E98B-4D8B-A71C-CFB438C15488 +sys_user_online +sys_user_online +1524449375 +Administrator +1538297754 +admin +在线用户记录 + + + +7FCC57CE-47DD-4948-B949-10401B2FC7B1 +sessionId +sessionId +1524449375 +Administrator +1524449375 +Administrator +用户会话id +'' +varchar(50) +50 +1 + + +FDE5B59D-8CF7-4AAE-987F-3FF2AEBE22CB +login_name +login_name +1524449375 +Administrator +1524449375 +Administrator +登录账号 +'' +varchar(50) +50 + + +AB65FF92-33A0-42C8-8B3F-454A1FAD5615 +dept_name +dept_name +1524449375 +Administrator +1524449375 +Administrator +部门名称 +'' +varchar(50) +50 + + +C4DAF2D0-9CDC-476B-A011-FF5D302371EB +ipaddr +ipaddr +1524449375 +Administrator +1524449375 +Administrator +登录IP地址 +'' +varchar(50) +50 + + +C8243FB0-425B-4A74-9ADA-C93B15E713EA +login_location +login_location +1538297178 +admin +1538297216 +admin +varchar(255) +255 + + +89EC40B0-0C22-4811-90BB-BEA385ACDF20 +browser +browser +1524449375 +Administrator +1524449375 +Administrator +浏览器类型 +'' +varchar(50) +50 + + +AC455631-CFE0-45BB-A0C5-788D695E4B6C +os +os +1524449375 +Administrator +1524449375 +Administrator +操作系统 +'' +varchar(50) +50 + + +5C56E3C9-4591-4762-89E1-C9BBFECB5F11 +status +status +1524449375 +Administrator +1524449375 +Administrator +在线状态on_line在线off_line离线 +'' +varchar(10) +10 + + +0CAF2F1F-459F-4F78-9075-D95F924A4FF7 +start_timestamp +start_timestamp +1524449375 +Administrator +1524449375 +Administrator +session创建时间 +timestamp + + +6AE6BDED-823E-4455-9A9F-338EC6F7BDB9 +last_access_time +last_access_time +1524449375 +Administrator +1524449375 +Administrator +session最后访问时间 +timestamp + + +CE390924-4628-421C-979F-002C2952E99E +expire_time +expire_time +1524449375 +Administrator +1524449375 +Administrator +超时时间,单位为分钟 +0 +int(5) +5 + + + + +365CC94D-6124-42C7-96BD-376B84B709F7 +Key_1 +Key_1 +1524449375 +Administrator +1524449375 +Administrator + + + + + + + + + + +AFCBF4DB-07EC-42D1-ACA7-56B5038F5AC5 +sys_job +sys_job +1524449375 +Administrator +1538297732 +admin +定时任务调度表 + + + +1658CED4-3885-4094-AB70-F35408EBCD5E +job_id +job_id +1524449375 +Administrator +1524449375 +Administrator +任务ID +int(11) +11 +1 +1 + + +731E7147-E3A4-4D93-8C7C-BB1C6D94DB9E +job_name +job_name +1524449375 +Administrator +1524449375 +Administrator +任务名称 +'' +varchar(64) +64 +1 + + +C64B3655-C240-44F0-83B4-F42FB76C8BEA +job_group +job_group +1524449375 +Administrator +1524449375 +Administrator +任务组名 +'' +varchar(64) +64 +1 + + +9F7E735D-B823-4ADA-BA3D-8FFFFEC92F5C +method_name +method_name +1524449375 +Administrator +1524449375 +Administrator +任务方法 +'' +varchar(500) +500 + + +28EEE4F4-E8E7-4052-8F10-88D6C74C595D +method_params +method_params +1524449375 +Administrator +1538297298 +admin +方法参数 +'' +varchar(200) +200 + + +C8986FAD-E2E7-4364-9E8B-B75366B9A4ED +cron_expression +cron_expression +1524449375 +Administrator +1524449375 +Administrator +cron执行表达式 +'' +varchar(255) +255 + + +FD188167-AC02-4161-BE89-D63E61412313 +misfire_policy +misfire_policy +1538297273 +admin +1538297298 +admin +varchar(20) +20 + + +2D4B6C8F-EEE8-4474-9D20-8206A7E80362 +status +status +1524449375 +Administrator +1524449375 +Administrator +状态(0正常 1暂停) +0 +int(1) +1 + + +CA78AC7F-19E7-47BC-BF7B-9F31EFB02702 +create_by +create_by +1524449375 +Administrator +1524449375 +Administrator +创建者 +'' +varchar(64) +64 + + +B8F807AE-9F19-4FCA-BA98-7BF71DD0CA02 +create_time +create_time +1524449375 +Administrator +1524449375 +Administrator +创建时间 +timestamp + + +3FBB42FA-ED0F-4D7C-99D0-5F7AF7B0F1DD +update_by +update_by +1524449375 +Administrator +1524449375 +Administrator +更新者 +'' +varchar(64) +64 + + +1C5863D2-A8B9-43DB-AA06-F8BE3E01093B +update_time +update_time +1524449375 +Administrator +1524449375 +Administrator +更新时间 +timestamp + + +889C3FF9-BB1E-4EB1-AFE9-1D1155984915 +remark +remark +1524449375 +Administrator +1524449375 +Administrator +备注信息 +'' +varchar(500) +500 + + + + +38106F1A-4FFB-4EC0-B979-55BD6C6C6FF7 +Key_1 +Key_1 +1524449375 +Administrator +1524449375 +Administrator + + + + + + + + + + + + +CF7C8958-5494-48C6-BE05-83F2CF8C7513 +sys_job_log +sys_job_log +1524449375 +Administrator +1538297742 +admin +定时任务调度日志表 + + + +308F32A1-A8EC-4002-9993-DF9234A303B7 +job_log_id +job_log_id +1524449375 +Administrator +1524449375 +Administrator +任务日志ID +int(11) +11 +1 +1 + + +F4D55B65-BB6B-4182-A6D6-F9CAABC19110 +job_name +job_name +1524449375 +Administrator +1524449375 +Administrator +任务名称 +varchar(64) +64 +1 + + +8AF383A0-01C0-4947-8384-FF0F13AC00AE +job_group +job_group +1524449375 +Administrator +1524449375 +Administrator +任务组名 +varchar(64) +64 +1 + + +96582B76-F1E9-4473-BA51-01B87B5F459E +method_name +method_name +1524449375 +Administrator +1524449375 +Administrator +任务方法 +varchar(500) +500 + + +2AB02ABA-02E3-4F72-95BA-4261A7F5729A +method_params +method_params +1524449375 +Administrator +1538297325 +admin +方法参数 +'' +varchar(200) +200 + + +8EB39444-CBFF-43AA-AA37-49217EF545B6 +job_message +job_message +1524449375 +Administrator +1524449375 +Administrator +日志信息 +varchar(500) +500 + + +18CD263C-0F57-4EDF-999E-1B5A7EE2BFF9 +is_exception +is_exception +1524449375 +Administrator +1538297325 +admin +是否异常 +0 +char(1) +1 + + +634ECD78-2251-43EB-B6CF-DF7FA9DA4354 +exception_info +exception_info +1524449375 +Administrator +1524449375 +Administrator +异常信息 +text + + +4EC075CC-507B-43D7-860F-34DAAEB1DBBF +create_time +create_time +1524449375 +Administrator +1524449375 +Administrator +创建时间 +timestamp + + + + +A87DCE10-894A-4CF7-B39C-AF18202C7F86 +Key_1 +Key_1 +1524449375 +Administrator +1524449375 +Administrator + + + + + + + + + + +FD6284E8-B6D4-43AF-A038-9C97DCD403DC +sys_role_dept +sys_role_dept +1538296083 +admin +1538297689 +admin +角色和部门关联表 + + + +2BC66204-4193-42E6-BB7B-7AD57C9E5BEF +role_id +role_id +1538296083 +admin +1538296150 +admin +用户ID +int(11) +11 +1 + + +A32BC025-6437-41AB-BAA4-3A150E406781 +dept_id +dept_id +1538296083 +admin +1538296150 +admin +岗位ID +int(11) +11 +1 + + + + +315FFED5-B0A0-4649-8255-2283896340C9 +Key_1 +Key_1 +1538296083 +admin +1538296083 +admin + + + + + + + + + + + +45EB995C-F5F6-4818-AEB1-2038DEBA9CEE +sys_config +sys_config +1538296587 +admin +1538297714 +admin +参数配置表 + + + +667C4616-146B-475C-8111-4720375D762C +config_id +config_id +1538296587 +admin +1538296691 +admin +字典编码 +int(5) +5 +1 +1 + + +EA798E0B-0CBE-4897-B0AF-1F2D3CD6DEF4 +config_name +config_name +1538296587 +admin +1538296691 +admin +字典排序 +0 +varchar(100) +100 + + +A9A2A6E0-C914-4516-AE4C-F33CE71B92E8 +config_key +config_key +1538296587 +admin +1538296691 +admin +字典标签 +'' +varchar(100) +100 + + +24CCA897-8671-402E-8229-9ED0C80C176A +config_value +config_value +1538296587 +admin +1538296691 +admin +字典键值 +'' +varchar(100) +100 + + +B4E76B1D-BFAF-42F3-8CCA-8B5A8CC7CBFF +config_type +config_type +1538296587 +admin +1538296691 +admin +字典类型 +'' +char(1) +1 + + +A6AC1891-F5C4-45B3-8CAB-8F4CE8B8BF08 +create_by +create_by +1538296587 +admin +1538296587 +admin +创建者 +'' +varchar(64) +64 + + +CC1E0367-A079-49A0-8F0A-FE5F7B3EB6EA +create_time +create_time +1538296587 +admin +1538296587 +admin +创建时间 +timestamp + + +081CD54E-AE38-4696-A326-F829B8EA5737 +update_by +update_by +1538296587 +admin +1538296587 +admin +更新者 +'' +varchar(64) +64 + + +E2118ECE-8F52-4FBA-B18A-F30FFB2BDD20 +update_time +update_time +1538296587 +admin +1538296587 +admin +更新时间 +timestamp + + +55A16121-8932-465E-8427-EBDA39B2B900 +remark +remark +1538296587 +admin +1538296587 +admin +备注 +'' +varchar(500) +500 + + + + +0F331278-2804-496A-A87B-B0944C80FB82 +Key_1 +Key_1 +1538296587 +admin +1538296587 +admin + + + + + + + + + + +F33DE1D6-C12D-43DB-A502-83BD1615F081 +sys_notice +sys_notice +1538297386 +admin +1538297746 +admin +通知公告表 + + + +FF4A9744-D7CA-450E-8AD7-B3E7E90075CE +notice_id +notice_id +1538297386 +admin +1538297496 +admin +任务日志ID +int(4) +4 +1 +1 + + +E2B08825-4C94-4209-80B2-21A7AD8CBF2D +notice_title +notice_title +1538297386 +admin +1538297496 +admin +任务名称 +varchar(50) +50 +1 + + +04414862-9ABC-4431-B1B7-B44ECC08CB6E +notice_type +notice_type +1538297386 +admin +1538297496 +admin +任务组名 +char(2) +2 +1 + + +E829DAD1-E3F9-4AED-A3DE-59CE4340333E +notice_content +notice_content +1538297386 +admin +1538297496 +admin +任务方法 +varchar(500) +500 + + +2EABC8DB-6700-4717-89A3-31461C4CB2D5 +status +status +1538297386 +admin +1538297496 +admin +方法参数 +'' +char(1) +1 + + +448D3EB6-DE24-4BE3-9C29-1FC3C71B0E8D +create_by +create_by +1538297386 +admin +1538297496 +admin +日志信息 +varchar(64) +64 + + +770ED87D-D4D7-499C-A266-7A54051B1A84 +create_time1 +create_time1 +1538297386 +admin +1538297496 +admin +是否异常 +0 +datetime + + +12DDF399-7CCB-4117-8B05-6AA9BEE845E5 +update_by +update_by +1538297386 +admin +1538297496 +admin +异常信息 +varchar(64) +64 + + +FE101CE4-9B66-4097-944D-36B01A9E2219 +update_time1 +update_time1 +1538297400 +admin +1538297496 +admin +datetime + + +D5F1728C-01D0-4C00-9AD6-AAA14228104B +remark +remark +1538297386 +admin +1538297496 +admin +创建时间 +varchar(255) +255 + + + + +43C7AC1D-CE7A-4B55-A474-8CB2376D446F +Key_1 +Key_1 +1538297386 +admin +1538297386 +admin + + + + + + + + + + + + +F2EBEA5B-F352-45CB-B349-39158064CEE8 +PUBLIC +PUBLIC +1524449325 +Administrator +1524449325 +Administrator + + + + +41740AEF-D7FB-4738-ABDF-47C3287A6AF6 +MySQL 5.0 +MYSQL50 +1524449337 +Administrator +1538295558 +admin +file:///%_DBMS%/mysql50.xdb +F4F16ECD-F2F1-4006-AF6F-638D5C65F35E +4BA9F647-DAB1-11D1-9944-006097355D9B + + + + + + + + + + \ No newline at end of file diff --git a/sql/ry_20230223.sql b/sql/ry_20230223.sql new file mode 100644 index 0000000..5a1d7e5 --- /dev/null +++ b/sql/ry_20230223.sql @@ -0,0 +1,720 @@ +-- ---------------------------- +-- 1、部门表 +-- ---------------------------- +drop table if exists sys_dept; +create table sys_dept ( + dept_id bigint(20) not null auto_increment comment '部门id', + parent_id bigint(20) default 0 comment '父部门id', + ancestors varchar(50) default '' comment '祖级列表', + dept_name varchar(30) default '' comment '部门名称', + order_num int(4) default 0 comment '显示顺序', + leader varchar(20) default null comment '负责人', + phone varchar(11) default null comment '联系电话', + email varchar(50) default null comment '邮箱', + status char(1) default '0' comment '部门状态(0正常 1停用)', + del_flag char(1) default '0' comment '删除标志(0代表存在 2代表删除)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + primary key (dept_id) +) engine=innodb auto_increment=200 comment = '部门表'; + +-- ---------------------------- +-- 初始化-部门表数据 +-- ---------------------------- +insert into sys_dept values(100, 0, '0', '若依科技', 0, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(101, 100, '0,100', '深圳总公司', 1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(102, 100, '0,100', '长沙分公司', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(103, 101, '0,100,101', '研发部门', 1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(104, 101, '0,100,101', '市场部门', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(105, 101, '0,100,101', '测试部门', 3, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(106, 101, '0,100,101', '财务部门', 4, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(107, 101, '0,100,101', '运维部门', 5, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(108, 102, '0,100,102', '市场部门', 1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(109, 102, '0,100,102', '财务部门', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null); + + +-- ---------------------------- +-- 2、用户信息表 +-- ---------------------------- +drop table if exists sys_user; +create table sys_user ( + user_id bigint(20) not null auto_increment comment '用户ID', + dept_id bigint(20) default null comment '部门ID', + login_name varchar(30) not null comment '登录账号', + user_name varchar(30) default '' comment '用户昵称', + user_type varchar(2) default '00' comment '用户类型(00系统用户 01注册用户)', + email varchar(50) default '' comment '用户邮箱', + phonenumber varchar(11) default '' comment '手机号码', + sex char(1) default '0' comment '用户性别(0男 1女 2未知)', + avatar varchar(100) default '' comment '头像路径', + password varchar(50) default '' comment '密码', + salt varchar(20) default '' comment '盐加密', + status char(1) default '0' comment '帐号状态(0正常 1停用)', + del_flag char(1) default '0' comment '删除标志(0代表存在 2代表删除)', + login_ip varchar(128) default '' comment '最后登录IP', + login_date datetime comment '最后登录时间', + pwd_update_date datetime comment '密码最后更新时间', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (user_id) +) engine=innodb auto_increment=100 comment = '用户信息表'; + +-- ---------------------------- +-- 初始化-用户信息表数据 +-- ---------------------------- +insert into sys_user values(1, 103, 'admin', '若依', '00', 'ry@163.com', '15888888888', '1', '', '29c67a30398638269fe600f73a054934', '111111', '0', '0', '127.0.0.1', sysdate(), sysdate(), 'admin', sysdate(), '', null, '管理员'); +insert into sys_user values(2, 105, 'ry', '若依', '00', 'ry@qq.com', '15666666666', '1', '', '8e6d98b90472783cc73c17047ddccf36', '222222', '0', '0', '127.0.0.1', sysdate(), sysdate(), 'admin', sysdate(), '', null, '测试员'); + + +-- ---------------------------- +-- 3、岗位信息表 +-- ---------------------------- +drop table if exists sys_post; +create table sys_post +( + post_id bigint(20) not null auto_increment comment '岗位ID', + post_code varchar(64) not null comment '岗位编码', + post_name varchar(50) not null comment '岗位名称', + post_sort int(4) not null comment '显示顺序', + status char(1) not null comment '状态(0正常 1停用)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (post_id) +) engine=innodb comment = '岗位信息表'; + +-- ---------------------------- +-- 初始化-岗位信息表数据 +-- ---------------------------- +insert into sys_post values(1, 'ceo', '董事长', 1, '0', 'admin', sysdate(), '', null, ''); +insert into sys_post values(2, 'se', '项目经理', 2, '0', 'admin', sysdate(), '', null, ''); +insert into sys_post values(3, 'hr', '人力资源', 3, '0', 'admin', sysdate(), '', null, ''); +insert into sys_post values(4, 'user', '普通员工', 4, '0', 'admin', sysdate(), '', null, ''); + + +-- ---------------------------- +-- 4、角色信息表 +-- ---------------------------- +drop table if exists sys_role; +create table sys_role ( + role_id bigint(20) not null auto_increment comment '角色ID', + role_name varchar(30) not null comment '角色名称', + role_key varchar(100) not null comment '角色权限字符串', + role_sort int(4) not null comment '显示顺序', + data_scope char(1) default '1' comment '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)', + status char(1) not null comment '角色状态(0正常 1停用)', + del_flag char(1) default '0' comment '删除标志(0代表存在 2代表删除)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (role_id) +) engine=innodb auto_increment=100 comment = '角色信息表'; + +-- ---------------------------- +-- 初始化-角色信息表数据 +-- ---------------------------- +insert into sys_role values('1', '超级管理员', 'admin', 1, 1, '0', '0', 'admin', sysdate(), '', null, '超级管理员'); +insert into sys_role values('2', '普通角色', 'common', 2, 2, '0', '0', 'admin', sysdate(), '', null, '普通角色'); + + +-- ---------------------------- +-- 5、菜单权限表 +-- ---------------------------- +drop table if exists sys_menu; +create table sys_menu ( + menu_id bigint(20) not null auto_increment comment '菜单ID', + menu_name varchar(50) not null comment '菜单名称', + parent_id bigint(20) default 0 comment '父菜单ID', + order_num int(4) default 0 comment '显示顺序', + url varchar(200) default '#' comment '请求地址', + target varchar(20) default '' comment '打开方式(menuItem页签 menuBlank新窗口)', + menu_type char(1) default '' comment '菜单类型(M目录 C菜单 F按钮)', + visible char(1) default 0 comment '菜单状态(0显示 1隐藏)', + is_refresh char(1) default 1 comment '是否刷新(0刷新 1不刷新)', + perms varchar(100) default null comment '权限标识', + icon varchar(100) default '#' comment '菜单图标', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default '' comment '备注', + primary key (menu_id) +) engine=innodb auto_increment=2000 comment = '菜单权限表'; + +-- ---------------------------- +-- 初始化-菜单信息表数据 +-- ---------------------------- +-- 一级菜单 +insert into sys_menu values('1', '系统管理', '0', '1', '#', '', 'M', '0', '1', '', 'fa fa-gear', 'admin', sysdate(), '', null, '系统管理目录'); +insert into sys_menu values('2', '系统监控', '0', '2', '#', '', 'M', '0', '1', '', 'fa fa-video-camera', 'admin', sysdate(), '', null, '系统监控目录'); +insert into sys_menu values('3', '系统工具', '0', '3', '#', '', 'M', '0', '1', '', 'fa fa-bars', 'admin', sysdate(), '', null, '系统工具目录'); +insert into sys_menu values('4', '若依官网', '0', '4', 'http://ruoyi.vip', 'menuBlank', 'C', '0', '1', '', 'fa fa-location-arrow', 'admin', sysdate(), '', null, '若依官网地址'); +-- 二级菜单 +insert into sys_menu values('100', '用户管理', '1', '1', '/system/user', '', 'C', '0', '1', 'system:user:view', 'fa fa-user-o', 'admin', sysdate(), '', null, '用户管理菜单'); +insert into sys_menu values('101', '角色管理', '1', '2', '/system/role', '', 'C', '0', '1', 'system:role:view', 'fa fa-user-secret', 'admin', sysdate(), '', null, '角色管理菜单'); +insert into sys_menu values('102', '菜单管理', '1', '3', '/system/menu', '', 'C', '0', '1', 'system:menu:view', 'fa fa-th-list', 'admin', sysdate(), '', null, '菜单管理菜单'); +insert into sys_menu values('103', '部门管理', '1', '4', '/system/dept', '', 'C', '0', '1', 'system:dept:view', 'fa fa-outdent', 'admin', sysdate(), '', null, '部门管理菜单'); +insert into sys_menu values('104', '岗位管理', '1', '5', '/system/post', '', 'C', '0', '1', 'system:post:view', 'fa fa-address-card-o', 'admin', sysdate(), '', null, '岗位管理菜单'); +insert into sys_menu values('105', '字典管理', '1', '6', '/system/dict', '', 'C', '0', '1', 'system:dict:view', 'fa fa-bookmark-o', 'admin', sysdate(), '', null, '字典管理菜单'); +insert into sys_menu values('106', '参数设置', '1', '7', '/system/config', '', 'C', '0', '1', 'system:config:view', 'fa fa-sun-o', 'admin', sysdate(), '', null, '参数设置菜单'); +insert into sys_menu values('107', '通知公告', '1', '8', '/system/notice', '', 'C', '0', '1', 'system:notice:view', 'fa fa-bullhorn', 'admin', sysdate(), '', null, '通知公告菜单'); +insert into sys_menu values('108', '日志管理', '1', '9', '#', '', 'M', '0', '1', '', 'fa fa-pencil-square-o', 'admin', sysdate(), '', null, '日志管理菜单'); +insert into sys_menu values('109', '在线用户', '2', '1', '/monitor/online', '', 'C', '0', '1', 'monitor:online:view', 'fa fa-user-circle', 'admin', sysdate(), '', null, '在线用户菜单'); +insert into sys_menu values('110', '定时任务', '2', '2', '/monitor/job', '', 'C', '0', '1', 'monitor:job:view', 'fa fa-tasks', 'admin', sysdate(), '', null, '定时任务菜单'); +insert into sys_menu values('111', '数据监控', '2', '3', '/monitor/data', '', 'C', '0', '1', 'monitor:data:view', 'fa fa-bug', 'admin', sysdate(), '', null, '数据监控菜单'); +insert into sys_menu values('112', '服务监控', '2', '4', '/monitor/server', '', 'C', '0', '1', 'monitor:server:view', 'fa fa-server', 'admin', sysdate(), '', null, '服务监控菜单'); +insert into sys_menu values('113', '缓存监控', '2', '5', '/monitor/cache', '', 'C', '0', '1', 'monitor:cache:view', 'fa fa-cube', 'admin', sysdate(), '', null, '缓存监控菜单'); +insert into sys_menu values('114', '表单构建', '3', '1', '/tool/build', '', 'C', '0', '1', 'tool:build:view', 'fa fa-wpforms', 'admin', sysdate(), '', null, '表单构建菜单'); +insert into sys_menu values('115', '代码生成', '3', '2', '/tool/gen', '', 'C', '0', '1', 'tool:gen:view', 'fa fa-code', 'admin', sysdate(), '', null, '代码生成菜单'); +insert into sys_menu values('116', '系统接口', '3', '3', '/tool/swagger', '', 'C', '0', '1', 'tool:swagger:view', 'fa fa-gg', 'admin', sysdate(), '', null, '系统接口菜单'); +-- 三级菜单 +insert into sys_menu values('500', '操作日志', '108', '1', '/monitor/operlog', '', 'C', '0', '1', 'monitor:operlog:view', 'fa fa-address-book', 'admin', sysdate(), '', null, '操作日志菜单'); +insert into sys_menu values('501', '登录日志', '108', '2', '/monitor/logininfor', '', 'C', '0', '1', 'monitor:logininfor:view', 'fa fa-file-image-o', 'admin', sysdate(), '', null, '登录日志菜单'); +-- 用户管理按钮 +insert into sys_menu values('1000', '用户查询', '100', '1', '#', '', 'F', '0', '1', 'system:user:list', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1001', '用户新增', '100', '2', '#', '', 'F', '0', '1', 'system:user:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1002', '用户修改', '100', '3', '#', '', 'F', '0', '1', 'system:user:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1003', '用户删除', '100', '4', '#', '', 'F', '0', '1', 'system:user:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1004', '用户导出', '100', '5', '#', '', 'F', '0', '1', 'system:user:export', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1005', '用户导入', '100', '6', '#', '', 'F', '0', '1', 'system:user:import', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1006', '重置密码', '100', '7', '#', '', 'F', '0', '1', 'system:user:resetPwd', '#', 'admin', sysdate(), '', null, ''); +-- 角色管理按钮 +insert into sys_menu values('1007', '角色查询', '101', '1', '#', '', 'F', '0', '1', 'system:role:list', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1008', '角色新增', '101', '2', '#', '', 'F', '0', '1', 'system:role:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1009', '角色修改', '101', '3', '#', '', 'F', '0', '1', 'system:role:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1010', '角色删除', '101', '4', '#', '', 'F', '0', '1', 'system:role:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1011', '角色导出', '101', '5', '#', '', 'F', '0', '1', 'system:role:export', '#', 'admin', sysdate(), '', null, ''); +-- 菜单管理按钮 +insert into sys_menu values('1012', '菜单查询', '102', '1', '#', '', 'F', '0', '1', 'system:menu:list', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1013', '菜单新增', '102', '2', '#', '', 'F', '0', '1', 'system:menu:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1014', '菜单修改', '102', '3', '#', '', 'F', '0', '1', 'system:menu:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1015', '菜单删除', '102', '4', '#', '', 'F', '0', '1', 'system:menu:remove', '#', 'admin', sysdate(), '', null, ''); +-- 部门管理按钮 +insert into sys_menu values('1016', '部门查询', '103', '1', '#', '', 'F', '0', '1', 'system:dept:list', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1017', '部门新增', '103', '2', '#', '', 'F', '0', '1', 'system:dept:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1018', '部门修改', '103', '3', '#', '', 'F', '0', '1', 'system:dept:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1019', '部门删除', '103', '4', '#', '', 'F', '0', '1', 'system:dept:remove', '#', 'admin', sysdate(), '', null, ''); +-- 岗位管理按钮 +insert into sys_menu values('1020', '岗位查询', '104', '1', '#', '', 'F', '0', '1', 'system:post:list', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1021', '岗位新增', '104', '2', '#', '', 'F', '0', '1', 'system:post:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1022', '岗位修改', '104', '3', '#', '', 'F', '0', '1', 'system:post:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1023', '岗位删除', '104', '4', '#', '', 'F', '0', '1', 'system:post:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1024', '岗位导出', '104', '5', '#', '', 'F', '0', '1', 'system:post:export', '#', 'admin', sysdate(), '', null, ''); +-- 字典管理按钮 +insert into sys_menu values('1025', '字典查询', '105', '1', '#', '', 'F', '0', '1', 'system:dict:list', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1026', '字典新增', '105', '2', '#', '', 'F', '0', '1', 'system:dict:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1027', '字典修改', '105', '3', '#', '', 'F', '0', '1', 'system:dict:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1028', '字典删除', '105', '4', '#', '', 'F', '0', '1', 'system:dict:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1029', '字典导出', '105', '5', '#', '', 'F', '0', '1', 'system:dict:export', '#', 'admin', sysdate(), '', null, ''); +-- 参数设置按钮 +insert into sys_menu values('1030', '参数查询', '106', '1', '#', '', 'F', '0', '1', 'system:config:list', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1031', '参数新增', '106', '2', '#', '', 'F', '0', '1', 'system:config:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1032', '参数修改', '106', '3', '#', '', 'F', '0', '1', 'system:config:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1033', '参数删除', '106', '4', '#', '', 'F', '0', '1', 'system:config:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1034', '参数导出', '106', '5', '#', '', 'F', '0', '1', 'system:config:export', '#', 'admin', sysdate(), '', null, ''); +-- 通知公告按钮 +insert into sys_menu values('1035', '公告查询', '107', '1', '#', '', 'F', '0', '1', 'system:notice:list', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1036', '公告新增', '107', '2', '#', '', 'F', '0', '1', 'system:notice:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1037', '公告修改', '107', '3', '#', '', 'F', '0', '1', 'system:notice:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1038', '公告删除', '107', '4', '#', '', 'F', '0', '1', 'system:notice:remove', '#', 'admin', sysdate(), '', null, ''); +-- 操作日志按钮 +insert into sys_menu values('1039', '操作查询', '500', '1', '#', '', 'F', '0', '1', 'monitor:operlog:list', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1040', '操作删除', '500', '2', '#', '', 'F', '0', '1', 'monitor:operlog:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1041', '详细信息', '500', '3', '#', '', 'F', '0', '1', 'monitor:operlog:detail', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1042', '日志导出', '500', '4', '#', '', 'F', '0', '1', 'monitor:operlog:export', '#', 'admin', sysdate(), '', null, ''); +-- 登录日志按钮 +insert into sys_menu values('1043', '登录查询', '501', '1', '#', '', 'F', '0', '1', 'monitor:logininfor:list', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1044', '登录删除', '501', '2', '#', '', 'F', '0', '1', 'monitor:logininfor:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1045', '日志导出', '501', '3', '#', '', 'F', '0', '1', 'monitor:logininfor:export', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1046', '账户解锁', '501', '4', '#', '', 'F', '0', '1', 'monitor:logininfor:unlock', '#', 'admin', sysdate(), '', null, ''); +-- 在线用户按钮 +insert into sys_menu values('1047', '在线查询', '109', '1', '#', '', 'F', '0', '1', 'monitor:online:list', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1048', '批量强退', '109', '2', '#', '', 'F', '0', '1', 'monitor:online:batchForceLogout', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1049', '单条强退', '109', '3', '#', '', 'F', '0', '1', 'monitor:online:forceLogout', '#', 'admin', sysdate(), '', null, ''); +-- 定时任务按钮 +insert into sys_menu values('1050', '任务查询', '110', '1', '#', '', 'F', '0', '1', 'monitor:job:list', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1051', '任务新增', '110', '2', '#', '', 'F', '0', '1', 'monitor:job:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1052', '任务修改', '110', '3', '#', '', 'F', '0', '1', 'monitor:job:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1053', '任务删除', '110', '4', '#', '', 'F', '0', '1', 'monitor:job:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1054', '状态修改', '110', '5', '#', '', 'F', '0', '1', 'monitor:job:changeStatus', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1055', '任务详细', '110', '6', '#', '', 'F', '0', '1', 'monitor:job:detail', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1056', '任务导出', '110', '7', '#', '', 'F', '0', '1', 'monitor:job:export', '#', 'admin', sysdate(), '', null, ''); +-- 代码生成按钮 +insert into sys_menu values('1057', '生成查询', '115', '1', '#', '', 'F', '0', '1', 'tool:gen:list', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1058', '生成修改', '115', '2', '#', '', 'F', '0', '1', 'tool:gen:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1059', '生成删除', '115', '3', '#', '', 'F', '0', '1', 'tool:gen:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1060', '预览代码', '115', '4', '#', '', 'F', '0', '1', 'tool:gen:preview', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1061', '生成代码', '115', '5', '#', '', 'F', '0', '1', 'tool:gen:code', '#', 'admin', sysdate(), '', null, ''); + + +-- ---------------------------- +-- 6、用户和角色关联表 用户N-1角色 +-- ---------------------------- +drop table if exists sys_user_role; +create table sys_user_role ( + user_id bigint(20) not null comment '用户ID', + role_id bigint(20) not null comment '角色ID', + primary key(user_id, role_id) +) engine=innodb comment = '用户和角色关联表'; + +-- ---------------------------- +-- 初始化-用户和角色关联表数据 +-- ---------------------------- +insert into sys_user_role values ('1', '1'); +insert into sys_user_role values ('2', '2'); + + +-- ---------------------------- +-- 7、角色和菜单关联表 角色1-N菜单 +-- ---------------------------- +drop table if exists sys_role_menu; +create table sys_role_menu ( + role_id bigint(20) not null comment '角色ID', + menu_id bigint(20) not null comment '菜单ID', + primary key(role_id, menu_id) +) engine=innodb comment = '角色和菜单关联表'; + +-- ---------------------------- +-- 初始化-角色和菜单关联表数据 +-- ---------------------------- +insert into sys_role_menu values ('2', '1'); +insert into sys_role_menu values ('2', '2'); +insert into sys_role_menu values ('2', '3'); +insert into sys_role_menu values ('2', '4'); +insert into sys_role_menu values ('2', '100'); +insert into sys_role_menu values ('2', '101'); +insert into sys_role_menu values ('2', '102'); +insert into sys_role_menu values ('2', '103'); +insert into sys_role_menu values ('2', '104'); +insert into sys_role_menu values ('2', '105'); +insert into sys_role_menu values ('2', '106'); +insert into sys_role_menu values ('2', '107'); +insert into sys_role_menu values ('2', '108'); +insert into sys_role_menu values ('2', '109'); +insert into sys_role_menu values ('2', '110'); +insert into sys_role_menu values ('2', '111'); +insert into sys_role_menu values ('2', '112'); +insert into sys_role_menu values ('2', '113'); +insert into sys_role_menu values ('2', '114'); +insert into sys_role_menu values ('2', '115'); +insert into sys_role_menu values ('2', '116'); +insert into sys_role_menu values ('2', '500'); +insert into sys_role_menu values ('2', '501'); +insert into sys_role_menu values ('2', '1000'); +insert into sys_role_menu values ('2', '1001'); +insert into sys_role_menu values ('2', '1002'); +insert into sys_role_menu values ('2', '1003'); +insert into sys_role_menu values ('2', '1004'); +insert into sys_role_menu values ('2', '1005'); +insert into sys_role_menu values ('2', '1006'); +insert into sys_role_menu values ('2', '1007'); +insert into sys_role_menu values ('2', '1008'); +insert into sys_role_menu values ('2', '1009'); +insert into sys_role_menu values ('2', '1010'); +insert into sys_role_menu values ('2', '1011'); +insert into sys_role_menu values ('2', '1012'); +insert into sys_role_menu values ('2', '1013'); +insert into sys_role_menu values ('2', '1014'); +insert into sys_role_menu values ('2', '1015'); +insert into sys_role_menu values ('2', '1016'); +insert into sys_role_menu values ('2', '1017'); +insert into sys_role_menu values ('2', '1018'); +insert into sys_role_menu values ('2', '1019'); +insert into sys_role_menu values ('2', '1020'); +insert into sys_role_menu values ('2', '1021'); +insert into sys_role_menu values ('2', '1022'); +insert into sys_role_menu values ('2', '1023'); +insert into sys_role_menu values ('2', '1024'); +insert into sys_role_menu values ('2', '1025'); +insert into sys_role_menu values ('2', '1026'); +insert into sys_role_menu values ('2', '1027'); +insert into sys_role_menu values ('2', '1028'); +insert into sys_role_menu values ('2', '1029'); +insert into sys_role_menu values ('2', '1030'); +insert into sys_role_menu values ('2', '1031'); +insert into sys_role_menu values ('2', '1032'); +insert into sys_role_menu values ('2', '1033'); +insert into sys_role_menu values ('2', '1034'); +insert into sys_role_menu values ('2', '1035'); +insert into sys_role_menu values ('2', '1036'); +insert into sys_role_menu values ('2', '1037'); +insert into sys_role_menu values ('2', '1038'); +insert into sys_role_menu values ('2', '1039'); +insert into sys_role_menu values ('2', '1040'); +insert into sys_role_menu values ('2', '1041'); +insert into sys_role_menu values ('2', '1042'); +insert into sys_role_menu values ('2', '1043'); +insert into sys_role_menu values ('2', '1044'); +insert into sys_role_menu values ('2', '1045'); +insert into sys_role_menu values ('2', '1046'); +insert into sys_role_menu values ('2', '1047'); +insert into sys_role_menu values ('2', '1048'); +insert into sys_role_menu values ('2', '1049'); +insert into sys_role_menu values ('2', '1050'); +insert into sys_role_menu values ('2', '1051'); +insert into sys_role_menu values ('2', '1052'); +insert into sys_role_menu values ('2', '1053'); +insert into sys_role_menu values ('2', '1054'); +insert into sys_role_menu values ('2', '1055'); +insert into sys_role_menu values ('2', '1056'); +insert into sys_role_menu values ('2', '1057'); +insert into sys_role_menu values ('2', '1058'); +insert into sys_role_menu values ('2', '1059'); +insert into sys_role_menu values ('2', '1060'); +insert into sys_role_menu values ('2', '1061'); + +-- ---------------------------- +-- 8、角色和部门关联表 角色1-N部门 +-- ---------------------------- +drop table if exists sys_role_dept; +create table sys_role_dept ( + role_id bigint(20) not null comment '角色ID', + dept_id bigint(20) not null comment '部门ID', + primary key(role_id, dept_id) +) engine=innodb comment = '角色和部门关联表'; + +-- ---------------------------- +-- 初始化-角色和部门关联表数据 +-- ---------------------------- +insert into sys_role_dept values ('2', '100'); +insert into sys_role_dept values ('2', '101'); +insert into sys_role_dept values ('2', '105'); + +-- ---------------------------- +-- 9、用户与岗位关联表 用户1-N岗位 +-- ---------------------------- +drop table if exists sys_user_post; +create table sys_user_post +( + user_id bigint(20) not null comment '用户ID', + post_id bigint(20) not null comment '岗位ID', + primary key (user_id, post_id) +) engine=innodb comment = '用户与岗位关联表'; + +-- ---------------------------- +-- 初始化-用户与岗位关联表数据 +-- ---------------------------- +insert into sys_user_post values ('1', '1'); +insert into sys_user_post values ('2', '2'); + + +-- ---------------------------- +-- 10、操作日志记录 +-- ---------------------------- +drop table if exists sys_oper_log; +create table sys_oper_log ( + oper_id bigint(20) not null auto_increment comment '日志主键', + title varchar(50) default '' comment '模块标题', + business_type int(2) default 0 comment '业务类型(0其它 1新增 2修改 3删除)', + method varchar(100) default '' comment '方法名称', + request_method varchar(10) default '' comment '请求方式', + operator_type int(1) default 0 comment '操作类别(0其它 1后台用户 2手机端用户)', + oper_name varchar(50) default '' comment '操作人员', + dept_name varchar(50) default '' comment '部门名称', + oper_url varchar(255) default '' comment '请求URL', + oper_ip varchar(128) default '' comment '主机地址', + oper_location varchar(255) default '' comment '操作地点', + oper_param varchar(2000) default '' comment '请求参数', + json_result varchar(2000) default '' comment '返回参数', + status int(1) default 0 comment '操作状态(0正常 1异常)', + error_msg varchar(2000) default '' comment '错误消息', + oper_time datetime comment '操作时间', + cost_time bigint(20) default 0 comment '消耗时间', + primary key (oper_id), + key idx_sys_oper_log_bt (business_type), + key idx_sys_oper_log_s (status), + key idx_sys_oper_log_ot (oper_time) +) engine=innodb auto_increment=100 comment = '操作日志记录'; + + +-- ---------------------------- +-- 11、字典类型表 +-- ---------------------------- +drop table if exists sys_dict_type; +create table sys_dict_type +( + dict_id bigint(20) not null auto_increment comment '字典主键', + dict_name varchar(100) default '' comment '字典名称', + dict_type varchar(100) default '' comment '字典类型', + status char(1) default '0' comment '状态(0正常 1停用)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (dict_id), + unique (dict_type) +) engine=innodb auto_increment=100 comment = '字典类型表'; + +insert into sys_dict_type values(1, '用户性别', 'sys_user_sex', '0', 'admin', sysdate(), '', null, '用户性别列表'); +insert into sys_dict_type values(2, '菜单状态', 'sys_show_hide', '0', 'admin', sysdate(), '', null, '菜单状态列表'); +insert into sys_dict_type values(3, '系统开关', 'sys_normal_disable', '0', 'admin', sysdate(), '', null, '系统开关列表'); +insert into sys_dict_type values(4, '任务状态', 'sys_job_status', '0', 'admin', sysdate(), '', null, '任务状态列表'); +insert into sys_dict_type values(5, '任务分组', 'sys_job_group', '0', 'admin', sysdate(), '', null, '任务分组列表'); +insert into sys_dict_type values(6, '系统是否', 'sys_yes_no', '0', 'admin', sysdate(), '', null, '系统是否列表'); +insert into sys_dict_type values(7, '通知类型', 'sys_notice_type', '0', 'admin', sysdate(), '', null, '通知类型列表'); +insert into sys_dict_type values(8, '通知状态', 'sys_notice_status', '0', 'admin', sysdate(), '', null, '通知状态列表'); +insert into sys_dict_type values(9, '操作类型', 'sys_oper_type', '0', 'admin', sysdate(), '', null, '操作类型列表'); +insert into sys_dict_type values(10, '系统状态', 'sys_common_status', '0', 'admin', sysdate(), '', null, '登录状态列表'); + + +-- ---------------------------- +-- 12、字典数据表 +-- ---------------------------- +drop table if exists sys_dict_data; +create table sys_dict_data +( + dict_code bigint(20) not null auto_increment comment '字典编码', + dict_sort int(4) default 0 comment '字典排序', + dict_label varchar(100) default '' comment '字典标签', + dict_value varchar(100) default '' comment '字典键值', + dict_type varchar(100) default '' comment '字典类型', + css_class varchar(100) default null comment '样式属性(其他样式扩展)', + list_class varchar(100) default null comment '表格回显样式', + is_default char(1) default 'N' comment '是否默认(Y是 N否)', + status char(1) default '0' comment '状态(0正常 1停用)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (dict_code) +) engine=innodb auto_increment=100 comment = '字典数据表'; + +insert into sys_dict_data values(1, 1, '男', '0', 'sys_user_sex', '', '', 'Y', '0', 'admin', sysdate(), '', null, '性别男'); +insert into sys_dict_data values(2, 2, '女', '1', 'sys_user_sex', '', '', 'N', '0', 'admin', sysdate(), '', null, '性别女'); +insert into sys_dict_data values(3, 3, '未知', '2', 'sys_user_sex', '', '', 'N', '0', 'admin', sysdate(), '', null, '性别未知'); +insert into sys_dict_data values(4, 1, '显示', '0', 'sys_show_hide', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '显示菜单'); +insert into sys_dict_data values(5, 2, '隐藏', '1', 'sys_show_hide', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '隐藏菜单'); +insert into sys_dict_data values(6, 1, '正常', '0', 'sys_normal_disable', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '正常状态'); +insert into sys_dict_data values(7, 2, '停用', '1', 'sys_normal_disable', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '停用状态'); +insert into sys_dict_data values(8, 1, '正常', '0', 'sys_job_status', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '正常状态'); +insert into sys_dict_data values(9, 2, '暂停', '1', 'sys_job_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '停用状态'); +insert into sys_dict_data values(10, 1, '默认', 'DEFAULT', 'sys_job_group', '', '', 'Y', '0', 'admin', sysdate(), '', null, '默认分组'); +insert into sys_dict_data values(11, 2, '系统', 'SYSTEM', 'sys_job_group', '', '', 'N', '0', 'admin', sysdate(), '', null, '系统分组'); +insert into sys_dict_data values(12, 1, '是', 'Y', 'sys_yes_no', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '系统默认是'); +insert into sys_dict_data values(13, 2, '否', 'N', 'sys_yes_no', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '系统默认否'); +insert into sys_dict_data values(14, 1, '通知', '1', 'sys_notice_type', '', 'warning', 'Y', '0', 'admin', sysdate(), '', null, '通知'); +insert into sys_dict_data values(15, 2, '公告', '2', 'sys_notice_type', '', 'success', 'N', '0', 'admin', sysdate(), '', null, '公告'); +insert into sys_dict_data values(16, 1, '正常', '0', 'sys_notice_status', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '正常状态'); +insert into sys_dict_data values(17, 2, '关闭', '1', 'sys_notice_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '关闭状态'); +insert into sys_dict_data values(18, 99, '其他', '0', 'sys_oper_type', '', 'info', 'N', '0', 'admin', sysdate(), '', null, '其他操作'); +insert into sys_dict_data values(19, 1, '新增', '1', 'sys_oper_type', '', 'info', 'N', '0', 'admin', sysdate(), '', null, '新增操作'); +insert into sys_dict_data values(20, 2, '修改', '2', 'sys_oper_type', '', 'info', 'N', '0', 'admin', sysdate(), '', null, '修改操作'); +insert into sys_dict_data values(21, 3, '删除', '3', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '删除操作'); +insert into sys_dict_data values(22, 4, '授权', '4', 'sys_oper_type', '', 'primary', 'N', '0', 'admin', sysdate(), '', null, '授权操作'); +insert into sys_dict_data values(23, 5, '导出', '5', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', sysdate(), '', null, '导出操作'); +insert into sys_dict_data values(24, 6, '导入', '6', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', sysdate(), '', null, '导入操作'); +insert into sys_dict_data values(25, 7, '强退', '7', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '强退操作'); +insert into sys_dict_data values(26, 8, '生成代码', '8', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', sysdate(), '', null, '生成操作'); +insert into sys_dict_data values(27, 9, '清空数据', '9', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '清空操作'); +insert into sys_dict_data values(28, 1, '成功', '0', 'sys_common_status', '', 'primary', 'N', '0', 'admin', sysdate(), '', null, '正常状态'); +insert into sys_dict_data values(29, 2, '失败', '1', 'sys_common_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '停用状态'); + + +-- ---------------------------- +-- 13、参数配置表 +-- ---------------------------- +drop table if exists sys_config; +create table sys_config ( + config_id int(5) not null auto_increment comment '参数主键', + config_name varchar(100) default '' comment '参数名称', + config_key varchar(100) default '' comment '参数键名', + config_value varchar(500) default '' comment '参数键值', + config_type char(1) default 'N' comment '系统内置(Y是 N否)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (config_id) +) engine=innodb auto_increment=100 comment = '参数配置表'; + +insert into sys_config values(1, '主框架页-默认皮肤样式名称', 'sys.index.skinName', 'skin-blue', 'Y', 'admin', sysdate(), '', null, '蓝色 skin-blue、绿色 skin-green、紫色 skin-purple、红色 skin-red、黄色 skin-yellow'); +insert into sys_config values(2, '用户管理-账号初始密码', 'sys.user.initPassword', '123456', 'Y', 'admin', sysdate(), '', null, '初始化密码 123456'); +insert into sys_config values(3, '主框架页-侧边栏主题', 'sys.index.sideTheme', 'theme-dark', 'Y', 'admin', sysdate(), '', null, '深黑主题theme-dark,浅色主题theme-light,深蓝主题theme-blue'); +insert into sys_config values(4, '账号自助-是否开启用户注册功能', 'sys.account.registerUser', 'false', 'Y', 'admin', sysdate(), '', null, '是否开启注册用户功能(true开启,false关闭)'); +insert into sys_config values(5, '用户管理-密码字符范围', 'sys.account.chrtype', '0', 'Y', 'admin', sysdate(), '', null, '默认任意字符范围,0任意(密码可以输入任意字符),1数字(密码只能为0-9数字),2英文字母(密码只能为a-z和A-Z字母),3字母和数字(密码必须包含字母,数字),4字母数字和特殊字符(目前支持的特殊字符包括:~!@#$%^&*()-=_+)'); +insert into sys_config values(6, '用户管理-初始密码修改策略', 'sys.account.initPasswordModify', '0', 'Y', 'admin', sysdate(), '', null, '0:初始密码修改策略关闭,没有任何提示,1:提醒用户,如果未修改初始密码,则在登录时就会提醒修改密码对话框'); +insert into sys_config values(7, '用户管理-账号密码更新周期', 'sys.account.passwordValidateDays', '0', 'Y', 'admin', sysdate(), '', null, '密码更新周期(填写数字,数据初始化值为0不限制,若修改必须为大于0小于365的正整数),如果超过这个周期登录系统时,则在登录时就会提醒修改密码对话框'); +insert into sys_config values(8, '主框架页-菜单导航显示风格', 'sys.index.menuStyle', 'default', 'Y', 'admin', sysdate(), '', null, '菜单导航显示风格(default为左侧导航菜单,topnav为顶部导航菜单)'); +insert into sys_config values(9, '主框架页-是否开启页脚', 'sys.index.footer', 'true', 'Y', 'admin', sysdate(), '', null, '是否开启底部页脚显示(true显示,false隐藏)'); +insert into sys_config values(10, '主框架页-是否开启页签', 'sys.index.tagsView', 'true', 'Y', 'admin', sysdate(), '', null, '是否开启菜单多页签显示(true显示,false隐藏)'); +insert into sys_config values(11, '用户登录-黑名单列表', 'sys.login.blackIPList', '', 'Y', 'admin', sysdate(), '', null, '设置登录IP黑名单限制,多个匹配项以;分隔,支持匹配(*通配、网段)'); + + +-- ---------------------------- +-- 14、系统访问记录 +-- ---------------------------- +drop table if exists sys_logininfor; +create table sys_logininfor ( + info_id bigint(20) not null auto_increment comment '访问ID', + login_name varchar(50) default '' comment '登录账号', + ipaddr varchar(128) default '' comment '登录IP地址', + login_location varchar(255) default '' comment '登录地点', + browser varchar(50) default '' comment '浏览器类型', + os varchar(50) default '' comment '操作系统', + status char(1) default '0' comment '登录状态(0成功 1失败)', + msg varchar(255) default '' comment '提示消息', + login_time datetime comment '访问时间', + primary key (info_id), + key idx_sys_logininfor_s (status), + key idx_sys_logininfor_lt (login_time) +) engine=innodb auto_increment=100 comment = '系统访问记录'; + + +-- ---------------------------- +-- 15、在线用户记录 +-- ---------------------------- +drop table if exists sys_user_online; +create table sys_user_online ( + sessionId varchar(50) default '' comment '用户会话id', + login_name varchar(50) default '' comment '登录账号', + dept_name varchar(50) default '' comment '部门名称', + ipaddr varchar(128) default '' comment '登录IP地址', + login_location varchar(255) default '' comment '登录地点', + browser varchar(50) default '' comment '浏览器类型', + os varchar(50) default '' comment '操作系统', + status varchar(10) default '' comment '在线状态on_line在线off_line离线', + start_timestamp datetime comment 'session创建时间', + last_access_time datetime comment 'session最后访问时间', + expire_time int(5) default 0 comment '超时时间,单位为分钟', + primary key (sessionId) +) engine=innodb comment = '在线用户记录'; + + +-- ---------------------------- +-- 16、定时任务调度表 +-- ---------------------------- +drop table if exists sys_job; +create table sys_job ( + job_id bigint(20) not null auto_increment comment '任务ID', + job_name varchar(64) default '' comment '任务名称', + job_group varchar(64) default 'DEFAULT' comment '任务组名', + invoke_target varchar(500) not null comment '调用目标字符串', + cron_expression varchar(255) default '' comment 'cron执行表达式', + misfire_policy varchar(20) default '3' comment '计划执行错误策略(1立即执行 2执行一次 3放弃执行)', + concurrent char(1) default '1' comment '是否并发执行(0允许 1禁止)', + status char(1) default '0' comment '状态(0正常 1暂停)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default '' comment '备注信息', + primary key (job_id, job_name, job_group) +) engine=innodb auto_increment=100 comment = '定时任务调度表'; + +insert into sys_job values(1, '系统默认(无参)', 'DEFAULT', 'ryTask.ryNoParams', '0/10 * * * * ?', '3', '1', '1', 'admin', sysdate(), '', null, ''); +insert into sys_job values(2, '系统默认(有参)', 'DEFAULT', 'ryTask.ryParams(\'ry\')', '0/15 * * * * ?', '3', '1', '1', 'admin', sysdate(), '', null, ''); +insert into sys_job values(3, '系统默认(多参)', 'DEFAULT', 'ryTask.ryMultipleParams(\'ry\', true, 2000L, 316.50D, 100)', '0/20 * * * * ?', '3', '1', '1', 'admin', sysdate(), '', null, ''); + + +-- ---------------------------- +-- 17、定时任务调度日志表 +-- ---------------------------- +drop table if exists sys_job_log; +create table sys_job_log ( + job_log_id bigint(20) not null auto_increment comment '任务日志ID', + job_name varchar(64) not null comment '任务名称', + job_group varchar(64) not null comment '任务组名', + invoke_target varchar(500) not null comment '调用目标字符串', + job_message varchar(500) comment '日志信息', + status char(1) default '0' comment '执行状态(0正常 1失败)', + exception_info varchar(2000) default '' comment '异常信息', + create_time datetime comment '创建时间', + primary key (job_log_id) +) engine=innodb comment = '定时任务调度日志表'; + + +-- ---------------------------- +-- 18、通知公告表 +-- ---------------------------- +drop table if exists sys_notice; +create table sys_notice ( + notice_id int(4) not null auto_increment comment '公告ID', + notice_title varchar(50) not null comment '公告标题', + notice_type char(1) not null comment '公告类型(1通知 2公告)', + notice_content varchar(2000) default null comment '公告内容', + status char(1) default '0' comment '公告状态(0正常 1关闭)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(255) default null comment '备注', + primary key (notice_id) +) engine=innodb auto_increment=10 comment = '通知公告表'; + +-- ---------------------------- +-- 初始化-公告信息表数据 +-- ---------------------------- +insert into sys_notice values('1', '温馨提醒:2018-07-01 若依新版本发布啦', '2', '新版本内容', '0', 'admin', sysdate(), '', null, '管理员'); +insert into sys_notice values('2', '维护通知:2018-07-01 若依系统凌晨维护', '1', '维护内容', '0', 'admin', sysdate(), '', null, '管理员'); + + +-- ---------------------------- +-- 19、代码生成业务表 +-- ---------------------------- +drop table if exists gen_table; +create table gen_table ( + table_id bigint(20) not null auto_increment comment '编号', + table_name varchar(200) default '' comment '表名称', + table_comment varchar(500) default '' comment '表描述', + sub_table_name varchar(64) default null comment '关联子表的表名', + sub_table_fk_name varchar(64) default null comment '子表关联的外键名', + class_name varchar(100) default '' comment '实体类名称', + tpl_category varchar(200) default 'crud' comment '使用的模板(crud单表操作 tree树表操作 sub主子表操作)', + package_name varchar(100) comment '生成包路径', + module_name varchar(30) comment '生成模块名', + business_name varchar(30) comment '生成业务名', + function_name varchar(50) comment '生成功能名', + function_author varchar(50) comment '生成功能作者', + gen_type char(1) default '0' comment '生成代码方式(0zip压缩包 1自定义路径)', + gen_path varchar(200) default '/' comment '生成路径(不填默认项目路径)', + options varchar(1000) comment '其它生成选项', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (table_id) +) engine=innodb auto_increment=1 comment = '代码生成业务表'; + + +-- ---------------------------- +-- 20、代码生成业务表字段 +-- ---------------------------- +drop table if exists gen_table_column; +create table gen_table_column ( + column_id bigint(20) not null auto_increment comment '编号', + table_id varchar(64) comment '归属表编号', + column_name varchar(200) comment '列名称', + column_comment varchar(500) comment '列描述', + column_type varchar(100) comment '列类型', + java_type varchar(500) comment 'JAVA类型', + java_field varchar(200) comment 'JAVA字段名', + is_pk char(1) comment '是否主键(1是)', + is_increment char(1) comment '是否自增(1是)', + is_required char(1) comment '是否必填(1是)', + is_insert char(1) comment '是否为插入字段(1是)', + is_edit char(1) comment '是否编辑字段(1是)', + is_list char(1) comment '是否列表字段(1是)', + is_query char(1) comment '是否查询字段(1是)', + query_type varchar(200) default 'EQ' comment '查询方式(等于、不等于、大于、小于、范围)', + html_type varchar(200) comment '显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)', + dict_type varchar(200) default '' comment '字典类型', + sort int comment '排序', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + primary key (column_id) +) engine=innodb auto_increment=1 comment = '代码生成业务表字段'; \ No newline at end of file